romp-rpc 0.2.0
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.
- data/README.md +24 -0
- data/ext/extconf.rb +3 -0
- data/ext/romp_helper.c +822 -0
- data/lib/romp-rpc.rb +548 -0
- data/sample/client.rb +115 -0
- data/sample/server.rb +67 -0
- metadata +74 -0
data/README.md
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
romp
|
2
|
+
====
|
3
|
+
|
4
|
+
ROMP is the Ruby Object Message Proxy. It is sort of like drb
|
5
|
+
(distributed Ruby) in that it allows a Ruby client program to
|
6
|
+
transparently talk to an object that is sitting on a server. Its
|
7
|
+
features include:
|
8
|
+
|
9
|
+
* Up to 40000 messages per second
|
10
|
+
* Allows more than one object to reside on the server; supports a sort
|
11
|
+
for naming service for retriving references to objects.
|
12
|
+
* Fully thread-safe, provided the object sitting on the server is
|
13
|
+
thread-safe.
|
14
|
+
* Supports oneway calls, so the client does not have to block waiting
|
15
|
+
for a response from the server.
|
16
|
+
* Allows exceptions to be propogated from server to client; massages
|
17
|
+
the backtrace to make debugging easier.
|
18
|
+
|
19
|
+
NEWS (Feb. 21, 2002) - The author of drb has done some performance tests
|
20
|
+
comparing ROMP and drb. It looks like drb is a LOT faster than it used
|
21
|
+
to be (but still not as fast as ROMP). You can see the results here:
|
22
|
+
|
23
|
+
http://www.jin.gr.jp/~nahi/RWiki/?cmd=view;name=ROMP%3A%3A%C2%AE%C5%D9%C8%E6%B3%D3 (Japanese)
|
24
|
+
|
data/ext/extconf.rb
ADDED
data/ext/romp_helper.c
ADDED
@@ -0,0 +1,822 @@
|
|
1
|
+
// ROMP - The Ruby Object Message Proxy
|
2
|
+
// (C) Copyright 2001 Paul Brannan (cout at rm-f.net)
|
3
|
+
//
|
4
|
+
// ROMP is a set of classes for providing distributed object support to a
|
5
|
+
// Ruby program. You may distribute and/or modify it under the same terms as
|
6
|
+
// Ruby (see http://www.ruby-lang.org/en/LICENSE.txt).
|
7
|
+
|
8
|
+
#include <ruby.h>
|
9
|
+
#include <rubyio.h>
|
10
|
+
#include <stdint.h>
|
11
|
+
#include <stdlib.h>
|
12
|
+
#include <unistd.h>
|
13
|
+
#include <fcntl.h>
|
14
|
+
#include <sys/time.h>
|
15
|
+
|
16
|
+
// ---------------------------------------------------------------------------
|
17
|
+
// Useful macros
|
18
|
+
// ---------------------------------------------------------------------------
|
19
|
+
|
20
|
+
// TODO: Are these portable?
|
21
|
+
|
22
|
+
#define PUTSHORT(s, buf) \
|
23
|
+
do { \
|
24
|
+
*buf = s >> 8; ++buf; \
|
25
|
+
*buf = s & 0xff; ++buf; \
|
26
|
+
} while(0)
|
27
|
+
|
28
|
+
#define GETSHORT(s, buf) \
|
29
|
+
do { \
|
30
|
+
s = (unsigned char)*buf; ++buf; \
|
31
|
+
s = (s << 8) | (unsigned char)*buf; ++buf; \
|
32
|
+
} while(0)
|
33
|
+
|
34
|
+
// ---------------------------------------------------------------------------
|
35
|
+
// Globals
|
36
|
+
// ---------------------------------------------------------------------------
|
37
|
+
|
38
|
+
// objects/functions created down below
|
39
|
+
//
|
40
|
+
static VALUE rb_mROMP = Qnil;
|
41
|
+
static VALUE rb_cSession = Qnil;
|
42
|
+
static VALUE rb_cProxy_Object = Qnil;
|
43
|
+
static VALUE rb_cServer = Qnil;
|
44
|
+
static VALUE rb_cObject_Reference = Qnil;
|
45
|
+
static ID id_object_id;
|
46
|
+
|
47
|
+
// objects/functions created elsewhere
|
48
|
+
//
|
49
|
+
static VALUE rb_mMarshal = Qnil;
|
50
|
+
|
51
|
+
static ID id_dump;
|
52
|
+
static ID id_load;
|
53
|
+
static ID id_message;
|
54
|
+
static ID id_backtrace;
|
55
|
+
static ID id_caller;
|
56
|
+
static ID id_raise;
|
57
|
+
static ID id_send;
|
58
|
+
static ID id_get_object;
|
59
|
+
static ID id_slice_bang;
|
60
|
+
static ID id_print_exception;
|
61
|
+
static ID id_lock;
|
62
|
+
static ID id_unlock;
|
63
|
+
|
64
|
+
static struct timeval zero_timeval;
|
65
|
+
|
66
|
+
static void init_globals() {
|
67
|
+
rb_mMarshal = rb_const_get(rb_cObject, rb_intern("Marshal"));
|
68
|
+
|
69
|
+
id_dump = rb_intern("dump");
|
70
|
+
id_load = rb_intern("load");
|
71
|
+
id_message = rb_intern("message");
|
72
|
+
id_backtrace = rb_intern("backtrace");
|
73
|
+
id_caller = rb_intern("caller");
|
74
|
+
id_raise = rb_intern("raise");
|
75
|
+
id_send = rb_intern("send");
|
76
|
+
id_get_object = rb_intern("get_object");
|
77
|
+
id_slice_bang = rb_intern("slice!");
|
78
|
+
id_print_exception = rb_intern("print_exception");
|
79
|
+
id_lock = rb_intern("lock");
|
80
|
+
id_unlock = rb_intern("unlock");
|
81
|
+
|
82
|
+
zero_timeval.tv_sec = 0;
|
83
|
+
zero_timeval.tv_usec = 0;
|
84
|
+
}
|
85
|
+
|
86
|
+
// ---------------------------------------------------------------------------
|
87
|
+
// Utility functions
|
88
|
+
// ---------------------------------------------------------------------------
|
89
|
+
|
90
|
+
// Forward declaration
|
91
|
+
static VALUE msg_to_obj(VALUE message, VALUE session, VALUE mutex);
|
92
|
+
|
93
|
+
#define WRITE_HELPER \
|
94
|
+
do { \
|
95
|
+
write_count = write(fd, buf, count); \
|
96
|
+
if(write_count < 0) { \
|
97
|
+
if(errno != EWOULDBLOCK) rb_sys_fail("write"); \
|
98
|
+
} else if(write_count == 0 && count != 0) { \
|
99
|
+
rb_raise(rb_eIOError, "disconnected"); \
|
100
|
+
} else { \
|
101
|
+
count -= write_count; \
|
102
|
+
buf += write_count; \
|
103
|
+
total += write_count; \
|
104
|
+
} \
|
105
|
+
} while(0)
|
106
|
+
|
107
|
+
#define READ_HELPER \
|
108
|
+
do { \
|
109
|
+
read_count = read(fd, buf, count); \
|
110
|
+
if(read_count < 0) { \
|
111
|
+
if(errno != EWOULDBLOCK) rb_sys_fail("read"); \
|
112
|
+
} else if(read_count == 0 && count != 0) { \
|
113
|
+
rb_raise(rb_eIOError, "disconnected"); \
|
114
|
+
} else { \
|
115
|
+
count -= read_count; \
|
116
|
+
buf += read_count; \
|
117
|
+
total += read_count; \
|
118
|
+
} \
|
119
|
+
} while(0)
|
120
|
+
|
121
|
+
// Write to an fd and raise an exception if an error occurs
|
122
|
+
static ssize_t ruby_write_throw(int fd, const void * buf, size_t count, int nonblock) {
|
123
|
+
int n;
|
124
|
+
size_t total = 0;
|
125
|
+
ssize_t write_count;
|
126
|
+
fd_set fds, error_fds;
|
127
|
+
|
128
|
+
if(!nonblock) {
|
129
|
+
FD_ZERO(&fds);
|
130
|
+
FD_SET(fd, &fds);
|
131
|
+
FD_ZERO(&error_fds);
|
132
|
+
FD_SET(fd, &error_fds);
|
133
|
+
n = select(fd + 1, 0, &fds, &fds, 0);
|
134
|
+
if(n > 0) {
|
135
|
+
WRITE_HELPER;
|
136
|
+
}
|
137
|
+
} else {
|
138
|
+
WRITE_HELPER;
|
139
|
+
}
|
140
|
+
|
141
|
+
while(count > 0) {
|
142
|
+
FD_ZERO(&fds);
|
143
|
+
FD_SET(fd, &fds);
|
144
|
+
FD_ZERO(&error_fds);
|
145
|
+
FD_SET(fd, &error_fds);
|
146
|
+
n = rb_thread_select(fd + 1, 0, &fds, &fds, 0);
|
147
|
+
if(n == -1) {
|
148
|
+
if(errno == EWOULDBLOCK) continue;
|
149
|
+
rb_sys_fail("select");
|
150
|
+
}
|
151
|
+
WRITE_HELPER;
|
152
|
+
};
|
153
|
+
return total;
|
154
|
+
}
|
155
|
+
|
156
|
+
// Read from an fd and raise an exception if an error occurs
|
157
|
+
static ssize_t ruby_read_throw(int fd, void * buf, size_t count, int nonblock) {
|
158
|
+
int n;
|
159
|
+
size_t total = 0;
|
160
|
+
ssize_t read_count;
|
161
|
+
fd_set fds, error_fds;
|
162
|
+
|
163
|
+
if(!nonblock) {
|
164
|
+
FD_ZERO(&fds);
|
165
|
+
FD_SET(fd, &fds);
|
166
|
+
FD_ZERO(&error_fds);
|
167
|
+
FD_SET(fd, &error_fds);
|
168
|
+
n = select(fd + 1, &fds, 0, &error_fds, &zero_timeval);
|
169
|
+
if(n > 0) {
|
170
|
+
READ_HELPER;
|
171
|
+
}
|
172
|
+
} else {
|
173
|
+
READ_HELPER;
|
174
|
+
}
|
175
|
+
|
176
|
+
while(count > 0) {
|
177
|
+
FD_ZERO(&fds);
|
178
|
+
FD_SET(fd, &fds);
|
179
|
+
FD_ZERO(&error_fds);
|
180
|
+
FD_SET(fd, &error_fds);
|
181
|
+
n = rb_thread_select(fd + 1, &fds, 0, &error_fds, 0);
|
182
|
+
if(n == -1) {
|
183
|
+
if(errno == EWOULDBLOCK) continue;
|
184
|
+
rb_sys_fail("select");
|
185
|
+
}
|
186
|
+
READ_HELPER;
|
187
|
+
};
|
188
|
+
|
189
|
+
return total;
|
190
|
+
}
|
191
|
+
|
192
|
+
// Return the message of an exception
|
193
|
+
static VALUE ruby_exc_message(VALUE exc) {
|
194
|
+
return rb_funcall(exc, id_message, 0);
|
195
|
+
}
|
196
|
+
|
197
|
+
// Return the backtrace of an exception
|
198
|
+
static VALUE ruby_exc_backtrace(VALUE exc) {
|
199
|
+
return rb_funcall(exc, id_backtrace, 0);
|
200
|
+
}
|
201
|
+
|
202
|
+
// Return the current Ruby call stack
|
203
|
+
static VALUE ruby_caller() {
|
204
|
+
// TODO: Why does calling caller() with 0 arguments not work?
|
205
|
+
return rb_funcall(rb_mKernel, id_caller, 1, INT2NUM(0));
|
206
|
+
}
|
207
|
+
|
208
|
+
// Raise a specific ruby exception with a specific message and backtrace
|
209
|
+
static void ruby_raise(VALUE exc, VALUE msg, VALUE bt) {
|
210
|
+
rb_funcall(rb_mKernel, id_raise, 3, exc, msg, bt);
|
211
|
+
}
|
212
|
+
|
213
|
+
// Send a message to a Ruby object
|
214
|
+
static VALUE ruby_send(VALUE obj, VALUE msg) {
|
215
|
+
return rb_apply(obj, id_send, msg);
|
216
|
+
}
|
217
|
+
|
218
|
+
// Call "get_object" on a Ruby object
|
219
|
+
static VALUE ruby_get_object(VALUE obj, VALUE object_id) {
|
220
|
+
return rb_funcall(obj, id_get_object, 1, INT2NUM(object_id));
|
221
|
+
}
|
222
|
+
|
223
|
+
// Call slice! on a Ruby object
|
224
|
+
static VALUE ruby_slice_bang(VALUE obj, size_t min, size_t max) {
|
225
|
+
VALUE range = rb_range_new(INT2NUM(min), INT2NUM(max), 0);
|
226
|
+
return rb_funcall(obj, id_slice_bang, 1, range);
|
227
|
+
}
|
228
|
+
|
229
|
+
// Print an exception to the screen using a function defined in the ROMP
|
230
|
+
// module (TODO: wouldn't it be nice if Ruby's error_print were available to
|
231
|
+
// the user?)
|
232
|
+
static void ruby_print_exception(VALUE exc) {
|
233
|
+
rb_funcall(rb_mROMP, id_print_exception, 1, exc);
|
234
|
+
}
|
235
|
+
|
236
|
+
// Call lock on an object (a mutex, probably)
|
237
|
+
static VALUE ruby_lock(VALUE obj) {
|
238
|
+
return rb_funcall(obj, id_lock, 0);
|
239
|
+
}
|
240
|
+
|
241
|
+
// Call unlock on an object (a mutex, probably)
|
242
|
+
static VALUE ruby_unlock(VALUE obj) {
|
243
|
+
return rb_funcall(obj, id_unlock, 0);
|
244
|
+
}
|
245
|
+
|
246
|
+
// ---------------------------------------------------------------------------
|
247
|
+
// Marshalling functions
|
248
|
+
// ---------------------------------------------------------------------------
|
249
|
+
|
250
|
+
// Take an object as input and return it as a marshalled string.
|
251
|
+
static VALUE marshal_dump(VALUE obj) {
|
252
|
+
return rb_funcall(rb_mMarshal, id_dump, 1, obj);
|
253
|
+
}
|
254
|
+
|
255
|
+
// Take a marshalled string as input and return it as an object.
|
256
|
+
static VALUE marshal_load(VALUE str) {
|
257
|
+
return rb_funcall(rb_mMarshal, id_load, 1, str);
|
258
|
+
}
|
259
|
+
|
260
|
+
// ---------------------------------------------------------------------------
|
261
|
+
// Session functions
|
262
|
+
// ---------------------------------------------------------------------------
|
263
|
+
|
264
|
+
#define ROMP_REQUEST 0x1001
|
265
|
+
#define ROMP_REQUEST_BLOCK 0x1002
|
266
|
+
#define ROMP_ONEWAY 0x1003
|
267
|
+
#define ROMP_ONEWAY_SYNC 0x1004
|
268
|
+
#define ROMP_RETVAL 0x2001
|
269
|
+
#define ROMP_EXCEPTION 0x2002
|
270
|
+
#define ROMP_YIELD 0x2003
|
271
|
+
#define ROMP_SYNC 0x4001
|
272
|
+
#define ROMP_NULL_MSG 0x4002
|
273
|
+
#define ROMP_MSG_START 0x4242
|
274
|
+
#define ROMP_MAX_ID (1<<16)
|
275
|
+
#define ROMP_MAX_MSG_TYPE (1<<16)
|
276
|
+
|
277
|
+
#define ROMP_BUFFER_SIZE 16
|
278
|
+
|
279
|
+
typedef struct {
|
280
|
+
VALUE io_object;
|
281
|
+
int read_fd, write_fd;
|
282
|
+
char buf[ROMP_BUFFER_SIZE];
|
283
|
+
int nonblock;
|
284
|
+
} ROMP_Session;
|
285
|
+
|
286
|
+
typedef uint16_t MESSAGE_TYPE_T;
|
287
|
+
typedef uint16_t OBJECT_ID_T;
|
288
|
+
|
289
|
+
// A ROMP message is broken into 3 components (see romp.rb for more details)
|
290
|
+
typedef struct {
|
291
|
+
MESSAGE_TYPE_T message_type;
|
292
|
+
OBJECT_ID_T object_id;
|
293
|
+
VALUE message_obj;
|
294
|
+
} ROMP_Message;
|
295
|
+
|
296
|
+
// Send a message to the server with data data and length len.
|
297
|
+
static void send_message_helper(
|
298
|
+
ROMP_Session * session,
|
299
|
+
char * data,
|
300
|
+
size_t len,
|
301
|
+
MESSAGE_TYPE_T message_type,
|
302
|
+
OBJECT_ID_T object_id) {
|
303
|
+
|
304
|
+
char * buf = session->buf;
|
305
|
+
|
306
|
+
PUTSHORT(ROMP_MSG_START, buf);
|
307
|
+
PUTSHORT(len, buf);
|
308
|
+
PUTSHORT(message_type, buf);
|
309
|
+
PUTSHORT(object_id, buf);
|
310
|
+
|
311
|
+
ruby_write_throw(session->write_fd, session->buf, ROMP_BUFFER_SIZE, session->nonblock);
|
312
|
+
ruby_write_throw(session->write_fd, data, len, session->nonblock);
|
313
|
+
}
|
314
|
+
|
315
|
+
// Send a message to the server with the data in message.
|
316
|
+
static void send_message(ROMP_Session * session, ROMP_Message * message) {
|
317
|
+
VALUE data;
|
318
|
+
struct RString * data_str;
|
319
|
+
|
320
|
+
data = marshal_dump(message->message_obj);
|
321
|
+
data_str = RSTRING(data);
|
322
|
+
send_message_helper(
|
323
|
+
session,
|
324
|
+
data_str->ptr,
|
325
|
+
data_str->len,
|
326
|
+
message->message_type,
|
327
|
+
message->object_id);
|
328
|
+
}
|
329
|
+
|
330
|
+
// Send a null message to the server (no data, data len = 0)
|
331
|
+
static void send_null_message(ROMP_Session * session) {
|
332
|
+
send_message_helper(session, "", 0, ROMP_NULL_MSG, 0);
|
333
|
+
}
|
334
|
+
|
335
|
+
// Receive a message from the server
|
336
|
+
static void get_message(ROMP_Session * session, ROMP_Message * message) {
|
337
|
+
uint16_t magic = 0;
|
338
|
+
uint16_t data_len = 0;
|
339
|
+
char * buf = 0;
|
340
|
+
// struct RString message_string;
|
341
|
+
VALUE ruby_str;
|
342
|
+
|
343
|
+
do {
|
344
|
+
buf = session->buf;
|
345
|
+
|
346
|
+
ruby_read_throw(session->read_fd, buf, ROMP_BUFFER_SIZE, session->nonblock);
|
347
|
+
|
348
|
+
GETSHORT(magic, buf);
|
349
|
+
GETSHORT(data_len, buf);
|
350
|
+
GETSHORT(message->message_type, buf);
|
351
|
+
GETSHORT(message->object_id, buf);
|
352
|
+
} while(magic != ROMP_MSG_START);
|
353
|
+
|
354
|
+
buf = ALLOCA_N(char, data_len);
|
355
|
+
ruby_read_throw(session->read_fd, buf, data_len, session->nonblock);
|
356
|
+
ruby_str = rb_str_new(buf, data_len);
|
357
|
+
|
358
|
+
if(message->message_type != ROMP_NULL_MSG) {
|
359
|
+
message->message_obj = marshal_load(ruby_str);
|
360
|
+
} else {
|
361
|
+
message->message_obj = Qnil;
|
362
|
+
}
|
363
|
+
}
|
364
|
+
|
365
|
+
// Ideally, this function should return true if the server has disconnected,
|
366
|
+
// but currently always returns false. The server thread will still exit
|
367
|
+
// when the client has disconnected, but currently does so via an exception.
|
368
|
+
static int session_finished(ROMP_Session * session) {
|
369
|
+
// TODO: Detect a disconnection
|
370
|
+
return 0;
|
371
|
+
}
|
372
|
+
|
373
|
+
// Send a sync message to the server.
|
374
|
+
static void send_sync(ROMP_Session * session) {
|
375
|
+
ROMP_Message message = { ROMP_SYNC, 0, Qnil };
|
376
|
+
send_message(session, &message);
|
377
|
+
}
|
378
|
+
|
379
|
+
// Wait for a sync response from the server. Ignore any messages that are
|
380
|
+
// received while waiting for the response.
|
381
|
+
static void wait_sync(ROMP_Session * session) {
|
382
|
+
ROMP_Message message;
|
383
|
+
|
384
|
+
// sleep(1);
|
385
|
+
get_message(session, &message);
|
386
|
+
if( message.message_type != ROMP_SYNC
|
387
|
+
&& message.object_id != 1
|
388
|
+
&& message.message_obj != Qnil) {
|
389
|
+
rb_raise(rb_eRuntimeError, "ROMP synchronization failed");
|
390
|
+
}
|
391
|
+
}
|
392
|
+
|
393
|
+
// Send a reply to a sync request.
|
394
|
+
static void reply_sync(ROMP_Session * session, int value) {
|
395
|
+
if(value == 0) {
|
396
|
+
ROMP_Message message = { ROMP_SYNC, 1, Qnil };
|
397
|
+
send_message(session, &message);
|
398
|
+
}
|
399
|
+
}
|
400
|
+
|
401
|
+
// ----------------------------------------------------------------------------
|
402
|
+
// Server functions
|
403
|
+
// ----------------------------------------------------------------------------
|
404
|
+
|
405
|
+
// We use this structure to pass data to our exception handler. This is done
|
406
|
+
// by casting a pointer to a Ruby VALUE... not 100% kosher, but it should work.
|
407
|
+
typedef struct {
|
408
|
+
ROMP_Session * session;
|
409
|
+
ROMP_Message * message;
|
410
|
+
VALUE obj;
|
411
|
+
int debug;
|
412
|
+
} Server_Info;
|
413
|
+
|
414
|
+
// Make a method call into a Ruby object.
|
415
|
+
static VALUE server_funcall(VALUE ruby_server_info) {
|
416
|
+
Server_Info * server_info = (Server_Info *)(ruby_server_info);
|
417
|
+
return ruby_send(server_info->obj, server_info->message->message_obj);
|
418
|
+
}
|
419
|
+
|
420
|
+
// Send a yield message to the client, indicating that it should call
|
421
|
+
// Kernel#yield with the message that is sent.
|
422
|
+
static VALUE server_send_yield(VALUE retval, VALUE ruby_server_info) {
|
423
|
+
Server_Info * server_info = (Server_Info *)(ruby_server_info);
|
424
|
+
|
425
|
+
server_info->message->message_type = ROMP_YIELD;
|
426
|
+
server_info->message->object_id = 0;
|
427
|
+
server_info->message->message_obj = retval;
|
428
|
+
send_message(server_info->session, server_info->message);
|
429
|
+
|
430
|
+
return Qnil;
|
431
|
+
}
|
432
|
+
|
433
|
+
// Send a return value to the client, indicating that it should return
|
434
|
+
// the message to the caller.
|
435
|
+
static VALUE server_send_retval(VALUE retval, VALUE ruby_server_info) {
|
436
|
+
Server_Info * server_info = (Server_Info *)(ruby_server_info);
|
437
|
+
|
438
|
+
server_info->message->message_type = ROMP_RETVAL;
|
439
|
+
server_info->message->object_id = 0;
|
440
|
+
server_info->message->message_obj = retval;
|
441
|
+
send_message(server_info->session, server_info->message);
|
442
|
+
|
443
|
+
return Qnil;
|
444
|
+
}
|
445
|
+
|
446
|
+
// Send an exception the client, indicating that it should raise an exception.
|
447
|
+
static VALUE server_exception(VALUE ruby_server_info, VALUE exc) {
|
448
|
+
Server_Info * server_info = (Server_Info *)(ruby_server_info);
|
449
|
+
VALUE caller = ruby_caller();
|
450
|
+
VALUE bt = ruby_exc_backtrace(exc);
|
451
|
+
|
452
|
+
server_info->message->message_type = ROMP_EXCEPTION;
|
453
|
+
server_info->message->object_id = 0;
|
454
|
+
server_info->message->message_obj = exc;
|
455
|
+
|
456
|
+
// Get rid of extraneous caller information to make debugging easier.
|
457
|
+
ruby_slice_bang(bt, RARRAY(bt)->len - RARRAY(caller)->len - 1, -1);
|
458
|
+
|
459
|
+
// If debugging is enabled, then print an exception.
|
460
|
+
if(server_info->debug) {
|
461
|
+
ruby_print_exception(exc);
|
462
|
+
}
|
463
|
+
|
464
|
+
send_message(server_info->session, server_info->message);
|
465
|
+
|
466
|
+
return Qnil;
|
467
|
+
}
|
468
|
+
|
469
|
+
// Proces a request from the client and send an appropriate reply.
|
470
|
+
static VALUE server_reply(VALUE ruby_server_info) {
|
471
|
+
Server_Info * server_info = (Server_Info *)(ruby_server_info);
|
472
|
+
VALUE retval;
|
473
|
+
int status;
|
474
|
+
|
475
|
+
server_info->obj = ruby_get_object(
|
476
|
+
server_info->obj,
|
477
|
+
server_info->message->object_id);
|
478
|
+
|
479
|
+
// TODO: The client should be able to pass a callback object to the server;
|
480
|
+
// msg_to_obj can create a Proxy_Object, but it needs a session to make
|
481
|
+
// calls over.
|
482
|
+
|
483
|
+
// Perform the appropriate action based on message type.
|
484
|
+
switch(server_info->message->message_type) {
|
485
|
+
case ROMP_ONEWAY_SYNC:
|
486
|
+
send_null_message(server_info->session);
|
487
|
+
// fallthrough
|
488
|
+
|
489
|
+
case ROMP_ONEWAY:
|
490
|
+
rb_protect(server_funcall, ruby_server_info, &status);
|
491
|
+
return Qnil;
|
492
|
+
|
493
|
+
case ROMP_REQUEST:
|
494
|
+
retval = ruby_send(
|
495
|
+
server_info->obj,
|
496
|
+
server_info->message->message_obj);
|
497
|
+
break;
|
498
|
+
|
499
|
+
case ROMP_REQUEST_BLOCK:
|
500
|
+
retval = rb_iterate(
|
501
|
+
server_funcall, ruby_server_info,
|
502
|
+
server_send_yield, ruby_server_info);
|
503
|
+
break;
|
504
|
+
|
505
|
+
case ROMP_SYNC:
|
506
|
+
reply_sync(
|
507
|
+
server_info->session,
|
508
|
+
server_info->message->object_id);
|
509
|
+
return Qnil;
|
510
|
+
|
511
|
+
default:
|
512
|
+
rb_raise(rb_eRuntimeError, "Bad session request");
|
513
|
+
}
|
514
|
+
|
515
|
+
server_send_retval(retval, ruby_server_info);
|
516
|
+
|
517
|
+
return Qnil;
|
518
|
+
}
|
519
|
+
|
520
|
+
// The main server loop. Wait for a message from the client, route the
|
521
|
+
// message to the appropriate object, send a response and repeat.
|
522
|
+
static void server_loop(ROMP_Session * session, VALUE resolve_server, int dbg) {
|
523
|
+
ROMP_Message message;
|
524
|
+
Server_Info server_info = { session, &message, resolve_server, dbg };
|
525
|
+
VALUE ruby_server_info = (VALUE)(&server_info);
|
526
|
+
|
527
|
+
while(!session_finished(session)) {
|
528
|
+
get_message(session, &message);
|
529
|
+
rb_rescue2(
|
530
|
+
server_reply, ruby_server_info,
|
531
|
+
server_exception, ruby_server_info, rb_eException, 0);
|
532
|
+
server_info.obj = resolve_server;
|
533
|
+
}
|
534
|
+
}
|
535
|
+
|
536
|
+
// ----------------------------------------------------------------------------
|
537
|
+
// Client functions
|
538
|
+
// ----------------------------------------------------------------------------
|
539
|
+
|
540
|
+
// We use this structure to pass data to our client functions by casting it
|
541
|
+
// to a Ruby VALUE (see above note with Server_Info).
|
542
|
+
typedef struct {
|
543
|
+
ROMP_Session * session;
|
544
|
+
VALUE ruby_session;
|
545
|
+
OBJECT_ID_T object_id;
|
546
|
+
VALUE message;
|
547
|
+
VALUE mutex;
|
548
|
+
} Proxy_Object;
|
549
|
+
|
550
|
+
// Send a request to the server, wait for a response, and perform an action
|
551
|
+
// based on what that response was. This is not thread-safe, so the caller
|
552
|
+
// should perform any necessary locking
|
553
|
+
static VALUE client_request(VALUE ruby_proxy_object) {
|
554
|
+
Proxy_Object * obj = (Proxy_Object *)(ruby_proxy_object);
|
555
|
+
ROMP_Message msg = {
|
556
|
+
rb_block_given_p() ? ROMP_REQUEST_BLOCK : ROMP_REQUEST,
|
557
|
+
obj->object_id,
|
558
|
+
obj->message
|
559
|
+
};
|
560
|
+
send_message(obj->session, &msg);
|
561
|
+
|
562
|
+
for(;;) {
|
563
|
+
get_message(obj->session, &msg);
|
564
|
+
switch(msg.message_type) {
|
565
|
+
case ROMP_RETVAL:
|
566
|
+
return msg_to_obj(msg.message_obj, obj->ruby_session, obj->mutex);
|
567
|
+
break;
|
568
|
+
case ROMP_YIELD:
|
569
|
+
rb_yield(msg_to_obj(msg.message_obj, obj->ruby_session, obj->mutex));
|
570
|
+
break;
|
571
|
+
case ROMP_EXCEPTION: {
|
572
|
+
ruby_raise(
|
573
|
+
msg.message_obj,
|
574
|
+
ruby_exc_message(msg.message_obj),
|
575
|
+
rb_ary_concat(ruby_exc_backtrace(msg.message_obj), ruby_caller())
|
576
|
+
);
|
577
|
+
break;
|
578
|
+
}
|
579
|
+
case ROMP_SYNC:
|
580
|
+
reply_sync(obj->session, NUM2INT(msg.message_obj));
|
581
|
+
break;
|
582
|
+
default:
|
583
|
+
rb_raise(rb_eRuntimeError, "Invalid msg type received");
|
584
|
+
}
|
585
|
+
}
|
586
|
+
}
|
587
|
+
|
588
|
+
// Send a oneway message to the server. This is not thread-safe, so the
|
589
|
+
// caller should perform any necessary locking.
|
590
|
+
static VALUE client_oneway(VALUE ruby_proxy_object) {
|
591
|
+
Proxy_Object * obj = (Proxy_Object *)(ruby_proxy_object);
|
592
|
+
ROMP_Message msg = {
|
593
|
+
ROMP_ONEWAY,
|
594
|
+
obj->object_id,
|
595
|
+
obj->message
|
596
|
+
};
|
597
|
+
send_message(obj->session, &msg);
|
598
|
+
return Qnil;
|
599
|
+
}
|
600
|
+
|
601
|
+
// Send a oneway message to the server and request a message in response.
|
602
|
+
// This is not thread-safe, so the caller should perform any necessary
|
603
|
+
// locking.
|
604
|
+
static VALUE client_oneway_sync(VALUE ruby_proxy_object) {
|
605
|
+
Proxy_Object * obj = (Proxy_Object *)(ruby_proxy_object);
|
606
|
+
ROMP_Message msg = {
|
607
|
+
ROMP_ONEWAY_SYNC,
|
608
|
+
obj->object_id,
|
609
|
+
obj->message
|
610
|
+
};
|
611
|
+
send_message(obj->session, &msg);
|
612
|
+
get_message(obj->session, &msg);
|
613
|
+
return Qnil;
|
614
|
+
}
|
615
|
+
|
616
|
+
// Synchronize with the server. This is not thread-safe, so the caller should
|
617
|
+
// perform any necessary locking.
|
618
|
+
static VALUE client_sync(VALUE ruby_proxy_object) {
|
619
|
+
Proxy_Object * obj = (Proxy_Object *)(ruby_proxy_object);
|
620
|
+
send_sync(obj->session);
|
621
|
+
wait_sync(obj->session);
|
622
|
+
return Qnil;
|
623
|
+
}
|
624
|
+
|
625
|
+
// ----------------------------------------------------------------------------
|
626
|
+
// Ruby interface functions
|
627
|
+
// ----------------------------------------------------------------------------
|
628
|
+
|
629
|
+
static void ruby_session_mark(ROMP_Session * session) {
|
630
|
+
rb_gc_mark(session->io_object);
|
631
|
+
}
|
632
|
+
|
633
|
+
static VALUE ruby_session_new(VALUE self, VALUE io_object) {
|
634
|
+
ROMP_Session * session;
|
635
|
+
VALUE ruby_session;
|
636
|
+
OpenFile * openfile;
|
637
|
+
FILE * read_fp;
|
638
|
+
FILE * write_fp;
|
639
|
+
|
640
|
+
if(!rb_obj_is_kind_of(io_object, rb_cIO)) {
|
641
|
+
rb_raise(rb_eTypeError, "Expecting an IO object");
|
642
|
+
}
|
643
|
+
|
644
|
+
ruby_session = Data_Make_Struct(
|
645
|
+
rb_cSession,
|
646
|
+
ROMP_Session,
|
647
|
+
(RUBY_DATA_FUNC)(ruby_session_mark),
|
648
|
+
(RUBY_DATA_FUNC)(free),
|
649
|
+
session);
|
650
|
+
|
651
|
+
GetOpenFile(io_object, openfile);
|
652
|
+
read_fp = GetReadFile(openfile);
|
653
|
+
write_fp = GetWriteFile(openfile);
|
654
|
+
session->read_fd = fileno(read_fp);
|
655
|
+
session->write_fd = fileno(write_fp);
|
656
|
+
session->io_object = io_object;
|
657
|
+
session->nonblock = 0;
|
658
|
+
|
659
|
+
return ruby_session;
|
660
|
+
}
|
661
|
+
|
662
|
+
static VALUE ruby_set_nonblock(VALUE self, VALUE nonblock) {
|
663
|
+
ROMP_Session * session;
|
664
|
+
Data_Get_Struct(self, ROMP_Session, session);
|
665
|
+
if(nonblock == Qtrue) {
|
666
|
+
session->nonblock = 1;
|
667
|
+
} else if(nonblock == Qfalse) {
|
668
|
+
session->nonblock = 0;
|
669
|
+
} else {
|
670
|
+
rb_raise(rb_eTypeError, "Expecting a boolean");
|
671
|
+
}
|
672
|
+
return Qnil;
|
673
|
+
}
|
674
|
+
|
675
|
+
static void ruby_proxy_object_mark(Proxy_Object * proxy_object) {
|
676
|
+
rb_gc_mark(proxy_object->ruby_session);
|
677
|
+
rb_gc_mark(proxy_object->mutex);
|
678
|
+
}
|
679
|
+
|
680
|
+
static VALUE ruby_proxy_object_new(
|
681
|
+
VALUE self, VALUE ruby_session, VALUE ruby_mutex, VALUE ruby_object_id) {
|
682
|
+
ROMP_Session * session;
|
683
|
+
OBJECT_ID_T object_id = NUM2INT(ruby_object_id);
|
684
|
+
Proxy_Object * proxy_object;
|
685
|
+
VALUE ruby_proxy_object;
|
686
|
+
|
687
|
+
if(!rb_obj_is_kind_of(ruby_session, rb_cSession)) {
|
688
|
+
rb_raise(rb_eTypeError, "Expecting a session");
|
689
|
+
}
|
690
|
+
Data_Get_Struct(ruby_session, ROMP_Session, session);
|
691
|
+
|
692
|
+
ruby_proxy_object = Data_Make_Struct(
|
693
|
+
rb_cProxy_Object,
|
694
|
+
Proxy_Object,
|
695
|
+
(RUBY_DATA_FUNC)(ruby_proxy_object_mark),
|
696
|
+
(RUBY_DATA_FUNC)(free),
|
697
|
+
proxy_object);
|
698
|
+
proxy_object->session = session;
|
699
|
+
proxy_object->ruby_session = ruby_session;
|
700
|
+
proxy_object->mutex = ruby_mutex;
|
701
|
+
proxy_object->object_id = object_id;
|
702
|
+
|
703
|
+
return ruby_proxy_object;
|
704
|
+
}
|
705
|
+
|
706
|
+
static VALUE ruby_proxy_object_method_missing(VALUE self, VALUE message) {
|
707
|
+
Proxy_Object * proxy_object;
|
708
|
+
Data_Get_Struct(self, Proxy_Object, proxy_object);
|
709
|
+
|
710
|
+
proxy_object->message = message;
|
711
|
+
ruby_lock(proxy_object->mutex);
|
712
|
+
return rb_ensure(
|
713
|
+
client_request, (VALUE)(proxy_object),
|
714
|
+
ruby_unlock, proxy_object->mutex);
|
715
|
+
}
|
716
|
+
|
717
|
+
static VALUE ruby_proxy_object_oneway(VALUE self, VALUE message) {
|
718
|
+
Proxy_Object * proxy_object;
|
719
|
+
Data_Get_Struct(self, Proxy_Object, proxy_object);
|
720
|
+
|
721
|
+
proxy_object->message = message;
|
722
|
+
ruby_lock(proxy_object->mutex);
|
723
|
+
rb_ensure(
|
724
|
+
client_oneway, (VALUE)(proxy_object),
|
725
|
+
ruby_unlock, proxy_object->mutex);
|
726
|
+
return Qnil;
|
727
|
+
}
|
728
|
+
|
729
|
+
static VALUE ruby_proxy_object_oneway_sync(VALUE self, VALUE message) {
|
730
|
+
Proxy_Object * proxy_object;
|
731
|
+
Data_Get_Struct(self, Proxy_Object, proxy_object);
|
732
|
+
|
733
|
+
proxy_object->message = message;
|
734
|
+
ruby_lock(proxy_object->mutex);
|
735
|
+
rb_ensure(
|
736
|
+
client_oneway_sync, (VALUE)(proxy_object),
|
737
|
+
ruby_unlock, proxy_object->mutex);
|
738
|
+
return Qnil;
|
739
|
+
}
|
740
|
+
|
741
|
+
static VALUE ruby_proxy_object_sync(VALUE self) {
|
742
|
+
Proxy_Object * proxy_object;
|
743
|
+
Data_Get_Struct(self, Proxy_Object, proxy_object);
|
744
|
+
|
745
|
+
ruby_lock(proxy_object->mutex);
|
746
|
+
rb_ensure(
|
747
|
+
client_sync, (VALUE)(proxy_object),
|
748
|
+
ruby_unlock, proxy_object->mutex);
|
749
|
+
return Qnil;
|
750
|
+
}
|
751
|
+
|
752
|
+
static VALUE ruby_server_loop(VALUE self, VALUE ruby_session) {
|
753
|
+
ROMP_Session * session;
|
754
|
+
VALUE resolve_server;
|
755
|
+
VALUE ruby_debug;
|
756
|
+
int debug;
|
757
|
+
|
758
|
+
if(!rb_obj_is_kind_of(ruby_session, rb_cSession)) {
|
759
|
+
rb_raise(rb_eTypeError, "Excpecting a session");
|
760
|
+
}
|
761
|
+
Data_Get_Struct(ruby_session, ROMP_Session, session);
|
762
|
+
|
763
|
+
resolve_server = rb_iv_get(self, "@resolve_server");
|
764
|
+
|
765
|
+
ruby_debug = rb_iv_get(self, "@debug");
|
766
|
+
debug = (ruby_debug != Qfalse) && !NIL_P(ruby_debug);
|
767
|
+
server_loop(session, resolve_server, debug);
|
768
|
+
return Qnil;
|
769
|
+
}
|
770
|
+
|
771
|
+
// Given a message, convert it into an object that can be returned. This
|
772
|
+
// function really only checks to see if an Object_Reference has been returned
|
773
|
+
// from the server, and creates a new Proxy_Object if this is the case.
|
774
|
+
// Otherwise, the original object is returned to the client.
|
775
|
+
static VALUE msg_to_obj(VALUE message, VALUE session, VALUE mutex) {
|
776
|
+
if(CLASS_OF(message) == rb_cObject_Reference) {
|
777
|
+
return ruby_proxy_object_new(
|
778
|
+
rb_cProxy_Object,
|
779
|
+
session,
|
780
|
+
mutex,
|
781
|
+
rb_funcall(message, id_object_id, 0));
|
782
|
+
} else {
|
783
|
+
return message;
|
784
|
+
}
|
785
|
+
}
|
786
|
+
|
787
|
+
void Init_romp_helper() {
|
788
|
+
init_globals();
|
789
|
+
|
790
|
+
rb_mROMP = rb_define_module("ROMP");
|
791
|
+
rb_cSession = rb_define_class_under(rb_mROMP, "Session", rb_cObject);
|
792
|
+
|
793
|
+
rb_define_const(rb_cSession, "REQUEST", INT2NUM(ROMP_REQUEST));
|
794
|
+
rb_define_const(rb_cSession, "REQUEST_BLOCK", INT2NUM(ROMP_REQUEST_BLOCK));
|
795
|
+
rb_define_const(rb_cSession, "ONEWAY", INT2NUM(ROMP_ONEWAY));
|
796
|
+
rb_define_const(rb_cSession, "ONEWAY_SYNC", INT2NUM(ROMP_ONEWAY_SYNC));
|
797
|
+
rb_define_const(rb_cSession, "RETVAL", INT2NUM(ROMP_RETVAL));
|
798
|
+
rb_define_const(rb_cSession, "EXCEPTION", INT2NUM(ROMP_EXCEPTION));
|
799
|
+
rb_define_const(rb_cSession, "YIELD", INT2NUM(ROMP_YIELD));
|
800
|
+
rb_define_const(rb_cSession, "SYNC", INT2NUM(ROMP_SYNC));
|
801
|
+
rb_define_const(rb_cSession, "NULL_MSG", INT2NUM(ROMP_NULL_MSG));
|
802
|
+
rb_define_const(rb_cSession, "MSG_START", INT2NUM(ROMP_MSG_START));
|
803
|
+
rb_define_const(rb_cSession, "MAX_ID", INT2NUM(ROMP_MAX_ID));
|
804
|
+
rb_define_const(rb_cSession, "MAX_MSG_TYPE", INT2NUM(ROMP_MAX_MSG_TYPE));
|
805
|
+
|
806
|
+
rb_define_singleton_method(rb_cSession, "new", ruby_session_new, 1);
|
807
|
+
rb_define_method(rb_cSession, "set_nonblock", ruby_set_nonblock, 1);
|
808
|
+
|
809
|
+
rb_cProxy_Object = rb_define_class_under(rb_mROMP, "Proxy_Object", rb_cObject);
|
810
|
+
rb_define_singleton_method(rb_cProxy_Object, "new", ruby_proxy_object_new, 3);
|
811
|
+
rb_define_method(rb_cProxy_Object, "method_missing", ruby_proxy_object_method_missing, -2);
|
812
|
+
rb_define_method(rb_cProxy_Object, "oneway", ruby_proxy_object_oneway, -2);
|
813
|
+
rb_define_method(rb_cProxy_Object, "oneway_sync", ruby_proxy_object_oneway_sync, -2);
|
814
|
+
rb_define_method(rb_cProxy_Object, "sync", ruby_proxy_object_sync, 0);
|
815
|
+
|
816
|
+
rb_cServer = rb_define_class_under(rb_mROMP, "Server", rb_cObject);
|
817
|
+
rb_define_method(rb_cServer, "server_loop", ruby_server_loop, 1);
|
818
|
+
|
819
|
+
rb_cObject_Reference = rb_define_class_under(rb_mROMP, "Object_Reference", rb_cObject);
|
820
|
+
|
821
|
+
id_object_id = rb_intern("object_id");
|
822
|
+
}
|