chrisxx commited on
Commit
14c60e7
·
1 Parent(s): 3b4fa66

Fix: Use reference logic with user_lock pattern, remove eventlet dependency

Browse files
Files changed (1) hide show
  1. app.py +96 -67
app.py CHANGED
@@ -4,10 +4,6 @@ Pong backend (GPU, eager) for Hugging Face Spaces.
4
  Broadcasts readiness via Socket.IO so the frontend can auto-hide a loading overlay once the model is ready.
5
  """
6
 
7
- # Eventlet must be imported first and monkey-patched before other imports
8
- import eventlet
9
- eventlet.monkey_patch()
10
-
11
  import sys
12
  import os
13
  import time
@@ -42,11 +38,11 @@ from src.config import Config
42
  # --------------------------
43
  app = Flask(__name__, static_folder='static')
44
  CORS(app)
45
- # Configure SocketIO - use eventlet for proper WebSocket support
46
  socketio = SocketIO(
47
  app,
48
  cors_allowed_origins="*",
49
- async_mode='eventlet',
50
  logger=False,
51
  engineio_logger=False,
52
  ping_timeout=60,
@@ -63,10 +59,13 @@ device = None
63
 
64
  server_ready = False # <--- readiness flag
65
 
 
 
 
 
66
  stream_lock = threading.Lock()
67
  stream_thread = None
68
  stream_running = False
69
- current_player_sid = None # Track which session is currently playing
70
  latest_action = 1 # 0=init, 1=nothing, 2=up, 3=down
71
  target_fps = 30
72
  frame_index = 0
@@ -148,7 +147,7 @@ def _reset_cache_fresh():
148
 
149
  def _broadcast_ready():
150
  """Tell all clients whether the server is ready."""
151
- socketio.emit('server_status', {'ready': server_ready, 'busy': False})
152
 
153
  # --------------------------
154
  # Model init (pure eager) & warmup
@@ -369,16 +368,13 @@ def generate_frames():
369
  # --------------------------
370
  # Socket events & helpers
371
  # --------------------------
372
- def start_stream(n_steps=8, cfg=0.0, fps=30, clamp=True, player_sid=None):
373
- global stream_thread, stream_running, frame_index, target_fps, latest_action, current_player_sid
374
  if not server_ready:
375
  _broadcast_ready()
376
  raise RuntimeError("Server not ready")
377
  with stream_lock:
378
- if stream_running and current_player_sid is not None:
379
- raise RuntimeError(f"Another player is already using the stream (session: {current_player_sid[:8]}...)")
380
  stop_stream()
381
- current_player_sid = player_sid
382
  target_fps = int(fps)
383
  frame_index = 0
384
  _reset_cache_fresh()
@@ -388,16 +384,12 @@ def start_stream(n_steps=8, cfg=0.0, fps=30, clamp=True, player_sid=None):
388
  stream_thread.start()
389
 
390
  def stop_stream():
391
- global stream_thread, stream_running, current_player_sid
392
- with stream_lock:
393
- if stream_thread is not None:
394
- stream_thread.stop()
395
- stream_thread.join(timeout=1.0)
396
- stream_thread = None
397
- stream_running = False
398
- current_player_sid = None
399
- # Notify all clients that the stream is available
400
- socketio.emit('stream_available', {'status': 'available'})
401
 
402
  @socketio.on_error_default
403
  def default_error_handler(e):
@@ -411,15 +403,20 @@ def handle_connect():
411
  sid = request.sid
412
  print(f'Client connected: {sid}')
413
 
414
- # Immediately tell the new client current readiness
 
 
 
415
  emit('server_status', {
416
  'ready': server_ready,
417
- 'busy': False
 
418
  })
419
  emit('connected', {
420
  'status': 'connected',
421
  'model_loaded': model is not None,
422
- 'ready': server_ready
 
423
  })
424
  except Exception as e:
425
  print(f"Error in handle_connect: {e}")
@@ -428,50 +425,73 @@ def handle_connect():
428
 
429
  @socketio.on('disconnect')
430
  def handle_disconnect(*args):
 
431
  sid = request.sid
432
  print(f'Client disconnected: {sid}')
433
- # If the current player disconnects, free up the stream
434
- with stream_lock:
435
- if current_player_sid == sid:
436
- print(f'Current player disconnected, freeing stream')
437
- stop_stream()
 
 
 
 
 
 
 
 
 
438
 
439
  @socketio.on('start_stream')
440
  def handle_start_stream(data):
 
441
  try:
442
  sid = request.sid
 
443
  if not server_ready:
444
  # Tell client to keep showing spinner
445
- emit('server_status', {'ready': server_ready, 'busy': False})
446
  return
447
 
448
- # Check if someone else is already playing
449
- with stream_lock:
450
- if stream_running and current_player_sid is not None and current_player_sid != sid:
451
- emit('error', {'message': 'Another player is currently using the stream. Please wait for them to finish.'})
452
- emit('stream_busy', {'current_player': current_player_sid[:8] if current_player_sid else 'unknown'})
 
 
 
 
 
453
  return
 
 
 
 
 
 
 
 
 
 
454
 
455
  n_steps = int(data.get('n_steps', 8))
456
  cfg = float(data.get('cfg', 0))
457
  fps = int(data.get('fps', 30))
458
  clamp = bool(data.get('clamp', True))
459
- print(f"Starting stream @ {fps} FPS (n_steps={n_steps}, cfg={cfg}, clamp={clamp}) for session {sid[:8]}")
460
  try:
461
- start_stream(n_steps=n_steps, cfg=cfg, fps=fps, clamp=clamp, player_sid=sid)
462
  emit('stream_started', {'status': 'ok'})
463
- # Notify other clients that stream is now busy
464
- socketio.emit('stream_busy', {'current_player': sid[:8]}, include_self=False)
465
- except RuntimeError as e:
466
- if "already using" in str(e):
467
- emit('error', {'message': str(e)})
468
- emit('stream_busy', {'current_player': current_player_sid[:8] if current_player_sid else 'unknown'})
469
- else:
470
- raise
471
  except Exception as e:
472
  print(f"Error starting stream: {e}")
473
  import traceback
474
  traceback.print_exc()
 
 
 
 
475
  emit('error', {'message': str(e)})
476
  except Exception as e:
477
  print(f"Error in handle_start_stream: {e}")
@@ -481,34 +501,43 @@ def handle_start_stream(data):
481
 
482
  @socketio.on('action')
483
  def handle_action(data):
484
- global latest_action, current_player_sid
485
  sid = request.sid
486
- # Only allow actions from the current player
 
 
 
 
 
 
487
  with stream_lock:
488
- if current_player_sid != sid:
489
- emit('error', {'message': 'You are not the current player. Please wait your turn.'})
490
- return
491
- action = int(data.get('action', 1))
492
  latest_action = action
493
  emit('action_ack', {'received': action, 'will_apply_to_frame_index': frame_index})
494
 
495
  @socketio.on('stop_stream')
496
  def handle_stop_stream():
 
497
  sid = request.sid
498
- print(f'Stopping stream for session {sid[:8]}')
499
- try:
500
- # Only allow the current player to stop
501
- with stream_lock:
502
- if current_player_sid != sid:
503
- emit('error', {'message': 'You are not the current player. Only the current player can stop the stream.'})
504
- return
505
- stop_stream()
506
- emit('stream_stopped', {'status': 'ok'})
507
- except Exception as e:
508
- print(f"Error stopping stream: {e}")
509
- import traceback
510
- traceback.print_exc()
511
- emit('error', {'message': f'Failed to stop stream: {str(e)}'})
 
 
 
 
 
 
512
 
513
  # --------------------------
514
  # Entrypoint
 
4
  Broadcasts readiness via Socket.IO so the frontend can auto-hide a loading overlay once the model is ready.
5
  """
6
 
 
 
 
 
7
  import sys
8
  import os
9
  import time
 
38
  # --------------------------
39
  app = Flask(__name__, static_folder='static')
40
  CORS(app)
41
+ # Configure SocketIO - use threading mode (no eventlet needed)
42
  socketio = SocketIO(
43
  app,
44
  cors_allowed_origins="*",
45
+ async_mode='threading',
46
  logger=False,
47
  engineio_logger=False,
48
  ping_timeout=60,
 
59
 
60
  server_ready = False # <--- readiness flag
61
 
62
+ # Single-user limitation
63
+ active_user_sid = None # Session ID of the active user
64
+ user_lock = threading.Lock() # Protects active_user_sid
65
+
66
  stream_lock = threading.Lock()
67
  stream_thread = None
68
  stream_running = False
 
69
  latest_action = 1 # 0=init, 1=nothing, 2=up, 3=down
70
  target_fps = 30
71
  frame_index = 0
 
147
 
148
  def _broadcast_ready():
149
  """Tell all clients whether the server is ready."""
150
+ socketio.emit('server_status', {'ready': server_ready})
151
 
152
  # --------------------------
153
  # Model init (pure eager) & warmup
 
368
  # --------------------------
369
  # Socket events & helpers
370
  # --------------------------
371
+ def start_stream(n_steps=8, cfg=0.0, fps=30, clamp=True):
372
+ global stream_thread, stream_running, frame_index, target_fps, latest_action
373
  if not server_ready:
374
  _broadcast_ready()
375
  raise RuntimeError("Server not ready")
376
  with stream_lock:
 
 
377
  stop_stream()
 
378
  target_fps = int(fps)
379
  frame_index = 0
380
  _reset_cache_fresh()
 
384
  stream_thread.start()
385
 
386
  def stop_stream():
387
+ global stream_thread, stream_running
388
+ if stream_thread is not None:
389
+ stream_thread.stop()
390
+ stream_thread.join(timeout=1.0)
391
+ stream_thread = None
392
+ stream_running = False
 
 
 
 
393
 
394
  @socketio.on_error_default
395
  def default_error_handler(e):
 
403
  sid = request.sid
404
  print(f'Client connected: {sid}')
405
 
406
+ with user_lock:
407
+ is_busy = active_user_sid is not None and active_user_sid != sid
408
+
409
+ # Immediately tell the new client current readiness and availability
410
  emit('server_status', {
411
  'ready': server_ready,
412
+ 'busy': is_busy,
413
+ 'is_active_user': not is_busy
414
  })
415
  emit('connected', {
416
  'status': 'connected',
417
  'model_loaded': model is not None,
418
+ 'ready': server_ready,
419
+ 'busy': is_busy
420
  })
421
  except Exception as e:
422
  print(f"Error in handle_connect: {e}")
 
425
 
426
  @socketio.on('disconnect')
427
  def handle_disconnect(*args):
428
+ global active_user_sid
429
  sid = request.sid
430
  print(f'Client disconnected: {sid}')
431
+
432
+ # Release the active user slot if this was the active user
433
+ with user_lock:
434
+ if active_user_sid == sid:
435
+ print(f'Active user {sid} disconnected, freeing slot')
436
+ active_user_sid = None
437
+ # Notify all other clients that server is now available
438
+ socketio.emit('server_status', {
439
+ 'ready': server_ready,
440
+ 'busy': False,
441
+ 'is_active_user': False
442
+ })
443
+
444
+ stop_stream()
445
 
446
  @socketio.on('start_stream')
447
  def handle_start_stream(data):
448
+ global active_user_sid
449
  try:
450
  sid = request.sid
451
+
452
  if not server_ready:
453
  # Tell client to keep showing spinner
454
+ emit('server_status', {'ready': server_ready})
455
  return
456
 
457
+ # Check if server is busy with another user
458
+ with user_lock:
459
+ if active_user_sid is not None and active_user_sid != sid:
460
+ emit('error', {'message': 'Server is currently being used by another user. Please wait.'})
461
+ emit('server_status', {
462
+ 'ready': server_ready,
463
+ 'busy': True,
464
+ 'is_active_user': False
465
+ })
466
+ emit('stream_busy', {'current_player': active_user_sid[:8] if active_user_sid else 'unknown'})
467
  return
468
+ # Claim the active user slot
469
+ active_user_sid = sid
470
+ print(f'User {sid} claimed active slot')
471
+
472
+ # Notify all clients about the new busy state
473
+ socketio.emit('server_status', {
474
+ 'ready': server_ready,
475
+ 'busy': True,
476
+ 'is_active_user': False
477
+ }, include_self=False)
478
 
479
  n_steps = int(data.get('n_steps', 8))
480
  cfg = float(data.get('cfg', 0))
481
  fps = int(data.get('fps', 30))
482
  clamp = bool(data.get('clamp', True))
483
+ print(f"Starting stream @ {fps} FPS (n_steps={n_steps}, cfg={cfg}, clamp={clamp})")
484
  try:
485
+ start_stream(n_steps=n_steps, cfg=cfg, fps=fps, clamp=clamp)
486
  emit('stream_started', {'status': 'ok'})
 
 
 
 
 
 
 
 
487
  except Exception as e:
488
  print(f"Error starting stream: {e}")
489
  import traceback
490
  traceback.print_exc()
491
+ # Release the slot on error
492
+ with user_lock:
493
+ if active_user_sid == sid:
494
+ active_user_sid = None
495
  emit('error', {'message': str(e)})
496
  except Exception as e:
497
  print(f"Error in handle_start_stream: {e}")
 
501
 
502
  @socketio.on('action')
503
  def handle_action(data):
504
+ global latest_action
505
  sid = request.sid
506
+
507
+ # Only accept actions from the active user
508
+ with user_lock:
509
+ if active_user_sid != sid:
510
+ return # Silently ignore actions from non-active users
511
+
512
+ action = int(data.get('action', 1))
513
  with stream_lock:
 
 
 
 
514
  latest_action = action
515
  emit('action_ack', {'received': action, 'will_apply_to_frame_index': frame_index})
516
 
517
  @socketio.on('stop_stream')
518
  def handle_stop_stream():
519
+ global active_user_sid
520
  sid = request.sid
521
+
522
+ # Only the active user can stop the stream
523
+ with user_lock:
524
+ if active_user_sid != sid:
525
+ return # Silently ignore stop requests from non-active users
526
+ # Release the active user slot
527
+ print(f'User {sid} stopped stream and released slot')
528
+ active_user_sid = None
529
+
530
+ # Notify all clients that server is now available
531
+ socketio.emit('server_status', {
532
+ 'ready': server_ready,
533
+ 'busy': False,
534
+ 'is_active_user': False
535
+ })
536
+ socketio.emit('stream_available', {'status': 'available'})
537
+
538
+ print('Stopping stream')
539
+ stop_stream()
540
+ emit('stream_stopped', {'status': 'ok'})
541
 
542
  # --------------------------
543
  # Entrypoint