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.
- nebu/processors/consumer.py +138 -35
- {nebu-0.1.76.dist-info → nebu-0.1.77.dist-info}/METADATA +1 -1
- {nebu-0.1.76.dist-info → nebu-0.1.77.dist-info}/RECORD +6 -6
- {nebu-0.1.76.dist-info → nebu-0.1.77.dist-info}/WHEEL +0 -0
- {nebu-0.1.76.dist-info → nebu-0.1.77.dist-info}/licenses/LICENSE +0 -0
- {nebu-0.1.76.dist-info → nebu-0.1.77.dist-info}/top_level.txt +0 -0
nebu/processors/consumer.py
CHANGED
@@ -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
|
-
#
|
365
|
-
|
366
|
-
# Pass message data via stdin
|
367
|
-
result = subprocess.run(
|
378
|
+
# Start the worker process
|
379
|
+
process = subprocess.Popen(
|
368
380
|
worker_cmd,
|
369
|
-
|
381
|
+
stdin=subprocess.PIPE,
|
382
|
+
stdout=subprocess.PIPE,
|
383
|
+
stderr=subprocess.PIPE,
|
370
384
|
text=True,
|
371
|
-
|
372
|
-
|
373
|
-
env=os.environ.copy(),
|
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
|
-
|
381
|
-
|
382
|
-
|
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
|
-
|
390
|
-
|
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
|
-
|
394
|
-
|
395
|
-
message_data.get(
|
396
|
-
|
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(
|
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
|
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) ---
|
@@ -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=
|
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.
|
26
|
-
nebu-0.1.
|
27
|
-
nebu-0.1.
|
28
|
-
nebu-0.1.
|
29
|
-
nebu-0.1.
|
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
|
File without changes
|
File without changes
|