roundhouse-x 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +39 -8
- data/lib/roundhouse.rb +11 -3
- data/lib/roundhouse/api.rb +1 -1
- data/lib/roundhouse/cli.rb +10 -32
- data/lib/roundhouse/fetch.rb +4 -2
- data/lib/roundhouse/launcher.rb +1 -1
- data/lib/roundhouse/monitor.rb +137 -36
- data/lib/roundhouse/processor.rb +2 -3
- data/lib/roundhouse/scheduled.rb +1 -0
- data/lib/roundhouse/script.rb +49 -0
- data/lib/roundhouse/version.rb +1 -1
- data/lib/roundhouse/web.rb +13 -10
- data/roundhouse.gemspec +1 -0
- data/test/test_cli.rb +1 -95
- data/test/test_fetch.rb +13 -45
- data/test/test_monitor.rb +307 -28
- data/test/test_processor.rb +2 -2
- data/web/assets/javascripts/dashboard.js +5 -0
- data/web/views/_summary.erb +20 -0
- data/web/views/busy.erb +0 -2
- metadata +17 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e419459121a0e984bdeb39afef3442ff962a37a9
|
4
|
+
data.tar.gz: 8781d7c6f13f23bdc40c2a30311ac73d26476b07
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5648e6e5e86ab1e55b5c5e1a6e6f3eb2f660ed7013dc67b9e6b6ebf9910ebf9e2f2b2a5f375b94528c44c9b54d61d4b61e2a3c57510211e1d7aba38e2054ac1b
|
7
|
+
data.tar.gz: 0b8591db28a22914c1eb5e1c0ca65fc90988e6f3b91ac623f07c1c2cca86453f03396112696bce5b0ff64fb1d13592e061266407d30909114aa98258a210f63f
|
data/README.md
CHANGED
@@ -1,23 +1,30 @@
|
|
1
1
|
Roundhouse
|
2
2
|
==============
|
3
3
|
|
4
|
-
Roundhouse is based on Sidekiq 3.5.0 (HEAD: f8ee0896076671b73739099e3c1eb7d8814e407d)
|
4
|
+
Roundhouse is based on Sidekiq 3.5.0 (`HEAD: f8ee0896076671b73739099e3c1eb7d8814e407d`). It
|
5
|
+
dispatches jobs in a very specialized way for a very narrow set of problems. For example,
|
6
|
+
if you don't need to manage a large number of rate-limited access to an external resource,
|
7
|
+
you probably want to use Sidekiq and not Roundhouse.
|
5
8
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
+
The problem Roundhouse solves resembles that of a load balancing problem: only one worker
|
10
|
+
should be working on a work for a given user. Any other worker can work on any other user's
|
11
|
+
work, as only as no other worker is working on it. We also want to do this fairly so that every
|
12
|
+
user has a chance at getting work done steadily. This requires:
|
9
13
|
|
10
|
-
1. A queue for each user
|
11
|
-
2. A
|
12
|
-
3. A way to track
|
14
|
+
1. A queue for each user. (Which often means a lot of queues)
|
15
|
+
2. A turntable semaphore to control access to queues in a round-robin fashion
|
16
|
+
3. A way to track the status of each queue (active, empty, suspended) and when to push a queue back into the turntable
|
13
17
|
|
14
18
|
Other things Roundhouse should be able to do:
|
15
19
|
|
16
|
-
1. It should be able to run side-by side with Sidekiq client
|
20
|
+
1. It should be able to run side-by side with Sidekiq client.
|
17
21
|
2. Be able to suspend, resume, and defer work
|
18
22
|
3. Metrics to measure lead times and give estimates on when work might be completed
|
19
23
|
4. Coordinate across a number of worker processes, across machines
|
20
24
|
|
25
|
+
Roundhouse is in pre-alpha right now, designed for a specialized need we have. You probably don't want to
|
26
|
+
try this in production.
|
27
|
+
|
21
28
|
Install
|
22
29
|
----------------
|
23
30
|
|
@@ -40,6 +47,30 @@ end
|
|
40
47
|
APIWorker.perform_async(api_token.id, item.id)
|
41
48
|
```
|
42
49
|
|
50
|
+
Why the name 'Roundhouse'?
|
51
|
+
--------------------------
|
52
|
+
|
53
|
+
The name 'Sidekiq' is obviously a play on the word, 'sidekick', both to mean a companion that
|
54
|
+
helps a hero out in the background, and the name of a martial arts kicking technique. It
|
55
|
+
is a great name for a background processing manager that works well with Rails apps.
|
56
|
+
|
57
|
+
When Roundhouse was first concieved, it was meant as a play on the word 'sidekick'. Since Roundhouse
|
58
|
+
controls access to a queue in a round-robin fashion, I thought of a 'roundhouse kick'. Later, while
|
59
|
+
implementing the code, I found out that a 'roundhouse' is an old term in the railway industry.
|
60
|
+
In the old days, you needed a turntable to move trains around, or to reverse their direction.
|
61
|
+
Although in modern times, 'Roundhouse' has come to mean the general facilities to maintain
|
62
|
+
trains, this idea of a turntable at the center of a roundhouse is a powerful metaphor for the way
|
63
|
+
Roundhouse dispatches work to background workers.
|
64
|
+
|
65
|
+
The structure that actually controls access is neither a counting or a binary semaphore. I
|
66
|
+
had originally thought to call it a "queing semaphore' but that confuses a lot of people. The
|
67
|
+
term 'turntable semaphore' better describes this.
|
68
|
+
|
69
|
+
Unfortunately, someone had written a Ruby gem several years ago called RoundhousE. I have no
|
70
|
+
idea what that project does, other than that it has something to do with Ruby and .NET. So for
|
71
|
+
now, this gem is available as `roundhouse-x`. (Yes, that is quite a creative name).
|
72
|
+
|
73
|
+
|
43
74
|
Sidekiq License
|
44
75
|
-----------------
|
45
76
|
|
data/lib/roundhouse.rb
CHANGED
@@ -14,7 +14,6 @@ module Roundhouse
|
|
14
14
|
LICENSE = 'See LICENSE and the LGPL-3.0 for licensing details.'
|
15
15
|
|
16
16
|
DEFAULTS = {
|
17
|
-
queues: [],
|
18
17
|
labels: [],
|
19
18
|
concurrency: 25,
|
20
19
|
require: '.',
|
@@ -33,8 +32,7 @@ module Roundhouse
|
|
33
32
|
}
|
34
33
|
|
35
34
|
DEFAULT_WORKER_OPTIONS = {
|
36
|
-
'retry' => true
|
37
|
-
'queue' => 'default'
|
35
|
+
'retry' => true
|
38
36
|
}
|
39
37
|
|
40
38
|
def self.❨╯°□°❩╯︵┻━┻
|
@@ -93,6 +91,16 @@ module Roundhouse
|
|
93
91
|
end
|
94
92
|
end
|
95
93
|
|
94
|
+
# Suspends a queue
|
95
|
+
def self.suspend_queue(queue_id)
|
96
|
+
self.redis { |conn| Roundhouse::Monitor.suspend(conn, queue_id) }
|
97
|
+
end
|
98
|
+
|
99
|
+
# Resumes a queue
|
100
|
+
def self.resume_queue(queue_id)
|
101
|
+
self.redis { |conn| Roundhouse::Monitor.resume(conn, queue_id) }
|
102
|
+
end
|
103
|
+
|
96
104
|
def self.client_middleware
|
97
105
|
@client_chain ||= Middleware::Chain.new
|
98
106
|
yield @client_chain if block_given?
|
data/lib/roundhouse/api.rb
CHANGED
@@ -62,7 +62,7 @@ module Roundhouse
|
|
62
62
|
conn.zcard('retry'.freeze)
|
63
63
|
conn.zcard('dead'.freeze)
|
64
64
|
conn.scard('processes'.freeze)
|
65
|
-
conn.llen(
|
65
|
+
conn.llen(Roundhouse::Monitor::TURNTABLE.freeze)
|
66
66
|
conn.smembers('processes'.freeze)
|
67
67
|
conn.smembers(Roundhouse::Monitor::BUCKETS)
|
68
68
|
end
|
data/lib/roundhouse/cli.rb
CHANGED
@@ -64,7 +64,6 @@ module Roundhouse
|
|
64
64
|
|
65
65
|
logger.info "Running in #{RUBY_DESCRIPTION}"
|
66
66
|
logger.info Roundhouse::LICENSE
|
67
|
-
logger.info "Upgrade to Roundhouse Pro for more features and support: http://roundhouse.org" unless defined?(::Roundhouse::Pro)
|
68
67
|
|
69
68
|
fire_event(:startup)
|
70
69
|
|
@@ -102,18 +101,16 @@ module Roundhouse
|
|
102
101
|
end
|
103
102
|
|
104
103
|
def self.banner
|
105
|
-
%q{
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
sss
|
116
|
-
sss }
|
104
|
+
%q{
|
105
|
+
______ _ _
|
106
|
+
| ___ \ | | |
|
107
|
+
| |_/ /___ _ _ _ __ __| | |__ ___ _ _ ___ ___
|
108
|
+
| // _ \| | | | '_ \ / _` | '_ \ / _ \| | | / __|/ _ \
|
109
|
+
| |\ \ (_) | |_| | | | | (_| | | | | (_) | |_| \__ \ __/
|
110
|
+
\_| \_\___/ \__,_|_| |_|\__,_|_| |_|\___/ \__,_|___/\___|
|
111
|
+
|
112
|
+
|
113
|
+
}
|
117
114
|
end
|
118
115
|
|
119
116
|
def handle_signal(sig)
|
@@ -262,8 +259,6 @@ module Roundhouse
|
|
262
259
|
end
|
263
260
|
|
264
261
|
def validate!
|
265
|
-
options[:queues] << 'default' if options[:queues].empty?
|
266
|
-
|
267
262
|
if !File.exist?(options[:require]) ||
|
268
263
|
(File.directory?(options[:require]) && !File.exist?("#{options[:require]}/config/application.rb"))
|
269
264
|
logger.info "=================================================================="
|
@@ -303,11 +298,6 @@ module Roundhouse
|
|
303
298
|
opts[:index] = Integer(arg.match(/\d+/)[0])
|
304
299
|
end
|
305
300
|
|
306
|
-
o.on "-q", "--queue QUEUE[,WEIGHT]", "Queues to process with optional weights" do |arg|
|
307
|
-
queue, weight = arg.split(",")
|
308
|
-
parse_queue opts, queue, weight
|
309
|
-
end
|
310
|
-
|
311
301
|
o.on '-r', '--require [PATH|DIR]', "Location of Rails application with workers or file to require" do |arg|
|
312
302
|
opts[:require] = arg
|
313
303
|
end
|
@@ -368,7 +358,6 @@ module Roundhouse
|
|
368
358
|
if File.exist?(cfile)
|
369
359
|
opts = YAML.load(ERB.new(IO.read(cfile)).result) || opts
|
370
360
|
opts = opts.merge(opts.delete(environment) || {})
|
371
|
-
parse_queues(opts, opts.delete(:queues) || [])
|
372
361
|
else
|
373
362
|
# allow a non-existent config file so Roundhouse
|
374
363
|
# can be deployed by cap with just the defaults.
|
@@ -381,16 +370,5 @@ module Roundhouse
|
|
381
370
|
end
|
382
371
|
opts
|
383
372
|
end
|
384
|
-
|
385
|
-
def parse_queues(opts, queues_and_weights)
|
386
|
-
queues_and_weights.each { |queue_and_weight| parse_queue(opts, *queue_and_weight) }
|
387
|
-
end
|
388
|
-
|
389
|
-
def parse_queue(opts, q, weight=nil)
|
390
|
-
[weight.to_i, 1].max.times do
|
391
|
-
(opts[:queues] ||= []) << q
|
392
|
-
end
|
393
|
-
opts[:strict] = false if weight.to_i > 0
|
394
|
-
end
|
395
373
|
end
|
396
374
|
end
|
data/lib/roundhouse/fetch.rb
CHANGED
@@ -32,7 +32,7 @@ module Roundhouse
|
|
32
32
|
# a new fetch if the current fetch turned up nothing.
|
33
33
|
def fetch
|
34
34
|
watchdog('Fetcher#fetch died') do
|
35
|
-
return if Roundhouse::Fetcher.done?
|
35
|
+
logger.debug 'Fetcher terminating in #fetch' and return if Roundhouse::Fetcher.done?
|
36
36
|
|
37
37
|
begin
|
38
38
|
work = @strategy.retrieve_work
|
@@ -77,6 +77,7 @@ module Roundhouse
|
|
77
77
|
# its mailbox when shutdown starts.
|
78
78
|
def self.done!
|
79
79
|
@done = true
|
80
|
+
Roundhouse.logger.debug 'Fetcher: setting done'
|
80
81
|
end
|
81
82
|
|
82
83
|
def self.reset # testing only
|
@@ -94,10 +95,11 @@ module Roundhouse
|
|
94
95
|
|
95
96
|
class RoundRobinFetch
|
96
97
|
def initialize(options = nil)
|
98
|
+
# ignore options
|
97
99
|
end
|
98
100
|
|
99
101
|
def retrieve_work
|
100
|
-
work = Roundhouse.redis { |conn| Roundhouse::Monitor.
|
102
|
+
work = Roundhouse.redis { |conn| Roundhouse::Monitor.maybe_next_job(conn) }
|
101
103
|
UnitOfWork.new(*work) if work
|
102
104
|
end
|
103
105
|
|
data/lib/roundhouse/launcher.rb
CHANGED
@@ -46,6 +46,7 @@ module Roundhouse
|
|
46
46
|
|
47
47
|
def stop
|
48
48
|
watchdog('Launcher#stop') do
|
49
|
+
logger.debug 'Stopping launcher'
|
49
50
|
@done = true
|
50
51
|
Roundhouse::Fetcher.done!
|
51
52
|
fetcher.terminate if fetcher.alive?
|
@@ -73,7 +74,6 @@ module Roundhouse
|
|
73
74
|
'pid' => $$,
|
74
75
|
'tag' => @options[:tag] || '',
|
75
76
|
'concurrency' => @options[:concurrency],
|
76
|
-
'queues' => @options[:queues].uniq,
|
77
77
|
'labels' => Roundhouse.options[:labels],
|
78
78
|
'identity' => identity,
|
79
79
|
}
|
data/lib/roundhouse/monitor.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
+
require 'roundhouse/script'
|
2
|
+
|
1
3
|
module Roundhouse
|
2
4
|
# This class implements two things:
|
3
|
-
# 1. A
|
5
|
+
# 1. A turntable semaphore - the fetcher can pop the next available
|
4
6
|
# exclusive right to something (such as API request with a given
|
5
7
|
# auth token)
|
6
8
|
# 2. Track which access right is temporarily suspended
|
@@ -10,25 +12,32 @@ module Roundhouse
|
|
10
12
|
SUSPENDED = -1
|
11
13
|
|
12
14
|
# This helps catch problems with key names at runtime
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
15
|
+
TURNTABLE = 'turntable'.freeze
|
16
|
+
IN_ROTATION = 'inrotation'.freeze
|
17
|
+
BUCKETS = 'buckets'.freeze
|
18
|
+
QUEUE = 'queue'.freeze
|
19
|
+
SCHEDULE = 'schedule'.freeze
|
20
|
+
STATUS = 'status'.freeze
|
21
|
+
|
22
|
+
# Number of seconds to block on turntable
|
23
|
+
TURNTABLE_TIMEOUT = 5
|
18
24
|
|
19
25
|
class << self
|
20
26
|
# Find the first active queue
|
21
|
-
#
|
27
|
+
# Return nil if nothing is there. The fetcher is responsible
|
28
|
+
# for polling.
|
22
29
|
def pop(conn)
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
30
|
+
_, q_id = conn.brpop(TURNTABLE, TURNTABLE_TIMEOUT)
|
31
|
+
conn.srem(IN_ROTATION, q_id)
|
32
|
+
return q_id if queue_status(conn, q_id) == ACTIVE
|
33
|
+
return nil
|
27
34
|
end
|
28
35
|
|
36
|
+
# Atomic push
|
37
|
+
# Push queue into turntable if and only if queue status is active
|
29
38
|
def push(conn, q_id)
|
30
|
-
|
31
|
-
conn
|
39
|
+
# NOTE: this version of redis-namespace has a bug when you do keys: argv: params
|
40
|
+
TURNTABLE_PUSH.call conn, [status_bucket(q_id), TURNTABLE, IN_ROTATION], [q_id]
|
32
41
|
end
|
33
42
|
|
34
43
|
# Bulk requeue (push from right). Usually done
|
@@ -37,17 +46,19 @@ module Roundhouse
|
|
37
46
|
conn.rpush("#{QUEUE}:#{q_id}", jobs)
|
38
47
|
end
|
39
48
|
|
40
|
-
def
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
49
|
+
def maybe_next_job(conn)
|
50
|
+
queue_id = pop(conn)
|
51
|
+
return nil unless queue_id
|
52
|
+
|
53
|
+
job = pop_job(conn, queue_id)
|
54
|
+
return queue_id, job if job
|
55
|
+
return nil
|
47
56
|
end
|
48
57
|
|
58
|
+
# Atomically pop job
|
59
|
+
# If nothing is in the queue, set queue status to empty
|
49
60
|
def pop_job(conn, q_id)
|
50
|
-
conn
|
61
|
+
POP_JOB.call conn, ["#{QUEUE}:#{q_id}", status_bucket(q_id)], [q_id]
|
51
62
|
end
|
52
63
|
|
53
64
|
def push_job(conn, payloads)
|
@@ -77,25 +88,20 @@ module Roundhouse
|
|
77
88
|
end
|
78
89
|
|
79
90
|
def resume(conn, q_id)
|
80
|
-
|
81
|
-
set_queue_status(conn, q_id, ACTIVE)
|
82
|
-
conn.lpush(SEMAPHORE, q_id)
|
91
|
+
RESUME_QUEUE.call conn, [status_bucket(q_id), TURNTABLE, IN_ROTATION], [q_id]
|
83
92
|
end
|
84
93
|
|
85
94
|
def queue_status(conn, q_id)
|
86
95
|
conn.hget(status_bucket(q_id), q_id).to_i || EMPTY
|
87
96
|
end
|
88
97
|
|
98
|
+
# Only push onto turntable is status is empty
|
99
|
+
# If empty, push into turntable and set to active
|
100
|
+
# Subsequent job pushes would see the active queue and
|
101
|
+
# not push into turntable
|
102
|
+
# Used after pushing a job, and conditionally putting it into rotation
|
89
103
|
def maybe_add_to_rotation(conn, q_id)
|
90
|
-
|
91
|
-
# sure this is set to ACTIVE after pushing it into the
|
92
|
-
# queuing semaphore. Otherwise, race conditions might
|
93
|
-
# creep in giving this queue an unfair advantage.
|
94
|
-
# See: https://github.com/resque/redis-namespace/blob/master/lib/redis/namespace.rb#L403-L413
|
95
|
-
# See: https://www.redisgreen.net/blog/intro-to-lua-for-redis-programmers/
|
96
|
-
return false unless queue_status(conn, q_id) == EMPTY
|
97
|
-
activate(conn, q_id)
|
98
|
-
conn.lpush(SEMAPHORE, q_id)
|
104
|
+
MAYBE_TURNTABLE_PUSH.call conn, [status_bucket(q_id), TURNTABLE, BUCKETS, IN_ROTATION], [q_id, bucket_num(q_id)]
|
99
105
|
end
|
100
106
|
|
101
107
|
def status_bucket(q_id)
|
@@ -106,6 +112,51 @@ module Roundhouse
|
|
106
112
|
q_id.to_i / 1000
|
107
113
|
end
|
108
114
|
|
115
|
+
# TODO: This needs to be in a lua script and atomic
|
116
|
+
# Need to test how well this scales too. Should be testing about
|
117
|
+
# 10,000 queues
|
118
|
+
def rebuild_turntable!
|
119
|
+
Roundhouse.redis do |conn|
|
120
|
+
buckets = conn.smembers(BUCKETS)
|
121
|
+
queues = conn.pipelined do
|
122
|
+
buckets.each { |bucket| conn.hgetall("#{STATUS}:#{bucket}") }
|
123
|
+
end
|
124
|
+
|
125
|
+
all_queue_ids = queues.map(&:keys).flatten
|
126
|
+
queue_len_res = conn.pipelined do
|
127
|
+
all_queue_ids.each { |queue| conn.llen("#{QUEUE}:#{queue}") }
|
128
|
+
end
|
129
|
+
|
130
|
+
queue_len = all_queue_ids.zip(queue_len_res)
|
131
|
+
status = queues.inject({}) { |a,x| a.merge(x) }
|
132
|
+
|
133
|
+
conn.multi do
|
134
|
+
conn.del(TURNTABLE)
|
135
|
+
queue_len.each do |(q_id, len)|
|
136
|
+
s = status[q_id].to_i
|
137
|
+
|
138
|
+
case s
|
139
|
+
when SUSPENDED then next
|
140
|
+
when ACTIVE then
|
141
|
+
if len > 0
|
142
|
+
conn.lpush(TURNTABLE, q_id)
|
143
|
+
conn.sadd(IN_ROTATION, q_id)
|
144
|
+
else
|
145
|
+
set_queue_status(conn, q_id, EMPTY, false)
|
146
|
+
end
|
147
|
+
when EMPTY then
|
148
|
+
next if len <= 0
|
149
|
+
conn.lpush(TURNTABLE, q_id)
|
150
|
+
conn.sadd(IN_ROTATION, q_id)
|
151
|
+
set_queue_status(conn, q_id, ACTIVE, false)
|
152
|
+
else
|
153
|
+
set_queue_status(conn, q_id, SUSPENDED, false)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
109
160
|
private
|
110
161
|
|
111
162
|
def schedule(conn, payloads)
|
@@ -115,10 +166,60 @@ module Roundhouse
|
|
115
166
|
end )
|
116
167
|
end
|
117
168
|
|
118
|
-
def set_queue_status(conn, q_id, status)
|
119
|
-
conn.sadd(BUCKETS, bucket_num(q_id))
|
169
|
+
def set_queue_status(conn, q_id, status, add_bucket = true)
|
170
|
+
conn.sadd(BUCKETS, bucket_num(q_id)) if add_bucket
|
120
171
|
conn.hset(status_bucket(q_id), q_id, status)
|
121
172
|
end
|
122
|
-
end
|
173
|
+
end # class << self
|
174
|
+
|
175
|
+
WOLVERINE_CONFIG = Roundhouse::Script::Configuration.new
|
176
|
+
|
177
|
+
LUA_TURNTABLE_PUSH = <<END
|
178
|
+
local status = redis.call("HGET", KEYS[1], ARGV[1])
|
179
|
+
local in_rotation = redis.call("SISMEMBER", KEYS[3], ARGV[1])
|
180
|
+
if status == "#{ACTIVE}" and in_rotation == 0 then
|
181
|
+
redis.call("SADD", KEYS[3], ARGV[1])
|
182
|
+
return redis.call("LPUSH", KEYS[2], ARGV[1])
|
183
|
+
end
|
184
|
+
return nil
|
185
|
+
END
|
186
|
+
LUA_MAYBE_TURNTABLE_PUSH = <<END
|
187
|
+
local status = redis.call("HGET", KEYS[1], ARGV[1])
|
188
|
+
if status == "#{EMPTY}" or status == nil or status == false then
|
189
|
+
redis.call('SADD', KEYS[3], ARGV[2])
|
190
|
+
redis.call("HSET", KEYS[1], ARGV[1], #{ACTIVE})
|
191
|
+
local in_rotation = redis.call("SISMEMBER", KEYS[4], ARGV[1])
|
192
|
+
if in_rotation == 0 then
|
193
|
+
redis.call('SADD', KEYS[4], ARGV[1])
|
194
|
+
return redis.call("LPUSH", KEYS[2], ARGV[1])
|
123
195
|
end
|
124
196
|
end
|
197
|
+
return nil
|
198
|
+
END
|
199
|
+
LUA_POP_JOB = <<END
|
200
|
+
local job = redis.call("RPOP", KEYS[1])
|
201
|
+
if type(job) == "string" then
|
202
|
+
return job
|
203
|
+
end
|
204
|
+
redis.call("HSET", KEYS[2], ARGV[1], #{EMPTY})
|
205
|
+
return nil
|
206
|
+
END
|
207
|
+
LUA_RESUME_QUEUE = <<END
|
208
|
+
local status = redis.call("HGET", KEYS[1], ARGV[1])
|
209
|
+
if status == "#{SUSPENDED}" then
|
210
|
+
redis.call("HSET", KEYS[1], ARGV[1], #{ACTIVE})
|
211
|
+
local in_rotation = redis.call("SISMEMBER", KEYS[3], ARGV[1])
|
212
|
+
if in_rotation == 0 then
|
213
|
+
redis.call('SADD', KEYS[4], ARGV[1])
|
214
|
+
return redis.call("LPUSH", KEYS[2], ARGV[1])
|
215
|
+
end
|
216
|
+
end
|
217
|
+
return nil
|
218
|
+
END
|
219
|
+
TURNTABLE_PUSH = Roundhouse::Script.new(LUA_TURNTABLE_PUSH.freeze, name: :turntable_push, config: WOLVERINE_CONFIG)
|
220
|
+
MAYBE_TURNTABLE_PUSH = Roundhouse::Script.new(LUA_MAYBE_TURNTABLE_PUSH.freeze, name: :maybe_turntable_push, config: WOLVERINE_CONFIG)
|
221
|
+
POP_JOB = Roundhouse::Script.new(LUA_POP_JOB.freeze, name: :pop_job, config: WOLVERINE_CONFIG)
|
222
|
+
RESUME_QUEUE = Roundhouse::Script.new(LUA_RESUME_QUEUE.freeze, name: :resume_queue, config: WOLVERINE_CONFIG)
|
223
|
+
|
224
|
+
end # Monitor
|
225
|
+
end
|