dbos 0.23.0a9__tar.gz → 0.23.0a10__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.

Potentially problematic release.


This version of dbos might be problematic. Click here for more details.

Files changed (95) hide show
  1. {dbos-0.23.0a9 → dbos-0.23.0a10}/PKG-INFO +1 -1
  2. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/_recovery.py +12 -6
  3. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/_sys_db.py +26 -12
  4. {dbos-0.23.0a9 → dbos-0.23.0a10}/pyproject.toml +1 -1
  5. {dbos-0.23.0a9 → dbos-0.23.0a10}/tests/test_queue.py +35 -1
  6. {dbos-0.23.0a9 → dbos-0.23.0a10}/LICENSE +0 -0
  7. {dbos-0.23.0a9 → dbos-0.23.0a10}/README.md +0 -0
  8. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/__init__.py +0 -0
  9. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/__main__.py +0 -0
  10. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/_admin_server.py +0 -0
  11. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/_app_db.py +0 -0
  12. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/_classproperty.py +0 -0
  13. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/_cloudutils/authentication.py +0 -0
  14. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/_cloudutils/cloudutils.py +0 -0
  15. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/_cloudutils/databases.py +0 -0
  16. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/_context.py +0 -0
  17. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/_core.py +0 -0
  18. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/_croniter.py +0 -0
  19. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/_db_wizard.py +0 -0
  20. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/_dbos.py +0 -0
  21. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/_dbos_config.py +0 -0
  22. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/_debug.py +0 -0
  23. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/_error.py +0 -0
  24. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/_fastapi.py +0 -0
  25. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/_flask.py +0 -0
  26. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/_kafka.py +0 -0
  27. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/_kafka_message.py +0 -0
  28. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/_logger.py +0 -0
  29. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/_migrations/env.py +0 -0
  30. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/_migrations/script.py.mako +0 -0
  31. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/_migrations/versions/04ca4f231047_workflow_queues_executor_id.py +0 -0
  32. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/_migrations/versions/50f3227f0b4b_fix_job_queue.py +0 -0
  33. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/_migrations/versions/5c361fc04708_added_system_tables.py +0 -0
  34. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/_migrations/versions/a3b18ad34abe_added_triggers.py +0 -0
  35. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/_migrations/versions/d76646551a6b_job_queue_limiter.py +0 -0
  36. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/_migrations/versions/d76646551a6c_workflow_queue.py +0 -0
  37. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/_migrations/versions/eab0cc1d9a14_job_queue.py +0 -0
  38. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/_outcome.py +0 -0
  39. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/_queue.py +0 -0
  40. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/_registrations.py +0 -0
  41. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/_request.py +0 -0
  42. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/_roles.py +0 -0
  43. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/_scheduler.py +0 -0
  44. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/_schemas/__init__.py +0 -0
  45. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/_schemas/application_database.py +0 -0
  46. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/_schemas/system_database.py +0 -0
  47. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/_serialization.py +0 -0
  48. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/_templates/dbos-db-starter/README.md +0 -0
  49. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/_templates/dbos-db-starter/__package/__init__.py +0 -0
  50. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/_templates/dbos-db-starter/__package/main.py +0 -0
  51. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/_templates/dbos-db-starter/__package/schema.py +0 -0
  52. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/_templates/dbos-db-starter/alembic.ini +0 -0
  53. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/_templates/dbos-db-starter/dbos-config.yaml.dbos +0 -0
  54. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/_templates/dbos-db-starter/migrations/env.py.dbos +0 -0
  55. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/_templates/dbos-db-starter/migrations/script.py.mako +0 -0
  56. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/_templates/dbos-db-starter/migrations/versions/2024_07_31_180642_init.py +0 -0
  57. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/_templates/dbos-db-starter/start_postgres_docker.py +0 -0
  58. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/_tracer.py +0 -0
  59. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/_utils.py +0 -0
  60. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/_workflow_commands.py +0 -0
  61. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/cli/_github_init.py +0 -0
  62. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/cli/_template_init.py +0 -0
  63. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/cli/cli.py +0 -0
  64. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/dbos-config.schema.json +0 -0
  65. {dbos-0.23.0a9 → dbos-0.23.0a10}/dbos/py.typed +0 -0
  66. {dbos-0.23.0a9 → dbos-0.23.0a10}/tests/__init__.py +0 -0
  67. {dbos-0.23.0a9 → dbos-0.23.0a10}/tests/atexit_no_ctor.py +0 -0
  68. {dbos-0.23.0a9 → dbos-0.23.0a10}/tests/atexit_no_launch.py +0 -0
  69. {dbos-0.23.0a9 → dbos-0.23.0a10}/tests/classdefs.py +0 -0
  70. {dbos-0.23.0a9 → dbos-0.23.0a10}/tests/conftest.py +0 -0
  71. {dbos-0.23.0a9 → dbos-0.23.0a10}/tests/more_classdefs.py +0 -0
  72. {dbos-0.23.0a9 → dbos-0.23.0a10}/tests/queuedworkflow.py +0 -0
  73. {dbos-0.23.0a9 → dbos-0.23.0a10}/tests/test_admin_server.py +0 -0
  74. {dbos-0.23.0a9 → dbos-0.23.0a10}/tests/test_async.py +0 -0
  75. {dbos-0.23.0a9 → dbos-0.23.0a10}/tests/test_classdecorators.py +0 -0
  76. {dbos-0.23.0a9 → dbos-0.23.0a10}/tests/test_concurrency.py +0 -0
  77. {dbos-0.23.0a9 → dbos-0.23.0a10}/tests/test_config.py +0 -0
  78. {dbos-0.23.0a9 → dbos-0.23.0a10}/tests/test_croniter.py +0 -0
  79. {dbos-0.23.0a9 → dbos-0.23.0a10}/tests/test_dbos.py +0 -0
  80. {dbos-0.23.0a9 → dbos-0.23.0a10}/tests/test_debug.py +0 -0
  81. {dbos-0.23.0a9 → dbos-0.23.0a10}/tests/test_failures.py +0 -0
  82. {dbos-0.23.0a9 → dbos-0.23.0a10}/tests/test_fastapi.py +0 -0
  83. {dbos-0.23.0a9 → dbos-0.23.0a10}/tests/test_fastapi_roles.py +0 -0
  84. {dbos-0.23.0a9 → dbos-0.23.0a10}/tests/test_flask.py +0 -0
  85. {dbos-0.23.0a9 → dbos-0.23.0a10}/tests/test_kafka.py +0 -0
  86. {dbos-0.23.0a9 → dbos-0.23.0a10}/tests/test_outcome.py +0 -0
  87. {dbos-0.23.0a9 → dbos-0.23.0a10}/tests/test_package.py +0 -0
  88. {dbos-0.23.0a9 → dbos-0.23.0a10}/tests/test_scheduler.py +0 -0
  89. {dbos-0.23.0a9 → dbos-0.23.0a10}/tests/test_schema_migration.py +0 -0
  90. {dbos-0.23.0a9 → dbos-0.23.0a10}/tests/test_singleton.py +0 -0
  91. {dbos-0.23.0a9 → dbos-0.23.0a10}/tests/test_spans.py +0 -0
  92. {dbos-0.23.0a9 → dbos-0.23.0a10}/tests/test_sqlalchemy.py +0 -0
  93. {dbos-0.23.0a9 → dbos-0.23.0a10}/tests/test_workflow_cancel.py +0 -0
  94. {dbos-0.23.0a9 → dbos-0.23.0a10}/tests/test_workflow_cmds.py +0 -0
  95. {dbos-0.23.0a9 → dbos-0.23.0a10}/version/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dbos
3
- Version: 0.23.0a9
3
+ Version: 0.23.0a10
4
4
  Summary: Ultra-lightweight durable execution in Python
5
5
  Author-Email: "DBOS, Inc." <contact@dbos.dev>
6
6
  License: MIT
@@ -27,8 +27,9 @@ def startup_recovery_thread(
27
27
  pending_workflow.queue_name
28
28
  and pending_workflow.queue_name != "_dbos_internal_queue"
29
29
  ):
30
- dbos._sys_db.clear_queue_assignment(pending_workflow.workflow_uuid)
31
- continue
30
+ cleared = dbos._sys_db.clear_queue_assignment(pending_workflow.workflow_uuid)
31
+ if cleared:
32
+ continue
32
33
  execute_workflow_by_id(dbos, pending_workflow.workflow_uuid)
33
34
  pending_workflows.remove(pending_workflow)
34
35
  except DBOSWorkflowFunctionNotFoundError:
@@ -56,10 +57,15 @@ def recover_pending_workflows(
56
57
  and pending_workflow.queue_name != "_dbos_internal_queue"
57
58
  ):
58
59
  try:
59
- dbos._sys_db.clear_queue_assignment(pending_workflow.workflow_uuid)
60
- workflow_handles.append(
61
- dbos.retrieve_workflow(pending_workflow.workflow_uuid)
62
- )
60
+ cleared = dbos._sys_db.clear_queue_assignment(pending_workflow.workflow_uuid)
61
+ if cleared:
62
+ workflow_handles.append(
63
+ dbos.retrieve_workflow(pending_workflow.workflow_uuid)
64
+ )
65
+ else:
66
+ workflow_handles.append(
67
+ execute_workflow_by_id(dbos, pending_workflow.workflow_uuid)
68
+ )
63
69
  except Exception as e:
64
70
  dbos.logger.error(e)
65
71
  else:
@@ -1460,21 +1460,35 @@ class SystemDatabase:
1460
1460
  .values(completed_at_epoch_ms=int(time.time() * 1000))
1461
1461
  )
1462
1462
 
1463
- def clear_queue_assignment(self, workflow_id: str) -> None:
1463
+
1464
+ def clear_queue_assignment(self, workflow_id: str) -> bool:
1464
1465
  if self._debug_mode:
1465
1466
  raise Exception("called clear_queue_assignment in debug mode")
1466
- with self.engine.begin() as c:
1467
- c.execute(
1468
- sa.update(SystemSchema.workflow_queue)
1469
- .where(SystemSchema.workflow_queue.c.workflow_uuid == workflow_id)
1470
- .values(executor_id=None, started_at_epoch_ms=None)
1471
- )
1472
- c.execute(
1473
- sa.update(SystemSchema.workflow_status)
1474
- .where(SystemSchema.workflow_status.c.workflow_uuid == workflow_id)
1475
- .values(executor_id=None, status=WorkflowStatusString.ENQUEUED.value)
1476
- )
1477
1467
 
1468
+ with self.engine.connect() as conn:
1469
+ with conn.begin() as transaction:
1470
+ res = conn.execute(
1471
+ sa.update(SystemSchema.workflow_queue)
1472
+ .where(SystemSchema.workflow_queue.c.workflow_uuid == workflow_id)
1473
+ .values(executor_id=None, started_at_epoch_ms=None)
1474
+ )
1475
+
1476
+ # If no rows were affected, the workflow is not anymore in the queue
1477
+ if res.rowcount == 0:
1478
+ transaction.rollback()
1479
+ return False
1480
+
1481
+ res = conn.execute(
1482
+ sa.update(SystemSchema.workflow_status)
1483
+ .where(SystemSchema.workflow_status.c.workflow_uuid == workflow_id)
1484
+ .values(executor_id=None, status=WorkflowStatusString.ENQUEUED.value)
1485
+ )
1486
+ if res.rowcount == 0:
1487
+ # This should never happen
1488
+ raise Exception(
1489
+ f"UNREACHABLE: Workflow {workflow_id} is found in the workflow_queue table but not found in the workflow_status table"
1490
+ )
1491
+ return True
1478
1492
 
1479
1493
  def reset_system_database(config: ConfigFile) -> None:
1480
1494
  sysdb_name = (
@@ -27,7 +27,7 @@ dependencies = [
27
27
  ]
28
28
  requires-python = ">=3.9"
29
29
  readme = "README.md"
30
- version = "0.23.0a9"
30
+ version = "0.23.0a10"
31
31
 
32
32
  [project.license]
33
33
  text = "MIT"
@@ -19,7 +19,7 @@ from dbos import (
19
19
  WorkflowHandle,
20
20
  )
21
21
  from dbos._schemas.system_database import SystemSchema
22
- from dbos._sys_db import WorkflowStatusString
22
+ from dbos._sys_db import WorkflowStatusString, _buffer_flush_interval_secs
23
23
  from tests.conftest import default_config, queue_entries_are_cleaned_up
24
24
 
25
25
 
@@ -900,6 +900,40 @@ def test_resuming_queued_workflows(dbos: DBOS) -> None:
900
900
  assert queue_entries_are_cleaned_up(dbos)
901
901
 
902
902
 
903
+ # Test a race condition between removing a task from the queue and flushing the status buffer
904
+ def test_resuming_already_completed_queue_workflow(dbos: DBOS) -> None:
905
+ dbos._sys_db._run_background_processes = False # Disable buffer flush
906
+
907
+ start_event = threading.Event()
908
+ counter = 0
909
+ @DBOS.workflow()
910
+ def test_step() -> None:
911
+ start_event.set()
912
+ nonlocal counter
913
+ counter += 1
914
+
915
+ queue = Queue("test_queue")
916
+ handle = queue.enqueue(test_step)
917
+ start_event.wait()
918
+ start_event.clear()
919
+ time.sleep(_buffer_flush_interval_secs)
920
+ assert handle.get_status().status == WorkflowStatusString.PENDING.value # Not flushed
921
+ assert counter == 1 # But, really, it's completed
922
+ dbos._sys_db._workflow_status_buffer = {} # Clear buffer (simulates a process restart)
923
+
924
+ # Recovery picks up on the workflow and recovers it
925
+ recovered_ids = DBOS.recover_pending_workflows()
926
+ assert len(recovered_ids) == 1
927
+ assert recovered_ids[0].get_workflow_id() == handle.get_workflow_id()
928
+ start_event.wait()
929
+ assert counter == 2 # The workflow ran again
930
+ time.sleep(_buffer_flush_interval_secs) # This is actually to wait that _get_wf_invoke_func buffers the status
931
+ dbos._sys_db._flush_workflow_status_buffer() # Manually flush
932
+ assert handle.get_status().status == WorkflowStatusString.SUCCESS.value # Is recovered
933
+ assert handle.get_status().executor_id == "local"
934
+ assert handle.get_status().recovery_attempts == 2
935
+
936
+
903
937
  def test_dlq_enqueued_workflows(dbos: DBOS) -> None:
904
938
  start_event = threading.Event()
905
939
  blocking_event = threading.Event()
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