SVGHMI: prevent browser and watchdog commands to become zombie once finished.
authorEdouard Tisserant
Tue, 18 Oct 2022 11:09:40 +0200
changeset 3647 7c427418396f
parent 3646 db87744d8900
child 3648 ff42600fddd7
SVGHMI: prevent browser and watchdog commands to become zombie once finished.
svghmi/svghmi.py
svghmi/svghmi_server.py
--- a/svghmi/svghmi.py	Wed Oct 12 11:59:47 2022 +0200
+++ b/svghmi/svghmi.py	Tue Oct 18 11:09:40 2022 +0200
@@ -636,19 +636,29 @@
              svghmi_cmds[thing] = (
                 "Popen(" +
                 repr(shlex.split(given_command.format(**svghmi_options))) +
-                ")") if given_command else "pass # no command given"
+                ")") if given_command else "None # no command given"
 
         runtimefile_path = os.path.join(buildpath, "runtime_%s_svghmi_.py" % location_str)
         runtimefile = open(runtimefile_path, 'w')
         runtimefile.write("""
-# TODO : multiple watchdog (one for each svghmi instance)
+#!/usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# generated by beremiz/svghmi/svghmi.py
+
+browser_proc = None
+
 def svghmi_{location}_watchdog_trigger():
-    {svghmi_cmds[Watchdog]}
+    global browser_proc
+    restart_proc = {svghmi_cmds[Watchdog]}
+    waitpid_timeout(restart_proc, "SVGHMI watchdog triggered command")
+    waitpid_timeout(browser_proc, "SVGHMI browser process")
+    browser_proc = None
 
 max_svghmi_sessions = {maxConnections_total}
 
 def _runtime_{location}_svghmi_start():
-    global svghmi_watchdog, svghmi_servers
+    global svghmi_watchdog, svghmi_servers, browser_proc
 
     srv = svghmi_servers.get("{interface}:{port}", None)
     if srv is not None:
@@ -673,7 +683,7 @@
 
     path_list.append("{path}")
 
-    {svghmi_cmds[Start]}
+    browser_proc = {svghmi_cmds[Start]}
 
     if {enable_watchdog}:
         if svghmi_watchdog is None:
@@ -686,7 +696,7 @@
 
 
 def _runtime_{location}_svghmi_stop():
-    global svghmi_watchdog, svghmi_servers
+    global svghmi_watchdog, svghmi_servers, browser_proc
 
     if svghmi_watchdog is not None:
         svghmi_watchdog.cancel()
@@ -702,7 +712,10 @@
         svghmi_listener.stopListening()
         svghmi_servers.pop("{interface}:{port}")
 
-    {svghmi_cmds[Stop]}
+    stop_proc = {svghmi_cmds[Stop]}
+    waitpid_timeout(stop_proc, "SVGHMI stop command")
+    waitpid_timeout(browser_proc, "SVGHMI browser process")
+    browser_proc = None
 
         """.format(location=location_str,
                    xhtml=target_fname,
--- a/svghmi/svghmi_server.py	Wed Oct 12 11:59:47 2022 +0200
+++ b/svghmi/svghmi_server.py	Tue Oct 18 11:09:40 2022 +0200
@@ -8,6 +8,7 @@
 from __future__ import absolute_import
 import errno
 from threading import RLock, Timer
+import os, time
 
 try:
     from runtime.spawn_subprocess import Popen
@@ -23,6 +24,9 @@
 from autobahn.websocket.protocol import WebSocketProtocol
 from autobahn.twisted.resource import  WebSocketResource
 
+from runtime.loglevels import LogLevelsDict
+from runtime import GetPLCObjectSingleton
+
 max_svghmi_sessions = None
 svghmi_watchdog = None
 
@@ -299,3 +303,21 @@
     render_HEAD = render_GET
 
 
+def waitpid_timeout(proc, helpstr="", timeout = 3):
+    if proc is None:
+        return
+    def waitpid_timeout_loop(pid=proc.pid, timeout = timeout):
+        try:
+            while os.waitpid(pid,os.WNOHANG) == (0,0):
+                time.sleep(1)
+                timeout = timeout - 1
+                if not timeout:
+                    GetPLCObjectSingleton().LogMessage(
+                        LogLevelsDict["WARNING"], 
+                        "Timeout waiting for {} PID: {}".format(helpstr, str(pid)))
+                    break
+        except OSError:
+            # workaround exception "OSError: [Errno 10] No child processes"
+            pass
+    Thread(target=waitpid_timeout_loop, name="Zombie hunter").start()
+