File size: 3,390 Bytes
fa87992
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
# fix_stats_lock.py -- fixes BACKEND-2: add threading lock for stats cache
import re

with open('api/main.py', 'r', encoding='utf-8') as f:
    content = f.read()

# First: check what actually exists in the file around stats cache
idx = content.find('_stats_cache')
if idx == -1:
    print('ERROR: _stats_cache not found in main.py at all')
    exit()

# Show context
print('Current stats cache area:')
print(repr(content[idx-10:idx+200]))
print()

# Add threading import + lock after _stats_cache = None line
# Find the module-level _stats_cache declaration
import_added = False
if '_stats_lock' not in content:
    # Add threading lock after _STATS_TTL line
    old_ttl = '_STATS_TTL       = 60.0'
    if old_ttl in content:
        content = content.replace(
            old_ttl,
            old_ttl + '\nimport threading as _th\n_stats_lock = _th.Lock()'
        )
        import_added = True
        print('OK: _stats_lock added after _STATS_TTL')
    else:
        # Try alternate TTL variable name
        for candidate in ['_STATS_TTL = 60.0', '_STATS_TTL=60.0']:
            if candidate in content:
                content = content.replace(
                    candidate,
                    candidate + '\nimport threading as _th\n_stats_lock = _th.Lock()'
                )
                import_added = True
                print(f'OK: lock added after {candidate}')
                break
        if not import_added:
            print('WARNING: could not find _STATS_TTL -- adding lock near _stats_cache')
            content = content.replace(
                '_stats_cache     = None',
                '_stats_cache     = None\nimport threading as _th\n_stats_lock = _th.Lock()',
                1
            )
            import_added = True

# Add double-check pattern inside get_stats
# Find the early return and add lock before the Neo4j call
old_early_return = '    if _stats_cache is not None and'
if old_early_return in content:
    # Find the full early-return block
    start = content.find(old_early_return)
    # Find end of that if block (the return statement)
    end = content.find('\n', content.find('return _stats_cache', start)) + 1
    early_block = content[start:end]
    print('Found early return block:')
    print(repr(early_block))

    # After the early return, add lock + double-check
    old_driver_line = '    driver      = get_driver()'
    if old_driver_line in content:
        content = content.replace(
            old_driver_line,
            '    # BACKEND-2 FIX: lock prevents two concurrent requests both\n'
            '    # seeing _stats_cache is None and both running the full graph scan\n'
            '    with _stats_lock:\n'
            '        import time as _tc\n'
            '        if _stats_cache is not None and (_tc.monotonic() - _stats_cached_at) < _STATS_TTL:\n'
            '            return _stats_cache\n'
            '    driver      = get_driver()',
            1
        )
        print('OK: double-check lock added before get_driver()')
    else:
        print('WARNING: "driver = get_driver()" not found in get_stats')
else:
    print('WARNING: early return not found')

with open('api/main.py', 'w', encoding='utf-8') as f:
    f.write(content)

# Verify syntax
import ast
try:
    ast.parse(content)
    print('SYNTAX OK')
except SyntaxError as e:
    print(f'SYNTAX ERROR at line {e.lineno}: {e.msg}')