workerholic 0.0.8 → 0.0.9
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/Gemfile.lock +1 -1
- data/app_test/job_test.rb +1 -6
- data/app_test/run.rb +47 -39
- data/lib/workerholic/adapters/active_job_adapter.rb +8 -6
- data/lib/workerholic/cli.rb +11 -4
- data/lib/workerholic/job.rb +3 -2
- data/lib/workerholic/job_processor.rb +17 -6
- data/lib/workerholic/job_retry.rb +5 -2
- data/lib/workerholic/job_scheduler.rb +5 -2
- data/lib/workerholic/job_serializer.rb +8 -0
- data/lib/workerholic/job_statistics.rb +38 -0
- data/lib/workerholic/job_wrapper.rb +14 -5
- data/lib/workerholic/manager.rb +5 -0
- data/lib/workerholic/statistics_api.rb +77 -0
- data/lib/workerholic/statistics_storage.rb +19 -0
- data/lib/workerholic/storage.rb +25 -0
- data/lib/workerholic/version.rb +1 -1
- data/lib/workerholic/worker.rb +3 -0
- data/lib/workerholic/worker_balancer.rb +0 -1
- data/lib/workerholic.rb +7 -1
- data/pkg/workerholic-0.0.8.gem +0 -0
- data/spec/integration/enqueuing_jobs_spec.rb +5 -3
- data/spec/job_processor_spec.rb +7 -7
- data/spec/{statistics_spec.rb → job_statistics.rb} +10 -5
- data/spec/job_wrapper_spec.rb +7 -4
- data/spec/worker_spec.rb +2 -2
- data/web/application.rb +13 -6
- data/web/public/javascripts/application.js +109 -0
- data/web/public/stylesheets/application.css +30 -14
- data/web/views/details.erb +48 -31
- data/web/views/failed.erb +27 -0
- data/web/views/index.erb +38 -33
- data/web/views/layout.erb +36 -0
- data/web/views/queues.erb +31 -0
- data/web/views/scheduled.erb +27 -0
- data/web/views/workers.erb +41 -41
- metadata +13 -5
- data/lib/workerholic/statistics.rb +0 -21
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c3ace2785bcb5ca82bc4e0d0d5c738410140f3f6
|
4
|
+
data.tar.gz: 5829d09c00b963cff402dbe34126564b28af1386
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4d310181c5f824813425a6e6c3b249d27ee0baf0e27970cf4a40d6be3529237f95567b62f96a5e58904736e4d2a816c777379eb3bb4cd95593f6bc23c2b535cc
|
7
|
+
data.tar.gz: eabb0f28a65a6a6e46757fd58e136dfa9e628bc7ab00c544bd147cd54638bfdba5c2e046ca6ccc75593ee2f5ee42cd6acfd4bd01ab3b7a4d5bc6045e1a7542fb
|
data/Gemfile.lock
CHANGED
data/app_test/job_test.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'workerholic'
|
2
|
+
require 'prime'
|
2
3
|
|
3
4
|
class JobTestFast
|
4
5
|
include Workerholic::Job
|
@@ -15,7 +16,6 @@ class JobTestSlow
|
|
15
16
|
|
16
17
|
def perform(str, num)
|
17
18
|
sleep(0.5)
|
18
|
-
puts "#{num} - #{str}"
|
19
19
|
end
|
20
20
|
end
|
21
21
|
|
@@ -23,7 +23,6 @@ class ThreadKiller
|
|
23
23
|
include Workerholic::Job
|
24
24
|
|
25
25
|
def perform(string, n)
|
26
|
-
puts "#{n}. #{string}"
|
27
26
|
Thread.main.kill
|
28
27
|
end
|
29
28
|
end
|
@@ -32,7 +31,6 @@ class LargeArg
|
|
32
31
|
include Workerholic::Job
|
33
32
|
|
34
33
|
def perform(arr, n)
|
35
|
-
puts n
|
36
34
|
end
|
37
35
|
end
|
38
36
|
|
@@ -40,7 +38,6 @@ class ManyArgs
|
|
40
38
|
include Workerholic::Job
|
41
39
|
|
42
40
|
def perform(n, *args)
|
43
|
-
puts "#{n}: #{args}"
|
44
41
|
end
|
45
42
|
end
|
46
43
|
|
@@ -50,7 +47,6 @@ class HeavyCalculation
|
|
50
47
|
|
51
48
|
def perform(n, arr)
|
52
49
|
arr = bubble_sort(arr)
|
53
|
-
puts "#{n}: #{arr[0..9]}"
|
54
50
|
end
|
55
51
|
|
56
52
|
def bubble_sort(array)
|
@@ -79,6 +75,5 @@ class GetPrimes
|
|
79
75
|
Prime.each(max) do |prime|
|
80
76
|
prime
|
81
77
|
end
|
82
|
-
puts n
|
83
78
|
end
|
84
79
|
end
|
data/app_test/run.rb
CHANGED
@@ -1,43 +1,51 @@
|
|
1
1
|
require_relative 'job_test'
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
3
|
+
module TestRunner
|
4
|
+
def self.non_blocking(num_of_cycles)
|
5
|
+
num_of_cycles.times do |n|
|
6
|
+
JobTestFast.new.perform_async('NONBLOCKING', n)
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.blocking(num_of_cycles)
|
11
|
+
num_of_cycles.times do |n|
|
12
|
+
JobTestSlow.new.perform_async('BLOCKING', n)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.thread_killer(num_of_cycles)
|
17
|
+
num_of_cycles.times do |n|
|
18
|
+
ThreadKiller.new.perform_async('Kill', n)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.large_arg(num_of_cycles)
|
23
|
+
arg = Array.new(10000, 'string')
|
24
|
+
|
25
|
+
num_of_cycles.times do |n|
|
26
|
+
LargeArg.new.perform_async(arg, n)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.sort_array(num_of_cycles, array_size)
|
31
|
+
unsorted_array = (0..array_size).to_a.shuffle
|
32
|
+
|
33
|
+
num_of_cycles.times do |n|
|
34
|
+
HeavyCalculation.new.perform_async(n, unsorted_array)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.many_args(num_of_cycles)
|
39
|
+
num_of_cycles.times do |n|
|
40
|
+
ManyArgs.new.perform_async(n, [1, 2, 3], { key: 'value'}, :symb, 'string', 22, false)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.calculate_primes(num_of_cycles)
|
45
|
+
num_of_cycles.times do |n|
|
46
|
+
GetPrimes.new.perform_async(n, 10)
|
47
|
+
end
|
48
|
+
end
|
26
49
|
end
|
27
50
|
|
28
|
-
|
29
|
-
JobTestFast.new.perform_async('NON BLOCKING', n)
|
30
|
-
JobTestFast.new.perform_async('NON BLOCKING', n)
|
31
|
-
# JobTestFast.new.perform_async('NON BLOCKING', n)
|
32
|
-
# JobTestFast.new.perform_async('NON BLOCKING', n)
|
33
|
-
# # JobTestSlow.new.perform_async('BLOCKING', n)
|
34
|
-
JobTestSlow.new.perform_async('BLOCKING', n)
|
35
|
-
end
|
36
|
-
|
37
|
-
# 100000.times do |n|
|
38
|
-
# ManyArgs.new.perform_async(n, [1, 2, 3], { key: 'value'}, :symb, 'string', 22, false)
|
39
|
-
# end
|
40
|
-
|
41
|
-
# 100000.times do |n|
|
42
|
-
# GetPrimes.new.perform_async(n, 1000000)
|
43
|
-
# end
|
51
|
+
TestRunner.non_blocking(2)
|
@@ -3,11 +3,15 @@ module ActiveJob
|
|
3
3
|
class WorkerholicAdapter
|
4
4
|
def enqueue(job)
|
5
5
|
job_data = job.serialize
|
6
|
+
queue_name = "workerholic:queue:#{job_data['queue_name']}"
|
6
7
|
|
7
|
-
JobWrapper.new
|
8
|
-
|
9
|
-
|
10
|
-
|
8
|
+
job = JobWrapper.new
|
9
|
+
job.instance_variable_set(:@queue_name, queue_name)
|
10
|
+
|
11
|
+
# capture job class so it can be passed to `Base.execute` in `JobWrapper#perform`
|
12
|
+
job.instance_variable_set(:@class, job_data['job_class'])
|
13
|
+
|
14
|
+
job.perform_async(*job_data['arguments'])
|
11
15
|
end
|
12
16
|
|
13
17
|
class JobWrapper
|
@@ -19,6 +23,4 @@ module ActiveJob
|
|
19
23
|
end
|
20
24
|
end
|
21
25
|
end
|
22
|
-
|
23
|
-
autoload :WorkerholicAdapter
|
24
26
|
end
|
data/lib/workerholic/cli.rb
CHANGED
@@ -29,7 +29,7 @@ module Workerholic
|
|
29
29
|
OptionParser.new do |opts|
|
30
30
|
opts.banner = 'Usage: workerholic [options]'
|
31
31
|
|
32
|
-
opts.on '-
|
32
|
+
opts.on '-a', '--auto-balance', 'auto-balance workers based on number of jobs in each queue' do
|
33
33
|
options[:auto_balance] = true
|
34
34
|
end
|
35
35
|
|
@@ -55,12 +55,19 @@ module Workerholic
|
|
55
55
|
def load_app
|
56
56
|
if File.exist?('./config/environment.rb')
|
57
57
|
require File.expand_path('./config/environment.rb')
|
58
|
+
|
58
59
|
require 'workerholic/adapters/active_job_adapter'
|
60
|
+
|
61
|
+
ActiveSupport.run_load_hooks(:before_eager_load, Rails.application)
|
62
|
+
Rails.application.config.eager_load_namespaces.each(&:eager_load!)
|
59
63
|
elsif options[:require]
|
60
|
-
|
61
|
-
|
64
|
+
file_path = File.expand_path(options[:require])
|
65
|
+
|
66
|
+
if File.exist?(file_path)
|
67
|
+
require file_path
|
62
68
|
else
|
63
|
-
|
69
|
+
logger.info('The file you specified to load your application is not valid!')
|
70
|
+
|
64
71
|
exit
|
65
72
|
end
|
66
73
|
else
|
data/lib/workerholic/job.rb
CHANGED
@@ -19,7 +19,7 @@ module Workerholic
|
|
19
19
|
def perform_async(*args)
|
20
20
|
serialized_job, queue_name = prepare_job_for_enqueueing(args)
|
21
21
|
|
22
|
-
Queue.new(queue_name).enqueue(serialized_job)
|
22
|
+
Queue.new(@queue_name || queue_name).enqueue(serialized_job)
|
23
23
|
end
|
24
24
|
|
25
25
|
def perform_delayed(*args)
|
@@ -40,7 +40,8 @@ module Workerholic
|
|
40
40
|
def prepare_job_for_enqueueing(args)
|
41
41
|
raise ArgumentError if self.method(:perform).arity != args.size
|
42
42
|
|
43
|
-
job = JobWrapper.new(
|
43
|
+
job = JobWrapper.new(klass: @class || self.class, arguments: args, wrapper: self.class)
|
44
|
+
|
44
45
|
job.statistics.enqueued_at = Time.now.to_f
|
45
46
|
|
46
47
|
[JobSerializer.serialize(job), specified_job_options[:queue_name]]
|
@@ -1,29 +1,40 @@
|
|
1
1
|
module Workerholic
|
2
2
|
class JobProcessor
|
3
|
+
attr_reader :serialized_job
|
4
|
+
|
3
5
|
def initialize(serialized_job)
|
4
6
|
@serialized_job = serialized_job
|
5
7
|
@logger = LogManager.new
|
6
8
|
end
|
7
9
|
|
8
10
|
def process
|
9
|
-
job = JobSerializer.deserialize(
|
11
|
+
job = JobSerializer.deserialize(serialized_job)
|
10
12
|
|
11
13
|
begin
|
12
14
|
job.statistics.started_at = Time.now.to_f
|
13
15
|
job_result = job.perform
|
14
16
|
job.statistics.completed_at = Time.now.to_f
|
15
17
|
|
16
|
-
|
18
|
+
StatsStorage.save_job('completed_jobs', job)
|
17
19
|
|
18
|
-
|
20
|
+
@logger.info("Completed: your job from class #{job.klass} was completed on #{job.statistics.completed_at}.")
|
19
21
|
rescue Exception => e
|
22
|
+
|
20
23
|
job.statistics.errors.push([e.class, e.message])
|
21
|
-
|
24
|
+
retry_job(job)
|
22
25
|
|
23
|
-
|
26
|
+
@logger.error("Failed: your job from class #{job.class} was unsuccessful. Retrying in 10 seconds.")
|
24
27
|
end
|
28
|
+
job_result
|
29
|
+
end
|
25
30
|
|
26
|
-
|
31
|
+
private
|
32
|
+
|
33
|
+
def retry_job(job)
|
34
|
+
limit_reached = JobRetry.new(job: job)
|
35
|
+
if limit_reached
|
36
|
+
job.statistics.failed_on = Time.now.to_f
|
37
|
+
end
|
27
38
|
end
|
28
39
|
end
|
29
40
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module Workerholic
|
2
2
|
class JobRetry
|
3
|
-
attr_reader :job, :sorted_set
|
3
|
+
attr_reader :job, :sorted_set, :stats_storage
|
4
4
|
|
5
5
|
def initialize(options={})
|
6
6
|
@job = options[:job]
|
@@ -12,7 +12,10 @@ module Workerholic
|
|
12
12
|
protected
|
13
13
|
|
14
14
|
def retry
|
15
|
-
|
15
|
+
if job.retry_count >= 5
|
16
|
+
StatsStorage.save_job('failed_jobs', job)
|
17
|
+
return false
|
18
|
+
end
|
16
19
|
|
17
20
|
increment_retry_count
|
18
21
|
schedule_job_for_retry
|
@@ -5,7 +5,7 @@ module Workerholic
|
|
5
5
|
|
6
6
|
def initialize(opts={})
|
7
7
|
@sorted_set = SortedSet.new(opts[:set_name] || 'workerholic:scheduled_jobs')
|
8
|
-
@queue = Queue.new(opts[:queue_name] || 'workerholic:main')
|
8
|
+
@queue = Queue.new(opts[:queue_name] || 'workerholic:queue:main')
|
9
9
|
@alive = true
|
10
10
|
end
|
11
11
|
|
@@ -35,12 +35,15 @@ module Workerholic
|
|
35
35
|
queue.enqueue(serialized_job)
|
36
36
|
end
|
37
37
|
else
|
38
|
-
sleep(
|
38
|
+
sleep(2)
|
39
39
|
end
|
40
40
|
end
|
41
41
|
|
42
42
|
def kill
|
43
43
|
self.alive = false
|
44
|
+
end
|
45
|
+
|
46
|
+
def join
|
44
47
|
scheduler_thread.join if scheduler_thread
|
45
48
|
end
|
46
49
|
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Workerholic
|
2
|
+
class JobStatistics
|
3
|
+
|
4
|
+
attr_accessor :enqueued_at,
|
5
|
+
:errors,
|
6
|
+
:started_at,
|
7
|
+
:completed_at,
|
8
|
+
:failed_on
|
9
|
+
|
10
|
+
def initialize(options={})
|
11
|
+
@enqueued_at = options[:enqueued_at]
|
12
|
+
@errors = options[:errors] || []
|
13
|
+
@started_at = options[:started_at]
|
14
|
+
@completed_at = options[:completed_at]
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_hash
|
18
|
+
{
|
19
|
+
enqueued_at: enqueued_at,
|
20
|
+
errors: errors,
|
21
|
+
started_at: started_at,
|
22
|
+
completed_at: completed_at,
|
23
|
+
elapsed_time: elapsed_time,
|
24
|
+
failed_on: failed_on
|
25
|
+
}
|
26
|
+
end
|
27
|
+
|
28
|
+
def elapsed_time
|
29
|
+
if completed_at && started_at
|
30
|
+
format_elapsed_time(completed_at - started_at)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def format_elapsed_time(time)
|
35
|
+
'%.10f' % time
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -1,19 +1,21 @@
|
|
1
1
|
module Workerholic
|
2
2
|
class JobWrapper
|
3
3
|
attr_accessor :retry_count, :execute_at
|
4
|
-
attr_reader :klass, :arguments, :statistics
|
4
|
+
attr_reader :klass, :arguments, :statistics, :wrapper
|
5
5
|
|
6
6
|
def initialize(options={})
|
7
|
-
@klass = options[:
|
7
|
+
@klass = options[:klass]
|
8
|
+
@wrapper = options[:wrapper]
|
8
9
|
@arguments = options[:arguments]
|
9
10
|
@execute_at = options[:execute_at]
|
10
11
|
@retry_count = options[:retry_count] || 0
|
11
|
-
@statistics =
|
12
|
+
@statistics = JobStatistics.new(options[:statistics] || {})
|
12
13
|
end
|
13
14
|
|
14
15
|
def to_hash
|
15
16
|
{
|
16
|
-
|
17
|
+
klass: klass,
|
18
|
+
wrapper: wrapper,
|
17
19
|
arguments: arguments,
|
18
20
|
retry_count: retry_count,
|
19
21
|
execute_at: execute_at,
|
@@ -22,7 +24,14 @@ module Workerholic
|
|
22
24
|
end
|
23
25
|
|
24
26
|
def perform
|
25
|
-
|
27
|
+
if wrapper == ActiveJob::QueueAdapters::WorkerholicAdapter::JobWrapper
|
28
|
+
wrapper.new.perform(
|
29
|
+
'job_class' => klass,
|
30
|
+
'arguments' => arguments
|
31
|
+
)
|
32
|
+
else
|
33
|
+
klass.new.perform(*arguments)
|
34
|
+
end
|
26
35
|
end
|
27
36
|
|
28
37
|
def ==(other)
|
data/lib/workerholic/manager.rb
CHANGED
@@ -0,0 +1,77 @@
|
|
1
|
+
module Workerholic
|
2
|
+
class StatsAPI
|
3
|
+
CATEGORIES = %w(completed_jobs failed_jobs)
|
4
|
+
|
5
|
+
def self.job_statistics(options={})
|
6
|
+
if CATEGORIES.include? options[:category]
|
7
|
+
job_classes = storage.keys_for_namespace(options[:category])
|
8
|
+
|
9
|
+
if options[:count_only]
|
10
|
+
self.parse_job_classes(job_classes)
|
11
|
+
else
|
12
|
+
self.parse_job_classes(job_classes, false)
|
13
|
+
end
|
14
|
+
else
|
15
|
+
logger("Invalid arguments. Please specify one of the following categories:\n'completed_jobs', 'failed_jobs'.")
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.jobs_classes
|
20
|
+
classes = storage.available_keys
|
21
|
+
|
22
|
+
parsed_classes = classes.map do |klass|
|
23
|
+
klass.split(':').last
|
24
|
+
end.uniq
|
25
|
+
|
26
|
+
parsed_classes.empty? ? 'No class data is available yet.' : parsed_classes
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.queue_names
|
30
|
+
fetched_queues = storage.fetch_queue_names
|
31
|
+
parsed_queues = fetched_queues.map do |queue|
|
32
|
+
queue_data = [queue.name, queue.size]
|
33
|
+
queues << queue_data
|
34
|
+
end
|
35
|
+
|
36
|
+
(parsed_queues.empty? ? 'No queues data is available yet.': parsed_queues)
|
37
|
+
end
|
38
|
+
|
39
|
+
class << self
|
40
|
+
private
|
41
|
+
|
42
|
+
def storage
|
43
|
+
@storage ||= Storage::RedisWrapper.new
|
44
|
+
end
|
45
|
+
|
46
|
+
def logger(message)
|
47
|
+
@log ||= LogManager.new
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def self.parse_job_classes(job_classes, count_only = true)
|
54
|
+
job_classes.map do |job_class|
|
55
|
+
if count_only
|
56
|
+
self.jobs_per_class(job_class)
|
57
|
+
else
|
58
|
+
self.get_jobs_for_class(job_class)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.get_jobs_for_class(job_class)
|
64
|
+
serialized_jobs = storage.peek_namespace(job_class)
|
65
|
+
deserialized_stats = serialized_jobs.map do |serialized_job|
|
66
|
+
JobSerializer.deserialize_stats(serialized_job)
|
67
|
+
end
|
68
|
+
|
69
|
+
deserialized_stats << deserialized_stats.size
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.jobs_per_class(job_class)
|
73
|
+
clean_class_name = job_class.split(':').last
|
74
|
+
[clean_class_name, storage.list_length(job_class)]
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Workerholic
|
2
|
+
class StatsStorage
|
3
|
+
|
4
|
+
def self.save_job(category, job)
|
5
|
+
serialized_job_stats = JobSerializer.serialize(job)
|
6
|
+
|
7
|
+
namespace = "workerholic:stats:#{category}:#{job.klass}"
|
8
|
+
storage.push(namespace, serialized_job_stats)
|
9
|
+
end
|
10
|
+
|
11
|
+
class << self
|
12
|
+
private
|
13
|
+
|
14
|
+
def storage
|
15
|
+
@storage ||= Storage::RedisWrapper.new
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/workerholic/storage.rb
CHANGED
@@ -42,12 +42,37 @@ module Workerholic
|
|
42
42
|
execute(retry_delay) { |conn| conn.zcount(key, 0, '+inf') }
|
43
43
|
end
|
44
44
|
|
45
|
+
def keys_count(namespace, retry_delay = 5)
|
46
|
+
execute(retry_delay) { |conn| conn.keys(namespace + ':*').size }
|
47
|
+
end
|
48
|
+
|
45
49
|
def fetch_queue_names(retry_delay = 5)
|
46
50
|
queue_name_pattern = $TESTING ? 'workerholic:testing:queue*' : 'workerholic:queue*'
|
47
51
|
|
48
52
|
execute(retry_delay) { |conn| conn.scan(0, match: queue_name_pattern).last }
|
49
53
|
end
|
50
54
|
|
55
|
+
def available_keys(retry_delay = 5)
|
56
|
+
execute(retry_delay) { |conn| conn.keys('workerholic:stats:*') }
|
57
|
+
end
|
58
|
+
|
59
|
+
def keys_for_namespace(namespace, retry_delay = 5)
|
60
|
+
execute(retry_delay) { |conn| conn.keys('workerholic:stats:' + namespace + ':*') }
|
61
|
+
end
|
62
|
+
|
63
|
+
def peek_namespace(key, retry_delay = 5)
|
64
|
+
execute(retry_delay) { |conn| conn.lrange(key, 0, -1) }
|
65
|
+
end
|
66
|
+
|
67
|
+
def peek_namespaces(keys, retry_delay = 5)
|
68
|
+
execute(retry_delay) do |conn|
|
69
|
+
keys.select do |namespace|
|
70
|
+
full_namespace = 'workerholic:stats:' + namespace
|
71
|
+
conn.keys(full_namespace + ':*').size > 0
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
51
76
|
class RedisCannotRecover < Redis::CannotConnectError; end
|
52
77
|
|
53
78
|
private
|
data/lib/workerholic/version.rb
CHANGED
data/lib/workerholic/worker.rb
CHANGED
data/lib/workerholic.rb
CHANGED
@@ -18,11 +18,17 @@ require 'workerholic/job_retry'
|
|
18
18
|
require 'workerholic/storage'
|
19
19
|
require 'workerholic/sorted_set'
|
20
20
|
require 'workerholic/queue'
|
21
|
+
require 'workerholic/statistics_storage'
|
21
22
|
|
22
23
|
require 'workerholic/job_serializer'
|
23
|
-
require 'workerholic/
|
24
|
+
require 'workerholic/job_statistics'
|
24
25
|
require 'workerholic/log_manager'
|
25
26
|
|
27
|
+
require 'workerholic/statistics_api'
|
28
|
+
require 'workerholic/statistics_storage'
|
29
|
+
|
30
|
+
require 'workerholic/adapters/active_job_adapter' if defined?(Rails)
|
31
|
+
|
26
32
|
module Workerholic
|
27
33
|
def self.workers_count
|
28
34
|
@workers_count || 25
|
Binary file
|
@@ -9,7 +9,7 @@ describe 'enqueuing jobs to Redis' do
|
|
9
9
|
serialized_job = redis.lpop(TEST_QUEUE)
|
10
10
|
job_from_redis = Workerholic::JobSerializer.deserialize(serialized_job)
|
11
11
|
|
12
|
-
expected_job = Workerholic::JobWrapper.new(
|
12
|
+
expected_job = Workerholic::JobWrapper.new(klass: SimpleJobTest, arguments: ['test job'], wrapper: SimpleJobTest)
|
13
13
|
expected_job.statistics.enqueued_at = job_from_redis.statistics.enqueued_at
|
14
14
|
|
15
15
|
expect(job_from_redis.to_hash).to eq(expected_job.to_hash)
|
@@ -21,9 +21,11 @@ describe 'enqueuing jobs to Redis' do
|
|
21
21
|
job_from_redis = Workerholic::JobSerializer.deserialize(serialized_job)
|
22
22
|
|
23
23
|
expected_job = Workerholic::JobWrapper.new(
|
24
|
-
|
25
|
-
arguments: ['test job', { a: 1, b: 2 }, [1, 2, 3]]
|
24
|
+
klass: ComplexJobTest,
|
25
|
+
arguments: ['test job', { a: 1, b: 2 }, [1, 2, 3]],
|
26
|
+
wrapper: ComplexJobTest
|
26
27
|
)
|
28
|
+
|
27
29
|
expected_job.statistics.enqueued_at = job_from_redis.statistics.enqueued_at
|
28
30
|
|
29
31
|
expect(job_from_redis.to_hash).to eq(expected_job.to_hash)
|