dex-python-sdk 1.14.0__tar.gz → 1.15.0__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.
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/PKG-INFO +42 -6
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/README.md +41 -5
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/dex/_pull_worker.py +74 -19
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/dex/async_worker.py +17 -0
- dex_python_sdk-1.15.0/dex/dexpb/dex_pb2.py +549 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/dex/dexpb/dex_pb2.pyi +73 -26
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/dex/worker.py +17 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/dex/worker_options.py +29 -2
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/pyproject.toml +1 -1
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/sdk-rust/Cargo.lock +1 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/sdk-rust/Cargo.toml +1 -0
- dex_python_sdk-1.14.0/dex/dexpb/dex_pb2.py +0 -545
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/LEGACY_NOTICES.md +0 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/LICENSE +0 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/dex/__init__.py +0 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/dex/_async_value_hydrator.py +0 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/dex/_async_worker_dispatcher.py +0 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/dex/_async_worker_service.py +0 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/dex/_credential_transport.py +0 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/dex/_grpc_errors.py +0 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/dex/_invocation_context.py +0 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/dex/_native.pyi +0 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/dex/_server_protocol.py +0 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/dex/_utils.py +0 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/dex/_value_hydrator.py +0 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/dex/_value_mapper.py +0 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/dex/_worker_dispatcher.py +0 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/dex/_worker_service.py +0 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/dex/async_client.py +0 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/dex/attribute.py +0 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/dex/attribute_match.py +0 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/dex/blob_cache.py +0 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/dex/channel.py +0 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/dex/client.py +0 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/dex/client_options.py +0 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/dex/codec.py +0 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/dex/condition.py +0 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/dex/context.py +0 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/dex/credential.py +0 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/dex/dexpb/__init__.py +0 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/dex/dexpb/dex_pb2_grpc.py +0 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/dex/flow.py +0 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/dex/flow_config.py +0 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/dex/flow_info.py +0 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/dex/flow_options.py +0 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/dex/flow_result.py +0 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/dex/pull_registration.py +0 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/dex/py.typed +0 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/dex/rpc_invoke_options.py +0 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/dex/runtime_errors.py +0 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/dex/step.py +0 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/dex/step_execution.py +0 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/dex/stream.py +0 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/dex/subflow.py +0 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/dex/timer.py +0 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/dex/wait.py +0 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/dex/wait_options.py +0 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/sdk-rust/crates/dex-blob-cache/Cargo.toml +0 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/sdk-rust/crates/dex-blob-cache/LICENSE +0 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/sdk-rust/crates/dex-blob-cache/src/config.rs +0 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/sdk-rust/crates/dex-blob-cache/src/entry.rs +0 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/sdk-rust/crates/dex-blob-cache/src/error.rs +0 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/sdk-rust/crates/dex-blob-cache/src/format.rs +0 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/sdk-rust/crates/dex-blob-cache/src/lib.rs +0 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/sdk-rust/crates/dex-blob-cache/src/policy.rs +0 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/sdk-rust/crates/dex-blob-cache/src/store.rs +0 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/sdk-rust/crates/dex-blob-cache/tests/blob_cache_integration.rs +0 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/sdk-rust/crates/dex-blob-cache-python/Cargo.toml +0 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/sdk-rust/crates/dex-blob-cache-python/LICENSE +0 -0
- {dex_python_sdk-1.14.0 → dex_python_sdk-1.15.0}/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.
|
|
3
|
+
Version: 1.15.0
|
|
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.
|
|
98
|
-
|
|
99
|
-
max_concurrent_tasks
|
|
100
|
-
streams
|
|
101
|
-
|
|
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
|
|
@@ -155,6 +157,40 @@ applies before and after readiness. Another registered stream for that queue
|
|
|
155
157
|
prevents shutdown. The default is False and retries indefinitely. Always call
|
|
156
158
|
`stop()` or `close()` afterward; both remain safe after `start()` returns.
|
|
157
159
|
|
|
160
|
+
### Reserved RPC capacity
|
|
161
|
+
|
|
162
|
+
Set `reserved_rpc_tasks` when synchronous RPC latency must stay responsive
|
|
163
|
+
behind long-running Steps. When unset, it reserves one slot if the resolved task
|
|
164
|
+
limit is at least two, otherwise zero. With the CPU-count default, Steps get one
|
|
165
|
+
fewer slot than before. Set it explicitly to zero to opt out. With one slot and
|
|
166
|
+
no explicit reserve, Worker start logs one warning: RPCs may wait behind
|
|
167
|
+
long-running Steps; use a limit of at least two or set the reserve explicitly.
|
|
168
|
+
Flows must stay responsive; Dex cannot prioritize synchronous RPCs like workflow
|
|
169
|
+
engines prioritize reads. The value must be nonnegative and less than
|
|
170
|
+
`max_concurrent_tasks` after its CPU-count default is resolved; invalid values
|
|
171
|
+
fail construction. RPC and Step handlers count together: WaitFor and Execute
|
|
172
|
+
tasks, including local attempts, are declined with `NO_CAPACITY` when the busy
|
|
173
|
+
count reaches the task limit minus the reserve. API-direct and transactional
|
|
174
|
+
InvokeWorkerRPC tasks can use the full limit.
|
|
175
|
+
|
|
176
|
+
```python
|
|
177
|
+
pull = PullWorkerOptions(max_concurrent_tasks=8, reserved_rpc_tasks=2)
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
A decline lets Dex try another Worker without consuming an attempt. If all
|
|
181
|
+
Workers remain at their Step limit, a Step can wait until the dispatch wait
|
|
182
|
+
expires, then receive `NO_WORKER_AVAILABLE`, consume an attempt, and follow its
|
|
183
|
+
retry policy, as when all Workers are full. Local attempts wait the local
|
|
184
|
+
dispatch wait, then fall back according to their policy. Each queue still keeps
|
|
185
|
+
its configured idle streams; a reserve decline does not close a stream or change
|
|
186
|
+
registration readiness.
|
|
187
|
+
|
|
188
|
+
Each reserve decline records the Flow type through the dex._pull_worker logger
|
|
189
|
+
at DEBUG level. Export the cumulative `pull_reserved_rpc_declines` Worker value
|
|
190
|
+
to distinguish reserve pressure from full-capacity rejections. The count
|
|
191
|
+
excludes full-capacity rejections and unknown names, never resets, and remains
|
|
192
|
+
available after shutdown. Listening Workers return zero.
|
|
193
|
+
|
|
158
194
|
## Flow state I/O
|
|
159
195
|
|
|
160
196
|
Applications read and write Flow state through typed RPCs. Client and AsyncClient
|
|
@@ -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.
|
|
82
|
-
|
|
83
|
-
max_concurrent_tasks
|
|
84
|
-
streams
|
|
85
|
-
|
|
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
|
|
@@ -139,6 +141,40 @@ applies before and after readiness. Another registered stream for that queue
|
|
|
139
141
|
prevents shutdown. The default is False and retries indefinitely. Always call
|
|
140
142
|
`stop()` or `close()` afterward; both remain safe after `start()` returns.
|
|
141
143
|
|
|
144
|
+
### Reserved RPC capacity
|
|
145
|
+
|
|
146
|
+
Set `reserved_rpc_tasks` when synchronous RPC latency must stay responsive
|
|
147
|
+
behind long-running Steps. When unset, it reserves one slot if the resolved task
|
|
148
|
+
limit is at least two, otherwise zero. With the CPU-count default, Steps get one
|
|
149
|
+
fewer slot than before. Set it explicitly to zero to opt out. With one slot and
|
|
150
|
+
no explicit reserve, Worker start logs one warning: RPCs may wait behind
|
|
151
|
+
long-running Steps; use a limit of at least two or set the reserve explicitly.
|
|
152
|
+
Flows must stay responsive; Dex cannot prioritize synchronous RPCs like workflow
|
|
153
|
+
engines prioritize reads. The value must be nonnegative and less than
|
|
154
|
+
`max_concurrent_tasks` after its CPU-count default is resolved; invalid values
|
|
155
|
+
fail construction. RPC and Step handlers count together: WaitFor and Execute
|
|
156
|
+
tasks, including local attempts, are declined with `NO_CAPACITY` when the busy
|
|
157
|
+
count reaches the task limit minus the reserve. API-direct and transactional
|
|
158
|
+
InvokeWorkerRPC tasks can use the full limit.
|
|
159
|
+
|
|
160
|
+
```python
|
|
161
|
+
pull = PullWorkerOptions(max_concurrent_tasks=8, reserved_rpc_tasks=2)
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
A decline lets Dex try another Worker without consuming an attempt. If all
|
|
165
|
+
Workers remain at their Step limit, a Step can wait until the dispatch wait
|
|
166
|
+
expires, then receive `NO_WORKER_AVAILABLE`, consume an attempt, and follow its
|
|
167
|
+
retry policy, as when all Workers are full. Local attempts wait the local
|
|
168
|
+
dispatch wait, then fall back according to their policy. Each queue still keeps
|
|
169
|
+
its configured idle streams; a reserve decline does not close a stream or change
|
|
170
|
+
registration readiness.
|
|
171
|
+
|
|
172
|
+
Each reserve decline records the Flow type through the dex._pull_worker logger
|
|
173
|
+
at DEBUG level. Export the cumulative `pull_reserved_rpc_declines` Worker value
|
|
174
|
+
to distinguish reserve pressure from full-capacity rejections. The count
|
|
175
|
+
excludes full-capacity rejections and unknown names, never resets, and remains
|
|
176
|
+
available after shutdown. Listening Workers return zero.
|
|
177
|
+
|
|
142
178
|
## Flow state I/O
|
|
143
179
|
|
|
144
180
|
Applications read and write Flow state through typed RPCs. Client and AsyncClient
|
|
@@ -94,6 +94,15 @@ class PullWorkerRuntime:
|
|
|
94
94
|
self._permanent_error: PullRegistrationError | None = None
|
|
95
95
|
self._rejection_stop_task: asyncio.Task[None] | None = None
|
|
96
96
|
self.max_concurrent_tasks = options.max_concurrent_tasks or os.cpu_count() or 1
|
|
97
|
+
self.reserved_rpc_tasks = (
|
|
98
|
+
options.reserved_rpc_tasks
|
|
99
|
+
if options.reserved_rpc_tasks is not None
|
|
100
|
+
else int(self.max_concurrent_tasks >= 2)
|
|
101
|
+
)
|
|
102
|
+
self._should_warn_rpc_capacity = (
|
|
103
|
+
options.reserved_rpc_tasks is None and self.max_concurrent_tasks == 1
|
|
104
|
+
)
|
|
105
|
+
self.reserved_rpc_declines = 0
|
|
97
106
|
self.idle_streams_per_queue = options.idle_streams_per_queue or 1
|
|
98
107
|
self.stop_drain_timeout = options.stop_drain_timeout.total_seconds() or 30
|
|
99
108
|
self._dispatcher = dispatcher
|
|
@@ -132,6 +141,10 @@ class PullWorkerRuntime:
|
|
|
132
141
|
return None
|
|
133
142
|
|
|
134
143
|
async def run(self) -> None:
|
|
144
|
+
if self._should_warn_rpc_capacity:
|
|
145
|
+
_LOGGER.warning(
|
|
146
|
+
"RPCs may wait behind long-running Steps; use max_concurrent_tasks >= 2 or set reserved_rpc_tasks explicitly"
|
|
147
|
+
)
|
|
135
148
|
self._loop = asyncio.get_running_loop()
|
|
136
149
|
self._loop_thread_id = threading.get_ident()
|
|
137
150
|
self._wake = asyncio.Event()
|
|
@@ -170,6 +183,8 @@ class PullWorkerRuntime:
|
|
|
170
183
|
def _reconcile(self, stub: Any) -> None:
|
|
171
184
|
if self._stopping is None or self._stopping.is_set():
|
|
172
185
|
return
|
|
186
|
+
for stream in tuple(self._streams):
|
|
187
|
+
stream.try_retire()
|
|
173
188
|
for queue in self.queues:
|
|
174
189
|
idle = sum(
|
|
175
190
|
stream.queue == queue
|
|
@@ -191,11 +206,24 @@ class PullWorkerRuntime:
|
|
|
191
206
|
_LOGGER.error("Worker task stream failed", exc_info=failure)
|
|
192
207
|
self.signal()
|
|
193
208
|
|
|
194
|
-
def accept_task(self) -> bool:
|
|
209
|
+
def accept_task(self, task: pb.PollWorkerTask | None = None) -> bool:
|
|
195
210
|
if self._stopping is None or self._stopping.is_set():
|
|
196
211
|
return False
|
|
197
212
|
if self._busy_tasks >= self.max_concurrent_tasks:
|
|
198
213
|
return False
|
|
214
|
+
if (
|
|
215
|
+
task is not None
|
|
216
|
+
and task.WhichOneof("request") in ("wait_for", "execute")
|
|
217
|
+
and self.reserved_rpc_tasks > 0
|
|
218
|
+
and self._busy_tasks >= self.max_concurrent_tasks - self.reserved_rpc_tasks
|
|
219
|
+
and self.unknown_task_reason(task) is None
|
|
220
|
+
):
|
|
221
|
+
self.reserved_rpc_declines += 1
|
|
222
|
+
request = task.wait_for if task.HasField("wait_for") else task.execute
|
|
223
|
+
_LOGGER.debug(
|
|
224
|
+
"decline Step task for reserved RPC capacity: %s", request.flow_type
|
|
225
|
+
)
|
|
226
|
+
return False
|
|
199
227
|
self._busy_tasks += 1
|
|
200
228
|
self.signal()
|
|
201
229
|
return True
|
|
@@ -279,6 +307,8 @@ class PullStream:
|
|
|
279
307
|
self.is_busy = False
|
|
280
308
|
self._is_registered = False
|
|
281
309
|
self.is_draining = False
|
|
310
|
+
self._is_retiring = False
|
|
311
|
+
self._retirement_requested = asyncio.Event()
|
|
282
312
|
self.is_half_closing = False
|
|
283
313
|
self._active = threading.Event()
|
|
284
314
|
self._call: Any = None
|
|
@@ -321,7 +351,11 @@ class PullStream:
|
|
|
321
351
|
self.runtime._rejection_stop_task = asyncio.create_task(
|
|
322
352
|
self.runtime.stop()
|
|
323
353
|
)
|
|
324
|
-
if
|
|
354
|
+
if (
|
|
355
|
+
self.runtime.is_stopping
|
|
356
|
+
or self.runtime._permanent_error is not None
|
|
357
|
+
or self._is_retiring
|
|
358
|
+
):
|
|
325
359
|
break
|
|
326
360
|
if self._is_reconnecting:
|
|
327
361
|
self._is_reconnecting = False
|
|
@@ -378,11 +412,12 @@ class PullStream:
|
|
|
378
412
|
)
|
|
379
413
|
was_registered = False
|
|
380
414
|
reader: asyncio.Task[Any] | None = None
|
|
415
|
+
retirement = asyncio.create_task(self._retirement_requested.wait())
|
|
381
416
|
try:
|
|
382
417
|
while not self.runtime.is_stopping or self.is_busy:
|
|
383
418
|
if reader is None:
|
|
384
419
|
reader = asyncio.create_task(self._call.read())
|
|
385
|
-
waiting: set[asyncio.Task[Any]] = {reader}
|
|
420
|
+
waiting: set[asyncio.Task[Any]] = {reader, retirement}
|
|
386
421
|
if self.handler_task is not None:
|
|
387
422
|
waiting.add(self.handler_task)
|
|
388
423
|
completed, _ = await asyncio.wait(
|
|
@@ -390,22 +425,23 @@ class PullStream:
|
|
|
390
425
|
)
|
|
391
426
|
if self.handler_task in completed:
|
|
392
427
|
await self._finish_handler()
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
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)
|
|
428
|
+
self.try_retire()
|
|
429
|
+
if self.is_draining and not self.is_busy:
|
|
430
|
+
if reader.done():
|
|
431
|
+
frame = reader.result()
|
|
406
432
|
reader = None
|
|
407
|
-
|
|
408
|
-
|
|
433
|
+
if (
|
|
434
|
+
frame is not _GRPC_AIO_EOF
|
|
435
|
+
and frame.WhichOneof("frame") == "task"
|
|
436
|
+
):
|
|
437
|
+
await self._reject_task(frame.task)
|
|
438
|
+
self.is_half_closing = True
|
|
439
|
+
self.runtime.signal()
|
|
440
|
+
await self._call.done_writing()
|
|
441
|
+
await self._await_stream_eof(reader)
|
|
442
|
+
reader = None
|
|
443
|
+
self._is_reconnecting = not self._is_retiring
|
|
444
|
+
return was_registered
|
|
409
445
|
if reader not in completed:
|
|
410
446
|
continue
|
|
411
447
|
frame = reader.result()
|
|
@@ -425,6 +461,7 @@ class PullStream:
|
|
|
425
461
|
was_registered = True
|
|
426
462
|
self._is_registered = True
|
|
427
463
|
self.runtime.registration.record_registered_stream(self.queue)
|
|
464
|
+
self.runtime.signal()
|
|
428
465
|
continue
|
|
429
466
|
if kind == "task":
|
|
430
467
|
await self._start_task(frame.task)
|
|
@@ -442,6 +479,8 @@ class PullStream:
|
|
|
442
479
|
self._is_reconnecting = True
|
|
443
480
|
return was_registered
|
|
444
481
|
finally:
|
|
482
|
+
retirement.cancel()
|
|
483
|
+
await asyncio.gather(retirement, return_exceptions=True)
|
|
445
484
|
self.is_half_closing = False
|
|
446
485
|
self.cancel_handler()
|
|
447
486
|
if self.handler_task is not None:
|
|
@@ -511,7 +550,7 @@ class PullStream:
|
|
|
511
550
|
if unknown_reason is not None:
|
|
512
551
|
await self._reject_task(task, unknown_reason)
|
|
513
552
|
return
|
|
514
|
-
if self.is_busy or self.is_draining or not self.runtime.accept_task():
|
|
553
|
+
if self.is_busy or self.is_draining or not self.runtime.accept_task(task):
|
|
515
554
|
await self._reject_task(task)
|
|
516
555
|
return
|
|
517
556
|
self.is_busy = True
|
|
@@ -658,6 +697,22 @@ class PullStream:
|
|
|
658
697
|
self._active.clear()
|
|
659
698
|
self.runtime.finish_task()
|
|
660
699
|
|
|
700
|
+
def try_retire(self) -> None:
|
|
701
|
+
if self.is_busy or self.is_draining or not self._is_registered:
|
|
702
|
+
return
|
|
703
|
+
idle_replacements = sum(
|
|
704
|
+
candidate is not self
|
|
705
|
+
and candidate.queue == self.queue
|
|
706
|
+
and candidate._is_registered
|
|
707
|
+
and not candidate.is_busy
|
|
708
|
+
and not candidate.is_draining
|
|
709
|
+
for candidate in self.runtime._streams
|
|
710
|
+
)
|
|
711
|
+
if idle_replacements >= self.runtime.idle_streams_per_queue:
|
|
712
|
+
self.is_draining = True
|
|
713
|
+
self._is_retiring = True
|
|
714
|
+
self._retirement_requested.set()
|
|
715
|
+
|
|
661
716
|
def cancel_handler(self) -> None:
|
|
662
717
|
self._active.clear()
|
|
663
718
|
if self.runtime._executor is None and self.handler_task is not None:
|
|
@@ -221,6 +221,23 @@ class AsyncWorker:
|
|
|
221
221
|
raise RuntimeError("wait_pull_registered requires a pull Worker")
|
|
222
222
|
await self._pull.registration.wait_async(timeout)
|
|
223
223
|
|
|
224
|
+
@property
|
|
225
|
+
def pull_reserved_rpc_declines(self) -> int:
|
|
226
|
+
"""Return cumulative Step declines caused by reserved RPC capacity.
|
|
227
|
+
|
|
228
|
+
Full-capacity rejections and unknown names are excluded. The count never
|
|
229
|
+
resets during this Worker's lifetime and stays available after stop.
|
|
230
|
+
Reading it alongside handlers is safe. Listening Workers return zero.
|
|
231
|
+
Export this value to a monitoring counter to identify reserve pressure.
|
|
232
|
+
|
|
233
|
+
Returns:
|
|
234
|
+
The cumulative reserve-decline count, or zero for a listening Worker.
|
|
235
|
+
|
|
236
|
+
Examples:
|
|
237
|
+
>>> declines = worker.pull_reserved_rpc_declines
|
|
238
|
+
"""
|
|
239
|
+
return 0 if self._pull is None else self._pull.reserved_rpc_declines
|
|
240
|
+
|
|
224
241
|
def pull_registration_state(self) -> PullRegistrationState:
|
|
225
242
|
"""Return an immutable snapshot of pull queue registrations.
|
|
226
243
|
|