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"))
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
},

@ -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
},

@ -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)

@ -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) {

@ -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 {

@ -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")
}

@ -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

@ -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)
}