From a696213e3870e01d2153f534c26efaa5616bb004 Mon Sep 17 00:00:00 2001 From: Noah Metz Date: Wed, 28 Jun 2023 00:48:49 -0600 Subject: [PATCH] Rework locking to hold all locks before changing any state --- gql.go | 10 +- gql_graph.go | 118 ++++++-------- gql_test.go | 3 +- graph.go | 16 +- lockable.go | 412 +++++++++++++++++++++++++++++------------------ lockable_test.go | 96 +++++------ thread.go | 66 ++++---- thread_test.go | 12 +- 8 files changed, 406 insertions(+), 327 deletions(-) diff --git a/gql.go b/gql.go index 31f87f9..b7c788e 100644 --- a/gql.go +++ b/gql.go @@ -370,13 +370,13 @@ var gql_actions ThreadActions = ThreadActions{ fs := http.FileServer(http.Dir("./site")) mux.Handle("/site/", http.StripPrefix("/site", fs)) - UseStates(ctx, []GraphNode{server}, func(states []NodeState)(interface{}, error){ + UseStates(ctx, []GraphNode{server}, func(states []NodeState)(error){ server_state := states[0].(*GQLThreadState) server.http_server = &http.Server{ Addr: server_state.Listen, Handler: mux, } - return nil, nil + return nil }) server.http_done.Add(1) @@ -395,18 +395,18 @@ var gql_actions ThreadActions = ThreadActions{ var gql_handlers ThreadHandlers = ThreadHandlers{ "child_added": func(ctx * GraphContext, thread Thread, signal GraphSignal) (string, error) { ctx.Log.Logf("gql", "GQL_THREAD_CHILD_ADDED: %+v", signal) - UseStates(ctx, []GraphNode{thread}, func(states []NodeState)(interface{}, error) { + UseStates(ctx, []GraphNode{thread}, func(states []NodeState)(error) { server_state := states[0].(*GQLThreadState) should_run, exists := server_state.child_info[signal.Source()].(*GQLThreadInfo) if exists == false { ctx.Log.Logf("gql", "GQL_THREAD_CHILD_ADDED: tried to start %s whis is not a child") - return nil, nil + return nil } if should_run.Start == true && should_run.Started == false { ChildGo(ctx, server_state, thread, signal.Source()) should_run.Started = false } - return nil, nil + return nil }) return "wait", nil }, diff --git a/gql_graph.go b/gql_graph.go index c21fc7c..20f0a4f 100644 --- a/gql_graph.go +++ b/gql_graph.go @@ -221,20 +221,17 @@ func GQLNodeName(p graphql.ResolveParams) (interface{}, error) { return nil, fmt.Errorf("Failed to cast context graph_context to GraphContext") } - name, err := UseStates(ctx, []GraphNode{node}, func(states []NodeState) (interface{}, error) { - return states[0].Name(), nil + name := "" + err := UseStates(ctx, []GraphNode{node}, func(states []NodeState) (error) { + name = states[0].Name() + return nil }) if err != nil { return nil, err } - name_str, ok := name.(string) - if ok == false { - return nil, fmt.Errorf("Failed to cast name to string %+v", name) - } - - return name_str, nil + return name, nil } func GQLThreadListen(p graphql.ResolveParams) (interface{}, error) { @@ -248,24 +245,21 @@ func GQLThreadListen(p graphql.ResolveParams) (interface{}, error) { return nil, fmt.Errorf("Failed to cast context graph_context to GraphContext") } - listen, err := UseStates(ctx, []GraphNode{node}, func(states []NodeState) (interface{}, error) { + listen := "" + err := UseStates(ctx, []GraphNode{node}, func(states []NodeState) (error) { gql_thread, ok := states[0].(*GQLThreadState) if ok == false { - return nil, fmt.Errorf("Failed to cast state to GQLThreadState") + return fmt.Errorf("Failed to cast state to GQLThreadState") } - return gql_thread.Listen, nil + listen = gql_thread.Listen + return nil }) if err != nil { return nil, err } - listen_str, ok := listen.(string) - if ok == false { - return nil, fmt.Errorf("Failed to cast listen to string %+v", listen) - } - - return listen_str, nil + return listen, nil } func GQLThreadParent(p graphql.ResolveParams) (interface{}, error) { @@ -279,24 +273,21 @@ func GQLThreadParent(p graphql.ResolveParams) (interface{}, error) { return nil, fmt.Errorf("Failed to cast context graph_context to GraphContext") } - parent, err := UseStates(ctx, []GraphNode{node}, func(states []NodeState) (interface{}, error) { + var parent Thread = nil + err := UseStates(ctx, []GraphNode{node}, func(states []NodeState) (error) { gql_thread, ok := states[0].(ThreadState) if ok == false { - return nil, fmt.Errorf("Failed to cast state to ThreadState") + return fmt.Errorf("Failed to cast state to ThreadState") } - return gql_thread.Parent(), nil + parent = gql_thread.Parent() + return nil }) if err != nil { return nil, err } - parent_node, ok := parent.(Thread) - if ok == false { - return nil, fmt.Errorf("Failed to cast parent to node %+v", parent) - } - - return parent_node, nil + return parent, nil } func GQLThreadChildren(p graphql.ResolveParams) (interface{}, error) { @@ -310,24 +301,21 @@ func GQLThreadChildren(p graphql.ResolveParams) (interface{}, error) { return nil, fmt.Errorf("Failed to cast context graph_context to GraphContext") } - children, err := UseStates(ctx, []GraphNode{node}, func(states []NodeState) (interface{}, error) { + var children []Thread = nil + err := UseStates(ctx, []GraphNode{node}, func(states []NodeState) (error) { gql_thread, ok := states[0].(ThreadState) if ok == false { - return nil, fmt.Errorf("Failed to cast state to ThreadState") + return fmt.Errorf("Failed to cast state to ThreadState") } - return gql_thread.Children(), nil + children = gql_thread.Children() + return nil }) if err != nil { return nil, err } - children_nodes, ok := children.([]Thread) - if ok == false { - return nil, fmt.Errorf("Failed to cast children to threads %+v", children) - } - - return children_nodes, nil + return children, nil } func GQLLockableRequirements(p graphql.ResolveParams) (interface{}, error) { @@ -341,24 +329,21 @@ func GQLLockableRequirements(p graphql.ResolveParams) (interface{}, error) { return nil, fmt.Errorf("Failed to cast context graph_context to GraphContext") } - requirements, err := UseStates(ctx, []GraphNode{node}, func(states []NodeState) (interface{}, error) { + var requirements []Lockable = nil + err := UseStates(ctx, []GraphNode{node}, func(states []NodeState) (error) { gql_thread, ok := states[0].(LockableState) if ok == false { - return nil, fmt.Errorf("Failed to cast state to LockableState") + return fmt.Errorf("Failed to cast state to LockableState") } - return gql_thread.Requirements(), nil + requirements = gql_thread.Requirements() + return nil }) if err != nil { return nil, err } - requirement_nodes, ok := requirements.([]Lockable) - if ok == false { - return nil, fmt.Errorf("Failed to cast requirements to lockables %+v", requirements) - } - - return requirement_nodes, nil + return requirements, nil } func GQLLockableDependencies(p graphql.ResolveParams) (interface{}, error) { @@ -372,24 +357,21 @@ func GQLLockableDependencies(p graphql.ResolveParams) (interface{}, error) { return nil, fmt.Errorf("Failed to cast context graph_context to GraphContext") } - dependencies, err := UseStates(ctx, []GraphNode{node}, func(states []NodeState) (interface{}, error) { + var dependencies []Lockable = nil + err := UseStates(ctx, []GraphNode{node}, func(states []NodeState) (error) { gql_thread, ok := states[0].(LockableState) if ok == false { - return nil, fmt.Errorf("Failed to cast state to LockableState") + return fmt.Errorf("Failed to cast state to LockableState") } - return gql_thread.Dependencies(), nil + dependencies = gql_thread.Dependencies() + return nil }) if err != nil { return nil, err } - dependency_nodes, ok := dependencies.([]Lockable) - if ok == false { - return nil, fmt.Errorf("Failed to cast dependencies to lockables %+v", dependencies) - } - - return dependency_nodes, nil + return dependencies, nil } func GQLLockableOwner(p graphql.ResolveParams) (interface{}, error) { @@ -403,25 +385,21 @@ func GQLLockableOwner(p graphql.ResolveParams) (interface{}, error) { return nil, fmt.Errorf("Failed to cast context graph_context to GraphContext") } - owner, err := UseStates(ctx, []GraphNode{node}, func(states []NodeState) (interface{}, error) { + var owner GraphNode = nil + err := UseStates(ctx, []GraphNode{node}, func(states []NodeState) (error) { gql_thread, ok := states[0].(LockableState) if ok == false { - return nil, fmt.Errorf("Failed to cast state to LockableState") + return fmt.Errorf("Failed to cast state to LockableState") } - return gql_thread.Owner(), nil + owner = gql_thread.Owner() + return nil }) if err != nil { return nil, err } - // TODO actually cast to LockHolder and add gql interface for it - owner_node, ok := owner.(Lockable) - if ok == false { - return nil, fmt.Errorf("Failed to cast owner to Lockable %+v", owner) - } - - return owner_node, nil + return owner, nil } @@ -847,23 +825,19 @@ func GQLMutationSendUpdate() *graphql.Field { return nil, fmt.Errorf("Failed to cast arg id to string") } - node_if, err := UseStates(ctx, []GraphNode{server}, func(states []NodeState) (interface{}, error){ + var node GraphNode = nil + err := UseStates(ctx, []GraphNode{server}, func(states []NodeState) (error){ server_state := states[0].(*GQLThreadState) - node := FindChild(ctx, server, server_state, NodeID(id)) + node = FindChild(ctx, server, server_state, NodeID(id)) if node == nil { - return nil, fmt.Errorf("Failed to find ID: %s as child of server thread", id) + return fmt.Errorf("Failed to find ID: %s as child of server thread", id) } - return node, nil + return nil }) if err != nil { return nil, err } - node, ok := node_if.(GraphNode) - if ok == false { - return nil, fmt.Errorf("Failed to cast found node to GraphNode") - } - SendUpdate(ctx, node, signal) return signal, nil }, diff --git a/gql_test.go b/gql_test.go index e8d67e4..563c715 100644 --- a/gql_test.go +++ b/gql_test.go @@ -6,8 +6,7 @@ import ( ) func TestGQLThread(t * testing.T) { - println("TEST_GQL") - ctx := logTestContext(t, []string{"gqlws", "gql", "thread", "update"}) + ctx := testContext(t) gql_thread, err := NewGQLThread(ctx, ":8080", []Lockable{}, ObjTypeMap{}, FieldMap{}, FieldMap{}, FieldMap{}) fatalErr(t, err) diff --git a/graph.go b/graph.go index 3e1a5f8..f82cca7 100644 --- a/graph.go +++ b/graph.go @@ -331,10 +331,10 @@ func checkForDuplicate(nodes []GraphNode) error { return nil } -func UseStates(ctx * GraphContext, nodes []GraphNode, states_fn func(states []NodeState)(interface{}, error)) (interface{}, error) { +func UseStates(ctx * GraphContext, nodes []GraphNode, states_fn func(states []NodeState)(error)) error { err := checkForDuplicate(nodes) if err != nil { - return nil, err + return err } for _, node := range(nodes) { @@ -346,19 +346,19 @@ func UseStates(ctx * GraphContext, nodes []GraphNode, states_fn func(states []No states[i] = node.State() } - val, err := states_fn(states) + err = states_fn(states) for _, node := range(nodes) { node.StateLock().RUnlock() } - return val, err + return err } -func UpdateStates(ctx * GraphContext, nodes []GraphNode, states_fn func(states []NodeState)([]NodeState, interface{}, error)) (interface{}, error) { +func UpdateStates(ctx * GraphContext, nodes []GraphNode, states_fn func(states []NodeState)([]NodeState, error)) error { err := checkForDuplicate(nodes) if err != nil { - return nil, err + return err } for _, node := range(nodes) { @@ -370,7 +370,7 @@ func UpdateStates(ctx * GraphContext, nodes []GraphNode, states_fn func(states [ states[i] = node.State() } - new_states, val, err := states_fn(states) + new_states, err := states_fn(states) if new_states != nil { if len(new_states) != len(nodes) { @@ -400,7 +400,7 @@ func UpdateStates(ctx * GraphContext, nodes []GraphNode, states_fn func(states [ node.StateLock().Unlock() } - return val, err + return err } func (node * BaseNode) UpdateListeners(ctx * GraphContext, update GraphSignal) { diff --git a/lockable.go b/lockable.go index b205f52..548a83a 100644 --- a/lockable.go +++ b/lockable.go @@ -5,63 +5,36 @@ import ( "encoding/json" ) -// LockHolderState is the interface that any node that wants to posses locks must implement +// LockableState is the interface that any node that wants to posses locks must implement // // ReturnLock returns the node that held the lockable pointed to by ID before this node and // removes the mapping from it's state, or nil if the lockable was unlocked previously // // AllowedToTakeLock returns true if the node pointed to by ID is allowed to take a lock from this node // -// RecordLockHolder records that lockable_id needs to be passed back to lock_holder -type LockHolderState interface { +type LockableState interface { NodeState - ReturnLock(lockable_id NodeID) GraphNode + + ReturnLock(lockable_id NodeID) Lockable AllowedToTakeLock(node_id NodeID, lockable_id NodeID) bool - RecordLockHolder(lockable_id NodeID, lock_holder GraphNode) -} + RecordLockHolder(lockable_id NodeID, lock_holder Lockable) -// LockableState is the interface that a lockables state must have to allow it to connect to the DAG -type LockableState interface { - LockHolderState Requirements() []Lockable AddRequirement(requirement Lockable) Dependencies() []Lockable AddDependency(dependency Lockable) - Owner() GraphNode - SetOwner(owner GraphNode) -} - -type BaseLockHolderState struct { - locks_held map[NodeID] GraphNode -} - -type BaseLockHolderStateJSON struct { - LocksHeld map[NodeID]*NodeID `json:"locks_held"` -} - -func (state * BaseLockHolderState) MarshalJSON() ([]byte, error) { - locks_held := map[NodeID]*NodeID{} - for lockable_id, node := range(state.locks_held) { - if node == nil { - locks_held[lockable_id] = nil - } else { - str := node.ID() - locks_held[lockable_id] = &str - } - } - return json.Marshal(&BaseLockHolderStateJSON{ - LocksHeld: locks_held, - }) + Owner() Lockable + SetOwner(owner Lockable) } // BaseLockableStates are a minimum collection of variables for a basic implementation of a LockHolder // Include in any state structs that should be lockable type BaseLockableState struct { - BaseLockHolderState name string - owner GraphNode + owner Lockable requirements []Lockable dependencies []Lockable + locks_held map[NodeID]Lockable } type BaseLockableStateJSON struct { @@ -69,7 +42,7 @@ type BaseLockableStateJSON struct { Owner *NodeID `json:"owner"` Dependencies []NodeID `json:"dependencies"` Requirements []NodeID `json:"requirements"` - HolderState *BaseLockHolderState `json:"holder"` + LocksHeld map[NodeID]*NodeID `json:"locks_held"` } func (state * BaseLockableState) MarshalJSON() ([]byte, error) { @@ -89,12 +62,22 @@ func (state * BaseLockableState) MarshalJSON() ([]byte, error) { owner_id = &new_str } + locks_held := map[NodeID]*NodeID{} + for lockable_id, node := range(state.locks_held) { + if node == nil { + locks_held[lockable_id] = nil + } else { + str := node.ID() + locks_held[lockable_id] = &str + } + } + return json.Marshal(&BaseLockableStateJSON{ Name: state.name, Owner: owner_id, Dependencies: dependency_ids, Requirements: requirement_ids, - HolderState: &state.BaseLockHolderState, + LocksHeld: locks_held, }) } @@ -104,7 +87,7 @@ func (state * BaseLockableState) Name() string { // Locks cannot be passed between base lockables, so the answer to // "who used to own this lock held by a base lockable" is always "nobody" -func (state * BaseLockHolderState) ReturnLock(lockable_id NodeID) GraphNode { +func (state * BaseLockableState) ReturnLock(lockable_id NodeID) Lockable { node, exists := state.locks_held[lockable_id] if exists == false { panic("Attempted to take a get the original lock holder of a lockable we don't own") @@ -114,7 +97,7 @@ func (state * BaseLockHolderState) ReturnLock(lockable_id NodeID) GraphNode { } // Nothing can take a lock from a base lockable either -func (state * BaseLockHolderState) AllowedToTakeLock(node_id NodeID, lockable_id NodeID) bool { +func (state * BaseLockableState) AllowedToTakeLock(node_id NodeID, lockable_id NodeID) bool { _, exists := state.locks_held[lockable_id] if exists == false { panic ("Trying to give away lock we don't own") @@ -122,7 +105,7 @@ func (state * BaseLockHolderState) AllowedToTakeLock(node_id NodeID, lockable_id return false } -func (state * BaseLockHolderState) RecordLockHolder(lockable_id NodeID, lock_holder GraphNode) { +func (state * BaseLockableState) RecordLockHolder(lockable_id NodeID, lock_holder Lockable) { _, exists := state.locks_held[lockable_id] if exists == true { panic("Attempted to lock a lockable we're already holding(lock cycle)") @@ -131,11 +114,11 @@ func (state * BaseLockHolderState) RecordLockHolder(lockable_id NodeID, lock_hol state.locks_held[lockable_id] = lock_holder } -func (state * BaseLockableState) Owner() GraphNode { +func (state * BaseLockableState) Owner() Lockable { return state.owner } -func (state * BaseLockableState) SetOwner(owner GraphNode) { +func (state * BaseLockableState) SetOwner(owner Lockable) { state.owner = owner } @@ -162,12 +145,6 @@ func (state * BaseLockableState) AddDependency(dependency Lockable) { state.dependencies = append(state.dependencies, dependency) } -func NewLockHolderState() BaseLockHolderState { - return BaseLockHolderState{ - locks_held: map[NodeID]GraphNode{}, - } -} - func LinkLockables(ctx * GraphContext, lockable Lockable, requirements []Lockable) error { if lockable == nil { return fmt.Errorf("LOCKABLE_LINK_ERR: Will not link Lockables to nil as requirements") @@ -188,19 +165,38 @@ func LinkLockables(ctx * GraphContext, lockable Lockable, requirements []Lockabl for i, node := range(requirements) { nodes[i+1] = node } - _, err := UpdateStates(ctx, nodes, func(states []NodeState) ([]NodeState, interface{}, error) { + err := UpdateStates(ctx, nodes, func(states []NodeState) ([]NodeState, error) { // Check that all the requirements can be added lockable_state := states[0].(LockableState) + // If the lockable is already locked, need to lock this resource as well before we can add it for i, requirement := range(requirements) { requirement_state := states[i+1].(LockableState) - - if checkIfRequirement(ctx, lockable.ID(), requirement_state, requirement.ID()) == true { - return nil, nil, fmt.Errorf("LOCKABLE_LINK_ERR: %s is a dependency of %s so cannot link as requirement", requirement.ID(), lockable.ID()) + return nil, fmt.Errorf("LOCKABLE_LINK_ERR: %s is a dependency of %s so cannot link as requirement", requirement.ID(), lockable.ID()) } if checkIfRequirement(ctx, requirement.ID(), lockable_state, lockable.ID()) == true { - return nil, nil, fmt.Errorf("LOCKABLE_LINK_ERR: %s is a dependency of %s so cannot link as dependency again", lockable.ID(), requirement.ID()) + return nil, fmt.Errorf("LOCKABLE_LINK_ERR: %s is a dependency of %s so cannot link as dependency again", lockable.ID(), requirement.ID()) + } + if lockable_state.Owner() == nil { + // If the new owner isn't locked, we can add the requirement + } else if requirement_state.Owner() == nil { + // See if the requirement can be locked by the owner right now + //TODO TODO + } + + if requirement_state.Owner() != nil && lockable_state.Owner() == nil { + // If the requirement is locked but not the owner, we can add and don't have to lock + + } else if requirement_state.Owner() == nil && lockable_state.Owner() == nil { + // If the requirement and the owner is unlocked, we can add and don't have to lock + + } else if requirement_state.Owner() != nil && lockable_state.Owner() != nil { + // If the requirement and the owner are locked, we can't add them unless the owner is already the owner + + } else if requirement_state.Owner() == nil && lockable_state.Owner() != nil { + // If the requirement is unlocked and the owner is locked, we need to lock the requirement first + } } // Update the states of the requirements @@ -211,7 +207,7 @@ func LinkLockables(ctx * GraphContext, lockable Lockable, requirements []Lockabl } // Return no error - return states, nil, nil + return states, nil }) return err @@ -219,7 +215,7 @@ func LinkLockables(ctx * GraphContext, lockable Lockable, requirements []Lockabl func NewBaseLockableState(name string) BaseLockableState { state := BaseLockableState{ - BaseLockHolderState: NewLockHolderState(), + locks_held: map[NodeID]Lockable{}, name: name, owner: nil, requirements: []Lockable{}, @@ -232,13 +228,17 @@ func NewBaseLockableState(name string) BaseLockableState { type Lockable interface { GraphNode // Called when locking the node to allow for custom lock behaviour - Lock(node GraphNode, state LockableState) error + Lock(node GraphNode, state LockableState) + // Called to check if the node can lock + CanLock(node GraphNode, state LockableState) error // Called when unlocking the node to allow for custom lock behaviour - Unlock(node GraphNode, state LockableState) error + Unlock(node GraphNode, state LockableState) + // Called to check if the node can unlock + CanUnlock(node GraphNode, state LockableState) error } func (lockable * BaseLockable) PropagateUpdate(ctx * GraphContext, signal GraphSignal) { - UseStates(ctx, []GraphNode{lockable}, func(states []NodeState) (interface{}, error){ + UseStates(ctx, []GraphNode{lockable}, func(states []NodeState) (error){ lockable_state := states[0].(LockableState) if signal.Direction() == Up { // Child->Parent, lockable updates dependency lockables @@ -264,7 +264,7 @@ func (lockable * BaseLockable) PropagateUpdate(ctx * GraphContext, signal GraphS } else { panic(fmt.Sprintf("Invalid signal direction: %d", signal.Direction())) } - return nil, nil + return nil }) } @@ -273,12 +273,13 @@ func checkIfRequirement(ctx * GraphContext, r_id NodeID, cur LockableState, cur_ if c.ID() == r_id { return true } - val, _ := UseStates(ctx, []GraphNode{c}, func(states []NodeState) (interface{}, error) { + is_requirement := false + UseStates(ctx, []GraphNode{c}, func(states []NodeState) (error) { requirement_state := states[0].(LockableState) - return checkIfRequirement(ctx, cur_id, requirement_state, c.ID()), nil + is_requirement = checkIfRequirement(ctx, cur_id, requirement_state, c.ID()) + return nil }) - is_requirement := val.(bool) if is_requirement { return true } @@ -287,132 +288,218 @@ func checkIfRequirement(ctx * GraphContext, r_id NodeID, cur LockableState, cur_ return false } -func UnlockLockable(ctx * GraphContext, lockable Lockable, node GraphNode, node_state LockHolderState) error { - if node == nil || lockable == nil{ - panic("Cannot unlock without a specified node and lockable") +func LockLockables(ctx * GraphContext, to_lock []Lockable, holder Lockable, holder_state LockableState, owner_states map[NodeID]LockableState) error { + if to_lock == nil { + return fmt.Errorf("LOCKABLE_LOCK_ERR: no list provided") } - ctx.Log.Logf("lockable", "Unlocking %s", lockable.ID()) - _, err := UpdateStates(ctx, []GraphNode{lockable}, func(states []NodeState) ([]NodeState, interface{}, error) { - if lockable.ID() == node.ID() { - if node_state != nil { - panic("node_state must be nil if unlocking lockable from itself") - } - node_state = states[0].(LockHolderState) + for _, l := range(to_lock) { + if l == nil { + return fmt.Errorf("LOCKABLE_LOCK_ERR: Can not lock nil") } - lockable_state := states[0].(LockableState) + } + if holder == nil { + return fmt.Errorf("LOCKABLE_LOCK_ERR: nil cannot hold locks") + } - if lockable_state.Owner() == nil { - return nil, nil, fmt.Errorf("Lockable already unlocked") - } + // Called with no requirements to lock, success + if len(to_lock) == 0 { + return nil + } - if lockable_state.Owner().ID() != node.ID() { - return nil, nil, fmt.Errorf("Lockable %s not locked by %s", lockable.ID(), node.ID()) + if holder_state == nil { + if len(to_lock) != 1 { + return fmt.Errorf("LOCKABLE_UNLOCK_ERR: if holder_state is nil, can only self-lock") + } else if holder.ID() != to_lock[0].ID() { + return fmt.Errorf("LOCKABLE_UNLOCK_ERR: if holder_state is nil, can only self-lock") } + } + - var lock_err error = nil - for _, requirement := range(lockable_state.Requirements()) { - var err error = nil - err = UnlockLockable(ctx, requirement, lockable, lockable_state) + node_list := make([]GraphNode, len(to_lock)) + for i, l := range(to_lock) { + node_list[i] = l + } + + err := UpdateStates(ctx, node_list, func(states []NodeState) ([]NodeState, error) { + // First loop is to check that the states can be locked, and locks all requirements + for i, state := range(states) { + req := to_lock[i] + req_state, ok := state.(LockableState) + ctx.Log.Logf("lockable", "LOCKABLE_LOCKING: %s from %s", req.ID(), holder.ID()) + if ok == false { + return nil, fmt.Errorf("LOCKABLE_LOCK_ERR: %s(requirement of %s) does not have a LockableState", req.ID(), holder.ID()) + } + + // Check custom lock conditions + err := req.CanLock(holder, req_state) if err != nil { - lock_err = err - break + return nil, err } - } - if lock_err != nil { - return nil, nil, fmt.Errorf("Lockable %s failed to unlock: %e", lockable.ID(), lock_err) + // If req is alreay locked, check that we can pass the lock + if req_state.Owner() != nil { + owner := req_state.Owner() + // Check if reqs owner will let holder take the lock from it + // The owner is either the same node, a node higher up in the dependency tree, or node outside the dependency tree(must be enforeced when linking dependencies) + // If the owner is the same node, we already have all the states we need to check lock passing + // If the owner is higher up in the dependency tree, we've either already got it's state getting to this node, or we won't try to get it's state as a dependency to lock this node, so we can grab the state and add it to a map + // If the owner is outside the dependency tree, then we won't try to grab it's lock trying to lock this node recursively + // So if the owner is the same node we don't need a new state, but if the owner is a different node then we need to grab it's state and add it to the list + if owner.ID() == req.ID() { + if req_state.AllowedToTakeLock(holder.ID(), req.ID()) == false { + return nil, fmt.Errorf("LOCKABLE_LOCK_ERR: %s is not allowed to take %s's lock from %s", holder.ID(), req.ID(), owner.ID()) + } + // RECURSE: At this point either: + // 1) req has no children and the next LockLockables will return instantly + // a) in this case, we're holding every state mutex up to the resource being locked + // and all the owners passing a lock, so we can start to change state + // 2) req has children, and we will recurse(checking that locking is allowed) until we reach a leaf and can release the locks as we change state. The call will either return nil if state has changed, on an error if no state has changed + err := LockLockables(ctx, req_state.Requirements(), req, req_state, owner_states) + if err != nil { + return nil, err + } + } else { + owner_state, exists := owner_states[owner.ID()] + if exists == false { + err := UseStates(ctx, []GraphNode{req_state.Owner()}, func(states []NodeState)(error){ + owner_state, ok := states[0].(LockableState) + if ok == false { + return fmt.Errorf("LOCKABLE_LOCK_ERR: %s does not have a LockableState", owner.ID()) + } + + if owner_state.AllowedToTakeLock(holder.ID(), req.ID()) == false { + return fmt.Errorf("LOCKABLE_LOCK_ERR: %s is not allowed to take %s's lock from %s", holder.ID(), req.ID(), owner.ID()) + } + owner_states[owner.ID()] = owner_state + err := LockLockables(ctx, req_state.Requirements(), req, req_state, owner_states) + return err + }) + if err != nil { + return nil, err + } + } else { + if owner_state.AllowedToTakeLock(holder.ID(), req.ID()) == false { + return nil, fmt.Errorf("LOCKABLE_LOCK_ERR: %s is not allowed to take %s's lock from %s", holder.ID(), req.ID(), owner.ID()) + } + err := LockLockables(ctx, req_state.Requirements(), req, req_state, owner_states) + if err != nil { + return nil, err + } + } + } + } else { + err := LockLockables(ctx, req_state.Requirements(), req, req_state, owner_states) + if err != nil { + return nil, err + } + } } - new_owner := node_state.ReturnLock(lockable.ID()) - lockable_state.SetOwner(new_owner) - err := lockable.Unlock(node, lockable_state) - if err != nil { - return nil, nil, fmt.Errorf("Lockable %s failed custom Unlock: %e", lockable.ID(), err) + // At this point state modification will be started, so no errors can be returned + for i, state := range(states) { + req := to_lock[i] + req_state := state.(LockableState) + old_owner := req_state.Owner() + req_state.SetOwner(holder) + if req.ID() == holder.ID() { + req_state.RecordLockHolder(req.ID(), old_owner) + } else { + holder_state.RecordLockHolder(req.ID(), old_owner) + } + req.Lock(holder, req_state) + if old_owner == nil { + ctx.Log.Logf("lockable", "LOCKABLE_LOCK: %s locked %s", holder.ID(), req.ID()) + } else { + ctx.Log.Logf("lockable", "LOCKABLE_LOCK: %s took lock of %s from %s", holder.ID(), req.ID(), old_owner.ID()) + } } + return states, nil + }) + return err +} - if lockable_state.Owner() == nil { - ctx.Log.Logf("lockable", "LOCKABLE_UNLOCK: %s unlocked %s", node.ID(), lockable.ID()) - } else { - ctx.Log.Logf("lockable", "LOCKABLE_UNLOCK: %s passed lock of %s back to %s", node.ID(), lockable.ID(), lockable_state.Owner().ID()) +func UnlockLockables(ctx * GraphContext, to_unlock []Lockable, holder Lockable, holder_state LockableState, owner_states map[NodeID]LockableState) 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 lock nil") } + } + if holder == nil { + return fmt.Errorf("LOCKABLE_UNLOCK_ERR: nil cannot hold locks") + } - return []NodeState{lockable_state}, nil, nil - }) + // Called with no requirements to lock, success + if len(to_unlock) == 0 { + return nil + } - return err -} + if holder_state == nil { + if len(to_unlock) != 1 { + return fmt.Errorf("LOCKABLE_UNLOCK_ERR: if holder_state is nil, can only self-lock") + } else if holder.ID() != to_unlock[0].ID() { + return fmt.Errorf("LOCKABLE_UNLOCK_ERR: if holder_state is nil, can only self-lock") + } + } -func LockLockable(ctx * GraphContext, lockable Lockable, node GraphNode, node_state LockHolderState) error { - if node == nil || lockable == nil { - return fmt.Errorf("Cannot lock without a specified node and lockable") + node_list := make([]GraphNode, len(to_unlock)) + for i, l := range(to_unlock) { + node_list[i] = l } - ctx.Log.Logf("lockable", "LOCKING: %s from %s", lockable.ID(), node.ID()) - _, err := UpdateStates(ctx, []GraphNode{lockable}, func(states []NodeState) ([]NodeState, interface{}, error) { - if lockable.ID() == node.ID() { - if node_state != nil { - return nil, nil, fmt.Errorf("node_state must be nil if locking lockable from itself") + err := UpdateStates(ctx, node_list, func(states []NodeState) ([]NodeState, error) { + // First loop is to check that the states can be locked, and locks all requirements + for i, state := range(states) { + req := to_unlock[i] + req_state, ok := state.(LockableState) + ctx.Log.Logf("lockable", "LOCKABLE_UNLOCKING: %s from %s", req.ID(), holder.ID()) + if ok == false { + return nil, fmt.Errorf("LOCKABLE_UNLOCK_ERR: %s(requirement of %s) does not have a LockableState", req.ID(), holder.ID()) } - node_state = states[0].(LockHolderState) - } - lockable_state := states[0].(LockableState) - if lockable_state.Owner() != nil { - var lock_pass_allowed bool = false - if lockable_state.Owner().ID() == lockable.ID() { - lock_pass_allowed = lockable_state.AllowedToTakeLock(node.ID(), lockable.ID()) + // Check if the owner is correct + if req_state.Owner() != nil { + if req_state.Owner().ID() != holder.ID() { + return nil, fmt.Errorf("LOCKABLE_UNLOCK_ERR: %s is not locked by %s", req.ID(), holder.ID()) + } } else { - tmp, _ := UseStates(ctx, []GraphNode{lockable_state.Owner()}, func(states []NodeState)(interface{}, error){ - return states[0].(LockHolderState).AllowedToTakeLock(node.ID(), lockable.ID()), nil - }) - lock_pass_allowed = tmp.(bool) + return nil, fmt.Errorf("LOCKABLE_UNLOCK_ERR: %s is not locked", req.ID()) } - - if lock_pass_allowed == false { - return nil, nil, fmt.Errorf("%s is not allowed to take a lock from %s", node.ID(), lockable_state.Owner().ID()) + // Check custom unlock conditions + err := req.CanUnlock(holder, req_state) + if err != nil { + return nil, err } - } - err := lockable.Lock(node, lockable_state) - if err != nil { - return nil, nil, fmt.Errorf("Failed to lock lockable: %e", err) - } - - var lock_err error = nil - locked_requirements := []Lockable{} - for _, requirement := range(lockable_state.Requirements()) { - err = LockLockable(ctx, requirement, lockable, lockable_state) + err = UnlockLockables(ctx, req_state.Requirements(), req, req_state, owner_states) if err != nil { - lock_err = err - break + return nil, err } - locked_requirements = append(locked_requirements, requirement) } - if lock_err != nil { - for _, locked_lockable := range(locked_requirements) { - err = UnlockLockable(ctx, locked_lockable, lockable, lockable_state) - if err != nil { - panic(err) - } + // At this point state modification will be started, so no errors can be returned + for i, state := range(states) { + req := to_unlock[i] + req_state := state.(LockableState) + var new_owner Lockable = nil + if holder_state == nil { + new_owner = req_state.ReturnLock(req.ID()) + } else { + new_owner = holder_state.ReturnLock(req.ID()) + } + req_state.SetOwner(new_owner) + req.Unlock(holder, req_state) + if new_owner == nil { + ctx.Log.Logf("lockable", "LOCKABLE_UNLOCK: %s unlocked %s", holder.ID(), req.ID()) + } else { + ctx.Log.Logf("lockable", "LOCKABLE_UNLOCK: %s passed lock of %s back to %s", holder.ID(), req.ID(), new_owner.ID()) } - return nil, nil, fmt.Errorf("Lockable failed to lock: %e", lock_err) - } - - old_owner := lockable_state.Owner() - lockable_state.SetOwner(node) - node_state.RecordLockHolder(lockable.ID(), old_owner) - - if old_owner == nil { - ctx.Log.Logf("lockable", "LOCKABLE_LOCK: %s locked %s", node.ID(), lockable.ID()) - } else { - ctx.Log.Logf("lockable", "LOCKABLE_LOCK: %s took lock of %s from %s", node.ID(), lockable.ID(), old_owner.ID()) } - - return []NodeState{lockable_state}, nil, nil + return states, nil }) - return err } @@ -422,14 +509,23 @@ type BaseLockable struct { } //BaseLockables don't check anything special when locking/unlocking -func (lockable * BaseLockable) Lock(node GraphNode, state LockableState) error { +func (lockable * BaseLockable) CanLock(node GraphNode, state LockableState) error { return nil } -func (lockable * BaseLockable) Unlock(node GraphNode, state LockableState) error { +func (lockable * BaseLockable) CanUnlock(node GraphNode, state LockableState) error { return nil } +//BaseLockables don't check anything special when locking/unlocking +func (lockable * BaseLockable) Lock(node GraphNode, state LockableState) { + return +} + +func (lockable * BaseLockable) Unlock(node GraphNode, state LockableState) { + return +} + func NewBaseLockable(ctx * GraphContext, state LockableState) (BaseLockable, error) { base_node, err := NewNode(ctx, state) if err != nil { diff --git a/lockable_test.go b/lockable_test.go index 74cf890..12f8cc9 100644 --- a/lockable_test.go +++ b/lockable_test.go @@ -35,34 +35,34 @@ func TestLockableSelfLock(t * testing.T) { r1, err := NewSimpleBaseLockable(ctx, "Test lockable 1", []Lockable{}) fatalErr(t, err) - err = LockLockable(ctx, r1, r1, nil) + err = LockLockables(ctx, []Lockable{r1}, r1, nil, map[NodeID]LockableState{}) fatalErr(t, err) - _, err = UseStates(ctx, []GraphNode{r1}, func(states []NodeState) (interface{}, error) { + err = UseStates(ctx, []GraphNode{r1}, func(states []NodeState) (error) { owner_id := states[0].(LockableState).Owner().ID() if owner_id != r1.ID() { - return nil, fmt.Errorf("r1 is owned by %s instead of self", owner_id) + return fmt.Errorf("r1 is owned by %s instead of self", owner_id) } - return nil, nil + return nil }) fatalErr(t, err) - err = UnlockLockable(ctx, r1, r1, nil) + err = UnlockLockables(ctx, []Lockable{r1}, r1, nil, map[NodeID]LockableState{}) fatalErr(t, err) - _, err = UseStates(ctx, []GraphNode{r1}, func(states []NodeState) (interface{}, error) { + err = UseStates(ctx, []GraphNode{r1}, func(states []NodeState) (error) { owner := states[0].(LockableState).Owner() if owner != nil { - return nil, fmt.Errorf("r1 is not unowned after unlock: %s", owner.ID()) + return fmt.Errorf("r1 is not unowned after unlock: %s", owner.ID()) } - return nil, nil + return nil }) fatalErr(t, err) } func TestLockableSelfLockTiered(t * testing.T) { - ctx := testContext(t) + ctx := logTestContext(t, []string{"lockable"}) r1, err := NewSimpleBaseLockable(ctx, "Test lockable 1", []Lockable{}) fatalErr(t, err) @@ -73,45 +73,45 @@ func TestLockableSelfLockTiered(t * testing.T) { r3, err := NewSimpleBaseLockable(ctx, "Test lockable 3", []Lockable{r1, r2}) fatalErr(t, err) - err = LockLockable(ctx, r3, r3, nil) + err = LockLockables(ctx, []Lockable{r3}, r3, nil, map[NodeID]LockableState{}) fatalErr(t, err) - _, err = UseStates(ctx, []GraphNode{r1, r2, r3}, func(states []NodeState) (interface{}, error) { + err = UseStates(ctx, []GraphNode{r1, r2, r3}, func(states []NodeState) (error) { owner_1_id := states[0].(LockableState).Owner().ID() if owner_1_id != r3.ID() { - return nil, fmt.Errorf("r1 is owned by %s instead of r3", owner_1_id) + return fmt.Errorf("r1 is owned by %s instead of r3", owner_1_id) } owner_2_id := states[1].(LockableState).Owner().ID() if owner_2_id != r3.ID() { - return nil, fmt.Errorf("r2 is owned by %s instead of r3", owner_2_id) + return fmt.Errorf("r2 is owned by %s instead of r3", owner_2_id) } ser, _ := json.MarshalIndent(states, "", " ") fmt.Printf("\n\n%s\n\n", ser) - return nil, nil + return nil }) fatalErr(t, err) - err = UnlockLockable(ctx, r3, r3, nil) + err = UnlockLockables(ctx, []Lockable{r3}, r3, nil, map[NodeID]LockableState{}) fatalErr(t, err) - _, err = UseStates(ctx, []GraphNode{r1, r2, r3}, func(states []NodeState) (interface{}, error) { + err = UseStates(ctx, []GraphNode{r1, r2, r3}, func(states []NodeState) (error) { owner_1 := states[0].(LockableState).Owner() if owner_1 != nil { - return nil, fmt.Errorf("r1 is not unowned after unlocking: %s", owner_1.ID()) + return fmt.Errorf("r1 is not unowned after unlocking: %s", owner_1.ID()) } owner_2 := states[1].(LockableState).Owner() if owner_2 != nil { - return nil, fmt.Errorf("r2 is not unowned after unlocking: %s", owner_2.ID()) + return fmt.Errorf("r2 is not unowned after unlocking: %s", owner_2.ID()) } owner_3 := states[2].(LockableState).Owner() if owner_3 != nil { - return nil, fmt.Errorf("r3 is not unowned after unlocking: %s", owner_3.ID()) + return fmt.Errorf("r3 is not unowned after unlocking: %s", owner_3.ID()) } - return nil, nil + return nil }) fatalErr(t, err) @@ -126,39 +126,39 @@ func TestLockableLockOther(t * testing.T) { r2, err := NewSimpleBaseLockable(ctx, "Test lockable 2", []Lockable{}) fatalErr(t, err) - _, err = UpdateStates(ctx, []GraphNode{r2}, func(states []NodeState) ([]NodeState, interface{}, error) { - node_state := states[0].(LockHolderState) - err := LockLockable(ctx, r1, r2, node_state) + err = UpdateStates(ctx, []GraphNode{r2}, func(states []NodeState) ([]NodeState, error) { + node_state := states[0].(LockableState) + err := LockLockables(ctx, []Lockable{r1}, r2, node_state, map[NodeID]LockableState{}) fatalErr(t, err) - return []NodeState{node_state}, nil, nil + return []NodeState{node_state}, nil }) fatalErr(t, err) - _, err = UseStates(ctx, []GraphNode{r1}, func(states []NodeState) (interface{}, error) { + err = UseStates(ctx, []GraphNode{r1}, func(states []NodeState) (error) { owner_id := states[0].(LockableState).Owner().ID() if owner_id != r2.ID() { - return nil, fmt.Errorf("r1 is owned by %s instead of r2", owner_id) + return fmt.Errorf("r1 is owned by %s instead of r2", owner_id) } - return nil, nil + return nil }) fatalErr(t, err) - _, err = UpdateStates(ctx, []GraphNode{r2}, func(states []NodeState) ([]NodeState, interface{}, error) { - node_state := states[0].(LockHolderState) - err := UnlockLockable(ctx, r1, r2, node_state) + err = UpdateStates(ctx, []GraphNode{r2}, func(states []NodeState) ([]NodeState, error) { + node_state := states[0].(LockableState) + err := UnlockLockables(ctx, []Lockable{r1}, r2, node_state, map[NodeID]LockableState{}) fatalErr(t, err) - return []NodeState{node_state}, nil, nil + return []NodeState{node_state}, nil }) fatalErr(t, err) - _, err = UseStates(ctx, []GraphNode{r1}, func(states []NodeState) (interface{}, error) { + err = UseStates(ctx, []GraphNode{r1}, func(states []NodeState) (error) { owner := states[0].(LockableState).Owner() if owner != nil { - return nil, fmt.Errorf("r1 is owned by %s instead of r2", owner.ID()) + return fmt.Errorf("r1 is owned by %s instead of r2", owner.ID()) } - return nil, nil + return nil }) fatalErr(t, err) @@ -173,40 +173,40 @@ func TestLockableLockSimpleConflict(t * testing.T) { r2, err := NewSimpleBaseLockable(ctx, "Test lockable 2", []Lockable{}) fatalErr(t, err) - err = LockLockable(ctx, r1, r1, nil) + err = LockLockables(ctx, []Lockable{r1}, r1, nil, map[NodeID]LockableState{}) fatalErr(t, err) - _, err = UpdateStates(ctx, []GraphNode{r2}, func(states []NodeState) ([]NodeState, interface{}, error) { - node_state := states[0].(LockHolderState) - err := LockLockable(ctx, r1, r2, node_state) + err = UpdateStates(ctx, []GraphNode{r2}, func(states []NodeState) ([]NodeState, error) { + node_state := states[0].(LockableState) + err := LockLockables(ctx, []Lockable{r1}, r2, node_state, map[NodeID]LockableState{}) if err == nil { t.Fatal("r2 took r1's lock from itself") } - return []NodeState{node_state}, nil, nil + return []NodeState{node_state}, nil }) fatalErr(t, err) - _, err = UseStates(ctx, []GraphNode{r1}, func(states []NodeState) (interface{}, error) { + err = UseStates(ctx, []GraphNode{r1}, func(states []NodeState) (error) { owner_id := states[0].(LockableState).Owner().ID() if owner_id != r1.ID() { - return nil, fmt.Errorf("r1 is owned by %s instead of r1", owner_id) + return fmt.Errorf("r1 is owned by %s instead of r1", owner_id) } - return nil, nil + return nil }) fatalErr(t, err) - err = UnlockLockable(ctx, r1, r1, nil) + err = UnlockLockables(ctx, []Lockable{r1}, r1, nil, map[NodeID]LockableState{}) fatalErr(t, err) - _, err = UseStates(ctx, []GraphNode{r1}, func(states []NodeState) (interface{}, error) { + err = UseStates(ctx, []GraphNode{r1}, func(states []NodeState) (error) { owner := states[0].(LockableState).Owner() if owner != nil { - return nil, fmt.Errorf("r1 is owned by %s instead of r1", owner.ID()) + return fmt.Errorf("r1 is owned by %s instead of r1", owner.ID()) } - return nil, nil + return nil }) fatalErr(t, err) @@ -224,10 +224,10 @@ func TestLockableLockTieredConflict(t * testing.T) { r3, err := NewSimpleBaseLockable(ctx, "Test lockable 3", []Lockable{r1}) fatalErr(t, err) - err = LockLockable(ctx, r2, r2, nil) + err = LockLockables(ctx, []Lockable{r2}, r2, nil, map[NodeID]LockableState{}) fatalErr(t, err) - err = LockLockable(ctx, r3, r3, nil) + err = LockLockables(ctx, []Lockable{r3}, r3, nil, map[NodeID]LockableState{}) if err == nil { t.Fatal("Locked r3 which depends on r1 while r2 which depends on r1 is already locked") } diff --git a/thread.go b/thread.go index 03ced49..6eee451 100644 --- a/thread.go +++ b/thread.go @@ -11,7 +11,7 @@ import ( // Update the threads listeners, and notify the parent to do the same func (thread * BaseThread) PropagateUpdate(ctx * GraphContext, signal GraphSignal) { - UseStates(ctx, []GraphNode{thread}, func(states []NodeState) (interface{}, error) { + UseStates(ctx, []GraphNode{thread}, func(states []NodeState) (error) { thread_state := states[0].(ThreadState) if signal.Direction() == Up { // Child->Parent, thread updates parent and connected requirement @@ -37,7 +37,7 @@ func (thread * BaseThread) PropagateUpdate(ctx * GraphContext, signal GraphSigna panic(fmt.Sprintf("Invalid signal direction: %d", signal.Direction())) } - return nil, nil + return nil }) thread.signal <- signal } @@ -48,7 +48,6 @@ type ThreadInfo interface { // An Thread is a lockable that has an additional parent->child relationship with other Threads // This relationship allows the thread tree to be modified independent of the lockable state type ThreadState interface { - LockHolderState LockableState Parent() Thread @@ -167,12 +166,12 @@ func checkIfChild(ctx * GraphContext, thread_id NodeID, cur_state ThreadState, c if child.ID() == thread_id { return true } - val, _ := UseStates(ctx, []GraphNode{child}, func(states []NodeState) (interface{}, error) { + is_child := false + UseStates(ctx, []GraphNode{child}, func(states []NodeState) (error) { child_state := states[0].(ThreadState) - return checkIfChild(ctx, cur_id, child_state, child.ID()), nil + is_child = checkIfChild(ctx, cur_id, child_state, child.ID()) + return nil }) - - is_child := val.(bool) if is_child { return true } @@ -191,29 +190,29 @@ func LinkThreads(ctx * GraphContext, thread Thread, child Thread, info ThreadInf } - _, err := UpdateStates(ctx, []GraphNode{thread, child}, func(states []NodeState) ([]NodeState, interface{}, error) { + err := UpdateStates(ctx, []GraphNode{thread, child}, func(states []NodeState) ([]NodeState, error) { thread_state := states[0].(ThreadState) child_state := states[1].(ThreadState) if child_state.Parent() != nil { - return nil, nil, fmt.Errorf("EVENT_LINK_ERR: %s already has a parent, cannot link as child", child.ID()) + return nil, fmt.Errorf("EVENT_LINK_ERR: %s already has a parent, cannot link as child", child.ID()) } if checkIfChild(ctx, thread.ID(), child_state, child.ID()) == true { - return nil, nil, fmt.Errorf("EVENT_LINK_ERR: %s is a child of %s so cannot add as parent", thread.ID(), child.ID()) + return nil, fmt.Errorf("EVENT_LINK_ERR: %s is a child of %s so cannot add as parent", thread.ID(), child.ID()) } if checkIfChild(ctx, child.ID(), thread_state, thread.ID()) == true { - return nil, nil, fmt.Errorf("EVENT_LINK_ERR: %s is already a parent of %s so will not add again", thread.ID(), child.ID()) + return nil, fmt.Errorf("EVENT_LINK_ERR: %s is already a parent of %s so will not add again", thread.ID(), child.ID()) } err := thread_state.AddChild(child, info) if err != nil { - return nil, nil, fmt.Errorf("EVENT_LINK_ERR: error adding %s as child to %s: %e", child.ID(), thread.ID(), err) + return nil, fmt.Errorf("EVENT_LINK_ERR: error adding %s as child to %s: %e", child.ID(), thread.ID(), err) } child_state.SetParent(thread) - return states, nil, nil + return states, nil }) if err != nil { @@ -250,12 +249,12 @@ func FindChild(ctx * GraphContext, thread Thread, thread_state ThreadState, id N for _, child := range thread_state.Children() { - res, _ := UseStates(ctx, []GraphNode{child}, func(states []NodeState) (interface{}, error) { + var result Thread = nil + UseStates(ctx, []GraphNode{child}, func(states []NodeState) (error) { child_state := states[0].(ThreadState) - result := FindChild(ctx, child, child_state, id) - return result, nil + result = FindChild(ctx, child, child_state, id) + return nil }) - result := res.(Thread) if result != nil { return result } @@ -285,22 +284,25 @@ func ChildGo(ctx * GraphContext, thread_state ThreadState, thread Thread, child_ func RunThread(ctx * GraphContext, thread Thread) error { ctx.Log.Logf("thread", "THREAD_RUN: %s", thread.ID()) - err := LockLockable(ctx, thread, thread, nil) + err := LockLockables(ctx, []Lockable{thread}, thread, nil, map[NodeID]LockableState{}) if err != nil { return err } - _, err = UseStates(ctx, []GraphNode{thread}, func(states []NodeState) (interface{}, error) { + err = UseStates(ctx, []GraphNode{thread}, func(states []NodeState) (error) { thread_state := states[0].(ThreadState) if thread_state.Owner() == nil { - return nil, fmt.Errorf("THREAD_RUN_NOT_LOCKED: %s", thread_state.Name()) + return fmt.Errorf("THREAD_RUN_NOT_LOCKED: %s", thread_state.Name()) } else if thread_state.Owner().ID() != thread.ID() { - return nil, fmt.Errorf("THREAD_RUN_RESOURCE_ALREADY_LOCKED: %s, %s", thread_state.Name(), thread_state.Owner().ID()) + return fmt.Errorf("THREAD_RUN_RESOURCE_ALREADY_LOCKED: %s, %s", thread_state.Name(), thread_state.Owner().ID()) } else if err := thread_state.Start(); err != nil { - return nil, fmt.Errorf("THREAD_START_ERR: %e", err) + return fmt.Errorf("THREAD_START_ERR: %e", err) } - return nil, nil + return nil }) + if err != nil { + return err + } SendUpdate(ctx, thread, NewSignal(thread, "thread_start")) @@ -319,10 +321,10 @@ func RunThread(ctx * GraphContext, thread Thread) error { } } - _, err = UseStates(ctx, []GraphNode{thread}, func(states []NodeState) (interface{}, error) { + err = UseStates(ctx, []GraphNode{thread}, func(states []NodeState) (error) { thread_state := states[0].(ThreadState) err := thread_state.Stop() - return nil, err + return err }) if err != nil { @@ -330,7 +332,7 @@ func RunThread(ctx * GraphContext, thread Thread) error { return err } - err = UnlockLockable(ctx, thread, thread, nil) + err = UnlockLockables(ctx, []Lockable{thread}, thread, nil, map[NodeID]LockableState{}) if err != nil { ctx.Log.Logf("thread", "THREAD_RUN_UNLOCK_ERR: %e", err) return err @@ -368,14 +370,22 @@ func (thread * BaseThread) ChildWaits() *sync.WaitGroup { return &thread.child_waits } -func (thread * BaseThread) Lock(node GraphNode, state LockableState) error { +func (thread * BaseThread) CanLock(node GraphNode, state LockableState) error { return nil } -func (thread * BaseThread) Unlock(node GraphNode, state LockableState) error { +func (thread * BaseThread) CanUnlock(node GraphNode, state LockableState) error { return nil } +func (thread * BaseThread) Lock(node GraphNode, state LockableState) { + return +} + +func (thread * BaseThread) Unlock(node GraphNode, state LockableState) { + return +} + func (thread * BaseThread) Action(action string) (ThreadAction, bool) { action_fn, exists := thread.Actions[action] return action_fn, exists diff --git a/thread_test.go b/thread_test.go index b924608..194108a 100644 --- a/thread_test.go +++ b/thread_test.go @@ -21,13 +21,13 @@ func TestNewEvent(t * testing.T) { err = RunThread(ctx, t1) fatalErr(t, err) - _, err = UseStates(ctx, []GraphNode{t1}, func(states []NodeState) (interface{}, error) { + err = UseStates(ctx, []GraphNode{t1}, func(states []NodeState) (error) { ser, err := json.MarshalIndent(states, "", " ") fatalErr(t, err) fmt.Printf("\n%s\n", ser) - return nil, nil + return nil }) } @@ -42,12 +42,12 @@ func TestEventWithRequirement(t * testing.T) { go func (thread Thread) { time.Sleep(10*time.Millisecond) - _, err := UseStates(ctx, []GraphNode{l1}, func(states []NodeState) (interface{}, error) { + err := UseStates(ctx, []GraphNode{l1}, func(states []NodeState) (error) { ser, err := json.MarshalIndent(states[0], "", " ") fatalErr(t, err) fmt.Printf("\n%s\n", ser) - return nil, nil + return nil }) fatalErr(t, err) SendUpdate(ctx, t1, CancelSignal(nil)) @@ -57,12 +57,12 @@ func TestEventWithRequirement(t * testing.T) { err = RunThread(ctx, t1) fatalErr(t, err) - _, err = UseStates(ctx, []GraphNode{l1}, func(states []NodeState) (interface{}, error) { + err = UseStates(ctx, []GraphNode{l1}, func(states []NodeState) (error) { ser, err := json.MarshalIndent(states[0], "", " ") fatalErr(t, err) fmt.Printf("\n%s\n", ser) - return nil, nil + return nil }) fatalErr(t, err) }