comfy-env 0.0.50__tar.gz → 0.0.52__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. {comfy_env-0.0.50 → comfy_env-0.0.52}/PKG-INFO +1 -1
  2. {comfy_env-0.0.50 → comfy_env-0.0.52}/pyproject.toml +1 -1
  3. {comfy_env-0.0.50 → comfy_env-0.0.52}/src/comfy_env/pixi.py +11 -0
  4. {comfy_env-0.0.50 → comfy_env-0.0.52}/src/comfy_env/stub_imports.py +18 -11
  5. {comfy_env-0.0.50 → comfy_env-0.0.52}/src/comfy_env/workers/venv.py +150 -11
  6. {comfy_env-0.0.50 → comfy_env-0.0.52}/.github/workflows/publish.yml +0 -0
  7. {comfy_env-0.0.50 → comfy_env-0.0.52}/.gitignore +0 -0
  8. {comfy_env-0.0.50 → comfy_env-0.0.52}/LICENSE +0 -0
  9. {comfy_env-0.0.50 → comfy_env-0.0.52}/README.md +0 -0
  10. {comfy_env-0.0.50 → comfy_env-0.0.52}/src/comfy_env/__init__.py +0 -0
  11. {comfy_env-0.0.50 → comfy_env-0.0.52}/src/comfy_env/cli.py +0 -0
  12. {comfy_env-0.0.50 → comfy_env-0.0.52}/src/comfy_env/decorator.py +0 -0
  13. {comfy_env-0.0.50 → comfy_env-0.0.52}/src/comfy_env/env/__init__.py +0 -0
  14. {comfy_env-0.0.50 → comfy_env-0.0.52}/src/comfy_env/env/config.py +0 -0
  15. {comfy_env-0.0.50 → comfy_env-0.0.52}/src/comfy_env/env/config_file.py +0 -0
  16. {comfy_env-0.0.50 → comfy_env-0.0.52}/src/comfy_env/env/cuda_gpu_detection.py +0 -0
  17. {comfy_env-0.0.50 → comfy_env-0.0.52}/src/comfy_env/env/manager.py +0 -0
  18. {comfy_env-0.0.50 → comfy_env-0.0.52}/src/comfy_env/env/platform/__init__.py +0 -0
  19. {comfy_env-0.0.50 → comfy_env-0.0.52}/src/comfy_env/env/platform/base.py +0 -0
  20. {comfy_env-0.0.50 → comfy_env-0.0.52}/src/comfy_env/env/platform/darwin.py +0 -0
  21. {comfy_env-0.0.50 → comfy_env-0.0.52}/src/comfy_env/env/platform/linux.py +0 -0
  22. {comfy_env-0.0.50 → comfy_env-0.0.52}/src/comfy_env/env/platform/windows.py +0 -0
  23. {comfy_env-0.0.50 → comfy_env-0.0.52}/src/comfy_env/env/security.py +0 -0
  24. {comfy_env-0.0.50 → comfy_env-0.0.52}/src/comfy_env/errors.py +0 -0
  25. {comfy_env-0.0.50 → comfy_env-0.0.52}/src/comfy_env/install.py +0 -0
  26. {comfy_env-0.0.50 → comfy_env-0.0.52}/src/comfy_env/ipc/__init__.py +0 -0
  27. {comfy_env-0.0.50 → comfy_env-0.0.52}/src/comfy_env/ipc/bridge.py +0 -0
  28. {comfy_env-0.0.50 → comfy_env-0.0.52}/src/comfy_env/ipc/protocol.py +0 -0
  29. {comfy_env-0.0.50 → comfy_env-0.0.52}/src/comfy_env/ipc/tensor.py +0 -0
  30. {comfy_env-0.0.50 → comfy_env-0.0.52}/src/comfy_env/ipc/torch_bridge.py +0 -0
  31. {comfy_env-0.0.50 → comfy_env-0.0.52}/src/comfy_env/ipc/transport.py +0 -0
  32. {comfy_env-0.0.50 → comfy_env-0.0.52}/src/comfy_env/ipc/worker.py +0 -0
  33. {comfy_env-0.0.50 → comfy_env-0.0.52}/src/comfy_env/isolation.py +0 -0
  34. {comfy_env-0.0.50 → comfy_env-0.0.52}/src/comfy_env/nodes.py +0 -0
  35. {comfy_env-0.0.50 → comfy_env-0.0.52}/src/comfy_env/registry.py +0 -0
  36. {comfy_env-0.0.50 → comfy_env-0.0.52}/src/comfy_env/resolver.py +0 -0
  37. {comfy_env-0.0.50 → comfy_env-0.0.52}/src/comfy_env/stubs/__init__.py +0 -0
  38. {comfy_env-0.0.50 → comfy_env-0.0.52}/src/comfy_env/stubs/comfy/__init__.py +0 -0
  39. {comfy_env-0.0.50 → comfy_env-0.0.52}/src/comfy_env/stubs/comfy/model_management.py +0 -0
  40. {comfy_env-0.0.50 → comfy_env-0.0.52}/src/comfy_env/stubs/comfy/utils.py +0 -0
  41. {comfy_env-0.0.50 → comfy_env-0.0.52}/src/comfy_env/stubs/folder_paths.py +0 -0
  42. {comfy_env-0.0.50 → comfy_env-0.0.52}/src/comfy_env/templates/comfy-env-instructions.txt +0 -0
  43. {comfy_env-0.0.50 → comfy_env-0.0.52}/src/comfy_env/templates/comfy-env.toml +0 -0
  44. {comfy_env-0.0.50 → comfy_env-0.0.52}/src/comfy_env/wheel_sources.yml +0 -0
  45. {comfy_env-0.0.50 → comfy_env-0.0.52}/src/comfy_env/workers/__init__.py +0 -0
  46. {comfy_env-0.0.50 → comfy_env-0.0.52}/src/comfy_env/workers/base.py +0 -0
  47. {comfy_env-0.0.50 → comfy_env-0.0.52}/src/comfy_env/workers/pool.py +0 -0
  48. {comfy_env-0.0.50 → comfy_env-0.0.52}/src/comfy_env/workers/tensor_utils.py +0 -0
  49. {comfy_env-0.0.50 → comfy_env-0.0.52}/src/comfy_env/workers/torch_mp.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: comfy-env
3
- Version: 0.0.50
3
+ Version: 0.0.52
4
4
  Summary: Environment management for ComfyUI custom nodes - CUDA wheel resolution and process isolation
5
5
  Project-URL: Homepage, https://github.com/PozzettiAndrea/comfy-env
6
6
  Project-URL: Repository, https://github.com/PozzettiAndrea/comfy-env
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "comfy-env"
3
- version = "0.0.50"
3
+ version = "0.0.52"
4
4
  description = "Environment management for ComfyUI custom nodes - CUDA wheel resolution and process isolation"
5
5
  readme = "README.md"
6
6
  license = {text = "MIT"}
@@ -233,12 +233,23 @@ def create_pixi_toml(
233
233
  elif sys.platform == "win32":
234
234
  lines.append('platforms = ["win-64"]')
235
235
 
236
+ # System requirements - specify glibc version for proper wheel resolution
237
+ # Ubuntu 22.04+ has glibc 2.35, enabling manylinux_2_35 wheels
238
+ if sys.platform == "linux":
239
+ lines.append("")
240
+ lines.append("[system-requirements]")
241
+ lines.append('libc = { family = "glibc", version = "2.35" }')
242
+
236
243
  lines.append("")
237
244
 
238
245
  # Dependencies section (conda packages)
239
246
  lines.append("[dependencies]")
240
247
  lines.append(f'python = "{env_config.python}.*"')
241
248
 
249
+ # On Windows, use MKL BLAS to avoid OpenBLAS crashes (numpy blas_fpe_check issue)
250
+ if sys.platform == "win32":
251
+ lines.append('libblas = { version = "*", build = "*mkl" }')
252
+
242
253
  for pkg in conda.packages:
243
254
  # Parse package spec (name=version or name>=version or just name)
244
255
  if "=" in pkg and not pkg.startswith("="):
@@ -156,19 +156,26 @@ def _get_import_names_from_pixi(node_dir: Path) -> Set[str]:
156
156
  """
157
157
  import_names = set()
158
158
 
159
- # Find the pixi site-packages
160
- pixi_lib = node_dir / ".pixi" / "envs" / "default" / "lib"
159
+ pixi_base = node_dir / ".pixi" / "envs" / "default"
161
160
 
162
- if not pixi_lib.exists():
163
- return import_names
164
-
165
- # Find the python version directory (e.g., python3.11)
166
- python_dirs = list(pixi_lib.glob("python3.*"))
167
- if not python_dirs:
168
- return import_names
161
+ # Find site-packages (different paths on Windows vs Linux)
162
+ # Linux: .pixi/envs/default/lib/python3.x/site-packages
163
+ # Windows: .pixi/envs/default/Lib/site-packages
164
+ site_packages = None
169
165
 
170
- site_packages = python_dirs[0] / "site-packages"
171
- if not site_packages.exists():
166
+ # Try Windows path first (Lib/site-packages)
167
+ win_site = pixi_base / "Lib" / "site-packages"
168
+ if win_site.exists():
169
+ site_packages = win_site
170
+ else:
171
+ # Try Linux path (lib/python3.x/site-packages)
172
+ pixi_lib = pixi_base / "lib"
173
+ if pixi_lib.exists():
174
+ python_dirs = list(pixi_lib.glob("python3.*"))
175
+ if python_dirs:
176
+ site_packages = python_dirs[0] / "site-packages"
177
+
178
+ if site_packages is None or not site_packages.exists():
172
179
  return import_names
173
180
 
174
181
  # Scan for importable modules
@@ -252,10 +252,29 @@ def _serialize_for_ipc(obj, visited=None):
252
252
  # Worker script template - minimal, runs in target venv
253
253
  _WORKER_SCRIPT = '''
254
254
  import sys
255
+ import os
255
256
  import json
256
257
  import traceback
257
258
  from types import SimpleNamespace
258
259
 
260
+ # On Windows, add DLL directories for proper library loading
261
+ if sys.platform == "win32" and hasattr(os, "add_dll_directory"):
262
+ _host_python_dir = os.environ.get("COMFYUI_HOST_PYTHON_DIR")
263
+ if _host_python_dir:
264
+ try:
265
+ os.add_dll_directory(_host_python_dir)
266
+ _dlls_dir = os.path.join(_host_python_dir, "DLLs")
267
+ if os.path.isdir(_dlls_dir):
268
+ os.add_dll_directory(_dlls_dir)
269
+ except Exception:
270
+ pass
271
+ _pixi_library_bin = os.environ.get("COMFYUI_PIXI_LIBRARY_BIN")
272
+ if _pixi_library_bin:
273
+ try:
274
+ os.add_dll_directory(_pixi_library_bin)
275
+ except Exception:
276
+ pass
277
+
259
278
  def _deserialize_isolated_objects(obj):
260
279
  """Reconstruct objects serialized with __isolated_object__ marker."""
261
280
  if isinstance(obj, dict):
@@ -475,12 +494,29 @@ class VenvWorker(Worker):
475
494
  env.update(self.extra_env)
476
495
  env["COMFYUI_ISOLATION_WORKER"] = "1"
477
496
 
478
- # For conda/pixi environments, add lib dir to LD_LIBRARY_PATH
497
+ # For conda/pixi environments, add lib dir to LD_LIBRARY_PATH (Linux)
479
498
  lib_dir = self.python.parent.parent / "lib"
480
499
  if lib_dir.is_dir():
481
500
  existing = env.get("LD_LIBRARY_PATH", "")
482
501
  env["LD_LIBRARY_PATH"] = f"{lib_dir}:{existing}" if existing else str(lib_dir)
483
502
 
503
+ # On Windows, pass host Python directory and pixi Library/bin for DLL loading
504
+ if sys.platform == "win32":
505
+ env["COMFYUI_HOST_PYTHON_DIR"] = str(Path(sys.executable).parent)
506
+
507
+ # For pixi environments with MKL, add Library/bin to PATH for DLL loading
508
+ # Pixi has python.exe directly in env dir, not in Scripts/
509
+ env_dir = self.python.parent
510
+ library_bin = env_dir / "Library" / "bin"
511
+ if library_bin.is_dir():
512
+ existing_path = env.get("PATH", "")
513
+ env["PATH"] = f"{env_dir};{library_bin};{existing_path}"
514
+ env["COMFYUI_PIXI_LIBRARY_BIN"] = str(library_bin)
515
+ # Allow duplicate OpenMP libraries (MKL's libiomp5md.dll + PyTorch's libomp.dll)
516
+ env["KMP_DUPLICATE_LIB_OK"] = "TRUE"
517
+ # Use UTF-8 encoding for stdout/stderr to handle Unicode symbols
518
+ env["PYTHONIOENCODING"] = "utf-8"
519
+
484
520
  # Run subprocess
485
521
  cmd = [
486
522
  str(self.python),
@@ -572,8 +608,13 @@ import json
572
608
  import socket
573
609
  import struct
574
610
  import traceback
611
+ import faulthandler
575
612
  from types import SimpleNamespace
576
613
 
614
+ # Enable faulthandler to dump traceback on SIGSEGV/SIGABRT/etc
615
+ faulthandler.enable(file=sys.stderr, all_threads=True)
616
+ print("[worker] Faulthandler enabled", flush=True)
617
+
577
618
  # On Windows, add host Python's DLL directories so packages like opencv can find VC++ runtime
578
619
  if sys.platform == "win32":
579
620
  _host_python_dir = os.environ.get("COMFYUI_HOST_PYTHON_DIR")
@@ -587,6 +628,15 @@ if sys.platform == "win32":
587
628
  except Exception:
588
629
  pass
589
630
 
631
+ # For pixi environments with MKL, add Library/bin for MKL DLLs
632
+ _pixi_library_bin = os.environ.get("COMFYUI_PIXI_LIBRARY_BIN")
633
+ if _pixi_library_bin and hasattr(os, "add_dll_directory"):
634
+ try:
635
+ os.add_dll_directory(_pixi_library_bin)
636
+ print(f"[worker] Added pixi Library/bin to DLL search: {_pixi_library_bin}", flush=True)
637
+ except Exception as e:
638
+ print(f"[worker] Failed to add pixi Library/bin: {e}", flush=True)
639
+
590
640
  # =============================================================================
591
641
  # Object Reference System - keep complex objects in worker, pass refs to host
592
642
  # =============================================================================
@@ -741,20 +791,24 @@ def _deserialize_isolated_objects(obj):
741
791
 
742
792
 
743
793
  def main():
794
+ print("[worker] Starting...", flush=True)
744
795
  # Get socket address from command line
745
796
  if len(sys.argv) < 2:
746
797
  print("Usage: worker.py <socket_addr>", file=sys.stderr)
747
798
  sys.exit(1)
748
799
  socket_addr = sys.argv[1]
800
+ print(f"[worker] Connecting to {socket_addr}...", flush=True)
749
801
 
750
802
  # Connect to host process
751
803
  sock = _connect(socket_addr)
752
804
  transport = SocketTransport(sock)
805
+ print("[worker] Connected, waiting for config...", flush=True)
753
806
 
754
807
  # Read config as first message
755
808
  config = transport.recv()
756
809
  if not config:
757
810
  return
811
+ print("[worker] Got config, setting up paths...", flush=True)
758
812
 
759
813
  # Setup sys.path
760
814
  for p in config.get("sys_paths", []):
@@ -762,10 +816,13 @@ def main():
762
816
  sys.path.insert(0, p)
763
817
 
764
818
  # Import torch after path setup
819
+ print("[worker] Importing torch...", flush=True)
765
820
  import torch
821
+ print(f"[worker] Torch imported: {torch.__version__}", flush=True)
766
822
 
767
823
  # Signal ready
768
824
  transport.send({"status": "ready"})
825
+ print("[worker] Ready, entering request loop...", flush=True)
769
826
 
770
827
  # Process requests
771
828
  while True:
@@ -784,30 +841,41 @@ def main():
784
841
  module_name = request["module"]
785
842
  inputs_path = request.get("inputs_path")
786
843
  outputs_path = request.get("outputs_path")
844
+ print(f"[worker] Request: {request_type} {module_name}", flush=True)
787
845
 
788
846
  # Load inputs
789
847
  if inputs_path:
848
+ print(f"[worker] Loading inputs from {inputs_path}...", flush=True)
790
849
  inputs = torch.load(inputs_path, weights_only=False)
850
+ print(f"[worker] Deserializing isolated objects...", flush=True)
791
851
  inputs = _deserialize_isolated_objects(inputs)
792
852
  # Resolve any object references from previous node calls
853
+ print(f"[worker] Resolving object references...", flush=True)
793
854
  inputs = _deserialize_input(inputs)
855
+ print(f"[worker] Inputs ready: {list(inputs.keys())}", flush=True)
794
856
  else:
795
857
  inputs = {}
796
858
 
797
859
  # Import module
860
+ print(f"[worker] Importing module {module_name}...", flush=True)
798
861
  module = __import__(module_name, fromlist=[""])
862
+ print(f"[worker] Module imported", flush=True)
799
863
 
800
864
  if request_type == "call_method":
801
865
  class_name = request["class_name"]
802
866
  method_name = request["method_name"]
803
867
  self_state = request.get("self_state")
868
+ print(f"[worker] Getting class {class_name}...", flush=True)
804
869
 
805
870
  cls = getattr(module, class_name)
871
+ print(f"[worker] Creating instance...", flush=True)
806
872
  instance = object.__new__(cls)
807
873
  if self_state:
808
874
  instance.__dict__.update(self_state)
875
+ print(f"[worker] Calling {method_name}...", flush=True)
809
876
  method = getattr(instance, method_name)
810
877
  result = method(**inputs)
878
+ print(f"[worker] Method returned", flush=True)
811
879
  else:
812
880
  func_name = request["func"]
813
881
  func = getattr(module, func_name)
@@ -892,6 +960,10 @@ class PersistentVenvWorker(Worker):
892
960
  self._socket_addr: Optional[str] = None
893
961
  self._transport: Optional[SocketTransport] = None
894
962
 
963
+ # Stderr buffer for crash diagnostics
964
+ self._stderr_buffer: List[str] = []
965
+ self._stderr_lock = threading.Lock()
966
+
895
967
  # Write worker script to temp file
896
968
  self._worker_script = self._temp_dir / "persistent_worker.py"
897
969
  self._worker_script.write_text(_PERSISTENT_WORKER_SCRIPT)
@@ -950,6 +1022,22 @@ class PersistentVenvWorker(Worker):
950
1022
  if sys.platform == "win32":
951
1023
  env["COMFYUI_HOST_PYTHON_DIR"] = str(Path(sys.executable).parent)
952
1024
 
1025
+ # For pixi environments with MKL, add Library/bin to PATH for DLL loading
1026
+ # MKL DLLs are in .pixi/envs/default/Library/bin/
1027
+ # Pixi has python.exe directly in env dir, not in Scripts/
1028
+ env_dir = self.python.parent
1029
+ library_bin = env_dir / "Library" / "bin"
1030
+ if library_bin.is_dir():
1031
+ existing_path = env.get("PATH", "")
1032
+ # Add env dir and Library/bin to PATH
1033
+ env["PATH"] = f"{env_dir};{library_bin};{existing_path}"
1034
+ # Also pass as env var so worker can use os.add_dll_directory()
1035
+ env["COMFYUI_PIXI_LIBRARY_BIN"] = str(library_bin)
1036
+ # Allow duplicate OpenMP libraries (MKL's libiomp5md.dll + PyTorch's libomp.dll)
1037
+ env["KMP_DUPLICATE_LIB_OK"] = "TRUE"
1038
+ # Use UTF-8 encoding for stdout/stderr to handle Unicode symbols
1039
+ env["PYTHONIOENCODING"] = "utf-8"
1040
+
953
1041
  # Find ComfyUI base and set env var for folder_paths stub
954
1042
  comfyui_base = self._find_comfyui_base()
955
1043
  if comfyui_base:
@@ -964,13 +1052,17 @@ class PersistentVenvWorker(Worker):
964
1052
  [str(self.python), str(self._worker_script), self._socket_addr],
965
1053
  stdin=subprocess.DEVNULL,
966
1054
  stdout=subprocess.PIPE,
967
- stderr=subprocess.STDOUT, # Merge stdout/stderr for forwarding
1055
+ stderr=subprocess.PIPE, # Capture stderr separately for crash diagnostics
968
1056
  cwd=str(self.working_dir),
969
1057
  env=env,
970
1058
  )
971
1059
 
972
- # Start output forwarding thread
973
- def forward_output():
1060
+ # Clear stderr buffer for new process
1061
+ with self._stderr_lock:
1062
+ self._stderr_buffer.clear()
1063
+
1064
+ # Start stdout forwarding thread
1065
+ def forward_stdout():
974
1066
  try:
975
1067
  for line in self._process.stdout:
976
1068
  if isinstance(line, bytes):
@@ -979,22 +1071,43 @@ class PersistentVenvWorker(Worker):
979
1071
  sys.stderr.flush()
980
1072
  except:
981
1073
  pass
982
- self._output_thread = threading.Thread(target=forward_output, daemon=True)
983
- self._output_thread.start()
1074
+ self._stdout_thread = threading.Thread(target=forward_stdout, daemon=True)
1075
+ self._stdout_thread.start()
1076
+
1077
+ # Start stderr capture thread (buffer for crash diagnostics)
1078
+ def capture_stderr():
1079
+ try:
1080
+ for line in self._process.stderr:
1081
+ if isinstance(line, bytes):
1082
+ line = line.decode('utf-8', errors='replace')
1083
+ # Print to terminal AND buffer for crash reporting
1084
+ sys.stderr.write(f" [stderr] {line}")
1085
+ sys.stderr.flush()
1086
+ with self._stderr_lock:
1087
+ self._stderr_buffer.append(line.rstrip())
1088
+ # Keep last 50 lines
1089
+ if len(self._stderr_buffer) > 50:
1090
+ self._stderr_buffer.pop(0)
1091
+ except:
1092
+ pass
1093
+ self._stderr_thread = threading.Thread(target=capture_stderr, daemon=True)
1094
+ self._stderr_thread.start()
984
1095
 
985
1096
  # Accept connection from worker with timeout
986
1097
  self._server_socket.settimeout(60)
987
1098
  try:
988
1099
  client_sock, _ = self._server_socket.accept()
989
1100
  except socket.timeout:
990
- stderr = ""
1101
+ # Collect stderr from buffer
1102
+ time.sleep(0.2) # Give stderr thread time to capture
1103
+ with self._stderr_lock:
1104
+ stderr = "\n".join(self._stderr_buffer) if self._stderr_buffer else "(no stderr captured)"
991
1105
  try:
992
1106
  self._process.kill()
993
- stdout, _ = self._process.communicate(timeout=5)
994
- stderr = stdout.decode('utf-8', errors='replace') if stdout else ""
1107
+ self._process.wait(timeout=5)
995
1108
  except:
996
1109
  pass
997
- raise RuntimeError(f"{self.name}: Worker failed to connect (timeout). output: {stderr}")
1110
+ raise RuntimeError(f"{self.name}: Worker failed to connect (timeout).\nStderr:\n{stderr}")
998
1111
  finally:
999
1112
  self._server_socket.settimeout(None)
1000
1113
 
@@ -1035,7 +1148,33 @@ class PersistentVenvWorker(Worker):
1035
1148
  self._transport.send(request)
1036
1149
 
1037
1150
  # Read response with timeout
1038
- response = self._transport.recv(timeout=timeout)
1151
+ try:
1152
+ response = self._transport.recv(timeout=timeout)
1153
+ except ConnectionError as e:
1154
+ # Socket closed - check if worker process died
1155
+ self._shutdown = True
1156
+ time.sleep(0.2) # Give process time to fully exit and stderr to flush
1157
+ exit_code = None
1158
+ if self._process:
1159
+ exit_code = self._process.poll()
1160
+
1161
+ # Get captured stderr
1162
+ with self._stderr_lock:
1163
+ stderr_output = "\n".join(self._stderr_buffer) if self._stderr_buffer else "(no stderr captured)"
1164
+
1165
+ if exit_code is not None:
1166
+ raise RuntimeError(
1167
+ f"{self.name}: Worker process died with exit code {exit_code}. "
1168
+ f"This usually indicates a crash in native code (CGAL, pymeshlab, etc.).\n"
1169
+ f"Stderr:\n{stderr_output}"
1170
+ ) from e
1171
+ else:
1172
+ # Process still alive but socket closed - something weird
1173
+ raise RuntimeError(
1174
+ f"{self.name}: Socket closed but worker process still running. "
1175
+ f"This may indicate a protocol error or worker bug.\n"
1176
+ f"Stderr:\n{stderr_output}"
1177
+ ) from e
1039
1178
 
1040
1179
  if response is None:
1041
1180
  # Timeout - kill process
File without changes
File without changes
File without changes