nebu 0.1.76__py3-none-any.whl → 0.1.77__py3-none-any.whl

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.
@@ -5,11 +5,12 @@ import os
5
5
  import socket
6
6
  import subprocess
7
7
  import sys
8
+ import threading
8
9
  import time
9
10
  import traceback
10
11
  import types
11
12
  from datetime import datetime, timezone
12
- from typing import Any, Callable, Dict, List, Optional, Tuple, TypeVar, cast
13
+ from typing import IO, Any, Callable, Dict, List, Optional, Tuple, TypeVar, cast
13
14
 
14
15
  import redis
15
16
  import socks
@@ -351,9 +352,22 @@ def process_message(message_id: str, message_data: Dict[str, str]) -> None:
351
352
  # --- Subprocess Execution Path ---
352
353
  if execution_mode == "subprocess":
353
354
  print(f"Processing message {message_id} in subprocess...")
355
+ process = None # Initialize process variable
356
+
357
+ # Helper function to read and print stream lines
358
+ def stream_reader(stream: IO[str], prefix: str):
359
+ try:
360
+ for line in iter(stream.readline, ""):
361
+ print(f"{prefix}: {line.strip()}", flush=True)
362
+ except Exception as e:
363
+ print(f"Error reading stream {prefix}: {e}")
364
+ finally:
365
+ stream.close()
366
+
354
367
  try:
355
368
  worker_cmd = [
356
369
  sys.executable,
370
+ "-u", # Force unbuffered stdout/stderr in the subprocess
357
371
  "-m",
358
372
  "nebu.processors.consumer_process_worker",
359
373
  ]
@@ -361,52 +375,109 @@ def process_message(message_id: str, message_data: Dict[str, str]) -> None:
361
375
  {"message_id": message_id, "message_data": message_data}
362
376
  )
363
377
 
364
- # Run the worker script as a module
365
- # Inherit environment variables automatically
366
- # Pass message data via stdin
367
- result = subprocess.run(
378
+ # Start the worker process
379
+ process = subprocess.Popen(
368
380
  worker_cmd,
369
- input=process_input,
381
+ stdin=subprocess.PIPE,
382
+ stdout=subprocess.PIPE,
383
+ stderr=subprocess.PIPE,
370
384
  text=True,
371
- capture_output=True,
372
- check=True, # Raise CalledProcessError on non-zero exit
373
- env=os.environ.copy(), # Ensure environment is passed
385
+ encoding="utf-8",
386
+ bufsize=1, # Line buffered
387
+ env=os.environ.copy(),
374
388
  )
375
- print(f"Subprocess for {message_id} completed successfully.")
376
- if result.stdout:
377
- print(f"Subprocess stdout:\n{result.stdout}")
378
- # Assume the subprocess handled response sending and acknowledgement
379
389
 
380
- except subprocess.CalledProcessError as e:
381
- print(
382
- f"Subprocess for message {message_id} failed with exit code {e.returncode}."
390
+ # Create threads to read stdout and stderr concurrently
391
+ stdout_thread = threading.Thread(
392
+ target=stream_reader,
393
+ args=(process.stdout, f"[Subprocess STDOUT {message_id[:8]}]"),
394
+ )
395
+ stderr_thread = threading.Thread(
396
+ target=stream_reader,
397
+ args=(process.stderr, f"[Subprocess STDERR {message_id[:8]}]"),
383
398
  )
384
- if e.stdout:
385
- print(f"Subprocess stdout:\n{e.stdout}")
386
- if e.stderr:
387
- print(f"Subprocess stderr:\n{e.stderr}")
388
399
 
389
- # Send a generic error message back, as the subprocess likely failed
390
- # before it could send its specific error.
400
+ stdout_thread.start()
401
+ stderr_thread.start()
402
+
403
+ # Send input data to the subprocess
404
+ # Ensure process and stdin are valid before writing/closing
405
+ if process and process.stdin:
406
+ try:
407
+ process.stdin.write(process_input)
408
+ process.stdin.close() # Signal end of input
409
+ except (BrokenPipeError, OSError) as e:
410
+ # Handle cases where the process might have exited early
411
+ print(
412
+ f"Warning: Failed to write full input to subprocess {message_id}: {e}. It might have exited prematurely."
413
+ )
414
+ # Continue to wait and check return code
415
+ else:
416
+ print(
417
+ f"Error: Subprocess stdin stream not available for {message_id}. Cannot send input."
418
+ )
419
+ # Handle this case - perhaps terminate and report error?
420
+ # For now, we'll let it proceed to wait() which will likely show an error code.
421
+
422
+ # Wait for the process to finish
423
+ return_code = (
424
+ process.wait() if process else -1
425
+ ) # Handle case where process is None
426
+
427
+ # Wait for reader threads to finish consuming remaining output
428
+ stdout_thread.join()
429
+ stderr_thread.join()
430
+
431
+ if return_code == 0:
432
+ print(
433
+ f"Subprocess for {message_id} completed successfully (return code 0)."
434
+ )
435
+ # Assume success handling (ack/response) was done by the worker
436
+ else:
437
+ print(
438
+ f"Subprocess for {message_id} failed with exit code {return_code}."
439
+ )
440
+ # Worker likely failed, send generic error and ACK here
441
+ _send_error_response(
442
+ message_id,
443
+ f"Subprocess execution failed with exit code {return_code}",
444
+ "See consumer logs for subprocess stderr.", # stderr was already printed
445
+ message_data.get("return_stream"),
446
+ message_data.get("user_id"),
447
+ )
448
+ # CRITICAL: Acknowledge the message here since the subprocess failed
449
+ try:
450
+ assert isinstance(REDIS_STREAM, str)
451
+ assert isinstance(REDIS_CONSUMER_GROUP, str)
452
+ r.xack(REDIS_STREAM, REDIS_CONSUMER_GROUP, message_id)
453
+ print(f"Acknowledged failed subprocess message {message_id}")
454
+ except Exception as e_ack:
455
+ print(
456
+ f"CRITICAL: Failed to acknowledge failed subprocess message {message_id}: {e_ack}"
457
+ )
458
+
459
+ except FileNotFoundError:
460
+ print(
461
+ "FATAL: Worker script 'nebu.processors.consumer_process_worker' not found. Check PYTHONPATH."
462
+ )
463
+ # Send error and ack if possible
391
464
  _send_error_response(
392
465
  message_id,
393
- f"Subprocess execution failed with exit code {e.returncode}",
394
- e.stderr or "No stderr captured",
395
- message_data.get(
396
- "return_stream"
397
- ), # Try to get return stream from original data
398
- message_data.get("user_id"), # Try to get user_id from original data
466
+ "Worker script not found",
467
+ traceback.format_exc(),
468
+ message_data.get("return_stream"),
469
+ message_data.get("user_id"),
399
470
  )
400
-
401
- # CRITICAL: Acknowledge the message here since the subprocess failed
402
471
  try:
403
472
  assert isinstance(REDIS_STREAM, str)
404
473
  assert isinstance(REDIS_CONSUMER_GROUP, str)
405
474
  r.xack(REDIS_STREAM, REDIS_CONSUMER_GROUP, message_id)
406
- print(f"Acknowledged failed subprocess message {message_id}")
475
+ print(
476
+ f"Acknowledged message {message_id} after worker script not found failure"
477
+ )
407
478
  except Exception as e_ack:
408
479
  print(
409
- f"CRITICAL: Failed to acknowledge failed subprocess message {message_id}: {e_ack}"
480
+ f"CRITICAL: Failed to acknowledge message {message_id} after worker script not found failure: {e_ack}"
410
481
  )
411
482
 
412
483
  except Exception as e:
@@ -417,7 +488,7 @@ def process_message(message_id: str, message_data: Dict[str, str]) -> None:
417
488
  # Also send an error and acknowledge
418
489
  _send_error_response(
419
490
  message_id,
420
- f"Failed to launch subprocess: {e}",
491
+ f"Failed to launch/manage subprocess: {e}",
421
492
  traceback.format_exc(),
422
493
  message_data.get("return_stream"),
423
494
  message_data.get("user_id"),
@@ -427,12 +498,44 @@ def process_message(message_id: str, message_data: Dict[str, str]) -> None:
427
498
  assert isinstance(REDIS_CONSUMER_GROUP, str)
428
499
  r.xack(REDIS_STREAM, REDIS_CONSUMER_GROUP, message_id)
429
500
  print(
430
- f"Acknowledged message {message_id} after subprocess launch failure"
501
+ f"Acknowledged message {message_id} after subprocess launch/manage failure"
431
502
  )
432
503
  except Exception as e_ack:
433
504
  print(
434
- f"CRITICAL: Failed to acknowledge message {message_id} after subprocess launch failure: {e_ack}"
505
+ f"CRITICAL: Failed to acknowledge message {message_id} after subprocess launch/manage failure: {e_ack}"
435
506
  )
507
+ # Ensure process is terminated if it's still running after an error
508
+ if process and process.poll() is None:
509
+ print(
510
+ f"Terminating potentially lingering subprocess for {message_id}..."
511
+ )
512
+ process.terminate()
513
+ process.wait(timeout=5) # Give it a moment to terminate
514
+ if process.poll() is None:
515
+ print(
516
+ f"Subprocess for {message_id} did not terminate gracefully, killing."
517
+ )
518
+ process.kill()
519
+ finally:
520
+ # Ensure streams are closed even if threads failed or process is None
521
+ if process:
522
+ if process.stdout:
523
+ try:
524
+ process.stdout.close()
525
+ except Exception:
526
+ pass # Ignore errors during cleanup close
527
+ if process.stderr:
528
+ try:
529
+ process.stderr.close()
530
+ except Exception:
531
+ pass # Ignore errors during cleanup close
532
+ # Stdin should already be closed, but doesn't hurt to be safe
533
+ if process.stdin and not process.stdin.closed:
534
+ try:
535
+ process.stdin.close()
536
+ except Exception:
537
+ pass
538
+
436
539
  return # Exit process_message after handling subprocess logic
437
540
 
438
541
  # --- Inline Execution Path (Original Logic) ---
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: nebu
3
- Version: 0.1.76
3
+ Version: 0.1.77
4
4
  Summary: A globally distributed container runtime
5
5
  Requires-Python: >=3.10.14
6
6
  Description-Content-Type: text/markdown
@@ -13,7 +13,7 @@ nebu/containers/models.py,sha256=0j6NGy4yto-enRDh_4JH_ZTbHrLdSpuMOqNQPnIrwC4,681
13
13
  nebu/containers/server.py,sha256=yFa2Y9PzBn59E1HftKiv0iapPonli2rbGAiU6r-wwe0,2513
14
14
  nebu/namespaces/models.py,sha256=EqUOpzhVBhvJw2P92ONDUbIgC31M9jMmcaG5vyOrsWg,497
15
15
  nebu/namespaces/namespace.py,sha256=Q_EDH7BgQrTkaDh_l4tbo22qpq-uARfIk8ZPBLjITGY,4967
16
- nebu/processors/consumer.py,sha256=Fxgl0cuqKpX3UMsig_aw5KBbg1blE4xUY4yNnIvIHuw,37806
16
+ nebu/processors/consumer.py,sha256=N1olarPEHHqisxuM6gkar_LHG9CUbGhGvrIz-tmsPT4,42267
17
17
  nebu/processors/consumer_process_worker.py,sha256=l5_BSMfqy-n2yK_UC3sm_pimzelaASeMdPxRE97HFwc,30959
18
18
  nebu/processors/decorate.py,sha256=mu1o05BjNcbJ4M1so4Xvt7UbslX--B4dsYLgs5h8bEg,54610
19
19
  nebu/processors/default.py,sha256=W4slJenG59rvyTlJ7gRp58eFfXcNOTT2Hfi6zzJAobI,365
@@ -22,8 +22,8 @@ nebu/processors/processor.py,sha256=OgEK8Fz0ehSe_VFiNsxweVKZIckhgVvQQ11NNffYZqA,
22
22
  nebu/processors/remote.py,sha256=TeAIPGEMqnDIb7H1iett26IEZrBlcbPB_-DSm6jcH1E,1285
23
23
  nebu/redis/models.py,sha256=coPovAcVXnOU1Xh_fpJL4PO3QctgK9nBe5QYoqEcnxg,1230
24
24
  nebu/services/service.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
25
- nebu-0.1.76.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
26
- nebu-0.1.76.dist-info/METADATA,sha256=j130KtOVQeqJRkv6xgC6CJ0BUSmqUmb8jFyhKAC11jE,1731
27
- nebu-0.1.76.dist-info/WHEEL,sha256=ck4Vq1_RXyvS4Jt6SI0Vz6fyVs4GWg7AINwpsaGEgPE,91
28
- nebu-0.1.76.dist-info/top_level.txt,sha256=uLIbEKJeGSHWOAJN5S0i5XBGwybALlF9bYoB1UhdEgQ,5
29
- nebu-0.1.76.dist-info/RECORD,,
25
+ nebu-0.1.77.dist-info/licenses/LICENSE,sha256=xx0jnfkXJvxRnG63LTGOxlggYnIysveWIZ6H3PNdCrQ,11357
26
+ nebu-0.1.77.dist-info/METADATA,sha256=6zdRYrPRNGO6NnN30EV26EVc5drWDvJKJbAhirgj2sQ,1731
27
+ nebu-0.1.77.dist-info/WHEEL,sha256=ck4Vq1_RXyvS4Jt6SI0Vz6fyVs4GWg7AINwpsaGEgPE,91
28
+ nebu-0.1.77.dist-info/top_level.txt,sha256=uLIbEKJeGSHWOAJN5S0i5XBGwybALlF9bYoB1UhdEgQ,5
29
+ nebu-0.1.77.dist-info/RECORD,,
File without changes