by-framework 0.2.2.dev14__py3-none-any.whl → 0.2.2.dev16__py3-none-any.whl

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.
@@ -367,6 +367,60 @@ _CATALOG: dict[str, MetricDefinition] = {
367
367
  "Instrumentation reliability signal pinpointing exporter failures."
368
368
  ),
369
369
  ),
370
+ # Self-monitoring for the background collector in metrics/collector.py.
371
+ # Exactly one process holds its Redis lock and writes history points, so
372
+ # these answer "is anyone still collecting?" — a gap here means the
373
+ # dashboards go stale silently rather than erroring.
374
+ "by_framework_metrics_collector_cycles_total": MetricDefinition(
375
+ name="by_framework_metrics_collector_cycles_total",
376
+ kind=MetricKind.COUNTER,
377
+ unit=MetricUnit.NONE,
378
+ labels=("result",),
379
+ description=(
380
+ "Metrics collector cycles by result "
381
+ "(success, snapshot_failed, lock_skipped, disabled)."
382
+ ),
383
+ interpretation=(
384
+ "Collector health signal: only the lock holder reports success, so "
385
+ "lock_skipped dominating across a fleet is expected."
386
+ ),
387
+ ),
388
+ "by_framework_metrics_collector_snapshot_duration_ms": MetricDefinition(
389
+ name="by_framework_metrics_collector_snapshot_duration_ms",
390
+ kind=MetricKind.HISTOGRAM,
391
+ unit=MetricUnit.MILLISECONDS,
392
+ description=(
393
+ "Duration of one collector snapshot build plus history write."
394
+ ),
395
+ interpretation=(
396
+ "Latency signal for collection itself; approaching the collection "
397
+ "interval means cycles are about to overlap."
398
+ ),
399
+ ),
400
+ "by_framework_metrics_collector_lock_held": MetricDefinition(
401
+ name="by_framework_metrics_collector_lock_held",
402
+ kind=MetricKind.GAUGE,
403
+ unit=MetricUnit.NONE,
404
+ description=(
405
+ "Whether this process currently holds the metrics collector lock."
406
+ ),
407
+ interpretation=(
408
+ "Saturation signal: should sum to 1 across the fleet — 0 means "
409
+ "nobody is collecting, above 1 means the lock split."
410
+ ),
411
+ ),
412
+ "by_framework_metrics_collector_last_success_timestamp_ms": MetricDefinition(
413
+ name="by_framework_metrics_collector_last_success_timestamp_ms",
414
+ kind=MetricKind.GAUGE,
415
+ unit=MetricUnit.MILLISECONDS,
416
+ description=(
417
+ "Unix timestamp in milliseconds of the last successful collection."
418
+ ),
419
+ interpretation=(
420
+ "Freshness signal: alert on now minus this value exceeding a few "
421
+ "collection intervals."
422
+ ),
423
+ ),
370
424
  }
371
425
 
372
426
 
@@ -83,6 +83,7 @@ class WorkerRunner:
83
83
  self.worker = worker
84
84
  self.group_name = group_name or self._auto_group_name()
85
85
  self.consumer_name = worker.worker_id
86
+ self.max_concurrency = max_concurrency
86
87
  self.semaphore = asyncio.Semaphore(max_concurrency)
87
88
  self.fetch_count = fetch_count
88
89
  # OTel for worker.execute is emitted via a live wrapping span (see
@@ -121,6 +122,14 @@ class WorkerRunner:
121
122
  float(self.worker.heartbeat_lease_ttl_seconds) * 2.0,
122
123
  stream_block_seconds * 3.0,
123
124
  )
125
+ # Poll interval used by _acquire_slot() while waiting for a saturated
126
+ # semaphore. Derived from _consumer_health_timeout_seconds (not a
127
+ # hardcoded constant) so it stays well under the health-check
128
+ # deadline under any configuration, with a floor to avoid busy-
129
+ # polling if the timeout is configured very small.
130
+ self._slot_wait_poll_seconds = max(
131
+ self._consumer_health_timeout_seconds / 4.0, 1.0
132
+ )
124
133
  # Set as the first step of _shutdown() - see
125
134
  # docs/architecture/worker-readiness-endpoint.md Decision 6.
126
135
  self._draining: bool = False
@@ -386,9 +395,23 @@ class WorkerRunner:
386
395
  finally:
387
396
  await self.redis.xack(stream_name, self.group_name, msg_id)
388
397
 
398
+ async def _acquire_slot(self) -> None:
399
+ """Wait for a concurrency slot, marking the consumer tick on every
400
+ poll so a saturated-but-alive loop isn't judged stalled.
401
+
402
+ Polling on `len(self._running_tasks)` instead of wrapping
403
+ `semaphore.acquire()` in `asyncio.wait_for()` avoids relying on
404
+ Semaphore cancellation semantics for a lock this runner is the only
405
+ waiter on — see design.md's "why not asyncio.wait_for" section.
406
+ """
407
+ while len(self._running_tasks) >= self.max_concurrency:
408
+ self._mark_consumer_tick()
409
+ await asyncio.sleep(self._slot_wait_poll_seconds)
410
+ await self.semaphore.acquire()
411
+
389
412
  async def _run_once(self) -> bool:
390
413
  """Fetch and start processing messages."""
391
- await self.semaphore.acquire()
414
+ await self._acquire_slot()
392
415
 
393
416
  try:
394
417
  messages = await self.fetch_messages(
@@ -401,7 +424,7 @@ class WorkerRunner:
401
424
 
402
425
  for i, (stream_name, msg_id, data_dict) in enumerate(messages):
403
426
  if i > 0:
404
- await self.semaphore.acquire()
427
+ await self._acquire_slot()
405
428
 
406
429
  task = asyncio.create_task(
407
430
  self._process_and_release(stream_name, msg_id, data_dict)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: by-framework
3
- Version: 0.2.2.dev14
3
+ Version: 0.2.2.dev16
4
4
  Summary: 分布式 Agent 调度框架
5
5
  License-File: LICENSE
6
6
  Requires-Python: >=3.12
@@ -69,7 +69,7 @@ by_framework/errors/http.py,sha256=xtm4EKwbE1Z0n4yqWTjPOVbDd9fIPfmT8W4ou5aOLCQ,7
69
69
  by_framework/errors/protocol.py,sha256=MWr15TFIHs8Ql67mgvU9mebGdE04yPxmg4AOEGbF9E4,1269
70
70
  by_framework/errors/registry.py,sha256=LmcqSXiW8FSOghpLPHTllkbrQCnVcMYTLzO2cNU3wIw,842
71
71
  by_framework/metrics/__init__.py,sha256=8AQpVnOwAmHoQsDhCfkNXSKUOX_ZCBF_QajBXmsNhgc,11353
72
- by_framework/metrics/catalog.py,sha256=CR9-1M_L0rR-5jgGUw3M1TplTzntpTjN2MXG34s9lnw,17355
72
+ by_framework/metrics/catalog.py,sha256=1yWxVCvvrbkT0Rdmi0x-rtRwtXFzQg6Dmuj75izN2X0,19684
73
73
  by_framework/metrics/collector.py,sha256=uIO2TJfX4zQTBi5gWbMfgwtdMjrhmhzjzW36BcqxBKE,8533
74
74
  by_framework/metrics/read_client.py,sha256=kcgHJlJh47eFPhlbH8QOyD-K6SpKbZHEX624XD6Mvxo,18511
75
75
  by_framework/metrics/snapshot.py,sha256=eI4JVV3uma7fT6nvp2bliO2ZUBJhJT6bzTvQDIeC-wk,101784
@@ -94,12 +94,12 @@ by_framework/worker/context.py,sha256=5As3GcnVrwIE1nCJDDYLt_Ke8-8nzh44CzQBvyncQs
94
94
  by_framework/worker/health_server.py,sha256=4Jw3ltAD7Ow4dPHhwTUU_4JjKz-iDCNG7RJzD2VzPlM,6090
95
95
  by_framework/worker/heartbeat.py,sha256=trHDS8FUALsA6jyzDPa087hpr8_2vY8ZUFn0ld1kI2o,15465
96
96
  by_framework/worker/processor.py,sha256=UgC4_p-bg2KOHVTEILeMXF44rT9VLrRP5vompX0EeiQ,16243
97
- by_framework/worker/runner.py,sha256=6_L_UUV1Kljys_4Eh_qnwMd7a-gGsikd4M_O5K879NA,51897
97
+ by_framework/worker/runner.py,sha256=nkJDpziUQaqqVEXc8OhoF4DKGiw7fthpvXudDjxYdm0,53093
98
98
  by_framework/worker/worker.py,sha256=IaPFLWCFo5U5Xo7PIQHtTt4JmtxZA1O_3nAuGl_g4N0,49527
99
99
  by_framework/worker/sandbox/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
100
100
  by_framework/worker/sandbox/hook_sandbox.py,sha256=XXSdH5YGX814LvV03vkq-zn5DVIBPYYt2NhRv1lT1Bk,2329
101
- by_framework-0.2.2.dev14.dist-info/METADATA,sha256=LpR98e5DnEucXKFwftIul_FFYGw3BQvTR3QNP2cayX0,28018
102
- by_framework-0.2.2.dev14.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
103
- by_framework-0.2.2.dev14.dist-info/entry_points.txt,sha256=5t6kgMjkR0UgJTpOsbuQDo0fOf7zeXjQW9DhkxfAGFc,56
104
- by_framework-0.2.2.dev14.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
105
- by_framework-0.2.2.dev14.dist-info/RECORD,,
101
+ by_framework-0.2.2.dev16.dist-info/METADATA,sha256=3Sbgc5Aw5L6vYzfRQpcfyMK9B-qYs9V-TMvAJGHwrAM,28018
102
+ by_framework-0.2.2.dev16.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
103
+ by_framework-0.2.2.dev16.dist-info/entry_points.txt,sha256=5t6kgMjkR0UgJTpOsbuQDo0fOf7zeXjQW9DhkxfAGFc,56
104
+ by_framework-0.2.2.dev16.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
105
+ by_framework-0.2.2.dev16.dist-info/RECORD,,