Skip to content
Open
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion redislite/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@
they are functionally identical to the :class:`redis.Redis()` and
:class:`redis.StrictRedis()` classes.
"""
import asyncio
import atexit
import inspect
import json
import logging
import os
Expand Down Expand Up @@ -109,7 +111,13 @@ def _cleanup(self, sys_modules=None):
if getattr(self, '_async_managed', False):
logger.debug('Skipping shutdown for async-managed client')
return # Let async wrapper handle shutdown
self.shutdown(save=True, now=True, force=True)
# Check if shutdown is a coroutine function to avoid async/sync confusion
if hasattr(self, 'shutdown') and inspect.iscoroutinefunction(self.shutdown):
# If it's async, use asyncio.run to properly await it
asyncio.run(self.shutdown(save=True, now=True, force=True))
Comment on lines +116 to +117

Copilot AI Feb 6, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

asyncio.run(...) will raise RuntimeError if _cleanup() executes while an event loop is already running (e.g., in async test environments). Consider handling the 'running loop' case by using asyncio.get_running_loop() and scheduling the coroutine (e.g., loop.create_task(...)) when a loop is active, and only falling back to asyncio.run(coro) when there is no running loop.

Suggested change
# If it's async, use asyncio.run to properly await it
asyncio.run(self.shutdown(save=True, now=True, force=True))
# If it's async, run it safely depending on whether an event loop is running
coro = self.shutdown(save=True, now=True, force=True)
try:
loop = asyncio.get_running_loop()
except RuntimeError:
# No running event loop; safe to use asyncio.run
asyncio.run(coro)
else:
# Event loop already running; schedule the coroutine on it
loop.create_task(coro)

Copilot uses AI. Check for mistakes.
else:
Comment on lines +114 to +118

Copilot AI Mar 2, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

asyncio.run(...) will raise RuntimeError if _cleanup() is invoked while an event loop is already running (common in async applications / notebooks). Since the exception isn’t caught (only redis.RedisError is handled), cleanup can abort before the fallback SIGTERM/SIGKILL logic runs, potentially leaving a redis-server process behind. Consider detecting an active loop (asyncio.get_running_loop()), and either scheduling the coroutine safely or falling back to the existing signal-based shutdown path, and/or catching RuntimeError so cleanup still proceeds.

Copilot uses AI. Check for mistakes.
# Use the normal shutdown method
self.shutdown(save=True, now=True, force=True)
try: # pragma: no cover
process = psutil.Process(pid)
except psutil.NoSuchProcess: # pragma: no cover
Expand Down
4 changes: 2 additions & 2 deletions redislite/debug.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,8 +45,8 @@
the module and it's build information.
"""
from __future__ import print_function
from distutils.spawn import find_executable
import os
import shutil
from .__init__ import __version__, __git_version__, __source_url__, \
__git_hash__, __git_origin__, __git_branch__, __redis_server_info__, \
__redis_executable__
Expand All @@ -58,7 +58,7 @@ def debug_info_list():
:return:
"""
info = []
redis_server = find_executable('redis-server')
redis_server = shutil.which('redis-server')
if __redis_executable__: # pragma: no cover
redis_server = __redis_executable__
info.append("Redislite debug information:")
Expand Down
Loading