mdp 0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,19 @@
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
+ pkg
19
+ ext/majordomo/Makefile
@@ -0,0 +1,4 @@
1
+ = Changelog
2
+
3
+ == 0.1 ( September 06, 2012)
4
+ * Initial public release
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source :rubygems
2
+
3
+ gemspec
4
+
5
+ gem 'rake'
@@ -0,0 +1,19 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ majordomo (0.1)
5
+
6
+ GEM
7
+ remote: http://rubygems.org/
8
+ specs:
9
+ rake (0.9.2.2)
10
+ rake-compiler (0.8.1)
11
+ rake
12
+
13
+ PLATFORMS
14
+ ruby
15
+
16
+ DEPENDENCIES
17
+ majordomo!
18
+ rake
19
+ rake-compiler (~> 0.8.0)
@@ -0,0 +1,158 @@
1
+ = Majordomo - A Ruby binding for libmdp (Majordomo implementation in C)
2
+
3
+ (c) 2012 Lourens Naudé (methodmissing), with README guidance and content from the Majordomo Protocol 0.2 spec (http://rfc.zeromq.org/spec:18)
4
+
5
+ http://github.com/methodmissing/majordomo_ruby
6
+
7
+ == What is Majordomo ?
8
+
9
+ The Majordomo Protocol (MDP) defines a reliable service-oriented request-reply dialog between :
10
+
11
+ * a set of clients
12
+ * a broker
13
+ * a set of workers
14
+
15
+ MDP covers presence, heartbeating and service-oriented request-reply processing. This extension implements MDP version 0.2, which adds support for multiple replies for a single request (http://rfc.zeromq.org/spec:18).
16
+
17
+ == Why should I use it ?
18
+
19
+ The goals of MDP are to:
20
+
21
+ * Allow requests to be routed to workers on the basis of abstract service names.
22
+ * Allow both peers to detect disconnection of the other peer, through the use of heartbeating.
23
+ * Allow the broker to implement a "least recently used" pattern for task distribution to workers for a given service.
24
+ * Allow the broker to recover from dead or disconnected workers by resending requests to other workers.
25
+
26
+ == What are clients and workers ?
27
+
28
+ Clients are applications that issue requests. Workers process them and are idempotent, thus it is safe to execute the same request more than once. Workers will handle at most one request a time, and will issue exactly one reply for each successful request.
29
+
30
+ == Reliability
31
+
32
+ The Majordomo pattern is designed to extend the basic ØMQ request-reply pattern with the ability to detect and recover from a specific set of failures:
33
+
34
+ * Worker applications which crash, run too slowly, or freeze.
35
+ * Worker applications that are disconnected from the network (temporarily or permanently).
36
+ * Client applications that are temporarily disconnected from the network.
37
+ * A queue broker that crashes and is restarted.
38
+ * A queue broker that suffers a permanent failure.
39
+ * Requests or replies that are lost due to any of these failures.
40
+
41
+ == Scalability and Performance
42
+
43
+ Majordomo is designed to be scalable to large numbers (thousands) of workers and clients, limited only by system resources on the broker. Partitioning of workers by service allows for multiple applications to share the same broker infrastructure.
44
+
45
+ == Usage
46
+
47
+ ==== Start the broker
48
+
49
+ Lourenss-MacBook-Air:src lourens$ mdp_broker -v
50
+ 12-09-06 02:05:06 I: MDP broker/0.2.0 is active at tcp://*:5555
51
+
52
+ ==== Start a worker
53
+
54
+ 2.0.0dev :001 > require 'majordomo'
55
+ => true
56
+ 2.0.0dev :002 > w = Majordomo::Worker.new("tcp://localhost:5555", "service", true)
57
+ 12-09-06 02:02:45 I: connecting to broker at tcp://localhost:5555...
58
+ 12-09-06 02:02:45 I: sending READY to broker
59
+ --------------------------------------
60
+ [000]
61
+ [006] MDPW0X
62
+ [001] 01
63
+ [007] service
64
+ => #<Majordomo::Worker:0x007fc533927840>
65
+ 2.0.0dev :003 > w.recv
66
+ 12-09-06 02:03:06 I: received message from broker:
67
+ --------------------------------------
68
+ [000]
69
+ [006] MDPW0X
70
+ [001] 04
71
+ 12-09-06 02:03:06 I: sending HEARTBEAT to broker
72
+ --------------------------------------
73
+ [000]
74
+ [006] MDPW0X
75
+ [001] 04
76
+ 12-09-06 02:03:06 I: received message from broker:
77
+ --------------------------------------
78
+ [000]
79
+ [006] MDPW0X
80
+ [001] 04
81
+ 12-09-06 02:03:06 I: received message from broker:
82
+ --------------------------------------
83
+ [000]
84
+ [006] MDPW0X
85
+ [001] 04
86
+ 12-09-06 02:03:06 I: received message from broker:
87
+ --------------------------------------
88
+ [000]
89
+ [006] MDPW0X
90
+ [001] 05
91
+ 12-09-06 02:03:06 I: connecting to broker at tcp://localhost:5555...
92
+ 12-09-06 02:03:06 I: sending READY to broker
93
+ --------------------------------------
94
+ [000]
95
+ [006] MDPW0X
96
+ [001] 01
97
+ [007] service
98
+
99
+ ==== Start a client
100
+
101
+ 2.0.0dev :001 > require 'majordomo'
102
+ => true
103
+ 2.0.0dev :002 > c = Majordomo::Client.new("tcp://localhost:5555", true)
104
+ 12-09-06 02:00:11 I: connecting to broker at tcp://localhost:5555...
105
+ => #<Majordomo::Client:0x007ffc738d71c0>
106
+ 2.0.0dev :003 > c.send("service", "message")
107
+ 12-09-06 02:00:26 I: send request to 'service' service:
108
+ --------------------------------------
109
+ [000]
110
+ [006] MDPC0X
111
+ [007] service
112
+ [007] message
113
+ => true
114
+
115
+ See the test/ folder for further use cases - runnable examples forthcoming.
116
+
117
+ == Resources
118
+
119
+ * ZeroMQ - http://www.zeromq.org/community
120
+ * CZMQ - http://czmq.zeromq.org/
121
+ * The Majordomo pattern - http://zguide.zeromq.org/page:all#Service-Oriented-Reliable-Queuing-Majordomo-Pattern
122
+ * Majordomo protocol - http://rfc.zeromq.org/spec:7
123
+ * Majordomo protocol 0.2 - http://rfc.zeromq.org/spec:18
124
+
125
+ == Requirements
126
+
127
+ * A POSIX compliant OS, known to work well on Linux, BSD variants and Mac OS X
128
+ * Ruby MRI 1.8, 1.9, 2.x or Rubinius (JRuby capi support is deprecated)
129
+ * A C compiler
130
+ * ZeroMQ - http://www.zeromq.org/intro:get-the-software
131
+ * CZMQ - http://czmq.zeromq.org/
132
+ * libmdp - https://github.com/zeromq/majordomo
133
+
134
+ == Installation
135
+
136
+ Rubygems installation
137
+
138
+ gem install mdp
139
+
140
+ Building from source
141
+
142
+ git clone git@github.com:methodmissing/majordomo_ruby
143
+ rake
144
+
145
+ Running tests
146
+
147
+ rake test
148
+
149
+ OS X notes:
150
+
151
+ If you are installing ZeroMQ and CZMQ on a new Mac ensure you have libtool and autoconf installed.
152
+ You can get those with the brew packaging system:
153
+
154
+ brew install libtool autoconf automake
155
+
156
+ == Contact, feedback and bugs
157
+
158
+ Please log bugs and suggestions at https://github.com/methodmissing/majordomo_ruby/issues
@@ -0,0 +1,47 @@
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
+
12
+ begin
13
+ require 'rdoc/task'
14
+ rescue LoadError # fallback to older 1.8.7 rubies
15
+ require 'rake/rdoctask'
16
+ end
17
+
18
+ gemspec = eval(IO.read('mdp.gemspec'))
19
+
20
+ Gem::PackageTask.new(gemspec) do |pkg|
21
+ end
22
+
23
+ Rake::ExtensionTask.new('majordomo', gemspec) do |ext|
24
+ ext.name = 'majordomo_ext'
25
+ ext.ext_dir = 'ext/majordomo'
26
+
27
+ CLEAN.include 'lib/**/majordomo_ext.*'
28
+ end
29
+
30
+ desc 'Run Majordomo tests'
31
+ Rake::TestTask.new(:test) do |t|
32
+ t.test_files = Dir.glob("test/**/test_*.rb")
33
+ t.verbose = true
34
+ t.warning = true
35
+ end
36
+
37
+ Rake::RDocTask.new do |rd|
38
+ files = FileList["README.rdoc", "ext/majordomo/*.c"]
39
+ rd.title = "A Ruby binding for libmd (Majordomo implementation in C)"
40
+ rd.main = "README.rdoc"
41
+ rd.rdoc_dir = "doc"
42
+ rd.options << "--promiscuous"
43
+ rd.rdoc_files.include(files)
44
+ end
45
+
46
+ task :test => :compile
47
+ task :default => :test
@@ -0,0 +1,302 @@
1
+ #include "majordomo_ext.h"
2
+
3
+ VALUE rb_cMajordomoClient;
4
+
5
+ /*
6
+ * :nodoc:
7
+ * GC mark callback
8
+ *
9
+ */
10
+ static void rb_mark_majordomo_client(void *ptr)
11
+ {
12
+ rb_majordomo_client_t *client = (rb_majordomo_client_t *)ptr;
13
+ if (client) {
14
+ rb_gc_mark(client->broker);
15
+ rb_gc_mark(client->timeout);
16
+ }
17
+ }
18
+
19
+ /*
20
+ * :nodoc:
21
+ * Release the GIL when closing a Majordomo client
22
+ *
23
+ */
24
+ static VALUE rb_nogvl_mdp_client_close(void *ptr)
25
+ {
26
+ mdp_client_t *client = ptr;
27
+ mdp_client_destroy(&client);
28
+ return Qnil;
29
+ }
30
+
31
+ /*
32
+ * :nodoc:
33
+ * GC free callback
34
+ *
35
+ */
36
+ static void rb_free_majordomo_client(void *ptr)
37
+ {
38
+ rb_majordomo_client_t *client = (rb_majordomo_client_t *)ptr;
39
+ if (client) {
40
+ if (client->client) rb_thread_blocking_region(rb_nogvl_mdp_client_close, (void *)client->client, RUBY_UBF_IO, 0);
41
+ #ifndef HAVE_RB_THREAD_BLOCKING_REGION
42
+ zlist_destroy(&(client->recv_buffer));
43
+ #endif
44
+ xfree(client);
45
+ client = NULL;
46
+ }
47
+ }
48
+
49
+ /*
50
+ * :nodoc:
51
+ * Release the GIL when creating a new Majordomo client
52
+ *
53
+ */
54
+ static VALUE rb_nogvl_mdp_client_new(void *ptr)
55
+ {
56
+ struct nogvl_md_client_new_args *args = ptr;
57
+ return (VALUE)mdp_client_new(args->broker, args->verbose);
58
+ }
59
+
60
+ /*
61
+ * call-seq:
62
+ * Majordomo::Client.new("tcp://0.0.0.0:5555") => Majordomo::Client
63
+ * Majordomo::Client.new("tcp://0.0.0.0:5555", true) => Majordomo::Client
64
+ *
65
+ * Creates a new Majordomo::Client instance. A broker URI is required and an optional verbose flag
66
+ * can be passed to the initializer.
67
+ *
68
+ * === Examples
69
+ * cl = Majordomo::Client.new("tcp://0.0.0.0:5555") => Majordomo::Client
70
+ * cl.broker => "tcp://0.0.0.0:5555"
71
+ * cl.timeout => 2500
72
+ * cl.send("test", "request") => "reply"
73
+ *
74
+ */
75
+ static VALUE rb_majordomo_client_s_new(int argc, VALUE *argv, VALUE klass)
76
+ {
77
+ rb_majordomo_client_t *client = NULL;
78
+ struct nogvl_md_client_new_args args;
79
+ VALUE obj, broker, verbose;
80
+ rb_scan_args(argc, argv, "11", &broker, &verbose);
81
+ if (verbose == Qnil)
82
+ verbose = Qfalse;
83
+ Check_Type(broker, T_STRING);
84
+ obj = Data_Make_Struct(klass, rb_majordomo_client_t, rb_mark_majordomo_client, rb_free_majordomo_client, client);
85
+ args.broker = RSTRING_PTR(broker);
86
+ args.verbose = (verbose == Qtrue ? 1 : 0);
87
+ client->client = (mdp_client_t *)rb_thread_blocking_region(rb_nogvl_mdp_client_new, (void *)&args, RUBY_UBF_IO, 0);
88
+ client->broker = rb_str_new4(broker);
89
+ client->timeout = INT2NUM(MAJORDOMO_CLIENT_TIMEOUT);
90
+ #ifndef HAVE_RB_THREAD_BLOCKING_REGION
91
+ client->recv_buffer = zlist_new();
92
+ #endif
93
+ rb_obj_call_init(obj, 0, NULL);
94
+ return obj;
95
+ }
96
+
97
+ /*
98
+ * call-seq:
99
+ * cl.broker => String
100
+ *
101
+ * Returns the URI of the broker this client is connected to.
102
+ *
103
+ * === Examples
104
+ * cl = Majordomo::Client.new("tcp://0.0.0.0:5555") => Majordomo::Client
105
+ * cl.broker => "tcp://0.0.0.0:5555"
106
+ *
107
+ */
108
+ static VALUE rb_majordomo_client_broker(VALUE obj){
109
+ GetMajordomoClient(obj);
110
+ return client->broker;
111
+ }
112
+
113
+ /*
114
+ * call-seq:
115
+ * cl.timeout => Fixnum
116
+ *
117
+ * Returns the request timeout for this client (in msecs).
118
+ *
119
+ * === Examples
120
+ * cl = Majordomo::Client.new("tcp://0.0.0.0:5555") => Majordomo::Client
121
+ * cl.timeout => 2500
122
+ *
123
+ */
124
+ static VALUE rb_majordomo_client_timeout(VALUE obj){
125
+ GetMajordomoClient(obj);
126
+ return client->timeout;
127
+ }
128
+
129
+ /*
130
+ * call-seq:
131
+ * cl.timeout = val => nil
132
+ *
133
+ * Sets the request timeout for this client (in msecs).
134
+ *
135
+ * === Examples
136
+ * cl = Majordomo::Client.new("tcp://0.0.0.0:5555") => Majordomo::Client
137
+ * cl.timeout = 100 => nil
138
+ * cl.timeout => 100
139
+ *
140
+ */
141
+ static VALUE rb_majordomo_client_timeout_equals(VALUE obj, VALUE timeout){
142
+ GetMajordomoClient(obj);
143
+ Check_Type(timeout, T_FIXNUM);
144
+ mdp_client_set_timeout(client->client, FIX2INT(timeout));
145
+ client->timeout = timeout;
146
+ return Qnil;
147
+ }
148
+
149
+ /*
150
+ * :nodoc:
151
+ * Release the GIL when sending a client message
152
+ *
153
+ */
154
+ static VALUE rb_nogvl_mdp_client_send(void *ptr)
155
+ {
156
+ struct nogvl_md_client_send_args *args = ptr;
157
+ #ifdef HAVE_RB_THREAD_BLOCKING_REGION
158
+ mdp_client_send(args->client, args->service, &args->request);
159
+ #else
160
+ uint32_t events;
161
+ size_t evopt_len = sizeof (uint32_t);
162
+ int fd;
163
+ size_t fdopt_len = sizeof (int);
164
+ if (rb_thread_alone()) {
165
+ mdp_client_send(args->client, args->service, &args->request);
166
+ return Qnil;
167
+ }
168
+ try_writable:
169
+ mdp_client_getsockopt (args->client, ZMQ_EVENTS, &events, &evopt_len);
170
+ if ((events & ZMQ_POLLOUT) == ZMQ_POLLOUT) {
171
+ mdp_client_send(args->client, args->service, &args->request);
172
+ } else {
173
+ mdp_client_getsockopt (args->client, ZMQ_FD, &fd, &fdopt_len);
174
+ rb_thread_wait_fd(fd);
175
+ goto try_writable;
176
+ }
177
+ #endif
178
+ return Qnil;
179
+ }
180
+
181
+ /*
182
+ * call-seq:
183
+ * cl.send("service", "message") => boolean
184
+ *
185
+ * Send a request to the broker. Returns true if the send was successful.
186
+ *
187
+ * === Examples
188
+ * cl = Majordomo::Client.new("tcp://0.0.0.0:5555") => Majordomo::Client
189
+ * cl.send("service", "message") => true
190
+ *
191
+ */
192
+ static VALUE rb_majordomo_client_send(VALUE obj, VALUE service, VALUE message){
193
+ struct nogvl_md_client_send_args args;
194
+ GetMajordomoClient(obj);
195
+ Check_Type(service, T_STRING);
196
+ Check_Type(message, T_STRING);
197
+ args.client = client->client;
198
+ args.service = RSTRING_PTR(service);
199
+ args.request = zmsg_new();
200
+ if (!args.request)
201
+ return Qfalse;
202
+ if (zmsg_pushstr(args.request, RSTRING_PTR(message)) != 0){
203
+ zmsg_destroy(&args.request);
204
+ return Qfalse;
205
+ }
206
+ rb_thread_blocking_region(rb_nogvl_mdp_client_send, (void *)&args, RUBY_UBF_IO, 0);
207
+ return Qtrue;
208
+ }
209
+
210
+ /*
211
+ * :nodoc:
212
+ * Release the GIL when receiving a client message
213
+ *
214
+ */
215
+ static VALUE rb_nogvl_mdp_client_recv(void *ptr)
216
+ {
217
+ struct nogvl_md_client_recv_args *args = ptr;
218
+ rb_majordomo_client_t *client = args->client;
219
+ #ifdef HAVE_RB_THREAD_BLOCKING_REGION
220
+ return (VALUE)mdp_client_recv(client->client, args->service);
221
+ #else
222
+ uint32_t events;
223
+ size_t evopt_len = sizeof (uint32_t);
224
+ int fd;
225
+ size_t fdopt_len = sizeof (int);
226
+ if (zlist_size(client->recv_buffer) != 0)
227
+ return (VALUE)zlist_pop(client->recv_buffer);
228
+ try_readable:
229
+ mdp_client_getsockopt (client->client, ZMQ_EVENTS, &events, &evopt_len);
230
+ if ((events & ZMQ_POLLIN) == ZMQ_POLLIN) {
231
+ do {
232
+ zlist_append(client->recv_buffer, mdp_client_recv(client->client, args->service));
233
+ } while (zmq_errno() != EAGAIN && zmq_errno() != EINTR);
234
+ return (VALUE)zlist_pop(client->recv_buffer);
235
+ } else {
236
+ mdp_client_getsockopt (client->client, ZMQ_FD, &fd, &fdopt_len);
237
+ rb_thread_wait_fd(fd);
238
+ goto try_readable;
239
+ }
240
+ #endif
241
+ }
242
+
243
+ /*
244
+ * call-seq:
245
+ * cl.recv("service") => String or nil
246
+ *
247
+ * Send a request to the broker and get a reply even if it has to retry several times. Valid replies are of type
248
+ * String and NilClass.
249
+ *
250
+ * === Examples
251
+ * cl = Majordomo::Client.new("tcp://0.0.0.0:5555") => Majordomo::Client
252
+ * cl.send("service", "message") => nil
253
+ * cl.recv("service") => "reply"
254
+ *
255
+ */
256
+ static VALUE rb_majordomo_client_recv(VALUE obj, VALUE service){
257
+ VALUE rep;
258
+ zmsg_t *reply = NULL;
259
+ struct nogvl_md_client_recv_args args;
260
+ GetMajordomoClient(obj);
261
+ Check_Type(service, T_STRING);
262
+ args.client = client;
263
+ args.service = RSTRING_PTR(service);
264
+ reply = (zmsg_t *)rb_thread_blocking_region(rb_nogvl_mdp_client_recv, (void *)&args, RUBY_UBF_IO, 0);
265
+ if (!reply)
266
+ return Qnil;
267
+ rep = MajordomoEncode(rb_str_new2(zmsg_popstr(reply)));
268
+ zmsg_destroy(&reply);
269
+ return rep;
270
+ }
271
+
272
+ /*
273
+ * call-seq:
274
+ * cl.close => nil
275
+ *
276
+ * Close the client connection to the broker.
277
+ *
278
+ * === Examples
279
+ * cl = Majordomo::Client.new("tcp://0.0.0.0:5555") => Majordomo::Client
280
+ * cl.close => nil
281
+ *
282
+ */
283
+ static VALUE rb_majordomo_client_close(VALUE obj){
284
+ VALUE ret;
285
+ GetMajordomoClient(obj);
286
+ ret = rb_thread_blocking_region(rb_nogvl_mdp_client_close, (void *)client->client, RUBY_UBF_IO, 0);
287
+ client->client = NULL;
288
+ return ret;
289
+ }
290
+
291
+ void _init_majordomo_client()
292
+ {
293
+ rb_cMajordomoClient = rb_define_class_under(rb_mMajordomo, "Client", rb_cObject);
294
+
295
+ rb_define_singleton_method(rb_cMajordomoClient, "new", rb_majordomo_client_s_new, -1);
296
+ rb_define_method(rb_cMajordomoClient, "broker", rb_majordomo_client_broker, 0);
297
+ rb_define_method(rb_cMajordomoClient, "timeout", rb_majordomo_client_timeout, 0);
298
+ rb_define_method(rb_cMajordomoClient, "timeout=", rb_majordomo_client_timeout_equals, 1);
299
+ rb_define_method(rb_cMajordomoClient, "send", rb_majordomo_client_send, 2);
300
+ rb_define_method(rb_cMajordomoClient, "recv", rb_majordomo_client_recv, 1);
301
+ rb_define_method(rb_cMajordomoClient, "close", rb_majordomo_client_close, 0);
302
+ }