redis/redis-py · error · RuntimeError
Scheduler is stopped
Error message
Scheduler is stopped
What it means
Raised as RuntimeError by BackgroundScheduler.run_coro_sync (redis/background.py:143) when it is called after stop() has set _stopped. The scheduler is single-use: once stopped it will no longer execute coroutines synchronously. This guards against submitting work to a torn-down scheduler.
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 da03cdc7e8)
Solutions
- Do not call run_coro_sync after stop(); order shutdown so no checks are scheduled post-stop.
- Create a fresh BackgroundScheduler if you need scheduling again after a stop.
- Guard call sites with a check of the scheduler lifecycle (or catch RuntimeError and skip).
Example fix
# before scheduler.stop() scheduler.run_coro_sync(initial_check) # RuntimeError # after # ensure all checks are done before stop, or construct a new scheduler new_scheduler = BackgroundScheduler() new_scheduler.run_coro_sync(initial_check)
Defensive patterns
Strategy: validation
Validate before calling
if scheduler._stopped:
scheduler = BackgroundScheduler() # fresh instance
scheduler.run_coro_sync(check) Type guard
def scheduler_alive(scheduler) -> bool:
return not scheduler._stopped Try / catch
try:
scheduler.run_coro_sync(check)
except RuntimeError as e:
if 'stopped' in str(e):
logger.debug('scheduler stopped; skipping check')
else:
raise Prevention
- Order shutdown so no checks run after stop().
- Create a new scheduler rather than reusing a stopped one.
When it happens
Trigger: Calling run_coro_sync (e.g. an initial health check) on a BackgroundScheduler whose stop() was already invoked; reuse of a scheduler object after shutdown; ordering bug where stop() runs before the first check.
Common situations: Shutdown sequence races with a pending health-check trigger; atexit/finally calling stop() then later code attempting a check; scheduler stored on an object that gets closed and reused.
Related errors
- Health check event loop failed to start within {timeout} sec
- Buffer is closed.
- pubsub connection not set: did you forget to call subscribe(
- Cannot disable maintenance notifications after enabling them
- Either maint_notifications_pool_handler or oss_cluster_maint
AI-assisted analysis of redis/redis-py@da03cdc7e8 (2026-08-04).
Data as JSON: /data/errors/483a4eccce96a941.json.
Report an issue: GitHub.