Skip to content
Open
Show file tree
Hide file tree
Changes from 5 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
9 changes: 8 additions & 1 deletion redislite/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
:class:`redis.StrictRedis()` classes.
"""
import atexit
import inspect
import json
import logging
import os
Expand Down Expand Up @@ -109,7 +110,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 execute_command which is always synchronous
self.execute_command('SHUTDOWN', 'SAVE', 'NOW', 'FORCE')

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

@copilot don't you think it's better to use asyncio when calling the async API?

asyncio.run(self.shutdown(save=True, now=True, force=True))

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

You're right! Using asyncio.run() is the proper way to handle the async shutdown. I've updated the code in commit 09bd73c to use asyncio.run(self.shutdown(save=True, now=True, force=True)) instead of falling back to execute_command.

This properly awaits the async method when detected, maintaining the original method signature and behavior. All tests pass without warnings.

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