ciyidogan commited on
Commit
088fb5d
·
verified ·
1 Parent(s): fcd13b0

Delete resource_manager.py

Browse files
Files changed (1) hide show
  1. resource_manager.py +0 -401
resource_manager.py DELETED
@@ -1,401 +0,0 @@
1
- """
2
- Resource Manager for Flare
3
- ==========================
4
- Manages lifecycle of all session resources
5
- """
6
- import asyncio
7
- from typing import Dict, Any, Optional, Callable, Set
8
- from datetime import datetime, timedelta
9
- from dataclasses import dataclass, field
10
- import traceback
11
- from enum import Enum
12
-
13
- from event_bus import EventBus, Event, EventType
14
- from utils.logger import log_info, log_error, log_debug, log_warning
15
-
16
-
17
- class ResourceType(Enum):
18
- """Types of resources managed"""
19
- STT_INSTANCE = "stt_instance"
20
- TTS_INSTANCE = "tts_instance"
21
- LLM_CONTEXT = "llm_context"
22
- AUDIO_BUFFER = "audio_buffer"
23
- WEBSOCKET = "websocket"
24
- GENERIC = "generic"
25
-
26
-
27
- @dataclass
28
- class Resource:
29
- """Resource wrapper with metadata"""
30
- resource_id: str
31
- resource_type: ResourceType
32
- session_id: str
33
- instance: Any
34
- created_at: datetime = field(default_factory=datetime.utcnow)
35
- last_accessed: datetime = field(default_factory=datetime.utcnow)
36
- disposal_task: Optional[asyncio.Task] = None
37
- cleanup_callback: Optional[Callable] = None
38
-
39
- def touch(self):
40
- """Update last accessed time"""
41
- self.last_accessed = datetime.utcnow()
42
-
43
- async def cleanup(self):
44
- """Cleanup the resource"""
45
- try:
46
- if self.cleanup_callback:
47
- if asyncio.iscoroutinefunction(self.cleanup_callback):
48
- await self.cleanup_callback(self.instance)
49
- else:
50
- await asyncio.to_thread(self.cleanup_callback, self.instance)
51
-
52
- log_debug(
53
- f"🧹 Resource cleaned up",
54
- resource_id=self.resource_id,
55
- resource_type=self.resource_type.value
56
- )
57
- except Exception as e:
58
- log_error(
59
- f"❌ Error cleaning up resource",
60
- resource_id=self.resource_id,
61
- error=str(e)
62
- )
63
-
64
-
65
- class ResourcePool:
66
- """Pool for reusable resources"""
67
-
68
- def __init__(self,
69
- resource_type: ResourceType,
70
- factory: Callable,
71
- max_idle: int = 10,
72
- max_age_seconds: int = 300):
73
- self.resource_type = resource_type
74
- self.factory = factory
75
- self.max_idle = max_idle
76
- self.max_age_seconds = max_age_seconds
77
- self.idle_resources: List[Resource] = []
78
- self.lock = asyncio.Lock()
79
-
80
- async def acquire(self, session_id: str) -> Any:
81
- """Get resource from pool or create new"""
82
- async with self.lock:
83
- # Try to get from pool
84
- now = datetime.utcnow()
85
- while self.idle_resources:
86
- resource = self.idle_resources.pop(0)
87
- age = (now - resource.created_at).total_seconds()
88
-
89
- if age < self.max_age_seconds:
90
- # Reuse this resource
91
- resource.session_id = session_id
92
- resource.touch()
93
- log_debug(
94
- f"♻️ Reused pooled resource",
95
- resource_type=self.resource_type.value,
96
- age_seconds=age
97
- )
98
- return resource.instance
99
- else:
100
- # Too old, cleanup
101
- await resource.cleanup()
102
-
103
- # Create new resource
104
- if asyncio.iscoroutinefunction(self.factory):
105
- instance = await self.factory()
106
- else:
107
- instance = await asyncio.to_thread(self.factory)
108
-
109
- log_debug(
110
- f"🏗️ Created new resource",
111
- resource_type=self.resource_type.value
112
- )
113
- return instance
114
-
115
- async def release(self, resource: Resource):
116
- """Return resource to pool"""
117
- async with self.lock:
118
- if len(self.idle_resources) < self.max_idle:
119
- resource.session_id = "" # Clear session
120
- self.idle_resources.append(resource)
121
- log_debug(
122
- f"📥 Resource returned to pool",
123
- resource_type=self.resource_type.value,
124
- pool_size=len(self.idle_resources)
125
- )
126
- else:
127
- # Pool full, cleanup
128
- await resource.cleanup()
129
-
130
- async def cleanup_old(self):
131
- """Cleanup old resources in pool"""
132
- async with self.lock:
133
- now = datetime.utcnow()
134
- active_resources = []
135
-
136
- for resource in self.idle_resources:
137
- age = (now - resource.created_at).total_seconds()
138
- if age < self.max_age_seconds:
139
- active_resources.append(resource)
140
- else:
141
- await resource.cleanup()
142
-
143
- self.idle_resources = active_resources
144
-
145
-
146
- class ResourceManager:
147
- """Manages all resources with lifecycle and pooling"""
148
-
149
- def __init__(self, event_bus: EventBus):
150
- self.event_bus = event_bus
151
- self.resources: Dict[str, Resource] = {}
152
- self.session_resources: Dict[str, Set[str]] = {}
153
- self.pools: Dict[ResourceType, ResourcePool] = {}
154
- self.disposal_delay_seconds = 60 # Default disposal delay
155
- self._cleanup_task: Optional[asyncio.Task] = None
156
- self._running = False
157
- self._setup_event_handlers()
158
-
159
- def _setup_event_handlers(self):
160
- """Subscribe to lifecycle events"""
161
- self.event_bus.subscribe(EventType.SESSION_STARTED, self._handle_session_started)
162
- self.event_bus.subscribe(EventType.SESSION_ENDED, self._handle_session_ended)
163
-
164
- async def start(self):
165
- """Start resource manager"""
166
- if self._running:
167
- return
168
-
169
- self._running = True
170
- self._cleanup_task = asyncio.create_task(self._periodic_cleanup())
171
- log_info("✅ Resource manager started")
172
-
173
- async def stop(self):
174
- """Stop resource manager"""
175
- self._running = False
176
-
177
- if self._cleanup_task:
178
- self._cleanup_task.cancel()
179
- try:
180
- await self._cleanup_task
181
- except asyncio.CancelledError:
182
- pass
183
-
184
- # Cleanup all resources
185
- for resource_id in list(self.resources.keys()):
186
- await self.release(resource_id, immediate=True)
187
-
188
- log_info("✅ Resource manager stopped")
189
-
190
- def register_pool(self,
191
- resource_type: ResourceType,
192
- factory: Callable,
193
- max_idle: int = 10,
194
- max_age_seconds: int = 300):
195
- """Register a resource pool"""
196
- self.pools[resource_type] = ResourcePool(
197
- resource_type=resource_type,
198
- factory=factory,
199
- max_idle=max_idle,
200
- max_age_seconds=max_age_seconds
201
- )
202
- log_info(
203
- f"📊 Resource pool registered",
204
- resource_type=resource_type.value,
205
- max_idle=max_idle
206
- )
207
-
208
- async def acquire(self,
209
- resource_id: str,
210
- session_id: str,
211
- resource_type: ResourceType,
212
- factory: Optional[Callable] = None,
213
- cleanup_callback: Optional[Callable] = None) -> Any:
214
- """Acquire a resource"""
215
-
216
- # Check if already exists
217
- if resource_id in self.resources:
218
- resource = self.resources[resource_id]
219
- resource.touch()
220
-
221
- # Cancel any pending disposal
222
- if resource.disposal_task:
223
- resource.disposal_task.cancel()
224
- resource.disposal_task = None
225
-
226
- return resource.instance
227
-
228
- # Try to get from pool
229
- instance = None
230
- if resource_type in self.pools:
231
- instance = await self.pools[resource_type].acquire(session_id)
232
- elif factory:
233
- # Create new resource
234
- if asyncio.iscoroutinefunction(factory):
235
- instance = await factory()
236
- else:
237
- instance = await asyncio.to_thread(factory)
238
- else:
239
- raise ValueError(f"No factory or pool for resource type: {resource_type}")
240
-
241
- # Create resource wrapper
242
- resource = Resource(
243
- resource_id=resource_id,
244
- resource_type=resource_type,
245
- session_id=session_id,
246
- instance=instance,
247
- cleanup_callback=cleanup_callback
248
- )
249
-
250
- # Track resource
251
- self.resources[resource_id] = resource
252
-
253
- if session_id not in self.session_resources:
254
- self.session_resources[session_id] = set()
255
- self.session_resources[session_id].add(resource_id)
256
-
257
- log_info(
258
- f"📌 Resource acquired",
259
- resource_id=resource_id,
260
- resource_type=resource_type.value,
261
- session_id=session_id
262
- )
263
-
264
- return instance
265
-
266
- async def release(self,
267
- resource_id: str,
268
- delay_seconds: Optional[int] = None,
269
- immediate: bool = False):
270
- """Release a resource with optional delay"""
271
-
272
- resource = self.resources.get(resource_id)
273
- if not resource:
274
- return
275
-
276
- if immediate:
277
- # Immediate cleanup
278
- await self._dispose_resource(resource_id)
279
- else:
280
- # Schedule disposal
281
- delay = delay_seconds or self.disposal_delay_seconds
282
- resource.disposal_task = asyncio.create_task(
283
- self._delayed_disposal(resource_id, delay)
284
- )
285
-
286
- log_debug(
287
- f"⏱️ Resource disposal scheduled",
288
- resource_id=resource_id,
289
- delay_seconds=delay
290
- )
291
-
292
- async def _delayed_disposal(self, resource_id: str, delay_seconds: int):
293
- """Dispose resource after delay"""
294
- try:
295
- await asyncio.sleep(delay_seconds)
296
- await self._dispose_resource(resource_id)
297
- except asyncio.CancelledError:
298
- log_debug(f"🚫 Disposal cancelled", resource_id=resource_id)
299
-
300
- async def _dispose_resource(self, resource_id: str):
301
- """Actually dispose of a resource"""
302
- resource = self.resources.pop(resource_id, None)
303
- if not resource:
304
- return
305
-
306
- # Remove from session tracking
307
- if resource.session_id in self.session_resources:
308
- self.session_resources[resource.session_id].discard(resource_id)
309
-
310
- # Return to pool or cleanup
311
- if resource.resource_type in self.pools:
312
- await self.pools[resource.resource_type].release(resource)
313
- else:
314
- await resource.cleanup()
315
-
316
- log_info(
317
- f"♻️ Resource disposed",
318
- resource_id=resource_id,
319
- resource_type=resource.resource_type.value
320
- )
321
-
322
- async def release_session_resources(self, session_id: str):
323
- """Release all resources for a session"""
324
- resource_ids = self.session_resources.get(session_id, set()).copy()
325
-
326
- for resource_id in resource_ids:
327
- await self.release(resource_id, immediate=True)
328
-
329
- # Remove session tracking
330
- self.session_resources.pop(session_id, None)
331
-
332
- log_info(
333
- f"🧹 Session resources released",
334
- session_id=session_id,
335
- count=len(resource_ids)
336
- )
337
-
338
- async def _handle_session_started(self, event: Event):
339
- """Initialize session resource tracking"""
340
- session_id = event.session_id
341
- self.session_resources[session_id] = set()
342
-
343
- async def _handle_session_ended(self, event: Event):
344
- """Cleanup session resources"""
345
- session_id = event.session_id
346
- await self.release_session_resources(session_id)
347
-
348
- async def _periodic_cleanup(self):
349
- """Periodic cleanup of old resources"""
350
- while self._running:
351
- try:
352
- await asyncio.sleep(60) # Check every minute
353
-
354
- # Cleanup old pooled resources
355
- for pool in self.pools.values():
356
- await pool.cleanup_old()
357
-
358
- # Check for orphaned resources
359
- now = datetime.utcnow()
360
- for resource_id, resource in list(self.resources.items()):
361
- age = (now - resource.last_accessed).total_seconds()
362
-
363
- # If not accessed for 5 minutes and no disposal scheduled
364
- if age > 300 and not resource.disposal_task:
365
- log_warning(
366
- f"⚠️ Orphaned resource detected",
367
- resource_id=resource_id,
368
- age_seconds=age
369
- )
370
- await self.release(resource_id, delay_seconds=30)
371
-
372
- except Exception as e:
373
- log_error(
374
- f"❌ Error in periodic cleanup",
375
- error=str(e),
376
- traceback=traceback.format_exc()
377
- )
378
-
379
- def get_stats(self) -> Dict[str, Any]:
380
- """Get resource manager statistics"""
381
- pool_stats = {}
382
- for resource_type, pool in self.pools.items():
383
- pool_stats[resource_type.value] = {
384
- "idle_count": len(pool.idle_resources),
385
- "max_idle": pool.max_idle
386
- }
387
-
388
- return {
389
- "active_resources": len(self.resources),
390
- "sessions": len(self.session_resources),
391
- "pools": pool_stats,
392
- "total_resources_by_type": self._count_by_type()
393
- }
394
-
395
- def _count_by_type(self) -> Dict[str, int]:
396
- """Count resources by type"""
397
- counts = {}
398
- for resource in self.resources.values():
399
- type_name = resource.resource_type.value
400
- counts[type_name] = counts.get(type_name, 0) + 1
401
- return counts