-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
dialect-wasqlite-worker transactions break concurrent requests #10
Comments
Updated snippet to use Kysely directly. |
Thanks for reporting and your detailed reproduction. I will look into this issue. |
Seems that this issue is caused by the usage of |
@subframe7536 Yes, that makes sense. Do you consider the current behavior to be expected, then? When the driver receives a request to perform transaction A (which may be an implicit transaction, i.e. a standalone query), but is in the middle of transaction B, I would expect transaction A to be blocked while transaction B is in-progress, but once B completes, A should continue, and eventually resolve. (I find this stuff hard to talk about clearly, I can try to illustrate it better if this is hard to understand.) |
I tested the reproduction with official sqlite dialect, and it also emit timeout error. Also, the proper way to execute sqls in transaction is reusing the And I'm curious about that you usecase to do this logic concurrently? Generally awaiting in transaction and everything works: let outerSelect
await db.transaction().execute(async (tx) => {
await tx.insertInto('test')
.values({ name: 'from transaction' })
.execute()
outerSelect = await tx.selectFrom('test')
.selectAll()
.where('name', '=', 'from transaction')
.execute()
throw rollback
}).catch((err) => {
// swallow rollback error
if (err !== rollback) {
throw err
}
})
console.log(outSelect) and, just use |
Thanks @subframe7536 - to clarify, do you mean the better-sqlite3 dialect that ships with Kysely? https://kysely-org.github.io/kysely-apidoc/classes/SqliteDialect.html My use case is that I'm processing data as it trickles in from multiple parallel API requests - as each API response arrives, I put its data into SQLite. Since I don't serialize the SQLite work from my end, I end up running stuff concurrently. (I think the current snippet in the issue doesn't demonstrate this well – the original snippet illustrated it better but I wasn't able to repro the issue using bare Kysely. This could definitely be my own code's issue!) I'm curious to know exactly which dialect you're talking about and see if it fixes the real issue I'm seeing, but I'll close this issue in the meantime. |
Yes. I think the operations of database and external APIs should be splitted. Also, |
Hi @subframe7536 – I found the issue in my project, and just wanted to share my findings in case you were interested: Here's a more succinct repro that better represents the real issue I was encountering: diff --git a/playground/src/modules/utils.ts b/playground/src/modules/utils.ts
index d2b42cc..0d5be6b 100644
--- a/playground/src/modules/utils.ts
+++ b/playground/src/modules/utils.ts
@@ -1,4 +1,4 @@
-import type { Dialect } from 'kysely'
+import type { Dialect, Kysely } from 'kysely'
import type { InferDatabase } from 'kysely-sqlite-builder/schema'
import { SqliteBuilder } from 'kysely-sqlite-builder'
import { column, defineTable, useSchema } from 'kysely-sqlite-builder/schema'
@@ -53,9 +53,30 @@ export async function testDB(dialect: Dialect) {
console.error(error)
}
+ await checkTxConcurrency(db.kysely)
+
return db.selectFrom('test').selectAll().execute().then(async (data) => {
await db.destroy()
console.log(data)
return data
})
}
+
+async function checkTxConcurrency(db: Kysely<DB>) {
+ await db.transaction().execute(async (tx) => {
+ // This will return no rows.
+ // This is necessary for this repro to fail: my understanding is that the
+ // subsequent INSERT INTO resolves on this query's response.
+ tx.selectFrom('test').selectAll().where('id', '=', 99999).execute()
+
+ // This should insert 1 row
+ tx.insertInto('test')
+ .values({
+ name: `test at ${Date.now()}`,
+ blobtest: Uint8Array.from([2, 3, 4, 5, 6, 7, 8]),
+ })
+ .returning('name')
+ // This raises a `no results` error
+ .executeTakeFirstOrThrow()
+ })
+} When this is run, Kysely raises a Note that the queries inside the transaction in I don't see a practical reason for someone to exit the transaction block without awaiting the inner queries, but it does stink that a typo like this can cause a hard-to-debug error. I think this issue happens when the worker receives a request, then receives another request before it is able to respond to the first. Since |
Yeah, in this case, the I will try to fix it. |
It seems complicated to bind message with something like |
Just to be clear: This isn't blocking my work, so no time pressure from me. Here's a really dumb, messy commit I made to my fork to help debug the issue which applies request IDs: d017539 the summary is that I generate a (I use a local worker that implements the same interface, since I wanted to add some features and wanted to use I wasn't familiar with streaming queries, so maybe there's something there that would make this approach fail (but reading the code, it looks like you could just "close over" the request ID in the async generator). |
Thanks for your suggesion! BTW there are some internal methods in |
It would be better to handle this issue at user side, so I just close this issue |
Patch to demonstrate the issue in the playground (updated to use Kysely directly)
When run, this times out at the
Promise.all
– specifically, theouterSelectPromise
seems to be timing out (i.e. somehow thesimple diagram of tested behavior because reading async code is hard
When a transaction is in-progress, any other requests (outside of the transaction) performed concurrently never resolve.
I have suspicions that also:
kysely-sqlite-builder
if you look at the edit history of this issue)WaSqliteWorkerDriver
– this manifested in my bigger project as anINSERT INTO ... RETURNING
complaining aboutno results
, but when I traced the request / response, the response (1) arrived at the worker driver after the Kysely query execution completed, and (2) did include results – hypothesis is that theno results
error was raised because theINSERT INTO
request thought that some other query response contained the results to its query.I'm going to continue trying to reproduce these issues in the playground, but the issue in the snippet above seems real enough 🤷
I haven't used any of the other packages in this repo, so I don't know if this is an issue in the other drivers.
I'm motivated to fix this for my own project, but I haven't tried fixing it yet – will PR if I'm able to fix it.
The text was updated successfully, but these errors were encountered: