Skip to content
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

Add threadsafety documentation #497

Merged
merged 1 commit into from
Feb 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
72 changes: 72 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,78 @@ end
# => ["Jane", "[email protected]", "A", "http://blog.janedoe.com"]
```

## Thread Safety

When `SQLite3.threadsafe?` returns `true`, then SQLite3 has been compiled to
support running in a multithreaded environment. However, this doesn't mean
that all classes in the SQLite3 gem can be considered "thread safe".

When `SQLite3.threadsafe?` returns `true`, it is safe to share only
`SQLite3::Database` instances among threads without providing your own locking
mechanism. For example, the following code is fine because only the database
instance is shared among threads:

```ruby
require 'sqlite3'

db = SQLite3::Database.new ":memory:"

latch = Queue.new

ts = 10.times.map {
Thread.new {
latch.pop
db.execute "SELECT '#{Thread.current.inspect}'"
}
}
10.times { latch << nil }

p ts.map(&:value)
```

Other instances can be shared among threads, but they require that you provide
your own locking for thread safety. For example, `SQLite3::Statement` objects
(prepared statements) are mutable, so applications must take care to add
appropriate locks to avoid data race conditions when sharing these objects
among threads.

Lets rewrite the above example but use a prepared statement and safely share
the prepared statement among threads:

```ruby
db = SQLite3::Database.new ":memory:"

# Prepare a statement
stmt = db.prepare "SELECT :inspect"
stmt_lock = Mutex.new

latch = Queue.new

ts = 10.times.map {
Thread.new {
latch.pop

# Add a lock when using the prepared statement.
# Binding values, and walking over results will mutate the statement, so
# in order to prevent other threads from "seeing" this thread's data, we
# must lock when using the statement object
stmt_lock.synchronize do
stmt.execute(Thread.current.inspect).to_a
end
}
}

10.times { latch << nil }

p ts.map(&:value)

stmt.close
```

It is generally recommended that if applications want to share a database among
threads, they _only_ share the database instance object. Other objects are
fine to share, but may require manual locking for thread safety.

## Support

### Installation or database extensions
Expand Down
8 changes: 8 additions & 0 deletions lib/sqlite3/database.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ module SQLite3
# ArrayFields module from Ara Howard. If you require the ArrayFields
# module before performing a query, and if you have not enabled results as
# hashes, then the results will all be indexible by field name.
#
# Thread safety:
#
# When `SQLite3.threadsafe?` returns true, it is safe to share instances of
# the database class among threads without adding specific locking. Other
# object instances may require applications to provide their own locks if
# they are to be shared among threads. Please see the README.md for more
# information.
class Database
attr_reader :collations

Expand Down
Loading