ciyidogan commited on
Commit
090cb24
·
verified ·
1 Parent(s): 041bedd

Delete event_bus.py

Browse files
Files changed (1) hide show
  1. event_bus.py +0 -430
event_bus.py DELETED
@@ -1,430 +0,0 @@
1
- """
2
- Event Bus Implementation for Flare
3
- ==================================
4
- Provides async event publishing and subscription mechanism
5
- """
6
- import asyncio
7
- from typing import Dict, List, Callable, Any, Optional
8
- from enum import Enum
9
- from dataclasses import dataclass, field
10
- from datetime import datetime
11
- import traceback
12
- from collections import defaultdict
13
- import sys
14
-
15
- from utils.logger import log_info, log_error, log_debug, log_warning
16
-
17
-
18
- class EventType(Enum):
19
- """All event types in the system"""
20
- # Lifecycle events
21
- SESSION_STARTED = "session_started"
22
- SESSION_ENDED = "session_ended"
23
- CONVERSATION_STARTED = "conversation_started"
24
- CONVERSATION_ENDED = "conversation_ended"
25
-
26
- # STT events
27
- STT_STARTED = "stt_started"
28
- STT_STOPPED = "stt_stopped"
29
- STT_RESULT = "stt_result"
30
- STT_ERROR = "stt_error"
31
- STT_READY = "stt_ready"
32
-
33
- # TTS events
34
- TTS_STARTED = "tts_started"
35
- TTS_CHUNK_READY = "tts_chunk_ready"
36
- TTS_COMPLETED = "tts_completed"
37
- TTS_ERROR = "tts_error"
38
- TTS_STOPPED = "tts_stopped"
39
-
40
- # Audio events
41
- AUDIO_PLAYBACK_STARTED = "audio_playback_started"
42
- AUDIO_PLAYBACK_COMPLETED = "audio_playback_completed"
43
- AUDIO_BUFFER_LOW = "audio_buffer_low"
44
- AUDIO_CHUNK_RECEIVED = "audio_chunk_received"
45
-
46
- # LLM events
47
- LLM_PROCESSING_STARTED = "llm_processing_started"
48
- LLM_RESPONSE_READY = "llm_response_ready"
49
- LLM_ERROR = "llm_error"
50
-
51
- # Error events
52
- CRITICAL_ERROR = "critical_error"
53
- RECOVERABLE_ERROR = "recoverable_error"
54
-
55
- # State events
56
- STATE_TRANSITION = "state_transition"
57
- STATE_ROLLBACK = "state_rollback"
58
-
59
- # WebSocket events
60
- WEBSOCKET_CONNECTED = "websocket_connected"
61
- WEBSOCKET_DISCONNECTED = "websocket_disconnected"
62
- WEBSOCKET_MESSAGE = "websocket_message"
63
- WEBSOCKET_ERROR = "websocket_error"
64
-
65
-
66
- @dataclass
67
- class Event:
68
- """Event data structure"""
69
- type: EventType
70
- data: Dict[str, Any]
71
- session_id: Optional[str] = None
72
- timestamp: datetime = field(default_factory=datetime.utcnow)
73
- priority: int = 0
74
-
75
- def __lt__(self, other):
76
- """Compare events by priority for PriorityQueue"""
77
- if not isinstance(other, Event):
78
- return NotImplemented
79
- # Önce priority'ye göre karşılaştır
80
- if self.priority != other.priority:
81
- return self.priority < other.priority
82
- # Priority eşitse timestamp'e göre karşılaştır
83
- return self.timestamp < other.timestamp
84
-
85
- def __eq__(self, other):
86
- """Check event equality"""
87
- if not isinstance(other, Event):
88
- return NotImplemented
89
- return (self.priority == other.priority and
90
- self.timestamp == other.timestamp and
91
- self.type == other.type)
92
-
93
- def __le__(self, other):
94
- """Less than or equal comparison"""
95
- return self == other or self < other
96
-
97
- def __gt__(self, other):
98
- """Greater than comparison"""
99
- return not self <= other
100
-
101
- def __ge__(self, other):
102
- """Greater than or equal comparison"""
103
- return not self < other
104
-
105
- def __post_init__(self):
106
- if self.timestamp is None:
107
- self.timestamp = datetime.utcnow()
108
-
109
- def to_dict(self) -> Dict[str, Any]:
110
- """Convert to dictionary for serialization"""
111
- return {
112
- "type": self.type.value,
113
- "session_id": self.session_id,
114
- "data": self.data,
115
- "timestamp": self.timestamp.isoformat(),
116
- "priority": self.priority
117
- }
118
-
119
-
120
- class EventBus:
121
- """Central event bus for component communication with session isolation"""
122
-
123
- def __init__(self):
124
- self._subscribers: Dict[EventType, List[Callable]] = defaultdict(list)
125
- self._session_handlers: Dict[str, Dict[EventType, List[Callable]]] = defaultdict(lambda: defaultdict(list))
126
-
127
- # Session-specific queues for parallel processing
128
- self._session_queues: Dict[str, asyncio.PriorityQueue] = {}
129
- self._session_processors: Dict[str, asyncio.Task] = {}
130
-
131
- # Global queue for non-session events
132
- self._global_queue: asyncio.PriorityQueue = asyncio.PriorityQueue()
133
- self._global_processor: Optional[asyncio.Task] = None
134
-
135
- self._running = False
136
- self._event_history: List[Event] = []
137
- self._max_history_size = 1000
138
-
139
- async def start(self):
140
- """Start the event processor"""
141
- if self._running:
142
- log_warning("EventBus already running")
143
- return
144
-
145
- self._running = True
146
-
147
- # Start global processor
148
- self._global_processor = asyncio.create_task(self._process_global_events())
149
-
150
- log_info("✅ EventBus started")
151
-
152
- async def stop(self):
153
- """Stop the event processor"""
154
- self._running = False
155
-
156
- # Stop all session processors
157
- for session_id, task in list(self._session_processors.items()):
158
- task.cancel()
159
- try:
160
- await asyncio.wait_for(task, timeout=2.0)
161
- except (asyncio.TimeoutError, asyncio.CancelledError):
162
- pass
163
-
164
- # Stop global processor
165
- if self._global_processor:
166
- await self._global_queue.put((999, None)) # Sentinel
167
- try:
168
- await asyncio.wait_for(self._global_processor, timeout=5.0)
169
- except asyncio.TimeoutError:
170
- log_warning("EventBus global processor timeout, cancelling")
171
- self._global_processor.cancel()
172
-
173
- log_info("✅ EventBus stopped")
174
-
175
- async def publish(self, event: Event):
176
- """Publish an event to the bus"""
177
- if not self._running:
178
- log_error("EventBus not running, cannot publish event", event_type=event.type.value)
179
- return
180
-
181
- # Add to history
182
- self._event_history.append(event)
183
- if len(self._event_history) > self._max_history_size:
184
- self._event_history.pop(0)
185
-
186
- # Route to appropriate queue
187
- if event.session_id:
188
- # Ensure session queue exists
189
- if event.session_id not in self._session_queues:
190
- await self._create_session_processor(event.session_id)
191
-
192
- # Add to session queue
193
- queue = self._session_queues[event.session_id]
194
- await queue.put((-event.priority, event))
195
- else:
196
- # Add to global queue
197
- await self._global_queue.put((-event.priority, event))
198
-
199
- async def _create_session_processor(self, session_id: str):
200
- """Create a processor for session-specific events"""
201
- if session_id in self._session_processors:
202
- return
203
-
204
- # Create queue
205
- self._session_queues[session_id] = asyncio.PriorityQueue()
206
-
207
- # Create processor task
208
- task = asyncio.create_task(self._process_session_events(session_id))
209
- self._session_processors[session_id] = task
210
-
211
- log_debug(f"📌 Created session processor", session_id=session_id)
212
-
213
- async def _process_session_events(self, session_id: str):
214
- """Process events for a specific session"""
215
- queue = self._session_queues[session_id]
216
- log_info(f"🔄 Session event processor started", session_id=session_id)
217
-
218
- while self._running:
219
- try:
220
- # Wait for event with timeout
221
- priority, event = await asyncio.wait_for(
222
- queue.get(),
223
- timeout=60.0 # Longer timeout for sessions
224
- )
225
-
226
- # Check for session cleanup
227
- if event is None:
228
- break
229
-
230
- # Process the event
231
- await self._dispatch_event(event)
232
-
233
- except asyncio.TimeoutError:
234
- # Check if session is still active
235
- if session_id not in self._session_handlers:
236
- log_info(f"Session inactive, stopping processor", session_id=session_id)
237
- break
238
- continue
239
- except Exception as e:
240
- log_error(
241
- f"❌ Error processing session event",
242
- session_id=session_id,
243
- error=str(e),
244
- traceback=traceback.format_exc()
245
- )
246
-
247
- # Cleanup
248
- self._session_queues.pop(session_id, None)
249
- self._session_processors.pop(session_id, None)
250
- log_info(f"🔄 Session event processor stopped", session_id=session_id)
251
-
252
- async def _process_global_events(self):
253
- """Process global events (no session_id)"""
254
- log_info("🔄 Global event processor started")
255
-
256
- while self._running:
257
- try:
258
- priority, event = await asyncio.wait_for(
259
- self._global_queue.get(),
260
- timeout=1.0
261
- )
262
-
263
- if event is None: # Sentinel
264
- break
265
-
266
- await self._dispatch_event(event)
267
-
268
- except asyncio.TimeoutError:
269
- continue
270
- except Exception as e:
271
- log_error(
272
- "❌ Error processing global event",
273
- error=str(e),
274
- traceback=traceback.format_exc()
275
- )
276
-
277
- log_info("🔄 Global event processor stopped")
278
-
279
- def subscribe(self, event_type: EventType, handler: Callable):
280
- """Subscribe to an event type globally"""
281
- self._subscribers[event_type].append(handler)
282
- log_debug(f"📌 Global subscription added", event_type=event_type.value)
283
-
284
- def subscribe_session(self, session_id: str, event_type: EventType, handler: Callable):
285
- """Subscribe to an event type for a specific session"""
286
- self._session_handlers[session_id][event_type].append(handler)
287
- log_debug(
288
- f"📌 Session subscription added",
289
- event_type=event_type.value,
290
- session_id=session_id
291
- )
292
-
293
- def unsubscribe(self, event_type: EventType, handler: Callable):
294
- """Unsubscribe from an event type"""
295
- if handler in self._subscribers[event_type]:
296
- self._subscribers[event_type].remove(handler)
297
- log_debug(f"📌 Global subscription removed", event_type=event_type.value)
298
-
299
- def unsubscribe_session(self, session_id: str, event_type: EventType = None):
300
- """Unsubscribe session handlers"""
301
- if event_type:
302
- # Remove specific event type for session
303
- if session_id in self._session_handlers and event_type in self._session_handlers[session_id]:
304
- del self._session_handlers[session_id][event_type]
305
- else:
306
- # Remove all handlers for session
307
- if session_id in self._session_handlers:
308
- del self._session_handlers[session_id]
309
- log_debug(f"📌 All session subscriptions removed", session_id=session_id)
310
-
311
-
312
- async def _dispatch_event(self, event: Event):
313
- """Dispatch event to all subscribers"""
314
- try:
315
- handlers = []
316
-
317
- # Get global handlers
318
- if event.type in self._subscribers:
319
- handlers.extend(self._subscribers[event.type])
320
-
321
- # Get session-specific handlers
322
- if event.session_id in self._session_handlers:
323
- if event.type in self._session_handlers[event.session_id]:
324
- handlers.extend(self._session_handlers[event.session_id][event.type])
325
-
326
- if not handlers:
327
- log_debug(
328
- f"📭 No handlers for event",
329
- event_type=event.type.value,
330
- session_id=event.session_id
331
- )
332
- return
333
-
334
- # Call all handlers concurrently
335
- tasks = []
336
- for handler in handlers:
337
- if asyncio.iscoroutinefunction(handler):
338
- task = asyncio.create_task(handler(event))
339
- else:
340
- # Wrap sync handler in async
341
- task = asyncio.create_task(asyncio.to_thread(handler, event))
342
- tasks.append(task)
343
-
344
- # Wait for all handlers to complete
345
- results = await asyncio.gather(*tasks, return_exceptions=True)
346
-
347
- # Log any exceptions
348
- for i, result in enumerate(results):
349
- if isinstance(result, Exception):
350
- log_error(
351
- f"❌ Handler error",
352
- handler=handlers[i].__name__,
353
- event_type=event.type.value,
354
- error=str(result),
355
- traceback=traceback.format_exception(type(result), result, result.__traceback__)
356
- )
357
-
358
- except Exception as e:
359
- log_error(
360
- f"❌ Error dispatching event",
361
- event_type=event.type.value,
362
- error=str(e),
363
- traceback=traceback.format_exc()
364
- )
365
-
366
- def get_event_history(self, session_id: Optional[str] = None, event_type: Optional[EventType] = None) -> List[Event]:
367
- """Get event history with optional filters"""
368
- history = self._event_history
369
-
370
- if session_id:
371
- history = [e for e in history if e.session_id == session_id]
372
-
373
- if event_type:
374
- history = [e for e in history if e.type == event_type]
375
-
376
- return history
377
-
378
- def clear_session_data(self, session_id: str):
379
- """Clear all session-related data and stop processor"""
380
- # Remove session handlers
381
- self.unsubscribe_session(session_id)
382
-
383
- # Stop session processor
384
- if session_id in self._session_processors:
385
- task = self._session_processors[session_id]
386
- task.cancel()
387
-
388
- # Clear queues
389
- self._session_queues.pop(session_id, None)
390
- self._session_processors.pop(session_id, None)
391
-
392
- # Remove session events from history
393
- self._event_history = [e for e in self._event_history if e.session_id != session_id]
394
-
395
- log_debug(f"🧹 Session data cleared", session_id=session_id)
396
-
397
-
398
- # Global event bus instance
399
- event_bus = EventBus()
400
-
401
-
402
- # Helper functions for common event publishing patterns
403
- async def publish_error(session_id: str, error_type: str, error_message: str, details: Dict[str, Any] = None):
404
- """Helper to publish error events"""
405
- event = Event(
406
- type=EventType.RECOVERABLE_ERROR if error_type != "critical" else EventType.CRITICAL_ERROR,
407
- session_id=session_id,
408
- data={
409
- "error_type": error_type,
410
- "message": error_message,
411
- "details": details or {}
412
- },
413
- priority=10 # High priority for errors
414
- )
415
- await event_bus.publish(event)
416
-
417
-
418
- async def publish_state_transition(session_id: str, from_state: str, to_state: str, reason: str = None):
419
- """Helper to publish state transition events"""
420
- event = Event(
421
- type=EventType.STATE_TRANSITION,
422
- session_id=session_id,
423
- data={
424
- "from_state": from_state,
425
- "to_state": to_state,
426
- "reason": reason
427
- },
428
- priority=5 # Medium priority for state changes
429
- )
430
- await event_bus.publish(event)