Skip to content

Commit 997ea4e

Browse files
committed
More improvements to possible blocking/hanging
1 parent 30f950d commit 997ea4e

File tree

2 files changed

+63
-16
lines changed

2 files changed

+63
-16
lines changed

backend/http_server.py

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,40 @@
88
async def handle_http_request(request, stats):
99
# Create a task with timeout to prevent blocking during high load
1010
try:
11-
# Run the potentially CPU-intensive operations in a separate thread
11+
# Run the potentially CPU-intensive operations in a separate thread with timeouts
1212
loop = asyncio.get_event_loop()
13-
svmem = await loop.run_in_executor(None, psutil.virtual_memory)
14-
parent = psutil.Process(os.getpid())
15-
child_count = await loop.run_in_executor(None, partial(len, parent.children(recursive=False)))
13+
14+
# Set timeout for system operations (3 seconds)
15+
TIMEOUT = 3
16+
17+
# Use asyncio.wait_for to enforce timeouts on executor tasks
18+
try:
19+
svmem_task = asyncio.create_task(loop.run_in_executor(None, psutil.virtual_memory))
20+
svmem = await asyncio.wait_for(svmem_task, timeout=TIMEOUT)
21+
22+
parent = psutil.Process(os.getpid())
23+
child_count_task = asyncio.create_task(loop.run_in_executor(None, partial(len, parent.children(recursive=False))))
24+
child_count = await asyncio.wait_for(child_count_task, timeout=TIMEOUT)
25+
26+
mem_use_percent = svmem.percent
27+
28+
except asyncio.TimeoutError:
29+
# If system calls timeout, use fallback values
30+
logger.warning("System monitoring calls timed out in stats endpoint")
31+
child_count = stats.get('last_child_count', 0)
32+
mem_use_percent = stats.get('last_mem_percent', 0)
33+
else:
34+
# Save latest successful values for future fallback
35+
stats['last_child_count'] = child_count
36+
stats['last_mem_percent'] = mem_use_percent
1637

1738
data = {
1839
'active_connections': stats['connection_count'],
1940
'child_count': child_count,
2041
'connection_count_total': stats['connection_count_total'],
2142
'dropped_threshold_reached': stats['dropped_threshold_reached'],
2243
'dropped_waited_too_long': stats['dropped_waited_too_long'],
23-
'mem_use_percent': svmem.percent,
44+
'mem_use_percent': mem_use_percent,
2445
'special_counter_len': len(stats['special_counter']),
2546
}
2647

backend/server.py

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -243,15 +243,32 @@ async def launchPuppeteerChromeProxy(websocket, path):
243243
f"WebSocket ID: {websocket.id} - Throttling/waiting, max connection limit reached {stats['connection_count']} of max {connection_count_max} ({time.time() - now:.1f}s)")
244244

245245
if DROP_EXCESS_CONNECTIONS:
246-
while svmem.percent > memory_use_limit_percent:
247-
logger.warning(f"WebSocket ID: {websocket.id} - {svmem.percent}% was > {memory_use_limit_percent}%.. delaying connecting and waiting for more free RAM ({time.time() - now:.1f}s)")
248-
await asyncio.sleep(5)
249-
if time.time() - now > 60:
250-
logger.critical(
251-
f"WebSocket ID: {websocket.id} - Too long waiting for memory usage to drop, dropping connection. {svmem.percent}% was > {memory_use_limit_percent}% ({time.time() - now:.1f}s)")
252-
await close_socket(websocket)
253-
stats['dropped_threshold_reached'] += 1
254-
return
246+
# Run memory check in executor with timeout to prevent blocking
247+
try:
248+
loop = asyncio.get_event_loop()
249+
svmem = await asyncio.wait_for(loop.run_in_executor(None, psutil.virtual_memory), timeout=2.0)
250+
251+
while svmem.percent > memory_use_limit_percent:
252+
logger.warning(f"WebSocket ID: {websocket.id} - {svmem.percent}% was > {memory_use_limit_percent}%.. delaying connecting and waiting for more free RAM ({time.time() - now:.1f}s)")
253+
await asyncio.sleep(5)
254+
255+
# Get updated memory info with timeout protection
256+
try:
257+
svmem = await asyncio.wait_for(loop.run_in_executor(None, psutil.virtual_memory), timeout=2.0)
258+
except asyncio.TimeoutError:
259+
logger.warning(f"WebSocket ID: {websocket.id} - Memory check timed out, assuming high memory usage")
260+
svmem = type('obj', (object,), {'percent': 100}) # Default to high value if timeout
261+
262+
if time.time() - now > 60:
263+
logger.critical(
264+
f"WebSocket ID: {websocket.id} - Too long waiting for memory usage to drop, dropping connection. {svmem.percent}% was > {memory_use_limit_percent}% ({time.time() - now:.1f}s)")
265+
await close_socket(websocket)
266+
stats['dropped_threshold_reached'] += 1
267+
return
268+
except asyncio.TimeoutError:
269+
logger.warning(f"WebSocket ID: {websocket.id} - Initial memory check timed out, skipping memory check")
270+
except Exception as e:
271+
logger.error(f"WebSocket ID: {websocket.id} - Error checking memory: {str(e)}, skipping memory check")
255272

256273
# Connections that joined but had to wait a long time before being processed
257274
if DROP_EXCESS_CONNECTIONS:
@@ -373,12 +390,21 @@ async def stats_thread_func():
373390
global shutdown
374391

375392
while True:
393+
# Log connection stats first, so we get at least this message even if memory check blocks
376394
logger.info(f"Connections: Active count {stats['connection_count']} of max {connection_count_max}, Total processed: {stats['connection_count_total']}.")
377395
if stats['connection_count'] > connection_count_max:
378396
logger.warning(f"{stats['connection_count']} of max {connection_count_max} over threshold, incoming connections will be delayed.")
379397

380-
svmem = psutil.virtual_memory()
381-
logger.info(f"Memory: Used {svmem.percent}% (Limit {memory_use_limit_percent}%) - Available {svmem.free / 1024 / 1024:.1f}MB.")
398+
# Run potentially blocking system calls in thread executor with timeout
399+
try:
400+
# Run directly in executor without wrapping in create_task
401+
loop = asyncio.get_event_loop()
402+
svmem = await asyncio.wait_for(loop.run_in_executor(None, psutil.virtual_memory), timeout=2.0)
403+
logger.info(f"Memory: Used {svmem.percent}% (Limit {memory_use_limit_percent}%) - Available {svmem.free / 1024 / 1024:.1f}MB.")
404+
except asyncio.TimeoutError:
405+
logger.warning("Memory stats check timed out, skipping this iteration")
406+
except Exception as e:
407+
logger.error(f"Error getting memory stats: {str(e)}")
382408

383409
await asyncio.sleep(stats_refresh_time)
384410

0 commit comments

Comments
 (0)