graphvent/thread.go

688 lines
19 KiB
Go

package graphvent
import (
"fmt"
"time"
"sync"
"errors"
"reflect"
"encoding/json"
)
2023-07-10 22:31:43 -06:00
// SimpleThread.Signal updates the parent and children, and sends the signal to an internal channel
2023-07-09 15:59:41 -06:00
func (thread * SimpleThread) Signal(ctx * Context, signal GraphSignal, nodes NodeMap) error {
err := thread.SimpleLockable.Signal(ctx, signal, nodes)
if err != nil {
return err
}
if signal.Direction() == Up {
// Child->Parent, thread updates parent and connected requirement
2023-07-09 15:59:41 -06:00
if thread.parent != nil {
UseMoreStates(ctx, []Node{thread.parent}, nodes, func(nodes NodeMap) error {
thread.parent.Signal(ctx, signal, nodes)
return nil
})
}
} else if signal.Direction() == Down {
// Parent->Child, updates children and dependencies
2023-07-09 15:59:41 -06:00
UseMoreStates(ctx, NodeList(thread.children), nodes, func(nodes NodeMap) error {
for _, child := range(thread.children) {
child.Signal(ctx, signal, nodes)
2023-06-23 21:21:14 -06:00
}
return nil
})
} else if signal.Direction() == Direct {
} else {
panic(fmt.Sprintf("Invalid signal direction: %d", signal.Direction()))
}
thread.signal <- signal
2023-07-09 15:59:41 -06:00
return nil
}
2023-07-09 15:59:41 -06:00
// Interface to represent any type of thread information
type ThreadInfo interface {
}
2023-07-09 15:59:41 -06:00
func (thread * SimpleThread) SetTimeout(timeout time.Time, action string) {
thread.timeout = timeout
thread.timeout_action = action
thread.timeout_chan = time.After(time.Until(timeout))
2023-06-23 22:19:43 -06:00
}
2023-07-09 15:59:41 -06:00
func (thread * SimpleThread) TimeoutAction() string {
return thread.timeout_action
}
2023-07-09 15:59:41 -06:00
func (thread * SimpleThread) State() string {
return thread.state_name
}
2023-07-09 15:59:41 -06:00
func (thread * SimpleThread) SetState(new_state string) error {
if new_state == "" {
return fmt.Errorf("Cannot set state to '' with SetState")
}
2023-07-09 15:59:41 -06:00
thread.state_name = new_state
return nil
}
2023-07-09 15:59:41 -06:00
func (thread * SimpleThread) Parent() Thread {
return thread.parent
}
2023-07-09 15:59:41 -06:00
func (thread * SimpleThread) SetParent(parent Thread) {
thread.parent = parent
}
2023-07-09 15:59:41 -06:00
func (thread * SimpleThread) Children() []Thread {
return thread.children
}
2023-07-09 15:59:41 -06:00
func (thread * SimpleThread) Child(id NodeID) Thread {
for _, child := range(thread.children) {
if child.ID() == id {
return child
}
}
return nil
}
2023-07-09 15:59:41 -06:00
func (thread * SimpleThread) ChildInfo(child NodeID) ThreadInfo {
return thread.child_info[child]
}
2023-07-09 15:59:41 -06:00
// Requires thread and childs thread to be locked for write
func UnlinkThreads(ctx * Context, thread Thread, child Thread) error {
var found Node = nil
for _, c := range(thread.Children()) {
if child.ID() == c.ID() {
found = c
break
}
}
if found == nil {
return fmt.Errorf("UNLINK_THREADS_ERR: %s is not a child of %s", child.ID(), thread.ID())
}
2023-07-09 15:59:41 -06:00
child.SetParent(nil)
thread.RemoveChild(child)
return nil
}
2023-07-09 15:59:41 -06:00
func (thread * SimpleThread) RemoveChild(child Thread) {
idx := -1
2023-07-09 15:59:41 -06:00
for i, c := range(thread.children) {
if c.ID() == child.ID() {
idx = i
break
}
}
if idx == -1 {
2023-07-09 15:59:41 -06:00
panic(fmt.Sprintf("%s is not a child of %s", child.ID(), thread.Name()))
}
2023-07-09 15:59:41 -06:00
child_len := len(thread.children)
thread.children[idx] = thread.children[child_len-1]
thread.children = thread.children[0:child_len-1]
}
2023-07-09 15:59:41 -06:00
func (thread * SimpleThread) AddChild(child Thread, info ThreadInfo) error {
if child == nil {
return fmt.Errorf("Will not connect nil to the thread tree")
}
2023-07-09 15:59:41 -06:00
_, exists := thread.child_info[child.ID()]
if exists == true {
return fmt.Errorf("Will not connect the same child twice")
}
2023-07-09 15:59:41 -06:00
if info == nil && thread.InfoType != nil {
return fmt.Errorf("nil info passed when expecting info")
} else if info != nil {
2023-07-09 15:59:41 -06:00
if reflect.TypeOf(info) != thread.InfoType {
2023-07-09 20:30:19 -06:00
return fmt.Errorf("info type mismatch, expecting %+v - %+v", thread.InfoType, reflect.TypeOf(info))
}
}
2023-07-09 15:59:41 -06:00
thread.children = append(thread.children, child)
thread.child_info[child.ID()] = info
return nil
}
2023-07-09 15:59:41 -06:00
func checkIfChild(ctx * Context, target Thread, cur Thread, nodes NodeMap) bool {
for _, child := range(cur.Children()) {
if child.ID() == target.ID() {
return true
}
is_child := false
2023-07-09 15:59:41 -06:00
UpdateMoreStates(ctx, []Node{child}, nodes, func(nodes NodeMap) error {
is_child = checkIfChild(ctx, target, child, nodes)
return nil
})
if is_child {
return true
}
}
return false
}
2023-07-09 15:59:41 -06:00
// Requires thread and childs thread to be locked for write
func LinkThreads(ctx * Context, thread Thread, child Thread, info ThreadInfo, nodes NodeMap) error {
if ctx == nil || thread == nil || child == nil {
return fmt.Errorf("invalid input")
}
if thread.ID() == child.ID() {
return fmt.Errorf("Will not link %s as a child of itself", thread.ID())
}
2023-07-09 15:59:41 -06:00
if child.Parent() != nil {
return fmt.Errorf("EVENT_LINK_ERR: %s already has a parent, cannot link as child", child.ID())
}
2023-07-09 15:59:41 -06:00
if checkIfChild(ctx, thread, child, nodes) == true {
return fmt.Errorf("EVENT_LINK_ERR: %s is a child of %s so cannot add as parent", thread.ID(), child.ID())
}
2023-07-09 15:59:41 -06:00
if checkIfChild(ctx, child, thread, nodes) == true {
return fmt.Errorf("EVENT_LINK_ERR: %s is already a parent of %s so will not add again", thread.ID(), child.ID())
}
2023-07-09 15:59:41 -06:00
err := thread.AddChild(child, info)
if err != nil {
return fmt.Errorf("EVENT_LINK_ERR: error adding %s as child to %s: %e", child.ID(), thread.ID(), err)
}
2023-07-09 15:59:41 -06:00
child.SetParent(thread)
if err != nil {
return err
}
return nil
}
2023-07-09 15:59:41 -06:00
type ThreadAction func(* Context, Thread)(string, error)
type ThreadActions map[string]ThreadAction
type ThreadHandler func(* Context, Thread, GraphSignal)(string, error)
type ThreadHandlers map[string]ThreadHandler
type Thread interface {
2023-07-09 15:59:41 -06:00
// All Threads are Lockables
2023-06-24 19:48:59 -06:00
Lockable
2023-07-09 15:59:41 -06:00
/// State Modification Functions
SetParent(parent Thread)
AddChild(child Thread, info ThreadInfo) error
RemoveChild(child Thread)
SetState(new_thread string) error
SetTimeout(end_time time.Time, action string)
/// State Reading Functions
Parent() Thread
Children() []Thread
Child(id NodeID) Thread
ChildInfo(child NodeID) ThreadInfo
State() string
TimeoutAction() string
2023-07-09 15:59:41 -06:00
/// Functions that dont read/write thread
// Deserialize the attribute map from json.Unmarshal
DeserializeInfo(ctx *Context, data []byte) (ThreadInfo, error)
SetActive(active bool) error
2023-06-23 21:21:14 -06:00
Action(action string) (ThreadAction, bool)
Handler(signal_type string) (ThreadHandler, bool)
2023-07-09 15:59:41 -06:00
// Internal timeout channel for thread
Timeout() <-chan time.Time
2023-07-09 15:59:41 -06:00
// Internal signal channel for thread
SignalChannel() <-chan GraphSignal
2023-07-02 12:14:04 -06:00
ClearTimeout()
ChildWaits() *sync.WaitGroup
2023-07-09 15:59:41 -06:00
}
// Data required by a parent thread to restore it's children
type ParentThreadInfo struct {
Start bool `json:"start"`
StartAction string `json:"start_action"`
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,
}
}
2023-07-09 15:59:41 -06:00
type SimpleThread struct {
SimpleLockable
actions ThreadActions
handlers ThreadHandlers
timeout_chan <-chan time.Time
signal chan GraphSignal
child_waits *sync.WaitGroup
active bool
active_lock *sync.Mutex
state_name string
parent Thread
children []Thread
child_info map[NodeID] ThreadInfo
InfoType reflect.Type
timeout time.Time
timeout_action string
}
2023-07-09 16:03:42 -06:00
func (thread * SimpleThread) Type() NodeType {
return NodeType("simple_thread")
}
2023-07-09 15:59:41 -06:00
func (thread * SimpleThread) Serialize() ([]byte, error) {
thread_json := NewSimpleThreadJSON(thread)
return json.MarshalIndent(&thread_json, "", " ")
}
func (thread * SimpleThread) SignalChannel() <-chan GraphSignal {
return thread.signal
}
type SimpleThreadJSON struct {
Parent *NodeID `json:"parent"`
Children map[NodeID]interface{} `json:"children"`
Timeout time.Time `json:"timeout"`
TimeoutAction string `json:"timeout_action"`
StateName string `json:"state_name"`
SimpleLockableJSON
}
func NewSimpleThreadJSON(thread *SimpleThread) SimpleThreadJSON {
children := map[NodeID]interface{}{}
for _, child := range(thread.children) {
children[child.ID()] = thread.child_info[child.ID()]
}
var parent_id *NodeID = nil
if thread.parent != nil {
new_str := thread.parent.ID()
parent_id = &new_str
}
lockable_json := NewSimpleLockableJSON(&thread.SimpleLockable)
return SimpleThreadJSON{
Parent: parent_id,
Children: children,
Timeout: thread.timeout,
TimeoutAction: thread.timeout_action,
StateName: thread.state_name,
SimpleLockableJSON: lockable_json,
}
}
func LoadSimpleThread(ctx *Context, id NodeID, data []byte, nodes NodeMap) (Node, error) {
var j SimpleThreadJSON
err := json.Unmarshal(data, &j)
if err != nil {
return nil, err
}
thread := NewSimpleThread(id, j.Name, j.StateName, nil, BaseThreadActions, BaseThreadHandlers)
nodes[id] = &thread
err = RestoreSimpleThread(ctx, &thread, j, nodes)
if err != nil {
return nil, err
}
return &thread, nil
}
// SimpleThread as no associated info with children
func (thread * SimpleThread) DeserializeInfo(ctx *Context, data []byte) (ThreadInfo, error) {
if len(data) > 0 {
return nil, fmt.Errorf("SimpleThread expected to deserialize no info but got %d length data: %s", len(data), string(data))
}
return nil, nil
}
func RestoreSimpleThread(ctx *Context, thread Thread, j SimpleThreadJSON, nodes NodeMap) error {
if j.TimeoutAction != "" {
thread.SetTimeout(j.Timeout, j.TimeoutAction)
}
2023-07-09 15:59:41 -06:00
if j.Parent != nil {
p, err := LoadNodeRecurse(ctx, *j.Parent, nodes)
if err != nil {
return err
}
p_t, ok := p.(Thread)
if ok == false {
return err
}
thread.SetParent(p_t)
}
for id, info_raw := range(j.Children) {
child_node, err := LoadNodeRecurse(ctx, id, nodes)
if err != nil {
return err
}
child_t, ok := child_node.(Thread)
if ok == false {
return fmt.Errorf("%+v is not a Thread as expected", child_node)
}
2023-07-11 16:39:47 -06:00
var info_ser []byte
2023-07-11 00:23:07 -06:00
if info_raw != nil {
2023-07-11 16:39:47 -06:00
info_ser, err = json.Marshal(info_raw)
2023-07-11 00:23:07 -06:00
if err != nil {
return err
}
2023-07-11 16:39:47 -06:00
}
2023-07-09 15:59:41 -06:00
2023-07-11 16:39:47 -06:00
parsed_info, err := thread.DeserializeInfo(ctx, info_ser)
if err != nil {
return err
2023-07-09 15:59:41 -06:00
}
thread.AddChild(child_t, parsed_info)
}
return RestoreSimpleLockable(ctx, thread, j.SimpleLockableJSON, nodes)
}
const THREAD_SIGNAL_BUFFER_SIZE = 128
func NewSimpleThread(id NodeID, name string, state_name string, info_type reflect.Type, actions ThreadActions, handlers ThreadHandlers) SimpleThread {
return SimpleThread{
SimpleLockable: NewSimpleLockable(id, name),
2023-07-09 20:30:19 -06:00
InfoType: info_type,
2023-07-09 15:59:41 -06:00
state_name: state_name,
signal: make(chan GraphSignal, THREAD_SIGNAL_BUFFER_SIZE),
children: []Thread{},
child_info: map[NodeID]ThreadInfo{},
actions: actions,
handlers: handlers,
child_waits: &sync.WaitGroup{},
active_lock: &sync.Mutex{},
}
}
2023-07-03 18:08:32 -06:00
// Requires that thread is already locked for read in UseStates
2023-07-09 15:59:41 -06:00
func FindChild(ctx * Context, thread Thread, id NodeID, nodes NodeMap) Thread {
if thread == nil {
panic("cannot recurse through nil")
}
if id == thread.ID() {
return thread
}
2023-07-09 15:59:41 -06:00
for _, child := range thread.Children() {
var result Thread = nil
2023-07-09 15:59:41 -06:00
UseMoreStates(ctx, []Node{child}, nodes, func(nodes NodeMap) error {
result = FindChild(ctx, child, id, nodes)
return nil
})
if result != nil {
return result
}
}
return nil
}
2023-07-09 15:59:41 -06:00
func ChildGo(ctx * Context, thread Thread, child Thread, first_action string) {
thread.ChildWaits().Add(1)
go func(child Thread) {
ctx.Log.Logf("thread", "THREAD_START_CHILD: %s from %s", thread.ID(), child.ID())
defer thread.ChildWaits().Done()
err := ThreadLoop(ctx, child, first_action)
if err != nil {
ctx.Log.Logf("thread", "THREAD_CHILD_RUN_ERR: %s %e", child.ID(), err)
} else {
ctx.Log.Logf("thread", "THREAD_CHILD_RUN_DONE: %s", child.ID())
}
}(child)
}
// Main Loop for Threads
2023-07-09 15:59:41 -06:00
func ThreadLoop(ctx * Context, thread Thread, first_action string) error {
// Start the thread, error if double-started
ctx.Log.Logf("thread", "THREAD_LOOP_START: %s - %s", thread.ID(), first_action)
2023-07-09 15:59:41 -06:00
err := thread.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.Action(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)
2023-06-23 21:21:14 -06:00
next_action, err = action(ctx, thread)
if err != nil {
return err
}
}
2023-07-09 15:59:41 -06:00
err = thread.SetActive(false)
if err != nil {
ctx.Log.Logf("thread", "THREAD_LOOP_STOP_ERR: %e", err)
return err
}
2023-07-09 15:59:41 -06:00
err = UpdateStates(ctx, []Node{thread}, func(nodes NodeMap) error {
err := thread.SetState("finished")
if err != nil {
return err
}
return UnlockLockables(ctx, []Lockable{thread}, thread, nodes)
})
2023-06-26 23:15:40 -06:00
if err != nil {
ctx.Log.Logf("thread", "THREAD_LOOP_UNLOCK_ERR: %e", err)
2023-06-26 23:15:40 -06:00
return err
}
ctx.Log.Logf("thread", "THREAD_LOOP_DONE: %s", thread.ID())
return nil
}
2023-07-09 15:59:41 -06:00
func (thread * SimpleThread) ChildWaits() *sync.WaitGroup {
2023-07-05 14:50:21 -06:00
return thread.child_waits
}
2023-07-09 15:59:41 -06:00
func (thread * SimpleThread) SetActive(active bool) error {
thread.active_lock.Lock()
defer thread.active_lock.Unlock()
2023-07-09 15:59:41 -06:00
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())
}
2023-07-09 15:59:41 -06:00
thread.active = active
return nil
}
2023-07-09 15:59:41 -06:00
func (thread * SimpleThread) Action(action string) (ThreadAction, bool) {
action_fn, exists := thread.actions[action]
return action_fn, exists
}
2023-07-09 15:59:41 -06:00
func (thread * SimpleThread) Handler(signal_type string) (ThreadHandler, bool) {
handler, exists := thread.handlers[signal_type]
2023-06-23 21:21:14 -06:00
return handler, exists
}
2023-07-09 15:59:41 -06:00
func (thread * SimpleThread) Timeout() <-chan time.Time {
2023-07-02 12:14:04 -06:00
return thread.timeout_chan
2023-06-23 21:21:14 -06:00
}
2023-07-09 15:59:41 -06:00
func (thread * SimpleThread) ClearTimeout() {
2023-07-02 12:14:04 -06:00
thread.timeout_chan = nil
2023-06-23 21:21:14 -06:00
}
func (thread * SimpleThread) AllowedToTakeLock(new_owner Lockable, lockable Lockable) bool {
for _, child := range(thread.children) {
if new_owner.ID() == child.ID() {
return true
}
}
return false
}
var ThreadRestore = func(ctx * Context, thread Thread) {
UpdateStates(ctx, []Node{thread}, func(nodes NodeMap)(error) {
return UpdateMoreStates(ctx, NodeList(thread.Children()), nodes, func(nodes NodeMap) error {
for _, child := range(thread.Children()) {
should_run := (thread.ChildInfo(child.ID())).(*ParentThreadInfo)
2023-07-11 17:16:51 -06:00
ctx.Log.Logf("thread", "THREAD_RESTORE: %s -> %s: %+v", thread.ID(), child.ID(), should_run)
if should_run.Start == true && child.State() != "finished" {
2023-07-11 17:16:51 -06:00
ctx.Log.Logf("thread", "THREAD_RESTORED: %s -> %s", thread.ID(), child.ID())
ChildGo(ctx, thread, child, should_run.RestoreAction)
}
}
return nil
})
})
}
2023-07-09 15:59:41 -06:00
var ThreadStart = func(ctx * Context, thread Thread) error {
return UpdateStates(ctx, []Node{thread}, func(nodes NodeMap) error {
owner_id := NodeID("")
2023-07-09 15:59:41 -06:00
if thread.Owner() != nil {
owner_id = thread.Owner().ID()
}
if owner_id != thread.ID() {
err := LockLockables(ctx, []Lockable{thread}, thread, nodes)
if err != nil {
return err
}
}
2023-07-09 15:59:41 -06:00
return thread.SetState("started")
})
}
2023-07-09 15:59:41 -06:00
var ThreadDefaultStart = func(ctx * Context, thread Thread) (string, error) {
ctx.Log.Logf("thread", "THREAD_DEFAULT_START: %s", thread.ID())
err := ThreadStart(ctx, thread)
if err != nil {
return "", err
}
2023-06-23 21:21:14 -06:00
return "wait", nil
}
2023-07-09 15:59:41 -06:00
var ThreadDefaultRestore = func(ctx * Context, thread Thread) (string, error) {
ctx.Log.Logf("thread", "THREAD_DEFAULT_RESTORE: %s", thread.ID())
return "wait", nil
}
2023-07-09 15:59:41 -06:00
var ThreadWait = func(ctx * Context, thread Thread) (string, error) {
2023-06-23 21:21:14 -06:00
ctx.Log.Logf("thread", "THREAD_WAIT: %s TIMEOUT: %+v", thread.ID(), thread.Timeout())
2023-06-24 19:48:59 -06:00
for {
select {
case signal := <- thread.SignalChannel():
if signal.Source() == thread.ID() {
ctx.Log.Logf("thread", "THREAD_SIGNAL_INTERNAL")
} else {
ctx.Log.Logf("thread", "THREAD_SIGNAL: %s %+v", thread.ID(), signal)
2023-06-24 19:48:59 -06:00
}
signal_fn, exists := thread.Handler(signal.Type())
if exists == true {
ctx.Log.Logf("thread", "THREAD_HANDLER: %s - %s", thread.ID(), signal.Type())
return signal_fn(ctx, thread, signal)
} else {
ctx.Log.Logf("thread", "THREAD_NOHANDLER: %s - %s", thread.ID(), signal.Type())
2023-06-24 19:48:59 -06:00
}
case <- thread.Timeout():
2023-07-02 12:14:04 -06:00
timeout_action := ""
2023-07-09 15:59:41 -06:00
err := UpdateStates(ctx, []Node{thread}, func(nodes NodeMap) error {
timeout_action = thread.TimeoutAction()
2023-07-02 12:14:04 -06:00
thread.ClearTimeout()
return nil
})
if err != nil {
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)
return timeout_action, nil
2023-06-24 19:48:59 -06:00
}
}
}
type ThreadAbortedError NodeID
func (e ThreadAbortedError) Is(target error) bool {
error_type := reflect.TypeOf(ThreadAbortedError(""))
target_type := reflect.TypeOf(target)
return error_type == target_type
}
func (e ThreadAbortedError) Error() string {
return fmt.Sprintf("Aborted by %s", string(e))
}
func NewThreadAbortedError(aborter NodeID) ThreadAbortedError {
return ThreadAbortedError(aborter)
}
// Default thread abort is to return a ThreadAbortedError
2023-07-09 15:59:41 -06:00
func ThreadAbort(ctx * Context, thread Thread, signal GraphSignal) (string, error) {
UseStates(ctx, []Node{thread}, func(nodes NodeMap) error {
thread.Signal(ctx, NewSignal(thread, "thread_aborted"), nodes)
return nil
})
return "", NewThreadAbortedError(signal.Source())
2023-06-23 21:21:14 -06:00
}
// Default thread cancel is to finish the thread
2023-07-09 15:59:41 -06:00
func ThreadCancel(ctx * Context, thread Thread, signal GraphSignal) (string, error) {
UseStates(ctx, []Node{thread}, func(nodes NodeMap) error {
thread.Signal(ctx, NewSignal(thread, "thread_cancelled"), nodes)
return nil
})
2023-06-23 21:21:14 -06:00
return "", nil
}
2023-07-02 12:14:04 -06:00
func NewThreadActions() ThreadActions{
actions := ThreadActions{}
for k, v := range(BaseThreadActions) {
actions[k] = v
}
return actions
}
func NewThreadHandlers() ThreadHandlers{
handlers := ThreadHandlers{}
for k, v := range(BaseThreadHandlers) {
handlers[k] = v
}
return handlers
}
var BaseThreadActions = ThreadActions{
"wait": ThreadWait,
"start": ThreadDefaultStart,
"restore": ThreadDefaultRestore,
2023-07-02 12:14:04 -06:00
}
var BaseThreadHandlers = ThreadHandlers{
"abort": ThreadAbort,
"cancel": ThreadCancel,
}