rbczmq 0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (101) hide show
  1. data/.gitignore +23 -0
  2. data/.travis.yml +19 -0
  3. data/Gemfile +5 -0
  4. data/Gemfile.lock +19 -0
  5. data/MIT-LICENSE +20 -0
  6. data/README.rdoc +247 -0
  7. data/Rakefile +67 -0
  8. data/examples/loop.rb +109 -0
  9. data/examples/poller.rb +37 -0
  10. data/examples/pub_sub.rb +101 -0
  11. data/examples/push_pull.rb +104 -0
  12. data/examples/req_rep.rb +100 -0
  13. data/ext/czmq.tar.gz +0 -0
  14. data/ext/rbczmq/context.c +280 -0
  15. data/ext/rbczmq/context.h +26 -0
  16. data/ext/rbczmq/extconf.rb +138 -0
  17. data/ext/rbczmq/frame.c +401 -0
  18. data/ext/rbczmq/frame.h +24 -0
  19. data/ext/rbczmq/jruby.h +22 -0
  20. data/ext/rbczmq/loop.c +413 -0
  21. data/ext/rbczmq/loop.h +24 -0
  22. data/ext/rbczmq/message.c +620 -0
  23. data/ext/rbczmq/message.h +24 -0
  24. data/ext/rbczmq/poller.c +308 -0
  25. data/ext/rbczmq/poller.h +29 -0
  26. data/ext/rbczmq/pollitem.c +251 -0
  27. data/ext/rbczmq/pollitem.h +25 -0
  28. data/ext/rbczmq/rbczmq_ext.c +198 -0
  29. data/ext/rbczmq/rbczmq_ext.h +94 -0
  30. data/ext/rbczmq/rbczmq_prelude.h +22 -0
  31. data/ext/rbczmq/rubinius.h +24 -0
  32. data/ext/rbczmq/ruby18.h +43 -0
  33. data/ext/rbczmq/ruby19.h +15 -0
  34. data/ext/rbczmq/socket.c +1570 -0
  35. data/ext/rbczmq/socket.h +136 -0
  36. data/ext/rbczmq/timer.c +110 -0
  37. data/ext/rbczmq/timer.h +23 -0
  38. data/ext/zeromq.tar.gz +0 -0
  39. data/lib/rbczmq.rb +3 -0
  40. data/lib/zmq.rb +77 -0
  41. data/lib/zmq/context.rb +50 -0
  42. data/lib/zmq/default_handler.rb +16 -0
  43. data/lib/zmq/frame.rb +11 -0
  44. data/lib/zmq/handler.rb +76 -0
  45. data/lib/zmq/loop.rb +131 -0
  46. data/lib/zmq/message.rb +9 -0
  47. data/lib/zmq/poller.rb +22 -0
  48. data/lib/zmq/pollitem.rb +31 -0
  49. data/lib/zmq/socket.rb +125 -0
  50. data/lib/zmq/socket/dealer.rb +33 -0
  51. data/lib/zmq/socket/pair.rb +39 -0
  52. data/lib/zmq/socket/pub.rb +30 -0
  53. data/lib/zmq/socket/pull.rb +29 -0
  54. data/lib/zmq/socket/push.rb +32 -0
  55. data/lib/zmq/socket/rep.rb +37 -0
  56. data/lib/zmq/socket/req.rb +37 -0
  57. data/lib/zmq/socket/router.rb +38 -0
  58. data/lib/zmq/socket/sub.rb +27 -0
  59. data/lib/zmq/timer.rb +12 -0
  60. data/lib/zmq/version.rb +5 -0
  61. data/perf/pair.rb +7 -0
  62. data/perf/pair/local.rb +22 -0
  63. data/perf/pair/remote.rb +25 -0
  64. data/perf/pub_sub.rb +7 -0
  65. data/perf/pub_sub/local.rb +22 -0
  66. data/perf/pub_sub/remote.rb +25 -0
  67. data/perf/push_pull.rb +7 -0
  68. data/perf/push_pull/local.rb +21 -0
  69. data/perf/push_pull/remote.rb +25 -0
  70. data/perf/req_rep.rb +7 -0
  71. data/perf/req_rep/local.rb +35 -0
  72. data/perf/req_rep/remote.rb +28 -0
  73. data/perf/runner.rb +142 -0
  74. data/rbczmq.gemspec +22 -0
  75. data/test/helper.rb +21 -0
  76. data/test/socket/test_dealer_socket.rb +14 -0
  77. data/test/socket/test_pair_socket.rb +24 -0
  78. data/test/socket/test_pair_sockets.rb +74 -0
  79. data/test/socket/test_pub_socket.rb +17 -0
  80. data/test/socket/test_pub_sub_sockets.rb +87 -0
  81. data/test/socket/test_pull_socket.rb +17 -0
  82. data/test/socket/test_push_pull_sockets.rb +81 -0
  83. data/test/socket/test_push_socket.rb +17 -0
  84. data/test/socket/test_rep_socket.rb +25 -0
  85. data/test/socket/test_req_rep_sockets.rb +42 -0
  86. data/test/socket/test_req_socket.rb +27 -0
  87. data/test/socket/test_router_socket.rb +14 -0
  88. data/test/socket/test_routing.rb +66 -0
  89. data/test/socket/test_sub_socket.rb +17 -0
  90. data/test/test_context.rb +86 -0
  91. data/test/test_frame.rb +78 -0
  92. data/test/test_handler.rb +28 -0
  93. data/test/test_loop.rb +252 -0
  94. data/test/test_message.rb +201 -0
  95. data/test/test_poller.rb +154 -0
  96. data/test/test_pollitem.rb +78 -0
  97. data/test/test_socket.rb +403 -0
  98. data/test/test_threading.rb +34 -0
  99. data/test/test_timer.rb +37 -0
  100. data/test/test_zmq.rb +62 -0
  101. metadata +208 -0
data/.gitignore ADDED
@@ -0,0 +1,23 @@
1
+ .libs/*
2
+ .rbx/*
3
+ *.rbc
4
+ *.lo
5
+ *.la
6
+ *.lai
7
+ *.dylib.dSYM
8
+ *.dylib
9
+ *.o
10
+ *.a
11
+ *.log
12
+ tmp/*
13
+ true/*
14
+ *.bundle
15
+ *.gem
16
+ doc/*
17
+ .DS_Store
18
+ ext/zeromq
19
+ ext/czmq
20
+ pkg
21
+ scratch
22
+ ext/rbczmq/dst
23
+ ext/rbczmq/Makefile
data/.travis.yml ADDED
@@ -0,0 +1,19 @@
1
+ language: ruby
2
+ rvm:
3
+ - rbx-18mode
4
+ - rbx-19mode
5
+ - ree
6
+ - 1.8.7
7
+ - 1.9.2
8
+ - 1.9.3
9
+ - ruby-head
10
+ script: "bundle exec rake"
11
+ before_install: "sudo apt-get install uuid-dev"
12
+ gemfile:
13
+ - Gemfile
14
+ notifications:
15
+ recipients:
16
+ - lourens@methodmissing.com
17
+ branches:
18
+ only:
19
+ - master
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source :rubygems
2
+
3
+ gemspec
4
+
5
+ gem 'rake'
data/Gemfile.lock ADDED
@@ -0,0 +1,19 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ rbczmq (0.1)
5
+
6
+ GEM
7
+ remote: http://rubygems.org/
8
+ specs:
9
+ rake (0.9.2.2)
10
+ rake-compiler (0.8.0)
11
+ rake
12
+
13
+ PLATFORMS
14
+ ruby
15
+
16
+ DEPENDENCIES
17
+ rake
18
+ rake-compiler (~> 0.8.0)
19
+ rbczmq!
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Lourens Naudé - http://github.com/methodmissing
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,247 @@
1
+ = rbczmq - binding for the high level ZeroMQ C API {<img src="https://secure.travis-ci.org/methodmissing/rbczmq.png" alt="Build Status" />}[http://travis-ci.org/methodmissing/rbczmq]
2
+
3
+ (c) 2011 Lourens Naudé (methodmissing), James Tucker (raggi) with API guidance from the czmq (http://czmq.zeromq.org/) project.
4
+
5
+ http://github.com/methodmissing/rbczmq
6
+
7
+ ==== NOT YET PRODUCTION READY, BUT ALMOST
8
+
9
+ == About ZeroMQ
10
+
11
+ In a nutshell, ZeroMQ is a hybrid networking library / concurrency framework. I quote the ØMQ Guide (http://zguide.zeromq.org/page:all) :
12
+
13
+ "ØMQ (ZeroMQ, 0MQ, zmq) looks like an embeddable networking library but acts like a concurrency framework. It gives you sockets that carry whole messages across various transports like in-process, inter-process, TCP, and multicast. You can connect sockets N-to-N with patterns like fanout, pub-sub, task distribution, and request-reply. It's fast enough to be the fabric for clustered products. Its asynchronous I/O model gives you scalable multicore applications, built as asynchronous message-processing tasks. It has a score of language APIs and runs on most operating systems. ØMQ is from iMatix and is LGPL open source."
14
+
15
+ == Another ZeroMQ extension ?
16
+
17
+ This extension bundles both ZeroMQ (libzmq, http://www.zeromq.org/) and CZMQ (libczmq, http://czmq.zeromq.org/) and as such have no third party dependencies other than a Ruby distribution and a C compiler. My goals for this project were :
18
+
19
+ * Access to a powerful messaging technology without having to install a bunch of dependencies
20
+ * A stable and mostly version agnostic (2.x and 3.x series) API
21
+ * Leverage and build upon a mature and maintained client (CZMQ)
22
+ * Target Ruby distributions with a stable and comprehensive C API (MRI, Rubinius, JRuby is work in progress)
23
+ * Support for running sockets in Threads - both green and native threads should be supported and preempt properly
24
+ with edge-triggered multiplexing from libzmq.
25
+ * Integrate with the Garbage Collector in a predictable way. CZMQ and the ZeroMQ framework is very fast and can allocate
26
+ an enormous amount of objects in no time when using the Frame, Message and String wrappers. Resources such as socket
27
+ connections should be cleaned up when objects are finalized as well.
28
+ * Expose Message envelopes and Frames to developers as well to allow for higher level protocols and constructs.
29
+ * Enforce well known best practices such as restricting socket interactions to within the thread the socket was created in etc.
30
+
31
+ == Performance
32
+
33
+ ZeroMQ can have higher throughput than TCP in most cases by using a message batching technique. Please have a look through the Performance section in the ZeroMQ FAQ (http://www.zeromq.org/area:faq#toc2) for further implementation details.
34
+
35
+ Some notes about these benchmarks :
36
+
37
+ * Messages go through the full network stack on localhost (TCP/IP transport)
38
+ * The sender and receiver endpoints are Ruby processes which coerce transferred data to native String objects
39
+ on both ends.
40
+ * There's thus a definite method dispatch cost in addition to intermittent pauses from the Garbage Collector
41
+ * It's still plenty fast for most soft real-time applications and we're able to push in excess of 1 gigabits/s with the 1024 byte payloads. A language with automatic memory management cannot easily comply to hard real-time guarantees anyways.
42
+
43
+ === TCP/IP loopback, 100k messages, 100 byte payloads
44
+
45
+ Lourenss-MacBook-Air:rbczmq lourens$ MSG_COUNT=100000 MSG_SIZE=100 ruby perf/pair.rb
46
+ Local pids: 2042
47
+ Remote pid: 2043
48
+ Sent 100000 messages in 0.3933s ...
49
+ [2043] Memory used before: 1128kb
50
+ [2043] Memory used after: 3012kb
51
+ [2042] Memory used before: 1120kb
52
+ ====== [2042] transfer stats ======
53
+ message encoding: string
54
+ message size: 100 [B]
55
+ message count: 100000
56
+ mean throughput: 227978 [msg/s]
57
+ mean throughput: 182.383 [Mb/s]
58
+ [2042] Memory used after: 22432kb
59
+
60
+ === TCP/IP loopback, 100k messages, 1024 byte payloads
61
+
62
+ Lourenss-MacBook-Air:rbczmq lourens$ MSG_COUNT=100000 MSG_SIZE=1024 ruby perf/pair.rb
63
+ Local pids: 2027
64
+ Remote pid: 2028
65
+ Sent 100000 messages in 0.641198s ...
66
+ [2028] Memory used before: 1120kb
67
+ [2028] Memory used after: 12776kb
68
+ [2027] Memory used before: 1144kb
69
+ ====== [2027] transfer stats ======
70
+ message encoding: string
71
+ message size: 1024 [B]
72
+ message count: 100000
73
+ mean throughput: 160756 [msg/s]
74
+ mean throughput: 1316.919 [Mb/s]
75
+ [2027] Memory used after: 189004kb
76
+
77
+ === TCP/IP loopback, 100k messages, 2048 byte payloads
78
+
79
+ Lourenss-MacBook-Air:rbczmq lourens$ MSG_COUNT=100000 MSG_SIZE=2048 ruby perf/pair.rb
80
+ Local pids: 2034
81
+ Remote pid: 2035
82
+ Sent 100000 messages in 0.94703s ...
83
+ [2035] Memory used before: 1140kb
84
+ [2035] Memory used after: 7212kb
85
+ [2034] Memory used before: 1128kb
86
+ ====== [2034] transfer stats ======
87
+ message encoding: string
88
+ message size: 2048 [B]
89
+ message count: 100000
90
+ mean throughput: 123506 [msg/s]
91
+ mean throughput: 2023.528 [Mb/s]
92
+ [2034] Memory used after: 277712kb
93
+
94
+ Have a play around with the performance runner and other socket pairs as well - https://github.com/methodmissing/rbczmq/tree/master/perf
95
+
96
+ == Usage
97
+
98
+ As a first step I'd highly recommend you read (and reread) through the zguide (http://zguide.zeromq.org/page:all) as understanding the supported messaging patterns and topologies is fundamental to getting the most from this binding.
99
+ Here's a few basic examples. Please refer to documentation (http://methodmissing.github.com/rbczmq/) and test cases (https://github.com/methodmissing/rbczmq/tree/master/test) for detailed usage information.
100
+
101
+ === Basic send / receive, in process transport
102
+
103
+ ctx = ZMQ::Context.new
104
+ rep = ctx.socket(:PAIR)
105
+ port = rep.bind("inproc://send.receive")
106
+ req = ctx.socket(:PAIR)
107
+ req.connect("inproc://send.receive")
108
+ req.send("ping") # true
109
+ rep.recv # "ping"
110
+
111
+ ctx.destroy
112
+
113
+ === Fair-queued work distribution to a set of worker threads
114
+
115
+ ctx = ZMQ::Context.new
116
+ push = ctx.bind(:PUSH, "inproc://push-pull-distribution.test")
117
+ threads = []
118
+ 5.times do
119
+ threads << Thread.new do
120
+ pull = ctx.connect(:PULL, "inproc://push-pull-distribution.test")
121
+ msg = pull.recv
122
+ pull.close
123
+ msg
124
+ end
125
+ end
126
+
127
+ sleep 0.5 # avoid "slow joiner" syndrome
128
+ messages = %w(a b c d e f)
129
+ messages.each do |m|
130
+ push.send m
131
+ end
132
+
133
+ threads.each{|t| t.join }
134
+ threads.all?{|t| messages.include?(t.value) } # true
135
+
136
+ ctx.destroy
137
+
138
+ === Async request / reply routing
139
+
140
+ ctx = ZMQ::Context.new
141
+ router = ctx.bind(:ROUTER, "inproc://routing-flow.test")
142
+ dealer = ctx.socket(:DEALER)
143
+ dealer.identity = "xyz"
144
+ dealer.connect("inproc://routing-flow.test")
145
+
146
+ router.sendm("xyz")
147
+ router.send("request")
148
+ dealer.recv # "request"
149
+
150
+ dealer.send("reply")
151
+ router.recv # "xyz"
152
+ router.recv # "reply"
153
+
154
+ ctx.destroy
155
+
156
+ === Send / receive frames
157
+
158
+ ctx = ZMQ::Context.new
159
+ rep = ctx.socket(:PAIR)
160
+ rep.bind("inproc://frames.test")
161
+ req = ctx.socket(:PAIR)
162
+ req.connect("inproc://frames.test")
163
+ ping = ZMQ::Frame("ping")
164
+ req.send_frame(ping) # true
165
+ rep.recv_frame # ZMQ::Frame("ping")
166
+ rep.send_frame(ZMQ::Frame("pong")) # true
167
+ req.recv_frame # ZMQ::Frame("pong")
168
+ rep.send_frame(ZMQ::Frame("pong")) # true
169
+ req.recv_frame_nonblock # nil
170
+ sleep 0.3
171
+ req.recv_frame_nonblock # ZMQ::Frame("pong")
172
+
173
+ ctx.destroy
174
+
175
+ === Send / receive messages
176
+
177
+ ctx = ZMQ::Context.new
178
+ rep = ctx.socket(:PAIR)
179
+ rep.bind("inproc://messages.test")
180
+ req = ctx.socket(:PAIR)
181
+ req.connect("inproc://messages.test")
182
+
183
+ msg = ZMQ::Message.new
184
+ msg.push ZMQ::Frame("header")
185
+ msg.push ZMQ::Frame("body")
186
+
187
+ req.send_message(msg) # nil
188
+
189
+ recvd_msg = rep.recv_message
190
+ recvd_msg.class # ZMQ::Message
191
+ recvd_msg.pop # ZMQ::Frame("header")
192
+ recvd_msg.pop # ZMQ::Frame("body")
193
+
194
+ ctx.destroy
195
+
196
+ == Resources
197
+
198
+ * ZeroMQ - http://www.zeromq.org/community
199
+ * The ØMQ Reference Manual - http://api.zeromq.org/
200
+ * The ØMQ FAQ - http://www.zeromq.org/area:faq
201
+ * Whitepapers - http://www.zeromq.org/area:whitepapers
202
+ * The ØMQ Guide - http://zguide.zeromq.org/page:all
203
+ * CZMQ - http://czmq.zeromq.org/
204
+ * Recent presentation on data transfer by the author of this binding - http://www.slideshare.net/methodmissing/sapo-codebits-2011
205
+
206
+ == Requirements
207
+
208
+ * A POSIX compliant OS, known to work well on Linux, BSD variants and Mac OS X
209
+ * Ruby MRI 1.8, 1.9 or Rubinius (JRuby capi support forthcoming)
210
+ * A C compiler
211
+
212
+ == Installation
213
+
214
+ Rubygems installation
215
+
216
+ gem install rbczmq
217
+
218
+ Building from source
219
+
220
+ git clone git@github.com:methodmissing/rbczmq.git
221
+ rake
222
+
223
+ Running tests
224
+
225
+ rake test
226
+
227
+ == TODO
228
+
229
+ * ZMQ::Message#save && ZMQ::Message.load
230
+ * Optimize zloop handler callbacks (perftools)
231
+ * OS X leaks utility - http://developer.apple.com/library/mac/#documentation/Darwin/Reference/ManPages/man1/leaks.1.html
232
+ * Handle GC issue with timers in loop callbacks
233
+ * czmq send methods aren't non-blocking by default
234
+ * Enforce socket timeouts
235
+ * Revisit the ZMQ::Loop API
236
+ * Push gem out to rubygems.org
237
+ * RDOC fail on mixed C and Ruby source files that document that same constants
238
+ * GC guards to prevent recycling objects being sent / received.
239
+ * Sockets can bind && connect to multiple endpoints - account for that
240
+ * Watch out for further cases where REQ / REP pairs could raise EFSM
241
+ * Do not clobber local scope from macros (James's commit in master)
242
+ * Support installation without vendor'ed libs as well
243
+ * Incorporate examples into CI as well
244
+
245
+ == Contact, feedback and bugs
246
+
247
+ This project is still work in progress and I'm looking for guidance on API design, use cases and any outlier experiences. Please log bugs and suggestions at https://github.com/methodmissing/rbczmq/issues
data/Rakefile ADDED
@@ -0,0 +1,67 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems' unless defined?(Gem)
4
+ require 'rake' unless defined?(Rake)
5
+
6
+ # Prefer compiled Rubinius bytecode in .rbx/
7
+ ENV["RBXOPT"] = "-Xrbc.db"
8
+
9
+ require 'rake/extensiontask'
10
+ require 'rake/testtask'
11
+ begin
12
+ require 'rdoc/task'
13
+ rescue LoadError # fallback to older 1.8.7 rubies
14
+ require 'rake/rdoctask'
15
+ end
16
+
17
+ gemspec = eval(IO.read('rbczmq.gemspec'))
18
+
19
+ Gem::PackageTask.new(gemspec) do |pkg|
20
+ end
21
+
22
+ Rake::ExtensionTask.new('rbczmq', gemspec) do |ext|
23
+ ext.name = 'rbczmq_ext'
24
+ ext.ext_dir = 'ext/rbczmq'
25
+
26
+ CLEAN.include 'ext/rbczmq/dst'
27
+ CLEAN.include 'ext/zeromq'
28
+ CLEAN.include 'ext/czmq'
29
+ CLEAN.include 'lib/**/rbczmq_ext.*'
30
+ end
31
+
32
+ Rake::RDocTask.new do |rd|
33
+ files = FileList["README.rdoc", "lib/**/*.rb", "ext/rbczmq/*.c"]
34
+ rd.title = "rbczmq - binding for the high level ZeroMQ C API"
35
+ rd.main = "README.rdoc"
36
+ rd.rdoc_dir = "doc"
37
+ rd.options << "--promiscuous"
38
+ rd.rdoc_files.include(files)
39
+ end
40
+
41
+ desc 'Run rbczmq tests'
42
+ Rake::TestTask.new(:test) do |t|
43
+ t.test_files = Dir.glob("test/**/test_*.rb")
44
+ t.verbose = true
45
+ t.warning = true
46
+ end
47
+
48
+ namespace :debug do
49
+ desc "Run the test suite under gdb"
50
+ task :gdb do
51
+ system "gdb --args ruby rake"
52
+ end
53
+
54
+ desc "Run the test suite under valgrind"
55
+ task :valgrind do
56
+ valgrind_opts = "--num-callers=5 --error-limit=no --partial-loads-ok=yes --undef-value-errors=no --leak-check=full"
57
+ system %[valgrind #{valgrind_opts} ruby -w -I"lib" -I"/Users/lourens/.rvm/gems/ruby-1.8.7-p352/gems/rake-0.9.2/lib" "/Users/lourens/.rvm/gems/ruby-1.8.7-p352/gems/rake-0.9.2/lib/rake/rake_test_loader.rb" "test/test_context.rb" "test/test_frame.rb" "test/test_loop.rb" "test/test_message.rb" "test/test_socket.rb" "test/test_threading.rb" "test/test_timer.rb" "test/test_zmq.rb"]
58
+ end
59
+ end
60
+
61
+ desc 'Clobber Rubinius .rbc files'
62
+ task :clobber_rbc do
63
+ sh 'find . -name *.rbc -print0 | xargs -0 rm'
64
+ end
65
+
66
+ task :test => :compile
67
+ task :default => :test
data/examples/loop.rb ADDED
@@ -0,0 +1,109 @@
1
+ # encoding: utf-8
2
+
3
+ $:.unshift('.')
4
+ $:.unshift(File.expand_path(File.dirname(__FILE__)) + '/../lib')
5
+ require 'zmq'
6
+ require 'pp'
7
+
8
+ class ConsumerHandler < ZMQ::Handler
9
+ def initialize(pollable, consumer)
10
+ super
11
+ @consumer = consumer
12
+ end
13
+
14
+ def on_readable
15
+ @consumer.perform recv
16
+ end
17
+ end
18
+
19
+ class ProducerHandler < ZMQ::Handler
20
+ def initialize(pollable, producer)
21
+ super
22
+ @producer = producer
23
+ end
24
+
25
+ def on_writable
26
+ @producer.work
27
+ end
28
+ end
29
+
30
+ class Consumer
31
+ attr_reader :thread
32
+ def initialize(ctx, endpoint, topic = "")
33
+ @socket = ctx.socket(:SUB)
34
+ @socket.subscribe("")
35
+ @socket.connect(endpoint)
36
+ # verbose output
37
+ @socket.verbose = true
38
+ @socket.subscribe(topic)
39
+ @jobs, @working = 0, 0.0
40
+ end
41
+
42
+ def start
43
+ ZL.register_readable(@socket, ConsumerHandler, self)
44
+ self
45
+ end
46
+
47
+ def stop
48
+ ZL.remove(@socket)
49
+ stats
50
+ end
51
+
52
+ def perform(work)
53
+ # Random hot loop to simulate CPU intensive work
54
+ start = Time.now
55
+ work.to_i.times{}
56
+ @jobs += 1
57
+ @working += (Time.now - start).to_f
58
+ end
59
+
60
+ private
61
+ def stats
62
+ puts "Processed #{@jobs} jobs in %.4f seconds" % @working
63
+ $stdout.flush
64
+ end
65
+ end
66
+
67
+ class Producer
68
+ def initialize(ctx, endpoint, topic = "")
69
+ @ctx, @endpoint, @topic, @consumers = ctx, endpoint, topic, []
70
+ @socket = ctx.socket(:PUB)
71
+ # verbose output
72
+ @socket.verbose = true
73
+ @socket.bind(endpoint)
74
+ @socket.linger = 1
75
+ @interrupted = false
76
+ end
77
+
78
+ def spawn_consumers(count = 10)
79
+ count.times do
80
+ @consumers << Consumer.new(@ctx, @endpoint, @topic).start
81
+ end
82
+ end
83
+
84
+ def start
85
+ ZL.register_writable(@socket, ProducerHandler, self)
86
+ end
87
+
88
+ def stop
89
+ @consumers.each{|c| c.stop }
90
+ ZL.remove(@socket)
91
+ ZL.stop
92
+ @ctx.destroy
93
+ end
94
+
95
+ def work
96
+ work = "#{@topic}#{rand(100_000).to_s}"
97
+ @socket.send(work)
98
+ end
99
+ end
100
+
101
+ ZL.run do
102
+ ctx = ZMQ::Context.new
103
+ producer = Producer.new(ctx, 'inproc://example.loop')
104
+ producer.spawn_consumers
105
+ trap(:INT) do
106
+ producer.stop
107
+ end
108
+ producer.start
109
+ end