litestack 0.1.4 → 0.1.6
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 +4 -4
- data/CHANGELOG.md +7 -0
- data/README.md +3 -3
- data/bench/bench_cache_raw.rb +1 -1
- data/bench/bench_queue.rb +2 -2
- data/lib/active_support/cache/litecache.rb +4 -2
- data/lib/litestack/litecache.rb +53 -59
- data/lib/litestack/litejob.rb +6 -6
- data/lib/litestack/litejobqueue.rb +4 -5
- data/lib/litestack/litequeue.rb +14 -17
- data/lib/litestack/litesupport.rb +74 -1
- data/lib/litestack/version.rb +1 -1
- data/lib/sequel/adapters/litedb.rb +8 -3
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 01c10017cec21bcabea357aecb8746779060718bb2e6b6e36f250c37d80656f1
|
4
|
+
data.tar.gz: 863b319c7a658f2a19410c614035068f8a2dec72b189698a5a081f595ecf5853
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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)
|
data/bench/bench_cache_raw.rb
CHANGED
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
|
-
|
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)
|
data/lib/litestack/litecache.rb
CHANGED
@@ -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
|
-
@
|
62
|
-
|
63
|
-
:
|
64
|
-
:
|
65
|
-
:
|
66
|
-
:
|
67
|
-
:
|
68
|
-
:
|
69
|
-
:
|
70
|
-
:
|
71
|
-
:
|
72
|
-
:
|
73
|
-
:
|
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
|
-
|
86
|
+
@cache.acquire do |cache|
|
87
87
|
begin
|
88
|
-
|
88
|
+
cache.stmts[:setter].execute!(key, value, expires_in)
|
89
89
|
rescue SQLite3::FullException
|
90
|
-
|
91
|
-
|
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
|
-
|
102
|
+
changes = 0
|
103
|
+
@cache.acquire do |cache|
|
103
104
|
begin
|
104
105
|
transaction(:immediate) do
|
105
|
-
|
106
|
+
cache.stmts[:inserter].execute!(key, value, expires_in)
|
106
107
|
changes = @cache.changes
|
107
108
|
end
|
108
109
|
rescue SQLite3::FullException
|
109
|
-
|
110
|
-
|
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 =
|
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
|
-
|
137
|
-
|
138
|
-
|
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
|
-
|
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
|
-
|
154
|
+
@cache.acquire do |cache|
|
158
155
|
if limit and limit.is_a? Integer
|
159
|
-
|
156
|
+
cache.stmts[:limited_pruner].execute!(limit)
|
160
157
|
elsif limit and limit.is_a? Float
|
161
|
-
|
158
|
+
cache.stmts[:extra_pruner].execute!(limit)
|
162
159
|
else
|
163
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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.
|
214
|
-
|
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
|
-
|
214
|
+
@cache.acquire do |cache|
|
224
215
|
begin
|
225
|
-
|
226
|
-
@last_visited.delete_if do |k|
|
227
|
-
|
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
|
-
|
220
|
+
cache.stmts[:pruner].execute!
|
230
221
|
end
|
231
222
|
rescue SQLite3::BusyException
|
232
223
|
retry
|
233
224
|
rescue SQLite3::FullException
|
234
|
-
|
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
|
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
|
|
data/lib/litestack/litejob.rb
CHANGED
@@ -43,21 +43,21 @@ module Litejob
|
|
43
43
|
private
|
44
44
|
def self.included(klass)
|
45
45
|
klass.extend(ClassMethods)
|
46
|
-
klass.
|
46
|
+
klass.get_jobqueue
|
47
47
|
end
|
48
48
|
|
49
49
|
module ClassMethods
|
50
50
|
def perform_async(*params)
|
51
|
-
|
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
|
-
|
56
|
+
get_jobqueue.push(self.name, params, delay, queue)
|
57
57
|
end
|
58
58
|
|
59
59
|
def perfrom_in(delay, *params)
|
60
|
-
|
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
|
80
|
-
Litejobqueue.
|
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.
|
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.
|
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
|
data/lib/litestack/litequeue.rb
CHANGED
@@ -34,15 +34,18 @@ class Litequeue
|
|
34
34
|
|
35
35
|
def initialize(options = {})
|
36
36
|
@options = DEFAULT_OPTIONS.merge(options)
|
37
|
-
@queue = create_db #
|
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
|
-
|
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
|
-
|
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 = @
|
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
|
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
|
data/lib/litestack/version.rb
CHANGED
@@ -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
|
-
|
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
|
+
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-
|
11
|
+
date: 2023-03-03 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: sqlite3
|