|
22 | 22 | from .integrations import Integration |
23 | 23 | from .logging import configure_logging, setup_logger |
24 | 24 | from .loop import LoopEvent, LoopManager |
25 | | -from .state.state import LoopState, StateManager, create_state_manager |
| 25 | +from .state.state import StateManager, create_state_manager |
26 | 26 | from .types import BaseConfig, LoopStatus |
27 | 27 | from .utils import get_func_import_path, import_func_from_path, infer_application_path |
28 | 28 |
|
@@ -499,120 +499,76 @@ def __init__( |
499 | 499 | wake_queue: Queue[str], |
500 | 500 | fastloop_instance: FastLoop, |
501 | 501 | ): |
502 | | - self.state_manager: StateManager = state_manager |
503 | | - self.loop_manager: LoopManager = loop_manager |
504 | | - self.restart_callback: Callable[[str], Coroutine[Any, Any, bool]] = ( |
505 | | - restart_callback |
506 | | - ) |
507 | | - self.wake_queue: Queue[str] = wake_queue |
508 | | - self.fastloop_instance: FastLoop = fastloop_instance |
509 | | - self._stop_event: asyncio.Event = asyncio.Event() |
| 502 | + self.state_manager = state_manager |
| 503 | + self.loop_manager = loop_manager |
| 504 | + self.restart_callback = restart_callback |
| 505 | + self.wake_queue = wake_queue |
| 506 | + self.fastloop_instance = fastloop_instance |
| 507 | + self._stop_event = asyncio.Event() |
510 | 508 |
|
511 | 509 | def stop(self) -> None: |
512 | 510 | self._stop_event.set() |
513 | 511 |
|
514 | | - async def run(self): |
515 | | - while not self._stop_event.is_set(): |
516 | | - try: |
517 | | - if not self.wake_queue.empty(): |
518 | | - loop_id = self.wake_queue.get() |
519 | | - if await self.state_manager.has_claim(loop_id): |
520 | | - continue |
521 | | - |
522 | | - logger.info( |
523 | | - "Loop woke up, restarting", |
524 | | - extra={"loop_id": loop_id}, |
525 | | - ) |
526 | | - if not await self.restart_callback(loop_id): |
527 | | - await self.state_manager.update_loop_status( |
528 | | - loop_id, LoopStatus.STOPPED |
529 | | - ) |
530 | | - await self.loop_manager.stop(loop_id) |
531 | | - |
532 | | - continue |
533 | | - |
534 | | - loop_ids: set[str] = await self.state_manager.get_all_loop_ids() |
535 | | - active_loop_ids: set[str] = await self.loop_manager.active_loop_ids() |
536 | | - loops_running: set[str] = active_loop_ids.intersection(loop_ids) |
537 | | - |
538 | | - for loop_id in loops_running: |
539 | | - loop = await self.state_manager.get_loop(loop_id) |
540 | | - |
541 | | - if ( |
542 | | - loop.status in LoopStatus.IDLE |
543 | | - or loop.status == LoopStatus.STOPPED |
544 | | - ): |
545 | | - if await self.state_manager.has_claim(loop_id): |
546 | | - continue |
547 | | - |
548 | | - logger.info( |
549 | | - "Loop is idle or stopped, stopping", |
550 | | - extra={"loop_id": loop_id}, |
551 | | - ) |
| 512 | + async def _process_wake(self, loop_id: str) -> None: |
| 513 | + if await self.state_manager.has_claim(loop_id): |
| 514 | + return |
| 515 | + logger.info("Loop woke up, restarting", extra={"loop_id": loop_id}) |
| 516 | + if not await self.restart_callback(loop_id): |
| 517 | + await self.state_manager.update_loop_status(loop_id, LoopStatus.STOPPED) |
552 | 518 |
|
553 | | - await self.loop_manager.stop(loop_id) |
554 | | - continue |
| 519 | + async def _check_orphaned_loops(self) -> None: |
| 520 | + running_loops = await self.state_manager.get_all_loops( |
| 521 | + status=LoopStatus.RUNNING |
| 522 | + ) |
| 523 | + for loop in running_loops: |
| 524 | + if await self.state_manager.has_claim(loop.loop_id): |
| 525 | + continue |
| 526 | + logger.info( |
| 527 | + "Loop has no claim, restarting", extra={"loop_id": loop.loop_id} |
| 528 | + ) |
| 529 | + if not await self.restart_callback(loop.loop_id): |
| 530 | + await self.state_manager.update_loop_status( |
| 531 | + loop.loop_id, LoopStatus.STOPPED |
| 532 | + ) |
555 | 533 |
|
556 | | - loops: list[LoopState] = await self.state_manager.get_all_loops( |
557 | | - status=LoopStatus.RUNNING |
| 534 | + async def _check_disconnect_stops(self) -> None: |
| 535 | + active_ids = await self.loop_manager.active_loop_ids() |
| 536 | + for loop_id in active_ids: |
| 537 | + try: |
| 538 | + loop = await self.state_manager.get_loop(loop_id) |
| 539 | + except LoopNotFoundError: |
| 540 | + continue |
| 541 | + if not loop.loop_name: |
| 542 | + continue |
| 543 | + metadata = self.fastloop_instance._loop_metadata.get(loop.loop_name) |
| 544 | + if not metadata or not metadata.get("stop_on_disconnect"): |
| 545 | + continue |
| 546 | + if not await self.fastloop_instance.has_active_clients(loop_id): |
| 547 | + logger.info( |
| 548 | + "Loop has no clients, stopping", |
| 549 | + extra={"loop_id": loop_id, "loop_name": loop.loop_name}, |
558 | 550 | ) |
559 | | - for loop in loops: |
560 | | - # Restart loop if it has no claim and is not idle (maybe the task crashed or was interrupted) |
561 | | - if not await self.state_manager.has_claim(loop.loop_id): |
562 | | - logger.info( |
563 | | - "Loop has no claim, restarting", |
564 | | - extra={ |
565 | | - "loop_id": loop.loop_id, |
566 | | - }, |
567 | | - ) |
| 551 | + await self.state_manager.update_loop_status(loop_id, LoopStatus.STOPPED) |
| 552 | + await self.loop_manager.stop(loop_id) |
| 553 | + |
| 554 | + async def run(self): |
| 555 | + while not self._stop_event.is_set(): |
| 556 | + try: |
| 557 | + while not self.wake_queue.empty(): |
| 558 | + await self._process_wake(self.wake_queue.get_nowait()) |
568 | 559 |
|
569 | | - if not await self.restart_callback(loop.loop_id): |
570 | | - await self.state_manager.update_loop_status( |
571 | | - loop.loop_id, LoopStatus.STOPPED |
572 | | - ) |
573 | | - await self.loop_manager.stop(loop.loop_id) |
574 | | - |
575 | | - continue |
576 | | - |
577 | | - # Check for loops with stop_on_disconnect=true that have no active clients |
578 | | - for loop in loops: |
579 | | - if ( |
580 | | - loop.loop_name |
581 | | - and loop.loop_name in self.fastloop_instance._loop_metadata |
582 | | - ): |
583 | | - metadata = self.fastloop_instance._loop_metadata[loop.loop_name] |
584 | | - if metadata.get("stop_on_disconnect", False): |
585 | | - has_clients = ( |
586 | | - await self.fastloop_instance.has_active_clients( |
587 | | - loop.loop_id |
588 | | - ) |
589 | | - ) |
590 | | - if not has_clients: |
591 | | - logger.info( |
592 | | - "Loop has stop_on_disconnect=true and no active clients, stopping", |
593 | | - extra={ |
594 | | - "loop_id": loop.loop_id, |
595 | | - "loop_name": loop.loop_name, |
596 | | - }, |
597 | | - ) |
598 | | - await self.state_manager.update_loop_status( |
599 | | - loop.loop_id, LoopStatus.STOPPED |
600 | | - ) |
601 | | - await self.loop_manager.stop(loop.loop_id) |
| 560 | + await self._check_orphaned_loops() |
| 561 | + await self._check_disconnect_stops() |
602 | 562 |
|
603 | 563 | try: |
604 | 564 | await asyncio.wait_for( |
605 | 565 | self._stop_event.wait(), timeout=WATCHDOG_INTERVAL_S |
606 | 566 | ) |
607 | 567 | break |
608 | 568 | except TimeoutError: |
609 | | - continue |
610 | | - |
| 569 | + pass |
611 | 570 | except asyncio.CancelledError: |
612 | 571 | break |
613 | | - except BaseException as e: |
614 | | - logger.error( |
615 | | - "Error in loop monitor", |
616 | | - extra={"error": str(e)}, |
617 | | - ) |
| 572 | + except Exception as e: |
| 573 | + logger.error("Error in loop monitor", extra={"error": str(e)}) |
618 | 574 | await asyncio.sleep(WATCHDOG_INTERVAL_S) |
0 commit comments