inst-jobs 0.11.0
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 +7 -0
- data/bin/inst_job +4 -0
- data/db/migrate/20101216224513_create_delayed_jobs.rb +40 -0
- data/db/migrate/20110208031356_add_delayed_jobs_tag.rb +14 -0
- data/db/migrate/20110426161613_add_delayed_jobs_max_attempts.rb +13 -0
- data/db/migrate/20110516225834_add_delayed_jobs_strand.rb +14 -0
- data/db/migrate/20110531144916_cleanup_delayed_jobs_indexes.rb +26 -0
- data/db/migrate/20110610213249_optimize_delayed_jobs.rb +40 -0
- data/db/migrate/20110831210257_add_delayed_jobs_next_in_strand.rb +52 -0
- data/db/migrate/20120510004759_delayed_jobs_delete_trigger_lock_for_update.rb +31 -0
- data/db/migrate/20120531150712_drop_psql_jobs_pop_fn.rb +15 -0
- data/db/migrate/20120607164022_delayed_jobs_use_advisory_locks.rb +80 -0
- data/db/migrate/20120607181141_index_jobs_on_locked_by.rb +15 -0
- data/db/migrate/20120608191051_add_jobs_run_at_index.rb +15 -0
- data/db/migrate/20120927184213_change_delayed_jobs_handler_to_text.rb +13 -0
- data/db/migrate/20140505215131_add_failed_jobs_original_job_id.rb +13 -0
- data/db/migrate/20140505215510_copy_failed_jobs_original_id.rb +13 -0
- data/db/migrate/20140505223637_drop_failed_jobs_original_id.rb +13 -0
- data/db/migrate/20140512213941_add_source_to_jobs.rb +15 -0
- data/db/migrate/20150807133223_add_max_concurrent_to_jobs.rb +70 -0
- data/db/migrate/20151123210429_add_expires_at_to_jobs.rb +15 -0
- data/db/migrate/20151210162949_improve_max_concurrent.rb +50 -0
- data/lib/delayed/backend/active_record.rb +340 -0
- data/lib/delayed/backend/base.rb +335 -0
- data/lib/delayed/backend/redis/bulk_update.lua +50 -0
- data/lib/delayed/backend/redis/destroy_job.lua +2 -0
- data/lib/delayed/backend/redis/enqueue.lua +29 -0
- data/lib/delayed/backend/redis/fail_job.lua +5 -0
- data/lib/delayed/backend/redis/find_available.lua +3 -0
- data/lib/delayed/backend/redis/functions.rb +57 -0
- data/lib/delayed/backend/redis/get_and_lock_next_available.lua +17 -0
- data/lib/delayed/backend/redis/includes/jobs_common.lua +203 -0
- data/lib/delayed/backend/redis/job.rb +497 -0
- data/lib/delayed/backend/redis/set_running.lua +5 -0
- data/lib/delayed/backend/redis/tickle_strand.lua +2 -0
- data/lib/delayed/batch.rb +56 -0
- data/lib/delayed/cli.rb +101 -0
- data/lib/delayed/daemon.rb +103 -0
- data/lib/delayed/engine.rb +4 -0
- data/lib/delayed/job_tracking.rb +31 -0
- data/lib/delayed/lifecycle.rb +90 -0
- data/lib/delayed/log_tailer.rb +22 -0
- data/lib/delayed/message_sending.rb +134 -0
- data/lib/delayed/performable_method.rb +52 -0
- data/lib/delayed/periodic.rb +85 -0
- data/lib/delayed/plugin.rb +22 -0
- data/lib/delayed/pool.rb +161 -0
- data/lib/delayed/server/helpers.rb +28 -0
- data/lib/delayed/server/public/css/app.css +12 -0
- data/lib/delayed/server/public/js/app.js +132 -0
- data/lib/delayed/server/views/index.erb +90 -0
- data/lib/delayed/server/views/layout.erb +47 -0
- data/lib/delayed/server.rb +120 -0
- data/lib/delayed/settings.rb +90 -0
- data/lib/delayed/testing.rb +32 -0
- data/lib/delayed/version.rb +3 -0
- data/lib/delayed/work_queue/in_process.rb +13 -0
- data/lib/delayed/work_queue/parent_process.rb +180 -0
- data/lib/delayed/worker.rb +234 -0
- data/lib/delayed/yaml_extensions.rb +109 -0
- data/lib/delayed_job.rb +46 -0
- data/lib/inst-jobs.rb +1 -0
- data/spec/active_record_job_spec.rb +246 -0
- data/spec/delayed/cli_spec.rb +23 -0
- data/spec/delayed/daemon_spec.rb +35 -0
- data/spec/delayed/server_spec.rb +63 -0
- data/spec/delayed/settings_spec.rb +32 -0
- data/spec/delayed/work_queue/in_process_spec.rb +31 -0
- data/spec/delayed/work_queue/parent_process_spec.rb +159 -0
- data/spec/delayed/worker_spec.rb +16 -0
- data/spec/gemfiles/32.gemfile +6 -0
- data/spec/gemfiles/40.gemfile +5 -0
- data/spec/gemfiles/41.gemfile +5 -0
- data/spec/gemfiles/42.gemfile +5 -0
- data/spec/migrate/20140924140513_add_story_table.rb +7 -0
- data/spec/redis_job_spec.rb +140 -0
- data/spec/sample_jobs.rb +28 -0
- data/spec/shared/delayed_batch.rb +85 -0
- data/spec/shared/delayed_method.rb +419 -0
- data/spec/shared/performable_method.rb +66 -0
- data/spec/shared/shared_backend.rb +819 -0
- data/spec/shared/testing.rb +48 -0
- data/spec/shared/worker.rb +378 -0
- data/spec/shared_jobs_specs.rb +15 -0
- data/spec/spec_helper.rb +97 -0
- metadata +390 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
require 'socket'
|
|
2
|
+
require 'tempfile'
|
|
3
|
+
require 'timeout'
|
|
4
|
+
|
|
5
|
+
module Delayed
|
|
6
|
+
module WorkQueue
|
|
7
|
+
# ParentProcess is a WorkQueue implementation that spawns a separate worker
|
|
8
|
+
# process for querying the queue. Each Worker child process sends requests to
|
|
9
|
+
# the ParentProcess via IPC, and receives responses. This centralized queue
|
|
10
|
+
# querying cuts down on db queries and lock contention, and allows the
|
|
11
|
+
# possibility for other centralized logic such as notifications when all workers
|
|
12
|
+
# are idle.
|
|
13
|
+
#
|
|
14
|
+
# The IPC implementation uses Unix stream sockets and Ruby's built-in Marshal
|
|
15
|
+
# functionality. The ParentProcess creates a Unix socket on the filesystem in
|
|
16
|
+
# the tmp directory, so that if a worker process dies and is restarted it can
|
|
17
|
+
# reconnect to the socket.
|
|
18
|
+
#
|
|
19
|
+
# While Unix and IP sockets are API compatible, we take a lot of shortcuts
|
|
20
|
+
# because we know it's just a local Unix socket. If we ever wanted to swap this
|
|
21
|
+
# out for a TCP/IP socket and have the WorkQueue running on another host, we'd
|
|
22
|
+
# want to be a lot more robust about partial reads/writes and timeouts.
|
|
23
|
+
class ParentProcess
|
|
24
|
+
class ProtocolError < RuntimeError
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def initialize
|
|
28
|
+
@path = self.class.generate_socket_path
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def self.generate_socket_path
|
|
32
|
+
# We utilize Tempfile as a convenient way to get a socket filename in the
|
|
33
|
+
# writeable temp directory. However, since we destroy the normal file and
|
|
34
|
+
# write a unix socket file to the same location, we lose the hard uniqueness
|
|
35
|
+
# guarantees of Tempfile. This is OK for this use case, we only generate one
|
|
36
|
+
# Tempfile with this prefix.
|
|
37
|
+
tmp = Tempfile.new("inst-jobs-#{Process.pid}-")
|
|
38
|
+
path = tmp.path
|
|
39
|
+
tmp.close!
|
|
40
|
+
path
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def server(parent_pid: nil)
|
|
44
|
+
# The unix_server_socket method takes care of cleaning up any existing
|
|
45
|
+
# socket for us if the work queue process dies and is restarted.
|
|
46
|
+
listen_socket = Socket.unix_server_socket(@path)
|
|
47
|
+
Server.new(listen_socket, parent_pid: parent_pid)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def client
|
|
51
|
+
Client.new(Addrinfo.unix(@path))
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
class Client
|
|
55
|
+
attr_reader :addrinfo
|
|
56
|
+
|
|
57
|
+
def initialize(addrinfo)
|
|
58
|
+
@addrinfo = addrinfo
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def get_and_lock_next_available(name, queue_name, min_priority, max_priority)
|
|
62
|
+
@socket ||= @addrinfo.connect
|
|
63
|
+
Marshal.dump([name, queue_name, min_priority, max_priority], @socket)
|
|
64
|
+
response = Marshal.load(@socket)
|
|
65
|
+
unless response.nil? || (response.is_a?(Delayed::Job) && response.locked_by == name)
|
|
66
|
+
raise(ProtocolError, "response is not a locked job: #{response.inspect}")
|
|
67
|
+
end
|
|
68
|
+
response
|
|
69
|
+
rescue SystemCallError, IOError
|
|
70
|
+
# The work queue process died. Return nil to signal the worker
|
|
71
|
+
# process should sleep as if no job was found, and then retry.
|
|
72
|
+
@socket = nil
|
|
73
|
+
nil
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
class Server
|
|
78
|
+
attr_reader :listen_socket
|
|
79
|
+
|
|
80
|
+
def initialize(listen_socket, parent_pid: nil)
|
|
81
|
+
@listen_socket = listen_socket
|
|
82
|
+
@parent_pid = parent_pid
|
|
83
|
+
@clients = {}
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def connected_clients
|
|
87
|
+
@clients.size
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def all_workers_idle?
|
|
91
|
+
!@clients.any? { |_, c| c.working }
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def say(msg, level = :debug)
|
|
95
|
+
if defined?(Rails.logger) && Rails.logger
|
|
96
|
+
Rails.logger.send(level, "[#{Process.pid}]Q #{msg}")
|
|
97
|
+
else
|
|
98
|
+
puts(msg)
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# run the server queue worker
|
|
103
|
+
# this method does not return, only exits or raises an exception
|
|
104
|
+
def run
|
|
105
|
+
say "Starting work queue process"
|
|
106
|
+
|
|
107
|
+
while !exit?
|
|
108
|
+
run_once
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
rescue => e
|
|
112
|
+
say "WorkQueue Server died: #{e.inspect}"
|
|
113
|
+
raise
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def run_once
|
|
117
|
+
handles = @clients.keys + [@listen_socket]
|
|
118
|
+
readable, _, _ = IO.select(handles, nil, nil, 1)
|
|
119
|
+
if readable
|
|
120
|
+
readable.each { |s| handle_read(s) }
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def handle_read(socket)
|
|
125
|
+
if socket == @listen_socket
|
|
126
|
+
handle_accept
|
|
127
|
+
else
|
|
128
|
+
handle_request(socket)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
# Any error on the listen socket other than WaitReadable will bubble up
|
|
133
|
+
# and terminate the work queue process, to be restarted by the parent daemon.
|
|
134
|
+
def handle_accept
|
|
135
|
+
client, _addr = @listen_socket.accept_nonblock
|
|
136
|
+
if client
|
|
137
|
+
@clients[client] = ClientState.new(false)
|
|
138
|
+
end
|
|
139
|
+
rescue IO::WaitReadable
|
|
140
|
+
# ignore and just try accepting again next time through the loop
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def handle_request(socket)
|
|
144
|
+
# There is an assumption here that the client will never send a partial
|
|
145
|
+
# request and then leave the socket open. Doing so would leave us hanging
|
|
146
|
+
# here forever. This is only a reasonable assumption because we control
|
|
147
|
+
# the client.
|
|
148
|
+
request = client_timeout { Marshal.load(socket) }
|
|
149
|
+
response = nil
|
|
150
|
+
Delayed::Worker.lifecycle.run_callbacks(:work_queue_pop, self) do
|
|
151
|
+
response = Delayed::Job.get_and_lock_next_available(*request)
|
|
152
|
+
@clients[socket].working = !response.nil?
|
|
153
|
+
end
|
|
154
|
+
client_timeout { Marshal.dump(response, socket) }
|
|
155
|
+
rescue SystemCallError, IOError, Timeout::Error
|
|
156
|
+
# this socket went away
|
|
157
|
+
begin
|
|
158
|
+
socket.close
|
|
159
|
+
rescue IOError
|
|
160
|
+
end
|
|
161
|
+
@clients.delete(socket)
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
def exit?
|
|
165
|
+
parent_exited?
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def parent_exited?
|
|
169
|
+
@parent_pid && @parent_pid != Process.ppid
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def client_timeout
|
|
173
|
+
Timeout.timeout(Settings.parent_process_client_timeout) { yield }
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
ClientState = Struct.new(:working)
|
|
177
|
+
end
|
|
178
|
+
end
|
|
179
|
+
end
|
|
180
|
+
end
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
module Delayed
|
|
2
|
+
|
|
3
|
+
class TimeoutError < RuntimeError; end
|
|
4
|
+
|
|
5
|
+
require 'tmpdir'
|
|
6
|
+
require 'set'
|
|
7
|
+
|
|
8
|
+
class Worker
|
|
9
|
+
attr_reader :config, :queue_name, :min_priority, :max_priority, :work_queue
|
|
10
|
+
|
|
11
|
+
# Callback to fire when a delayed job fails max_attempts times. If this
|
|
12
|
+
# callback is defined, then the value of destroy_failed_jobs is ignored, and
|
|
13
|
+
# the job is destroyed if this block returns true.
|
|
14
|
+
#
|
|
15
|
+
# This allows for destroying "uninteresting" failures, while keeping around
|
|
16
|
+
# interesting failures to be investigated later.
|
|
17
|
+
#
|
|
18
|
+
# The block is called with args(job, last_exception)
|
|
19
|
+
def self.on_max_failures=(block)
|
|
20
|
+
@@on_max_failures = block
|
|
21
|
+
end
|
|
22
|
+
cattr_reader :on_max_failures
|
|
23
|
+
|
|
24
|
+
cattr_accessor :plugins
|
|
25
|
+
self.plugins = Set.new
|
|
26
|
+
|
|
27
|
+
def self.lifecycle
|
|
28
|
+
@lifecycle ||= Delayed::Lifecycle.new
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def initialize(options = {})
|
|
32
|
+
@exit = false
|
|
33
|
+
@config = options
|
|
34
|
+
@parent_pid = options[:parent_pid]
|
|
35
|
+
@queue_name = options[:queue] || Settings.queue
|
|
36
|
+
@min_priority = options[:min_priority]
|
|
37
|
+
@max_priority = options[:max_priority]
|
|
38
|
+
@max_job_count = options[:worker_max_job_count].to_i
|
|
39
|
+
@max_memory_usage = options[:worker_max_memory_usage].to_i
|
|
40
|
+
@work_queue = options[:work_queue] || WorkQueue::InProcess.new
|
|
41
|
+
@job_count = 0
|
|
42
|
+
|
|
43
|
+
app = Rails.application
|
|
44
|
+
if app && !app.config.cache_classes
|
|
45
|
+
Delayed::Worker.lifecycle.around(:perform) do |&block|
|
|
46
|
+
reload = app.config.reload_classes_only_on_change != true || app.reloaders.map(&:updated?).any?
|
|
47
|
+
ActionDispatch::Reloader.prepare! if reload
|
|
48
|
+
begin
|
|
49
|
+
block.call
|
|
50
|
+
ensure
|
|
51
|
+
ActionDispatch::Reloader.cleanup! if reload
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
plugins.each { |plugin| plugin.inject! }
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def name
|
|
60
|
+
@name ||= "#{Socket.gethostname rescue "X"}:#{self.id}"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def set_process_name(new_name)
|
|
64
|
+
$0 = "delayed:#{new_name}"
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def exit?
|
|
68
|
+
@exit || parent_exited?
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def parent_exited?
|
|
72
|
+
@parent_pid && @parent_pid != Process.ppid
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def start
|
|
76
|
+
say "Starting worker", :info
|
|
77
|
+
|
|
78
|
+
trap('INT') { say 'Exiting'; @exit = true }
|
|
79
|
+
|
|
80
|
+
self.class.lifecycle.run_callbacks(:execute, self) do
|
|
81
|
+
loop do
|
|
82
|
+
run
|
|
83
|
+
break if exit?
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
say "Stopping worker", :info
|
|
88
|
+
rescue => e
|
|
89
|
+
Rails.logger.fatal("Child process died: #{e.inspect}") rescue nil
|
|
90
|
+
self.class.lifecycle.run_callbacks(:exceptional_exit, self, e) { }
|
|
91
|
+
ensure
|
|
92
|
+
Delayed::Job.clear_locks!(name)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def run
|
|
96
|
+
self.class.lifecycle.run_callbacks(:loop, self) do
|
|
97
|
+
job = self.class.lifecycle.run_callbacks(:pop, self) do
|
|
98
|
+
work_queue.get_and_lock_next_available(name, queue_name, min_priority, max_priority)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
if job
|
|
102
|
+
configure_for_job(job) do
|
|
103
|
+
@job_count += perform(job)
|
|
104
|
+
|
|
105
|
+
if @max_job_count > 0 && @job_count >= @max_job_count
|
|
106
|
+
say "Max job count of #{@max_job_count} exceeded, dying"
|
|
107
|
+
@exit = true
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
if @max_memory_usage > 0
|
|
111
|
+
memory = sample_memory
|
|
112
|
+
if memory > @max_memory_usage
|
|
113
|
+
say "Memory usage of #{memory} exceeds max of #{@max_memory_usage}, dying"
|
|
114
|
+
@exit = true
|
|
115
|
+
else
|
|
116
|
+
say "Memory usage: #{memory}"
|
|
117
|
+
end
|
|
118
|
+
end
|
|
119
|
+
end
|
|
120
|
+
else
|
|
121
|
+
set_process_name("wait:#{Settings.worker_procname_prefix}#{@queue_name}:#{min_priority || 0}:#{max_priority || 'max'}")
|
|
122
|
+
sleep(Settings.sleep_delay + (rand * Settings.sleep_delay_stagger))
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def perform(job)
|
|
128
|
+
count = 1
|
|
129
|
+
raise Delayed::Backend::JobExpired, "job expired at #{job.expires_at}" if job.expired?
|
|
130
|
+
self.class.lifecycle.run_callbacks(:perform, self, job) do
|
|
131
|
+
set_process_name("run:#{Settings.worker_procname_prefix}#{job.id}:#{job.name}")
|
|
132
|
+
say("Processing #{log_job(job, :long)}", :info)
|
|
133
|
+
runtime = Benchmark.realtime do
|
|
134
|
+
if job.batch?
|
|
135
|
+
# each job in the batch will have perform called on it, so we don't
|
|
136
|
+
# need a timeout around this
|
|
137
|
+
count = perform_batch(job)
|
|
138
|
+
else
|
|
139
|
+
job.invoke_job
|
|
140
|
+
end
|
|
141
|
+
job.destroy
|
|
142
|
+
end
|
|
143
|
+
say("Completed #{log_job(job)} #{"%.0fms" % (runtime * 1000)}", :info)
|
|
144
|
+
end
|
|
145
|
+
count
|
|
146
|
+
rescue Exception => e
|
|
147
|
+
self.class.lifecycle.run_callbacks(:error, self, job, e) do
|
|
148
|
+
handle_failed_job(job, e)
|
|
149
|
+
end
|
|
150
|
+
count
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def perform_batch(parent_job)
|
|
154
|
+
batch = parent_job.payload_object
|
|
155
|
+
if batch.mode == :serial
|
|
156
|
+
batch.jobs.each do |job|
|
|
157
|
+
job.source = parent_job.source
|
|
158
|
+
job.create_and_lock!(name)
|
|
159
|
+
configure_for_job(job) do
|
|
160
|
+
perform(job)
|
|
161
|
+
end
|
|
162
|
+
end
|
|
163
|
+
batch.items.size
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def handle_failed_job(job, error)
|
|
168
|
+
job.last_error = "#{error.message}\n#{error.backtrace.join("\n")}"
|
|
169
|
+
say("Failed with #{error.class} [#{error.message}] (#{job.attempts} attempts)", :error)
|
|
170
|
+
job.reschedule(error)
|
|
171
|
+
end
|
|
172
|
+
|
|
173
|
+
def id
|
|
174
|
+
Process.pid
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def say(msg, level = :debug)
|
|
178
|
+
Rails.logger.send(level, msg)
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
def log_job(job, format = :short)
|
|
182
|
+
case format
|
|
183
|
+
when :long
|
|
184
|
+
"#{job.full_name} #{ job.to_json(:include_root => false, :only => %w(tag strand priority attempts created_at max_attempts source)) }"
|
|
185
|
+
else
|
|
186
|
+
job.full_name
|
|
187
|
+
end
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# set up the session context information, so that it gets logged with the job log lines
|
|
191
|
+
# also set up a unique tmpdir, which will get removed at the end of the job.
|
|
192
|
+
def configure_for_job(job)
|
|
193
|
+
previous_tmpdir = ENV['TMPDIR']
|
|
194
|
+
Thread.current[:running_delayed_job] = job
|
|
195
|
+
|
|
196
|
+
dir = Dir.mktmpdir("job-#{job.id}-#{self.name.gsub(/[^\w\.]/, '.')}-")
|
|
197
|
+
begin
|
|
198
|
+
ENV['TMPDIR'] = dir
|
|
199
|
+
yield
|
|
200
|
+
ensure
|
|
201
|
+
FileUtils.remove_entry(dir, true)
|
|
202
|
+
end
|
|
203
|
+
ensure
|
|
204
|
+
ENV['TMPDIR'] = previous_tmpdir
|
|
205
|
+
Thread.current[:running_delayed_job] = nil
|
|
206
|
+
end
|
|
207
|
+
|
|
208
|
+
def self.current_job
|
|
209
|
+
Thread.current[:running_delayed_job]
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# `sample` reports KB, not B
|
|
213
|
+
if File.directory?("/proc")
|
|
214
|
+
# linux w/ proc fs
|
|
215
|
+
LINUX_PAGE_SIZE = (size = `getconf PAGESIZE`.to_i; size > 0 ? size : 4096)
|
|
216
|
+
def sample_memory
|
|
217
|
+
s = File.read("/proc/#{Process.pid}/statm").to_i rescue 0
|
|
218
|
+
s * LINUX_PAGE_SIZE / 1024
|
|
219
|
+
end
|
|
220
|
+
else
|
|
221
|
+
# generic unix solution
|
|
222
|
+
def sample_memory
|
|
223
|
+
if Rails.env.test?
|
|
224
|
+
0
|
|
225
|
+
else
|
|
226
|
+
# hmm this is actually resident set size, doesn't include swapped-to-disk
|
|
227
|
+
# memory.
|
|
228
|
+
`ps -o rss= -p #{Process.pid}`.to_i
|
|
229
|
+
end
|
|
230
|
+
end
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
end
|
|
234
|
+
end
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# New definitions for YAML to aid in serialization and deserialization of delayed jobs.
|
|
2
|
+
|
|
3
|
+
require 'yaml'
|
|
4
|
+
|
|
5
|
+
# First, tell YAML how to load a Module. This depends on Rails .constantize and autoloading.
|
|
6
|
+
YAML.add_ruby_type("object:Module") do |type, val|
|
|
7
|
+
val.constantize
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
Psych.add_domain_type("ruby/object", "Module") do |type, val|
|
|
11
|
+
val.constantize
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
Psych.add_domain_type("ruby/object", "Class") do |type, val|
|
|
15
|
+
val.constantize
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
class Module
|
|
19
|
+
def to_yaml(opts = {})
|
|
20
|
+
YAML.quick_emit(self.object_id, opts) do |out|
|
|
21
|
+
out.scalar(taguri, name)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Now we have to do the same for Class.
|
|
27
|
+
YAML.add_ruby_type("object:Class") do |type, val|
|
|
28
|
+
val.constantize
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
class Class
|
|
32
|
+
def to_yaml(opts = {})
|
|
33
|
+
YAML.quick_emit(self.object_id, opts) do |out|
|
|
34
|
+
out.scalar(taguri, name)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def encode_with(coder)
|
|
39
|
+
coder.scalar("!ruby/object:Class", name)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Now, tell YAML how to intelligently load ActiveRecord objects, using the
|
|
44
|
+
# database rather than just serializing their attributes to the YAML. This
|
|
45
|
+
# ensures the object is up to date when we use it in the job.
|
|
46
|
+
class ActiveRecord::Base
|
|
47
|
+
yaml_as "tag:ruby.yaml.org,2002:ActiveRecord"
|
|
48
|
+
|
|
49
|
+
def to_yaml(opts = {})
|
|
50
|
+
if id.nil?
|
|
51
|
+
raise("Can't serialize unsaved ActiveRecord object for delayed job: #{self.inspect}")
|
|
52
|
+
end
|
|
53
|
+
YAML.quick_emit(self.object_id, opts) do |out|
|
|
54
|
+
out.scalar(taguri, id.to_s)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def encode_with(coder)
|
|
59
|
+
if id.nil?
|
|
60
|
+
raise("Can't serialize unsaved ActiveRecord object for delayed job: #{self.inspect}")
|
|
61
|
+
end
|
|
62
|
+
coder.scalar("!ruby/ActiveRecord:#{self.class.name}", id.to_s)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def self.yaml_new(klass, tag, val)
|
|
66
|
+
klass.find(val)
|
|
67
|
+
rescue ActiveRecord::RecordNotFound
|
|
68
|
+
raise Delayed::Backend::RecordNotFound, "Couldn't find #{klass} with id #{val.inspect}"
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
module Delayed
|
|
73
|
+
module PsychExt
|
|
74
|
+
module ToRuby
|
|
75
|
+
def visit_Psych_Nodes_Scalar(object)
|
|
76
|
+
case object.tag
|
|
77
|
+
when %r{^!ruby/ActiveRecord:(.+)$}
|
|
78
|
+
begin
|
|
79
|
+
klass = resolve_class(Regexp.last_match[1])
|
|
80
|
+
klass.unscoped.find(object.value)
|
|
81
|
+
rescue ActiveRecord::RecordNotFound
|
|
82
|
+
raise Delayed::Backend::RecordNotFound, "Couldn't find #{klass} with id #{object.value.inspect}"
|
|
83
|
+
end
|
|
84
|
+
when "tag:ruby.yaml.org,2002:Delayed::Periodic", "!ruby/Delayed::Periodic"
|
|
85
|
+
Delayed::Periodic.scheduled[object.value] || raise(NameError, "job #{object.value} is no longer scheduled")
|
|
86
|
+
else
|
|
87
|
+
super
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def resolve_class(klass_name)
|
|
92
|
+
return nil if !klass_name || klass_name.empty?
|
|
93
|
+
klass_name.constantize
|
|
94
|
+
rescue
|
|
95
|
+
super
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
Psych::Visitors::ToRuby.prepend(Delayed::PsychExt::ToRuby)
|
|
102
|
+
|
|
103
|
+
# Load Module/Class from yaml tag.
|
|
104
|
+
class Module
|
|
105
|
+
def yaml_tag_read_class(name)
|
|
106
|
+
name.constantize
|
|
107
|
+
name
|
|
108
|
+
end
|
|
109
|
+
end
|
data/lib/delayed_job.rb
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
module Delayed
|
|
2
|
+
MIN_PRIORITY = 0
|
|
3
|
+
HIGH_PRIORITY = 0
|
|
4
|
+
NORMAL_PRIORITY = 10
|
|
5
|
+
LOW_PRIORITY = 20
|
|
6
|
+
LOWER_PRIORITY = 50
|
|
7
|
+
MAX_PRIORITY = 1_000_000
|
|
8
|
+
|
|
9
|
+
def self.select_backend(backend)
|
|
10
|
+
remove_const(:Job) if defined?(::Delayed::Job)
|
|
11
|
+
const_set(:Job, backend)
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
require 'rails'
|
|
16
|
+
require 'active_support/core_ext/module/attribute_accessors'
|
|
17
|
+
require 'active_record'
|
|
18
|
+
require 'after_transaction_commit'
|
|
19
|
+
|
|
20
|
+
require 'delayed/settings'
|
|
21
|
+
require 'delayed/yaml_extensions'
|
|
22
|
+
|
|
23
|
+
require 'delayed/backend/base'
|
|
24
|
+
require 'delayed/backend/active_record'
|
|
25
|
+
require 'delayed/backend/redis/job'
|
|
26
|
+
require 'delayed/batch'
|
|
27
|
+
require 'delayed/cli'
|
|
28
|
+
require 'delayed/daemon'
|
|
29
|
+
require 'delayed/job_tracking'
|
|
30
|
+
require 'delayed/lifecycle'
|
|
31
|
+
require 'delayed/log_tailer'
|
|
32
|
+
require 'delayed/message_sending'
|
|
33
|
+
require 'delayed/performable_method'
|
|
34
|
+
require 'delayed/periodic'
|
|
35
|
+
require 'delayed/plugin'
|
|
36
|
+
require 'delayed/pool'
|
|
37
|
+
require 'delayed/worker'
|
|
38
|
+
require 'delayed/work_queue/in_process'
|
|
39
|
+
require 'delayed/work_queue/parent_process'
|
|
40
|
+
|
|
41
|
+
require 'delayed/engine'
|
|
42
|
+
|
|
43
|
+
Delayed.select_backend(Delayed::Backend::ActiveRecord::Job)
|
|
44
|
+
|
|
45
|
+
Object.send(:include, Delayed::MessageSending)
|
|
46
|
+
Module.send(:include, Delayed::MessageSending::ClassMethods)
|
data/lib/inst-jobs.rb
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
require 'delayed_job'
|