log-foundry 0.10.2.dev37__tar.gz → 0.10.2.dev39__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.
- {log_foundry-0.10.2.dev37 → log_foundry-0.10.2.dev39}/PKG-INFO +1 -1
- {log_foundry-0.10.2.dev37 → log_foundry-0.10.2.dev39}/pyproject.toml +1 -1
- {log_foundry-0.10.2.dev37 → log_foundry-0.10.2.dev39}/src/log_foundry/decorator.py +71 -4
- {log_foundry-0.10.2.dev37 → log_foundry-0.10.2.dev39}/src/log_foundry/worker.py +85 -9
- {log_foundry-0.10.2.dev37 → log_foundry-0.10.2.dev39}/LICENSE +0 -0
- {log_foundry-0.10.2.dev37 → log_foundry-0.10.2.dev39}/README.md +0 -0
- {log_foundry-0.10.2.dev37 → log_foundry-0.10.2.dev39}/src/log_foundry/__init__.py +0 -0
- {log_foundry-0.10.2.dev37 → log_foundry-0.10.2.dev39}/src/log_foundry/_diag.py +0 -0
- {log_foundry-0.10.2.dev37 → log_foundry-0.10.2.dev39}/src/log_foundry/_lifecycle.py +0 -0
- {log_foundry-0.10.2.dev37 → log_foundry-0.10.2.dev39}/src/log_foundry/api.py +0 -0
- {log_foundry-0.10.2.dev37 → log_foundry-0.10.2.dev39}/src/log_foundry/config.py +0 -0
- {log_foundry-0.10.2.dev37 → log_foundry-0.10.2.dev39}/src/log_foundry/console.py +0 -0
- {log_foundry-0.10.2.dev37 → log_foundry-0.10.2.dev39}/src/log_foundry/context.py +0 -0
- {log_foundry-0.10.2.dev37 → log_foundry-0.10.2.dev39}/src/log_foundry/ids.py +0 -0
- {log_foundry-0.10.2.dev37 → log_foundry-0.10.2.dev39}/src/log_foundry/model.py +0 -0
- {log_foundry-0.10.2.dev37 → log_foundry-0.10.2.dev39}/src/log_foundry/py.typed +0 -0
- {log_foundry-0.10.2.dev37 → log_foundry-0.10.2.dev39}/src/log_foundry/sanitize.py +0 -0
- {log_foundry-0.10.2.dev37 → log_foundry-0.10.2.dev39}/src/log_foundry/sinks/__init__.py +0 -0
- {log_foundry-0.10.2.dev37 → log_foundry-0.10.2.dev39}/src/log_foundry/sinks/_batch.py +0 -0
- {log_foundry-0.10.2.dev37 → log_foundry-0.10.2.dev39}/src/log_foundry/sinks/_chunk.py +0 -0
- {log_foundry-0.10.2.dev37 → log_foundry-0.10.2.dev39}/src/log_foundry/sinks/_retry.py +0 -0
- {log_foundry-0.10.2.dev37 → log_foundry-0.10.2.dev39}/src/log_foundry/sinks/_socket.py +0 -0
- {log_foundry-0.10.2.dev37 → log_foundry-0.10.2.dev39}/src/log_foundry/sinks/_time.py +0 -0
- {log_foundry-0.10.2.dev37 → log_foundry-0.10.2.dev39}/src/log_foundry/sinks/base.py +0 -0
- {log_foundry-0.10.2.dev37 → log_foundry-0.10.2.dev39}/src/log_foundry/sinks/callback.py +0 -0
- {log_foundry-0.10.2.dev37 → log_foundry-0.10.2.dev39}/src/log_foundry/sinks/clickhouse.py +0 -0
- {log_foundry-0.10.2.dev37 → log_foundry-0.10.2.dev39}/src/log_foundry/sinks/datadog.py +0 -0
- {log_foundry-0.10.2.dev37 → log_foundry-0.10.2.dev39}/src/log_foundry/sinks/elasticsearch.py +0 -0
- {log_foundry-0.10.2.dev37 → log_foundry-0.10.2.dev39}/src/log_foundry/sinks/eventhubs.py +0 -0
- {log_foundry-0.10.2.dev37 → log_foundry-0.10.2.dev39}/src/log_foundry/sinks/file.py +0 -0
- {log_foundry-0.10.2.dev37 → log_foundry-0.10.2.dev39}/src/log_foundry/sinks/filtering.py +0 -0
- {log_foundry-0.10.2.dev37 → log_foundry-0.10.2.dev39}/src/log_foundry/sinks/firehose.py +0 -0
- {log_foundry-0.10.2.dev37 → log_foundry-0.10.2.dev39}/src/log_foundry/sinks/honeycomb.py +0 -0
- {log_foundry-0.10.2.dev37 → log_foundry-0.10.2.dev39}/src/log_foundry/sinks/http.py +0 -0
- {log_foundry-0.10.2.dev37 → log_foundry-0.10.2.dev39}/src/log_foundry/sinks/kafka.py +0 -0
- {log_foundry-0.10.2.dev37 → log_foundry-0.10.2.dev39}/src/log_foundry/sinks/kinesis.py +0 -0
- {log_foundry-0.10.2.dev37 → log_foundry-0.10.2.dev39}/src/log_foundry/sinks/logging_sink.py +0 -0
- {log_foundry-0.10.2.dev37 → log_foundry-0.10.2.dev39}/src/log_foundry/sinks/logstash.py +0 -0
- {log_foundry-0.10.2.dev37 → log_foundry-0.10.2.dev39}/src/log_foundry/sinks/loki.py +0 -0
- {log_foundry-0.10.2.dev37 → log_foundry-0.10.2.dev39}/src/log_foundry/sinks/mongodb.py +0 -0
- {log_foundry-0.10.2.dev37 → log_foundry-0.10.2.dev39}/src/log_foundry/sinks/multi.py +0 -0
- {log_foundry-0.10.2.dev37 → log_foundry-0.10.2.dev39}/src/log_foundry/sinks/nats.py +0 -0
- {log_foundry-0.10.2.dev37 → log_foundry-0.10.2.dev39}/src/log_foundry/sinks/newrelic.py +0 -0
- {log_foundry-0.10.2.dev37 → log_foundry-0.10.2.dev39}/src/log_foundry/sinks/postgres.py +0 -0
- {log_foundry-0.10.2.dev37 → log_foundry-0.10.2.dev39}/src/log_foundry/sinks/pubsub.py +0 -0
- {log_foundry-0.10.2.dev37 → log_foundry-0.10.2.dev39}/src/log_foundry/sinks/rabbitmq.py +0 -0
- {log_foundry-0.10.2.dev37 → log_foundry-0.10.2.dev39}/src/log_foundry/sinks/redis.py +0 -0
- {log_foundry-0.10.2.dev37 → log_foundry-0.10.2.dev39}/src/log_foundry/sinks/sentry.py +0 -0
- {log_foundry-0.10.2.dev37 → log_foundry-0.10.2.dev39}/src/log_foundry/sinks/sns.py +0 -0
- {log_foundry-0.10.2.dev37 → log_foundry-0.10.2.dev39}/src/log_foundry/sinks/splunk.py +0 -0
- {log_foundry-0.10.2.dev37 → log_foundry-0.10.2.dev39}/src/log_foundry/sinks/sqlite.py +0 -0
- {log_foundry-0.10.2.dev37 → log_foundry-0.10.2.dev39}/src/log_foundry/sinks/sqs.py +0 -0
- {log_foundry-0.10.2.dev37 → log_foundry-0.10.2.dev39}/src/log_foundry/sinks/stdout.py +0 -0
- {log_foundry-0.10.2.dev37 → log_foundry-0.10.2.dev39}/src/log_foundry/sinks/syslog.py +0 -0
- {log_foundry-0.10.2.dev37 → log_foundry-0.10.2.dev39}/src/log_foundry/sinks/transform.py +0 -0
- {log_foundry-0.10.2.dev37 → log_foundry-0.10.2.dev39}/src/log_foundry/sinks/util.py +0 -0
|
@@ -350,6 +350,21 @@ def _offer_orphan_signal(sink: Sink) -> None:
|
|
|
350
350
|
leave the drain thread serving a full backoff across ``Worker.shutdown``'s join, which is the
|
|
351
351
|
global pause SPEC-027 exists to remove.
|
|
352
352
|
|
|
353
|
+
Ownership alone is not the whole predicate either, and this site is the one place in the
|
|
354
|
+
module where neither of the two categories fits (SPEC-035 FR-001). :func:`_live_worker` was
|
|
355
|
+
wrong: ``retired`` latches on **entry** to ``Worker.shutdown``, so for the whole of the drain
|
|
356
|
+
the skip stopped applying and an orphan log handed the sink a fresh unset event — precisely
|
|
357
|
+
the one the drain thread was about to wait on. Measured, a 20 s backoff against
|
|
358
|
+
``shutdown(timeout=3)`` was then still outstanding when the shutdown expired at 3.01 s with
|
|
359
|
+
the sink left open — the wait was never cut, not merely slow. But bare ``_worker.sink is
|
|
360
|
+
sink`` is wrong in the opposite direction: it skips for a worker whose shutdown has
|
|
361
|
+
**finished**, leaving a sink still being written to holding a set event that can never clear,
|
|
362
|
+
which is SPEC-033 FR-004's tight retry loop and is covered by a test that spec shipped. The
|
|
363
|
+
predicate is therefore the ownership term **conjoined with** ``Worker.draining`` — the
|
|
364
|
+
moment as well as the identity, since without the identity an orphan log to sink Y would be
|
|
365
|
+
skipped merely because a live worker is draining into sink X. That property's docstring
|
|
366
|
+
carries the rest of the reasoning.
|
|
367
|
+
|
|
353
368
|
A fresh event replaces one that is already set, because an ``Event`` never clears and
|
|
354
369
|
``sinks/_retry.wait`` returns immediately on a set one — a sink handed the shutdown's event
|
|
355
370
|
would have every subsequent backoff collapsed to zero, which against a rate-limited
|
|
@@ -368,8 +383,8 @@ def _offer_orphan_signal(sink: Sink) -> None:
|
|
|
368
383
|
None.
|
|
369
384
|
"""
|
|
370
385
|
global _orphan_stop
|
|
371
|
-
worker =
|
|
372
|
-
if worker is not None and worker.sink is sink:
|
|
386
|
+
worker = _worker
|
|
387
|
+
if worker is not None and worker.sink is sink and worker.draining:
|
|
373
388
|
return
|
|
374
389
|
if _orphan_stop.is_set():
|
|
375
390
|
_orphan_stop = threading.Event()
|
|
@@ -506,7 +521,10 @@ def _swap_sink(new_sink: Sink, timeout: float | None = DEFAULT_SWAP_TIMEOUT) ->
|
|
|
506
521
|
``sinks/base.py`` requires ``close()`` to tolerate a concurrent ``emit`` (SPEC-028 FR-001).
|
|
507
522
|
This inherits that contract rather than weakening it.
|
|
508
523
|
|
|
509
|
-
**Who performs the swap
|
|
524
|
+
**Who performs the swap, who owns the old sink's close, and who owns the new one when the
|
|
525
|
+
swap is declined are three questions** — the third added by SPEC-035 FR-003 and answered by
|
|
526
|
+
:func:`_adopt_declined_swap`, off ``Worker.swap_sink``'s return value rather than a predicate
|
|
527
|
+
here, because only the worker knows whether it got as far as reassigning. The first is
|
|
510
528
|
liveness — a retired worker performs nothing, since :meth:`Worker.swap_sink` returns early
|
|
511
529
|
once shut down, so routing the swap to it loses the handoff entirely. The second is
|
|
512
530
|
ownership, exactly as in :func:`_close_orphan_sink`: a worker that *holds* ``old`` has either
|
|
@@ -559,16 +577,65 @@ def _swap_sink(new_sink: Sink, timeout: float | None = DEFAULT_SWAP_TIMEOUT) ->
|
|
|
559
577
|
closer = _lifecycle.close_detached(old)
|
|
560
578
|
if worker is not None:
|
|
561
579
|
try:
|
|
562
|
-
worker.swap_sink(new_sink, timeout)
|
|
580
|
+
adopted = worker.swap_sink(new_sink, timeout)
|
|
563
581
|
except Exception as exc:
|
|
564
582
|
_diag.absorbed(
|
|
565
583
|
"swapping the log sink", exc, "events may still be delivered to the previous sink"
|
|
566
584
|
)
|
|
585
|
+
return
|
|
586
|
+
if not adopted:
|
|
587
|
+
_adopt_declined_swap(new_sink)
|
|
567
588
|
return
|
|
568
589
|
if closer is not None:
|
|
569
590
|
closer.join(timeout)
|
|
570
591
|
|
|
571
592
|
|
|
593
|
+
def _adopt_declined_swap(new_sink: Sink) -> None:
|
|
594
|
+
"""Takes ownership of a sink a worker refused mid-swap (SPEC-035 FR-003).
|
|
595
|
+
|
|
596
|
+
``Worker.swap_sink`` re-checks retirement after its first ``flush()`` and returns early once
|
|
597
|
+
``_shutdown_done`` latched, but :func:`_swap_sink` had already cleared ``_orphan_sink`` on the
|
|
598
|
+
strength of a worker being live a few instructions earlier. The new sink then sat in the
|
|
599
|
+
config, installed nowhere and recorded nowhere: measured, ``config.sink is B`` was ``True``,
|
|
600
|
+
``B`` was never closed, and ``health()`` read entirely clean — for a sink whose ``close()``
|
|
601
|
+
*is* its delivery, a ``KafkaSink`` flushing its producer, a silently lost buffer.
|
|
602
|
+
|
|
603
|
+
Only the new sink is re-homed. ``Worker.swap_sink`` declines before reassigning anything, so
|
|
604
|
+
whatever the worker held it still holds, and the worker closes it through its own
|
|
605
|
+
``_close_if_owed``; closing it here would be the double-close :func:`_close_orphan_sink`
|
|
606
|
+
exists to avoid. The one case with no "old" sink at all is a retired worker that already
|
|
607
|
+
holds ``new_sink`` — the entry check tests ``_shutdown_done`` before identity, so it declines
|
|
608
|
+
a swap that was already satisfied — and there the same ownership guard makes the worker
|
|
609
|
+
perform the single close.
|
|
610
|
+
|
|
611
|
+
**A sink already recorded as closed is refused re-arming**, the guard
|
|
612
|
+
:func:`_note_orphan_emit` carries and for its reason. Without it, an orphan log arming
|
|
613
|
+
``_orphan_sink`` while this thread is inside ``swap_sink``'s first drain, followed by a
|
|
614
|
+
``shutdown()`` that closes it, lets this re-arm a closed sink for a second ``close()`` at
|
|
615
|
+
exit — reproduced, and ``Sink.close`` promises no idempotency.
|
|
616
|
+
|
|
617
|
+
``incomplete_swaps`` is deliberately not moved. It counts an unconfirmed *drain*, and this
|
|
618
|
+
swap had no drain to confirm — the worker declined before reassigning anything — so counting
|
|
619
|
+
it would stop telling an operator whether events were misrouted or a close was merely slow.
|
|
620
|
+
|
|
621
|
+
Args:
|
|
622
|
+
new_sink: The sink the worker refused to adopt.
|
|
623
|
+
|
|
624
|
+
Returns:
|
|
625
|
+
None.
|
|
626
|
+
|
|
627
|
+
Raises:
|
|
628
|
+
None.
|
|
629
|
+
"""
|
|
630
|
+
global _orphan_sink
|
|
631
|
+
with _worker_lock:
|
|
632
|
+
_offer_orphan_signal(new_sink)
|
|
633
|
+
if new_sink is _orphan_sink or new_sink is _orphan_closed_sink:
|
|
634
|
+
return
|
|
635
|
+
_register_exit_handler()
|
|
636
|
+
_orphan_sink = new_sink
|
|
637
|
+
|
|
638
|
+
|
|
572
639
|
def _flush_worker(timeout: float | None = 5.0) -> bool:
|
|
573
640
|
"""Drains the process worker without retiring it, backing ``flush()`` (SPEC-013 FR-003).
|
|
574
641
|
|
|
@@ -226,6 +226,7 @@ class Worker:
|
|
|
226
226
|
self._queue: queue.Queue[object] = queue.Queue(maxsize=max_queue)
|
|
227
227
|
self._stop = threading.Event()
|
|
228
228
|
self._drain_finished = threading.Event()
|
|
229
|
+
self._drain_settled = threading.Event()
|
|
229
230
|
self._shutdown_done = False
|
|
230
231
|
self._sink_closed = False
|
|
231
232
|
self._lock = threading.Lock()
|
|
@@ -351,6 +352,54 @@ class Worker:
|
|
|
351
352
|
"""
|
|
352
353
|
return self._shutdown_done
|
|
353
354
|
|
|
355
|
+
@property
|
|
356
|
+
def draining(self) -> bool:
|
|
357
|
+
"""Whether this worker's own ``_stop`` is still the sink's route to a cut-short backoff.
|
|
358
|
+
|
|
359
|
+
Neither ``retired`` nor a bare ``_worker is not None`` answers this, and the two specs
|
|
360
|
+
that got it wrong disagreed in opposite directions (SPEC-035 FR-001). SPEC-033 requires a
|
|
361
|
+
sink still being written to *after* a shutdown to be handed a **fresh** event, because
|
|
362
|
+
``_stop`` is set forever and an ``Event`` never clears, so every later backoff would
|
|
363
|
+
collapse to zero. SPEC-035 requires a sink whose drain is **in flight** to keep the event
|
|
364
|
+
that drain is about to wait on, or the shutdown serves a full backoff and expires. Both
|
|
365
|
+
are the same sink held by the same worker; only the moment differs, so the predicate has
|
|
366
|
+
to be the moment.
|
|
367
|
+
|
|
368
|
+
An abandoned drain counts as not draining. The thread is wedged and the shutdown has
|
|
369
|
+
already given up on it (``_close_orphan_sink`` and SPEC-027 FR-004 leave the sink open
|
|
370
|
+
for that reason), so nothing further will cut its backoff — where SPEC-033's tight retry
|
|
371
|
+
loop would go on costing the still-running application every emit.
|
|
372
|
+
|
|
373
|
+
It reads **one** event, ``_drain_settled``, set both where the loop stops and where a
|
|
374
|
+
shutdown gives up on it — not a conjunction of the two facts. A reader that tested them
|
|
375
|
+
separately would be correct only at the instant it looked: :meth:`shutdown`'s idempotent
|
|
376
|
+
path evaluates this and *then* waits, so an abandonment landing in between has to release
|
|
377
|
+
that wait, and only an event already being waited on can do that. Measured before the
|
|
378
|
+
change, with the first caller expiring while the second was inside its wait: a second
|
|
379
|
+
``shutdown(timeout=20)`` returned after 20.01 s, and with ``timeout=None`` the process
|
|
380
|
+
never exited at all.
|
|
381
|
+
|
|
382
|
+
``_drain_finished`` is deliberately left alone rather than widened: :meth:`flush` and
|
|
383
|
+
:meth:`shutdown`'s sentinel gate both read it as "the loop stopped reading the queue",
|
|
384
|
+
which an abandoned — and still running — drain has not done. The two events stay
|
|
385
|
+
distinguishable without a third flag, which is why no ``_drain_abandoned`` is kept:
|
|
386
|
+
abandoned is ``_drain_settled`` set with ``_drain_finished`` unset, and the operator-facing
|
|
387
|
+
form of the same fact is ``stopped_reason == "ShutdownTimeout"``.
|
|
388
|
+
|
|
389
|
+
Read without the lock, as ``retired`` is: the event is set once and never cleared, so a
|
|
390
|
+
racing reader sees one of two answers and both are momentarily true.
|
|
391
|
+
|
|
392
|
+
Args:
|
|
393
|
+
None.
|
|
394
|
+
|
|
395
|
+
Returns:
|
|
396
|
+
Whether the drain loop is still running and has not been abandoned.
|
|
397
|
+
|
|
398
|
+
Raises:
|
|
399
|
+
None.
|
|
400
|
+
"""
|
|
401
|
+
return not self._drain_settled.is_set()
|
|
402
|
+
|
|
354
403
|
def health(self) -> Health:
|
|
355
404
|
"""Snapshots the delivery counters (SPEC-017 FR-005, SPEC-019 FR-003).
|
|
356
405
|
|
|
@@ -476,7 +525,7 @@ class Worker:
|
|
|
476
525
|
return False
|
|
477
526
|
return marker.delivered
|
|
478
527
|
|
|
479
|
-
def swap_sink(self, new_sink: Sink, timeout: float | None = DEFAULT_SWAP_TIMEOUT) ->
|
|
528
|
+
def swap_sink(self, new_sink: Sink, timeout: float | None = DEFAULT_SWAP_TIMEOUT) -> bool:
|
|
480
529
|
"""Retargets delivery at a new sink, draining and closing the previous one (FR-003).
|
|
481
530
|
|
|
482
531
|
This is what makes a late ``configure(sink=...)`` mean what it says. The sink was
|
|
@@ -515,33 +564,41 @@ class Worker:
|
|
|
515
564
|
hold ``configure()`` for a multiple of the budget. ``None`` waits indefinitely.
|
|
516
565
|
|
|
517
566
|
Returns:
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
567
|
+
Whether this worker now holds ``new_sink``, and therefore owns its close. ``False``
|
|
568
|
+
means **declined**, which happens only when ``_shutdown_done`` latched — and the
|
|
569
|
+
caller must then own the handoff itself, because a declined swap leaves the new sink
|
|
570
|
+
installed nowhere (SPEC-035 FR-003). ``True`` covers the sink already being this
|
|
571
|
+
worker's, which owes nothing further. The *quality* of the swap is still reported
|
|
572
|
+
through ``health().incomplete_swaps`` and one stderr line rather than here: an
|
|
573
|
+
unconfirmed drain is a swap that happened, so it returns ``True``, and conflating the
|
|
574
|
+
two would make ``_swap_sink`` re-home a sink this worker is delivering to.
|
|
521
575
|
|
|
522
576
|
Raises:
|
|
523
577
|
None on a sink fault. A close that fails is announced, as everywhere else
|
|
524
578
|
(SPEC-025 FR-004).
|
|
525
579
|
"""
|
|
526
580
|
with self._lock:
|
|
527
|
-
if self._shutdown_done
|
|
528
|
-
return
|
|
581
|
+
if self._shutdown_done:
|
|
582
|
+
return False
|
|
583
|
+
if self.sink is new_sink:
|
|
584
|
+
return True
|
|
529
585
|
deadline = None if timeout is None else time.monotonic() + timeout
|
|
530
586
|
drained = self.flush(timeout)
|
|
531
587
|
with self._lock:
|
|
532
588
|
if self._shutdown_done:
|
|
533
|
-
return
|
|
589
|
+
return False
|
|
534
590
|
old = self.sink
|
|
535
591
|
if old is new_sink:
|
|
536
|
-
return
|
|
592
|
+
return True
|
|
537
593
|
self.sink = new_sink
|
|
538
594
|
self._offer_stop_signal()
|
|
539
595
|
remaining = None if deadline is None else max(0.0, deadline - time.monotonic())
|
|
540
596
|
if not (drained and self.flush(remaining)):
|
|
541
597
|
self._record_incomplete_swap(timeout)
|
|
542
|
-
return
|
|
598
|
+
return True
|
|
543
599
|
left = None if deadline is None else max(0.0, deadline - time.monotonic())
|
|
544
600
|
self._close_swapped_out(old, left)
|
|
601
|
+
return True
|
|
545
602
|
|
|
546
603
|
def _record_incomplete_swap(self, timeout: float | None) -> None:
|
|
547
604
|
"""Counts a swap whose drain could not be confirmed, then announces it (FR-003).
|
|
@@ -663,6 +720,19 @@ class Worker:
|
|
|
663
720
|
liveness test would queue a sentinel through that whole window. The flag is set before
|
|
664
721
|
that call, so this one does not.
|
|
665
722
|
|
|
723
|
+
**The idempotent path waits for the drain it found running** (SPEC-035 FR-004).
|
|
724
|
+
``_shutdown_done`` latches on *entry*, so a second caller used to take that branch and
|
|
725
|
+
return in under a millisecond over a drain that had barely started. The shape is rarely
|
|
726
|
+
two user calls: it is a ``shutdown()`` on one thread and ``atexit`` on the main thread,
|
|
727
|
+
and measured with a 2 s-emit sink and three traced calls it delivered **nothing**, never
|
|
728
|
+
closed the sink, and left the process gone in 0.39 s with nothing on stderr. The
|
|
729
|
+
wait is on ``_drain_settled`` and bounded by *this* call's own budget, so
|
|
730
|
+
``shutdown(timeout=0)`` still returns promptly and a caller never inherits the other
|
|
731
|
+
call's deadline. It is skipped where :attr:`draining` is already false, and **released**
|
|
732
|
+
where the drain is abandoned after the wait began — the first caller sets the same event
|
|
733
|
+
when its join expires, which is what stops a wedged thread costing the second caller its
|
|
734
|
+
whole budget in front of an exit that has to happen.
|
|
735
|
+
|
|
666
736
|
:meth:`_release_waiters` runs on the way out for the sibling case the ordering cannot
|
|
667
737
|
reach: a ``flush()`` that passed its liveness check microseconds before the thread
|
|
668
738
|
finished can still queue a marker nothing will answer, and with ``timeout=None`` that
|
|
@@ -691,6 +761,10 @@ class Worker:
|
|
|
691
761
|
first = not self._shutdown_done
|
|
692
762
|
self._shutdown_done = True
|
|
693
763
|
if not first:
|
|
764
|
+
if self.draining:
|
|
765
|
+
self._drain_settled.wait(
|
|
766
|
+
None if deadline is None else max(0.0, deadline - time.monotonic())
|
|
767
|
+
)
|
|
694
768
|
self._close_if_owed()
|
|
695
769
|
self._join_closers(None if deadline is None else max(0.0, deadline - time.monotonic()))
|
|
696
770
|
return
|
|
@@ -706,6 +780,7 @@ class Worker:
|
|
|
706
780
|
with self._lock:
|
|
707
781
|
if self.stopped_reason is None:
|
|
708
782
|
self.stopped_reason = "ShutdownTimeout"
|
|
783
|
+
self._drain_settled.set()
|
|
709
784
|
_diag.lost(
|
|
710
785
|
"item",
|
|
711
786
|
queued,
|
|
@@ -879,6 +954,7 @@ class Worker:
|
|
|
879
954
|
self._drain(pending)
|
|
880
955
|
finally:
|
|
881
956
|
self._drain_finished.set()
|
|
957
|
+
self._drain_settled.set()
|
|
882
958
|
except BaseException as exc:
|
|
883
959
|
self._terminal_failure(exc, len(pending))
|
|
884
960
|
finally:
|
|
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
|
{log_foundry-0.10.2.dev37 → log_foundry-0.10.2.dev39}/src/log_foundry/sinks/elasticsearch.py
RENAMED
|
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
|