|  |  |  | @ -71,18 +71,25 @@ func NewLockableExt() *LockableExt { | 
		
	
		
			
				|  |  |  |  |   return &LockableExt{ | 
		
	
		
			
				|  |  |  |  |     Owner: nil, | 
		
	
		
			
				|  |  |  |  |     PendingOwner: nil, | 
		
	
		
			
				|  |  |  |  |     Requirements: map[NodeID]string{}, | 
		
	
		
			
				|  |  |  |  |     Requirements: map[NodeID]ReqState{}, | 
		
	
		
			
				|  |  |  |  |     Dependencies: map[NodeID]string{}, | 
		
	
		
			
				|  |  |  |  |     LockStates: map[NodeID]string{}, | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | type ReqState struct { | 
		
	
		
			
				|  |  |  |  |   Link string `json:"link"` | 
		
	
		
			
				|  |  |  |  |   Lock string `json:"lock"` | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | type LockableExt struct { | 
		
	
		
			
				|  |  |  |  |   Owner *NodeID `json:"owner"` | 
		
	
		
			
				|  |  |  |  |   PendingOwner *NodeID `json:"pending_owner"` | 
		
	
		
			
				|  |  |  |  |   Requirements map[NodeID]string `json:"requirements"` | 
		
	
		
			
				|  |  |  |  |   Requirements map[NodeID]ReqState `json:"requirements"` | 
		
	
		
			
				|  |  |  |  |   Dependencies map[NodeID]string `json:"dependencies"` | 
		
	
		
			
				|  |  |  |  |   LockStates map[NodeID]string `json:"lock_states"` | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | func UnlockLockable(ctx *Context, node *Node) error { | 
		
	
		
			
				|  |  |  |  |   return ctx.Send(node.ID, node.ID, NewLockSignal("unlock")) | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | func LockLockable(ctx *Context, node *Node) error { | 
		
	
	
		
			
				
					|  |  |  | @ -105,7 +112,7 @@ func LinkRequirement(ctx *Context, dependency *Node, requirement NodeID) error { | 
		
	
		
			
				|  |  |  |  |     return fmt.Errorf("%s is a dependency of %s, cannot link as requirement", requirement, dependency.ID) | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   dep_ext.Requirements[requirement] = "start" | 
		
	
		
			
				|  |  |  |  |   dep_ext.Requirements[requirement] = ReqState{"linking", "unlocked"} | 
		
	
		
			
				|  |  |  |  |   return ctx.Send(dependency.ID, requirement, NewLinkSignal("req_link")) | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
	
		
			
				
					|  |  |  | @ -113,55 +120,149 @@ func (ext *LockableExt) HandleLockSignal(ctx *Context, source NodeID, node *Node | 
		
	
		
			
				|  |  |  |  |   ctx.Log.Logf("lockable", "LOCK_SIGNAL: %s->%s %+v", source, node.ID, signal) | 
		
	
		
			
				|  |  |  |  |   state := signal.State | 
		
	
		
			
				|  |  |  |  |   switch state { | 
		
	
		
			
				|  |  |  |  |   case "locked": | 
		
	
		
			
				|  |  |  |  |   case "unlock": | 
		
	
		
			
				|  |  |  |  |     if ext.Owner == nil { | 
		
	
		
			
				|  |  |  |  |       ctx.Send(node.ID, source, NewLockSignal("already_unlocked")) | 
		
	
		
			
				|  |  |  |  |     } else if source != *ext.Owner { | 
		
	
		
			
				|  |  |  |  |       ctx.Send(node.ID, source, NewLockSignal("not_owner")) | 
		
	
		
			
				|  |  |  |  |     } else if ext.PendingOwner == nil { | 
		
	
		
			
				|  |  |  |  |       ctx.Send(node.ID, source, NewLockSignal("already_unlocking")) | 
		
	
		
			
				|  |  |  |  |     } else { | 
		
	
		
			
				|  |  |  |  |       if len(ext.Requirements) == 0 { | 
		
	
		
			
				|  |  |  |  |         ext.Owner = nil | 
		
	
		
			
				|  |  |  |  |         ext.PendingOwner = nil | 
		
	
		
			
				|  |  |  |  |         ctx.Send(node.ID, source, NewLockSignal("unlocked")) | 
		
	
		
			
				|  |  |  |  |       } else { | 
		
	
		
			
				|  |  |  |  |         ext.PendingOwner = nil | 
		
	
		
			
				|  |  |  |  |         for id, state := range(ext.Requirements) { | 
		
	
		
			
				|  |  |  |  |           if state.Link == "linked" { | 
		
	
		
			
				|  |  |  |  |             if state.Lock != "locked" { | 
		
	
		
			
				|  |  |  |  |               panic("NOT_LOCKED") | 
		
	
		
			
				|  |  |  |  |             } | 
		
	
		
			
				|  |  |  |  |             state.Lock = "unlocking" | 
		
	
		
			
				|  |  |  |  |             ext.Requirements[id] = state | 
		
	
		
			
				|  |  |  |  |             ctx.Send(node.ID, id, NewLockSignal("unlock")) | 
		
	
		
			
				|  |  |  |  |           } | 
		
	
		
			
				|  |  |  |  |         } | 
		
	
		
			
				|  |  |  |  |         if source != node.ID { | 
		
	
		
			
				|  |  |  |  |           ctx.Send(node.ID, source, NewLockSignal("unlocking")) | 
		
	
		
			
				|  |  |  |  |         } | 
		
	
		
			
				|  |  |  |  |       } | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |   case "unlocking": | 
		
	
		
			
				|  |  |  |  |     state, exists := ext.Requirements[source] | 
		
	
		
			
				|  |  |  |  |     if exists == false { | 
		
	
		
			
				|  |  |  |  |       ctx.Send(node.ID, source, NewLockSignal("not_requirement")) | 
		
	
		
			
				|  |  |  |  |     } else if state.Link != "linked" { | 
		
	
		
			
				|  |  |  |  |       ctx.Send(node.ID, source, NewLockSignal("node_not_linked")) | 
		
	
		
			
				|  |  |  |  |     } else if state.Lock != "unlocking" { | 
		
	
		
			
				|  |  |  |  |       ctx.Send(node.ID, source, NewLockSignal("not_unlocking")) | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   case "unlocked": | 
		
	
		
			
				|  |  |  |  |     if source == node.ID { | 
		
	
		
			
				|  |  |  |  |       return | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |     _, exists := ext.LockStates[source] | 
		
	
		
			
				|  |  |  |  |     if exists == true { | 
		
	
		
			
				|  |  |  |  |       ext.LockStates[source] = "locked" | 
		
	
		
			
				|  |  |  |  |       locked_reqs := 0 | 
		
	
		
			
				|  |  |  |  |       for _, state := range(ext.LockStates) { | 
		
	
		
			
				|  |  |  |  |         if state == "locked" { | 
		
	
		
			
				|  |  |  |  |           locked_reqs += 1 | 
		
	
		
			
				|  |  |  |  |     state, exists := ext.Requirements[source] | 
		
	
		
			
				|  |  |  |  |     if exists == false { | 
		
	
		
			
				|  |  |  |  |       ctx.Send(node.ID, source, NewLockSignal("not_requirement")) | 
		
	
		
			
				|  |  |  |  |     } else if state.Link != "linked" { | 
		
	
		
			
				|  |  |  |  |       ctx.Send(node.ID, source, NewLockSignal("not_linked")) | 
		
	
		
			
				|  |  |  |  |     } else if state.Lock != "unlocking" { | 
		
	
		
			
				|  |  |  |  |       ctx.Send(node.ID, source, NewLockSignal("not_unlocking")) | 
		
	
		
			
				|  |  |  |  |     } else { | 
		
	
		
			
				|  |  |  |  |       state.Lock = "unlocked" | 
		
	
		
			
				|  |  |  |  |       ext.Requirements[source] = state | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |       if ext.PendingOwner == nil { | 
		
	
		
			
				|  |  |  |  |         linked := 0 | 
		
	
		
			
				|  |  |  |  |         unlocked := 0 | 
		
	
		
			
				|  |  |  |  |         for _, s := range(ext.Requirements) { | 
		
	
		
			
				|  |  |  |  |           if s.Link == "linked" { | 
		
	
		
			
				|  |  |  |  |             linked += 1 | 
		
	
		
			
				|  |  |  |  |           } | 
		
	
		
			
				|  |  |  |  |           if s.Lock == "unlocked" { | 
		
	
		
			
				|  |  |  |  |             unlocked += 1 | 
		
	
		
			
				|  |  |  |  |           } | 
		
	
		
			
				|  |  |  |  |         } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |         if linked == unlocked { | 
		
	
		
			
				|  |  |  |  |           previous_owner := *ext.Owner | 
		
	
		
			
				|  |  |  |  |           ext.Owner = nil | 
		
	
		
			
				|  |  |  |  |           ctx.Send(node.ID, previous_owner, NewLockSignal("unlocked")) | 
		
	
		
			
				|  |  |  |  |         } | 
		
	
		
			
				|  |  |  |  |       } | 
		
	
		
			
				|  |  |  |  |       if len(ext.Requirements) == locked_reqs { | 
		
	
		
			
				|  |  |  |  |         ext.Owner = ext.PendingOwner | 
		
	
		
			
				|  |  |  |  |         ext.PendingOwner = nil | 
		
	
		
			
				|  |  |  |  |         ctx.Send(node.ID, *ext.Owner, NewLockSignal("locked")) | 
		
	
		
			
				|  |  |  |  |       } | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |   case "locked": | 
		
	
		
			
				|  |  |  |  |     if source == node.ID { | 
		
	
		
			
				|  |  |  |  |       return | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |     state, exists := ext.Requirements[source] | 
		
	
		
			
				|  |  |  |  |     if exists == false { | 
		
	
		
			
				|  |  |  |  |       ctx.Send(node.ID, source, NewLockSignal("not_requirement")) | 
		
	
		
			
				|  |  |  |  |     } else if state.Link != "linked" { | 
		
	
		
			
				|  |  |  |  |       ctx.Send(node.ID, source, NewLockSignal("not_linked")) | 
		
	
		
			
				|  |  |  |  |     } else if state.Lock != "locking" { | 
		
	
		
			
				|  |  |  |  |       ctx.Send(node.ID, source, NewLockSignal("not_locking")) | 
		
	
		
			
				|  |  |  |  |     } else { | 
		
	
		
			
				|  |  |  |  |       ctx.Send(node.ID, source, NewLockSignal("reset")) | 
		
	
		
			
				|  |  |  |  |       state.Lock = "locked" | 
		
	
		
			
				|  |  |  |  |       ext.Requirements[source] = state | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |       if ext.PendingOwner != nil { | 
		
	
		
			
				|  |  |  |  |         linked := 0 | 
		
	
		
			
				|  |  |  |  |         locked := 0 | 
		
	
		
			
				|  |  |  |  |         for _, s := range(ext.Requirements) { | 
		
	
		
			
				|  |  |  |  |           if s.Link == "linked" { | 
		
	
		
			
				|  |  |  |  |             linked += 1 | 
		
	
		
			
				|  |  |  |  |           } | 
		
	
		
			
				|  |  |  |  |           if s.Lock == "locked" { | 
		
	
		
			
				|  |  |  |  |             locked += 1 | 
		
	
		
			
				|  |  |  |  |           } | 
		
	
		
			
				|  |  |  |  |         } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |         if linked == locked { | 
		
	
		
			
				|  |  |  |  |           ext.Owner = ext.PendingOwner | 
		
	
		
			
				|  |  |  |  |           ctx.Send(node.ID, *ext.Owner, NewLockSignal("locked")) | 
		
	
		
			
				|  |  |  |  |         } | 
		
	
		
			
				|  |  |  |  |       } | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |   case "pending": | 
		
	
		
			
				|  |  |  |  |     state, exists := ext.LockStates[source] | 
		
	
		
			
				|  |  |  |  |     if exists == true && state != "pending" { | 
		
	
		
			
				|  |  |  |  |       delete(ext.LockStates, source) | 
		
	
		
			
				|  |  |  |  |       ctx.Send(node.ID, source, NewLockSignal("reset")) | 
		
	
		
			
				|  |  |  |  |     } else if exists == false { | 
		
	
		
			
				|  |  |  |  |       ctx.Send(node.ID, source, NewLockSignal("reset")) | 
		
	
		
			
				|  |  |  |  |   case "locking": | 
		
	
		
			
				|  |  |  |  |     state, exists := ext.Requirements[source] | 
		
	
		
			
				|  |  |  |  |     if exists == false { | 
		
	
		
			
				|  |  |  |  |       ctx.Send(node.ID, source, NewLockSignal("not_requirement")) | 
		
	
		
			
				|  |  |  |  |     } else if state.Link != "linked" { | 
		
	
		
			
				|  |  |  |  |       ctx.Send(node.ID, source, NewLockSignal("node_not_linked")) | 
		
	
		
			
				|  |  |  |  |     } else if state.Lock != "locking" { | 
		
	
		
			
				|  |  |  |  |       ctx.Send(node.ID, source, NewLockSignal("not_locking")) | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   case "lock": | 
		
	
		
			
				|  |  |  |  |     if ext.Owner != nil { | 
		
	
		
			
				|  |  |  |  |       ctx.Send(node.ID, source, NewLockSignal("already_locked")) | 
		
	
		
			
				|  |  |  |  |     } else if ext.PendingOwner == nil { | 
		
	
		
			
				|  |  |  |  |     } else if ext.PendingOwner != nil { | 
		
	
		
			
				|  |  |  |  |       ctx.Send(node.ID, source, NewLockSignal("already_locking")) | 
		
	
		
			
				|  |  |  |  |     } else { | 
		
	
		
			
				|  |  |  |  |       owner := source | 
		
	
		
			
				|  |  |  |  |       if len(ext.Requirements) == 0 { | 
		
	
		
			
				|  |  |  |  |         owner := source | 
		
	
		
			
				|  |  |  |  |         ext.Owner = &owner | 
		
	
		
			
				|  |  |  |  |         ext.PendingOwner = ext.Owner | 
		
	
		
			
				|  |  |  |  |         ctx.Send(node.ID, source, NewLockSignal("locked")) | 
		
	
		
			
				|  |  |  |  |       } else { | 
		
	
		
			
				|  |  |  |  |         pending_owner := source | 
		
	
		
			
				|  |  |  |  |         ext.PendingOwner = &pending_owner | 
		
	
		
			
				|  |  |  |  |         ext.PendingOwner = &owner | 
		
	
		
			
				|  |  |  |  |         for id, state := range(ext.Requirements) { | 
		
	
		
			
				|  |  |  |  |           if state == "linked" { | 
		
	
		
			
				|  |  |  |  |             ext.LockStates[id] = "pending" | 
		
	
		
			
				|  |  |  |  |           if state.Link == "linked" { | 
		
	
		
			
				|  |  |  |  |             if state.Lock != "unlocked" { | 
		
	
		
			
				|  |  |  |  |               panic("NOT_UNLOCKED") | 
		
	
		
			
				|  |  |  |  |             } | 
		
	
		
			
				|  |  |  |  |             state.Lock = "locking" | 
		
	
		
			
				|  |  |  |  |             ext.Requirements[id] = state | 
		
	
		
			
				|  |  |  |  |             ctx.Send(node.ID, id, NewLockSignal("lock")) | 
		
	
		
			
				|  |  |  |  |           } | 
		
	
		
			
				|  |  |  |  |         } | 
		
	
		
			
				|  |  |  |  |         if source != node.ID { | 
		
	
		
			
				|  |  |  |  |           ctx.Send(node.ID, source, NewLockSignal("pending")) | 
		
	
		
			
				|  |  |  |  |           ctx.Send(node.ID, source, NewLockSignal("locking")) | 
		
	
		
			
				|  |  |  |  |         } | 
		
	
		
			
				|  |  |  |  |       } | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
	
		
			
				
					|  |  |  | @ -181,9 +282,9 @@ func (ext *LockableExt) HandleLinkSignal(ctx *Context, source NodeID, node *Node | 
		
	
		
			
				|  |  |  |  |     if exists == false { | 
		
	
		
			
				|  |  |  |  |       dep_state, exists := ext.Dependencies[source] | 
		
	
		
			
				|  |  |  |  |       if exists == false { | 
		
	
		
			
				|  |  |  |  |         ext.Dependencies[source] = "start" | 
		
	
		
			
				|  |  |  |  |         ext.Dependencies[source] = "linking" | 
		
	
		
			
				|  |  |  |  |         ctx.Send(node.ID, source, NewLinkSignal("dep_link")) | 
		
	
		
			
				|  |  |  |  |       } else if dep_state == "start" { | 
		
	
		
			
				|  |  |  |  |       } else if dep_state == "linking" { | 
		
	
		
			
				|  |  |  |  |         ext.Dependencies[source] = "linked" | 
		
	
		
			
				|  |  |  |  |         ctx.Send(node.ID, source, NewLinkSignal("dep_linked")) | 
		
	
		
			
				|  |  |  |  |       } | 
		
	
	
		
			
				
					|  |  |  | @ -196,10 +297,11 @@ func (ext *LockableExt) HandleLinkSignal(ctx *Context, source NodeID, node *Node | 
		
	
		
			
				|  |  |  |  |     if exists == false { | 
		
	
		
			
				|  |  |  |  |       req_state, exists := ext.Requirements[source] | 
		
	
		
			
				|  |  |  |  |       if exists == false { | 
		
	
		
			
				|  |  |  |  |         ext.Requirements[source] = "start" | 
		
	
		
			
				|  |  |  |  |         ext.Requirements[source] = ReqState{"linking", "unlocked"} | 
		
	
		
			
				|  |  |  |  |         ctx.Send(node.ID, source, NewLinkSignal("req_link")) | 
		
	
		
			
				|  |  |  |  |       } else if req_state == "start" { | 
		
	
		
			
				|  |  |  |  |         ext.Requirements[source] = "linked" | 
		
	
		
			
				|  |  |  |  |       } else if req_state.Link == "linking" { | 
		
	
		
			
				|  |  |  |  |         req_state.Link = "linked" | 
		
	
		
			
				|  |  |  |  |         ext.Requirements[source] = req_state | 
		
	
		
			
				|  |  |  |  |         ctx.Send(node.ID, source, NewLinkSignal("req_linked")) | 
		
	
		
			
				|  |  |  |  |       } | 
		
	
		
			
				|  |  |  |  |     } else { | 
		
	
	
		
			
				
					|  |  |  | @ -213,15 +315,16 @@ func (ext *LockableExt) HandleLinkSignal(ctx *Context, source NodeID, node *Node | 
		
	
		
			
				|  |  |  |  |   case "dep_linked": | 
		
	
		
			
				|  |  |  |  |     ctx.Log.Logf("lockable", "%s is a dependency of %s", node.ID, source) | 
		
	
		
			
				|  |  |  |  |     req_state, exists := ext.Requirements[source] | 
		
	
		
			
				|  |  |  |  |     if exists == true && req_state == "start" { | 
		
	
		
			
				|  |  |  |  |       ext.Requirements[source] = "linked" | 
		
	
		
			
				|  |  |  |  |     if exists == true && req_state.Link == "linking" { | 
		
	
		
			
				|  |  |  |  |       req_state.Link = "linked" | 
		
	
		
			
				|  |  |  |  |       ext.Requirements[source] = req_state | 
		
	
		
			
				|  |  |  |  |       ctx.Send(node.ID, source, NewLinkSignal("req_linked")) | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   case "req_linked": | 
		
	
		
			
				|  |  |  |  |     ctx.Log.Logf("lockable", "%s is a requirement of %s", node.ID, source) | 
		
	
		
			
				|  |  |  |  |     dep_state, exists := ext.Dependencies[source] | 
		
	
		
			
				|  |  |  |  |     if exists == true && dep_state == "start" { | 
		
	
		
			
				|  |  |  |  |     if exists == true && dep_state == "linking" { | 
		
	
		
			
				|  |  |  |  |       ext.Dependencies[source] = "linked" | 
		
	
		
			
				|  |  |  |  |       ctx.Send(node.ID, source, NewLinkSignal("dep_linked")) | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
	
		
			
				
					|  |  |  | @ -262,7 +365,7 @@ func (ext *LockableExt) Process(ctx *Context, source NodeID, node *Node, signal | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |   case Down: | 
		
	
		
			
				|  |  |  |  |     for requirement, state := range(ext.Requirements) { | 
		
	
		
			
				|  |  |  |  |       if state == "linked" { | 
		
	
		
			
				|  |  |  |  |       if state.Link == "linked" { | 
		
	
		
			
				|  |  |  |  |         err := ctx.Send(node.ID, requirement, signal) | 
		
	
		
			
				|  |  |  |  |         if err != nil { | 
		
	
		
			
				|  |  |  |  |           ctx.Log.Logf("signal", "LOCKABLE_SIGNAL_ERR: %s->%s - %e", node.ID, requirement, err) | 
		
	
	
		
			
				
					|  |  |  | 
 |