redis/redis-py · error · RuntimeError
Scheduler is stopped
Error message
Scheduler is stopped
What it means
Raised as RuntimeError in BackgroundScheduler.run_coro_sync when self._stopped is True. The scheduler has been shut down (stop() was called, or __del__ ran), so it can no longer submit coroutines to its event loop. This protects against scheduling work on a dead scheduler.
Solutions
- Ensure health checks are not scheduled after calling stop() on the scheduler/client (fix shutdown ordering).
- Guard callers with a check of scheduler._stopped (or a public is_running flag) before submitting work.
- Cancel pending health-check timers before stopping the scheduler.
Example fix
# before await client.close() # stops scheduler await run_health_check() # run_coro_sync -> RuntimeError # after await client.close() # stops scheduler # do not schedule further work after close
Defensive patterns
Strategy: validation
Validate before calling
if scheduler._stopped:
logger.debug("scheduler stopped; skipping health check")
else:
scheduler.run_coro_sync(hc, client) Type guard
def scheduler_alive(scheduler) -> bool:
return not getattr(scheduler, "_stopped", True) Try / catch
try:
scheduler.run_coro_sync(hc, client)
except RuntimeError as e:
if "Scheduler is stopped" in str(e):
logger.debug("scheduler stopped; ignoring health check")
else:
raise Prevention
- Order shutdown so health-check timers are cancelled before stop().
- Avoid issuing work after client.close()/scheduler.stop().
When it happens
Trigger: Calling run_coro_sync(...) on a BackgroundScheduler after stop() has been invoked. This happens in multidb health-check/circuit-breaker paths when a health check is attempted after the client or scheduler was shut down, or during interpreter teardown when __del__ already stopped it.
Common situations: Application shutdown ordering: the client/scheduler was stopped before a final health check fired. A background thread attempting a health check racing with explicit stop(). Interpreter exit triggering __del__ while a thread is still using the scheduler.
Related errors
- Buffer is closed.
- Buffer is closed.
- Cannot disable maintenance notifications after enabling them
- Cannot issue nested calls to MULTI
- Cannot release a lock that's not owned or is already…
AI-assisted analysis of redis/redis-py@6a6b581b48 (2026-08-10).
Data as JSON: /api/errors/483a4eccce96a941.
Report an issue: GitHub.
Appendix: source
Thrown at redis/background.py:143
Args:
coro: Coroutine function to execute
*args: Arguments to pass to the coroutine
timeout: Maximum seconds to wait for the result. None means wait
forever. Default is 10 seconds to avoid blocking indefinitely
if the event loop is busy with long-running health checks.
Returns:
The result of the coroutine
Raises:
TimeoutError: If the coroutine doesn't complete within timeout
Any exception raised by the coroutine
"""
with self._lock:
if self._stopped:
raise RuntimeError("Scheduler is stopped")
# Ensure the shared loop exists
self._ensure_health_check_loop()
with self._lock:
loop = self._health_check_loop
# Submit the coroutine to the shared loop and wait for result
future = asyncio.run_coroutine_threadsafe(coro(*args), loop)
try:
return future.result(timeout=timeout)
except TimeoutError:
# Cancel the future to avoid leaving orphaned tasks
future.cancel()
raise
def run_coro_fire_and_forget(
self, coro: Callable[..., Coroutine[Any, Any, Any]], *argsView on GitHub (pinned to 6a6b581b48)