redis/redis-py · error · RuntimeError

Health check event loop failed to start within

Error message

Health check event loop failed to start within {timeout} seconds

What it means

Raised as RuntimeError in BackgroundScheduler._ensure_health_check_loop when the health-check event loop fails to signal readiness within the timeout (default 5.0s). The background thread is supposed to set _health_check_loop_ready once asyncio.run_forever begins; if it never does, the loop is unusable. The code cleans up the failed loop so a retry can attempt a fresh one.

Solutions

  1. Retry the operation — _ensure_health_check_loop cleans up the failed loop and a subsequent call creates a new one.
  2. Reduce system load or raise thread/resource limits (ulimit -u) so the daemon thread can start promptly.
  3. Increase the readiness timeout by calling a code path that passes a larger timeout if exposed, or investigate _run_health_check_loop for exceptions.
  4. Check logs for exceptions from the health-check thread that prevented readiness signaling.

Example fix

# before: single attempt fails under load
result = scheduler.run_coro_sync(hc, client, timeout=10)  # RuntimeError
# after: retry with backoff
for attempt in range(3):
    try:
        result = scheduler.run_coro_sync(hc, client, timeout=10)
        break
    except RuntimeError:
        time.sleep(1)
Defensive patterns

Strategy: retry

Try / catch

for attempt in range(3):
    try:
        return scheduler.run_coro_sync(hc, client, timeout=10)
    except RuntimeError as e:
        if "failed to start" in str(e):
            await asyncio.sleep(backoff)
            continue
        raise

Prevention

When it happens

Trigger: run_coro_sync or run_coro_fire_and_forget triggers _ensure_health_check_loop, the daemon thread is started, but _health_check_loop_ready.wait(timeout) returns False — the thread never reached the set() call, e.g. thread startup blocked, loop creation failed, or the system is starved of resources.

Common situations: System under extreme load / thread starvation so the daemon thread does not get scheduled within 5s. Thread creation blocked by resource limits (ulimit -u) or a restricted runtime. A bug or exception inside _run_health_check_loop before it sets the ready event. CI environment with constrained CPU.

Understand the failure class

Related errors


AI-assisted analysis of redis/redis-py@6a6b581b48 (2026-08-10). Data as JSON: /api/errors/510feee8de97ce34. Report an issue: GitHub.

Appendix: source

Thrown at redis/background.py:254

            )
            self._health_check_thread.start()

            # Wait for loop to be running INSIDE the lock with a timeout.
            # This prevents other threads from trying to create another loop
            # before this one is fully started, while avoiding permanent deadlock
            # if the background thread fails to start the loop.
            if not self._health_check_loop_ready.wait(timeout=timeout):
                # Timeout expired - the loop failed to start
                # Clean up the failed loop to allow retry
                failed_loop = self._health_check_loop
                self._health_check_loop = None
                if failed_loop in self._event_loops:
                    self._event_loops.remove(failed_loop)
                try:
                    failed_loop.close()
                except Exception:
                    pass
                raise RuntimeError(
                    f"Health check event loop failed to start within {timeout} seconds"
                )

    def _run_health_check_loop(self):
        """Run the shared health check event loop."""
        asyncio.set_event_loop(self._health_check_loop)

        # Signal that the loop is ready before running
        # Use call_soon to signal after run_forever starts processing
        self._health_check_loop.call_soon(self._health_check_loop_ready.set)

        try:
            self._health_check_loop.run_forever()
        finally:
            try:
                pending = asyncio.all_tasks(self._health_check_loop)
                for task in pending:
                    task.cancel()

View on GitHub (pinned to 6a6b581b48)