streamlit-octostar-utils 0.5.0.dev12__tar.gz → 0.5.0.dev14__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.
- {streamlit_octostar_utils-0.5.0.dev12 → streamlit_octostar_utils-0.5.0.dev14}/PKG-INFO +1 -1
- {streamlit_octostar_utils-0.5.0.dev12 → streamlit_octostar_utils-0.5.0.dev14}/pyproject.toml +1 -1
- {streamlit_octostar_utils-0.5.0.dev12 → streamlit_octostar_utils-0.5.0.dev14}/streamlit_octostar_utils/api_crafter/celery.py +62 -43
- {streamlit_octostar_utils-0.5.0.dev12 → streamlit_octostar_utils-0.5.0.dev14}/LICENSE +0 -0
- {streamlit_octostar_utils-0.5.0.dev12 → streamlit_octostar_utils-0.5.0.dev14}/README.md +0 -0
- {streamlit_octostar_utils-0.5.0.dev12 → streamlit_octostar_utils-0.5.0.dev14}/streamlit_octostar_utils/__init__.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev12 → streamlit_octostar_utils-0.5.0.dev14}/streamlit_octostar_utils/api_crafter/__init__.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev12 → streamlit_octostar_utils-0.5.0.dev14}/streamlit_octostar_utils/api_crafter/contents.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev12 → streamlit_octostar_utils-0.5.0.dev14}/streamlit_octostar_utils/api_crafter/fastapi.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev12 → streamlit_octostar_utils-0.5.0.dev14}/streamlit_octostar_utils/api_crafter/nifi.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev12 → streamlit_octostar_utils-0.5.0.dev14}/streamlit_octostar_utils/api_crafter/parallelism.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev12 → streamlit_octostar_utils-0.5.0.dev14}/streamlit_octostar_utils/api_crafter/parser/__init__.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev12 → streamlit_octostar_utils-0.5.0.dev14}/streamlit_octostar_utils/api_crafter/parser/combine_fields.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev12 → streamlit_octostar_utils-0.5.0.dev14}/streamlit_octostar_utils/api_crafter/parser/entities_parser.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev12 → streamlit_octostar_utils-0.5.0.dev14}/streamlit_octostar_utils/api_crafter/parser/generics.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev12 → streamlit_octostar_utils-0.5.0.dev14}/streamlit_octostar_utils/api_crafter/parser/info.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev12 → streamlit_octostar_utils-0.5.0.dev14}/streamlit_octostar_utils/api_crafter/parser/linkchart_functions.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev12 → streamlit_octostar_utils-0.5.0.dev14}/streamlit_octostar_utils/api_crafter/parser/matches.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev12 → streamlit_octostar_utils-0.5.0.dev14}/streamlit_octostar_utils/api_crafter/parser/parameters.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev12 → streamlit_octostar_utils-0.5.0.dev14}/streamlit_octostar_utils/api_crafter/parser/rules.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev12 → streamlit_octostar_utils-0.5.0.dev14}/streamlit_octostar_utils/api_crafter/parser/signals.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev12 → streamlit_octostar_utils-0.5.0.dev14}/streamlit_octostar_utils/core/__init__.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev12 → streamlit_octostar_utils-0.5.0.dev14}/streamlit_octostar_utils/core/dict.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev12 → streamlit_octostar_utils-0.5.0.dev14}/streamlit_octostar_utils/core/filetypes.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev12 → streamlit_octostar_utils-0.5.0.dev14}/streamlit_octostar_utils/core/threading/__init__.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev12 → streamlit_octostar_utils-0.5.0.dev14}/streamlit_octostar_utils/core/threading/key_queue.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev12 → streamlit_octostar_utils-0.5.0.dev14}/streamlit_octostar_utils/core/timestamp.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev12 → streamlit_octostar_utils-0.5.0.dev14}/streamlit_octostar_utils/nlp/__init__.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev12 → streamlit_octostar_utils-0.5.0.dev14}/streamlit_octostar_utils/nlp/custom_recognizers.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev12 → streamlit_octostar_utils-0.5.0.dev14}/streamlit_octostar_utils/nlp/language.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev12 → streamlit_octostar_utils-0.5.0.dev14}/streamlit_octostar_utils/nlp/ner.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev12 → streamlit_octostar_utils-0.5.0.dev14}/streamlit_octostar_utils/octostar/__init__.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev12 → streamlit_octostar_utils-0.5.0.dev14}/streamlit_octostar_utils/octostar/client.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev12 → streamlit_octostar_utils-0.5.0.dev14}/streamlit_octostar_utils/octostar/context.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev12 → streamlit_octostar_utils-0.5.0.dev14}/streamlit_octostar_utils/octostar/permissions.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev12 → streamlit_octostar_utils-0.5.0.dev14}/streamlit_octostar_utils/ontology/__init__.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev12 → streamlit_octostar_utils-0.5.0.dev14}/streamlit_octostar_utils/ontology/inheritance.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev12 → streamlit_octostar_utils-0.5.0.dev14}/streamlit_octostar_utils/ontology/relationships.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev12 → streamlit_octostar_utils-0.5.0.dev14}/streamlit_octostar_utils/ontology/validation.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev12 → streamlit_octostar_utils-0.5.0.dev14}/streamlit_octostar_utils/style/__init__.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev12 → streamlit_octostar_utils-0.5.0.dev14}/streamlit_octostar_utils/style/common.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev12 → streamlit_octostar_utils-0.5.0.dev14}/streamlit_octostar_utils/threading/__init__.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev12 → streamlit_octostar_utils-0.5.0.dev14}/streamlit_octostar_utils/threading/async_task_manager.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev12 → streamlit_octostar_utils-0.5.0.dev14}/streamlit_octostar_utils/threading/session_callback_manager.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev12 → streamlit_octostar_utils-0.5.0.dev14}/streamlit_octostar_utils/threading/session_state_hot_swapper.py +0 -0
|
@@ -13,7 +13,6 @@ import atexit
|
|
|
13
13
|
import redis
|
|
14
14
|
import uuid
|
|
15
15
|
import json
|
|
16
|
-
import hashlib
|
|
17
16
|
import shutil
|
|
18
17
|
import threading
|
|
19
18
|
from pottery import Redlock
|
|
@@ -56,14 +55,12 @@ class CeleryQueueConfig:
|
|
|
56
55
|
max_tasks_in_queue=None,
|
|
57
56
|
max_tasks_per_child=None,
|
|
58
57
|
max_memory_per_child=None,
|
|
59
|
-
stall_timeout=1200,
|
|
60
58
|
**options,
|
|
61
59
|
):
|
|
62
60
|
self.n_workers = n_workers
|
|
63
61
|
self.max_tasks_in_queue = max_tasks_in_queue
|
|
64
62
|
self.max_tasks_per_child = max_tasks_per_child
|
|
65
63
|
self.max_memory_per_child = max_memory_per_child # KiB
|
|
66
|
-
self.stall_timeout = stall_timeout # seconds; None or 0 to disable
|
|
67
64
|
self.options = options
|
|
68
65
|
|
|
69
66
|
|
|
@@ -257,9 +254,9 @@ class CeleryExecutor(object):
|
|
|
257
254
|
self.worker_info = {}
|
|
258
255
|
|
|
259
256
|
# Queue stall detection
|
|
260
|
-
self._queue_fingerprints = {}
|
|
261
|
-
self._queue_fingerprint_changed_at = {}
|
|
262
257
|
self._queue_stalled = {}
|
|
258
|
+
self._last_stall_check = 0
|
|
259
|
+
self._stall_check_interval = 60
|
|
263
260
|
|
|
264
261
|
atexit.register(self.close)
|
|
265
262
|
self.set_cleanup_task()
|
|
@@ -291,6 +288,11 @@ class CeleryExecutor(object):
|
|
|
291
288
|
def set_started_state(self, task_id, task, *args, **kwargs):
|
|
292
289
|
result = AsyncResult(task_id, app=self.app)
|
|
293
290
|
result.backend.store_result(task_id, result=None, state=CeleryExecutor.STARTED)
|
|
291
|
+
queue = task.request.delivery_info.get(
|
|
292
|
+
"routing_key", self.app.conf.task_default_routing_key
|
|
293
|
+
) if task else None
|
|
294
|
+
if queue:
|
|
295
|
+
self.redis_client.set(f"queue:last_started:{queue}", str(time.time()))
|
|
294
296
|
request_timelimit = getattr(getattr(task, "request", None), "timelimit", None) or (None, None)
|
|
295
297
|
time_limit = request_timelimit[0] or getattr(task, "time_limit", None) or 0
|
|
296
298
|
extended_ttl = int(time_limit) + int(self.app.conf.result_expires)
|
|
@@ -307,20 +309,17 @@ class CeleryExecutor(object):
|
|
|
307
309
|
if self.preload_functions:
|
|
308
310
|
celery_signals.worker_process_init.connect(self.preload_on_worker_init)
|
|
309
311
|
|
|
310
|
-
def
|
|
312
|
+
def cleanup_task_keys(self, sender=None, task_id=None, **kwargs):
|
|
311
313
|
try:
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
) if task else None
|
|
315
|
-
if queue:
|
|
316
|
-
self.redis_client.set(f"queue:last_completed:{queue}", str(time.time()))
|
|
314
|
+
if task_id:
|
|
315
|
+
self.redis_client.delete(f"task:queue:{task_id}")
|
|
317
316
|
except Exception:
|
|
318
317
|
pass
|
|
319
318
|
|
|
320
319
|
def register_state_signals(self):
|
|
321
320
|
celery_signals.before_task_publish.connect(self.set_awaiting_state)
|
|
322
321
|
celery_signals.task_prerun.connect(self.set_started_state)
|
|
323
|
-
celery_signals.task_postrun.connect(self.
|
|
322
|
+
celery_signals.task_postrun.connect(self.cleanup_task_keys)
|
|
324
323
|
|
|
325
324
|
def cleanup_task_results(in_dir, out_dir, redis_host, redis_port, task_expires, result_expires):
|
|
326
325
|
logger.info("Starting cleanup of expired task results...")
|
|
@@ -419,6 +418,9 @@ class CeleryExecutor(object):
|
|
|
419
418
|
if attempts_done == CeleryExecutor.MAX_STARTUP_CHECKS:
|
|
420
419
|
raise TimeoutError("Redis not ready after a long wait!")
|
|
421
420
|
self.redis_client.flushall()
|
|
421
|
+
boot_time = str(time.time())
|
|
422
|
+
for queue_name in self.queue_config:
|
|
423
|
+
self.redis_client.set(f"queue:last_started:{queue_name}", boot_time)
|
|
422
424
|
for queue, queue_config in self.queue_config.items():
|
|
423
425
|
for slot in range(queue_config.n_workers):
|
|
424
426
|
worker_name = f"celery@{self.name}:{queue}:{slot}"
|
|
@@ -493,7 +495,10 @@ class CeleryExecutor(object):
|
|
|
493
495
|
while not self.stop_event.is_set():
|
|
494
496
|
try:
|
|
495
497
|
self._restart_dead_processes()
|
|
496
|
-
|
|
498
|
+
now = time.time()
|
|
499
|
+
if now - self._last_stall_check >= self._stall_check_interval:
|
|
500
|
+
self._last_stall_check = now
|
|
501
|
+
self._check_queue_stalls()
|
|
497
502
|
time.sleep(5)
|
|
498
503
|
except Exception as e:
|
|
499
504
|
logger.error(f"Error in worker health check: {e}")
|
|
@@ -528,44 +533,56 @@ class CeleryExecutor(object):
|
|
|
528
533
|
logger.info(f"Restarted beat process (PID: {self.beat_process.pid})")
|
|
529
534
|
|
|
530
535
|
def _check_queue_stalls(self):
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
536
|
+
try:
|
|
537
|
+
inspector = self.app.control.inspect(timeout=5)
|
|
538
|
+
active_data = inspector.active() or {}
|
|
539
|
+
except Exception as e:
|
|
540
|
+
logger.error(f"Failed to inspect active workers: {e}")
|
|
541
|
+
return
|
|
542
|
+
|
|
543
|
+
now = time.time()
|
|
544
|
+
grace_period = 3 * self.app.conf.worker_proc_alive_timeout
|
|
545
|
+
|
|
546
|
+
active_queues = set()
|
|
547
|
+
for _worker_name, tasks in active_data.items():
|
|
548
|
+
for task_info in (tasks or []):
|
|
549
|
+
queue = (task_info.get("delivery_info") or {}).get(
|
|
550
|
+
"routing_key", self.app.conf.task_default_routing_key
|
|
551
|
+
)
|
|
552
|
+
active_queues.add(queue)
|
|
553
|
+
|
|
554
|
+
for queue_name in self.queue_config:
|
|
534
555
|
try:
|
|
535
|
-
|
|
536
|
-
|
|
556
|
+
pending_count = self.redis_client.llen(queue_name)
|
|
557
|
+
was_stalled = self._queue_stalled.get(queue_name, False)
|
|
558
|
+
|
|
559
|
+
if pending_count == 0:
|
|
560
|
+
self.redis_client.delete(f"queue:first_enqueued:{queue_name}")
|
|
561
|
+
if was_stalled:
|
|
562
|
+
logger.info(f"Queue '{queue_name}' has recovered from stall.")
|
|
537
563
|
self._queue_stalled[queue_name] = False
|
|
538
|
-
self._queue_fingerprints.pop(queue_name, None)
|
|
539
|
-
self._queue_fingerprint_changed_at.pop(queue_name, None)
|
|
540
564
|
continue
|
|
541
565
|
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
if fingerprint != prev_fingerprint:
|
|
547
|
-
self._queue_fingerprints[queue_name] = fingerprint
|
|
548
|
-
self._queue_fingerprint_changed_at[queue_name] = now_time
|
|
566
|
+
if queue_name in active_queues:
|
|
567
|
+
if was_stalled:
|
|
568
|
+
logger.info(f"Queue '{queue_name}' has recovered from stall.")
|
|
549
569
|
self._queue_stalled[queue_name] = False
|
|
550
570
|
continue
|
|
551
571
|
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
572
|
+
last_started_raw = self.redis_client.get(f"queue:last_started:{queue_name}")
|
|
573
|
+
first_enqueued_raw = self.redis_client.get(f"queue:first_enqueued:{queue_name}")
|
|
574
|
+
last_started = float(last_started_raw) if last_started_raw else 0
|
|
575
|
+
first_enqueued = float(first_enqueued_raw) if first_enqueued_raw else 0
|
|
576
|
+
last_activity = max(last_started, first_enqueued)
|
|
577
|
+
time_since_activity = (now - last_activity) if last_activity else float("inf")
|
|
557
578
|
|
|
558
|
-
|
|
559
|
-
is_stalled = (
|
|
560
|
-
fingerprint_age >= queue_config.stall_timeout
|
|
561
|
-
and time_since_completion >= queue_config.stall_timeout
|
|
562
|
-
)
|
|
579
|
+
is_stalled = time_since_activity >= grace_period
|
|
563
580
|
self._queue_stalled[queue_name] = is_stalled
|
|
564
581
|
|
|
565
582
|
if is_stalled and not was_stalled:
|
|
566
583
|
logger.error(
|
|
567
|
-
f"Queue '{queue_name}' is STALLED: {
|
|
568
|
-
f"
|
|
584
|
+
f"Queue '{queue_name}' is STALLED: {pending_count} task(s) pending, "
|
|
585
|
+
f"no active workers, no task started in {time_since_activity:.0f}s. "
|
|
569
586
|
f"New requests will receive 503."
|
|
570
587
|
)
|
|
571
588
|
elif not is_stalled and was_stalled:
|
|
@@ -719,10 +736,10 @@ class CeleryExecutor(object):
|
|
|
719
736
|
task_fn.apply_async(task_id=task_id, **options)
|
|
720
737
|
|
|
721
738
|
def _store_task_queue_mapping(task_id, queue_name):
|
|
722
|
-
self.redis_client.
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
)
|
|
739
|
+
pipe = self.redis_client.pipeline()
|
|
740
|
+
pipe.set(f"task:queue:{task_id}", queue_name, ex=self.app.conf.result_expires)
|
|
741
|
+
pipe.set(f"queue:first_enqueued:{queue_name}", str(time.time()), nx=True)
|
|
742
|
+
pipe.execute()
|
|
726
743
|
|
|
727
744
|
task_id = str(uuid.uuid4())
|
|
728
745
|
queue_name = self.app.conf.task_default_routing_key
|
|
@@ -830,6 +847,7 @@ class CeleryExecutor(object):
|
|
|
830
847
|
|
|
831
848
|
def _remove_task_data(celery_app, in_folder, out_folder, task_id):
|
|
832
849
|
celery_app.AsyncResult(task_id).forget()
|
|
850
|
+
self.redis_client.delete(f"task:queue:{task_id}")
|
|
833
851
|
if os.path.isfile(os.path.join(in_folder, task_id)):
|
|
834
852
|
with RedisFileLock(self.redis_client, os.path.join(self.in_folder, task_id)):
|
|
835
853
|
os.remove(os.path.join(in_folder, task_id))
|
|
@@ -889,6 +907,7 @@ class CeleryExecutor(object):
|
|
|
889
907
|
|
|
890
908
|
def _remove_task_data(celery_app, in_folder, out_folder, task_id):
|
|
891
909
|
celery_app.AsyncResult(task_id).forget()
|
|
910
|
+
self.redis_client.delete(f"task:queue:{task_id}")
|
|
892
911
|
if os.path.isfile(os.path.join(in_folder, task_id)):
|
|
893
912
|
with RedisFileLock(self.redis_client, os.path.join(self.in_folder, task_id)):
|
|
894
913
|
os.remove(os.path.join(in_folder, task_id))
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|