graphvent/lockable.go

643 lines
20 KiB
Go

package graphvent
import (
"fmt"
"encoding/json"
)
2023-07-09 14:30:30 -06:00
// A Lockable represents a Node that can be locked and hold other Nodes locks
type Lockable interface {
2023-07-09 16:03:42 -06:00
// All Lockables are nodes
2023-07-09 14:30:30 -06:00
Node
//// State Modification Function
// Record that lockable was returned to it's owner and is no longer held by this Node
// Returns the previous owner of the lockable
RecordUnlock(lockable Lockable) Lockable
// Record that lockable was locked by this node, and that it should be returned to last_owner
RecordLock(lockable Lockable, last_owner Lockable)
// Link a requirement to this Node
AddRequirement(requirement Lockable)
2023-07-09 14:30:30 -06:00
// Remove a requirement linked to this Node
RemoveRequirement(requirement Lockable)
2023-07-09 14:30:30 -06:00
// Link a dependency to this Node
AddDependency(dependency Lockable)
2023-07-09 14:30:30 -06:00
// Remove a dependency linked to this Node
RemoveDependency(dependency Lockable)
2023-07-09 14:30:30 -06:00
//
SetOwner(new_owner Lockable)
//// State Reading Functions
2023-07-09 20:30:19 -06:00
Name() string
2023-07-09 14:30:30 -06:00
// Called when new_owner wants to take lockable's lock but it's owned by this node
// A true return value means that the lock can be passed
AllowedToTakeLock(new_owner Lockable, lockable Lockable) bool
// Get all the linked requirements to this node
Requirements() []Lockable
// Get all the linked dependencies to this node
Dependencies() []Lockable
// Get the node's Owner
Owner() Lockable
2023-07-09 14:30:30 -06:00
// Called during the lock process after locking the state and before updating the Node's state
// a non-nil return value will abort the lock attempt
CanLock(new_owner Lockable) error
// Called during the unlock process after locking the state and before updating the Node's state
// a non-nil return value will abort the unlock attempt
CanUnlock(old_owner Lockable) error
2023-06-23 22:19:43 -06:00
}
2023-07-09 14:30:30 -06:00
// SimpleLockable is a simple Lockable implementation that can be embedded into more complex structures
type SimpleLockable struct {
GraphNode
name string
owner Lockable
requirements []Lockable
dependencies []Lockable
locks_held map[NodeID]Lockable
2023-06-23 22:19:43 -06:00
}
2023-07-09 14:30:30 -06:00
func (state * SimpleLockable) Type() NodeType {
return NodeType("simple_lockable")
}
type SimpleLockableJSON struct {
2023-07-20 23:19:10 -06:00
GraphNodeJSON
2023-06-23 22:19:43 -06:00
Name string `json:"name"`
Owner string `json:"owner"`
Dependencies []string `json:"dependencies"`
Requirements []string `json:"requirements"`
LocksHeld map[string]string `json:"locks_held"`
}
2023-07-09 14:30:30 -06:00
func (lockable * SimpleLockable) Serialize() ([]byte, error) {
lockable_json := NewSimpleLockableJSON(lockable)
return json.MarshalIndent(&lockable_json, "", " ")
}
2023-07-09 14:30:30 -06:00
func NewSimpleLockableJSON(lockable *SimpleLockable) SimpleLockableJSON {
requirement_ids := make([]string, len(lockable.requirements))
2023-07-09 14:30:30 -06:00
for i, requirement := range(lockable.requirements) {
requirement_ids[i] = requirement.ID().String()
}
dependency_ids := make([]string, len(lockable.dependencies))
2023-07-09 14:30:30 -06:00
for i, dependency := range(lockable.dependencies) {
dependency_ids[i] = dependency.ID().String()
}
owner_id := ""
2023-07-09 14:30:30 -06:00
if lockable.owner != nil {
owner_id = lockable.owner.ID().String()
}
locks_held := map[string]string{}
2023-07-09 14:30:30 -06:00
for lockable_id, node := range(lockable.locks_held) {
if node == nil {
locks_held[lockable_id.String()] = ""
} else {
locks_held[lockable_id.String()] = node.ID().String()
}
}
2023-07-20 23:19:10 -06:00
node_json := NewGraphNodeJSON(&lockable.GraphNode)
2023-07-09 14:30:30 -06:00
return SimpleLockableJSON{
2023-07-20 23:19:10 -06:00
GraphNodeJSON: node_json,
2023-07-09 14:30:30 -06:00
Name: lockable.name,
Owner: owner_id,
Dependencies: dependency_ids,
Requirements: requirement_ids,
LocksHeld: locks_held,
}
}
2023-07-09 14:30:30 -06:00
func (lockable * SimpleLockable) Name() string {
return lockable.name
}
2023-07-09 14:30:30 -06:00
func (lockable * SimpleLockable) RecordUnlock(l Lockable) Lockable {
lockable_id := l.ID()
last_owner, exists := lockable.locks_held[lockable_id]
if exists == false {
2023-06-24 19:48:59 -06:00
panic("Attempted to take a get the original lock holder of a lockable we don't own")
}
2023-07-09 14:30:30 -06:00
delete(lockable.locks_held, lockable_id)
return last_owner
}
2023-07-09 14:30:30 -06:00
func (lockable * SimpleLockable) RecordLock(l Lockable, last_owner Lockable) {
lockable_id := l.ID()
_, exists := lockable.locks_held[lockable_id]
if exists == true {
2023-06-24 19:48:59 -06:00
panic("Attempted to lock a lockable we're already holding(lock cycle)")
}
2023-07-09 14:30:30 -06:00
lockable.locks_held[lockable_id] = last_owner
}
2023-07-09 14:30:30 -06:00
// Nothing can take a lock from a simple lockable
func (lockable * SimpleLockable) AllowedToTakeLock(l Lockable, new_owner Lockable) bool {
return false
}
func (lockable * SimpleLockable) Owner() Lockable {
return lockable.owner
}
2023-07-09 14:30:30 -06:00
func (lockable * SimpleLockable) SetOwner(owner Lockable) {
lockable.owner = owner
}
2023-07-09 14:30:30 -06:00
func (lockable * SimpleLockable) Requirements() []Lockable {
return lockable.requirements
}
2023-07-09 14:30:30 -06:00
func (lockable * SimpleLockable) AddRequirement(requirement Lockable) {
if requirement == nil {
panic("Will not connect nil to the DAG")
}
2023-07-09 14:30:30 -06:00
lockable.requirements = append(lockable.requirements, requirement)
}
2023-07-09 14:30:30 -06:00
func (lockable * SimpleLockable) Dependencies() []Lockable {
return lockable.dependencies
}
2023-07-09 14:30:30 -06:00
func (lockable * SimpleLockable) AddDependency(dependency Lockable) {
if dependency == nil {
panic("Will not connect nil to the DAG")
}
2023-07-09 14:30:30 -06:00
lockable.dependencies = append(lockable.dependencies, dependency)
}
2023-07-09 14:30:30 -06:00
func (lockable * SimpleLockable) RemoveDependency(dependency Lockable) {
idx := -1
2023-07-09 14:30:30 -06:00
for i, dep := range(lockable.dependencies) {
if dep.ID() == dependency.ID() {
idx = i
break
}
}
if idx == -1 {
2023-07-09 14:30:30 -06:00
panic(fmt.Sprintf("%s is not a dependency of %s", dependency.ID(), lockable.Name()))
}
2023-07-09 14:30:30 -06:00
dep_len := len(lockable.dependencies)
lockable.dependencies[idx] = lockable.dependencies[dep_len-1]
lockable.dependencies = lockable.dependencies[0:(dep_len-1)]
}
2023-07-09 14:30:30 -06:00
func (lockable * SimpleLockable) RemoveRequirement(requirement Lockable) {
idx := -1
2023-07-09 14:30:30 -06:00
for i, req := range(lockable.requirements) {
if req.ID() == requirement.ID() {
idx = i
break
}
}
if idx == -1 {
2023-07-09 14:30:30 -06:00
panic(fmt.Sprintf("%s is not a requirement of %s", requirement.ID(), lockable.Name()))
}
2023-07-09 14:30:30 -06:00
req_len := len(lockable.requirements)
lockable.requirements[idx] = lockable.requirements[req_len-1]
lockable.requirements = lockable.requirements[0:(req_len-1)]
}
func (lockable * SimpleLockable) CanLock(new_owner Lockable) error {
return nil
}
func (lockable * SimpleLockable) CanUnlock(new_owner Lockable) error {
return nil
}
// Assumed that lockable is already locked for signal
func (lockable * SimpleLockable) Signal(context *ReadContext, signal GraphSignal) error {
2023-07-22 21:24:54 -06:00
err := lockable.GraphNode.Signal(context, signal)
2023-07-09 16:03:42 -06:00
if err != nil {
return err
}
switch signal.Direction() {
case Up:
2023-07-22 21:24:54 -06:00
err = UseMoreStates(context, lockable, NewLockMap(
NewLockInfo(lockable, []string{"dependencies", "owner"}),
2023-07-22 21:24:54 -06:00
LockList(lockable.requirements, []string{"signal"}),
), func(context *ReadContext) error {
owner_sent := false
for _, dependency := range(lockable.dependencies) {
2023-07-22 21:24:54 -06:00
context.Graph.Log.Logf("signal", "SENDING_TO_DEPENDENCY: %s -> %s", lockable.ID(), dependency.ID())
dependency.Signal(context, signal)
2023-07-09 14:30:30 -06:00
if lockable.owner != nil {
if dependency.ID() == lockable.owner.ID() {
owner_sent = true
}
}
}
if lockable.owner != nil && owner_sent == false {
if lockable.owner.ID() != lockable.ID() {
2023-07-22 21:24:54 -06:00
context.Graph.Log.Logf("signal", "SENDING_TO_OWNER: %s -> %s", lockable.ID(), lockable.owner.ID())
return UseMoreStates(context, lockable, NewLockMap(NewLockInfo(lockable.owner, []string{"signal"})), func(context *ReadContext) error {
return lockable.owner.Signal(context, signal)
})
}
}
2023-07-09 14:30:30 -06:00
return nil
})
case Down:
err = UseMoreStates(context, lockable, NewLockMap(
2023-07-22 21:24:54 -06:00
NewLockInfo(lockable, []string{"requirements"}),
LockList(lockable.requirements, []string{"signal"}),
), func(context *ReadContext) error {
2023-07-09 14:30:30 -06:00
for _, requirement := range(lockable.requirements) {
2023-07-22 21:24:54 -06:00
err := requirement.Signal(context, signal)
2023-07-09 14:30:30 -06:00
if err != nil {
return err
}
}
return nil
})
case Direct:
err = nil
default:
return fmt.Errorf("invalid signal direction %d", signal.Direction())
2023-07-09 14:30:30 -06:00
}
return err
}
2023-07-10 22:31:43 -06:00
// Removes requirement as a requirement from lockable
// Requires lockable and requirement be locked for write
2023-07-09 14:30:30 -06:00
func UnlinkLockables(ctx * Context, lockable Lockable, requirement Lockable) error {
var found Node = nil
for _, req := range(lockable.Requirements()) {
if requirement.ID() == req.ID() {
found = req
break
}
}
if found == nil {
return fmt.Errorf("UNLINK_LOCKABLES_ERR: %s is not a requirement of %s", requirement.ID(), lockable.ID())
}
2023-07-09 14:30:30 -06:00
requirement.RemoveDependency(lockable)
lockable.RemoveRequirement(requirement)
return nil
}
2023-07-10 22:31:43 -06:00
// Link requirements as requirements to lockable
// Requires lockable and requirements to be locked for write, nodes passed because requirement check recursively locks
func LinkLockables(context *WriteContext, princ Node, lockable Lockable, requirements []Lockable) error {
2023-06-25 13:39:00 -06:00
if lockable == nil {
return fmt.Errorf("LOCKABLE_LINK_ERR: Will not link Lockables to nil as requirements")
}
if len(requirements) == 0 {
return nil
}
found := map[NodeID]bool{}
2023-06-25 13:39:00 -06:00
for _, requirement := range(requirements) {
if requirement == nil {
return fmt.Errorf("LOCKABLE_LINK_ERR: Will not link nil to a Lockable as a requirement")
}
2023-06-25 13:39:00 -06:00
if lockable.ID() == requirement.ID() {
return fmt.Errorf("LOCKABLE_LINK_ERR: cannot link %s to itself", lockable.ID())
}
_, exists := found[requirement.ID()]
if exists == true {
return fmt.Errorf("LOCKABLE_LINK_ERR: cannot link %s twice", requirement.ID())
}
found[requirement.ID()] = true
}
2023-07-22 21:24:54 -06:00
return UpdateMoreStates(context, princ, NewLockMap(
NewLockInfo(lockable, []string{"requirements"}),
2023-07-22 21:24:54 -06:00
LockList(requirements, []string{"dependencies"}),
), func(context *WriteContext) error {
// Check that all the requirements can be added
// If the lockable is already locked, need to lock this resource as well before we can add it
for _, requirement := range(requirements) {
for _, req := range(requirements) {
if req.ID() == requirement.ID() {
continue
}
if checkIfRequirement(context, req, requirement) == true {
return fmt.Errorf("LOCKABLE_LINK_ERR: %s is a dependenyc of %s so cannot add the same dependency", req.ID(), requirement.ID())
}
}
if checkIfRequirement(context, lockable, requirement) == true {
return fmt.Errorf("LOCKABLE_LINK_ERR: %s is a dependency of %s so cannot link as requirement", requirement.ID(), lockable.ID())
2023-06-25 13:39:00 -06:00
}
if checkIfRequirement(context, requirement, lockable) == true {
return fmt.Errorf("LOCKABLE_LINK_ERR: %s is a dependency of %s so cannot link as dependency again", lockable.ID(), requirement.ID())
}
if lockable.Owner() == nil {
// If the new owner isn't locked, we can add the requirement
} else if requirement.Owner() == nil {
// if the new requirement isn't already locked but the owner is, the requirement needs to be locked first
return fmt.Errorf("LOCKABLE_LINK_ERR: %s is locked, %s must be locked to add", lockable.ID(), requirement.ID())
} else {
// If the new requirement is already locked and the owner is already locked, their owners need to match
if requirement.Owner().ID() != lockable.Owner().ID() {
return fmt.Errorf("LOCKABLE_LINK_ERR: %s is not locked by the same owner as %s, can't link as requirement", requirement.ID(), lockable.ID())
}
}
}
// Update the states of the requirements
for _, requirement := range(requirements) {
requirement.AddDependency(lockable)
lockable.AddRequirement(requirement)
2023-07-22 21:24:54 -06:00
context.Graph.Log.Logf("lockable", "LOCKABLE_LINK: linked %s to %s as a requirement", requirement.ID(), lockable.ID())
}
// Return no error
return nil
})
}
2023-07-09 14:30:30 -06:00
// Must be called withing update context
func checkIfRequirement(context *WriteContext, r Lockable, cur Lockable) bool {
for _, c := range(cur.Requirements()) {
2023-07-09 14:30:30 -06:00
if c.ID() == r.ID() {
return true
}
is_requirement := false
2023-07-22 21:24:54 -06:00
UpdateMoreStates(context, cur, NewLockMap(NewLockInfo(c, []string{"requirements"})), func(context *WriteContext) error {
is_requirement = checkIfRequirement(context, cur, c)
return nil
})
if is_requirement {
return true
}
}
return false
}
2023-07-10 22:31:43 -06:00
// Lock nodes in the to_lock slice with new_owner, does not modify any states if returning an error
// Assumes that new_owner will be written to after returning, even though it doesn't get locked during the call
func LockLockables(context *WriteContext, to_lock []Lockable, new_owner Lockable) error {
if to_lock == nil {
return fmt.Errorf("LOCKABLE_LOCK_ERR: no list provided")
}
2023-07-09 14:30:30 -06:00
for _, l := range(to_lock) {
if l == nil {
return fmt.Errorf("LOCKABLE_LOCK_ERR: Can not lock nil")
}
}
2023-07-09 14:30:30 -06:00
if new_owner == nil {
return fmt.Errorf("LOCKABLE_LOCK_ERR: nil cannot hold locks")
}
// Called with no requirements to lock, success
if len(to_lock) == 0 {
return nil
}
2023-07-22 21:24:54 -06:00
return UpdateMoreStates(context, new_owner, NewLockMap(
LockList(to_lock, []string{"lock"}),
NewLockInfo(new_owner, []string{}),
), func(context *WriteContext) error {
// First loop is to check that the states can be locked, and locks all requirements
for _, req := range(to_lock) {
2023-07-22 21:24:54 -06:00
context.Graph.Log.Logf("lockable", "LOCKABLE_LOCKING: %s from %s", req.ID(), new_owner.ID())
// Check custom lock conditions
err := req.CanLock(new_owner)
if err != nil {
return err
}
// If req is alreay locked, check that we can pass the lock
if req.Owner() != nil {
owner := req.Owner()
if owner.ID() == new_owner.ID() {
continue
} else {
2023-07-22 21:24:54 -06:00
err := UpdateMoreStates(context, new_owner, NewLockMap(NewLockInfo(owner, []string{"take_lock"})), func(context *WriteContext)(error){
return LockLockables(context, req.Requirements(), req)
})
if err != nil {
return err
}
2023-07-10 22:31:43 -06:00
}
} else {
2023-07-22 21:24:54 -06:00
err := LockLockables(context, req.Requirements(), req)
if err != nil {
return err
}
}
}
// At this point state modification will be started, so no errors can be returned
for _, req := range(to_lock) {
old_owner := req.Owner()
// If the lockable was previously unowned, update the state
if old_owner == nil {
2023-07-22 21:24:54 -06:00
context.Graph.Log.Logf("lockable", "LOCKABLE_LOCK: %s locked %s", new_owner.ID(), req.ID())
req.SetOwner(new_owner)
new_owner.RecordLock(req, old_owner)
// Otherwise if the new owner already owns it, no need to update state
} else if old_owner.ID() == new_owner.ID() {
2023-07-22 21:24:54 -06:00
context.Graph.Log.Logf("lockable", "LOCKABLE_LOCK: %s already owns %s", new_owner.ID(), req.ID())
// Otherwise update the state
} else {
req.SetOwner(new_owner)
new_owner.RecordLock(req, old_owner)
2023-07-22 21:24:54 -06:00
context.Graph.Log.Logf("lockable", "LOCKABLE_LOCK: %s took lock of %s from %s", new_owner.ID(), req.ID(), old_owner.ID())
}
}
return nil
})
}
func UnlockLockables(context *WriteContext, to_unlock []Lockable, old_owner Lockable) error {
if to_unlock == nil {
return fmt.Errorf("LOCKABLE_UNLOCK_ERR: no list provided")
}
for _, l := range(to_unlock) {
if l == nil {
return fmt.Errorf("LOCKABLE_UNLOCK_ERR: Can not unlock nil")
}
}
2023-07-09 14:30:30 -06:00
if old_owner == nil {
return fmt.Errorf("LOCKABLE_UNLOCK_ERR: nil cannot hold locks")
}
// Called with no requirements to unlock, success
if len(to_unlock) == 0 {
return nil
}
2023-07-22 21:24:54 -06:00
return UpdateMoreStates(context, old_owner, NewLockMap(
LockList(to_unlock, []string{"lock"}),
NewLockInfo(old_owner, []string{}),
), func(context *WriteContext) error {
// First loop is to check that the states can be locked, and locks all requirements
for _, req := range(to_unlock) {
2023-07-22 21:24:54 -06:00
context.Graph.Log.Logf("lockable", "LOCKABLE_UNLOCKING: %s from %s", req.ID(), old_owner.ID())
// Check if the owner is correct
if req.Owner() != nil {
if req.Owner().ID() != old_owner.ID() {
return fmt.Errorf("LOCKABLE_UNLOCK_ERR: %s is not locked by %s", req.ID(), old_owner.ID())
}
} else {
return fmt.Errorf("LOCKABLE_UNLOCK_ERR: %s is not locked", req.ID())
}
// Check custom unlock conditions
err := req.CanUnlock(old_owner)
if err != nil {
return err
}
2023-07-22 21:24:54 -06:00
err = UnlockLockables(context, req.Requirements(), req)
if err != nil {
return err
}
2023-07-10 22:31:43 -06:00
}
// At this point state modification will be started, so no errors can be returned
for _, req := range(to_unlock) {
new_owner := old_owner.RecordUnlock(req)
req.SetOwner(new_owner)
if new_owner == nil {
2023-07-22 21:24:54 -06:00
context.Graph.Log.Logf("lockable", "LOCKABLE_UNLOCK: %s unlocked %s", old_owner.ID(), req.ID())
} else {
2023-07-22 21:24:54 -06:00
context.Graph.Log.Logf("lockable", "LOCKABLE_UNLOCK: %s passed lock of %s back to %s", old_owner.ID(), req.ID(), new_owner.ID())
}
}
return nil
})
}
2023-07-10 22:31:43 -06:00
// Load function for SimpleLockable
2023-07-09 14:30:30 -06:00
func LoadSimpleLockable(ctx *Context, id NodeID, data []byte, nodes NodeMap) (Node, error) {
var j SimpleLockableJSON
err := json.Unmarshal(data, &j)
if err != nil {
2023-07-09 14:30:30 -06:00
return nil, err
}
2023-07-09 14:30:30 -06:00
lockable := NewSimpleLockable(id, j.Name)
nodes[id] = &lockable
2023-07-09 14:30:30 -06:00
err = RestoreSimpleLockable(ctx, &lockable, j, nodes)
if err != nil {
return nil, err
}
return &lockable, nil
}
2023-07-09 14:30:30 -06:00
func NewSimpleLockable(id NodeID, name string) SimpleLockable {
return SimpleLockable{
GraphNode: NewGraphNode(id),
name: name,
owner: nil,
2023-07-09 14:30:30 -06:00
requirements: []Lockable{},
dependencies: []Lockable{},
locks_held: map[NodeID]Lockable{},
}
2023-07-09 14:30:30 -06:00
}
2023-07-10 22:31:43 -06:00
// Helper function to load links when loading a struct that embeds SimpleLockable
2023-07-09 14:30:30 -06:00
func RestoreSimpleLockable(ctx * Context, lockable Lockable, j SimpleLockableJSON, nodes NodeMap) error {
if j.Owner != "" {
owner_id, err := ParseID(j.Owner)
if err != nil {
2023-07-09 14:30:30 -06:00
return err
}
owner_node, err := LoadNodeRecurse(ctx, owner_id, nodes)
if err != nil {
return err
}
owner, ok := owner_node.(Lockable)
if ok == false {
return fmt.Errorf("%s is not a Lockable", j.Owner)
}
lockable.SetOwner(owner)
}
for _, dep_str := range(j.Dependencies) {
dep_id, err := ParseID(dep_str)
if err != nil {
return err
}
dep_node, err := LoadNodeRecurse(ctx, dep_id, nodes)
if err != nil {
2023-07-09 14:30:30 -06:00
return err
}
dep, ok := dep_node.(Lockable)
if ok == false {
2023-07-09 14:30:30 -06:00
return fmt.Errorf("%+v is not a Lockable as expected", dep_node)
}
lockable.AddDependency(dep)
}
for _, req_str := range(j.Requirements) {
req_id, err := ParseID(req_str)
if err != nil {
2023-07-09 14:30:30 -06:00
return err
}
req_node, err := LoadNodeRecurse(ctx, req_id, nodes)
if err != nil {
return err
}
req, ok := req_node.(Lockable)
if ok == false {
2023-07-09 14:30:30 -06:00
return fmt.Errorf("%+v is not a Lockable as expected", req_node)
}
lockable.AddRequirement(req)
}
for l_id_str, h_str := range(j.LocksHeld) {
l_id, err := ParseID(l_id_str)
2023-07-09 14:30:30 -06:00
l, err := LoadNodeRecurse(ctx, l_id, nodes)
if err != nil {
2023-07-09 14:30:30 -06:00
return err
}
l_l, ok := l.(Lockable)
if ok == false {
return fmt.Errorf("%s is not a Lockable", l.ID())
}
2023-07-09 14:30:30 -06:00
var h_l Lockable = nil
if h_str != "" {
h_id, err := ParseID(h_str)
if err != nil {
return err
}
h_node, err := LoadNodeRecurse(ctx, h_id, nodes)
if err != nil {
2023-07-09 14:30:30 -06:00
return err
}
h, ok := h_node.(Lockable)
if ok == false {
2023-07-09 14:30:30 -06:00
return err
}
h_l = h
}
2023-07-09 14:30:30 -06:00
lockable.RecordLock(l_l, h_l)
}
2023-07-20 23:19:10 -06:00
return RestoreGraphNode(ctx, lockable, j.GraphNodeJSON, nodes)
}