dynamiq 0.1.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 00323db75f37fea50203c50ab9f3652f213fd9aa
4
+ data.tar.gz: b8dd9805d3f23d13bf5e84c3b7e4a057830cdd12
5
+ SHA512:
6
+ metadata.gz: 897695cde48f51f4c8ce4481d74eb0a56a2c09a590f2afcb4690cba3880485dd6f86e0ac8ae543c3ca722fbdfcb77339c8c25e64479818f1716b45460e3d85a5
7
+ data.tar.gz: b78bf75261a6db61cb6b3fe646582b3a9a2fdc73c030de0ec2f83ee27b7917f4319c37f7ef66676e3808f0298dd08ef84fb6fcfb54b4c908577445cc45e0bad4
data/.gitignore ADDED
@@ -0,0 +1,16 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
15
+ *.rdb
16
+ .DS_Store
data/.pryrc ADDED
@@ -0,0 +1,8 @@
1
+ require 'awesome_print'
2
+
3
+ Pry.config.hooks.add_hook :before_session, :load_project_lib do
4
+ dir = `pwd`.chomp
5
+ %w(lib spec test).map { |d| "#{dir}/#{d}" }.each { |p| $: << p unless !File.exist?(p) || $:.include?(p) }
6
+ require 'dynamiq'
7
+ end
8
+
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ # 0.1.0
2
+
3
+ Initial release.
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in dynamiq.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Bob Breznak
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/Procfile ADDED
@@ -0,0 +1 @@
1
+ redis: redis-server
data/README.md ADDED
@@ -0,0 +1,31 @@
1
+ # Dynamiq
2
+
3
+ A dynamic priority queue extension for [Sidekiq](https://github.com/mperham/sidekiq).
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ ```ruby
10
+ gem 'dynamiq'
11
+ ```
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install dynamiq
20
+
21
+ ## Usage
22
+
23
+ TODO: Write usage instructions here
24
+
25
+ ## Contributing
26
+
27
+ 1. Fork it ( https://github.com/[my-github-username]/dynamiq/fork )
28
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
29
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
30
+ 4. Push to the branch (`git push origin my-new-feature`)
31
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,21 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ require 'rspec/core/rake_task'
4
+
5
+ RSpec::Core::RakeTask.new :spec
6
+
7
+ task default: :spec
8
+
9
+ require 'byebug'
10
+
11
+ task :monitor do
12
+ require './lib/dynamiq'
13
+
14
+ require 'sinatra'
15
+ require './lib/dynamiq/web'
16
+ app = Dynamiq::Web
17
+ app.set :environment, :production
18
+ app.set :bind, '0.0.0.0'
19
+ app.set :port, 9494
20
+ app.run!
21
+ end
data/dynamiq.gemspec ADDED
@@ -0,0 +1,34 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'dynamiq/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'dynamiq'
8
+ spec.version = Dynamiq::VERSION
9
+ spec.authors = ['Bob Breznak']
10
+ spec.email = ['bob.breznak@gmail.com']
11
+ spec.summary = 'A dynamic priority queue extension for Sidekiq'
12
+ spec.description = spec.summary
13
+ spec.homepage = 'https://github.com/bobbrez/dynamiq'
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 'sidekiq', '>= 3.0.0'
22
+
23
+ spec.add_development_dependency 'bundler', '~> 1.7'
24
+ spec.add_development_dependency 'rake', '~> 10.0'
25
+
26
+ spec.add_development_dependency 'foreman', '~> 0.74.0'
27
+
28
+ spec.add_development_dependency 'pry', '~> 0.10.0'
29
+ spec.add_development_dependency 'pry-byebug', '~> 1.3.3'
30
+
31
+ spec.add_development_dependency 'rspec', '~> 3.0.0'
32
+ spec.add_development_dependency 'simplecov', '~> 0.9.0'
33
+ spec.add_development_dependency 'timecop', '~> 0.7.1'
34
+ end
@@ -0,0 +1,6 @@
1
+ :queues:
2
+ - default
3
+ - [dyqueue, 2]
4
+
5
+ :dynamic:
6
+ - dyqueue
@@ -0,0 +1,43 @@
1
+ require 'sidekiq'
2
+ require './lib/dynamiq'
3
+ require 'byebug'
4
+
5
+ class DefaultWorker
6
+ include Sidekiq::Worker
7
+
8
+ def perform(value)
9
+ puts "DefaultWorker"
10
+ sleep 20
11
+ puts value
12
+ end
13
+ end
14
+
15
+ class PrioritizedWorker
16
+ include Dynamiq::Worker
17
+
18
+ sidekiq_options queue: :dyqueue
19
+
20
+ def perform(value)
21
+ puts "PrioritizedWorker"
22
+ sleep 20
23
+ puts value
24
+ end
25
+ end
26
+
27
+ class BrokenWorker
28
+ include Sidekiq::Worker
29
+
30
+ def perform(value)
31
+ raise 'Some error'
32
+ end
33
+ end
34
+
35
+ class BrokenPrioritizedWorker
36
+ include Dynamiq::Worker
37
+
38
+ sidekiq_options queue: :dyqueue
39
+
40
+ def perform(value)
41
+ raise 'Some error'
42
+ end
43
+ end
data/lib/dynamiq.rb ADDED
@@ -0,0 +1,15 @@
1
+ require 'sidekiq'
2
+
3
+ require 'dynamiq/version'
4
+ require 'dynamiq/worker'
5
+ require 'dynamiq/client'
6
+ require 'dynamiq/job'
7
+ require 'dynamiq/fetcher'
8
+ require 'dynamiq/queue'
9
+
10
+ module Dynamiq
11
+ QUEUE_LIST = 'queues'
12
+ DYNAMIC_QUEUE_LIST = 'dynamic_queues'
13
+ end
14
+
15
+ Sidekiq.options[:fetch] = Dynamiq::Fetcher
@@ -0,0 +1,30 @@
1
+ module Dynamiq
2
+ class Client < Sidekiq::Client
3
+ def push_message(message)
4
+ redis_pool.with do |conn|
5
+ atomic_push conn, [ JSON.parse(message) ]
6
+ end
7
+ end
8
+
9
+ private
10
+
11
+ def atomic_push(conn, payloads)
12
+ if payloads.first['at']
13
+ payload = payloads.map do |hash|
14
+ [ hash.delete('at').to_s, Sidekiq.dump_json(hash) ]
15
+ end
16
+
17
+ conn.zadd 'schedule', payload
18
+ else
19
+ q = payloads.first['queue']
20
+ to_push = payloads.map do |entry|
21
+ [ entry.delete('score').to_i, Sidekiq.dump_json(entry) ]
22
+ end
23
+
24
+ conn.sadd :queues, q
25
+ conn.sadd :dynamic_queues, q
26
+ conn.zadd [:dynamic_queue, q].join(':'), to_push
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,24 @@
1
+ module Dynamiq
2
+ class Fetcher
3
+ def initialize(options)
4
+ @strictly_ordered_queues = !!options[:strict]
5
+ end
6
+
7
+ def retrieve_work
8
+ queues.each { |queue| job = queue.pop and return job }
9
+ end
10
+
11
+ def queues
12
+ @strictly_ordered_queues ? Queue.all : Queue.all.shuffle.uniq
13
+ end
14
+
15
+ def self.bulk_requeue(inprogress, options)
16
+ return if inprogress.empty?
17
+
18
+ inprogress.each(&:requeue)
19
+
20
+ Sidekiq.logger.debug { "Re-queueing terminated jobs" }
21
+ Sidekiq.logger.debug { "J/K NOT REALLY" }
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,89 @@
1
+ module Dynamiq
2
+ class Job
3
+ attr_reader :queue, :message, :item
4
+
5
+ def initialize(queue, message = nil)
6
+ @queue = queue
7
+ @message = message
8
+ @item = message.is_a?(Hash) ? message : Sidekiq.load_json(message)
9
+ end
10
+
11
+ def klass
12
+ @item['class']
13
+ end
14
+
15
+ def display_class
16
+ # Unwrap known wrappers so they show up in a human-friendly manner in the Web UI
17
+ @klass ||= case klass
18
+ when /\ASidekiq::Extensions::Delayed/
19
+ safe_load(args[0], klass) do |target, method, _|
20
+ "#{target}.#{method}"
21
+ end
22
+ when "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper"
23
+ args[0]
24
+ else
25
+ klass
26
+ end
27
+ end
28
+
29
+ def display_args
30
+ # Unwrap known wrappers so they show up in a human-friendly manner in the Web UI
31
+ @args ||= case klass
32
+ when /\ASidekiq::Extensions::Delayed/
33
+ safe_load(args[0], args) do |_, _, arg|
34
+ arg
35
+ end
36
+ when "ActiveJob::QueueAdapters::SidekiqAdapter::JobWrapper"
37
+ args[1..-1]
38
+ else
39
+ args
40
+ end
41
+ end
42
+
43
+ def args
44
+ @item['args']
45
+ end
46
+
47
+ def jid
48
+ @item['jid']
49
+ end
50
+
51
+ def score
52
+ @item['score']
53
+ end
54
+
55
+ def enqueued_at
56
+ Time.at(@item['enqueued_at'] || 0).utc
57
+ end
58
+
59
+ def latency
60
+ Time.now.to_f - @item['enqueued_at']
61
+ end
62
+
63
+ def acknowledge
64
+ # nothing to do
65
+ end
66
+
67
+ def [](name)
68
+ @item.__send__(:[], name)
69
+ end
70
+
71
+ def queue_name
72
+ queue.name
73
+ end
74
+
75
+ def requeue
76
+ queue.requeue message
77
+ end
78
+
79
+ def delete
80
+ deleted = Sidekiq.redis do |conn|
81
+ rem_value = @value.dup
82
+ rem_value.delete 'score'
83
+ conn.zrem [:dynamic_queue, @queue].join(':'), rem_value.to_json
84
+ end
85
+
86
+ deleted ? 1 : 0
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,141 @@
1
+ module Dynamiq
2
+ class Queue
3
+ include Enumerable
4
+
5
+ attr_reader :name, :path
6
+
7
+ REDIS_RPOPZADD = "local value = redis.call('zrange', KEYS[1], -1, -1)[1]
8
+ local score = redis.call('zscore', KEYS[1], value)
9
+ redis.call('zremrangebyrank', KEYS[1], -1, -1)
10
+ return { value, score }"
11
+
12
+ def initialize(name = 'default')
13
+ @name = name
14
+ @path = [ (dynamic? ? :dynamic_queue : :queue), name].join(':')
15
+ end
16
+
17
+ def dynamic?
18
+ @dynamic ||= Sidekiq.redis { |redis| redis.sismember :dynamic_queues, name }
19
+ end
20
+
21
+ def paused?
22
+ false
23
+ end
24
+
25
+ def requeue(message)
26
+ if dynamic?
27
+ Dynamiq::Client.new.push_message message
28
+ else
29
+ Sidekiq.redis { |redis| redis.rpush path, message }
30
+ end
31
+ end
32
+
33
+ def pop
34
+ job = pop_job and Job.new self, job
35
+ end
36
+
37
+ def each(&block)
38
+ initial_size = size
39
+ deleted_size = 0
40
+ page = 0
41
+ page_size = 50
42
+
43
+ loop do
44
+ start = page * page_size - deleted_size
45
+ entries = fetch_jobs start, page_size
46
+
47
+ break if entries.empty?
48
+
49
+ page += 1
50
+ entries.each do |entry|
51
+ block.call Job.new(*entry, @name)
52
+ end
53
+
54
+ deleted_size = initial_size - size
55
+ end
56
+ end
57
+
58
+ def latency
59
+ method = dynamic? ? :zrange : :lrange
60
+ entry = Sidekiq.redis { |redis| redis.send(method, path, -1, -1) }.first
61
+ return 0 unless entry
62
+ Time.now.to_f - Sidekiq.load_json(entry)['enqueued_at']
63
+ end
64
+
65
+ def clear
66
+ index_list = dynamic? ? :dynamic_queues : :queues
67
+
68
+ Sidekiq.redis do |redis|
69
+ redis.multi do
70
+ redis.del path
71
+ redis.srem index_list, name
72
+ end
73
+ end
74
+ end
75
+ alias_method :💣, :clear
76
+
77
+ def size
78
+ if dynamic?
79
+ Sidekiq.redis { |redis| redis.zcount path, '-inf', '+inf' }
80
+ else
81
+ Sidekiq.redis { |redis| redis.llen path }
82
+ end
83
+ end
84
+
85
+ def eql?(obj)
86
+ return false unless obj.respond_to? :name
87
+
88
+ name.eql? obj.name
89
+ end
90
+
91
+ def hash
92
+ name.hash
93
+ end
94
+
95
+ def ==(object)
96
+ object.respond_to?(:name) and @name == object.name
97
+ end
98
+
99
+ def self.dynamic
100
+ Sidekiq.redis { |redis| redis.smembers :dynamic_queues }
101
+ .sort
102
+ .map { |queue| Queue.new queue }
103
+ end
104
+
105
+ def self.fifo
106
+ queues = Sidekiq.redis { |redis| redis.smembers :queues }
107
+ dynamic = Sidekiq.redis { |redis| redis.smembers :dynamic_queues }
108
+ (queues - dynamic).sort.map { |queue| Queue.new queue }
109
+ end
110
+
111
+
112
+ def self.all
113
+ Sidekiq.redis { |redis| redis.smembers 'queues' }
114
+ .sort
115
+ .map { |queue| Queue.new queue }
116
+ end
117
+
118
+ private
119
+
120
+ def fetch_jobs(start, page_size)
121
+ Sidekiq.redis do |redis|
122
+ if dynamic?
123
+ limit = [start, page_size]
124
+ redis.zrangebyscore @rname, '-inf', '+inf', limit: limit, with_scores: true
125
+ else
126
+ redis.lrange @rname, start, (start + page_size - 1)
127
+ end
128
+ end
129
+ end
130
+
131
+ def pop_job
132
+ if dynamic?
133
+ job = Sidekiq.redis { |redis| redis.eval REDIS_RPOPZADD, [ path ] }
134
+ return nil if job.empty?
135
+ JSON.parse(job.first).merge(score: job.last.to_f).to_json
136
+ else
137
+ Sidekiq.redis { |redis| redis.rpop path }
138
+ end
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,7 @@
1
+ module Dynamiq
2
+ class Stats < Sidekiq::Stats
3
+ def queues
4
+ Hash[ Queue.all.map { |queue| [queue.name, queue.size] } ]
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,3 @@
1
+ module Dynamiq
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,61 @@
1
+ require 'sidekiq/web'
2
+
3
+ require 'dynamiq/web_helpers'
4
+ require 'dynamiq/stats'
5
+
6
+ module Dynamiq
7
+ class Web < Sidekiq::Web
8
+ helpers WebHelpers
9
+
10
+ get "/queues" do
11
+ @queues = Dynamiq::Queue.all
12
+ erb :queues
13
+ end
14
+
15
+ get "/queues/:name" do
16
+ halt 404 unless params[:name]
17
+ @count = (params[:count] || 25).to_i
18
+ @name = params[:name]
19
+ @queue = Dynamiq::Queue.new @name
20
+ (@current_page, @total_size, @messages) = page(@queue.path, params[:page], @count)
21
+ @messages = @messages.map { |msg|
22
+ Dynamiq::Job.new @queue, (msg.respond_to?(:first) ? msg.first : msg)
23
+ }
24
+ @queue.size
25
+ erb :queue
26
+ end
27
+
28
+ post "/queues/:name" do
29
+ Dynamiq::Queue.new(params[:name]).clear
30
+ redirect "#{root_path}queues"
31
+ end
32
+
33
+ post "/queues/:name/delete" do
34
+ byebug
35
+ Dynamiq::Job.new(params[:key_val], params[:name]).delete
36
+ redirect_with_query("#{root_path}queues/#{params[:name]}")
37
+ end
38
+
39
+ get '/dashboard/stats' do
40
+ sidekiq_stats = Dynamiq::Stats.new
41
+ queue = Dynamiq::Queue.new
42
+ redis_stats = redis_info.select{ |k, v| REDIS_KEYS.include? k }
43
+
44
+ content_type :json
45
+ Sidekiq.dump_json({
46
+ sidekiq: {
47
+ processed: sidekiq_stats.processed,
48
+ failed: sidekiq_stats.failed,
49
+ busy: workers_size,
50
+ enqueued: sidekiq_stats.enqueued,
51
+ scheduled: sidekiq_stats.scheduled_size,
52
+ retries: sidekiq_stats.retry_size,
53
+ dead: sidekiq_stats.dead_size,
54
+ default_latency: queue.latency,
55
+ },
56
+ redis: redis_stats
57
+ })
58
+ end
59
+ end
60
+ end
61
+
@@ -0,0 +1,7 @@
1
+ module Dynamiq
2
+ module WebHelpers
3
+ def stats
4
+ @stats ||= Dynamiq::Stats.new
5
+ end
6
+ end
7
+ end
@@ -0,0 +1,40 @@
1
+ module Dynamiq
2
+ module Worker
3
+ attr_accessor :jid
4
+
5
+ def self.included(base)
6
+ base.class.include Sidekiq::Worker
7
+ base.extend Sidekiq::Worker::ClassMethods
8
+ base.extend ClassMethods
9
+
10
+ base.class_attribute :sidekiq_options_hash
11
+ base.class_attribute :sidekiq_retry_in_block
12
+ base.class_attribute :sidekiq_retries_exhausted_block
13
+ end
14
+
15
+ module ClassMethods
16
+ def perform_async(score, *args)
17
+ client_push score: score, class: self, args: args
18
+ end
19
+
20
+ def perform_in(interval, score, *args)
21
+ int = interval.to_f
22
+ now = Time.now.to_f
23
+ ts = (int < 1_000_000_000 ? now + int : int)
24
+
25
+ item = { score: score, class: self, args: args, at: ts }
26
+
27
+ # Optimization to enqueue something now that is scheduled to go out now or in the past
28
+ item.delete 'at' if ts <= now
29
+
30
+ client_push item
31
+ end
32
+ alias_method :perform_at, :perform_in
33
+
34
+ def client_push(item) # :nodoc:
35
+ pool = Thread.current[:sidekiq_via_pool] || get_sidekiq_options['pool'] || Sidekiq.redis_pool
36
+ Dynamiq::Client.new(pool).push(item.stringify_keys)
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,67 @@
1
+ require 'spec_helper'
2
+
3
+ describe Dynamiq::Queue do
4
+ before(:each) { Sidekiq.redis { |redis| redis.flushall } }
5
+
6
+ context '#size' do
7
+ it "returns 0 if the queue doesn't exist" do
8
+ expect(Dynamiq::Queue.new.size).to eq 0
9
+ end
10
+
11
+ it 'returns the number of items in the queue' do
12
+ count = Random.rand 50
13
+ count.times { redis_queue_job 'foo', 0 }
14
+ expect(Dynamiq::Queue.new('foo').size).to eq count
15
+ end
16
+ end
17
+
18
+ context '#each' do
19
+ it 'loops through each queued job' do
20
+ 200.times { |i| redis_queue_job 'foo', i, index: i }
21
+
22
+ queue = Dynamiq::Queue.new 'foo'
23
+
24
+ current_score = 0
25
+ queue.each do |job|
26
+ expect(job.score).to eq current_score
27
+ current_score += 1
28
+ end
29
+ end
30
+ end
31
+
32
+ context '#clear' do
33
+ it 'clears the items in the queue and removes it from the list of queues' do
34
+ 200.times { |i| redis_queue_job 'foo', i, index: i }
35
+
36
+ queue = Dynamiq::Queue.new 'foo'
37
+ expect(queue.size).to eq 200
38
+
39
+ queue.clear
40
+
41
+ expect(queue.size).to eq 0
42
+ expect(Dynamiq::Queue.all).not_to include(Dynamiq::Queue.new('foo'))
43
+ end
44
+ end
45
+
46
+ context '#==' do
47
+ it 'is true when the queus have the same name' do
48
+ q1, q2 = Dynamiq::Queue.new('foo'), Dynamiq::Queue.new('foo')
49
+ expect(q1 == q2).to be true
50
+ end
51
+
52
+ it 'is false when the queues have different names' do
53
+ q1, q2 = Dynamiq::Queue.new('foo'), Dynamiq::Queue.new('bar')
54
+ expect(q1 == q2).to be false
55
+ end
56
+ end
57
+
58
+ context '.all' do
59
+ it 'returns all dynamic queues' do
60
+ queues = %w(foo bar biz)
61
+
62
+ redis_add_queues queues
63
+
64
+ expect(Dynamiq::Queue.all).to eq(queues.sort.map { |q| Dynamiq::Queue.new q })
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,48 @@
1
+ require 'spec_helper'
2
+
3
+ describe Dynamiq::Worker do
4
+ before(:each) { Sidekiq.redis { |redis| redis.flushall }}
5
+
6
+ class SimpleWorker
7
+ include Dynamiq::Worker
8
+ end
9
+
10
+ subject { SimpleWorker }
11
+
12
+ context '#perform_async' do
13
+ it 'queues the job with a numeric priority' do
14
+ queue = Dynamiq::Queue.new
15
+
16
+ expect(queue.size).to eq 0
17
+
18
+ subject.perform_async 10, arg1: 'arg1'
19
+
20
+ expect(queue.size).to eq 1
21
+
22
+ job = queue.first.item.slice('queue', 'score', 'args')
23
+ expect(job).to eq({ 'queue' => 'default',
24
+ 'score' => 10,
25
+ 'args' => [{ 'arg1' => 'arg1' }]})
26
+ end
27
+ end
28
+
29
+ context '#perform_in' do
30
+ it 'queues the job for a later time' do
31
+ Timecop.freeze do
32
+ perform_at = 86_400
33
+ subject.perform_in perform_at, 10, arg1: 'arg1'
34
+
35
+ scheduled = Sidekiq::ScheduledSet.new
36
+ expect(scheduled.size).to eq 1
37
+
38
+ exec_at = scheduled.first.score
39
+ expect(exec_at).to eq (Time.now + perform_at).to_f
40
+
41
+ job = scheduled.first.item.slice('queue', 'score', 'args')
42
+ expect(job).to eq({ 'queue' => 'default',
43
+ 'score' => 10,
44
+ 'args' => [{ 'arg1' => 'arg1' }]})
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,17 @@
1
+ $:.unshift File.expand_path('..', __FILE__)
2
+ $:.unshift File.expand_path('../../lib', __FILE__)
3
+
4
+ require 'pry'
5
+ require 'pry-byebug'
6
+ require 'timecop'
7
+
8
+ Dir[File.join './spec/support/**/*.rb'].each { |f| require f }
9
+
10
+ require File.expand_path('../../lib/dynamiq', __FILE__)
11
+
12
+ RSpec.configure do |config|
13
+ config.profile_examples = 10
14
+
15
+ Kernel.srand config.seed
16
+ config.order = :random
17
+ end
@@ -0,0 +1,3 @@
1
+ require 'simplecov'
2
+
3
+ SimpleCov.start
@@ -0,0 +1,16 @@
1
+ def redis_add_queues(queues)
2
+ Sidekiq.redis do |redis|
3
+ queues.each { |q| redis.sadd Dynamiq::QUEUE_LIST, q }
4
+ end
5
+ end
6
+
7
+ def redis_queue_job(dynamic_queue, score, args = nil)
8
+ payload = { retry: true, class: "Foo", jid: SecureRandom.hex, enqueued_at: Time.now.to_f }
9
+ payload.merge! queue: dynamic_queue, score: score, args: args
10
+
11
+ Sidekiq.redis do |redis|
12
+ redis.zadd [Dynamiq::QUEUE_LIST, dynamic_queue].join(':'), score, payload.to_json
13
+ end
14
+
15
+ payload
16
+ end
@@ -0,0 +1,4 @@
1
+ require 'sidekiq'
2
+
3
+ Sidekiq.logger = nil
4
+ Sidekiq.redis = { namespace: 'test' }
metadata ADDED
@@ -0,0 +1,205 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: dynamiq
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Bob Breznak
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-10-31 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: sidekiq
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 3.0.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 3.0.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.7'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.7'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '10.0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '10.0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: foreman
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 0.74.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 0.74.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: pry
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 0.10.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.10.0
83
+ - !ruby/object:Gem::Dependency
84
+ name: pry-byebug
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 1.3.3
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 1.3.3
97
+ - !ruby/object:Gem::Dependency
98
+ name: rspec
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 3.0.0
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 3.0.0
111
+ - !ruby/object:Gem::Dependency
112
+ name: simplecov
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: 0.9.0
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: 0.9.0
125
+ - !ruby/object:Gem::Dependency
126
+ name: timecop
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: 0.7.1
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: 0.7.1
139
+ description: A dynamic priority queue extension for Sidekiq
140
+ email:
141
+ - bob.breznak@gmail.com
142
+ executables: []
143
+ extensions: []
144
+ extra_rdoc_files: []
145
+ files:
146
+ - ".gitignore"
147
+ - ".pryrc"
148
+ - ".rspec"
149
+ - CHANGELOG.md
150
+ - Gemfile
151
+ - LICENSE.txt
152
+ - Procfile
153
+ - README.md
154
+ - Rakefile
155
+ - dynamiq.gemspec
156
+ - example/config.yml
157
+ - example/sidekiq.rb
158
+ - lib/dynamiq.rb
159
+ - lib/dynamiq/client.rb
160
+ - lib/dynamiq/fetcher.rb
161
+ - lib/dynamiq/job.rb
162
+ - lib/dynamiq/queue.rb
163
+ - lib/dynamiq/stats.rb
164
+ - lib/dynamiq/version.rb
165
+ - lib/dynamiq/web.rb
166
+ - lib/dynamiq/web_helpers.rb
167
+ - lib/dynamiq/worker.rb
168
+ - spec/lib/dynamiq/queue_spec.rb
169
+ - spec/lib/dynamiq/worker_spec.rb
170
+ - spec/spec_helper.rb
171
+ - spec/support/coverage.rb
172
+ - spec/support/redis.rb
173
+ - spec/support/sidekiq.rb
174
+ homepage: https://github.com/bobbrez/dynamiq
175
+ licenses:
176
+ - MIT
177
+ metadata: {}
178
+ post_install_message:
179
+ rdoc_options: []
180
+ require_paths:
181
+ - lib
182
+ required_ruby_version: !ruby/object:Gem::Requirement
183
+ requirements:
184
+ - - ">="
185
+ - !ruby/object:Gem::Version
186
+ version: '0'
187
+ required_rubygems_version: !ruby/object:Gem::Requirement
188
+ requirements:
189
+ - - ">="
190
+ - !ruby/object:Gem::Version
191
+ version: '0'
192
+ requirements: []
193
+ rubyforge_project:
194
+ rubygems_version: 2.2.2
195
+ signing_key:
196
+ specification_version: 4
197
+ summary: A dynamic priority queue extension for Sidekiq
198
+ test_files:
199
+ - spec/lib/dynamiq/queue_spec.rb
200
+ - spec/lib/dynamiq/worker_spec.rb
201
+ - spec/spec_helper.rb
202
+ - spec/support/coverage.rb
203
+ - spec/support/redis.rb
204
+ - spec/support/sidekiq.rb
205
+ has_rdoc: