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:
Miguel 2025-08-19 17:07:00 +02:00
parent 4a064937d3
commit 5e89921f05
3 changed files with 781 additions and 1051 deletions

File diff suppressed because it is too large Load Diff

140
main.py
View File

@ -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)

View File

@ -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"
}