redis-scheduler 0.6 → 0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. checksums.yaml +4 -4
  2. data/lib/redis-scheduler.rb +77 -26
  3. metadata +1 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 119e0e42239d8d6f0df0ab381135d55cfe73efec
4
- data.tar.gz: ed15962b933d1b4e0adb64d873cb72338b20a52d
3
+ metadata.gz: afd06d80f04150ef2250553dd178039364be4115
4
+ data.tar.gz: 4e415b9297295a1a6c2d6a63b2faaf9d9f9659f6
5
5
  SHA512:
6
- metadata.gz: e04c00b6ee38ed895c454725a5d239bd0182dd8642d90c5516339058c2071cf06d23308474556b7c30fac4dcfba79863e0c4b6e9692dd6c2504b13ffad31deda
7
- data.tar.gz: 5f0e2268f031c80d28673531b86557a3339f57edbbbc46e97593640e808101714a564f0327962bd54e1bcb8d57900688d0277341e9da25db4cba2c4446be685c
6
+ metadata.gz: c1c4f5e1ce194abf48e5064d39c3be94f752db8ac724ea365e2f7e5a2460ae78e631d7bd031784412522999caefed5b8e098c4c8632681908b5c35a0cf2df7ce
7
+ data.tar.gz: 6fb59c26621545b5a5fa63752e513781a7167af1d1c7291b323e581fa1e33388c302711c2e0716a1dc8b8395a77a2dae2deb5756852d1ad11f578939e9db9f78
@@ -1,15 +1,32 @@
1
- ## A basic chronological scheduler for Redis.
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 later returned to you as such.
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 in the schedule which are ready for
7
- ## processing. In blocking mode, this call will never terminate. In nonblocking
8
- ## mode, this call will terminate when there are no items ready for processing.
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 purposes.
13
+ ## Use #items to simply iterate over all items in the queue, for debugging
14
+ ## purposes.
11
15
  ##
12
- ## == Ensuring reliable behavior in the presence of segfaults
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 part of
32
- ## the check-and-set semantics, i.e. block during contention from multiple
33
- ## clients. "Nonblocking" refers to whether the scheduler should wait until
34
- ## events in the schedule are ready, or only return those items that are
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
- id = @redis.incr @counter
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. only returns items on or
64
- ## after their scheduled times. items are returned as strings. if @blocking is
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
- entry =~ /^\d+:(\S+)$/ or raise InvalidEntryException, entry
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) - 1, :withscores => true
168
- v = elements.each_slice(2).map do |item, at|
169
- item =~ /^\d+:(\S+)$/ or raise InvalidEntryException, 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
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: redis-scheduler
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.6'
4
+ version: '0.7'
5
5
  platform: ruby
6
6
  authors:
7
7
  - William Morgan