33
44from temporalio import workflow
55
6+
67@dataclass
78class AssignedResource :
89 resource : str
910
11+
1012@dataclass
1113class AcquireRequest :
1214 workflow_id : str
1315 run_id : str
1416
17+
1518@dataclass
1619class ReleaseRequest :
1720 resource : str
1821 workflow_id : str
1922 run_id : str
2023
24+
2125@dataclass
2226class HandoffRequest :
2327 resource : str
2428 workflow_id : str
2529 old_run_id : str
2630 new_run_id : str
2731
32+
2833LOCK_MANAGER_WORKFLOW_ID = "lock_manager"
2934
35+
3036@dataclass
3137class LockManagerWorkflowInput :
3238 # Key is resource, value is users of the resource. The first item in each list is the current holder of the lease
3339 # on that resource. A similar data structure could allow for multiple holders (perhaps the first n items are the
3440 # current holders).
3541 resource_queues : dict [str , list [AcquireRequest ]]
3642
43+
3744@workflow .defn
3845class LockManagerWorkflow :
3946 def __init__ (self ):
@@ -47,73 +54,117 @@ async def acquire_resource(self, request: AcquireRequest):
4754 for resource in self .resource_queues :
4855 # Naively give out the first free resource, if we have one
4956 if len (self .resource_queues [resource ]) == 0 :
50- workflow .logger .info (f"workflow_id={ request .workflow_id } run_id={ request .run_id } acquired resource { resource } " )
57+ workflow .logger .info (
58+ f"workflow_id={ request .workflow_id } run_id={ request .run_id } acquired resource { resource } "
59+ )
5160 self .resource_queues [resource ].append (request )
52- requester = workflow .get_external_workflow_handle (request .workflow_id , run_id = request .run_id )
61+ requester = workflow .get_external_workflow_handle (
62+ request .workflow_id , run_id = request .run_id
63+ )
5364 await requester .signal ("assign_resource" , AssignedResource (resource ))
5465 return
5566
5667 # Otherwise put this resource in a random queue.
5768 resource = workflow .random ().choice (list (self .resource_queues .keys ()))
58- workflow .logger .info (f"workflow_id={ request .workflow_id } run_id={ request .run_id } is waiting for resource { resource } " )
69+ workflow .logger .info (
70+ f"workflow_id={ request .workflow_id } run_id={ request .run_id } is waiting for resource { resource } "
71+ )
5972 self .resource_queues [resource ].append (request )
6073
6174 @workflow .signal
6275 async def release_resource (self , request : ReleaseRequest ):
6376 queue = self .resource_queues [request .resource ]
6477 if queue is None :
65- workflow .logger .warning (f"Ignoring request from { request .workflow_id } to release non-existent resource: { request .resource } " )
78+ workflow .logger .warning (
79+ f"Ignoring request from { request .workflow_id } to release non-existent resource: { request .resource } "
80+ )
6681 return
6782
6883 if len (queue ) == 0 :
69- workflow .logger .warning (f"Ignoring request from { request .workflow_id } to release resource that is not held: { request .resource } " )
84+ workflow .logger .warning (
85+ f"Ignoring request from { request .workflow_id } to release resource that is not held: { request .resource } "
86+ )
7087 return
7188
7289 holder = queue [0 ]
73- if not (holder .workflow_id == request .workflow_id and holder .run_id == request .run_id ):
74- workflow .logger .warning (f"Ignoring request from non-holder to release resource { request .resource } " )
75- workflow .logger .warning (f"resource is currently held by wf_id={ holder .workflow_id } run_id={ holder .run_id } " )
76- workflow .logger .warning (f"request was from wf_id={ request .workflow_id } run_id={ request .run_id } " )
90+ if not (
91+ holder .workflow_id == request .workflow_id
92+ and holder .run_id == request .run_id
93+ ):
94+ workflow .logger .warning (
95+ f"Ignoring request from non-holder to release resource { request .resource } "
96+ )
97+ workflow .logger .warning (
98+ f"resource is currently held by wf_id={ holder .workflow_id } run_id={ holder .run_id } "
99+ )
100+ workflow .logger .warning (
101+ f"request was from wf_id={ request .workflow_id } run_id={ request .run_id } "
102+ )
77103 return
78104
79105 # Remove the current holder from the head of the queue
80- workflow .logger .info (f"workflow_id={ request .workflow_id } run_id={ request .run_id } released resource { request .resource } " )
106+ workflow .logger .info (
107+ f"workflow_id={ request .workflow_id } run_id={ request .run_id } released resource { request .resource } "
108+ )
81109 queue = queue [1 :]
82110 self .resource_queues [request .resource ] = queue
83111
84112 # If there are queued requests, assign the resource to the next one
85113 if len (queue ) > 0 :
86114 next_holder = queue [0 ]
87- workflow .logger .info (f"workflow_id={ next_holder .workflow_id } run_id={ next_holder .run_id } acquired resource { request .resource } after waiting" )
88- requester = workflow .get_external_workflow_handle (next_holder .workflow_id , run_id = next_holder .run_id )
89- await requester .signal ("assign_resource" , AssignedResource (request .resource ))
115+ workflow .logger .info (
116+ f"workflow_id={ next_holder .workflow_id } run_id={ next_holder .run_id } acquired resource { request .resource } after waiting"
117+ )
118+ requester = workflow .get_external_workflow_handle (
119+ next_holder .workflow_id , run_id = next_holder .run_id
120+ )
121+ await requester .signal (
122+ "assign_resource" , AssignedResource (request .resource )
123+ )
90124
91125 @workflow .signal
92126 async def handoff_resource (self , request : HandoffRequest ):
93127 queue = self .resource_queues [request .resource ]
94128 if queue is None :
95- workflow .logger .warning (f"Ignoring request from { request .workflow_id } to hand off non-existent resource: { request .resource } " )
129+ workflow .logger .warning (
130+ f"Ignoring request from { request .workflow_id } to hand off non-existent resource: { request .resource } "
131+ )
96132 return
97133
98134 if len (queue ) == 0 :
99- workflow .logger .warning (f"Ignoring request from { request .workflow_id } to hand off resource that is not held: { request .resource } " )
135+ workflow .logger .warning (
136+ f"Ignoring request from { request .workflow_id } to hand off resource that is not held: { request .resource } "
137+ )
100138 return
101139
102140 holder = queue [0 ]
103- if not (holder .workflow_id == request .workflow_id and holder .run_id == request .old_run_id ):
104- workflow .logger .warning (f"Ignoring request from non-holder to hand off resource { request .resource } " )
105- workflow .logger .warning (f"resource is currently held by wf_id={ holder .workflow_id } run_id={ holder .run_id } " )
106- workflow .logger .warning (f"request was from wf_id={ request .workflow_id } run_id={ request .old_run_id } " )
141+ if not (
142+ holder .workflow_id == request .workflow_id
143+ and holder .run_id == request .old_run_id
144+ ):
145+ workflow .logger .warning (
146+ f"Ignoring request from non-holder to hand off resource { request .resource } "
147+ )
148+ workflow .logger .warning (
149+ f"resource is currently held by wf_id={ holder .workflow_id } run_id={ holder .run_id } "
150+ )
151+ workflow .logger .warning (
152+ f"request was from wf_id={ request .workflow_id } run_id={ request .old_run_id } "
153+ )
107154 return
108155
109- workflow .logger .info (f"workflow_id={ request .workflow_id } handed off resource { request .resource } from run_id={ request .old_run_id } to run_id={ request .new_run_id } " )
156+ workflow .logger .info (
157+ f"workflow_id={ request .workflow_id } handed off resource { request .resource } from run_id={ request .old_run_id } to run_id={ request .new_run_id } "
158+ )
110159 queue [0 ] = AcquireRequest (request .workflow_id , request .new_run_id )
111- requester = workflow .get_external_workflow_handle (request .workflow_id , run_id = request .new_run_id )
160+ requester = workflow .get_external_workflow_handle (
161+ request .workflow_id , run_id = request .new_run_id
162+ )
112163 await requester .signal ("assign_resource" , AssignedResource (request .resource ))
113164
114165 @workflow .query
115166 def get_current_holders (self ) -> dict [str , AcquireRequest ]:
116- return { k : v [0 ] if v else None for k , v in self .resource_queues .items () }
167+ return {k : v [0 ] if v else None for k , v in self .resource_queues .items ()}
117168
118169 @workflow .run
119170 async def run (self , input : LockManagerWorkflowInput ) -> None :
@@ -125,4 +176,4 @@ async def run(self, input: LockManagerWorkflowInput) -> None:
125176 timeout = timedelta (hours = 12 ),
126177 )
127178
128- workflow .continue_as_new (LockManagerWorkflowInput (self .resource_queues ))
179+ workflow .continue_as_new (LockManagerWorkflowInput (self .resource_queues ))
0 commit comments