File: test_integration_pending.rb

package info (click to toggle)
ruby-sqlite3 2.9.0-1
  • links: PTS, VCS
  • area: main
  • in suites: sid
  • size: 680 kB
  • sloc: ruby: 4,827; ansic: 1,868; sh: 91; makefile: 7
file content (213 lines) | stat: -rw-r--r-- 5,112 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
require "helper"

class IntegrationPendingTestCase < SQLite3::TestCase
  class ThreadSynchronizer
    def initialize
      @main_to_thread = Queue.new
      @thread_to_main = Queue.new
    end

    def send_to_thread state
      @main_to_thread.push state
    end

    def send_to_main state
      @thread_to_main.push state
    end

    def wait_for_thread expected_state, non_block = false
      state = @thread_to_main.pop(non_block)
      raise "Invalid state #{state}. #{expected_state} is expected" if state != expected_state
    end

    def wait_for_main expected_state, non_block = false
      state = @main_to_thread.pop(non_block)
      raise "Invalid state #{state}. #{expected_state} is expected" if state != expected_state
    end

    def close_thread
      @thread_to_main.close
    end

    def close_main
      @main_to_thread.close
    end

    def close
      close_thread
      close_main
    end
  end

  def setup
    @db = SQLite3::Database.new("test.db")
    @db.transaction do
      @db.execute "create table foo ( a integer primary key, b text )"
      @db.execute "insert into foo ( b ) values ( 'foo' )"
      @db.execute "insert into foo ( b ) values ( 'bar' )"
      @db.execute "insert into foo ( b ) values ( 'baz' )"
    end
  end

  def teardown
    @db.close
    File.delete("test.db")
  end

  def test_busy_handler_impatient
    synchronizer = ThreadSynchronizer.new
    handler_call_count = 0

    t = Thread.new(synchronizer) do |sync|
      db2 = SQLite3::Database.open("test.db")
      db2.transaction(:exclusive) do
        sync.send_to_main :ready_0
        sync.wait_for_main :end_1
      end
    ensure
      db2&.close
      sync.close_thread
    end
    synchronizer.wait_for_thread :ready_0

    @db.busy_handler do
      handler_call_count += 1
      false
    end

    assert_raise(SQLite3::BusyException) do
      @db.execute "insert into foo (b) values ( 'from 2' )"
    end
    assert_equal 1, handler_call_count

    synchronizer.send_to_thread :end_1
    synchronizer.close_main
    t.join
  end

  def test_busy_timeout
    @db.busy_timeout 1000
    synchronizer = ThreadSynchronizer.new

    t = Thread.new(synchronizer) do |sync|
      db2 = SQLite3::Database.open("test.db")
      db2.transaction(:exclusive) do
        sync.send_to_main :ready_0
        sync.wait_for_main :end_1
      end
    ensure
      db2&.close
      sync.close_thread
    end
    synchronizer.wait_for_thread :ready_0

    start_time = Time.now
    assert_raise(SQLite3::BusyException) do
      @db.execute "insert into foo (b) values ( 'from 2' )"
    end
    end_time = Time.now
    assert_operator(end_time - start_time, :>=, 1.0)

    synchronizer.send_to_thread :end_1
    synchronizer.close_main
    t.join
  end

  def test_busy_handler_timeout_releases_gvl
    @db.busy_handler_timeout = 100

    t1sync = ThreadSynchronizer.new
    t2sync = ThreadSynchronizer.new

    busy = Mutex.new
    busy.lock

    count = 0
    active_thread = Thread.new(t1sync) do |sync|
      sync.send_to_main :ready
      sync.wait_for_main :start

      loop do
        sleep 0.005
        count += 1
        begin
          sync.wait_for_main :end, true
          break
        rescue ThreadError
        end
      end
      sync.send_to_main :done
    end

    blocking_thread = Thread.new(t2sync) do |sync|
      db2 = SQLite3::Database.open("test.db")
      db2.transaction(:exclusive) do
        sync.send_to_main :ready
        busy.lock
      end
      sync.send_to_main :done
    ensure
      db2&.close
    end

    t1sync.wait_for_thread :ready
    t2sync.wait_for_thread :ready

    t1sync.send_to_thread :start
    assert_raises(SQLite3::BusyException) do
      @db.execute "insert into foo (b) values ( 'from 2' )"
    end
    t1sync.send_to_thread :end

    busy.unlock
    t2sync.wait_for_thread :done

    expected = if RUBY_PLATFORM.include?("linux")
      # 20 is the theoretical max if timeout is 100ms and active thread sleeps 5ms
      15
    else
      # in CI, macos and windows systems seem to really not thread very well, so let's set a lower bar.
      2
    end
    assert_operator(count, :>=, expected)
  ensure
    active_thread&.join
    blocking_thread&.join

    t1sync&.close
    t2sync&.close
  end

  def test_busy_handler_outwait
    synchronizer = ThreadSynchronizer.new
    handler_call_count = 0

    t = Thread.new(synchronizer) do |sync|
      db2 = SQLite3::Database.open("test.db")
      db2.transaction(:exclusive) do
        sync.send_to_main :ready_0
        sync.wait_for_main :busy_handler_called_1
      end
      sync.send_to_main :end_of_transaction_2
    ensure
      db2&.close
      sync.close_thread
    end
    synchronizer.wait_for_thread :ready_0

    @db.busy_handler do |count|
      handler_call_count += 1
      synchronizer.send_to_thread :busy_handler_called_1
      synchronizer.wait_for_thread :end_of_transaction_2
      true
    end

    assert_nothing_raised do
      @db.execute "insert into foo (b) values ( 'from 2' )"
    end
    assert_equal 1, handler_call_count

    synchronizer.close_main
    t.join
  end
end