Give back a run of clicks without recomputing the order once per place
Adds TransactionManagerInterface::recomputeOnce(), a bracket a caller can open around a batch of line changes, and opens it for the two picker paths that give places back.
What was happening
Every released line announces itself, and OrderLifecycleSubscriber::onChange answers each announcement by locking the order's row and reading every line on it. It already steps aside during the manager's own whole-transaction transitions, with a comment saying this avoids an O(n^2) storm; a run of clicks was simply never able to say it was one.
Measured
PlaceReleaseCostTest, 24-place hall, one run of clicks:
| before | after | |
|---|---|---|
| giving back 1 place | 16 queries, 2 recomputes | unchanged |
| giving back 6 places | 58 queries, 6 recomputes | 53 queries, 1 recompute |
The five queries are the smaller half. The five locks on the order's row are the half that matters under load, and the shape no longer grows with the basket.
The test
Compares one place against six and asserts the recompute does not grow with them, rather than asserting a ceiling, which a number written from today's code would pass by construction. It counts the read of the order's row rather than the lock taken around it: a locking read is written FOR UPDATE on MySQL and nothing at all on SQLite, so counting the lock would measure the driver. Seen to fail against the unfixed path at 6 against 2, and the figures are in the register.
Care taken
The bracket cannot be left open, recomputes whether the work returned or threw, and nests safely, an inner one leaving the recompute to the outer that owns it. The order is read before the releases, because giving back the last place empties the basket.
Found while measuring the release path for [#3615593].