Update app.py
Browse files
app.py
CHANGED
|
@@ -10,7 +10,7 @@ from typing import Dict, Any, Optional
|
|
| 10 |
import threading
|
| 11 |
import asyncio
|
| 12 |
import signal
|
| 13 |
-
from concurrent.futures import ThreadPoolExecutor
|
| 14 |
from fastapi import FastAPI, WebSocket, WebSocketDisconnect, Request
|
| 15 |
from pydantic import BaseModel
|
| 16 |
from starlette.responses import HTMLResponse
|
|
@@ -384,70 +384,31 @@ class SecurePythonExecutor:
|
|
| 384 |
result = {'success': False, 'output': '', 'error': ''}
|
| 385 |
print(f"DEBUG: run_with_timeout called. Client: {websocket_io.websocket.client}")
|
| 386 |
|
| 387 |
-
# Store original stdin/stdout/stderr
|
| 388 |
-
original_stdin = sys.stdin
|
| 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
|
| 394 |
# This function runs in a separate thread managed by the event loop's executor
|
| 395 |
nonlocal result
|
| 396 |
-
|
| 397 |
-
|
| 398 |
-
|
| 399 |
-
|
| 400 |
-
|
| 401 |
-
|
| 402 |
-
|
| 403 |
-
|
| 404 |
-
|
| 405 |
-
|
| 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
|
| 445 |
-
# Run the synchronous
|
| 446 |
await asyncio.wait_for(
|
| 447 |
-
loop.run_in_executor(None,
|
| 448 |
timeout=self.timeout
|
| 449 |
)
|
| 450 |
-
print("DEBUG:
|
| 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"
|
|
@@ -457,7 +418,7 @@ class SecurePythonExecutor:
|
|
| 457 |
except Exception:
|
| 458 |
pass # Ignore if websocket is already closed
|
| 459 |
except Exception as e:
|
| 460 |
-
# This catches exceptions from
|
| 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)}"
|
|
@@ -468,7 +429,7 @@ class SecurePythonExecutor:
|
|
| 468 |
pass
|
| 469 |
|
| 470 |
# After execution (or timeout), collect output
|
| 471 |
-
if result['success']: # Only if execution was successful in
|
| 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()
|
|
@@ -523,17 +484,17 @@ async def websocket_endpoint(websocket: WebSocket):
|
|
| 523 |
|
| 524 |
if data["type"] == "code":
|
| 525 |
code = data["content"]
|
| 526 |
-
print(f"Received code from {websocket.client}: {code[:50]}...")
|
| 527 |
# Execute code asynchronously in the background
|
| 528 |
asyncio.create_task(executor.execute_code(code, websocket_io))
|
| 529 |
elif data["type"] == "input":
|
| 530 |
user_input = data["content"]
|
| 531 |
-
print(f"Received input from {websocket.client}: {user_input.strip()[:50]}...")
|
| 532 |
await websocket_io.input_queue.put(user_input + "\n") # Add newline for readline
|
| 533 |
elif data["type"] == "ping":
|
| 534 |
# Respond to pings to keep connection alive
|
| 535 |
await websocket.send_text(json.dumps({"type": "pong"}))
|
| 536 |
-
print(f"DEBUG: Sent pong to {websocket.client}")
|
| 537 |
else:
|
| 538 |
await websocket_io.write(f"Unknown message type: {data['type']}\n")
|
| 539 |
|
|
@@ -551,29 +512,4 @@ async def websocket_endpoint(websocket: WebSocket):
|
|
| 551 |
websocket_io.input_queue.get_nowait()
|
| 552 |
except asyncio.QueueEmpty:
|
| 553 |
pass
|
| 554 |
-
print(f"WebSocket connection closed for {websocket_io.websocket.client}")
|
| 555 |
-
|
| 556 |
-
# This block is for local development using `uvicorn`
|
| 557 |
-
# Hugging Face Spaces will use `uvicorn` directly via the Dockerfile CMD
|
| 558 |
-
if __name__ == "__main__":
|
| 559 |
-
import uvicorn
|
| 560 |
-
print("Starting Interactive Python Code Executor API (FastAPI)...")
|
| 561 |
-
print(f"Python version: {sys.version}")
|
| 562 |
-
print(f"Working directory: {os.getcwd()}")
|
| 563 |
-
|
| 564 |
-
server_name = os.getenv('FASTAPI_SERVER_NAME', '0.0.0.0')
|
| 565 |
-
server_port = int(os.getenv('FASTAPI_SERVER_PORT', 7860))
|
| 566 |
-
|
| 567 |
-
print(f"Starting server on {server_name}:{server_port}")
|
| 568 |
-
|
| 569 |
-
try:
|
| 570 |
-
uvicorn.run(
|
| 571 |
-
"app:app", # app:app means the `app` object in `app.py`
|
| 572 |
-
host=server_name,
|
| 573 |
-
port=server_port,
|
| 574 |
-
reload=False, # Set to True for local dev to auto-reload on code changes
|
| 575 |
-
log_level="info"
|
| 576 |
-
)
|
| 577 |
-
except Exception as e:
|
| 578 |
-
print(f"Failed to start server: {e}")
|
| 579 |
-
sys.exit(1)
|
|
|
|
| 10 |
import threading
|
| 11 |
import asyncio
|
| 12 |
import signal
|
| 13 |
+
from concurrent.futures import ThreadPoolExecutor
|
| 14 |
from fastapi import FastAPI, WebSocket, WebSocketDisconnect, Request
|
| 15 |
from pydantic import BaseModel
|
| 16 |
from starlette.responses import HTMLResponse
|
|
|
|
| 384 |
result = {'success': False, 'output': '', 'error': ''}
|
| 385 |
print(f"DEBUG: run_with_timeout called. Client: {websocket_io.websocket.client}")
|
| 386 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 387 |
# Define the synchronous target function to be run in the executor thread
|
| 388 |
+
def _execute_in_thread_test1():
|
| 389 |
# This function runs in a separate thread managed by the event loop's executor
|
| 390 |
nonlocal result
|
| 391 |
+
|
| 392 |
+
# --- START TEST 1 MODIFICATIONS ---
|
| 393 |
+
# DO NOT redirect sys.stdin/stdout/stderr here yet
|
| 394 |
+
print(f"DEBUG: Executor thread '{threading.current_thread().name}' started. (No IO redirected yet)") # UPDATED PRINT
|
| 395 |
+
result['success'] = True
|
| 396 |
+
# Do not call exec yet. Just write directly to output_buffer for testing.
|
| 397 |
+
websocket_io.output_buffer.write("DEBUG: Test message from executor thread (no exec).\n") # Write to buffer directly
|
| 398 |
+
# Do not call asyncio.run(websocket_io.write) here. The main loop will check buffer.
|
| 399 |
+
print("DEBUG: _execute_in_thread finished with direct buffer write.") # ADDED THIS LINE
|
| 400 |
+
# --- END TEST 1 MODIFICATIONS ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 401 |
|
| 402 |
# Use the event loop's default ThreadPoolExecutor to run the synchronous code
|
| 403 |
loop = asyncio.get_running_loop()
|
| 404 |
try:
|
| 405 |
+
print(f"DEBUG: Scheduling _execute_in_thread_test1 in executor with timeout {self.timeout}s.")
|
| 406 |
+
# Run the synchronous _execute_in_thread_test1 in a separate thread
|
| 407 |
await asyncio.wait_for(
|
| 408 |
+
loop.run_in_executor(None, _execute_in_thread_test1), # None uses the default ThreadPoolExecutor
|
| 409 |
timeout=self.timeout
|
| 410 |
)
|
| 411 |
+
print("DEBUG: _execute_in_thread_test1 completed via executor.")
|
| 412 |
except asyncio.TimeoutError:
|
| 413 |
print(f"ERROR: Code execution timed out after {self.timeout} seconds in run_in_executor.")
|
| 414 |
result['error'] = f"Code execution timed out after {self.timeout} seconds"
|
|
|
|
| 418 |
except Exception:
|
| 419 |
pass # Ignore if websocket is already closed
|
| 420 |
except Exception as e:
|
| 421 |
+
# This catches exceptions from _execute_in_thread_test1 that propagate up
|
| 422 |
# (e.g., if the thread itself crashes in an unrecoverable way before our try/except)
|
| 423 |
print(f"ERROR: Unexpected exception from executor: {e}")
|
| 424 |
result['error'] = f"Unexpected executor error: {type(e).__name__}: {str(e)}"
|
|
|
|
| 429 |
pass
|
| 430 |
|
| 431 |
# After execution (or timeout), collect output
|
| 432 |
+
if result['success']: # Only if execution was successful in _execute_in_thread_test1
|
| 433 |
result['output'] = websocket_io.output_buffer.getvalue()
|
| 434 |
if websocket_io.error_buffer.getvalue(): # Error buffer always checked
|
| 435 |
result['error'] = result['error'] + "\n" + websocket_io.error_buffer.getvalue() if result['error'] else websocket_io.error_buffer.getvalue()
|
|
|
|
| 484 |
|
| 485 |
if data["type"] == "code":
|
| 486 |
code = data["content"]
|
| 487 |
+
print(f"Received code from {websocket_io.websocket.client}: {code[:50]}...") # Use websocket_io.websocket.client here
|
| 488 |
# Execute code asynchronously in the background
|
| 489 |
asyncio.create_task(executor.execute_code(code, websocket_io))
|
| 490 |
elif data["type"] == "input":
|
| 491 |
user_input = data["content"]
|
| 492 |
+
print(f"Received input from {websocket_io.websocket.client}: {user_input.strip()[:50]}...") # Use websocket_io.websocket.client here
|
| 493 |
await websocket_io.input_queue.put(user_input + "\n") # Add newline for readline
|
| 494 |
elif data["type"] == "ping":
|
| 495 |
# Respond to pings to keep connection alive
|
| 496 |
await websocket.send_text(json.dumps({"type": "pong"}))
|
| 497 |
+
print(f"DEBUG: Sent pong to {websocket_io.websocket.client}") # Use websocket_io.websocket.client here
|
| 498 |
else:
|
| 499 |
await websocket_io.write(f"Unknown message type: {data['type']}\n")
|
| 500 |
|
|
|
|
| 512 |
websocket_io.input_queue.get_nowait()
|
| 513 |
except asyncio.QueueEmpty:
|
| 514 |
pass
|
| 515 |
+
print(f"WebSocket connection closed for {websocket_io.websocket.client}")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|