dbos 1.7.0a2__tar.gz → 1.7.0a3__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.
Files changed (108) hide show
  1. {dbos-1.7.0a2 → dbos-1.7.0a3}/PKG-INFO +1 -1
  2. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/_queue.py +18 -3
  3. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/_sys_db.py +7 -5
  4. {dbos-1.7.0a2 → dbos-1.7.0a3}/pyproject.toml +1 -1
  5. {dbos-1.7.0a2 → dbos-1.7.0a3}/tests/test_queue.py +8 -8
  6. {dbos-1.7.0a2 → dbos-1.7.0a3}/tests/test_scheduler.py +8 -8
  7. {dbos-1.7.0a2 → dbos-1.7.0a3}/LICENSE +0 -0
  8. {dbos-1.7.0a2 → dbos-1.7.0a3}/README.md +0 -0
  9. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/__init__.py +0 -0
  10. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/__main__.py +0 -0
  11. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/_admin_server.py +0 -0
  12. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/_app_db.py +0 -0
  13. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/_classproperty.py +0 -0
  14. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/_client.py +0 -0
  15. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/_conductor/conductor.py +0 -0
  16. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/_conductor/protocol.py +0 -0
  17. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/_context.py +0 -0
  18. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/_core.py +0 -0
  19. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/_croniter.py +0 -0
  20. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/_dbos.py +0 -0
  21. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/_dbos_config.py +0 -0
  22. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/_debug.py +0 -0
  23. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/_docker_pg_helper.py +0 -0
  24. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/_error.py +0 -0
  25. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/_event_loop.py +0 -0
  26. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/_fastapi.py +0 -0
  27. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/_flask.py +0 -0
  28. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/_kafka.py +0 -0
  29. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/_kafka_message.py +0 -0
  30. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/_logger.py +0 -0
  31. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/_migrations/env.py +0 -0
  32. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/_migrations/script.py.mako +0 -0
  33. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/_migrations/versions/04ca4f231047_workflow_queues_executor_id.py +0 -0
  34. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/_migrations/versions/27ac6900c6ad_add_queue_dedup.py +0 -0
  35. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/_migrations/versions/50f3227f0b4b_fix_job_queue.py +0 -0
  36. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/_migrations/versions/5c361fc04708_added_system_tables.py +0 -0
  37. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/_migrations/versions/66478e1b95e5_consolidate_queues.py +0 -0
  38. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/_migrations/versions/83f3732ae8e7_workflow_timeout.py +0 -0
  39. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/_migrations/versions/933e86bdac6a_add_queue_priority.py +0 -0
  40. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/_migrations/versions/a3b18ad34abe_added_triggers.py +0 -0
  41. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/_migrations/versions/d76646551a6b_job_queue_limiter.py +0 -0
  42. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/_migrations/versions/d76646551a6c_workflow_queue.py +0 -0
  43. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/_migrations/versions/d994145b47b6_consolidate_inputs.py +0 -0
  44. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/_migrations/versions/eab0cc1d9a14_job_queue.py +0 -0
  45. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/_migrations/versions/f4b9b32ba814_functionname_childid_op_outputs.py +0 -0
  46. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/_outcome.py +0 -0
  47. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/_recovery.py +0 -0
  48. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/_registrations.py +0 -0
  49. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/_roles.py +0 -0
  50. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/_scheduler.py +0 -0
  51. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/_schemas/__init__.py +0 -0
  52. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/_schemas/application_database.py +0 -0
  53. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/_schemas/system_database.py +0 -0
  54. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/_serialization.py +0 -0
  55. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/_templates/dbos-db-starter/README.md +0 -0
  56. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/_templates/dbos-db-starter/__package/__init__.py +0 -0
  57. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/_templates/dbos-db-starter/__package/main.py.dbos +0 -0
  58. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/_templates/dbos-db-starter/__package/schema.py +0 -0
  59. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/_templates/dbos-db-starter/alembic.ini +0 -0
  60. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/_templates/dbos-db-starter/dbos-config.yaml.dbos +0 -0
  61. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/_templates/dbos-db-starter/migrations/env.py.dbos +0 -0
  62. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/_templates/dbos-db-starter/migrations/script.py.mako +0 -0
  63. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/_templates/dbos-db-starter/migrations/versions/2024_07_31_180642_init.py +0 -0
  64. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/_templates/dbos-db-starter/start_postgres_docker.py +0 -0
  65. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/_tracer.py +0 -0
  66. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/_utils.py +0 -0
  67. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/_workflow_commands.py +0 -0
  68. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/cli/_github_init.py +0 -0
  69. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/cli/_template_init.py +0 -0
  70. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/cli/cli.py +0 -0
  71. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/dbos-config.schema.json +0 -0
  72. {dbos-1.7.0a2 → dbos-1.7.0a3}/dbos/py.typed +0 -0
  73. {dbos-1.7.0a2 → dbos-1.7.0a3}/tests/__init__.py +0 -0
  74. {dbos-1.7.0a2 → dbos-1.7.0a3}/tests/atexit_no_ctor.py +0 -0
  75. {dbos-1.7.0a2 → dbos-1.7.0a3}/tests/atexit_no_launch.py +0 -0
  76. {dbos-1.7.0a2 → dbos-1.7.0a3}/tests/classdefs.py +0 -0
  77. {dbos-1.7.0a2 → dbos-1.7.0a3}/tests/client_collateral.py +0 -0
  78. {dbos-1.7.0a2 → dbos-1.7.0a3}/tests/client_worker.py +0 -0
  79. {dbos-1.7.0a2 → dbos-1.7.0a3}/tests/conftest.py +0 -0
  80. {dbos-1.7.0a2 → dbos-1.7.0a3}/tests/dupname_classdefs1.py +0 -0
  81. {dbos-1.7.0a2 → dbos-1.7.0a3}/tests/dupname_classdefsa.py +0 -0
  82. {dbos-1.7.0a2 → dbos-1.7.0a3}/tests/more_classdefs.py +0 -0
  83. {dbos-1.7.0a2 → dbos-1.7.0a3}/tests/queuedworkflow.py +0 -0
  84. {dbos-1.7.0a2 → dbos-1.7.0a3}/tests/test_admin_server.py +0 -0
  85. {dbos-1.7.0a2 → dbos-1.7.0a3}/tests/test_async.py +0 -0
  86. {dbos-1.7.0a2 → dbos-1.7.0a3}/tests/test_classdecorators.py +0 -0
  87. {dbos-1.7.0a2 → dbos-1.7.0a3}/tests/test_cli.py +0 -0
  88. {dbos-1.7.0a2 → dbos-1.7.0a3}/tests/test_client.py +0 -0
  89. {dbos-1.7.0a2 → dbos-1.7.0a3}/tests/test_concurrency.py +0 -0
  90. {dbos-1.7.0a2 → dbos-1.7.0a3}/tests/test_config.py +0 -0
  91. {dbos-1.7.0a2 → dbos-1.7.0a3}/tests/test_croniter.py +0 -0
  92. {dbos-1.7.0a2 → dbos-1.7.0a3}/tests/test_dbos.py +0 -0
  93. {dbos-1.7.0a2 → dbos-1.7.0a3}/tests/test_debug.py +0 -0
  94. {dbos-1.7.0a2 → dbos-1.7.0a3}/tests/test_docker_secrets.py +0 -0
  95. {dbos-1.7.0a2 → dbos-1.7.0a3}/tests/test_failures.py +0 -0
  96. {dbos-1.7.0a2 → dbos-1.7.0a3}/tests/test_fastapi.py +0 -0
  97. {dbos-1.7.0a2 → dbos-1.7.0a3}/tests/test_fastapi_roles.py +0 -0
  98. {dbos-1.7.0a2 → dbos-1.7.0a3}/tests/test_flask.py +0 -0
  99. {dbos-1.7.0a2 → dbos-1.7.0a3}/tests/test_kafka.py +0 -0
  100. {dbos-1.7.0a2 → dbos-1.7.0a3}/tests/test_outcome.py +0 -0
  101. {dbos-1.7.0a2 → dbos-1.7.0a3}/tests/test_package.py +0 -0
  102. {dbos-1.7.0a2 → dbos-1.7.0a3}/tests/test_schema_migration.py +0 -0
  103. {dbos-1.7.0a2 → dbos-1.7.0a3}/tests/test_singleton.py +0 -0
  104. {dbos-1.7.0a2 → dbos-1.7.0a3}/tests/test_spans.py +0 -0
  105. {dbos-1.7.0a2 → dbos-1.7.0a3}/tests/test_sqlalchemy.py +0 -0
  106. {dbos-1.7.0a2 → dbos-1.7.0a3}/tests/test_workflow_introspection.py +0 -0
  107. {dbos-1.7.0a2 → dbos-1.7.0a3}/tests/test_workflow_management.py +0 -0
  108. {dbos-1.7.0a2 → dbos-1.7.0a3}/version/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: dbos
3
- Version: 1.7.0a2
3
+ Version: 1.7.0a3
4
4
  Summary: Ultra-lightweight durable execution in Python
5
5
  Author-Email: "DBOS, Inc." <contact@dbos.dev>
6
6
  License: MIT
@@ -1,3 +1,4 @@
1
+ import random
1
2
  import threading
2
3
  from typing import TYPE_CHECKING, Any, Callable, Coroutine, Optional, TypedDict
3
4
 
@@ -94,8 +95,12 @@ class Queue:
94
95
 
95
96
 
96
97
  def queue_thread(stop_event: threading.Event, dbos: "DBOS") -> None:
98
+ polling_interval = 1.0
99
+ min_polling_interval = 1.0
100
+ max_polling_interval = 120.0
97
101
  while not stop_event.is_set():
98
- if stop_event.wait(timeout=1):
102
+ # Wait for the polling interval with jitter
103
+ if stop_event.wait(timeout=polling_interval * random.uniform(0.95, 1.05)):
99
104
  return
100
105
  queues = dict(dbos._registry.queue_info_map)
101
106
  for _, queue in queues.items():
@@ -106,12 +111,22 @@ def queue_thread(stop_event: threading.Event, dbos: "DBOS") -> None:
106
111
  for id in wf_ids:
107
112
  execute_workflow_by_id(dbos, id)
108
113
  except OperationalError as e:
109
- # Ignore serialization error
110
- if not isinstance(
114
+ if isinstance(
111
115
  e.orig, (errors.SerializationFailure, errors.LockNotAvailable)
112
116
  ):
117
+ # If a serialization error is encountered, increase the polling interval
118
+ polling_interval = min(
119
+ max_polling_interval,
120
+ polling_interval * 2.0,
121
+ )
122
+ dbos.logger.warning(
123
+ f"Contention detected in queue thread for {queue.name}. Increasing polling interval to {polling_interval:.2f}."
124
+ )
125
+ else:
113
126
  dbos.logger.warning(f"Exception encountered in queue thread: {e}")
114
127
  except Exception as e:
115
128
  if not stop_event.is_set():
116
129
  # Only print the error if the thread is not stopping
117
130
  dbos.logger.warning(f"Exception encountered in queue thread: {e}")
131
+ # Attempt to scale back the polling interval on each iteration
132
+ polling_interval = max(min_polling_interval, polling_interval * 0.9)
@@ -1650,7 +1650,7 @@ class SystemDatabase:
1650
1650
  return []
1651
1651
 
1652
1652
  # Compute max_tasks, the number of workflows that can be dequeued given local and global concurrency limits,
1653
- max_tasks = float("inf")
1653
+ max_tasks = 100 # To minimize contention with large queues, never dequeue more than 100 tasks
1654
1654
  if queue.worker_concurrency is not None or queue.concurrency is not None:
1655
1655
  # Count how many workflows on this queue are currently PENDING both locally and globally.
1656
1656
  pending_tasks_query = (
@@ -1694,6 +1694,7 @@ class SystemDatabase:
1694
1694
 
1695
1695
  # Retrieve the first max_tasks workflows in the queue.
1696
1696
  # Only retrieve workflows of the local version (or without version set)
1697
+ skip_locks = queue.concurrency is None
1697
1698
  query = (
1698
1699
  sa.select(
1699
1700
  SystemSchema.workflow_status.c.workflow_uuid,
@@ -1711,7 +1712,10 @@ class SystemDatabase:
1711
1712
  SystemSchema.workflow_status.c.application_version.is_(None),
1712
1713
  )
1713
1714
  )
1714
- .with_for_update(nowait=True) # Error out early
1715
+ # Unless global concurrency is set, use skip_locked to only select
1716
+ # rows that can be locked. If global concurrency is set, use no_wait
1717
+ # to ensure all processes have a consistent view of the table.
1718
+ .with_for_update(skip_locked=skip_locks, nowait=(not skip_locks))
1715
1719
  )
1716
1720
  if queue.priority_enabled:
1717
1721
  query = query.order_by(
@@ -1720,9 +1724,7 @@ class SystemDatabase:
1720
1724
  )
1721
1725
  else:
1722
1726
  query = query.order_by(SystemSchema.workflow_status.c.created_at.asc())
1723
- # Apply limit only if max_tasks is finite
1724
- if max_tasks != float("inf"):
1725
- query = query.limit(int(max_tasks))
1727
+ query = query.limit(int(max_tasks))
1726
1728
 
1727
1729
  rows = c.execute(query).fetchall()
1728
1730
 
@@ -27,7 +27,7 @@ dependencies = [
27
27
  ]
28
28
  requires-python = ">=3.9"
29
29
  readme = "README.md"
30
- version = "1.7.0a2"
30
+ version = "1.7.0a3"
31
31
 
32
32
  [project.license]
33
33
  text = "MIT"
@@ -215,7 +215,7 @@ def test_limiter(dbos: DBOS) -> None:
215
215
  return time.time()
216
216
 
217
217
  limit = 5
218
- period = 2
218
+ period = 1.8
219
219
  queue = Queue("test_queue", limiter={"limit": limit, "period": period})
220
220
 
221
221
  handles: list[WorkflowHandle[float]] = []
@@ -235,12 +235,12 @@ def test_limiter(dbos: DBOS) -> None:
235
235
  # Verify that each "wave" of tasks started at the ~same time.
236
236
  for wave in range(num_waves):
237
237
  for i in range(wave * limit, (wave + 1) * limit - 1):
238
- assert times[i + 1] - times[i] < 0.3
238
+ assert times[i + 1] - times[i] < 0.5
239
239
 
240
240
  # Verify that the gap between "waves" is ~equal to the period
241
241
  for wave in range(num_waves - 1):
242
- assert times[limit * (wave + 1)] - times[limit * wave] > period - 0.3
243
- assert times[limit * (wave + 1)] - times[limit * wave] < period + 0.3
242
+ assert times[limit * (wave + 1)] - times[limit * wave] > period - 0.5
243
+ assert times[limit * (wave + 1)] - times[limit * wave] < period + 0.5
244
244
 
245
245
  # Verify all workflows get the SUCCESS status eventually
246
246
  for h in handles:
@@ -280,7 +280,7 @@ def test_multiple_queues(dbos: DBOS) -> None:
280
280
  return time.time()
281
281
 
282
282
  limit = 5
283
- period = 2
283
+ period = 1.8
284
284
  limiter_queue = Queue(
285
285
  "test_limit_queue", limiter={"limit": limit, "period": period}
286
286
  )
@@ -302,12 +302,12 @@ def test_multiple_queues(dbos: DBOS) -> None:
302
302
  # Verify that each "wave" of tasks started at the ~same time.
303
303
  for wave in range(num_waves):
304
304
  for i in range(wave * limit, (wave + 1) * limit - 1):
305
- assert times[i + 1] - times[i] < 0.3
305
+ assert times[i + 1] - times[i] < 0.5
306
306
 
307
307
  # Verify that the gap between "waves" is ~equal to the period
308
308
  for wave in range(num_waves - 1):
309
- assert times[limit * (wave + 1)] - times[limit * wave] > period - 0.3
310
- assert times[limit * (wave + 1)] - times[limit * wave] < period + 0.3
309
+ assert times[limit * (wave + 1)] - times[limit * wave] > period - 0.5
310
+ assert times[limit * (wave + 1)] - times[limit * wave] < period + 0.5
311
311
 
312
312
  # Verify all workflows get the SUCCESS status eventually
313
313
  for h in handles:
@@ -101,8 +101,8 @@ def test_scheduled_workflow(dbos: DBOS) -> None:
101
101
  nonlocal wf_counter
102
102
  wf_counter += 1
103
103
 
104
- time.sleep(4)
105
- assert wf_counter > 2 and wf_counter <= 4
104
+ time.sleep(5)
105
+ assert wf_counter > 2 and wf_counter <= 5
106
106
 
107
107
 
108
108
  def test_appdb_downtime(dbos: DBOS) -> None:
@@ -152,8 +152,8 @@ def test_scheduled_transaction(dbos: DBOS) -> None:
152
152
  nonlocal txn_counter
153
153
  txn_counter += 1
154
154
 
155
- time.sleep(4)
156
- assert txn_counter > 2 and txn_counter <= 4
155
+ time.sleep(5)
156
+ assert txn_counter > 2 and txn_counter <= 5
157
157
 
158
158
 
159
159
  def test_scheduled_step(dbos: DBOS) -> None:
@@ -165,8 +165,8 @@ def test_scheduled_step(dbos: DBOS) -> None:
165
165
  nonlocal step_counter
166
166
  step_counter += 1
167
167
 
168
- time.sleep(4)
169
- assert step_counter > 2 and step_counter <= 4
168
+ time.sleep(5)
169
+ assert step_counter > 2 and step_counter <= 5
170
170
 
171
171
 
172
172
  def test_scheduled_workflow_exception(dbos: DBOS) -> None:
@@ -179,8 +179,8 @@ def test_scheduled_workflow_exception(dbos: DBOS) -> None:
179
179
  wf_counter += 1
180
180
  raise Exception("error")
181
181
 
182
- time.sleep(3)
183
- assert wf_counter >= 1 and wf_counter <= 3
182
+ time.sleep(4)
183
+ assert wf_counter >= 1 and wf_counter <= 4
184
184
 
185
185
 
186
186
  def test_scheduler_oaoo(dbos: DBOS) -> None:
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