pax_global_header00006660000000000000000000000064122242146470014516gustar00rootroot0000000000000052 comment=e8f1b0566d9fae250436336577919addbbbc27d0 ruby-ffi-rzmq-1.0.3/000077500000000000000000000000001222421464700142315ustar00rootroot00000000000000ruby-ffi-rzmq-1.0.3/.bnsignore000066400000000000000000000007111222421464700162170ustar00rootroot00000000000000# The list of files that should be ignored by Mr Bones. # Lines that start with '#' are comments. # # A .gitignore file can be used instead by setting it as the ignore # file in your Rakefile: # # Bones { # ignore_file '.gitignore' # } # # For a project with a C extension, the following would be a good set of # exclude patterns (uncomment them if you want to use them): # *.[oa] # *~ announcement.txt coverage doc pkg *.tmproj *.gem *.rbc *.html ruby-ffi-rzmq-1.0.3/.gitignore000066400000000000000000000000611222421464700162160ustar00rootroot00000000000000*.gem .bundle Gemfile.lock pkg/* *.rbc .redcar/ ruby-ffi-rzmq-1.0.3/.travis.yml000066400000000000000000000003661222421464700163470ustar00rootroot00000000000000before_install: sudo apt-get install libzmq3-dev script: bundle exec rspec language: ruby rvm: - 1.9.3 - 2.0.0 - ruby-head - jruby-19mode - jruby-head - rbx-19mode matrix: allow_failures: - rvm: ruby-head - rvm: jruby-head ruby-ffi-rzmq-1.0.3/AUTHORS.txt000066400000000000000000000007761222421464700161310ustar00rootroot00000000000000Chuck Remes, github: chuckremes Andrew Cholakian, github: andrewvc Ar Vicco, github: arvicco Ben Mabey, github: bmabey Julien Ammous, github: schmurfy Zachary Belzer, github: zbelzer Cory Forsyth, github: bantic Stefan Kaes, github: skaes Dmitry Ustalov, github: eveel Patrik Sundberg, github: sundbp Pawel Pacana, github: pawelpacana Brian Ford, github: brixen Nilson Santos F. Jr., github: nilsonfsj Simon Chiang , github: thinkerbot Harlow Ward, github: harlow Paul Chechetin, github: paulcheruby-ffi-rzmq-1.0.3/Gemfile000066400000000000000000000001201222421464700155150ustar00rootroot00000000000000source "http://rubygems.org" gemspec gem "jruby-openssl", :platform => :jruby ruby-ffi-rzmq-1.0.3/History.txt000066400000000000000000000417611222421464700164440ustar00rootroot00000000000000== 1.0.3 / 20131003 * Fixes for issues #96 and #97. Thanks to Paul Chechetin (paulche) for the issues and patches. == 1.0.2 / 20130929 * Fix for issue #94. Now includes local_path when searching for a libzmq to load. * Fix for issue #92. Context and Socket finalizers now track the process' PID to handle a special case when forking a process. Thanks to thinkerbot for the issue and a fix. (Spec is disabled on JRuby since it does not support fork().) * Fix a few RSpec deprecation warnings related to expectations on specific Exception types. == 1.0.1 / 20130318 * Fix for issue #77 was not included in 1.0.0 release by mistake. * Add MIR 2.0.0 to travis runs. * Add support for LAST_ENDPOINT and MULTICAST_HOPS to Socket#getsockopt. == 1.0.0 / 20130109 * Fix for issue #74 (send_multiple improperly handled single part messages). * Fix for issue #77 (unbind and disconnect). * Fix for issue #75 (socket monitor events). * The API is stable. Releasing 1.0. == 0.9.7 / 20121221 * BROKE THE API. ZMQ::Poller#register and ZMQ::Poller#deregister don't take fd argument anymore. ZMQ::Poller#readables and ZMQ::Poller#writables return pollables instead of just fd when pollable is other than ZMQ socket. ZMQ::Poller#register now returns nil instead of false when no pollable or events to register to are given, which is consistent with rest of api. Thanks to Pawel Pacana for this code contribution. * Added support in ZMQ::Poller for pollables responding to fileno and socket. Standard Ruby Sockets and IOs can be now registered in poller to listen for events. Thanks to Pawel Pacana for this code contribution. * Fixed a bug in ZMQ::Poller#deregister where it would raise exception when trying to deregister already closed ZMQ socket. Issue 59. * Improved specs to use random inproc socket address to avoid race conditions between tests under same context. * Added continous integration for all supported platforms on Travis-CI. Thanks to Pawel Pacana for this code contribution. * Signed up for codeclimate.com and made some code changes to get a better "grade" from it. * Modified the library to *always* load the 'ffi' library upon startup. It used to be conditional for Rubinius. Thanks to brixen for the change. * There was a little bit of churn on the zmq "monitor" api. Thanks to Nilson Santos F. Jr. for some code to conditionally attach to the appropriate api depending on library version. == 0.9.6 / 20120808 * Never released 0.9.5 as a gem. It was available via github only. * Improved error message when DLL loading fails on Windows. * Added support for 0mq 2.2. Support for 2.1 might be getting shakey... patches to make it fully support 2.1 (assuming it's even broken at all) are welcome. * Added support for 0mq 3.2 (no support for 3.0 or 3.1). Not all methods are exposed yet. For example, setting values on the context after it has been created is not supported; instead, pass the correct keys (:io_threads and :max_sockets) to the call to Context.create or Context#new. * Reduced spec running time from 30+ seconds to under 1 by eliminating most uses of "sleep." It now polls sockets to wait for message delivery. It also uses a technique of binding an inproc transport and busy-looping on the connect side until it succeeds. These techniques both allowed me to eliminate most uses of sleep. * Some changes to support usage on Win7x64 with a 64-bit libzmq DLL. == 0.9.5 / 20120119 * BROKE THE API. In 0mq 2.x, there were two functions zmq_send() and zmq_recv(). As of 3.x, those functions were renamed zmq_sendmsg() and zmq_recvmsg(). As everyone starts moving to 0mq 3.x, it doesn't make sense to make the code break with 2.x. So, I'm breaking this binding so that it always uses sendmsg/recvmsg and eliminates the original send/recv methods. Sorry! This is likely to be the last non-backward-compatible API breakage. Release 1.0 is around the corner and the API will be stable (I follow semantic versioning). * Introduced ZMQ::NonBlocking. This flag returns the correct value to set a socket in non-blocking mode when sending/receiving. This hides the differences between 0mq 2.x and 3.x since the constant names have changed. == 0.9.4 / 20120102 * Fixed bug in Poller#delete. Added specs to catch a regression. In short, a socket that was deleted from the Poller set wasn't always actually *removed* from the array. This led to a closed socket being part of the pollset which would return errno 38. This took about 4 days to find. == 0.9.3 / 20111214 * Performance optimizations for #getsockopt. * Fixed Message#copy and Message#move. They didn't work before. * Cache LibZM::Msg.size in the ZMQ::Message class so that initialization can skip recalculating what is effectively a constant value. This speeds up ZMQ::Message instantiation by 5 to 10%. Wow. * Modified calls to #super to use explicit arguments (e.g. #super()) because otherwise the Ruby runtime has to (at runtime) dig out the arguments that are expected to be passed up the chain. By explicitly listing the args and using parentheses, the runtime can avoid that work and dispatch directly. This effects all Ruby runtimes, but it was through the work of Evan Phoenix that I figured this out. Results in a 2-5% speedup on method dispatch. == 0.9.2 / 20111115 * Removed all references to the version4 API. * Dropped support for 3.0.x and added support for 3.1.x. The 0mq community has pretty much voted to abandon the path taken in 3.0 so the 3.1 branch is the API that will be supported. * Fixed a bug in Poller#delete where it would erroneously return false even when it successfully deleted a socket. Issue 46. * All specs pass for 2.1.x API. * 3 specs fail when run with 3.1 API; these are due to bugs in the 0mq library and are *not* ffi-rzmq bugs. * Rescue LoadErrors when loading libzmq. Print a warning about adding libzmq.dll to the Windows PATH for that platform. Print the search paths where the gem looks for libzmq. == 0.9.1 / 20111027 * Moved LibC and LibZMQ into the ZMQ module namespace. Necessary to avoid namespace collisions with other libraries that also use the constants LibC and/or LibZMQ. * Fixed a bug where file descriptors registered on Poll were never returned as readable or writable. * Added Socket#recv_multipart. This returns the message body and return address envelope as separate arrays. Only to be used with XREQ/XREP/DEALER/ROUTER sockets. == 0.9.0 / 20110930 * Changed the behavior of every method that used to produce exceptions. The methods now behave more like the C API functions. They return result codes instead of raising exceptions. Further, the "receive" methods on Socket now all take an empty string as a buffer to read the message into. This is a BREAKING CHANGE and is NOT backward compatible with earlier releases. I apologize for the inconvenience, but this API will be much easier to test/spec and maintain. It will also allow for the production of more logical code. * Major refactoring of Socket internals so that a single gem can support libzmq 2.x, 3.x and 4.x APIs without any user intervention. The correct libzmq version is detected at runtime and used to configure the Ruby classes to conform to the proper API. * Added Socket#recvmsgs as a convenience method for receiving a multipart message into an array of Messages. * Added support for new 0mq API introduced in the 3.0 branch. API mostly changed for sending and receiving messages with new POSIX-compliant send() and recv() functions. The original functions were renamed sendmsg() and recvmsg(). Additionally, most getsockopt() and setsockopt() calls now use an int (4 bytes) instead of a mish-mash of 32-bit and 64-bit values. For a full list of differences, visit the 0mq wiki page at: http://www.zeromq.org/docs:3-0-upgrade * Created a new ext/ directory so that users can copy the libzmq* library files directly into the gem for easier distribution. This path is checked *before* the usual system paths. * Rewrote all examples to use the revised API. == 0.8.2 / 20110728 * Fixed major bug with Socket#setsockopt when writing 8-byte longs. * Clarified a bit of logic for non-blocking sends. * Improved readability of exceptions. == 0.8.1 / 20110504 * Fixed bug where Socket#setsockopt was using a size from the current runtime to determine how many bytes to use for HWM, et al. This was incorrect. All of those socket options require 8 bytes. Discovered this while getting the code running under mingw on Windows using a 32-bit Ruby runtime. == 0.8.0 / 20110307 * API change! Socket#send_message no longer automatically calls Message#close on behalf of the user. The user is completely responsible for the lifecycle management of all buffers associated with the ZMQ::Message objects. This is a breaking change. If you want the old behavior (auto-close messages on send) then use the new Socket#send_and_close method which does as its name implies. * Fixed bug with type :size_t on Windows (thank you to arvicco) == 0.7.3 / 20110304 * Fixed a bug where we had a small memory leak. When closing a socket I forgot to release a small amount of native memory used as a cache for doing #getsockopt calls. * Util.minimum_api? didn't work. Fixed. * Added ROUTER/DEALER constants to reflect new naming for XREQ/XREP. XREQ and XREP remain aliased for backward compatibility. == 0.7.2 / 20110224 * Several minor refactorings to make the code intent clearer and to allow for better testing. In particular, the error condition checking for a non-blocking send/recv is much clearer. == 0.7.1 / 20110130 * Fixed 1.9.1 Binary Encoding bug when UTF8 set as default (Thanks schmurfy) * Improved rubinius compat for specs * Improved spec compatibility on linux == 0.7.0 / 20101222 * Improved performance of calls to Socket#getsockopt. There are usually a lot of calls passing RCVMORE, so we now cache those buffers instead of reallocating them every time. * Updated the docs on Poller#poll to warn about a possible busy-loop condition. * Fixed some more specs to conform with the 0mq 2.1 requirement that all sockets must be closed explicitly otherwise the program may hang on exit. == 0.6.1 / 20101127 * API Change! Moved the #version method from the Util module and made it a class method instead. Invoke as ZMQ::Util.version. Used for conditionally enabling certain features based upon the 0mq version that is loaded. * Preliminary support for the Windows platform. Patches supplied by arvicco. * Added support for FD and EVENTS socket options. These were added in 0mq 2.1.0. Patches + specs supplied by andrewvc. * Added support for LINGER, RECONNECT_IVL, BACKLOG and RECOVERY_IVL_MSEC socket options. * Conditionally re-enable the socket finalizer when we are running with 0mq 2.1.0 or later. * Drop support for MRI 1.8.x since the 'ffi' gem has dropped it as a supported Ruby runtime with its 1.0 release. No action is taken to prevent running with MRI 1.8.x but it won't be supported. * Misc. spec fixes. Need more specs! == 0.6.0 / 20100911 * API Change! Modified ZMQ::Message by removing automatic memory management. While doing some performance tests I saw that defining/undefining the finalizer added 15-30% processing overhead on the latency test. So, I split this functionality out to a subclass called ZMQ::ManagedMemory. Any existing code that relies on the default Message class to clean up after itself will now have a memory leak. Explicitly call #close on these received messages *unless* they are sent out again. The #send method automatically closes call on your behalf. * Rubinius/rbx compatibility! Requires an rbx code pull from git from 20100911 or later to get the necessary code fixes. * Modify Message to use the @pointer directly rather than indirectly via the @struct object. Provides better compatibility for rbx since rbx does not yet support the FFI pointer protocol for structs like the FFI gem. * Modify Message to pass libC's free function for disposing of message data buffers rather than trying to callback into ruby code to do the same thing. External thread callbacks into ruby code will never be supported in rbx; this also improves compatibility and performance with MRI and JRuby. (In particular, MRI enqueues these kinds of callbacks and spawns a *new* thread to execute each one. Avoiding the ruby callback entirely eliminates this extra work for MRI.) * Modify FFI wrapper to capture the libC dynamic library to fetch a pointer to the free function. * Modify FFI wrapper to remove the FFI::Function callback used by Message. It's no longer necessary since we now use free directly. == 0.5.1 / 20100830 * Works with 0mq 2.0.8 release. * Removed the socket finalizer. The current 0mq framework cannot handle the case where zmq_close is called on a socket that was created from another thread. Therefore, the garbage collection thread causes the framework to break. Version 2.1 (or later) should fix this 0mq limitation. * Misc fixes. See commits. == 0.5.0 / 20100606 * Updated the bindings to conform to the 0mq 2.0.7 release. Several parts of the API changed. * Updated all examples to use the new Context api. * Added Socket#getsockopt. * Added a Socket#identity and Socket#identity= method pair to allow for easy get/put on socket identities. Useful for async request/reply using XREQ/XREP sockets. * Added more specs (slowly but surely). * Support multi-part messages (new as of 2.0.7). I am unsure how to best support multi-part messages so the Message (and related) API may change in the future. Added Socket#more_parts?. * Lots of fixes. Many classes use finalizers to deallocate native memory when they go out of scope; be sure to use JRuby 1.5.1 or later to get important finalizer fixes. == 0.4.1 / 20100511 * I was misusing all of the FFI memory allocator classes. I now wrap libc and use malloc/free directly for creating buffers used by libzmq. == 0.4.0 / 20100510 * Changed the Socket#recv method signature to take an optional message object as its first argument. This allows the library user to allocate and pass in their own message object for the purposes of zero-copy. Original behavior was for the library to *always* allocate a new message object to receive a message into. Hopefully this is the last change required. * Modified the Socket constructor to take an optional hash as its final argument. It honors two keys; :receiver_klass and :sender_klass. Passing in a new constant for either (or both) keys will override the class used by Socket for allocating new Message objects. == 0.3.1 / 20100509 * Modified ZMQ::Message so we have both an UnmanagedMessage where memory management is manual via the #close method, and Message where memory management is automated via a finalizer method run during garbage collection. * Updated ZMQ::Message docs to make it clearer how to use a subclass and FFI::Struct to lazily access the message buffer. This gets us as close to zero-copy as possible for performance. * Fixed a memory leak in Message where the FFI::Struct backing the C struct was not being freed. * Tested the FFI code against MRI 1.8.x and 1.9.x. It works! * Patched a potential problem in LibZMQ::MessageDeallocator. It was crashing under MRI because it complained that FFI::Pointer did not have a free method. It now checks for :free before calling it. Need to investigate this further because it never happened under JRuby. * Modified the Socket constructor slightly to allow for using unmanaged or managed messages. * Changed the /examples to print a throughput (msgs/s) number upon completion. == 0.3.0 / 20100507 * ZMQ::Socket#send and ZMQ::Socket#recv semantics changed * The official 0mq ruby bindings utilize strings for #send and #recv. However, to do so requires lots of copying to and from buffers which greatly impacts performance. These methods now return a ZMQ::Message object which can be subclassed to do lazy evaluation of the buffer. * Added ZMQ::Socket#send_string and ZMQ::Socket#recv_string. They automatically convert the messages to strings just like the official 0mq ruby bindings. * Fixed bug in ZMQ::Util#error_string * Split the ZMQ::Message class into two classes. The base class called UnmanagedMessage requires manual memory management. The Message class (used by default by Socket) has a finalizer defined to automatically release memory when the message object gets garbage collected. == 0.2.0 / 20100505 * 1 major enhancement * Birthday! ruby-ffi-rzmq-1.0.3/README.rdoc000066400000000000000000000134771222421464700160530ustar00rootroot00000000000000ffi-rzmq by Chuck Remes http://www.zeromq.org/bindings:ruby-ffi == DESCRIPTION: This gem wraps the ZeroMQ networking library using the ruby FFI (foreign function interface). It's a pure ruby wrapper so this gem can be loaded and run by any ruby runtime that supports FFI. That's all of them: MRI 1.9.x, Rubinius and JRuby. This single gem supports 0mq 2.2.x and 3.2.x 0mq APIs. The 0mq project started making backward-incompatible changes to the API with the 3.1.x release. The gem auto-configures itself to expose the API conforming to the loaded C library. 0mq API 3.0 is *not* supported; the 0mq community voted to abandon it. The impetus behind this library was to provide support for ZeroMQ in JRuby which has native threads. Unlike MRI, which has a GIL, JRuby and Rubinius allow for threaded access to Ruby code from outside extensions. ZeroMQ is heavily threaded, so until the MRI runtime removes its GIL, JRuby and Rubinius will likely be the best environments to run this library. Please read the History.txt file for a description of all changes, including API changes, since the last release! == PERFORMANCE: Check out the latest performance results: http://www.zeromq.org/bindings:ruby-ffi The short version is that the FFI bindings are a few microseconds slower than using a C extension. == FEATURES/PROBLEMS: This gem needs more tests. This gem has been battle tested by myself and others for over a year, so I am fairly confident that it is solid. However, it is inevitable that there will be bugs, so please open issues for them here or fork this project, fix them, and send me a pull request. The 'ffi' gem has dropped support for MRI 1.8.x. Since this project relies on that gem to load and run this code, then this project also no longer supports MRI 1.8.x. I recommend JRuby for the best performance and stability. All features are implemented. == BUILD STATUS: {Build Status}[http://travis-ci.org/chuckremes/ffi-rzmq] {}[https://codeclimate.com/github/chuckremes/ffi-rzmq] == SYNOPSIS: 0mq API v2 client code: require 'rubygems' require 'ffi-rzmq' if ARGV.length < 3 puts "usage: local_lat " exit end bind_to = ARGV[0] message_size = ARGV[1].to_i roundtrip_count = ARGV[2].to_i ctx = ZMQ::Context.new s = ctx.socket ZMQ::REP rc = s.setsockopt(ZMQ::HWM, 100) rc = s.bind(bind_to) msg = "" roundtrip_count.times do rc = s.recv_string msg raise "Message size doesn't match, expected [#{message_size}] but received [#{msg.size}]" if message_size != msg.size rc = s.send_string msg, 0 end 0mq API v2 server code: require 'rubygems' require 'ffi-rzmq' if ARGV.length < 3 puts "usage: remote_lat " exit end connect_to = ARGV[0] message_size = ARGV[1].to_i roundtrip_count = ARGV[2].to_i ctx = ZMQ::Context.new s = ctx.socket ZMQ::REQ rc = s.connect(connect_to) msg = "#{ '3' * message_size }" start_time = Time.now msg = "" roundtrip_count.times do rc = s.send_string msg, 0 rc = s.recv_string msg raise "Message size doesn't match, expected [#{message_size}] but received [#{msg.size}]" if message_size != msg.size end == Better Examples I highly recommend visiting the Learn Ruby 0mq project for a bunch of good code examples. http://github.com/andrewvc/learn-ruby-zeromq == REQUIREMENTS: * 0mq 2.2.x, 3.2.x or later; 2.0.x, 3.0.x and 3.1.x are no longer supported The ZeroMQ library must be installed on your system in a well-known location like /usr/local/lib. This is the default for new ZeroMQ installs. If you have installed ZeroMQ using brew, you need to `brew link zeromq` before installing this gem. Future releases may include the library as a C extension built at time of installation. * ffi (>= 1.0.0) This is a requirement for MRI and Rubinius. JRuby has FFI support built in as a standard component. Do *not* run this gem under MRI with an old 'ffi' gem. It will crash randomly and you will be sad. == INSTALL: Make sure the ZeroMQ library is already installed on your system. % gem install ffi-rzmq # should grab the latest release To build from git master: % git clone git://github.com/chuckremes/ffi-rzmq % cd ffi-rzmq % gem build ffi-rzmq.gemspec % gem install ffi-rzmq-*.gem NOTE for Windows users! In order for this gem to find the libzmq.dll, it *must* be on the Windows PATH. Google for "modify windows path" for instructions on how to do that if you are unfamiliar with that activity. That DLL also requires that you copy libstdc++-6.dll and libgcc_s_sjlj-1.dll from DevKit MinGW into the same folder that you copied libzmq.dll. == LICENSE: (The MIT License) Copyright (c) 2013 Chuck Remes Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ruby-ffi-rzmq-1.0.3/Rakefile000066400000000000000000000001561222421464700157000ustar00rootroot00000000000000require 'bundler/gem_tasks' require 'rspec/core/rake_task' RSpec::Core::RakeTask.new task :default => :spec ruby-ffi-rzmq-1.0.3/examples/000077500000000000000000000000001222421464700160475ustar00rootroot00000000000000ruby-ffi-rzmq-1.0.3/examples/README.rdoc000066400000000000000000000130471222421464700176620ustar00rootroot00000000000000= Examples == Requirements 1. lib dir All of the examples assume the lib directory containing the gem sources is two directories up from the location of the example. 2. Installed libzmq library 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. This gem auto-configures itself to conform to the API for 0mq 2.1.x and 3.1.x. The 0mq project started making backward-incompatible changes with the 3.x branch. Rather than create separate gems, this one handles all of them. It is possible to install the libzmq* files directly into the gem in the ext/ directory. This directory is checked for loadable libraries first before it falls back to checking the system paths. 3. One terminal window 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. Several of the examples start the client and server components within separate threads or use the polling mechanism to interleave I/O operations amongst several sockets. A few examples need two terminal windows because the client and server code is in separate files (local_lat.rb/remote_lat.rb, local_throughput.rb/remote_throughput.rb). == Latency Test 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. ==== Files * latency_measurement.rb ==== Arguments The remote_lat.rb program takes 3 arguments: [link_address] Requires a transport string of the format "transport"://"endpoint"<:>. For example, tcp://127.0.0.1:5555 [message size] Size of each message measured in bytes. Allowable range is 1 to 2^(64-1). [message count] The number of round-trips used for the latency measurements. Allowable range is 1 to 2^(64-1). ==== Execution In an open terminal window, execute the latency_measurement.rb file. % ruby latency_measurement.rb tcp://127.0.0.1:5555 1024 100_000 On a relatively new system, it can run 100k messages in under 30 seconds. When complete, the program prints out a few statistics and exits. 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. 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. == Throughput Test 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. Since the publisher completes first, the program waits for all subscribers to exit before closing the PUB socket. This is necessary because all enqueued messages are discarded when the socket is closed. The subscriber prints some statistics when it exits. ==== Files * throughput_measurement.rb ==== Arguments The throughput_measurement.rb program takes 3 arguments: [link_address] Requires a transport string of the format "transport"://"endpoint"<:>. For example, tcp://127.0.0.1:5555 [message size] Size of each message measured in bytes. Allowable range is 1 to 2^(64-1). [message count] The number of round-trips used for the latency measurements. Allowable range is 1 to 2^(64-1). ==== Execution In an open terminal, execute the throughput_measurement.rb script. % ruby throughput_measurement.rb tcp://127.0.0.1:5555 1024 100_000 On a relatively new system, it can run 100k messages in under 10 seconds. When complete, the program prints out a few statistics and exits. 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. 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. == Poll 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/ZMQ::DONTWAIT for non-blocking send and receive. ==== Files * reqrep_poll.rb ==== Arguments None. ==== Execution This program is completely self-contained, so it only requires a single terminal window for execution. % ruby reqrep_poll.rb ruby-ffi-rzmq-1.0.3/examples/v2api/000077500000000000000000000000001222421464700170705ustar00rootroot00000000000000ruby-ffi-rzmq-1.0.3/examples/v2api/latency_measurement.rb000066400000000000000000000063271222421464700234710ustar00rootroot00000000000000 require File.join(File.dirname(__FILE__), '..', '..', 'lib', 'ffi-rzmq') # Within a single process, we start up two threads. One thread has a REQ (request) # socket and the second thread has a REP (reply) socket. We measure the # *round-trip* latency between these sockets. Only *one* message is in flight at # any given moment. # # This example also illustrates how a single context can be shared amongst several # threads. Sharing a single context also allows a user to specify the "inproc" # transport in addition to "tcp" and "ipc". # # % ruby latency_measurement.rb tcp://127.0.0.1:5555 1024 1_000_000 # # % ruby latency_measurement.rb inproc://lm_sock 1024 1_000_000 # if ARGV.length < 3 puts "usage: ruby latency_measurement.rb " exit end link = ARGV[0] message_size = ARGV[1].to_i roundtrip_count = ARGV[2].to_i def assert(rc) raise "Last API call failed at #{caller(1)}" unless rc >= 0 end begin master_context = ZMQ::Context.new rescue ContextError => e STDERR.puts "Failed to allocate context or socket!" raise end class Receiver def initialize context, link, size, count @context = context @link = link @size = size @count = count begin @socket = @context.socket(ZMQ::REP) rescue ContextError => e STDERR.puts "Failed to allocate REP socket!" raise end assert(@socket.setsockopt(ZMQ::LINGER, 100)) assert(@socket.setsockopt(ZMQ::HWM, 100)) assert(@socket.bind(@link)) end def run @count.times do string = '' assert(@socket.recv_string(string, 0)) raise "Message size doesn't match, expected [#{@size}] but received [#{string.size}]" if @size != string.size assert(@socket.send_string(string, 0)) end assert(@socket.close) end end class Transmitter def initialize context, link, size, count @context = context @link = link @size = size @count = count begin @socket = @context.socket(ZMQ::REQ) rescue ContextError => e STDERR.puts "Failed to allocate REP socket!" raise end assert(@socket.setsockopt(ZMQ::LINGER, 100)) assert(@socket.setsockopt(ZMQ::HWM, 100)) assert(@socket.connect(@link)) end def run msg = "#{ '3' * @size }" elapsed = elapsed_microseconds do @count.times do assert(@socket.send_string(msg, 0)) assert(@socket.recv_string(msg, 0)) raise "Message size doesn't match, expected [#{@size}] but received [#{msg.size}]" if @size != msg.size end end latency = elapsed / @count / 2 puts "message size: %i [B]" % @size puts "roundtrip count: %i" % @count puts "throughput (msgs/s): %i" % (@count / (elapsed / 1_000_000)) puts "mean latency: %.3f [us]" % latency assert(@socket.close) end def elapsed_microseconds(&blk) start = Time.now yield value = ((Time.now - start) * 1_000_000) end end threads = [] threads << Thread.new do receiver = Receiver.new(master_context, link, message_size, roundtrip_count) receiver.run end sleep 1 threads << Thread.new do transmitter = Transmitter.new(master_context, link, message_size, roundtrip_count) transmitter.run end threads.each {|t| t.join} master_context.terminate ruby-ffi-rzmq-1.0.3/examples/v2api/local_lat.rb000066400000000000000000000031261222421464700213510ustar00rootroot00000000000000# # Copyright (c) 2007-2010 iMatix Corporation # # This file is part of 0MQ. # # 0MQ is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # 0MQ is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . require File.join(File.dirname(__FILE__), '..', '..', 'lib', 'ffi-rzmq') if ARGV.length < 3 puts "usage: ruby local_lat.rb " exit end bind_to = ARGV[0] message_size = ARGV[1].to_i roundtrip_count = ARGV[2].to_i def assert(rc) raise "Last API call failed at #{caller(1)}" unless rc >= 0 end begin ctx = ZMQ::Context.new s = ctx.socket(ZMQ::REP) rescue ContextError => e STDERR.puts "Failed to allocate context or socket!" raise end assert(s.setsockopt(ZMQ::LINGER, 100)) assert(s.setsockopt(ZMQ::HWM, 100)) assert(s.bind(bind_to)) roundtrip_count.times do string = '' assert(s.recv_string(string, 0)) raise "Message size doesn't match, expected [#{message_size}] but received [#{string.size}]" if message_size != string.size assert(s.send_string(string, 0)) end assert(s.close) ctx.terminate ruby-ffi-rzmq-1.0.3/examples/v2api/local_lat_poll.rb000066400000000000000000000026401222421464700223770ustar00rootroot00000000000000 require File.join(File.dirname(__FILE__), '..', '..', 'lib', 'ffi-rzmq') if ARGV.length < 3 puts "usage: ruby local_lat.rb " exit end link = ARGV[0] message_size = ARGV[1].to_i roundtrip_count = ARGV[2].to_i def assert(rc) raise "Last API call failed at #{caller(1)}" unless rc >= 0 end begin ctx = ZMQ::Context.new s1 = ctx.socket(ZMQ::REQ) s2 = ctx.socket(ZMQ::REP) rescue ContextError => e STDERR.puts "Failed to allocate context or socket!" raise end assert(s1.setsockopt(ZMQ::LINGER, 100)) assert(s2.setsockopt(ZMQ::LINGER, 100)) assert(s1.connect(link)) assert(s2.bind(link)) poller = ZMQ::Poller.new poller.register_readable(s2) poller.register_readable(s1) start_time = Time.now # kick it off message = ZMQ::Message.new("a" * message_size) assert(s1.sendmsg(message, ZMQ::NonBlocking)) i = roundtrip_count until i.zero? i -= 1 assert(poller.poll_nonblock) poller.readables.each do |socket| received_message = '' assert(socket.recv_string(received_message, ZMQ::NonBlocking)) assert(socket.sendmsg(ZMQ::Message.new(received_message), ZMQ::NonBlocking)) end end elapsed_usecs = (Time.now.to_f - start_time.to_f) * 1_000_000 latency = elapsed_usecs / roundtrip_count / 2 puts "mean latency: %.3f [us]" % latency puts "received all messages in %.3f seconds" % (elapsed_usecs / 1_000_000) assert(s1.close) assert(s2.close) ctx.terminate ruby-ffi-rzmq-1.0.3/examples/v2api/local_throughput.rb000066400000000000000000000022451222421464700230030ustar00rootroot00000000000000 require File.join(File.dirname(__FILE__), '..', '..', 'lib', 'ffi-rzmq') if ARGV.length != 3 puts "usage: ruby local_throughtput.rb " Process.exit end def assert(rc) raise "Last API call failed at #{caller(1)}" unless rc >= 0 end bind_to = ARGV[0] message_size = ARGV[1].to_i message_count = ARGV[2].to_i begin ctx = ZMQ::Context.new s = ZMQ::Socket.new(ctx.pointer, ZMQ::SUB) rescue ContextError => e STDERR.puts "Failed to allocate context or socket!" raise end assert(s.setsockopt(ZMQ::LINGER, 100)) assert(s.setsockopt(ZMQ::SUBSCRIBE, "")) assert(s.bind(bind_to)) msg = ZMQ::Message.new assert(s.recvmsg(msg)) start_time = Time.now i = 1 while i < message_count assert(s.recvmsg(msg)) i += 1 end end_time = Time.now elapsed = (end_time.to_f - start_time.to_f) * 1000000 if elapsed == 0 elapsed = 1 end throughput = message_count * 1000000 / elapsed megabits = throughput * message_size * 8 / 1000000 puts "message size: %i [B]" % message_size puts "message count: %i" % message_count puts "mean throughput: %i [msg/s]" % throughput puts "mean throughput: %.3f [Mb/s]" % megabits assert(s.close) ctx.terminate ruby-ffi-rzmq-1.0.3/examples/v2api/pub.rb000066400000000000000000000017361222421464700202120ustar00rootroot00000000000000 require File.join(File.dirname(__FILE__), '..', '..', 'lib', 'ffi-rzmq') if ARGV.length != 3 puts "usage: ruby remote_throughput.rb " Process.exit end connect_to = ARGV[0] message_size = ARGV[1].to_i message_count = ARGV[2].to_i def assert(rc) raise "Last API call failed at #{caller(1)}" unless rc >= 0 end begin ctx = ZMQ::Context.new s = ZMQ::Socket.new(ctx.pointer, ZMQ::PUB) rescue ContextError => e STDERR.puts "Could not allocate a context or socket!" raise end #assert(s.setsockopt(ZMQ::LINGER, 1_000)) #assert(s.setsockopt(ZMQ::RCVHWM, 0)) assert(s.setsockopt(ZMQ::HWM, 10)) assert(s.bind(connect_to)) # the sleep gives the downstream SUB socket a chance to register its # subscription filters with this PUB socket puts "Hit any key to start publishing" STDIN.gets i = 0 while i < message_count msg = ZMQ::Message.new(i.to_s) assert(s.sendmsg(msg)) puts i i += 1 end sleep 10 assert(s.close) ctx.terminate ruby-ffi-rzmq-1.0.3/examples/v2api/publish_subscribe.rb000066400000000000000000000037121222421464700231270ustar00rootroot00000000000000require File.join(File.dirname(__FILE__), '..', '..', 'lib', 'ffi-rzmq') def assert(rc) raise "Last API call failed at #{caller(1)}" unless rc >= 0 end link = "tcp://127.0.0.1:5555" begin ctx = ZMQ::Context.new s1 = ctx.socket(ZMQ::PUB) s2 = ctx.socket(ZMQ::SUB) s3 = ctx.socket(ZMQ::SUB) s4 = ctx.socket(ZMQ::SUB) s5 = ctx.socket(ZMQ::SUB) rescue ContextError => e STDERR.puts "Failed to allocate context or socket!" raise end assert(s1.setsockopt(ZMQ::LINGER, 100)) assert(s2.setsockopt(ZMQ::SUBSCRIBE, '')) # receive all assert(s3.setsockopt(ZMQ::SUBSCRIBE, 'animals')) # receive any starting with this string assert(s4.setsockopt(ZMQ::SUBSCRIBE, 'animals.dog')) assert(s5.setsockopt(ZMQ::SUBSCRIBE, 'animals.cat')) assert(s1.bind(link)) assert(s2.connect(link)) assert(s3.connect(link)) assert(s4.connect(link)) assert(s5.connect(link)) sleep 1 topic = "animals.dog" payload = "Animal crackers!" s1.identity = "publisher-A" puts "sending" # use the new multi-part messaging support to # automatically separate the topic from the body assert(s1.send_string(topic, ZMQ::SNDMORE)) assert(s1.send_string(payload, ZMQ::SNDMORE)) assert(s1.send_string(s1.identity)) topic = '' assert(s2.recv_string(topic)) body = '' assert(s2.recv_string(body)) if s2.more_parts? identity = '' assert(s2.recv_string(identity)) if s2.more_parts? puts "s2 received topic [#{topic}], body [#{body}], identity [#{identity}]" topic = '' assert(s3.recv_string(topic)) body = '' assert(s3.recv_string(body)) if s3.more_parts? puts "s3 received topic [#{topic}], body [#{body}]" topic = '' assert(s4.recv_string(topic)) body = '' assert(s4.recv_string(body)) if s4.more_parts? puts "s4 received topic [#{topic}], body [#{body}]" s5_string = '' rc = s5.recv_string(s5_string, ZMQ::NonBlocking) eagain = (rc == -1 && ZMQ::Util.errno == ZMQ::EAGAIN) puts(eagain ? "s5 received no messages" : "s5 FAILED") [s1, s2, s3, s4, s5].each do |socket| assert(socket.close) end ctx.terminate ruby-ffi-rzmq-1.0.3/examples/v2api/remote_lat.rb000066400000000000000000000037041222421464700215540ustar00rootroot00000000000000# # Copyright (c) 2007-2010 iMatix Corporation # # This file is part of 0MQ. # # 0MQ is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # 0MQ is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . require File.join(File.dirname(__FILE__), '..', '..', 'lib', 'ffi-rzmq') if ARGV.length < 3 puts "usage: ruby remote_lat.rb " exit end def assert(rc) raise "Last API call failed at #{caller(1)}" unless rc >= 0 end connect_to = ARGV[0] message_size = ARGV[1].to_i roundtrip_count = ARGV[2].to_i begin ctx = ZMQ::Context.new s = ctx.socket(ZMQ::REQ) rescue ContextError => e STDERR.puts "Failed to allocate context or socket!" raise end assert(s.setsockopt(ZMQ::LINGER, 100)) assert(s.connect(connect_to)) msg = "#{ '3' * message_size }" start_time = Time.now roundtrip_count.times do assert(s.send_string(msg, 0)) msg = '' assert(s.recv_string(msg, 0)) raise "Message size doesn't match, expected [#{message_size}] but received [#{msg.size}]" if message_size != msg.size end end_time = Time.now elapsed_secs = (end_time.to_f - start_time.to_f) elapsed_usecs = elapsed_secs * 1000000 latency = elapsed_usecs / roundtrip_count / 2 puts "message size: %i [B]" % message_size puts "roundtrip count: %i" % roundtrip_count puts "throughput (msgs/s): %i" % (roundtrip_count / elapsed_secs) puts "mean latency: %.3f [us]" % latency assert(s.close) ctx.terminate ruby-ffi-rzmq-1.0.3/examples/v2api/remote_throughput.rb000066400000000000000000000014051222421464700232010ustar00rootroot00000000000000 require File.join(File.dirname(__FILE__), '..', '..', 'lib', 'ffi-rzmq') if ARGV.length != 3 puts "usage: ruby remote_throughput.rb " Process.exit end connect_to = ARGV[0] message_size = ARGV[1].to_i message_count = ARGV[2].to_i def assert(rc) raise "Last API call failed at #{caller(1)}" unless rc >= 0 end begin ctx = ZMQ::Context.new s = ZMQ::Socket.new(ctx.pointer, ZMQ::PUB) rescue ContextError => e STDERR.puts "Could not allocate a context or socket!" raise end assert(s.setsockopt(ZMQ::LINGER, 1_000)) assert(s.connect(connect_to)) contents = "#{'0'*message_size}" i = 0 while i < message_count msg = ZMQ::Message.new(contents) assert(s.sendmsg(msg)) i += 1 end assert(s.close) ctx.terminate ruby-ffi-rzmq-1.0.3/examples/v2api/reqrep_poll.rb000066400000000000000000000024011222421464700217360ustar00rootroot00000000000000 require File.join(File.dirname(__FILE__), '..', '..', 'lib', 'ffi-rzmq') def assert(rc) raise "Last API call failed at #{caller(1)}" unless rc >= 0 end link = "tcp://127.0.0.1:5554" begin ctx = ZMQ::Context.new s1 = ctx.socket(ZMQ::REQ) s2 = ctx.socket(ZMQ::REP) rescue ContextError => e STDERR.puts "Failed to allocate context or socket!" raise end assert(s1.setsockopt(ZMQ::LINGER, 100)) assert(s2.setsockopt(ZMQ::LINGER, 100)) assert(s1.connect(link)) assert(s2.bind(link)) poller = ZMQ::Poller.new poller.register_readable(s2) poller.register_writable(s1) start_time = Time.now @unsent = true until @done do assert(poller.poll_nonblock) # send the message after 5 seconds if Time.now - start_time > 5 && @unsent payload = "#{ '3' * 1024 }" puts "sending payload nonblocking" assert(s1.send_string(payload, ZMQ::NonBlocking)) @unsent = false end # check for messages after 1 second if Time.now - start_time > 1 poller.readables.each do |sock| received_msg = '' assert(sock.recv_string(received_msg, ZMQ::NonBlocking)) puts "message received [#{received_msg}]" @done = true end end end puts "executed in [#{Time.now - start_time}] seconds" assert(s1.close) assert(s2.close) ctx.terminate ruby-ffi-rzmq-1.0.3/examples/v2api/request_response.rb000066400000000000000000000014561222421464700230310ustar00rootroot00000000000000 require File.join(File.dirname(__FILE__), '..', '..', 'lib', 'ffi-rzmq') def assert(rc) raise "Last API call failed at #{caller(1)}" unless rc >= 0 end link = "tcp://127.0.0.1:5555" begin ctx = ZMQ::Context.new s1 = ctx.socket(ZMQ::REQ) s2 = ctx.socket(ZMQ::REP) rescue ContextError => e STDERR.puts "Failed to allocate context or socket" raise end assert(s1.setsockopt(ZMQ::LINGER, 100)) assert(s2.setsockopt(ZMQ::LINGER, 100)) assert(s2.bind(link)) assert(s1.connect(link)) payload = "#{ '3' * 2048 }" sent_msg = ZMQ::Message.new(payload) received_msg = ZMQ::Message.new assert(s1.sendmsg(sent_msg)) assert(s2.recvmsg(received_msg)) result = payload == received_msg.copy_out_string ? "Request received" : "Received wrong payload" p result assert(s1.close) assert(s2.close) ctx.terminate ruby-ffi-rzmq-1.0.3/examples/v2api/sub.rb000066400000000000000000000031411222421464700202050ustar00rootroot00000000000000 require File.join(File.dirname(__FILE__), '..', '..', 'lib', 'ffi-rzmq') #if ARGV.length != 3 # puts "usage: ruby local_throughtput.rb " # Process.exit #end p ZMQ::Util.version def assert(rc) raise "Last API call failed at #{caller(1)}" unless rc >= 0 end bind_to = ARGV[0] message_size = ARGV[1].to_i message_count = ARGV[2].to_i sleep_time = ARGV[3].to_f begin ctx = ZMQ::Context.new s = ZMQ::Socket.new(ctx.pointer, ZMQ::SUB) rescue ContextError => e STDERR.puts "Failed to allocate context or socket!" raise end #assert(s.setsockopt(ZMQ::LINGER, 100)) assert(s.setsockopt(ZMQ::IDENTITY, rand(999_999).to_s)) assert(s.setsockopt(ZMQ::SUBSCRIBE, "")) assert(s.setsockopt(ZMQ::HWM, 1)) #assert(s.setsockopt(ZMQ::RCVHWM, 0)) #assert(s.setsockopt(ZMQ::SNDHWM, 0)) assert(s.connect(bind_to)) sleep 1 msg = ZMQ::Message.new msg = '' assert(s.recv_string(msg)) raise unless msg.to_i == 0 start_time = Time.now i = 1 while i < message_count - 1 assert(s.recv_string(msg)) msg_i = msg.to_i missed = (msg_i - i) - 1 puts "missed [#{missed}] messages" if missed > 0 i = msg_i start = Time.now while (Time.now - start) < sleep_time end end end_time = Time.now elapsed = (end_time.to_f - start_time.to_f) * 1000000 if elapsed == 0 elapsed = 1 end throughput = message_count * 1000000 / elapsed megabits = throughput * message_size * 8 / 1000000 puts "message size: %i [B]" % message_size puts "message count: %i" % message_count puts "mean throughput: %i [msg/s]" % throughput puts "mean throughput: %.3f [Mb/s]" % megabits assert(s.close) ctx.terminate ruby-ffi-rzmq-1.0.3/examples/v2api/throughput_measurement.rb000066400000000000000000000061061222421464700242360ustar00rootroot00000000000000require File.join(File.dirname(__FILE__), '..', '..', 'lib', 'ffi-rzmq') # Within a single process, we start up five threads. Main thread has a PUB (publisher) # socket and the secondary threads have SUB (subscription) sockets. We measure the # *throughput* between these sockets. A high-water mark (HWM) is *not* set, so the # publisher queue is free to grow to the size of memory without dropping packets. # # This example also illustrates how a single context can be shared amongst several # threads. Sharing a single context also allows a user to specify the "inproc" # transport in addition to "tcp" and "ipc". # # % ruby throughput_measurement.rb tcp://127.0.0.1:5555 1024 1_000_000 # # % ruby throughput_measurement.rb inproc://lm_sock 1024 1_000_000 # if ARGV.length < 3 puts "usage: ruby throughput_measurement.rb " exit end link = ARGV[0] message_size = ARGV[1].to_i count = ARGV[2].to_i def assert(rc) raise "Last API call failed at #{caller(1)}" unless rc >= 0 end begin master_context = ZMQ::Context.new rescue ContextError => e STDERR.puts "Failed to allocate context or socket!" raise end class Receiver def initialize context, link, size, count @context = context @link = link @size = size @count = count begin @socket = @context.socket(ZMQ::SUB) rescue ContextError => e STDERR.puts "Failed to allocate SUB socket!" raise end assert(@socket.setsockopt(ZMQ::LINGER, 100)) assert(@socket.setsockopt(ZMQ::SUBSCRIBE, "")) assert(@socket.connect(@link)) end def run msg = ZMQ::Message.new assert(@socket.recvmsg(msg)) elapsed = elapsed_microseconds do (@count -1).times do assert(@socket.recvmsg(msg)) end end throughput = @count * 1000000 / elapsed megabits = throughput * @size * 8 / 1000000 puts "message size: %i [B]" % @size puts "message count: %i" % @count puts "mean throughput: %i [msg/s]" % throughput puts "mean throughput: %.3f [Mb/s]" % megabits assert(@socket.close) end def elapsed_microseconds(&blk) start = Time.now yield ((Time.now - start) * 1_000_000) end end class Transmitter def initialize context, link, size, count @context = context @link = link @size = size @count = count begin @socket = @context.socket(ZMQ::PUB) rescue ContextError => e STDERR.puts "Failed to allocate PUB socket!" raise end assert(@socket.setsockopt(ZMQ::LINGER, 100)) assert(@socket.bind(@link)) end def run sleep 1 contents = "#{'0' * @size}" i = 0 while i < @count msg = ZMQ::Message.new(contents) assert(@socket.sendmsg(msg)) i += 1 end assert(@socket.close) end end threads = [] threads << Thread.new do transmitter = Transmitter.new(master_context, link, message_size, count) transmitter.run end 1.times do threads << Thread.new do receiver = Receiver.new(master_context, link, message_size, count) receiver.run end end threads.each {|t| t.join} master_context.terminate ruby-ffi-rzmq-1.0.3/examples/v2api/xreqxrep_poll.rb000066400000000000000000000044711222421464700223270ustar00rootroot00000000000000 require File.join(File.dirname(__FILE__), '..', '..', 'lib', 'ffi-rzmq') def assert(rc) raise "Last API call failed at #{caller(1)}" unless rc >= 0 end link = "tcp://127.0.0.1:5555" begin ctx = ZMQ::Context.new s1 = ctx.socket(ZMQ::XREQ) s2 = ctx.socket(ZMQ::XREP) rescue ContextError => e STDERR.puts "Failed to allocate context or socket" raise end s1.identity = 'socket1.xreq' s2.identity = 'socket2.xrep' assert(s1.setsockopt(ZMQ::LINGER, 100)) assert(s2.setsockopt(ZMQ::LINGER, 100)) assert(s1.bind(link)) assert(s2.connect(link)) poller = ZMQ::Poller.new poller.register_readable(s2) poller.register_writable(s1) start_time = Time.now @unsent = true until @done do assert(poller.poll_nonblock) # send the message after 5 seconds if Time.now - start_time > 5 && @unsent puts "sending payload nonblocking" 5.times do |i| payload = "#{ i.to_s * 40 }" assert(s1.send_string(payload, ZMQ::NonBlocking)) end @unsent = false end # check for messages after 1 second if Time.now - start_time > 1 poller.readables.each do |sock| if sock.identity =~ /xrep/ routing_info = '' assert(sock.recv_string(routing_info, ZMQ::NonBlocking)) puts "routing_info received [#{routing_info}] on socket.identity [#{sock.identity}]" else routing_info = nil received_msg = '' assert(sock.recv_string(received_msg, ZMQ::NonBlocking)) # skip to the next iteration if received_msg is nil; that means we got an EAGAIN next unless received_msg puts "message received [#{received_msg}] on socket.identity [#{sock.identity}]" end while sock.more_parts? do received_msg = '' assert(sock.recv_string(received_msg, ZMQ::NonBlocking)) puts "message received [#{received_msg}]" end puts "kick back a reply" assert(sock.send_string(routing_info, ZMQ::SNDMORE | ZMQ::NonBlocking)) if routing_info time = Time.now.strftime "%Y-%m-%dT%H:%M:%S.#{Time.now.usec}" reply = "reply " + sock.identity.upcase + " #{time}" puts "sent reply [#{reply}], #{time}" assert(sock.send_string(reply)) @done = true poller.register_readable(s1) end end end puts "executed in [#{Time.now - start_time}] seconds" assert(s1.close) assert(s2.close) ctx.terminate ruby-ffi-rzmq-1.0.3/examples/v3api/000077500000000000000000000000001222421464700170715ustar00rootroot00000000000000ruby-ffi-rzmq-1.0.3/examples/v3api/latency_measurement.rb000066400000000000000000000064751222421464700234760ustar00rootroot00000000000000 require File.join(File.dirname(__FILE__), '..', '..', 'lib', 'ffi-rzmq') # Within a single process, we start up two threads. One thread has a REQ (request) # socket and the second thread has a REP (reply) socket. We measure the # *round-trip* latency between these sockets. Only *one* message is in flight at # any given moment. # # This example also illustrates how a single context can be shared amongst several # threads. Sharing a single context also allows a user to specify the "inproc" # transport in addition to "tcp" and "ipc". # # % ruby latency_measurement.rb tcp://127.0.0.1:5555 1024 1_000_000 # # % ruby latency_measurement.rb inproc://lm_sock 1024 1_000_000 # if ARGV.length < 3 puts "usage: ruby latency_measurement.rb " exit end link = ARGV[0] message_size = ARGV[1].to_i roundtrip_count = ARGV[2].to_i def assert(rc) raise "Last API call failed at #{caller(1)}" unless rc >= 0 end begin master_context = ZMQ::Context.new rescue ContextError => e STDERR.puts "Failed to allocate context or socket!" raise end class Receiver def initialize context, link, size, count @context = context @link = link @size = size @count = count begin @socket = @context.socket(ZMQ::REP) rescue ContextError => e STDERR.puts "Failed to allocate REP socket!" raise end assert(@socket.setsockopt(ZMQ::LINGER, 100)) assert(@socket.setsockopt(ZMQ::RCVHWM, 100)) assert(@socket.setsockopt(ZMQ::SNDHWM, 100)) assert(@socket.bind(@link)) end def run @count.times do string = '' assert(@socket.recv_string(string, 0)) raise "Message size doesn't match, expected [#{@size}] but received [#{string.size}]" if @size != string.size assert(@socket.send_string(string, 0)) end assert(@socket.close) end end class Transmitter def initialize context, link, size, count @context = context @link = link @size = size @count = count begin @socket = @context.socket(ZMQ::REQ) rescue ContextError => e STDERR.puts "Failed to allocate REP socket!" raise end assert(@socket.setsockopt(ZMQ::LINGER, 100)) assert(@socket.setsockopt(ZMQ::RCVHWM, 100)) assert(@socket.setsockopt(ZMQ::SNDHWM, 100)) assert(@socket.connect(@link)) end def run msg = "#{ '3' * @size }" elapsed = elapsed_microseconds do @count.times do assert(@socket.send_string(msg, 0)) assert(@socket.recv_string(msg, 0)) raise "Message size doesn't match, expected [#{@size}] but received [#{msg.size}]" if @size != msg.size end end latency = elapsed / @count / 2 puts "message size: %i [B]" % @size puts "roundtrip count: %i" % @count puts "throughput (msgs/s): %i" % (@count / (elapsed / 1_000_000)) puts "mean latency: %.3f [us]" % latency assert(@socket.close) end def elapsed_microseconds(&blk) start = Time.now yield value = ((Time.now - start) * 1_000_000) end end threads = [] threads << Thread.new do receiver = Receiver.new(master_context, link, message_size, roundtrip_count) receiver.run end sleep 1 threads << Thread.new do transmitter = Transmitter.new(master_context, link, message_size, roundtrip_count) transmitter.run end threads.each {|t| t.join} master_context.terminate ruby-ffi-rzmq-1.0.3/examples/v3api/local_lat.rb000066400000000000000000000032001222421464700213430ustar00rootroot00000000000000# # Copyright (c) 2007-2010 iMatix Corporation # # This file is part of 0MQ. # # 0MQ is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # 0MQ is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . require File.join(File.dirname(__FILE__), '..', '..', 'lib', 'ffi-rzmq') if ARGV.length < 3 puts "usage: ruby local_lat.rb " exit end bind_to = ARGV[0] message_size = ARGV[1].to_i roundtrip_count = ARGV[2].to_i def assert(rc) raise "Last API call failed at #{caller(1)}" unless rc >= 0 end begin ctx = ZMQ::Context.new s = ctx.socket(ZMQ::REP) rescue ContextError => e STDERR.puts "Failed to allocate context or socket!" raise end assert(s.setsockopt(ZMQ::LINGER, 100)) assert(s.setsockopt(ZMQ::RCVHWM, 100)) assert(s.setsockopt(ZMQ::SNDHWM, 100)) assert(s.bind(bind_to)) roundtrip_count.times do string = '' assert(s.recv_string(string, 0)) raise "Message size doesn't match, expected [#{message_size}] but received [#{string.size}]" if message_size != string.size assert(s.send_string(string, 0)) end assert(s.close) ctx.terminate ruby-ffi-rzmq-1.0.3/examples/v3api/local_lat_poll.rb000066400000000000000000000026401222421464700224000ustar00rootroot00000000000000 require File.join(File.dirname(__FILE__), '..', '..', 'lib', 'ffi-rzmq') if ARGV.length < 3 puts "usage: ruby local_lat.rb " exit end link = ARGV[0] message_size = ARGV[1].to_i roundtrip_count = ARGV[2].to_i def assert(rc) raise "Last API call failed at #{caller(1)}" unless rc >= 0 end begin ctx = ZMQ::Context.new s1 = ctx.socket(ZMQ::REQ) s2 = ctx.socket(ZMQ::REP) rescue ContextError => e STDERR.puts "Failed to allocate context or socket!" raise end assert(s1.setsockopt(ZMQ::LINGER, 100)) assert(s2.setsockopt(ZMQ::LINGER, 100)) assert(s1.connect(link)) assert(s2.bind(link)) poller = ZMQ::Poller.new poller.register_readable(s2) poller.register_readable(s1) start_time = Time.now # kick it off message = ZMQ::Message.new("a" * message_size) assert(s1.sendmsg(message, ZMQ::NonBlocking)) i = roundtrip_count until i.zero? i -= 1 assert(poller.poll_nonblock) poller.readables.each do |socket| received_message = '' assert(socket.recv_string(received_message, ZMQ::NonBlocking)) assert(socket.sendmsg(ZMQ::Message.new(received_message), ZMQ::NonBlocking)) end end elapsed_usecs = (Time.now.to_f - start_time.to_f) * 1_000_000 latency = elapsed_usecs / roundtrip_count / 2 puts "mean latency: %.3f [us]" % latency puts "received all messages in %.3f seconds" % (elapsed_usecs / 1_000_000) assert(s1.close) assert(s2.close) ctx.terminate ruby-ffi-rzmq-1.0.3/examples/v3api/local_throughput.rb000066400000000000000000000025061222421464700230040ustar00rootroot00000000000000 require File.join(File.dirname(__FILE__), '..', '..', 'lib', 'ffi-rzmq') if ARGV.length != 3 puts "usage: ruby local_throughtput.rb " Process.exit end def assert(rc) raise "Last API call failed at #{caller(1)}" unless rc >= 0 end bind_to = ARGV[0] message_size = ARGV[1].to_i message_count = ARGV[2].to_i begin ctx = ZMQ::Context.new s = ZMQ::Socket.new(ctx.pointer, ZMQ::SUB) rescue ContextError => e STDERR.puts "Failed to allocate context or socket!" raise end #assert(s.setsockopt(ZMQ::LINGER, 100)) assert(s.setsockopt(ZMQ::SUBSCRIBE, "")) #assert(s.setsockopt(ZMQ::RCVHWM, 0)) #assert(s.setsockopt(ZMQ::SNDHWM, 0)) assert(s.bind(bind_to)) sleep 1 msg = ZMQ::Message.new msg = '' assert(s.recv_string(msg)) #assert(s.recvmsg(msg)) start_time = Time.now i = 1 while i < message_count #assert(s.recvmsg(msg)) assert(s.recv_string(msg)) puts i i += 1 end end_time = Time.now elapsed = (end_time.to_f - start_time.to_f) * 1000000 if elapsed == 0 elapsed = 1 end throughput = message_count * 1000000 / elapsed megabits = throughput * message_size * 8 / 1000000 puts "message size: %i [B]" % message_size puts "message count: %i" % message_count puts "mean throughput: %i [msg/s]" % throughput puts "mean throughput: %.3f [Mb/s]" % megabits assert(s.close) ctx.terminate ruby-ffi-rzmq-1.0.3/examples/v3api/pub.rb000066400000000000000000000017421222421464700202100ustar00rootroot00000000000000 require File.join(File.dirname(__FILE__), '..', '..', 'lib', 'ffi-rzmq') if ARGV.length != 3 puts "usage: ruby remote_throughput.rb " Process.exit end connect_to = ARGV[0] message_size = ARGV[1].to_i message_count = ARGV[2].to_i def assert(rc) raise "Last API call failed at #{caller(1)}" unless rc >= 0 end begin ctx = ZMQ::Context.new s = ZMQ::Socket.new(ctx.pointer, ZMQ::PUB) rescue ContextError => e STDERR.puts "Could not allocate a context or socket!" raise end #assert(s.setsockopt(ZMQ::LINGER, 1_000)) #assert(s.setsockopt(ZMQ::RCVHWM, 0)) assert(s.setsockopt(ZMQ::SNDHWM, 100)) assert(s.bind(connect_to)) # the sleep gives the downstream SUB socket a chance to register its # subscription filters with this PUB socket puts "Hit any key to start publishing" STDIN.gets i = 0 while i < message_count msg = ZMQ::Message.new(i.to_s) assert(s.sendmsg(msg)) puts i i += 1 end sleep 10 assert(s.close) ctx.terminate ruby-ffi-rzmq-1.0.3/examples/v3api/publish_subscribe.rb000066400000000000000000000037121222421464700231300ustar00rootroot00000000000000require File.join(File.dirname(__FILE__), '..', '..', 'lib', 'ffi-rzmq') def assert(rc) raise "Last API call failed at #{caller(1)}" unless rc >= 0 end link = "tcp://127.0.0.1:5555" begin ctx = ZMQ::Context.new s1 = ctx.socket(ZMQ::PUB) s2 = ctx.socket(ZMQ::SUB) s3 = ctx.socket(ZMQ::SUB) s4 = ctx.socket(ZMQ::SUB) s5 = ctx.socket(ZMQ::SUB) rescue ContextError => e STDERR.puts "Failed to allocate context or socket!" raise end assert(s1.setsockopt(ZMQ::LINGER, 100)) assert(s2.setsockopt(ZMQ::SUBSCRIBE, '')) # receive all assert(s3.setsockopt(ZMQ::SUBSCRIBE, 'animals')) # receive any starting with this string assert(s4.setsockopt(ZMQ::SUBSCRIBE, 'animals.dog')) assert(s5.setsockopt(ZMQ::SUBSCRIBE, 'animals.cat')) assert(s1.bind(link)) assert(s2.connect(link)) assert(s3.connect(link)) assert(s4.connect(link)) assert(s5.connect(link)) sleep 1 topic = "animals.dog" payload = "Animal crackers!" s1.identity = "publisher-A" puts "sending" # use the new multi-part messaging support to # automatically separate the topic from the body assert(s1.send_string(topic, ZMQ::SNDMORE)) assert(s1.send_string(payload, ZMQ::SNDMORE)) assert(s1.send_string(s1.identity)) topic = '' assert(s2.recv_string(topic)) body = '' assert(s2.recv_string(body)) if s2.more_parts? identity = '' assert(s2.recv_string(identity)) if s2.more_parts? puts "s2 received topic [#{topic}], body [#{body}], identity [#{identity}]" topic = '' assert(s3.recv_string(topic)) body = '' assert(s3.recv_string(body)) if s3.more_parts? puts "s3 received topic [#{topic}], body [#{body}]" topic = '' assert(s4.recv_string(topic)) body = '' assert(s4.recv_string(body)) if s4.more_parts? puts "s4 received topic [#{topic}], body [#{body}]" s5_string = '' rc = s5.recv_string(s5_string, ZMQ::NonBlocking) eagain = (rc == -1 && ZMQ::Util.errno == ZMQ::EAGAIN) puts(eagain ? "s5 received no messages" : "s5 FAILED") [s1, s2, s3, s4, s5].each do |socket| assert(socket.close) end ctx.terminate ruby-ffi-rzmq-1.0.3/examples/v3api/remote_lat.rb000066400000000000000000000037041222421464700215550ustar00rootroot00000000000000# # Copyright (c) 2007-2010 iMatix Corporation # # This file is part of 0MQ. # # 0MQ is free software; you can redistribute it and/or modify it under # the terms of the Lesser GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # 0MQ is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # Lesser GNU General Public License for more details. # # You should have received a copy of the Lesser GNU General Public License # along with this program. If not, see . require File.join(File.dirname(__FILE__), '..', '..', 'lib', 'ffi-rzmq') if ARGV.length < 3 puts "usage: ruby remote_lat.rb " exit end def assert(rc) raise "Last API call failed at #{caller(1)}" unless rc >= 0 end connect_to = ARGV[0] message_size = ARGV[1].to_i roundtrip_count = ARGV[2].to_i begin ctx = ZMQ::Context.new s = ctx.socket(ZMQ::REQ) rescue ContextError => e STDERR.puts "Failed to allocate context or socket!" raise end assert(s.setsockopt(ZMQ::LINGER, 100)) assert(s.connect(connect_to)) msg = "#{ '3' * message_size }" start_time = Time.now roundtrip_count.times do assert(s.send_string(msg, 0)) msg = '' assert(s.recv_string(msg, 0)) raise "Message size doesn't match, expected [#{message_size}] but received [#{msg.size}]" if message_size != msg.size end end_time = Time.now elapsed_secs = (end_time.to_f - start_time.to_f) elapsed_usecs = elapsed_secs * 1000000 latency = elapsed_usecs / roundtrip_count / 2 puts "message size: %i [B]" % message_size puts "roundtrip count: %i" % roundtrip_count puts "throughput (msgs/s): %i" % (roundtrip_count / elapsed_secs) puts "mean latency: %.3f [us]" % latency assert(s.close) ctx.terminate ruby-ffi-rzmq-1.0.3/examples/v3api/remote_throughput.rb000066400000000000000000000017361222421464700232110ustar00rootroot00000000000000 require File.join(File.dirname(__FILE__), '..', '..', 'lib', 'ffi-rzmq') if ARGV.length != 3 puts "usage: ruby remote_throughput.rb " Process.exit end connect_to = ARGV[0] message_size = ARGV[1].to_i message_count = ARGV[2].to_i def assert(rc) raise "Last API call failed at #{caller(1)}" unless rc >= 0 end begin ctx = ZMQ::Context.new s = ZMQ::Socket.new(ctx.pointer, ZMQ::PUB) rescue ContextError => e STDERR.puts "Could not allocate a context or socket!" raise end #assert(s.setsockopt(ZMQ::LINGER, 1_000)) #assert(s.setsockopt(ZMQ::RCVHWM, 0)) #assert(s.setsockopt(ZMQ::SNDHWM, 0)) assert(s.connect(connect_to)) # the sleep gives the downstream SUB socket a chance to register its # subscription filters with this PUB socket sleep 1 contents = "#{'0'*message_size}" i = 0 while i < message_count msg = ZMQ::Message.new(contents) assert(s.sendmsg(msg)) puts i i += 1 end sleep 10 assert(s.close) ctx.terminate ruby-ffi-rzmq-1.0.3/examples/v3api/reqrep_poll.rb000066400000000000000000000024011222421464700217370ustar00rootroot00000000000000 require File.join(File.dirname(__FILE__), '..', '..', 'lib', 'ffi-rzmq') def assert(rc) raise "Last API call failed at #{caller(1)}" unless rc >= 0 end link = "tcp://127.0.0.1:5554" begin ctx = ZMQ::Context.new s1 = ctx.socket(ZMQ::REQ) s2 = ctx.socket(ZMQ::REP) rescue ContextError => e STDERR.puts "Failed to allocate context or socket!" raise end assert(s1.setsockopt(ZMQ::LINGER, 100)) assert(s2.setsockopt(ZMQ::LINGER, 100)) assert(s1.connect(link)) assert(s2.bind(link)) poller = ZMQ::Poller.new poller.register_readable(s2) poller.register_writable(s1) start_time = Time.now @unsent = true until @done do assert(poller.poll_nonblock) # send the message after 5 seconds if Time.now - start_time > 5 && @unsent payload = "#{ '3' * 1024 }" puts "sending payload nonblocking" assert(s1.send_string(payload, ZMQ::NonBlocking)) @unsent = false end # check for messages after 1 second if Time.now - start_time > 1 poller.readables.each do |sock| received_msg = '' assert(sock.recv_string(received_msg, ZMQ::NonBlocking)) puts "message received [#{received_msg}]" @done = true end end end puts "executed in [#{Time.now - start_time}] seconds" assert(s1.close) assert(s2.close) ctx.terminate ruby-ffi-rzmq-1.0.3/examples/v3api/request_response.rb000066400000000000000000000014561222421464700230320ustar00rootroot00000000000000 require File.join(File.dirname(__FILE__), '..', '..', 'lib', 'ffi-rzmq') def assert(rc) raise "Last API call failed at #{caller(1)}" unless rc >= 0 end link = "tcp://127.0.0.1:5555" begin ctx = ZMQ::Context.new s1 = ctx.socket(ZMQ::REQ) s2 = ctx.socket(ZMQ::REP) rescue ContextError => e STDERR.puts "Failed to allocate context or socket" raise end assert(s1.setsockopt(ZMQ::LINGER, 100)) assert(s2.setsockopt(ZMQ::LINGER, 100)) assert(s2.bind(link)) assert(s1.connect(link)) payload = "#{ '3' * 2048 }" sent_msg = ZMQ::Message.new(payload) received_msg = ZMQ::Message.new assert(s1.sendmsg(sent_msg)) assert(s2.recvmsg(received_msg)) result = payload == received_msg.copy_out_string ? "Request received" : "Received wrong payload" p result assert(s1.close) assert(s2.close) ctx.terminate ruby-ffi-rzmq-1.0.3/examples/v3api/sub.rb000066400000000000000000000027231222421464700202130ustar00rootroot00000000000000 require File.join(File.dirname(__FILE__), '..', '..', 'lib', 'ffi-rzmq') #if ARGV.length != 3 # puts "usage: ruby local_throughtput.rb " # Process.exit #end p ZMQ::Util.version def assert(rc) raise "Last API call failed at #{caller(1)}" unless rc >= 0 end bind_to = ARGV[0] message_size = ARGV[1].to_i message_count = ARGV[2].to_i sleep_time = ARGV[3].to_f begin ctx = ZMQ::Context.new s = ZMQ::Socket.new(ctx.pointer, ZMQ::SUB) rescue ContextError => e STDERR.puts "Failed to allocate context or socket!" raise end #assert(s.setsockopt(ZMQ::LINGER, 100)) assert(s.setsockopt(ZMQ::SUBSCRIBE, "")) assert(s.setsockopt(ZMQ::RCVHWM, 20)) #assert(s.setsockopt(ZMQ::RCVHWM, 0)) #assert(s.setsockopt(ZMQ::SNDHWM, 0)) assert(s.connect(bind_to)) sleep 1 msg = ZMQ::Message.new msg = '' assert(s.recv_string(msg)) raise unless msg.to_i == 0 start_time = Time.now i = 1 while i < message_count assert(s.recv_string(msg)) msg_i = msg.to_i puts "missed [#{msg_i - i}] messages" i = msg_i sleep(sleep_time) end end_time = Time.now elapsed = (end_time.to_f - start_time.to_f) * 1000000 if elapsed == 0 elapsed = 1 end throughput = message_count * 1000000 / elapsed megabits = throughput * message_size * 8 / 1000000 puts "message size: %i [B]" % message_size puts "message count: %i" % message_count puts "mean throughput: %i [msg/s]" % throughput puts "mean throughput: %.3f [Mb/s]" % megabits assert(s.close) ctx.terminate ruby-ffi-rzmq-1.0.3/examples/v3api/throughput_measurement.rb000066400000000000000000000070521222421464700242400ustar00rootroot00000000000000require File.join(File.dirname(__FILE__), '..', '..', 'lib', 'ffi-rzmq') require 'thread' # Within a single process, we start up five threads. Main thread has a PUB (publisher) # socket and the secondary threads have SUB (subscription) sockets. We measure the # *throughput* between these sockets. A high-water mark (HWM) is *not* set, so the # publisher queue is free to grow to the size of memory without dropping packets. # # This example also illustrates how a single context can be shared amongst several # threads. Sharing a single context also allows a user to specify the "inproc" # transport in addition to "tcp" and "ipc". # # % ruby throughput_measurement.rb tcp://127.0.0.1:5555 1024 1_000_000 # # % ruby throughput_measurement.rb inproc://lm_sock 1024 1_000_000 # if ARGV.length < 3 puts "usage: ruby throughput_measurement.rb " exit end link = ARGV[0] message_size = ARGV[1].to_i count = ARGV[2].to_i def assert(rc) raise "Last API call failed at #{caller(1)}" unless rc >= 0 end begin master_context = ZMQ::Context.new rescue ContextError => e STDERR.puts "Failed to allocate context or socket!" raise end class Receiver def initialize context, link, size, count, stats @context = context @link = link @size = size @count = count @stats = stats begin @socket = @context.socket(ZMQ::SUB) rescue ContextError => e STDERR.puts "Failed to allocate SUB socket!" raise end assert(@socket.setsockopt(ZMQ::LINGER, 100)) assert(@socket.setsockopt(ZMQ::SUBSCRIBE, "")) assert(@socket.connect(@link)) end def run msg = ZMQ::Message.new assert(@socket.recvmsg(msg)) elapsed = elapsed_microseconds do (@count - 1).times do assert(@socket.recvmsg(msg)) end end @stats.record_elapsed(elapsed) assert(@socket.close) end def elapsed_microseconds(&blk) start = Time.now yield ((Time.now - start) * 1_000_000) end end class Transmitter def initialize context, link, size, count @context = context @link = link @size = size @count = count begin @socket = @context.socket(ZMQ::PUB) rescue ContextError => e STDERR.puts "Failed to allocate PUB socket!" raise end assert(@socket.setsockopt(ZMQ::LINGER, 100)) assert(@socket.bind(@link)) end def run sleep 1 contents = "#{'0' * @size}" i = 0 while i < @count msg = ZMQ::Message.new(contents) assert(@socket.sendmsg(msg)) i += 1 end end def close assert(@socket.close) end end class Stats def initialize size, count @size = size @count = count @mutex = Mutex.new @elapsed = [] end def record_elapsed(elapsed) @mutex.synchronize do @elapsed << elapsed end end def output @elapsed.each do |elapsed| throughput = @count * 1000000 / elapsed megabits = throughput * @size * 8 / 1000000 puts "message size: %i [B]" % @size puts "message count: %i" % @count puts "mean throughput: %i [msg/s]" % throughput puts "mean throughput: %.3f [Mb/s]" % megabits puts end end end threads = [] stats = Stats.new message_size, count transmitter = Transmitter.new(master_context, link, message_size, count) threads << Thread.new do transmitter.run end 1.times do threads << Thread.new do receiver = Receiver.new(master_context, link, message_size, count, stats) receiver.run end end threads.each {|t| t.join} transmitter.close stats.output master_context.terminate ruby-ffi-rzmq-1.0.3/examples/v3api/xreqxrep_poll.rb000066400000000000000000000044711222421464700223300ustar00rootroot00000000000000 require File.join(File.dirname(__FILE__), '..', '..', 'lib', 'ffi-rzmq') def assert(rc) raise "Last API call failed at #{caller(1)}" unless rc >= 0 end link = "tcp://127.0.0.1:5555" begin ctx = ZMQ::Context.new s1 = ctx.socket(ZMQ::XREQ) s2 = ctx.socket(ZMQ::XREP) rescue ContextError => e STDERR.puts "Failed to allocate context or socket" raise end s1.identity = 'socket1.xreq' s2.identity = 'socket2.xrep' assert(s1.setsockopt(ZMQ::LINGER, 100)) assert(s2.setsockopt(ZMQ::LINGER, 100)) assert(s1.bind(link)) assert(s2.connect(link)) poller = ZMQ::Poller.new poller.register_readable(s2) poller.register_writable(s1) start_time = Time.now @unsent = true until @done do assert(poller.poll_nonblock) # send the message after 5 seconds if Time.now - start_time > 5 && @unsent puts "sending payload nonblocking" 5.times do |i| payload = "#{ i.to_s * 40 }" assert(s1.send_string(payload, ZMQ::NonBlocking)) end @unsent = false end # check for messages after 1 second if Time.now - start_time > 1 poller.readables.each do |sock| if sock.identity =~ /xrep/ routing_info = '' assert(sock.recv_string(routing_info, ZMQ::NonBlocking)) puts "routing_info received [#{routing_info}] on socket.identity [#{sock.identity}]" else routing_info = nil received_msg = '' assert(sock.recv_string(received_msg, ZMQ::NonBlocking)) # skip to the next iteration if received_msg is nil; that means we got an EAGAIN next unless received_msg puts "message received [#{received_msg}] on socket.identity [#{sock.identity}]" end while sock.more_parts? do received_msg = '' assert(sock.recv_string(received_msg, ZMQ::NonBlocking)) puts "message received [#{received_msg}]" end puts "kick back a reply" assert(sock.send_string(routing_info, ZMQ::SNDMORE | ZMQ::NonBlocking)) if routing_info time = Time.now.strftime "%Y-%m-%dT%H:%M:%S.#{Time.now.usec}" reply = "reply " + sock.identity.upcase + " #{time}" puts "sent reply [#{reply}], #{time}" assert(sock.send_string(reply)) @done = true poller.register_readable(s1) end end end puts "executed in [#{Time.now - start_time}] seconds" assert(s1.close) assert(s2.close) ctx.terminate ruby-ffi-rzmq-1.0.3/ext/000077500000000000000000000000001222421464700150315ustar00rootroot00000000000000ruby-ffi-rzmq-1.0.3/ext/README000066400000000000000000000003441222421464700157120ustar00rootroot00000000000000To avoid loading a system-wide 0mq library, place the C libraries here. This let's you run your Ruby code with a 0mq C library build that is different from the system-wide one. This can be handy for rolling upgrades or testing. ruby-ffi-rzmq-1.0.3/ffi-rzmq.gemspec000066400000000000000000000021411222421464700173270ustar00rootroot00000000000000# -*- encoding: utf-8 -*- $:.push File.expand_path("../lib", __FILE__) require "ffi-rzmq/version" Gem::Specification.new do |s| s.name = "ffi-rzmq" s.version = ZMQ::VERSION s.authors = ["Chuck Remes"] s.email = ["git@chuckremes.com"] s.homepage = "http://github.com/chuckremes/ffi-rzmq" s.summary = %q{This gem wraps the ZeroMQ (0mq) networking library using Ruby FFI (foreign function interface).} s.description = %q{This gem wraps the ZeroMQ networking library using the ruby FFI (foreign function interface). It's a pure ruby wrapper so this gem can be loaded and run by any ruby runtime that supports FFI. That's all of the major ones - MRI, Rubinius and JRuby.} s.rubyforge_project = "ffi-rzmq" s.files = `git ls-files`.split("\n") s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } s.require_paths = ["lib"] s.add_runtime_dependency "ffi"#, [">= 1.0.9"] s.add_development_dependency "rspec", ["~> 2.6"] s.add_development_dependency "rake" end ruby-ffi-rzmq-1.0.3/lib/000077500000000000000000000000001222421464700147775ustar00rootroot00000000000000ruby-ffi-rzmq-1.0.3/lib/ffi-rzmq.rb000066400000000000000000000040101222421464700170520ustar00rootroot00000000000000module ZMQ # :stopdoc: LIBPATH = ::File.expand_path(::File.dirname(__FILE__)) + ::File::SEPARATOR PATH = ::File.dirname(LIBPATH) + ::File::SEPARATOR # :startdoc: # Returns the version string for the library. # def self.version @version ||= ZMQ::VERSION end # Returns the library path for the module. If any arguments are given, # they will be joined to the end of the libray path using # File.join. # def self.libpath( *args, &block ) rv = args.empty? ? LIBPATH : ::File.join(LIBPATH, args.flatten) if block begin $LOAD_PATH.unshift LIBPATH rv = block.call ensure $LOAD_PATH.shift end end return rv end # Returns the lpath for the module. If any arguments are given, # they will be joined to the end of the path using # File.join. # def self.path( *args, &block ) rv = args.empty? ? PATH : ::File.join(PATH, args.flatten) if block begin $LOAD_PATH.unshift PATH rv = block.call ensure $LOAD_PATH.shift end end return rv end # Utility method used to require all files ending in .rb that lie in the # directory below this file that has the same name as the filename passed # in. Optionally, a specific _directory_ name can be passed in such that # the _filename_ does not have to be equivalent to the directory. # def self.require_all_libs_relative_to( fname, dir = nil ) dir ||= ::File.basename(fname, '.*') search_me = ::File.expand_path( ::File.join(::File.dirname(fname), dir, '**', '*.rb')) Dir.glob(search_me).sort.each {|rb| require rb} end end # module ZMQ # some code is conditionalized based upon what ruby engine we are # executing require 'ffi' # the order of files is important #%w(wrapper zmq exceptions context message socket poll_items poll device).each do |file| %w(libc libzmq constants util exceptions context message socket poll_items poll_item poll device).each do |file| require ZMQ.libpath(['ffi-rzmq', file]) end ruby-ffi-rzmq-1.0.3/lib/ffi-rzmq/000077500000000000000000000000001222421464700165325ustar00rootroot00000000000000ruby-ffi-rzmq-1.0.3/lib/ffi-rzmq/constants.rb000066400000000000000000000110551222421464700210750ustar00rootroot00000000000000module ZMQ # Set up all of the constants that are *common* to all API # versions # Socket types PAIR = 0 PUB = 1 SUB = 2 REQ = 3 REP = 4 XREQ = 5 XREP = 6 PULL = 7 PUSH = 8 SocketTypeNameMap = { PAIR => "PAIR", PUB => "PUB", SUB => "SUB", REQ => "REQ", REP => "REP", PULL => "PULL", PUSH => "PUSH", XREQ => "XREQ", XREP => "XREP" } # Socket options AFFINITY = 4 SUBSCRIBE = 6 UNSUBSCRIBE = 7 RATE = 8 RECOVERY_IVL = 9 SNDBUF = 11 RCVBUF = 12 RCVMORE = 13 FD = 14 EVENTS = 15 TYPE = 16 LINGER = 17 RECONNECT_IVL = 18 BACKLOG = 19 RECONNECT_IVL_MAX = 21 RCVTIMEO = 27 SNDTIMEO = 28 # Send/recv options SNDMORE = 2 # I/O multiplexing POLL = 1 POLLIN = 1 POLLOUT = 2 POLLERR = 4 # Socket errors EAGAIN = Errno::EAGAIN::Errno EINVAL = Errno::EINVAL::Errno ENOMEM = Errno::ENOMEM::Errno ENODEV = Errno::ENODEV::Errno EFAULT = Errno::EFAULT::Errno EINTR = Errno::EINTR::Errno # ZMQ errors HAUSNUMERO = 156384712 EFSM = (HAUSNUMERO + 51) ENOCOMPATPROTO = (HAUSNUMERO + 52) ETERM = (HAUSNUMERO + 53) EMTHREAD = (HAUSNUMERO + 54) # Rescue unknown constants and use the ZeroMQ defined values # Usually only happens on Windows though some don't resolve on # OSX too (ENOTSUP) ENOTSUP = Errno::ENOTSUP::Errno rescue (HAUSNUMERO + 1) EPROTONOSUPPORT = Errno::EPROTONOSUPPORT::Errno rescue (HAUSNUMERO + 2) ENOBUFS = Errno::ENOBUFS::Errno rescue (HAUSNUMERO + 3) ENETDOWN = Errno::ENETDOWN::Errno rescue (HAUSNUMERO + 4) EADDRINUSE = Errno::EADDRINUSE::Errno rescue (HAUSNUMERO + 5) EADDRNOTAVAIL = Errno::EADDRNOTAVAIL::Errno rescue (HAUSNUMERO + 6) ECONNREFUSED = Errno::ECONNREFUSED::Errno rescue (HAUSNUMERO + 7) EINPROGRESS = Errno::EINPROGRESS::Errno rescue (HAUSNUMERO + 8) ENOTSOCK = Errno::ENOTSOCK::Errno rescue (HAUSNUMERO + 9) EMSGSIZE = Errno::EMSGSIZE::Errno rescue (HAUSNUMERO + 10) EAFNOSUPPORT = Errno::EAFNOSUPPORT::Errno rescue (HAUSNUMERO + 11) ENETUNREACH = Errno::ENETUNREACH::Errno rescue (HAUSNUMERO + 12) ECONNABORTED = Errno::ECONNABORTED::Errno rescue (HAUSNUMERO + 13) ECONNRESET = Errno::ECONNRESET::Errno rescue (HAUSNUMERO + 14) ENOTCONN = Errno::ENOTCONN::Errno rescue (HAUSNUMERO + 15) ETIMEDOUT = Errno::ETIMEDOUT::Errno rescue (HAUSNUMERO + 16) EHOSTUNREACH = Errno::EHOSTUNREACH::Errno rescue (HAUSNUMERO + 17) ENETRESET = Errno::ENETRESET::Errno rescue (HAUSNUMERO + 18) # Device Types STREAMER = 1 FORWARDER = 2 QUEUE = 3 end # module ZMQ if ZMQ::LibZMQ.version2? module ZMQ # Socket types UPSTREAM = PULL DOWNSTREAM = PUSH DEALER = XREQ ROUTER = XREP SocketTypeNameMap[ROUTER] = 'ROUTER' SocketTypeNameMap[DEALER] = 'DEALER' # Socket options HWM = 1 IDENTITY = 5 MCAST_LOOP = 10 SWAP = 3 RECOVERY_IVL_MSEC = 20 # Send/recv options NOBLOCK = 1 NonBlocking = NOBLOCK end end # version2? if ZMQ::LibZMQ.version3? module ZMQ # Socket types XPUB = 9 XSUB = 10 DEALER = XREQ ROUTER = XREP SocketTypeNameMap[ROUTER] = 'ROUTER' SocketTypeNameMap[DEALER] = 'DEALER' SocketTypeNameMap[XPUB] = 'XPUB' SocketTypeNameMap[XSUB] = 'XSUB' # Context options IO_THREADS = 1 MAX_SOCKETS = 2 IO_THREADS_DFLT = 1 MAX_SOCKETS_DFLT = 1024 # Socket options IDENTITY = 5 MAXMSGSIZE = 22 SNDHWM = 23 RCVHWM = 24 MULTICAST_HOPS = 25 IPV4ONLY = 31 LAST_ENDPOINT = 32 ROUTER_BEHAVIOR = 33 TCP_KEEPALIVE = 34 TCP_KEEPALIVE_CNT = 35 TCP_KEEPALIVE_IDLE = 36 TCP_KEEPALIVE_INTVL = 37 TCP_ACCEPT_FILTER = 38 # Message options MORE = 1 # Send/recv options DONTWAIT = 1 SNDLABEL = 4 NonBlocking = DONTWAIT # Socket events and monitoring EVENT_CONNECTED = 1 EVENT_CONNECT_DELAYED = 2 EVENT_CONNECT_RETRIED = 4 EVENT_LISTENING = 8 EVENT_BIND_FAILED = 16 EVENT_ACCEPTED = 32 EVENT_ACCEPT_FAILED = 64 EVENT_CLOSED = 128 EVENT_CLOSE_FAILED = 256 EVENT_DISCONNECTED = 512 EVENT_ALL = EVENT_CONNECTED | EVENT_CONNECT_DELAYED | EVENT_CONNECT_RETRIED | EVENT_LISTENING | EVENT_BIND_FAILED | EVENT_ACCEPTED | EVENT_ACCEPT_FAILED | EVENT_CLOSED | EVENT_CLOSE_FAILED | EVENT_DISCONNECTED # Socket & other errors EMFILE = Errno::EMFILE::Errno end end # version3? ruby-ffi-rzmq-1.0.3/lib/ffi-rzmq/context.rb000066400000000000000000000104651222421464700205510ustar00rootroot00000000000000 module ZMQ # Recommended to use the default for +io_threads+ # since most programs will not saturate I/O. # # The rule of thumb is to make +io_threads+ equal to the number # gigabits per second that the application will produce. # # The +io_threads+ number specifies the size of the thread pool # allocated by 0mq for processing incoming/outgoing messages. # # Returns a context object when allocation succeeds. It's necessary # for passing to the # #Socket constructor when allocating new sockets. All sockets # live within a context. # # Also, Sockets should *only* be accessed from the thread where they # were first created. Do *not* pass sockets between threads; pass # in the context and allocate a new socket per thread. If you must # use threads, then make sure to execute a full memory barrier (e.g. # mutex) as you pass a socket from one thread to the next. # # To connect sockets between contexts, use +inproc+ or +ipc+ # transport and set up a 0mq socket between them. This is also the # recommended technique for allowing sockets to communicate between # threads. # # context = ZMQ::Context.create # if context # socket = context.socket(ZMQ::REQ) # if socket # ... # else # STDERR.puts "Socket allocation failed" # end # else # STDERR.puts "Context allocation failed" # end # # class Context attr_reader :context, :io_threads, :max_sockets alias :pointer :context # Use the factory method Context#create to make contexts. # if LibZMQ.version2? def self.create io_threads = 1 new(io_threads) rescue nil end def initialize io_threads = 1 @io_threads = io_threads @context = LibZMQ.zmq_init io_threads ZMQ::Util.error_check 'zmq_init', (@context.nil? || @context.null?) ? -1 : 0 define_finalizer end elsif LibZMQ.version3? def self.create(opts = {}) new(opts) rescue nil end def initialize(opts = {}) if opts.respond_to?(:empty?) @io_threads = opts[:io_threads] || IO_THREADS_DFLT @max_sockets = opts[:max_sockets] || MAX_SOCKETS_DFLT else @io_threads = opts || 1 @max_sockets = MAX_SOCKETS_DFLT end @context = LibZMQ.zmq_ctx_new ZMQ::Util.error_check 'zmq_ctx_new', (@context.nil? || @context.null?) ? -1 : 0 rc = LibZMQ.zmq_ctx_set(@context, ZMQ::IO_THREADS, @io_threads) ZMQ::Util.error_check 'zmq_ctx_set', rc rc = LibZMQ.zmq_ctx_set(@context, ZMQ::MAX_SOCKETS, @max_sockets) ZMQ::Util.error_check 'zmq_ctx_set', rc define_finalizer end end # Call to release the context and any remaining data associated # with past sockets. This will close any sockets that remain # open; further calls to those sockets will return -1 to indicate # the operation failed. # # Returns 0 for success, -1 for failure. # if LibZMQ.version2? def terminate unless @context.nil? || @context.null? remove_finalizer rc = LibZMQ.zmq_term @context @context = nil rc else 0 end end elsif LibZMQ.version3? def terminate unless @context.nil? || @context.null? remove_finalizer rc = LibZMQ.zmq_ctx_destroy(@context) @context = nil rc else 0 end end end # Short-cut to allocate a socket for a specific context. # # Takes several +type+ values: # #ZMQ::REQ # #ZMQ::REP # #ZMQ::PUB # #ZMQ::SUB # #ZMQ::PAIR # #ZMQ::PULL # #ZMQ::PUSH # #ZMQ::DEALER # #ZMQ::ROUTER # # Returns a #ZMQ::Socket when the allocation succeeds, nil # if it fails. # def socket type sock = nil begin sock = Socket.new @context, type rescue ContextError => e sock = nil end sock end private def define_finalizer ObjectSpace.define_finalizer(self, self.class.close(@context, Process.pid)) end def remove_finalizer ObjectSpace.undefine_finalizer self end def self.close context, pid Proc.new { LibZMQ.zmq_term context if !context.null? && Process.pid == pid } end end end # module ZMQ ruby-ffi-rzmq-1.0.3/lib/ffi-rzmq/device.rb000066400000000000000000000011671222421464700203230ustar00rootroot00000000000000module ZMQ class Device attr_reader :device def self.create(device_type, frontend, backend) dev = nil begin dev = new(device_type, frontend, backend) rescue ArgumentError dev = nil end dev end def initialize(device_type, frontend, backend) [["frontend", frontend], ["backend", backend]].each do |name, socket| unless socket.is_a?(ZMQ::Socket) raise ArgumentError, "Expected a ZMQ::Socket, not a #{socket.class} as the #{name}" end end LibZMQ.zmq_device(device_type, frontend.socket, backend.socket) end end end ruby-ffi-rzmq-1.0.3/lib/ffi-rzmq/exceptions.rb000066400000000000000000000023241222421464700212410ustar00rootroot00000000000000 module ZMQ class ZeroMQError < StandardError attr_reader :source, :result_code, :error_code, :message def initialize source, result_code, error_code, message @source = source @result_code = result_code @error_code = error_code @message = "msg [#{message}], error code [#{error_code}], rc [#{result_code}]" super message end end # call ZeroMQError class ContextError < ZeroMQError # True when the exception was raised due to the library # returning EINVAL. # # Occurs when he number of app_threads requested is less # than one, or the number of io_threads requested is # negative. # def einval?() EINVAL == @error_code; end # True when the exception was raised due to the library # returning ETERM. # # The associated context was terminated. # def eterm?() ETERM == @error_code; end end # class ContextError class MessageError < ZeroMQError # True when the exception was raised due to the library # returning ENOMEM. # # Only ever raised by the #Message class when it fails # to allocate sufficient memory to send a message. # def enomem?() ENOMEM == @error_code; end end end # module ZMQ ruby-ffi-rzmq-1.0.3/lib/ffi-rzmq/libc.rb000066400000000000000000000011031222421464700177630ustar00rootroot00000000000000 module LibC extend FFI::Library # figures out the correct libc for each platform including Windows library = ffi_lib(FFI::Library::LIBC).first # Size_t not working properly on Windows find_type(:size_t) rescue typedef(:ulong, :size_t) # memory allocators attach_function :malloc, [:size_t], :pointer attach_function :free, [:pointer], :void # get a pointer to the free function; used for ZMQ::Message deallocation Free = library.find_symbol('free') # memory movers attach_function :memcpy, [:pointer, :pointer, :size_t], :pointer end # module LibC ruby-ffi-rzmq-1.0.3/lib/ffi-rzmq/libzmq.rb000066400000000000000000000221021222421464700203520ustar00rootroot00000000000000module ZMQ # Wraps the libzmq library and attaches to the functions that are # common across the 2.x and 3.x APIs. # module LibZMQ extend FFI::Library begin # bias the library discovery to a path inside the gem first, then # to the usual system paths inside_gem = File.join(File.dirname(__FILE__), '..', '..', 'ext') local_path = FFI::Platform::IS_WINDOWS ? ENV['PATH'].split(';') : ENV['PATH'].split(':') # Search for libzmq in the following order... ZMQ_LIB_PATHS =([inside_gem] + local_path + [ '/usr/local/lib', '/opt/local/lib', '/usr/local/homebrew/lib', '/usr/lib64' ]).map{|path| "#{path}/libzmq.#{FFI::Platform::LIBSUFFIX}"} ffi_lib(ZMQ_LIB_PATHS + %w{libzmq}) rescue LoadError => e if ZMQ_LIB_PATHS.any? {|path| File.file?(path) } warn "Unable to load this gem. The libzmq library exists, but cannot be loaded." warn "If this is Windows:" warn "- Check that you have MSVC runtime installed or statically linked" warn "- Check that your DLL is compiled for #{FFI::Platform::ADDRESS_SIZE} bit" else warn "Unable to load this gem. The libzmq library (or DLL) could not be found." warn "If this is a Windows platform, make sure libzmq.dll is on the PATH." warn "If the DLL was built with mingw, make sure the other two dependent DLLs," warn "libgcc_s_sjlj-1.dll and libstdc++6.dll, are also on the PATH." warn "For non-Windows platforms, make sure libzmq is located in this search path:" warn ZMQ_LIB_PATHS.inspect end raise LoadError, e.message end # Size_t not working properly on Windows find_type(:size_t) rescue typedef(:ulong, :size_t) # Context and misc api # # @blocking = true is a hint to FFI that the following (and only the following) # function may block, therefore it should release the GIL before calling it. # This can aid in situations where the function call will/may block and another # thread within the lib may try to call back into the ruby runtime. Failure to # release the GIL will result in a hang; the hint *may* allow things to run # smoothly for Ruby runtimes hampered by a GIL. # # This is really only honored by the MRI implementation but it *is* necessary # otherwise the runtime hangs (and requires a kill -9 to terminate) # @blocking = true attach_function :zmq_version, [:pointer, :pointer, :pointer], :void @blocking = true attach_function :zmq_errno, [], :int @blocking = true attach_function :zmq_strerror, [:int], :pointer def self.version if @version.nil? major = FFI::MemoryPointer.new :int minor = FFI::MemoryPointer.new :int patch = FFI::MemoryPointer.new :int LibZMQ.zmq_version major, minor, patch @version = {:major => major.read_int, :minor => minor.read_int, :patch => patch.read_int} end @version end def self.version2?() version[:major] == 2 && version[:minor] >= 1 end def self.version3?() version[:major] == 3 && version[:minor] >= 2 end # Context initialization and destruction @blocking = true attach_function :zmq_init, [:int], :pointer @blocking = true attach_function :zmq_term, [:pointer], :int # Message API @blocking = true attach_function :zmq_msg_init, [:pointer], :int @blocking = true attach_function :zmq_msg_init_size, [:pointer, :size_t], :int @blocking = true attach_function :zmq_msg_init_data, [:pointer, :pointer, :size_t, :pointer, :pointer], :int @blocking = true attach_function :zmq_msg_close, [:pointer], :int @blocking = true attach_function :zmq_msg_data, [:pointer], :pointer @blocking = true attach_function :zmq_msg_size, [:pointer], :size_t @blocking = true attach_function :zmq_msg_copy, [:pointer, :pointer], :int @blocking = true attach_function :zmq_msg_move, [:pointer, :pointer], :int # Used for casting pointers back to the struct # class Msg < FFI::Struct layout :content, :pointer, :flags, :uint8, :vsm_size, :uint8, :vsm_data, [:uint8, 30] end # class Msg # Socket API @blocking = true attach_function :zmq_socket, [:pointer, :int], :pointer @blocking = true attach_function :zmq_setsockopt, [:pointer, :int, :pointer, :int], :int @blocking = true attach_function :zmq_getsockopt, [:pointer, :int, :pointer, :pointer], :int @blocking = true attach_function :zmq_bind, [:pointer, :string], :int @blocking = true attach_function :zmq_connect, [:pointer, :string], :int @blocking = true attach_function :zmq_close, [:pointer], :int # Device API @blocking = true attach_function :zmq_device, [:int, :pointer, :pointer], :int # Poll API @blocking = true attach_function :zmq_poll, [:pointer, :int, :long], :int module PollItemLayout def self.included(base) if FFI::Platform::IS_WINDOWS && FFI::Platform::ADDRESS_SIZE==64 # On Windows, zmq.h defines fd as a SOCKET, which is 64 bits on x64. fd_type=:uint64 else fd_type=:int end base.class_eval do layout :socket, :pointer, :fd, fd_type, :events, :short, :revents, :short end end end # module PollItemLayout class PollItem < FFI::Struct include PollItemLayout def socket() self[:socket]; end def fd() self[:fd]; end def readable? (self[:revents] & ZMQ::POLLIN) > 0 end def writable? (self[:revents] & ZMQ::POLLOUT) > 0 end def both_accessible? readable? && writable? end def inspect "socket [#{socket}], fd [#{fd}], events [#{self[:events]}], revents [#{self[:revents]}]" end def to_s; inspect; end end # class PollItem end # Attaches to those functions specific to the 2.x API # if LibZMQ.version2? module LibZMQ # Socket api @blocking = true attach_function :zmq_recv, [:pointer, :pointer, :int], :int @blocking = true attach_function :zmq_send, [:pointer, :pointer, :int], :int end end # Attaches to those functions specific to the 3.x API # if LibZMQ.version3? module LibZMQ # New Context API @blocking = true attach_function :zmq_ctx_new, [], :pointer @blocking = true attach_function :zmq_ctx_destroy, [:pointer], :int @blocking = true attach_function :zmq_ctx_set, [:pointer, :int, :int], :int @blocking = true attach_function :zmq_ctx_get, [:pointer, :int], :int # Message API @blocking = true attach_function :zmq_msg_send, [:pointer, :pointer, :int], :int @blocking = true attach_function :zmq_msg_recv, [:pointer, :pointer, :int], :int @blocking = true attach_function :zmq_msg_more, [:pointer], :int @blocking = true attach_function :zmq_msg_get, [:pointer, :int], :int @blocking = true attach_function :zmq_msg_set, [:pointer, :int, :int], :int # Monitoring API # zmq_ctx_set_monitor is no longer supported as of version >= 3.2.1 # replaced by zmq_socket_monitor if LibZMQ.version[:minor] > 2 || (LibZMQ.version[:minor] == 2 && LibZMQ.version[:patch] >= 1) @blocking = true attach_function :zmq_socket_monitor, [:pointer, :pointer, :int], :int else @blocking = true attach_function :zmq_ctx_set_monitor, [:pointer, :pointer], :int end # Socket API @blocking = true attach_function :zmq_unbind, [:pointer, :string], :int @blocking = true attach_function :zmq_disconnect, [:pointer, :string], :int @blocking = true attach_function :zmq_recvmsg, [:pointer, :pointer, :int], :int @blocking = true attach_function :zmq_recv, [:pointer, :pointer, :size_t, :int], :int @blocking = true attach_function :zmq_sendmsg, [:pointer, :pointer, :int], :int @blocking = true attach_function :zmq_send, [:pointer, :pointer, :size_t, :int], :int module EventDataLayout def self.included(base) base.class_eval do layout :event, :int, :addr, :string, :field2, :int end end end # module EventDataLayout class EventData < FFI::Struct include EventDataLayout def event() self[:event]; end def addr() self[:addr]; end alias :address :addr def fd() self[:field2]; end alias :err :fd alias :interval :fd def inspect "event [#{event}], addr [#{addr}], fd [#{fd}], field2 [#{fd}]" end def to_s; inspect; end end # class EventData end end # Sanity check; print an error and exit if we are trying to load an unsupported # version of libzmq. # unless LibZMQ.version2? || LibZMQ.version3? hash = LibZMQ.version version = "#{hash[:major]}.#{hash[:minor]}.#{hash[:patch]}" raise LoadError, "The libzmq version #{version} is incompatible with ffi-rzmq." end end # module ZMQ ruby-ffi-rzmq-1.0.3/lib/ffi-rzmq/message.rb000066400000000000000000000221471222421464700205110ustar00rootroot00000000000000 module ZMQ # The factory constructor optionally takes a string as an argument. It will # copy this string to native memory in preparation for transmission. # So, don't pass a string unless you intend to send it. Internally it # calls #copy_in_string. # # Call #close to release buffers when you are done with the data. # # (This class is not really zero-copy. Ruby makes this near impossible # since Ruby objects can be relocated in memory by the GC at any # time. There is no way to peg them to native memory or have them # use non-movable native memory as backing store.) # # Message represents ruby equivalent of the +zmq_msg_t+ C struct. # Access the underlying memory buffer and the buffer size using the # #data and #size methods respectively. # # It is recommended that this class be composed inside another class for # access to the underlying buffer. The outer wrapper class can provide # nice accessors for the information in the data buffer; a clever # implementation can probably lazily encode/decode the data buffer # on demand. Lots of protocols send more information than is strictly # necessary, so only decode (copy from the 0mq buffer to Ruby) that # which is necessary. # # When you are done using a *received* message object, call #close to # release the associated buffers. # # received_message = Message.create # if received_message # rc = socket.recvmsg(received_message) # if ZMQ::Util.resultcode_ok?(rc) # puts "Message contained: #{received_message.copy_out_string}" # else # STDERR.puts "Error when receiving message: #{ZMQ::Util.error_string}" # end # # # Define a custom layout for the data sent between 0mq peers. # # class MyMessage # class Layout < FFI::Struct # layout :value1, :uint8, # :value2, :uint64, # :value3, :uint32, # :value4, [:char, 30] # end # # def initialize msg_struct = nil # if msg_struct # @msg_t = msg_struct # @data = Layout.new(@msg_t.data) # else # @pointer = FFI::MemoryPointer.new :byte, Layout.size, true # @data = Layout.new @pointer # end # end # # def size() @size = @msg_t.size; end # # def value1 # @data[:value1] # end # # def value4 # @data[:value4].to_ptr.read_string # end # # def value1=(val) # @data[:value1] = val # end # # def create_sendable_message # msg = Message.new # msg.copy_in_bytes @pointer, Layout.size # end # # # message = Message.new # successful_read = socket.recv message # message = MyMessage.new message if successful_read # puts "value1 is #{message.value1}" # class Message # Recommended way to create a standard message. A Message object is # returned upon success, nil when allocation fails. # def self.create message = nil new(message) rescue nil end def initialize message = nil # allocate our own pointer so that we can tell it to *not* zero out # the memory; it's pointless work since the library is going to # overwrite it anyway. @pointer = FFI::MemoryPointer.new Message.msg_size, 1, false if message copy_in_string message else # initialize an empty message structure to receive a message result_code = LibZMQ.zmq_msg_init @pointer raise unless Util.resultcode_ok?(result_code) end end # Makes a copy of the ruby +string+ into a native memory buffer so # that libzmq can send it. The underlying library will handle # deallocation of the native memory buffer. # # Can only be initialized via #copy_in_string or #copy_in_bytes once. # def copy_in_string string string_size = string.respond_to?(:bytesize) ? string.bytesize : string.size copy_in_bytes string, string_size if string end # Makes a copy of +len+ bytes from the ruby string +bytes+. Library # handles deallocation of the native memory buffer. # # Can only be initialized via #copy_in_string or #copy_in_bytes once. # def copy_in_bytes bytes, len data_buffer = LibC.malloc len # writes the exact number of bytes, no null byte to terminate string data_buffer.write_string bytes, len # use libC to call free on the data buffer; earlier versions used an # FFI::Function here that called back into Ruby, but Rubinius won't # support that and there are issues with the other runtimes too LibZMQ.zmq_msg_init_data @pointer, data_buffer, len, LibC::Free, nil end # Provides the memory address of the +zmq_msg_t+ struct. Used mostly for # passing to other methods accessing the underlying library that # require a real data address. # def address @pointer end alias :pointer :address def copy source LibZMQ.zmq_msg_copy @pointer, source end def move source LibZMQ.zmq_msg_move @pointer, source end # Provides the size of the data buffer for this +zmq_msg_t+ C struct. # def size LibZMQ.zmq_msg_size @pointer end # Returns a pointer to the data buffer. # This pointer should *never* be freed. It will automatically be freed # when the +message+ object goes out of scope and gets garbage # collected. # def data LibZMQ.zmq_msg_data @pointer end # Returns the data buffer as a string. # # Note: If this is binary data, it won't print very prettily. # def copy_out_string data.read_string(size) end # Manually release the message struct and its associated data # buffer. # # Only releases the buffer a single time. Subsequent calls are # no ops. # def close rc = 0 if @pointer rc = LibZMQ.zmq_msg_close @pointer @pointer = nil end rc end # cache the msg size so we don't have to recalculate it when creating # each new instance @msg_size = LibZMQ::Msg.size def self.msg_size() @msg_size; end end # class Message if LibZMQ.version3? class Message # Version3 only # def get(property) LibZMQ.zmq_msg_get(@pointer, property) end # Version3 only # # Returns true if this message has additional parts coming. # def more? Util.resultcode_ok?(get(MORE)) end def set(property, value) LibZMQ.zmq_msg_set(@pointer, property, value) end end end # A subclass of #Message that includes finalizers for deallocating # native memory when this object is garbage collected. Note that on # certain Ruby runtimes the use of finalizers can add 10s of # microseconds of overhead for each message. The convenience comes # at a price. # # The constructor optionally takes a string as an argument. It will # copy this string to native memory in preparation for transmission. # So, don't pass a string unless you intend to send it. Internally it # calls #copy_in_string. # # Call #close to release buffers when you have *not* passed this on # to Socket#send. That method calls #close on your behalf. # # When you are done using a *received* message object, just let it go out of # scope to release the memory. During the next garbage collection run # it will call the equivalent of #LibZMQ.zmq_msg_close to release # all buffers. Obviously, this automatic collection of message objects # comes at the price of a larger memory footprint (for the # finalizer proc object) and lower performance. If you wanted blistering # performance, Ruby isn't there just yet. # # As noted above, for sent objects the underlying library will call close # for you. # class ManagedMessage < Message # Makes a copy of +len+ bytes from the ruby string +bytes+. Library # handles deallocation of the native memory buffer. # def copy_in_bytes bytes, len rc = super(bytes, len) # make sure we have a way to deallocate this memory if the object goes # out of scope define_finalizer rc end # Manually release the message struct and its associated data # buffer. # def close rc = super() remove_finalizer rc end private def define_finalizer ObjectSpace.define_finalizer(self, self.class.close(@pointer)) end def remove_finalizer ObjectSpace.undefine_finalizer self end # Message finalizer # Note that there is no error checking for the call to #zmq_msg_close. # This is intentional. Since this code runs as a finalizer, there is no # way to catch a raised exception anywhere near where the error actually # occurred in the code, so we just ignore deallocation failures here. def self.close ptr Proc.new do # release the data buffer LibZMQ.zmq_msg_close ptr end end # cache the msg size so we don't have to recalculate it when creating # each new instance # need to do this again because ivars are not inheritable @msg_size = LibZMQ::Msg.size end # class ManagedMessage end # module ZMQ ruby-ffi-rzmq-1.0.3/lib/ffi-rzmq/poll.rb000066400000000000000000000115261222421464700200320ustar00rootroot00000000000000require 'forwardable' module ZMQ class Poller extend Forwardable def_delegators :@poll_items, :size, :inspect attr_reader :readables, :writables def initialize @poll_items = ZMQ::PollItems.new @readables = [] @writables = [] end # Checks each registered socket for selectability based on the poll items' # registered +events+. Will block for up to +timeout+ milliseconds. # A millisecond is 1/1000 of a second, so to block for 1 second # pass the value "1000" to #poll. # # Pass "-1" or +:blocking+ for +timeout+ for this call to block # indefinitely. # # This method will return *immediately* when there are no registered # sockets. In that case, the +timeout+ parameter is not honored. To # prevent a CPU busy-loop, the caller of this method should detect # this possible condition (via #size) and throttle the call # frequency. # # Returns 0 when there are no registered sockets that are readable # or writable. # # Return 1 (or greater) to indicate the number of readable or writable # sockets. These sockets should be processed using the #readables and # #writables accessors. # # Returns -1 when there is an error. Use ZMQ::Util.errno to get the related # error number. # def poll timeout = :blocking unless @poll_items.empty? timeout = adjust timeout items_triggered = LibZMQ.zmq_poll @poll_items.address, @poll_items.size, timeout update_selectables if Util.resultcode_ok?(items_triggered) items_triggered else 0 end end # The non-blocking version of #poll. See the #poll description for # potential exceptions. # # May return -1 when an error is encounted. Check ZMQ::Util.errno # to determine the underlying cause. # def poll_nonblock poll 0 end # Register the +pollable+ for +events+. This method is idempotent meaning # it can be called multiple times with the same data and the socket # will only get registered at most once. Calling multiple times with # different values for +events+ will OR the event information together. # def register pollable, events = ZMQ::POLLIN | ZMQ::POLLOUT return if pollable.nil? || events.zero? unless item = @poll_items[pollable] item = PollItem.from_pollable(pollable) @poll_items << item end item.events |= events end # Deregister the +pollable+ for +events+. When there are no events left # or socket has been closed this also deletes the socket from the poll items. # def deregister pollable, events return unless pollable item = @poll_items[pollable] if item && (item.events & events) > 0 item.events ^= events delete(pollable) if item.events.zero? || item.closed? true else false end end # A helper method to register a +pollable+ as readable events only. # def register_readable pollable register pollable, ZMQ::POLLIN end # A helper method to register a +pollable+ for writable events only. # def register_writable pollable register pollable, ZMQ::POLLOUT end # A helper method to deregister a +pollable+ for readable events. # def deregister_readable pollable deregister pollable, ZMQ::POLLIN end # A helper method to deregister a +pollable+ for writable events. # def deregister_writable pollable deregister pollable, ZMQ::POLLOUT end # Deletes the +pollable+ for all subscribed events. Called internally # when a socket has been deregistered and has no more events # registered anywhere. # # Can also be called directly to remove the socket from the polling # array. # def delete pollable return false if @poll_items.empty? @poll_items.delete(pollable) end def to_s; inspect; end private def update_selectables @readables.clear @writables.clear @poll_items.each do |poll_item| @readables << poll_item.pollable if poll_item.readable? @writables << poll_item.pollable if poll_item.writable? end end # Convert the timeout value to something usable by # the library. # # -1 or :blocking should be converted to -1. # # Users will pass in values measured as # milliseconds, so we need to convert that value to # microseconds for the library. # if LibZMQ.version2? def adjust timeout if :blocking == timeout || -1 == timeout -1 else (timeout * 1000).to_i end end else # version3 changed units from microseconds to milliseconds def adjust timeout if :blocking == timeout || -1 == timeout -1 else timeout.to_i end end end end end ruby-ffi-rzmq-1.0.3/lib/ffi-rzmq/poll_item.rb000066400000000000000000000024331222421464700210450ustar00rootroot00000000000000require 'forwardable' require 'io_extensions' module ZMQ class PollItem extend Forwardable def_delegators :@poll_item, :pointer, :readable?, :writable? attr_accessor :pollable, :poll_item def initialize(zmq_poll_item = nil) @poll_item = zmq_poll_item || LibZMQ::PollItem.new end def self.from_pointer(pointer) self.new(LibZMQ::PollItem.new(pointer)) end def self.from_pollable(pollable) item = self.new item.pollable = pollable case when pollable.respond_to?(:socket) item.socket = pollable.socket when pollable.respond_to?(:posix_fileno) item.fd = pollable.posix_fileno when pollable.respond_to?(:io) item.fd = pollable.io.posix_fileno end item end def closed? case when pollable.respond_to?(:closed?) pollable.closed? when pollable.respond_to?(:socket) pollable.socket.nil? when pollable.respond_to?(:io) pollable.io.closed? end end def socket=(arg); @poll_item[:socket] = arg; end def socket; @poll_item[:socket]; end def fd=(arg); @poll_item[:fd] = arg; end def fd; @poll_item[:fd]; end def events=(arg); @poll_item[:events] = arg; end def events; @poll_item[:events]; end end end ruby-ffi-rzmq-1.0.3/lib/ffi-rzmq/poll_items.rb000066400000000000000000000035251222421464700212330ustar00rootroot00000000000000require 'forwardable' require 'ostruct' module ZMQ class PollItems include Enumerable extend Forwardable def_delegators :@pollables, :size, :empty? def initialize @pollables = {} @item_size = LibZMQ::PollItem.size @item_store = nil end def address clean @item_store end def get pollable return unless entry = @pollables[pollable] clean pointer = @item_store + (@item_size * entry.index) item = ZMQ::PollItem.from_pointer(pointer) item.pollable = pollable item end alias :[] :get def <<(poll_item) @dirty = true @pollables[poll_item.pollable] = OpenStruct.new(:index => size, :data => poll_item) end alias :push :<< def delete pollable if @pollables.delete(pollable) @dirty = true clean true else false end end def each &blk clean @pollables.each_key do |pollable| yield get(pollable) end end def inspect clean str = "" each { |item| str << "ptr [#{item[:socket]}], events [#{item[:events]}], revents [#{item[:revents]}], " } str.chop.chop end def to_s; inspect; end private # Allocate a contiguous chunk of memory and copy over the PollItem structs # to this block. Note that the old +@store+ value goes out of scope so when # it is garbage collected that native memory should be automatically freed. def clean if @dirty @item_store = FFI::MemoryPointer.new @item_size, size, true offset = 0 @pollables.each_with_index do |(pollable, entry), index| entry.index = index LibC.memcpy(@item_store + offset, entry.data.pointer, @item_size) offset += @item_size end @dirty = false end end end end ruby-ffi-rzmq-1.0.3/lib/ffi-rzmq/socket.rb000066400000000000000000000551451222421464700203610ustar00rootroot00000000000000 module ZMQ module CommonSocketBehavior attr_reader :socket, :name # Allocates a socket of type +type+ for sending and receiving data. # # +type+ can be one of ZMQ::REQ, ZMQ::REP, ZMQ::PUB, # ZMQ::SUB, ZMQ::PAIR, ZMQ::PULL, ZMQ::PUSH, ZMQ::XREQ, ZMQ::REP, # ZMQ::DEALER or ZMQ::ROUTER. # # By default, this class uses ZMQ::Message for manual # memory management. For automatic garbage collection of received messages, # it is possible to override the :receiver_class to use ZMQ::ManagedMessage. # # sock = Socket.create(Context.create, ZMQ::REQ, :receiver_class => ZMQ::ManagedMessage) # # Advanced users may want to replace the receiver class with their # own custom class. The custom class must conform to the same public API # as ZMQ::Message. # # Creation of a new Socket object can return nil when socket creation # fails. # # if (socket = Socket.new(context.pointer, ZMQ::REQ)) # ... # else # STDERR.puts "Socket creation failed" # end # def self.create context_ptr, type, opts = {:receiver_class => ZMQ::Message} new(context_ptr, type, opts) rescue nil end # To avoid rescuing exceptions, use the factory method #create for # all socket creation. # # Allocates a socket of type +type+ for sending and receiving data. # # +type+ can be one of ZMQ::REQ, ZMQ::REP, ZMQ::PUB, # ZMQ::SUB, ZMQ::PAIR, ZMQ::PULL, ZMQ::PUSH, ZMQ::XREQ, ZMQ::REP, # ZMQ::DEALER or ZMQ::ROUTER. # # By default, this class uses ZMQ::Message for manual # memory management. For automatic garbage collection of received messages, # it is possible to override the :receiver_class to use ZMQ::ManagedMessage. # # sock = Socket.new(Context.new, ZMQ::REQ, :receiver_class => ZMQ::ManagedMessage) # # Advanced users may want to replace the receiver class with their # own custom class. The custom class must conform to the same public API # as ZMQ::Message. # # Creation of a new Socket object can raise an exception. This occurs when the # +context_ptr+ is null or when the allocation of the 0mq socket within the # context fails. # # begin # socket = Socket.new(context.pointer, ZMQ::REQ) # rescue ContextError => e # # error handling # end # def initialize context_ptr, type, opts = {:receiver_class => ZMQ::Message} # users may override the classes used for receiving; class must conform to the # same public API as ZMQ::Message @receiver_klass = opts[:receiver_class] context_ptr = context_ptr.pointer if context_ptr.kind_of?(ZMQ::Context) unless context_ptr.null? @socket = LibZMQ.zmq_socket context_ptr, type if @socket && !@socket.null? @name = SocketTypeNameMap[type] else raise ContextError.new 'zmq_socket', 0, ETERM, "Socket pointer was null" end else raise ContextError.new 'zmq_socket', 0, ETERM, "Context pointer was null" end @longlong_cache = @int_cache = nil @more_parts_array = [] @option_lookup = [] populate_option_lookup define_finalizer end # Set the queue options on this socket. # # Valid +name+ values that take a numeric +value+ are: # ZMQ::HWM # ZMQ::SWAP (version 2 only) # ZMQ::AFFINITY # ZMQ::RATE # ZMQ::RECOVERY_IVL # ZMQ::MCAST_LOOP (version 2 only) # ZMQ::LINGER # ZMQ::RECONNECT_IVL # ZMQ::BACKLOG # ZMQ::RECOVER_IVL_MSEC (version 2 only) # ZMQ::RECONNECT_IVL_MAX (version 3 only) # ZMQ::MAXMSGSIZE (version 3 only) # ZMQ::SNDHWM (version 3 only) # ZMQ::RCVHWM (version 3 only) # ZMQ::MULTICAST_HOPS (version 3 only) # ZMQ::RCVTIMEO (version 3 only) # ZMQ::SNDTIMEO (version 3 only) # # Valid +name+ values that take a string +value+ are: # ZMQ::IDENTITY (version 2/3 only) # ZMQ::SUBSCRIBE # ZMQ::UNSUBSCRIBE # # Returns 0 when the operation completed successfully. # Returns -1 when this operation failed. # # With a -1 return code, the user must check ZMQ.errno to determine the # cause. # # rc = socket.setsockopt(ZMQ::LINGER, 1_000) # ZMQ::Util.resultcode_ok?(rc) ? puts("succeeded") : puts("failed") # def setsockopt name, value, length = nil if 1 == @option_lookup[name] length = 8 pointer = LibC.malloc length pointer.write_long_long value elsif 0 == @option_lookup[name] length = 4 pointer = LibC.malloc length pointer.write_int value elsif 2 == @option_lookup[name] length ||= value.size # note: not checking errno for failed memory allocations :( pointer = LibC.malloc length pointer.write_string value end rc = LibZMQ.zmq_setsockopt @socket, name, pointer, length LibC.free(pointer) unless pointer.nil? || pointer.null? rc end # Convenience method for checking on additional message parts. # # Equivalent to calling Socket#getsockopt with ZMQ::RCVMORE. # # Warning: if the call to #getsockopt fails, this method will return # false and swallow the error. # # message_parts = [] # message = Message.new # rc = socket.recvmsg(message) # if ZMQ::Util.resultcode_ok?(rc) # message_parts << message # while more_parts? # message = Message.new # rc = socket.recvmsg(message) # message_parts.push(message) if resulcode_ok?(rc) # end # end # def more_parts? rc = getsockopt ZMQ::RCVMORE, @more_parts_array Util.resultcode_ok?(rc) ? @more_parts_array.at(0) : false end # Binds the socket to an +address+. # # socket.bind("tcp://127.0.0.1:5555") # def bind address LibZMQ.zmq_bind @socket, address end # Connects the socket to an +address+. # # rc = socket.connect("tcp://127.0.0.1:5555") # def connect address rc = LibZMQ.zmq_connect @socket, address end # Closes the socket. Any unprocessed messages in queue are sent or dropped # depending upon the value of the socket option ZMQ::LINGER. # # Returns 0 upon success *or* when the socket has already been closed. # Returns -1 when the operation fails. Check ZMQ.errno for the error code. # # rc = socket.close # puts("Given socket was invalid!") unless 0 == rc # def close if @socket remove_finalizer rc = LibZMQ.zmq_close @socket @socket = nil release_cache rc else 0 end end # Queues the message for transmission. Message is assumed to conform to the # same public API as #Message. # # +flags+ may take two values: # * 0 (default) - blocking operation # * ZMQ::NonBlocking - non-blocking operation # * ZMQ::SNDMORE - this message is part of a multi-part message # # Returns 0 when the message was successfully enqueued. # Returns -1 under two conditions. # 1. The message could not be enqueued # 2. When +flags+ is set with ZMQ::NonBlocking and the socket returned EAGAIN. # # With a -1 return code, the user must check ZMQ.errno to determine the # cause. # def sendmsg message, flags = 0 __sendmsg__(@socket, message.address, flags) end # Helper method to make a new #Message instance out of the +string+ passed # in for transmission. # # +flags+ may be ZMQ::NonBlocking and ZMQ::SNDMORE. # # Returns 0 when the message was successfully enqueued. # Returns -1 under two conditions. # 1. The message could not be enqueued # 2. When +flags+ is set with ZMQ::NonBlocking and the socket returned EAGAIN. # # With a -1 return code, the user must check ZMQ.errno to determine the # cause. # def send_string string, flags = 0 message = Message.new string send_and_close message, flags end # Send a sequence of strings as a multipart message out of the +parts+ # passed in for transmission. Every element of +parts+ should be # a String. # # +flags+ may be ZMQ::NonBlocking. # # Returns 0 when the messages were successfully enqueued. # Returns -1 under two conditions. # 1. A message could not be enqueued # 2. When +flags+ is set with ZMQ::NonBlocking and the socket returned EAGAIN. # # With a -1 return code, the user must check ZMQ.errno to determine the # cause. # def send_strings parts, flags = 0 send_multiple(parts, flags, :send_string) end # Send a sequence of messages as a multipart message out of the +parts+ # passed in for transmission. Every element of +parts+ should be # a Message (or subclass). # # +flags+ may be ZMQ::NonBlocking. # # Returns 0 when the messages were successfully enqueued. # Returns -1 under two conditions. # 1. A message could not be enqueued # 2. When +flags+ is set with ZMQ::NonBlocking and the socket returned EAGAIN. # # With a -1 return code, the user must check ZMQ.errno to determine the # cause. # def sendmsgs parts, flags = 0 send_multiple(parts, flags, :sendmsg) end # Sends a message. This will automatically close the +message+ for both successful # and failed sends. # # Returns 0 when the message was successfully enqueued. # Returns -1 under two conditions. # 1. The message could not be enqueued # 2. When +flags+ is set with ZMQ::NonBlocking and the socket returned EAGAIN. # # With a -1 return code, the user must check ZMQ.errno to determine the # cause. # def send_and_close message, flags = 0 rc = sendmsg message, flags message.close rc end # Dequeues a message from the underlying queue. By default, this is a blocking operation. # # +flags+ may take two values: # 0 (default) - blocking operation # ZMQ::NonBlocking - non-blocking operation # # Returns 0 when the message was successfully dequeued. # Returns -1 under two conditions. # 1. The message could not be dequeued # 2. When +flags+ is set with ZMQ::NonBlocking and the socket returned EAGAIN. # # With a -1 return code, the user must check ZMQ.errno to determine the # cause. # # The application code is responsible for handling the +message+ object lifecycle # when #recv returns an error code. # def recvmsg message, flags = 0 #LibZMQ.zmq_recvmsg @socket, message.address, flags __recvmsg__(@socket, message.address, flags) end # Helper method to make a new #Message instance and convert its payload # to a string. # # +flags+ may be ZMQ::NonBlocking. # # Returns 0 when the message was successfully dequeued. # Returns -1 under two conditions. # 1. The message could not be dequeued # 2. When +flags+ is set with ZMQ::NonBlocking and the socket returned EAGAIN. # # With a -1 return code, the user must check ZMQ.errno to determine the # cause. # # The application code is responsible for handling the +message+ object lifecycle # when #recv returns an error code. # def recv_string string, flags = 0 message = @receiver_klass.new rc = recvmsg message, flags string.replace(message.copy_out_string) if Util.resultcode_ok?(rc) message.close rc end # Receive a multipart message as a list of strings. # # +flag+ may be ZMQ::NonBlocking. Any other flag will be # removed. # def recv_strings list, flag = 0 array = [] rc = recvmsgs array, flag if Util.resultcode_ok?(rc) array.each do |message| list << message.copy_out_string message.close end end rc end # Receive a multipart message as an array of objects # (by default these are instances of Message). # # +flag+ may be ZMQ::NonBlocking. Any other flag will be # removed. # def recvmsgs list, flag = 0 flag = NonBlocking if dontwait?(flag) message = @receiver_klass.new rc = recvmsg message, flag if Util.resultcode_ok?(rc) list << message # check rc *first*; necessary because the call to #more_parts? can reset # the zmq_errno to a weird value, so the zmq_errno that was set on the # call to #recv gets lost while Util.resultcode_ok?(rc) && more_parts? message = @receiver_klass.new rc = recvmsg message, flag if Util.resultcode_ok?(rc) list << message else message.close list.each { |msg| msg.close } list.clear end end else message.close end rc end # Should only be used for XREQ, XREP, DEALER and ROUTER type sockets. Takes # a +list+ for receiving the message body parts and a +routing_envelope+ # for receiving the message parts comprising the 0mq routing information. # def recv_multipart list, routing_envelope, flag = 0 parts = [] rc = recvmsgs parts, flag if Util.resultcode_ok?(rc) routing = true parts.each do |part| if routing routing_envelope << part routing = part.size > 0 else list << part end end end rc end private def send_multiple(parts, flags, method_name) if !parts || parts.empty? -1 else flags = NonBlocking if dontwait?(flags) rc = 0 parts[0..-2].each do |part| rc = send(method_name, part, (flags | ZMQ::SNDMORE)) break unless Util.resultcode_ok?(rc) end Util.resultcode_ok?(rc) ? send(method_name, parts[-1], flags) : rc end end def __getsockopt__ name, array # a small optimization so we only have to determine the option # type a single time; gives approx 5% speedup to do it this way. option_type = @option_lookup[name] value, length = sockopt_buffers option_type rc = LibZMQ.zmq_getsockopt @socket, name, value, length if Util.resultcode_ok?(rc) array[0] = if 1 == option_type value.read_long_long elsif 0 == option_type value.read_int elsif 2 == option_type value.read_string(length.read_int) end end rc end # Calls to ZMQ.getsockopt require us to pass in some pointers. We can cache and save those buffers # for subsequent calls. This is a big perf win for calling RCVMORE which happens quite often. # Cannot save the buffer for the IDENTITY. def sockopt_buffers option_type if 1 == option_type # int64_t or uint64_t @longlong_cache ||= alloc_pointer(:int64, 8) elsif 0 == option_type # int, 0mq assumes int is 4-bytes @int_cache ||= alloc_pointer(:int32, 4) elsif 2 == option_type # could be a string of up to 255 bytes, so allocate for worst case alloc_pointer(255, 255) else # uh oh, someone passed in an unknown option; use a slop buffer @int_cache ||= alloc_pointer(:int32, 4) end end def populate_option_lookup # integer options [EVENTS, LINGER, RCVTIMEO, SNDTIMEO, RECONNECT_IVL, FD, TYPE, BACKLOG].each { |option| @option_lookup[option] = 0 } # long long options [RCVMORE, AFFINITY].each { |option| @option_lookup[option] = 1 } # string options [SUBSCRIBE, UNSUBSCRIBE].each { |option| @option_lookup[option] = 2 } end def release_cache @longlong_cache = nil @int_cache = nil end def dontwait?(flags) (NonBlocking & flags) == NonBlocking end alias :noblock? :dontwait? def alloc_pointer(kind, length) pointer = FFI::MemoryPointer.new :size_t pointer.write_int(length) [FFI::MemoryPointer.new(kind), pointer] end end # module CommonSocketBehavior module IdentitySupport # Convenience method for getting the value of the socket IDENTITY. # def identity array = [] getsockopt IDENTITY, array array.at(0) end # Convenience method for setting the value of the socket IDENTITY. # def identity=(value) setsockopt IDENTITY, value.to_s end private def populate_option_lookup super() # string options [IDENTITY].each { |option| @option_lookup[option] = 2 } end end # module IdentitySupport if LibZMQ.version2? class Socket # Inclusion order is *important* since later modules may have a call # to #super. We want those calls to go up the chain in a particular # order include CommonSocketBehavior include IdentitySupport # Get the options set on this socket. # # +name+ determines the socket option to request # +array+ should be an empty array; a result of the proper type # (numeric, string, boolean) will be inserted into # the first position. # # Valid +option_name+ values: # ZMQ::RCVMORE - true or false # ZMQ::HWM - integer # ZMQ::SWAP - integer # ZMQ::AFFINITY - bitmap in an integer # ZMQ::IDENTITY - string # ZMQ::RATE - integer # ZMQ::RECOVERY_IVL - integer # ZMQ::MCAST_LOOP - true or false # ZMQ::SNDBUF - integer # ZMQ::RCVBUF - integer # ZMQ::FD - fd in an integer # ZMQ::EVENTS - bitmap integer # ZMQ::LINGER - integer measured in milliseconds # ZMQ::RECONNECT_IVL - integer measured in milliseconds # ZMQ::BACKLOG - integer # ZMQ::RECOVER_IVL_MSEC - integer measured in milliseconds # # Returns 0 when the operation completed successfully. # Returns -1 when this operation failed. # # With a -1 return code, the user must check ZMQ.errno to determine the # cause. # # # retrieve high water mark # array = [] # rc = socket.getsockopt(ZMQ::HWM, array) # hwm = array.first if ZMQ::Util.resultcode_ok?(rc) # def getsockopt name, array rc = __getsockopt__ name, array if Util.resultcode_ok?(rc) && (RCVMORE == name || MCAST_LOOP == name) # convert to boolean array[0] = 1 == array[0] end rc end private def __sendmsg__(socket, address, flags) LibZMQ.zmq_send(socket, address, flags) end def __recvmsg__(socket, address, flags) LibZMQ.zmq_recv(socket, address, flags) end def populate_option_lookup super() # integer options [RECONNECT_IVL_MAX].each { |option| @option_lookup[option] = 0 } # long long options [HWM, SWAP, RATE, RECOVERY_IVL, RECOVERY_IVL_MSEC, MCAST_LOOP, SNDBUF, RCVBUF].each { |option| @option_lookup[option] = 1 } end # these finalizer-related methods cannot live in the CommonSocketBehavior # module; they *must* be in the class definition directly def define_finalizer ObjectSpace.define_finalizer(self, self.class.close(@socket, Process.pid)) end def remove_finalizer ObjectSpace.undefine_finalizer self end def self.close socket, pid Proc.new do LibZMQ.zmq_close(socket) if socket && !socket.nil? && Process.pid == pid end end end # class Socket for version2 end # LibZMQ.version2? if LibZMQ.version3? class Socket include CommonSocketBehavior include IdentitySupport # Get the options set on this socket. # # +name+ determines the socket option to request # +array+ should be an empty array; a result of the proper type # (numeric, string, boolean) will be inserted into # the first position. # # Valid +option_name+ values: # ZMQ::RCVMORE - true or false # ZMQ::HWM - integer # ZMQ::SWAP - integer # ZMQ::AFFINITY - bitmap in an integer # ZMQ::IDENTITY - string # ZMQ::RATE - integer # ZMQ::RECOVERY_IVL - integer # ZMQ::SNDBUF - integer # ZMQ::RCVBUF - integer # ZMQ::FD - fd in an integer # ZMQ::EVENTS - bitmap integer # ZMQ::LINGER - integer measured in milliseconds # ZMQ::RECONNECT_IVL - integer measured in milliseconds # ZMQ::BACKLOG - integer # ZMQ::RECOVER_IVL_MSEC - integer measured in milliseconds # ZMQ::IPV4ONLY - integer # # Returns 0 when the operation completed successfully. # Returns -1 when this operation failed. # # With a -1 return code, the user must check ZMQ.errno to determine the # cause. # # # retrieve high water mark # array = [] # rc = socket.getsockopt(ZMQ::HWM, array) # hwm = array.first if ZMQ::Util.resultcode_ok?(rc) # def getsockopt name, array rc = __getsockopt__ name, array if Util.resultcode_ok?(rc) && (RCVMORE == name) # convert to boolean array[0] = 1 == array[0] end rc end # Version3 only # # Disconnect the socket from the given +endpoint+. # def disconnect(endpoint) LibZMQ.zmq_disconnect(socket, endpoint) end # Version3 only # # Unbind the socket from the given +endpoint+. # def unbind(endpoint) LibZMQ.zmq_unbind(socket, endpoint) end private def __sendmsg__(socket, address, flags) LibZMQ.zmq_sendmsg(socket, address, flags) end def __recvmsg__(socket, address, flags) LibZMQ.zmq_recvmsg(socket, address, flags) end def populate_option_lookup super() # integer options [RECONNECT_IVL_MAX, RCVHWM, SNDHWM, RATE, RECOVERY_IVL, SNDBUF, RCVBUF, IPV4ONLY, ROUTER_BEHAVIOR, TCP_KEEPALIVE, TCP_KEEPALIVE_CNT, TCP_KEEPALIVE_IDLE, TCP_KEEPALIVE_INTVL, TCP_ACCEPT_FILTER, MULTICAST_HOPS ].each { |option| @option_lookup[option] = 0 } # long long options [MAXMSGSIZE].each { |option| @option_lookup[option] = 1 } # string options [LAST_ENDPOINT].each { |option| @option_lookup[option] = 2 } end # these finalizer-related methods cannot live in the CommonSocketBehavior # module; they *must* be in the class definition directly def define_finalizer ObjectSpace.define_finalizer(self, self.class.close(@socket, Process.pid)) end def remove_finalizer ObjectSpace.undefine_finalizer self end def self.close socket, pid Proc.new { LibZMQ.zmq_close socket if Process.pid == pid } end end # Socket for version3 end # LibZMQ.version3? end # module ZMQ ruby-ffi-rzmq-1.0.3/lib/ffi-rzmq/util.rb000066400000000000000000000070751222421464700200450ustar00rootroot00000000000000 module ZMQ # General utility methods. # class Util # Returns true when +rc+ is greater than or equal to 0, false otherwise. # # We use the >= test because zmq_poll() returns the number of sockets # that had a read or write event triggered. So, a >= 0 result means # it succeeded. # def self.resultcode_ok? rc rc >= 0 end # Returns the +errno+ as set by the libzmq library. # def self.errno LibZMQ.zmq_errno end # Returns a string corresponding to the currently set #errno. These # error strings are defined by libzmq. # def self.error_string LibZMQ.zmq_strerror(errno).read_string end # Returns an array of the form [major, minor, patch] to represent the # version of libzmq. # # Class method! Invoke as: ZMQ::Util.version # def self.version major = FFI::MemoryPointer.new :int minor = FFI::MemoryPointer.new :int patch = FFI::MemoryPointer.new :int LibZMQ.zmq_version major, minor, patch [major.read_int, minor.read_int, patch.read_int] end # Attempts to bind to a random tcp port on +host+ up to +max_tries+ # times. Returns the port number upon success or nil upon failure. # def self.bind_to_random_tcp_port host = '127.0.0.1', max_tries = 500 tries = 0 rc = -1 while !resultcode_ok?(rc) && tries < max_tries tries += 1 random = random_port rc = socket.bind "tcp://#{host}:#{random}" end resultcode_ok?(rc) ? random : nil end # :doc: # Called to verify whether there were any errors during # operation. If any are found, raise the appropriate #ZeroMQError. # # When no error is found, this method returns +true+ which is behavior # used internally by #send and #recv. # def self.error_check source, result_code if -1 == result_code raise_error source, result_code end # used by Socket::send/recv, ignored by others true end private # generate a random port between 10_000 and 65534 def self.random_port rand(55534) + 10_000 end def self.raise_error source, result_code if context_error?(source) raise ContextError.new source, result_code, ZMQ::Util.errno, ZMQ::Util.error_string elsif message_error?(source) raise MessageError.new source, result_code, ZMQ::Util.errno, ZMQ::Util.error_string else raise ZeroMQError.new source, result_code, -1, "Source [#{source}] does not match any zmq_* strings, rc [#{result_code}], errno [#{ZMQ::Util.errno}], error_string [#{ZMQ::Util.error_string}]" end end def self.eagain? EAGAIN == ZMQ::Util.errno end if LibZMQ.version2? def self.context_error?(source) 'zmq_init' == source || 'zmq_socket' == source end def self.message_error?(source) ['zmq_msg_init', 'zmq_msg_init_data', 'zmq_msg_copy', 'zmq_msg_move'].include?(source) end elsif LibZMQ.version3? def self.context_error?(source) 'zmq_ctx_new' == source || 'zmq_ctx_set' == source || 'zmq_ctx_get' == source || 'zmq_ctx_destory' == source || 'zmq_ctx_set_monitor' == source end def self.message_error?(source) ['zmq_msg_init', 'zmq_msg_init_data', 'zmq_msg_copy', 'zmq_msg_move', 'zmq_msg_close', 'zmq_msg_get', 'zmq_msg_more', 'zmq_msg_recv', 'zmq_msg_send', 'zmq_msg_set'].include?(source) end end # if LibZMQ.version...? end # module Util end # module ZMQ ruby-ffi-rzmq-1.0.3/lib/ffi-rzmq/version.rb000066400000000000000000000000431222421464700205410ustar00rootroot00000000000000module ZMQ VERSION = "1.0.3" end ruby-ffi-rzmq-1.0.3/lib/io_extensions.rb000066400000000000000000000005631222421464700202160ustar00rootroot00000000000000class IO if defined? JRUBY_VERSION require 'jruby' def posix_fileno case self when STDIN, $stdin 0 when STDOUT, $stdout 1 when STDERR, $stderr 2 else JRuby.reference(self).getOpenFile.getMainStream.getDescriptor.getChannel.getFDVal end end else alias :posix_fileno :fileno end end ruby-ffi-rzmq-1.0.3/spec/000077500000000000000000000000001222421464700151635ustar00rootroot00000000000000ruby-ffi-rzmq-1.0.3/spec/context_spec.rb000066400000000000000000000061161222421464700202120ustar00rootroot00000000000000$: << "." # added for ruby 1.9.2 compatibilty; it doesn't include the current directory on the load path anymore require File.join(File.dirname(__FILE__), %w[spec_helper]) module ZMQ describe Context do context "when initializing with factory method #create" do include APIHelper it "should return nil for negative io threads" do Context.create(-1).should be_nil end it "should default to requesting 1 i/o thread when no argument is passed" do ctx = Context.create ctx.io_threads.should == 1 end it "should set the :pointer accessor to non-nil" do ctx = Context.create ctx.pointer.should_not be_nil end it "should set the :context accessor to non-nil" do ctx = Context.create ctx.context.should_not be_nil end it "should set the :pointer and :context accessors to the same value" do ctx = Context.create ctx.pointer.should == ctx.context end it "should define a finalizer on this object" do ObjectSpace.should_receive(:define_finalizer) ctx = Context.create end end # context initializing context "when initializing with #new" do include APIHelper it "should raise a ContextError exception for negative io threads" do lambda { Context.new(-1) }.should raise_exception(ZMQ::ContextError) end it "should default to requesting 1 i/o thread when no argument is passed" do ctx = Context.new ctx.io_threads.should == 1 end it "should set the :pointer accessor to non-nil" do ctx = Context.new ctx.pointer.should_not be_nil end it "should set the :context accessor to non-nil" do ctx = Context.new ctx.context.should_not be_nil end it "should set the :pointer and :context accessors to the same value" do ctx = Context.new ctx.pointer.should == ctx.context end it "should define a finalizer on this object" do ObjectSpace.should_receive(:define_finalizer) Context.new 1 end end # context initializing context "when terminating" do it "should set the context to nil when terminating the library's context" do ctx = Context.new # can't use a shared context here because we are terminating it! ctx.terminate ctx.pointer.should be_nil end it "should call the correct library function to terminate the context" do ctx = Context.new if LibZMQ.version2? LibZMQ.should_receive(:zmq_term).and_return(0) ctx.terminate else LibZMQ.should_receive(:zmq_ctx_destroy).with(ctx.pointer).and_return(0) ctx.terminate end end end # context terminate context "when allocating a socket" do it "should return nil when allocation fails" do ctx = Context.new LibZMQ.stub(:zmq_socket => nil) ctx.socket(ZMQ::REQ).should be_nil end end # context socket end # describe Context end # module ZMQ ruby-ffi-rzmq-1.0.3/spec/device_spec.rb000066400000000000000000000033751222421464700177710ustar00rootroot00000000000000 require File.join(File.dirname(__FILE__), %w[spec_helper]) module ZMQ describe Device do include APIHelper before(:all) do @ctx = Context.new poller_setup @front_endpoint = "inproc://device_front_test" @back_endpoint = "inproc://device_back_test" @mutex = Mutex.new end after(:all) do @ctx.terminate end def create_streamer @device_thread = false Thread.new do back = @ctx.socket(ZMQ::PULL) back.bind(@back_endpoint) front = @ctx.socket(ZMQ::PUSH) front.bind(@front_endpoint) @mutex.synchronize { @device_thread = true } Device.new(ZMQ::STREAMER, back, front) back.close front.close end end def wait_for_device loop do can_break = false @mutex.synchronize do can_break = true if @device_thread end break if can_break end end it "should create a device without error given valid opts" do create_streamer wait_for_device end it "should be able to send messages through the device" do create_streamer wait_for_device pusher = @ctx.socket(ZMQ::PUSH) connect_to_inproc(pusher, @back_endpoint) puller = @ctx.socket(ZMQ::PULL) connect_to_inproc(puller, @front_endpoint) poll_it_for_read(puller) do pusher.send_string("hello") end res = '' rc = puller.recv_string(res, ZMQ::NonBlocking) res.should == "hello" pusher.close puller.close end it "should raise an ArgumentError when trying to pass non-socket objects into the device" do lambda { Device.new(ZMQ::STREAMER, 1,2) }.should raise_exception(ArgumentError) end end end ruby-ffi-rzmq-1.0.3/spec/message_spec.rb000066400000000000000000000056061222421464700201550ustar00rootroot00000000000000 require File.join(File.dirname(__FILE__), %w[spec_helper]) module ZMQ describe Message do context "when initializing with an argument" do it "calls zmq_msg_init_data()" do LibZMQ.should_receive(:zmq_msg_init_data) message = Message.new "text" end it "should *not* define a finalizer on this object" do ObjectSpace.should_not_receive(:define_finalizer) Message.new "text" end end # context initializing with arg context "when initializing *without* an argument" do it "calls zmq_msg_init()" do LibZMQ.should_receive(:zmq_msg_init).and_return(0) message = Message.new end it "should *not* define a finalizer on this object" do ObjectSpace.should_not_receive(:define_finalizer) Message.new "text" end end # context initializing with arg context "#copy_in_string" do it "calls zmq_msg_init_data()" do message = Message.new "text" LibZMQ.should_receive(:zmq_msg_init_data) message.copy_in_string("new text") end it "correctly finds the length of binary data by ignoring encoding" do message = Message.new message.copy_in_string("\x83\x6e\x04\x00\x00\x44\xd1\x81") message.size.should == 8 end end context "#copy" do it "calls zmq_msg_copy()" do message = Message.new "text" copy = Message.new LibZMQ.should_receive(:zmq_msg_copy) copy.copy(message) end end # context copy context "#move" do it "calls zmq_msg_move()" do message = Message.new "text" copy = Message.new LibZMQ.should_receive(:zmq_msg_move) copy.move(message) end end # context move context "#size" do it "calls zmq_msg_size()" do message = Message.new "text" LibZMQ.should_receive(:zmq_msg_size) message.size end end # context size context "#data" do it "calls zmq_msg_data()" do message = Message.new "text" LibZMQ.should_receive(:zmq_msg_data) message.data end end # context data context "#close" do it "calls zmq_msg_close() the first time" do message = Message.new "text" LibZMQ.should_receive(:zmq_msg_close) message.close end it "*does not* call zmq_msg_close() on subsequent invocations" do message = Message.new "text" message.close LibZMQ.should_not_receive(:zmq_msg_close) message.close end end # context close end # describe Message describe ManagedMessage do context "when initializing with an argument" do it "should define a finalizer on this object" do ObjectSpace.should_receive(:define_finalizer) ManagedMessage.new "text" end end # context initializing end # describe ManagedMessage end # module ZMQ ruby-ffi-rzmq-1.0.3/spec/multipart_spec.rb000066400000000000000000000077131222421464700205530ustar00rootroot00000000000000require File.join(File.dirname(__FILE__), %w[spec_helper]) module ZMQ describe Socket do context "multipart messages" do before(:all) { @ctx = Context.new } after(:all) { @ctx.terminate } context "using #send_strings" do include APIHelper before(:all) do @receiver = Socket.new(@ctx.pointer, ZMQ::REP) port = bind_to_random_tcp_port(@receiver) @sender = Socket.new(@ctx.pointer, ZMQ::REQ) rc = @sender.connect("tcp://127.0.0.1:#{port}") end after(:all) do @sender.close @receiver.close end it "correctly handles a multipart message array with 1 element" do data = [ "1" ] @sender.send_strings(data) sleep 1 strings = [] rc = @receiver.recv_strings(strings) strings.should == data end end context "without identity" do include APIHelper before(:all) do @rep = Socket.new(@ctx.pointer, ZMQ::REP) port = bind_to_random_tcp_port(@rep) @req = Socket.new(@ctx.pointer, ZMQ::REQ) @req.connect("tcp://127.0.0.1:#{port}") end after(:all) do @req.close @rep.close end it "should be delivered between REQ and REP returning an array of strings" do req_data, rep_data = [ "1", "2" ], [ "2", "3" ] @req.send_strings(req_data) strings = [] rc = @rep.recv_strings(strings) strings.should == req_data @rep.send_strings(rep_data) strings = [] rc = @req.recv_strings(strings) strings.should == rep_data end it "should be delivered between REQ and REP returning an array of messages" do req_data, rep_data = [ "1", "2" ], [ "2", "3" ] @req.send_strings(req_data) messages = [] rc = @rep.recvmsgs(messages) messages.each_with_index do |message, index| message.copy_out_string.should == req_data[index] end @rep.send_strings(rep_data) messages = [] rc = @req.recvmsgs(messages) messages.each_with_index do |message, index| message.copy_out_string.should == rep_data[index] end end end context "with identity" do include APIHelper before(:each) do # was :all @rep = Socket.new(@ctx.pointer, ZMQ::XREP) port = bind_to_random_tcp_port(@rep) @req = Socket.new(@ctx.pointer, ZMQ::REQ) @req.identity = 'foo' @req.connect("tcp://127.0.0.1:#{port}") end after(:each) do # was :all @req.close @rep.close end it "should be delivered between REQ and REP returning an array of strings with an empty string as the envelope delimiter" do req_data, rep_data = "hello", [ @req.identity, "", "ok" ] @req.send_string(req_data) strings = [] rc = @rep.recv_strings(strings) strings.should == [ @req.identity, "", "hello" ] @rep.send_strings(rep_data) string = '' rc = @req.recv_string(string) string.should == rep_data.last end it "should be delivered between REQ and REP returning an array of messages with an empty string as the envelope delimiter" do req_data, rep_data = "hello", [ @req.identity, "", "ok" ] @req.send_string(req_data) msgs = [] rc = @rep.recvmsgs(msgs) msgs[0].copy_out_string.should == @req.identity msgs[1].copy_out_string.should == "" msgs[2].copy_out_string.should == "hello" @rep.send_strings(rep_data) msgs = [] rc = @req.recvmsgs(msgs) msgs[0].copy_out_string.should == rep_data.last end end end end end ruby-ffi-rzmq-1.0.3/spec/nonblocking_recv_spec.rb000066400000000000000000000234351222421464700220530ustar00rootroot00000000000000 require File.join(File.dirname(__FILE__), %w[spec_helper]) module ZMQ describe Socket do include APIHelper shared_examples_for "any socket" do it "returns -1 when there are no messages to read" do array = [] rc = @receiver.recvmsgs(array, ZMQ::NonBlocking) Util.resultcode_ok?(rc).should be_false end it "gets EAGAIN when there are no messages to read" do array = [] rc = @receiver.recvmsgs(array, ZMQ::NonBlocking) ZMQ::Util.errno.should == ZMQ::EAGAIN end it "returns the given array unmodified when there are no messages to read" do array = [] rc = @receiver.recvmsgs(array, ZMQ::NonBlocking) array.size.should be_zero end end shared_examples_for "sockets without exposed envelopes" do it "read the single message and returns a successful result code" do poll_it_for_read(@receiver) do rc = @sender.send_string('test') Util.resultcode_ok?(rc).should be_true end array = [] rc = @receiver.recvmsgs(array, ZMQ::NonBlocking) Util.resultcode_ok?(rc).should be_true array.size.should == 1 end it "read all message parts transmitted and returns a successful result code" do poll_it_for_read(@receiver) do strings = Array.new(10, 'test') rc = @sender.send_strings(strings) Util.resultcode_ok?(rc).should be_true end array = [] rc = @receiver.recvmsgs(array, ZMQ::NonBlocking) Util.resultcode_ok?(rc).should be_true array.size.should == 10 end end shared_examples_for "sockets with exposed envelopes" do it "read the single message and returns a successful result code" do poll_it_for_read(@receiver) do rc = @sender.send_string('test') Util.resultcode_ok?(rc).should be_true end array = [] rc = @receiver.recvmsgs(array, ZMQ::NonBlocking) Util.resultcode_ok?(rc).should be_true array.size.should == 1 + 1 # extra 1 for envelope end it "read all message parts transmitted and returns a successful result code" do poll_it_for_read(@receiver) do strings = Array.new(10, 'test') rc = @sender.send_strings(strings) Util.resultcode_ok?(rc).should be_true end array = [] rc = @receiver.recvmsgs(array, ZMQ::NonBlocking) Util.resultcode_ok?(rc).should be_true array.size.should == 10 + 1 # add 1 for the envelope end end context "PUB" do describe "non-blocking #recvmsgs where sender connects & receiver binds" do include APIHelper before(:each) do @context = Context.new poller_setup endpoint = "inproc://nonblocking_test" @receiver = @context.socket ZMQ::SUB assert_ok(@receiver.setsockopt(ZMQ::SUBSCRIBE, '')) @sender = @context.socket ZMQ::PUB @receiver.bind(endpoint) connect_to_inproc(@sender, endpoint) end after(:each) do @receiver.close @sender.close @context.terminate end it_behaves_like "any socket" #it_behaves_like "sockets without exposed envelopes" # see Jira LIBZMQ-270; fails with tcp transport end # describe 'non-blocking recvmsgs' describe "non-blocking #recvmsgs where sender binds & receiver connects" do include APIHelper before(:each) do @context = Context.new poller_setup endpoint = "inproc://nonblocking_test" @receiver = @context.socket ZMQ::SUB port = connect_to_random_tcp_port(@receiver) assert_ok(@receiver.setsockopt(ZMQ::SUBSCRIBE, '')) @sender = @context.socket ZMQ::PUB @sender.bind(endpoint) connect_to_inproc(@receiver, endpoint) end after(:each) do @receiver.close @sender.close @context.terminate end it_behaves_like "any socket" it_behaves_like "sockets without exposed envelopes" # see Jira LIBZMQ-270; fails with tcp transport end # describe 'non-blocking recvmsgs' end # Pub context "REQ" do describe "non-blocking #recvmsgs where sender connects & receiver binds" do include APIHelper before(:each) do @context = Context.new poller_setup endpoint = "inproc://nonblocking_test" @receiver = @context.socket ZMQ::REP @sender = @context.socket ZMQ::REQ @receiver.bind(endpoint) connect_to_inproc(@sender, endpoint) end after(:each) do @receiver.close @sender.close @context.terminate end it_behaves_like "any socket" it_behaves_like "sockets without exposed envelopes" end # describe 'non-blocking recvmsgs' describe "non-blocking #recvmsgs where sender binds & receiver connects" do include APIHelper before(:each) do @context = Context.new poller_setup endpoint = "inproc://nonblocking_test" @receiver = @context.socket ZMQ::REP @sender = @context.socket ZMQ::REQ @sender.bind(endpoint) connect_to_inproc(@receiver, endpoint) end after(:each) do @receiver.close @sender.close @context.terminate end it_behaves_like "any socket" it_behaves_like "sockets without exposed envelopes" end # describe 'non-blocking recvmsgs' end # REQ context "PUSH" do describe "non-blocking #recvmsgs where sender connects & receiver binds" do include APIHelper before(:each) do @context = Context.new poller_setup endpoint = "inproc://nonblocking_test" @receiver = @context.socket ZMQ::PULL @sender = @context.socket ZMQ::PUSH @receiver.bind(endpoint) connect_to_inproc(@sender, endpoint) end after(:each) do @receiver.close @sender.close @context.terminate end it_behaves_like "any socket" it_behaves_like "sockets without exposed envelopes" end # describe 'non-blocking recvmsgs' describe "non-blocking #recvmsgs where sender binds & receiver connects" do include APIHelper before(:each) do @context = Context.new poller_setup endpoint = "inproc://nonblocking_test" @receiver = @context.socket ZMQ::PULL @sender = @context.socket ZMQ::PUSH @sender.bind(endpoint) connect_to_inproc(@receiver, endpoint) end after(:each) do @receiver.close @sender.close @context.terminate end it_behaves_like "any socket" it_behaves_like "sockets without exposed envelopes" end # describe 'non-blocking recvmsgs' end # PUSH context "DEALER" do describe "non-blocking #recvmsgs where sender connects & receiver binds" do include APIHelper before(:each) do @context = Context.new poller_setup endpoint = "inproc://nonblocking_test" @receiver = @context.socket ZMQ::ROUTER @sender = @context.socket ZMQ::DEALER @receiver.bind(endpoint) connect_to_inproc(@sender, endpoint) end after(:each) do @receiver.close @sender.close @context.terminate end it_behaves_like "any socket" it_behaves_like "sockets with exposed envelopes" end # describe 'non-blocking recvmsgs' describe "non-blocking #recvmsgs where sender binds & receiver connects" do include APIHelper before(:each) do @context = Context.new poller_setup endpoint = "inproc://nonblocking_test" @receiver = @context.socket ZMQ::ROUTER @sender = @context.socket ZMQ::DEALER @sender.bind(endpoint) connect_to_inproc(@receiver, endpoint) end after(:each) do @receiver.close @sender.close @context.terminate end it_behaves_like "any socket" it_behaves_like "sockets with exposed envelopes" end # describe 'non-blocking recvmsgs' end # DEALER context "XREQ" do describe "non-blocking #recvmsgs where sender connects & receiver binds" do include APIHelper before(:each) do @context = Context.new poller_setup endpoint = "inproc://nonblocking_test" @receiver = @context.socket ZMQ::XREP @sender = @context.socket ZMQ::XREQ @receiver.bind(endpoint) connect_to_inproc(@sender, endpoint) end after(:each) do @receiver.close @sender.close @context.terminate end it_behaves_like "any socket" it_behaves_like "sockets with exposed envelopes" end # describe 'non-blocking recvmsgs' describe "non-blocking #recvmsgs where sender binds & receiver connects" do include APIHelper before(:each) do @context = Context.new poller_setup endpoint = "inproc://nonblocking_test" @receiver = @context.socket ZMQ::XREP @sender = @context.socket ZMQ::XREQ @sender.bind(endpoint) connect_to_inproc(@receiver, endpoint) end after(:each) do @receiver.close @sender.close @context.terminate end it_behaves_like "any socket" it_behaves_like "sockets with exposed envelopes" end # describe 'non-blocking recvmsgs' end # XREQ end # describe Socket end # module ZMQ ruby-ffi-rzmq-1.0.3/spec/poll_spec.rb000066400000000000000000000203451222421464700174740ustar00rootroot00000000000000require 'spec_helper' module ZMQ describe Poller do context "when initializing" do include APIHelper it "should allocate a PollItems instance" do PollItems.should_receive(:new) Poller.new end end context "#register" do let(:pollable) { double('pollable') } let(:poller) { Poller.new } let(:socket) { FFI::MemoryPointer.new(4) } let(:io) { double(:posix_fileno => fd) } let(:fd) { 1 } it "returns false when given a nil pollable" do poller.register(nil, ZMQ::POLLIN).should be_false end it "returns false when given 0 for +events+ (e.g. no registration)" do poller.register(pollable, 0).should be_false end it "returns the default registered event value when given a valid pollable" do poller.register(pollable).should == (ZMQ::POLLIN | ZMQ::POLLOUT) end it "returns the registered event value when given a pollable responding to socket (ZMQ::Socket)" do pollable.should_receive(:socket).and_return(socket) poller.register(pollable, ZMQ::POLLIN).should == ZMQ::POLLIN end it "returns the registered event value when given a pollable responding to file descriptor (IO, BasicSocket)" do pollable.should_receive(:posix_fileno).and_return(fd) poller.register(pollable, ZMQ::POLLIN).should == ZMQ::POLLIN end it "returns the registered event value when given a pollable responding to io (SSLSocket)" do pollable.should_receive(:io).and_return(io) poller.register(pollable, ZMQ::POLLIN).should == ZMQ::POLLIN end end context "#deregister" do let(:pollable) { double('pollable') } let(:poller) { Poller.new } let(:socket) { FFI::MemoryPointer.new(4) } let(:io) { double(:posix_fileno => fd) } let(:fd) { 1 } it "returns true when deregistered pollable from event" do pollable.should_receive(:socket).at_least(:once).and_return(socket) poller.register(pollable) poller.deregister(pollable, ZMQ::POLLIN).should be_true end it "returns false when pollable not registered" do poller.deregister(pollable, ZMQ::POLLIN).should be_false end it "returns false when pollable not registered for deregistered event" do pollable.should_receive(:socket).at_least(:once).and_return(socket) poller.register(pollable, ZMQ::POLLOUT) poller.deregister(pollable, ZMQ::POLLIN).should be_false end it "deletes pollable when no events left" do poller.register(pollable, ZMQ::POLLIN) poller.deregister(pollable, ZMQ::POLLIN).should be_true poller.size.should == 0 end it "deletes closed pollable responding to socket (ZMQ::Socket)" do pollable.should_receive(:socket).and_return(socket) poller.register(pollable) pollable.should_receive(:socket).and_return(nil) poller.deregister(pollable, ZMQ::POLLIN).should be_true poller.size.should == 0 end it "deletes closed pollable responding to fileno (IO, BasicSocket)" do pollable.should_receive(:posix_fileno).and_return(fd) poller.register(pollable) pollable.should_receive(:closed?).and_return(true) poller.deregister(pollable, ZMQ::POLLIN).should be_true poller.size.should == 0 end it "deletes closed pollable responding to io (SSLSocket)" do pollable.should_receive(:io).at_least(:once).and_return(io) poller.register(pollable) io.should_receive(:closed?).and_return(true) poller.deregister(pollable, ZMQ::POLLIN).should be_true poller.size.should == 0 end end context "#delete" do before(:all) { @context = Context.new } after(:all) { @context.terminate } before(:each) do @socket = @context.socket(XREQ) @socket.setsockopt(LINGER, 0) @poller = Poller.new end after(:each) do @socket.close end it "should return false for an unregistered socket (i.e. not found)" do @poller.delete(@socket).should be_false end it "returns true for a sucessfully deleted socket when only 1 is registered" do socket1 = @context.socket(REP) socket1.setsockopt(LINGER, 0) @poller.register socket1 @poller.delete(socket1).should be_true socket1.close end it "returns true for a sucessfully deleted socket when more than 1 is registered" do socket1 = @context.socket(REP) socket2 = @context.socket(REP) socket1.setsockopt(LINGER, 0) socket2.setsockopt(LINGER, 0) @poller.register socket1 @poller.register socket2 @poller.delete(socket2).should be_true socket1.close socket2.close end it "returns true for a successfully deleted socket when the socket has been previously closed" do socket1 = @context.socket(REP) socket1.setsockopt(LINGER, 0) @poller.register socket1 socket1.close @poller.delete(socket1).should be_true end end context "poll" do include APIHelper before(:all) { @context = Context.new } after(:all) { @context.terminate } before(:each) do endpoint = "inproc://poll_test_#{SecureRandom.hex}" @sockets = [@context.socket(DEALER), @context.socket(ROUTER)] @sockets.each { |s| s.setsockopt(LINGER, 0) } @sockets.first.bind(endpoint) connect_to_inproc(@sockets.last, endpoint) @poller = Poller.new end after(:each) { @sockets.each(&:close) } it "returns 0 when there are no sockets to poll" do @poller.poll(100).should be_zero end it "returns 0 when there is a single socket to poll and no events" do @poller.register(@sockets.first, 0) @poller.poll(100).should be_zero end it "returns 1 when there is a read event on a socket" do first, last = @sockets @poller.register_readable(last) first.send_string('test') @poller.poll(1000).should == 1 end it "returns 1 when there is a read event on one socket and the second socket has been removed from polling" do first, last = @sockets @poller.register_readable(last) @poller.register_writable(first) first.send_string('test') @poller.deregister_writable(first) @poller.poll(1000).should == 1 end it "works with BasiSocket" do server = TCPServer.new("127.0.0.1", 0) f, port, host, addr = server.addr client = TCPSocket.new("127.0.0.1", port) s = server.accept @poller.register(s, ZMQ::POLLIN) @poller.register(client, ZMQ::POLLOUT) client.send("message", 0) @poller.poll.should == 2 @poller.readables.should == [s] @poller.writables.should == [client] msg = s.read_nonblock(7) msg.should == "message" end it "works with IO objects" do r, w = IO.pipe @poller.register(r, ZMQ::POLLIN) @poller.register(w, ZMQ::POLLOUT) w.write("message") @poller.poll.should == 2 @poller.readables.should == [r] @poller.writables.should == [w] msg = r.read(7) msg.should == "message" end it "works with SSLSocket" do crt, key = %w[crt key].map { |ext| File.read(File.join(File.dirname(__FILE__), "support", "test." << ext)) } ctx = OpenSSL::SSL::SSLContext.new ctx.key = OpenSSL::PKey::RSA.new(key) ctx.cert = OpenSSL::X509::Certificate.new(crt) server = TCPServer.new("127.0.0.1", 0) f, port, host, addr = server.addr client = TCPSocket.new("127.0.0.1", port) s = server.accept client = OpenSSL::SSL::SSLSocket.new(client) server = OpenSSL::SSL::SSLSocket.new(s, ctx) t = Thread.new { client.connect } s = server.accept t.join @poller.register_readable(s) @poller.register_writable(client) client.write("message") @poller.poll.should == 2 @poller.readables.should == [s] @poller.writables.should == [client] msg = s.read(7) msg.should == "message" end end end end ruby-ffi-rzmq-1.0.3/spec/pushpull_spec.rb000066400000000000000000000066611222421464700204070ustar00rootroot00000000000000 require File.join(File.dirname(__FILE__), %w[spec_helper]) module ZMQ describe Socket do context "when running basic push pull" do include APIHelper let(:string) { "booga-booga" } before(:each) do # Use new context for each iteration to avoid inproc race. See # poll_spec.rb for more details. @context = Context.new poller_setup @push = @context.socket ZMQ::PUSH @pull = @context.socket ZMQ::PULL @push.setsockopt ZMQ::LINGER, 0 @pull.setsockopt ZMQ::LINGER, 0 @link = "inproc://push_pull_test" @push.bind @link connect_to_inproc(@pull, @link) end after(:each) do @push.close @pull.close @context.terminate end it "should receive an exact copy of the sent message using Message objects directly on one pull socket" do @push.send_string string received = '' rc = @pull.recv_string received assert_ok(rc) received.should == string end it "should receive an exact string copy of the message sent when receiving in non-blocking mode and using Message objects directly" do sent_message = Message.new string received_message = Message.new poll_it_for_read(@pull) do rc = @push.sendmsg sent_message LibZMQ.version2? ? rc.should == 0 : rc.should == string.size end rc = @pull.recvmsg received_message, ZMQ::NonBlocking LibZMQ.version2? ? rc.should == 0 : rc.should == string.size received_message.copy_out_string.should == string end it "should receive a single message for each message sent on each socket listening, when an equal number of sockets pulls messages and where each socket is unique per thread" do received = [] threads = [] sockets = [] count = 4 mutex = Mutex.new # make sure all sockets are connected before we do our load-balancing test (count - 1).times do socket = @context.socket ZMQ::PULL socket.setsockopt ZMQ::LINGER, 0 connect_to_inproc(socket, @link) sockets << socket end sockets << @pull sockets.each do |socket| threads << Thread.new do buffer = '' rc = socket.recv_string buffer version2? ? (rc.should == 0) : (rc.should == buffer.size) mutex.synchronize { received << buffer } socket.close end end count.times { @push.send_string(string) } threads.each {|t| t.join} received.find_all {|r| r == string}.length.should == count end it "should receive a single message for each message sent when using a single shared socket protected by a mutex" do received = [] threads = [] count = 4 mutex = Mutex.new count.times do |i| threads << Thread.new do buffer = '' rc = 0 mutex.synchronize { rc = @pull.recv_string buffer } version2? ? (rc.should == 0) : (rc.should == buffer.size) mutex.synchronize { received << buffer } end end count.times { @push.send_string(string) } threads.each {|t| t.join} received.find_all {|r| r == string}.length.should == count end end # @context ping-pong end # describe end # module ZMQ ruby-ffi-rzmq-1.0.3/spec/reqrep_spec.rb000066400000000000000000000047331222421464700200270ustar00rootroot00000000000000 require File.join(File.dirname(__FILE__), %w[spec_helper]) module ZMQ describe Socket do context "when running ping pong" do include APIHelper let(:string) { "booga-booga" } # reset sockets each time because we only send 1 message which leaves # the REQ socket in a bad state. It cannot send again unless we were to # send a reply with the REP and read it. before(:each) do @context = ZMQ::Context.new poller_setup endpoint = "inproc://reqrep_test" @ping = @context.socket ZMQ::REQ @pong = @context.socket ZMQ::REP @pong.bind(endpoint) connect_to_inproc(@ping, endpoint) end after(:each) do @ping.close @pong.close @context.terminate end def send_ping(string) @ping.send_string string received_message = '' rc = @pong.recv_string received_message [rc, received_message] end it "should receive an exact string copy of the string message sent" do rc, received_message = send_ping(string) received_message.should == string end it "should generate a EFSM error when sending via the REQ socket twice in a row without an intervening receive operation" do send_ping(string) rc = @ping.send_string(string) rc.should == -1 Util.errno.should == ZMQ::EFSM end it "should receive an exact copy of the sent message using Message objects directly" do received_message = Message.new rc = @ping.sendmsg(Message.new(string)) LibZMQ.version2? ? rc.should == 0 : rc.should == string.size rc = @pong.recvmsg received_message LibZMQ.version2? ? rc.should == 0 : rc.should == string.size received_message.copy_out_string.should == string end it "should receive an exact copy of the sent message using Message objects directly in non-blocking mode" do sent_message = Message.new string received_message = Message.new poll_it_for_read(@pong) do rc = @ping.sendmsg(Message.new(string), ZMQ::NonBlocking) LibZMQ.version2? ? rc.should == 0 : rc.should == string.size end rc = @pong.recvmsg received_message, ZMQ::NonBlocking LibZMQ.version2? ? rc.should == 0 : rc.should == string.size received_message.copy_out_string.should == string end end # context ping-pong end # describe end # module ZMQ ruby-ffi-rzmq-1.0.3/spec/socket_spec.rb000066400000000000000000000476731222421464700200330ustar00rootroot00000000000000 require File.join(File.dirname(__FILE__), %w[spec_helper]) module ZMQ describe Socket do include APIHelper socket_types = if LibZMQ.version2? [ZMQ::REQ, ZMQ::REP, ZMQ::DEALER, ZMQ::ROUTER, ZMQ::PUB, ZMQ::SUB, ZMQ::PUSH, ZMQ::PULL, ZMQ::PAIR] elsif LibZMQ.version3? [ZMQ::REQ, ZMQ::REP, ZMQ::DEALER, ZMQ::ROUTER, ZMQ::PUB, ZMQ::SUB, ZMQ::PUSH, ZMQ::PULL, ZMQ::PAIR, ZMQ::XPUB, ZMQ::XSUB] end context "when initializing" do before(:all) { @ctx = Context.new } after(:all) { @ctx.terminate } it "should raise an error for a nil context" do lambda { Socket.new(FFI::Pointer.new(0), ZMQ::REQ) }.should raise_exception(ZMQ::ContextError) end it "works with a Context#pointer as the context_ptr" do lambda do s = Socket.new(@ctx.pointer, ZMQ::REQ) s.close end.should_not raise_exception end it "works with a Context instance as the context_ptr" do lambda do s = Socket.new(@ctx, ZMQ::SUB) s.close end.should_not raise_exception end socket_types.each do |socket_type| it "should not raise an error for a [#{ZMQ::SocketTypeNameMap[socket_type]}] socket type" do sock = nil lambda { sock = Socket.new(@ctx.pointer, socket_type) }.should_not raise_error sock.close end end # each socket_type it "should set the :socket accessor to the raw socket allocated by libzmq" do socket = double('socket') socket.stub(:null? => false) LibZMQ.should_receive(:zmq_socket).and_return(socket) sock = Socket.new(@ctx.pointer, ZMQ::REQ) sock.socket.should == socket end it "should define a finalizer on this object" do ObjectSpace.should_receive(:define_finalizer).at_least(1) sock = Socket.new(@ctx.pointer, ZMQ::REQ) sock.close end unless jruby? it "should track pid in finalizer so subsequent fork will not segfault" do sock = Socket.new(@ctx.pointer, ZMQ::REQ) pid = fork { } Process.wait(pid) sock.close end end end # context initializing context "calling close" do before(:all) { @ctx = Context.new } after(:all) { @ctx.terminate } it "should call LibZMQ.close only once" do sock = Socket.new @ctx.pointer, ZMQ::REQ raw_socket = sock.socket LibZMQ.should_receive(:close).with(raw_socket) sock.close sock.close LibZMQ.close raw_socket # *really close it otherwise the context will block indefinitely end end # context calling close context "identity=" do before(:all) { @ctx = Context.new } after(:all) { @ctx.terminate } it "fails to set identity for identities in excess of 255 bytes" do sock = Socket.new @ctx.pointer, ZMQ::REQ sock.identity = ('a' * 256) sock.identity.should == '' sock.close end it "fails to set identity for identities of length 0" do sock = Socket.new @ctx.pointer, ZMQ::REQ sock.identity = '' sock.identity.should == '' sock.close end it "sets the identity for identities of 1 byte" do sock = Socket.new @ctx.pointer, ZMQ::REQ sock.identity = 'a' sock.identity.should == 'a' sock.close end it "set the identity identities of 255 bytes" do sock = Socket.new @ctx.pointer, ZMQ::REQ sock.identity = ('a' * 255) sock.identity.should == ('a' * 255) sock.close end it "should convert numeric identities to strings" do sock = Socket.new @ctx.pointer, ZMQ::REQ sock.identity = 7 sock.identity.should == '7' sock.close end end # context identity= socket_types.each do |socket_type| context "#setsockopt for a #{ZMQ::SocketTypeNameMap[socket_type]} socket" do before(:all) { @ctx = Context.new } after(:all) { @ctx.terminate } let(:socket) do Socket.new @ctx.pointer, socket_type end after(:each) do socket.close end context "using option ZMQ::IDENTITY" do it "should set the identity given any string under 255 characters" do length = 4 (1..255).each do |length| identity = 'a' * length socket.setsockopt ZMQ::IDENTITY, identity array = [] rc = socket.getsockopt(ZMQ::IDENTITY, array) rc.should == 0 array[0].should == identity end end it "returns -1 given a string 256 characters or longer" do identity = 'a' * 256 array = [] rc = socket.setsockopt(ZMQ::IDENTITY, identity) rc.should == -1 end end # context using option ZMQ::IDENTITY if version2? context "using option ZMQ::HWM" do it "should set the high water mark given a positive value" do hwm = 4 socket.setsockopt ZMQ::HWM, hwm array = [] rc = socket.getsockopt(ZMQ::HWM, array) rc.should == 0 array[0].should == hwm end end # context using option ZMQ::HWM context "using option ZMQ::SWAP" do it "should set the swap value given a positive value" do swap = 10_000 socket.setsockopt ZMQ::SWAP, swap array = [] rc = socket.getsockopt(ZMQ::SWAP, array) rc.should == 0 array[0].should == swap end it "returns -1 given a negative value" do swap = -10_000 rc = socket.setsockopt(ZMQ::SWAP, swap) rc.should == -1 end end # context using option ZMQ::SWP context "using option ZMQ::MCAST_LOOP" do it "should enable the multicast loopback given a 1 (true) value" do socket.setsockopt ZMQ::MCAST_LOOP, 1 array = [] rc = socket.getsockopt(ZMQ::MCAST_LOOP, array) rc.should == 0 array[0].should be_true end it "should disable the multicast loopback given a 0 (false) value" do socket.setsockopt ZMQ::MCAST_LOOP, 0 array = [] rc = socket.getsockopt(ZMQ::MCAST_LOOP, array) rc.should == 0 array[0].should be_false end end # context using option ZMQ::MCAST_LOOP context "using option ZMQ::RECOVERY_IVL_MSEC" do it "should set the time interval for saving messages measured in milliseconds given a positive value" do value = 200 socket.setsockopt ZMQ::RECOVERY_IVL_MSEC, value array = [] rc = socket.getsockopt(ZMQ::RECOVERY_IVL_MSEC, array) rc.should == 0 array[0].should == value end it "should default to a value of -1" do value = -1 array = [] rc = socket.getsockopt(ZMQ::RECOVERY_IVL_MSEC, array) rc.should == 0 array[0].should == value end end # context using option ZMQ::RECOVERY_IVL_MSEC else # version3 or higher context "using option ZMQ::IPV4ONLY" do it "should enable use of IPV6 sockets when set to 0" do value = 0 socket.setsockopt ZMQ::IPV4ONLY, value array = [] rc = socket.getsockopt(ZMQ::IPV4ONLY, array) rc.should == 0 array[0].should == value end it "should default to a value of 1" do value = 1 array = [] rc = socket.getsockopt(ZMQ::IPV4ONLY, array) rc.should == 0 array[0].should == value end it "returns -1 given a negative value" do value = -1 rc = socket.setsockopt ZMQ::IPV4ONLY, value rc.should == -1 end it "returns -1 given a value > 1" do value = 2 rc = socket.setsockopt ZMQ::IPV4ONLY, value rc.should == -1 end end # context using option ZMQ::IPV4ONLY context "using option ZMQ::LAST_ENDPOINT" do it "should return last enpoint" do random_port = bind_to_random_tcp_port(socket, max_tries = 500) array = [] rc = socket.getsockopt(ZMQ::LAST_ENDPOINT, array) ZMQ::Util.resultcode_ok?(rc).should == true endpoint_regex = %r{\Atcp://(.*):(\d+)\0\z} array[0].should =~ endpoint_regex Integer(array[0][endpoint_regex, 2]).should == random_port end end end # version2? if/else block context "using option ZMQ::SUBSCRIBE" do if ZMQ::SUB == socket_type it "returns 0 for a SUB socket" do rc = socket.setsockopt(ZMQ::SUBSCRIBE, "topic.string") rc.should == 0 end else it "returns -1 for non-SUB sockets" do rc = socket.setsockopt(ZMQ::SUBSCRIBE, "topic.string") rc.should == -1 end end end # context using option ZMQ::SUBSCRIBE context "using option ZMQ::UNSUBSCRIBE" do if ZMQ::SUB == socket_type it "returns 0 given a topic string that was previously subscribed" do socket.setsockopt ZMQ::SUBSCRIBE, "topic.string" rc = socket.setsockopt(ZMQ::UNSUBSCRIBE, "topic.string") rc.should == 0 end else it "returns -1 for non-SUB sockets" do rc = socket.setsockopt(ZMQ::UNSUBSCRIBE, "topic.string") rc.should == -1 end end end # context using option ZMQ::UNSUBSCRIBE context "using option ZMQ::AFFINITY" do it "should set the affinity value given a positive value" do affinity = 3 socket.setsockopt ZMQ::AFFINITY, affinity array = [] rc = socket.getsockopt(ZMQ::AFFINITY, array) rc.should == 0 array[0].should == affinity end end # context using option ZMQ::AFFINITY context "using option ZMQ::RATE" do it "should set the multicast send rate given a positive value" do rate = 200 socket.setsockopt ZMQ::RATE, rate array = [] rc = socket.getsockopt(ZMQ::RATE, array) rc.should == 0 array[0].should == rate end it "returns -1 given a negative value" do rate = -200 rc = socket.setsockopt ZMQ::RATE, rate rc.should == -1 end end # context using option ZMQ::RATE context "using option ZMQ::RECOVERY_IVL" do it "should set the multicast recovery buffer measured in seconds given a positive value" do rate = 200 socket.setsockopt ZMQ::RECOVERY_IVL, rate array = [] rc = socket.getsockopt(ZMQ::RECOVERY_IVL, array) rc.should == 0 array[0].should == rate end it "returns -1 given a negative value" do rate = -200 rc = socket.setsockopt ZMQ::RECOVERY_IVL, rate rc.should == -1 end end # context using option ZMQ::RECOVERY_IVL context "using option ZMQ::SNDBUF" do it "should set the OS send buffer given a positive value" do size = 100 socket.setsockopt ZMQ::SNDBUF, size array = [] rc = socket.getsockopt(ZMQ::SNDBUF, array) rc.should == 0 array[0].should == size end end # context using option ZMQ::SNDBUF context "using option ZMQ::RCVBUF" do it "should set the OS receive buffer given a positive value" do size = 100 socket.setsockopt ZMQ::RCVBUF, size array = [] rc = socket.getsockopt(ZMQ::RCVBUF, array) rc.should == 0 array[0].should == size end end # context using option ZMQ::RCVBUF context "using option ZMQ::LINGER" do it "should set the socket message linger option measured in milliseconds given a positive value" do value = 200 socket.setsockopt ZMQ::LINGER, value array = [] rc = socket.getsockopt(ZMQ::LINGER, array) rc.should == 0 array[0].should == value end it "should set the socket message linger option to 0 for dropping packets" do value = 0 socket.setsockopt ZMQ::LINGER, value array = [] rc = socket.getsockopt(ZMQ::LINGER, array) rc.should == 0 array[0].should == value end if (ZMQ::SUB == socket_type) && version3? || (defined?(ZMQ::XSUB) && ZMQ::XSUB == socket_type) it "should default to a value of 0" do value = 0 array = [] rc = socket.getsockopt(ZMQ::LINGER, array) rc.should == 0 array[0].should == value end else it "should default to a value of -1" do value = -1 array = [] rc = socket.getsockopt(ZMQ::LINGER, array) rc.should == 0 array[0].should == value end end end # context using option ZMQ::LINGER context "using option ZMQ::RECONNECT_IVL" do it "should set the time interval for reconnecting disconnected sockets measured in milliseconds given a positive value" do value = 200 socket.setsockopt ZMQ::RECONNECT_IVL, value array = [] rc = socket.getsockopt(ZMQ::RECONNECT_IVL, array) rc.should == 0 array[0].should == value end it "should default to a value of 100" do value = 100 array = [] rc = socket.getsockopt(ZMQ::RECONNECT_IVL, array) rc.should == 0 array[0].should == value end end # context using option ZMQ::RECONNECT_IVL context "using option ZMQ::BACKLOG" do it "should set the maximum number of pending socket connections given a positive value" do value = 200 socket.setsockopt ZMQ::BACKLOG, value array = [] rc = socket.getsockopt(ZMQ::BACKLOG, array) rc.should == 0 array[0].should == value end it "should default to a value of 100" do value = 100 array = [] rc = socket.getsockopt(ZMQ::BACKLOG, array) rc.should == 0 array[0].should == value end end # context using option ZMQ::BACKLOG end # context #setsockopt context "#getsockopt for a #{ZMQ::SocketTypeNameMap[socket_type]} socket" do before(:all) { @ctx = Context.new } after(:all) { @ctx.terminate } let(:socket) do Socket.new @ctx.pointer, socket_type end after(:each) do socket.close end if RUBY_PLATFORM =~ /linux|darwin/ # this spec doesn't work on Windows; hints welcome context "using option ZMQ::FD" do it "should return an FD as a positive integer" do array = [] rc = socket.getsockopt(ZMQ::FD, array) rc.should == 0 array[0].should > 0 end it "returns a valid FD that is accepted by the system poll() function" do # Use FFI to wrap the C library function +poll+ so that we can execute it # on the 0mq file descriptor. If it returns 0, then it succeeded and the FD # is valid! module LibSocket extend FFI::Library # figures out the correct libc for each platform including Windows library = ffi_lib(FFI::Library::LIBC).first find_type(:nfds_t) rescue typedef(:uint32, :nfds_t) attach_function :poll, [:pointer, :nfds_t, :int], :int class PollFD < FFI::Struct layout :fd, :int, :events, :short, :revents, :short end end # module LibSocket array = [] rc = socket.getsockopt(ZMQ::FD, array) rc.should be_zero fd = array[0] # setup the BSD poll_fd struct pollfd = LibSocket::PollFD.new pollfd[:fd] = fd pollfd[:events] = 0 pollfd[:revents] = 0 rc = LibSocket.poll(pollfd, 1, 0) rc.should be_zero end end end # posix platform context "using option ZMQ::EVENTS" do it "should return a mask of events as a Fixnum" do array = [] rc = socket.getsockopt(ZMQ::EVENTS, array) rc.should == 0 array[0].should be_a(Fixnum) end end context "using option ZMQ::TYPE" do it "should return the socket type" do array = [] rc = socket.getsockopt(ZMQ::TYPE, array) rc.should == 0 array[0].should == socket_type end end end # context #getsockopt end # each socket_type describe "Mapping socket EVENTS to POLLIN and POLLOUT" do include APIHelper shared_examples_for "pubsub sockets where" do it "SUB socket that received a message always has POLLIN set" do events = [] rc = @sub.getsockopt(ZMQ::EVENTS, events) rc.should == 0 events[0].should == ZMQ::POLLIN end it "PUB socket always has POLLOUT set" do events = [] rc = @pub.getsockopt(ZMQ::EVENTS, events) rc.should == 0 events[0].should == ZMQ::POLLOUT end it "PUB socket never has POLLIN set" do events = [] rc = @pub.getsockopt(ZMQ::EVENTS, events) rc.should == 0 events[0].should_not == ZMQ::POLLIN end it "SUB socket never has POLLOUT set" do events = [] rc = @sub.getsockopt(ZMQ::EVENTS, events) rc.should == 0 events[0].should_not == ZMQ::POLLOUT end end # shared example for pubsub context "when SUB binds and PUB connects" do before(:each) do @ctx = Context.new poller_setup endpoint = "inproc://socket_test" @sub = @ctx.socket ZMQ::SUB rc = @sub.setsockopt ZMQ::SUBSCRIBE, '' rc.should == 0 @pub = @ctx.socket ZMQ::PUB @sub.bind(endpoint) connect_to_inproc(@pub, endpoint) @pub.send_string('test') end #it_behaves_like "pubsub sockets where" # see Jira LIBZMQ-270 end # context SUB binds PUB connects context "when SUB connects and PUB binds" do before(:each) do @ctx = Context.new poller_setup endpoint = "inproc://socket_test" @sub = @ctx.socket ZMQ::SUB rc = @sub.setsockopt ZMQ::SUBSCRIBE, '' @pub = @ctx.socket ZMQ::PUB @pub.bind(endpoint) connect_to_inproc(@sub, endpoint) poll_it_for_read(@sub) do rc = @pub.send_string('test') end end it_behaves_like "pubsub sockets where" end # context SUB binds PUB connects after(:each) do @sub.close @pub.close # must call close on *every* socket before calling terminate otherwise it blocks indefinitely @ctx.terminate end end # describe 'events mapping to pollin and pollout' end # describe Socket end # module ZMQ ruby-ffi-rzmq-1.0.3/spec/spec_helper.rb000066400000000000000000000045521222421464700200070ustar00rootroot00000000000000# To run these specs using rake, make sure the 'bones' and 'bones-extras' # gems are installed. Then execute 'rake spec' from the main directory # to run all specs. require File.expand_path( File.join(File.dirname(__FILE__), %w[.. lib ffi-rzmq])) require 'thread' # necessary when testing in MRI 1.8 mode Thread.abort_on_exception = true require 'openssl' require 'socket' require 'securerandom' # define some version guards so we can turn on/off specs based upon # the version of the 0mq library that is loaded def version2? ZMQ::LibZMQ.version2? end def version3? ZMQ::LibZMQ.version3? end def jruby? RUBY_PLATFORM =~ /java/ end def connect_to_inproc(socket, endpoint) begin rc = socket.connect(endpoint) end until ZMQ::Util.resultcode_ok?(rc) end module APIHelper def poller_setup @helper_poller ||= ZMQ::Poller.new end def poller_register_socket(socket) @helper_poller.register(socket, ZMQ::POLLIN) end def poller_deregister_socket(socket) @helper_poller.deregister(socket, ZMQ::POLLIN) end def poll_delivery # timeout after 1 second @helper_poller.poll(1000) end def poll_it_for_read(socket, &blk) poller_register_socket(socket) blk.call poll_delivery poller_deregister_socket(socket) end # generate a random port between 10_000 and 65534 def random_port rand(55534) + 10_000 end def bind_to_random_tcp_port(socket, max_tries = 500) tries = 0 rc = -1 while !ZMQ::Util.resultcode_ok?(rc) && tries < max_tries tries += 1 random = random_port rc = socket.bind(local_transport_string(random)) end unless ZMQ::Util.resultcode_ok?(rc) raise "Could not bind to random port successfully; retries all failed!" end random end def connect_to_random_tcp_port socket, max_tries = 500 tries = 0 rc = -1 while !ZMQ::Util.resultcode_ok?(rc) && tries < max_tries tries += 1 random = random_port rc = socket.connect(local_transport_string(random)) end unless ZMQ::Util.resultcode_ok?(rc) raise "Could not connect to random port successfully; retries all failed!" end random end def local_transport_string(port) "tcp://127.0.0.1:#{port}" end def assert_ok(rc) raise "Failed with rc [#{rc}] and errno [#{ZMQ::Util.errno}], msg [#{ZMQ::Util.error_string}]! #{caller(0)}" unless rc >= 0 end end ruby-ffi-rzmq-1.0.3/spec/support/000077500000000000000000000000001222421464700166775ustar00rootroot00000000000000ruby-ffi-rzmq-1.0.3/spec/support/test.crt000066400000000000000000000015231222421464700203710ustar00rootroot00000000000000-----BEGIN CERTIFICATE----- MIICRzCCAbCgAwIBAgIJAIhPfXaKAijbMA0GCSqGSIb3DQEBBQUAMCIxCzAJBgNV BAYTAlhZMRMwEQYDVQQIEwpOZXJ2ZXJsYW5kMB4XDTEyMDgyODE2NDIzMloXDTEz MDgyODE2NDIzMlowIjELMAkGA1UEBhMCWFkxEzARBgNVBAgTCk5lcnZlcmxhbmQw gZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAPTe1/MFg4gGjB5Kx/XzP5HQys2k R+X7+h+cG7jz9p8mNiW/G54WQX7z4EzLrSFrNdXhe6NRXzqcVtyNl9NPh4QpPpOi ddOjNWworai6d7NShK4gFzL62qY5gSsZ4TYLxfSDEy6Zggy0fFFu8C7iHJVo/1kY A1OxikDkbfHX0rW3AgMBAAGjgYQwgYEwHQYDVR0OBBYEFEbSBVWuzrSmust9Sa6J sm7Tg40KMFIGA1UdIwRLMEmAFEbSBVWuzrSmust9Sa6Jsm7Tg40KoSakJDAiMQsw CQYDVQQGEwJYWTETMBEGA1UECBMKTmVydmVybGFuZIIJAIhPfXaKAijbMAwGA1Ud EwQFMAMBAf8wDQYJKoZIhvcNAQEFBQADgYEAS0+17FjKmjCPHfJApTZgVXGD/LHC Bdzo+bl/hWpMyF27CUJxdZOMRpHqpE/Qv77jG/tfHnuArYPwDcB8AIErmvhNKCZx GLm19kTd4K1Y5JcAqkOHBma1e1V/g4ryWvUtpQkqD6tQxX0ctlBmXXK/Jsj/wG0W NNCQq2S19aNZcGo= -----END CERTIFICATE----- ruby-ffi-rzmq-1.0.3/spec/support/test.key000066400000000000000000000015671222421464700204010ustar00rootroot00000000000000-----BEGIN RSA PRIVATE KEY----- MIICXQIBAAKBgQD03tfzBYOIBoweSsf18z+R0MrNpEfl+/ofnBu48/afJjYlvxue FkF+8+BMy60hazXV4XujUV86nFbcjZfTT4eEKT6TonXTozVsKK2ounezUoSuIBcy +tqmOYErGeE2C8X0gxMumYIMtHxRbvAu4hyVaP9ZGANTsYpA5G3x19K1twIDAQAB AoGBALdY8BsAIudD98Bqv+RxuUSGMIPfoRIcJMFsUvmeeifaJasHuDcbdPkIxAbc boraSpoV1kyIDiTFkOhdgLPxFYaxlHGN7c/WqaGMtTMUuKgyItXPEFd9vHOWImIM gJKaYGSrKbAsiFt1mGBKEPRDE1TwnrPZAKj9mGJA1gtzq6GxAkEA/z3HvaXoBuGw 9f7uSzURSIkL+HYPejr83IBeqFdH+roxhgkwh+WMWBbNicCu0LsM00QAuZXlnqX7 FUaKguL1GQJBAPWZK/+Gs7LF6GqkANu+FOVhe9zxbffvZ+ibvDraB7fvgay+TwYi 88J6IhSp30F/tUtTGRl2UyszXLXbAAxpC08CQCwQTFVPOPlHKTeupRDSvoMZNbnV F+LwIAspFi5VsxVz42zSVVCArnPeq+kmHIfoYtRuHvnrCNMUsH4ByZPC/rECQQDO GJ+Haq5ZkyKaes4NmNFIPCoJGsDBkrGLzUSDznszq1USdREzgRk1VfBLjtG+0UB9 2VnyuAzK7+sY4JKF15CZAkAewe1vMGsFAXYojCI+wsDVlvlGTFmAZYnXE4hUVft+ j/XwOPp7WiWl9dgJ25wQrkYOrYMkZ9SqHO0SSxeSo2yT -----END RSA PRIVATE KEY-----