Skip to content

Commit

Permalink
Merge pull request #497 from sparklemotion/docs
Browse files Browse the repository at this point in the history
Add threadsafety documentation
  • Loading branch information
flavorjones committed Feb 2, 2024
2 parents 5ad47ae + aba1e22 commit e9e722e
Show file tree
Hide file tree
Showing 2 changed files with 80 additions and 0 deletions.
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

0 comments on commit e9e722e

Please sign in to comment.