Why ‘Already In A Transaction’ Keeps Popping Up
When working with async text widgets - like in a textual interface - running a del followed by an insert inside a with doc.transaction() block often triggers an Already in a transaction error, even though both operations are technically synchronous. The catch? Async code can subtly unravel transactional consistency. Here’s what’s really going on: transaction objects in anyio are immutable once started - once a transaction is created, no further changes to the same doc can occur without blocking or failing. But if your code schedules or awaits the transaction’s outcome across coroutines, timing or context mismatches can cause a race. For example, if doc.apply_update(update) runs after a transaction has ended but before its changes are fully recognized, the system treats it as a conflict. In minimal terms: transactions are state-locked; async context doesn’t erase that lock. This isn’t just a technical bug - it’s a warning about context ambiguity in async CRDTs. To debug, avoid mixing direct transaction references with external updates; wrap critical edits in consistent transactional scopes and use locks like anyio.Lock only when coordinating across coroutines - not inside transaction blocks. The real insight? Even silent state changes can break transaction integrity if timing slips - stay sharp, not just fast.