Repository navigation
Conversation
`sql.exec` rejects `BEGIN`, and `Storage::transaction` wraps a callback, so `begin()` parks one open: it opens `Storage::transaction` in a task of its own with a callback that only waits for a commit or rollback signal, while the statements run on the connection through `&mut *tx` as with any sqlx driver. A spike measured that this works: statements outside the callback are part of the transaction, the request can await timers and sub-requests while it is open, and other requests are held off until it ends. - `commit()` / `rollback()` signal the parked transaction and wait for it. - A dropped `Transaction` signals the rollback without waiting (sqlx's `start_rollback` is synchronous), and every query on the connection first waits for it to land -- a write right after the drop was measured to be rolled back with it otherwise. - sqlx drops a guard, calling `start_rollback`, when `begin()` fails; a refused nested `begin()` records a phantom level so that drop cannot roll back the outer transaction. - Nested `begin()` (savepoints), a custom `BEGIN` statement, and batches or `transaction()` while one is open are refused rather than deadlocking. - `DoConnection::transaction(callback)` stays, as the form that cannot be left open; its docs note that sqlx's own `Connection::transaction` wins method resolution on a `&mut DoConnection`, and works too now. Worker scenarios cover commit, rollback, drop-then-write, read-your-writes across an await, the nested refusal keeping the outer's writes, the refusals while open, and a second request held off while a `begin()` transaction is open. BREAKING CHANGE: `DoConnection::into_storage` returns `Rc<worker::Storage>` instead of `worker::Storage`: the parked transaction needs its own handle to the storage, which cannot be cloned. Method calls still work through auto-deref; only code naming the type changes.
Contributor
Author
|
Closing without merging. The implementation works, and all 42 DO scenarios pass, but it isn't worth carrying for now:
If code that has to call sqlx's |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
sqlx's own
begin()/commit()/rollback()now work on Durable Object storage, as with any sqlx driver:How
sql.execrejectsBEGIN, andStorage::transactiononly wraps a callback. Sobegin()parks one open: it opensStorage::transactionin its own task, with a callback that only waits for a commit or rollback signal. Meanwhile the statements run on the connection as usual.A spike measured this before the implementation, with identical results over three rounds:
Details
commit/rollbacksignal the parked transaction and wait for it to finish.start_rollbackis synchronous, so it signals the rollback without waiting. Every query on the connection then waits for that rollback to land first. Without this wait, the spike showed a write made right after the drop was rolled back too.begin(). sqlx drops a guard, callingstart_rollback, wheneverbegin()fails. A refused nestedbegin()records a phantom level so that drop can't roll back the outer transaction. The Worker scenario caught this bug; the outer transaction's writes from before and after the refusal now both commit.begin()(savepoints), a customBEGINstatement, and batches ortransaction(callback)while a transaction is open.DoConnection::transaction(callback)stays, as the form that can't be left open. Its docs note that on a&mut DoConnectionwithsqlx::Connectionin scope,conn.transaction(…)resolves to sqlx's ownConnection::transaction, which now works too.Breaking change
DoConnection::into_storagereturnsRc<worker::Storage>. The parked transaction needs its own handle to the storage, andworker::Storagecan't be cloned. Method calls still work through auto-deref; only code that names the type changes. The commit is markedfeat(do)!, so release-please proposes 0.3.0.New dependencies (DO crate)
futures-channel, for one-shot signals.futures-util'sstdfeature, forShared: every query waits on a pending rollback.Both are wasm-safe.
Tests
RETURNINGvalue;begin()transaction is open, and its write survives the rollback. It now runs for both transaction styles.begin_failsscenario is removed.begin()andcommit()futures areSend.Verification
make lint(host and wasm32, zero warnings),make testincluding the newbegin()doctest, andmake doc-check.make test-worker: 29 of 29 D1 scenarios and 42 of 42 DO scenarios.