-
Notifications
You must be signed in to change notification settings - Fork 203
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Gem is not threadsafe #391
Comments
Thanks for opening this issue. I'd like to spend some time to understand more deeply why this is happening. |
I believe if you have separate instances of the DB, one per thread, it will work correctly. To achieve this easily, you can use the |
Should also work when using a connection pool (eg. Ie. as long as no two threads use the same connection concurrently I'm assuming things will be fine? |
I don't think this is the problem OP is trying to demo. Separate instances of an in-memory database may not make sense. I think the database connection itself is threadsafe and should be fine to share, but the script that OP presented is sharing a prepared statement among threads. I've reduced the repro to this: require "sqlite3"
# Ensure that we have a thread-safe SQLite library for this test
raise 'SQLite3 is not threadsafe' unless SQLite3.threadsafe?
db = SQLite3::Database.new ':memory:'
statement = db.prepare('SELECT :foo')
# Thread 1
statement.reset!
# Thread 2
statement.reset!
# Thread 1
statement.bind_params(foo: 1)
statement.step
# Thread 2
statement.bind_params(foo: 2) # exception Prepared statements are basically mutable (you can bind parameters, step through results, etc) so I'm not really clear on how to ensure their entire API is thread safe. For example, we on the sqlite3-ruby team cannot make the script I posted above thread safe for the user. However, we could synchronize "one shot" APIs like |
After spending quite some time chasing an obscure bug in one of my employer's applications, I thought I'd share my experiences here so that other people might benefit from it.
Our code contained a singleton that used a prepared statement to query a SQLite DB in read-only mode. This led to strange problems like empty result sets for queries that were guaranteed to return at least one row.
For us, these issues only manifested when running the app in production with Puma (a multi-threaded web server).
Here is a simple reproducer:
This will eventually result in an exception like this:
In hindsight, this behaviour is not surprising me at all.
My concern is that it is very easy to introduce this kind of concurrency bug, even if the application code itself does not do any explicit threading.
Judging by the discussions in some other issues (e.g. #217, #287), I think that the README should contain a prominent warning about using this gem in multi-threaded code; even if
libsqlite3.so
itself has been compiled with-DSQLITE_THREADSAFE=2
.The text was updated successfully, but these errors were encountered: