mirror of
https://github.com/langgenius/dify.git
synced 2026-03-05 07:37:07 +08:00
187 lines
5.3 KiB
Python
187 lines
5.3 KiB
Python
"""
|
|
Unified event manager for collecting and emitting events.
|
|
"""
|
|
|
|
import logging
|
|
import threading
|
|
import time
|
|
from collections.abc import Generator
|
|
from contextlib import contextmanager
|
|
from typing import final
|
|
|
|
from dify_graph.graph_events import GraphEngineEvent
|
|
|
|
from ..layers.base import GraphEngineLayer
|
|
|
|
_logger = logging.getLogger(__name__)
|
|
|
|
|
|
@final
|
|
class ReadWriteLock:
|
|
"""
|
|
A read-write lock implementation that allows multiple concurrent readers
|
|
but only one writer at a time.
|
|
"""
|
|
|
|
def __init__(self) -> None:
|
|
self._read_ready = threading.Condition(threading.RLock())
|
|
self._readers = 0
|
|
|
|
def acquire_read(self) -> None:
|
|
"""Acquire a read lock."""
|
|
_ = self._read_ready.acquire()
|
|
try:
|
|
self._readers += 1
|
|
finally:
|
|
self._read_ready.release()
|
|
|
|
def release_read(self) -> None:
|
|
"""Release a read lock."""
|
|
_ = self._read_ready.acquire()
|
|
try:
|
|
self._readers -= 1
|
|
if self._readers == 0:
|
|
self._read_ready.notify_all()
|
|
finally:
|
|
self._read_ready.release()
|
|
|
|
def acquire_write(self) -> None:
|
|
"""Acquire a write lock."""
|
|
_ = self._read_ready.acquire()
|
|
while self._readers > 0:
|
|
_ = self._read_ready.wait()
|
|
|
|
def release_write(self) -> None:
|
|
"""Release a write lock."""
|
|
self._read_ready.release()
|
|
|
|
@contextmanager
|
|
def read_lock(self):
|
|
"""Return a context manager for read locking."""
|
|
self.acquire_read()
|
|
try:
|
|
yield
|
|
finally:
|
|
self.release_read()
|
|
|
|
@contextmanager
|
|
def write_lock(self):
|
|
"""Return a context manager for write locking."""
|
|
self.acquire_write()
|
|
try:
|
|
yield
|
|
finally:
|
|
self.release_write()
|
|
|
|
|
|
@final
|
|
class EventManager:
|
|
"""
|
|
Unified event manager that collects, buffers, and emits events.
|
|
|
|
This class combines event collection with event emission, providing
|
|
thread-safe event management with support for notifying layers and
|
|
streaming events to external consumers.
|
|
"""
|
|
|
|
def __init__(self) -> None:
|
|
"""Initialize the event manager."""
|
|
self._events: list[GraphEngineEvent] = []
|
|
self._lock = ReadWriteLock()
|
|
self._layers: list[GraphEngineLayer] = []
|
|
self._execution_complete = threading.Event()
|
|
|
|
def set_layers(self, layers: list[GraphEngineLayer]) -> None:
|
|
"""
|
|
Set the layers to notify on event collection.
|
|
|
|
Args:
|
|
layers: List of layers to notify
|
|
"""
|
|
self._layers = layers
|
|
|
|
def notify_layers(self, event: GraphEngineEvent) -> None:
|
|
"""Notify registered layers about an event without buffering it."""
|
|
self._notify_layers(event)
|
|
|
|
def collect(self, event: GraphEngineEvent) -> None:
|
|
"""
|
|
Thread-safe method to collect an event.
|
|
|
|
Args:
|
|
event: The event to collect
|
|
"""
|
|
with self._lock.write_lock():
|
|
self._events.append(event)
|
|
|
|
# NOTE: `_notify_layers` is intentionally called outside the critical section
|
|
# to minimize lock contention and avoid blocking other readers or writers.
|
|
#
|
|
# The public `notify_layers` method also does not use a write lock,
|
|
# so protecting `_notify_layers` with a lock here is unnecessary.
|
|
self._notify_layers(event)
|
|
|
|
def _get_new_events(self, start_index: int) -> list[GraphEngineEvent]:
|
|
"""
|
|
Get new events starting from a specific index.
|
|
|
|
Args:
|
|
start_index: The index to start from
|
|
|
|
Returns:
|
|
List of new events
|
|
"""
|
|
with self._lock.read_lock():
|
|
return list(self._events[start_index:])
|
|
|
|
def _event_count(self) -> int:
|
|
"""
|
|
Get the current count of collected events.
|
|
|
|
Returns:
|
|
Number of collected events
|
|
"""
|
|
with self._lock.read_lock():
|
|
return len(self._events)
|
|
|
|
def mark_complete(self) -> None:
|
|
"""Mark execution as complete to stop the event emission generator."""
|
|
self._execution_complete.set()
|
|
|
|
def emit_events(self) -> Generator[GraphEngineEvent, None, None]:
|
|
"""
|
|
Generator that yields events as they're collected.
|
|
|
|
Yields:
|
|
GraphEngineEvent instances as they're processed
|
|
"""
|
|
yielded_count = 0
|
|
|
|
while not self._execution_complete.is_set() or yielded_count < self._event_count():
|
|
# Get new events since last yield
|
|
new_events = self._get_new_events(yielded_count)
|
|
|
|
# Yield any new events
|
|
for event in new_events:
|
|
yield event
|
|
yielded_count += 1
|
|
|
|
# Small sleep to avoid busy waiting
|
|
if not self._execution_complete.is_set() and not new_events:
|
|
time.sleep(0.001)
|
|
|
|
def _notify_layers(self, event: GraphEngineEvent) -> None:
|
|
"""
|
|
Notify all layers of an event.
|
|
|
|
Layer exceptions are caught and logged to prevent disrupting collection.
|
|
|
|
Args:
|
|
event: The event to send to layers
|
|
"""
|
|
for layer in self._layers:
|
|
try:
|
|
layer.on_event(event)
|
|
except Exception:
|
|
_logger.exception("Error in layer on_event, layer_type=%s", type(layer))
|