litestack 0.1.6 → 0.1.8
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/BENCHMARKS.md +1 -1
- data/CHANGELOG.md +15 -0
- data/README.md +4 -4
- data/bench/bench.rb +3 -3
- data/bench/bench_cache_raw.rb +10 -5
- data/bench/bench_jobs_rails.rb +20 -14
- data/bench/bench_jobs_raw.rb +26 -12
- data/bench/skjob.rb +5 -3
- data/bench/uljob.rb +5 -4
- data/lib/active_job/queue_adapters/litejob_adapter.rb +7 -3
- data/lib/active_record/connection_adapters/litedb_adapter.rb +1 -1
- data/lib/litestack/litejob.rb +18 -13
- data/lib/litestack/litejobqueue.rb +132 -29
- data/lib/litestack/litequeue.rb +14 -4
- data/lib/litestack/litesupport.rb +32 -8
- data/lib/litestack/version.rb +1 -1
- data/lib/sequel/adapters/shared/litedb.rb +1054 -0
- metadata +3 -3
- data/lib/active_support/cache/ultralite_cache_store.rb +0 -100
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: d08724a0b9293f55ebed24ba6d738b103795b69563a210c4454322fc382e174e
|
4
|
+
data.tar.gz: 01401bfca727b4ef9452a9efafba846c886845dd43c1d8f5f5ce84cee22ab61f
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 729320670e62261596eabbfd8f8d931117507317127e9fd9e5a796928e7031418455ca351dd2de815d4860edc2e688c0bbdb79560d3907cb3545deb75a1b4fae
|
7
|
+
data.tar.gz: d75cc4f23694c726a361b0bc474f561b8ffc4db074ef275839917d4c82461aca7a1c4a0b0e939cd749a7e7afbb58de86f3de254a6adadfc7f6d91ea9021218f3
|
data/BENCHMARKS.md
CHANGED
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,20 @@
|
|
1
1
|
## [Unreleased]
|
2
2
|
|
3
|
+
## [0.1.8] - 2022-03-08
|
4
|
+
|
5
|
+
- More code cleanups, more test coverage
|
6
|
+
- Retry support for jobs in Litejob
|
7
|
+
- Job storage and garbage collection for failed jobs
|
8
|
+
- Initial graceful shutdown support for Litejob (incomplete)
|
9
|
+
- More configuration options for Litejob
|
10
|
+
|
11
|
+
## [0.1.7] - 2022-03-05
|
12
|
+
|
13
|
+
- Code cleanup, removal of references to older name
|
14
|
+
- Fix for the litedb rake tasks (thanks: netmute)
|
15
|
+
- More fixes for the new concurrency model
|
16
|
+
- Introduced a logger for the Litejobqueue (doesn't work with Polyphony, fix should come soon)
|
17
|
+
|
3
18
|
## [0.1.6] - 2022-03-03
|
4
19
|
|
5
20
|
- 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
|
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,
|
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.
|
86
|
+
DB = Sequel.connect("litedb://path_to_db_file")
|
87
87
|
```
|
88
88
|
|
89
89
|
|
@@ -120,7 +120,7 @@ litejob is a fast and very efficient job queue processor for Ruby applications.
|
|
120
120
|
require 'litestack'
|
121
121
|
# define your job class
|
122
122
|
class MyJob
|
123
|
-
include ::
|
123
|
+
include ::Litejob
|
124
124
|
|
125
125
|
queue = :default
|
126
126
|
|
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
|
-
|
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
|
-
|
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
|
data/bench/bench_cache_raw.rb
CHANGED
@@ -16,15 +16,18 @@ redis = Redis.new # default settings
|
|
16
16
|
|
17
17
|
values = []
|
18
18
|
keys = []
|
19
|
-
count =
|
19
|
+
count = 5
|
20
20
|
count.times { keys << random_str(10) }
|
21
21
|
|
22
|
-
[10, 100, 1000, 10000].each do |size|
|
22
|
+
[10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000].each do |size|
|
23
23
|
count.times do
|
24
24
|
values << random_str(size)
|
25
25
|
end
|
26
26
|
|
27
27
|
random_keys = keys.shuffle
|
28
|
+
|
29
|
+
GC.compact
|
30
|
+
|
28
31
|
puts "Benchmarks for values of size #{size} bytes"
|
29
32
|
puts "=========================================================="
|
30
33
|
puts "== Writes =="
|
@@ -33,7 +36,7 @@ count.times { keys << random_str(10) }
|
|
33
36
|
end
|
34
37
|
|
35
38
|
bench("Redis writes", count) do |i|
|
36
|
-
redis.set(keys[i], values[i])
|
39
|
+
#redis.set(keys[i], values[i])
|
37
40
|
end
|
38
41
|
|
39
42
|
puts "== Reads =="
|
@@ -42,11 +45,13 @@ count.times { keys << random_str(10) }
|
|
42
45
|
end
|
43
46
|
|
44
47
|
bench("Redis reads", count) do |i|
|
45
|
-
redis.get(random_keys[i])
|
48
|
+
#redis.get(random_keys[i])
|
46
49
|
end
|
47
50
|
puts "=========================================================="
|
48
51
|
|
49
52
|
values = []
|
53
|
+
|
54
|
+
|
50
55
|
end
|
51
56
|
|
52
57
|
|
@@ -64,5 +69,5 @@ end
|
|
64
69
|
cache.clear
|
65
70
|
redis.flushdb
|
66
71
|
|
67
|
-
sleep
|
72
|
+
#sleep
|
68
73
|
|
data/bench/bench_jobs_rails.rb
CHANGED
@@ -1,22 +1,14 @@
|
|
1
1
|
require './bench'
|
2
|
-
require 'async/scheduler'
|
3
|
-
|
4
|
-
#ActiveJob::Base.logger = Logger.new(IO::NULL)
|
5
2
|
|
3
|
+
count = ARGV[0].to_i rescue 1000
|
4
|
+
env = ARGV[1] || "t"
|
5
|
+
delay = ARGV[2].to_f rescue 0
|
6
6
|
|
7
|
-
Fiber.set_scheduler Async::Scheduler.new
|
8
7
|
|
9
|
-
|
10
|
-
|
11
|
-
ActiveSupport::IsolatedExecutionState.isolation_level = :fiber
|
8
|
+
#ActiveJob::Base.logger = Logger.new(IO::NULL)
|
12
9
|
|
13
10
|
require './rails_job.rb'
|
14
11
|
|
15
|
-
|
16
|
-
puts Litesupport.environment
|
17
|
-
|
18
|
-
count = 1000
|
19
|
-
|
20
12
|
RailsJob.queue_adapter = :sidekiq
|
21
13
|
t = Time.now.to_f
|
22
14
|
puts "Make sure sidekiq is started with -c ./rails_job.rb"
|
@@ -26,13 +18,27 @@ end
|
|
26
18
|
|
27
19
|
puts "Don't forget to check the sidekiq log for processing time conclusion"
|
28
20
|
|
21
|
+
|
22
|
+
# Litejob bench
|
23
|
+
###############
|
24
|
+
|
25
|
+
if env == "a" # threaded
|
26
|
+
require 'async/scheduler'
|
27
|
+
Fiber.set_scheduler Async::Scheduler.new
|
28
|
+
ActiveSupport::IsolatedExecutionState.isolation_level = :fiber
|
29
|
+
end
|
30
|
+
|
31
|
+
require_relative '../lib/active_job/queue_adapters/litejob_adapter'
|
32
|
+
puts Litesupport.environment
|
33
|
+
|
29
34
|
RailsJob.queue_adapter = :litejob
|
30
35
|
t = Time.now.to_f
|
31
36
|
bench("enqueuing litejobs", count) do
|
32
37
|
RailsJob.perform_later(count, t)
|
33
38
|
end
|
34
39
|
|
35
|
-
|
36
|
-
|
40
|
+
if env == "a" # threaded
|
41
|
+
Fiber.scheduler.run
|
42
|
+
end
|
37
43
|
|
38
44
|
sleep
|
data/bench/bench_jobs_raw.rb
CHANGED
@@ -1,29 +1,43 @@
|
|
1
|
-
#require 'polyphony'
|
2
|
-
require 'async/scheduler'
|
3
1
|
require './bench'
|
4
2
|
|
5
|
-
|
3
|
+
count = ARGV[0].to_i rescue 1000
|
4
|
+
env = ARGV[1] || "t"
|
5
|
+
delay = ARGV[2].to_f rescue 0
|
6
6
|
|
7
|
-
|
7
|
+
# Sidekiq bench
|
8
|
+
###############
|
8
9
|
require './skjob.rb'
|
9
|
-
require './uljob.rb'
|
10
|
-
|
11
|
-
puts Litesupport.environment
|
12
10
|
|
13
|
-
t =
|
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
|
-
|
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
|
+
end
|
28
|
+
|
29
|
+
require './uljob.rb'
|
30
|
+
|
31
|
+
STDERR.puts "litejob started in #{Litesupport.environment} environmnet"
|
32
|
+
|
33
|
+
t = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
22
34
|
bench("enqueuing litejobs", count) do |i|
|
23
|
-
MyJob.perform_async(count, t)
|
35
|
+
MyJob.perform_async(count, t, delay)
|
24
36
|
end
|
25
37
|
|
26
|
-
|
38
|
+
puts "Please wait for the benchmark to finish .."
|
39
|
+
|
40
|
+
Fiber.scheduler.run if env == "a"
|
27
41
|
|
28
42
|
sleep
|
29
43
|
|
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
|
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
|
-
|
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
|
-
#
|
8
|
-
def perform(count, time)
|
9
|
-
|
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
|
-
|
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
|
@@ -16,12 +16,16 @@ module ActiveJob
|
|
16
16
|
DEFAULT_OPTIONS = {
|
17
17
|
config_path: "./config/litejob.yml",
|
18
18
|
path: "../db/queue.db",
|
19
|
-
queues: [["default", 1
|
20
|
-
|
19
|
+
queues: [["default", 1]],
|
20
|
+
logger: nil, # Rails performs its logging already
|
21
|
+
retries: 5, # It is recommended to stop retries at the Rails level
|
22
|
+
workers: 5
|
21
23
|
}
|
22
24
|
|
23
25
|
def initialize(options={})
|
24
|
-
|
26
|
+
# we currently don't honour individual options per job class
|
27
|
+
# possible in the future?
|
28
|
+
# Job.options = DEFAULT_OPTIONS.merge(options)
|
25
29
|
end
|
26
30
|
|
27
31
|
def enqueue(job) # :nodoc:
|
data/lib/litestack/litejob.rb
CHANGED
@@ -43,7 +43,7 @@ module Litejob
|
|
43
43
|
private
|
44
44
|
def self.included(klass)
|
45
45
|
klass.extend(ClassMethods)
|
46
|
-
klass.get_jobqueue
|
46
|
+
#klass.get_jobqueue
|
47
47
|
end
|
48
48
|
|
49
49
|
module ClassMethods
|
@@ -52,32 +52,37 @@ module Litejob
|
|
52
52
|
end
|
53
53
|
|
54
54
|
def perform_at(time, *params)
|
55
|
-
delay = time - Time.now.to_i
|
55
|
+
delay = time.to_i - Time.now.to_i
|
56
56
|
get_jobqueue.push(self.name, params, delay, queue)
|
57
57
|
end
|
58
58
|
|
59
|
-
def
|
59
|
+
def perform_in(delay, *params)
|
60
60
|
get_jobqueue.push(self.name, params, delay, queue)
|
61
61
|
end
|
62
|
-
|
63
|
-
def
|
64
|
-
|
62
|
+
|
63
|
+
def perform_after(delay, *params)
|
64
|
+
perform_in(delay, *params)
|
65
65
|
end
|
66
|
-
|
67
|
-
def
|
68
|
-
|
66
|
+
|
67
|
+
def process_jobs
|
68
|
+
get_jobqueue
|
69
69
|
end
|
70
|
-
|
70
|
+
|
71
|
+
def delete(id, queue_name=nil)
|
72
|
+
queue_name ||= queue
|
73
|
+
get_jobqueue.delete(id, queue)
|
74
|
+
end
|
75
|
+
|
71
76
|
def queue
|
72
|
-
@@
|
77
|
+
@@queue ||= "default"
|
73
78
|
end
|
74
79
|
|
75
80
|
def queue=(queue_name)
|
76
|
-
@@
|
81
|
+
@@queue = queue_name.to_s
|
77
82
|
end
|
78
83
|
|
79
84
|
def get_jobqueue
|
80
|
-
Litejobqueue.jobqueue
|
85
|
+
Litejobqueue.jobqueue
|
81
86
|
end
|
82
87
|
end
|
83
88
|
|
@@ -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,13 +32,21 @@ class Litejobqueue
|
|
32
32
|
DEFAULT_OPTIONS = {
|
33
33
|
config_path: "./litejob.yml",
|
34
34
|
path: "./queue.db",
|
35
|
-
queues: [["default",
|
36
|
-
workers:
|
37
|
-
|
35
|
+
queues: [["default", 1]],
|
36
|
+
workers: 5,
|
37
|
+
retries: 5,
|
38
|
+
retry_delay: 60,
|
39
|
+
retry_delay_multiplier: 10,
|
40
|
+
dead_job_retention: 10 * 24 * 3600,
|
41
|
+
gc_sleep_interval: 7200,
|
42
|
+
logger: 'STDOUT',
|
43
|
+
sleep_intervals: [0.001, 0.005, 0.025, 0.125, 0.625, 1.0, 2.0]
|
38
44
|
}
|
39
45
|
|
40
46
|
@@queue = nil
|
41
47
|
|
48
|
+
attr_reader :running
|
49
|
+
|
42
50
|
# a method that returns a single instance of the job queue
|
43
51
|
# for use by Litejob
|
44
52
|
def self.jobqueue(options = {})
|
@@ -57,14 +65,30 @@ class Litejobqueue
|
|
57
65
|
#
|
58
66
|
def initialize(options = {})
|
59
67
|
@options = DEFAULT_OPTIONS.merge(options)
|
60
|
-
@worker_sleep_index = 0
|
61
68
|
config = YAML.load_file(@options[:config_path]) rescue {} # an empty hash won't hurt
|
62
69
|
config.keys.each do |k| # symbolize keys
|
63
70
|
config[k.to_sym] = config[k]
|
64
71
|
config.delete k
|
65
72
|
end
|
66
73
|
@options.merge!(config)
|
74
|
+
@options.merge!(options) # make sure options passed to initialize trump everything else
|
75
|
+
|
67
76
|
@queue = Litequeue.new(@options) # create a new queue object
|
77
|
+
|
78
|
+
# create logger
|
79
|
+
if @options[:logger].respond_to? :info
|
80
|
+
@logger = @options[:logger]
|
81
|
+
elsif @options[:logger] == 'STDOUT'
|
82
|
+
@logger = Logger.new(STDOUT)
|
83
|
+
elsif @options[:logger] == 'STDERR'
|
84
|
+
@logger = Logger.new(STDERR)
|
85
|
+
elsif @options[:logger].nil?
|
86
|
+
@logger = Logger.new(IO::NULL)
|
87
|
+
elsif @options[:logger].is_a? String
|
88
|
+
@logger = Logger.new(@options[:logger])
|
89
|
+
else
|
90
|
+
@logger = Logger.new(IO::NULL)
|
91
|
+
end
|
68
92
|
# group and order queues according to their priority
|
69
93
|
pgroups = {}
|
70
94
|
@options[:queues].each do |q|
|
@@ -72,7 +96,25 @@ class Litejobqueue
|
|
72
96
|
pgroups[q[1]] << [q[0], q[2] == "spawn"]
|
73
97
|
end
|
74
98
|
@queues = pgroups.keys.sort.reverse.collect{|p| [p, pgroups[p]]}
|
75
|
-
@
|
99
|
+
@running = true
|
100
|
+
@workers = @options[:workers].times.collect{ create_worker }
|
101
|
+
|
102
|
+
@gc = create_garbage_collector
|
103
|
+
@jobs_in_flight = 0
|
104
|
+
@mutex = Litesupport::Mutex.new
|
105
|
+
|
106
|
+
at_exit do
|
107
|
+
@running = false
|
108
|
+
puts "--- Litejob detected an exit attempt, cleaning up"
|
109
|
+
index = 0
|
110
|
+
while @jobs_in_flight > 0 and index < 5
|
111
|
+
puts "--- Waiting for #{@jobs_in_flight} jobs to finish"
|
112
|
+
sleep 1
|
113
|
+
index += 1
|
114
|
+
end
|
115
|
+
puts " --- Exiting with #{@jobs_in_flight} jobs in flight"
|
116
|
+
end
|
117
|
+
|
76
118
|
end
|
77
119
|
|
78
120
|
# push a job to the queue
|
@@ -84,8 +126,10 @@ class Litejobqueue
|
|
84
126
|
# jobqueue = Litejobqueue.new
|
85
127
|
# jobqueue.push(EasyJob, params) # the job will be performed asynchronously
|
86
128
|
def push(jobclass, params, delay=0, queue=nil)
|
87
|
-
payload = Oj.dump(
|
88
|
-
@queue.push(payload, delay, queue)
|
129
|
+
payload = Oj.dump({klass: jobclass, params: params, retries: @options[:retries], queue: queue})
|
130
|
+
res = @queue.push(payload, delay, queue)
|
131
|
+
@logger.info("[litejob]:[ENQ] id: #{res} job: #{jobclass}")
|
132
|
+
res
|
89
133
|
end
|
90
134
|
|
91
135
|
# delete a job from the job queue
|
@@ -96,14 +140,42 @@ class Litejobqueue
|
|
96
140
|
# end
|
97
141
|
# jobqueue = Litejobqueue.new
|
98
142
|
# id = jobqueue.push(EasyJob, params, 10) # queue for processing in 10 seconds
|
99
|
-
# jobqueue.delete(id)
|
100
|
-
def delete(id)
|
101
|
-
job = @queue.delete(id)
|
102
|
-
|
143
|
+
# jobqueue.delete(id, 'default')
|
144
|
+
def delete(id, queue=nil)
|
145
|
+
job = @queue.delete(id, queue)
|
146
|
+
@logger.info("[litejob]:[DEL] job: #{job}")
|
147
|
+
job = Oj.load(job[0]) if job
|
148
|
+
job
|
149
|
+
end
|
150
|
+
|
151
|
+
# delete all jobs in a certain named queue
|
152
|
+
# or delete all jobs if the queue name is nil
|
153
|
+
def clear(queue=nil)
|
154
|
+
@queue.clear(queue)
|
155
|
+
end
|
156
|
+
|
157
|
+
# stop the queue object (does not delete the jobs in the queue)
|
158
|
+
# specifically useful for testing
|
159
|
+
def stop
|
160
|
+
@running = false
|
161
|
+
@@queue = nil
|
162
|
+
end
|
163
|
+
|
164
|
+
|
165
|
+
def count(queue=nil)
|
166
|
+
@queue.count(queue)
|
103
167
|
end
|
104
168
|
|
105
169
|
private
|
106
170
|
|
171
|
+
def job_started
|
172
|
+
Litesupport.synchronize(@mutex){@jobs_in_flight += 1}
|
173
|
+
end
|
174
|
+
|
175
|
+
def job_finished
|
176
|
+
Litesupport.synchronize(@mutex){@jobs_in_flight -= 1}
|
177
|
+
end
|
178
|
+
|
107
179
|
# optionally run a job in its own context
|
108
180
|
def schedule(spawn = false, &block)
|
109
181
|
if spawn
|
@@ -111,50 +183,81 @@ class Litejobqueue
|
|
111
183
|
else
|
112
184
|
yield
|
113
185
|
end
|
114
|
-
end
|
115
|
-
|
186
|
+
end
|
187
|
+
|
116
188
|
# create a worker according to environment
|
117
189
|
def create_worker
|
118
190
|
Litesupport.spawn do
|
119
|
-
|
191
|
+
worker_sleep_index = 0
|
192
|
+
while @running do
|
120
193
|
processed = 0
|
121
194
|
@queues.each do |level| # iterate through the levels
|
122
195
|
level[1].each do |q| # iterate through the queues in the level
|
123
196
|
index = 0
|
124
197
|
max = level[0]
|
125
|
-
while index < max && payload = @queue.pop(q[0]) # fearlessly use the same queue object
|
198
|
+
while index < max && payload = @queue.pop(q[0], 1) # fearlessly use the same queue object
|
126
199
|
processed += 1
|
127
200
|
index += 1
|
128
201
|
begin
|
129
202
|
id, job = payload[0], payload[1]
|
130
203
|
job = Oj.load(job)
|
131
|
-
|
204
|
+
# first capture the original job id
|
205
|
+
job[:id] = id if job[:retries].to_i == @options[:retries].to_i
|
206
|
+
@logger.info "[litejob]:[DEQ] job:#{job}"
|
207
|
+
klass = eval(job[:klass])
|
132
208
|
schedule(q[1]) do # run the job in a new context
|
209
|
+
job_started #(Litesupport.current_context)
|
133
210
|
begin
|
134
|
-
klass.new.perform(*job[
|
211
|
+
klass.new.perform(*job[:params])
|
212
|
+
@logger.info "[litejob]:[END] job:#{job}"
|
135
213
|
rescue Exception => e
|
136
|
-
|
137
|
-
|
138
|
-
|
214
|
+
# we can retry the failed job now
|
215
|
+
if job[:retries] == 0
|
216
|
+
@logger.error "[litejob]:[ERR] job: #{job} failed with #{e}:#{e.message}, retries exhausted, moved to _dead queue"
|
217
|
+
@queue.push(Oj.dump(job), @options[:dead_job_retention], '_dead')
|
218
|
+
else
|
219
|
+
retry_delay = @options[:retry_delay_multiplier].pow(@options[:retries] - job[:retries]) * @options[:retry_delay]
|
220
|
+
job[:retries] -= 1
|
221
|
+
@logger.error "[litejob]:[ERR] job: #{job} failed with #{e}:#{e.message}, retrying in #{retry_delay}"
|
222
|
+
@queue.push(Oj.dump(job), retry_delay, q[0])
|
223
|
+
@logger.info "[litejob]:[ENQ] job: #{job} enqueued"
|
224
|
+
end
|
139
225
|
end
|
226
|
+
job_finished #(Litesupport.current_context)
|
140
227
|
end
|
141
228
|
rescue Exception => e
|
142
|
-
|
143
|
-
|
144
|
-
|
229
|
+
# this is an error in the extraction of job info
|
230
|
+
# retrying here will not be useful
|
231
|
+
@logger.error "[litejob]:[ERR] failed to extract job info for: #{payload} with #{e}:#{e.message}"
|
145
232
|
end
|
146
|
-
Litesupport.switch #give other
|
233
|
+
Litesupport.switch #give other contexts a chance to run here
|
147
234
|
end
|
148
235
|
end
|
149
236
|
end
|
150
237
|
if processed == 0
|
151
|
-
sleep @options[:sleep_intervals][
|
152
|
-
|
238
|
+
sleep @options[:sleep_intervals][worker_sleep_index]
|
239
|
+
worker_sleep_index += 1 if worker_sleep_index < @options[:sleep_intervals].length - 1
|
153
240
|
else
|
154
|
-
|
241
|
+
worker_sleep_index = 0 # reset the index
|
155
242
|
end
|
156
243
|
end
|
157
244
|
end
|
158
|
-
end
|
245
|
+
end
|
246
|
+
|
247
|
+
# create a gc for dead jobs
|
248
|
+
def create_garbage_collector
|
249
|
+
Litesupport.spawn do
|
250
|
+
while @running do
|
251
|
+
while jobs = @queue.pop('_dead', 100)
|
252
|
+
if jobs[0].is_a? Array
|
253
|
+
@logger.info "[litejob]:[DEL] garbage collector deleted #{jobs.length} dead jobs"
|
254
|
+
else
|
255
|
+
@logger.info "[litejob]:[DEL] garbage collector deleted 1 dead job"
|
256
|
+
end
|
257
|
+
end
|
258
|
+
sleep @options[:gc_sleep_interval]
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
159
262
|
|
160
263
|
end
|