Srevarshan1502 commited on
Commit
987d7ef
·
verified ·
1 Parent(s): 28dd64f

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +48 -39
app.py CHANGED
@@ -10,6 +10,7 @@ from typing import Dict, Any, Optional
10
  import threading
11
  import asyncio
12
  import signal
 
13
  from fastapi import FastAPI, WebSocket, WebSocketDisconnect, Request
14
  from pydantic import BaseModel
15
  from starlette.responses import HTMLResponse
@@ -77,6 +78,7 @@ class WebSocketInputOutput:
77
  """Synchronous wrapper for readline for use with exec."""
78
  try:
79
  print("DEBUG: Synchronous input wrapper called.")
 
80
  return asyncio.run(self.readline())
81
  except Exception as e:
82
  print(f"ERROR: Synchronous input wrapper error: {e}")
@@ -387,17 +389,16 @@ class SecurePythonExecutor:
387
  original_stdout = sys.stdout
388
  original_stderr = sys.stderr
389
 
390
- # Use a threading.Event to signal completion from the execution thread
391
- execution_finished_event = threading.Event()
392
-
393
- def target():
394
  nonlocal result
395
  try:
396
  # Redirect sys.stdin, sys.stdout, sys.stderr for this thread
397
  sys.stdin = websocket_io
398
  sys.stdout = websocket_io
399
  sys.stderr = websocket_io
400
- print(f"DEBUG: Thread '{threading.current_thread().name}' started. IO redirected.")
401
 
402
  # Create restricted execution environment
403
  restricted_globals = {
@@ -405,65 +406,73 @@ class SecurePythonExecutor:
405
  '__name__': '__main__',
406
  '__doc__': None,
407
  }
408
- print("DEBUG: Restricted globals prepared.") # ADDED THIS LINE
409
 
410
- print(f"DEBUG: Preparing to execute code (thread): {code[:100]}...")
411
  exec(code, restricted_globals)
412
- print("DEBUG: Code execution completed successfully in thread.")
413
  result['success'] = True
414
 
415
  except EOFError as e:
416
  # This happens if the WebSocket disconnects during input()
417
  result['error'] = f"Input stream closed: {str(e)}"
418
  result['success'] = False
419
- print(f"ERROR: EOFError during execution in thread: {e}")
420
  except Exception as e:
421
  error_msg = f"{type(e).__name__}: {str(e)}\n{traceback.format_exc()}"
422
- print(f"ERROR: Unhandled exception during execution in thread: {error_msg}")
423
- # Write error to WebSocket and internal buffer
424
  try:
425
- # Use asyncio.run for writing within a synchronous thread, if necessary
426
- # This is generally safe if it's a new event loop per call or in a separate thread.
427
  asyncio.run(websocket_io.write(f"Error: {error_msg}\n"))
428
- print("DEBUG: Attempted to write error via websocket_io.write from thread.")
429
  except Exception as write_e:
430
- print(f"ERROR: Failed to write error to WebSocket from thread (inner): {write_e}")
431
  websocket_io.error_buffer.write(error_msg)
432
  result['success'] = False
433
  finally:
434
- # Restore original stdin/stdout/stderr for the thread pool (if applicable)
435
  sys.stdin = original_stdin
436
  sys.stdout = original_stdout
437
  sys.stderr = original_stderr
438
- execution_finished_event.set() # Signal completion
439
- print(f"DEBUG: Thread '{threading.current_thread().name}' finished. IO restored.")
440
 
441
- # Run the execution in a separate thread to allow for timeout
442
- thread = threading.Thread(target=target, name=f"CodeExecutorThread-{id(websocket_io)}")
443
- thread.daemon = True # Allow the program to exit even if thread is running
444
- thread.start()
445
- print(f"DEBUG: Code execution thread '{thread.name}' started. Waiting for join.")
446
 
447
- # Wait for the thread to finish or timeout
448
- thread.join(timeout=self.timeout)
449
-
450
- if thread.is_alive():
451
- print(f"DEBUG: Thread '{thread.name}' timed out after {self.timeout} seconds.")
 
 
 
 
 
 
 
452
  result['error'] = f"Code execution timed out after {self.timeout} seconds"
453
  result['success'] = False
454
- # Attempt to send timeout message to client
455
  try:
456
  await websocket_io.write(f"Error: {result['error']}\n")
457
  except Exception:
458
  pass # Ignore if websocket is already closed
459
- else:
460
- print(f"DEBUG: Thread '{thread.name}' finished within timeout.")
 
 
 
 
 
 
 
 
 
 
 
461
  result['output'] = websocket_io.output_buffer.getvalue()
462
- if websocket_io.error_buffer.getvalue():
463
- result['error'] = websocket_io.error_buffer.getvalue()
464
- result['success'] = False
465
- else:
466
- result['success'] = True
467
 
468
  # Send a final message to the client indicating execution is complete
469
  try:
@@ -474,7 +483,7 @@ class SecurePythonExecutor:
474
  print("DEBUG: WebSocketDisconnect when sending execution_complete.")
475
  pass # Ignore if websocket is already closed
476
  except Exception as e:
477
- print(f"ERROR: Error sending execution_complete message: {e}") # Warning if this fails
478
 
479
  print(f"DEBUG: run_with_timeout returning result for client: {websocket_io.websocket.client}")
480
  return result
@@ -495,7 +504,7 @@ app = FastAPI(
495
  async def get_index():
496
  """Serves the interactive frontend."""
497
  # This assumes index.html is in the same directory as app.py
498
- # [cite_start]Explicitly copy index.html to /app/index.html to avoid any ambiguity with '.' [cite: 5, 6]
499
  with open("index.html", "r") as f:
500
  return HTMLResponse(content=f.read())
501
 
@@ -542,7 +551,7 @@ async def websocket_endpoint(websocket: WebSocket):
542
  websocket_io.input_queue.get_nowait()
543
  except asyncio.QueueEmpty:
544
  pass
545
- print(f"WebSocket connection closed for {websocket.client}")
546
 
547
  # This block is for local development using `uvicorn`
548
  # Hugging Face Spaces will use `uvicorn` directly via the Dockerfile CMD
 
10
  import threading
11
  import asyncio
12
  import signal
13
+ from concurrent.futures import ThreadPoolExecutor # NEW: for loop.run_in_executor
14
  from fastapi import FastAPI, WebSocket, WebSocketDisconnect, Request
15
  from pydantic import BaseModel
16
  from starlette.responses import HTMLResponse
 
78
  """Synchronous wrapper for readline for use with exec."""
79
  try:
80
  print("DEBUG: Synchronous input wrapper called.")
81
+ # This is called from the executor thread, so asyncio.run is appropriate here
82
  return asyncio.run(self.readline())
83
  except Exception as e:
84
  print(f"ERROR: Synchronous input wrapper error: {e}")
 
389
  original_stdout = sys.stdout
390
  original_stderr = sys.stderr
391
 
392
+ # Define the synchronous target function to be run in the executor thread
393
+ def _execute_in_thread():
394
+ # This function runs in a separate thread managed by the event loop's executor
 
395
  nonlocal result
396
  try:
397
  # Redirect sys.stdin, sys.stdout, sys.stderr for this thread
398
  sys.stdin = websocket_io
399
  sys.stdout = websocket_io
400
  sys.stderr = websocket_io
401
+ print(f"DEBUG: Executor thread '{threading.current_thread().name}' started. IO redirected.")
402
 
403
  # Create restricted execution environment
404
  restricted_globals = {
 
406
  '__name__': '__main__',
407
  '__doc__': None,
408
  }
409
+ print("DEBUG: Restricted globals prepared in executor thread.") # ADDED THIS LINE
410
 
411
+ print(f"DEBUG: Preparing to execute code (executor thread): {code[:100]}...")
412
  exec(code, restricted_globals)
413
+ print("DEBUG: Code execution completed successfully in executor thread.")
414
  result['success'] = True
415
 
416
  except EOFError as e:
417
  # This happens if the WebSocket disconnects during input()
418
  result['error'] = f"Input stream closed: {str(e)}"
419
  result['success'] = False
420
+ print(f"ERROR: EOFError during execution in executor thread: {e}")
421
  except Exception as e:
422
  error_msg = f"{type(e).__name__}: {str(e)}\n{traceback.format_exc()}"
423
+ print(f"ERROR: Unhandled exception during execution in executor thread: {error_msg}")
 
424
  try:
425
+ # If this write fails, it's still good to log the error to stderr buffer
426
+ # This asyncio.run call is crucial and runs a new event loop for this async call
427
  asyncio.run(websocket_io.write(f"Error: {error_msg}\n"))
428
+ print("DEBUG: Attempted to write error via websocket_io.write from executor thread.")
429
  except Exception as write_e:
430
+ print(f"ERROR: Failed to write error to WebSocket from executor thread (inner): {write_e}")
431
  websocket_io.error_buffer.write(error_msg)
432
  result['success'] = False
433
  finally:
434
+ # Restore original stdin/stdout/stderr for the thread pool
435
  sys.stdin = original_stdin
436
  sys.stdout = original_stdout
437
  sys.stderr = original_stderr
438
+ print(f"DEBUG: Executor thread '{threading.current_thread().name}' finished. IO restored.")
 
439
 
 
 
 
 
 
440
 
441
+ # Use the event loop's default ThreadPoolExecutor to run the synchronous code
442
+ loop = asyncio.get_running_loop()
443
+ try:
444
+ print(f"DEBUG: Scheduling _execute_in_thread in executor with timeout {self.timeout}s.")
445
+ # Run the synchronous _execute_in_thread in a separate thread
446
+ await asyncio.wait_for(
447
+ loop.run_in_executor(None, _execute_in_thread), # None uses the default ThreadPoolExecutor
448
+ timeout=self.timeout
449
+ )
450
+ print("DEBUG: _execute_in_thread completed via executor.")
451
+ except asyncio.TimeoutError:
452
+ print(f"ERROR: Code execution timed out after {self.timeout} seconds in run_in_executor.")
453
  result['error'] = f"Code execution timed out after {self.timeout} seconds"
454
  result['success'] = False
 
455
  try:
456
  await websocket_io.write(f"Error: {result['error']}\n")
457
  except Exception:
458
  pass # Ignore if websocket is already closed
459
+ except Exception as e:
460
+ # This catches exceptions from _execute_in_thread that propagate up
461
+ # (e.g., if the thread itself crashes in an unrecoverable way before our try/except)
462
+ print(f"ERROR: Unexpected exception from executor: {e}")
463
+ result['error'] = f"Unexpected executor error: {type(e).__name__}: {str(e)}"
464
+ result['success'] = False
465
+ try:
466
+ await websocket_io.write(f"Error: {result['error']}\n")
467
+ except Exception:
468
+ pass
469
+
470
+ # After execution (or timeout), collect output
471
+ if result['success']: # Only if execution was successful in _execute_in_thread
472
  result['output'] = websocket_io.output_buffer.getvalue()
473
+ if websocket_io.error_buffer.getvalue(): # Error buffer always checked
474
+ result['error'] = result['error'] + "\n" + websocket_io.error_buffer.getvalue() if result['error'] else websocket_io.error_buffer.getvalue()
475
+ result['success'] = False # Ensure success is false if there's any error output
 
 
476
 
477
  # Send a final message to the client indicating execution is complete
478
  try:
 
483
  print("DEBUG: WebSocketDisconnect when sending execution_complete.")
484
  pass # Ignore if websocket is already closed
485
  except Exception as e:
486
+ print(f"ERROR: Error sending execution_complete message: {e}")
487
 
488
  print(f"DEBUG: run_with_timeout returning result for client: {websocket_io.websocket.client}")
489
  return result
 
504
  async def get_index():
505
  """Serves the interactive frontend."""
506
  # This assumes index.html is in the same directory as app.py
507
+ # Explicitly copy index.html to /app/index.html to avoid any ambiguity with '.'
508
  with open("index.html", "r") as f:
509
  return HTMLResponse(content=f.read())
510
 
 
551
  websocket_io.input_queue.get_nowait()
552
  except asyncio.QueueEmpty:
553
  pass
554
+ print(f"WebSocket connection closed for {websocket_io.websocket.client}") # Use websocket_io.websocket.client here too
555
 
556
  # This block is for local development using `uvicorn`
557
  # Hugging Face Spaces will use `uvicorn` directly via the Dockerfile CMD