streamlit-octostar-utils 0.5.0.dev13__tar.gz → 0.5.0.dev15__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.dev13 → streamlit_octostar_utils-0.5.0.dev15}/PKG-INFO +1 -1
- {streamlit_octostar_utils-0.5.0.dev13 → streamlit_octostar_utils-0.5.0.dev15}/pyproject.toml +1 -1
- {streamlit_octostar_utils-0.5.0.dev13 → streamlit_octostar_utils-0.5.0.dev15}/streamlit_octostar_utils/api_crafter/celery.py +58 -43
- {streamlit_octostar_utils-0.5.0.dev13 → streamlit_octostar_utils-0.5.0.dev15}/streamlit_octostar_utils/api_crafter/nifi.py +1 -1
- {streamlit_octostar_utils-0.5.0.dev13 → streamlit_octostar_utils-0.5.0.dev15}/LICENSE +0 -0
- {streamlit_octostar_utils-0.5.0.dev13 → streamlit_octostar_utils-0.5.0.dev15}/README.md +0 -0
- {streamlit_octostar_utils-0.5.0.dev13 → streamlit_octostar_utils-0.5.0.dev15}/streamlit_octostar_utils/__init__.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev13 → streamlit_octostar_utils-0.5.0.dev15}/streamlit_octostar_utils/api_crafter/__init__.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev13 → streamlit_octostar_utils-0.5.0.dev15}/streamlit_octostar_utils/api_crafter/contents.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev13 → streamlit_octostar_utils-0.5.0.dev15}/streamlit_octostar_utils/api_crafter/fastapi.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev13 → streamlit_octostar_utils-0.5.0.dev15}/streamlit_octostar_utils/api_crafter/parallelism.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev13 → streamlit_octostar_utils-0.5.0.dev15}/streamlit_octostar_utils/api_crafter/parser/__init__.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev13 → streamlit_octostar_utils-0.5.0.dev15}/streamlit_octostar_utils/api_crafter/parser/combine_fields.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev13 → streamlit_octostar_utils-0.5.0.dev15}/streamlit_octostar_utils/api_crafter/parser/entities_parser.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev13 → streamlit_octostar_utils-0.5.0.dev15}/streamlit_octostar_utils/api_crafter/parser/generics.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev13 → streamlit_octostar_utils-0.5.0.dev15}/streamlit_octostar_utils/api_crafter/parser/info.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev13 → streamlit_octostar_utils-0.5.0.dev15}/streamlit_octostar_utils/api_crafter/parser/linkchart_functions.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev13 → streamlit_octostar_utils-0.5.0.dev15}/streamlit_octostar_utils/api_crafter/parser/matches.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev13 → streamlit_octostar_utils-0.5.0.dev15}/streamlit_octostar_utils/api_crafter/parser/parameters.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev13 → streamlit_octostar_utils-0.5.0.dev15}/streamlit_octostar_utils/api_crafter/parser/rules.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev13 → streamlit_octostar_utils-0.5.0.dev15}/streamlit_octostar_utils/api_crafter/parser/signals.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev13 → streamlit_octostar_utils-0.5.0.dev15}/streamlit_octostar_utils/core/__init__.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev13 → streamlit_octostar_utils-0.5.0.dev15}/streamlit_octostar_utils/core/dict.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev13 → streamlit_octostar_utils-0.5.0.dev15}/streamlit_octostar_utils/core/filetypes.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev13 → streamlit_octostar_utils-0.5.0.dev15}/streamlit_octostar_utils/core/threading/__init__.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev13 → streamlit_octostar_utils-0.5.0.dev15}/streamlit_octostar_utils/core/threading/key_queue.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev13 → streamlit_octostar_utils-0.5.0.dev15}/streamlit_octostar_utils/core/timestamp.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev13 → streamlit_octostar_utils-0.5.0.dev15}/streamlit_octostar_utils/nlp/__init__.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev13 → streamlit_octostar_utils-0.5.0.dev15}/streamlit_octostar_utils/nlp/custom_recognizers.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev13 → streamlit_octostar_utils-0.5.0.dev15}/streamlit_octostar_utils/nlp/language.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev13 → streamlit_octostar_utils-0.5.0.dev15}/streamlit_octostar_utils/nlp/ner.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev13 → streamlit_octostar_utils-0.5.0.dev15}/streamlit_octostar_utils/octostar/__init__.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev13 → streamlit_octostar_utils-0.5.0.dev15}/streamlit_octostar_utils/octostar/client.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev13 → streamlit_octostar_utils-0.5.0.dev15}/streamlit_octostar_utils/octostar/context.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev13 → streamlit_octostar_utils-0.5.0.dev15}/streamlit_octostar_utils/octostar/permissions.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev13 → streamlit_octostar_utils-0.5.0.dev15}/streamlit_octostar_utils/ontology/__init__.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev13 → streamlit_octostar_utils-0.5.0.dev15}/streamlit_octostar_utils/ontology/inheritance.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev13 → streamlit_octostar_utils-0.5.0.dev15}/streamlit_octostar_utils/ontology/relationships.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev13 → streamlit_octostar_utils-0.5.0.dev15}/streamlit_octostar_utils/ontology/validation.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev13 → streamlit_octostar_utils-0.5.0.dev15}/streamlit_octostar_utils/style/__init__.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev13 → streamlit_octostar_utils-0.5.0.dev15}/streamlit_octostar_utils/style/common.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev13 → streamlit_octostar_utils-0.5.0.dev15}/streamlit_octostar_utils/threading/__init__.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev13 → streamlit_octostar_utils-0.5.0.dev15}/streamlit_octostar_utils/threading/async_task_manager.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev13 → streamlit_octostar_utils-0.5.0.dev15}/streamlit_octostar_utils/threading/session_callback_manager.py +0 -0
- {streamlit_octostar_utils-0.5.0.dev13 → streamlit_octostar_utils-0.5.0.dev15}/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,13 +309,8 @@ 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
|
-
queue = task.request.delivery_info.get(
|
|
313
|
-
"routing_key", self.app.conf.task_default_routing_key
|
|
314
|
-
) if task else None
|
|
315
|
-
if queue:
|
|
316
|
-
self.redis_client.set(f"queue:last_completed:{queue}", str(time.time()))
|
|
317
314
|
if task_id:
|
|
318
315
|
self.redis_client.delete(f"task:queue:{task_id}")
|
|
319
316
|
except Exception:
|
|
@@ -322,7 +319,7 @@ class CeleryExecutor(object):
|
|
|
322
319
|
def register_state_signals(self):
|
|
323
320
|
celery_signals.before_task_publish.connect(self.set_awaiting_state)
|
|
324
321
|
celery_signals.task_prerun.connect(self.set_started_state)
|
|
325
|
-
celery_signals.task_postrun.connect(self.
|
|
322
|
+
celery_signals.task_postrun.connect(self.cleanup_task_keys)
|
|
326
323
|
|
|
327
324
|
def cleanup_task_results(in_dir, out_dir, redis_host, redis_port, task_expires, result_expires):
|
|
328
325
|
logger.info("Starting cleanup of expired task results...")
|
|
@@ -421,6 +418,9 @@ class CeleryExecutor(object):
|
|
|
421
418
|
if attempts_done == CeleryExecutor.MAX_STARTUP_CHECKS:
|
|
422
419
|
raise TimeoutError("Redis not ready after a long wait!")
|
|
423
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)
|
|
424
424
|
for queue, queue_config in self.queue_config.items():
|
|
425
425
|
for slot in range(queue_config.n_workers):
|
|
426
426
|
worker_name = f"celery@{self.name}:{queue}:{slot}"
|
|
@@ -495,7 +495,10 @@ class CeleryExecutor(object):
|
|
|
495
495
|
while not self.stop_event.is_set():
|
|
496
496
|
try:
|
|
497
497
|
self._restart_dead_processes()
|
|
498
|
-
|
|
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()
|
|
499
502
|
time.sleep(5)
|
|
500
503
|
except Exception as e:
|
|
501
504
|
logger.error(f"Error in worker health check: {e}")
|
|
@@ -530,44 +533,56 @@ class CeleryExecutor(object):
|
|
|
530
533
|
logger.info(f"Restarted beat process (PID: {self.beat_process.pid})")
|
|
531
534
|
|
|
532
535
|
def _check_queue_stalls(self):
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
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:
|
|
536
555
|
try:
|
|
537
|
-
|
|
538
|
-
|
|
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.")
|
|
539
563
|
self._queue_stalled[queue_name] = False
|
|
540
|
-
self._queue_fingerprints.pop(queue_name, None)
|
|
541
|
-
self._queue_fingerprint_changed_at.pop(queue_name, None)
|
|
542
564
|
continue
|
|
543
565
|
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
if fingerprint != prev_fingerprint:
|
|
549
|
-
self._queue_fingerprints[queue_name] = fingerprint
|
|
550
|
-
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.")
|
|
551
569
|
self._queue_stalled[queue_name] = False
|
|
552
570
|
continue
|
|
553
571
|
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
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")
|
|
559
578
|
|
|
560
|
-
|
|
561
|
-
is_stalled = (
|
|
562
|
-
fingerprint_age >= queue_config.stall_timeout
|
|
563
|
-
and time_since_completion >= queue_config.stall_timeout
|
|
564
|
-
)
|
|
579
|
+
is_stalled = time_since_activity >= grace_period
|
|
565
580
|
self._queue_stalled[queue_name] = is_stalled
|
|
566
581
|
|
|
567
582
|
if is_stalled and not was_stalled:
|
|
568
583
|
logger.error(
|
|
569
|
-
f"Queue '{queue_name}' is STALLED: {
|
|
570
|
-
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. "
|
|
571
586
|
f"New requests will receive 503."
|
|
572
587
|
)
|
|
573
588
|
elif not is_stalled and was_stalled:
|
|
@@ -721,10 +736,10 @@ class CeleryExecutor(object):
|
|
|
721
736
|
task_fn.apply_async(task_id=task_id, **options)
|
|
722
737
|
|
|
723
738
|
def _store_task_queue_mapping(task_id, queue_name):
|
|
724
|
-
self.redis_client.
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
)
|
|
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()
|
|
728
743
|
|
|
729
744
|
task_id = str(uuid.uuid4())
|
|
730
745
|
queue_name = self.app.conf.task_default_routing_key
|
|
@@ -671,7 +671,7 @@ class NifiContextManager(object):
|
|
|
671
671
|
try:
|
|
672
672
|
yield True
|
|
673
673
|
finally:
|
|
674
|
-
short_expiry =
|
|
674
|
+
short_expiry = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
675
675
|
statuses = [
|
|
676
676
|
{
|
|
677
677
|
"entity_id": rec["os_entity_uid"],
|
|
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
|