v3.28.0 audit: the mid-equip race
The review of the connection-check fix found the second-order race it opened: _apply_equipped_card sets _CARD_CONNECTED = True and bumps card_session *before* it clears and reads the ICCID (and long before the network state / TERMINAL PROFILE / drain finish). A worker poll landing in that window saw connected + a fresh session + iccid = None and consumed the card with a bogus 'no ICCID' skip - the card was never installed. The `equipping` flag could not guard it either: the manual /api/command equip path never sets it. The fix marks the finished init instead of guessing: - _apply_equipped_card sets `server.card_ready_session = server.card_session` as its last statement; - the startup init (which inlines the post-equip work and never runs _apply_equipped_card) sets the same marker; - the worker requires the marker to match the card session, so it acts only on a fully initialized card - a genuinely unreadable ICCID still acts and reports its skip, as designed. Tests: a worker test that keeps the marker stale for a couple of polls (no attempt - verified to fail without the guard), the marker pins for both equip paths, and the fake server carries the marker (a real attribute) instead of inventing state. The unused `server` parameter of _ram_batch_start is gone, and the two comments that called this work a "review" now say audit. 915 frontend + 924 Python; sw cache simple-v438 (unchanged).
This commit is contained in:
@@ -2701,8 +2701,13 @@ def _ram_batch_worker(state):
|
||||
session = getattr(server, 'card_session', 0) if server is not None else None
|
||||
# The connection state is the module global the whole server uses
|
||||
# (_apply_equipped_card / _handle_card_disconnect); the server
|
||||
# object itself has no `connected` attribute (v3.28.0 review).
|
||||
connected = bool(server is not None and _CARD_CONNECTED)
|
||||
# object itself has no `connected` attribute. The equip must also
|
||||
# have finished (`card_ready_session`, set at the end of
|
||||
# _apply_equipped_card and by the startup init) - acting mid-equip
|
||||
# would consume the card session with a bogus 'no ICCID' skip
|
||||
# (v3.28.0 audit).
|
||||
connected = bool(server is not None and _CARD_CONNECTED
|
||||
and getattr(server, 'card_ready_session', None) == session)
|
||||
with _RAM_BATCH_LOCK:
|
||||
if state['stop']:
|
||||
break
|
||||
@@ -2732,9 +2737,9 @@ def _ram_batch_worker(state):
|
||||
sys.stderr.write('RAM-BATCH: %s after %d attempts\n' % (phase, total))
|
||||
|
||||
|
||||
def _ram_batch_start(server, body):
|
||||
def _ram_batch_start(body):
|
||||
"""Validate the CAP/parameters (the plan is card independent, so it is built
|
||||
once) and spawn the worker. Raises ValueError (400) for a bad request and
|
||||
once) and spawn the worker (the worker reads `_server_ref` itself). Raises ValueError (400) for a bad request and
|
||||
RuntimeError (409) when another batch or a test run is active."""
|
||||
global _RAM_BATCH
|
||||
with _RAM_BATCH_LOCK:
|
||||
@@ -5106,6 +5111,12 @@ def _apply_equipped_card(server):
|
||||
server.sim_menu = sm
|
||||
server.event_list = el
|
||||
_tlog('equip: terminal profile done')
|
||||
# The card session is now fully initialized (the ICCID read, the network
|
||||
# state, the TERMINAL PROFILE and its drain are done). The batch worker
|
||||
# waits for this marker before acting: _CARD_CONNECTED flips True before
|
||||
# the ICCID is read, so a poll landing mid-equip would consume the session
|
||||
# with a bogus 'no ICCID' skip (v3.28.0 audit).
|
||||
server.card_ready_session = server.card_session
|
||||
|
||||
|
||||
def _card_reset_reinit(server, label='RESET'):
|
||||
@@ -10038,7 +10049,7 @@ class PysimHandler(BaseHTTPRequestHandler):
|
||||
body = self._read_body()
|
||||
self._log_req(body)
|
||||
try:
|
||||
_ram_batch_start(self.server, body)
|
||||
_ram_batch_start(body)
|
||||
except ValueError as e:
|
||||
resp = {'error': str(e)}
|
||||
self._send_json(resp, 400)
|
||||
|
||||
Reference in New Issue
Block a user