litestack 0.1.6 → 0.1.7

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: 01c10017cec21bcabea357aecb8746779060718bb2e6b6e36f250c37d80656f1
4
- data.tar.gz: 863b319c7a658f2a19410c614035068f8a2dec72b189698a5a081f595ecf5853
3
+ metadata.gz: 2662941f303da99554039370e6e53e4c13956c52070e93ad6862742881ca8063
4
+ data.tar.gz: ccd5583a9e0e7f5c559f8dafdde913bba256b7b5a679fe927cd31805c1289022
5
5
  SHA512:
6
- metadata.gz: 7bb0fd84ee582eddfe32e48b0373a7545cad8581217ba56d7ac6c984b60b137b86d5196e1bc37486b88d21c28bc5ff62da573a1ba6ce6b49d66ba744ddbcabc6
7
- data.tar.gz: 38da99eae6a37222a8689577900504ea1b131f0ec56f67531dff00f5ae4f4c62a29baca21ba2a102435cc334773f8ef4709a361dff6f28de988471eac0937e08
6
+ metadata.gz: 1a139b3c42cd3f9a327fc7d8f1487dab2e95f2c88d07e063eecc2e438c91236f301a8e3928c9a3b41030fd8c08f4c118c449e56ea4be35b73b00fe2aa240faf8
7
+ data.tar.gz: 409221a087df5707bd6da477bb9091daf2ee0fc5f8114d71a7666324676bc62e5d77b6edfd1e3e7d29d601985f7936b2e3545e618b1c888b6c7a748544bbafde
data/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.1.7] - 2022-03-05
4
+
5
+ - Code cleanup, removal of references to older name
6
+ - Fix for the litedb rake tasks (thanks: netmute)
7
+ - More fixes for the new concurrency model
8
+ - Introduced a logger for the Litejobqueue (doesn't work with Polyphony, fix should come soon)
9
+
3
10
  ## [0.1.6] - 2022-03-03
4
11
 
5
12
  - Revamped the locking model, more robust, minimal performance hit
data/README.md CHANGED
@@ -1,9 +1,9 @@
1
1
  ![litestack](https://github.com/oldmoe/litestack/blob/master/assets/litestack_logo_teal_large.png?raw=true)
2
2
 
3
3
 
4
- litestack is a revolutionary gem for Ruby and Ruby on Rails that provides an all-in-one solution for web application development. It exploits the power and embeddedness of SQLite to include a full-fledged SQL database, a fast cache, a robust job queue, and a simple yet performant full-text search all in a single package.
4
+ litestack is a revolutionary gem for Ruby and Ruby on Rails that provides an all-in-one solution for web application development. It exploits the power and embeddedness of SQLite to include a full-fledged SQL database, a fast cache and a robust job queue all in a single package.
5
5
 
6
- Compared to conventional approaches that require separate servers and databases, LiteStack offers superior performance, efficiency, ease of use, and cost savings. Its embedded database and cache reduce memory and CPU usage, while its simple interface streamlines the development process. Overall, LiteStack sets a new standard for web application development and is an excellent choice for those who demand speed, efficiency, and simplicity.
6
+ Compared to conventional approaches that require separate servers and databases, Litestack offers superior performance, efficiency, ease of use, and cost savings. Its embedded database and cache reduce memory and CPU usage, while its simple interface streamlines the development process. Overall, LiteStack sets a new standard for web application development and is an excellent choice for those who demand speed, efficiency, and simplicity.
7
7
 
8
8
  You can read more about why litestack can be a good choice for your next web application **[here](WHYLITESTACK.md)**, you might also be interested in litestack **[benchmarks](BENCHMARKS.md)**.
9
9
 
@@ -83,7 +83,7 @@ adapter: litedb
83
83
  litedb offers integration with the Sequel database toolkit and can be configured as follows
84
84
 
85
85
  ```ruby
86
- DB = Sequel.conncet("litedb://path_to_db_file")
86
+ DB = Sequel.connect("litedb://path_to_db_file")
87
87
  ```
88
88
 
89
89
 
data/bench/bench.rb CHANGED
@@ -2,8 +2,8 @@ require 'sqlite3'
2
2
 
3
3
  def bench(msg, iterations=1000)
4
4
  GC.start
5
- GC.compact
6
- print "Starting #{iterations} iterations of #{msg} ... "
5
+ #GC.compact
6
+ STDERR.puts "Starting #{iterations} iterations of #{msg} ... "
7
7
  t1 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
8
8
  iterations.times do |i|
9
9
  yield i
@@ -12,7 +12,7 @@ def bench(msg, iterations=1000)
12
12
  time = ((t2 - t1)*1000).to_i.to_f / 1000 rescue 0
13
13
  ips = ((iterations/(t2-t1))*100).to_i.to_f / 100 rescue "infinity?"
14
14
  #{m: msg, t: time, ips: iteratinos/time, i: iterations}
15
- puts "finished in #{time} seconds (#{ips} ips)"
15
+ STDERR.puts " .. finished in #{time} seconds (#{ips} ips)"
16
16
  end
17
17
 
18
18
  @db = SQLite3::Database.new(":memory:") # sqlite database for fast random string generation
@@ -1,29 +1,45 @@
1
- #require 'polyphony'
2
- require 'async/scheduler'
3
1
  require './bench'
4
2
 
5
- Fiber.set_scheduler Async::Scheduler.new
3
+ count = ARGV[0].to_i rescue 1000
4
+ env = ARGV[1] || "t"
5
+ delay = ARGV[2].to_f rescue 0
6
6
 
7
- count = 10000
7
+ # Sidekiq bench
8
+ ###############
8
9
  require './skjob.rb'
9
- require './uljob.rb'
10
-
11
- puts Litesupport.environment
12
10
 
13
- t = Time.now.to_f
11
+ t = Process.clock_gettime(Process::CLOCK_MONOTONIC)
14
12
  puts "make sure sidekiq is started with skjob.rb as the job"
15
13
  bench("enqueuing sidekiq jobs", count) do |i|
16
- SidekiqJob.perform_async(count, t)
14
+ SidekiqJob.perform_async(count, t, delay)
17
15
  end
18
16
 
19
17
  puts "Don't forget to check the sidekiq log for processing time conclusion"
20
18
 
21
- t = Time.now.to_f
19
+ # Litejob bench
20
+ ###############
21
+
22
+ if env == "t" # threaded
23
+ # do nothing
24
+ elsif env == "a" # async
25
+ require 'async/scheduler'
26
+ Fiber.set_scheduler Async::Scheduler.new
27
+ elsif env == "p" # polyphony
28
+ require 'polyphony'
29
+ end
30
+
31
+ require './uljob.rb'
32
+
33
+ STDERR.puts "litejob started in #{Litesupport.environment} environmnet"
34
+
35
+ t = Process.clock_gettime(Process::CLOCK_MONOTONIC)
22
36
  bench("enqueuing litejobs", count) do |i|
23
- MyJob.perform_async(count, t)
37
+ MyJob.perform_async(count, t, delay)
24
38
  end
25
39
 
26
- Fiber.scheduler.run
40
+ puts "Please wait for the benchmark to finish .."
41
+
42
+ Fiber.scheduler.run if env == "a"
27
43
 
28
44
  sleep
29
45
 
data/bench/skjob.rb CHANGED
@@ -3,11 +3,13 @@ require 'sidekiq'
3
3
  class SidekiqJob
4
4
  include Sidekiq::Job
5
5
  @@count = 0
6
- def perform(count, time)
7
- sleep 0.1
6
+ def perform(count, time, sleep_interval = nil)
7
+ sleep sleep_interval if sleep_interval
8
8
  @@count += 1
9
9
  if @@count == count
10
- puts "finished in #{Time.now.to_f - time} seconds (#{count / (Time.now.to_f - time)} jps)"
10
+ now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
11
+ STDERR.puts "Sidekiq finished in #{now - time} seconds (#{count / (now - time)} jps)"
12
+ @@count = 0
11
13
  end
12
14
  end
13
15
  end
data/bench/uljob.rb CHANGED
@@ -4,12 +4,13 @@ require '../lib/litestack'
4
4
  class MyJob
5
5
  include Litejob
6
6
  @@count = 0
7
- # self.queue = :normal
8
- def perform(count, time)
9
- #sleep 0.1
7
+ #self.queue = :default
8
+ def perform(count, time, sleep_interval = nil)
9
+ sleep sleep_interval if sleep_interval
10
10
  @@count += 1
11
11
  if @@count == count
12
- puts "UL finished in #{Time.now.to_f - time} seconds (#{count / (Time.now.to_f - time)} jps)"
12
+ now = Process.clock_gettime(Process::CLOCK_MONOTONIC)
13
+ STDERR.puts "Litejob finished in #{now - time} seconds (#{count / (now - time)} jps)"
13
14
  end
14
15
  end
15
16
  end
@@ -95,7 +95,7 @@ module ActiveRecord
95
95
  end
96
96
 
97
97
  module DatabaseTasks
98
- register_task(/ultralite/, "ActiveRecord::Tasks::LitedbDatabaseTasks")
98
+ register_task(/litedb/, "ActiveRecord::Tasks::LitedbDatabaseTasks")
99
99
 
100
100
  end
101
101
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_stringe_literal: true
2
-
2
+ require 'logger'
3
3
  require 'oj'
4
4
  require 'yaml'
5
5
  require_relative './litequeue'
@@ -32,9 +32,10 @@ class Litejobqueue
32
32
  DEFAULT_OPTIONS = {
33
33
  config_path: "./litejob.yml",
34
34
  path: "./queue.db",
35
- queues: [["default", 5]],
36
- workers: 1,
37
- sleep_intervals: [0.001, 0.005, 0.025, 0.125, 0.625, 3.125]
35
+ queues: [["default", 1]],
36
+ workers: 5,
37
+ logger: STDOUT,
38
+ sleep_intervals: [0.001, 0.005, 0.025, 0.125, 0.625, 1.0, 2.0]
38
39
  }
39
40
 
40
41
  @@queue = nil
@@ -57,7 +58,6 @@ class Litejobqueue
57
58
  #
58
59
  def initialize(options = {})
59
60
  @options = DEFAULT_OPTIONS.merge(options)
60
- @worker_sleep_index = 0
61
61
  config = YAML.load_file(@options[:config_path]) rescue {} # an empty hash won't hurt
62
62
  config.keys.each do |k| # symbolize keys
63
63
  config[k.to_sym] = config[k]
@@ -65,6 +65,11 @@ class Litejobqueue
65
65
  end
66
66
  @options.merge!(config)
67
67
  @queue = Litequeue.new(@options) # create a new queue object
68
+ if @options[:logger].respond_to? :info
69
+ @logger = @options[:logger]
70
+ else
71
+ @logger = Logger.new(@options[:logger])
72
+ end
68
73
  # group and order queues according to their priority
69
74
  pgroups = {}
70
75
  @options[:queues].each do |q|
@@ -72,7 +77,7 @@ class Litejobqueue
72
77
  pgroups[q[1]] << [q[0], q[2] == "spawn"]
73
78
  end
74
79
  @queues = pgroups.keys.sort.reverse.collect{|p| [p, pgroups[p]]}
75
- @workers = @options[:workers].times.collect{create_worker}
80
+ @workers = @options[:workers].times.collect{ create_worker }
76
81
  end
77
82
 
78
83
  # push a job to the queue
@@ -85,7 +90,10 @@ class Litejobqueue
85
90
  # jobqueue.push(EasyJob, params) # the job will be performed asynchronously
86
91
  def push(jobclass, params, delay=0, queue=nil)
87
92
  payload = Oj.dump([jobclass, params])
88
- @queue.push(payload, delay, queue)
93
+ #res =
94
+ res = @queue.push(payload, delay, queue)
95
+ @logger.info("[litejob]:[ENQ] id: #{res} class: #{jobclass}")
96
+ res
89
97
  end
90
98
 
91
99
  # delete a job from the job queue
@@ -99,7 +107,9 @@ class Litejobqueue
99
107
  # jobqueue.delete(id)
100
108
  def delete(id)
101
109
  job = @queue.delete(id)
110
+ @logger.info("[litejob]:[DEL] job: #{job}")
102
111
  Oj.load(job) if job
112
+ job
103
113
  end
104
114
 
105
115
  private
@@ -111,27 +121,36 @@ class Litejobqueue
111
121
  else
112
122
  yield
113
123
  end
114
- end
115
-
124
+ end
125
+
116
126
  # create a worker according to environment
117
127
  def create_worker
118
128
  Litesupport.spawn do
129
+ if @options[:logger].respond_to? :info
130
+ logger = @options[:logger]
131
+ else
132
+ logger = Logger.new(@options[:logger])
133
+ end
134
+ worker_sleep_index = 0
135
+ i = 0
119
136
  loop do
120
137
  processed = 0
121
138
  @queues.each do |level| # iterate through the levels
122
139
  level[1].each do |q| # iterate through the queues in the level
123
140
  index = 0
124
141
  max = level[0]
125
- while index < max && payload = @queue.pop(q[0]) # fearlessly use the same queue object
142
+ while index < max && payload = @queue.pop(q[0], 1) # fearlessly use the same queue object
126
143
  processed += 1
127
144
  index += 1
128
145
  begin
129
146
  id, job = payload[0], payload[1]
130
147
  job = Oj.load(job)
148
+ logger.info "[litejob]:[DEQ] id: #{id} class: #{job[0]}"
131
149
  klass = eval(job[0])
132
150
  schedule(q[1]) do # run the job in a new context
133
151
  begin
134
152
  klass.new.perform(*job[1])
153
+ logger.info "[litejob]:[END] id: #{id} class: #{job[0]}"
135
154
  rescue Exception => e
136
155
  puts e
137
156
  puts e.message
@@ -143,18 +162,18 @@ class Litejobqueue
143
162
  puts e.message
144
163
  puts e.backtrace
145
164
  end
146
- Litesupport.switch #give other context a chance to run here
165
+ Litesupport.switch #give other contexts a chance to run here
147
166
  end
148
167
  end
149
168
  end
150
169
  if processed == 0
151
- sleep @options[:sleep_intervals][@worker_sleep_index]
152
- @worker_sleep_index += 1 if @worker_sleep_index < @options[:sleep_intervals].length - 1
170
+ sleep @options[:sleep_intervals][worker_sleep_index]
171
+ worker_sleep_index += 1 if worker_sleep_index < @options[:sleep_intervals].length - 1
153
172
  else
154
- @worker_sleep_index = 0 # reset the index
173
+ worker_sleep_index = 0 # reset the index
155
174
  end
156
175
  end
157
176
  end
158
- end
177
+ end
159
178
 
160
179
  end
@@ -52,8 +52,10 @@ class Litequeue
52
52
  alias_method :"<<", :push
53
53
 
54
54
  # pop an item from the queue, optionally with a specific queue name (default queue name is 'default')
55
- def pop(queue='default')
56
- @queue.acquire {|q| q.stmts[:pop].execute!(queue)[0] }
55
+ def pop(queue='default', limit = 1)
56
+ res = @queue.acquire {|q| res = q.stmts[:pop].execute!(queue, limit)[0] }
57
+ #return res[0] if res.length == 1
58
+ #res
57
59
  end
58
60
 
59
61
  # delete an item from the queue
@@ -90,7 +92,7 @@ class Litequeue
90
92
  db.mmap_size = @options[:mmap_size]
91
93
  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
94
  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")
95
+ db.stmts[:pop] = db.prepare("DELETE FROM _ul_queue_ WHERE (queue, fire_at, id) IN (SELECT queue, fire_at, id FROM _ul_queue_ WHERE queue = ifnull($1, 'default') AND fire_at <= (unixepoch()) ORDER BY fire_at ASC LIMIT ifnull($2, 1)) RETURNING fire_at || '-' || id, value")
94
96
  db.stmts[:delete] = db.prepare("DELETE FROM _ul_queue_ WHERE queue = ifnull($1, 'default') AND fire_at = $2 AND id = $3 RETURNING value")
95
97
  db
96
98
  end
@@ -1,4 +1,5 @@
1
1
  require 'sqlite3'
2
+ require 'hiredis'
2
3
 
3
4
  module Litesupport
4
5
 
@@ -11,6 +12,11 @@ module Litesupport
11
12
  @env ||= detect_environment
12
13
  end
13
14
 
15
+ def self.max_contexts
16
+ return 50 if environment == :fiber || environment == :polyphony
17
+ 5
18
+ end
19
+
14
20
  # identify which environment we are running in
15
21
  # we currently support :fiber, :polyphony, :iodine & :threaded
16
22
  # in the future we might want to expand to other environments
@@ -55,7 +61,7 @@ module Litesupport
55
61
  Thread.current.switch_fiber
56
62
  true
57
63
  else
58
- # do nothing in case of thread, switching will auto-happen
64
+ #Thread.pass
59
65
  false
60
66
  end
61
67
  end
@@ -124,7 +130,7 @@ module Litesupport
124
130
  result = nil
125
131
  while !acquired do
126
132
  @mutex.synchronize do
127
- if resource = @resources.find{|r| r[1] == :free}
133
+ if resource = @resources.find{|r| r[1] == :free }
128
134
  resource[1] = :busy
129
135
  begin
130
136
  result = yield resource[0]
@@ -133,11 +139,10 @@ module Litesupport
133
139
  ensure
134
140
  resource[1] = :free
135
141
  acquired = true
136
- return nil
137
142
  end
138
143
  end
139
144
  end
140
- sleep 0.0001 unless acquired
145
+ sleep 0.001 unless acquired
141
146
  end
142
147
  result
143
148
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Litestack
4
- VERSION = "0.1.6"
4
+ VERSION = "0.1.7"
5
5
  end