66
77import logging
88import warnings
9+ from asyncio import (
10+ TimeoutError ,
11+ wait_for ,
12+ )
913from functools import (
1014 reduce ,
1115)
@@ -96,6 +100,7 @@ async def run(
96100 pause_on_disk : bool = False ,
97101 raise_on_error : bool = True ,
98102 return_execution : bool = True ,
103+ timeout : Optional [float ] = None ,
99104 ** kwargs ,
100105 ) -> Union [UUID , SagaExecution ]:
101106 """Perform a run of a ``Saga``.
@@ -114,73 +119,69 @@ async def run(
114119 but with ``Errored`` status.
115120 :param return_execution: If ``True`` the ``SagaExecution`` instance is returned. Otherwise, only the
116121 identifier (``UUID``) is returned.
122+ :param timeout: Maximum execution time in seconds.
117123 :param kwargs: Additional named arguments.
118124 :return: This method does not return anything.
119125 """
120126 if isinstance (definition , SagaDecoratorWrapper ):
121127 definition = definition .meta .definition
122128
123- if response is not None :
124- return await self ._load_and_run (
125- response = response ,
126- autocommit = autocommit ,
127- pause_on_disk = pause_on_disk ,
128- raise_on_error = raise_on_error ,
129- return_execution = return_execution ,
130- ** kwargs ,
131- )
132-
133- return await self ._run_new (
134- definition = definition ,
135- context = context ,
136- user = user ,
129+ if response is None :
130+ execution = await self ._create (definition , context , user )
131+ else :
132+ execution = await self ._load (response )
133+
134+ execution = await self ._run (
135+ execution ,
136+ timeout = timeout ,
137+ response = response ,
137138 autocommit = autocommit ,
138139 pause_on_disk = pause_on_disk ,
139140 raise_on_error = raise_on_error ,
140- return_execution = return_execution ,
141141 ** kwargs ,
142142 )
143+ if return_execution :
144+ return execution
143145
144- async def _run_new (
145- self , definition : Saga , context : Optional [SagaContext ] = None , user : Optional [UUID ] = None , ** kwargs
146- ) -> Union [UUID , SagaExecution ]:
146+ return execution .uuid
147+
148+ @staticmethod
149+ async def _create (definition : Saga , context : Optional [SagaContext ], user : Optional [UUID ]) -> SagaExecution :
147150 if REQUEST_USER_CONTEXT_VAR .get () is not None :
148151 if user is not None :
149152 warnings .warn ("The `user` Argument will be ignored in favor of the `user` ContextVar" , RuntimeWarning )
150153 user = REQUEST_USER_CONTEXT_VAR .get ()
151154
152- execution = SagaExecution .from_definition (definition , context = context , user = user )
153- return await self ._run (execution , ** kwargs )
155+ return SagaExecution .from_definition (definition , context = context , user = user )
154156
155- async def _load_and_run (self , response : SagaResponse , ** kwargs ) -> Union [UUID , SagaExecution ]:
156- execution = await self .storage .load (response .uuid )
157- return await self ._run (execution , response = response , ** kwargs )
157+ async def _load (self , response : SagaResponse ) -> Union [UUID , SagaExecution ]:
158+ return await self .storage .load (response .uuid )
158159
159- async def _run (
160- self ,
161- execution : SagaExecution ,
162- pause_on_disk : bool = False ,
163- raise_on_error : bool = True ,
164- return_execution : bool = True ,
165- ** kwargs ,
166- ) -> Union [UUID , SagaExecution ]:
160+ async def _run (self , execution : SagaExecution , raise_on_error : bool , ** kwargs ) -> SagaExecution :
167161 try :
168- if pause_on_disk :
169- await self ._run_with_pause_on_disk (execution , ** kwargs )
170- else :
171- await self ._run_with_pause_on_memory (execution , ** kwargs )
162+ await self ._run_with_timeout (execution , ** kwargs )
172163 except SagaFailedExecutionException as exc :
173164 if raise_on_error :
174165 raise exc
175- logger .exception (f"The execution identified by { execution .uuid !s} failed" )
166+ logger .exception (f"The execution identified by { execution .uuid !s} failed: { exc . exception !r } " )
176167 finally :
177168 await self .storage .store (execution )
178169 self ._update_request_headers (execution )
179170
180- if return_execution :
181- return execution
171+ return execution
182172
183- return execution .uuid
173+ async def _run_with_timeout (self , execution : SagaExecution , timeout : Optional [float ], ** kwargs ) -> None :
174+ future = self ._run_with_pause (execution , ** kwargs )
175+ try :
176+ return await wait_for (future , timeout = timeout )
177+ except TimeoutError as exc :
178+ raise SagaFailedExecutionException (exc )
179+
180+ async def _run_with_pause (self , execution : SagaExecution , pause_on_disk : bool , ** kwargs ) -> None :
181+ if pause_on_disk :
182+ await self ._run_with_pause_on_disk (execution , ** kwargs )
183+ else :
184+ await self ._run_with_pause_on_memory (execution , ** kwargs )
184185
185186 @staticmethod
186187 def _update_request_headers (execution : SagaExecution ) -> None :
@@ -197,9 +198,11 @@ def _update_request_headers(execution: SagaExecution) -> None:
197198 headers ["related_services" ] = "," .join (related_services )
198199
199200 @staticmethod
200- async def _run_with_pause_on_disk (execution : SagaExecution , autocommit : bool = True , ** kwargs ) -> None :
201+ async def _run_with_pause_on_disk (
202+ execution : SagaExecution , response : Optional [SagaResponse ] = None , autocommit : bool = True , ** kwargs
203+ ) -> None :
201204 try :
202- await execution .execute (autocommit = False , ** kwargs )
205+ await execution .execute (autocommit = False , response = response , ** kwargs )
203206 if autocommit :
204207 await execution .commit (** kwargs )
205208 except SagaPausedExecutionStepException :
0 commit comments