2023-06-18 18:33:17 -06:00
|
|
|
package graphvent
|
2023-05-29 19:17:52 -06:00
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
)
|
|
|
|
|
2023-06-23 10:10:25 -06:00
|
|
|
// Link a resource with a child
|
|
|
|
func LinkResource(ctx * GraphContext, resource Resource, child Resource) error {
|
|
|
|
if resource == nil || child == nil {
|
|
|
|
return fmt.Errorf("Will not connect nil to DAG")
|
|
|
|
}
|
|
|
|
_, err := UpdateStates(ctx, []GraphNode{resource, child}, func(states []NodeState) ([]NodeState, interface{}, error) {
|
|
|
|
resource_state := states[0].(ResourceState)
|
|
|
|
child_state := states[1].(ResourceState)
|
|
|
|
|
|
|
|
if checkIfChild(ctx, resource_state, resource.ID(), child_state, child.ID()) == true {
|
|
|
|
return nil, nil, fmt.Errorf("RESOURCE_LINK_ERR: %s is a parent of %s so cannot link as child", child.ID(), resource.ID())
|
2023-06-18 18:11:59 -06:00
|
|
|
}
|
2023-06-04 13:18:10 -06:00
|
|
|
|
2023-06-23 10:10:25 -06:00
|
|
|
resource_state.children = append(resource_state.children, child)
|
|
|
|
child_state.parents = append(child_state.parents, resource)
|
|
|
|
return []NodeState{resource_state, child_state}, nil, nil
|
|
|
|
})
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
// Link multiple children to a resource
|
|
|
|
func LinkResources(ctx * GraphContext, resource Resource, children []Resource) error {
|
|
|
|
if resource == nil || children == nil {
|
|
|
|
return fmt.Errorf("Invalid input")
|
|
|
|
}
|
|
|
|
|
|
|
|
found := map[NodeID]bool{}
|
|
|
|
child_nodes := make([]GraphNode, len(children))
|
|
|
|
for i, child := range(children) {
|
|
|
|
if child == nil {
|
|
|
|
return fmt.Errorf("Will not connect nil to DAG")
|
|
|
|
}
|
|
|
|
_, exists := found[child.ID()]
|
|
|
|
if exists == true {
|
|
|
|
return fmt.Errorf("Will not connect the same child twice")
|
2023-06-18 18:11:59 -06:00
|
|
|
}
|
2023-06-23 10:10:25 -06:00
|
|
|
found[child.ID()] = true
|
|
|
|
child_nodes[i] = child
|
2023-06-18 18:11:59 -06:00
|
|
|
}
|
2023-05-29 19:17:52 -06:00
|
|
|
|
2023-06-23 10:10:25 -06:00
|
|
|
_, err := UpdateStates(ctx, append([]GraphNode{resource}, child_nodes...), func(states []NodeState) ([]NodeState, interface{}, error) {
|
|
|
|
resource_state := states[0].(ResourceState)
|
2023-06-02 17:31:29 -06:00
|
|
|
|
2023-06-23 10:10:25 -06:00
|
|
|
new_states := make([]ResourceState, len(states))
|
|
|
|
for i, state := range(states) {
|
|
|
|
new_states[i] = state.(ResourceState)
|
|
|
|
}
|
2023-06-02 17:31:29 -06:00
|
|
|
|
2023-06-23 10:10:25 -06:00
|
|
|
for i, state := range(states[1:]) {
|
|
|
|
child_state := state.(ResourceState)
|
2023-06-02 17:31:29 -06:00
|
|
|
|
2023-06-23 10:10:25 -06:00
|
|
|
if checkIfChild(ctx, resource_state, resource.ID(), child_state, children[i].ID()) == true {
|
|
|
|
return nil, nil, fmt.Errorf("RESOURCES_LINK_ERR: %s is a parent of %s so cannot link as child", children[i].ID() , resource.ID())
|
|
|
|
}
|
2023-06-20 16:22:22 -06:00
|
|
|
|
2023-06-23 10:10:25 -06:00
|
|
|
new_states[0].children = append(new_states[0].children, children[i])
|
|
|
|
new_states[i+1].parents = append(new_states[i+1].parents, resource)
|
|
|
|
}
|
|
|
|
ret_states := make([]NodeState, len(states))
|
|
|
|
for i, state := range(new_states) {
|
|
|
|
ret_states[i] = state
|
|
|
|
}
|
|
|
|
return ret_states, nil, nil
|
|
|
|
})
|
2023-05-29 19:17:52 -06:00
|
|
|
|
2023-06-23 10:10:25 -06:00
|
|
|
return err
|
2023-06-20 16:22:22 -06:00
|
|
|
}
|
|
|
|
|
2023-06-23 10:10:25 -06:00
|
|
|
type ResourceState struct {
|
|
|
|
name string
|
|
|
|
owner GraphNode
|
|
|
|
children []Resource
|
|
|
|
parents []Resource
|
|
|
|
}
|
2023-05-29 19:17:52 -06:00
|
|
|
|
2023-06-23 10:10:25 -06:00
|
|
|
func (state ResourceState) Serialize() []byte {
|
|
|
|
return []byte(state.name)
|
|
|
|
}
|
2023-06-20 15:48:17 -06:00
|
|
|
|
2023-06-23 10:10:25 -06:00
|
|
|
// Locks cannot be passed between resources, so the answer to
|
|
|
|
// "who used to own this lock held by a resource" is always "nobody"
|
|
|
|
func (state ResourceState) OriginalLockHolder(id NodeID) GraphNode {
|
|
|
|
return nil
|
|
|
|
}
|
2023-06-01 13:11:32 -06:00
|
|
|
|
2023-06-23 10:10:25 -06:00
|
|
|
// Nothing can take a lock from a resource
|
|
|
|
func (state ResourceState) AllowedToTakeLock(id NodeID) bool {
|
2023-06-20 15:48:17 -06:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2023-06-23 10:10:25 -06:00
|
|
|
func (state ResourceState) RecordLockHolder(id NodeID, lock_holder GraphNode) NodeState {
|
|
|
|
if lock_holder != nil {
|
|
|
|
panic("Attempted to delegate a lock to a resource")
|
2023-06-20 15:48:17 -06:00
|
|
|
}
|
|
|
|
|
2023-06-23 10:10:25 -06:00
|
|
|
return state
|
|
|
|
}
|
2023-06-20 15:48:17 -06:00
|
|
|
|
2023-06-23 10:10:25 -06:00
|
|
|
func NewResourceState(name string) ResourceState {
|
|
|
|
return ResourceState{
|
|
|
|
name: name,
|
|
|
|
owner: nil,
|
|
|
|
children: []Resource{},
|
|
|
|
parents: []Resource{},
|
2023-06-20 15:48:17 -06:00
|
|
|
}
|
2023-05-29 19:17:52 -06:00
|
|
|
}
|
|
|
|
|
2023-06-23 10:10:25 -06:00
|
|
|
// Resource represents a Node which can be locked by another node,
|
|
|
|
// and needs to own all it's childrens locks before being locked.
|
|
|
|
// Resource connections form a directed acyclic graph
|
|
|
|
// Resources do not allow any other nodes to take locks from them
|
|
|
|
type Resource interface {
|
|
|
|
GraphNode
|
2023-06-01 13:48:38 -06:00
|
|
|
|
2023-06-23 10:10:25 -06:00
|
|
|
// Called when locking the node to allow for custom lock behaviour
|
|
|
|
Lock(node GraphNode, state NodeState) (NodeState, error)
|
|
|
|
// Called when unlocking the node to allow for custom lock behaviour
|
|
|
|
Unlock(node GraphNode, state NodeState) (NodeState, error)
|
|
|
|
}
|
2023-06-02 17:31:29 -06:00
|
|
|
|
2023-06-23 10:10:25 -06:00
|
|
|
// Resources propagate update up to multiple parents, and not downwards
|
|
|
|
// (subscriber to team won't get update to alliance, but subscriber to alliance will get update to team)
|
|
|
|
func (resource * BaseResource) PropagateUpdate(ctx * GraphContext, signal GraphSignal) {
|
|
|
|
UseStates(ctx, []GraphNode{resource}, func(states []NodeState) (interface{}, error){
|
|
|
|
resource_state := states[0].(ResourceState)
|
|
|
|
if signal.Direction() == Up {
|
|
|
|
// Child->Parent, resource updates parent resources
|
|
|
|
for _, parent := range resource_state.parents {
|
|
|
|
SendUpdate(ctx, parent, signal)
|
|
|
|
}
|
|
|
|
} else if signal.Direction() == Down {
|
|
|
|
// Parent->Child, resource updates lock holder
|
|
|
|
if resource_state.owner != nil {
|
|
|
|
SendUpdate(ctx, resource_state.owner, signal)
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, child := range(resource_state.children) {
|
|
|
|
SendUpdate(ctx, child, signal)
|
|
|
|
}
|
|
|
|
} else if signal.Direction() == Direct {
|
|
|
|
} else {
|
|
|
|
panic(fmt.Sprintf("Invalid signal direction: %d", signal.Direction()))
|
2023-06-01 13:48:38 -06:00
|
|
|
}
|
2023-06-23 10:10:25 -06:00
|
|
|
return nil, nil
|
|
|
|
})
|
|
|
|
}
|
2023-06-02 17:31:29 -06:00
|
|
|
|
2023-06-23 10:10:25 -06:00
|
|
|
func checkIfChild(ctx * GraphContext, r ResourceState, r_id NodeID, cur ResourceState, cur_id NodeID) bool {
|
|
|
|
if r_id == cur_id {
|
|
|
|
return true
|
2023-06-02 17:31:29 -06:00
|
|
|
}
|
|
|
|
|
2023-06-23 10:10:25 -06:00
|
|
|
for _, c := range(cur.children) {
|
|
|
|
val, _ := UseStates(ctx, []GraphNode{c}, func(states []NodeState) (interface{}, error) {
|
|
|
|
child_state := states[0].(ResourceState)
|
|
|
|
return checkIfChild(ctx, cur, cur_id, child_state, c.ID()), nil
|
|
|
|
})
|
2023-06-01 13:48:38 -06:00
|
|
|
|
2023-06-23 10:10:25 -06:00
|
|
|
is_child := val.(bool)
|
|
|
|
if is_child {
|
2023-06-20 22:36:18 -06:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
2023-06-23 10:10:25 -06:00
|
|
|
|
2023-06-20 22:36:18 -06:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2023-06-23 10:10:25 -06:00
|
|
|
func UnlockResource(ctx * GraphContext, resource Resource, node GraphNode, node_state NodeState) (NodeState, error) {
|
|
|
|
if node == nil || resource == nil{
|
|
|
|
panic("Cannot unlock without a specified node and resource")
|
|
|
|
}
|
|
|
|
_, err := UpdateStates(ctx, []GraphNode{resource}, func(states []NodeState) ([]NodeState, interface{}, error) {
|
|
|
|
if resource.ID() == node.ID() {
|
|
|
|
if node_state != nil {
|
|
|
|
panic("node_state must be nil if unlocking resource from itself")
|
|
|
|
}
|
|
|
|
node_state = states[0]
|
|
|
|
}
|
|
|
|
resource_state := states[0].(ResourceState)
|
2023-06-20 22:36:18 -06:00
|
|
|
|
2023-06-23 10:10:25 -06:00
|
|
|
if resource_state.owner == nil {
|
|
|
|
return nil, nil, fmt.Errorf("Resource already unlocked")
|
2023-06-20 22:36:18 -06:00
|
|
|
}
|
2023-06-01 13:11:32 -06:00
|
|
|
|
2023-06-23 10:10:25 -06:00
|
|
|
if resource_state.owner.ID() != node.ID() {
|
|
|
|
return nil, nil, fmt.Errorf("Resource %s not locked by %s", resource.ID(), node.ID())
|
|
|
|
}
|
2023-06-03 01:38:35 -06:00
|
|
|
|
2023-06-23 10:10:25 -06:00
|
|
|
var lock_err error = nil
|
|
|
|
for _, child := range(resource_state.children) {
|
|
|
|
var err error = nil
|
|
|
|
node_state, err = UnlockResource(ctx, child, node, node_state)
|
|
|
|
if err != nil {
|
|
|
|
lock_err = err
|
|
|
|
break
|
|
|
|
}
|
2023-06-01 13:11:32 -06:00
|
|
|
}
|
|
|
|
|
2023-06-23 10:10:25 -06:00
|
|
|
if lock_err != nil {
|
|
|
|
return nil, nil, fmt.Errorf("Resource %s failed to unlock: %e", resource.ID(), lock_err)
|
|
|
|
}
|
2023-06-02 17:31:29 -06:00
|
|
|
|
2023-06-23 10:10:25 -06:00
|
|
|
resource_state.owner = node_state.OriginalLockHolder(resource.ID())
|
|
|
|
unlock_state, err := resource.Unlock(node, resource_state)
|
|
|
|
resource_state = unlock_state.(ResourceState)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, fmt.Errorf("Resource %s failed custom Unlock: %e", resource.ID(), err)
|
|
|
|
}
|
2023-06-02 17:31:29 -06:00
|
|
|
|
2023-06-23 10:10:25 -06:00
|
|
|
if resource_state.owner == nil {
|
|
|
|
ctx.Log.Logf("resource", "RESOURCE_UNLOCK: %s unlocked %s", node.ID(), resource.ID())
|
|
|
|
} else {
|
|
|
|
ctx.Log.Logf("resource", "RESOURCE_UNLOCK: %s passed lock of %s back to %s", node.ID(), resource.ID(), resource_state.owner.ID())
|
|
|
|
}
|
2023-06-01 13:11:32 -06:00
|
|
|
|
2023-06-23 10:10:25 -06:00
|
|
|
return []NodeState{resource_state}, nil, nil
|
|
|
|
})
|
2023-05-29 19:17:52 -06:00
|
|
|
|
2023-06-23 10:10:25 -06:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-05-29 19:17:52 -06:00
|
|
|
|
2023-06-23 10:10:25 -06:00
|
|
|
return node_state, nil
|
2023-06-02 17:31:29 -06:00
|
|
|
}
|
|
|
|
|
2023-06-23 10:10:25 -06:00
|
|
|
// TODO: State
|
|
|
|
func LockResource(ctx * GraphContext, resource Resource, node GraphNode, node_state NodeState) (NodeState, error) {
|
|
|
|
if node == nil || resource == nil {
|
|
|
|
panic("Cannot lock without a specified node and resource")
|
|
|
|
}
|
2023-05-29 19:17:52 -06:00
|
|
|
|
2023-06-23 10:10:25 -06:00
|
|
|
_, err := UpdateStates(ctx, []GraphNode{resource}, func(states []NodeState) ([]NodeState, interface{}, error) {
|
|
|
|
if resource.ID() == node.ID() {
|
|
|
|
if node_state != nil {
|
|
|
|
panic("node_state must be nil if locking resource from itself")
|
|
|
|
}
|
|
|
|
node_state = states[0]
|
|
|
|
}
|
|
|
|
resource_state := states[0].(ResourceState)
|
|
|
|
if resource_state.owner != nil {
|
|
|
|
var lock_pass_allowed bool = false
|
|
|
|
|
|
|
|
if resource_state.owner.ID() == resource.ID() {
|
|
|
|
lock_pass_allowed = resource_state.AllowedToTakeLock(node.ID())
|
|
|
|
} else {
|
|
|
|
tmp, _ := UseStates(ctx, []GraphNode{resource_state.owner}, func(states []NodeState)(interface{}, error){
|
|
|
|
return states[0].AllowedToTakeLock(node.ID()), nil
|
|
|
|
})
|
|
|
|
lock_pass_allowed = tmp.(bool)
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if lock_pass_allowed == false {
|
|
|
|
return nil, nil, fmt.Errorf("%s is not allowed to take a lock from %s", node.ID(), resource_state.owner.ID())
|
|
|
|
}
|
|
|
|
}
|
2023-06-02 17:31:29 -06:00
|
|
|
|
2023-06-23 10:10:25 -06:00
|
|
|
lock_state, err := resource.Lock(node, resource_state)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, fmt.Errorf("Failed to lock resource: %e", err)
|
|
|
|
}
|
2023-06-02 17:31:29 -06:00
|
|
|
|
2023-06-23 10:10:25 -06:00
|
|
|
resource_state = lock_state.(ResourceState)
|
|
|
|
|
|
|
|
var lock_err error = nil
|
|
|
|
locked_resources := []Resource{}
|
|
|
|
for _, child := range(resource_state.children) {
|
|
|
|
node_state, err = LockResource(ctx, child, node, node_state)
|
|
|
|
if err != nil {
|
|
|
|
lock_err = err
|
|
|
|
break
|
|
|
|
}
|
|
|
|
locked_resources = append(locked_resources, child)
|
|
|
|
}
|
2023-05-29 19:17:52 -06:00
|
|
|
|
2023-06-23 10:10:25 -06:00
|
|
|
if lock_err != nil {
|
|
|
|
for _, locked_resource := range(locked_resources) {
|
|
|
|
node_state, err = UnlockResource(ctx, locked_resource, node, node_state)
|
|
|
|
if err != nil {
|
|
|
|
panic(err)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil, nil, fmt.Errorf("Resource failed to lock: %e", lock_err)
|
|
|
|
}
|
2023-05-29 19:17:52 -06:00
|
|
|
|
2023-06-23 10:10:25 -06:00
|
|
|
old_owner := resource_state.owner
|
|
|
|
resource_state.owner = node
|
|
|
|
node_state = node_state.RecordLockHolder(node.ID(), old_owner)
|
2023-05-29 19:17:52 -06:00
|
|
|
|
2023-06-23 10:10:25 -06:00
|
|
|
if old_owner == nil {
|
|
|
|
ctx.Log.Logf("resource", "RESOURCE_LOCK: %s locked %s", node.ID(), resource.ID())
|
|
|
|
} else {
|
|
|
|
ctx.Log.Logf("resource", "RESOURCE_LOCK: %s took lock of %s from %s", node.ID(), resource.ID(), old_owner.ID())
|
|
|
|
}
|
2023-05-29 19:17:52 -06:00
|
|
|
|
2023-06-23 10:10:25 -06:00
|
|
|
return []NodeState{resource_state}, nil, nil
|
|
|
|
})
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2023-05-29 19:17:52 -06:00
|
|
|
|
2023-06-23 10:10:25 -06:00
|
|
|
return node_state, nil
|
2023-05-29 19:17:52 -06:00
|
|
|
}
|
|
|
|
|
2023-06-23 10:10:25 -06:00
|
|
|
// BaseResources represent simple resources in the DAG that can be used to create a hierarchy of locks that store names
|
|
|
|
type BaseResource struct {
|
|
|
|
BaseNode
|
2023-06-20 15:48:17 -06:00
|
|
|
}
|
|
|
|
|
2023-06-23 10:10:25 -06:00
|
|
|
//BaseResources don't check anything special when locking/unlocking
|
|
|
|
func (resource * BaseResource) Lock(node GraphNode, state NodeState) (NodeState, error) {
|
|
|
|
return state, nil
|
|
|
|
}
|
2023-05-29 19:17:52 -06:00
|
|
|
|
2023-06-23 10:10:25 -06:00
|
|
|
func (resource * BaseResource) Unlock(node GraphNode, state NodeState) (NodeState, error) {
|
|
|
|
return state, nil
|
2023-05-29 19:17:52 -06:00
|
|
|
}
|
2023-06-01 22:42:47 -06:00
|
|
|
|
2023-06-23 10:10:25 -06:00
|
|
|
/*func FindResource(root Event, id string) Resource {
|
2023-06-21 12:26:22 -06:00
|
|
|
if root == nil || id == ""{
|
|
|
|
panic("invalid input")
|
|
|
|
}
|
|
|
|
|
|
|
|
for _, resource := range(root.Resources()) {
|
|
|
|
if resource.ID() == id {
|
|
|
|
return resource
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for _, child := range(root.Children()) {
|
|
|
|
resource := FindResource(child, id)
|
|
|
|
if resource != nil {
|
|
|
|
return resource
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return nil
|
2023-06-23 10:10:25 -06:00
|
|
|
}*/
|
2023-06-21 12:26:22 -06:00
|
|
|
|
2023-06-23 10:10:25 -06:00
|
|
|
func NewResource(ctx * GraphContext, name string, children []Resource) (* BaseResource, error) {
|
|
|
|
resource := &BaseResource{
|
|
|
|
BaseNode: NewNode(ctx, RandID(), NewResourceState(name)),
|
2023-06-20 15:48:17 -06:00
|
|
|
}
|
|
|
|
|
2023-06-23 10:10:25 -06:00
|
|
|
err := LinkResources(ctx, resource, children)
|
2023-06-20 15:48:17 -06:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2023-06-23 10:10:25 -06:00
|
|
|
return resource, nil
|
2023-06-01 22:42:47 -06:00
|
|
|
}
|