Add single instance protection to prevent multiple app windows
- Implement SingleInstance class using QSharedMemory and file locking - Cross-platform support with fcntl on Unix/macOS and file creation on Windows - Show warning dialog when user tries to launch second instance - Automatic cleanup of resources on application exit - Graceful handling of instance detection failures 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
0cf0bc6350
commit
2ceedb0f45
89
main.py
89
main.py
@ -21,9 +21,17 @@ Usage:
|
||||
|
||||
import sys
|
||||
import os
|
||||
from PyQt5.QtWidgets import QApplication
|
||||
import tempfile
|
||||
from PyQt5.QtWidgets import QApplication, QMessageBox
|
||||
from PyQt5.QtGui import QFont
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtCore import Qt, QSharedMemory
|
||||
|
||||
# Import fcntl only on Unix-like systems
|
||||
try:
|
||||
import fcntl
|
||||
HAS_FCNTL = True
|
||||
except ImportError:
|
||||
HAS_FCNTL = False
|
||||
|
||||
# Add the parent directory to the path for imports
|
||||
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
@ -32,6 +40,63 @@ from config.theme import apply_theme
|
||||
from ui.windows.login import DashboardLogin
|
||||
|
||||
|
||||
class SingleInstance:
|
||||
"""Ensure only one instance of the application can run."""
|
||||
|
||||
def __init__(self, app_name="Cluster4NPU"):
|
||||
self.app_name = app_name
|
||||
self.shared_memory = QSharedMemory(app_name)
|
||||
self.lock_file = None
|
||||
self.lock_fd = None
|
||||
|
||||
def is_running(self):
|
||||
"""Check if another instance is already running."""
|
||||
# Try to create shared memory
|
||||
if self.shared_memory.attach():
|
||||
# Another instance is already running
|
||||
return True
|
||||
|
||||
# Try to create the shared memory
|
||||
if not self.shared_memory.create(1):
|
||||
# Failed to create, likely another instance exists
|
||||
return True
|
||||
|
||||
# Also use file locking as backup (works better on some systems)
|
||||
if HAS_FCNTL:
|
||||
try:
|
||||
self.lock_file = os.path.join(tempfile.gettempdir(), f"{self.app_name}.lock")
|
||||
self.lock_fd = os.open(self.lock_file, os.O_CREAT | os.O_EXCL | os.O_RDWR)
|
||||
fcntl.lockf(self.lock_fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
|
||||
except (OSError, IOError):
|
||||
# Another instance is running
|
||||
if self.lock_fd:
|
||||
os.close(self.lock_fd)
|
||||
return True
|
||||
else:
|
||||
# On Windows, try simple file creation
|
||||
try:
|
||||
self.lock_file = os.path.join(tempfile.gettempdir(), f"{self.app_name}.lock")
|
||||
self.lock_fd = os.open(self.lock_file, os.O_CREAT | os.O_EXCL | os.O_RDWR)
|
||||
except (OSError, IOError):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def cleanup(self):
|
||||
"""Clean up resources."""
|
||||
if self.shared_memory.isAttached():
|
||||
self.shared_memory.detach()
|
||||
|
||||
if self.lock_fd:
|
||||
try:
|
||||
if HAS_FCNTL:
|
||||
fcntl.lockf(self.lock_fd, fcntl.LOCK_UN)
|
||||
os.close(self.lock_fd)
|
||||
os.unlink(self.lock_file)
|
||||
except:
|
||||
pass
|
||||
|
||||
|
||||
def setup_application():
|
||||
"""Initialize and configure the QApplication."""
|
||||
# Enable high DPI support BEFORE creating QApplication
|
||||
@ -60,14 +125,31 @@ def setup_application():
|
||||
|
||||
def main():
|
||||
"""Main application entry point."""
|
||||
# Create a minimal QApplication first for the message box
|
||||
temp_app = QApplication(sys.argv) if not QApplication.instance() else QApplication.instance()
|
||||
|
||||
# Check for single instance
|
||||
single_instance = SingleInstance()
|
||||
|
||||
if single_instance.is_running():
|
||||
QMessageBox.warning(
|
||||
None,
|
||||
"Application Already Running",
|
||||
"Cluster4NPU is already running. Please check your taskbar or system tray.",
|
||||
)
|
||||
sys.exit(0)
|
||||
|
||||
try:
|
||||
# Setup the application
|
||||
# Setup the full application
|
||||
app = setup_application()
|
||||
|
||||
# Create and show the main dashboard login window
|
||||
dashboard = DashboardLogin()
|
||||
dashboard.show()
|
||||
|
||||
# Clean up single instance on app exit
|
||||
app.aboutToQuit.connect(single_instance.cleanup)
|
||||
|
||||
# Start the application event loop
|
||||
sys.exit(app.exec_())
|
||||
|
||||
@ -75,6 +157,7 @@ def main():
|
||||
print(f"Error starting application: {e}")
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
single_instance.cleanup()
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user