Snapshot transactions sharing the graph backend's writer lock.
A transaction holds the lock from snapshot through rollback or commit. The
owner is explicit: an awaited child joins by entering transaction(), while a
separate writer waits even if it inherited the parent's context variable.
A consolidation cycle rejects multiple graph domains; callers that manually
nest distinct backends must use a consistent lock order.
snapshot
async
snapshot(store: Any) -> AsyncIterator[Any]
Restore the full preimage on failure; nested entries join the owner.
Source code in src/symfonic/memory/backends/in_memory_transaction.py
| @asynccontextmanager
async def snapshot(store: Any) -> AsyncIterator[Any]:
"""Restore the full preimage on failure; nested entries join the owner."""
if _context_owner(store):
task = current_task()
counts = store._write_owner_tasks # noqa: SLF001
counts[task] = counts.get(task, 0) + 1
joined_token = _JOINED.set((store, store._write_owner, task, _JOINED.get())) # noqa: SLF001
try:
yield store
finally:
counts[task] -= 1
if not counts[task]:
del counts[task]
_JOINED.reset(joined_token)
return
_refuse_expired_join(store)
async with store._write_lock: # noqa: SLF001
original_nodes = {t: dict(rows) for t, rows in store._nodes.items()} # noqa: SLF001
original_edges = {t: dict(rows) for t, rows in store._edges.items()} # noqa: SLF001
saved = (
deepcopy(original_nodes),
deepcopy(original_edges),
dict(store._workset_cursors), # noqa: SLF001
)
owner = object()
token = _OWNER.set((store, owner, _OWNER.get()))
store._write_owner = owner # noqa: SLF001
store._write_owner_tasks = {current_task(): 1} # noqa: SLF001
try:
yield store
if any(task is not current_task() for task in store._write_owner_tasks): # noqa: SLF001
raise RuntimeError("joined child transaction remains open")
except BaseException:
try:
for tenant, rows in original_nodes.items():
for identifier, original in rows.items():
_restore_row(original, saved[0][tenant][identifier])
for tenant, rows in original_edges.items():
for identifier, original in rows.items():
_restore_row(original, saved[1][tenant][identifier])
finally:
store._nodes = defaultdict(dict, original_nodes) # noqa: SLF001
store._edges = defaultdict(dict, original_edges) # noqa: SLF001
store._workset_cursors = saved[2] # noqa: SLF001
raise
finally:
store._write_owner = None # noqa: SLF001
store._write_owner_tasks = {} # noqa: SLF001
_OWNER.reset(token)
|
writer
async
writer(store: Any) -> AsyncIterator[None]
Share the transaction lock only with tasks that joined its ownership.
Source code in src/symfonic/memory/backends/in_memory_transaction.py
| @asynccontextmanager
async def writer(store: Any) -> AsyncIterator[None]:
"""Share the transaction lock only with tasks that joined its ownership."""
if _owns(store):
yield
else:
_refuse_expired_join(store)
async with store._write_lock: # noqa: SLF001
yield
|