sysvmq 0.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.
- checksums.yaml +7 -0
- data/.gitignore +18 -0
- data/.travis.yml +5 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +26 -0
- data/Rakefile +15 -0
- data/bin/coderay +16 -0
- data/bin/pry +16 -0
- data/bin/rake +16 -0
- data/ext/extconf.rb +5 -0
- data/ext/sysvmq.c +358 -0
- data/sysvmq.gemspec +24 -0
- data/test/sysv_mq_test.rb +114 -0
- data/test/test_helper.rb +3 -0
- metadata +106 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: d84404b90167dfecfe52eae482a6284f2af9bbc1
|
4
|
+
data.tar.gz: c4b84de9d1fa95c093f953797cd8d794e52e69bb
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1ad89cfac4699a9f283024e0a75c2587200196b8e91410c5cdb2e10e70df3612d2887ba5ce92c9a9efc81a2dc76fb757446444ae3038f72cf79b0a1d3a50ebcd
|
7
|
+
data.tar.gz: acdf82f69e86f30542da3f98917c8b2ee75f859222bd7f41905d0d8a749d85070801d6fdb5e663dd0cd3add051b96145cadfa4d6cc0761f50df4aff98038d33e
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Simon Eskildsen
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# sysvmq
|
2
|
+
|
3
|
+
`sysvmq` is a C extension that wraps SysV IPC Message Queues. Only compatible
|
4
|
+
with MRI 2.0 and 2.1 currently.
|
5
|
+
|
6
|
+
```ruby
|
7
|
+
# Create a message queue with a 1024 byte buffer.
|
8
|
+
require 'sysvmq'
|
9
|
+
mq = SysVMQ.new(0xDEADC0DE, 1024, SysVMQ::IPC_CREAT | 0666)
|
10
|
+
|
11
|
+
mq.send "Hellø Wårld!"
|
12
|
+
assert_equal 1, mq.stats[:count]
|
13
|
+
|
14
|
+
assert_equal "Hellø Wårld!", mq.receive
|
15
|
+
|
16
|
+
# Raise an exception instead of blocking until a message is available
|
17
|
+
mq.receive(0, SysVMQ::IPC_NOWAIT)
|
18
|
+
|
19
|
+
ensure
|
20
|
+
# Delete queue
|
21
|
+
mq.destroy
|
22
|
+
```
|
23
|
+
|
24
|
+
## Installation
|
25
|
+
|
26
|
+
gem 'sysv-mq', github: "Sirupsen/sysvmq" # until published to rubygems
|
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
2
|
+
require 'rake/testtask'
|
3
|
+
require 'rake/extensiontask'
|
4
|
+
|
5
|
+
Rake::ExtensionTask.new 'sysvmq' do |ext|
|
6
|
+
ext.ext_dir = 'ext/'
|
7
|
+
end
|
8
|
+
|
9
|
+
task :default => [:compile, :test]
|
10
|
+
|
11
|
+
Rake::TestTask.new do |t|
|
12
|
+
t.libs << "test"
|
13
|
+
t.test_files = FileList['test/*_test.rb']
|
14
|
+
t.verbose = true
|
15
|
+
end
|
data/bin/coderay
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This file was generated by Bundler.
|
4
|
+
#
|
5
|
+
# The application 'coderay' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
require 'pathname'
|
10
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
|
11
|
+
Pathname.new(__FILE__).realpath)
|
12
|
+
|
13
|
+
require 'rubygems'
|
14
|
+
require 'bundler/setup'
|
15
|
+
|
16
|
+
load Gem.bin_path('coderay', 'coderay')
|
data/bin/pry
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This file was generated by Bundler.
|
4
|
+
#
|
5
|
+
# The application 'pry' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
require 'pathname'
|
10
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
|
11
|
+
Pathname.new(__FILE__).realpath)
|
12
|
+
|
13
|
+
require 'rubygems'
|
14
|
+
require 'bundler/setup'
|
15
|
+
|
16
|
+
load Gem.bin_path('pry', 'pry')
|
data/bin/rake
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
#
|
3
|
+
# This file was generated by Bundler.
|
4
|
+
#
|
5
|
+
# The application 'rake' is installed as part of a gem, and
|
6
|
+
# this file is here to facilitate running it.
|
7
|
+
#
|
8
|
+
|
9
|
+
require 'pathname'
|
10
|
+
ENV['BUNDLE_GEMFILE'] ||= File.expand_path("../../Gemfile",
|
11
|
+
Pathname.new(__FILE__).realpath)
|
12
|
+
|
13
|
+
require 'rubygems'
|
14
|
+
require 'bundler/setup'
|
15
|
+
|
16
|
+
load Gem.bin_path('rake', 'rake')
|
data/ext/extconf.rb
ADDED
data/ext/sysvmq.c
ADDED
@@ -0,0 +1,358 @@
|
|
1
|
+
#include <ruby.h>
|
2
|
+
#include <ruby/util.h>
|
3
|
+
#include <ruby/thread.h>
|
4
|
+
#include <ruby/io.h>
|
5
|
+
|
6
|
+
#include <sys/types.h>
|
7
|
+
#include <sys/ipc.h>
|
8
|
+
#include <sys/msg.h>
|
9
|
+
|
10
|
+
#include <stdlib.h>
|
11
|
+
#include <errno.h>
|
12
|
+
#include <stdio.h>
|
13
|
+
#include <string.h>
|
14
|
+
|
15
|
+
// This is the buffer passed to msg{rcv,snd,ctl}(2)
|
16
|
+
typedef struct {
|
17
|
+
long mtype;
|
18
|
+
char mtext[];
|
19
|
+
}
|
20
|
+
sysvmq_msgbuf_t;
|
21
|
+
|
22
|
+
// Used for rb_thread_wait_for to signal time between EINTR tries
|
23
|
+
struct timeval polling_interval;
|
24
|
+
|
25
|
+
// C struct linked to all Ruby objects
|
26
|
+
typedef struct {
|
27
|
+
key_t key;
|
28
|
+
int id;
|
29
|
+
size_t buffer_size;
|
30
|
+
sysvmq_msgbuf_t* msgbuf;
|
31
|
+
}
|
32
|
+
sysvmq_t;
|
33
|
+
|
34
|
+
static void
|
35
|
+
sysvmq_mark(void *ptr)
|
36
|
+
{
|
37
|
+
// noop, no Ruby objects in the internal struct currently
|
38
|
+
}
|
39
|
+
|
40
|
+
static void
|
41
|
+
sysvmq_free(void *ptr)
|
42
|
+
{
|
43
|
+
sysvmq_t* sysv = ptr;
|
44
|
+
xfree(sysv->msgbuf);
|
45
|
+
xfree(sysv);
|
46
|
+
}
|
47
|
+
|
48
|
+
static size_t
|
49
|
+
sysvmq_memsize(const void* ptr)
|
50
|
+
{
|
51
|
+
const sysvmq_t* sysv = ptr;
|
52
|
+
return sizeof(sysvmq_t) + sizeof(char) * sysv->buffer_size;
|
53
|
+
}
|
54
|
+
|
55
|
+
static const rb_data_type_t
|
56
|
+
sysvmq_type = {
|
57
|
+
"sysvmq_type",
|
58
|
+
{
|
59
|
+
sysvmq_mark,
|
60
|
+
sysvmq_free,
|
61
|
+
sysvmq_memsize
|
62
|
+
}
|
63
|
+
};
|
64
|
+
|
65
|
+
static VALUE
|
66
|
+
sysvmq_alloc(VALUE klass)
|
67
|
+
{
|
68
|
+
sysvmq_t* sysv;
|
69
|
+
VALUE obj = TypedData_Make_Struct(klass, sysvmq_t, &sysvmq_type, sysv);
|
70
|
+
|
71
|
+
sysv->key = 0;
|
72
|
+
sysv->id = -1;
|
73
|
+
sysv->buffer_size = 0;
|
74
|
+
sysv->msgbuf = memset(sysv, 0, sizeof(sysvmq_t));
|
75
|
+
|
76
|
+
return obj;
|
77
|
+
}
|
78
|
+
|
79
|
+
// int msgctl(int msqid, int cmd, struct msqid_ds *buf);
|
80
|
+
// http://man7.org/linux/man-pages/man2/msgctl.2.html
|
81
|
+
//
|
82
|
+
// Controls the queue with IPC_SET, IPC_INFO and IPC_RMID via msgctl(2). When no
|
83
|
+
// argument is passed, it'll return the information about the queue from
|
84
|
+
// IPC_INFO.
|
85
|
+
//
|
86
|
+
// TODO: IPC_SET is currently not supported.
|
87
|
+
static VALUE
|
88
|
+
sysvmq_stats(int argc, VALUE *argv, VALUE self)
|
89
|
+
{
|
90
|
+
struct msqid_ds info;
|
91
|
+
VALUE info_hash;
|
92
|
+
VALUE cmd;
|
93
|
+
sysvmq_t* sysv;
|
94
|
+
|
95
|
+
// Optional argument handling
|
96
|
+
if (argc > 1) {
|
97
|
+
rb_raise(rb_eArgError, "Wrong number of arguments (0..1)");
|
98
|
+
}
|
99
|
+
|
100
|
+
// Default to IPC_STAT
|
101
|
+
cmd = argc == 1 ? argv[0] : INT2FIX(IPC_STAT);
|
102
|
+
|
103
|
+
TypedData_Get_Struct(self, sysvmq_t, &sysvmq_type, sysv);
|
104
|
+
|
105
|
+
// TODO: Does FIX2INT actually perform this check already?
|
106
|
+
Check_Type(cmd, T_FIXNUM);
|
107
|
+
|
108
|
+
while (msgctl(sysv->id, FIX2INT(cmd), &info) < 0) {
|
109
|
+
if (errno == EINTR) {
|
110
|
+
rb_thread_wait_for(polling_interval);
|
111
|
+
continue;
|
112
|
+
}
|
113
|
+
rb_sys_fail("Failed executing msgctl(2) command.");
|
114
|
+
}
|
115
|
+
|
116
|
+
// Map values from struct to a hash
|
117
|
+
// TODO: Add all the fields
|
118
|
+
// TODO: They are probably not ints..
|
119
|
+
info_hash = rb_hash_new();
|
120
|
+
rb_hash_aset(info_hash, ID2SYM(rb_intern("count")), INT2FIX(info.msg_qnum));
|
121
|
+
rb_hash_aset(info_hash, ID2SYM(rb_intern("maximum_size")), INT2FIX(info.msg_qbytes));
|
122
|
+
|
123
|
+
// TODO: Can probably make a better checker here for whether the struct
|
124
|
+
// actually has the member.
|
125
|
+
// TODO: BSD support?
|
126
|
+
#ifdef __linux__
|
127
|
+
rb_hash_aset(info_hash, ID2SYM(rb_intern("size")), INT2FIX(info.__msg_cbytes));
|
128
|
+
#elif __APPLE__
|
129
|
+
rb_hash_aset(info_hash, ID2SYM(rb_intern("size")), INT2FIX(info.msg_cbytes));
|
130
|
+
#endif
|
131
|
+
|
132
|
+
return info_hash;
|
133
|
+
}
|
134
|
+
|
135
|
+
// Proxies a call with IPC_RMID to `sysvmq_stats` to remove the queue.
|
136
|
+
static VALUE
|
137
|
+
sysvmq_destroy(VALUE self)
|
138
|
+
{
|
139
|
+
VALUE argv[1];
|
140
|
+
argv[0] = INT2FIX(IPC_RMID);
|
141
|
+
return sysvmq_stats(1, argv, self);
|
142
|
+
}
|
143
|
+
|
144
|
+
// This is used for passing values between the `maybe_blocking` function and the
|
145
|
+
// Ruby function. There's definitely a better way.
|
146
|
+
typedef struct {
|
147
|
+
size_t size;
|
148
|
+
int flags;
|
149
|
+
int type;
|
150
|
+
size_t msg_size; // TODO: typelol
|
151
|
+
sysvmq_t* sysv;
|
152
|
+
|
153
|
+
int retval;
|
154
|
+
}
|
155
|
+
sysvmq_blocking_call_t;
|
156
|
+
|
157
|
+
// Blocking call to msgsnd(2) (see sysvmq_send). This is to be called without
|
158
|
+
// the GVL, and must therefore not use any Ruby functions.
|
159
|
+
static void*
|
160
|
+
sysvmq_maybe_blocking_receive(void *args)
|
161
|
+
{
|
162
|
+
sysvmq_blocking_call_t* arguments = (sysvmq_blocking_call_t*) args;
|
163
|
+
arguments->retval = msgrcv(arguments->sysv->id, arguments->sysv->msgbuf, arguments->sysv->buffer_size, arguments->type, arguments->flags);
|
164
|
+
|
165
|
+
return NULL;
|
166
|
+
}
|
167
|
+
|
168
|
+
// ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
|
169
|
+
// http://man7.org/linux/man-pages/man2/msgsnd.2.html
|
170
|
+
//
|
171
|
+
// Receive a message from the message queue.
|
172
|
+
VALUE
|
173
|
+
sysvmq_receive(int argc, VALUE *argv, VALUE self)
|
174
|
+
{
|
175
|
+
VALUE type = INT2FIX(0);
|
176
|
+
VALUE flags = INT2FIX(0);
|
177
|
+
sysvmq_t* sysv;
|
178
|
+
sysvmq_blocking_call_t blocking;
|
179
|
+
|
180
|
+
if (argc > 2) {
|
181
|
+
rb_raise(rb_eArgError, "Wrong number of arguments (0..2)");
|
182
|
+
}
|
183
|
+
|
184
|
+
if (argc >= 1) type = argv[0];
|
185
|
+
if (argc == 2) flags = argv[1];
|
186
|
+
|
187
|
+
TypedData_Get_Struct(self, sysvmq_t, &sysvmq_type, sysv);
|
188
|
+
|
189
|
+
Check_Type(type, T_FIXNUM);
|
190
|
+
Check_Type(flags, T_FIXNUM);
|
191
|
+
|
192
|
+
// Attach blocking call parameters to the struct passed to the blocking
|
193
|
+
// function wrapper.
|
194
|
+
blocking.flags = FIX2INT(flags);
|
195
|
+
blocking.type = FIX2INT(type);
|
196
|
+
blocking.sysv = sysv;
|
197
|
+
|
198
|
+
// msgrcv(2) can block sending a message, if IPC_NOWAIT is not passed.
|
199
|
+
// We unlock the GVL waiting for the call so other threads (e.g. signal
|
200
|
+
// handling) can continue to work. Sets `msg_size` on `blocking` with the size
|
201
|
+
// of the message returned.
|
202
|
+
while (rb_thread_call_without_gvl2(sysvmq_maybe_blocking_receive, &blocking, RUBY_UBF_IO, NULL) == NULL
|
203
|
+
&& blocking.retval < 0) {
|
204
|
+
if (errno == EINTR) {
|
205
|
+
rb_thread_check_ints();
|
206
|
+
continue;
|
207
|
+
}
|
208
|
+
|
209
|
+
rb_sys_fail("Failed receiving message from queue");
|
210
|
+
}
|
211
|
+
|
212
|
+
// Reencode with default external encoding
|
213
|
+
return rb_enc_str_new(sysv->msgbuf->mtext, blocking.retval, rb_default_external_encoding());
|
214
|
+
}
|
215
|
+
|
216
|
+
// Blocking call to msgsnd(2) (see sysvmq_send). This is to be called without
|
217
|
+
// the GVL, and must therefore not use any Ruby functions.
|
218
|
+
static void*
|
219
|
+
sysvmq_maybe_blocking_send(void *data)
|
220
|
+
{
|
221
|
+
sysvmq_blocking_call_t* arguments = (sysvmq_blocking_call_t*) data;
|
222
|
+
|
223
|
+
arguments->retval = msgsnd(arguments->sysv->id, arguments->sysv->msgbuf, arguments->size, arguments->flags);
|
224
|
+
|
225
|
+
return NULL;
|
226
|
+
}
|
227
|
+
|
228
|
+
// int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
|
229
|
+
// http://man7.org/linux/man-pages/man2/msgsnd.2.html
|
230
|
+
//
|
231
|
+
// Sends a message to the message queue.
|
232
|
+
VALUE
|
233
|
+
sysvmq_send(int argc, VALUE *argv, VALUE self)
|
234
|
+
{
|
235
|
+
VALUE message;
|
236
|
+
VALUE priority = INT2FIX(1);
|
237
|
+
VALUE flags = INT2FIX(0);
|
238
|
+
sysvmq_blocking_call_t blocking;
|
239
|
+
sysvmq_t* sysv;
|
240
|
+
|
241
|
+
if (argc > 3 || argc == 0) {
|
242
|
+
rb_raise(rb_eArgError, "Wrong number of arguments (1..3)");
|
243
|
+
}
|
244
|
+
|
245
|
+
message = argv[0];
|
246
|
+
if (argc >= 2) priority = argv[1];
|
247
|
+
if (argc == 3) flags = argv[2];
|
248
|
+
|
249
|
+
TypedData_Get_Struct(self, sysvmq_t, &sysvmq_type, sysv);
|
250
|
+
|
251
|
+
Check_Type(flags, T_FIXNUM);
|
252
|
+
Check_Type(priority, T_FIXNUM);
|
253
|
+
// TODO: Call to_s on message if it responds to
|
254
|
+
|
255
|
+
// Attach blocking call parameters to the struct passed to the blocking
|
256
|
+
// function wrapper.
|
257
|
+
blocking.flags = FIX2INT(flags);
|
258
|
+
blocking.size = RSTRING_LEN(message);
|
259
|
+
blocking.sysv = sysv;
|
260
|
+
|
261
|
+
// The buffer can be obtained from `sysvmq_maybe_blocking_send`, instead of
|
262
|
+
// passing it, set it directly on the instance struct.
|
263
|
+
sysv->msgbuf->mtype = FIX2INT(priority);
|
264
|
+
|
265
|
+
if (blocking.size > sysv->buffer_size) {
|
266
|
+
rb_raise(rb_eArgError, "Size of message is bigger than buffer size.");
|
267
|
+
}
|
268
|
+
|
269
|
+
// TODO: Can a string copy be avoided?
|
270
|
+
strncpy(sysv->msgbuf->mtext, StringValueCStr(message), blocking.size);
|
271
|
+
|
272
|
+
// msgsnd(2) can block waiting for a message, if IPC_NOWAIT is not passed.
|
273
|
+
// We unlock the GVL waiting for the call so other threads (e.g. signal
|
274
|
+
// handling) can continue to work.
|
275
|
+
while (rb_thread_call_without_gvl2(sysvmq_maybe_blocking_send, &blocking, RUBY_UBF_IO, NULL) == NULL
|
276
|
+
&& blocking.retval < 0) {
|
277
|
+
if (errno == EINTR) {
|
278
|
+
rb_thread_check_ints();
|
279
|
+
continue;
|
280
|
+
}
|
281
|
+
|
282
|
+
rb_sys_fail("Failed sending message to queue");
|
283
|
+
}
|
284
|
+
|
285
|
+
return message;
|
286
|
+
}
|
287
|
+
|
288
|
+
// int msgget(key_t key, int msgflg);
|
289
|
+
// http://man7.org/linux/man-pages/man2/msgget.2.html
|
290
|
+
//
|
291
|
+
// Instead of calling `msgget` method directly to get the `msgid` (the return
|
292
|
+
// value of `msgget`) from Rubyland and pass it around, it's stored internally
|
293
|
+
// in a struct only directly accessible from C-land. It's passed to all the
|
294
|
+
// other calls that require a `msgid`, for convienence and to share the buffer.
|
295
|
+
VALUE
|
296
|
+
sysvmq_initialize(VALUE self, VALUE key, VALUE buffer_size, VALUE flags)
|
297
|
+
{
|
298
|
+
sysvmq_t* sysv;
|
299
|
+
size_t msgbuf_size;
|
300
|
+
|
301
|
+
// TODO: Also support string keys, so you can pass '0xDEADC0DE'
|
302
|
+
Check_Type(key, T_FIXNUM);
|
303
|
+
Check_Type(flags, T_FIXNUM);
|
304
|
+
Check_Type(buffer_size, T_FIXNUM);
|
305
|
+
|
306
|
+
TypedData_Get_Struct(self, sysvmq_t, &sysvmq_type, sysv);
|
307
|
+
|
308
|
+
// TODO: This probably doesn't hold on all platforms.
|
309
|
+
sysv->key = FIX2LONG(key);
|
310
|
+
|
311
|
+
while ((sysv->id = msgget(sysv->key, FIX2INT(flags))) < 0) {
|
312
|
+
if (errno == EINTR) {
|
313
|
+
rb_thread_wait_for(polling_interval); // TODO: Really necessary here?
|
314
|
+
continue;
|
315
|
+
}
|
316
|
+
rb_sys_fail("Failed opening the message queue.");
|
317
|
+
}
|
318
|
+
|
319
|
+
// Allocate the msgbuf buffer once for the instance, to not allocate a buffer
|
320
|
+
// for each message sent. This makes SysVMQ not thread-safe (requiring a
|
321
|
+
// buffer for each thread), but is a reasonable trade-off for now for the
|
322
|
+
// performance.
|
323
|
+
sysv->buffer_size = FIX2INT(buffer_size);
|
324
|
+
msgbuf_size = sysv->buffer_size * sizeof(char) + sizeof(long);
|
325
|
+
|
326
|
+
// Note that this is a zero-length array, so we size the struct to size of the
|
327
|
+
// header (long, the mtype) and then the rest of the space for message buffer.
|
328
|
+
sysv->msgbuf = (sysvmq_msgbuf_t*) xmalloc(msgbuf_size);
|
329
|
+
|
330
|
+
return self;
|
331
|
+
}
|
332
|
+
|
333
|
+
void Init_sysvmq()
|
334
|
+
{
|
335
|
+
VALUE sysvmq = rb_define_class("SysVMQ", rb_cObject);
|
336
|
+
|
337
|
+
// Waiting between blocking calls that have been interrupted outside the GVL,
|
338
|
+
// this is to allow time for signal handlers to process signals.
|
339
|
+
polling_interval.tv_sec = 0;
|
340
|
+
polling_interval.tv_usec = 5;
|
341
|
+
|
342
|
+
// Define platform specific constants from headers
|
343
|
+
rb_define_const(sysvmq, "IPC_CREAT", INT2NUM(IPC_CREAT));
|
344
|
+
rb_define_const(sysvmq, "IPC_EXCL", INT2NUM(IPC_EXCL));
|
345
|
+
rb_define_const(sysvmq, "IPC_NOWAIT", INT2NUM(IPC_NOWAIT));
|
346
|
+
rb_define_const(sysvmq, "IPC_RMID", INT2NUM(IPC_RMID));
|
347
|
+
rb_define_const(sysvmq, "IPC_SET", INT2NUM(IPC_SET));
|
348
|
+
rb_define_const(sysvmq, "IPC_STAT", INT2NUM(IPC_STAT));
|
349
|
+
rb_define_const(sysvmq, "IPC_INFO", INT2NUM(IPC_INFO));
|
350
|
+
|
351
|
+
// Define the SysVMQ class and its methods
|
352
|
+
rb_define_alloc_func(sysvmq, sysvmq_alloc);
|
353
|
+
rb_define_method(sysvmq, "initialize", sysvmq_initialize, 3);
|
354
|
+
rb_define_method(sysvmq, "send", sysvmq_send, -1);
|
355
|
+
rb_define_method(sysvmq, "receive", sysvmq_receive, -1);
|
356
|
+
rb_define_method(sysvmq, "stats", sysvmq_stats, -1);
|
357
|
+
rb_define_method(sysvmq, "destroy", sysvmq_destroy, 0);
|
358
|
+
}
|
data/sysvmq.gemspec
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
|
5
|
+
Gem::Specification.new do |spec|
|
6
|
+
spec.name = "sysvmq"
|
7
|
+
spec.version = '0.0.1'
|
8
|
+
spec.authors = ["Simon Eskildsen"]
|
9
|
+
spec.email = ["sirup@sirupsen.com"]
|
10
|
+
spec.summary = %q{Ruby wrapper for SysV Message Queues}
|
11
|
+
spec.description = %q{Ruby wrapper for SysV Message Queues}
|
12
|
+
spec.homepage = "https://github.com/Sirupsen/sysvmq"
|
13
|
+
spec.license = "MIT"
|
14
|
+
|
15
|
+
spec.files = `git ls-files`.split($/)
|
16
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
17
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
18
|
+
spec.require_paths = ["lib"]
|
19
|
+
spec.extensions = ["ext/extconf.rb"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "rake-compiler", "~> 0.9"
|
24
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
require_relative 'test_helper'
|
2
|
+
|
3
|
+
class SysVMQTest < MiniTest::Unit::TestCase
|
4
|
+
def setup
|
5
|
+
@size = 1024
|
6
|
+
@mq = SysVMQ.new(0xDEADC0DE, @size, SysVMQ::IPC_CREAT | 0666)
|
7
|
+
end
|
8
|
+
|
9
|
+
def teardown
|
10
|
+
@mq.destroy
|
11
|
+
end
|
12
|
+
|
13
|
+
def test_send_message
|
14
|
+
@mq.send("Hello world")
|
15
|
+
end
|
16
|
+
|
17
|
+
def test_send_and_receive_message
|
18
|
+
message = "Hello World"
|
19
|
+
@mq.send message
|
20
|
+
assert_equal message, @mq.receive
|
21
|
+
end
|
22
|
+
|
23
|
+
def test_send_and_count_message
|
24
|
+
@mq.send "test"
|
25
|
+
assert_equal 1, @mq.stats[:count]
|
26
|
+
@mq.send "test"
|
27
|
+
assert_equal 2, @mq.stats[:count]
|
28
|
+
end
|
29
|
+
|
30
|
+
def test_send_and_receive_many_times
|
31
|
+
many = 100_000
|
32
|
+
message = "Hello World"
|
33
|
+
|
34
|
+
many.times do
|
35
|
+
@mq.send message
|
36
|
+
assert_equal message, @mq.receive
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def test_sends_and_receives_utf8
|
41
|
+
message = "simån hørup"
|
42
|
+
@mq.send message
|
43
|
+
assert_equal message, @mq.receive
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_sending_5_bytes_should_report_5_byte_queue
|
47
|
+
message = "B" * 5
|
48
|
+
@mq.send message
|
49
|
+
assert_equal 5, @mq.stats[:size]
|
50
|
+
end
|
51
|
+
|
52
|
+
def test_sending_utf_should_report_correct_size_queue
|
53
|
+
message = "ø" * 5
|
54
|
+
@mq.send message
|
55
|
+
assert_equal "ø".bytes.size * 5, @mq.stats[:size]
|
56
|
+
end
|
57
|
+
|
58
|
+
def test_receive_on_empty_queue_raises_enomsg_if_ipc_nowait
|
59
|
+
assert_raises Errno::ENOMSG do
|
60
|
+
@mq.receive(0, SysVMQ::IPC_NOWAIT)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def test_send_and_receive_empty_message
|
65
|
+
@mq.send ""
|
66
|
+
assert_equal "", @mq.receive
|
67
|
+
end
|
68
|
+
|
69
|
+
def test_allow_multiple_queues_with_different_sizes
|
70
|
+
mq2 = SysVMQ.new(0xDEADCAFE, 2048, SysVMQ::IPC_CREAT | 0660)
|
71
|
+
mq2.send("B" * 2048)
|
72
|
+
mq2.destroy
|
73
|
+
end
|
74
|
+
|
75
|
+
def test_send_and_receive_with_type
|
76
|
+
@mq.send("10", 10)
|
77
|
+
@mq.send("5", 5)
|
78
|
+
|
79
|
+
assert_equal "5", @mq.receive(5)
|
80
|
+
assert_equal "10", @mq.receive(10)
|
81
|
+
end
|
82
|
+
|
83
|
+
def test_send_and_receive_with_negative_type
|
84
|
+
@mq.send("10", 10)
|
85
|
+
@mq.send("5", 5)
|
86
|
+
|
87
|
+
assert_equal "5", @mq.receive(-7)
|
88
|
+
assert_equal "10", @mq.receive(-10)
|
89
|
+
end
|
90
|
+
|
91
|
+
def test_responds_to_sigint
|
92
|
+
pid = fork {
|
93
|
+
begin
|
94
|
+
mq = SysVMQ.new(0xDEADCAFE, 2048, SysVMQ::IPC_CREAT | 0660)
|
95
|
+
mq.receive
|
96
|
+
rescue Interrupt
|
97
|
+
mq.destroy
|
98
|
+
end
|
99
|
+
}
|
100
|
+
sleep 0.01
|
101
|
+
Process.kill(:SIGINT, pid)
|
102
|
+
Process.wait(pid)
|
103
|
+
end
|
104
|
+
|
105
|
+
def test_kills_thread_cleanly
|
106
|
+
thread = Thread.new {
|
107
|
+
mq = SysVMQ.new(0xDEADCAFE, 2048, SysVMQ::IPC_CREAT | 0660)
|
108
|
+
mq.receive
|
109
|
+
}
|
110
|
+
|
111
|
+
sleep 0.01
|
112
|
+
thread.kill
|
113
|
+
end
|
114
|
+
end
|
data/test/test_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: sysvmq
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Simon Eskildsen
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2014-02-16 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rake-compiler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0.9'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0.9'
|
55
|
+
description: Ruby wrapper for SysV Message Queues
|
56
|
+
email:
|
57
|
+
- sirup@sirupsen.com
|
58
|
+
executables:
|
59
|
+
- coderay
|
60
|
+
- pry
|
61
|
+
- rake
|
62
|
+
extensions:
|
63
|
+
- ext/extconf.rb
|
64
|
+
extra_rdoc_files: []
|
65
|
+
files:
|
66
|
+
- ".gitignore"
|
67
|
+
- ".travis.yml"
|
68
|
+
- Gemfile
|
69
|
+
- LICENSE.txt
|
70
|
+
- README.md
|
71
|
+
- Rakefile
|
72
|
+
- bin/coderay
|
73
|
+
- bin/pry
|
74
|
+
- bin/rake
|
75
|
+
- ext/extconf.rb
|
76
|
+
- ext/sysvmq.c
|
77
|
+
- sysvmq.gemspec
|
78
|
+
- test/sysv_mq_test.rb
|
79
|
+
- test/test_helper.rb
|
80
|
+
homepage: https://github.com/Sirupsen/sysvmq
|
81
|
+
licenses:
|
82
|
+
- MIT
|
83
|
+
metadata: {}
|
84
|
+
post_install_message:
|
85
|
+
rdoc_options: []
|
86
|
+
require_paths:
|
87
|
+
- lib
|
88
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
89
|
+
requirements:
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: '0'
|
93
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
94
|
+
requirements:
|
95
|
+
- - ">="
|
96
|
+
- !ruby/object:Gem::Version
|
97
|
+
version: '0'
|
98
|
+
requirements: []
|
99
|
+
rubyforge_project:
|
100
|
+
rubygems_version: 2.2.0
|
101
|
+
signing_key:
|
102
|
+
specification_version: 4
|
103
|
+
summary: Ruby wrapper for SysV Message Queues
|
104
|
+
test_files:
|
105
|
+
- test/sysv_mq_test.rb
|
106
|
+
- test/test_helper.rb
|