dbos 1.1.0a3__tar.gz → 1.1.0a4__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {dbos-1.1.0a3 → dbos-1.1.0a4}/PKG-INFO +1 -1
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/_context.py +6 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/_error.py +14 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/_sys_db.py +22 -4
- {dbos-1.1.0a3 → dbos-1.1.0a4}/pyproject.toml +1 -1
- {dbos-1.1.0a3 → dbos-1.1.0a4}/tests/test_dbos.py +10 -4
- {dbos-1.1.0a3 → dbos-1.1.0a4}/tests/test_failures.py +6 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/tests/test_queue.py +116 -11
- {dbos-1.1.0a3 → dbos-1.1.0a4}/tests/test_workflow_introspection.py +4 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/LICENSE +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/README.md +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/__init__.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/__main__.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/_admin_server.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/_app_db.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/_classproperty.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/_client.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/_conductor/conductor.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/_conductor/protocol.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/_core.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/_croniter.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/_dbos.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/_dbos_config.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/_debug.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/_docker_pg_helper.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/_event_loop.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/_fastapi.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/_flask.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/_kafka.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/_kafka_message.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/_logger.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/_migrations/env.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/_migrations/script.py.mako +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/_migrations/versions/04ca4f231047_workflow_queues_executor_id.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/_migrations/versions/27ac6900c6ad_add_queue_dedup.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/_migrations/versions/50f3227f0b4b_fix_job_queue.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/_migrations/versions/5c361fc04708_added_system_tables.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/_migrations/versions/83f3732ae8e7_workflow_timeout.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/_migrations/versions/933e86bdac6a_add_queue_priority.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/_migrations/versions/a3b18ad34abe_added_triggers.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/_migrations/versions/d76646551a6b_job_queue_limiter.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/_migrations/versions/d76646551a6c_workflow_queue.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/_migrations/versions/eab0cc1d9a14_job_queue.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/_migrations/versions/f4b9b32ba814_functionname_childid_op_outputs.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/_outcome.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/_queue.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/_recovery.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/_registrations.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/_roles.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/_scheduler.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/_schemas/__init__.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/_schemas/application_database.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/_schemas/system_database.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/_serialization.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/_templates/dbos-db-starter/README.md +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/_templates/dbos-db-starter/__package/__init__.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/_templates/dbos-db-starter/__package/main.py.dbos +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/_templates/dbos-db-starter/__package/schema.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/_templates/dbos-db-starter/alembic.ini +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/_templates/dbos-db-starter/dbos-config.yaml.dbos +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/_templates/dbos-db-starter/migrations/env.py.dbos +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/_templates/dbos-db-starter/migrations/script.py.mako +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/_templates/dbos-db-starter/migrations/versions/2024_07_31_180642_init.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/_templates/dbos-db-starter/start_postgres_docker.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/_tracer.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/_utils.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/_workflow_commands.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/cli/_github_init.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/cli/_template_init.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/cli/cli.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/dbos-config.schema.json +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/py.typed +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/tests/__init__.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/tests/atexit_no_ctor.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/tests/atexit_no_launch.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/tests/classdefs.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/tests/client_collateral.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/tests/client_worker.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/tests/conftest.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/tests/dupname_classdefs1.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/tests/dupname_classdefsa.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/tests/more_classdefs.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/tests/queuedworkflow.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/tests/test_admin_server.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/tests/test_async.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/tests/test_classdecorators.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/tests/test_cli.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/tests/test_client.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/tests/test_concurrency.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/tests/test_config.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/tests/test_croniter.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/tests/test_debug.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/tests/test_docker_secrets.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/tests/test_fastapi.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/tests/test_fastapi_roles.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/tests/test_flask.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/tests/test_kafka.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/tests/test_outcome.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/tests/test_package.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/tests/test_scheduler.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/tests/test_schema_migration.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/tests/test_singleton.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/tests/test_spans.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/tests/test_sqlalchemy.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/tests/test_workflow_management.py +0 -0
- {dbos-1.1.0a3 → dbos-1.1.0a4}/version/__init__.py +0 -0
@@ -392,6 +392,7 @@ class SetWorkflowTimeout:
|
|
392
392
|
else None
|
393
393
|
)
|
394
394
|
self.saved_workflow_timeout: Optional[int] = None
|
395
|
+
self.saved_workflow_deadline_epoch_ms: Optional[int] = None
|
395
396
|
|
396
397
|
def __enter__(self) -> SetWorkflowTimeout:
|
397
398
|
# Code to create a basic context
|
@@ -402,6 +403,8 @@ class SetWorkflowTimeout:
|
|
402
403
|
ctx = assert_current_dbos_context()
|
403
404
|
self.saved_workflow_timeout = ctx.workflow_timeout_ms
|
404
405
|
ctx.workflow_timeout_ms = self.workflow_timeout_ms
|
406
|
+
self.saved_workflow_deadline_epoch_ms = ctx.workflow_deadline_epoch_ms
|
407
|
+
ctx.workflow_deadline_epoch_ms = None
|
405
408
|
return self
|
406
409
|
|
407
410
|
def __exit__(
|
@@ -411,6 +414,9 @@ class SetWorkflowTimeout:
|
|
411
414
|
traceback: Optional[TracebackType],
|
412
415
|
) -> Literal[False]:
|
413
416
|
assert_current_dbos_context().workflow_timeout_ms = self.saved_workflow_timeout
|
417
|
+
assert_current_dbos_context().workflow_deadline_epoch_ms = (
|
418
|
+
self.saved_workflow_deadline_epoch_ms
|
419
|
+
)
|
414
420
|
# Code to clean up the basic context if we created it
|
415
421
|
if self.created_ctx:
|
416
422
|
_clear_local_dbos_context()
|
@@ -62,6 +62,7 @@ class DBOSErrorCode(Enum):
|
|
62
62
|
WorkflowCancelled = 10
|
63
63
|
UnexpectedStep = 11
|
64
64
|
QueueDeduplicated = 12
|
65
|
+
AwaitedWorkflowCancelled = 13
|
65
66
|
ConflictingRegistrationError = 25
|
66
67
|
|
67
68
|
|
@@ -206,6 +207,19 @@ class DBOSQueueDeduplicatedError(DBOSException):
|
|
206
207
|
)
|
207
208
|
|
208
209
|
|
210
|
+
class DBOSAwaitedWorkflowCancelledError(DBOSException):
|
211
|
+
def __init__(self, workflow_id: str):
|
212
|
+
self.workflow_id = workflow_id
|
213
|
+
super().__init__(
|
214
|
+
f"Awaited workflow {workflow_id} was cancelled",
|
215
|
+
dbos_error_code=DBOSErrorCode.AwaitedWorkflowCancelled.value,
|
216
|
+
)
|
217
|
+
|
218
|
+
def __reduce__(self) -> Any:
|
219
|
+
# Tell jsonpickle how to reconstruct this object
|
220
|
+
return (self.__class__, (self.workflow_id,))
|
221
|
+
|
222
|
+
|
209
223
|
#######################################
|
210
224
|
## BaseException
|
211
225
|
#######################################
|
@@ -32,6 +32,7 @@ from dbos._utils import INTERNAL_QUEUE_NAME
|
|
32
32
|
from . import _serialization
|
33
33
|
from ._context import get_local_dbos_context
|
34
34
|
from ._error import (
|
35
|
+
DBOSAwaitedWorkflowCancelledError,
|
35
36
|
DBOSConflictingWorkflowError,
|
36
37
|
DBOSDeadLetterQueueError,
|
37
38
|
DBOSNonExistentWorkflowError,
|
@@ -96,6 +97,10 @@ class WorkflowStatus:
|
|
96
97
|
executor_id: Optional[str]
|
97
98
|
# The application version on which this workflow was started
|
98
99
|
app_version: Optional[str]
|
100
|
+
# The start-to-close timeout of the workflow in ms
|
101
|
+
workflow_timeout_ms: Optional[int]
|
102
|
+
# The deadline of a workflow, computed by adding its timeout to its start time.
|
103
|
+
workflow_deadline_epoch_ms: Optional[int]
|
99
104
|
|
100
105
|
# INTERNAL FIELDS
|
101
106
|
|
@@ -761,9 +766,9 @@ class SystemDatabase:
|
|
761
766
|
error = row[2]
|
762
767
|
raise _serialization.deserialize_exception(error)
|
763
768
|
elif status == WorkflowStatusString.CANCELLED.value:
|
764
|
-
# Raise
|
769
|
+
# Raise AwaitedWorkflowCancelledError here, not the cancellation exception
|
765
770
|
# because the awaiting workflow is not being cancelled.
|
766
|
-
raise
|
771
|
+
raise DBOSAwaitedWorkflowCancelledError(workflow_id)
|
767
772
|
else:
|
768
773
|
pass # CB: I guess we're assuming the WF will show up eventually.
|
769
774
|
time.sleep(1)
|
@@ -837,6 +842,8 @@ class SystemDatabase:
|
|
837
842
|
SystemSchema.workflow_inputs.c.inputs,
|
838
843
|
SystemSchema.workflow_status.c.output,
|
839
844
|
SystemSchema.workflow_status.c.error,
|
845
|
+
SystemSchema.workflow_status.c.workflow_deadline_epoch_ms,
|
846
|
+
SystemSchema.workflow_status.c.workflow_timeout_ms,
|
840
847
|
).join(
|
841
848
|
SystemSchema.workflow_inputs,
|
842
849
|
SystemSchema.workflow_status.c.workflow_uuid
|
@@ -918,6 +925,8 @@ class SystemDatabase:
|
|
918
925
|
info.input = inputs
|
919
926
|
info.output = output
|
920
927
|
info.error = exception
|
928
|
+
info.workflow_deadline_epoch_ms = row[18]
|
929
|
+
info.workflow_timeout_ms = row[19]
|
921
930
|
|
922
931
|
infos.append(info)
|
923
932
|
return infos
|
@@ -947,6 +956,8 @@ class SystemDatabase:
|
|
947
956
|
SystemSchema.workflow_inputs.c.inputs,
|
948
957
|
SystemSchema.workflow_status.c.output,
|
949
958
|
SystemSchema.workflow_status.c.error,
|
959
|
+
SystemSchema.workflow_status.c.workflow_deadline_epoch_ms,
|
960
|
+
SystemSchema.workflow_status.c.workflow_timeout_ms,
|
950
961
|
).select_from(
|
951
962
|
SystemSchema.workflow_queue.join(
|
952
963
|
SystemSchema.workflow_status,
|
@@ -1024,6 +1035,8 @@ class SystemDatabase:
|
|
1024
1035
|
info.input = inputs
|
1025
1036
|
info.output = output
|
1026
1037
|
info.error = exception
|
1038
|
+
info.workflow_deadline_epoch_ms = row[18]
|
1039
|
+
info.workflow_timeout_ms = row[19]
|
1027
1040
|
|
1028
1041
|
infos.append(info)
|
1029
1042
|
|
@@ -1827,8 +1840,13 @@ class SystemDatabase:
|
|
1827
1840
|
# If a timeout is set, set the deadline on dequeue
|
1828
1841
|
workflow_deadline_epoch_ms=sa.case(
|
1829
1842
|
(
|
1830
|
-
|
1831
|
-
|
1843
|
+
sa.and_(
|
1844
|
+
SystemSchema.workflow_status.c.workflow_timeout_ms.isnot(
|
1845
|
+
None
|
1846
|
+
),
|
1847
|
+
SystemSchema.workflow_status.c.workflow_deadline_epoch_ms.is_(
|
1848
|
+
None
|
1849
|
+
),
|
1832
1850
|
),
|
1833
1851
|
sa.func.extract("epoch", sa.func.now()) * 1000
|
1834
1852
|
+ SystemSchema.workflow_status.c.workflow_timeout_ms,
|
@@ -24,6 +24,7 @@ from dbos import (
|
|
24
24
|
# Private API because this is a test
|
25
25
|
from dbos._context import assert_current_dbos_context, get_local_dbos_context
|
26
26
|
from dbos._error import (
|
27
|
+
DBOSAwaitedWorkflowCancelledError,
|
27
28
|
DBOSConflictingRegistrationError,
|
28
29
|
DBOSMaxStepRetriesExceeded,
|
29
30
|
DBOSWorkflowCancelledError,
|
@@ -1507,7 +1508,14 @@ def test_workflow_timeout(dbos: DBOS) -> None:
|
|
1507
1508
|
with SetWorkflowID(wfid):
|
1508
1509
|
blocked_workflow()
|
1509
1510
|
assert assert_current_dbos_context().workflow_deadline_epoch_ms is None
|
1511
|
+
start_time = time.time() * 1000
|
1510
1512
|
handle = DBOS.start_workflow(blocked_workflow)
|
1513
|
+
status = handle.get_status()
|
1514
|
+
assert status.workflow_timeout_ms == 100
|
1515
|
+
assert (
|
1516
|
+
status.workflow_deadline_epoch_ms is not None
|
1517
|
+
and status.workflow_deadline_epoch_ms > start_time
|
1518
|
+
)
|
1511
1519
|
with pytest.raises(DBOSWorkflowCancelledError):
|
1512
1520
|
handle.get_result()
|
1513
1521
|
|
@@ -1555,13 +1563,11 @@ def test_workflow_timeout(dbos: DBOS) -> None:
|
|
1555
1563
|
with pytest.raises(DBOSWorkflowCancelledError):
|
1556
1564
|
parent_workflow()
|
1557
1565
|
|
1558
|
-
with pytest.raises(
|
1566
|
+
with pytest.raises(DBOSAwaitedWorkflowCancelledError):
|
1559
1567
|
DBOS.retrieve_workflow(start_child).get_result()
|
1560
|
-
assert "was cancelled" in str(exc_info.value)
|
1561
1568
|
|
1562
|
-
with pytest.raises(
|
1569
|
+
with pytest.raises(DBOSAwaitedWorkflowCancelledError):
|
1563
1570
|
DBOS.retrieve_workflow(direct_child).get_result()
|
1564
|
-
assert "was cancelled" in str(exc_info.value)
|
1565
1571
|
|
1566
1572
|
# Verify the context variables are set correctly
|
1567
1573
|
with SetWorkflowTimeout(1.0):
|
@@ -9,6 +9,7 @@ from sqlalchemy.exc import InvalidRequestError, OperationalError
|
|
9
9
|
|
10
10
|
from dbos import DBOS, Queue, SetWorkflowID
|
11
11
|
from dbos._error import (
|
12
|
+
DBOSAwaitedWorkflowCancelledError,
|
12
13
|
DBOSDeadLetterQueueError,
|
13
14
|
DBOSMaxStepRetriesExceeded,
|
14
15
|
DBOSNotAuthorizedError,
|
@@ -461,6 +462,11 @@ def test_error_serialization() -> None:
|
|
461
462
|
d = deserialize_exception(serialize_exception(e))
|
462
463
|
assert isinstance(d, DBOSQueueDeduplicatedError)
|
463
464
|
assert str(d) == str(e)
|
465
|
+
# AwaitedWorkflowCancelledError
|
466
|
+
e = DBOSAwaitedWorkflowCancelledError("id")
|
467
|
+
d = deserialize_exception(serialize_exception(e))
|
468
|
+
assert isinstance(d, DBOSAwaitedWorkflowCancelledError)
|
469
|
+
assert str(d) == str(e)
|
464
470
|
|
465
471
|
# Test safe_deserialize
|
466
472
|
class BadException(Exception):
|
@@ -26,6 +26,7 @@ from dbos import (
|
|
26
26
|
)
|
27
27
|
from dbos._context import assert_current_dbos_context
|
28
28
|
from dbos._dbos import WorkflowHandleAsync
|
29
|
+
from dbos._error import DBOSAwaitedWorkflowCancelledError, DBOSWorkflowCancelledError
|
29
30
|
from dbos._schemas.system_database import SystemSchema
|
30
31
|
from dbos._sys_db import WorkflowStatusString
|
31
32
|
from dbos._utils import GlobalParams
|
@@ -853,9 +854,8 @@ def test_cancelling_queued_workflows(dbos: DBOS) -> None:
|
|
853
854
|
|
854
855
|
# Complete the blocked workflow
|
855
856
|
blocking_event.set()
|
856
|
-
with pytest.raises(
|
857
|
+
with pytest.raises(DBOSAwaitedWorkflowCancelledError):
|
857
858
|
blocked_handle.get_result()
|
858
|
-
assert "was cancelled" in str(exc_info.value)
|
859
859
|
|
860
860
|
# Verify all queue entries eventually get cleaned up.
|
861
861
|
assert queue_entries_are_cleaned_up(dbos)
|
@@ -891,9 +891,8 @@ def test_timeout_queue(dbos: DBOS) -> None:
|
|
891
891
|
|
892
892
|
# Verify the blocked workflows are cancelled
|
893
893
|
for handle in handles:
|
894
|
-
with pytest.raises(
|
894
|
+
with pytest.raises(DBOSAwaitedWorkflowCancelledError):
|
895
895
|
handle.get_result()
|
896
|
-
assert "was cancelled" in str(exc_info.value)
|
897
896
|
|
898
897
|
# Verify the normal workflow succeeds
|
899
898
|
normal_handle.get_result()
|
@@ -911,17 +910,14 @@ def test_timeout_queue(dbos: DBOS) -> None:
|
|
911
910
|
|
912
911
|
with SetWorkflowTimeout(1.0):
|
913
912
|
handle = queue.enqueue(parent_workflow)
|
914
|
-
with pytest.raises(
|
913
|
+
with pytest.raises(DBOSAwaitedWorkflowCancelledError):
|
915
914
|
handle.get_result()
|
916
|
-
assert "was cancelled" in str(exc_info.value)
|
917
915
|
|
918
|
-
with pytest.raises(
|
916
|
+
with pytest.raises(DBOSAwaitedWorkflowCancelledError):
|
919
917
|
DBOS.retrieve_workflow(child_id).get_result()
|
920
|
-
assert "was cancelled" in str(exc_info.value)
|
921
918
|
|
922
919
|
# Verify if a parent called with a timeout enqueues a blocked child
|
923
920
|
# then exits the deadline propagates and the child is cancelled.
|
924
|
-
child_id = str(uuid.uuid4())
|
925
921
|
queue = Queue("regular_queue")
|
926
922
|
|
927
923
|
@DBOS.workflow()
|
@@ -931,9 +927,41 @@ def test_timeout_queue(dbos: DBOS) -> None:
|
|
931
927
|
|
932
928
|
with SetWorkflowTimeout(1.0):
|
933
929
|
child_id = exiting_parent_workflow()
|
934
|
-
with pytest.raises(
|
930
|
+
with pytest.raises(DBOSAwaitedWorkflowCancelledError):
|
931
|
+
DBOS.retrieve_workflow(child_id).get_result()
|
932
|
+
|
933
|
+
# Verify if a parent called with a timeout enqueues a child that
|
934
|
+
# never starts because the queue is blocked, the deadline propagates
|
935
|
+
# and both parent and child are cancelled.
|
936
|
+
child_id = str(uuid.uuid4())
|
937
|
+
queue = Queue("stuck_queue", concurrency=1)
|
938
|
+
|
939
|
+
start_event = threading.Event()
|
940
|
+
blocking_event = threading.Event()
|
941
|
+
|
942
|
+
@DBOS.workflow()
|
943
|
+
def stuck_workflow() -> None:
|
944
|
+
start_event.set()
|
945
|
+
blocking_event.wait()
|
946
|
+
|
947
|
+
stuck_handle = queue.enqueue(stuck_workflow)
|
948
|
+
start_event.wait()
|
949
|
+
|
950
|
+
@DBOS.workflow()
|
951
|
+
def blocked_parent_workflow() -> None:
|
952
|
+
with SetWorkflowID(child_id):
|
953
|
+
queue.enqueue(blocking_workflow)
|
954
|
+
while True:
|
955
|
+
DBOS.sleep(0.1)
|
956
|
+
|
957
|
+
with SetWorkflowTimeout(1.0):
|
958
|
+
handle = DBOS.start_workflow(blocked_parent_workflow)
|
959
|
+
with pytest.raises(DBOSWorkflowCancelledError):
|
960
|
+
handle.get_result()
|
961
|
+
with pytest.raises(DBOSAwaitedWorkflowCancelledError):
|
935
962
|
DBOS.retrieve_workflow(child_id).get_result()
|
936
|
-
|
963
|
+
blocking_event.set()
|
964
|
+
stuck_handle.get_result()
|
937
965
|
|
938
966
|
# Verify all queue entries eventually get cleaned up.
|
939
967
|
assert queue_entries_are_cleaned_up(dbos)
|
@@ -1341,3 +1369,80 @@ def test_worker_concurrency_across_versions(dbos: DBOS, client: DBOSClient) -> N
|
|
1341
1369
|
# Change the version, verify the other version complets
|
1342
1370
|
GlobalParams.app_version = other_version
|
1343
1371
|
assert other_version_handle.get_result()
|
1372
|
+
|
1373
|
+
|
1374
|
+
def test_timeout_queue_recovery(dbos: DBOS) -> None:
|
1375
|
+
queue = Queue("test_queue")
|
1376
|
+
evt = threading.Event()
|
1377
|
+
|
1378
|
+
@DBOS.workflow()
|
1379
|
+
def blocking_workflow() -> None:
|
1380
|
+
evt.set()
|
1381
|
+
while True:
|
1382
|
+
DBOS.sleep(0.1)
|
1383
|
+
|
1384
|
+
timeout = 3.0
|
1385
|
+
enqueue_time = time.time()
|
1386
|
+
with SetWorkflowTimeout(timeout):
|
1387
|
+
original_handle = queue.enqueue(blocking_workflow)
|
1388
|
+
|
1389
|
+
# Verify the workflow's timeout is properly configured
|
1390
|
+
evt.wait()
|
1391
|
+
original_status = original_handle.get_status()
|
1392
|
+
assert original_status.workflow_timeout_ms == timeout * 1000
|
1393
|
+
assert (
|
1394
|
+
original_status.workflow_deadline_epoch_ms is not None
|
1395
|
+
and original_status.workflow_deadline_epoch_ms > enqueue_time * 1000
|
1396
|
+
)
|
1397
|
+
|
1398
|
+
# Recover the workflow. Verify its deadline remains the same
|
1399
|
+
evt.clear()
|
1400
|
+
handles = DBOS._recover_pending_workflows()
|
1401
|
+
assert len(handles) == 1
|
1402
|
+
evt.wait()
|
1403
|
+
recovered_handle = handles[0]
|
1404
|
+
recovered_status = recovered_handle.get_status()
|
1405
|
+
assert recovered_status.workflow_timeout_ms == timeout * 1000
|
1406
|
+
assert (
|
1407
|
+
recovered_status.workflow_deadline_epoch_ms
|
1408
|
+
== original_status.workflow_deadline_epoch_ms
|
1409
|
+
)
|
1410
|
+
|
1411
|
+
with pytest.raises(DBOSAwaitedWorkflowCancelledError):
|
1412
|
+
original_handle.get_result()
|
1413
|
+
|
1414
|
+
with pytest.raises(DBOSAwaitedWorkflowCancelledError):
|
1415
|
+
recovered_handle.get_result()
|
1416
|
+
|
1417
|
+
|
1418
|
+
def test_unsetting_timeout(dbos: DBOS) -> None:
|
1419
|
+
|
1420
|
+
queue = Queue("test_queue")
|
1421
|
+
|
1422
|
+
@DBOS.workflow()
|
1423
|
+
def child() -> str:
|
1424
|
+
for _ in range(5):
|
1425
|
+
DBOS.sleep(1)
|
1426
|
+
return DBOS.workflow_id
|
1427
|
+
|
1428
|
+
@DBOS.workflow()
|
1429
|
+
def parent(child_one: str, child_two: str) -> None:
|
1430
|
+
with SetWorkflowID(child_two):
|
1431
|
+
with SetWorkflowTimeout(None):
|
1432
|
+
queue.enqueue(child)
|
1433
|
+
|
1434
|
+
with SetWorkflowID(child_one):
|
1435
|
+
queue.enqueue(child)
|
1436
|
+
|
1437
|
+
child_one, child_two = str(uuid.uuid4()), str(uuid.uuid4())
|
1438
|
+
with SetWorkflowTimeout(1.0):
|
1439
|
+
queue.enqueue(parent, child_one, child_two).get_result()
|
1440
|
+
|
1441
|
+
# Verify child one, which has a propagated timeout, is cancelled
|
1442
|
+
handle: WorkflowHandle[str] = DBOS.retrieve_workflow(child_one)
|
1443
|
+
with pytest.raises(DBOSAwaitedWorkflowCancelledError):
|
1444
|
+
handle.get_result()
|
1445
|
+
|
1446
|
+
# Verify child two, which doesn't have a timeout, succeeds
|
1447
|
+
handle = DBOS.retrieve_workflow(child_two)
|
1448
|
+
assert handle.get_result() == child_two
|
@@ -41,6 +41,8 @@ def test_list_workflow(dbos: DBOS) -> None:
|
|
41
41
|
assert output.app_version == GlobalParams.app_version
|
42
42
|
assert output.app_id == ""
|
43
43
|
assert output.recovery_attempts == 1
|
44
|
+
assert output.workflow_timeout_ms is None
|
45
|
+
assert output.workflow_deadline_epoch_ms is None
|
44
46
|
|
45
47
|
# Test searching by status
|
46
48
|
outputs = DBOS.list_workflows(status="PENDING")
|
@@ -222,6 +224,8 @@ def test_queued_workflows(dbos: DBOS) -> None:
|
|
222
224
|
assert workflow.created_at is not None and workflow.created_at > 0
|
223
225
|
assert workflow.updated_at is not None and workflow.updated_at > 0
|
224
226
|
assert workflow.recovery_attempts == 1
|
227
|
+
assert workflow.workflow_timeout_ms is None
|
228
|
+
assert workflow.workflow_deadline_epoch_ms is None
|
225
229
|
|
226
230
|
# Test sort_desc inverts the order
|
227
231
|
workflows = DBOS.list_queued_workflows(sort_desc=True)
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
{dbos-1.1.0a3 → dbos-1.1.0a4}/dbos/_migrations/versions/04ca4f231047_workflow_queues_executor_id.py
RENAMED
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|