riftenlabs-indexer/linters/rpc_write_lock_check.py
Dagur Valberg Johannsson e1ebc853f5 Fix potential API hangs from write lock contention
- 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
2026-02-02 21:01:50 +01:00

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()