|  |  |  | @ -8,24 +8,64 @@ import ( | 
		
	
		
			
				|  |  |  |  |   "encoding/json" | 
		
	
		
			
				|  |  |  |  | ) | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | type ThreadExt struct { | 
		
	
		
			
				|  |  |  |  |   Actions ThreadActions | 
		
	
		
			
				|  |  |  |  |   Handlers ThreadHandlers | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   SignalChan chan GraphSignal | 
		
	
		
			
				|  |  |  |  |   TimeoutChan <-chan time.Time | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   ChildWaits sync.WaitGroup | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   ActiveLock sync.Mutex | 
		
	
		
			
				|  |  |  |  |   Active bool | 
		
	
		
			
				|  |  |  |  |   StateName string | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   Parent *Node | 
		
	
		
			
				|  |  |  |  |   Children map[NodeID]ChildInfo | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   ActionQueue []QueuedAction | 
		
	
		
			
				|  |  |  |  |   NextAction *QueuedAction | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | func (ext *ThreadExt) Serialize() ([]byte, error) { | 
		
	
		
			
				|  |  |  |  |   return nil, fmt.Errorf("NOT_IMPLEMENTED") | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | const ThreadExtType = ExtType("THREAD") | 
		
	
		
			
				|  |  |  |  | func (ext *ThreadExt) Type() ExtType { | 
		
	
		
			
				|  |  |  |  |   return ThreadExtType | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | func (ext *ThreadExt) ChildList() []*Node { | 
		
	
		
			
				|  |  |  |  |   ret := make([]*Node, len(ext.Children)) | 
		
	
		
			
				|  |  |  |  |   i := 0 | 
		
	
		
			
				|  |  |  |  |   for _, info := range(ext.Children) { | 
		
	
		
			
				|  |  |  |  |     ret[i] = info.Child | 
		
	
		
			
				|  |  |  |  |     i += 1 | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   return ret | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | // Assumed that thread is already locked for signal
 | 
		
	
		
			
				|  |  |  |  | func (thread *Thread) Process(context *StateContext, signal GraphSignal) error { | 
		
	
		
			
				|  |  |  |  |   context.Graph.Log.Logf("signal", "THREAD_PROCESS: %s", thread.ID()) | 
		
	
		
			
				|  |  |  |  | func (ext *ThreadExt) Process(context *StateContext, node *Node, signal GraphSignal) error { | 
		
	
		
			
				|  |  |  |  |   context.Graph.Log.Logf("signal", "THREAD_PROCESS: %s", node.ID) | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   var err error | 
		
	
		
			
				|  |  |  |  |   switch signal.Direction() { | 
		
	
		
			
				|  |  |  |  |   case Up: | 
		
	
		
			
				|  |  |  |  |     err = UseStates(context, thread, NewLockInfo(thread, []string{"parent"}), func(context *StateContext) error { | 
		
	
		
			
				|  |  |  |  |       if thread.Parent != nil { | 
		
	
		
			
				|  |  |  |  |         return Signal(context, thread.Parent, thread, signal) | 
		
	
		
			
				|  |  |  |  |     err = UseStates(context, node, NewACLInfo(node, []string{"parent"}), func(context *StateContext) error { | 
		
	
		
			
				|  |  |  |  |       if ext.Parent != nil { | 
		
	
		
			
				|  |  |  |  |         return Signal(context, ext.Parent, node, signal) | 
		
	
		
			
				|  |  |  |  |       } else { | 
		
	
		
			
				|  |  |  |  |         return nil | 
		
	
		
			
				|  |  |  |  |       } | 
		
	
		
			
				|  |  |  |  |     }) | 
		
	
		
			
				|  |  |  |  |   case Down: | 
		
	
		
			
				|  |  |  |  |     err = UseStates(context, thread, NewLockInfo(thread, []string{"children"}), func(context *StateContext) error { | 
		
	
		
			
				|  |  |  |  |       for _, info := range(thread.Children) { | 
		
	
		
			
				|  |  |  |  |         err := Signal(context, info.Child, thread, signal) | 
		
	
		
			
				|  |  |  |  |     err = UseStates(context, node, NewACLInfo(node, []string{"children"}), func(context *StateContext) error { | 
		
	
		
			
				|  |  |  |  |       for _, info := range(ext.Children) { | 
		
	
		
			
				|  |  |  |  |         err := Signal(context, info.Child, node, signal) | 
		
	
		
			
				|  |  |  |  |         if err != nil { | 
		
	
		
			
				|  |  |  |  |           return err | 
		
	
		
			
				|  |  |  |  |         } | 
		
	
	
		
			
				
					|  |  |  | @ -37,91 +77,121 @@ func (thread *Thread) Process(context *StateContext, signal GraphSignal) error { | 
		
	
		
			
				|  |  |  |  |   default: | 
		
	
		
			
				|  |  |  |  |     return fmt.Errorf("Invalid signal direction %d", signal.Direction()) | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  |   ext.SignalChan <- signal | 
		
	
		
			
				|  |  |  |  |   return err | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | func UnlinkThreads(context *StateContext, principal *Node, thread *Node, child *Node) error { | 
		
	
		
			
				|  |  |  |  |   thread_ext, err := GetExt[*ThreadExt](thread) | 
		
	
		
			
				|  |  |  |  |   if err != nil { | 
		
	
		
			
				|  |  |  |  |     return err | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   thread.Chan <- signal | 
		
	
		
			
				|  |  |  |  |   return thread.Lockable.Process(context, signal) | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | // Requires thread and childs thread to be locked for write
 | 
		
	
		
			
				|  |  |  |  | func UnlinkThreads(ctx * Context, node ThreadNode, child_node ThreadNode) error { | 
		
	
		
			
				|  |  |  |  |   thread := node.ThreadHandle() | 
		
	
		
			
				|  |  |  |  |   child := child_node.ThreadHandle() | 
		
	
		
			
				|  |  |  |  |   _, is_child := thread.Children[child_node.ID()] | 
		
	
		
			
				|  |  |  |  |   if is_child == false { | 
		
	
		
			
				|  |  |  |  |     return fmt.Errorf("UNLINK_THREADS_ERR: %s is not a child of %s", child.ID(), thread.ID()) | 
		
	
		
			
				|  |  |  |  |   child_ext, err := GetExt[*ThreadExt](child) | 
		
	
		
			
				|  |  |  |  |   if err != nil { | 
		
	
		
			
				|  |  |  |  |     return err | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   child.Parent = nil | 
		
	
		
			
				|  |  |  |  |   delete(thread.Children, child.ID()) | 
		
	
		
			
				|  |  |  |  |   return UpdateStates(context, principal, ACLMap{ | 
		
	
		
			
				|  |  |  |  |     thread.ID: ACLInfo{thread, []string{"children"}}, | 
		
	
		
			
				|  |  |  |  |     child.ID: ACLInfo{child, []string{"parent"}}, | 
		
	
		
			
				|  |  |  |  |   }, func(context *StateContext) error { | 
		
	
		
			
				|  |  |  |  |     _, is_child := thread_ext.Children[child.ID] | 
		
	
		
			
				|  |  |  |  |     if is_child == false { | 
		
	
		
			
				|  |  |  |  |       return fmt.Errorf("UNLINK_THREADS_ERR: %s is not a child of %s", child.ID, thread.ID) | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   return nil | 
		
	
		
			
				|  |  |  |  |     delete(thread_ext.Children, child.ID) | 
		
	
		
			
				|  |  |  |  |     child_ext.Parent = nil | 
		
	
		
			
				|  |  |  |  |     return nil | 
		
	
		
			
				|  |  |  |  |   }) | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | func checkIfChild(context *StateContext, target ThreadNode, cur ThreadNode) bool { | 
		
	
		
			
				|  |  |  |  |   for _, info := range(cur.ThreadHandle().Children) { | 
		
	
		
			
				|  |  |  |  |     if info.Child.ID() == target.ID() { | 
		
	
		
			
				|  |  |  |  |       return true | 
		
	
		
			
				|  |  |  |  | func checkIfChild(context *StateContext, id NodeID, cur *ThreadExt) (bool, error) { | 
		
	
		
			
				|  |  |  |  |   for _, info := range(cur.Children) { | 
		
	
		
			
				|  |  |  |  |     child := info.Child | 
		
	
		
			
				|  |  |  |  |     if child.ID == id { | 
		
	
		
			
				|  |  |  |  |       return true, nil | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |     is_child := false | 
		
	
		
			
				|  |  |  |  |     UpdateStates(context, cur, NewLockMap( | 
		
	
		
			
				|  |  |  |  |       NewLockInfo(info.Child, []string{"children"}), | 
		
	
		
			
				|  |  |  |  |     ), func(context *StateContext) error { | 
		
	
		
			
				|  |  |  |  |       is_child = checkIfChild(context, target, info.Child) | 
		
	
		
			
				|  |  |  |  |       return nil | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |     child_ext, err := GetExt[*ThreadExt](child) | 
		
	
		
			
				|  |  |  |  |     if err != nil { | 
		
	
		
			
				|  |  |  |  |       return false, err | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |     var is_child bool | 
		
	
		
			
				|  |  |  |  |     err = UpdateStates(context, child, NewACLInfo(child, []string{"children"}), func(context *StateContext) error { | 
		
	
		
			
				|  |  |  |  |       is_child, err = checkIfChild(context, id, child_ext) | 
		
	
		
			
				|  |  |  |  |       return err | 
		
	
		
			
				|  |  |  |  |     }) | 
		
	
		
			
				|  |  |  |  |     if err != nil { | 
		
	
		
			
				|  |  |  |  |       return false, err | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |     if is_child { | 
		
	
		
			
				|  |  |  |  |       return true | 
		
	
		
			
				|  |  |  |  |       return true, nil | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   return false | 
		
	
		
			
				|  |  |  |  |   return false, nil | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | // Links child to parent with info as the associated info
 | 
		
	
		
			
				|  |  |  |  | // Continues the write context with princ, getting children for thread and parent for child
 | 
		
	
		
			
				|  |  |  |  | func LinkThreads(context *StateContext, princ Node, thread_node ThreadNode, info ChildInfo) error { | 
		
	
		
			
				|  |  |  |  |   if context == nil || thread_node == nil || info.Child == nil { | 
		
	
		
			
				|  |  |  |  | func LinkThreads(context *StateContext, principal *Node, thread *Node, info ChildInfo) error { | 
		
	
		
			
				|  |  |  |  |   if context == nil || principal == nil || thread == nil || info.Child == nil { | 
		
	
		
			
				|  |  |  |  |     return fmt.Errorf("invalid input") | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  |   thread := thread_node.ThreadHandle() | 
		
	
		
			
				|  |  |  |  |   child := info.Child.ThreadHandle() | 
		
	
		
			
				|  |  |  |  |   child_node := info.Child | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   if thread.ID() == child.ID() { | 
		
	
		
			
				|  |  |  |  |     return fmt.Errorf("Will not link %s as a child of itself", thread.ID()) | 
		
	
		
			
				|  |  |  |  |   child := info.Child | 
		
	
		
			
				|  |  |  |  |   if thread.ID == child.ID { | 
		
	
		
			
				|  |  |  |  |     return fmt.Errorf("Will not link %s as a child of itself", thread.ID) | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   thread_ext, err := GetExt[*ThreadExt](thread) | 
		
	
		
			
				|  |  |  |  |   if err != nil { | 
		
	
		
			
				|  |  |  |  |     return err | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   child_ext, err := GetExt[*ThreadExt](thread) | 
		
	
		
			
				|  |  |  |  |   if err != nil { | 
		
	
		
			
				|  |  |  |  |     return err | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   return UpdateStates(context, princ, LockMap{ | 
		
	
		
			
				|  |  |  |  |     child.ID(): LockInfo{Node: child_node, Resources: []string{"parent"}}, | 
		
	
		
			
				|  |  |  |  |     thread.ID(): LockInfo{Node: thread_node, Resources: []string{"children"}}, | 
		
	
		
			
				|  |  |  |  |   return UpdateStates(context, principal, ACLMap{ | 
		
	
		
			
				|  |  |  |  |     child.ID: ACLInfo{Node: child, Resources: []string{"parent"}}, | 
		
	
		
			
				|  |  |  |  |     thread.ID: ACLInfo{Node: thread, Resources: []string{"children"}}, | 
		
	
		
			
				|  |  |  |  |   }, func(context *StateContext) error { | 
		
	
		
			
				|  |  |  |  |     if child.Parent != nil { | 
		
	
		
			
				|  |  |  |  |       return fmt.Errorf("EVENT_LINK_ERR: %s already has a parent, cannot link as child", child.ID()) | 
		
	
		
			
				|  |  |  |  |     if child_ext.Parent != nil { | 
		
	
		
			
				|  |  |  |  |       return fmt.Errorf("EVENT_LINK_ERR: %s already has a parent, cannot link as child", child.ID) | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |     if checkIfChild(context, thread, child) == true { | 
		
	
		
			
				|  |  |  |  |       return fmt.Errorf("EVENT_LINK_ERR: %s is a child of %s so cannot add as parent", thread.ID(), child.ID()) | 
		
	
		
			
				|  |  |  |  |     is_child, err := checkIfChild(context, thread.ID, child_ext) | 
		
	
		
			
				|  |  |  |  |     if err != nil { | 
		
	
		
			
				|  |  |  |  |       return err | 
		
	
		
			
				|  |  |  |  |     } else if is_child == true { | 
		
	
		
			
				|  |  |  |  |       return fmt.Errorf("EVENT_LINK_ERR: %s is a child of %s so cannot add as parent", thread.ID, child.ID) | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |     if checkIfChild(context, child, thread) == true { | 
		
	
		
			
				|  |  |  |  |       return fmt.Errorf("EVENT_LINK_ERR: %s is already a parent of %s so will not add again", thread.ID(), child.ID()) | 
		
	
		
			
				|  |  |  |  |     is_child, err = checkIfChild(context, child.ID, thread_ext) | 
		
	
		
			
				|  |  |  |  |     if err != nil { | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |         return err | 
		
	
		
			
				|  |  |  |  |     } else if is_child == true { | 
		
	
		
			
				|  |  |  |  |       return fmt.Errorf("EVENT_LINK_ERR: %s is already a parent of %s so will not add again", thread.ID, child.ID) | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |     // TODO check for info types
 | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |     thread.Children[child.ID()] = info | 
		
	
		
			
				|  |  |  |  |     child.Parent = thread_node | 
		
	
		
			
				|  |  |  |  |     thread_ext.Children[child.ID] = info | 
		
	
		
			
				|  |  |  |  |     child_ext.Parent = thread | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |     return nil | 
		
	
		
			
				|  |  |  |  |   }) | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | type ThreadAction func(*Context, ThreadNode)(string, error) | 
		
	
		
			
				|  |  |  |  | type ThreadAction func(*Context, *Node, *ThreadExt)(string, error) | 
		
	
		
			
				|  |  |  |  | type ThreadActions map[string]ThreadAction | 
		
	
		
			
				|  |  |  |  | type ThreadHandler func(*Context, ThreadNode, GraphSignal)(string, error) | 
		
	
		
			
				|  |  |  |  | type ThreadHandler func(*Context, *Node, *ThreadExt, GraphSignal)(string, error) | 
		
	
		
			
				|  |  |  |  | type ThreadHandlers map[string]ThreadHandler | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | type InfoType string | 
		
	
	
		
			
				
					|  |  |  | @ -129,6 +199,10 @@ func (t InfoType) String() string { | 
		
	
		
			
				|  |  |  |  |   return string(t) | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | type ThreadInfo interface { | 
		
	
		
			
				|  |  |  |  |   Serializable[InfoType] | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | // Data required by a parent thread to restore it's children
 | 
		
	
		
			
				|  |  |  |  | type ParentThreadInfo struct { | 
		
	
		
			
				|  |  |  |  |   Start bool `json:"start"` | 
		
	
	
		
			
				
					|  |  |  | @ -136,22 +210,23 @@ type ParentThreadInfo struct { | 
		
	
		
			
				|  |  |  |  |   RestoreAction string `json:"restore_action"` | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | func NewParentThreadInfo(start bool, start_action string, restore_action string) ParentThreadInfo { | 
		
	
		
			
				|  |  |  |  |   return ParentThreadInfo{ | 
		
	
		
			
				|  |  |  |  |     Start: start, | 
		
	
		
			
				|  |  |  |  |     StartAction: start_action, | 
		
	
		
			
				|  |  |  |  |     RestoreAction: restore_action, | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | const ParentThreadInfoType = InfoType("PARENT") | 
		
	
		
			
				|  |  |  |  | func (info *ParentThreadInfo) Type() InfoType { | 
		
	
		
			
				|  |  |  |  |   return ParentThreadInfoType | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | func (info *ParentThreadInfo) Serialize() ([]byte, error) { | 
		
	
		
			
				|  |  |  |  |   return json.MarshalIndent(info, "", "  ") | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | type ChildInfo struct { | 
		
	
		
			
				|  |  |  |  |   Child ThreadNode | 
		
	
		
			
				|  |  |  |  |   Infos map[InfoType]interface{} | 
		
	
		
			
				|  |  |  |  |   Child *Node | 
		
	
		
			
				|  |  |  |  |   Infos map[InfoType]ThreadInfo | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | func NewChildInfo(child ThreadNode, infos map[InfoType]interface{}) ChildInfo { | 
		
	
		
			
				|  |  |  |  | func NewChildInfo(child *Node, infos map[InfoType]ThreadInfo) ChildInfo { | 
		
	
		
			
				|  |  |  |  |   if infos == nil { | 
		
	
		
			
				|  |  |  |  |     infos = map[InfoType]interface{}{} | 
		
	
		
			
				|  |  |  |  |     infos = map[InfoType]ThreadInfo{} | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   return ChildInfo{ | 
		
	
	
		
			
				
					|  |  |  | @ -165,110 +240,21 @@ type QueuedAction struct { | 
		
	
		
			
				|  |  |  |  |   Action string `json:"action"` | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | type ThreadNode interface { | 
		
	
		
			
				|  |  |  |  |   LockableNode | 
		
	
		
			
				|  |  |  |  |   ThreadHandle() *Thread | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | type Thread struct { | 
		
	
		
			
				|  |  |  |  |   Lockable | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   Actions ThreadActions | 
		
	
		
			
				|  |  |  |  |   Handlers ThreadHandlers | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   TimeoutChan <-chan time.Time | 
		
	
		
			
				|  |  |  |  |   Chan chan GraphSignal | 
		
	
		
			
				|  |  |  |  |   ChildWaits sync.WaitGroup | 
		
	
		
			
				|  |  |  |  |   Active bool | 
		
	
		
			
				|  |  |  |  |   ActiveLock sync.Mutex | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   StateName string | 
		
	
		
			
				|  |  |  |  |   Parent ThreadNode | 
		
	
		
			
				|  |  |  |  |   Children map[NodeID]ChildInfo | 
		
	
		
			
				|  |  |  |  |   InfoTypes []InfoType | 
		
	
		
			
				|  |  |  |  |   ActionQueue []QueuedAction | 
		
	
		
			
				|  |  |  |  |   NextAction *QueuedAction | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | func (thread *Thread) QueueAction(end time.Time, action string) { | 
		
	
		
			
				|  |  |  |  |   thread.ActionQueue = append(thread.ActionQueue, QueuedAction{end, action}) | 
		
	
		
			
				|  |  |  |  |   thread.NextAction, thread.TimeoutChan = thread.SoonestAction() | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | func (thread *Thread) ClearActionQueue() { | 
		
	
		
			
				|  |  |  |  |   thread.ActionQueue = []QueuedAction{} | 
		
	
		
			
				|  |  |  |  |   thread.NextAction = nil | 
		
	
		
			
				|  |  |  |  |   thread.TimeoutChan = nil | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | func (thread *Thread) ThreadHandle() *Thread { | 
		
	
		
			
				|  |  |  |  |   return thread | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | func (thread *Thread) Type() NodeType { | 
		
	
		
			
				|  |  |  |  |   return NodeType("thread") | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | func (thread *Thread) Serialize() ([]byte, error) { | 
		
	
		
			
				|  |  |  |  |   thread_json := NewThreadJSON(thread) | 
		
	
		
			
				|  |  |  |  |   return json.MarshalIndent(&thread_json, "", "  ") | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | func (thread *Thread) ChildList() []ThreadNode { | 
		
	
		
			
				|  |  |  |  |   ret := make([]ThreadNode, len(thread.Children)) | 
		
	
		
			
				|  |  |  |  |   i := 0 | 
		
	
		
			
				|  |  |  |  |   for _, info := range(thread.Children) { | 
		
	
		
			
				|  |  |  |  |     ret[i] = info.Child | 
		
	
		
			
				|  |  |  |  |     i += 1 | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  |   return ret | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | type ThreadJSON struct { | 
		
	
		
			
				|  |  |  |  |   LockableJSON | 
		
	
		
			
				|  |  |  |  |   Parent string `json:"parent"` | 
		
	
		
			
				|  |  |  |  |   Children map[string]map[string]interface{} `json:"children"` | 
		
	
		
			
				|  |  |  |  |   ActionQueue []QueuedAction `json:"action_queue"` | 
		
	
		
			
				|  |  |  |  |   StateName string `json:"state_name"` | 
		
	
		
			
				|  |  |  |  |   InfoTypes []InfoType `json:"info_types"` | 
		
	
		
			
				|  |  |  |  | func (ext *ThreadExt) QueueAction(end time.Time, action string) { | 
		
	
		
			
				|  |  |  |  |   ext.ActionQueue = append(ext.ActionQueue, QueuedAction{end, action}) | 
		
	
		
			
				|  |  |  |  |   ext.NextAction, ext.TimeoutChan = ext.SoonestAction() | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | func NewThreadJSON(thread *Thread) ThreadJSON { | 
		
	
		
			
				|  |  |  |  |   children := map[string]map[string]interface{}{} | 
		
	
		
			
				|  |  |  |  |   for id, info := range(thread.Children) { | 
		
	
		
			
				|  |  |  |  |     tmp := map[string]interface{}{} | 
		
	
		
			
				|  |  |  |  |     for name, i := range(info.Infos) { | 
		
	
		
			
				|  |  |  |  |       tmp[name.String()] = i | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |     children[id.String()] = tmp | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   parent_id := "" | 
		
	
		
			
				|  |  |  |  |   if thread.Parent != nil { | 
		
	
		
			
				|  |  |  |  |     parent_id = thread.Parent.ID().String() | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   lockable_json := NewLockableJSON(&thread.Lockable) | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   return ThreadJSON{ | 
		
	
		
			
				|  |  |  |  |     Parent: parent_id, | 
		
	
		
			
				|  |  |  |  |     Children: children, | 
		
	
		
			
				|  |  |  |  |     ActionQueue: thread.ActionQueue, | 
		
	
		
			
				|  |  |  |  |     StateName: thread.StateName, | 
		
	
		
			
				|  |  |  |  |     LockableJSON: lockable_json, | 
		
	
		
			
				|  |  |  |  |     InfoTypes: thread.InfoTypes, | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | func (ext *ThreadExt) ClearActionQueue() { | 
		
	
		
			
				|  |  |  |  |   ext.ActionQueue = []QueuedAction{} | 
		
	
		
			
				|  |  |  |  |   ext.NextAction = nil | 
		
	
		
			
				|  |  |  |  |   ext.TimeoutChan = nil | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | var LoadThread = LoadJSONNode(func(id NodeID, j ThreadJSON) (Node, error) { | 
		
	
		
			
				|  |  |  |  |   thread := NewThread(id, j.Name, j.StateName, j.InfoTypes, BaseThreadActions, BaseThreadHandlers) | 
		
	
		
			
				|  |  |  |  |   return &thread, nil | 
		
	
		
			
				|  |  |  |  | }, RestoreThread) | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | func (thread *Thread) SoonestAction() (*QueuedAction, <-chan time.Time) { | 
		
	
		
			
				|  |  |  |  | func (ext *ThreadExt) SoonestAction() (*QueuedAction, <-chan time.Time) { | 
		
	
		
			
				|  |  |  |  |   var soonest_action *QueuedAction | 
		
	
		
			
				|  |  |  |  |   var soonest_time time.Time | 
		
	
		
			
				|  |  |  |  |   for _, action := range(thread.ActionQueue) { | 
		
	
		
			
				|  |  |  |  |   for _, action := range(ext.ActionQueue) { | 
		
	
		
			
				|  |  |  |  |     if action.Timeout.Compare(soonest_time) == -1 || soonest_action == nil { | 
		
	
		
			
				|  |  |  |  |       soonest_action = &action | 
		
	
		
			
				|  |  |  |  |       soonest_time = action.Timeout | 
		
	
	
		
			
				
					|  |  |  | @ -281,55 +267,6 @@ func (thread *Thread) SoonestAction() (*QueuedAction, <-chan time.Time) { | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | func RestoreThread(ctx *Context, thread ThreadNode, j ThreadJSON, nodes NodeMap) error { | 
		
	
		
			
				|  |  |  |  |   thread_ptr := thread.ThreadHandle() | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   thread_ptr.ActionQueue = j.ActionQueue | 
		
	
		
			
				|  |  |  |  |   thread_ptr.NextAction, thread_ptr.TimeoutChan = thread_ptr.SoonestAction() | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   if j.Parent != "" { | 
		
	
		
			
				|  |  |  |  |     parent_id, err := ParseID(j.Parent) | 
		
	
		
			
				|  |  |  |  |     if err != nil { | 
		
	
		
			
				|  |  |  |  |       return err | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |     p, err := LoadNodeRecurse(ctx, parent_id, nodes) | 
		
	
		
			
				|  |  |  |  |     if err != nil { | 
		
	
		
			
				|  |  |  |  |       return err | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |     p_t, ok := p.(ThreadNode) | 
		
	
		
			
				|  |  |  |  |     if ok == false { | 
		
	
		
			
				|  |  |  |  |       return err | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |     thread_ptr.Parent = p_t | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   for id_str, info_raw := range(j.Children) { | 
		
	
		
			
				|  |  |  |  |     id, err := ParseID(id_str) | 
		
	
		
			
				|  |  |  |  |     if err != nil { | 
		
	
		
			
				|  |  |  |  |       return err | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |     child_node, err := LoadNodeRecurse(ctx, id, nodes) | 
		
	
		
			
				|  |  |  |  |     if err != nil { | 
		
	
		
			
				|  |  |  |  |       return err | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |     child_t, ok := child_node.(ThreadNode) | 
		
	
		
			
				|  |  |  |  |     if ok == false { | 
		
	
		
			
				|  |  |  |  |       return fmt.Errorf("%+v is not a Thread as expected", child_node) | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |     parsed_info, err := DeserializeChildInfo(ctx, info_raw) | 
		
	
		
			
				|  |  |  |  |     if err != nil { | 
		
	
		
			
				|  |  |  |  |       return err | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |     thread_ptr.Children[id] = ChildInfo{child_t, parsed_info} | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   return RestoreLockable(ctx, thread, j.LockableJSON, nodes) | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | var deserializers = map[InfoType]func(interface{})(interface{}, error) { | 
		
	
		
			
				|  |  |  |  |   "parent": func(raw interface{})(interface{}, error) { | 
		
	
		
			
				|  |  |  |  |     m, ok := raw.(map[string]interface{}) | 
		
	
	
		
			
				
					|  |  |  | @ -357,141 +294,133 @@ var deserializers = map[InfoType]func(interface{})(interface{}, error) { | 
		
	
		
			
				|  |  |  |  |   }, | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | func DeserializeChildInfo(ctx *Context, infos_raw map[string]interface{}) (map[InfoType]interface{}, error) { | 
		
	
		
			
				|  |  |  |  |   ret := map[InfoType]interface{}{} | 
		
	
		
			
				|  |  |  |  |   for type_str, info_raw := range(infos_raw) { | 
		
	
		
			
				|  |  |  |  |     info_type := InfoType(type_str) | 
		
	
		
			
				|  |  |  |  |     deserializer, exists := deserializers[info_type] | 
		
	
		
			
				|  |  |  |  |     if exists == false { | 
		
	
		
			
				|  |  |  |  |       return nil, fmt.Errorf("No deserializer for %s", info_type) | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |     var err error | 
		
	
		
			
				|  |  |  |  |     ret[info_type], err = deserializer(info_raw) | 
		
	
		
			
				|  |  |  |  |     if err != nil { | 
		
	
		
			
				|  |  |  |  |       return nil, err | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   return ret, nil | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | const THREAD_SIGNAL_BUFFER_SIZE = 128 | 
		
	
		
			
				|  |  |  |  | func NewThread(id NodeID, name string, state_name string, info_types []InfoType, actions ThreadActions, handlers ThreadHandlers) Thread { | 
		
	
		
			
				|  |  |  |  |   return Thread{ | 
		
	
		
			
				|  |  |  |  |     Lockable: NewLockable(id, name), | 
		
	
		
			
				|  |  |  |  |     InfoTypes: info_types, | 
		
	
		
			
				|  |  |  |  |     StateName: state_name, | 
		
	
		
			
				|  |  |  |  |     Chan: make(chan GraphSignal, THREAD_SIGNAL_BUFFER_SIZE), | 
		
	
		
			
				|  |  |  |  |     Children: map[NodeID]ChildInfo{}, | 
		
	
		
			
				|  |  |  |  | func NewThreadExt(buffer int, name string, state_name string, actions ThreadActions, handlers ThreadHandlers) ThreadExt { | 
		
	
		
			
				|  |  |  |  |   return ThreadExt{ | 
		
	
		
			
				|  |  |  |  |     Actions: actions, | 
		
	
		
			
				|  |  |  |  |     Handlers: handlers, | 
		
	
		
			
				|  |  |  |  |     SignalChan: make(chan GraphSignal, buffer), | 
		
	
		
			
				|  |  |  |  |     TimeoutChan: nil, | 
		
	
		
			
				|  |  |  |  |     Active: false, | 
		
	
		
			
				|  |  |  |  |     StateName: state_name, | 
		
	
		
			
				|  |  |  |  |     Parent: nil, | 
		
	
		
			
				|  |  |  |  |     Children: map[NodeID]ChildInfo{}, | 
		
	
		
			
				|  |  |  |  |     ActionQueue: []QueuedAction{}, | 
		
	
		
			
				|  |  |  |  |     NextAction: nil, | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | func (thread *Thread) SetActive(active bool) error { | 
		
	
		
			
				|  |  |  |  |   thread.ActiveLock.Lock() | 
		
	
		
			
				|  |  |  |  |   defer thread.ActiveLock.Unlock() | 
		
	
		
			
				|  |  |  |  |   if thread.Active == true && active == true { | 
		
	
		
			
				|  |  |  |  |     return fmt.Errorf("%s is active, cannot set active", thread.ID()) | 
		
	
		
			
				|  |  |  |  |   } else if thread.Active == false && active == false { | 
		
	
		
			
				|  |  |  |  |     return fmt.Errorf("%s is already inactive, canot set inactive", thread.ID()) | 
		
	
		
			
				|  |  |  |  | func (ext *ThreadExt) SetActive(active bool) error { | 
		
	
		
			
				|  |  |  |  |   ext.ActiveLock.Lock() | 
		
	
		
			
				|  |  |  |  |   defer ext.ActiveLock.Unlock() | 
		
	
		
			
				|  |  |  |  |   if ext.Active == true && active == true { | 
		
	
		
			
				|  |  |  |  |     return fmt.Errorf("alreday active, cannot set active") | 
		
	
		
			
				|  |  |  |  |   } else if ext.Active == false && active == false { | 
		
	
		
			
				|  |  |  |  |     return fmt.Errorf("already inactive, canot set inactive") | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  |   thread.Active = active | 
		
	
		
			
				|  |  |  |  |   ext.Active = active | 
		
	
		
			
				|  |  |  |  |   return nil | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | func (thread *Thread) SetState(state string) error { | 
		
	
		
			
				|  |  |  |  |   thread.StateName = state | 
		
	
		
			
				|  |  |  |  | func (ext *ThreadExt) SetState(state string) error { | 
		
	
		
			
				|  |  |  |  |   ext.StateName = state | 
		
	
		
			
				|  |  |  |  |   return nil | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | // Requires the read permission of threads children
 | 
		
	
		
			
				|  |  |  |  | func FindChild(context *StateContext, princ Node, node ThreadNode, id NodeID) ThreadNode { | 
		
	
		
			
				|  |  |  |  |   if node == nil { | 
		
	
		
			
				|  |  |  |  | func FindChild(context *StateContext, principal *Node, thread *Node, id NodeID) (*Node, error) { | 
		
	
		
			
				|  |  |  |  |   if thread == nil { | 
		
	
		
			
				|  |  |  |  |     panic("cannot recurse through nil") | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  |   thread := node.ThreadHandle() | 
		
	
		
			
				|  |  |  |  |   if id ==  thread.ID() { | 
		
	
		
			
				|  |  |  |  |     return thread | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   if id ==  thread.ID { | 
		
	
		
			
				|  |  |  |  |     return thread, nil | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   for _, info := range thread.Children { | 
		
	
		
			
				|  |  |  |  |     var result ThreadNode | 
		
	
		
			
				|  |  |  |  |     UseStates(context, princ, NewLockInfo(info.Child, []string{"children"}), func(context *StateContext) error { | 
		
	
		
			
				|  |  |  |  |       result = FindChild(context, princ, info.Child, id) | 
		
	
		
			
				|  |  |  |  |       return nil | 
		
	
		
			
				|  |  |  |  |     }) | 
		
	
		
			
				|  |  |  |  |     if result != nil { | 
		
	
		
			
				|  |  |  |  |       return result | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |   thread_ext, err := GetExt[*ThreadExt](thread) | 
		
	
		
			
				|  |  |  |  |   if err != nil { | 
		
	
		
			
				|  |  |  |  |     return nil, err | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   return nil | 
		
	
		
			
				|  |  |  |  |   var found *Node = nil | 
		
	
		
			
				|  |  |  |  |   err = UseStates(context, principal, NewACLInfo(thread, []string{"children"}), func(context *StateContext) error { | 
		
	
		
			
				|  |  |  |  |     for _, info := range(thread_ext.Children) { | 
		
	
		
			
				|  |  |  |  |       found, err = FindChild(context, principal, info.Child, id) | 
		
	
		
			
				|  |  |  |  |       if err != nil { | 
		
	
		
			
				|  |  |  |  |         return err | 
		
	
		
			
				|  |  |  |  |       } | 
		
	
		
			
				|  |  |  |  |       if found != nil { | 
		
	
		
			
				|  |  |  |  |         return nil | 
		
	
		
			
				|  |  |  |  |       } | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |     return nil | 
		
	
		
			
				|  |  |  |  |   }) | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   return found, err | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | func ChildGo(ctx * Context, thread *Thread, child ThreadNode, first_action string) { | 
		
	
		
			
				|  |  |  |  |   thread.ChildWaits.Add(1) | 
		
	
		
			
				|  |  |  |  |   go func(child ThreadNode) { | 
		
	
		
			
				|  |  |  |  |     ctx.Log.Logf("thread", "THREAD_START_CHILD: %s from %s", thread.ID(), child.ID()) | 
		
	
		
			
				|  |  |  |  |     defer thread.ChildWaits.Done() | 
		
	
		
			
				|  |  |  |  | func ChildGo(ctx * Context, thread_ext *ThreadExt, child *Node, first_action string) { | 
		
	
		
			
				|  |  |  |  |   thread_ext.ChildWaits.Add(1) | 
		
	
		
			
				|  |  |  |  |   go func(child *Node) { | 
		
	
		
			
				|  |  |  |  |     defer thread_ext.ChildWaits.Done() | 
		
	
		
			
				|  |  |  |  |     err := ThreadLoop(ctx, child, first_action) | 
		
	
		
			
				|  |  |  |  |     if err != nil { | 
		
	
		
			
				|  |  |  |  |       ctx.Log.Logf("thread", "THREAD_CHILD_RUN_ERR: %s %s", child.ID(), err) | 
		
	
		
			
				|  |  |  |  |       ctx.Log.Logf("thread", "THREAD_CHILD_RUN_ERR: %s %s", child.ID, err) | 
		
	
		
			
				|  |  |  |  |     } else { | 
		
	
		
			
				|  |  |  |  |       ctx.Log.Logf("thread", "THREAD_CHILD_RUN_DONE: %s", child.ID()) | 
		
	
		
			
				|  |  |  |  |       ctx.Log.Logf("thread", "THREAD_CHILD_RUN_DONE: %s", child.ID) | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |   }(child) | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | // Main Loop for Threads, starts a write context, so cannot be called from a write or read context
 | 
		
	
		
			
				|  |  |  |  | func ThreadLoop(ctx * Context, node ThreadNode, first_action string) error { | 
		
	
		
			
				|  |  |  |  |   // Start the thread, error if double-started
 | 
		
	
		
			
				|  |  |  |  |   thread := node.ThreadHandle() | 
		
	
		
			
				|  |  |  |  |   ctx.Log.Logf("thread", "THREAD_LOOP_START: %s - %s", thread.ID(), first_action) | 
		
	
		
			
				|  |  |  |  |   err := thread.SetActive(true) | 
		
	
		
			
				|  |  |  |  | func ThreadLoop(ctx * Context, thread *Node, first_action string) error { | 
		
	
		
			
				|  |  |  |  |   thread_ext, err := GetExt[*ThreadExt](thread) | 
		
	
		
			
				|  |  |  |  |   if err != nil { | 
		
	
		
			
				|  |  |  |  |     return err | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   ctx.Log.Logf("thread", "THREAD_LOOP_START: %s - %s", thread.ID, first_action) | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   err = thread_ext.SetActive(true) | 
		
	
		
			
				|  |  |  |  |   if err != nil { | 
		
	
		
			
				|  |  |  |  |     ctx.Log.Logf("thread", "THREAD_LOOP_START_ERR: %e", err) | 
		
	
		
			
				|  |  |  |  |     return err | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  |   next_action := first_action | 
		
	
		
			
				|  |  |  |  |   for next_action != "" { | 
		
	
		
			
				|  |  |  |  |     action, exists := thread.Actions[next_action] | 
		
	
		
			
				|  |  |  |  |     action, exists := thread_ext.Actions[next_action] | 
		
	
		
			
				|  |  |  |  |     if exists == false { | 
		
	
		
			
				|  |  |  |  |       error_str := fmt.Sprintf("%s is not a valid action", next_action) | 
		
	
		
			
				|  |  |  |  |       return errors.New(error_str) | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |     ctx.Log.Logf("thread", "THREAD_ACTION: %s - %s", thread.ID(), next_action) | 
		
	
		
			
				|  |  |  |  |     next_action, err = action(ctx, node) | 
		
	
		
			
				|  |  |  |  |     ctx.Log.Logf("thread", "THREAD_ACTION: %s - %s", thread.ID, next_action) | 
		
	
		
			
				|  |  |  |  |     next_action, err = action(ctx, thread, thread_ext) | 
		
	
		
			
				|  |  |  |  |     if err != nil { | 
		
	
		
			
				|  |  |  |  |       return err | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   err = thread.SetActive(false) | 
		
	
		
			
				|  |  |  |  |   err = thread_ext.SetActive(false) | 
		
	
		
			
				|  |  |  |  |   if err != nil { | 
		
	
		
			
				|  |  |  |  |     ctx.Log.Logf("thread", "THREAD_LOOP_STOP_ERR: %e", err) | 
		
	
		
			
				|  |  |  |  |     return err | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   ctx.Log.Logf("thread", "THREAD_LOOP_DONE: %s", thread.ID()) | 
		
	
		
			
				|  |  |  |  |   ctx.Log.Logf("thread", "THREAD_LOOP_DONE: %s", thread.ID) | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   return nil | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | func ThreadChildLinked(ctx *Context, node ThreadNode, signal GraphSignal) (string, error) { | 
		
	
		
			
				|  |  |  |  |   thread := node.ThreadHandle() | 
		
	
		
			
				|  |  |  |  |   ctx.Log.Logf("thread", "THREAD_CHILD_LINKED: %+v", signal) | 
		
	
		
			
				|  |  |  |  | func ThreadChildLinked(ctx *Context, thread *Node, thread_ext *ThreadExt, signal GraphSignal) (string, error) { | 
		
	
		
			
				|  |  |  |  |   ctx.Log.Logf("thread", "THREAD_CHILD_LINKED: %s - %+v", thread.ID, signal) | 
		
	
		
			
				|  |  |  |  |   context := NewWriteContext(ctx) | 
		
	
		
			
				|  |  |  |  |   err := UpdateStates(context, node, NewLockMap( | 
		
	
		
			
				|  |  |  |  |     NewLockInfo(node, []string{"children"}), | 
		
	
		
			
				|  |  |  |  |   ), func(context *StateContext) error { | 
		
	
		
			
				|  |  |  |  |   err := UpdateStates(context, thread, NewACLInfo(thread, []string{"children"}), func(context *StateContext) error { | 
		
	
		
			
				|  |  |  |  |     sig, ok := signal.(IDSignal) | 
		
	
		
			
				|  |  |  |  |     if ok == false { | 
		
	
		
			
				|  |  |  |  |       ctx.Log.Logf("thread", "THREAD_NODE_LINKED_BAD_CAST") | 
		
	
		
			
				|  |  |  |  |       return nil | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |     info, exists := thread.Children[sig.ID] | 
		
	
		
			
				|  |  |  |  |     info, exists := thread_ext.Children[sig.ID] | 
		
	
		
			
				|  |  |  |  |     if exists == false { | 
		
	
		
			
				|  |  |  |  |       ctx.Log.Logf("thread", "THREAD_NODE_LINKED: %s is not a child of %s", sig.ID) | 
		
	
		
			
				|  |  |  |  |       return nil | 
		
	
	
		
			
				
					|  |  |  | @ -502,7 +431,7 @@ func ThreadChildLinked(ctx *Context, node ThreadNode, signal GraphSignal) (strin | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |     if parent_info.Start == true { | 
		
	
		
			
				|  |  |  |  |       ChildGo(ctx, thread, info.Child, parent_info.StartAction) | 
		
	
		
			
				|  |  |  |  |       ChildGo(ctx, thread_ext, info.Child, parent_info.StartAction) | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |     return nil | 
		
	
		
			
				|  |  |  |  |   }) | 
		
	
	
		
			
				
					|  |  |  | @ -517,28 +446,26 @@ func ThreadChildLinked(ctx *Context, node ThreadNode, signal GraphSignal) (strin | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | // Helper function to start a child from a thread during a signal handler
 | 
		
	
		
			
				|  |  |  |  | // Starts a write context, so cannot be called from either a write or read context
 | 
		
	
		
			
				|  |  |  |  | func ThreadStartChild(ctx *Context, node ThreadNode, signal GraphSignal) (string, error) { | 
		
	
		
			
				|  |  |  |  | func ThreadStartChild(ctx *Context, thread *Node, thread_ext *ThreadExt, signal GraphSignal) (string, error) { | 
		
	
		
			
				|  |  |  |  |   sig, ok := signal.(StartChildSignal) | 
		
	
		
			
				|  |  |  |  |   if ok == false { | 
		
	
		
			
				|  |  |  |  |     return "wait", nil | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   thread := node.ThreadHandle() | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   context := NewWriteContext(ctx) | 
		
	
		
			
				|  |  |  |  |   return "wait", UpdateStates(context, node, NewLockInfo(node, []string{"children"}), func(context *StateContext) error { | 
		
	
		
			
				|  |  |  |  |     info, exists:= thread.Children[sig.ID] | 
		
	
		
			
				|  |  |  |  |   return "wait", UpdateStates(context, thread, NewACLInfo(thread, []string{"children"}), func(context *StateContext) error { | 
		
	
		
			
				|  |  |  |  |     info, exists:= thread_ext.Children[sig.ID] | 
		
	
		
			
				|  |  |  |  |     if exists == false { | 
		
	
		
			
				|  |  |  |  |       return fmt.Errorf("%s is not a child of %s", sig.ID, thread.ID()) | 
		
	
		
			
				|  |  |  |  |       return fmt.Errorf("%s is not a child of %s", sig.ID, thread.ID) | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |     return UpdateStates(context, node, NewLockInfo(info.Child, []string{"start"}), func(context *StateContext) error { | 
		
	
		
			
				|  |  |  |  |     return UpdateStates(context, thread, NewACLInfo(info.Child, []string{"start"}), func(context *StateContext) error { | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |       parent_info, exists := info.Infos["parent"].(*ParentThreadInfo) | 
		
	
		
			
				|  |  |  |  |       if exists == false { | 
		
	
		
			
				|  |  |  |  |         return fmt.Errorf("Called ThreadStartChild from a thread that doesn't require parent child info") | 
		
	
		
			
				|  |  |  |  |       } | 
		
	
		
			
				|  |  |  |  |       parent_info.Start = true | 
		
	
		
			
				|  |  |  |  |       ChildGo(ctx, thread, info.Child, sig.Action) | 
		
	
		
			
				|  |  |  |  |       ChildGo(ctx, thread_ext, info.Child, sig.Action) | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |       return nil | 
		
	
		
			
				|  |  |  |  |     }) | 
		
	
	
		
			
				
					|  |  |  | @ -547,19 +474,23 @@ func ThreadStartChild(ctx *Context, node ThreadNode, signal GraphSignal) (string | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | // Helper function to restore threads that should be running from a parents restore action
 | 
		
	
		
			
				|  |  |  |  | // Starts a write context, so cannot be called from either a write or read context
 | 
		
	
		
			
				|  |  |  |  | func ThreadRestore(ctx * Context, node ThreadNode, start bool) error { | 
		
	
		
			
				|  |  |  |  |   thread := node.ThreadHandle() | 
		
	
		
			
				|  |  |  |  | func ThreadRestore(ctx * Context, thread *Node, thread_ext *ThreadExt, start bool) error { | 
		
	
		
			
				|  |  |  |  |   context := NewWriteContext(ctx) | 
		
	
		
			
				|  |  |  |  |   return UpdateStates(context, node, NewLockInfo(node, []string{"children"}), func(context *StateContext) error { | 
		
	
		
			
				|  |  |  |  |     return UpdateStates(context, node, LockList(thread.ChildList(), []string{"start"}), func(context *StateContext) error { | 
		
	
		
			
				|  |  |  |  |       for _, info := range(thread.Children) { | 
		
	
		
			
				|  |  |  |  |   return UpdateStates(context, thread, NewACLInfo(thread, []string{"children"}), func(context *StateContext) error { | 
		
	
		
			
				|  |  |  |  |     return UpdateStates(context, thread, ACLList(thread_ext.ChildList(), []string{"start", "state"}), func(context *StateContext) error { | 
		
	
		
			
				|  |  |  |  |       for _, info := range(thread_ext.Children) { | 
		
	
		
			
				|  |  |  |  |         child_ext, err := GetExt[*ThreadExt](info.Child) | 
		
	
		
			
				|  |  |  |  |         if err != nil { | 
		
	
		
			
				|  |  |  |  |           return err | 
		
	
		
			
				|  |  |  |  |         } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |         parent_info := info.Infos["parent"].(*ParentThreadInfo) | 
		
	
		
			
				|  |  |  |  |         if parent_info.Start == true && info.Child.ThreadHandle().StateName != "finished" { | 
		
	
		
			
				|  |  |  |  |           ctx.Log.Logf("thread", "THREAD_RESTORED: %s -> %s", thread.ID(), info.Child.ID()) | 
		
	
		
			
				|  |  |  |  |         if parent_info.Start == true && child_ext.StateName != "finished" { | 
		
	
		
			
				|  |  |  |  |           ctx.Log.Logf("thread", "THREAD_RESTORED: %s -> %s", thread.ID, info.Child.ID) | 
		
	
		
			
				|  |  |  |  |           if start == true { | 
		
	
		
			
				|  |  |  |  |             ChildGo(ctx, thread, info.Child, parent_info.StartAction) | 
		
	
		
			
				|  |  |  |  |             ChildGo(ctx, thread_ext, info.Child, parent_info.StartAction) | 
		
	
		
			
				|  |  |  |  |           } else { | 
		
	
		
			
				|  |  |  |  |             ChildGo(ctx, thread, info.Child, parent_info.RestoreAction) | 
		
	
		
			
				|  |  |  |  |             ChildGo(ctx, thread_ext, info.Child, parent_info.RestoreAction) | 
		
	
		
			
				|  |  |  |  |           } | 
		
	
		
			
				|  |  |  |  |         } | 
		
	
		
			
				|  |  |  |  |       } | 
		
	
	
		
			
				
					|  |  |  | @ -571,73 +502,70 @@ func ThreadRestore(ctx * Context, node ThreadNode, start bool) error { | 
		
	
		
			
				|  |  |  |  | // Helper function to be called during a threads start action, sets the thread state to started
 | 
		
	
		
			
				|  |  |  |  | // Starts a write context, so cannot be called from either a write or read context
 | 
		
	
		
			
				|  |  |  |  | // Returns "wait", nil on success, so the first return value can be ignored safely
 | 
		
	
		
			
				|  |  |  |  | func ThreadStart(ctx * Context, node ThreadNode) (string, error) { | 
		
	
		
			
				|  |  |  |  |   thread := node.ThreadHandle() | 
		
	
		
			
				|  |  |  |  | func ThreadStart(ctx * Context, thread *Node, thread_ext *ThreadExt) (string, error) { | 
		
	
		
			
				|  |  |  |  |   context := NewWriteContext(ctx) | 
		
	
		
			
				|  |  |  |  |   err := UpdateStates(context, node, NewLockInfo(node, []string{"state"}), func(context *StateContext) error { | 
		
	
		
			
				|  |  |  |  |     err := LockLockables(context, map[NodeID]LockableNode{node.ID(): node}, node) | 
		
	
		
			
				|  |  |  |  |   err := UpdateStates(context, thread, NewACLInfo(thread, []string{"state"}), func(context *StateContext) error { | 
		
	
		
			
				|  |  |  |  |     err := LockLockables(context, map[NodeID]*Node{thread.ID: thread}, thread) | 
		
	
		
			
				|  |  |  |  |     if err != nil { | 
		
	
		
			
				|  |  |  |  |       return err | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |     return thread.SetState("started") | 
		
	
		
			
				|  |  |  |  |     return thread_ext.SetState("started") | 
		
	
		
			
				|  |  |  |  |   }) | 
		
	
		
			
				|  |  |  |  |   if err != nil { | 
		
	
		
			
				|  |  |  |  |     return "", err | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  |   context = NewReadContext(ctx) | 
		
	
		
			
				|  |  |  |  |   return "wait", Signal(context, node, node, NewStatusSignal("started", node.ID())) | 
		
	
		
			
				|  |  |  |  |   return "wait", Signal(context, thread, thread, NewStatusSignal("started", thread.ID)) | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | func ThreadWait(ctx * Context, node ThreadNode) (string, error) { | 
		
	
		
			
				|  |  |  |  |   thread := node.ThreadHandle() | 
		
	
		
			
				|  |  |  |  |   ctx.Log.Logf("thread", "THREAD_WAIT: %s - %+v", thread.ID(), thread.ActionQueue) | 
		
	
		
			
				|  |  |  |  | func ThreadWait(ctx * Context, thread *Node, thread_ext *ThreadExt) (string, error) { | 
		
	
		
			
				|  |  |  |  |   ctx.Log.Logf("thread", "THREAD_WAIT: %s - %+v", thread.ID, thread_ext.ActionQueue) | 
		
	
		
			
				|  |  |  |  |   for { | 
		
	
		
			
				|  |  |  |  |     select { | 
		
	
		
			
				|  |  |  |  |       case signal := <- thread.Chan: | 
		
	
		
			
				|  |  |  |  |         ctx.Log.Logf("thread", "THREAD_SIGNAL: %s %+v", thread.ID(), signal) | 
		
	
		
			
				|  |  |  |  |         signal_fn, exists := thread.Handlers[signal.Type()] | 
		
	
		
			
				|  |  |  |  |       case signal := <- thread_ext.SignalChan: | 
		
	
		
			
				|  |  |  |  |         ctx.Log.Logf("thread", "THREAD_SIGNAL: %s %+v", thread.ID, signal) | 
		
	
		
			
				|  |  |  |  |         signal_fn, exists := thread_ext.Handlers[signal.Type()] | 
		
	
		
			
				|  |  |  |  |         if exists == true { | 
		
	
		
			
				|  |  |  |  |           ctx.Log.Logf("thread", "THREAD_HANDLER: %s - %s", thread.ID(), signal.Type()) | 
		
	
		
			
				|  |  |  |  |           return signal_fn(ctx, node, signal) | 
		
	
		
			
				|  |  |  |  |           ctx.Log.Logf("thread", "THREAD_HANDLER: %s - %s", thread.ID, signal.Type()) | 
		
	
		
			
				|  |  |  |  |           return signal_fn(ctx, thread, thread_ext, signal) | 
		
	
		
			
				|  |  |  |  |         } else { | 
		
	
		
			
				|  |  |  |  |           ctx.Log.Logf("thread", "THREAD_NOHANDLER: %s - %s", thread.ID(), signal.Type()) | 
		
	
		
			
				|  |  |  |  |           ctx.Log.Logf("thread", "THREAD_NOHANDLER: %s - %s", thread.ID, signal.Type()) | 
		
	
		
			
				|  |  |  |  |         } | 
		
	
		
			
				|  |  |  |  |       case <- thread.TimeoutChan: | 
		
	
		
			
				|  |  |  |  |       case <- thread_ext.TimeoutChan: | 
		
	
		
			
				|  |  |  |  |         timeout_action := "" | 
		
	
		
			
				|  |  |  |  |         context := NewWriteContext(ctx) | 
		
	
		
			
				|  |  |  |  |         err := UpdateStates(context, node, NewLockMap(NewLockInfo(node, []string{"timeout"})), func(context *StateContext) error { | 
		
	
		
			
				|  |  |  |  |           timeout_action = thread.NextAction.Action | 
		
	
		
			
				|  |  |  |  |           thread.NextAction, thread.TimeoutChan = thread.SoonestAction() | 
		
	
		
			
				|  |  |  |  |         err := UpdateStates(context, thread, NewACLMap(NewACLInfo(thread, []string{"timeout"})), func(context *StateContext) error { | 
		
	
		
			
				|  |  |  |  |           timeout_action = thread_ext.NextAction.Action | 
		
	
		
			
				|  |  |  |  |           thread_ext.NextAction, thread_ext.TimeoutChan = thread_ext.SoonestAction() | 
		
	
		
			
				|  |  |  |  |           return nil | 
		
	
		
			
				|  |  |  |  |         }) | 
		
	
		
			
				|  |  |  |  |         if err != nil { | 
		
	
		
			
				|  |  |  |  |           ctx.Log.Logf("thread", "THREAD_TIMEOUT_ERR: %s - %e", thread.ID(), err) | 
		
	
		
			
				|  |  |  |  |           ctx.Log.Logf("thread", "THREAD_TIMEOUT_ERR: %s - %e", thread.ID, err) | 
		
	
		
			
				|  |  |  |  |         } | 
		
	
		
			
				|  |  |  |  |         ctx.Log.Logf("thread", "THREAD_TIMEOUT %s - NEXT_STATE: %s", thread.ID(), timeout_action) | 
		
	
		
			
				|  |  |  |  |         ctx.Log.Logf("thread", "THREAD_TIMEOUT %s - NEXT_STATE: %s", thread.ID, timeout_action) | 
		
	
		
			
				|  |  |  |  |         return timeout_action, nil | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | func ThreadFinish(ctx *Context, node ThreadNode) (string, error) { | 
		
	
		
			
				|  |  |  |  |   thread := node.ThreadHandle() | 
		
	
		
			
				|  |  |  |  | func ThreadFinish(ctx *Context, thread *Node, thread_ext *ThreadExt) (string, error) { | 
		
	
		
			
				|  |  |  |  |   context := NewWriteContext(ctx) | 
		
	
		
			
				|  |  |  |  |   return "", UpdateStates(context, node, NewLockInfo(node, []string{"state"}), func(context *StateContext) error { | 
		
	
		
			
				|  |  |  |  |     err := thread.SetState("finished") | 
		
	
		
			
				|  |  |  |  |   return "", UpdateStates(context, thread, NewACLInfo(thread, []string{"state"}), func(context *StateContext) error { | 
		
	
		
			
				|  |  |  |  |     err := thread_ext.SetState("finished") | 
		
	
		
			
				|  |  |  |  |     if err != nil { | 
		
	
		
			
				|  |  |  |  |       return err | 
		
	
		
			
				|  |  |  |  |     } | 
		
	
		
			
				|  |  |  |  |     return UnlockLockables(context, map[NodeID]LockableNode{node.ID(): node}, node) | 
		
	
		
			
				|  |  |  |  |     return UnlockLockables(context, map[NodeID]*Node{thread.ID: thread}, thread) | 
		
	
		
			
				|  |  |  |  |   }) | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | var ThreadAbortedError = errors.New("Thread aborted by signal") | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | // Default thread action function for "abort", sends a signal and returns a ThreadAbortedError
 | 
		
	
		
			
				|  |  |  |  | func ThreadAbort(ctx * Context, node ThreadNode, signal GraphSignal) (string, error) { | 
		
	
		
			
				|  |  |  |  | func ThreadAbort(ctx * Context, thread *Node, thread_ext *ThreadExt, signal GraphSignal) (string, error) { | 
		
	
		
			
				|  |  |  |  |   context := NewReadContext(ctx) | 
		
	
		
			
				|  |  |  |  |   err := Signal(context, node, node, NewStatusSignal("aborted", node.ID())) | 
		
	
		
			
				|  |  |  |  |   err := Signal(context, thread, thread, NewStatusSignal("aborted", thread.ID)) | 
		
	
		
			
				|  |  |  |  |   if err != nil { | 
		
	
		
			
				|  |  |  |  |     return "", err | 
		
	
		
			
				|  |  |  |  |   } | 
		
	
	
		
			
				
					|  |  |  | @ -645,9 +573,9 @@ func ThreadAbort(ctx * Context, node ThreadNode, signal GraphSignal) (string, er | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
		
			
				|  |  |  |  | // Default thread action for "stop", sends a signal and returns no error
 | 
		
	
		
			
				|  |  |  |  | func ThreadStop(ctx * Context, node ThreadNode, signal GraphSignal) (string, error) { | 
		
	
		
			
				|  |  |  |  | func ThreadStop(ctx * Context, thread *Node, thread_ext *ThreadExt, signal GraphSignal) (string, error) { | 
		
	
		
			
				|  |  |  |  |   context := NewReadContext(ctx) | 
		
	
		
			
				|  |  |  |  |   err := Signal(context, node, node, NewStatusSignal("stopped", node.ID())) | 
		
	
		
			
				|  |  |  |  |   err := Signal(context, thread, thread, NewStatusSignal("stopped", thread.ID)) | 
		
	
		
			
				|  |  |  |  |   return "finish", err | 
		
	
		
			
				|  |  |  |  | } | 
		
	
		
			
				|  |  |  |  | 
 | 
		
	
	
		
			
				
					|  |  |  | 
 |