redis-scheduler 0.6 → 0.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/redis-scheduler.rb +77 -26
- metadata +1 -1
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: afd06d80f04150ef2250553dd178039364be4115
|
4
|
+
data.tar.gz: 4e415b9297295a1a6c2d6a63b2faaf9d9f9659f6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c1c4f5e1ce194abf48e5064d39c3be94f752db8ac724ea365e2f7e5a2460ae78e631d7bd031784412522999caefed5b8e098c4c8632681908b5c35a0cf2df7ce
|
7
|
+
data.tar.gz: 6fb59c26621545b5a5fa63752e513781a7167af1d1c7291b323e581fa1e33388c302711c2e0716a1dc8b8395a77a2dae2deb5756852d1ad11f578939e9db9f78
|
data/lib/redis-scheduler.rb
CHANGED
@@ -1,15 +1,32 @@
|
|
1
|
-
## A
|
1
|
+
## A simple, production-ready chronological scheduler for Redis.
|
2
2
|
##
|
3
3
|
## Use #schedule! to add an item to be processed at an arbitrary point in time.
|
4
|
-
## The item will be converted to a string and
|
4
|
+
## The item will be converted to a string and returned to you as such.
|
5
5
|
##
|
6
|
-
## Use #each to iterate over those items
|
7
|
-
##
|
8
|
-
## mode, this call will
|
6
|
+
## Use #each to iterate over those items at the scheduled time. This call
|
7
|
+
## iterates over all items that are scheduled on or before the current time, in
|
8
|
+
## chronological order. In blocking mode, this call will wait forever until
|
9
|
+
## such items become available, and will never terminate. In non-blocking mode,
|
10
|
+
## this call will only iterate over ready items and will terminate when there
|
11
|
+
## are no items ready for processing.
|
9
12
|
##
|
10
|
-
## Use #items to iterate over all items in the queue, for debugging
|
13
|
+
## Use #items to simply iterate over all items in the queue, for debugging
|
14
|
+
## purposes.
|
11
15
|
##
|
12
|
-
## ==
|
16
|
+
## == Exceptions during processing
|
17
|
+
##
|
18
|
+
## Any exceptions during #each will result in the item being re-added to the
|
19
|
+
## schedule at the original time.
|
20
|
+
##
|
21
|
+
## == Multiple producers and consumers
|
22
|
+
##
|
23
|
+
## Multiple producers and consumers are fine.
|
24
|
+
##
|
25
|
+
## == Concurrent reads and writes
|
26
|
+
##
|
27
|
+
## Concurrent reads and writes are fine.
|
28
|
+
##
|
29
|
+
## == Segfaults
|
13
30
|
##
|
14
31
|
## The scheduler maintains a "processing set" of items currently being
|
15
32
|
## processed. If a process dies (i.e. not as a result of a Ruby exception, but
|
@@ -25,18 +42,25 @@ class RedisScheduler
|
|
25
42
|
CAS_DELAY = 0.5 # seconds
|
26
43
|
|
27
44
|
## Options:
|
28
|
-
## * +namespace+: prefix for Redis keys, e.g. "scheduler/"
|
45
|
+
## * +namespace+: prefix for Redis keys, e.g. "scheduler/".
|
29
46
|
## * +blocking+: whether #each should block or return immediately if there are items to be processed immediately.
|
47
|
+
## * +uniq+: when false (default), the same item can be scheduled for multiple times. When true, scheduling the same item multiple times only updates its scheduled time, and does not represent the item multiple times in the schedule.
|
48
|
+
##
|
49
|
+
## Note that uniq is set on a per-schedule basis and cannot be changed. Once
|
50
|
+
## a uniq schedule is created, it is forever uniq (until #reset! is called,
|
51
|
+
## at least). Attempts to use non-uniq queues in a uniq manner, or vice versa,
|
52
|
+
## will result in undefined behavior (probably errors).
|
30
53
|
##
|
31
|
-
## Note that nonblocking mode may still actually block momentarily as
|
32
|
-
## the check-and-set semantics, i.e. block during contention from
|
33
|
-
## clients. "Nonblocking" refers to whether the scheduler should
|
34
|
-
## events in the schedule are ready, or only return those items
|
35
|
-
## ready currently.
|
54
|
+
## Note also that nonblocking mode may still actually block momentarily as
|
55
|
+
## part of the check-and-set semantics, i.e. block during contention from
|
56
|
+
## multiple clients. "Nonblocking" refers to whether the scheduler should
|
57
|
+
## wait until events in the schedule are ready, or only return those items
|
58
|
+
## that are ready currently.
|
36
59
|
def initialize redis, opts={}
|
37
60
|
@redis = redis
|
38
61
|
@namespace = opts[:namespace]
|
39
62
|
@blocking = opts[:blocking]
|
63
|
+
@uniq = opts[:uniq]
|
40
64
|
|
41
65
|
@queue = [@namespace, "q"].join
|
42
66
|
@processing_set = [@namespace, "processing"].join
|
@@ -45,8 +69,7 @@ class RedisScheduler
|
|
45
69
|
|
46
70
|
## Schedule an item at a specific time. item will be converted to a string.
|
47
71
|
def schedule! item, time
|
48
|
-
|
49
|
-
@redis.zadd @queue, time.to_f, "#{id}:#{item}"
|
72
|
+
@redis.zadd @queue, time.to_f, make_entry(item)
|
50
73
|
end
|
51
74
|
|
52
75
|
## Drop all data and reset the schedule entirely.
|
@@ -60,8 +83,8 @@ class RedisScheduler
|
|
60
83
|
## Returns the total number of items currently being processed.
|
61
84
|
def processing_set_size; @redis.scard @processing_set end
|
62
85
|
|
63
|
-
## Yields items along with their scheduled times.
|
64
|
-
## after their scheduled times.
|
86
|
+
## Yields items along with their scheduled times. Only returns items on or
|
87
|
+
## after their scheduled times. Items are returned as strings. If @blocking is
|
65
88
|
## false, will stop once there are no more items that can be processed
|
66
89
|
## immediately; if it's true, will wait until items become available (and
|
67
90
|
## never terminate).
|
@@ -93,7 +116,7 @@ class RedisScheduler
|
|
93
116
|
## to the schedule happen while iterating.
|
94
117
|
##
|
95
118
|
## For these reasons, this is mainly useful for debugging purposes.
|
96
|
-
def items; ItemEnumerator.new(@redis, @queue) end
|
119
|
+
def items; ItemEnumerator.new(@redis, @queue, @uniq) end
|
97
120
|
|
98
121
|
## Returns an Array of [item, timestamp, descriptor] tuples representing the
|
99
122
|
## set of in-process items. The timestamp corresponds to the time at which
|
@@ -107,6 +130,26 @@ class RedisScheduler
|
|
107
130
|
|
108
131
|
private
|
109
132
|
|
133
|
+
## generate the value actually stored in redis
|
134
|
+
def make_entry item
|
135
|
+
if @uniq
|
136
|
+
item.to_s
|
137
|
+
else
|
138
|
+
id = @redis.incr @counter
|
139
|
+
"#{id}:#{item}"
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
## the inverse of #make_item_value
|
144
|
+
def parse_entry entry
|
145
|
+
if @uniq
|
146
|
+
entry
|
147
|
+
else
|
148
|
+
entry =~ /^\d+:(\S+)$/ or raise InvalidEntryException, entry
|
149
|
+
$1
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
110
153
|
def get descriptor; @blocking ? blocking_get(descriptor) : nonblocking_get(descriptor) end
|
111
154
|
|
112
155
|
def blocking_get descriptor
|
@@ -125,8 +168,7 @@ private
|
|
125
168
|
entries = @redis.zrangebyscore @queue, 0, Time.now.to_f, :withscores => true, :limit => [0, 1]
|
126
169
|
break unless entries.size > 0
|
127
170
|
entry, at = entries.first
|
128
|
-
|
129
|
-
item = $1
|
171
|
+
item = parse_entry entry
|
130
172
|
descriptor = Marshal.dump [item, Time.now.to_i, descriptor]
|
131
173
|
@redis.multi do # try and grab it
|
132
174
|
@redis.zrem @queue, entry
|
@@ -148,9 +190,10 @@ private
|
|
148
190
|
## Supports random access with #[], with the same caveats as above.
|
149
191
|
class ItemEnumerator
|
150
192
|
include Enumerable
|
151
|
-
def initialize redis, q
|
193
|
+
def initialize redis, q, uniq
|
152
194
|
@redis = redis
|
153
195
|
@q = q
|
196
|
+
@uniq = uniq
|
154
197
|
end
|
155
198
|
|
156
199
|
PAGE_SIZE = 50
|
@@ -164,16 +207,24 @@ private
|
|
164
207
|
end
|
165
208
|
|
166
209
|
def [] start, num=nil
|
167
|
-
elements = @redis.zrange @q, start, start + (num || 0)
|
168
|
-
v = elements.
|
169
|
-
item
|
170
|
-
item = $1
|
210
|
+
elements = @redis.zrange @q, start, start + (num || 0), :withscores => true
|
211
|
+
v = elements.map do |entry, at|
|
212
|
+
item = parse_entry entry
|
171
213
|
[item, Time.at(at.to_f)]
|
172
214
|
end
|
173
215
|
num ? v : v.first
|
174
216
|
end
|
175
217
|
|
176
218
|
def size; @redis.zcard @q end
|
177
|
-
end
|
178
219
|
|
220
|
+
## duplicated :(
|
221
|
+
def parse_entry entry
|
222
|
+
if @uniq
|
223
|
+
entry
|
224
|
+
else
|
225
|
+
entry =~ /^\d+:(\S+)$/ or raise InvalidEntryException, entry
|
226
|
+
$1
|
227
|
+
end
|
228
|
+
end
|
229
|
+
end
|
179
230
|
end
|