Skip to content

Commit

Permalink
Make Delayed::Worker.new idempotent
Browse files Browse the repository at this point in the history
Calling Delayed::Worker.new multiple times would continue to re-add the
plugins to the lifecycle.

Some gems and projects use the pattern `Delayed::Worker.new.work_off` to
trigger delayed jobs in specs. Over time, the callback chain would get
very long and, in our case, caused a StackLevelTooDeep error.

Better would be to have a proper singleton: 

`Delayed::Worker.worker.work_off`

In the absence of a proper singleton, this will make the pattern work.
  • Loading branch information
brianhempel committed Jan 30, 2015
1 parent 1f88156 commit bdbbf60
Show file tree
Hide file tree
Showing 2 changed files with 32 additions and 3 deletions.
17 changes: 14 additions & 3 deletions lib/delayed/worker.rb
Expand Up @@ -25,7 +25,7 @@ class Worker # rubocop:disable ClassLength
# Named queue into which jobs are enqueued by default
cattr_accessor :default_queue_name

cattr_reader :backend
cattr_reader :backend, :lifecycle

# name_prefix is ignored if name is set directly
attr_accessor :name_prefix
Expand All @@ -39,6 +39,7 @@ def self.reset
self.delay_jobs = DEFAULT_DELAY_JOBS
self.queues = DEFAULT_QUEUES
self.read_ahead = DEFAULT_READ_AHEAD
@lifecycle = nil
end

reset
Expand Down Expand Up @@ -97,7 +98,15 @@ def self.after_fork
end

def self.lifecycle
@lifecycle ||= Delayed::Lifecycle.new
# In case a worker has not been set up, job enqueueing needs a lifecycle.
setup_lifecycle unless @lifecycle

@lifecycle
end

def self.setup_lifecycle
@lifecycle = Delayed::Lifecycle.new
plugins.each { |klass| klass.new }
end

def self.reload_app?
Expand All @@ -112,7 +121,9 @@ def initialize(options = {})
self.class.send("#{option}=", options[option]) if options.key?(option)
end

plugins.each { |klass| klass.new }
# Reset lifecycle on the offhand chance that something lazily
# triggered its creation before all plugins had been registered.
self.class.setup_lifecycle
end

# Every worker has a unique name which by default is the pid of the process. There are some
Expand Down
18 changes: 18 additions & 0 deletions spec/worker_spec.rb
Expand Up @@ -154,4 +154,22 @@
@worker.say(@text, Delayed::Worker.default_log_level)
end
end

describe 'plugin registration' do
it 'does not double-register plugins on worker instantiation' do
performances = 0
plugin = Class.new(Delayed::Plugin) do
callbacks do |lifecycle|
lifecycle.before(:enqueue) { performances += 1 }
end
end
Delayed::Worker.plugins << plugin

Delayed::Worker.new
Delayed::Worker.new
Delayed::Worker.lifecycle.run_callbacks(:enqueue, nil) {}

expect(performances).to eq(1)
end
end
end

0 comments on commit bdbbf60

Please sign in to comment.