resque-workers-lock 1.9 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 46137f8bebbbe0ced3b3c8bc255ecdc72fb718e3
4
+ data.tar.gz: d2185948f051bbc073ae22a1f41782f985dd3ad5
5
+ SHA512:
6
+ metadata.gz: 3f0aa3cc230357b85e8ba84e4fb8230dceb3bee78d39c7eb764dd93c35267b587f2f39f38efe83561fff19e027c988ed8c6ab7071d9cbd2ff4ac06b414884251
7
+ data.tar.gz: 4f4920fcfc473b25589cfa07e75d7cd5c473fa49baad5827f0f94a88f93e7d031e07e1053d5e698ef7e9aa46eb36cf7fe0e81d83ed851d464d24ccfd9bacc076
data/README.md CHANGED
@@ -5,13 +5,14 @@ This is a [resque](https://github.com/defunkt/resque) plugin inspired by [resque
5
5
  gem 'resque-workers-lock'
6
6
  ```
7
7
 
8
- **Important notice** - As of this gem version 1.7, Resque Workers Lock no longer includes a enqueue lock but focusses solely on a workers lock. If you're also looking for enqueue lock functionality, just add [resque-lock](https://github.com/defunkt/resque-lock) or another plugin in the mix.
8
+ **Important notice** - As of this gem version 1.7, Resque Workers Lock no longer includes an enqueue lock but focusses solely on a workers lock. If you're also looking for enqueue lock functionality, just add [resque-lock](https://github.com/defunkt/resque-lock) or another plugin in the mix.
9
9
 
10
10
  ## What does it do?
11
- If resque jobs have the same lock applied this means that those jobs cannot be processed simultaneously by two or more workers. When this situation occurs the second job gets pushed back to the queue.
11
+ If resque jobs have the same lock(s) applied this means that those jobs cannot be processed simultaneously by two or more workers. When this situation occurs the second job gets pushed back to the queue.
12
12
 
13
13
  ## What is the default lock?
14
14
  By default the lock is the instance name + arguments (just like the classic resque-lock). Override this lock to lock on specific arguments.
15
+ You can specify only one lock or an array of locks. A job with an array of locks will only be processed when all the (individual) locks are not being processed by workers.
15
16
 
16
17
  ## How does it differ from resque-lock?
17
18
  Resque-lock will not let you enqueue jobs when you locked them. Resque-workers-lock locks on a workers-level and will requeue the locked jobs. If a worker takes on a job that is already being processed by another worker it will put the job back up in the queue!
@@ -66,3 +67,4 @@ Do a delayed resque (re)queue. However, this will have approximately the same re
66
67
  ## Authors/Contributors
67
68
  [nicholaides](https://github.com/nicholaides)
68
69
  [jgarber](https://github.com/jgarber)
70
+ [mohawke](https://github.com/mohawke)
@@ -19,13 +19,21 @@ module Resque
19
19
  3600
20
20
  end
21
21
 
22
- # Override in your job to control the workers lock key.
22
+ # Override in your job to control the workers lock key(s).
23
23
  def lock_workers(*args)
24
24
  "#{name}-#{args.to_s}"
25
25
  end
26
26
 
27
27
  def get_lock_workers(*args)
28
- "workerslock:"+lock_workers(*args).to_s
28
+ lock_result = lock_workers(*args)
29
+
30
+ if lock_result.kind_of?(Array)
31
+ lock_result.map do |lock|
32
+ "workerslock:#{lock}"
33
+ end
34
+ else
35
+ ["workerslock:#{lock_result}"]
36
+ end
29
37
  end
30
38
 
31
39
  # Override in your job to change the perform requeue delay
@@ -37,8 +45,12 @@ module Resque
37
45
  # If it raises Resque::Job::DontPerform, the job is aborted.
38
46
  def before_perform_workers_lock(*args)
39
47
  if lock_workers(*args)
40
- if Resque.redis.setnx(get_lock_workers(*args), true)
41
- Resque.redis.expire(get_lock_workers(*args), worker_lock_timeout(*args))
48
+ lock_result = get_lock_workers(*args)
49
+
50
+ if Resque.redis.msetnx lock_result.zip([true]*lock_result.size).flatten
51
+ lock_result.each do |lock|
52
+ Resque.redis.expire(lock, worker_lock_timeout(*args))
53
+ end
42
54
  else
43
55
  sleep(requeue_perform_delay)
44
56
  Resque.enqueue(self, *args)
@@ -48,7 +60,11 @@ module Resque
48
60
  end
49
61
 
50
62
  def clear_workers_lock(*args)
51
- Resque.redis.del(get_lock_workers(*args))
63
+ lock_result = get_lock_workers(*args)
64
+
65
+ lock_result.each do |lock|
66
+ Resque.redis.del(lock)
67
+ end
52
68
  end
53
69
 
54
70
  def around_perform_workers_lock(*args)
@@ -0,0 +1,137 @@
1
+ require 'test/unit'
2
+ require File.expand_path('../../lib/resque/plugins/workers/lock', __FILE__)
3
+ require 'tempfile'
4
+ require 'timeout'
5
+
6
+ require_relative 'unique_job_mkey'
7
+
8
+ class LockMKeyTest < Test::Unit::TestCase
9
+
10
+
11
+ def setup
12
+ Resque.redis.keys('UniqueJobMkey*').each do |key|
13
+ Resque.redis.del key
14
+ end
15
+ end
16
+
17
+ def test_lint
18
+ assert_nothing_raised do
19
+ Resque::Plugin.lint(Resque::Plugins::Workers::Lock)
20
+ end
21
+ end
22
+
23
+ def test_workers_dont_work_simultaneously
24
+ assert_locking_works_with jobs: 2, workers: 2
25
+ end
26
+
27
+ def test_worker_locks_timeout
28
+ output_file = Tempfile.new 'output_file'
29
+
30
+ Resque.enqueue UniqueJobMKey, job: 'interrupted-job', output_file: output_file.path, sleep: 1000
31
+
32
+ worker_pid = start_worker
33
+ wait_until(10){ lock_has_been_acquired }
34
+ kill_worker(worker_pid)
35
+
36
+ Resque.enqueue UniqueJobMKey, job: 'completing-job', output_file: output_file.path, sleep: 0
37
+ process_jobs workers: 1, timeout: UniqueJobMKey.worker_lock_timeout + 2
38
+
39
+ lines = File.readlines(output_file).map(&:chomp)
40
+ assert_equal ['starting interrupted-job', 'starting completing-job', 'finished completing-job'], lines
41
+ end
42
+
43
+ private
44
+
45
+ def lock_has_been_acquired
46
+ Resque.redis.exists(UniqueJobMKey.get_lock_workers.first)
47
+ end
48
+
49
+ def kill_worker(worker_pid)
50
+ Process.kill("TERM", worker_pid)
51
+ Process.waitpid(worker_pid)
52
+ end
53
+
54
+ def start_worker
55
+ fork.tap do |pid|
56
+ if !pid
57
+ worker = Resque::Worker.new('*')
58
+ worker.term_child = true
59
+ worker.reconnect
60
+ worker.work(0.5)
61
+ exit!
62
+ end
63
+ end
64
+ end
65
+
66
+ def assert_worker_lock_exists(job_class, *args)
67
+ assert Resque.redis.exists(job_class.get_lock_workers(*args).first, "lock does not exist")
68
+ end
69
+
70
+ def assert_locking_works_with options
71
+ jobs = (1..options[:jobs]).map{|job| "Job #{job}" }
72
+ output_file = Tempfile.new 'output_file'
73
+
74
+ jobs.each do |job|
75
+ Resque.enqueue UniqueJobMKey, job: job, output_file: output_file.path
76
+ end
77
+
78
+ process_jobs workers: options[:workers], timeout: 10
79
+
80
+ lines = File.readlines(output_file).map(&:chomp)
81
+ lines.each_slice(2) do |a,b|
82
+ assert_equal a.split.last,b.split.last, "#{a} was interrupted by #{b}"
83
+ end
84
+ end
85
+
86
+ def process_jobs options
87
+ with_workers options[:workers] do
88
+ wait_until(options[:timeout]) do
89
+ no_busy_workers && no_queued_jobs
90
+ end
91
+ end
92
+ end
93
+
94
+ def with_workers n
95
+ pids = []
96
+ n.times do
97
+ if pid = fork
98
+ pids << pid
99
+ else
100
+ pids = [] # Don't kill from child's ensure
101
+ worker = Resque::Worker.new('*')
102
+ worker.term_child = true
103
+ worker.reconnect
104
+ worker.work(0.5)
105
+ exit!
106
+ end
107
+ end
108
+
109
+ yield
110
+
111
+ ensure
112
+ pids.each do |pid|
113
+ Process.kill("QUIT", pid)
114
+ end
115
+
116
+ pids.each do |pid|
117
+ Process.waitpid(pid)
118
+ end
119
+ end
120
+
121
+ def no_busy_workers
122
+ Resque::Worker.working.size == 0
123
+ end
124
+
125
+ def no_queued_jobs
126
+ Resque.redis.llen("queue:mlock_test") == 0
127
+ end
128
+
129
+ def wait_until(timeout)
130
+ Timeout::timeout(timeout) do
131
+ loop do
132
+ return if yield
133
+ sleep 1
134
+ end
135
+ end
136
+ end
137
+ end
@@ -0,0 +1,26 @@
1
+ require File.expand_path('../../lib/resque/plugins/workers/lock', __FILE__)
2
+
3
+ class UniqueJobMKey
4
+ extend Resque::Plugins::Workers::Lock
5
+ @queue = :mlock_test
6
+
7
+ def self.worker_lock_timeout(*)
8
+ 5
9
+ end
10
+
11
+ def self.lock_workers(*args)
12
+ [self.name + '1', "#{self.name}#{args.to_s}"]
13
+ end
14
+
15
+ def self.append_output filename, string
16
+ File.open(filename, 'a') do |output_file|
17
+ output_file.puts string
18
+ end
19
+ end
20
+
21
+ def self.perform params
22
+ append_output params['output_file'], "starting #{params['job']}"
23
+ sleep(params['sleep'] || 1)
24
+ append_output params['output_file'], "finished #{params['job']}"
25
+ end
26
+ end
metadata CHANGED
@@ -1,90 +1,85 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: resque-workers-lock
3
3
  version: !ruby/object:Gem::Version
4
- version: '1.9'
5
- prerelease:
4
+ version: 2.0.0
6
5
  platform: ruby
7
6
  authors:
8
7
  - Bart Olsthoorn
9
8
  - Mike Nicholaides
10
9
  - Jason Garber
10
+ - Tijs Planckaert
11
11
  autorequire:
12
12
  bindir: bin
13
13
  cert_chain: []
14
- date: 2013-05-27 00:00:00.000000000 Z
14
+ date: 2014-05-30 00:00:00.000000000 Z
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
17
17
  name: resque
18
18
  requirement: !ruby/object:Gem::Requirement
19
- none: false
20
19
  requirements:
21
- - - ! '>='
20
+ - - ">="
22
21
  - !ruby/object:Gem::Version
23
22
  version: '0'
24
23
  type: :runtime
25
24
  prerelease: false
26
25
  version_requirements: !ruby/object:Gem::Requirement
27
- none: false
28
26
  requirements:
29
- - - ! '>='
27
+ - - ">="
30
28
  - !ruby/object:Gem::Version
31
29
  version: '0'
32
30
  - !ruby/object:Gem::Dependency
33
31
  name: rake
34
32
  requirement: !ruby/object:Gem::Requirement
35
- none: false
36
33
  requirements:
37
- - - ! '>='
34
+ - - ">="
38
35
  - !ruby/object:Gem::Version
39
36
  version: '0'
40
37
  type: :development
41
38
  prerelease: false
42
39
  version_requirements: !ruby/object:Gem::Requirement
43
- none: false
44
40
  requirements:
45
- - - ! '>='
41
+ - - ">="
46
42
  - !ruby/object:Gem::Version
47
43
  version: '0'
48
- description: ! 'A Resque plugin. Two or more jobs with the same lock cannot be processed
49
- simultaneously by multiple workers.
50
-
44
+ description: |
45
+ A Resque plugin. Two or more jobs with the same lock cannot be processed simultaneously by multiple workers.
51
46
  When this situation occurs the second job gets pushed back to the queue.
52
-
53
- '
54
47
  email: bartolsthoorn@gmail.com
55
48
  executables: []
56
49
  extensions: []
57
50
  extra_rdoc_files: []
58
51
  files:
52
+ - LICENSE
59
53
  - README.md
60
54
  - Rakefile
61
- - LICENSE
62
55
  - lib/resque/plugins/workers/lock.rb
56
+ - test/lock_mkey_test.rb
63
57
  - test/lock_test.rb
64
58
  - test/unique_job.rb
59
+ - test/unique_job_mkey.rb
65
60
  homepage: http://github.com/bartolsthoorn/resque-workers-lock
66
- licenses: []
61
+ licenses:
62
+ - MIT
63
+ metadata: {}
67
64
  post_install_message:
68
65
  rdoc_options: []
69
66
  require_paths:
70
67
  - lib
71
68
  required_ruby_version: !ruby/object:Gem::Requirement
72
- none: false
73
69
  requirements:
74
- - - ! '>='
70
+ - - ">="
75
71
  - !ruby/object:Gem::Version
76
72
  version: '0'
77
73
  required_rubygems_version: !ruby/object:Gem::Requirement
78
- none: false
79
74
  requirements:
80
- - - ! '>='
75
+ - - ">="
81
76
  - !ruby/object:Gem::Version
82
77
  version: '0'
83
78
  requirements: []
84
79
  rubyforge_project:
85
- rubygems_version: 1.8.25
80
+ rubygems_version: 2.2.2
86
81
  signing_key:
87
- specification_version: 3
82
+ specification_version: 4
88
83
  summary: Resque plugin, prevent specific jobs to be processed simultaneously by multiple
89
84
  workers.
90
85
  test_files: []