zmqmachine 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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