litestack 0.1.4 → 0.1.6

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: c34bfdfaaa6a2472e132e05b1f80494a76d2311026c764b312a463f4f5cb2ee0
4
- data.tar.gz: c27e887802afe1ee2f5c7e2c67aeb390fde3ef62adb6cec610e9e3f7af697753
3
+ metadata.gz: 01c10017cec21bcabea357aecb8746779060718bb2e6b6e36f250c37d80656f1
4
+ data.tar.gz: 863b319c7a658f2a19410c614035068f8a2dec72b189698a5a081f595ecf5853
5
5
  SHA512:
6
- metadata.gz: abd6ae7cd31c4988b911f147bcdd00f947fdda537993f854250b3d5c8b91bbb2d702faf0f73e07e6b54f4603253d90dd89ef80c41baab61cb313ccb9420d8e54
7
- data.tar.gz: 58e3f6d9b443afc614d4254ace16c7ff3bd5bdfa1dbbfcfabf9e2eb8f69f57b7b1b15153ac59a7de91e303a305987b9aa92701f204140eea4e5ba4108a05725b
6
+ metadata.gz: 7bb0fd84ee582eddfe32e48b0373a7545cad8581217ba56d7ac6c984b60b137b86d5196e1bc37486b88d21c28bc5ff62da573a1ba6ce6b49d66ba744ddbcabc6
7
+ data.tar.gz: 38da99eae6a37222a8689577900504ea1b131f0ec56f67531dff00f5ae4f4c62a29baca21ba2a102435cc334773f8ef4709a361dff6f28de988471eac0937e08
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.1.6] - 2022-03-03
4
+
5
+ - Revamped the locking model, more robust, minimal performance hit
6
+ - Introduced a new resource pooling class
7
+ - Litecache and Litejob now use the resource pool
8
+ - Much less memory usage for Litecache and Litejob
9
+
3
10
  ## [0.1.0] - 2022-02-26
4
11
 
5
12
  - Initial release
data/README.md CHANGED
@@ -152,9 +152,9 @@ You can add more configuration in litejob.yml (or config/litejob.yml if you are
152
152
 
153
153
  ```yaml
154
154
  queues:
155
- - [default 1]
156
- - [urgent 5]
157
- - [critical 10 "spawn"]
155
+ - [default, 1]
156
+ - [urgent, 5]
157
+ - [critical, 10, "spawn"]
158
158
  ```
159
159
 
160
160
  The queues need to include a name and a priority (a number between 1 and 10) and can also optionally add the token "spawn", which means every job will run it its own concurrency context (thread or fiber)
@@ -9,7 +9,7 @@ Fiber.set_scheduler Async::Scheduler.new
9
9
  Fiber.scheduler.run
10
10
 
11
11
  require_relative '../lib/litestack'
12
-
12
+ #require 'litestack'
13
13
 
14
14
  cache = Litecache.new({path: '../db/cache.db'}) # default settings
15
15
  redis = Redis.new # default settings
data/bench/bench_queue.rb CHANGED
@@ -5,11 +5,11 @@ count = 1000
5
5
 
6
6
  q = Litequeue.new({path: '../db/queue.db' })
7
7
 
8
- bench("enqueue", count) do |i|
8
+ bench("Litequeue enqueue", count) do |i|
9
9
  q.push i.to_s
10
10
  end
11
11
 
12
- bench("dequeue", count) do |i|
12
+ bench("Litequeue dequeue", count) do |i|
13
13
  q.pop
14
14
  end
15
15
 
@@ -25,12 +25,14 @@ module ActiveSupport
25
25
  def increment(key, amount = 1, options = nil)
26
26
  key = key.to_s
27
27
  options = merged_options(options)
28
- @cache.transaction(:immediate) do
28
+ # todo: fix me
29
+ # this is currently a hack to avoid dealing with Rails cache encoding and decoding
30
+ #@cache.transaction(:immediate) do
29
31
  if value = read(key, options)
30
32
  value = value.to_i + amount
31
33
  write(key, value, options)
32
34
  end
33
- end
35
+ #end
34
36
  end
35
37
 
36
38
  def decrement(key, amount = 1, options = nil)
@@ -58,21 +58,21 @@ class Litecache
58
58
  def initialize(options = {})
59
59
  @options = DEFAULT_OPTIONS.merge(options)
60
60
  @options[:size] = @options[:min_size] if @options[:size] < @options[:min_size]
61
- @cache = create_store
62
- @stmts = {
63
- :pruner => @cache.prepare("DELETE FROM data WHERE expires_in <= $1"),
64
- :extra_pruner => @cache.prepare("DELETE FROM data WHERE id IN (SELECT id FROM data ORDER BY last_used ASC LIMIT (SELECT CAST((count(*) * $1) AS int) FROM data))"),
65
- :limited_pruner => @cache.prepare("DELETE FROM data WHERE id IN (SELECT id FROM data ORDER BY last_used asc limit $1)"),
66
- :toucher => @cache.prepare("UPDATE data SET last_used = unixepoch('now') WHERE id = $1"),
67
- :setter => @cache.prepare("INSERT into data (id, value, expires_in, last_used) VALUES ($1, $2, unixepoch('now') + $3, unixepoch('now')) on conflict(id) do UPDATE SET value = excluded.value, last_used = excluded.last_used, expires_in = excluded.expires_in"),
68
- :inserter => @cache.prepare("INSERT into data (id, value, expires_in, last_used) VALUES ($1, $2, unixepoch('now') + $3, unixepoch('now')) on conflict(id) do UPDATE SET value = excluded.value, last_used = excluded.last_used, expires_in = excluded.expires_in WHERE id = $1 and expires_in <= unixepoch('now')"),
69
- :finder => @cache.prepare("SELECT id FROM data WHERE id = $1"),
70
- :getter => @cache.prepare("SELECT id, value, expires_in FROM data WHERE id = $1"),
71
- :deleter => @cache.prepare("delete FROM data WHERE id = $1 returning value"),
72
- :incrementer => @cache.prepare("INSERT into data (id, value, expires_in, last_used) VALUES ($1, $2, unixepoch('now') + $3, unixepoch('now')) on conflict(id) do UPDATE SET value = cast(value AS int) + cast(excluded.value as int), last_used = excluded.last_used, expires_in = excluded.expires_in"),
73
- :counter => @cache.prepare("SELECT count(*) FROM data"),
74
- :sizer => @cache.prepare("SELECT size.page_size * count.page_count FROM pragma_page_size() AS size, pragma_page_count() AS count")
61
+ @sql = {
62
+ :pruner => "DELETE FROM data WHERE expires_in <= $1",
63
+ :extra_pruner => "DELETE FROM data WHERE id IN (SELECT id FROM data ORDER BY last_used ASC LIMIT (SELECT CAST((count(*) * $1) AS int) FROM data))",
64
+ :limited_pruner => "DELETE FROM data WHERE id IN (SELECT id FROM data ORDER BY last_used asc limit $1)",
65
+ :toucher => "UPDATE data SET last_used = unixepoch('now') WHERE id = $1",
66
+ :setter => "INSERT into data (id, value, expires_in, last_used) VALUES ($1, $2, unixepoch('now') + $3, unixepoch('now')) on conflict(id) do UPDATE SET value = excluded.value, last_used = excluded.last_used, expires_in = excluded.expires_in",
67
+ :inserter => "INSERT into data (id, value, expires_in, last_used) VALUES ($1, $2, unixepoch('now') + $3, unixepoch('now')) on conflict(id) do UPDATE SET value = excluded.value, last_used = excluded.last_used, expires_in = excluded.expires_in WHERE id = $1 and expires_in <= unixepoch('now')",
68
+ :finder => "SELECT id FROM data WHERE id = $1",
69
+ :getter => "SELECT id, value, expires_in FROM data WHERE id = $1",
70
+ :deleter => "delete FROM data WHERE id = $1 returning value",
71
+ :incrementer => "INSERT into data (id, value, expires_in, last_used) VALUES ($1, $2, unixepoch('now') + $3, unixepoch('now')) on conflict(id) do UPDATE SET value = cast(value AS int) + cast(excluded.value as int), last_used = excluded.last_used, expires_in = excluded.expires_in",
72
+ :counter => "SELECT count(*) FROM data",
73
+ :sizer => "SELECT size.page_size * count.page_count FROM pragma_page_size() AS size, pragma_page_count() AS count"
75
74
  }
75
+ @cache = Litesupport::Pool.new(1){create_db}
76
76
  @stats = {hit: 0, miss: 0}
77
77
  @last_visited = {}
78
78
  @running = true
@@ -83,12 +83,12 @@ class Litecache
83
83
  def set(key, value, expires_in = nil)
84
84
  key = key.to_s
85
85
  expires_in = @options[:expires_in] if expires_in.nil? or expires_in.zero?
86
- Litesupport.synchronize do
86
+ @cache.acquire do |cache|
87
87
  begin
88
- @stmts[:setter].execute!(key, value, expires_in)
88
+ cache.stmts[:setter].execute!(key, value, expires_in)
89
89
  rescue SQLite3::FullException
90
- @stmts[:extra_pruner].execute!(0.2)
91
- @cache.execute("vacuum")
90
+ cache.stmts[:extra_pruner].execute!(0.2)
91
+ cache.execute("vacuum")
92
92
  retry
93
93
  end
94
94
  end
@@ -99,15 +99,16 @@ class Litecache
99
99
  def set_unless_exists(key, value, expires_in = nil)
100
100
  key = key.to_s
101
101
  expires_in = @options[:expires_in] if expires_in.nil? or expires_in.zero?
102
- Litesupport.synchronize do
102
+ changes = 0
103
+ @cache.acquire do |cache|
103
104
  begin
104
105
  transaction(:immediate) do
105
- @stmts[:inserter].execute!(key, value, expires_in)
106
+ cache.stmts[:inserter].execute!(key, value, expires_in)
106
107
  changes = @cache.changes
107
108
  end
108
109
  rescue SQLite3::FullException
109
- @stmts[:extra_pruner].execute!(0.2)
110
- @cache.execute("vacuum")
110
+ cache.stmts[:extra_pruner].execute!(0.2)
111
+ cache.execute("vacuum")
111
112
  retry
112
113
  end
113
114
  end
@@ -118,11 +119,7 @@ class Litecache
118
119
  # if the key doesn't exist or it is expired then null will be returned
119
120
  def get(key)
120
121
  key = key.to_s
121
- record = nil
122
- Litesupport.synchronize do
123
- record = @stmts[:getter].execute!(key)[0]
124
- end
125
- if record
122
+ if record = @cache.acquire{|cache| cache.stmts[:getter].execute!(key)[0] }
126
123
  @last_visited[key] = true
127
124
  @stats[:hit] +=1
128
125
  return record[1]
@@ -133,18 +130,18 @@ class Litecache
133
130
 
134
131
  # delete a key, value pair from the cache
135
132
  def delete(key)
136
- Litesupport.synchronize do
137
- @stmts[:deleter].execute!(key)
138
- return @cache.changes > 0
133
+ changes = 0
134
+ @cache.aquire do |cache|
135
+ cache.stmts[:deleter].execute!(key)
136
+ changes = cache.changes
139
137
  end
138
+ return changes > 0
140
139
  end
141
140
 
142
141
  # increment an integer value by amount, optionally add an expiry value (in seconds)
143
142
  def increment(key, amount, expires_in = nil)
144
143
  expires_in = @expires_in unless expires_in
145
- Litesupport.synchronize do
146
- @stmts[:incrementer].execute!(key.to_s, amount, expires_in)
147
- end
144
+ @cache.acquire{|cache| cache.stmts[:incrementer].execute!(key.to_s, amount, expires_in) }
148
145
  end
149
146
 
150
147
  # decrement an integer value by amount, optionally add an expiry value (in seconds)
@@ -154,51 +151,43 @@ class Litecache
154
151
 
155
152
  # delete all entries in the cache up limit (ordered by LRU), if no limit is provided approximately 20% of the entries will be deleted
156
153
  def prune(limit=nil)
157
- Litesupport.synchronize do
154
+ @cache.acquire do |cache|
158
155
  if limit and limit.is_a? Integer
159
- @stmts[:limited_pruner].execute!(limit)
156
+ cache.stmts[:limited_pruner].execute!(limit)
160
157
  elsif limit and limit.is_a? Float
161
- @stmts[:extra_pruner].execute!(limit)
158
+ cache.stmts[:extra_pruner].execute!(limit)
162
159
  else
163
- @stmts[:pruner].execute!
160
+ cache.stmts[:pruner].execute!
164
161
  end
165
162
  end
166
163
  end
167
164
 
168
165
  # return the number of key, value pairs in the cache
169
166
  def count
170
- Litesupport.synchronize do
171
- @stmts[:counter].execute!.to_a[0][0]
172
- end
167
+ @cache.acquire{|cache| cache.stmts[:counter].execute!.to_a[0][0] }
173
168
  end
174
169
 
175
170
  # return the actual size of the cache file
176
171
  def size
177
- Litesupport.synchronize do
178
- @stmts[:sizer].execute!.to_a[0][0]
179
- end
172
+ @cache.acquire{|cache| cache.stmts[:sizer].execute!.to_a[0][0] }
180
173
  end
181
174
 
182
175
  # delete all key, value pairs in the cache
183
176
  def clear
184
- Litesupport.synchronize do
185
- @cache.execute("delete FROM data")
186
- end
177
+ @cache.acquire{|cache| cache.execute("delete FROM data") }
187
178
  end
188
179
 
189
180
  # close the connection to the cache file
190
181
  def close
191
182
  @running = false
192
183
  #Litesupport.synchronize do
193
- #@cache.close
184
+ @cache.acquire{|cache| cache.close }
194
185
  #end
195
186
  end
196
187
 
197
188
  # return the maximum size of the cache
198
189
  def max_size
199
- Litesupport.synchronize do
200
- @cache.get_first_value("SELECT s.page_size * c.max_page_count FROM pragma_page_size() as s, pragma_max_page_count() as c")
201
- end
190
+ @cache.acquire{|cache| cache.get_first_value("SELECT s.page_size * c.max_page_count FROM pragma_page_size() as s, pragma_max_page_count() as c") }
202
191
  end
203
192
 
204
193
  # hits and misses for get operations performed over this particular connection (not cache wide)
@@ -210,8 +199,10 @@ class Litecache
210
199
 
211
200
  # low level access to SQLite transactions, use with caution
212
201
  def transaction(mode)
213
- @cache.transaction(mode) do
214
- yield
202
+ @cache.acquire do |cache|
203
+ cache.transaction(mode) do
204
+ yield
205
+ end
215
206
  end
216
207
  end
217
208
 
@@ -220,18 +211,20 @@ class Litecache
220
211
  def spawn_worker
221
212
  Litesupport.spawn do
222
213
  while @running
223
- Litesupport.synchronize do
214
+ @cache.acquire do |cache|
224
215
  begin
225
- @cache.transaction(:immediate) do
226
- @last_visited.delete_if do |k|
227
- @stmts[:toucher].execute!(k) || true
216
+ cache.transaction(:immediate) do
217
+ @last_visited.delete_if do |k| # there is a race condition here, but not a serious one
218
+ cache.stmts[:toucher].execute!(k) || true
228
219
  end
229
- @stmts[:pruner].execute!
220
+ cache.stmts[:pruner].execute!
230
221
  end
231
222
  rescue SQLite3::BusyException
232
223
  retry
233
224
  rescue SQLite3::FullException
234
- @stmts[:extra_pruner].execute!(0.2)
225
+ cache.stmts[:extra_pruner].execute!(0.2)
226
+ rescue Exception
227
+ # database is closed
235
228
  end
236
229
  end
237
230
  sleep @options[:sleep_interval]
@@ -239,7 +232,7 @@ class Litecache
239
232
  end
240
233
  end
241
234
 
242
- def create_store
235
+ def create_db
243
236
  db = Litesupport.create_db(@options[:path])
244
237
  db.synchronous = 0
245
238
  db.cache_size = 2000
@@ -250,6 +243,7 @@ class Litecache
250
243
  db.execute("CREATE table if not exists data(id text primary key, value text, expires_in integer, last_used integer)")
251
244
  db.execute("CREATE index if not exists expiry_index on data (expires_in)")
252
245
  db.execute("CREATE index if not exists last_used_index on data (last_used)")
246
+ @sql.each_pair{|k, v| db.stmts[k] = db.prepare(v)}
253
247
  db
254
248
  end
255
249
 
@@ -43,21 +43,21 @@ module Litejob
43
43
  private
44
44
  def self.included(klass)
45
45
  klass.extend(ClassMethods)
46
- klass.get_queue
46
+ klass.get_jobqueue
47
47
  end
48
48
 
49
49
  module ClassMethods
50
50
  def perform_async(*params)
51
- get_queue.push(self.name, params, 0, queue)
51
+ get_jobqueue.push(self.name, params, 0, queue)
52
52
  end
53
53
 
54
54
  def perform_at(time, *params)
55
55
  delay = time - Time.now.to_i
56
- get_queue.push(self.name, params, delay, queue)
56
+ get_jobqueue.push(self.name, params, delay, queue)
57
57
  end
58
58
 
59
59
  def perfrom_in(delay, *params)
60
- get_queue.push(self.name, params, delay, queue)
60
+ get_jobqueue.push(self.name, params, delay, queue)
61
61
  end
62
62
 
63
63
  def options
@@ -76,8 +76,8 @@ module Litejob
76
76
  @@queue_name = queue_name.to_s
77
77
  end
78
78
 
79
- def get_queue
80
- Litejobqueue.queue(options)
79
+ def get_jobqueue
80
+ Litejobqueue.jobqueue(options)
81
81
  end
82
82
  end
83
83
 
@@ -41,7 +41,7 @@ class Litejobqueue
41
41
 
42
42
  # a method that returns a single instance of the job queue
43
43
  # for use by Litejob
44
- def self.queue(options = {})
44
+ def self.jobqueue(options = {})
45
45
  @@queue ||= Litesupport.synchronize{self.new(options)}
46
46
  end
47
47
 
@@ -58,8 +58,8 @@ class Litejobqueue
58
58
  def initialize(options = {})
59
59
  @options = DEFAULT_OPTIONS.merge(options)
60
60
  @worker_sleep_index = 0
61
- config = YAML.load_file(@options[:config_path]) rescue {} #an empty hash won't hurt
62
- config.each_key do |k| # symbolize keys
61
+ config = YAML.load_file(@options[:config_path]) rescue {} # an empty hash won't hurt
62
+ config.keys.each do |k| # symbolize keys
63
63
  config[k.to_sym] = config[k]
64
64
  config.delete k
65
65
  end
@@ -116,14 +116,13 @@ class Litejobqueue
116
116
  # create a worker according to environment
117
117
  def create_worker
118
118
  Litesupport.spawn do
119
- Litesupport.switch
120
119
  loop do
121
120
  processed = 0
122
121
  @queues.each do |level| # iterate through the levels
123
122
  level[1].each do |q| # iterate through the queues in the level
124
123
  index = 0
125
124
  max = level[0]
126
- while index < max && payload = @queue.pop(q[0])
125
+ while index < max && payload = @queue.pop(q[0]) # fearlessly use the same queue object
127
126
  processed += 1
128
127
  index += 1
129
128
  begin
@@ -34,15 +34,18 @@ class Litequeue
34
34
 
35
35
  def initialize(options = {})
36
36
  @options = DEFAULT_OPTIONS.merge(options)
37
- @queue = create_db #(@options[:path])
38
- prepare
37
+ @queue = Litesupport::Pool.new(1){create_db} # delegate the db creation to the litepool
39
38
  end
40
39
 
41
40
  # push an item to the queue, optionally specifying the queue name (defaults to default) and after how many seconds it should be ready to pop (defaults to zero)
42
41
  # a unique job id is returned from this method, can be used later to delete it before it fires. You can push string, integer, float, true, false or nil values
43
42
  #
44
43
  def push(value, delay=0, queue='default')
45
- result = @push.execute!(queue, delay, value)[0]
44
+ # @todo - check if queue is busy, back off if it is
45
+ # also bring back the synchronize block, to prevent
46
+ # a race condition if a thread hits the busy handler
47
+ # before the current thread proceeds after a backoff
48
+ result = @queue.acquire { |q| q.stmts[:push].execute!(queue, delay, value)[0] }
46
49
  return result[0] if result
47
50
  end
48
51
 
@@ -50,11 +53,7 @@ class Litequeue
50
53
 
51
54
  # pop an item from the queue, optionally with a specific queue name (default queue name is 'default')
52
55
  def pop(queue='default')
53
- result = nil
54
- Litesupport.synchronize do
55
- result = @pop.execute!(queue)[0]
56
- end
57
- result
56
+ @queue.acquire {|q| q.stmts[:pop].execute!(queue)[0] }
58
57
  end
59
58
 
60
59
  # delete an item from the queue
@@ -64,22 +63,22 @@ class Litequeue
64
63
  # queue.pop # => nil
65
64
  def delete(id, queue='default')
66
65
  fire_at, id = id.split("_")
67
- result = @deleter.execute!(queue, fire_at.to_i, id)[0]
66
+ result = @queue.acquire{|q| q.stmts[:delete].execute!(queue, fire_at.to_i, id)[0] }
68
67
  end
69
68
 
70
69
  # deletes all the entries in all queues, or if a queue name is given, deletes all entries in that specific queue
71
70
  def clear(queue=nil)
72
- @queue.execute("DELETE FROM _ul_queue_ WHERE iif(?, queue = ?, 1)", queue)
71
+ @queue.acquire{|q| q.execute("DELETE FROM _ul_queue_ WHERE iif(?, queue = ?, 1)", queue) }
73
72
  end
74
73
 
75
74
  # returns a count of entries in all queues, or if a queue name is given, reutrns the count of entries in that queue
76
75
  def count(queue=nil)
77
- @queue.get_first_value("SELECT count(*) FROM _ul_queue_ WHERE iif(?, queue = ?, 1)", queue)
76
+ @queue.acquire{|q| q.get_first_value("SELECT count(*) FROM _ul_queue_ WHERE iif(?, queue = ?, 1)", queue) }
78
77
  end
79
78
 
80
79
  # return the size of the queue file on disk
81
80
  def size
82
- @queue.get_first_value("SELECT size.page_size * count.page_count FROM pragma_page_size() AS size, pragma_page_count() AS count")
81
+ @queue.acquire{|q| q.get_first_value("SELECT size.page_size * count.page_count FROM pragma_page_size() AS size, pragma_page_count() AS count") }
83
82
  end
84
83
 
85
84
  private
@@ -90,14 +89,12 @@ class Litequeue
90
89
  db.wal_autocheckpoint = 10000
91
90
  db.mmap_size = @options[:mmap_size]
92
91
  db.execute("CREATE TABLE IF NOT EXISTS _ul_queue_(queue TEXT DEFAULT('default') NOT NULL ON CONFLICT REPLACE, fire_at INTEGER DEFAULT(unixepoch()) NOT NULL ON CONFLICT REPLACE, id TEXT DEFAULT(hex(randomblob(8)) || (strftime('%f') * 100)) NOT NULL ON CONFLICT REPLACE, value TEXT, created_at INTEGER DEFAULT(unixepoch()) NOT NULL ON CONFLICT REPLACE, PRIMARY KEY(queue, fire_at ASC, id) ) WITHOUT ROWID")
92
+ db.stmts[:push] = db.prepare("INSERT INTO _ul_queue_(queue, fire_at, value) VALUES ($1, (strftime('%s') + $2), $3) RETURNING fire_at || '-' || id")
93
+ db.stmts[:pop] = db.prepare("DELETE FROM _ul_queue_ WHERE (queue, fire_at, id) = (SELECT queue, min(fire_at), id FROM _ul_queue_ WHERE queue = ifnull($1, 'default') AND fire_at <= (unixepoch()) limit 1) RETURNING fire_at || '-' || id, value")
94
+ db.stmts[:delete] = db.prepare("DELETE FROM _ul_queue_ WHERE queue = ifnull($1, 'default') AND fire_at = $2 AND id = $3 RETURNING value")
93
95
  db
94
96
  end
95
97
 
96
- def prepare
97
- @push = @queue.prepare("INSERT INTO _ul_queue_(queue, fire_at, value) VALUES ($1, (strftime('%s') + $2), $3) RETURNING fire_at || '-' || id")
98
- @pop = @queue.prepare("DELETE FROM _ul_queue_ WHERE (queue, fire_at, id) = (SELECT queue, min(fire_at), id FROM _ul_queue_ WHERE queue = ifnull($1, 'default') AND fire_at <= (unixepoch()) limit 1) RETURNING fire_at || '-' || id, value")
99
- @deleter = @queue.prepare("DELETE FROM _ul_queue_ WHERE queue = ifnull($1, 'default') AND fire_at = $2 AND id = $3 RETURNING value")
100
- end
101
98
 
102
99
  end
103
100
 
@@ -33,15 +33,30 @@ module Litesupport
33
33
  # we should never reach here
34
34
  end
35
35
 
36
+ def self.detect_context
37
+ if environment == :fiber || environment == :poylphony
38
+ Fiber.current.storage
39
+ else
40
+ Thread.current
41
+ end
42
+ end
43
+
44
+ def self.context
45
+ @ctx ||= detect_context
46
+ end
47
+
36
48
  # switch the execution context to allow others to run
37
49
  def self.switch
38
50
  if self.environment == :fiber
39
51
  Fiber.scheduler.yield
52
+ true
40
53
  elsif self.environment == :polyphony
41
54
  Fiber.current.schedule
42
55
  Thread.current.switch_fiber
56
+ true
43
57
  else
44
58
  # do nothing in case of thread, switching will auto-happen
59
+ false
45
60
  end
46
61
  end
47
62
 
@@ -66,9 +81,67 @@ module Litesupport
66
81
  # common db object options
67
82
  def self.create_db(path)
68
83
  db = SQLite3::Database.new(path)
69
- db.busy_handler{ sleep 0.001 }
84
+ db.busy_handler{ switch || sleep(0.001) }
70
85
  db.journal_mode = "WAL"
86
+ db.instance_variable_set(:@stmts, {})
87
+ class << db
88
+ attr_reader :stmts
89
+ end
71
90
  db
72
91
  end
73
92
 
93
+ class Mutex
94
+
95
+ def initialize
96
+ @mutex = Thread::Mutex.new
97
+ end
98
+
99
+ def synchronize(&block)
100
+ if Litesupport.environment == :threaded || Litesupport.environment == :iodine
101
+ @mutex.synchronize{ block.call }
102
+ else
103
+ block.call
104
+ end
105
+ end
106
+
107
+ end
108
+
109
+ class Pool
110
+
111
+ def initialize(count, &block)
112
+ @count = count
113
+ @block = block
114
+ @resources = []
115
+ @mutex = Litesupport::Mutex.new
116
+ @count.times do
117
+ resource = @mutex.synchronize{ block.call }
118
+ @resources << [resource, :free]
119
+ end
120
+ end
121
+
122
+ def acquire
123
+ acquired = false
124
+ result = nil
125
+ while !acquired do
126
+ @mutex.synchronize do
127
+ if resource = @resources.find{|r| r[1] == :free}
128
+ resource[1] = :busy
129
+ begin
130
+ result = yield resource[0]
131
+ rescue Exception => e
132
+ raise e
133
+ ensure
134
+ resource[1] = :free
135
+ acquired = true
136
+ return nil
137
+ end
138
+ end
139
+ end
140
+ sleep 0.0001 unless acquired
141
+ end
142
+ result
143
+ end
144
+
145
+ end
146
+
74
147
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Litestack
4
- VERSION = "0.1.4"
4
+ VERSION = "0.1.6"
5
5
  end
@@ -1,6 +1,7 @@
1
1
  require_relative '../../litestack/litedb'
2
2
  require 'sequel'
3
3
  require 'sequel/adapters/sqlite'
4
+ #require 'shared/litedb'
4
5
 
5
6
  module Sequel
6
7
  module Litedb
@@ -12,15 +13,19 @@ module Sequel
12
13
 
13
14
  set_adapter_scheme :litedb
14
15
 
15
-
16
16
  def connect(server)
17
+
18
+ Sequel.extension :fiber_concurrency if [:fiber, :polyphony].include? Litesupport.environment
19
+
17
20
  opts = server_opts(server)
18
21
  opts[:database] = ':memory:' if blank_object?(opts[:database])
19
22
  sqlite3_opts = {}
20
23
  sqlite3_opts[:readonly] = typecast_value_boolean(opts[:readonly]) if opts.has_key?(:readonly)
21
24
  db = ::Litedb.new(opts[:database].to_s, sqlite3_opts)
25
+
26
+ self.transaction_mode = :immediate
22
27
 
23
- if sqlite_version >= 104
28
+ if sqlite_version >= 104
24
29
  db.extended_result_codes = true
25
30
  end
26
31
 
@@ -29,8 +34,8 @@ module Sequel
29
34
  class << db
30
35
  attr_reader :prepared_statements
31
36
  end
32
- db.instance_variable_set(:@prepared_statements, {})
33
37
 
38
+ db.instance_variable_set(:@prepared_statements, {})
34
39
  db
35
40
  end
36
41
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: litestack
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.1.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mohamed Hassan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2023-02-26 00:00:00.000000000 Z
11
+ date: 2023-03-03 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: sqlite3