From 027bb74887a900eed1a28aee95a1979dcd8a851d Mon Sep 17 00:00:00 2001 From: Noah Metz Date: Thu, 27 Jul 2023 22:25:00 -0600 Subject: [PATCH] Added unlock, need to update link to match pattern and see if it can be generalized --- lockable.go | 187 ++++++++++++++++++++++++++++++++++++----------- lockable_test.go | 8 +- 2 files changed, 151 insertions(+), 44 deletions(-) diff --git a/lockable.go b/lockable.go index 7d30c13..3b9fb89 100644 --- a/lockable.go +++ b/lockable.go @@ -71,18 +71,25 @@ func NewLockableExt() *LockableExt { return &LockableExt{ Owner: nil, PendingOwner: nil, - Requirements: map[NodeID]string{}, + Requirements: map[NodeID]ReqState{}, Dependencies: map[NodeID]string{}, - LockStates: map[NodeID]string{}, } } +type ReqState struct { + Link string `json:"link"` + Lock string `json:"lock"` +} + type LockableExt struct { Owner *NodeID `json:"owner"` PendingOwner *NodeID `json:"pending_owner"` - Requirements map[NodeID]string `json:"requirements"` + Requirements map[NodeID]ReqState `json:"requirements"` Dependencies map[NodeID]string `json:"dependencies"` - LockStates map[NodeID]string `json:"lock_states"` +} + +func UnlockLockable(ctx *Context, node *Node) error { + return ctx.Send(node.ID, node.ID, NewLockSignal("unlock")) } func LockLockable(ctx *Context, node *Node) error { @@ -105,7 +112,7 @@ func LinkRequirement(ctx *Context, dependency *Node, requirement NodeID) error { return fmt.Errorf("%s is a dependency of %s, cannot link as requirement", requirement, dependency.ID) } - dep_ext.Requirements[requirement] = "start" + dep_ext.Requirements[requirement] = ReqState{"linking", "unlocked"} return ctx.Send(dependency.ID, requirement, NewLinkSignal("req_link")) } @@ -113,55 +120,149 @@ func (ext *LockableExt) HandleLockSignal(ctx *Context, source NodeID, node *Node ctx.Log.Logf("lockable", "LOCK_SIGNAL: %s->%s %+v", source, node.ID, signal) state := signal.State switch state { - case "locked": + case "unlock": + if ext.Owner == nil { + ctx.Send(node.ID, source, NewLockSignal("already_unlocked")) + } else if source != *ext.Owner { + ctx.Send(node.ID, source, NewLockSignal("not_owner")) + } else if ext.PendingOwner == nil { + ctx.Send(node.ID, source, NewLockSignal("already_unlocking")) + } else { + if len(ext.Requirements) == 0 { + ext.Owner = nil + ext.PendingOwner = nil + ctx.Send(node.ID, source, NewLockSignal("unlocked")) + } else { + ext.PendingOwner = nil + for id, state := range(ext.Requirements) { + if state.Link == "linked" { + if state.Lock != "locked" { + panic("NOT_LOCKED") + } + state.Lock = "unlocking" + ext.Requirements[id] = state + ctx.Send(node.ID, id, NewLockSignal("unlock")) + } + } + if source != node.ID { + ctx.Send(node.ID, source, NewLockSignal("unlocking")) + } + } + } + case "unlocking": + state, exists := ext.Requirements[source] + if exists == false { + ctx.Send(node.ID, source, NewLockSignal("not_requirement")) + } else if state.Link != "linked" { + ctx.Send(node.ID, source, NewLockSignal("node_not_linked")) + } else if state.Lock != "unlocking" { + ctx.Send(node.ID, source, NewLockSignal("not_unlocking")) + } + + case "unlocked": if source == node.ID { return } - _, exists := ext.LockStates[source] - if exists == true { - ext.LockStates[source] = "locked" - locked_reqs := 0 - for _, state := range(ext.LockStates) { - if state == "locked" { - locked_reqs += 1 + state, exists := ext.Requirements[source] + if exists == false { + ctx.Send(node.ID, source, NewLockSignal("not_requirement")) + } else if state.Link != "linked" { + ctx.Send(node.ID, source, NewLockSignal("not_linked")) + } else if state.Lock != "unlocking" { + ctx.Send(node.ID, source, NewLockSignal("not_unlocking")) + } else { + state.Lock = "unlocked" + ext.Requirements[source] = state + + if ext.PendingOwner == nil { + linked := 0 + unlocked := 0 + for _, s := range(ext.Requirements) { + if s.Link == "linked" { + linked += 1 + } + if s.Lock == "unlocked" { + unlocked += 1 + } + } + + if linked == unlocked { + previous_owner := *ext.Owner + ext.Owner = nil + ctx.Send(node.ID, previous_owner, NewLockSignal("unlocked")) } } - if len(ext.Requirements) == locked_reqs { - ext.Owner = ext.PendingOwner - ext.PendingOwner = nil - ctx.Send(node.ID, *ext.Owner, NewLockSignal("locked")) - } + } + case "locked": + if source == node.ID { + return + } + + state, exists := ext.Requirements[source] + if exists == false { + ctx.Send(node.ID, source, NewLockSignal("not_requirement")) + } else if state.Link != "linked" { + ctx.Send(node.ID, source, NewLockSignal("not_linked")) + } else if state.Lock != "locking" { + ctx.Send(node.ID, source, NewLockSignal("not_locking")) } else { - ctx.Send(node.ID, source, NewLockSignal("reset")) + state.Lock = "locked" + ext.Requirements[source] = state + + if ext.PendingOwner != nil { + linked := 0 + locked := 0 + for _, s := range(ext.Requirements) { + if s.Link == "linked" { + linked += 1 + } + if s.Lock == "locked" { + locked += 1 + } + } + + if linked == locked { + ext.Owner = ext.PendingOwner + ctx.Send(node.ID, *ext.Owner, NewLockSignal("locked")) + } + } } - case "pending": - state, exists := ext.LockStates[source] - if exists == true && state != "pending" { - delete(ext.LockStates, source) - ctx.Send(node.ID, source, NewLockSignal("reset")) - } else if exists == false { - ctx.Send(node.ID, source, NewLockSignal("reset")) + case "locking": + state, exists := ext.Requirements[source] + if exists == false { + ctx.Send(node.ID, source, NewLockSignal("not_requirement")) + } else if state.Link != "linked" { + ctx.Send(node.ID, source, NewLockSignal("node_not_linked")) + } else if state.Lock != "locking" { + ctx.Send(node.ID, source, NewLockSignal("not_locking")) } + case "lock": if ext.Owner != nil { ctx.Send(node.ID, source, NewLockSignal("already_locked")) - } else if ext.PendingOwner == nil { + } else if ext.PendingOwner != nil { + ctx.Send(node.ID, source, NewLockSignal("already_locking")) + } else { + owner := source if len(ext.Requirements) == 0 { - owner := source ext.Owner = &owner + ext.PendingOwner = ext.Owner ctx.Send(node.ID, source, NewLockSignal("locked")) } else { - pending_owner := source - ext.PendingOwner = &pending_owner + ext.PendingOwner = &owner for id, state := range(ext.Requirements) { - if state == "linked" { - ext.LockStates[id] = "pending" + if state.Link == "linked" { + if state.Lock != "unlocked" { + panic("NOT_UNLOCKED") + } + state.Lock = "locking" + ext.Requirements[id] = state ctx.Send(node.ID, id, NewLockSignal("lock")) } } if source != node.ID { - ctx.Send(node.ID, source, NewLockSignal("pending")) + ctx.Send(node.ID, source, NewLockSignal("locking")) } } } @@ -181,9 +282,9 @@ func (ext *LockableExt) HandleLinkSignal(ctx *Context, source NodeID, node *Node if exists == false { dep_state, exists := ext.Dependencies[source] if exists == false { - ext.Dependencies[source] = "start" + ext.Dependencies[source] = "linking" ctx.Send(node.ID, source, NewLinkSignal("dep_link")) - } else if dep_state == "start" { + } else if dep_state == "linking" { ext.Dependencies[source] = "linked" ctx.Send(node.ID, source, NewLinkSignal("dep_linked")) } @@ -196,10 +297,11 @@ func (ext *LockableExt) HandleLinkSignal(ctx *Context, source NodeID, node *Node if exists == false { req_state, exists := ext.Requirements[source] if exists == false { - ext.Requirements[source] = "start" + ext.Requirements[source] = ReqState{"linking", "unlocked"} ctx.Send(node.ID, source, NewLinkSignal("req_link")) - } else if req_state == "start" { - ext.Requirements[source] = "linked" + } else if req_state.Link == "linking" { + req_state.Link = "linked" + ext.Requirements[source] = req_state ctx.Send(node.ID, source, NewLinkSignal("req_linked")) } } else { @@ -213,15 +315,16 @@ func (ext *LockableExt) HandleLinkSignal(ctx *Context, source NodeID, node *Node case "dep_linked": ctx.Log.Logf("lockable", "%s is a dependency of %s", node.ID, source) req_state, exists := ext.Requirements[source] - if exists == true && req_state == "start" { - ext.Requirements[source] = "linked" + if exists == true && req_state.Link == "linking" { + req_state.Link = "linked" + ext.Requirements[source] = req_state ctx.Send(node.ID, source, NewLinkSignal("req_linked")) } case "req_linked": ctx.Log.Logf("lockable", "%s is a requirement of %s", node.ID, source) dep_state, exists := ext.Dependencies[source] - if exists == true && dep_state == "start" { + if exists == true && dep_state == "linking" { ext.Dependencies[source] = "linked" ctx.Send(node.ID, source, NewLinkSignal("dep_linked")) } @@ -262,7 +365,7 @@ func (ext *LockableExt) Process(ctx *Context, source NodeID, node *Node, signal } case Down: for requirement, state := range(ext.Requirements) { - if state == "linked" { + if state.Link == "linked" { err := ctx.Send(node.ID, requirement, signal) if err != nil { ctx.Log.Logf("signal", "LOCKABLE_SIGNAL_ERR: %s->%s - %e", node.ID, requirement, err) diff --git a/lockable_test.go b/lockable_test.go index 26c1d04..e2f0e17 100644 --- a/lockable_test.go +++ b/lockable_test.go @@ -102,7 +102,11 @@ func TestLock(t *testing.T) { err = LockLockable(ctx, l1) fatalErr(t, err) (*GraphTester)(t).WaitForState(ctx, l1_listener, LockSignalType, "locked", time.Millisecond*10, "No locked") - err = LockLockable(ctx, l0) - fatalErr(t, err) (*GraphTester)(t).WaitForState(ctx, l1_listener, LockSignalType, "locked", time.Millisecond*10, "No locked") + (*GraphTester)(t).WaitForState(ctx, l1_listener, LockSignalType, "locked", time.Millisecond*10, "No locked") + (*GraphTester)(t).WaitForState(ctx, l1_listener, LockSignalType, "locked", time.Millisecond*10, "No locked") + (*GraphTester)(t).WaitForState(ctx, l1_listener, LockSignalType, "locked", time.Millisecond*10, "No locked") + + err = UnlockLockable(ctx, l1) + fatalErr(t, err) }