ask-state-providers 0.4.1 → 0.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 128f88b945a38f01707b8bac7bb1d78e85ffff581c392894346cdace4aad4f64
4
- data.tar.gz: 0fae335db04415df0ebb3482afc5f132f83fe6822a988787f4cc10828d979197
3
+ metadata.gz: 15f0b0f326dfb8c8edb6de15f540d9586336431aa187277e7a708600de665daa
4
+ data.tar.gz: 109159d685d50ee6070575abb4e8b58e8422b24c20fab8a65c1a4143af53a258
5
5
  SHA512:
6
- metadata.gz: d266562afb53d4e22cb30b340e5e973cc88962386baa295f430a0dcd53873562b8e0f0029215dfbbe241e3a62d11187e09b9eadc546e89517cffb70e9b28cf61
7
- data.tar.gz: 37a5a82527b15ab1837f8ebf5c918af68de9e504b70f773b615bfa7d2adbe1700007b0df482428aa8fe709cb95ebd6215aa8b014133c54e842e28e5e8df7d797
6
+ metadata.gz: 4fc50fe29074ef8f25079cc572c037e4b8f665bc746c579117f3726934c076027541663747eca6c3984d411e3ae712ef0d1e0ded76869f9d2cc2cddcc4b71379
7
+ data.tar.gz: cc5cbd6989e1a04ca8e6d69fc09e37673dd4bd83479bfd97e3ad2360622c1eb887ca386806d8b4b461b78fcf57e08e29a73ab7db3f1d7fec2cc99591ec381a19
@@ -25,22 +25,17 @@ module Ask
25
25
  def initialize(path: "sessions.db", **pragmas)
26
26
  require "sqlite3"
27
27
 
28
+ @path = path
29
+ @pid = Process.pid
28
30
  @mutex = Mutex.new
29
- @db = SQLite3::Database.new(path)
30
- @db.results_as_hash = true
31
- @db.busy_timeout = 5000
32
-
33
- defaults = {
31
+ @pragmas = {
34
32
  journal_mode: "WAL",
35
33
  synchronous: "NORMAL",
36
34
  foreign_keys: "ON",
37
35
  cache_size: -64_000
38
- }
39
-
40
- defaults.merge(pragmas).each do |key, value|
41
- @db.execute("PRAGMA #{key} = #{value}")
42
- end
36
+ }.merge(pragmas)
43
37
 
38
+ connect
44
39
  migrate
45
40
  end
46
41
 
@@ -48,7 +43,7 @@ module Ask
48
43
 
49
44
  def get(key)
50
45
  @mutex.synchronize do
51
- row = @db.get_first_row(<<~SQL, [key, Time.now.to_f])
46
+ row = db.get_first_row(<<~SQL, [key, Time.now.to_f])
52
47
  SELECT value FROM state_store
53
48
  WHERE key = ? AND (expires_at IS NULL OR expires_at > ?)
54
49
  SQL
@@ -58,7 +53,7 @@ module Ask
58
53
 
59
54
  def set(key, value, ttl: nil)
60
55
  @mutex.synchronize do
61
- @db.execute(<<~SQL, [key, JSON.generate(value), ttl ? Time.now.to_f + ttl : nil])
56
+ db.execute(<<~SQL, [key, JSON.generate(value), ttl ? Time.now.to_f + ttl : nil])
62
57
  INSERT OR REPLACE INTO state_store (key, value, expires_at)
63
58
  VALUES (?, ?, ?)
64
59
  SQL
@@ -67,10 +62,10 @@ module Ask
67
62
 
68
63
  def delete(key)
69
64
  @mutex.synchronize do
70
- @db.execute("DELETE FROM state_store WHERE key = ?", [key])
65
+ db.execute("DELETE FROM state_store WHERE key = ?", [key])
71
66
  # delete removes everything under the key, including ordered
72
67
  # lists (consumers store event feeds as lists).
73
- @db.execute("DELETE FROM lists WHERE list_key = ?", [key])
68
+ db.execute("DELETE FROM lists WHERE list_key = ?", [key])
74
69
  end
75
70
  end
76
71
 
@@ -79,15 +74,15 @@ module Ask
79
74
  now = Time.now.to_f
80
75
  expires = ttl ? now + ttl : nil
81
76
 
82
- row = @db.get_first_row(
77
+ row = db.get_first_row(
83
78
  "SELECT 1 FROM state_store WHERE key = ? AND (expires_at IS NULL OR expires_at > ?)",
84
79
  [key, now]
85
80
  )
86
81
  return false if row
87
82
 
88
83
  # Key doesn't exist or is expired — delete any leftovers, then insert
89
- @db.execute("DELETE FROM state_store WHERE key = ?", [key])
90
- @db.execute(<<~SQL, [key, JSON.generate(value), expires])
84
+ db.execute("DELETE FROM state_store WHERE key = ?", [key])
85
+ db.execute(<<~SQL, [key, JSON.generate(value), expires])
91
86
  INSERT INTO state_store (key, value, expires_at)
92
87
  VALUES (?, ?, ?)
93
88
  SQL
@@ -97,16 +92,16 @@ module Ask
97
92
 
98
93
  def clear
99
94
  @mutex.synchronize do
100
- @db.execute("DELETE FROM state_store")
101
- @db.execute("DELETE FROM locks")
102
- @db.execute("DELETE FROM queues")
103
- @db.execute("DELETE FROM lists")
95
+ db.execute("DELETE FROM state_store")
96
+ db.execute("DELETE FROM locks")
97
+ db.execute("DELETE FROM queues")
98
+ db.execute("DELETE FROM lists")
104
99
  end
105
100
  end
106
101
 
107
102
  def exists?(key)
108
103
  @mutex.synchronize do
109
- row = @db.get_first_row(<<~SQL, [key, Time.now.to_f])
104
+ row = db.get_first_row(<<~SQL, [key, Time.now.to_f])
110
105
  SELECT 1 FROM state_store
111
106
  WHERE key = ? AND (expires_at IS NULL OR expires_at > ?)
112
107
  SQL
@@ -129,7 +124,7 @@ module Ask
129
124
  WHERE (expires_at IS NULL OR expires_at > ?)
130
125
  SQL
131
126
  end
132
- @db.execute(sql, params).map { |r| r["key"] }
127
+ db.execute(sql, params).map { |r| r["key"] }
133
128
  end
134
129
  end
135
130
 
@@ -141,14 +136,14 @@ module Ask
141
136
  expires_at_time = Time.now + ttl
142
137
  token = SecureRandom.hex(16)
143
138
 
144
- row = @db.get_first_row(
139
+ row = db.get_first_row(
145
140
  "SELECT 1 FROM locks WHERE key = ? AND expires_at > ?",
146
141
  [key, now]
147
142
  )
148
143
  return nil if row
149
144
 
150
- @db.execute("DELETE FROM locks WHERE key = ?", [key])
151
- @db.execute(<<~SQL, [key, expires_at_time.to_f, token])
145
+ db.execute("DELETE FROM locks WHERE key = ?", [key])
146
+ db.execute(<<~SQL, [key, expires_at_time.to_f, token])
152
147
  INSERT INTO locks (key, expires_at, token)
153
148
  VALUES (?, ?, ?)
154
149
  SQL
@@ -159,11 +154,11 @@ module Ask
159
154
 
160
155
  def release_lock(key, lock)
161
156
  @mutex.synchronize do
162
- @db.execute(
157
+ db.execute(
163
158
  "DELETE FROM locks WHERE key = ? AND token = ?",
164
159
  [key, lock.token]
165
160
  )
166
- @db.changes > 0
161
+ db.changes > 0
167
162
  end
168
163
  end
169
164
 
@@ -171,18 +166,18 @@ module Ask
171
166
 
172
167
  def enqueue(queue, value)
173
168
  @mutex.synchronize do
174
- @db.execute(<<~SQL, [queue, JSON.generate(value), Time.now.iso8601])
169
+ db.execute(<<~SQL, [queue, JSON.generate(value), Time.now.iso8601])
175
170
  INSERT INTO queues (queue_name, value, enqueued_at)
176
171
  VALUES (?, ?, ?)
177
172
  SQL
178
- id = @db.last_insert_row_id
173
+ id = db.last_insert_row_id
179
174
  QueueEntry.new(id: id.to_s, value: value, enqueued_at: Time.now)
180
175
  end
181
176
  end
182
177
 
183
178
  def dequeue(queue)
184
179
  @mutex.synchronize do
185
- row = @db.get_first_row(<<~SQL, [queue])
180
+ row = db.get_first_row(<<~SQL, [queue])
186
181
  DELETE FROM queues
187
182
  WHERE id = (
188
183
  SELECT id FROM queues
@@ -204,7 +199,7 @@ module Ask
204
199
 
205
200
  def queue_depth(queue)
206
201
  @mutex.synchronize do
207
- row = @db.get_first_row(
202
+ row = db.get_first_row(
208
203
  "SELECT COUNT(*) AS cnt FROM queues WHERE queue_name = ?", [queue]
209
204
  )
210
205
  row["cnt"]
@@ -217,14 +212,14 @@ module Ask
217
212
  @mutex.synchronize do
218
213
  serialized = JSON.generate(value)
219
214
 
220
- @db.execute(<<~SQL, [key, serialized])
215
+ db.execute(<<~SQL, [key, serialized])
221
216
  INSERT INTO lists (list_key, value)
222
217
  VALUES (?, ?)
223
218
  SQL
224
219
 
225
220
  return unless max_length
226
221
 
227
- row = @db.get_first_row(<<~SQL, [key, max_length])
222
+ row = db.get_first_row(<<~SQL, [key, max_length])
228
223
  SELECT MIN(id) AS cutoff FROM (
229
224
  SELECT id FROM lists
230
225
  WHERE list_key = ?
@@ -234,7 +229,7 @@ module Ask
234
229
  SQL
235
230
  return unless row && row["cutoff"]
236
231
 
237
- @db.execute(<<~SQL, [key, row["cutoff"]])
232
+ db.execute(<<~SQL, [key, row["cutoff"]])
238
233
  DELETE FROM lists WHERE list_key = ? AND id < ?
239
234
  SQL
240
235
  end
@@ -243,7 +238,7 @@ module Ask
243
238
  def list_range(key, start = 0, stop = -1)
244
239
  @mutex.synchronize do
245
240
  rows = if stop == -1
246
- @db.execute(<<~SQL, [key, start])
241
+ db.execute(<<~SQL, [key, start])
247
242
  SELECT value FROM lists
248
243
  WHERE list_key = ?
249
244
  ORDER BY id ASC
@@ -251,7 +246,7 @@ module Ask
251
246
  SQL
252
247
  else
253
248
  limit = stop - start + 1
254
- @db.execute(<<~SQL, [key, limit, start])
249
+ db.execute(<<~SQL, [key, limit, start])
255
250
  SELECT value FROM lists
256
251
  WHERE list_key = ?
257
252
  ORDER BY id ASC
@@ -265,11 +260,11 @@ module Ask
265
260
  def list_remove(key, value)
266
261
  @mutex.synchronize do
267
262
  serialized = JSON.generate(value)
268
- @db.execute(
263
+ db.execute(
269
264
  "DELETE FROM lists WHERE list_key = ? AND value = ?",
270
265
  [key, serialized]
271
266
  )
272
- @db.changes
267
+ db.changes
273
268
  end
274
269
  end
275
270
 
@@ -285,14 +280,41 @@ module Ask
285
280
 
286
281
  def close
287
282
  @mutex.synchronize do
288
- @db.close
283
+ @db&.close unless @db&.closed?
289
284
  end
290
285
  end
291
286
 
292
287
  private
293
288
 
289
+ # Open (or reopen) the database connection. Reconnect is the
290
+ # fork story: when a process forks (SolidQueue workers, Puma
291
+ # cluster), the child inherits the parent's connection, which
292
+ # sqlite3's fork safety closes — so every operation goes through
293
+ # #db, which reopens the connection and gives the child a fresh
294
+ # mutex when the process id changed.
295
+ def connect
296
+ @db = SQLite3::Database.new(@path)
297
+ @db.results_as_hash = true
298
+ @db.busy_timeout = 5000
299
+ @pragmas.each do |key, value|
300
+ @db.execute("PRAGMA #{key} = #{value}")
301
+ end
302
+ end
303
+
304
+ # The live connection, reopened if a fork closed it. Call this
305
+ # inside the mutex synchronize (connect swaps the mutex when the
306
+ # process changed, so the old mutex is the one we're holding).
307
+ def db
308
+ if @pid != Process.pid || @db.nil? || @db.closed?
309
+ @pid = Process.pid
310
+ @mutex = Mutex.new
311
+ connect
312
+ end
313
+ @db
314
+ end
315
+
294
316
  def migrate
295
- @db.execute_batch(<<~SQL)
317
+ db.execute_batch(<<~SQL)
296
318
  CREATE TABLE IF NOT EXISTS state_store (
297
319
  key TEXT PRIMARY KEY NOT NULL,
298
320
  value TEXT NOT NULL,
@@ -3,7 +3,7 @@
3
3
  module Ask
4
4
  module State
5
5
  module Providers
6
- VERSION = "0.4.1"
6
+ VERSION = "0.4.2"
7
7
  end
8
8
  end
9
9
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ask-state-providers
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Kaka Ruto
@@ -106,7 +106,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
106
106
  - !ruby/object:Gem::Version
107
107
  version: '0'
108
108
  requirements: []
109
- rubygems_version: 4.0.3
109
+ rubygems_version: 4.0.18
110
110
  specification_version: 4
111
111
  summary: Pluggable state backends for the ask-rb ecosystem
112
112
  test_files: []