resque-serial-queues 0.0.1

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: 34c9b997a83c81205f422f22b616de56ba0516b4
4
+ data.tar.gz: ed19138a00d01994dce31852244ad817b7e7e0b4
5
+ SHA512:
6
+ metadata.gz: 6b2a21f5b1fd9ac020d3a538fd6b56ff76c98c76cf695d6c83ab8a607e662b6c3285424655fdc8dcab78a0a8d969192db9a93f1e0d59120eeb64a1088360d371
7
+ data.tar.gz: 045083e0b65352dc9aa272792130f00a38a8d2fb1da124c72df2f4bd84acbff244b265bf0c67eaeb1399e8b29586ba7433708c1ddb7d27394cd49bcf70f0d959
data/.gitignore ADDED
@@ -0,0 +1,22 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
18
+ *.bundle
19
+ *.so
20
+ *.o
21
+ *.a
22
+ mkmf.log
data/.travis.yml ADDED
@@ -0,0 +1,3 @@
1
+ language: ruby
2
+ rvm:
3
+ - 2.0.0
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in resque-serial-queues.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Cristian Bica
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,45 @@
1
+ # Resque::Plugins::SerialQueues
2
+
3
+ Declaring resque queues as serial.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'resque-serial-queues'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install resque-serial-queues
18
+
19
+ ## Usage
20
+
21
+ ```ruby
22
+
23
+ Resque::Plugins::SerialQueues.configure do |config|
24
+ config.serial_queues = [:serial_jobs]
25
+ config.lock_timeout = 120 #default 3600 seconds
26
+ end
27
+
28
+ class MyJob
29
+
30
+ @queue = :serial_jobs
31
+
32
+ def perform(*)
33
+ end
34
+
35
+ end
36
+
37
+ ```
38
+
39
+ ## Contributing
40
+
41
+ 1. Fork it ( https://github.com/cristianbica/resque-serial-queues/fork )
42
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
43
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
44
+ 4. Push to the branch (`git push origin my-new-feature`)
45
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,11 @@
1
+ require "bundler/gem_tasks"
2
+ require "rake/testtask"
3
+
4
+ Rake::TestTask.new(:test) do |t|
5
+ t.libs << "test"
6
+ t.test_files = FileList['test/cases/**/*.rb']
7
+ t.verbose = true
8
+ end
9
+
10
+ task :default => :test
11
+
@@ -0,0 +1,5 @@
1
+ require 'resque-serial-queues/version'
2
+ require 'resque/plugins/serial_queues'
3
+ require 'resque/plugins/serial_queues/config'
4
+ require 'resque/plugins/serial_queues/resque_extension'
5
+
@@ -0,0 +1,7 @@
1
+ module Resque
2
+ module Plugins
3
+ module SerialQueues
4
+ VERSION = "0.0.1"
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,38 @@
1
+ module Resque
2
+ module Plugins
3
+ module SerialQueues extend self
4
+ def config
5
+ @config ||= Config.new
6
+ end
7
+
8
+ def configure
9
+ yield(config) if block_given?
10
+ end
11
+
12
+ def redis
13
+ @redis ||= Redis::Namespace.new(:serial_queues, redis: Resque.redis)
14
+ end
15
+
16
+ def self.is_queue_serial?(queue)
17
+ config.serial_queues.include?(queue.to_s)
18
+ end
19
+
20
+ def self.lock_queue(queue)
21
+ if redis.setnx("queue-lock:#{queue}", 1)
22
+ redis.expire("queue-lock:#{queue}", config.lock_timeout)
23
+ true
24
+ else
25
+ false
26
+ end
27
+ end
28
+
29
+ def self.is_queue_locked?(queue)
30
+ redis.exists("queue-lock:#{queue}")
31
+ end
32
+
33
+ def self.unlock_queue(queue)
34
+ redis.del("queue-lock:#{queue}")
35
+ end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,20 @@
1
+ module Resque
2
+ module Plugins
3
+ module SerialQueues
4
+ class Config
5
+ attr_accessor :serial_queues
6
+ attr_accessor :lock_timeout
7
+
8
+ def initialize
9
+ self.serial_queues = []
10
+ self.lock_timeout = 60*60
11
+ end
12
+
13
+ def serial_queues=(queues)
14
+ raise "queues should be an Array" unless queues.is_a?(Array)
15
+ @serial_queues = queues.map(&:to_s)
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,25 @@
1
+ require 'resque'
2
+ require 'resque/job'
3
+
4
+ class Resque::Job
5
+ def self.reserve(queue)
6
+ return if is_serial_queue?(queue) and not lock_queue(queue)
7
+ return unless payload = Resque.pop(queue)
8
+ new(queue, payload)
9
+ end
10
+
11
+ protected
12
+ def self.is_serial_queue?(queue)
13
+ Resque::Plugins::SerialQueues.is_queue_serial?(queue)
14
+ end
15
+
16
+ def self.lock_queue(queue)
17
+ Resque::Plugins::SerialQueues.lock_queue(queue)
18
+ end
19
+ end
20
+
21
+ class Object
22
+ def self.after_perform_unlock_serial_queue(*)
23
+ Resque::Plugins::SerialQueues.unlock_queue(@queue) if Resque::Plugins::SerialQueues.is_queue_serial?(@queue)
24
+ end
25
+ end
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'resque-serial-queues/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "resque-serial-queues"
8
+ spec.version = Resque::Plugins::SerialQueues::VERSION
9
+ spec.authors = ["Cristian Bica"]
10
+ spec.email = ["cristian.bica@gmail.com"]
11
+ spec.summary = %q{Declare resque queues serial}
12
+ spec.description = %q{Declare resque queues serial and jobs in that queue will be run in serial mode}
13
+ spec.homepage = "http://github.com/cristianbica/resque-serial-queues"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency 'resque', '~>1.0'
22
+ spec.add_dependency 'redis-namespace'
23
+ spec.add_dependency 'activesupport'
24
+
25
+ spec.add_development_dependency "bundler", "~> 1.6"
26
+ spec.add_development_dependency "rake"
27
+ spec.add_development_dependency "minitest"
28
+ end
@@ -0,0 +1,75 @@
1
+ require 'minitest_helper'
2
+ require 'jobs/sleeping_job'
3
+ require 'jobs/delayed_failing_job'
4
+ require 'jobs/benchmark_job'
5
+
6
+ class LockingTest < ResqueSerialQueuesTest
7
+
8
+ def test_checking_if_a_queue_is_serial
9
+ assert_serial_queue :serial_jobs
10
+ end
11
+
12
+ def test_checking_if_a_queue_is_not_serial
13
+ assert_concurrent_queue :parallel_jobs
14
+ end
15
+
16
+ def test_locking_a_queue
17
+ assert Resque::Plugins::SerialQueues.lock_queue(:serial_jobs)
18
+ assert_queue_locked :serial_jobs
19
+ end
20
+
21
+ def test_queue_lock_timeout
22
+ timeout_was = Resque::Plugins::SerialQueues.config.lock_timeout
23
+ Resque::Plugins::SerialQueues.config.lock_timeout = 1
24
+ Resque::Plugins::SerialQueues.lock_queue(:serial_jobs)
25
+ assert_queue_locked :serial_jobs
26
+ sleep 1.1
27
+ assert_queue_unlocked :serial_jobs
28
+ ensure
29
+ Resque::Plugins::SerialQueues.config.lock_timeout = timeout_was
30
+ end
31
+
32
+ def test_a_queue_is_locked_while_running
33
+ self.class.start_redis_workers 1
34
+ Resque.enqueue SleepingJob, 1
35
+ sleep 0.5
36
+ assert_queue_locked :serial_jobs
37
+ end
38
+
39
+ def test_a_queue_is_not_locked_after_running
40
+ self.class.start_redis_workers 1
41
+ Resque.enqueue SleepingJob, 0.1
42
+ sleep 0.4
43
+ assert_queue_unlocked :serial_jobs
44
+ end
45
+
46
+ def test_a_queue_is_not_locked_after_a_job_fails
47
+ self.class.start_redis_workers 1
48
+ Resque.enqueue DelayedFailingJob, 0.1
49
+ sleep 0.4
50
+ assert_queue_unlocked :serial_jobs
51
+ end
52
+
53
+ def test_running_many_concurrent_jobs
54
+ jobs = 1000
55
+ max_sleep = 0.05
56
+ workers = 20
57
+ self.class.start_redis_workers workers
58
+ jobs.times do
59
+ Resque.enqueue BenchmarkJob, max_sleep
60
+ end
61
+ assert_queue_locked :serial_jobs
62
+ while Resque.info[:pending]>0
63
+ sleep 0.1
64
+ assert Resque.info[:working]<=1
65
+ end
66
+ assert_queue_unlocked :serial_jobs
67
+ results = []
68
+ while row = Resque::Plugins::SerialQueues.redis.lpop("benchmark-results")
69
+ results << row.split("-").map(&:to_f)
70
+ end
71
+ results.flatten!
72
+ assert_equal results, results.sort
73
+ end
74
+
75
+ end
@@ -0,0 +1,11 @@
1
+ class BenchmarkJob
2
+ @queue = :serial_jobs
3
+
4
+ def self.perform(max_seconds)
5
+ t0 = Time.now.to_f
6
+ sleep rand*1000%max_seconds
7
+ t1 = Time.now.to_f
8
+ Resque::Plugins::SerialQueues.redis.rpush "benchmark-results", [t0, t1].join("-")
9
+ end
10
+
11
+ end
@@ -0,0 +1,8 @@
1
+ class DelayedFailingJob
2
+ @queue = :serial_jobs
3
+
4
+ def self.perform(seconds)
5
+ sleep seconds
6
+ raise
7
+ end
8
+ end
@@ -0,0 +1,7 @@
1
+ class SleepingJob
2
+ @queue = :serial_jobs
3
+
4
+ def self.perform(seconds)
5
+ sleep seconds
6
+ end
7
+ end
@@ -0,0 +1,125 @@
1
+ $LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
2
+
3
+ require 'resque-serial-queues'
4
+ require "minitest"
5
+ require 'minitest/test'
6
+ Minitest.autorun
7
+
8
+ Resque::Plugins::SerialQueues.configure do |config|
9
+ config.serial_queues = [:serial_jobs]
10
+ config.lock_timeout = 60
11
+ end
12
+
13
+ Resque.redis = "127.0.0.1:6379:10/resque-serial-queues:resque"
14
+
15
+ $redis_workers_pids = []
16
+
17
+ class ResqueSerialQueuesTest < Minitest::Test
18
+
19
+ #alias_method :run_without_resque_serial_queue_hooks, :run
20
+ def run(*args)
21
+ log "Running test #{self.name}"
22
+ self.class.clear_redis_keys
23
+ r = super
24
+ self.class.stop_redis_workers
25
+ self.class.clear_redis_keys
26
+ log "Finished running test #{self.name}"
27
+ r
28
+ end
29
+
30
+ def assert_queue_locked(queue)
31
+ Resque::Plugins::SerialQueues.is_queue_locked?(queue)
32
+ end
33
+
34
+ def assert_queue_unlocked(queue)
35
+ !Resque::Plugins::SerialQueues.is_queue_locked?(queue)
36
+ end
37
+
38
+ def assert_serial_queue(queue)
39
+ Resque::Plugins::SerialQueues.is_queue_serial?(queue)
40
+ end
41
+
42
+ def assert_concurrent_queue(queue)
43
+ !assert_serial_queue(queue)
44
+ end
45
+
46
+ def log(message)
47
+ self.class.log(message)
48
+ end
49
+
50
+ def self.log(message)
51
+ puts "[PID: #{Process.pid}] [#{Time.now.to_f}] #{message}"
52
+ end
53
+
54
+ protected
55
+ def self.start_redis_workers(number_of_workers = 1)
56
+ number_of_workers.times do
57
+ start_worker
58
+ end
59
+ end
60
+
61
+ def self.start_worker
62
+ if pid = Kernel.fork
63
+ log "Forked to pid #{pid} to start worker"
64
+ started = wait_for_worker_to_start(pid)
65
+ log "Waiting for worker to start"
66
+ if started
67
+ log "Worker started"
68
+ $redis_workers_pids << pid
69
+ else
70
+ raise "Failed to start worker"
71
+ end
72
+ else
73
+ Resque.redis.client.reconnect
74
+ worker = Resque::Worker.new("*")
75
+ worker.term_timeout = 4.0
76
+ worker.term_child = true
77
+ Resque.logger.level = Logger::DEBUG
78
+ log "Worker started working"
79
+ worker.work 0.1
80
+ Kernel.exit!
81
+ end
82
+ end
83
+
84
+ def self.wait_for_worker_to_start(pid)
85
+ worker_started = false
86
+ 25.times do
87
+ worker_started = worker_exists?(pid)
88
+ break if worker_started
89
+ sleep 0.2
90
+ end
91
+ worker_started
92
+ end
93
+
94
+ def self.wait_for_worker_to_finish(pid)
95
+ worker_finished = false
96
+ 25.times do
97
+ worker_finished = !worker_exists?(pid)
98
+ break if worker_finished
99
+ sleep 0.2
100
+ end
101
+ worker_finished
102
+ end
103
+
104
+ def self.worker_exists?(pid)
105
+ Resque::Worker.all.map(&:to_s).grep(/\:#{pid}\:/).any?
106
+ end
107
+
108
+ def self.stop_redis_workers
109
+ while pid = $redis_workers_pids.pop do
110
+ log "Stopping worker with pid #{pid} (QUIT)"
111
+ Process.kill "QUIT", pid
112
+ log "Stopping worker with pid #{pid} (TERM)" and Process.kill "TERM", pid unless wait_for_worker_to_finish(pid)
113
+ log "Stopped worker with pid #{pid}"
114
+ Process.kill 9, pid rescue nil
115
+ end
116
+ end
117
+
118
+ def self.clear_redis_keys
119
+ log "Clear redis keys"
120
+ Resque.redis.keys("*").each do |key|
121
+ Resque.redis.del(key)
122
+ end
123
+ end
124
+
125
+ end
metadata ADDED
@@ -0,0 +1,152 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: resque-serial-queues
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Cristian Bica
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-06-20 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: resque
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: redis-namespace
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
+ - !ruby/object:Gem::Dependency
42
+ name: activesupport
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.6'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.6'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: minitest
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ description: Declare resque queues serial and jobs in that queue will be run in serial
98
+ mode
99
+ email:
100
+ - cristian.bica@gmail.com
101
+ executables: []
102
+ extensions: []
103
+ extra_rdoc_files: []
104
+ files:
105
+ - ".gitignore"
106
+ - ".travis.yml"
107
+ - Gemfile
108
+ - LICENSE.txt
109
+ - README.md
110
+ - Rakefile
111
+ - lib/resque-serial-queues.rb
112
+ - lib/resque-serial-queues/version.rb
113
+ - lib/resque/plugins/serial_queues.rb
114
+ - lib/resque/plugins/serial_queues/config.rb
115
+ - lib/resque/plugins/serial_queues/resque_extension.rb
116
+ - resque-serial-queues.gemspec
117
+ - test/cases/locking_test.rb
118
+ - test/jobs/benchmark_job.rb
119
+ - test/jobs/delayed_failing_job.rb
120
+ - test/jobs/sleeping_job.rb
121
+ - test/minitest_helper.rb
122
+ homepage: http://github.com/cristianbica/resque-serial-queues
123
+ licenses:
124
+ - MIT
125
+ metadata: {}
126
+ post_install_message:
127
+ rdoc_options: []
128
+ require_paths:
129
+ - lib
130
+ required_ruby_version: !ruby/object:Gem::Requirement
131
+ requirements:
132
+ - - ">="
133
+ - !ruby/object:Gem::Version
134
+ version: '0'
135
+ required_rubygems_version: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - ">="
138
+ - !ruby/object:Gem::Version
139
+ version: '0'
140
+ requirements: []
141
+ rubyforge_project:
142
+ rubygems_version: 2.2.2
143
+ signing_key:
144
+ specification_version: 4
145
+ summary: Declare resque queues serial
146
+ test_files:
147
+ - test/cases/locking_test.rb
148
+ - test/jobs/benchmark_job.rb
149
+ - test/jobs/delayed_failing_job.rb
150
+ - test/jobs/sleeping_job.rb
151
+ - test/minitest_helper.rb
152
+ has_rdoc: