Skip to content

Commit 2b0fff5

Browse files
committed
Allow databases to be shareable but only in full-mutex mode
https://www.sqlite.org/threadsafe.html "Full mutex" mode is the only time when a SQLite database can be shared between multiple threads. Multi-thread mode only means that multiple threads can have their own instances of a database object that point at the same file, but multiple threads cannot access the object concurrently.
1 parent 093c6cc commit 2b0fff5

File tree

3 files changed

+16
-33
lines changed

3 files changed

+16
-33
lines changed

ext/sqlite3/database.c

+4
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,10 @@ rb_sqlite3_open_v2(VALUE self, VALUE file, VALUE mode, VALUE zvfs)
158158
ctx->flags |= SQLITE3_RB_DATABASE_READONLY;
159159
}
160160

161+
if (flags & SQLITE_OPEN_FULLMUTEX) {
162+
FL_SET_RAW(self, RUBY_FL_SHAREABLE);
163+
}
164+
161165
return self;
162166
}
163167

lib/sqlite3/database.rb

+1-1
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ def initialize file, options = {}, zvfs = nil
181181

182182
initialize_extensions(options[:extensions])
183183

184-
ForkSafety.track(self)
184+
ForkSafety.track(self) if Ractor.main?
185185

186186
if block_given?
187187
begin

test/test_integration_ractor.rb

+11-32
Original file line numberDiff line numberDiff line change
@@ -44,37 +44,16 @@ def test_ractor_share_database
4444
end
4545
end
4646

47-
def test_ractor_stress
48-
skip("Requires Ruby with Ractors") unless SQLite3.ractor_safe?
49-
50-
# Testing with a file instead of :memory: since it can be more realistic
51-
# compared with real production use, and so discover problems that in-
52-
# memory testing won't find. Trivial example: STRESS_DB_NAME needs to be
53-
# frozen to pass into the Ractor, but :memory: might avoid that problem by
54-
# using a literal string.
55-
db = SQLite3::Database.open(STRESS_DB_NAME)
56-
db.execute("PRAGMA journal_mode=WAL") # A little slow without this
57-
db.execute("create table stress_test (a integer primary_key, b text)")
58-
random = Random.new.freeze
59-
ractors = (0..9).map do |ractor_number|
60-
Ractor.new(random, ractor_number) do |random, ractor_number|
61-
db_in_ractor = SQLite3::Database.open(STRESS_DB_NAME)
62-
db_in_ractor.busy_handler do
63-
sleep random.rand / 100 # Lots of busy errors happen with multiple concurrent writers
64-
true
65-
end
66-
100.times do |i|
67-
db_in_ractor.execute("insert into stress_test(a, b) values (#{ractor_number * 100 + i}, '#{random.rand}')")
68-
end
69-
end
70-
end
71-
ractors.each { |r| r.take }
72-
final_check = Ractor.new do
73-
db_in_ractor = SQLite3::Database.open(STRESS_DB_NAME)
74-
res = db_in_ractor.execute("select count(*) from stress_test")
75-
Ractor.yield res
76-
end
77-
res = final_check.take
78-
assert_equal 1000, res[0][0]
47+
def test_shareable_db
48+
# databases are shareable between ractors, but only if they're opened
49+
# in "full mutex" mode
50+
db = SQLite3::Database.new ":memory:",
51+
flags: SQLite3::Constants::Open::FULLMUTEX |
52+
SQLite3::Constants::Open::READWRITE |
53+
SQLite3::Constants::Open::CREATE
54+
assert Ractor.shareable?(db)
55+
56+
db = SQLite3::Database.new ":memory:"
57+
refute Ractor.shareable?(db)
7958
end
8059
end

0 commit comments

Comments
 (0)