feat: Implement early instance check and enhance shutdown process
- Added a function to check for a running instance of the application before initializing PLCDataStreamer, providing faster feedback to the user. - Improved the graceful shutdown process to ensure proper cleanup of resources and instance locks. - Updated the main execution flow to include early instance checks and handle potential runtime errors more gracefully. - Modified system_state.json to set "should_connect" to true and updated the active datasets.
This commit is contained in:
parent
4a064937d3
commit
5e89921f05
File diff suppressed because it is too large
Load Diff
140
main.py
140
main.py
|
@ -37,12 +37,65 @@ try:
|
|||
except ImportError:
|
||||
TKINTER_AVAILABLE = False
|
||||
print("Warning: tkinter not available. File browse functionality will be limited.")
|
||||
|
||||
# Import core modules
|
||||
from core import PLCDataStreamer
|
||||
from core.historical_cache import HistoricalDataCache
|
||||
from utils.json_manager import JSONManager, SchemaManager
|
||||
from utils.symbol_loader import SymbolLoader
|
||||
from utils.symbol_processor import SymbolProcessor
|
||||
|
||||
|
||||
def check_for_running_instance_early():
|
||||
"""
|
||||
Optional early check for running instance before initializing PLCDataStreamer.
|
||||
This provides faster feedback to the user without going through full initialization.
|
||||
"""
|
||||
import psutil
|
||||
|
||||
lock_file = "plc_streamer.lock"
|
||||
|
||||
if not os.path.exists(lock_file):
|
||||
return True # No lock file, safe to proceed
|
||||
|
||||
try:
|
||||
with open(lock_file, "r") as f:
|
||||
old_pid = int(f.read().strip())
|
||||
|
||||
if psutil.pid_exists(old_pid):
|
||||
proc = psutil.Process(old_pid)
|
||||
cmdline = " ".join(proc.cmdline())
|
||||
|
||||
# Check if it's really our application
|
||||
if (
|
||||
("main.py" in cmdline and "S7_snap7_Stremer_n_Log" in cmdline)
|
||||
or ("plc_streamer" in cmdline.lower())
|
||||
or ("PLCDataStreamer" in cmdline)
|
||||
):
|
||||
print(f"🚫 Another instance is already running (PID: {old_pid})")
|
||||
print(f"📋 Process: {proc.name()}")
|
||||
print(f"💻 Command: {cmdline}")
|
||||
return False
|
||||
|
||||
# Process not running or different process, remove stale lock
|
||||
os.remove(lock_file)
|
||||
print(f"🧹 Removed stale lock file")
|
||||
return True
|
||||
|
||||
except (ValueError, psutil.NoSuchProcess, psutil.AccessDenied, FileNotFoundError):
|
||||
# Invalid or inaccessible, remove lock file if exists
|
||||
if os.path.exists(lock_file):
|
||||
try:
|
||||
os.remove(lock_file)
|
||||
print(f"🧹 Removed invalid lock file")
|
||||
except:
|
||||
pass
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"⚠️ Error checking instance: {e}")
|
||||
return True # On error, allow to proceed
|
||||
|
||||
|
||||
app = Flask(__name__)
|
||||
CORS(
|
||||
app,
|
||||
|
@ -3116,26 +3169,53 @@ def graceful_shutdown():
|
|||
"""Perform graceful shutdown"""
|
||||
print("\n⏹️ Performing graceful shutdown...")
|
||||
try:
|
||||
streamer.stop_streaming()
|
||||
# 🚀 PRESERVE auto-reconnect state: Don't clear reconnection data
|
||||
streamer.disconnect_plc(manual_disconnect=False) # Keep auto-reconnect state
|
||||
# 🔑 PRIORITY: Shutdown priority manager and performance monitor
|
||||
if hasattr(streamer.data_streamer, "priority_manager"):
|
||||
streamer.data_streamer.priority_manager.shutdown()
|
||||
if hasattr(streamer.data_streamer, "performance_monitor"):
|
||||
streamer.data_streamer.performance_monitor.stop_monitoring()
|
||||
streamer.release_instance_lock()
|
||||
if streamer is not None:
|
||||
print("🛑 Stopping streaming...")
|
||||
streamer.stop_streaming()
|
||||
|
||||
print("📡 Disconnecting PLC...")
|
||||
# 🚀 PRESERVE auto-reconnect state: Don't clear reconnection data
|
||||
streamer.disconnect_plc(
|
||||
manual_disconnect=False
|
||||
) # Keep auto-reconnect state
|
||||
|
||||
print("🧹 Shutting down priority manager and performance monitor...")
|
||||
# 🔑 PRIORITY: Shutdown priority manager and performance monitor
|
||||
if hasattr(streamer.data_streamer, "priority_manager"):
|
||||
streamer.data_streamer.priority_manager.shutdown()
|
||||
if hasattr(streamer.data_streamer, "performance_monitor"):
|
||||
streamer.data_streamer.performance_monitor.stop_monitoring()
|
||||
|
||||
print("🔓 Releasing instance lock...")
|
||||
streamer.release_instance_lock()
|
||||
print("✅ Instance lock released")
|
||||
else:
|
||||
print("⚠️ Streamer not initialized, skipping shutdown steps")
|
||||
|
||||
print("📝 Closing rotating logger system...")
|
||||
# 📝 Close rotating logger system
|
||||
backend_logger.close()
|
||||
print("✅ Shutdown completed successfully")
|
||||
|
||||
except Exception as e:
|
||||
print(f"⚠️ Error during shutdown: {e}")
|
||||
# Try to force cleanup lock file as last resort
|
||||
try:
|
||||
import os
|
||||
|
||||
lock_file = "plc_streamer.lock"
|
||||
if os.path.exists(lock_file):
|
||||
os.remove(lock_file)
|
||||
print(f"🧹 Emergency cleanup: Removed lock file")
|
||||
except:
|
||||
pass # Silent fail for emergency cleanup
|
||||
|
||||
|
||||
def signal_handler(sig, frame):
|
||||
"""Handle interrupt signals (Ctrl+C)"""
|
||||
"""Handle interrupt signals (Ctrl+C) with proper cleanup"""
|
||||
print(f"\n🛑 Received signal {sig}...")
|
||||
graceful_shutdown()
|
||||
print("👋 Exiting application...")
|
||||
sys.exit(0)
|
||||
|
||||
|
||||
|
@ -3789,10 +3869,48 @@ def open_csv_in_excel():
|
|||
|
||||
|
||||
if __name__ == "__main__":
|
||||
print(f"🚀 Starting PLC S7-315 Streamer & Logger...")
|
||||
print(f"🐍 Process PID: {os.getpid()}")
|
||||
|
||||
# 🔍 OPTIONAL: Early check for existing instance (faster feedback)
|
||||
# Comment out the next 4 lines if you prefer the full error handling in PLCDataStreamer
|
||||
if not check_for_running_instance_early():
|
||||
print("❌ Startup aborted due to existing instance")
|
||||
# input("Press Enter to exit...")
|
||||
sys.exit(1)
|
||||
|
||||
try:
|
||||
# Initialize streamer instance
|
||||
# Initialize streamer instance with instance check
|
||||
print("✅ No conflicting instances found (early check)")
|
||||
print("🔧 Initializing PLCDataStreamer...")
|
||||
streamer = PLCDataStreamer()
|
||||
|
||||
print("📊 Initializing Historical Cache...")
|
||||
# Pass the backend_logger to HistoricalDataCache
|
||||
historical_cache = HistoricalDataCache(backend_logger)
|
||||
|
||||
print("✅ Backend initialization complete")
|
||||
main()
|
||||
|
||||
except RuntimeError as e:
|
||||
if "Another instance" in str(e):
|
||||
print("🚫 Another instance of the application is already running.")
|
||||
print("💡 Please stop the other instance first or wait for it to finish.")
|
||||
print(f"📋 Error details: {e}")
|
||||
print("\n🔍 You can check Task Manager for 'python.exe' processes")
|
||||
print(
|
||||
"🔍 Or wait a few seconds and try again if the other instance is closing"
|
||||
)
|
||||
input("\nPress Enter to exit...")
|
||||
else:
|
||||
print(f"💥 Runtime error during initialization: {e}")
|
||||
input("Press Enter to exit...")
|
||||
sys.exit(1)
|
||||
|
||||
except Exception as e:
|
||||
print(f"💥 Critical error during initialization: {e}")
|
||||
import traceback
|
||||
|
||||
traceback.print_exc()
|
||||
input("Press Enter to exit...")
|
||||
sys.exit(1)
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
{
|
||||
"last_state": {
|
||||
"should_connect": false,
|
||||
"should_connect": true,
|
||||
"should_stream": false,
|
||||
"active_datasets": [
|
||||
"Test",
|
||||
"DAR",
|
||||
"Fast",
|
||||
"DAR"
|
||||
"Test"
|
||||
]
|
||||
},
|
||||
"auto_recovery_enabled": true,
|
||||
"last_update": "2025-08-19T10:28:32.782080"
|
||||
"last_update": "2025-08-19T17:05:49.105884"
|
||||
}
|
Loading…
Reference in New Issue