dex-python-sdk 1.14.0__tar.gz → 1.14.1__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 (69) hide show
  1. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/PKG-INFO +8 -6
  2. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/README.md +7 -5
  3. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/dex/_pull_worker.py +46 -17
  4. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/pyproject.toml +1 -1
  5. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/LEGACY_NOTICES.md +0 -0
  6. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/LICENSE +0 -0
  7. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/dex/__init__.py +0 -0
  8. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/dex/_async_value_hydrator.py +0 -0
  9. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/dex/_async_worker_dispatcher.py +0 -0
  10. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/dex/_async_worker_service.py +0 -0
  11. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/dex/_credential_transport.py +0 -0
  12. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/dex/_grpc_errors.py +0 -0
  13. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/dex/_invocation_context.py +0 -0
  14. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/dex/_native.pyi +0 -0
  15. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/dex/_server_protocol.py +0 -0
  16. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/dex/_utils.py +0 -0
  17. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/dex/_value_hydrator.py +0 -0
  18. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/dex/_value_mapper.py +0 -0
  19. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/dex/_worker_dispatcher.py +0 -0
  20. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/dex/_worker_service.py +0 -0
  21. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/dex/async_client.py +0 -0
  22. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/dex/async_worker.py +0 -0
  23. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/dex/attribute.py +0 -0
  24. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/dex/attribute_match.py +0 -0
  25. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/dex/blob_cache.py +0 -0
  26. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/dex/channel.py +0 -0
  27. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/dex/client.py +0 -0
  28. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/dex/client_options.py +0 -0
  29. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/dex/codec.py +0 -0
  30. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/dex/condition.py +0 -0
  31. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/dex/context.py +0 -0
  32. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/dex/credential.py +0 -0
  33. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/dex/dexpb/__init__.py +0 -0
  34. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/dex/dexpb/dex_pb2.py +0 -0
  35. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/dex/dexpb/dex_pb2.pyi +0 -0
  36. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/dex/dexpb/dex_pb2_grpc.py +0 -0
  37. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/dex/flow.py +0 -0
  38. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/dex/flow_config.py +0 -0
  39. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/dex/flow_info.py +0 -0
  40. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/dex/flow_options.py +0 -0
  41. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/dex/flow_result.py +0 -0
  42. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/dex/pull_registration.py +0 -0
  43. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/dex/py.typed +0 -0
  44. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/dex/rpc_invoke_options.py +0 -0
  45. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/dex/runtime_errors.py +0 -0
  46. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/dex/step.py +0 -0
  47. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/dex/step_execution.py +0 -0
  48. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/dex/stream.py +0 -0
  49. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/dex/subflow.py +0 -0
  50. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/dex/timer.py +0 -0
  51. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/dex/wait.py +0 -0
  52. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/dex/wait_options.py +0 -0
  53. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/dex/worker.py +0 -0
  54. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/dex/worker_options.py +0 -0
  55. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/sdk-rust/Cargo.lock +0 -0
  56. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/sdk-rust/Cargo.toml +0 -0
  57. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/sdk-rust/crates/dex-blob-cache/Cargo.toml +0 -0
  58. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/sdk-rust/crates/dex-blob-cache/LICENSE +0 -0
  59. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/sdk-rust/crates/dex-blob-cache/src/config.rs +0 -0
  60. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/sdk-rust/crates/dex-blob-cache/src/entry.rs +0 -0
  61. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/sdk-rust/crates/dex-blob-cache/src/error.rs +0 -0
  62. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/sdk-rust/crates/dex-blob-cache/src/format.rs +0 -0
  63. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/sdk-rust/crates/dex-blob-cache/src/lib.rs +0 -0
  64. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/sdk-rust/crates/dex-blob-cache/src/policy.rs +0 -0
  65. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/sdk-rust/crates/dex-blob-cache/src/store.rs +0 -0
  66. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/sdk-rust/crates/dex-blob-cache/tests/blob_cache_integration.rs +0 -0
  67. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/sdk-rust/crates/dex-blob-cache-python/Cargo.toml +0 -0
  68. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/sdk-rust/crates/dex-blob-cache-python/LICENSE +0 -0
  69. {dex_python_sdk-1.14.0 → dex_python_sdk-1.14.1}/sdk-rust/crates/dex-blob-cache-python/src/lib.rs +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dex-python-sdk
3
- Version: 1.14.0
3
+ Version: 1.14.1
4
4
  Requires-Dist: grpcio>=1.83.0
5
5
  Requires-Dist: grpcio-status>=1.83.0
6
6
  Requires-Dist: protobuf>=7.35.1
@@ -94,11 +94,13 @@ The Worker polls its sticky queue and every registered Flow type queue. Flow
94
94
  type names must be unique within a Dex server. `max_concurrent_tasks` defaults
95
95
  to the CPU count, and `idle_streams_per_queue` defaults to one. At capacity,
96
96
  the Worker rejects an assignment so Dex can reassign it. Each stream runs one
97
- task at a time; Step heartbeat and stream writes use the same stream. A Worker
98
- may hold up to `(1 + number of Flow types) * (idle_streams_per_queue +
99
- max_concurrent_tasks)` streams because busy streams are replaced with idle
100
- streams and retained after tasks finish. Size these options for the server's
101
- per-Worker stream cap (256 by default).
97
+ task at a time; Step heartbeat and stream writes use the same stream. After tasks finish, the Worker
98
+ gracefully closes surplus idle streams while preserving readiness. After load drops,
99
+ the steady-state bound is `max_concurrent_tasks + (1 + number of Flow types) *
100
+ idle_streams_per_queue` streams. The transient peak during a burst remains bounded
101
+ by the matching caps. Size these options for `matching.maxConcurrentTasksCap` per
102
+ app Worker ID per matching owner (256 by default) and `matching.maxStreams` per
103
+ Matching instance (100,000 by default).
102
104
 
103
105
  The Worker rejects an unregistered Step or RPC by name. Dex retries that task
104
106
  on another pull Worker without consuming a Step attempt, including when the
@@ -78,11 +78,13 @@ The Worker polls its sticky queue and every registered Flow type queue. Flow
78
78
  type names must be unique within a Dex server. `max_concurrent_tasks` defaults
79
79
  to the CPU count, and `idle_streams_per_queue` defaults to one. At capacity,
80
80
  the Worker rejects an assignment so Dex can reassign it. Each stream runs one
81
- task at a time; Step heartbeat and stream writes use the same stream. A Worker
82
- may hold up to `(1 + number of Flow types) * (idle_streams_per_queue +
83
- max_concurrent_tasks)` streams because busy streams are replaced with idle
84
- streams and retained after tasks finish. Size these options for the server's
85
- per-Worker stream cap (256 by default).
81
+ task at a time; Step heartbeat and stream writes use the same stream. After tasks finish, the Worker
82
+ gracefully closes surplus idle streams while preserving readiness. After load drops,
83
+ the steady-state bound is `max_concurrent_tasks + (1 + number of Flow types) *
84
+ idle_streams_per_queue` streams. The transient peak during a burst remains bounded
85
+ by the matching caps. Size these options for `matching.maxConcurrentTasksCap` per
86
+ app Worker ID per matching owner (256 by default) and `matching.maxStreams` per
87
+ Matching instance (100,000 by default).
86
88
 
87
89
  The Worker rejects an unregistered Step or RPC by name. Dex retries that task
88
90
  on another pull Worker without consuming a Step attempt, including when the
@@ -170,6 +170,8 @@ class PullWorkerRuntime:
170
170
  def _reconcile(self, stub: Any) -> None:
171
171
  if self._stopping is None or self._stopping.is_set():
172
172
  return
173
+ for stream in tuple(self._streams):
174
+ stream.try_retire()
173
175
  for queue in self.queues:
174
176
  idle = sum(
175
177
  stream.queue == queue
@@ -279,6 +281,8 @@ class PullStream:
279
281
  self.is_busy = False
280
282
  self._is_registered = False
281
283
  self.is_draining = False
284
+ self._is_retiring = False
285
+ self._retirement_requested = asyncio.Event()
282
286
  self.is_half_closing = False
283
287
  self._active = threading.Event()
284
288
  self._call: Any = None
@@ -321,7 +325,11 @@ class PullStream:
321
325
  self.runtime._rejection_stop_task = asyncio.create_task(
322
326
  self.runtime.stop()
323
327
  )
324
- if self.runtime.is_stopping or self.runtime._permanent_error is not None:
328
+ if (
329
+ self.runtime.is_stopping
330
+ or self.runtime._permanent_error is not None
331
+ or self._is_retiring
332
+ ):
325
333
  break
326
334
  if self._is_reconnecting:
327
335
  self._is_reconnecting = False
@@ -378,11 +386,12 @@ class PullStream:
378
386
  )
379
387
  was_registered = False
380
388
  reader: asyncio.Task[Any] | None = None
389
+ retirement = asyncio.create_task(self._retirement_requested.wait())
381
390
  try:
382
391
  while not self.runtime.is_stopping or self.is_busy:
383
392
  if reader is None:
384
393
  reader = asyncio.create_task(self._call.read())
385
- waiting: set[asyncio.Task[Any]] = {reader}
394
+ waiting: set[asyncio.Task[Any]] = {reader, retirement}
386
395
  if self.handler_task is not None:
387
396
  waiting.add(self.handler_task)
388
397
  completed, _ = await asyncio.wait(
@@ -390,22 +399,23 @@ class PullStream:
390
399
  )
391
400
  if self.handler_task in completed:
392
401
  await self._finish_handler()
393
- if self.is_draining:
394
- if reader.done():
395
- frame = reader.result()
396
- reader = None
397
- if (
398
- frame is not _GRPC_AIO_EOF
399
- and frame.WhichOneof("frame") == "task"
400
- ):
401
- await self._reject_task(frame.task)
402
- self.is_half_closing = True
403
- self.runtime.signal()
404
- await self._call.done_writing()
405
- await self._await_stream_eof(reader)
402
+ self.try_retire()
403
+ if self.is_draining and not self.is_busy:
404
+ if reader.done():
405
+ frame = reader.result()
406
406
  reader = None
407
- self._is_reconnecting = True
408
- return was_registered
407
+ if (
408
+ frame is not _GRPC_AIO_EOF
409
+ and frame.WhichOneof("frame") == "task"
410
+ ):
411
+ await self._reject_task(frame.task)
412
+ self.is_half_closing = True
413
+ self.runtime.signal()
414
+ await self._call.done_writing()
415
+ await self._await_stream_eof(reader)
416
+ reader = None
417
+ self._is_reconnecting = not self._is_retiring
418
+ return was_registered
409
419
  if reader not in completed:
410
420
  continue
411
421
  frame = reader.result()
@@ -425,6 +435,7 @@ class PullStream:
425
435
  was_registered = True
426
436
  self._is_registered = True
427
437
  self.runtime.registration.record_registered_stream(self.queue)
438
+ self.runtime.signal()
428
439
  continue
429
440
  if kind == "task":
430
441
  await self._start_task(frame.task)
@@ -442,6 +453,8 @@ class PullStream:
442
453
  self._is_reconnecting = True
443
454
  return was_registered
444
455
  finally:
456
+ retirement.cancel()
457
+ await asyncio.gather(retirement, return_exceptions=True)
445
458
  self.is_half_closing = False
446
459
  self.cancel_handler()
447
460
  if self.handler_task is not None:
@@ -658,6 +671,22 @@ class PullStream:
658
671
  self._active.clear()
659
672
  self.runtime.finish_task()
660
673
 
674
+ def try_retire(self) -> None:
675
+ if self.is_busy or self.is_draining or not self._is_registered:
676
+ return
677
+ idle_replacements = sum(
678
+ candidate is not self
679
+ and candidate.queue == self.queue
680
+ and candidate._is_registered
681
+ and not candidate.is_busy
682
+ and not candidate.is_draining
683
+ for candidate in self.runtime._streams
684
+ )
685
+ if idle_replacements >= self.runtime.idle_streams_per_queue:
686
+ self.is_draining = True
687
+ self._is_retiring = True
688
+ self._retirement_requested.set()
689
+
661
690
  def cancel_handler(self) -> None:
662
691
  self._active.clear()
663
692
  if self.runtime._executor is None and self.handler_task is not None:
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  name = "dex-python-sdk"
3
3
  # Publish uses tag sdk-python/v*; CI stamps this for release builds.
4
- version = "1.14.0"
4
+ version = "1.14.1"
5
5
  description = "Python SDK for the Dex workflow engine"
6
6
  authors = [{name = "Super Durable"}]
7
7
  readme = "README.md"
File without changes