|
|
|
@ -4,94 +4,10 @@ import (
|
|
|
|
|
"encoding/json"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// A Listener extension provides a channel that can receive signals on a different thread
|
|
|
|
|
type ListenerExt struct {
|
|
|
|
|
Buffer int
|
|
|
|
|
Chan chan Signal
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Create a new listener extension with a given buffer size
|
|
|
|
|
func NewListenerExt(buffer int) *ListenerExt {
|
|
|
|
|
return &ListenerExt{
|
|
|
|
|
Buffer: buffer,
|
|
|
|
|
Chan: make(chan Signal, buffer),
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (ext *ListenerExt) Field(name string) interface{} {
|
|
|
|
|
return ResolveFields(ext, name, map[string]func(*ListenerExt)interface{}{
|
|
|
|
|
"buffer": func(ext *ListenerExt) interface{} {
|
|
|
|
|
return ext.Buffer
|
|
|
|
|
},
|
|
|
|
|
"chan": func(ext *ListenerExt) interface{} {
|
|
|
|
|
return ext.Chan
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Simple load function, unmarshal the buffer int from json
|
|
|
|
|
func (ext *ListenerExt) Deserialize(ctx *Context, data []byte) error {
|
|
|
|
|
err := json.Unmarshal(data, &ext.Buffer)
|
|
|
|
|
ext.Chan = make(chan Signal, ext.Buffer)
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (listener *ListenerExt) Type() ExtType {
|
|
|
|
|
return ListenerExtType
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Send the signal to the channel, logging an overflow if it occurs
|
|
|
|
|
func (ext *ListenerExt) Process(ctx *Context, node *Node, source NodeID, signal Signal) Messages {
|
|
|
|
|
ctx.Log.Logf("listener", "LISTENER_PROCESS: %s - %+v", node.ID, signal)
|
|
|
|
|
select {
|
|
|
|
|
case ext.Chan <- signal:
|
|
|
|
|
default:
|
|
|
|
|
ctx.Log.Logf("listener", "LISTENER_OVERFLOW: %s", node.ID)
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ReqState holds the multiple states of a requirement
|
|
|
|
|
type LinkState struct {
|
|
|
|
|
Link string `json:"link"`
|
|
|
|
|
Lock string `json:"lock"`
|
|
|
|
|
Initiator NodeID `json:"initiator"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// A LockableExt allows a node to be linked to other nodes(via LinkSignal) and locked/unlocked(via LockSignal)
|
|
|
|
|
type LinkMap map[NodeID]LinkState
|
|
|
|
|
func (m LinkMap) MarshalJSON() ([]byte, error) {
|
|
|
|
|
tmp := map[string]LinkState{}
|
|
|
|
|
for id, state := range(m) {
|
|
|
|
|
tmp[id.String()] = state
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return json.Marshal(tmp)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (m LinkMap) UnmarshalJSON(data []byte) error {
|
|
|
|
|
tmp := map[string]LinkState{}
|
|
|
|
|
err := json.Unmarshal(data, &tmp)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for id_str, state := range(tmp) {
|
|
|
|
|
id, err := ParseID(id_str)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
m[id] = state
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
type LockableExt struct {
|
|
|
|
|
Owner *NodeID `json:"owner"`
|
|
|
|
|
PendingOwner *NodeID `json:"pending_owner"`
|
|
|
|
|
Requirements LinkMap `json:"requirements"`
|
|
|
|
|
Dependencies LinkMap `json:"dependencies"`
|
|
|
|
|
Requirements map[NodeID]string `json:"requirements"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (ext *LockableExt) Field(name string) interface{} {
|
|
|
|
@ -105,16 +21,9 @@ func (ext *LockableExt) Field(name string) interface{} {
|
|
|
|
|
"requirements": func(ext *LockableExt) interface{} {
|
|
|
|
|
return ext.Requirements
|
|
|
|
|
},
|
|
|
|
|
"dependencies": func(ext *LockableExt) interface{} {
|
|
|
|
|
return ext.Dependencies
|
|
|
|
|
},
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (ext *ListenerExt) Serialize() ([]byte, error) {
|
|
|
|
|
return json.Marshal(ext.Buffer)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (ext *LockableExt) Type() ExtType {
|
|
|
|
|
return LockableExtType
|
|
|
|
|
}
|
|
|
|
@ -127,12 +36,15 @@ func (ext *LockableExt) Deserialize(ctx *Context, data []byte) error {
|
|
|
|
|
return json.Unmarshal(data, ext)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func NewLockableExt() *LockableExt {
|
|
|
|
|
func NewLockableExt(requirements []NodeID) *LockableExt {
|
|
|
|
|
reqs := map[NodeID]string{}
|
|
|
|
|
for _, id := range(requirements) {
|
|
|
|
|
reqs[id] = "unlocked"
|
|
|
|
|
}
|
|
|
|
|
return &LockableExt{
|
|
|
|
|
Owner: nil,
|
|
|
|
|
PendingOwner: nil,
|
|
|
|
|
Requirements: map[NodeID]LinkState{},
|
|
|
|
|
Dependencies: map[NodeID]LinkState{},
|
|
|
|
|
Requirements: reqs,
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -150,13 +62,6 @@ func LockLockable(ctx *Context, node *Node) error {
|
|
|
|
|
return ctx.Send(msgs)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Setup a node to send the initial requirement link signal, then send the signal
|
|
|
|
|
func LinkRequirement(ctx *Context, dependency *Node, requirement NodeID) error {
|
|
|
|
|
msgs := Messages{}
|
|
|
|
|
msgs = msgs.Add(dependency.ID, dependency.Key, NewLinkStartSignal("req", requirement), dependency.ID)
|
|
|
|
|
return ctx.Send(msgs)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handle a LockSignal and update the extensions owner/requirement states
|
|
|
|
|
func (ext *LockableExt) HandleLockSignal(log Logger, node *Node, source NodeID, signal *StringSignal) Messages {
|
|
|
|
|
state := signal.Str
|
|
|
|
@ -179,14 +84,11 @@ func (ext *LockableExt) HandleLockSignal(log Logger, node *Node, source NodeID,
|
|
|
|
|
} 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
|
|
|
|
|
messages = messages.Add(node.ID, node.Key, NewLockSignal("unlock"), id)
|
|
|
|
|
if state != "locked" {
|
|
|
|
|
panic("NOT_LOCKED")
|
|
|
|
|
}
|
|
|
|
|
ext.Requirements[id] = "unlocking"
|
|
|
|
|
messages = messages.Add(node.ID, node.Key, NewLockSignal("unlock"), id)
|
|
|
|
|
}
|
|
|
|
|
if source != node.ID {
|
|
|
|
|
messages = messages.Add(node.ID, node.Key, NewLockSignal("unlocking"), source)
|
|
|
|
@ -197,9 +99,7 @@ func (ext *LockableExt) HandleLockSignal(log Logger, node *Node, source NodeID,
|
|
|
|
|
state, exists := ext.Requirements[source]
|
|
|
|
|
if exists == false {
|
|
|
|
|
messages = messages.Add(node.ID, node.Key, NewErrorSignal(signal.ID(), "not_requirement"), source)
|
|
|
|
|
} else if state.Link != "linked" {
|
|
|
|
|
messages = messages.Add(node.ID, node.Key, NewErrorSignal(signal.ID(), "not_linked"), source)
|
|
|
|
|
} else if state.Lock != "unlocking" {
|
|
|
|
|
} else if state != "unlocking" {
|
|
|
|
|
messages = messages.Add(node.ID, node.Key, NewErrorSignal(signal.ID(), "not_unlocking"), source)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -211,27 +111,20 @@ func (ext *LockableExt) HandleLockSignal(log Logger, node *Node, source NodeID,
|
|
|
|
|
state, exists := ext.Requirements[source]
|
|
|
|
|
if exists == false {
|
|
|
|
|
messages = messages.Add(node.ID, node.Key, NewErrorSignal(signal.ID(), "not_requirement"), source)
|
|
|
|
|
} else if state.Link != "linked" {
|
|
|
|
|
messages = messages.Add(node.ID, node.Key, NewErrorSignal(signal.ID(), "not_linked"), source)
|
|
|
|
|
} else if state.Lock != "unlocking" {
|
|
|
|
|
} else if state != "unlocking" {
|
|
|
|
|
messages = messages.Add(node.ID, node.Key, NewErrorSignal(signal.ID(), "not_unlocking"), source)
|
|
|
|
|
} else {
|
|
|
|
|
state.Lock = "unlocked"
|
|
|
|
|
ext.Requirements[source] = state
|
|
|
|
|
ext.Requirements[source] = "unlocked"
|
|
|
|
|
|
|
|
|
|
if ext.PendingOwner == nil {
|
|
|
|
|
linked := 0
|
|
|
|
|
unlocked := 0
|
|
|
|
|
for _, s := range(ext.Requirements) {
|
|
|
|
|
if s.Link == "linked" {
|
|
|
|
|
linked += 1
|
|
|
|
|
}
|
|
|
|
|
if s.Lock == "unlocked" {
|
|
|
|
|
if s == "unlocked" {
|
|
|
|
|
unlocked += 1
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if linked == unlocked {
|
|
|
|
|
if len(ext.Requirements) == unlocked {
|
|
|
|
|
previous_owner := *ext.Owner
|
|
|
|
|
ext.Owner = nil
|
|
|
|
|
messages = messages.Add(node.ID, node.Key, NewLockSignal("unlocked"), previous_owner)
|
|
|
|
@ -246,27 +139,20 @@ func (ext *LockableExt) HandleLockSignal(log Logger, node *Node, source NodeID,
|
|
|
|
|
state, exists := ext.Requirements[source]
|
|
|
|
|
if exists == false {
|
|
|
|
|
messages = messages.Add(node.ID, node.Key, NewErrorSignal(signal.ID(), "not_requirement"), source)
|
|
|
|
|
} else if state.Link != "linked" {
|
|
|
|
|
messages = messages.Add(node.ID, node.Key, NewErrorSignal(signal.ID(), "not_linked"), source)
|
|
|
|
|
} else if state.Lock != "locking" {
|
|
|
|
|
} else if state != "locking" {
|
|
|
|
|
messages = messages.Add(node.ID, node.Key, NewErrorSignal(signal.ID(), "not_locking"), source)
|
|
|
|
|
} else {
|
|
|
|
|
state.Lock = "locked"
|
|
|
|
|
ext.Requirements[source] = state
|
|
|
|
|
ext.Requirements[source] = "locked"
|
|
|
|
|
|
|
|
|
|
if ext.PendingOwner != nil {
|
|
|
|
|
linked := 0
|
|
|
|
|
locked := 0
|
|
|
|
|
for _, s := range(ext.Requirements) {
|
|
|
|
|
if s.Link == "linked" {
|
|
|
|
|
linked += 1
|
|
|
|
|
}
|
|
|
|
|
if s.Lock == "locked" {
|
|
|
|
|
if s == "locked" {
|
|
|
|
|
locked += 1
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if linked == locked {
|
|
|
|
|
if len(ext.Requirements) == locked {
|
|
|
|
|
ext.Owner = ext.PendingOwner
|
|
|
|
|
messages = messages.Add(node.ID, node.Key, NewLockSignal("locked"), *ext.Owner)
|
|
|
|
|
}
|
|
|
|
@ -276,9 +162,7 @@ func (ext *LockableExt) HandleLockSignal(log Logger, node *Node, source NodeID,
|
|
|
|
|
state, exists := ext.Requirements[source]
|
|
|
|
|
if exists == false {
|
|
|
|
|
messages = messages.Add(node.ID, node.Key, NewErrorSignal(signal.ID(), "not_requirement"), source)
|
|
|
|
|
} else if state.Link != "linked" {
|
|
|
|
|
messages = messages.Add(node.ID, node.Key, NewErrorSignal(signal.ID(), "not_linked"), source)
|
|
|
|
|
} else if state.Lock != "locking" {
|
|
|
|
|
} else if state != "locking" {
|
|
|
|
|
messages = messages.Add(node.ID, node.Key, NewErrorSignal(signal.ID(), "not_locking"), source)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@ -296,15 +180,12 @@ func (ext *LockableExt) HandleLockSignal(log Logger, node *Node, source NodeID,
|
|
|
|
|
} else {
|
|
|
|
|
ext.PendingOwner = &owner
|
|
|
|
|
for id, state := range(ext.Requirements) {
|
|
|
|
|
if state.Link == "linked" {
|
|
|
|
|
log.Logf("lockable", "LOCK_REQ: %s sending 'lock' to %s", node.ID, id)
|
|
|
|
|
if state.Lock != "unlocked" {
|
|
|
|
|
panic("NOT_UNLOCKED")
|
|
|
|
|
}
|
|
|
|
|
state.Lock = "locking"
|
|
|
|
|
ext.Requirements[id] = state
|
|
|
|
|
messages = messages.Add(node.ID, node.Key, NewLockSignal("lock"), id)
|
|
|
|
|
log.Logf("lockable", "LOCK_REQ: %s sending 'lock' to %s", node.ID, id)
|
|
|
|
|
if state != "unlocked" {
|
|
|
|
|
panic("NOT_UNLOCKED")
|
|
|
|
|
}
|
|
|
|
|
ext.Requirements[id] = "locking"
|
|
|
|
|
messages = messages.Add(node.ID, node.Key, NewLockSignal("lock"), id)
|
|
|
|
|
}
|
|
|
|
|
if source != node.ID {
|
|
|
|
|
messages = messages.Add(node.ID, node.Key, NewLockSignal("locking"), source)
|
|
|
|
@ -318,119 +199,25 @@ func (ext *LockableExt) HandleLockSignal(log Logger, node *Node, source NodeID,
|
|
|
|
|
return messages
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (ext *LockableExt) HandleLinkStartSignal(log Logger, node *Node, source NodeID, signal *IDStringSignal) Messages {
|
|
|
|
|
link_type := signal.Str
|
|
|
|
|
target := signal.NodeID
|
|
|
|
|
log.Logf("lockable", "LINK_START_SIGNAL: %s->%s %s %s", source, node.ID, link_type, target)
|
|
|
|
|
|
|
|
|
|
messages := Messages{}
|
|
|
|
|
switch link_type {
|
|
|
|
|
case "req":
|
|
|
|
|
state, exists := ext.Requirements[target]
|
|
|
|
|
_, dep_exists := ext.Dependencies[target]
|
|
|
|
|
if ext.Owner != nil {
|
|
|
|
|
messages = messages.Add(node.ID, node.Key, NewErrorSignal(signal.ID(), "already_locked"), source)
|
|
|
|
|
} else if ext.Owner != ext.PendingOwner {
|
|
|
|
|
if ext.PendingOwner == nil {
|
|
|
|
|
messages = messages.Add(node.ID, node.Key, NewErrorSignal(signal.ID(), "unlocking"), source)
|
|
|
|
|
} else {
|
|
|
|
|
messages = messages.Add(node.ID, node.Key, NewErrorSignal(signal.ID(), "locking"), source)
|
|
|
|
|
}
|
|
|
|
|
} else if exists == true {
|
|
|
|
|
if state.Link == "linking" {
|
|
|
|
|
messages = messages.Add(node.ID, node.Key, NewErrorSignal(signal.ID(), "already_linking_req"), source)
|
|
|
|
|
} else if state.Link == "linked" {
|
|
|
|
|
messages = messages.Add(node.ID, node.Key, NewErrorSignal(signal.ID(), "already_req"), source)
|
|
|
|
|
}
|
|
|
|
|
} else if dep_exists == true {
|
|
|
|
|
messages = messages.Add(node.ID, node.Key, NewErrorSignal(signal.ID(), "already_dep"), source)
|
|
|
|
|
} else {
|
|
|
|
|
ext.Requirements[target] = LinkState{"linking", "unlocked", source}
|
|
|
|
|
messages = messages.Add(node.ID, node.Key, NewLinkSignal("linked_as_req"), target)
|
|
|
|
|
messages = messages.Add(node.ID, node.Key, NewLinkStartSignal("linking_req", target), source)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return messages
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Handle LinkSignal, updating the extensions requirements and dependencies as necessary
|
|
|
|
|
// TODO: Add unlink
|
|
|
|
|
func (ext *LockableExt) HandleLinkSignal(log Logger, node *Node, source NodeID, signal *StringSignal) Messages {
|
|
|
|
|
log.Logf("lockable", "LINK_SIGNAL: %s->%s %+v", source, node.ID, signal)
|
|
|
|
|
state := signal.Str
|
|
|
|
|
|
|
|
|
|
messages := Messages{}
|
|
|
|
|
switch state {
|
|
|
|
|
case "dep_done":
|
|
|
|
|
state, exists := ext.Requirements[source]
|
|
|
|
|
if exists == false {
|
|
|
|
|
messages = messages.Add(node.ID, node.Key, NewErrorSignal(signal.ID(), "not_linking"), source)
|
|
|
|
|
} else if state.Link == "linking" {
|
|
|
|
|
state.Link = "linked"
|
|
|
|
|
ext.Requirements[source] = state
|
|
|
|
|
log.Logf("lockable", "FINISHED_LINKING_REQ: %s->%s", node.ID, source)
|
|
|
|
|
}
|
|
|
|
|
case "linked_as_req":
|
|
|
|
|
state, exists := ext.Dependencies[source]
|
|
|
|
|
if exists == false {
|
|
|
|
|
ext.Dependencies[source] = LinkState{"linked", "unlocked", source}
|
|
|
|
|
messages = messages.Add(node.ID, node.Key, NewLinkSignal("dep_done"), source)
|
|
|
|
|
} else if state.Link == "linking" {
|
|
|
|
|
messages = messages.Add(node.ID, node.Key, NewErrorSignal(signal.ID(), "already_linking"), source)
|
|
|
|
|
} else if state.Link == "linked" {
|
|
|
|
|
messages = messages.Add(node.ID, node.Key, NewErrorSignal(signal.ID(), "already_linked"), source)
|
|
|
|
|
} else if ext.PendingOwner != ext.Owner {
|
|
|
|
|
if ext.Owner == nil {
|
|
|
|
|
messages = messages.Add(node.ID, node.Key, NewErrorSignal(signal.ID(), "locking"), source)
|
|
|
|
|
} else {
|
|
|
|
|
messages = messages.Add(node.ID, node.Key, NewErrorSignal(signal.ID(), "unlocking"), source)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
default:
|
|
|
|
|
log.Logf("lockable", "LINK_ERROR: unknown state %s", state)
|
|
|
|
|
}
|
|
|
|
|
return messages
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// LockableExts process Up/Down signals by forwarding them to owner, dependency, and requirement nodes
|
|
|
|
|
// LockSignal and LinkSignal Direct signals are processed to update the requirement/dependency/lock state
|
|
|
|
|
func (ext *LockableExt) Process(ctx *Context, node *Node, source NodeID, signal Signal) Messages {
|
|
|
|
|
messages := Messages{}
|
|
|
|
|
switch signal.Direction() {
|
|
|
|
|
case Up:
|
|
|
|
|
ctx.Log.Logf("lockable", "LOCKABLE_DEPENDENCIES: %+v", ext.Dependencies)
|
|
|
|
|
owner_sent := false
|
|
|
|
|
for dependency, state := range(ext.Dependencies) {
|
|
|
|
|
if state.Link == "linked" {
|
|
|
|
|
messages = messages.Add(node.ID, node.Key, signal, dependency)
|
|
|
|
|
if ext.Owner != nil {
|
|
|
|
|
if dependency == *ext.Owner {
|
|
|
|
|
owner_sent = true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ext.Owner != nil && owner_sent == false {
|
|
|
|
|
if ext.Owner != nil {
|
|
|
|
|
if *ext.Owner != node.ID {
|
|
|
|
|
messages = messages.Add(node.ID, node.Key, signal, *ext.Owner)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
case Down:
|
|
|
|
|
for requirement, state := range(ext.Requirements) {
|
|
|
|
|
if state.Link == "linked" {
|
|
|
|
|
messages = messages.Add(node.ID, node.Key, signal, requirement)
|
|
|
|
|
}
|
|
|
|
|
for requirement, _ := range(ext.Requirements) {
|
|
|
|
|
messages = messages.Add(node.ID, node.Key, signal, requirement)
|
|
|
|
|
}
|
|
|
|
|
case Direct:
|
|
|
|
|
switch signal.Type() {
|
|
|
|
|
case LinkSignalType:
|
|
|
|
|
messages = ext.HandleLinkSignal(ctx.Log, node, source, signal.(*StringSignal))
|
|
|
|
|
case LockSignalType:
|
|
|
|
|
messages = ext.HandleLockSignal(ctx.Log, node, source, signal.(*StringSignal))
|
|
|
|
|
case LinkStartSignalType:
|
|
|
|
|
messages = ext.HandleLinkStartSignal(ctx.Log, node, source, signal.(*IDStringSignal))
|
|
|
|
|
default:
|
|
|
|
|
}
|
|
|
|
|
default:
|
|
|
|
|