comfy-env 0.0.50__tar.gz → 0.0.51__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.51}/PKG-INFO +1 -1
  2. {comfy_env-0.0.50 → comfy_env-0.0.51}/pyproject.toml +1 -1
  3. {comfy_env-0.0.50 → comfy_env-0.0.51}/src/comfy_env/pixi.py +7 -0
  4. {comfy_env-0.0.50 → comfy_env-0.0.51}/src/comfy_env/workers/venv.py +88 -10
  5. {comfy_env-0.0.50 → comfy_env-0.0.51}/.github/workflows/publish.yml +0 -0
  6. {comfy_env-0.0.50 → comfy_env-0.0.51}/.gitignore +0 -0
  7. {comfy_env-0.0.50 → comfy_env-0.0.51}/LICENSE +0 -0
  8. {comfy_env-0.0.50 → comfy_env-0.0.51}/README.md +0 -0
  9. {comfy_env-0.0.50 → comfy_env-0.0.51}/src/comfy_env/__init__.py +0 -0
  10. {comfy_env-0.0.50 → comfy_env-0.0.51}/src/comfy_env/cli.py +0 -0
  11. {comfy_env-0.0.50 → comfy_env-0.0.51}/src/comfy_env/decorator.py +0 -0
  12. {comfy_env-0.0.50 → comfy_env-0.0.51}/src/comfy_env/env/__init__.py +0 -0
  13. {comfy_env-0.0.50 → comfy_env-0.0.51}/src/comfy_env/env/config.py +0 -0
  14. {comfy_env-0.0.50 → comfy_env-0.0.51}/src/comfy_env/env/config_file.py +0 -0
  15. {comfy_env-0.0.50 → comfy_env-0.0.51}/src/comfy_env/env/cuda_gpu_detection.py +0 -0
  16. {comfy_env-0.0.50 → comfy_env-0.0.51}/src/comfy_env/env/manager.py +0 -0
  17. {comfy_env-0.0.50 → comfy_env-0.0.51}/src/comfy_env/env/platform/__init__.py +0 -0
  18. {comfy_env-0.0.50 → comfy_env-0.0.51}/src/comfy_env/env/platform/base.py +0 -0
  19. {comfy_env-0.0.50 → comfy_env-0.0.51}/src/comfy_env/env/platform/darwin.py +0 -0
  20. {comfy_env-0.0.50 → comfy_env-0.0.51}/src/comfy_env/env/platform/linux.py +0 -0
  21. {comfy_env-0.0.50 → comfy_env-0.0.51}/src/comfy_env/env/platform/windows.py +0 -0
  22. {comfy_env-0.0.50 → comfy_env-0.0.51}/src/comfy_env/env/security.py +0 -0
  23. {comfy_env-0.0.50 → comfy_env-0.0.51}/src/comfy_env/errors.py +0 -0
  24. {comfy_env-0.0.50 → comfy_env-0.0.51}/src/comfy_env/install.py +0 -0
  25. {comfy_env-0.0.50 → comfy_env-0.0.51}/src/comfy_env/ipc/__init__.py +0 -0
  26. {comfy_env-0.0.50 → comfy_env-0.0.51}/src/comfy_env/ipc/bridge.py +0 -0
  27. {comfy_env-0.0.50 → comfy_env-0.0.51}/src/comfy_env/ipc/protocol.py +0 -0
  28. {comfy_env-0.0.50 → comfy_env-0.0.51}/src/comfy_env/ipc/tensor.py +0 -0
  29. {comfy_env-0.0.50 → comfy_env-0.0.51}/src/comfy_env/ipc/torch_bridge.py +0 -0
  30. {comfy_env-0.0.50 → comfy_env-0.0.51}/src/comfy_env/ipc/transport.py +0 -0
  31. {comfy_env-0.0.50 → comfy_env-0.0.51}/src/comfy_env/ipc/worker.py +0 -0
  32. {comfy_env-0.0.50 → comfy_env-0.0.51}/src/comfy_env/isolation.py +0 -0
  33. {comfy_env-0.0.50 → comfy_env-0.0.51}/src/comfy_env/nodes.py +0 -0
  34. {comfy_env-0.0.50 → comfy_env-0.0.51}/src/comfy_env/registry.py +0 -0
  35. {comfy_env-0.0.50 → comfy_env-0.0.51}/src/comfy_env/resolver.py +0 -0
  36. {comfy_env-0.0.50 → comfy_env-0.0.51}/src/comfy_env/stub_imports.py +0 -0
  37. {comfy_env-0.0.50 → comfy_env-0.0.51}/src/comfy_env/stubs/__init__.py +0 -0
  38. {comfy_env-0.0.50 → comfy_env-0.0.51}/src/comfy_env/stubs/comfy/__init__.py +0 -0
  39. {comfy_env-0.0.50 → comfy_env-0.0.51}/src/comfy_env/stubs/comfy/model_management.py +0 -0
  40. {comfy_env-0.0.50 → comfy_env-0.0.51}/src/comfy_env/stubs/comfy/utils.py +0 -0
  41. {comfy_env-0.0.50 → comfy_env-0.0.51}/src/comfy_env/stubs/folder_paths.py +0 -0
  42. {comfy_env-0.0.50 → comfy_env-0.0.51}/src/comfy_env/templates/comfy-env-instructions.txt +0 -0
  43. {comfy_env-0.0.50 → comfy_env-0.0.51}/src/comfy_env/templates/comfy-env.toml +0 -0
  44. {comfy_env-0.0.50 → comfy_env-0.0.51}/src/comfy_env/wheel_sources.yml +0 -0
  45. {comfy_env-0.0.50 → comfy_env-0.0.51}/src/comfy_env/workers/__init__.py +0 -0
  46. {comfy_env-0.0.50 → comfy_env-0.0.51}/src/comfy_env/workers/base.py +0 -0
  47. {comfy_env-0.0.50 → comfy_env-0.0.51}/src/comfy_env/workers/pool.py +0 -0
  48. {comfy_env-0.0.50 → comfy_env-0.0.51}/src/comfy_env/workers/tensor_utils.py +0 -0
  49. {comfy_env-0.0.50 → comfy_env-0.0.51}/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.51
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.51"
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,6 +233,13 @@ 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)
@@ -572,8 +572,13 @@ import json
572
572
  import socket
573
573
  import struct
574
574
  import traceback
575
+ import faulthandler
575
576
  from types import SimpleNamespace
576
577
 
578
+ # Enable faulthandler to dump traceback on SIGSEGV/SIGABRT/etc
579
+ faulthandler.enable(file=sys.stderr, all_threads=True)
580
+ print("[worker] Faulthandler enabled", flush=True)
581
+
577
582
  # On Windows, add host Python's DLL directories so packages like opencv can find VC++ runtime
578
583
  if sys.platform == "win32":
579
584
  _host_python_dir = os.environ.get("COMFYUI_HOST_PYTHON_DIR")
@@ -741,20 +746,24 @@ def _deserialize_isolated_objects(obj):
741
746
 
742
747
 
743
748
  def main():
749
+ print("[worker] Starting...", flush=True)
744
750
  # Get socket address from command line
745
751
  if len(sys.argv) < 2:
746
752
  print("Usage: worker.py <socket_addr>", file=sys.stderr)
747
753
  sys.exit(1)
748
754
  socket_addr = sys.argv[1]
755
+ print(f"[worker] Connecting to {socket_addr}...", flush=True)
749
756
 
750
757
  # Connect to host process
751
758
  sock = _connect(socket_addr)
752
759
  transport = SocketTransport(sock)
760
+ print("[worker] Connected, waiting for config...", flush=True)
753
761
 
754
762
  # Read config as first message
755
763
  config = transport.recv()
756
764
  if not config:
757
765
  return
766
+ print("[worker] Got config, setting up paths...", flush=True)
758
767
 
759
768
  # Setup sys.path
760
769
  for p in config.get("sys_paths", []):
@@ -762,10 +771,13 @@ def main():
762
771
  sys.path.insert(0, p)
763
772
 
764
773
  # Import torch after path setup
774
+ print("[worker] Importing torch...", flush=True)
765
775
  import torch
776
+ print(f"[worker] Torch imported: {torch.__version__}", flush=True)
766
777
 
767
778
  # Signal ready
768
779
  transport.send({"status": "ready"})
780
+ print("[worker] Ready, entering request loop...", flush=True)
769
781
 
770
782
  # Process requests
771
783
  while True:
@@ -784,30 +796,41 @@ def main():
784
796
  module_name = request["module"]
785
797
  inputs_path = request.get("inputs_path")
786
798
  outputs_path = request.get("outputs_path")
799
+ print(f"[worker] Request: {request_type} {module_name}", flush=True)
787
800
 
788
801
  # Load inputs
789
802
  if inputs_path:
803
+ print(f"[worker] Loading inputs from {inputs_path}...", flush=True)
790
804
  inputs = torch.load(inputs_path, weights_only=False)
805
+ print(f"[worker] Deserializing isolated objects...", flush=True)
791
806
  inputs = _deserialize_isolated_objects(inputs)
792
807
  # Resolve any object references from previous node calls
808
+ print(f"[worker] Resolving object references...", flush=True)
793
809
  inputs = _deserialize_input(inputs)
810
+ print(f"[worker] Inputs ready: {list(inputs.keys())}", flush=True)
794
811
  else:
795
812
  inputs = {}
796
813
 
797
814
  # Import module
815
+ print(f"[worker] Importing module {module_name}...", flush=True)
798
816
  module = __import__(module_name, fromlist=[""])
817
+ print(f"[worker] Module imported", flush=True)
799
818
 
800
819
  if request_type == "call_method":
801
820
  class_name = request["class_name"]
802
821
  method_name = request["method_name"]
803
822
  self_state = request.get("self_state")
823
+ print(f"[worker] Getting class {class_name}...", flush=True)
804
824
 
805
825
  cls = getattr(module, class_name)
826
+ print(f"[worker] Creating instance...", flush=True)
806
827
  instance = object.__new__(cls)
807
828
  if self_state:
808
829
  instance.__dict__.update(self_state)
830
+ print(f"[worker] Calling {method_name}...", flush=True)
809
831
  method = getattr(instance, method_name)
810
832
  result = method(**inputs)
833
+ print(f"[worker] Method returned", flush=True)
811
834
  else:
812
835
  func_name = request["func"]
813
836
  func = getattr(module, func_name)
@@ -892,6 +915,10 @@ class PersistentVenvWorker(Worker):
892
915
  self._socket_addr: Optional[str] = None
893
916
  self._transport: Optional[SocketTransport] = None
894
917
 
918
+ # Stderr buffer for crash diagnostics
919
+ self._stderr_buffer: List[str] = []
920
+ self._stderr_lock = threading.Lock()
921
+
895
922
  # Write worker script to temp file
896
923
  self._worker_script = self._temp_dir / "persistent_worker.py"
897
924
  self._worker_script.write_text(_PERSISTENT_WORKER_SCRIPT)
@@ -964,13 +991,17 @@ class PersistentVenvWorker(Worker):
964
991
  [str(self.python), str(self._worker_script), self._socket_addr],
965
992
  stdin=subprocess.DEVNULL,
966
993
  stdout=subprocess.PIPE,
967
- stderr=subprocess.STDOUT, # Merge stdout/stderr for forwarding
994
+ stderr=subprocess.PIPE, # Capture stderr separately for crash diagnostics
968
995
  cwd=str(self.working_dir),
969
996
  env=env,
970
997
  )
971
998
 
972
- # Start output forwarding thread
973
- def forward_output():
999
+ # Clear stderr buffer for new process
1000
+ with self._stderr_lock:
1001
+ self._stderr_buffer.clear()
1002
+
1003
+ # Start stdout forwarding thread
1004
+ def forward_stdout():
974
1005
  try:
975
1006
  for line in self._process.stdout:
976
1007
  if isinstance(line, bytes):
@@ -979,22 +1010,43 @@ class PersistentVenvWorker(Worker):
979
1010
  sys.stderr.flush()
980
1011
  except:
981
1012
  pass
982
- self._output_thread = threading.Thread(target=forward_output, daemon=True)
983
- self._output_thread.start()
1013
+ self._stdout_thread = threading.Thread(target=forward_stdout, daemon=True)
1014
+ self._stdout_thread.start()
1015
+
1016
+ # Start stderr capture thread (buffer for crash diagnostics)
1017
+ def capture_stderr():
1018
+ try:
1019
+ for line in self._process.stderr:
1020
+ if isinstance(line, bytes):
1021
+ line = line.decode('utf-8', errors='replace')
1022
+ # Print to terminal AND buffer for crash reporting
1023
+ sys.stderr.write(f" [stderr] {line}")
1024
+ sys.stderr.flush()
1025
+ with self._stderr_lock:
1026
+ self._stderr_buffer.append(line.rstrip())
1027
+ # Keep last 50 lines
1028
+ if len(self._stderr_buffer) > 50:
1029
+ self._stderr_buffer.pop(0)
1030
+ except:
1031
+ pass
1032
+ self._stderr_thread = threading.Thread(target=capture_stderr, daemon=True)
1033
+ self._stderr_thread.start()
984
1034
 
985
1035
  # Accept connection from worker with timeout
986
1036
  self._server_socket.settimeout(60)
987
1037
  try:
988
1038
  client_sock, _ = self._server_socket.accept()
989
1039
  except socket.timeout:
990
- stderr = ""
1040
+ # Collect stderr from buffer
1041
+ time.sleep(0.2) # Give stderr thread time to capture
1042
+ with self._stderr_lock:
1043
+ stderr = "\n".join(self._stderr_buffer) if self._stderr_buffer else "(no stderr captured)"
991
1044
  try:
992
1045
  self._process.kill()
993
- stdout, _ = self._process.communicate(timeout=5)
994
- stderr = stdout.decode('utf-8', errors='replace') if stdout else ""
1046
+ self._process.wait(timeout=5)
995
1047
  except:
996
1048
  pass
997
- raise RuntimeError(f"{self.name}: Worker failed to connect (timeout). output: {stderr}")
1049
+ raise RuntimeError(f"{self.name}: Worker failed to connect (timeout).\nStderr:\n{stderr}")
998
1050
  finally:
999
1051
  self._server_socket.settimeout(None)
1000
1052
 
@@ -1035,7 +1087,33 @@ class PersistentVenvWorker(Worker):
1035
1087
  self._transport.send(request)
1036
1088
 
1037
1089
  # Read response with timeout
1038
- response = self._transport.recv(timeout=timeout)
1090
+ try:
1091
+ response = self._transport.recv(timeout=timeout)
1092
+ except ConnectionError as e:
1093
+ # Socket closed - check if worker process died
1094
+ self._shutdown = True
1095
+ time.sleep(0.2) # Give process time to fully exit and stderr to flush
1096
+ exit_code = None
1097
+ if self._process:
1098
+ exit_code = self._process.poll()
1099
+
1100
+ # Get captured stderr
1101
+ with self._stderr_lock:
1102
+ stderr_output = "\n".join(self._stderr_buffer) if self._stderr_buffer else "(no stderr captured)"
1103
+
1104
+ if exit_code is not None:
1105
+ raise RuntimeError(
1106
+ f"{self.name}: Worker process died with exit code {exit_code}. "
1107
+ f"This usually indicates a crash in native code (CGAL, pymeshlab, etc.).\n"
1108
+ f"Stderr:\n{stderr_output}"
1109
+ ) from e
1110
+ else:
1111
+ # Process still alive but socket closed - something weird
1112
+ raise RuntimeError(
1113
+ f"{self.name}: Socket closed but worker process still running. "
1114
+ f"This may indicate a protocol error or worker bug.\n"
1115
+ f"Stderr:\n{stderr_output}"
1116
+ ) from e
1039
1117
 
1040
1118
  if response is None:
1041
1119
  # Timeout - kill process
File without changes
File without changes
File without changes