Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

several changes

- print a warning if the reactor is modified from a non-reactor
thread.

- start the logging client from the reactor thread to avoid some
startup race conditions

- capture all Exceptions rather than just those that descend from
StandardError.
  • Loading branch information...
commit 5ad1ab324c8bc5e9c1b51267893290e01cadbe36 1 parent c5977b9
Chuck Remes authored
133 lib/zm/reactor.rb
@@ -79,10 +79,10 @@ class Reactor
79 79 #
80 80 def initialize configuration = nil
81 81 configuration ||= Configuration.new
82   - @name = configuration.name || 'unnamed'
  82 + @name = configuration.name.to_s
83 83 @running = false
84 84 @thread = nil
85   - @poll_interval = determine_interval(configuration.poll_interval || 10)
  85 + @poll_interval = determine_interval(configuration.poll_interval)
86 86
87 87 @proc_queue = []
88 88 @proc_queue_mutex = Mutex.new
@@ -101,13 +101,11 @@ def initialize configuration = nil
101 101 @raw_to_socket = {}
102 102 Thread.abort_on_exception = true
103 103
104   - if configuration.log_endpoint
105   - @logger = LogClient.new self, configuration.log_endpoint
106   - @logging_enabled = true
107   - end
  104 + @log_endpoint = configuration.log_endpoint
  105 + @logging_enabled = @log_endpoint ? true : false
108 106
109   - @exception_handler = configuration.exception_handler if configuration.exception_handler
110   - @timers = ZMQMachine::Timers.new(@exception_handler)
  107 + @exception_handler = configuration.exception_handler
  108 + @timers = ZMQMachine::Timers.new(self, @exception_handler)
111 109 end
112 110
113 111 def shared_context?
@@ -132,6 +130,10 @@ def run blk = nil, &block
132 130 @running, @stopping = true, false
133 131
134 132 @thread = Thread.new do
  133 + Thread.current["reactor-name"] = @name
  134 +
  135 + start_log_client
  136 +
135 137 blk.call self if blk
136 138
137 139 while !@stopping && running? do
@@ -210,12 +212,16 @@ def next_tick blk = nil, &block
210 212 # Returns +true+ for a succesful close, +false+ otherwise.
211 213 #
212 214 def close_socket sock
213   - return false unless sock
  215 + if reactor_thread?
  216 + return false unless sock
214 217
215   - removed = delete_socket sock
216   - sock.raw_socket.close
  218 + removed = delete_socket sock
  219 + sock.raw_socket.close
217 220
218   - removed
  221 + removed
  222 + else
  223 + false
  224 + end
219 225 end
220 226
221 227 # Creates a REQ socket and attaches +handler_instance+ to the
@@ -358,28 +364,36 @@ def pull_socket handler_instance
358 364 # reactor to call the handler's on_writable method.
359 365 #
360 366 def register_writable sock
361   - @poller.register_writable sock.raw_socket
  367 + if reactor_thread?
  368 + @poller.register_writable sock.raw_socket
  369 + end
362 370 end
363 371
364 372 # Deregisters the +sock+ for POLLOUT. The handler will no longer
365 373 # receive calls to on_writable.
366 374 #
367 375 def deregister_writable sock
368   - @poller.deregister_writable sock.raw_socket
  376 + if reactor_thread?
  377 + @poller.deregister_writable sock.raw_socket
  378 + end
369 379 end
370 380
371 381 # Registers the +sock+ for POLLIN events that will cause the
372 382 # reactor to call the handler's on_readable method.
373 383 #
374 384 def register_readable sock
375   - @poller.register_readable sock.raw_socket
  385 + if reactor_thread?
  386 + @poller.register_readable sock.raw_socket
  387 + end
376 388 end
377 389
378 390 # Deregisters the +sock+ for POLLIN events. The handler will no longer
379 391 # receive calls to on_readable.
380 392 #
381 393 def deregister_readable sock
382   - @poller.deregister_readable sock.raw_socket
  394 + if reactor_thread?
  395 + @poller.deregister_readable sock.raw_socket
  396 + end
383 397 end
384 398
385 399 # Creates a timer that will fire a single time. Expects either a
@@ -390,7 +404,7 @@ def deregister_readable sock
390 404 #
391 405 def oneshot_timer delay, timer_proc = nil, &blk
392 406 blk ||= timer_proc
393   - @timers.add_oneshot delay, blk
  407 + timer = @timers.add_oneshot delay, blk
394 408 end
395 409
396 410 # Creates a timer that will fire once at a specific
@@ -399,8 +413,10 @@ def oneshot_timer delay, timer_proc = nil, &blk
399 413 # +exact_time+ may be either a Time object or a Numeric.
400 414 #
401 415 def oneshot_timer_at exact_time, timer_proc = nil, &blk
402   - blk ||= timer_proc
403   - @timers.add_oneshot_at exact_time, blk
  416 + if reactor_thread?
  417 + blk ||= timer_proc
  418 + timer = @timers.add_oneshot_at exact_time, blk
  419 + end
404 420 end
405 421
406 422 # Creates a timer that will fire every +delay+ milliseconds until
@@ -411,8 +427,10 @@ def oneshot_timer_at exact_time, timer_proc = nil, &blk
411 427 # milliseconds)
412 428 #
413 429 def periodical_timer delay, timer_proc = nil, &blk
414   - blk ||= timer_proc
415   - @timers.add_periodical delay, blk
  430 + if reactor_thread?
  431 + blk ||= timer_proc
  432 + timer = @timers.add_periodical delay, blk
  433 + end
416 434 end
417 435
418 436 # Cancels an existing timer if it hasn't already fired.
@@ -420,7 +438,9 @@ def periodical_timer delay, timer_proc = nil, &blk
420 438 # Returns true if cancelled, false if otherwise.
421 439 #
422 440 def cancel_timer timer
423   - @timers.cancel timer
  441 + if reactor_thread?
  442 + @timers.cancel timer
  443 + end
424 444 end
425 445
426 446 # Asks all timers to reschedule themselves starting from Timers.now.
@@ -433,10 +453,11 @@ def reschedule_timers
433 453 end
434 454
435 455 def list_timers
436   - @timers.list.each do |timer|
437   - name = timer.respond_to?(:name) ? timer.timer_proc.name : timer.timer_proc.to_s
438   - puts "fire time [#{Time.at(timer.fire_time / 1000)}], method [#{name}]"
  456 + list = @timers.list
  457 + list.each do |timer|
  458 + log :timer, timer.to_s
439 459 end
  460 + log(:timer, "No timers for reactor [#{@name}]") if list.empty?
440 461 end
441 462
442 463 def open_socket_count kind = :all
@@ -469,10 +490,34 @@ def open_socket_count kind = :all
469 490 #
470 491 def log level, message
471 492 if @logging_enabled
472   - @logger.write level, message
  493 + if reactor_thread?
  494 + @logger.write level, message
  495 + end
  496 + end
  497 + end
  498 +
  499 + def reactor_thread?
  500 + unless thread_match?
  501 + str = "Reactor violation! Accessing reactor from a non-reactor thread!\n"
  502 + str << "Expected reactor thread [#{@name}] but got [#{self.class.current_thread_name}]\n"
  503 + str << "Begin backtrace:\n"
  504 + str << caller.join("\n")
  505 + str << "\nEnd backtrace.\n"
  506 + STDERR.print(str)
  507 + false
  508 + else
  509 + true
473 510 end
474 511 end
475 512
  513 + def thread_match?
  514 + @name == Reactor.current_thread_name
  515 + end
  516 +
  517 + def self.current_thread_name
  518 + Thread.current['reactor-name']
  519 + end
  520 +
476 521
477 522 private
478 523
@@ -481,7 +526,7 @@ def run_once
481 526 run_procs
482 527 run_timers
483 528 poll
484   - rescue => e
  529 + rescue Exception => e
485 530 if @exception_handler
486 531 @exception_handler.call(e)
487 532 else
@@ -493,6 +538,7 @@ def run_once
493 538 # Close each open socket and terminate the reactor context; this will
494 539 # release the native memory backing each of these objects
495 540 def cleanup
  541 + log(:info, "#{self.class}, Cleanup called, exiting reactor loop.")
496 542 @proc_queue_mutex.synchronize { @proc_queue.clear }
497 543
498 544 # work on a dup since #close_socket deletes from @sockets
@@ -542,6 +588,9 @@ def poll
542 588 reactor_socket = @raw_to_socket[sock]
543 589 reactor_socket.resume_write if reactor_socket
544 590 end
  591 +
  592 + else
  593 + STDERR.print("#{self.class}, Poll returned an error, errno [#{ZMQ::Util.errno}] desc [#{ZMQ::Util.error_string}]\n")
545 594 end
546 595 end
547 596
@@ -549,16 +598,18 @@ def poll
549 598 end
550 599
551 600 def create_socket handler_instance, kind
552   - sock = nil
553   -
554   - begin
555   - sock = kind.new @context, handler_instance
556   - save_socket sock
557   - rescue ZMQ::ContextError => e
  601 + if reactor_thread?
558 602 sock = nil
559   - end
560 603
561   - sock
  604 + begin
  605 + sock = kind.new @context, handler_instance
  606 + save_socket sock
  607 + rescue ZMQ::ContextError => e
  608 + sock = nil
  609 + end
  610 +
  611 + sock
  612 + end
562 613 end
563 614
564 615 def save_socket sock
@@ -570,14 +621,13 @@ def save_socket sock
570 621 # Returns true when all steps succeed, false otherwise
571 622 #
572 623 def delete_socket sock
573   - poll_deleted = @poller.delete(sock.raw_socket)
574   - sockets_deleted = @sockets.delete(sock)
575   - ffi_deleted = @raw_to_socket.delete(sock.raw_socket)
  624 + poll_deleted = @poller.delete(sock.raw_socket) ? true : false
  625 + sockets_deleted = @sockets.delete(sock) ? true : false
  626 + ffi_deleted = @raw_to_socket.delete(sock.raw_socket) ? true : false
576 627
577 628 poll_deleted && sockets_deleted && ffi_deleted
578 629 end
579 630
580   -
581 631 # Unnecessary to convert the number to microseconds; the ffi-rzmq
582 632 # library does this for us.
583 633 #
@@ -586,6 +636,11 @@ def determine_interval interval
586 636 interval <= 0 ? 1.0 : interval.to_i
587 637 end
588 638
  639 + def start_log_client
  640 + if @logging_enabled
  641 + @logger = LogClient.new self, @log_endpoint
  642 + end
  643 + end
589 644 end # class Reactor
590 645
591 646
17 lib/zm/server/base.rb
@@ -17,7 +17,7 @@ def initialize configuration
17 17 def shutdown
18 18 @reactor.log :debug, "#{self.class}#shutdown_socket, closing reactor socket"
19 19 @on_read = nil
20   - @reactor.close_socket @socket
  20 + @reactor.close_socket(@socket)
21 21 end
22 22
23 23 def on_attach socket
@@ -47,9 +47,11 @@ def on_attach socket
47 47 # received*.
48 48 #
49 49 def write messages, verbose = false
50   - @verbose = verbose
51   - @message_queue << messages
52   - write_queue_to_socket
  50 + if @reactor.reactor_thread?
  51 + @verbose = verbose
  52 + @message_queue << messages
  53 + write_queue_to_socket
  54 + end
53 55 end
54 56
55 57 # Prints each message when global debugging is enabled.
@@ -57,7 +59,11 @@ def write messages, verbose = false
57 59 # Forwards +messages+ on to the :on_read callback given in the constructor.
58 60 #
59 61 def on_readable socket, messages
60   - @on_read.call socket, messages
  62 + if @reactor.reactor_thread?
  63 + @on_read.call socket, messages
  64 + else
  65 + STDERR.print("error, #{self.class} Thread violation! Expected [#{Reactor.current_thread_name}] but got [#{Thread.current['reactor-name']}]\n")
  66 + end
61 67 close_messages messages
62 68 end
63 69
@@ -114,6 +120,7 @@ def write_queue_to_socket
114 120 elsif ZMQ::Util.errno == ZMQ::EAGAIN
115 121 # schedule another write attempt in 10 ms; break out of the loop
116 122 @reactor.log :debug, "#{self.class}#write_queue_to_socket, failed to write messages; scheduling next attempt"
  123 + STDERR.print("debug, #{self.class}#write_queue_to_socket, failed to write messages; scheduling next attempt\n")
117 124 @reactor.oneshot_timer 10, method(:write_queue_to_socket)
118 125 break
119 126 end
6 lib/zm/server/rep.rb
@@ -18,7 +18,11 @@ module XREP
18 18 include Base
19 19
20 20 def on_readable socket, messages, envelope
21   - @on_read.call socket, messages, envelope
  21 + if @reactor.reactor_thread?
  22 + @on_read.call socket, messages, envelope
  23 + else
  24 + STDERR.print("error, #{self.class} Thread violation! Expected [#{Reactor.current_thread_name}] but got [#{Thread.current['reactor-name']}]\n")
  25 + end
22 26 close_messages(envelope + messages)
23 27 end
24 28
6 lib/zm/server/req.rb
@@ -18,7 +18,11 @@ module XREQ
18 18 include Base
19 19
20 20 def on_readable socket, messages, envelope
21   - @on_read.call socket, messages, envelope
  21 + if @reactor.reactor_thread?
  22 + @on_read.call socket, messages, envelope
  23 + else
  24 + STDERR.print("error, #{self.class} Thread violation! Expected [#{Reactor.current_thread_name}] but got [#{Thread.current['reactor-name']}]\n")
  25 + end
22 26 close_messages(envelope + messages)
23 27 end
24 28
11 lib/zm/sockets/base.rb
@@ -108,8 +108,8 @@ def connect address
108 108 # ZMQ::Util.errno to check for errors.
109 109 #
110 110 def send_message message, multipart = false
111   - flag = multipart ? (ZMQ::SNDMORE | ZMQ::Util.nonblocking_flag) : ZMQ::Util.nonblocking_flag
112   - @raw_socket.send(message, flag)
  111 + flag = multipart ? (ZMQ::SNDMORE | ZMQ::NonBlocking) : ZMQ::NonBlocking
  112 + @raw_socket.sendmsg(message, flag)
113 113 end
114 114
115 115 # Convenience method to send a string on the socket. It handles
@@ -119,7 +119,7 @@ def send_message message, multipart = false
119 119 # details on the error.
120 120 #
121 121 def send_message_string message, multipart = false
122   - @raw_socket.send_string message, ZMQ::Util.nonblocking_flag | (multipart ? ZMQ::SNDMORE : 0)
  122 + @raw_socket.send_string message, ZMQ::NonBlocking | (multipart ? ZMQ::SNDMORE : 0)
123 123 end
124 124
125 125 # Convenience method for sending a multi-part message. The
@@ -152,7 +152,7 @@ def resume_read
152 152
153 153 while ZMQ::Util.resultcode_ok?(rc) && more
154 154 parts = []
155   - rc = @raw_socket.recvmsgs parts, ZMQ::Util.nonblocking_flag
  155 + rc = @raw_socket.recvmsgs parts, ZMQ::NonBlocking
156 156
157 157 if ZMQ::Util.resultcode_ok?(rc)
158 158 @handler.on_readable self, parts
@@ -161,7 +161,10 @@ def resume_read
161 161 if eagain?
162 162 more = false
163 163 elsif valid_socket_error?
  164 + STDERR.print("#{self.class} Received a valid socket error [#{ZMQ::Util.errno}], [#{ZMQ::Util.error_string}]\n")
164 165 @handler.on_readable_error self, rc
  166 + else
  167 + STDERR.print("#{self.class} Unhandled read error [#{ZMQ::Util.errno}], [#{ZMQ::Util.error_string}]\n")
165 168 end
166 169 end
167 170 end
5 lib/zm/sockets/envelope_help.rb
@@ -28,7 +28,7 @@ def resume_read
28 28
29 29 while ZMQ::Util.resultcode_ok?(rc) && more
30 30 parts, envelope = [], []
31   - rc = @raw_socket.recv_multipart parts, envelope, ZMQ::Util.nonblocking_flag
  31 + rc = @raw_socket.recv_multipart parts, envelope, ZMQ::NonBlocking
32 32
33 33 if ZMQ::Util.resultcode_ok?(rc)
34 34 @handler.on_readable self, parts, envelope
@@ -37,7 +37,10 @@ def resume_read
37 37 if eagain?
38 38 more = false
39 39 elsif valid_socket_error?
  40 + STDERR.print("#{self.class} Received a valid socket error [#{ZMQ::Util.errno}], [#{ZMQ::Util.error_string}]\n")
40 41 @handler.on_readable_error self, rc
  42 + else
  43 + STDERR.print("#{self.class} Unhandled read error [#{ZMQ::Util.errno}], [#{ZMQ::Util.error_string}]\n")
41 44 end
42 45 end
43 46 end
2  lib/zm/sockets/rep.rb
@@ -50,7 +50,7 @@ def initialize context, handler
50 50
51 51 # Attach a handler to the REP socket.
52 52 #
53   - # A REP socket must alternate between recv.send (i.e.
  53 + # A REP socket must alternate between recv.sendmsg (i.e.
54 54 # it cannot receive twice in a row without an intervening
55 55 # send). This socket expects its +handler+ to
56 56 # implement at least the #on_readable method. This method

0 comments on commit 5ad1ab3

Please sign in to comment.
Something went wrong with that request. Please try again.