resque-restriction 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/README.markdown +9 -4
- data/VERSION +1 -1
- data/lib/resque-restriction/job.rb +2 -2
- data/lib/resque-restriction/restriction_job.rb +72 -57
- data/resque-restriction.gemspec +61 -0
- data/spec/resque-restriction/job_spec.rb +14 -5
- data/spec/resque-restriction/restriction_job_spec.rb +23 -1
- data/spec/spec_helper.rb +24 -1
- metadata +21 -9
data/.gitignore
CHANGED
data/README.markdown
CHANGED
@@ -9,18 +9,23 @@ Resque Restriction is a plugin for the [Resque][0] queueing system (http://githu
|
|
9
9
|
|
10
10
|
Resque Restriction requires Resque 1.7.0.
|
11
11
|
|
12
|
+
Install
|
13
|
+
-------
|
14
|
+
|
15
|
+
sudo gem install resque-restriction
|
16
|
+
|
12
17
|
To use
|
13
18
|
------
|
14
19
|
|
15
|
-
|
20
|
+
It is especially useful when a system has an email invitation resque job, because sending emails too frequentyly will be treated as a spam. What you should do for the InvitationJob is to inherit it from Resque::Plugins::RestrictionJob class and add restrict definition. Example:
|
16
21
|
|
17
|
-
class
|
18
|
-
restrict :per_day => 1000, :per_hour =>
|
22
|
+
class InvitationJob < Resque::Plugins::RestrictionJob
|
23
|
+
restrict :per_day => 1000, :per_hour => 100, :per_300 => 30
|
19
24
|
|
20
25
|
#rest of your class here
|
21
26
|
end
|
22
27
|
|
23
|
-
|
28
|
+
That means the InvitationJob can not be executed more than 1000 times per day, 100 times per hour and 30 times per 300 seconds.
|
24
29
|
|
25
30
|
The argument of restrict method is a hash, the key of the hash is a period time, including :per_minute, :per_hour, :per_day, :per_week, :per_month, :per_year, and you can also define any period like :per_300 means per 300 seconds. the value of the hash is the job execution limit number in a period.
|
26
31
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0
|
@@ -4,8 +4,8 @@ module Resque
|
|
4
4
|
alias_method :origin_reserve, :reserve
|
5
5
|
|
6
6
|
def reserve(queue)
|
7
|
-
if queue == 'restriction' && payload = Resque.
|
8
|
-
constantize(payload['class']).repush
|
7
|
+
if queue == 'restriction' && payload = Resque.pop(queue)
|
8
|
+
constantize(payload['class']).repush(*payload['args'])
|
9
9
|
return
|
10
10
|
end
|
11
11
|
origin_reserve(queue)
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module Resque
|
2
2
|
module Plugins
|
3
|
-
|
3
|
+
module Restriction
|
4
4
|
SECONDS = {
|
5
5
|
:per_minute => 60,
|
6
6
|
:per_hour => 60*60,
|
@@ -10,77 +10,92 @@ module Resque
|
|
10
10
|
:per_year => 366*24*60*60
|
11
11
|
}
|
12
12
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
end
|
13
|
+
def settings
|
14
|
+
@options ||= {}
|
15
|
+
end
|
17
16
|
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
17
|
+
def restrict(options={})
|
18
|
+
settings.merge!(options)
|
19
|
+
end
|
20
|
+
|
21
|
+
def before_perform_restriction(*args)
|
22
|
+
keys_decremented = []
|
23
|
+
settings.each do |period, number|
|
24
|
+
key = redis_key(period, *args)
|
25
|
+
|
26
|
+
# first try to set period key to be the total allowed for the period
|
27
|
+
# if we get a 0 result back, the key wasn't set, so we know we are
|
28
|
+
# already tracking the count for that period'
|
29
|
+
period_active = ! Resque.redis.setnx(key, number.to_i - 1)
|
26
30
|
|
27
|
-
|
28
|
-
|
29
|
-
|
31
|
+
# If we are already tracking that period, then decrement by one to
|
32
|
+
# see if we are allowed to run, pushing to restriction queue to run
|
33
|
+
# later if not. Note that the value stored is the number of outstanding
|
34
|
+
# jobs allowed, thus we need to reincrement if the decr discovers that
|
35
|
+
# we have bypassed the limit
|
36
|
+
if period_active
|
37
|
+
value = Resque.redis.decrby(key, 1).to_i
|
38
|
+
keys_decremented << key
|
39
|
+
if value < 0
|
40
|
+
# reincrement the keys if one of the periods triggers DontPerform so
|
41
|
+
# that we accurately track capacity
|
42
|
+
keys_decremented.each {|k| Resque.redis.incrby(k, 1) }
|
30
43
|
Resque.push "restriction", :class => to_s, :args => args
|
31
44
|
raise Resque::Job::DontPerform
|
32
45
|
end
|
33
46
|
end
|
34
47
|
end
|
35
|
-
|
36
|
-
def after_perform_restriction(*args)
|
37
|
-
settings.each do |period, number|
|
38
|
-
key = redis_key(period)
|
39
|
-
Resque.redis.decrby(key, 1)
|
40
|
-
end
|
41
|
-
end
|
48
|
+
end
|
42
49
|
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
when :per_year then Date.today.year.to_s
|
48
|
-
else period.to_s =~ /^per_(\d+)$/ and (Time.now.to_i / $1.to_i).to_s end
|
49
|
-
[self.to_s, period_str].compact.join(":")
|
50
|
+
def after_perform_restriction(*args)
|
51
|
+
if settings[:concurrent]
|
52
|
+
key = redis_key(:concurrent, *args)
|
53
|
+
Resque.redis.incrby(key, 1)
|
50
54
|
end
|
55
|
+
end
|
51
56
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
57
|
+
def redis_key(period, *args)
|
58
|
+
period_str = case period
|
59
|
+
when :concurrent then "*"
|
60
|
+
when :per_minute, :per_hour, :per_day, :per_week then (Time.now.to_i / SECONDS[period]).to_s
|
61
|
+
when :per_month then Date.today.strftime("%Y-%m")
|
62
|
+
when :per_year then Date.today.year.to_s
|
63
|
+
else period.to_s =~ /^per_(\d+)$/ and (Time.now.to_i / $1.to_i).to_s end
|
64
|
+
[self.identifier(*args), period_str].compact.join(":")
|
65
|
+
end
|
66
|
+
|
67
|
+
def identifier(*args)
|
68
|
+
self.to_s
|
69
|
+
end
|
70
|
+
|
71
|
+
def seconds(period)
|
72
|
+
if SECONDS.keys.include? period
|
73
|
+
SECONDS[period]
|
74
|
+
else
|
75
|
+
period.to_s =~ /^per_(\d+)$/ and $1
|
58
76
|
end
|
77
|
+
end
|
59
78
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
79
|
+
def repush(*args)
|
80
|
+
no_restrictions = true
|
81
|
+
queue_name = Resque.queue_from_class(self)
|
82
|
+
settings.each do |period, number|
|
83
|
+
key = redis_key(period, *args)
|
84
|
+
value = Resque.redis.get(key)
|
85
|
+
no_restrictions &&= (value.nil? or value == "" or value.to_i > 0)
|
86
|
+
end
|
87
|
+
if no_restrictions
|
88
|
+
Resque.push queue_name, :class => to_s, :args => args
|
89
|
+
else
|
90
|
+
Resque.push "restriction", :class => to_s, :args => args
|
69
91
|
end
|
92
|
+
end
|
70
93
|
|
71
|
-
|
72
|
-
# after operation incrby - expire, then decrby will reset the value to 0 first
|
73
|
-
# use operation set - expire - incrby instead
|
74
|
-
def set_restrict(key, seconds, number)
|
75
|
-
Resque.redis.set(key, '')
|
76
|
-
Resque.redis.expire(key, seconds)
|
77
|
-
Resque.redis.incrby(key, number)
|
78
|
-
end
|
94
|
+
end
|
79
95
|
|
80
|
-
|
81
|
-
|
82
|
-
end
|
83
|
-
end
|
96
|
+
class RestrictionJob
|
97
|
+
extend Restriction
|
84
98
|
end
|
99
|
+
|
85
100
|
end
|
86
101
|
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{resque-restriction}
|
8
|
+
s.version = "0.2.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Richard Huang"]
|
12
|
+
s.date = %q{2010-06-29}
|
13
|
+
s.description = %q{resque-restriction is an extension to resque queue system that restricts the execution number of certain jobs in a period time, the exceeded jobs will be executed at the next period.}
|
14
|
+
s.email = %q{flyerhzm@gmail.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README.markdown"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".gitignore",
|
21
|
+
"LICENSE",
|
22
|
+
"README.markdown",
|
23
|
+
"Rakefile",
|
24
|
+
"VERSION",
|
25
|
+
"init.rb",
|
26
|
+
"lib/resque-restriction.rb",
|
27
|
+
"lib/resque-restriction/job.rb",
|
28
|
+
"lib/resque-restriction/restriction_job.rb",
|
29
|
+
"rails/init.rb",
|
30
|
+
"resque-restriction.gemspec",
|
31
|
+
"spec/redis-test.conf",
|
32
|
+
"spec/resque-restriction/job_spec.rb",
|
33
|
+
"spec/resque-restriction/restriction_job_spec.rb",
|
34
|
+
"spec/spec.opts",
|
35
|
+
"spec/spec_helper.rb"
|
36
|
+
]
|
37
|
+
s.homepage = %q{http://github.com/flyerhzm/resque-restriction}
|
38
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
39
|
+
s.require_paths = ["lib"]
|
40
|
+
s.rubygems_version = %q{1.3.6}
|
41
|
+
s.summary = %q{resque-restriction is an extension to resque queue system that restricts the execution number of certain jobs in a period time.}
|
42
|
+
s.test_files = [
|
43
|
+
"spec/resque-restriction/job_spec.rb",
|
44
|
+
"spec/resque-restriction/restriction_job_spec.rb",
|
45
|
+
"spec/spec_helper.rb"
|
46
|
+
]
|
47
|
+
|
48
|
+
if s.respond_to? :specification_version then
|
49
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
50
|
+
s.specification_version = 3
|
51
|
+
|
52
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
53
|
+
s.add_runtime_dependency(%q<resque>, [">= 1.7.0"])
|
54
|
+
else
|
55
|
+
s.add_dependency(%q<resque>, [">= 1.7.0"])
|
56
|
+
end
|
57
|
+
else
|
58
|
+
s.add_dependency(%q<resque>, [">= 1.7.0"])
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
@@ -1,17 +1,26 @@
|
|
1
1
|
require File.join(File.dirname(__FILE__) + '/../spec_helper')
|
2
2
|
|
3
3
|
describe Resque::Job do
|
4
|
-
it "should repush
|
4
|
+
it "should repush restriction queue when reserve" do
|
5
5
|
Resque.redis.flushall
|
6
|
-
Resque.push('restriction', :class => 'OneHourRestrictionJob', :args => 'any args')
|
6
|
+
Resque.push('restriction', :class => 'OneHourRestrictionJob', :args => ['any args'])
|
7
7
|
Resque::Job.reserve('restriction').should be_nil
|
8
|
-
Resque::Job.reserve('normal').should == Resque::Job.new('normal', {'class' => 'OneHourRestrictionJob', 'args' => 'any args'})
|
8
|
+
Resque::Job.reserve('normal').should == Resque::Job.new('normal', {'class' => 'OneHourRestrictionJob', 'args' => ['any args']})
|
9
|
+
Resque::Job.reserve('normal').should be_nil
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should push back to restriction queue when still restricted" do
|
13
|
+
Resque.redis.flushall
|
14
|
+
Resque.redis.set(OneHourRestrictionJob.redis_key(:per_hour), -1)
|
15
|
+
Resque.push('restriction', :class => 'OneHourRestrictionJob', :args => ['any args'])
|
16
|
+
Resque::Job.reserve('restriction').should be_nil
|
17
|
+
Resque.pop('restriction').should == {'class' => 'OneHourRestrictionJob', 'args' => ['any args']}
|
9
18
|
Resque::Job.reserve('normal').should be_nil
|
10
19
|
end
|
11
20
|
|
12
21
|
it "should not repush when reserve normal queue" do
|
13
|
-
Resque.push('normal', :class => 'OneHourRestrictionJob', :args => 'any args')
|
14
|
-
Resque::Job.reserve('normal').should == Resque::Job.new('normal', {'class' => 'OneHourRestrictionJob', 'args' => 'any args'})
|
22
|
+
Resque.push('normal', :class => 'OneHourRestrictionJob', :args => ['any args'])
|
23
|
+
Resque::Job.reserve('normal').should == Resque::Job.new('normal', {'class' => 'OneHourRestrictionJob', 'args' => ['any args']})
|
15
24
|
Resque::Job.reserve('normal').should be_nil
|
16
25
|
end
|
17
26
|
end
|
@@ -42,6 +42,17 @@ describe Resque::Plugins::RestrictionJob do
|
|
42
42
|
Resque.redis.get(OneHourRestrictionJob.redis_key(:per_hour)).should == "9"
|
43
43
|
end
|
44
44
|
|
45
|
+
it "should use identifier to set exclusive execution counts" do
|
46
|
+
result = perform_job(IdentifiedRestrictionJob, 1)
|
47
|
+
result.should be_true
|
48
|
+
result = perform_job(IdentifiedRestrictionJob, 1)
|
49
|
+
result.should be_true
|
50
|
+
result = perform_job(IdentifiedRestrictionJob, 2)
|
51
|
+
result.should be_true
|
52
|
+
Resque.redis.get(IdentifiedRestrictionJob.redis_key(:per_hour, 1)).should == "8"
|
53
|
+
Resque.redis.get(IdentifiedRestrictionJob.redis_key(:per_hour, 2)).should == "9"
|
54
|
+
end
|
55
|
+
|
45
56
|
it "should decrement execution number when one job executed" do
|
46
57
|
Resque.redis.set(OneHourRestrictionJob.redis_key(:per_hour), 6)
|
47
58
|
result = perform_job(OneHourRestrictionJob, "any args")
|
@@ -49,7 +60,18 @@ describe Resque::Plugins::RestrictionJob do
|
|
49
60
|
Resque.redis.get(OneHourRestrictionJob.redis_key(:per_hour)).should == "5"
|
50
61
|
end
|
51
62
|
|
52
|
-
it "should
|
63
|
+
it "should increment execution number when concurrent job completes" do
|
64
|
+
t = Thread.new do
|
65
|
+
result = perform_job(ConcurrentRestrictionJob, "any args")
|
66
|
+
result.should be_true
|
67
|
+
end
|
68
|
+
sleep 0.1
|
69
|
+
Resque.redis.get(ConcurrentRestrictionJob.redis_key(:concurrent)).should == "0"
|
70
|
+
t.join
|
71
|
+
Resque.redis.get(ConcurrentRestrictionJob.redis_key(:concurrent)).should == "1"
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should put the job into restriction queue when execution count < 0" do
|
53
75
|
Resque.redis.set(OneHourRestrictionJob.redis_key(:per_hour), 0)
|
54
76
|
result = perform_job(OneHourRestrictionJob, "any args")
|
55
77
|
result.should_not be_true
|
data/spec/spec_helper.rb
CHANGED
@@ -28,7 +28,7 @@ at_exit do
|
|
28
28
|
exit_code = Spec::Runner.run
|
29
29
|
|
30
30
|
pid = `ps -e -o pid,command | grep [r]edis-test`.split(" ")[0]
|
31
|
-
puts "Killing test redis server..."
|
31
|
+
puts "Killing test redis server [#{pid}]..."
|
32
32
|
`rm -f #{dir}/dump.rdb`
|
33
33
|
Process.kill("KILL", pid.to_i)
|
34
34
|
exit exit_code
|
@@ -66,6 +66,29 @@ class OneHourRestrictionJob < Resque::Plugins::RestrictionJob
|
|
66
66
|
end
|
67
67
|
end
|
68
68
|
|
69
|
+
class IdentifiedRestrictionJob < Resque::Plugins::RestrictionJob
|
70
|
+
restrict :per_hour => 10
|
71
|
+
|
72
|
+
@queue = 'normal'
|
73
|
+
|
74
|
+
def self.identifier(*args)
|
75
|
+
[self.to_s, args.first].join(":")
|
76
|
+
end
|
77
|
+
|
78
|
+
def self.perform(*args)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
class ConcurrentRestrictionJob < Resque::Plugins::RestrictionJob
|
83
|
+
restrict :concurrent => 1
|
84
|
+
|
85
|
+
@queue = 'normal'
|
86
|
+
|
87
|
+
def self.perform(*args)
|
88
|
+
sleep 0.2
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
69
92
|
class MultipleRestrictionJob < Resque::Plugins::RestrictionJob
|
70
93
|
restrict :per_hour => 10, :per_300 => 2
|
71
94
|
|
metadata
CHANGED
@@ -1,7 +1,12 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: resque-restriction
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 2
|
8
|
+
- 0
|
9
|
+
version: 0.2.0
|
5
10
|
platform: ruby
|
6
11
|
authors:
|
7
12
|
- Richard Huang
|
@@ -9,19 +14,23 @@ autorequire:
|
|
9
14
|
bindir: bin
|
10
15
|
cert_chain: []
|
11
16
|
|
12
|
-
date: 2010-
|
17
|
+
date: 2010-06-29 00:00:00 +08:00
|
13
18
|
default_executable:
|
14
19
|
dependencies:
|
15
20
|
- !ruby/object:Gem::Dependency
|
16
21
|
name: resque
|
17
|
-
|
18
|
-
|
19
|
-
version_requirements: !ruby/object:Gem::Requirement
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
20
24
|
requirements:
|
21
25
|
- - ">="
|
22
26
|
- !ruby/object:Gem::Version
|
27
|
+
segments:
|
28
|
+
- 1
|
29
|
+
- 7
|
30
|
+
- 0
|
23
31
|
version: 1.7.0
|
24
|
-
|
32
|
+
type: :runtime
|
33
|
+
version_requirements: *id001
|
25
34
|
description: resque-restriction is an extension to resque queue system that restricts the execution number of certain jobs in a period time, the exceeded jobs will be executed at the next period.
|
26
35
|
email: flyerhzm@gmail.com
|
27
36
|
executables: []
|
@@ -42,6 +51,7 @@ files:
|
|
42
51
|
- lib/resque-restriction/job.rb
|
43
52
|
- lib/resque-restriction/restriction_job.rb
|
44
53
|
- rails/init.rb
|
54
|
+
- resque-restriction.gemspec
|
45
55
|
- spec/redis-test.conf
|
46
56
|
- spec/resque-restriction/job_spec.rb
|
47
57
|
- spec/resque-restriction/restriction_job_spec.rb
|
@@ -60,18 +70,20 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
60
70
|
requirements:
|
61
71
|
- - ">="
|
62
72
|
- !ruby/object:Gem::Version
|
73
|
+
segments:
|
74
|
+
- 0
|
63
75
|
version: "0"
|
64
|
-
version:
|
65
76
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
66
77
|
requirements:
|
67
78
|
- - ">="
|
68
79
|
- !ruby/object:Gem::Version
|
80
|
+
segments:
|
81
|
+
- 0
|
69
82
|
version: "0"
|
70
|
-
version:
|
71
83
|
requirements: []
|
72
84
|
|
73
85
|
rubyforge_project:
|
74
|
-
rubygems_version: 1.3.
|
86
|
+
rubygems_version: 1.3.6
|
75
87
|
signing_key:
|
76
88
|
specification_version: 3
|
77
89
|
summary: resque-restriction is an extension to resque queue system that restricts the execution number of certain jobs in a period time.
|