- Refactor update_changes_score_volume_and_ranking to use read connections for read/compute phases, only acquiring write connection for final writes - Change first_pool_creation to use try_get() for opportunistic caching, preventing thread exhaustion if write pool is busy - Add linter to flag blocking _w.get() calls in RPC handlers
80 lines
2.7 KiB
Python
Executable file
80 lines
2.7 KiB
Python
Executable file
#!/usr/bin/env python3
|
|
# Copyright (C) 2024-2026 Whiterun LLC,
|
|
# This software is licensed under the GNU Affero General Public License (AGPL), version 3.0 or later.,
|
|
# A copy of the license can be found in the LICENSE file or at https://www.gnu.org/licenses/agpl-3.0.html
|
|
|
|
import os
|
|
import sys
|
|
import re
|
|
|
|
"""
|
|
Disallow blocking write pool access (_w.get()) in RPC handlers.
|
|
|
|
Blocking .get() calls on write pools can exhaust Rocket's thread pool if a
|
|
background task holds the write lock for an extended period. All threads
|
|
waiting on .get() will block, causing the entire API to hang.
|
|
|
|
Use try_get() instead, which returns None immediately if the lock is unavailable.
|
|
|
|
OK (non-blocking):
|
|
if let Some(cw) = dbp.cauldron_w.try_get() { ... }
|
|
|
|
NOT OK (blocks thread, can cause API hang):
|
|
let cw = dbp.cauldron_w.get()?;
|
|
"""
|
|
|
|
OUR_PATH = os.path.dirname(os.path.realpath(__file__))
|
|
|
|
# Pattern to detect blocking write pool access: something_w.get()
|
|
FORBIDDEN_PATTERN = re.compile(r"\b\w+_w\.get\(\)")
|
|
|
|
|
|
def check_file_for_blocking_write_lock(file_path):
|
|
"""Check if the file contains blocking _w.get() calls outside of test modules."""
|
|
violations = []
|
|
|
|
with open(file_path, 'r', encoding='utf-8') as file:
|
|
for line_num, line in enumerate(file, 1):
|
|
# Stop checking if we enter test module
|
|
if re.search(r"\bmod tests \{", line):
|
|
break
|
|
|
|
if FORBIDDEN_PATTERN.search(line):
|
|
violations.append((line_num, line.strip()))
|
|
|
|
return violations
|
|
|
|
|
|
def check_rpc_files():
|
|
"""
|
|
Traverse src/rpc and check each .rs file for blocking write lock access.
|
|
"""
|
|
rpc_dir = os.path.join(OUR_PATH, "..", "src", "rpc")
|
|
found_violations = False
|
|
|
|
for root, dirs, files in os.walk(rpc_dir):
|
|
for file in files:
|
|
if file.endswith('.rs'):
|
|
file_path = os.path.join(root, file)
|
|
violations = check_file_for_blocking_write_lock(file_path)
|
|
|
|
if violations:
|
|
found_violations = True
|
|
rel_path = os.path.relpath(file_path, os.path.join(OUR_PATH, ".."))
|
|
for line_num, line in violations:
|
|
print(f"{rel_path}:{line_num}: blocking _w.get() found")
|
|
print(f" {line}")
|
|
print(f" hint: use try_get() instead to avoid thread exhaustion")
|
|
print()
|
|
|
|
if found_violations:
|
|
print("ERROR: Blocking write pool access found in RPC handlers.")
|
|
print("This can cause API hangs if a background task holds the write lock.")
|
|
sys.exit(1)
|
|
else:
|
|
print("OK")
|
|
sys.exit(0)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
check_rpc_files()
|