@@ -1356,14 +1356,18 @@ def stop(self) -> None:
13561356 # - set _stop_flag
13571357 # - kill the elevated PID (macOS)
13581358 # - cleanup temp files
1359- if self ._thread and self . _thread . isRunning () :
1359+ if self ._thread is not None :
13601360 self ._logger .append (
1361- "[OpenVPN] Requesting worker thread to stop." ,
1361+ f "[OpenVPN] Requesting worker thread to stop (isRunning= { self . _thread . isRunning () } ) ." ,
13621362 channel = self ._log_channel ,
13631363 )
13641364 try :
1365+ # Even if the thread is no longer running, its stop()
1366+ # will attempt to kill any elevated OpenVPN PID it knows
1367+ # about (on macOS) and perform process cleanup.
13651368 self ._thread .stop ()
1366- self ._thread .wait ()
1369+ if self ._thread .isRunning ():
1370+ self ._thread .wait ()
13671371 except Exception as e :
13681372 self ._logger .append (
13691373 f"[OpenVPN] Exception stopping worker thread: { e } " ,
@@ -1394,8 +1398,59 @@ def stop(self) -> None:
13941398 level = "debug" ,
13951399 )
13961400
1397- # 3) Emit final state
1401+ # 3) macOS safety net: kill any leftover elevated OpenVPN process
1402+ # that may still be running under our runtime directory.
1403+ try :
1404+ if self ._helper .get_os () == "macos" :
1405+ run_dir = self ._runtime_dir ()
1406+ pid_path = os .path .join (run_dir , "openvpn.pid" )
1407+ if os .path .exists (pid_path ):
1408+ try :
1409+ with open (pid_path , "r" , encoding = "utf-8" ) as f :
1410+ pid_str = f .read ().strip ()
1411+ except Exception as e :
1412+ pid_str = ""
1413+ self ._logger .append (
1414+ f"[OpenVPN] Safety net: failed to read PID file { pid_path } : { e } " ,
1415+ channel = self ._log_channel ,
1416+ level = "warning" ,
1417+ )
1418+
1419+ if pid_str :
1420+ kill_cmd = f"kill { pid_str } "
1421+ as_cmd = kill_cmd .replace ("\\ " , "\\ \\ " ).replace ('"' , '\\ "' )
1422+ applescript = f'do shell script "{ as_cmd } " with administrator privileges'
1423+ self ._logger .append (
1424+ f"[OpenVPN] Safety net: requesting termination of OpenVPN PID { pid_str } via AppleScript." ,
1425+ channel = self ._log_channel ,
1426+ level = "debug" ,
1427+ )
1428+ proc = subprocess .Popen (
1429+ ["osascript" , "-e" , applescript ],
1430+ stdout = subprocess .PIPE ,
1431+ stderr = subprocess .PIPE ,
1432+ text = True ,
1433+ )
1434+ try :
1435+ out , err = proc .communicate (timeout = 10 )
1436+ except Exception :
1437+ out , err = "" , ""
1438+ self ._logger .append (
1439+ f"[OpenVPN] Safety net kill result: returncode={ proc .returncode } , "
1440+ f"stdout={ out !r} , stderr={ err !r} " ,
1441+ channel = self ._log_channel ,
1442+ level = "debug" ,
1443+ )
1444+ except Exception as e :
1445+ self ._logger .append (
1446+ f"[OpenVPN] Safety net macOS kill failed: { e } " ,
1447+ channel = self ._log_channel ,
1448+ level = "warning" ,
1449+ )
1450+
1451+ # 4) Emit final state
13981452 self .stateChanged .emit ("stopped" )
1453+
13991454 # ------------------------------------------------------------------
14001455 # Command generation
14011456 # ------------------------------------------------------------------
0 commit comments