zmqmachine 0.3.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.
data/lib/zm/reactor.rb ADDED
@@ -0,0 +1,458 @@
1
+ #--
2
+ #
3
+ # Author:: Chuck Remes
4
+ # Homepage:: http://github.com/chuckremes/zmqmachine
5
+ # Date:: 20100602
6
+ #
7
+ #----------------------------------------------------------------------------
8
+ #
9
+ # Copyright (C) 2010 by Chuck Remes. All Rights Reserved.
10
+ # Email: cremes at mac dot com
11
+ #
12
+ # (The MIT License)
13
+ #
14
+ # Permission is hereby granted, free of charge, to any person obtaining
15
+ # a copy of this software and associated documentation files (the
16
+ # 'Software'), to deal in the Software without restriction, including
17
+ # without limitation the rights to use, copy, modify, merge, publish,
18
+ # distribute, sublicense, and/or sell copies of the Software, and to
19
+ # permit persons to whom the Software is furnished to do so, subject to
20
+ # the following conditions:
21
+ #
22
+ # The above copyright notice and this permission notice shall be
23
+ # included in all copies or substantial portions of the Software.
24
+ #
25
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
26
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
27
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
28
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
29
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
30
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
31
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32
+ #
33
+ #---------------------------------------------------------------------------
34
+ #
35
+ #
36
+
37
+ module ZMQMachine
38
+
39
+ class Reactor
40
+ attr_reader :name
41
+
42
+ # +poll_interval+ is the number of milliseconds to block while
43
+ # waiting for new 0mq socket events; default is 0
44
+ #
45
+ def initialize name, poll_interval = 0
46
+ @name = name
47
+ @running = false
48
+ @thread = nil
49
+ @poll_interval = determine_interval poll_interval
50
+ @timers = ZMQMachine::Timers.new
51
+
52
+ @proc_queue = []
53
+ @proc_queue_mutex = Mutex.new
54
+
55
+ # could raise if it fails
56
+ @context = ZMQ::Context.new 1
57
+ @poller = ZMQ::Poller.new
58
+ @sockets = []
59
+ @raw_to_socket = {}
60
+ Thread.abort_on_exception = true
61
+ end
62
+
63
+ # Returns true when the reactor is running OR while it is in the
64
+ # midst of a shutdown request.
65
+ #
66
+ # Returns false when the reactor thread does not exist.
67
+ #
68
+ def running?() @running; end
69
+
70
+ # The main entry point for all new reactor contexts. This proc
71
+ # or block given to this method is evaluated *once* before
72
+ # entering the reactor loop. This evaluation generally sets up
73
+ # sockets and timers that will do the real work once the loop
74
+ # is executed.
75
+ #
76
+ def run blk = nil, &block
77
+ blk ||= block
78
+ @running, @stopping = true, false
79
+
80
+ @thread = Thread.new do
81
+ blk.call self if blk
82
+
83
+ while !@stopping && @running do
84
+ run_once
85
+ end
86
+
87
+ cleanup
88
+ end
89
+ self
90
+ end
91
+
92
+ # Marks the reactor as eligible for termination. Then waits for the
93
+ # reactor thread to exit via #join (no timeout).
94
+ #
95
+ # The reactor is not forcibly terminated if it is currently blocked
96
+ # by some long-running operation. Use #kill to forcibly terminate
97
+ # the reactor.
98
+ #
99
+ def stop
100
+ # wait until the thread loops around again and exits on its own
101
+ @stopping = true
102
+ join
103
+ end
104
+
105
+ # Join on the thread running this reactor instance. Default behavior
106
+ # is to wait indefinitely for the thread to exit.
107
+ #
108
+ # Pass an optional +delay+ value measured in milliseconds; the
109
+ # thread will be stopped if it hasn't exited by the end of +delay+
110
+ # milliseconds.
111
+ #
112
+ # Returns immediately when the thread has already exited.
113
+ #
114
+ def join delay = nil
115
+ # don't allow the thread to try and join itself and only worry about
116
+ # joining for live threads
117
+ if @thread.alive? && @thread != Thread.current
118
+ if delay
119
+ # convert to seconds to meet the argument expectations of Thread#join
120
+ seconds = delay / 1000.0
121
+ @thread.join seconds
122
+ else
123
+ @thread.join
124
+ end
125
+ end
126
+ end
127
+
128
+ # Kills the running reactor instance by terminating its thread.
129
+ #
130
+ # After the thread exits, the reactor attempts to clean up after itself
131
+ # and kill any pending I/O.
132
+ #
133
+ def kill
134
+ @stopping = true
135
+ @thread.kill
136
+ cleanup
137
+ end
138
+
139
+ # Schedules a proc or block to execute on the next trip through the
140
+ # reactor loop.
141
+ #
142
+ # This method is thread-safe.
143
+ #
144
+ def next_tick blk = nil, &block
145
+ blk ||= block
146
+ @proc_queue_mutex.synchronize do
147
+ @proc_queue << blk
148
+ end
149
+ end
150
+
151
+ # Removes the given +sock+ socket from the reactor context. It is deregistered
152
+ # for new events and closed. Any queued messages are silently dropped.
153
+ #
154
+ def close_socket sock
155
+ delete_socket sock
156
+ sock.raw_socket.close
157
+ end
158
+
159
+ # Creates a REQ socket and attaches +handler_instance+ to the
160
+ # resulting socket. Should only be paired with one other
161
+ # #rep_socket instance.
162
+ #
163
+ # +handler_instance+ must implement the #on_writable and
164
+ # #on_writable_error methods. The reactor will call those methods
165
+ # based upon new events.
166
+ #
167
+ # All handlers must implement the #on_attach method.
168
+ #
169
+ def req_socket handler_instance
170
+ sock = ZMQMachine::Socket::Req.new @context, handler_instance
171
+ save_socket sock
172
+ sock
173
+ end
174
+
175
+ # Creates a REP socket and attaches +handler_instance+ to the
176
+ # resulting socket. Should only be paired with one other
177
+ # #req_socket instance.
178
+ #
179
+ # +handler_instance+ must implement the #on_readable and
180
+ # #on_readable_error methods. The reactor will call those methods
181
+ # based upon new events.
182
+ #
183
+ # All handlers must implement the #on_attach method.
184
+ #
185
+ def rep_socket handler_instance
186
+ sock = ZMQMachine::Socket::Rep.new @context, handler_instance
187
+ save_socket sock
188
+ sock
189
+ end
190
+
191
+ # Creates a XREQ socket and attaches +handler_instance+ to the
192
+ # resulting socket. Should only be paired with one other
193
+ # #rep_socket instance.
194
+ #
195
+ # +handler_instance+ must implement the #on_readable,
196
+ # #on_readable_error, #on_writable and #on_writable_error
197
+ # methods. The reactor will call those methods
198
+ # based upon new events.
199
+ #
200
+ # All handlers must implement the #on_attach method.
201
+ #
202
+ def xreq_socket handler_instance
203
+ sock = ZMQMachine::Socket::XReq.new @context, handler_instance
204
+ save_socket sock
205
+ sock
206
+ end
207
+
208
+ # Creates a XREP socket and attaches +handler_instance+ to the
209
+ # resulting socket. Should only be paired with one other
210
+ # #req_socket instance.
211
+ #
212
+ # +handler_instance+ must implement the #on_readable,
213
+ # #on_readable_error, #on_writable and #on_writable_error
214
+ # methods. The reactor will call those methods
215
+ # based upon new events.
216
+ #
217
+ # All handlers must implement the #on_attach method.
218
+ #
219
+ def xrep_socket handler_instance
220
+ sock = ZMQMachine::Socket::XRep.new @context, handler_instance
221
+ save_socket sock
222
+ sock
223
+ end
224
+
225
+ # Creates a PAIR socket and attaches +handler_instance+ to the
226
+ # resulting socket. Works only with other #pair_socket instances
227
+ # in the same or other reactor instance.
228
+ #
229
+ # +handler_instance+ must implement the #on_readable and
230
+ # #on_readable_error methods. Each handler must also implement
231
+ # the #on_writable and #on_writable_error methods.
232
+ # The reactor will call those methods
233
+ # based upon new events.
234
+ #
235
+ # All handlers must implement the #on_attach method.
236
+ #
237
+ def pair_socket handler_instance
238
+ sock = ZMQMachine::Socket::Pair.new @context, handler_instance
239
+ save_socket sock
240
+ sock
241
+ end
242
+
243
+ # Creates a PUB socket and attaches +handler_instance+ to the
244
+ # resulting socket. Usually paired with one or more
245
+ # #sub_socket instances in the same or other reactor instance.
246
+ #
247
+ # +handler_instance+ must implement the #on_writable and
248
+ # #on_writable_error methods. The reactor will call those methods
249
+ # based upon new events. This socket type can *only* write; it
250
+ # can never receive/read messages.
251
+ #
252
+ # All handlers must implement the #on_attach method.
253
+ #
254
+ def pub_socket handler_instance
255
+ sock = ZMQMachine::Socket::Pub.new @context, handler_instance
256
+ save_socket sock
257
+ sock
258
+ end
259
+
260
+ # Creates a SUB socket and attaches +handler_instance+ to the
261
+ # resulting socket. Usually paired with one or more
262
+ # #pub_socket in the same or different reactor context.
263
+ #
264
+ # +handler_instance+ must implement the #on_readable and
265
+ # #on_readable_error methods. The reactor will call those methods
266
+ # based upon new events. This socket type can *only* read; it
267
+ # can never write/send messages.
268
+ #
269
+ # All handlers must implement the #on_attach method.
270
+ #
271
+ def sub_socket handler_instance
272
+ sock = ZMQMachine::Socket::Sub.new @context, handler_instance
273
+ save_socket sock
274
+ sock
275
+ end
276
+
277
+ # Registers the +sock+ for POLLOUT events that will cause the
278
+ # reactor to call the handler's on_writable method.
279
+ #
280
+ def register_writable sock
281
+ @poller.register_writable sock.raw_socket
282
+ end
283
+
284
+ # Deregisters the +sock+ for POLLOUT. The handler will no longer
285
+ # receive calls to on_writable.
286
+ #
287
+ def deregister_writable sock
288
+ @poller.deregister_writable sock.raw_socket
289
+ end
290
+
291
+ # Registers the +sock+ for POLLIN events that will cause the
292
+ # reactor to call the handler's on_readable method.
293
+ #
294
+ def register_readable sock
295
+ @poller.register_readable sock.raw_socket
296
+ end
297
+
298
+ # Deregisters the +sock+ for POLLIN events. The handler will no longer
299
+ # receive calls to on_readable.
300
+ #
301
+ def deregister_readable sock
302
+ @poller.deregister_readable sock.raw_socket
303
+ end
304
+
305
+ # Creates a timer that will fire a single time. Expects either a
306
+ # +timer_proc+ proc or a block, otherwise no timer is created.
307
+ #
308
+ # +delay+ is measured in milliseconds (1 second equals 1000
309
+ # milliseconds)
310
+ #
311
+ def oneshot_timer delay, timer_proc = nil, &blk
312
+ blk ||= timer_proc
313
+ @timers.add_oneshot delay, blk
314
+ end
315
+
316
+ # Creates a timer that will fire every +delay+ milliseconds until
317
+ # it is explicitly cancelled. Expects either a +timer_proc+ proc
318
+ # or a block, otherwise no timer is created.
319
+ #
320
+ # +delay+ is measured in milliseconds (1 second equals 1000
321
+ # milliseconds)
322
+ #
323
+ def periodical_timer delay, timer_proc = nil, &blk
324
+ blk ||= timer_proc
325
+ @timers.add_periodical delay, blk
326
+ end
327
+
328
+ # Cancels an existing timer if it hasn't already fired.
329
+ #
330
+ # Returns true if cancelled, false if otherwise.
331
+ #
332
+ def cancel_timer timer
333
+ @timers.cancel timer
334
+ end
335
+
336
+
337
+ private
338
+
339
+ def run_once
340
+ run_procs
341
+ run_timers
342
+ poll
343
+ end
344
+
345
+ # Close each open socket and terminate the reactor context; this will
346
+ # release the native memory backing each of these objects
347
+ def cleanup
348
+ # work on a dup since #close_socket deletes from @sockets
349
+ @sockets.dup.each { |sock| close_socket sock }
350
+ @context.terminate
351
+ @running = false
352
+ end
353
+
354
+ def run_timers
355
+ @timers.fire_expired
356
+ end
357
+
358
+ # work on a copy of the queue; some procs may reschedule themselves to
359
+ # run again immediately, so by using a copy we make them wait until the next
360
+ # loop
361
+ def run_procs
362
+ work = nil
363
+ @proc_queue_mutex.synchronize do
364
+ work, @proc_queue = @proc_queue, []
365
+ end
366
+
367
+ until work.empty? do
368
+ work.pop.call
369
+ end
370
+ end
371
+
372
+ def poll
373
+ @poller.poll @poll_interval
374
+
375
+ @poller.readables.each { |sock| @raw_to_socket[sock].resume_read }
376
+ @poller.writables.each { |sock| @raw_to_socket[sock].resume_write }
377
+ end
378
+
379
+ def save_socket sock
380
+ @poller.register sock.raw_socket, sock.poll_options
381
+ @sockets << sock
382
+ @raw_to_socket[sock.raw_socket] = sock
383
+ end
384
+
385
+ def delete_socket sock
386
+ @poller.delete sock.raw_socket
387
+ @sockets.delete sock
388
+ @raw_to_socket.delete sock.raw_socket
389
+ end
390
+
391
+
392
+ # Internally converts the number to microseconds
393
+ def determine_interval interval
394
+ # set a lower bound of 100 usec so we don't burn up the CPU
395
+ interval <= 0 ? 100 : (interval * 1000).to_i
396
+ end
397
+
398
+ end # class Reactor
399
+
400
+
401
+ # Not implemented. Just a (broken) thought experiment at the moment.
402
+ #
403
+ class SyncReactor
404
+
405
+ def initialize name
406
+ @klass_path = ZMQMachine::Sync
407
+ @poll_interval = 10
408
+ @active = true
409
+ super name
410
+ end
411
+
412
+ def run blk = nil, &block
413
+ blk ||= block
414
+ @running = true
415
+
416
+ @thread = Thread.new do
417
+ @fiber = Fiber.new { |context| blk.call context }
418
+ func = Proc.new { |context| @fiber.resume context }
419
+
420
+ run_once func
421
+ while @running do
422
+ run_once
423
+ end
424
+
425
+ cleanup
426
+ end
427
+
428
+ self
429
+ end
430
+
431
+ def deactivate() @active = false; end
432
+
433
+ def active?() @active; end
434
+
435
+ def while_active? &blk
436
+ begin
437
+ while @active do
438
+ yield
439
+ end
440
+ rescue => e
441
+ p e
442
+ end
443
+
444
+ @running = false
445
+ end
446
+
447
+
448
+ private
449
+
450
+ def poll
451
+ @poller.poll @poll_interval
452
+
453
+ @poller.writables.each { |sock| sock.resume }
454
+ @poller.readables.each { |sock| sock.resume }
455
+ end
456
+ end # class SyncReactor
457
+
458
+ end # module ZMQMachine
@@ -0,0 +1,216 @@
1
+ #--
2
+ #
3
+ # Author:: Chuck Remes
4
+ # Homepage:: http://github.com/chuckremes/zmqmachine
5
+ # Date:: 20100602
6
+ #
7
+ #----------------------------------------------------------------------------
8
+ #
9
+ # Copyright (C) 2010 by Chuck Remes. All Rights Reserved.
10
+ # Email: cremes at mac dot com
11
+ #
12
+ # (The MIT License)
13
+ #
14
+ # Permission is hereby granted, free of charge, to any person obtaining
15
+ # a copy of this software and associated documentation files (the
16
+ # 'Software'), to deal in the Software without restriction, including
17
+ # without limitation the rights to use, copy, modify, merge, publish,
18
+ # distribute, sublicense, and/or sell copies of the Software, and to
19
+ # permit persons to whom the Software is furnished to do so, subject to
20
+ # the following conditions:
21
+ #
22
+ # The above copyright notice and this permission notice shall be
23
+ # included in all copies or substantial portions of the Software.
24
+ #
25
+ # THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
26
+ # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
27
+ # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
28
+ # IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
29
+ # CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
30
+ # TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
31
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
32
+ #
33
+ #---------------------------------------------------------------------------
34
+ #
35
+ #
36
+
37
+ module ZMQMachine
38
+
39
+ module Socket
40
+
41
+ module Base
42
+ attr_reader :raw_socket, :kind
43
+ attr_reader :poll_options
44
+
45
+ def initialize context, handler
46
+ @state = :init
47
+ @context = context
48
+ @bindings = []
49
+ @connections = []
50
+
51
+ @handler = handler
52
+ @raw_socket = allocate_socket @context
53
+ attach @handler
54
+ end
55
+
56
+ # Call the handler's #on_attach method and pass itself
57
+ # so the handler may complete its setup.
58
+ #
59
+ # The #on_attach method is passed a single argument named
60
+ # +socket+. The method should probably #bind or #connect
61
+ # to an address and potentially schedule (via timer) an
62
+ # operation or begin sending messages immediately.
63
+ #
64
+ def attach handler
65
+ raise ArgumentError, "Handler must provide an 'on_attach' method" unless handler.respond_to? :on_attach
66
+ handler.on_attach self
67
+ end
68
+
69
+ # Creates a 0mq socket endpoint for the transport given in the
70
+ # +address+. Other 0mq sockets may then #connect to this bound
71
+ # endpoint.
72
+ #
73
+ def bind address
74
+ begin
75
+ @bindings << address
76
+ @raw_socket.bind address.to_s
77
+ true
78
+ rescue ZMQ::ZeroMQError
79
+ @bindings.pop
80
+ false
81
+ end
82
+ end
83
+
84
+ # Connect this 0mq socket to the 0mq socket bound to the endpoint
85
+ # described by the +address+.
86
+ #
87
+ def connect address
88
+ begin
89
+ @connections << address
90
+ @raw_socket.connect address.to_s
91
+ true
92
+ rescue ZMQ::ZeroMQError
93
+ @connections.pop
94
+ false
95
+ end
96
+ end
97
+
98
+ # Called to send a ZMQ::Message that was populated with data.
99
+ #
100
+ # Returns true on success, false otherwise.
101
+ #
102
+ # May raise a ZMQ::SocketError.
103
+ #
104
+ def send_message message, multipart = false
105
+ queued = @raw_socket.send message, ZMQ::NOBLOCK | (multipart ? ZMQ::SNDMORE : 0)
106
+ queued
107
+ end
108
+
109
+ # Convenience method to send a string on the socket. It handles
110
+ # the creation of a ZMQ::Message and populates it appropriately.
111
+ #
112
+ # Returns true on success, false otherwise.
113
+ #
114
+ # May raise a ZMQ::SocketError.
115
+ #
116
+ def send_message_string message
117
+ queued = @raw_socket.send_string message, ZMQ::NOBLOCK
118
+ queued
119
+ end
120
+
121
+ # Convenience method for sending a multi-part message. The
122
+ # +messages+ argument must respond to :size and :at (like
123
+ # an Array).
124
+ #
125
+ # May raise a ZMQ::SocketError.
126
+ #
127
+ def send_messages messages
128
+ rc = false
129
+ i = 0
130
+ size = messages.size
131
+
132
+ # loop through all messages but the last
133
+ while size > 1 && i < size - 1 do
134
+ rc = send_message messages.at(i), true
135
+ i += 1
136
+ end
137
+
138
+ # send the last message without the multipart arg to flush
139
+ # the message to the 0mq queue
140
+ rc = send_messages.last if size > 0
141
+ rc
142
+ end
143
+
144
+ # Retrieve the IDENTITY value assigned to this socket.
145
+ #
146
+ def identity() @raw_socket.identity; end
147
+
148
+ # Assign a custom IDENTITY value to this socket. Limit is
149
+ # 255 bytes and must be greater than 0 bytes.
150
+ #
151
+ def identity=(value) @raw_socket.identity = value; end
152
+
153
+ # Used by the reactor. Never called by user code.
154
+ #
155
+ # FIXME: need to rework all of this +rc+ stuff. The underlying lib returns
156
+ # nil when a NOBLOCK socket gets EAGAIN. It returns true when a message
157
+ # was successfully dequeued. The use of rc here is really ugly and wrong.
158
+ #
159
+ def resume_read
160
+ messages = []
161
+ rc = read_message_part messages
162
+
163
+ while 0 == rc && @raw_socket.more_parts?
164
+ rc = read_message_part messages
165
+ end
166
+
167
+ # only deliver the messages when rc is 0; otherwise, we
168
+ # may have gotten EAGAIN and no message was read;
169
+ # don't deliver empty messages
170
+ deliver messages, rc unless 0 == rc
171
+ end
172
+
173
+ # Used by the reactor. Never called by user code.
174
+ #
175
+ def resume_write
176
+ @state = :ready
177
+ @handler.on_writable self
178
+ end
179
+
180
+ def inspect
181
+ "kind [#{@kind}] poll options [#{@poll_options}] state [#{@state}]"
182
+ end
183
+
184
+
185
+ private
186
+
187
+ def ready_state?
188
+ :ready == @state
189
+ end
190
+
191
+ def read_message_part messages
192
+ message = ZMQ::Message.new
193
+ begin
194
+ rc = @raw_socket.recv message, ZMQ::NOBLOCK
195
+ rc = 0 if rc
196
+ rescue ZMQ::ZeroMQError => e
197
+ rc = e
198
+ end
199
+ messages << message
200
+ rc
201
+ end
202
+
203
+ def deliver messages, rc
204
+ if 0 == rc
205
+ @state = :ready
206
+ @handler.on_readable self, messages
207
+ else
208
+ @handler.on_readable_error self, rc
209
+ end
210
+ end
211
+
212
+ end # module Base
213
+
214
+ end # module Socket
215
+
216
+ end # module ZMQMachine