@@ -278,17 +278,28 @@ def _cleanup_socket(self) -> None:
278278 def _kill_existing_processes (self ) -> None :
279279 """Kill any existing PGlite processes that might conflict with this socket."""
280280 try :
281- my_socket_dir = str (Path (self .config .socket_path ).parent )
281+ # Fix for issue #31: Compare work directory, not socket directory
282+ # Socket and work directories are different by design for isolation
283+ # Use work_dir if available, otherwise fall back to socket directory
284+ if hasattr (self , "work_dir" ) and self .work_dir :
285+ my_target_dir = str (self .work_dir )
286+ comparison_type = "work directory"
287+ else :
288+ my_target_dir = str (Path (self .config .socket_path ).parent )
289+ comparison_type = "socket directory"
290+
282291 for proc in psutil .process_iter (["pid" , "name" , "cmdline" , "cwd" ]):
283292 if proc .info ["cmdline" ] and any (
284293 "pglite_manager.js" in cmd for cmd in proc .info ["cmdline" ]
285294 ):
286- # Only kill processes in the same socket directory to avoid killing other instances
295+ # Use exact directory match to avoid killing processes in similar paths
287296 try :
288297 proc_cwd = proc .info .get ("cwd" , "" )
289- if my_socket_dir in proc_cwd or proc_cwd in my_socket_dir :
298+ if proc_cwd == my_target_dir :
290299 pid = proc .info ["pid" ]
291- self .logger .info (f"Killing existing PGlite process: { pid } " )
300+ self .logger .info (
301+ f"Killing existing PGlite process: { pid } (matching { comparison_type } )"
302+ )
292303 proc .kill ()
293304 proc .wait (timeout = 5 )
294305 except (psutil .NoSuchProcess , psutil .AccessDenied ):
@@ -297,6 +308,31 @@ def _kill_existing_processes(self) -> None:
297308 except Exception as e :
298309 self .logger .warning (f"Error killing existing PGlite processes: { e } " )
299310
311+ def _kill_all_pglite_processes (self ) -> None :
312+ """Kill all PGlite processes globally (more aggressive cleanup for termination)."""
313+ try :
314+ killed_processes = []
315+ for proc in psutil .process_iter (["pid" , "name" , "cmdline" ]):
316+ if proc .info ["cmdline" ] and any (
317+ "pglite_manager.js" in cmd for cmd in proc .info ["cmdline" ]
318+ ):
319+ try :
320+ pid = proc .info ["pid" ]
321+ self .logger .info (f"Killing PGlite process globally: { pid } " )
322+ proc .kill ()
323+ proc .wait (timeout = 5 )
324+ killed_processes .append (pid )
325+ except (psutil .NoSuchProcess , psutil .AccessDenied ):
326+ # Process already gone or can't access it
327+ continue
328+
329+ if killed_processes :
330+ self .logger .info (
331+ f"Killed { len (killed_processes )} PGlite processes: { killed_processes } "
332+ )
333+ except Exception as e :
334+ self .logger .warning (f"Error killing all PGlite processes: { e } " )
335+
300336 def _install_dependencies (self , work_dir : Path ) -> None :
301337 """Install npm dependencies if needed."""
302338 if not self .config .auto_install_deps :
@@ -322,12 +358,13 @@ def start(self) -> None:
322358 self .logger .warning ("PGlite process already running" )
323359 return
324360
361+ # Setup work directory first so it's available for cleanup logic
362+ self .work_dir = self ._setup_work_dir ()
363+
325364 # Setup
326365 self ._kill_existing_processes ()
327366 self ._cleanup_socket ()
328367
329- # Setup work directory
330- self .work_dir = self ._setup_work_dir ()
331368 self ._original_cwd = os .getcwd ()
332369 os .chdir (self .work_dir )
333370
@@ -360,6 +397,9 @@ def start(self) -> None:
360397 bufsize = 0 , # Unbuffered for real-time monitoring
361398 universal_newlines = True ,
362399 env = env ,
400+ preexec_fn = os .setsid
401+ if hasattr (os , "setsid" )
402+ else None , # Create new process group on Unix
363403 )
364404
365405 # Wait for startup with robust monitoring
@@ -462,7 +502,18 @@ def stop(self) -> None:
462502 try :
463503 # Send SIGTERM first for graceful shutdown
464504 self .logger .debug ("Sending SIGTERM to PGlite process..." )
465- self .process .terminate ()
505+
506+ # Try to terminate the entire process group if it exists
507+ if hasattr (os , "killpg" ) and hasattr (self .process , "pid" ):
508+ try :
509+ # Try to kill the process group first (includes child processes)
510+ os .killpg (os .getpgid (self .process .pid ), 15 ) # SIGTERM
511+ self .logger .debug ("Sent SIGTERM to process group" )
512+ except (OSError , ProcessLookupError ):
513+ # Fall back to single process termination
514+ self .process .terminate ()
515+ else :
516+ self .process .terminate ()
466517
467518 # Wait for graceful shutdown with timeout
468519 try :
@@ -473,17 +524,32 @@ def stop(self) -> None:
473524 self .logger .warning (
474525 "PGlite process didn't stop gracefully, force killing..."
475526 )
476- self .process .kill ()
527+
528+ # Try to kill the entire process group first
529+ if hasattr (os , "killpg" ) and hasattr (self .process , "pid" ):
530+ try :
531+ os .killpg (os .getpgid (self .process .pid ), 9 ) # SIGKILL
532+ self .logger .debug ("Sent SIGKILL to process group" )
533+ except (OSError , ProcessLookupError ):
534+ # Fall back to single process kill
535+ self .process .kill ()
536+ else :
537+ self .process .kill ()
538+
477539 try :
478540 self .process .wait (timeout = 2 )
479541 self .logger .info ("PGlite server stopped forcefully" )
480542 except subprocess .TimeoutExpired :
481543 self .logger .error ("Failed to kill PGlite process!" )
544+ # Use global cleanup as last resort when normal termination fails
545+ self ._kill_all_pglite_processes ()
482546
483547 except Exception as e :
484548 self .logger .warning (f"Error stopping PGlite: { e } " )
485549 finally :
486550 self .process = None
551+ # Additional cleanup: kill any remaining pglite processes
552+ # Note: Global cleanup is only used in error conditions, not normal stop
487553 if self .config .cleanup_on_exit :
488554 self ._cleanup_socket ()
489555
0 commit comments