da03 commited on
Commit
4555c1c
·
1 Parent(s): b9e6b75
Files changed (2) hide show
  1. dispatcher.py +40 -13
  2. static/index.html +21 -21
dispatcher.py CHANGED
@@ -329,6 +329,7 @@ class UserSession:
329
  worker_id: Optional[str] = None
330
  last_activity: Optional[float] = None
331
  max_session_time: Optional[float] = None
 
332
  user_has_interacted: bool = False
333
  ip_address: Optional[str] = None
334
  interaction_count: int = 0
@@ -411,6 +412,7 @@ class SessionManager:
411
  if session and session.max_session_time is None: # Currently unlimited
412
  # Give them 60 seconds from now
413
  session.max_session_time = 60.0
 
414
  session.last_activity = current_time # Reset activity timer to start 60s countdown
415
  session.session_warning_sent = False # Reset warning flag
416
  affected_sessions += 1
@@ -418,8 +420,7 @@ class SessionManager:
418
  # Notify the user about the new time limit
419
  try:
420
  queue_size = len(self.session_queue)
421
- user_text = "user" if queue_size == 1 else "users"
422
- message = f"{queue_size} other {user_text} waiting. You have 60 seconds to finish."
423
 
424
  await session.websocket.send_json({
425
  "type": "queue_limit_applied",
@@ -464,6 +465,7 @@ class SessionManager:
464
  # Set session time limit based on queue status
465
  if len(self.session_queue) > 0:
466
  session.max_session_time = self.MAX_SESSION_TIME_WITH_QUEUE
 
467
 
468
  worker.is_available = False
469
  worker.current_session = session_id
@@ -520,9 +522,15 @@ class SessionManager:
520
  while session.status == SessionStatus.ACTIVE:
521
  current_time = time.time()
522
 
523
- # Check if session has exceeded time limit
 
 
 
 
524
  if session.max_session_time:
525
- elapsed = current_time - session.last_activity if session.last_activity else 0
 
 
526
  remaining = session.max_session_time - elapsed
527
 
528
  # Send warning at 15 seconds before timeout (only once)
@@ -540,6 +548,7 @@ class SessionManager:
540
  # Check if queue is empty - if so, extend session
541
  if len(self.session_queue) == 0:
542
  session.max_session_time = None # Remove time limit
 
543
  session.session_warning_sent = False # Reset warning since limit removed
544
  await session.websocket.send_json({
545
  "type": "time_limit_removed",
@@ -553,24 +562,31 @@ class SessionManager:
553
  "queue_size": len(self.session_queue)
554
  })
555
 
556
- # Timeout
557
  elif remaining <= 0:
558
- await self.end_session(session_id, SessionStatus.TIMEOUT)
559
- return
560
 
561
- # Check idle timeout when no queue
562
- elif not session.max_session_time and session.last_activity:
563
  idle_time = current_time - session.last_activity
564
  if idle_time >= self.IDLE_TIMEOUT:
565
- await self.end_session(session_id, SessionStatus.TIMEOUT)
566
- return
567
  elif idle_time >= self.QUEUE_WARNING_TIME and not session.idle_warning_sent:
 
568
  await session.websocket.send_json({
569
  "type": "idle_warning",
570
  "time_remaining": self.IDLE_TIMEOUT - idle_time
571
  })
572
  session.idle_warning_sent = True
573
- logger.info(f"Idle warning sent to {session_id}, time remaining: {self.IDLE_TIMEOUT - idle_time:.1f}s")
 
 
 
 
 
 
 
 
574
 
575
  await asyncio.sleep(1) # Check every second
576
 
@@ -679,9 +695,17 @@ class SessionManager:
679
  session = self.sessions.get(session_id)
680
  if session:
681
  old_time = session.last_activity
682
- session.last_activity = time.time()
683
  session.interaction_count += 1
684
 
 
 
 
 
 
 
 
 
 
685
  # Reset warning flags if user activity detected after warnings
686
  warning_reset = False
687
  if session.idle_warning_sent:
@@ -832,6 +856,9 @@ async def websocket_endpoint(websocket: WebSocket):
832
 
833
  # Update activity only for real user inputs, not auto inputs
834
  if not data.get("is_auto_input", False):
 
 
 
835
  await session_manager.handle_user_activity(session_id)
836
 
837
  # Handle different message types
 
329
  worker_id: Optional[str] = None
330
  last_activity: Optional[float] = None
331
  max_session_time: Optional[float] = None
332
+ session_limit_start_time: Optional[float] = None # When the time limit was first applied
333
  user_has_interacted: bool = False
334
  ip_address: Optional[str] = None
335
  interaction_count: int = 0
 
412
  if session and session.max_session_time is None: # Currently unlimited
413
  # Give them 60 seconds from now
414
  session.max_session_time = 60.0
415
+ session.session_limit_start_time = current_time # Track when limit started
416
  session.last_activity = current_time # Reset activity timer to start 60s countdown
417
  session.session_warning_sent = False # Reset warning flag
418
  affected_sessions += 1
 
420
  # Notify the user about the new time limit
421
  try:
422
  queue_size = len(self.session_queue)
423
+ message = f"Other users waiting. Time remaining: 60 seconds."
 
424
 
425
  await session.websocket.send_json({
426
  "type": "queue_limit_applied",
 
465
  # Set session time limit based on queue status
466
  if len(self.session_queue) > 0:
467
  session.max_session_time = self.MAX_SESSION_TIME_WITH_QUEUE
468
+ session.session_limit_start_time = time.time() # Track when limit started
469
 
470
  worker.is_available = False
471
  worker.current_session = session_id
 
522
  while session.status == SessionStatus.ACTIVE:
523
  current_time = time.time()
524
 
525
+ # Check timeouts - both session limit AND idle timeout can apply
526
+ session_timeout = False
527
+ idle_timeout = False
528
+
529
+ # Check session time limit (when queue exists)
530
  if session.max_session_time:
531
+ # Use session_limit_start_time for absolute timeout, fall back to last_activity if not set
532
+ start_time = session.session_limit_start_time if session.session_limit_start_time else session.last_activity
533
+ elapsed = current_time - start_time if start_time else 0
534
  remaining = session.max_session_time - elapsed
535
 
536
  # Send warning at 15 seconds before timeout (only once)
 
548
  # Check if queue is empty - if so, extend session
549
  if len(self.session_queue) == 0:
550
  session.max_session_time = None # Remove time limit
551
+ session.session_limit_start_time = None # Clear time limit start time
552
  session.session_warning_sent = False # Reset warning since limit removed
553
  await session.websocket.send_json({
554
  "type": "time_limit_removed",
 
562
  "queue_size": len(self.session_queue)
563
  })
564
 
565
+ # Session timeout
566
  elif remaining <= 0:
567
+ session_timeout = True
 
568
 
569
+ # Check idle timeout (always check when user has interacted)
570
+ if session.last_activity:
571
  idle_time = current_time - session.last_activity
572
  if idle_time >= self.IDLE_TIMEOUT:
573
+ idle_timeout = True
 
574
  elif idle_time >= self.QUEUE_WARNING_TIME and not session.idle_warning_sent:
575
+ # Send idle warning (even when session limit exists)
576
  await session.websocket.send_json({
577
  "type": "idle_warning",
578
  "time_remaining": self.IDLE_TIMEOUT - idle_time
579
  })
580
  session.idle_warning_sent = True
581
+ reason = "with session limit" if session.max_session_time else "no session limit"
582
+ logger.info(f"Idle warning sent to {session_id}, time remaining: {self.IDLE_TIMEOUT - idle_time:.1f}s ({reason})")
583
+
584
+ # End session if either timeout triggered
585
+ if session_timeout or idle_timeout:
586
+ timeout_reason = "session_limit" if session_timeout else "idle"
587
+ logger.info(f"Ending session {session_id} due to {timeout_reason} timeout")
588
+ await self.end_session(session_id, SessionStatus.TIMEOUT)
589
+ return
590
 
591
  await asyncio.sleep(1) # Check every second
592
 
 
695
  session = self.sessions.get(session_id)
696
  if session:
697
  old_time = session.last_activity
 
698
  session.interaction_count += 1
699
 
700
+ # Always update last_activity for idle detection
701
+ # Session time limits are now tracked separately via session_limit_start_time
702
+ session.last_activity = time.time()
703
+
704
+ if session.max_session_time is not None:
705
+ logger.info(f"Activity detected for session {session_id} - idle timer reset (session limit still active)")
706
+ else:
707
+ logger.info(f"Activity detected for session {session_id} - idle timer reset (no session limit)")
708
+
709
  # Reset warning flags if user activity detected after warnings
710
  warning_reset = False
711
  if session.idle_warning_sent:
 
856
 
857
  # Update activity only for real user inputs, not auto inputs
858
  if not data.get("is_auto_input", False):
859
+ # Log stay-connected attempts for monitoring
860
+ if data.get("is_stay_connected", False):
861
+ logger.info(f"Stay-connected ping from session {session_id} - idle timer will be reset")
862
  await session_manager.handle_user_activity(session_id)
863
 
864
  # Handle different message types
static/index.html CHANGED
@@ -275,7 +275,7 @@
275
  } else if (data.type === "timeout_warning") {
276
  console.log(`Received timeout warning: ${data.timeout_in} seconds remaining`);
277
  setTimeoutMessage(`No activity detected. Connection will be dropped in <span id="timeoutCountdown">${data.timeout_in}</span> seconds and page will refresh automatically.`);
278
- startTimeoutCountdown(data.timeout_in);
279
  } else if (data.type === "activity_reset") {
280
  console.log("Server detected user activity, resetting timeout");
281
  stopTimeoutCountdown();
@@ -288,16 +288,8 @@
288
  waitText = "Starting soon...";
289
  } else if (waitSeconds === 1) {
290
  waitText = "1 second max";
291
- } else if (waitSeconds < 60) {
292
- waitText = `${waitSeconds} seconds max`;
293
  } else {
294
- const minutes = Math.floor(waitSeconds / 60);
295
- const seconds = waitSeconds % 60;
296
- if (seconds === 0) {
297
- waitText = `${minutes} minutes max`;
298
- } else {
299
- waitText = `${minutes}m ${seconds}s max`;
300
- }
301
  }
302
 
303
  const statusText = data.available_workers > 0 ?
@@ -314,23 +306,23 @@
314
  ctx.clearRect(0, 0, canvas.width, canvas.height);
315
  } else if (data.type === "session_warning") {
316
  console.log(`Session time warning: ${data.time_remaining} seconds remaining`);
317
- setTimeoutMessage(`Session time limit reached. You will be disconnected in <span id="timeoutCountdown">${Math.ceil(data.time_remaining)}</span> seconds due to queue waiting.`);
318
- startTimeoutCountdown(Math.ceil(data.time_remaining));
319
  } else if (data.type === "idle_warning") {
320
  console.log(`Idle warning: ${data.time_remaining} seconds until timeout`);
321
  setTimeoutMessage(`No activity detected. Connection will be dropped in <span id="timeoutCountdown">${Math.ceil(data.time_remaining)}</span> seconds and page will refresh automatically.`);
322
- startTimeoutCountdown(Math.ceil(data.time_remaining));
323
  } else if (data.type === "grace_period") {
324
  console.log(`Grace period: ${data.time_remaining} seconds remaining`);
325
- setTimeoutMessage(`Grace period: Session will end in <span id="timeoutCountdown">${Math.ceil(data.time_remaining)}</span> seconds unless queue empties.`);
326
- startTimeoutCountdown(Math.ceil(data.time_remaining));
327
  } else if (data.type === "time_limit_removed") {
328
  console.log("Time limit removed - queue became empty");
329
  stopTimeoutCountdown();
330
  } else if (data.type === "queue_limit_applied") {
331
  console.log(`Queue limit applied: ${data.message}, ${data.time_remaining} seconds remaining`);
332
- setTimeoutMessage(`⏰ ${data.message} Time remaining: <span id="timeoutCountdown">${Math.ceil(data.time_remaining)}</span> seconds.`);
333
- startTimeoutCountdown(Math.ceil(data.time_remaining));
334
  }
335
  };
336
  }
@@ -463,7 +455,7 @@
463
  }
464
  }
465
 
466
- function startTimeoutCountdown(initialTime = 10) {
467
  if (timeoutCountdownInterval) {
468
  clearInterval(timeoutCountdownInterval);
469
  }
@@ -477,6 +469,12 @@
477
  warning.style.display = 'block';
478
  }
479
 
 
 
 
 
 
 
480
  // Update initial display
481
  const countdownElement = document.getElementById('timeoutCountdown');
482
  if (countdownElement) {
@@ -532,7 +530,7 @@
532
  }
533
 
534
  function resetTimeout() {
535
- // Send a regular input to reset the server's timeout
536
  if (socket && socket.readyState === WebSocket.OPEN && lastSentPosition) {
537
  try {
538
  socket.send(JSON.stringify({
@@ -543,11 +541,13 @@
543
  "keys_down": [],
544
  "keys_up": [],
545
  "wheel_delta_x": 0,
546
- "wheel_delta_y": 0
 
547
  }));
548
  updateLastUserInputTime(); // Update for auto-input mechanism
 
549
  } catch (error) {
550
- console.error("Error sending timeout reset input:", error);
551
  }
552
  }
553
  stopTimeoutCountdown();
 
275
  } else if (data.type === "timeout_warning") {
276
  console.log(`Received timeout warning: ${data.timeout_in} seconds remaining`);
277
  setTimeoutMessage(`No activity detected. Connection will be dropped in <span id="timeoutCountdown">${data.timeout_in}</span> seconds and page will refresh automatically.`);
278
+ startTimeoutCountdown(data.timeout_in, false); // false = show stay connected button
279
  } else if (data.type === "activity_reset") {
280
  console.log("Server detected user activity, resetting timeout");
281
  stopTimeoutCountdown();
 
288
  waitText = "Starting soon...";
289
  } else if (waitSeconds === 1) {
290
  waitText = "1 second max";
 
 
291
  } else {
292
+ waitText = `${waitSeconds} seconds max`;
 
 
 
 
 
 
293
  }
294
 
295
  const statusText = data.available_workers > 0 ?
 
306
  ctx.clearRect(0, 0, canvas.width, canvas.height);
307
  } else if (data.type === "session_warning") {
308
  console.log(`Session time warning: ${data.time_remaining} seconds remaining`);
309
+ setTimeoutMessage(`Other users waiting. Time remaining: <span id="timeoutCountdown">${Math.ceil(data.time_remaining)}</span> seconds.`);
310
+ startTimeoutCountdown(Math.ceil(data.time_remaining), true); // true = hide stay connected button
311
  } else if (data.type === "idle_warning") {
312
  console.log(`Idle warning: ${data.time_remaining} seconds until timeout`);
313
  setTimeoutMessage(`No activity detected. Connection will be dropped in <span id="timeoutCountdown">${Math.ceil(data.time_remaining)}</span> seconds and page will refresh automatically.`);
314
+ startTimeoutCountdown(Math.ceil(data.time_remaining), false); // false = show stay connected button
315
  } else if (data.type === "grace_period") {
316
  console.log(`Grace period: ${data.time_remaining} seconds remaining`);
317
+ setTimeoutMessage(`Other users waiting. Time remaining: <span id="timeoutCountdown">${Math.ceil(data.time_remaining)}</span> seconds.`);
318
+ startTimeoutCountdown(Math.ceil(data.time_remaining), true); // true = hide stay connected button
319
  } else if (data.type === "time_limit_removed") {
320
  console.log("Time limit removed - queue became empty");
321
  stopTimeoutCountdown();
322
  } else if (data.type === "queue_limit_applied") {
323
  console.log(`Queue limit applied: ${data.message}, ${data.time_remaining} seconds remaining`);
324
+ setTimeoutMessage(`⏰ ${data.message}`);
325
+ startTimeoutCountdown(Math.ceil(data.time_remaining), true); // true = hide stay connected button
326
  }
327
  };
328
  }
 
455
  }
456
  }
457
 
458
+ function startTimeoutCountdown(initialTime = 10, hideStayConnectedButton = false) {
459
  if (timeoutCountdownInterval) {
460
  clearInterval(timeoutCountdownInterval);
461
  }
 
469
  warning.style.display = 'block';
470
  }
471
 
472
+ // Show/hide Stay Connected button based on context
473
+ const stayConnectedButton = warning.querySelector('button');
474
+ if (stayConnectedButton) {
475
+ stayConnectedButton.style.display = hideStayConnectedButton ? 'none' : 'inline-block';
476
+ }
477
+
478
  // Update initial display
479
  const countdownElement = document.getElementById('timeoutCountdown');
480
  if (countdownElement) {
 
530
  }
531
 
532
  function resetTimeout() {
533
+ // Send a stay-connected ping to reset the server's timeout
534
  if (socket && socket.readyState === WebSocket.OPEN && lastSentPosition) {
535
  try {
536
  socket.send(JSON.stringify({
 
541
  "keys_down": [],
542
  "keys_up": [],
543
  "wheel_delta_x": 0,
544
+ "wheel_delta_y": 0,
545
+ "is_stay_connected": true // Mark this as a stay-connected ping
546
  }));
547
  updateLastUserInputTime(); // Update for auto-input mechanism
548
+ console.log("Stay connected ping sent to server");
549
  } catch (error) {
550
+ console.error("Error sending stay connected ping:", error);
551
  }
552
  }
553
  stopTimeoutCountdown();