mirror of
https://github.com/BigBodyCobain/Shadowbroker.git
synced 2026-07-05 11:57:56 +02:00
71a2ef4ce7
get_latest_data_deepcopy_snapshot deep-copies layers outside the data lock; a writer mutating a nested object in place races it and raises "dictionary changed size during iteration" (500 on /api/health, /api/live-data). Two changes: (1) _merge_sigint_snapshot now shallow-copies each entry so latest_data["sigint"] no longer aliases the SIGINT bridge dicts or the meshtastic_map_nodes layer (the concrete offender); (2) the snapshot retries a few times as defense-in-depth for any other in-place mutator. Plus regression tests.
48 lines
1.6 KiB
Python
48 lines
1.6 KiB
Python
"""The full-store snapshot must survive a transient concurrent-mutation race.
|
|
|
|
``get_latest_data_deepcopy_snapshot`` deep-copies each top-level layer outside
|
|
the data lock. If a misbehaving writer mutates a nested object in place during
|
|
the copy, ``copy.deepcopy`` raises ``RuntimeError: dictionary changed size
|
|
during iteration``. The snapshot retries a few times (the mutation window is
|
|
tiny) so /api/health and /api/live-data do not 500 on a transient race.
|
|
"""
|
|
|
|
import copy
|
|
|
|
from services.fetchers import _store
|
|
|
|
|
|
def test_snapshot_retries_then_succeeds(monkeypatch):
|
|
real_deepcopy = copy.deepcopy
|
|
calls = {"n": 0}
|
|
|
|
def flaky_deepcopy(value, *args, **kwargs):
|
|
calls["n"] += 1
|
|
# Fail only on the very first deepcopy call, then behave normally.
|
|
if calls["n"] == 1:
|
|
raise RuntimeError("dictionary changed size during iteration")
|
|
return real_deepcopy(value, *args, **kwargs)
|
|
|
|
monkeypatch.setattr(_store.copy, "deepcopy", flaky_deepcopy)
|
|
|
|
snapshot = _store.get_latest_data_deepcopy_snapshot()
|
|
|
|
assert isinstance(snapshot, dict)
|
|
assert calls["n"] >= 2 # it retried after the simulated race
|
|
|
|
|
|
def test_snapshot_reraises_if_race_never_clears(monkeypatch):
|
|
def always_racing(value, *args, **kwargs):
|
|
raise RuntimeError("dictionary changed size during iteration")
|
|
|
|
monkeypatch.setattr(_store.copy, "deepcopy", always_racing)
|
|
|
|
# A persistent (non-transient) violation is a real bug — surface it rather
|
|
# than hang or return corrupt data.
|
|
raised = False
|
|
try:
|
|
_store.get_latest_data_deepcopy_snapshot()
|
|
except RuntimeError:
|
|
raised = True
|
|
assert raised
|