ffi-rzmq 0.5.1 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,2 @@
1
+ *.rbc
2
+ *.swp
data/History.txt CHANGED
@@ -1,3 +1,38 @@
1
+ == 0.6.0 / 20100911
2
+ * API Change! Modified ZMQ::Message by removing automatic memory
3
+ management. While doing some performance tests I saw that
4
+ defining/undefining the finalizer added 15-30% processing
5
+ overhead on the latency test. So, I split this functionality
6
+ out to a subclass called ZMQ::ManagedMemory. Any existing code
7
+ that relies on the default Message class to clean up after itself
8
+ will now have a memory leak. Explicitly call #close on these
9
+ received messages *unless* they are sent out again. The #send
10
+ method automatically closes call on your behalf.
11
+
12
+ * Rubinius/rbx compatibility! Requires an rbx code pull from git
13
+ from 20100911 or later to get the necessary code fixes.
14
+
15
+ * Modify Message to use the @pointer directly rather than indirectly
16
+ via the @struct object. Provides better compatibility for rbx
17
+ since rbx does not yet support the FFI pointer protocol for structs
18
+ like the FFI gem.
19
+
20
+ * Modify Message to pass libC's free function for disposing of message
21
+ data buffers rather than trying to callback into ruby code to
22
+ do the same thing. External thread callbacks into ruby code will
23
+ never be supported in rbx; this also improves compatibility and
24
+ performance with MRI and JRuby. (In particular, MRI enqueues these
25
+ kinds of callbacks and spawns a *new* thread to execute each one.
26
+ Avoiding the ruby callback entirely eliminates this extra work
27
+ for MRI.)
28
+
29
+ * Modify FFI wrapper to capture the libC dynamic library to fetch
30
+ a pointer to the free function.
31
+
32
+ * Modify FFI wrapper to remove the FFI::Function callback used
33
+ by Message. It's no longer necessary since we now use free
34
+ directly.
35
+
1
36
  == 0.5.1 / 20100830
2
37
  * Works with 0mq 2.0.8 release.
3
38
 
data/README.rdoc CHANGED
@@ -6,8 +6,8 @@ ffi-rzmq
6
6
 
7
7
  This gem wraps the ZeroMQ networking library using the ruby FFI (foreign
8
8
  function interface). It's a pure ruby wrapper so this gem can be loaded
9
- and run by any ruby runtime that supports FFI. Right now that means
10
- MRI 1.9.x and JRuby.
9
+ and run by any ruby runtime that supports FFI. That's all of them:
10
+ MRI 1.8.7+, 1.9.x, Rubinius and JRuby.
11
11
 
12
12
  The impetus behind this library was to provide support for ZeroMQ in
13
13
  JRuby which has native threads. Unlike MRI, MacRuby, IronRuby and
@@ -18,31 +18,13 @@ environment to run this library.
18
18
 
19
19
  == PERFORMANCE
20
20
 
21
- Using FFI introduces some minimal overhead. In my latest benchmarks,
22
- I was unable to detect any measurable performance drop due to FFI
23
- regardless of which ruby runtime was tested. JRuby had the best overall
24
- performance (with --server) once it warmed up. MRI behaved quite well
25
- too and has a much lower memory footprint than JRuby (use the trunk
26
- version of the FFI bindings to fix several threading issues affecting
27
- MRI).
21
+ Check out the latest performance results:
28
22
 
29
- Due to odd interactions between 0mq threads and the GIL (Giant Interpreter
30
- Lock), I recommend using JRuby 1.5.1 or later. JRuby has no GIL.
31
-
32
- The hope is that in a multi-threaded environment that JRuby's native
33
- threads and lack of GIL will provide the best ZeroMQ performance using
34
- the ruby language.
35
-
36
- Unfortunately, there is really no reasonable way to support zero-copy
37
- using Ruby. Any time data needs to be accessible by the Ruby runtime,
38
- it must be copied out of native memory to the Ruby heap. The same is
39
- true for the reverse. I am investigating ways to "pin" primitive arrays
40
- in memory for Rubinius and JRuby to achieve zero-copy, but that is
41
- a ways off.
23
+ http://www.zeromq.org/bindings:ruby-ffi
42
24
 
43
25
  == FEATURES/PROBLEMS:
44
26
 
45
- This gem is brand new and has minimal tests. I'm certain there are a
27
+ This gem needs more tests. I'm certain there are a
46
28
  ton of bugs, so please open issues for them here or fork this project,
47
29
  fix them, and send me a pull request.
48
30
 
@@ -52,10 +34,12 @@ in MRI 1.8.x is irrevocably broken (according to the brains behind the
52
34
  Ruby FFI project) so tread carefully.
53
35
 
54
36
  Running this gem with MRI 1.9.x may also exhibit some hangs particularly at
55
- shutdown. This is due to a threading interaction between the 0mq library
56
- and MRI's threads. Apparently MRI does not "adopt" external threads when
57
- they make a call into the MRI runtime so some deadlocks may occur due to
58
- the GIL not getting released. Fixes for this should be sent to ruby-core.
37
+ shutdown. This is due to a signaling interaction between the 0mq library
38
+ and MRI. If the 0mq library is blocked on a call and the program receives
39
+ a signal (e.g. SIGINT), the Ruby runtime has no opportunity to run its signal
40
+ handler and process it. The 2.1.x branch of 0mq resolves this problem by
41
+ interrupting the blocking call and returning EINTR. The 2.0.x branch will
42
+ not get this fix because the API change breaks backward compatibility.
59
43
 
60
44
  All features are implemented with the exception of the 0mq devices
61
45
  (forwarder, queue, streamer). For implementations of these devices, see
@@ -79,7 +63,7 @@ Client code:
79
63
  message_size = ARGV[1].to_i
80
64
  roundtrip_count = ARGV[2].to_i
81
65
 
82
- ctx = ZMQ::Context.new 1
66
+ ctx = ZMQ::Context.new
83
67
  s = ctx.socket ZMQ::REP
84
68
  s.setsockopt(ZMQ::HWM, 100)
85
69
  s.bind(bind_to)
@@ -107,7 +91,7 @@ Server code:
107
91
  message_size = ARGV[1].to_i
108
92
  roundtrip_count = ARGV[2].to_i
109
93
 
110
- ctx = ZMQ::Context.new 1
94
+ ctx = ZMQ::Context.new
111
95
  s = ctx.socket ZMQ::REQ
112
96
  s.connect(connect_to)
113
97
 
@@ -123,7 +107,7 @@ Server code:
123
107
 
124
108
  == REQUIREMENTS:
125
109
 
126
- * 0mq 2.0.7 or 2.0.8
110
+ * 0mq 2.0.8 or 2.0.9
127
111
 
128
112
  The ZeroMQ library must be installed on your system in a well-known location
129
113
  like /usr/local/lib. This is the default for new ZeroMQ installs.
@@ -0,0 +1,163 @@
1
+ = Examples
2
+
3
+ == Requirements
4
+
5
+ 1. Installed gem
6
+
7
+ All of the examples assume the gem has been successfully installed.
8
+
9
+ 2. Installed libzmq library
10
+
11
+ The ZeroMQ C libraries need to be downloaded, compiled and installed separately from the gem. Please see http://www.zeromq.org/area:download for links to the downloadable files along with some simple installation instructions. Also, be sure to check the FAQ if you run into problems with compiling.
12
+
13
+ 3. Two terminal windows
14
+
15
+ ZeroMQ is used to build network applications. At minimum, there is a "client" application and a "server" application that talk to each other over the network, IPC or an internal thread queue. For the sake of code simplicity, these programs are in separate files and need to be executed in different windows.
16
+
17
+ == Latency Test
18
+
19
+ The examples include a latency performance test. The example sets up a pair of REQ/REP sockets and send a message back and forth as fast as possible. There is only a single message in flight at any given moment. The time required to send the message the requested number of times determines overall single-message latency for this type of socket.
20
+
21
+ ==== Files
22
+
23
+ * local_lat.rb
24
+ * remote_lat.rb
25
+
26
+ ==== Arguments
27
+
28
+ The remote_lat.rb program takes 3 arguments:
29
+
30
+ [bind_to] Requires a transport string of the format "transport"://"endpoint"<:><port>. For example, tcp://127.0.0.1:5555
31
+
32
+ [message size] Size of each message measured in bytes. Allowable range is 1 to 2^(64-1).
33
+
34
+ [message count] The number of round-trips used for the latency measurements. Allowable range is 1 to 2^(64-1).
35
+
36
+
37
+ The local_lat.rb program also takes 3 arguments. They should exactly mirror the arguments given to remote_lat.rb.
38
+
39
+
40
+ ==== Execution
41
+
42
+ In one of the terminals, start up the remote_lat.rb program first. It *must* be launched first so that it can be ready and waiting for the first message sent by the local_lat.rb program.
43
+
44
+ % ruby remote_lat.rb tcp://127.0.0.1:5555 1024 100_000
45
+
46
+ In the second terminal window, start up the local_lat.rb program too.
47
+
48
+ % ruby local_lat.rb tcp://127.0.0.1:5555 1024 100_000
49
+
50
+ On a relatively new system, it can run 100k messages in under 30 seconds. When complete, the remote_lat.rb program prints out a few statistics and exits. The local_lat.rb program does not print any message when it exits.
51
+
52
+ Running with a larger "message count" will yield a more accurate latency measurement since nearly all Ruby runtimes require a little warm up time to hit their stride. I recommend 100k as a minimum while 10 million is better for determining a true measure.
53
+
54
+ On a desktop computer purchased in 2007, all of the Ruby runtimes report a latency of approximately 110 microseconds per message. For comparison, the pure C latency test reports approximately 88 microseconds of latency.
55
+
56
+
57
+
58
+ == Zero Copy Latency Test
59
+
60
+ The examples include a zero copy latency performance test. The example sets up a pair of REQ/REP sockets and send a message back and forth as fast as possible. There is only a single message in flight at any given moment. The time required to send the message the requested number of times determines overall single-message latency for this type of socket.
61
+
62
+ The difference between this test and the first latency test has to do with the management of the sent and received messages. This test does not examine or copy the contents of the message at all. Instead, its mere presence is sufficient to echo it back to the other program. Therefore, this test is doing a lot less work for each message send & receive than the first latency test. Also, this test more perfectly mirrors the work being performed by the C latency test.
63
+
64
+ ==== Files
65
+
66
+ * local_lat_zerocopy.rb
67
+ * remote_lat_zerocopy.rb
68
+
69
+ ==== Arguments
70
+
71
+ The remote_lat_zerocopy.rb program takes 3 arguments:
72
+
73
+ [bind_to] Requires a transport string of the format "transport"://"endpoint"<:><port>. For example, tcp://127.0.0.1:5555
74
+
75
+ [message size] Size of each message measured in bytes. Allowable range is 1 to 2^(64-1).
76
+
77
+ [message count] The number of round-trips used for the latency measurements. Allowable range is 1 to 2^(64-1).
78
+
79
+
80
+ The local_lat_zerocopy.rb program also takes 3 arguments. They should exactly mirror the arguments given to remote_lat_zerocopy.rb.
81
+
82
+
83
+ ==== Execution
84
+
85
+ In one of the terminals, start up the remote_lat_zerocopy.rb program first. It *must* be launched first so that it can be ready and waiting for the first message sent by the local_lat_zerocopy.rb program.
86
+
87
+ % ruby remote_lat_zerocopy.rb tcp://127.0.0.1:5555 1024 100_000
88
+
89
+ In the second terminal window, start up the local_lat.rb program too.
90
+
91
+ % ruby local_lat_zerocopy.rb tcp://127.0.0.1:5555 1024 100_000
92
+
93
+ On a relatively new system, it can run 100k messages in under 30 seconds. When complete, the remote_lat_zerocopy.rb program prints out a few statistics and exits. The local_lat_zerocopy.rb program does not print any message when it exits.
94
+
95
+ Running with a larger "message count" will yield a more accurate latency measurement since nearly all Ruby runtimes require a little warm up time to hit their stride. I recommend 100k as a minimum while 10 million is better for determining a true measure.
96
+
97
+ On a desktop computer purchased in 2007, all of the Ruby runtimes report a latency of approximately 95 microseconds per message. For comparison, the pure C latency test reports approximately 88 microseconds of latency.
98
+
99
+
100
+
101
+ == Throughput Test
102
+
103
+ The examples include a throughput performance test. The example sets up a pair of PUB/SUB sockets and publish messages as fast as possible to a subscriber listening for every message. The publisher can send much faster than the subscriber can retrieve messages.
104
+
105
+ Since the publisher completes first, that program contains a "sleep" statement to keep the program alive and active which gives the subscriber more time to consume the queued messages. When the publisher exits, the socket closes and discards all remaining messages.
106
+
107
+ The subscriber prints some statistics when it exits.
108
+
109
+ ==== Files
110
+
111
+ * local_throughput.rb
112
+ * remote_throughput.rb
113
+
114
+ ==== Arguments
115
+
116
+ The local_throughput.rb program takes 3 arguments:
117
+
118
+ [bind_to] Requires a transport string of the format "transport"://"endpoint"<:><port>. For example, tcp://127.0.0.1:5555
119
+
120
+ [message size] Size of each message measured in bytes. Allowable range is 1 to 2^(64-1).
121
+
122
+ [message count] The number of round-trips used for the latency measurements. Allowable range is 1 to 2^(64-1).
123
+
124
+
125
+ The remote_throughput.rb program also takes 3 arguments. They should exactly mirror the arguments given to local_throughput.rb.
126
+
127
+
128
+ ==== Execution
129
+
130
+ In one of the terminals, start up the local_throughput.rb program first. It *must* be launched first so that it can be ready and waiting for the first message published by the remote_throughput.rb program.
131
+
132
+ % ruby local_throughput.rb tcp://127.0.0.1:5555 1024 100_000
133
+
134
+ In the second terminal window, start up the second program.
135
+
136
+ % ruby remote_throughput.rb tcp://127.0.0.1:5555 1024 100_000
137
+
138
+ On a relatively new system, it can run 100k messages in under 10 seconds. When complete, the local_throughput.rb program prints out a few statistics and exits. The remote_throughput.rb program does not print any message when it exits.
139
+
140
+ Running with a larger "message count" will yield a more accurate latency measurement since nearly all Ruby runtimes require a little warm up time to hit their stride. I recommend 100k as a minimum while 1 million is better for determining a true measure. NOTE! The publisher can send much faster than the subscriber so the publisher's queue will grow very rapidly in RAM. For 1 million messages (or more) this can consume hundreds of megabytes or gigabytes of RAM. On my system, sending 10 million messages requires 10 GB of RAM before the subscriber can catch up.
141
+
142
+ On a desktop computer purchased in 2007, all of the Ruby runtimes report a throughput of approximately 150k messages per second. For comparison, the pure C throughput test reports approximately 260k messages per second.
143
+
144
+
145
+
146
+ == Poll
147
+
148
+ For a reasonable example of using zmq_poll(), take a look at the reqrep_poll.rb program. It illustrates the use of zmq_poll(), as wrapped by the Ruby library, for detecting and responding to read and write events recorded on sockets. It also shows how to use ZMQ::NO_BLOCK for non-blocking send and receive.
149
+
150
+ ==== Files
151
+
152
+ * reqrep_poll.rb
153
+
154
+ ==== Arguments
155
+
156
+ None.
157
+
158
+ ==== Execution
159
+
160
+ This program is completely self-contained, so it only requires a single terminal window for execution.
161
+
162
+ % ruby reqrep_poll.rb
163
+
@@ -0,0 +1,43 @@
1
+ require 'rubygems'
2
+ require 'ffi-rzmq'
3
+
4
+ if ARGV.length != 3
5
+ puts "usage: local_thr <bind-to> <message-size> <message-count>"
6
+ Process.exit
7
+ end
8
+
9
+ bind_to = ARGV[0]
10
+ message_size = ARGV[1].to_i
11
+ message_count = ARGV[2].to_i
12
+
13
+ ctx = ZMQ::Context.new
14
+ s = ZMQ::Socket.new ctx.pointer, ZMQ::SUB
15
+ s.setsockopt ZMQ::SUBSCRIBE, ""
16
+
17
+ s.bind bind_to
18
+
19
+ msg = ZMQ::Message.new
20
+ rc = s.recv msg
21
+
22
+ start_time = Time.now
23
+
24
+ i = 1
25
+ while i < message_count
26
+ result_code = s.recv msg
27
+ i += 1
28
+ end
29
+
30
+ end_time = Time.now
31
+
32
+ elapsed = (end_time.to_f - start_time.to_f) * 1000000
33
+ if elapsed == 0
34
+ elapsed = 1
35
+ end
36
+
37
+ throughput = message_count * 1000000 / elapsed
38
+ megabits = throughput * message_size * 8 / 1000000
39
+
40
+ puts "message size: %i [B]" % message_size
41
+ puts "message count: %i" % message_count
42
+ puts "mean throughput: %i [msg/s]" % throughput
43
+ puts "mean throughput: %.3f [Mb/s]" % megabits
@@ -0,0 +1,27 @@
1
+ require 'rubygems'
2
+ require 'ffi-rzmq'
3
+
4
+ if ARGV.length != 3
5
+ puts "usage: remote_thr <connect-to> <message-size> <message-count>"
6
+ Process.exit
7
+ end
8
+
9
+ connect_to = ARGV[0]
10
+ message_size = ARGV[1].to_i
11
+ message_count = ARGV[2].to_i
12
+
13
+ ctx = ZMQ::Context.new
14
+ s = ZMQ::Socket.new ctx.pointer, ZMQ::PUB
15
+
16
+ s.connect connect_to
17
+
18
+ contents = "#{'0'*message_size}"
19
+
20
+ i = 0
21
+ while i < message_count
22
+ msg = ZMQ::Message.new contents
23
+ s.send msg
24
+ i += 1
25
+ end
26
+
27
+ sleep 10
@@ -73,7 +73,7 @@ until @done do
73
73
  reply = "reply " + sock.identity.upcase + " #{time}"
74
74
  puts "sent reply [#{reply}], #{time}"
75
75
  sock.send_string reply
76
- #@done = true
76
+ @done = true
77
77
  poller.register_readable s1
78
78
  end
79
79
  end
data/ffi-rzmq.gemspec CHANGED
@@ -2,15 +2,15 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{ffi-rzmq}
5
- s.version = "0.5.1"
5
+ s.version = "0.6.0"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Chuck Remes"]
9
- s.date = %q{2010-08-30}
9
+ s.date = %q{2010-09-21}
10
10
  s.description = %q{This gem wraps the ZeroMQ networking library using the ruby FFI (foreign
11
11
  function interface). It's a pure ruby wrapper so this gem can be loaded
12
- and run by any ruby runtime that supports FFI. Right now that means
13
- MRI 1.9.x and JRuby.
12
+ and run by any ruby runtime that supports FFI. That's all of them:
13
+ MRI 1.8.7+, 1.9.x, Rubinius and JRuby.
14
14
 
15
15
  The impetus behind this library was to provide support for ZeroMQ in
16
16
  JRuby which has native threads. Unlike MRI, MacRuby, IronRuby and
@@ -19,20 +19,20 @@ code from outside extensions. ZeroMQ is heavily threaded, so until the
19
19
  other runtimes remove their GIL, JRuby will likely be the best
20
20
  environment to run this library.}
21
21
  s.email = %q{cremes@mac.com}
22
- s.extra_rdoc_files = ["History.txt", "README.rdoc", "version.txt"]
23
- s.files = [".bnsignore", "History.txt", "README.rdoc", "Rakefile", "examples/async_req_rep.rb", "examples/local_lat.rb", "examples/local_lat_zerocopy.rb", "examples/publish_subscribe.rb", "examples/remote_lat.rb", "examples/remote_lat_zerocopy.rb", "examples/reqrep_poll.rb", "examples/request_response.rb", "examples/t", "examples/xreqxrep_poll.rb", "ffi-rzmq.gemspec", "lib/ffi-rzmq.rb", "lib/ffi-rzmq/context.rb", "lib/ffi-rzmq/exceptions.rb", "lib/ffi-rzmq/message.rb", "lib/ffi-rzmq/poll.rb", "lib/ffi-rzmq/poll_items.rb", "lib/ffi-rzmq/socket.rb", "lib/ffi-rzmq/wrapper.rb", "lib/ffi-rzmq/zmq.rb", "spec/context_spec.rb", "spec/reqrep_spec.rb", "spec/socket_spec.rb", "spec/spec_helper.rb", "version.txt"]
22
+ s.extra_rdoc_files = ["History.txt", "README.rdoc", "examples/README.rdoc", "version.txt"]
23
+ s.files = [".gitignore", "History.txt", "README.rdoc", "Rakefile", "examples/README.rdoc", "examples/local_lat.rb", "examples/local_lat_zerocopy.rb", "examples/local_throughput.rb", "examples/publish_subscribe.rb", "examples/remote_lat.rb", "examples/remote_lat_zerocopy.rb", "examples/remote_throughput.rb", "examples/reqrep_poll.rb", "examples/request_response.rb", "examples/xreqxrep_poll.rb", "ffi-rzmq.gemspec", "lib/ffi-rzmq.rb", "lib/ffi-rzmq/context.rb", "lib/ffi-rzmq/exceptions.rb", "lib/ffi-rzmq/message.rb", "lib/ffi-rzmq/poll.rb", "lib/ffi-rzmq/poll_items.rb", "lib/ffi-rzmq/socket.rb", "lib/ffi-rzmq/wrapper.rb", "lib/ffi-rzmq/zmq.rb", "spec/context_spec.rb", "spec/message_spec.rb", "spec/pushpull_spec.rb", "spec/reqrep_spec.rb", "spec/socket_spec.rb", "spec/spec_helper.rb", "version.txt"]
24
24
  s.homepage = %q{http://github.com/chuckremes/ffi-rzmq}
25
25
  s.rdoc_options = ["--main", "README.rdoc"]
26
26
  s.require_paths = ["lib"]
27
27
  s.rubyforge_project = %q{ffi-rzmq}
28
- s.rubygems_version = %q{1.3.6}
28
+ s.rubygems_version = %q{1.3.7}
29
29
  s.summary = %q{This gem wraps the ZeroMQ networking library using the ruby FFI (foreign function interface)}
30
30
 
31
31
  if s.respond_to? :specification_version then
32
32
  current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
33
33
  s.specification_version = 3
34
34
 
35
- if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
35
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
36
36
  s.add_development_dependency(%q<bones>, [">= 3.4.7"])
37
37
  else
38
38
  s.add_dependency(%q<bones>, [">= 3.4.7"])
@@ -11,10 +11,12 @@ module ZMQ
11
11
 
12
12
  attr_reader :context, :pointer
13
13
 
14
- # Recommended to just pass 1 for +io_threads+
15
- # since most programs are not heavily threaded. The rule of thumb
16
- # is to make +io_threads+ equal to the number of application
17
- # threads that will be accessing 0mq sockets within this context.
14
+ # Recommended to use the default for +io_threads+
15
+ # since most programs will not saturate I/O.
16
+ #
17
+ # The rule of thumb is to make +io_threads+ equal to the number
18
+ # gigabits per second that the application will produce.
19
+ #
18
20
  # The +io_threads+ number specifies the size of the thread pool
19
21
  # allocated by 0mq for processing incoming/outgoing messages.
20
22
  #
@@ -23,12 +25,19 @@ module ZMQ
23
25
  # live within a context. Sockets in one context may not be accessed
24
26
  # from another context; doing so raises an exception.
25
27
  #
28
+ # Also, Sockets should *only* be accessed from the thread where they
29
+ # were first created. Do *not* pass sockets between threads; pass
30
+ # in the context and allocate a new socket per thread.
31
+ #
26
32
  # To connect sockets between contexts, use +inproc+ or +ipc+
27
- # transport and set up a 0mq socket between them.
33
+ # transport and set up a 0mq socket between them. This is also the
34
+ # recommended technique for allowing sockets to communicate between
35
+ # threads.
28
36
  #
29
- # May raise a #ContextError.
37
+ # Will raise a #ContextError when the native library context cannot be
38
+ # be allocated.
30
39
  #
31
- def initialize io_threads
40
+ def initialize io_threads = 1
32
41
  @sockets ||= []
33
42
  @context = LibZMQ.zmq_init io_threads
34
43
  @pointer = @context
@@ -44,7 +53,8 @@ module ZMQ
44
53
  #
45
54
  # Returns nil.
46
55
  #
47
- # May raise a #ContextError.
56
+ # Will raise a #ContextError when the call fails. Failures occur when
57
+ # the context has somehow become null (indicates a libzmq bug).
48
58
  #
49
59
  def terminate
50
60
  unless @context.nil? || @context.null?