Rework locking to hold all locks before changing any state

graph-rework-2
noah metz 2023-06-28 00:48:49 -06:00
parent ee98e13044
commit a696213e38
8 changed files with 406 additions and 327 deletions

@ -370,13 +370,13 @@ var gql_actions ThreadActions = ThreadActions{
fs := http.FileServer(http.Dir("./site")) fs := http.FileServer(http.Dir("./site"))
mux.Handle("/site/", http.StripPrefix("/site", fs)) 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_state := states[0].(*GQLThreadState)
server.http_server = &http.Server{ server.http_server = &http.Server{
Addr: server_state.Listen, Addr: server_state.Listen,
Handler: mux, Handler: mux,
} }
return nil, nil return nil
}) })
server.http_done.Add(1) server.http_done.Add(1)
@ -395,18 +395,18 @@ var gql_actions ThreadActions = ThreadActions{
var gql_handlers ThreadHandlers = ThreadHandlers{ var gql_handlers ThreadHandlers = ThreadHandlers{
"child_added": func(ctx * GraphContext, thread Thread, signal GraphSignal) (string, error) { "child_added": func(ctx * GraphContext, thread Thread, signal GraphSignal) (string, error) {
ctx.Log.Logf("gql", "GQL_THREAD_CHILD_ADDED: %+v", signal) 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) server_state := states[0].(*GQLThreadState)
should_run, exists := server_state.child_info[signal.Source()].(*GQLThreadInfo) should_run, exists := server_state.child_info[signal.Source()].(*GQLThreadInfo)
if exists == false { if exists == false {
ctx.Log.Logf("gql", "GQL_THREAD_CHILD_ADDED: tried to start %s whis is not a child") 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 { if should_run.Start == true && should_run.Started == false {
ChildGo(ctx, server_state, thread, signal.Source()) ChildGo(ctx, server_state, thread, signal.Source())
should_run.Started = false should_run.Started = false
} }
return nil, nil return nil
}) })
return "wait", nil return "wait", nil
}, },

@ -221,20 +221,17 @@ func GQLNodeName(p graphql.ResolveParams) (interface{}, error) {
return nil, fmt.Errorf("Failed to cast context graph_context to GraphContext") return nil, fmt.Errorf("Failed to cast context graph_context to GraphContext")
} }
name, err := UseStates(ctx, []GraphNode{node}, func(states []NodeState) (interface{}, error) { name := ""
return states[0].Name(), nil err := UseStates(ctx, []GraphNode{node}, func(states []NodeState) (error) {
name = states[0].Name()
return nil
}) })
if err != nil { if err != nil {
return nil, err return nil, err
} }
name_str, ok := name.(string) return name, nil
if ok == false {
return nil, fmt.Errorf("Failed to cast name to string %+v", name)
}
return name_str, nil
} }
func GQLThreadListen(p graphql.ResolveParams) (interface{}, error) { 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") 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) gql_thread, ok := states[0].(*GQLThreadState)
if ok == false { 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 { if err != nil {
return nil, err return nil, err
} }
listen_str, ok := listen.(string) return listen, nil
if ok == false {
return nil, fmt.Errorf("Failed to cast listen to string %+v", listen)
}
return listen_str, nil
} }
func GQLThreadParent(p graphql.ResolveParams) (interface{}, error) { 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") 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) gql_thread, ok := states[0].(ThreadState)
if ok == false { 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 { if err != nil {
return nil, err return nil, err
} }
parent_node, ok := parent.(Thread) return parent, nil
if ok == false {
return nil, fmt.Errorf("Failed to cast parent to node %+v", parent)
}
return parent_node, nil
} }
func GQLThreadChildren(p graphql.ResolveParams) (interface{}, error) { 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") 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) gql_thread, ok := states[0].(ThreadState)
if ok == false { 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 { if err != nil {
return nil, err return nil, err
} }
children_nodes, ok := children.([]Thread) return children, nil
if ok == false {
return nil, fmt.Errorf("Failed to cast children to threads %+v", children)
}
return children_nodes, nil
} }
func GQLLockableRequirements(p graphql.ResolveParams) (interface{}, error) { 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") 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) gql_thread, ok := states[0].(LockableState)
if ok == false { 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 { if err != nil {
return nil, err return nil, err
} }
requirement_nodes, ok := requirements.([]Lockable) return requirements, nil
if ok == false {
return nil, fmt.Errorf("Failed to cast requirements to lockables %+v", requirements)
}
return requirement_nodes, nil
} }
func GQLLockableDependencies(p graphql.ResolveParams) (interface{}, error) { 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") 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) gql_thread, ok := states[0].(LockableState)
if ok == false { 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 { if err != nil {
return nil, err return nil, err
} }
dependency_nodes, ok := dependencies.([]Lockable) return dependencies, nil
if ok == false {
return nil, fmt.Errorf("Failed to cast dependencies to lockables %+v", dependencies)
}
return dependency_nodes, nil
} }
func GQLLockableOwner(p graphql.ResolveParams) (interface{}, error) { 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") 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) gql_thread, ok := states[0].(LockableState)
if ok == false { 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 { if err != nil {
return nil, err return nil, err
} }
// TODO actually cast to LockHolder and add gql interface for it return owner, nil
owner_node, ok := owner.(Lockable)
if ok == false {
return nil, fmt.Errorf("Failed to cast owner to Lockable %+v", owner)
}
return owner_node, nil
} }
@ -847,23 +825,19 @@ func GQLMutationSendUpdate() *graphql.Field {
return nil, fmt.Errorf("Failed to cast arg id to string") 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) server_state := states[0].(*GQLThreadState)
node := FindChild(ctx, server, server_state, NodeID(id)) node = FindChild(ctx, server, server_state, NodeID(id))
if node == nil { 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 { if err != nil {
return nil, err 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) SendUpdate(ctx, node, signal)
return signal, nil return signal, nil
}, },

@ -6,8 +6,7 @@ import (
) )
func TestGQLThread(t * testing.T) { func TestGQLThread(t * testing.T) {
println("TEST_GQL") ctx := testContext(t)
ctx := logTestContext(t, []string{"gqlws", "gql", "thread", "update"})
gql_thread, err := NewGQLThread(ctx, ":8080", []Lockable{}, ObjTypeMap{}, FieldMap{}, FieldMap{}, FieldMap{}) gql_thread, err := NewGQLThread(ctx, ":8080", []Lockable{}, ObjTypeMap{}, FieldMap{}, FieldMap{}, FieldMap{})
fatalErr(t, err) fatalErr(t, err)

@ -331,10 +331,10 @@ func checkForDuplicate(nodes []GraphNode) error {
return nil 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) err := checkForDuplicate(nodes)
if err != nil { if err != nil {
return nil, err return err
} }
for _, node := range(nodes) { for _, node := range(nodes) {
@ -346,19 +346,19 @@ func UseStates(ctx * GraphContext, nodes []GraphNode, states_fn func(states []No
states[i] = node.State() states[i] = node.State()
} }
val, err := states_fn(states) err = states_fn(states)
for _, node := range(nodes) { for _, node := range(nodes) {
node.StateLock().RUnlock() 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) err := checkForDuplicate(nodes)
if err != nil { if err != nil {
return nil, err return err
} }
for _, node := range(nodes) { for _, node := range(nodes) {
@ -370,7 +370,7 @@ func UpdateStates(ctx * GraphContext, nodes []GraphNode, states_fn func(states [
states[i] = node.State() states[i] = node.State()
} }
new_states, val, err := states_fn(states) new_states, err := states_fn(states)
if new_states != nil { if new_states != nil {
if len(new_states) != len(nodes) { if len(new_states) != len(nodes) {
@ -400,7 +400,7 @@ func UpdateStates(ctx * GraphContext, nodes []GraphNode, states_fn func(states [
node.StateLock().Unlock() node.StateLock().Unlock()
} }
return val, err return err
} }
func (node * BaseNode) UpdateListeners(ctx * GraphContext, update GraphSignal) { func (node * BaseNode) UpdateListeners(ctx * GraphContext, update GraphSignal) {

@ -5,63 +5,36 @@ import (
"encoding/json" "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 // 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 // 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 // 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 LockableState interface {
type LockHolderState interface {
NodeState NodeState
ReturnLock(lockable_id NodeID) GraphNode
ReturnLock(lockable_id NodeID) Lockable
AllowedToTakeLock(node_id NodeID, lockable_id NodeID) bool 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 Requirements() []Lockable
AddRequirement(requirement Lockable) AddRequirement(requirement Lockable)
Dependencies() []Lockable Dependencies() []Lockable
AddDependency(dependency Lockable) AddDependency(dependency Lockable)
Owner() GraphNode Owner() Lockable
SetOwner(owner GraphNode) SetOwner(owner Lockable)
}
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,
})
} }
// BaseLockableStates are a minimum collection of variables for a basic implementation of a LockHolder // BaseLockableStates are a minimum collection of variables for a basic implementation of a LockHolder
// Include in any state structs that should be lockable // Include in any state structs that should be lockable
type BaseLockableState struct { type BaseLockableState struct {
BaseLockHolderState
name string name string
owner GraphNode owner Lockable
requirements []Lockable requirements []Lockable
dependencies []Lockable dependencies []Lockable
locks_held map[NodeID]Lockable
} }
type BaseLockableStateJSON struct { type BaseLockableStateJSON struct {
@ -69,7 +42,7 @@ type BaseLockableStateJSON struct {
Owner *NodeID `json:"owner"` Owner *NodeID `json:"owner"`
Dependencies []NodeID `json:"dependencies"` Dependencies []NodeID `json:"dependencies"`
Requirements []NodeID `json:"requirements"` Requirements []NodeID `json:"requirements"`
HolderState *BaseLockHolderState `json:"holder"` LocksHeld map[NodeID]*NodeID `json:"locks_held"`
} }
func (state * BaseLockableState) MarshalJSON() ([]byte, error) { func (state * BaseLockableState) MarshalJSON() ([]byte, error) {
@ -89,12 +62,22 @@ func (state * BaseLockableState) MarshalJSON() ([]byte, error) {
owner_id = &new_str 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{ return json.Marshal(&BaseLockableStateJSON{
Name: state.name, Name: state.name,
Owner: owner_id, Owner: owner_id,
Dependencies: dependency_ids, Dependencies: dependency_ids,
Requirements: requirement_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 // 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" // "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] node, exists := state.locks_held[lockable_id]
if exists == false { if exists == false {
panic("Attempted to take a get the original lock holder of a lockable we don't own") 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 // 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] _, exists := state.locks_held[lockable_id]
if exists == false { if exists == false {
panic ("Trying to give away lock we don't own") 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 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] _, exists := state.locks_held[lockable_id]
if exists == true { if exists == true {
panic("Attempted to lock a lockable we're already holding(lock cycle)") 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 state.locks_held[lockable_id] = lock_holder
} }
func (state * BaseLockableState) Owner() GraphNode { func (state * BaseLockableState) Owner() Lockable {
return state.owner return state.owner
} }
func (state * BaseLockableState) SetOwner(owner GraphNode) { func (state * BaseLockableState) SetOwner(owner Lockable) {
state.owner = owner state.owner = owner
} }
@ -162,12 +145,6 @@ func (state * BaseLockableState) AddDependency(dependency Lockable) {
state.dependencies = append(state.dependencies, dependency) 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 { func LinkLockables(ctx * GraphContext, lockable Lockable, requirements []Lockable) error {
if lockable == nil { if lockable == nil {
return fmt.Errorf("LOCKABLE_LINK_ERR: Will not link Lockables to nil as requirements") 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) { for i, node := range(requirements) {
nodes[i+1] = node 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 // Check that all the requirements can be added
lockable_state := states[0].(LockableState) 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) { for i, requirement := range(requirements) {
requirement_state := states[i+1].(LockableState) requirement_state := states[i+1].(LockableState)
if checkIfRequirement(ctx, lockable.ID(), requirement_state, requirement.ID()) == true { 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 { 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 // Update the states of the requirements
@ -211,7 +207,7 @@ func LinkLockables(ctx * GraphContext, lockable Lockable, requirements []Lockabl
} }
// Return no error // Return no error
return states, nil, nil return states, nil
}) })
return err return err
@ -219,7 +215,7 @@ func LinkLockables(ctx * GraphContext, lockable Lockable, requirements []Lockabl
func NewBaseLockableState(name string) BaseLockableState { func NewBaseLockableState(name string) BaseLockableState {
state := BaseLockableState{ state := BaseLockableState{
BaseLockHolderState: NewLockHolderState(), locks_held: map[NodeID]Lockable{},
name: name, name: name,
owner: nil, owner: nil,
requirements: []Lockable{}, requirements: []Lockable{},
@ -232,13 +228,17 @@ func NewBaseLockableState(name string) BaseLockableState {
type Lockable interface { type Lockable interface {
GraphNode GraphNode
// Called when locking the node to allow for custom lock behaviour // 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 // 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) { 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) lockable_state := states[0].(LockableState)
if signal.Direction() == Up { if signal.Direction() == Up {
// Child->Parent, lockable updates dependency lockables // Child->Parent, lockable updates dependency lockables
@ -264,7 +264,7 @@ func (lockable * BaseLockable) PropagateUpdate(ctx * GraphContext, signal GraphS
} else { } else {
panic(fmt.Sprintf("Invalid signal direction: %d", signal.Direction())) 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 { if c.ID() == r_id {
return true 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) 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 { if is_requirement {
return true return true
} }
@ -287,132 +288,218 @@ func checkIfRequirement(ctx * GraphContext, r_id NodeID, cur LockableState, cur_
return false return false
} }
func UnlockLockable(ctx * GraphContext, lockable Lockable, node GraphNode, node_state LockHolderState) error { func LockLockables(ctx * GraphContext, to_lock []Lockable, holder Lockable, holder_state LockableState, owner_states map[NodeID]LockableState) error {
if node == nil || lockable == nil{ if to_lock == nil {
panic("Cannot unlock without a specified node and lockable") return fmt.Errorf("LOCKABLE_LOCK_ERR: no list provided")
} }
ctx.Log.Logf("lockable", "Unlocking %s", lockable.ID()) for _, l := range(to_lock) {
_, err := UpdateStates(ctx, []GraphNode{lockable}, func(states []NodeState) ([]NodeState, interface{}, error) { if l == nil {
if lockable.ID() == node.ID() { return fmt.Errorf("LOCKABLE_LOCK_ERR: Can not lock nil")
if node_state != nil {
panic("node_state must be nil if unlocking lockable from itself")
}
node_state = states[0].(LockHolderState)
} }
lockable_state := states[0].(LockableState) }
if holder == nil {
return fmt.Errorf("LOCKABLE_LOCK_ERR: nil cannot hold locks")
}
if lockable_state.Owner() == nil { // Called with no requirements to lock, success
return nil, nil, fmt.Errorf("Lockable already unlocked") if len(to_lock) == 0 {
} return nil
}
if lockable_state.Owner().ID() != node.ID() { if holder_state == nil {
return nil, nil, fmt.Errorf("Lockable %s not locked by %s", lockable.ID(), node.ID()) 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")
} }
}
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())
}
var lock_err error = nil // Check custom lock conditions
for _, requirement := range(lockable_state.Requirements()) { err := req.CanLock(holder, req_state)
var err error = nil
err = UnlockLockable(ctx, requirement, lockable, lockable_state)
if err != nil { if err != nil {
lock_err = err return nil, err
break
} }
}
if lock_err != nil { // If req is alreay locked, check that we can pass the lock
return nil, nil, fmt.Errorf("Lockable %s failed to unlock: %e", lockable.ID(), lock_err) 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()) // At this point state modification will be started, so no errors can be returned
lockable_state.SetOwner(new_owner) for i, state := range(states) {
err := lockable.Unlock(node, lockable_state) req := to_lock[i]
if err != nil { req_state := state.(LockableState)
return nil, nil, fmt.Errorf("Lockable %s failed custom Unlock: %e", lockable.ID(), err) 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 { func UnlockLockables(ctx * GraphContext, to_unlock []Lockable, holder Lockable, holder_state LockableState, owner_states map[NodeID]LockableState) error {
ctx.Log.Logf("lockable", "LOCKABLE_UNLOCK: %s unlocked %s", node.ID(), lockable.ID()) if to_unlock == nil {
} else { return fmt.Errorf("LOCKABLE_UNLOCK_ERR: no list provided")
ctx.Log.Logf("lockable", "LOCKABLE_UNLOCK: %s passed lock of %s back to %s", node.ID(), lockable.ID(), lockable_state.Owner().ID()) }
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 { node_list := make([]GraphNode, len(to_unlock))
if node == nil || lockable == nil { for i, l := range(to_unlock) {
return fmt.Errorf("Cannot lock without a specified node and lockable") 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) { err := UpdateStates(ctx, node_list, func(states []NodeState) ([]NodeState, error) {
if lockable.ID() == node.ID() { // First loop is to check that the states can be locked, and locks all requirements
if node_state != nil { for i, state := range(states) {
return nil, nil, fmt.Errorf("node_state must be nil if locking lockable from itself") 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() { // Check if the owner is correct
lock_pass_allowed = lockable_state.AllowedToTakeLock(node.ID(), lockable.ID()) 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 { } else {
tmp, _ := UseStates(ctx, []GraphNode{lockable_state.Owner()}, func(states []NodeState)(interface{}, error){ return nil, fmt.Errorf("LOCKABLE_UNLOCK_ERR: %s is not locked", req.ID())
return states[0].(LockHolderState).AllowedToTakeLock(node.ID(), lockable.ID()), nil
})
lock_pass_allowed = tmp.(bool)
} }
// Check custom unlock conditions
if lock_pass_allowed == false { err := req.CanUnlock(holder, req_state)
return nil, nil, fmt.Errorf("%s is not allowed to take a lock from %s", node.ID(), lockable_state.Owner().ID()) 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 err = UnlockLockables(ctx, req_state.Requirements(), req, req_state, owner_states)
locked_requirements := []Lockable{}
for _, requirement := range(lockable_state.Requirements()) {
err = LockLockable(ctx, requirement, lockable, lockable_state)
if err != nil { if err != nil {
lock_err = err return nil, err
break
} }
locked_requirements = append(locked_requirements, requirement)
} }
if lock_err != nil { // At this point state modification will be started, so no errors can be returned
for _, locked_lockable := range(locked_requirements) { for i, state := range(states) {
err = UnlockLockable(ctx, locked_lockable, lockable, lockable_state) req := to_unlock[i]
if err != nil { req_state := state.(LockableState)
panic(err) 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 states, nil
return []NodeState{lockable_state}, nil, nil
}) })
return err return err
} }
@ -422,14 +509,23 @@ type BaseLockable struct {
} }
//BaseLockables don't check anything special when locking/unlocking //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 return nil
} }
func (lockable * BaseLockable) Unlock(node GraphNode, state LockableState) error { func (lockable * BaseLockable) CanUnlock(node GraphNode, state LockableState) error {
return nil 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) { func NewBaseLockable(ctx * GraphContext, state LockableState) (BaseLockable, error) {
base_node, err := NewNode(ctx, state) base_node, err := NewNode(ctx, state)
if err != nil { if err != nil {

@ -35,34 +35,34 @@ func TestLockableSelfLock(t * testing.T) {
r1, err := NewSimpleBaseLockable(ctx, "Test lockable 1", []Lockable{}) r1, err := NewSimpleBaseLockable(ctx, "Test lockable 1", []Lockable{})
fatalErr(t, err) fatalErr(t, err)
err = LockLockable(ctx, r1, r1, nil) err = LockLockables(ctx, []Lockable{r1}, r1, nil, map[NodeID]LockableState{})
fatalErr(t, err) 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() owner_id := states[0].(LockableState).Owner().ID()
if owner_id != r1.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) fatalErr(t, err)
err = UnlockLockable(ctx, r1, r1, nil) err = UnlockLockables(ctx, []Lockable{r1}, r1, nil, map[NodeID]LockableState{})
fatalErr(t, err) 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() owner := states[0].(LockableState).Owner()
if owner != nil { 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) fatalErr(t, err)
} }
func TestLockableSelfLockTiered(t * testing.T) { func TestLockableSelfLockTiered(t * testing.T) {
ctx := testContext(t) ctx := logTestContext(t, []string{"lockable"})
r1, err := NewSimpleBaseLockable(ctx, "Test lockable 1", []Lockable{}) r1, err := NewSimpleBaseLockable(ctx, "Test lockable 1", []Lockable{})
fatalErr(t, err) fatalErr(t, err)
@ -73,45 +73,45 @@ func TestLockableSelfLockTiered(t * testing.T) {
r3, err := NewSimpleBaseLockable(ctx, "Test lockable 3", []Lockable{r1, r2}) r3, err := NewSimpleBaseLockable(ctx, "Test lockable 3", []Lockable{r1, r2})
fatalErr(t, err) fatalErr(t, err)
err = LockLockable(ctx, r3, r3, nil) err = LockLockables(ctx, []Lockable{r3}, r3, nil, map[NodeID]LockableState{})
fatalErr(t, err) 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() owner_1_id := states[0].(LockableState).Owner().ID()
if owner_1_id != r3.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() owner_2_id := states[1].(LockableState).Owner().ID()
if owner_2_id != r3.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, "", " ") ser, _ := json.MarshalIndent(states, "", " ")
fmt.Printf("\n\n%s\n\n", ser) fmt.Printf("\n\n%s\n\n", ser)
return nil, nil return nil
}) })
fatalErr(t, err) fatalErr(t, err)
err = UnlockLockable(ctx, r3, r3, nil) err = UnlockLockables(ctx, []Lockable{r3}, r3, nil, map[NodeID]LockableState{})
fatalErr(t, err) 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() owner_1 := states[0].(LockableState).Owner()
if owner_1 != nil { 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() owner_2 := states[1].(LockableState).Owner()
if owner_2 != nil { 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() owner_3 := states[2].(LockableState).Owner()
if owner_3 != nil { 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) fatalErr(t, err)
@ -126,39 +126,39 @@ func TestLockableLockOther(t * testing.T) {
r2, err := NewSimpleBaseLockable(ctx, "Test lockable 2", []Lockable{}) r2, err := NewSimpleBaseLockable(ctx, "Test lockable 2", []Lockable{})
fatalErr(t, err) fatalErr(t, err)
_, err = UpdateStates(ctx, []GraphNode{r2}, func(states []NodeState) ([]NodeState, interface{}, error) { err = UpdateStates(ctx, []GraphNode{r2}, func(states []NodeState) ([]NodeState, error) {
node_state := states[0].(LockHolderState) node_state := states[0].(LockableState)
err := LockLockable(ctx, r1, r2, node_state) err := LockLockables(ctx, []Lockable{r1}, r2, node_state, map[NodeID]LockableState{})
fatalErr(t, err) fatalErr(t, err)
return []NodeState{node_state}, nil, nil return []NodeState{node_state}, nil
}) })
fatalErr(t, err) 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() owner_id := states[0].(LockableState).Owner().ID()
if owner_id != r2.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) fatalErr(t, err)
_, err = UpdateStates(ctx, []GraphNode{r2}, func(states []NodeState) ([]NodeState, interface{}, error) { err = UpdateStates(ctx, []GraphNode{r2}, func(states []NodeState) ([]NodeState, error) {
node_state := states[0].(LockHolderState) node_state := states[0].(LockableState)
err := UnlockLockable(ctx, r1, r2, node_state) err := UnlockLockables(ctx, []Lockable{r1}, r2, node_state, map[NodeID]LockableState{})
fatalErr(t, err) fatalErr(t, err)
return []NodeState{node_state}, nil, nil return []NodeState{node_state}, nil
}) })
fatalErr(t, err) 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() owner := states[0].(LockableState).Owner()
if owner != nil { 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) fatalErr(t, err)
@ -173,40 +173,40 @@ func TestLockableLockSimpleConflict(t * testing.T) {
r2, err := NewSimpleBaseLockable(ctx, "Test lockable 2", []Lockable{}) r2, err := NewSimpleBaseLockable(ctx, "Test lockable 2", []Lockable{})
fatalErr(t, err) fatalErr(t, err)
err = LockLockable(ctx, r1, r1, nil) err = LockLockables(ctx, []Lockable{r1}, r1, nil, map[NodeID]LockableState{})
fatalErr(t, err) fatalErr(t, err)
_, err = UpdateStates(ctx, []GraphNode{r2}, func(states []NodeState) ([]NodeState, interface{}, error) { err = UpdateStates(ctx, []GraphNode{r2}, func(states []NodeState) ([]NodeState, error) {
node_state := states[0].(LockHolderState) node_state := states[0].(LockableState)
err := LockLockable(ctx, r1, r2, node_state) err := LockLockables(ctx, []Lockable{r1}, r2, node_state, map[NodeID]LockableState{})
if err == nil { if err == nil {
t.Fatal("r2 took r1's lock from itself") t.Fatal("r2 took r1's lock from itself")
} }
return []NodeState{node_state}, nil, nil return []NodeState{node_state}, nil
}) })
fatalErr(t, err) 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() owner_id := states[0].(LockableState).Owner().ID()
if owner_id != r1.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) fatalErr(t, err)
err = UnlockLockable(ctx, r1, r1, nil) err = UnlockLockables(ctx, []Lockable{r1}, r1, nil, map[NodeID]LockableState{})
fatalErr(t, err) 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() owner := states[0].(LockableState).Owner()
if owner != nil { 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) fatalErr(t, err)
@ -224,10 +224,10 @@ func TestLockableLockTieredConflict(t * testing.T) {
r3, err := NewSimpleBaseLockable(ctx, "Test lockable 3", []Lockable{r1}) r3, err := NewSimpleBaseLockable(ctx, "Test lockable 3", []Lockable{r1})
fatalErr(t, err) fatalErr(t, err)
err = LockLockable(ctx, r2, r2, nil) err = LockLockables(ctx, []Lockable{r2}, r2, nil, map[NodeID]LockableState{})
fatalErr(t, err) fatalErr(t, err)
err = LockLockable(ctx, r3, r3, nil) err = LockLockables(ctx, []Lockable{r3}, r3, nil, map[NodeID]LockableState{})
if err == nil { if err == nil {
t.Fatal("Locked r3 which depends on r1 while r2 which depends on r1 is already locked") t.Fatal("Locked r3 which depends on r1 while r2 which depends on r1 is already locked")
} }

@ -11,7 +11,7 @@ import (
// Update the threads listeners, and notify the parent to do the same // Update the threads listeners, and notify the parent to do the same
func (thread * BaseThread) PropagateUpdate(ctx * GraphContext, signal GraphSignal) { 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) thread_state := states[0].(ThreadState)
if signal.Direction() == Up { if signal.Direction() == Up {
// Child->Parent, thread updates parent and connected requirement // 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())) panic(fmt.Sprintf("Invalid signal direction: %d", signal.Direction()))
} }
return nil, nil return nil
}) })
thread.signal <- signal 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 // 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 // This relationship allows the thread tree to be modified independent of the lockable state
type ThreadState interface { type ThreadState interface {
LockHolderState
LockableState LockableState
Parent() Thread Parent() Thread
@ -167,12 +166,12 @@ func checkIfChild(ctx * GraphContext, thread_id NodeID, cur_state ThreadState, c
if child.ID() == thread_id { if child.ID() == thread_id {
return true 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) 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 { if is_child {
return true 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) thread_state := states[0].(ThreadState)
child_state := states[1].(ThreadState) child_state := states[1].(ThreadState)
if child_state.Parent() != nil { 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 { 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 { 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) err := thread_state.AddChild(child, info)
if err != nil { 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) child_state.SetParent(thread)
return states, nil, nil return states, nil
}) })
if err != 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() { 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) child_state := states[0].(ThreadState)
result := FindChild(ctx, child, child_state, id) result = FindChild(ctx, child, child_state, id)
return result, nil return nil
}) })
result := res.(Thread)
if result != nil { if result != nil {
return result return result
} }
@ -285,22 +284,25 @@ func ChildGo(ctx * GraphContext, thread_state ThreadState, thread Thread, child_
func RunThread(ctx * GraphContext, thread Thread) error { func RunThread(ctx * GraphContext, thread Thread) error {
ctx.Log.Logf("thread", "THREAD_RUN: %s", thread.ID()) 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 { if err != nil {
return err 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) thread_state := states[0].(ThreadState)
if thread_state.Owner() == nil { 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() { } 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 { } 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")) 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) thread_state := states[0].(ThreadState)
err := thread_state.Stop() err := thread_state.Stop()
return nil, err return err
}) })
if err != nil { if err != nil {
@ -330,7 +332,7 @@ func RunThread(ctx * GraphContext, thread Thread) error {
return err return err
} }
err = UnlockLockable(ctx, thread, thread, nil) err = UnlockLockables(ctx, []Lockable{thread}, thread, nil, map[NodeID]LockableState{})
if err != nil { if err != nil {
ctx.Log.Logf("thread", "THREAD_RUN_UNLOCK_ERR: %e", err) ctx.Log.Logf("thread", "THREAD_RUN_UNLOCK_ERR: %e", err)
return err return err
@ -368,14 +370,22 @@ func (thread * BaseThread) ChildWaits() *sync.WaitGroup {
return &thread.child_waits return &thread.child_waits
} }
func (thread * BaseThread) Lock(node GraphNode, state LockableState) error { func (thread * BaseThread) CanLock(node GraphNode, state LockableState) error {
return nil return nil
} }
func (thread * BaseThread) Unlock(node GraphNode, state LockableState) error { func (thread * BaseThread) CanUnlock(node GraphNode, state LockableState) error {
return nil 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) { func (thread * BaseThread) Action(action string) (ThreadAction, bool) {
action_fn, exists := thread.Actions[action] action_fn, exists := thread.Actions[action]
return action_fn, exists return action_fn, exists

@ -21,13 +21,13 @@ func TestNewEvent(t * testing.T) {
err = RunThread(ctx, t1) err = RunThread(ctx, t1)
fatalErr(t, err) 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, "", " ") ser, err := json.MarshalIndent(states, "", " ")
fatalErr(t, err) fatalErr(t, err)
fmt.Printf("\n%s\n", ser) fmt.Printf("\n%s\n", ser)
return nil, nil return nil
}) })
} }
@ -42,12 +42,12 @@ func TestEventWithRequirement(t * testing.T) {
go func (thread Thread) { go func (thread Thread) {
time.Sleep(10*time.Millisecond) 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], "", " ") ser, err := json.MarshalIndent(states[0], "", " ")
fatalErr(t, err) fatalErr(t, err)
fmt.Printf("\n%s\n", ser) fmt.Printf("\n%s\n", ser)
return nil, nil return nil
}) })
fatalErr(t, err) fatalErr(t, err)
SendUpdate(ctx, t1, CancelSignal(nil)) SendUpdate(ctx, t1, CancelSignal(nil))
@ -57,12 +57,12 @@ func TestEventWithRequirement(t * testing.T) {
err = RunThread(ctx, t1) err = RunThread(ctx, t1)
fatalErr(t, err) 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], "", " ") ser, err := json.MarshalIndent(states[0], "", " ")
fatalErr(t, err) fatalErr(t, err)
fmt.Printf("\n%s\n", ser) fmt.Printf("\n%s\n", ser)
return nil, nil return nil
}) })
fatalErr(t, err) fatalErr(t, err)
} }