From 8b7c58f996800a05e6ea2a6c7bbceb373e2c1904 Mon Sep 17 00:00:00 2001 From: John | Elite Encoder Date: Wed, 10 Dec 2025 11:45:51 -0500 Subject: [PATCH 1/3] fix(state): ensure server health state remains consistent with client health state --- pytrickle/state.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/pytrickle/state.py b/pytrickle/state.py index 2781a52..63bc915 100644 --- a/pytrickle/state.py +++ b/pytrickle/state.py @@ -70,9 +70,24 @@ def pipeline_ready(self) -> bool: """Compatibility: True when the pipeline is warmed/ready.""" return self._state in (PipelineState.IDLE, PipelineState.OK) + def _update_state_from_activity(self) -> None: + """Update internal state based on current activity levels. + + Transitions between OK and IDLE states based on whether there are + active streams or clients. Only transitions if not in ERROR state. + """ + if not self.error_event.is_set(): + if self.active_streams > 0 or self.active_client: + if self._state != PipelineState.OK: + self.set_state(PipelineState.OK) + elif self.startup_complete and self.pipeline_ready: + if self._state != PipelineState.IDLE: + self.set_state(PipelineState.IDLE) + def set_active_client(self, active: bool): """Track whether there's an active streaming client.""" self.active_client = active + self._update_state_from_activity() def update_component_health(self, component_name: str, health_data: dict): """Update component health and log errors without persisting them. @@ -154,6 +169,7 @@ def set_startup_complete(self) -> None: def update_active_streams(self, count: int) -> None: """Update number of active streams for health/status reporting.""" self.active_streams = max(0, int(count)) + self._update_state_from_activity() def is_error(self) -> bool: return self.error_event.is_set() From 74b786236655868e4aa8b3ecab4419acbe6e2e06 Mon Sep 17 00:00:00 2001 From: John | Elite Encoder Date: Wed, 10 Dec 2025 11:46:33 -0500 Subject: [PATCH 2/3] refactor(server, state): consolidate duplicated fields in pipeline_state --- pytrickle/server.py | 2 +- pytrickle/state.py | 6 +----- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/pytrickle/server.py b/pytrickle/server.py index da9b850..bd75d3f 100644 --- a/pytrickle/server.py +++ b/pytrickle/server.py @@ -832,7 +832,7 @@ def is_healthy(self) -> bool: def get_health_summary(self) -> str: """Get a simple health status string.""" state = self.state.get_pipeline_state() - return state.get('state', 'unknown') + return state.get('status', 'unknown') async def run_forever(self): """Run the server forever.""" diff --git a/pytrickle/state.py b/pytrickle/state.py index 63bc915..622fe36 100644 --- a/pytrickle/state.py +++ b/pytrickle/state.py @@ -215,12 +215,8 @@ def get_pipeline_state(self) -> dict: status = "LOADING" return { - "status": status, # Primary health status - "state": status, # Backward compatibility - "error_message": None, # Keep compatibility with previous health payload + "status": status, "pipeline_ready": self.pipeline_ready, "active_streams": self.active_streams, "startup_complete": self.startup_complete, - "pipeline_state": self._state.name, # Internal state name for debugging - "additional_info": {}, } From a52ba6b5a7859370559174597059ffe96a5014e1 Mon Sep 17 00:00:00 2001 From: John | Elite Encoder Date: Wed, 10 Dec 2025 12:20:28 -0500 Subject: [PATCH 3/3] fix(tests, state): update tests, simplify changes to state --- pytrickle/state.py | 24 +++++++++++++----------- tests/test_state_integration.py | 2 +- 2 files changed, 14 insertions(+), 12 deletions(-) diff --git a/pytrickle/state.py b/pytrickle/state.py index 622fe36..8b90256 100644 --- a/pytrickle/state.py +++ b/pytrickle/state.py @@ -70,12 +70,11 @@ def pipeline_ready(self) -> bool: """Compatibility: True when the pipeline is warmed/ready.""" return self._state in (PipelineState.IDLE, PipelineState.OK) - def _update_state_from_activity(self) -> None: - """Update internal state based on current activity levels. + def set_active_client(self, active: bool): + """Track whether there's an active streaming client.""" + self.active_client = active - Transitions between OK and IDLE states based on whether there are - active streams or clients. Only transitions if not in ERROR state. - """ + # Transition internal state based on activity (only if not in ERROR state) if not self.error_event.is_set(): if self.active_streams > 0 or self.active_client: if self._state != PipelineState.OK: @@ -84,11 +83,6 @@ def _update_state_from_activity(self) -> None: if self._state != PipelineState.IDLE: self.set_state(PipelineState.IDLE) - def set_active_client(self, active: bool): - """Track whether there's an active streaming client.""" - self.active_client = active - self._update_state_from_activity() - def update_component_health(self, component_name: str, health_data: dict): """Update component health and log errors without persisting them. @@ -169,7 +163,15 @@ def set_startup_complete(self) -> None: def update_active_streams(self, count: int) -> None: """Update number of active streams for health/status reporting.""" self.active_streams = max(0, int(count)) - self._update_state_from_activity() + + # Transition internal state based on activity (only if not in ERROR state) + if not self.error_event.is_set(): + if self.active_streams > 0 or self.active_client: + if self._state != PipelineState.OK: + self.set_state(PipelineState.OK) + elif self.startup_complete and self.pipeline_ready: + if self._state != PipelineState.IDLE: + self.set_state(PipelineState.IDLE) def is_error(self) -> bool: return self.error_event.is_set() diff --git a/tests/test_state_integration.py b/tests/test_state_integration.py index 68c8324..82c0eb4 100644 --- a/tests/test_state_integration.py +++ b/tests/test_state_integration.py @@ -66,7 +66,7 @@ def test_state_transitions(self): state.update_active_streams(1) state.set_active_client(True) data = state.get_state() - assert state.state == PipelineState.IDLE # Internal state doesn't auto-transition + assert state.state == PipelineState.OK # Internal state auto-transitions to OK after stream starts assert data["status"] == "OK" # But status reflects activity # When streams stop → status becomes IDLE