Repository navigation
The pool and Database are safe and Share across workers - #2
Merged
Merged
Conversation
A Database shared by tasks on every worker needs spawn_parallel (0.19.0) and the @[Share] defs and SharedProc that check the blocks it keeps (0.19.1).
Its counts are atomics, and the connections and the last result set it keeps for the tests are behind a Mutex, each in a Made(T) that is Share on trust for the test double alone. db#1's program on it, at 12 workers, read the registry from every worker at once.
v0.3.0's pool kept its bookkeeping with no lock, from when iyi's tasks shared one thread, and two tasks on different workers were handed one connection. db#1's program on the dummy driver answered other tasks' numbers or hung at 2, 4 and 12 workers (Linux, 12 cores). * Pool: the bookkeeping is a PoolBook behind a std Mutex; a resource goes in and out of it in a PoolSlot, Share on trust (owns: true), the one claim; opening and closing run outside the lock. A checkout that finds the pool full parks on a channel of its own, registered under the same lock, and a release hands its resource, or the room it left, to the checkout that has waited longest. close closes the idle resources and each one out when it is released. stats reads atomics. Pool.new is @[Share]: its factory is checked where written and kept as a SharedProc. * Database: Share by its fields. Database.new, Database.open and setup_connection are @[Share]; the setup block is behind a Mutex. Db.open wraps the driver's ConnectionBuilder in SharedBuilder, trusted by the ConnectionBuilder contract (pg's and mysql's builders are not Share by their fields). * The driver registry is behind a Mutex. * Statement hooks are SharedProcs in a List behind a Mutex, with an atomic count so a statement with none takes no lock. * LOG is a SharedLogger: std's Logger behind a Mutex, its level in an atomic beside it, so a statement with debug off takes no lock. iyi check of the issue's program (master befaa8bc4): 4 #148 warnings before (DATABASE, LOG, AROUND_HOOKS, the dummy's last result set), none after; on pg 0.3.1 the 3 left are pg's (DECODERS, LOG, QUOTED), and none with pg's perf/statement-cache.
parallel_test sets IyiWorkers.count = 12 and runs the issue's program through a constant and twenty times through a local, a full pool's waiting checkouts outnumbering the workers, a close while every worker queries, and failing and lost connections. manual/pool_concurrency runs its tasks with spawn_parallel, as crystal's did under preview_mt.
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.
Closes #1.
What
Db::Core::Databaseand its pool are safe across iyi's pool workers (spawn_parallel,Server.serve), and Share by their fields. An app can keepDATABASE = Db.open(...)as a constant and use it from parallel routes.Mutexaround the pool's bookkeeping. Connections are opened and closed outside the lock.closecloses idle connections at once, and a busy one when its task releases it.statsreports are atomics, so it takes no lock.Pool.new,Database.new,Database.openandsetup_connectioncheck their blocks where they are written (@[Share]).DRIVERSsits behind aMutex.Mutex(List(SharedProc))with an atomic count, so a statement with no hooks takes no lock.LOGis aSharedLogger: std's logger behind aMutex, with its level in an atomic beside it. Logging off takes no lock.Breaking
LOG's type changed. Set the level withDb::Core::LOG.level =, and a backend onLog.for("db").Measured
SELECT $1::int8: a median of 16,073 q/s before against 15,857 after, inside the noise.SharedLoggerand a plain std logger read the same, within noise.SharedDatabaseworkaround (167 lines → 37) for a plainDb.openconstant, with the same throughput.Tests
New
db/parallel_test.iyi, run at 12 workers:All 16 test files pass, also from artifacts. A run on iyi 0.19.1 itself, the new minimum, hasn't been made yet.