MCPcopy Index your code
hub / github.com/riverqueue/river / dbPoolSQLite

Function dbPoolSQLite

rivershared/riversharedtest/riversharedtest.go:130–191  ·  view source on GitHub ↗
(ctx context.Context, tb testing.TB, schema, driverName string)

Source from the content-addressed store, hash-verified

128}
129
130func dbPoolSQLite(ctx context.Context, tb testing.TB, schema, driverName string) *sql.DB { //nolint:unparam
131 tb.Helper()
132
133 var databaseURLBuilder strings.Builder
134
135 databaseURLBuilder.WriteString("file:" + filepath.Join(sqliteTestDir(), schema+".sqlite3"))
136
137 // This innocuous line turns out to be quite important at the tail.
138 //
139 // When running the test suite via SQLite, most of the time everything goes
140 // well and we get no problems. But sometimes, especially when using `-race`
141 // or at higher iteration counts, SQLite will arbitrarily return the error
142 // "database is locked (5) (SQLITE_BUSY)". This is a death sentence because
143 // SQLite provides no tooling for figuring out _what_ is locking the
144 // database, so any further tests using TestSchema that try to reuse that
145 // schema will fail on the same error.
146 //
147 // I tried a number of techniques to fix this include doing a post-flight
148 // check on schema health before checking a schema back into the TestSchema
149 // pool, and while that also semed to the trick, a simpler alternative is to
150 // make sure that SQLite is doing its journaling via WAL:
151 //
152 // https://sqlite.org/pragma.html#pragma_journal_mode
153 // https://sqlite.org/wal.html
154 //
155 // There's a lot of potential reading to do on the subject of WAL, but the
156 // short answer is that it unlocks more concurrency, and it's faster anyway.
157 //
158 // My results in using WAL to decrease the prevalance of "database is
159 // locked" problems also seems to be mirrored by other peoples' findings:
160 //
161 // https://til.simonwillison.net/sqlite/enabling-wal-mode
162 //
163 // I write all this because this line is a little dangerous. Removing it
164 // will probably still allow a basic test run to pass so it might seem okay,
165 // but actually it opens the door to intermittency hell.
166 databaseURLBuilder.WriteString("?_pragma=journal_mode(WAL)")
167
168 dbPool, err := sql.Open(driverName, databaseURLBuilder.String())
169 require.NoError(tb, err)
170 tb.Cleanup(func() { require.NoError(tb, dbPool.Close()) })
171
172 // River does enough concurrent work that given multiple active SQLite
173 // connections, it'll immediately start erroring with "database is locked
174 // (5) (SQLITE_BUSY)" because SQLite can only handle one operation at a time
175 // and explicitly errors if another is in flight. To prevent this problem,
176 // we constrain the maximum pool size to 1 so it limits concurrent access
177 // for us.
178 //
179 // I've seen some broad recommendations that it might be better to
180 // always set maximum connections for a single SQLite database to 1
181 // anyway. See for example:
182 //
183 // https://news.ycombinator.com/item?id=30369095
184 //
185 // An alternative approach is to increase `PRAGMA busy_timeout`, but I've
186 // found that we still run into `SQLITE_BUSY` errors with that at higher
187 // iteration counts like `-run TestClientWithDriverRiverSQLite -count 100`.

Callers 2

DBPoolLibSQLFunction · 0.85
DBPoolSQLiteFunction · 0.85

Calls 4

OpenMethod · 0.80
CleanupMethod · 0.80
HelperMethod · 0.65
CloseMethod · 0.65

Tested by

no test coverage detected

Used in the wild real call sites across dependent graphs

searching dependent graphs…