resque_sliding_window 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,17 @@
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
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in resque_sliding_window.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Jon Phenow
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.
@@ -0,0 +1,70 @@
1
+ # ResqueSlidingWindow
2
+
3
+ Want a sliding window for debouncing bursty Resque events? Here it is.
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'resque_sliding_window'
11
+ gem 'resque-scheduler', require: 'resque_scheduler', git: "https://github.com/jonhyman/resque-scheduler.git" # prefered repo
12
+ ```
13
+
14
+ And then execute:
15
+
16
+ ```bash
17
+ $ bundle
18
+ ```
19
+
20
+ ## Usage
21
+
22
+ ```ruby
23
+ module Jobs
24
+ class Publish
25
+ extend Resque::Plugins::SlidingWindow
26
+
27
+ @queue = :low
28
+
29
+ def self.perform(id)
30
+ # Crazy stuff
31
+ end
32
+ end
33
+ end
34
+
35
+ Resque.enqueue_in(30, Jobs::Publish, id)
36
+ ```
37
+
38
+ This will put a job in a delayed queue for 30 seconds. If a similar job is entered (same args, same klass),
39
+ it will be deleted in favor of the new 30-second job. However, it can only be
40
+ pushed off for a 1m 30s from its initial queue-time before it force runs one. When it forces
41
+ one through, others will not be run.
42
+
43
+ Default `max_time` is 1m and 30s. To change this:
44
+
45
+ ```ruby
46
+ module Jobs
47
+ class Publish
48
+ extend Resque::Plugins::SlidingWindow
49
+
50
+ @queue = :low
51
+
52
+ def self.max_time
53
+ 30.minutes
54
+ end
55
+
56
+ def self.perform(id)
57
+ # Crazy stuff
58
+ end
59
+ end
60
+ end
61
+ ```
62
+
63
+
64
+ ## Contributing
65
+
66
+ 1. Fork it
67
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
68
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
69
+ 4. Push to the branch (`git push origin my-new-feature`)
70
+ 5. Create new Pull Request
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,13 @@
1
+ module Resque
2
+ module Plugins
3
+ module SlidingWindow
4
+ def before_schedule_sliding_window(*args)
5
+ ResqueSlidingWindow::WindowSlider.new(self, args).slide
6
+ end
7
+
8
+ def after_perform_sliding_window(*args)
9
+ ResqueSlidingWindow::WindowSlider.new(self, args).close
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,4 @@
1
+ require "resque_sliding_window/version"
2
+ require "resque_sliding_window/window_slider"
3
+ require "resque_sliding_window/resque_extensions"
4
+ require "resque/plugins/sliding_window"
@@ -0,0 +1,35 @@
1
+ require 'resque'
2
+ require 'resque_scheduler'
3
+ require 'resque/scheduler'
4
+ module ResqueSlidingWindow
5
+ module SchedulerPatches
6
+ def self.extended(base)
7
+ class << base
8
+ alias_method :delayed_timestamp_peek_without_rescue, :delayed_timestamp_peek
9
+ alias_method :delayed_timestamp_peek, :delayed_timestamp_peek_with_rescue
10
+ end
11
+ end
12
+
13
+ def next_item_for_timestamp(timestamp)
14
+ key = "delayed:#{timestamp.to_i}"
15
+
16
+ item = patched_decode redis.lpop(key)
17
+
18
+ # If the list is empty, remove it.
19
+ clean_up_timestamp(key, timestamp)
20
+ item
21
+ end
22
+
23
+ def delayed_timestamp_peek_with_rescue(timestamp, start, count)
24
+ delayed_timestamp_peek_without_rescue timestamp, start, count
25
+ rescue Resque::Helpers::DecodeException => e
26
+ []
27
+ end
28
+
29
+ def patched_decode(payload)
30
+ decode payload
31
+ rescue Exception => e
32
+ end
33
+ end
34
+ end
35
+ Resque.extend ResqueSlidingWindow::SchedulerPatches
@@ -0,0 +1,3 @@
1
+ module ResqueSlidingWindow
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,119 @@
1
+ module ResqueSlidingWindow
2
+ class WindowSlider
3
+ extend Forwardable
4
+
5
+ attr_accessor :klass
6
+ attr_accessor :args
7
+
8
+ def_delegators :Resque, :job_to_hash, :encode, :redis, :remove_delayed_job_from_timestamp
9
+
10
+ def initialize(klass, args)
11
+ self.klass = klass
12
+ self.args = args
13
+ end
14
+
15
+ def slide
16
+ if first?
17
+ mark_first
18
+ elsif should_requeue?
19
+ requeue
20
+ else
21
+ false
22
+ end
23
+ end
24
+
25
+ def close
26
+ redis.del(slide_key)
27
+ end
28
+
29
+ private
30
+
31
+ def first?
32
+ !on_queue?
33
+ end
34
+
35
+ def mark_first
36
+ mark_time
37
+ end
38
+
39
+ def requeue
40
+ matching_delayed.each do |key, _|
41
+ remove_delayed_job_from_timestamp(key_to_timestamp(key), klass, *args)
42
+ end
43
+ true
44
+ end
45
+
46
+ def should_requeue?
47
+ !maxed_out?
48
+ end
49
+
50
+ def matching_delayed
51
+ delayed_key_values.select { |_, value| value == search }
52
+ end
53
+
54
+ def search
55
+ encode(job_to_hash klass, args)
56
+ end
57
+
58
+ def on_queue?
59
+ matching_delayed.count > 0
60
+ end
61
+
62
+ def max_time
63
+ klass.respond_to?(:max_time) ? klass.public_send(:max_time) : 1.minute
64
+ end
65
+
66
+ def maxed_out?
67
+ (redis.get(slide_key).to_i + max_time) <= Time.now.utc.to_i
68
+ end
69
+
70
+ def slide_key
71
+ "sliding:#{klass.name}:#{keyify(args)}:count"
72
+ end
73
+
74
+ def mark_time
75
+ redis.set(slide_key, Time.now.utc.to_i)
76
+ end
77
+
78
+ def keyify(args)
79
+ "(#{args.map { |arg|
80
+ if arg.is_a?(Array)
81
+ keyify(arg)
82
+ elsif arg.respond_to?(:to_a) && !(arg.is_a?(String) || arg.is_a?(Symbol) || arg.is_a?(Numeric))
83
+ keyify(arg.to_a)
84
+ else
85
+ if arg.respond_to?(:to_key)
86
+ arg.to_key
87
+ else
88
+ arg.to_s
89
+ end
90
+ end
91
+ }.join(",").gsub(/\s/,"")})"
92
+ end
93
+
94
+ def key_to_timestamp(key)
95
+ key.to_s.gsub(/delayed:/, "").to_i
96
+ end
97
+
98
+ def timestamp_to_key(timestamp)
99
+ "delayed:#{timestamp}"
100
+ end
101
+
102
+ def delayed_timestamps
103
+ redis.zrange(:delayed_queue_schedule, 0,-1)
104
+ end
105
+
106
+ def get(key)
107
+ redis.rpop(key).tap { |val|
108
+ redis.rpush(key, val)
109
+ }
110
+ end
111
+
112
+ def delayed_key_values
113
+ delayed_timestamps.inject({}) do |hash, timestamp|
114
+ hash[timestamp_to_key(timestamp)] = get(timestamp_to_key(timestamp))
115
+ hash
116
+ end
117
+ end
118
+ end
119
+ end
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'resque_sliding_window/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "resque_sliding_window"
8
+ spec.version = ResqueSlidingWindow::VERSION
9
+ spec.authors = ["Jon Phenow"]
10
+ spec.email = ["j.phenow@gmail.com"]
11
+ spec.description = %q{Sliding Window unique-job workflow for Resque jobs}
12
+ spec.summary = %q{Sliding Window unique-job workflow for Resque jobs}
13
+ spec.homepage = "https://github.com/sportngin/resque_sliding_window"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
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_development_dependency "bundler", "~> 1.3"
22
+ spec.add_development_dependency "rake"
23
+
24
+ spec.add_dependency 'resque', '~> 1.21'
25
+ end
metadata ADDED
@@ -0,0 +1,111 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: resque_sliding_window
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Jon Phenow
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-06-11 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ type: :development
17
+ requirement: !ruby/object:Gem::Requirement
18
+ none: false
19
+ requirements:
20
+ - - ~>
21
+ - !ruby/object:Gem::Version
22
+ version: '1.3'
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ~>
27
+ - !ruby/object:Gem::Version
28
+ version: '1.3'
29
+ prerelease: false
30
+ - !ruby/object:Gem::Dependency
31
+ name: rake
32
+ type: :development
33
+ requirement: !ruby/object:Gem::Requirement
34
+ none: false
35
+ requirements:
36
+ - - ! '>='
37
+ - !ruby/object:Gem::Version
38
+ version: '0'
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ none: false
41
+ requirements:
42
+ - - ! '>='
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ prerelease: false
46
+ - !ruby/object:Gem::Dependency
47
+ name: resque
48
+ type: :runtime
49
+ requirement: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '1.21'
55
+ version_requirements: !ruby/object:Gem::Requirement
56
+ none: false
57
+ requirements:
58
+ - - ~>
59
+ - !ruby/object:Gem::Version
60
+ version: '1.21'
61
+ prerelease: false
62
+ description: Sliding Window unique-job workflow for Resque jobs
63
+ email:
64
+ - j.phenow@gmail.com
65
+ executables: []
66
+ extensions: []
67
+ extra_rdoc_files: []
68
+ files:
69
+ - .gitignore
70
+ - Gemfile
71
+ - LICENSE.txt
72
+ - README.md
73
+ - Rakefile
74
+ - lib/resque/plugins/sliding_window.rb
75
+ - lib/resque_sliding_window.rb
76
+ - lib/resque_sliding_window/resque_extensions.rb
77
+ - lib/resque_sliding_window/version.rb
78
+ - lib/resque_sliding_window/window_slider.rb
79
+ - resque_sliding_window.gemspec
80
+ homepage: https://github.com/sportngin/resque_sliding_window
81
+ licenses:
82
+ - MIT
83
+ post_install_message:
84
+ rdoc_options: []
85
+ require_paths:
86
+ - lib
87
+ required_ruby_version: !ruby/object:Gem::Requirement
88
+ none: false
89
+ requirements:
90
+ - - ! '>='
91
+ - !ruby/object:Gem::Version
92
+ segments:
93
+ - 0
94
+ version: '0'
95
+ hash: -1390516187519382713
96
+ required_rubygems_version: !ruby/object:Gem::Requirement
97
+ none: false
98
+ requirements:
99
+ - - ! '>='
100
+ - !ruby/object:Gem::Version
101
+ segments:
102
+ - 0
103
+ version: '0'
104
+ hash: -1390516187519382713
105
+ requirements: []
106
+ rubyforge_project:
107
+ rubygems_version: 1.8.24
108
+ signing_key:
109
+ specification_version: 3
110
+ summary: Sliding Window unique-job workflow for Resque jobs
111
+ test_files: []