hualinxin8615 commited on
Commit
98b541b
·
verified ·
1 Parent(s): d0146ca

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +428 -103
app.py CHANGED
@@ -5,6 +5,8 @@ from datetime import datetime, timedelta
5
  from huggingface_hub import Repository
6
  import threading
7
  import time
 
 
8
 
9
  # Configuration
10
  DATA_STORAGE_REPO = "CIV3283/Data_Storage"
@@ -14,6 +16,371 @@ MIN_IDLE_MINUTES = 10 # Minimum idle time required for space assignment
14
  ALLOCATION_RECORD_FILE = "allocation_records.csv" # New file for tracking allocations
15
  ALLOCATION_LOCK_DURATION = 5 # Lock duration in minutes
16
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  # 新增:本地防撞车机制
18
  class LocalAllocationTracker:
19
  def __init__(self):
@@ -343,89 +710,7 @@ def simple_push_allocation_record(repo, space_name, student_id):
343
  print(f"[simple_push_allocation_record] Allocation recorded locally, sync will happen eventually")
344
  return False
345
 
346
- def get_last_activity_time_optimized(csv_file_path):
347
- """优化版本:使用文件修改时间而不是读取文件内容"""
348
- try:
349
- if not os.path.exists(csv_file_path):
350
- print(f"[get_last_activity_time_optimized] File not found: {csv_file_path}")
351
- return datetime.min # Never used
352
-
353
- # 获取文件的修改时间戳
354
- file_mtime = os.path.getmtime(csv_file_path)
355
- last_modified = datetime.fromtimestamp(file_mtime)
356
-
357
- print(f"[get_last_activity_time_optimized] {os.path.basename(csv_file_path)} last modified: {last_modified}")
358
- return last_modified
359
-
360
- except Exception as e:
361
- print(f"[get_last_activity_time_optimized] Error getting file mtime for {csv_file_path}: {e}")
362
- return datetime.min
363
-
364
- def get_file_size_for_validation(csv_file_path):
365
- """获取文件大小,用于验证文件是否有实际内容"""
366
- try:
367
- if not os.path.exists(csv_file_path):
368
- return 0
369
- return os.path.getsize(csv_file_path)
370
- except:
371
- return 0
372
 
373
- def analyze_space_activity_optimized(available_spaces, repo_dir):
374
- """优化版本:使用文件修改时间分析空间活动"""
375
- space_activity = []
376
- current_time = datetime.now()
377
-
378
- # Read allocation records to filter out recently allocated spaces
379
- active_allocations = read_allocation_records(repo_dir)
380
-
381
- print(f"[analyze_space_activity_optimized] Analyzing {len(available_spaces)} spaces using file modification time...")
382
-
383
- for space_name in available_spaces:
384
- csv_file = os.path.join(repo_dir, f"{space_name}_query_log.csv")
385
-
386
- # 使用文件修改时间而不是读取文件内容
387
- last_activity = get_last_activity_time_optimized(csv_file)
388
- file_size = get_file_size_for_validation(csv_file)
389
-
390
- # Calculate idle time in minutes
391
- if last_activity == datetime.min or file_size <= 100: # 小于100字节认为是空文件或只有header
392
- idle_minutes = float('inf') # Never used
393
- status = "Never used"
394
- last_activity_str = "Never"
395
- else:
396
- idle_minutes = (current_time - last_activity).total_seconds() / 60
397
- status = f"Idle for {idle_minutes:.1f} minutes"
398
- last_activity_str = last_activity.strftime('%Y-%m-%d %H:%M:%S')
399
-
400
- # Check if space is recently allocated (remote)
401
- is_recently_allocated_remote = space_name in active_allocations
402
- if is_recently_allocated_remote:
403
- alloc_info = active_allocations[space_name]
404
- minutes_until_free = (alloc_info['expires_at'] - current_time).total_seconds() / 60
405
- status += f" (Recently allocated remotely, free in {minutes_until_free:.1f} min)"
406
-
407
- # Check if space is recently allocated (local)
408
- is_recently_allocated_local, local_student = local_tracker.is_recently_allocated_locally(space_name)
409
- if is_recently_allocated_local:
410
- status += f" (Recently allocated locally to {local_student})"
411
-
412
- space_activity.append({
413
- 'space_name': space_name,
414
- 'last_activity': last_activity,
415
- 'last_activity_str': last_activity_str,
416
- 'idle_minutes': idle_minutes,
417
- 'status': status,
418
- 'is_recently_allocated_remote': is_recently_allocated_remote,
419
- 'is_recently_allocated_local': is_recently_allocated_local,
420
- 'file_size': file_size # 添加文件大小信息用于调试
421
- })
422
-
423
- print(f"[analyze_space_activity_optimized] {space_name}: {status} (file size: {file_size} bytes)")
424
-
425
- # Sort by idle time (most idle first)
426
- space_activity.sort(key=lambda x: x['idle_minutes'], reverse=True)
427
-
428
- return space_activity
429
 
430
  def create_status_display(space_activity):
431
  """Create formatted status display for all spaces with proper line breaks"""
@@ -595,26 +880,30 @@ def redirect_to_space(redirect_url, selected_space, status_display):
595
 
596
  return gr.HTML(redirect_html)
597
 
598
- def load_balance_user(student_id):
599
- """Main load balancing function with enhanced collision avoidance"""
600
- print(f"[load_balance_user] Starting enhanced load balancing for student ID: {student_id}")
601
 
602
  # Initialize connection to data storage
603
  repo = init_data_storage_connection()
604
  if not repo:
605
  raise gr.Error("🚫 Unable to connect to the data storage system. Please try again later.", duration=8)
606
 
 
 
 
607
  # Get available spaces dynamically
608
  available_spaces = get_available_spaces(LOCAL_DATA_DIR)
609
 
610
  if not available_spaces:
611
  raise gr.Error("🚫 No student learning assistant spaces found in the system. Please contact your instructor.", duration=8)
612
 
613
- # Analyze space activity (including both remote and local allocation records)
614
- space_activity = analyze_space_activity_optimized(available_spaces, LOCAL_DATA_DIR)
615
 
616
- # Select space with enhanced collision avoidance
617
- return select_space_with_enhanced_collision_avoidance(space_activity, student_id, repo)
 
618
 
619
  def get_url_params(request: gr.Request):
620
  """Extract URL parameters from request"""
@@ -627,8 +916,8 @@ def get_url_params(request: gr.Request):
627
  return "Load Distributor", None
628
  return "Load Distributor", None
629
 
630
- def handle_user_access(request: gr.Request):
631
- """Handle user access and perform load balancing"""
632
  title, check_id = get_url_params(request)
633
 
634
  if not check_id:
@@ -645,9 +934,9 @@ def handle_user_access(request: gr.Request):
645
  """
646
  return title, gr.HTML(error_html)
647
 
648
- # Valid student ID - perform load balancing
649
  try:
650
- result = load_balance_user(check_id)
651
  return title, result
652
  except Exception as e:
653
  # Handle any errors during load balancing
@@ -663,16 +952,52 @@ def handle_user_access(request: gr.Request):
663
  """
664
  return title, gr.HTML(error_html)
665
 
666
- # Create Gradio interface
667
- with gr.Blocks(title="CIV3283 Load Distributor") as interface:
668
- title_display = gr.Markdown("# 🔄 CIV3283 Learning Assistant Load Distributor", elem_id="title")
669
- content_display = gr.HTML("")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
670
 
671
- # Initialize on page load
672
- interface.load(
673
- fn=handle_user_access,
674
- outputs=[title_display, content_display]
675
- )
676
 
 
677
  if __name__ == "__main__":
678
- interface.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
5
  from huggingface_hub import Repository
6
  import threading
7
  import time
8
+ import json
9
+
10
 
11
  # Configuration
12
  DATA_STORAGE_REPO = "CIV3283/Data_Storage"
 
16
  ALLOCATION_RECORD_FILE = "allocation_records.csv" # New file for tracking allocations
17
  ALLOCATION_LOCK_DURATION = 5 # Lock duration in minutes
18
 
19
+ # 缓存配置
20
+ ACTIVITY_CACHE_FILE = "space_activity_cache.json"
21
+ CACHE_UPDATE_INTERVAL = 600 # 10分钟 = 600秒
22
+ CACHE_LOCK = threading.Lock()
23
+
24
+ class SpaceActivityCache:
25
+ def __init__(self, repo_dir):
26
+ self.repo_dir = repo_dir
27
+ self.cache_file_path = os.path.join(repo_dir, ACTIVITY_CACHE_FILE)
28
+ self._cache_data = {}
29
+ self._last_update = None
30
+ self._update_thread = None
31
+ self._stop_event = threading.Event()
32
+
33
+ # 启动时立即更新一次缓存
34
+ self._update_cache()
35
+
36
+ # 启动后台更新线程
37
+ self.start_background_updates()
38
+
39
+ def start_background_updates(self):
40
+ """启动后台缓存更新线程"""
41
+ if self._update_thread and self._update_thread.is_alive():
42
+ return # 已经在运行
43
+
44
+ self._stop_event.clear()
45
+ self._update_thread = threading.Thread(target=self._background_update_worker, daemon=True)
46
+ self._update_thread.start()
47
+ print("[SpaceActivityCache] Background update thread started")
48
+
49
+ def stop_background_updates(self):
50
+ """停止后台更新线程"""
51
+ if self._update_thread:
52
+ self._stop_event.set()
53
+ print("[SpaceActivityCache] Background update thread stopping...")
54
+
55
+ def _background_update_worker(self):
56
+ """后台更新工作线程"""
57
+ while not self._stop_event.is_set():
58
+ try:
59
+ # 等待指定间隔或停止信号
60
+ if self._stop_event.wait(timeout=CACHE_UPDATE_INTERVAL):
61
+ break # 收到停止信号
62
+
63
+ # 执行缓存更新
64
+ print("[SpaceActivityCache] Starting scheduled cache update...")
65
+ self._update_cache()
66
+ print("[SpaceActivityCache] Scheduled cache update completed")
67
+
68
+ except Exception as e:
69
+ print(f"[SpaceActivityCache] Error in background update: {e}")
70
+ # 发生错误时等待较短时间后重试
71
+ if not self._stop_event.wait(timeout=60): # 1分钟后重试
72
+ continue
73
+
74
+ def _update_cache(self):
75
+ """更新缓存数据"""
76
+ try:
77
+ print("[SpaceActivityCache] Updating activity cache...")
78
+ start_time = time.time()
79
+
80
+ # 获取所有可用空间
81
+ available_spaces = self._get_available_spaces()
82
+
83
+ new_cache_data = {
84
+ 'last_updated': datetime.now().isoformat(),
85
+ 'update_interval_seconds': CACHE_UPDATE_INTERVAL,
86
+ 'spaces': {}
87
+ }
88
+
89
+ # 为每个空间读取最后活动时间
90
+ for space_name in available_spaces:
91
+ csv_file = os.path.join(self.repo_dir, f"{space_name}_query_log.csv")
92
+ last_activity, status = self._get_last_activity_from_file(csv_file)
93
+
94
+ new_cache_data['spaces'][space_name] = {
95
+ 'last_activity': last_activity.isoformat() if last_activity != datetime.min else None,
96
+ 'status': status,
97
+ 'cache_update_time': datetime.now().isoformat()
98
+ }
99
+
100
+ print(f"[SpaceActivityCache] {space_name}: {status}")
101
+
102
+ # 线程安全地更新缓存
103
+ with CACHE_LOCK:
104
+ self._cache_data = new_cache_data
105
+ self._last_update = datetime.now()
106
+
107
+ # 保存到文件
108
+ self._save_cache_to_file()
109
+
110
+ elapsed_time = time.time() - start_time
111
+ print(f"[SpaceActivityCache] Cache updated in {elapsed_time:.2f}s, {len(available_spaces)} spaces processed")
112
+
113
+ except Exception as e:
114
+ print(f"[SpaceActivityCache] Error updating cache: {e}")
115
+ import traceback
116
+ print(f"[SpaceActivityCache] Traceback: {traceback.format_exc()}")
117
+
118
+ def _get_available_spaces(self):
119
+ """获取可用空间列表"""
120
+ available_spaces = set()
121
+ try:
122
+ for filename in os.listdir(self.repo_dir):
123
+ if filename.endswith('_query_log.csv') and '_Student_' in filename:
124
+ space_name = filename.replace('_query_log.csv', '')
125
+ available_spaces.add(space_name)
126
+ return sorted(list(available_spaces))
127
+ except Exception as e:
128
+ print(f"[SpaceActivityCache] Error getting available spaces: {e}")
129
+ return []
130
+
131
+ def _get_last_activity_from_file(self, csv_file_path):
132
+ """从文件读取最后活动时间(使用原来的方法)"""
133
+ try:
134
+ if not os.path.exists(csv_file_path):
135
+ return datetime.min, "file_not_found"
136
+
137
+ # 检查文件大小
138
+ file_size = os.path.getsize(csv_file_path)
139
+ if file_size <= 100:
140
+ return datetime.min, "empty_or_header_only"
141
+
142
+ # 使用CSV reader读取最后一行
143
+ with open(csv_file_path, 'r', encoding='utf-8') as f:
144
+ csv_reader = csv.reader(f)
145
+
146
+ # 跳过header
147
+ try:
148
+ header = next(csv_reader)
149
+ except StopIteration:
150
+ return datetime.min, "empty_file"
151
+
152
+ # 读取所有行,获取最后一行
153
+ rows = []
154
+ try:
155
+ for row in csv_reader:
156
+ if row: # 跳过空行
157
+ rows.append(row)
158
+ except Exception as csv_error:
159
+ print(f"[SpaceActivityCache] CSV parsing error for {csv_file_path}: {csv_error}")
160
+ return datetime.min, "csv_parse_error"
161
+
162
+ if not rows:
163
+ return datetime.min, "no_data_rows"
164
+
165
+ # 解析最后一行的时间戳
166
+ last_row = rows[-1]
167
+ if len(last_row) >= 3:
168
+ timestamp_str = last_row[2].strip() # timestamp column
169
+ try:
170
+ parsed_time = datetime.strptime(timestamp_str, '%Y-%m-%d %H:%M:%S')
171
+ return parsed_time, f"active_last_at_{parsed_time.strftime('%Y-%m-%d_%H:%M:%S')}"
172
+ except ValueError as ve:
173
+ print(f"[SpaceActivityCache] Date parsing error for '{timestamp_str}': {ve}")
174
+ return datetime.min, "date_parse_error"
175
+ else:
176
+ return datetime.min, "invalid_row_format"
177
+
178
+ except Exception as e:
179
+ print(f"[SpaceActivityCache] Error reading {csv_file_path}: {e}")
180
+ return datetime.min, "read_error"
181
+
182
+ def _save_cache_to_file(self):
183
+ """保存缓存到文件"""
184
+ try:
185
+ with open(self.cache_file_path, 'w', encoding='utf-8') as f:
186
+ json.dump(self._cache_data, f, indent=2, ensure_ascii=False)
187
+ print(f"[SpaceActivityCache] Cache saved to {self.cache_file_path}")
188
+ except Exception as e:
189
+ print(f"[SpaceActivityCache] Error saving cache to file: {e}")
190
+
191
+ def _load_cache_from_file(self):
192
+ """从文件加载缓存"""
193
+ try:
194
+ if os.path.exists(self.cache_file_path):
195
+ with open(self.cache_file_path, 'r', encoding='utf-8') as f:
196
+ data = json.load(f)
197
+ print(f"[SpaceActivityCache] Cache loaded from file")
198
+ return data
199
+ except Exception as e:
200
+ print(f"[SpaceActivityCache] Error loading cache from file: {e}")
201
+ return {}
202
+
203
+ def get_space_activity(self, space_name):
204
+ """获取指定空间的活动信息"""
205
+ with CACHE_LOCK:
206
+ if not self._cache_data or 'spaces' not in self._cache_data:
207
+ # 缓存为空,尝试从文件加载
208
+ self._cache_data = self._load_cache_from_file()
209
+ if not self._cache_data:
210
+ # 文件也没有,立即更新一次
211
+ print("[SpaceActivityCache] No cache available, updating immediately...")
212
+ self._update_cache()
213
+
214
+ spaces_data = self._cache_data.get('spaces', {})
215
+ space_info = spaces_data.get(space_name, {})
216
+
217
+ if not space_info:
218
+ return datetime.min, "not_in_cache"
219
+
220
+ # 解析时间戳
221
+ last_activity_str = space_info.get('last_activity')
222
+ if last_activity_str:
223
+ try:
224
+ last_activity = datetime.fromisoformat(last_activity_str)
225
+ except:
226
+ last_activity = datetime.min
227
+ else:
228
+ last_activity = datetime.min
229
+
230
+ status = space_info.get('status', 'unknown')
231
+ return last_activity, status
232
+
233
+ def get_all_spaces_activity(self):
234
+ """获取所有空间的活动信息"""
235
+ with CACHE_LOCK:
236
+ if not self._cache_data or 'spaces' not in self._cache_data:
237
+ self._cache_data = self._load_cache_from_file()
238
+ if not self._cache_data:
239
+ print("[SpaceActivityCache] No cache available, updating immediately...")
240
+ self._update_cache()
241
+
242
+ result = {}
243
+ spaces_data = self._cache_data.get('spaces', {})
244
+
245
+ for space_name, space_info in spaces_data.items():
246
+ last_activity_str = space_info.get('last_activity')
247
+ if last_activity_str:
248
+ try:
249
+ last_activity = datetime.fromisoformat(last_activity_str)
250
+ except:
251
+ last_activity = datetime.min
252
+ else:
253
+ last_activity = datetime.min
254
+
255
+ result[space_name] = {
256
+ 'last_activity': last_activity,
257
+ 'status': space_info.get('status', 'unknown')
258
+ }
259
+
260
+ return result
261
+
262
+ def get_cache_info(self):
263
+ """获取缓存状态信息"""
264
+ with CACHE_LOCK:
265
+ if self._cache_data and 'last_updated' in self._cache_data:
266
+ last_updated_str = self._cache_data['last_updated']
267
+ try:
268
+ last_updated = datetime.fromisoformat(last_updated_str)
269
+ age_minutes = (datetime.now() - last_updated).total_seconds() / 60
270
+ return {
271
+ 'last_updated': last_updated,
272
+ 'age_minutes': age_minutes,
273
+ 'spaces_count': len(self._cache_data.get('spaces', {})),
274
+ 'is_fresh': age_minutes < (CACHE_UPDATE_INTERVAL / 60) * 1.5 # 允许1.5倍的间隔
275
+ }
276
+ except:
277
+ pass
278
+
279
+ return {
280
+ 'last_updated': None,
281
+ 'age_minutes': float('inf'),
282
+ 'spaces_count': 0,
283
+ 'is_fresh': False
284
+ }
285
+
286
+ def force_update(self):
287
+ """强制立即更新缓存"""
288
+ print("[SpaceActivityCache] Force updating cache...")
289
+ self._update_cache()
290
+
291
+ # 全局缓存实例
292
+ activity_cache = None
293
+
294
+ def init_activity_cache(repo_dir):
295
+ """初始化活动缓存"""
296
+ global activity_cache
297
+ if activity_cache is None:
298
+ activity_cache = SpaceActivityCache(repo_dir)
299
+ print("[init_activity_cache] Activity cache initialized")
300
+ return activity_cache
301
+
302
+ def analyze_space_activity_cached(available_spaces, repo_dir):
303
+ """使用缓存的空间活动分析"""
304
+ global activity_cache
305
+
306
+ # 确保缓存已初始化
307
+ if activity_cache is None:
308
+ activity_cache = init_activity_cache(repo_dir)
309
+
310
+ space_activity = []
311
+ current_time = datetime.now()
312
+
313
+ # 获取缓存信息
314
+ cache_info = activity_cache.get_cache_info()
315
+ print(f"[analyze_space_activity_cached] Using cache (age: {cache_info['age_minutes']:.1f} min, fresh: {cache_info['is_fresh']})")
316
+
317
+ # 如果缓存太旧,可以选择强制更新(可选)
318
+ if not cache_info['is_fresh']:
319
+ print("[analyze_space_activity_cached] Cache is stale, but using it anyway for speed")
320
+ # 可以选择在后台触发更新:activity_cache.force_update()
321
+
322
+ # 获取所有空间的缓存活动数据
323
+ all_spaces_activity = activity_cache.get_all_spaces_activity()
324
+
325
+ # Read allocation records to filter out recently allocated spaces
326
+ active_allocations = read_allocation_records(repo_dir)
327
+
328
+ print(f"[analyze_space_activity_cached] Analyzing {len(available_spaces)} spaces using cached data...")
329
+
330
+ for space_name in available_spaces:
331
+ # 从缓存获取活动信息
332
+ cached_info = all_spaces_activity.get(space_name)
333
+
334
+ if cached_info:
335
+ last_activity = cached_info['last_activity']
336
+ cached_status = cached_info['status']
337
+ else:
338
+ # 缓存中没有这个空间,可能是新空间
339
+ last_activity = datetime.min
340
+ cached_status = "not_in_cache"
341
+
342
+ # Calculate idle time in minutes
343
+ if last_activity == datetime.min or 'empty' in cached_status or 'not_found' in cached_status:
344
+ idle_minutes = float('inf') # Never used
345
+ status = "Never used"
346
+ last_activity_str = "Never"
347
+ else:
348
+ idle_minutes = (current_time - last_activity).total_seconds() / 60
349
+ status = f"Idle for {idle_minutes:.1f} minutes (cached)"
350
+ last_activity_str = last_activity.strftime('%Y-%m-%d %H:%M:%S')
351
+
352
+ # Check if space is recently allocated (remote)
353
+ is_recently_allocated_remote = space_name in active_allocations
354
+ if is_recently_allocated_remote:
355
+ alloc_info = active_allocations[space_name]
356
+ minutes_until_free = (alloc_info['expires_at'] - current_time).total_seconds() / 60
357
+ status += f" (Recently allocated remotely, free in {minutes_until_free:.1f} min)"
358
+
359
+ # Check if space is recently allocated (local)
360
+ is_recently_allocated_local, local_student = local_tracker.is_recently_allocated_locally(space_name)
361
+ if is_recently_allocated_local:
362
+ status += f" (Recently allocated locally to {local_student})"
363
+
364
+ space_activity.append({
365
+ 'space_name': space_name,
366
+ 'last_activity': last_activity,
367
+ 'last_activity_str': last_activity_str,
368
+ 'idle_minutes': idle_minutes,
369
+ 'status': status,
370
+ 'is_recently_allocated_remote': is_recently_allocated_remote,
371
+ 'is_recently_allocated_local': is_recently_allocated_local,
372
+ 'cached_status': cached_status
373
+ })
374
+
375
+ print(f"[analyze_space_activity_cached] {space_name}: {status}")
376
+
377
+ # Sort by idle time (most idle first)
378
+ space_activity.sort(key=lambda x: x['idle_minutes'], reverse=True)
379
+
380
+ return space_activity
381
+
382
+
383
+
384
  # 新增:本地防撞车机制
385
  class LocalAllocationTracker:
386
  def __init__(self):
 
710
  print(f"[simple_push_allocation_record] Allocation recorded locally, sync will happen eventually")
711
  return False
712
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
713
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
714
 
715
  def create_status_display(space_activity):
716
  """Create formatted status display for all spaces with proper line breaks"""
 
880
 
881
  return gr.HTML(redirect_html)
882
 
883
+ def load_balance_user_final(student_id):
884
+ """最终版本的负载均衡函数"""
885
+ print(f"[load_balance_user_final] Starting cached load balancing for student ID: {student_id}")
886
 
887
  # Initialize connection to data storage
888
  repo = init_data_storage_connection()
889
  if not repo:
890
  raise gr.Error("🚫 Unable to connect to the data storage system. Please try again later.", duration=8)
891
 
892
+ # 初始化活动缓存
893
+ init_activity_cache(LOCAL_DATA_DIR)
894
+
895
  # Get available spaces dynamically
896
  available_spaces = get_available_spaces(LOCAL_DATA_DIR)
897
 
898
  if not available_spaces:
899
  raise gr.Error("🚫 No student learning assistant spaces found in the system. Please contact your instructor.", duration=8)
900
 
901
+ # 使用缓存版本的分析函数
902
+ space_activity = analyze_space_activity_cached(available_spaces, LOCAL_DATA_DIR)
903
 
904
+ # 使用缓存版本的选择函数
905
+ return select_space_with_enhanced_collision_avoidance_cached(space_activity, student_id, repo)
906
+
907
 
908
  def get_url_params(request: gr.Request):
909
  """Extract URL parameters from request"""
 
916
  return "Load Distributor", None
917
  return "Load Distributor", None
918
 
919
+ def handle_user_access_final(request: gr.Request):
920
+ """最终版本的用户访问处理"""
921
  title, check_id = get_url_params(request)
922
 
923
  if not check_id:
 
934
  """
935
  return title, gr.HTML(error_html)
936
 
937
+ # Valid student ID - perform cached load balancing
938
  try:
939
+ result = load_balance_user_final(check_id)
940
  return title, result
941
  except Exception as e:
942
  # Handle any errors during load balancing
 
952
  """
953
  return title, gr.HTML(error_html)
954
 
955
+ # 修改后的主Gradio界面
956
+ def create_main_interface():
957
+ """创建主要的用户界面"""
958
+ with gr.Blocks(title="CIV3283 Load Distributor") as interface:
959
+ title_display = gr.Markdown("# 🔄 CIV3283 Learning Assistant Load Distributor", elem_id="title")
960
+ content_display = gr.HTML("")
961
+
962
+ # 添加隐藏的缓存信息显示(用于调试,默认不显示)
963
+ with gr.Accordion("🔧 System Information", open=False, visible=False):
964
+ cache_info = gr.JSON(label="Cache Status", visible=False)
965
+
966
+ def update_cache_info():
967
+ return get_cache_status()
968
+
969
+ # 每30秒更新一次缓存信息(如果界面是打开的)
970
+ interface.load(
971
+ fn=update_cache_info,
972
+ outputs=cache_info,
973
+ every=30
974
+ )
975
+
976
+ # 主要的负载均衡逻辑
977
+ interface.load(
978
+ fn=handle_user_access_final,
979
+ outputs=[title_display, content_display]
980
+ )
981
 
982
+ return interface
 
 
 
 
983
 
984
+ # 修改后的main函数
985
  if __name__ == "__main__":
986
+ # 创建主界面
987
+ main_interface = create_main_interface()
988
+
989
+ # 启动主界面
990
+ main_interface.launch()
991
+
992
+ # 在程序退出时优雅地关闭后台线程
993
+ import atexit
994
+
995
+ def cleanup_on_exit():
996
+ """程序退出时的清理函数"""
997
+ global activity_cache
998
+ if activity_cache:
999
+ print("[cleanup_on_exit] Stopping background cache updates...")
1000
+ activity_cache.stop_background_updates()
1001
+
1002
+ # 注册退出时的清理函数
1003
+ atexit.register(cleanup_on_exit)