cuetip 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/cuetip +50 -0
- data/lib/cuetip.rb +22 -0
- data/lib/cuetip/config.rb +47 -0
- data/lib/cuetip/engine.rb +7 -0
- data/lib/cuetip/job.rb +100 -0
- data/lib/cuetip/models/job.rb +135 -0
- data/lib/cuetip/models/queued_job.rb +45 -0
- data/lib/cuetip/serialized_hashie.rb +19 -0
- data/lib/cuetip/version.rb +5 -0
- data/lib/cuetip/worker.rb +87 -0
- data/lib/cuetip/worker_group.rb +54 -0
- metadata +83 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 80caddd838028973a0877c6a4b303d0b36b6ad6f5c98564ed865a60f41ce348a
|
4
|
+
data.tar.gz: 8b5a04b3537ff9a6bad21225ea11becb3ce5d2b2dd1e8c4f30cf8649a55f90d9
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d4b32a2ec0ad43135fd41e9a81cccf217a6415058f43244972835d6f902b5b6ac54cedd547dad7643a0a7ca7852d7a469465b263d2f392d6ba53f191f824accc
|
7
|
+
data.tar.gz: e02bbcd49dac8eada655e0b86d5ce31d8e1f9bf6186e0e0bd14ff79cf2927102a4a3457b6e31fe624a0e486f9b18ca1041244efa57de618ec8e0e1ad16c80dda
|
data/bin/cuetip
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# frozen_string_literal: true
|
3
|
+
|
4
|
+
require 'cuetip'
|
5
|
+
require 'cuetip/config'
|
6
|
+
require 'cuetip/version'
|
7
|
+
require 'cuetip/worker_group'
|
8
|
+
require 'optparse'
|
9
|
+
|
10
|
+
$stdout.sync = true
|
11
|
+
$stderr.sync = true
|
12
|
+
|
13
|
+
options = {}
|
14
|
+
OptionParser.new do |opts|
|
15
|
+
opts.version = Cuetip::VERSION
|
16
|
+
opts.banner = 'Usage: cuetip [options]'
|
17
|
+
|
18
|
+
opts.on('-c', '--config PATH', 'The path to your cuetip config file') do |config|
|
19
|
+
options[:config] = config
|
20
|
+
end
|
21
|
+
|
22
|
+
opts.on('-h', '--help', 'Prints this help') do
|
23
|
+
puts opts
|
24
|
+
exit
|
25
|
+
end
|
26
|
+
|
27
|
+
opts.on('-w NUMBER', 'The number of workers to run') do |i|
|
28
|
+
options[:quantity] = i.to_i
|
29
|
+
end
|
30
|
+
|
31
|
+
opts.on('-q', '--queues QUEUE1,QUEUE2', 'Queues that you wish to work on') do |queues|
|
32
|
+
options[:queues] = []
|
33
|
+
queues.split(/,/).uniq.each do |queue|
|
34
|
+
options[:queues] << queue
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end.parse!
|
38
|
+
|
39
|
+
if options[:config]
|
40
|
+
if File.file?(options[:config])
|
41
|
+
file = File.expand_path(options[:config])
|
42
|
+
require file
|
43
|
+
else
|
44
|
+
puts "Cuetip config file not found at #{options[:config]}"
|
45
|
+
exit 1
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
worker = Cuetip::WorkerGroup.new(options[:quantity].to_i == 0 ? Cuetip.config.worker_threads : options[:quantity].to_i, options[:queues])
|
50
|
+
worker.start
|
data/lib/cuetip.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuetip'
|
4
|
+
require 'cuetip/config'
|
5
|
+
require 'cuetip/job'
|
6
|
+
require 'cuetip/version'
|
7
|
+
|
8
|
+
module Cuetip
|
9
|
+
def self.config
|
10
|
+
@config ||= Config.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.logger
|
14
|
+
config.logger
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.configure(&block)
|
18
|
+
block.call(config)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
require 'cuetip/engine' if defined?(Rails)
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_support'
|
4
|
+
require 'active_support/core_ext/numeric/bytes'
|
5
|
+
require 'active_support/core_ext/numeric/time'
|
6
|
+
|
7
|
+
module Cuetip
|
8
|
+
class Config
|
9
|
+
# The length of time between polling
|
10
|
+
def polling_interval
|
11
|
+
@polling_interval || 5
|
12
|
+
end
|
13
|
+
attr_writer :polling_interval
|
14
|
+
|
15
|
+
# The number of worker threads to run
|
16
|
+
def worker_threads
|
17
|
+
@worker_threads || 1
|
18
|
+
end
|
19
|
+
attr_writer :worker_threads
|
20
|
+
|
21
|
+
# Return the logger
|
22
|
+
def logger
|
23
|
+
@logger ||= Logger.new(STDOUT)
|
24
|
+
end
|
25
|
+
attr_writer :logger
|
26
|
+
|
27
|
+
# Define a job event callback
|
28
|
+
def on(event, &block)
|
29
|
+
callbacks[event.to_sym] ||= []
|
30
|
+
callbacks[event.to_sym] << block
|
31
|
+
end
|
32
|
+
|
33
|
+
# Return all callbacks
|
34
|
+
def callbacks
|
35
|
+
@callbacks ||= Hash.new
|
36
|
+
end
|
37
|
+
|
38
|
+
# Emit some callbacks
|
39
|
+
def emit(event, *args)
|
40
|
+
return unless callbacks[event.to_sym]
|
41
|
+
|
42
|
+
callbacks[event.to_sym].each do |callback|
|
43
|
+
callback.call(*args)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
data/lib/cuetip/job.rb
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuetip/models/job'
|
4
|
+
|
5
|
+
module Cuetip
|
6
|
+
class Job
|
7
|
+
class << self
|
8
|
+
# The queue that this job should be executed on
|
9
|
+
def queue_name
|
10
|
+
@queue_name || 'default'
|
11
|
+
end
|
12
|
+
attr_writer :queue_name
|
13
|
+
|
14
|
+
# The maximum length of time (in seconds) that a job can run for
|
15
|
+
def maximum_execution_time
|
16
|
+
@maximum_execution_time || 12.hours
|
17
|
+
end
|
18
|
+
attr_writer :maximum_execution_time
|
19
|
+
|
20
|
+
# The maximum length of time (in seconds) between the job being created and it being run
|
21
|
+
def ttl
|
22
|
+
@ttl || 6.hours
|
23
|
+
end
|
24
|
+
attr_writer :ttl
|
25
|
+
|
26
|
+
# The maximum number of times this job can be run
|
27
|
+
def retry_count
|
28
|
+
@retry_count || 0
|
29
|
+
end
|
30
|
+
attr_writer :retry_count
|
31
|
+
|
32
|
+
# The maximum length of time (in seconds) between each execution of this job
|
33
|
+
def retry_interval
|
34
|
+
@retry_interval || 1.minute
|
35
|
+
end
|
36
|
+
attr_writer :retry_interval
|
37
|
+
|
38
|
+
# The length of time (in seconds) from when this job is queued to when it should be executed
|
39
|
+
def delay_execution
|
40
|
+
@delay_execution || 0
|
41
|
+
end
|
42
|
+
attr_writer :delay_execution
|
43
|
+
|
44
|
+
# Queue this job
|
45
|
+
#
|
46
|
+
# @param params [Hash]
|
47
|
+
# @return [Cuetip::Models::Job]
|
48
|
+
def queue(params = {}, &block)
|
49
|
+
# Create our new job
|
50
|
+
job = Models::Job.new(class_name: name, params: params)
|
51
|
+
# Copy over any class leve lconfig
|
52
|
+
job.queue_name = queue_name
|
53
|
+
job.maximum_execution_time = maximum_execution_time
|
54
|
+
job.ttl = ttl
|
55
|
+
job.retry_count = retry_count
|
56
|
+
job.retry_interval = retry_interval
|
57
|
+
job.delay_execution = delay_execution
|
58
|
+
# Call the block
|
59
|
+
block.call(job) if block_given?
|
60
|
+
# Create the job
|
61
|
+
job.save!
|
62
|
+
# Return the job
|
63
|
+
job
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Initialize this job instance by providing a queued job instance
|
68
|
+
#
|
69
|
+
# @param queued_job [Cuetip::Models::Job]
|
70
|
+
def initialize(job)
|
71
|
+
@job = job
|
72
|
+
end
|
73
|
+
|
74
|
+
# Perform a job
|
75
|
+
#
|
76
|
+
# @return [void]
|
77
|
+
def perform; end
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
# Return all parameters for the job
|
82
|
+
#
|
83
|
+
# @return [Hashie::Mash]
|
84
|
+
def params
|
85
|
+
@job.params
|
86
|
+
end
|
87
|
+
|
88
|
+
# Return the queued job object
|
89
|
+
#
|
90
|
+
# @return [Cuetip::Models::Job]
|
91
|
+
attr_reader :job
|
92
|
+
|
93
|
+
# Return a quick access for the job
|
94
|
+
#
|
95
|
+
# @return [Logger]
|
96
|
+
def logger
|
97
|
+
Cuetip.logger
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,135 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'active_record'
|
4
|
+
require 'cuetip/models/queued_job'
|
5
|
+
require 'cuetip/serialized_hashie'
|
6
|
+
|
7
|
+
module Cuetip
|
8
|
+
module Models
|
9
|
+
class Job < ActiveRecord::Base
|
10
|
+
self.table_name = 'cuetip_jobs'
|
11
|
+
|
12
|
+
STATUSES = %w[Pending Running Complete Aborted Expired].freeze
|
13
|
+
|
14
|
+
has_one :queued_job, class_name: 'Cuetip::Models::QueuedJob'
|
15
|
+
belongs_to :associated_object, polymorphic: true, optional: true
|
16
|
+
|
17
|
+
serialize :params, Cuetip::SerializedHashie
|
18
|
+
|
19
|
+
before_validation(on: :create) do
|
20
|
+
self.status = 'Pending'
|
21
|
+
end
|
22
|
+
|
23
|
+
after_create do
|
24
|
+
# After creation, automatically add this job into the job queue for execution
|
25
|
+
create_queued_job!(run_after: run_after || delay_execution&.seconds&.from_now, queue_name: queue_name)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Is this job in the queue
|
29
|
+
def queued?
|
30
|
+
queued_job.present?
|
31
|
+
end
|
32
|
+
|
33
|
+
# Has this job expired?
|
34
|
+
def expired?
|
35
|
+
ttl? ? expires_at <= Time.now : false
|
36
|
+
end
|
37
|
+
|
38
|
+
# The time that this job expired
|
39
|
+
def expires_at
|
40
|
+
ttl? ? created_at + ttl : nil
|
41
|
+
end
|
42
|
+
|
43
|
+
# Should this job be requeued on a failure right now?
|
44
|
+
def requeue_on_failure?
|
45
|
+
retry_count && retry_interval ? executions <= retry_count : false
|
46
|
+
end
|
47
|
+
|
48
|
+
# Remove this job from the queue
|
49
|
+
def remove_from_queue
|
50
|
+
queued_job&.destroy
|
51
|
+
self.queued_job = nil
|
52
|
+
log 'Removed from queue'
|
53
|
+
end
|
54
|
+
|
55
|
+
# Log some text about this job
|
56
|
+
#
|
57
|
+
# @param text [String]
|
58
|
+
def log(text)
|
59
|
+
Cuetip.logger.info "[#{id}] #{text}"
|
60
|
+
end
|
61
|
+
|
62
|
+
# Execute the job
|
63
|
+
#
|
64
|
+
# @return [Boolean] whether the job executed successfully or not
|
65
|
+
def execute(&block)
|
66
|
+
log "Beginning execution of job #{id} with #{class_name}"
|
67
|
+
# Initialize a new instance of the job we wish to execute
|
68
|
+
job_klass = class_name.constantize.new(self)
|
69
|
+
|
70
|
+
# If the job has expired, we should not be executing this so we'll just
|
71
|
+
# remove it from the queue and mark it as expired.
|
72
|
+
if expired?
|
73
|
+
log 'Job has expired'
|
74
|
+
self.status = 'Expired'
|
75
|
+
remove_from_queue
|
76
|
+
Cuetip.config.emit(:expired, self, job_klass)
|
77
|
+
return false
|
78
|
+
end
|
79
|
+
|
80
|
+
Cuetip.config.emit(:before_perform, self, job_klass)
|
81
|
+
|
82
|
+
# If we have a block, call this so we can manipulate our actual job class
|
83
|
+
# before execution if needed (mostly for testing)
|
84
|
+
block.call(job_klass) if block_given?
|
85
|
+
|
86
|
+
# Mark the job as runnign
|
87
|
+
update!(status: 'Running', started_at: Time.now, executions: executions + 1)
|
88
|
+
|
89
|
+
begin
|
90
|
+
# Perform the job within a timeout
|
91
|
+
Timeout.timeout(maximum_execution_time || 1.year) do
|
92
|
+
job_klass.perform
|
93
|
+
end
|
94
|
+
# Mark the job as complete and remove it from the queue
|
95
|
+
self.status = 'Complete'
|
96
|
+
log 'Job completed successfully'
|
97
|
+
remove_from_queue
|
98
|
+
|
99
|
+
Cuetip.config.emit(:completed, self, job_klass)
|
100
|
+
|
101
|
+
true
|
102
|
+
rescue Exception, Timeout::TimeoutError => e
|
103
|
+
log "Job failed with #{e.class} (#{e.message})"
|
104
|
+
|
105
|
+
# If there's an error, mark the job as failed and copy exception
|
106
|
+
# data into the job
|
107
|
+
self.status = 'Failed'
|
108
|
+
self.exception_class = e.class.name
|
109
|
+
self.exception_message = e.message
|
110
|
+
self.exception_backtrace = e.backtrace.join("\n")
|
111
|
+
|
112
|
+
# Handle requeing the job if needed.
|
113
|
+
if requeue_on_failure?
|
114
|
+
# Requeue this job for execution again after the retry interval.
|
115
|
+
new_job = queued_job.requeue(run_after: Time.now + retry_interval.to_i)
|
116
|
+
log "Requeing job to run after #{new_job.run_after.to_s(:long)}"
|
117
|
+
self.status = 'Pending'
|
118
|
+
else
|
119
|
+
# We're done with this job. We can't do any more retries.
|
120
|
+
remove_from_queue
|
121
|
+
end
|
122
|
+
|
123
|
+
Cuetip.config.emit(:exception, e, self, job_klass)
|
124
|
+
|
125
|
+
false
|
126
|
+
end
|
127
|
+
ensure
|
128
|
+
self.finished_at = Time.now
|
129
|
+
save!
|
130
|
+
Cuetip.config.emit(:finished, self, job_klass)
|
131
|
+
log 'Finished processing'
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'socket'
|
4
|
+
require 'active_record'
|
5
|
+
require 'cuetip/models/job'
|
6
|
+
|
7
|
+
module Cuetip
|
8
|
+
module Models
|
9
|
+
class QueuedJob < ActiveRecord::Base
|
10
|
+
PROCESS_IDENTIFIER = Socket.gethostname + ":#{Process.pid}"
|
11
|
+
self.table_name = 'cuetip_job_queue'
|
12
|
+
|
13
|
+
scope :pending, -> { where(locked_at: nil).where('run_after is null or run_after < ?', Time.now) }
|
14
|
+
scope :from_queues, -> (queues) { where(queue_name: queues) }
|
15
|
+
|
16
|
+
belongs_to :job, class_name: 'Cuetip::Models::Job'
|
17
|
+
|
18
|
+
# Unlock the job and allow it to be re-run elsewhere.
|
19
|
+
def requeue(attributes = {})
|
20
|
+
self.attributes = attributes
|
21
|
+
self.locked_by = nil
|
22
|
+
self.locked_at = nil
|
23
|
+
save!
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
# Generate a random lock ID to use in the locking process
|
28
|
+
def self.generate_lock_id
|
29
|
+
PROCESS_IDENTIFIER + ':' + rand(1_000_000_000).to_s.rjust(9, '0')
|
30
|
+
end
|
31
|
+
|
32
|
+
# Simultaneously find an outstanding job and lock it
|
33
|
+
def self.find_and_lock(queued_job_id = nil)
|
34
|
+
lock_id = generate_lock_id
|
35
|
+
scope = if queued_job_id
|
36
|
+
where(id: queued_job_id)
|
37
|
+
else
|
38
|
+
self
|
39
|
+
end
|
40
|
+
count = scope.pending.limit(1).update_all(locked_by: lock_id, locked_at: Time.now)
|
41
|
+
QueuedJob.find_by_locked_by(lock_id) if count > 0
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'hashie/mash'
|
4
|
+
|
5
|
+
module Cuetip
|
6
|
+
class SerializedHashie < Hashie::Mash
|
7
|
+
def self.dump(obj)
|
8
|
+
obj.reject! { |_k, v| v.blank? }
|
9
|
+
obj.each do |key, value|
|
10
|
+
obj[key] = value.reject(&:blank?) if value.is_a?(Array)
|
11
|
+
end
|
12
|
+
ActiveSupport::JSON.encode(obj.to_h)
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.load(raw_hash)
|
16
|
+
new(JSON.parse(raw_hash || '{}'))
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuetip/models/queued_job'
|
4
|
+
|
5
|
+
module Cuetip
|
6
|
+
class Worker
|
7
|
+
attr_reader :status
|
8
|
+
|
9
|
+
include ActiveSupport::Callbacks
|
10
|
+
|
11
|
+
define_callbacks :execute, :poll
|
12
|
+
|
13
|
+
def initialize(group, id, queues)
|
14
|
+
@group = group
|
15
|
+
@id = id
|
16
|
+
@queues = queues
|
17
|
+
end
|
18
|
+
|
19
|
+
def request_exit!
|
20
|
+
@exit_requested = true
|
21
|
+
interrupt_sleep
|
22
|
+
end
|
23
|
+
|
24
|
+
def run
|
25
|
+
set_status('idle')
|
26
|
+
loop do
|
27
|
+
unless run_once
|
28
|
+
interruptible_sleep(Cuetip.config.polling_interval + rand)
|
29
|
+
end
|
30
|
+
|
31
|
+
break if @exit_requested
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def run_once
|
36
|
+
set_status('polling')
|
37
|
+
run_callbacks :poll do
|
38
|
+
queued_job = silence do
|
39
|
+
if @queues.any?
|
40
|
+
scope = Cuetip::Models::QueuedJob.from_queues(@queues)
|
41
|
+
else
|
42
|
+
scope = Cuetip::Models::QueuedJob
|
43
|
+
end
|
44
|
+
scope.find_and_lock
|
45
|
+
end
|
46
|
+
|
47
|
+
if queued_job
|
48
|
+
set_status("executing #{queued_job.job.id}")
|
49
|
+
run_callbacks :execute do
|
50
|
+
queued_job.job.execute
|
51
|
+
end
|
52
|
+
set_status('idle')
|
53
|
+
true
|
54
|
+
else
|
55
|
+
set_status('idle')
|
56
|
+
false
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def set_status(status)
|
64
|
+
@status = status
|
65
|
+
@group&.set_process_name
|
66
|
+
end
|
67
|
+
|
68
|
+
def interruptible_sleep(seconds)
|
69
|
+
sleep_check, @sleep_interrupt = IO.pipe
|
70
|
+
IO.select([sleep_check], nil, nil, seconds)
|
71
|
+
sleep_check.close
|
72
|
+
@sleep_interrupt.close
|
73
|
+
end
|
74
|
+
|
75
|
+
def interrupt_sleep
|
76
|
+
@sleep_interrupt&.close
|
77
|
+
end
|
78
|
+
|
79
|
+
def silence(&block)
|
80
|
+
if ActiveRecord::Base.logger
|
81
|
+
ActiveRecord::Base.logger.silence(&block)
|
82
|
+
else
|
83
|
+
block.call
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'cuetip/worker'
|
4
|
+
|
5
|
+
module Cuetip
|
6
|
+
class WorkerGroup
|
7
|
+
include ActiveSupport::Callbacks
|
8
|
+
|
9
|
+
define_callbacks :run_worker
|
10
|
+
|
11
|
+
attr_reader :quantity
|
12
|
+
attr_reader :workers
|
13
|
+
attr_reader :threads
|
14
|
+
|
15
|
+
def initialize(quantity, queues)
|
16
|
+
@quantity = quantity
|
17
|
+
@queues = queues || []
|
18
|
+
@workers = {}
|
19
|
+
@threads = {}
|
20
|
+
end
|
21
|
+
|
22
|
+
def start
|
23
|
+
Cuetip.logger.info "Starting #{@quantity} Cuetip workers"
|
24
|
+
if @queues.any?
|
25
|
+
@queues.each { |q| Cuetip.logger.info "-> Joined queue: #{q.to_s}" }
|
26
|
+
end
|
27
|
+
|
28
|
+
exit_trap = proc do
|
29
|
+
@workers.each { |_, worker| worker.request_exit! }
|
30
|
+
puts 'Exiting...'
|
31
|
+
end
|
32
|
+
|
33
|
+
trap('INT', &exit_trap)
|
34
|
+
trap('TERM', &exit_trap)
|
35
|
+
|
36
|
+
@quantity.times do |i|
|
37
|
+
@workers[i] = Worker.new(self, i, @queues)
|
38
|
+
Cuetip.logger.info "-> Starting worker #{i}"
|
39
|
+
@threads[i] = Thread.new(@workers[i]) do |worker|
|
40
|
+
run_callbacks :run_worker do
|
41
|
+
worker.run
|
42
|
+
end
|
43
|
+
end
|
44
|
+
@threads[i].abort_on_exception = true
|
45
|
+
end
|
46
|
+
@threads.values.each(&:join)
|
47
|
+
end
|
48
|
+
|
49
|
+
def set_process_name
|
50
|
+
thread_names = @workers.values.map(&:status)
|
51
|
+
Process.setproctitle("Cuetip: #{thread_names.inspect}")
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
metadata
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: cuetip
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.2.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Adam Cooke
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-04-27 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activerecord
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '5.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '5.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: hashie
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
description: An ActiveRecord job queueing system
|
42
|
+
email:
|
43
|
+
- me@adamcooke.io
|
44
|
+
executables:
|
45
|
+
- cuetip
|
46
|
+
extensions: []
|
47
|
+
extra_rdoc_files: []
|
48
|
+
files:
|
49
|
+
- bin/cuetip
|
50
|
+
- lib/cuetip.rb
|
51
|
+
- lib/cuetip/config.rb
|
52
|
+
- lib/cuetip/engine.rb
|
53
|
+
- lib/cuetip/job.rb
|
54
|
+
- lib/cuetip/models/job.rb
|
55
|
+
- lib/cuetip/models/queued_job.rb
|
56
|
+
- lib/cuetip/serialized_hashie.rb
|
57
|
+
- lib/cuetip/version.rb
|
58
|
+
- lib/cuetip/worker.rb
|
59
|
+
- lib/cuetip/worker_group.rb
|
60
|
+
homepage: https://github.com/adamcooke/cuetip
|
61
|
+
licenses:
|
62
|
+
- MIT
|
63
|
+
metadata: {}
|
64
|
+
post_install_message:
|
65
|
+
rdoc_options: []
|
66
|
+
require_paths:
|
67
|
+
- lib
|
68
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
69
|
+
requirements:
|
70
|
+
- - ">="
|
71
|
+
- !ruby/object:Gem::Version
|
72
|
+
version: '0'
|
73
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
74
|
+
requirements:
|
75
|
+
- - ">="
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
requirements: []
|
79
|
+
rubygems_version: 3.0.3
|
80
|
+
signing_key:
|
81
|
+
specification_version: 4
|
82
|
+
summary: An ActiveRecord job queueing system
|
83
|
+
test_files: []
|