resque-restriction 0.1.0 → 0.2.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.
- 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.
|