rbczmq 0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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