(ctx context.Context, tb testing.TB, schema, driverName string)
| 128 | } |
| 129 | |
| 130 | func 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`. |
no test coverage detected
searching dependent graphs…