55
66from temporalio import activity , workflow
77
8- from resource_locking .sem_workflow import AssignedResource , SEMAPHORE_WORKFLOW_ID , \
8+ from resource_locking .lock_manager_workflow import AssignedResource , LOCK_MANAGER_WORKFLOW_ID , \
99 ReleaseRequest , AcquireRequest , HandoffRequest
1010
11-
1211@dataclass
13- class LoadActivityInput :
12+ class UseResourceActivityInput :
1413 resource : str
1514 iteration : str
1615
1716@activity .defn
18- async def load (input : LoadActivityInput ) -> None :
19- workflow_id = activity .info (). workflow_id
20- print (f"Workflow { workflow_id } starts using { input .resource } the { input .iteration } time" )
21- await asyncio .sleep (5 )
22- print (f"Workflow { workflow_id } finishes using { input .resource } the { input .iteration } time" )
17+ async def use_resource (input : UseResourceActivityInput ) -> None :
18+ info = activity .info ()
19+ activity . logger . info (f"{ info . workflow_id } starts using { input .resource } the { input .iteration } time" )
20+ await asyncio .sleep (3 )
21+ activity . logger . info (f"{ info . workflow_id } done using { input .resource } the { input .iteration } time" )
2322
2423@dataclass
25- class LoadWorkflowInput :
24+ class ResourceLockingWorkflowInput :
2625 # If set, this workflow will fail after the "first", "second", or "third" activity.
2726 iteration_to_fail_after : Optional [str ]
2827
@@ -36,15 +35,13 @@ class LoadWorkflowInput:
3635class FailWorkflowException (Exception ):
3736 pass
3837
38+ # Wait this long for a resource before giving up
3939MAX_RESOURCE_WAIT_TIME = timedelta (minutes = 5 )
4040
41- def has_timeout (timeout : Optional [timedelta ]) -> bool :
42- return timeout is not None and timeout > timedelta (0 )
43-
4441@workflow .defn (
4542 failure_exception_types = [FailWorkflowException ]
4643)
47- class LoadWorkflow :
44+ class ResourceLockingWorkflow :
4845
4946 def __init__ (self ):
5047 self .assigned_resource : Optional [str ] = None
@@ -54,15 +51,15 @@ def handle_assign_resource(self, input: AssignedResource):
5451 self .assigned_resource = input .resource
5552
5653 @workflow .run
57- async def run (self , input : LoadWorkflowInput ):
54+ async def run (self , input : ResourceLockingWorkflowInput ):
5855 workflow .info ()
5956 if has_timeout (workflow .info ().run_timeout ):
6057 # See "locking" comment below for rationale
61- raise FailWorkflowException (f"LoadWorkflow cannot have a run_timeout (found { workflow .info ().run_timeout } )" )
58+ raise FailWorkflowException (f"ResourceLockingWorkflow cannot have a run_timeout (found { workflow .info ().run_timeout } )" )
6259 if has_timeout (workflow .info ().execution_timeout ):
63- raise FailWorkflowException (f"LoadWorkflow cannot have an execution_timeout (found { workflow .info ().execution_timeout } )" )
60+ raise FailWorkflowException (f"ResourceLockingWorkflow cannot have an execution_timeout (found { workflow .info ().execution_timeout } )" )
6461
65- sem_handle = workflow .get_external_workflow_handle (SEMAPHORE_WORKFLOW_ID )
62+ sem_handle = workflow .get_external_workflow_handle (LOCK_MANAGER_WORKFLOW_ID )
6663
6764 info = workflow .info ()
6865 if input .already_owned_resource is None :
@@ -78,13 +75,13 @@ async def run(self, input: LoadWorkflowInput):
7875 raise FailWorkflowException (f"No resource was assigned after { MAX_RESOURCE_WAIT_TIME } " )
7976
8077 # From this point forward, we own the resource. Note that this is a lock, not a lease! Our finally block will
81- # free up the resource if an activity fails. This is why we asserted the lack of workflow-level timeouts
78+ # release the resource if an activity fails. This is why we asserted the lack of workflow-level timeouts
8279 # above - the finally block wouldn't run if there was a timeout.
8380 try :
8481 for iteration in ["first" , "second" , "third" ]:
8582 await workflow .execute_activity (
86- load ,
87- LoadActivityInput (self .assigned_resource , iteration ),
83+ use_resource ,
84+ UseResourceActivityInput (self .assigned_resource , iteration ),
8885 start_to_close_timeout = timedelta (seconds = 10 ),
8986 )
9087
@@ -93,15 +90,20 @@ async def run(self, input: LoadWorkflowInput):
9390 raise FailWorkflowException ()
9491
9592 if input .should_continue_as_new :
96- next_input = LoadWorkflowInput (
93+ next_input = ResourceLockingWorkflowInput (
9794 iteration_to_fail_after = input .iteration_to_fail_after ,
9895 should_continue_as_new = False ,
9996 already_owned_resource = self .assigned_resource ,
10097 )
10198 workflow .continue_as_new (next_input )
10299 finally :
103100 # Only release the resource if we didn't continue-as-new. workflow.continue_as_new raises to halt workflow
104- # execution, but the code in this finally block will still run. It wouldn't successfully send the signal...
101+ # execution, but this code in this finally block will still run. It wouldn't successfully send the signal...
105102 # the if statement just avoids some warnings in the log.
106103 if not input .should_continue_as_new :
107- await sem_handle .signal ("release_resource" , ReleaseRequest (self .assigned_resource , info .workflow_id , info .run_id ))
104+ await sem_handle .signal ("release_resource" , ReleaseRequest (self .assigned_resource , info .workflow_id , info .run_id ))
105+
106+ def has_timeout (timeout : Optional [timedelta ]) -> bool :
107+ # After continue_as_new, timeouts are 0, even if they were None before continue_as_new (and were not set in the
108+ # continue_as_new call).
109+ return timeout is not None and timeout > timedelta (0 )
0 commit comments