Spaces:
Runtime error
Runtime error
da03
commited on
Commit
·
4555c1c
1
Parent(s):
b9e6b75
- dispatcher.py +40 -13
- 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 |
-
|
| 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
|
|
|
|
|
|
|
|
|
|
|
|
|
| 524 |
if session.max_session_time:
|
| 525 |
-
|
|
|
|
|
|
|
| 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 |
-
#
|
| 557 |
elif remaining <= 0:
|
| 558 |
-
|
| 559 |
-
return
|
| 560 |
|
| 561 |
-
# Check idle timeout when
|
| 562 |
-
|
| 563 |
idle_time = current_time - session.last_activity
|
| 564 |
if idle_time >= self.IDLE_TIMEOUT:
|
| 565 |
-
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
|
| 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(`
|
| 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(`
|
| 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}
|
| 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
|
| 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
|
| 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();
|