Our naive update for [email protected] / [email protected] led to buggy behaviour whereby
the `Tx` extractor would always fail with `OverlappingExtractors` if
there were any outstanding clones of the request extensions (see the new
test). Since request extensions now must all implement `Clone`, it's
possible that some middleware might wish to keep a clone of all request
extensions (e.g. for request inspection/tracing/debugging), rendering
`Tx` unusable with those middleware.
To fix it, we simplify the synchronisation by implementing `Clone` for
`Slot` and creating new `Extension<DB>` and `LazyTransaction<DB>` types
to replace `TxSlot<DB>` and `Lazy<DB>`.
`Slot<T>` is a wrapper around an `Arc<Mutex<Option<T>>>`, and as such it
can trivially implement `Clone` (there was some "pit of success"
considerations with the previous API intended to enforce proper usage,
but that is unnecesarily limiting given the underlying `Mutex`).
The `Extension<DB>` holds a `Slot` containing a `LazyTransaction<DB>`.
`Extension<DB>` is trivially clonable since `Slot` itself is. The
`LazyTransaction<DB>` then implements a simple "lazily acquired
transaction" protocol, making use of normal rust ownership and borrowing
rules to manage the transaction (i.e. it has no internal
synchronisation).
This makes the overall synchronisation picture much simpler: the
middleware future and all clones of the request extension hold a
reference to the same `Slot`. The `Tx` extractor obtains its copy of the
request extension and attempts to `lease` the inner `LazyTransaction`,
failing with `OverlappingExtractors` if the lease is already taken (this
is the only public invocation of `lease`, and so overlapping extractors
can be the only* cause of an absent transaction). If the lease is
successful, the extractor can acquire a transaction (if there's not one
already) and package it up for request handlers to then interact with.
* Technically the transaction can be "stolen" from the `Tx` extractor by
committing explicitly, but this considered to create an endless
"overlap" in the current semantics.
The main caveat of this approach seems to be that the `Tx` extractor no
longer has type-level knowledge that it can access a `Transaction` - the
`Transaction` can only be accessed by matching on the `LazyTransaction`
state. This doesn't affect the API, but it could make bumping into a
panic more likely, or there may be performance implications (though
these would likely be dwarfed by the I/O involved in interacting with a
database).