reactor-runtime 2.7.0__tar.gz → 2.7.2__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.
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/PKG-INFO +1 -1
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/pyproject.toml +1 -1
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/recording/chunk_uploader.py +37 -8
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/recording/config.py +8 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/recording/markers.py +51 -5
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/recording/session_recorder.py +53 -9
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/utils/launch.py +4 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/utils/log.py +14 -4
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime.egg-info/PKG-INFO +1 -1
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/README.md +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/setup.cfg +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/api/__init__.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/__init__.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/config.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/interface/__init__.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/interface/defaults.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/interface/driver/__init__.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/interface/driver/pipeline_executor.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/interface/driver/step_result.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/interface/events/__init__.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/interface/events/connected.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/interface/events/event.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/interface/events/messages.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/interface/events/upload.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/interface/internal/__init__.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/interface/internal/input_buffer.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/interface/internal/output_buffer.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/interface/internal/reactor_core.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/interface/model/__init__.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/interface/model/decorators.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/interface/model/handlers.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/interface/model/reactor_model.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/interface/pipeline/__init__.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/interface/pipeline/idle.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/interface/pipeline/input_state.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/interface/pipeline/reactor_pipeline.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/interface/tracks/__init__.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/interface/tracks/descriptors.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/interface/tracks/input.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/interface/tracks/output.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/interface/upload.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/model_state.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/profiling/__init__.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/profiling/backends/__init__.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/profiling/backends/base.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/profiling/backends/file.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/profiling/backends/otlp.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/profiling/helpers.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/profiling/plotting/__init__.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/profiling/plotting/plot_profiling.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/profiling/profiler.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/profiling/singleton.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/recording/__init__.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/recording/chunk_encoder.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/recording/sinks.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/recording/track_resolver.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/runtime_api.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/runtimes/headless/config.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/runtimes/headless/headless_runtime.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/runtimes/headless/input_feeder.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/runtimes/http/config.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/runtimes/http/http_runtime.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/runtimes/http/types.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/schema.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/schema_validator.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/serve/__init__.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/serve/commands/__init__.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/serve/commands/run.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/serve/commands/schema.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/serve/main.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/serve/utils/__init__.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/serve/utils/config.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/serve/utils/runtime.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/__init__.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/aiortc/__init__.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/aiortc/audio_track.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/aiortc/client.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/aiortc/frame_conversion.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/aiortc/ice_connection.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/aiortc/video_track.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/config.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/events.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/gstreamer/__init__.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/gstreamer/client.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/gstreamer/decoders/__init__.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/gstreamer/decoders/av1.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/gstreamer/decoders/base.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/gstreamer/decoders/factory.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/gstreamer/decoders/h264.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/gstreamer/decoders/h265.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/gstreamer/decoders/opus.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/gstreamer/decoders/vp8.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/gstreamer/decoders/vp9.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/gstreamer/encoders/__init__.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/gstreamer/encoders/av1.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/gstreamer/encoders/base.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/gstreamer/encoders/factory.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/gstreamer/encoders/h264.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/gstreamer/encoders/h265.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/gstreamer/encoders/opus.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/gstreamer/encoders/vp8.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/gstreamer/encoders/vp9.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/gstreamer/gst.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/gstreamer/gst_helpers.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/gstreamer/probes/__init__.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/gstreamer/probes/fps_probe.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/gstreamer/receiver/__init__.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/gstreamer/receiver/audio.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/gstreamer/receiver/base.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/gstreamer/receiver/video.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/gstreamer/sdp/__init__.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/gstreamer/sdp/bundle.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/gstreamer/sdp/codec.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/gstreamer/sdp/extmap.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/gstreamer/sdp/ice.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/gstreamer/sender/__init__.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/gstreamer/sender/audio.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/gstreamer/sender/base.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/gstreamer/sender/video.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/gstreamer/settings.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/gstreamer/signals.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/ice_uris.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/interface.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/media.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/types.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/utils/loader.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/utils/messages.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/utils/paths.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/utils/ports.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/utils/typing.py +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime.egg-info/SOURCES.txt +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime.egg-info/dependency_links.txt +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime.egg-info/requires.txt +0 -0
- {reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime.egg-info/top_level.txt +0 -0
{reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/recording/chunk_uploader.py
RENAMED
|
@@ -66,6 +66,13 @@ class ChunkUploader:
|
|
|
66
66
|
# math (``chunk_idx * chunk_seconds``) and the ``/clips``
|
|
67
67
|
# manifest endpoint already assume. No translation needed.
|
|
68
68
|
self._next_idx = 0
|
|
69
|
+
# Read by ``_run`` after the stop signal to decide whether the
|
|
70
|
+
# worker should drain leftover chunks before exiting. Set by
|
|
71
|
+
# ``stop()`` *before* signalling stop so the worker reads it
|
|
72
|
+
# consistently on its way out. See ``stop()`` docstring for
|
|
73
|
+
# why the drain has to happen on this thread rather than the
|
|
74
|
+
# caller's.
|
|
75
|
+
self._flush_on_exit = False
|
|
69
76
|
|
|
70
77
|
def start(self) -> None:
|
|
71
78
|
if self._thread is not None:
|
|
@@ -76,22 +83,38 @@ class ChunkUploader:
|
|
|
76
83
|
)
|
|
77
84
|
self._thread.start()
|
|
78
85
|
|
|
79
|
-
def stop(self, *, flush_remaining: bool = True, timeout: float =
|
|
86
|
+
def stop(self, *, flush_remaining: bool = True, timeout: float = 30.0) -> bool:
|
|
80
87
|
"""Signal the watcher to exit; optionally drain final chunks.
|
|
81
88
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
89
|
+
Returns ``True`` when the worker joined cleanly, ``False`` when
|
|
90
|
+
both join attempts timed out. Always closes the sink before
|
|
91
|
+
returning.
|
|
92
|
+
|
|
93
|
+
The drain runs on the worker thread; see REA-2268.
|
|
87
94
|
"""
|
|
95
|
+
self._flush_on_exit = flush_remaining
|
|
88
96
|
self._stop.set()
|
|
89
97
|
thread = self._thread
|
|
90
98
|
self._thread = None
|
|
99
|
+
clean = True
|
|
91
100
|
if thread is not None:
|
|
92
101
|
thread.join(timeout=timeout)
|
|
93
|
-
|
|
94
|
-
|
|
102
|
+
if thread.is_alive():
|
|
103
|
+
# Close the sink to cancel any in-flight presign future,
|
|
104
|
+
# then give the worker a short grace window to exit.
|
|
105
|
+
self._safe_close_sink()
|
|
106
|
+
thread.join(timeout=2.0)
|
|
107
|
+
if thread.is_alive():
|
|
108
|
+
clean = False
|
|
109
|
+
logger.error(
|
|
110
|
+
"ChunkUploader.stop join timed out; thread leaked",
|
|
111
|
+
timeout_s=timeout,
|
|
112
|
+
next_idx=self._next_idx,
|
|
113
|
+
)
|
|
114
|
+
self._safe_close_sink()
|
|
115
|
+
return clean
|
|
116
|
+
|
|
117
|
+
def _safe_close_sink(self) -> None:
|
|
95
118
|
try:
|
|
96
119
|
self._sink.close()
|
|
97
120
|
except Exception:
|
|
@@ -103,6 +126,12 @@ class ChunkUploader:
|
|
|
103
126
|
progress = self._tick()
|
|
104
127
|
if not progress:
|
|
105
128
|
self._stop.wait(self._poll_interval)
|
|
129
|
+
# Final drain happens here, in the worker thread, so the
|
|
130
|
+
# caller-side ``stop()`` doesn't accidentally drive
|
|
131
|
+
# ``run_coroutine_threadsafe(...).result()`` from the
|
|
132
|
+
# asyncio loop thread. See ``stop()`` docstring.
|
|
133
|
+
if self._flush_on_exit:
|
|
134
|
+
self._drain_final()
|
|
106
135
|
except Exception:
|
|
107
136
|
logger.exception("ChunkUploader thread crashed")
|
|
108
137
|
|
|
@@ -91,6 +91,11 @@ class RecordingConfig:
|
|
|
91
91
|
# ``reactor.yaml``.
|
|
92
92
|
chunk_seconds: int = 4
|
|
93
93
|
clip_max_seconds: int = 300
|
|
94
|
+
# Trim the session-start pre-roll from clip playlists by clamping
|
|
95
|
+
# ``start_marker`` forward to the first real (non-duplicate) frame
|
|
96
|
+
# the recorder observes. Set to ``False`` to keep that pre-roll
|
|
97
|
+
# in clips.
|
|
98
|
+
skip_leading_black: bool = True
|
|
94
99
|
video_track: Optional[str] = None
|
|
95
100
|
audio_track: Optional[str] = None
|
|
96
101
|
video: VideoEncoderConfig = field(default_factory=VideoEncoderConfig)
|
|
@@ -105,6 +110,9 @@ class RecordingConfig:
|
|
|
105
110
|
enabled=bool(raw.get("enabled", cls.enabled)),
|
|
106
111
|
chunk_seconds=int(raw.get("chunk_seconds", cls.chunk_seconds)),
|
|
107
112
|
clip_max_seconds=int(raw.get("clip_max_seconds", cls.clip_max_seconds)),
|
|
113
|
+
skip_leading_black=bool(
|
|
114
|
+
raw.get("skip_leading_black", cls.skip_leading_black)
|
|
115
|
+
),
|
|
108
116
|
video_track=raw.get("video_track"),
|
|
109
117
|
audio_track=raw.get("audio_track"),
|
|
110
118
|
video=VideoEncoderConfig.from_dict(raw.get("video") or {}),
|
|
@@ -12,7 +12,7 @@ from __future__ import annotations
|
|
|
12
12
|
|
|
13
13
|
import threading
|
|
14
14
|
import time
|
|
15
|
-
from typing import Tuple
|
|
15
|
+
from typing import Optional, Tuple
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
class MarkerBookkeeper:
|
|
@@ -26,6 +26,10 @@ class MarkerBookkeeper:
|
|
|
26
26
|
def __init__(self) -> None:
|
|
27
27
|
self._session_start = time.monotonic()
|
|
28
28
|
self._last_uploaded_chunk_idx: int = -1
|
|
29
|
+
# Sticky marker latched by :meth:`mark_first_real_frame`;
|
|
30
|
+
# ``None`` until the recorder sees its first non-duplicate
|
|
31
|
+
# bundle.
|
|
32
|
+
self._first_real_frame_marker: Optional[float] = None
|
|
29
33
|
self._lock = threading.Lock()
|
|
30
34
|
|
|
31
35
|
def now_marker(self) -> float:
|
|
@@ -38,6 +42,13 @@ class MarkerBookkeeper:
|
|
|
38
42
|
with self._lock:
|
|
39
43
|
return self._last_uploaded_chunk_idx
|
|
40
44
|
|
|
45
|
+
@property
|
|
46
|
+
def first_real_frame_marker(self) -> Optional[float]:
|
|
47
|
+
"""Wall-clock of the first real (non-duplicate) bundle, or
|
|
48
|
+
``None`` if no real frame has landed yet."""
|
|
49
|
+
with self._lock:
|
|
50
|
+
return self._first_real_frame_marker
|
|
51
|
+
|
|
41
52
|
def mark_chunk_uploaded(self, idx: int) -> None:
|
|
42
53
|
"""Advance ``last_uploaded_chunk_idx`` monotonically.
|
|
43
54
|
|
|
@@ -48,7 +59,20 @@ class MarkerBookkeeper:
|
|
|
48
59
|
if idx > self._last_uploaded_chunk_idx:
|
|
49
60
|
self._last_uploaded_chunk_idx = idx
|
|
50
61
|
|
|
51
|
-
def
|
|
62
|
+
def mark_first_real_frame(self) -> None:
|
|
63
|
+
"""Latch the wall-clock at which the first real frame was seen.
|
|
64
|
+
|
|
65
|
+
Idempotent and one-shot — only the first call has an effect,
|
|
66
|
+
so the marker pins the session-start pre-roll and is never
|
|
67
|
+
moved by later idle periods.
|
|
68
|
+
"""
|
|
69
|
+
with self._lock:
|
|
70
|
+
if self._first_real_frame_marker is None:
|
|
71
|
+
self._first_real_frame_marker = time.monotonic() - self._session_start
|
|
72
|
+
|
|
73
|
+
def compute_clip_range(
|
|
74
|
+
self, duration_seconds: float, *, skip_leading_black: bool = False
|
|
75
|
+
) -> Tuple[float, float]:
|
|
52
76
|
"""Resolve ``requestClip`` to a ``(start_marker, end_marker)`` pair.
|
|
53
77
|
|
|
54
78
|
``end_marker`` is the wall-clock at request time (``now_marker``),
|
|
@@ -59,11 +83,33 @@ class MarkerBookkeeper:
|
|
|
59
83
|
polls until ``200``. This keeps the runtime's response
|
|
60
84
|
latency at one round-trip regardless of where in the chunk
|
|
61
85
|
cycle the client asks.
|
|
86
|
+
|
|
87
|
+
When ``skip_leading_black`` is true and the first-real-frame
|
|
88
|
+
marker has been latched, ``start`` is clamped forward to that
|
|
89
|
+
marker. Without the latch the clamp is skipped so callers
|
|
90
|
+
always receive a non-empty range.
|
|
62
91
|
"""
|
|
63
92
|
end_marker = self.now_marker()
|
|
64
93
|
start_marker = max(0.0, end_marker - duration_seconds)
|
|
94
|
+
if skip_leading_black:
|
|
95
|
+
with self._lock:
|
|
96
|
+
first_real = self._first_real_frame_marker
|
|
97
|
+
if first_real is not None:
|
|
98
|
+
start_marker = max(start_marker, first_real)
|
|
65
99
|
return start_marker, end_marker
|
|
66
100
|
|
|
67
|
-
def compute_recording_range(
|
|
68
|
-
|
|
69
|
-
|
|
101
|
+
def compute_recording_range(
|
|
102
|
+
self, *, skip_leading_black: bool = False
|
|
103
|
+
) -> Tuple[float, float]:
|
|
104
|
+
"""``requestRecording`` is just a clip from session start to
|
|
105
|
+
now. Honours ``skip_leading_black`` with the same fallback
|
|
106
|
+
as :meth:`compute_clip_range`.
|
|
107
|
+
"""
|
|
108
|
+
end_marker = self.now_marker()
|
|
109
|
+
start_marker = 0.0
|
|
110
|
+
if skip_leading_black:
|
|
111
|
+
with self._lock:
|
|
112
|
+
first_real = self._first_real_frame_marker
|
|
113
|
+
if first_real is not None:
|
|
114
|
+
start_marker = first_real
|
|
115
|
+
return start_marker, end_marker
|
{reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/recording/session_recorder.py
RENAMED
|
@@ -86,6 +86,11 @@ class ClipResult:
|
|
|
86
86
|
now_marker: float
|
|
87
87
|
predicted_ready_at_ms: int
|
|
88
88
|
playlist_url: str
|
|
89
|
+
# Wall-clock of the first real (non-duplicate) frame the recorder
|
|
90
|
+
# observed, or ``None`` if none has landed yet. Surfaced for
|
|
91
|
+
# observability; ``start_marker`` already uses it when
|
|
92
|
+
# ``RecordingConfig.skip_leading_black`` is on.
|
|
93
|
+
first_real_frame_marker: Optional[float] = None
|
|
89
94
|
|
|
90
95
|
def to_dict(self) -> dict:
|
|
91
96
|
return {
|
|
@@ -96,6 +101,7 @@ class ClipResult:
|
|
|
96
101
|
"now_marker": self.now_marker,
|
|
97
102
|
"predicted_ready_at_ms": self.predicted_ready_at_ms,
|
|
98
103
|
"playlist_url": self.playlist_url,
|
|
104
|
+
"first_real_frame_marker": self.first_real_frame_marker,
|
|
99
105
|
}
|
|
100
106
|
|
|
101
107
|
|
|
@@ -215,9 +221,18 @@ class SessionRecorder:
|
|
|
215
221
|
)
|
|
216
222
|
|
|
217
223
|
def stop(self) -> None:
|
|
218
|
-
"""
|
|
224
|
+
"""Tear down feed worker, encoder, and uploader.
|
|
225
|
+
|
|
226
|
+
Emits a structured ``SessionRecorder stopped`` log with
|
|
227
|
+
``clean=…`` and per-thread leak flags so failed joins surface
|
|
228
|
+
in monitoring instead of leaking ffmpeg + uploader threads
|
|
229
|
+
silently.
|
|
230
|
+
"""
|
|
219
231
|
if not self._started:
|
|
220
232
|
return
|
|
233
|
+
# Disable callbacks first so a racing _on_bundle_sync becomes
|
|
234
|
+
# a no-op even before its OutputBuffer subscription is removed.
|
|
235
|
+
self._disabled = True
|
|
221
236
|
# Signal the feed loop to drain and exit; sentinel unblocks .get().
|
|
222
237
|
self._feed_stop.set()
|
|
223
238
|
try:
|
|
@@ -227,10 +242,14 @@ class SessionRecorder:
|
|
|
227
242
|
pass
|
|
228
243
|
feed_thread = self._feed_thread
|
|
229
244
|
self._feed_thread = None
|
|
245
|
+
# Stop the encoder before joining the feed worker: the worker's
|
|
246
|
+
# blocking call is ``os.write()`` into the ffmpeg pipe and only
|
|
247
|
+
# unblocks when the pipe is closed.
|
|
248
|
+
self._encoder.stop()
|
|
230
249
|
if feed_thread is not None:
|
|
231
250
|
feed_thread.join(timeout=2.0)
|
|
232
|
-
|
|
233
|
-
self._uploader.stop(flush_remaining=True)
|
|
251
|
+
feed_leaked = feed_thread is not None and feed_thread.is_alive()
|
|
252
|
+
uploader_clean = self._uploader.stop(flush_remaining=True)
|
|
234
253
|
self._started = False
|
|
235
254
|
# Cleanup the encoder's transient working dir. Chunks that
|
|
236
255
|
# made it to the sink are already copied to the served dir;
|
|
@@ -244,11 +263,16 @@ class SessionRecorder:
|
|
|
244
263
|
"Failed to clean encoder work dir; leaking",
|
|
245
264
|
work_dir=str(self._work_dir),
|
|
246
265
|
)
|
|
247
|
-
|
|
266
|
+
clean = not feed_leaked and uploader_clean
|
|
267
|
+
log = logger.info if clean else logger.error
|
|
268
|
+
log(
|
|
248
269
|
"SessionRecorder stopped",
|
|
249
270
|
session_id=self._session_id,
|
|
250
271
|
last_uploaded_chunk_idx=self._markers.last_uploaded_chunk_idx,
|
|
251
272
|
dropped=self._dropped_frames,
|
|
273
|
+
clean=clean,
|
|
274
|
+
feed_leaked=feed_leaked,
|
|
275
|
+
uploader_leaked=not uploader_clean,
|
|
252
276
|
)
|
|
253
277
|
|
|
254
278
|
# ------------------------------------------------------------------
|
|
@@ -276,6 +300,9 @@ class SessionRecorder:
|
|
|
276
300
|
if self._disabled or not self._started:
|
|
277
301
|
return
|
|
278
302
|
now = time.monotonic()
|
|
303
|
+
if not duplicate:
|
|
304
|
+
# Idempotent latch; only the first call takes effect.
|
|
305
|
+
self._markers.mark_first_real_frame()
|
|
279
306
|
if duplicate:
|
|
280
307
|
if (
|
|
281
308
|
self._keepalive_interval <= 0.0
|
|
@@ -295,8 +322,17 @@ class SessionRecorder:
|
|
|
295
322
|
audio_td = bundle.tracks.get(self._audio_track)
|
|
296
323
|
if audio_td is not None:
|
|
297
324
|
audio_data = audio_td.data
|
|
325
|
+
# The OutputBuffer's cache is per-model, so the first
|
|
326
|
+
# duplicates of a new session can carry the previous session's
|
|
327
|
+
# last frame. Black-fill them until a real frame lands so the
|
|
328
|
+
# leading pre-roll is genuinely black rather than stale content.
|
|
329
|
+
video_data = video_td.data
|
|
330
|
+
if duplicate and self._markers.first_real_frame_marker is None:
|
|
331
|
+
video_data = np.zeros_like(video_data)
|
|
332
|
+
if audio_data is not None:
|
|
333
|
+
audio_data = np.zeros_like(audio_data)
|
|
298
334
|
try:
|
|
299
|
-
self._feed_queue.put_nowait((
|
|
335
|
+
self._feed_queue.put_nowait((video_data, audio_data))
|
|
300
336
|
except queue.Full:
|
|
301
337
|
self._dropped_frames += 1
|
|
302
338
|
# Cold-start can fill the queue for a few hundred frames;
|
|
@@ -428,23 +464,30 @@ class SessionRecorder:
|
|
|
428
464
|
promise includes the chunk ffmpeg is currently writing. The
|
|
429
465
|
``/clips`` manifest endpoint will ``202 Retry-After`` until
|
|
430
466
|
that chunk lands; the SDK polls until ``200``.
|
|
467
|
+
|
|
468
|
+
Honours :attr:`RecordingConfig.skip_leading_black`.
|
|
431
469
|
"""
|
|
432
470
|
if self.disabled:
|
|
433
471
|
raise RecorderDisabledError("recorder disabled or encoder crashed")
|
|
434
472
|
capped = min(float(duration_seconds), float(self._config.clip_max_seconds))
|
|
435
473
|
if capped <= 0:
|
|
436
474
|
raise ValueError("duration_seconds must be positive")
|
|
437
|
-
start, end = self._markers.compute_clip_range(
|
|
475
|
+
start, end = self._markers.compute_clip_range(
|
|
476
|
+
capped, skip_leading_black=self._config.skip_leading_black
|
|
477
|
+
)
|
|
438
478
|
return self._build_result(kind=kind, start=start, end=end)
|
|
439
479
|
|
|
440
480
|
def request_recording(self) -> ClipResult:
|
|
441
|
-
"""Resolve a full-recording request; ``
|
|
481
|
+
"""Resolve a full-recording request; ``end = now``.
|
|
442
482
|
|
|
443
|
-
Same promise-then-poll semantics as :meth:`request_clip
|
|
483
|
+
Same promise-then-poll semantics as :meth:`request_clip` and
|
|
484
|
+
honours :attr:`RecordingConfig.skip_leading_black`.
|
|
444
485
|
"""
|
|
445
486
|
if self.disabled:
|
|
446
487
|
raise RecorderDisabledError("recorder disabled or encoder crashed")
|
|
447
|
-
start, end = self._markers.compute_recording_range(
|
|
488
|
+
start, end = self._markers.compute_recording_range(
|
|
489
|
+
skip_leading_black=self._config.skip_leading_black
|
|
490
|
+
)
|
|
448
491
|
return self._build_result(kind="recording", start=start, end=end)
|
|
449
492
|
|
|
450
493
|
def _build_result(self, kind: str, start: float, end: float) -> ClipResult:
|
|
@@ -481,4 +524,5 @@ class SessionRecorder:
|
|
|
481
524
|
now_marker=now,
|
|
482
525
|
predicted_ready_at_ms=predicted_ready_at_ms,
|
|
483
526
|
playlist_url=playlist_url,
|
|
527
|
+
first_real_frame_marker=self._markers.first_real_frame_marker,
|
|
484
528
|
)
|
|
@@ -42,6 +42,10 @@ def configure_logging(level: str, show_timestamps: bool = True) -> None:
|
|
|
42
42
|
logging.getLogger("uvicorn").setLevel(logging.WARNING)
|
|
43
43
|
logging.getLogger("uvicorn.error").setLevel(logging.WARNING)
|
|
44
44
|
logging.getLogger("uvicorn.access").setLevel(logging.WARNING)
|
|
45
|
+
# Per-request httpx INFO logs would emit one line per recorded
|
|
46
|
+
# chunk and embed presigned credentials in the URL.
|
|
47
|
+
logging.getLogger("httpx").setLevel(logging.WARNING)
|
|
48
|
+
logging.getLogger("httpcore").setLevel(logging.WARNING)
|
|
45
49
|
|
|
46
50
|
|
|
47
51
|
def _infer_runtime_name(serve_fn: Callable) -> str:
|
|
@@ -35,9 +35,19 @@ _LEVEL_COLORS: dict[int, str] = {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
|
|
38
|
+
# Loggers under these prefixes are not model code and render without the
|
|
39
|
+
# cyan ``MDL`` tag. Update when adopting a new dependency that emits
|
|
40
|
+
# through stdlib ``logging``.
|
|
41
|
+
_NON_MODEL_PREFIXES = (
|
|
42
|
+
"reactor_runtime.",
|
|
43
|
+
"opentelemetry.",
|
|
44
|
+
"aiortc.",
|
|
45
|
+
"aioice.",
|
|
46
|
+
"av.",
|
|
47
|
+
"httpx",
|
|
48
|
+
"httpcore",
|
|
49
|
+
"uvicorn",
|
|
50
|
+
)
|
|
41
51
|
_MODEL_COLOR = "\033[36m"
|
|
42
52
|
|
|
43
53
|
|
|
@@ -50,7 +60,7 @@ class ColorFormatter(logging.Formatter):
|
|
|
50
60
|
orig = record.levelname
|
|
51
61
|
color = _LEVEL_COLORS.get(record.levelno, "")
|
|
52
62
|
|
|
53
|
-
if record.name.startswith(
|
|
63
|
+
if record.name.startswith(_NON_MODEL_PREFIXES):
|
|
54
64
|
pad = self._COL - len(orig)
|
|
55
65
|
record.levelname = f"{color}{orig}{_RESET}{' ' * pad}"
|
|
56
66
|
else:
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/interface/driver/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/interface/driver/step_result.py
RENAMED
|
File without changes
|
{reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/interface/events/__init__.py
RENAMED
|
File without changes
|
{reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/interface/events/connected.py
RENAMED
|
File without changes
|
{reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/interface/events/event.py
RENAMED
|
File without changes
|
{reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/interface/events/messages.py
RENAMED
|
File without changes
|
{reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/interface/events/upload.py
RENAMED
|
File without changes
|
{reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/interface/internal/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/interface/model/__init__.py
RENAMED
|
File without changes
|
{reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/interface/model/decorators.py
RENAMED
|
File without changes
|
{reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/interface/model/handlers.py
RENAMED
|
File without changes
|
{reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/interface/model/reactor_model.py
RENAMED
|
File without changes
|
{reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/interface/pipeline/__init__.py
RENAMED
|
File without changes
|
{reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/interface/pipeline/idle.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/interface/tracks/__init__.py
RENAMED
|
File without changes
|
{reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/interface/tracks/descriptors.py
RENAMED
|
File without changes
|
{reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/interface/tracks/input.py
RENAMED
|
File without changes
|
{reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/interface/tracks/output.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/profiling/backends/__init__.py
RENAMED
|
File without changes
|
{reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/profiling/backends/base.py
RENAMED
|
File without changes
|
{reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/profiling/backends/file.py
RENAMED
|
File without changes
|
{reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/profiling/backends/otlp.py
RENAMED
|
File without changes
|
|
File without changes
|
{reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/profiling/plotting/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/recording/chunk_encoder.py
RENAMED
|
File without changes
|
|
File without changes
|
{reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/recording/track_resolver.py
RENAMED
|
File without changes
|
|
File without changes
|
{reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/runtimes/headless/config.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/runtimes/http/http_runtime.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/serve/commands/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
{reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/serve/commands/schema.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/aiortc/__init__.py
RENAMED
|
File without changes
|
{reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/aiortc/audio_track.py
RENAMED
|
File without changes
|
{reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/aiortc/client.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/aiortc/video_track.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/gstreamer/__init__.py
RENAMED
|
File without changes
|
{reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/gstreamer/client.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
|
{reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/gstreamer/gst.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
|
{reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/gstreamer/sdp/ice.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/gstreamer/settings.py
RENAMED
|
File without changes
|
{reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime/transports/gstreamer/signals.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
|
{reactor_runtime-2.7.0 → reactor_runtime-2.7.2}/src/reactor_runtime.egg-info/dependency_links.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|