concurrent-shm 0.1.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.
- checksums.yaml +7 -0
- data/AUTHORS +7 -0
- data/CHANGELOG.md +5 -0
- data/LICENSE +201 -0
- data/README.md +64 -0
- data/ext/concurrent-shm/extconf.rb +22 -0
- data/ext/concurrent-shm/main.c +15 -0
- data/ext/concurrent-shm/main.h +72 -0
- data/ext/concurrent-shm/posix.c +813 -0
- data/ext/concurrent-shm/types.c +180 -0
- data/ext/concurrent-shm/varargs.h +71 -0
- data/lib/concurrent-shm.rb +4 -0
- data/lib/concurrent-shm/channel.rb +524 -0
- data/lib/concurrent-shm/gem.rb +26 -0
- data/lib/concurrent-shm/int_ptr.rb +53 -0
- metadata +58 -0
@@ -0,0 +1,180 @@
|
|
1
|
+
#include "main.h"
|
2
|
+
|
3
|
+
VALUE IntDomainError;
|
4
|
+
|
5
|
+
static inline int64_t __get_int_value(VALUE v) {
|
6
|
+
if (v == Qtrue) {
|
7
|
+
return 1;
|
8
|
+
} else if (v == Qfalse) {
|
9
|
+
return 0;
|
10
|
+
} else if (RB_TYPE_P(v, T_FIXNUM)) {
|
11
|
+
return FIX2LONG(v);
|
12
|
+
} else {
|
13
|
+
rb_raise(rb_eArgError, "Value must be an integer or boolean");
|
14
|
+
}
|
15
|
+
}
|
16
|
+
|
17
|
+
static inline uint64_t __value2int(VALUE v, uint8_t bits)
|
18
|
+
{
|
19
|
+
if (bits > 64) {
|
20
|
+
rb_raise(rb_eException, "Internal error - Invalid bit size");
|
21
|
+
}
|
22
|
+
|
23
|
+
int64_t s = __get_int_value(v);
|
24
|
+
if (bits == 64) {
|
25
|
+
return s;
|
26
|
+
}
|
27
|
+
|
28
|
+
if (s > (1 << (bits - 1)) - 1) {
|
29
|
+
rb_raise(IntDomainError, "Value overflows %d-bit signed integer", bits);
|
30
|
+
}
|
31
|
+
if (s < -(1 << (bits - 1))) {
|
32
|
+
rb_raise(IntDomainError, "Value underflows %d-bit signed integer", bits);
|
33
|
+
}
|
34
|
+
return s;
|
35
|
+
}
|
36
|
+
|
37
|
+
static inline uint64_t __value2uint(VALUE v, uint8_t bits)
|
38
|
+
{
|
39
|
+
if (bits > 64) {
|
40
|
+
rb_raise(rb_eException, "Internal error - Invalid bit size");
|
41
|
+
}
|
42
|
+
|
43
|
+
int64_t s = __get_int_value(v);
|
44
|
+
if (s < 0) {
|
45
|
+
rb_raise(IntDomainError, "Cannot assign negative value to unsigned integer");
|
46
|
+
}
|
47
|
+
|
48
|
+
uint64_t u = (uint64_t)s;
|
49
|
+
if (bits == 64) {
|
50
|
+
return u;
|
51
|
+
}
|
52
|
+
|
53
|
+
if (u > (uint64_t)((1 << bits) - 1)) {
|
54
|
+
rb_raise(IntDomainError, "Value overflows %d-bit unsigned integer", bits);
|
55
|
+
}
|
56
|
+
return u;
|
57
|
+
}
|
58
|
+
|
59
|
+
#define X(rtype, ctype, conv, size, ...) \
|
60
|
+
VALUE rtype##Ptr; \
|
61
|
+
static inline size_t __##ctype##_size(const void * _) { return sizeof(ctype##_t); } \
|
62
|
+
static const rb_data_type_t __##ctype##_rb_type = { \
|
63
|
+
.wrap_struct_name = #ctype "_t", \
|
64
|
+
.function = { \
|
65
|
+
.dfree = RUBY_TYPED_NEVER_FREE, \
|
66
|
+
.dsize = (size_t (*)(const void*))__##ctype##_size, \
|
67
|
+
}, \
|
68
|
+
}; \
|
69
|
+
Ptr2ValueFn(rtype, ctype) { return rb_data_typed_object_wrap(rtype##Ptr, (void *)v, &__##ctype##_rb_type); } \
|
70
|
+
Value2PtrFn(rtype, ctype) { return (ctype##_t *)rb_check_typeddata(v, &__##ctype##_rb_type); } \
|
71
|
+
static VALUE rtype##Ptr_read(VALUE self) { return INT2FIX(*Value2##rtype##Ptr(self)); } \
|
72
|
+
static VALUE rtype##Ptr_write(VALUE self, VALUE v) { *Value2##rtype##Ptr(self) = (ctype##_t)conv(v, size); return self; }
|
73
|
+
PtrTypes
|
74
|
+
#undef X
|
75
|
+
|
76
|
+
|
77
|
+
/*
|
78
|
+
* Document-class: ConcurrentSHM::Value::IntDomainError
|
79
|
+
* Raised when writing an integer pointer under or overflows.
|
80
|
+
*/
|
81
|
+
|
82
|
+
/*
|
83
|
+
* Document-class: ConcurrentSHM::Value::Int8Ptr < ConcurrentSHM::Value::IntPtr
|
84
|
+
* A pointer to an 8-bit signed integer.
|
85
|
+
* @!method read
|
86
|
+
* (see IntPtr#read)
|
87
|
+
* @!method write(value)
|
88
|
+
* (see IntPtr#write)
|
89
|
+
* @raise [ArgumentError] if the value is not an integer or boolean
|
90
|
+
* @raise [IntDomainError] if the value is greater than 2<sup>7</sup>-1 or less than -2<sup>7</sup>
|
91
|
+
*/
|
92
|
+
|
93
|
+
/*
|
94
|
+
* Document-class: ConcurrentSHM::Value::Int16Ptr < ConcurrentSHM::Value::IntPtr
|
95
|
+
* A pointer to an 16-bit signed integer.
|
96
|
+
* @!method read
|
97
|
+
* (see IntPtr#read)
|
98
|
+
* @!method write(value)
|
99
|
+
* (see IntPtr#write)
|
100
|
+
* @raise [ArgumentError] if the value is not an integer or boolean
|
101
|
+
* @raise [IntDomainError] if the value is greater than 2<sup>15</sup>-1 or less than -2<sup>15</sup>
|
102
|
+
*/
|
103
|
+
|
104
|
+
/*
|
105
|
+
* Document-class: ConcurrentSHM::Value::Int32Ptr < ConcurrentSHM::Value::IntPtr
|
106
|
+
* A pointer to an 32-bit signed integer.
|
107
|
+
* @!method read
|
108
|
+
* (see IntPtr#read)
|
109
|
+
* @!method write(value)
|
110
|
+
* (see IntPtr#write)
|
111
|
+
* @raise [ArgumentError] if the value is not an integer or boolean
|
112
|
+
* @raise [IntDomainError] if the value is greater than 2<sup>31</sup>-1 or less than -2<sup>31</sup>
|
113
|
+
*/
|
114
|
+
|
115
|
+
/*
|
116
|
+
* Document-class: ConcurrentSHM::Value::Int64Ptr < ConcurrentSHM::Value::IntPtr
|
117
|
+
* A pointer to an 64-bit signed integer.
|
118
|
+
* @!method read
|
119
|
+
* (see IntPtr#read)
|
120
|
+
* @!method write(value)
|
121
|
+
* (see IntPtr#write)
|
122
|
+
* @raise [ArgumentError] if the value is not an integer or boolean
|
123
|
+
* @raise [IntDomainError] if the value is greater than 2<sup>63</sup>-1 or less than -2<sup>63</sup>
|
124
|
+
*/
|
125
|
+
|
126
|
+
/*
|
127
|
+
* Document-class: ConcurrentSHM::Value::UInt8Ptr < ConcurrentSHM::Value::IntPtr
|
128
|
+
* A pointer to an 8-bit unsigned integer.
|
129
|
+
* @!method read
|
130
|
+
* (see IntPtr#read)
|
131
|
+
* @!method write(value)
|
132
|
+
* (see IntPtr#write)
|
133
|
+
* @raise [ArgumentError] if the value is not an integer or boolean
|
134
|
+
* @raise [IntDomainError] if the value is greater than 2<sup>8</sup>-1 or negative
|
135
|
+
*/
|
136
|
+
|
137
|
+
/*
|
138
|
+
* Document-class: ConcurrentSHM::Value::UInt16Ptr < ConcurrentSHM::Value::IntPtr
|
139
|
+
* A pointer to an 16-bit unsigned integer.
|
140
|
+
* @!method read
|
141
|
+
* (see IntPtr#read)
|
142
|
+
* @!method write(value)
|
143
|
+
* (see IntPtr#write)
|
144
|
+
* @raise [ArgumentError] if the value is not an integer or boolean
|
145
|
+
* @raise [IntDomainError] if the value is greater than 2<sup>16</sup>-1 or negative
|
146
|
+
*/
|
147
|
+
|
148
|
+
/*
|
149
|
+
* Document-class: ConcurrentSHM::Value::UInt32Ptr < ConcurrentSHM::Value::IntPtr
|
150
|
+
* A pointer to an 32-bit unsigned integer.
|
151
|
+
* @!method read
|
152
|
+
* (see IntPtr#read)
|
153
|
+
* @!method write(value)
|
154
|
+
* (see IntPtr#write)
|
155
|
+
* @raise [ArgumentError] if the value is not an integer or boolean
|
156
|
+
* @raise [IntDomainError] if the value is greater than 2<sup>32</sup>-1 or negative
|
157
|
+
*/
|
158
|
+
|
159
|
+
/*
|
160
|
+
* Document-class: ConcurrentSHM::Value::UInt64Ptr < ConcurrentSHM::Value::IntPtr
|
161
|
+
* A pointer to an 64-bit unsigned integer.
|
162
|
+
* @!method read
|
163
|
+
* (see IntPtr#read)
|
164
|
+
* @!method write(value)
|
165
|
+
* (see IntPtr#write)
|
166
|
+
* @raise [ArgumentError] if the value is not an integer or boolean
|
167
|
+
* @raise [IntDomainError] if the value is greater than 2<sup>64</sup>-1 or negative
|
168
|
+
*/
|
169
|
+
|
170
|
+
void Init_PtrTypes(VALUE Value, VALUE IntPtr)
|
171
|
+
{
|
172
|
+
IntDomainError = rb_define_class_under(Value, "IntDomainError", rb_eRangeError);
|
173
|
+
|
174
|
+
#define X(rtype, ctype, ...) \
|
175
|
+
rtype##Ptr = rb_define_class_under(Value, #rtype "Ptr", IntPtr); \
|
176
|
+
rb_define_method(rtype##Ptr, "read", rtype##Ptr_read, 0); \
|
177
|
+
rb_define_method(rtype##Ptr, "write", rtype##Ptr_write, 1);
|
178
|
+
PtrTypes
|
179
|
+
#undef X
|
180
|
+
}
|
@@ -0,0 +1,71 @@
|
|
1
|
+
#define __STR(v) __STR1(v)
|
2
|
+
#define __STR1(v) __STR2(v)
|
3
|
+
#define __STR2(v) #v
|
4
|
+
|
5
|
+
#define __CAT(a, b) __CAT1(a, b)
|
6
|
+
#define __CAT1(a, b) __CAT2(a, b)
|
7
|
+
#define __CAT2(a, b) a##b
|
8
|
+
|
9
|
+
#define __NARG(...) __NARG_(__VA_ARGS__, __RSEQ_N())
|
10
|
+
#define __NARG_(...) __ARG_N(__VA_ARGS__)
|
11
|
+
#define __ARG_N(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, _12, _13, _14, _15, _16, _17, _18, _19, _20, N, ...) N
|
12
|
+
#define __RSEQ_N() 20, 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0
|
13
|
+
|
14
|
+
#define __KWARGS_DECL(n) \
|
15
|
+
static ID kwarg_ids[1]; \
|
16
|
+
VALUE opts, kwargs[1]; \
|
17
|
+
if (!kwarg_ids[0])
|
18
|
+
|
19
|
+
#define __KWARGS_CALL(n, scan, required) \
|
20
|
+
scan; \
|
21
|
+
rb_get_kwargs(opts, kwarg_ids, required, n-required, kwargs);
|
22
|
+
|
23
|
+
#define __ARGS_0(fmt) rb_scan_args(argc, argv, fmt, &opts);
|
24
|
+
#define __ARGS_1(fmt, v0) rb_scan_args(argc, argv, fmt, &v0, &opts);
|
25
|
+
#define __ARGS_2(fmt, v0, v1) rb_scan_args(argc, argv, fmt, &v0, &v1, &opts);
|
26
|
+
#define __ARGS_3(fmt, v0, v1, v2) rb_scan_args(argc, argv, fmt, &v0, &v1, &v2, &opts);
|
27
|
+
#define __ARGS_4(fmt, v0, v1, v2, v3) rb_scan_args(argc, argv, fmt, &v0, &v1, &v2, &v3, &opts);
|
28
|
+
#define __ARGS_5(fmt, v0, v1, v2, v3, v4) rb_scan_args(argc, argv, fmt, &v0, &v1, &v2, &v3, &v4, &opts);
|
29
|
+
#define __ARGS_(N, fmt, ...) __CAT(__ARGS_, N)(fmt, __VA_ARGS__)
|
30
|
+
#define ARGS(fmt, ...) __ARGS_(__NARG(__VA_ARGS__), fmt, __VA_ARGS__)
|
31
|
+
|
32
|
+
#define __KWARGS_1(scan, required, v0) \
|
33
|
+
__KWARGS_DECL(1) { CONST_ID(kwarg_ids[0], #v0); } \
|
34
|
+
__KWARGS_CALL(1, scan, required); \
|
35
|
+
VALUE v0 = kwargs[0];
|
36
|
+
|
37
|
+
#define __KWARGS_2(scan, required, v0, v1) \
|
38
|
+
__KWARGS_DECL(2) { CONST_ID(kwarg_ids[0], #v0); CONST_ID(kwarg_ids[1], #v1); } \
|
39
|
+
__KWARGS_CALL(2, scan, required); \
|
40
|
+
VALUE v0 = kwargs[0]; VALUE v1 = kwargs[1];
|
41
|
+
|
42
|
+
#define __KWARGS_3(scan, required, v0, v1, v2) \
|
43
|
+
__KWARGS_DECL(3) { CONST_ID(kwarg_ids[0], #v0); CONST_ID(kwarg_ids[1], #v1); CONST_ID(kwarg_ids[2], #v2); } \
|
44
|
+
__KWARGS_CALL(3, scan, required); \
|
45
|
+
VALUE v0 = kwargs[0]; VALUE v1 = kwargs[1]; VALUE v2 = kwargs[2];
|
46
|
+
|
47
|
+
#define __KWARGS_4(scan, required, v0, v1, v2, v3) \
|
48
|
+
__KWARGS_DECL(4) { CONST_ID(kwarg_ids[0], #v0); CONST_ID(kwarg_ids[1], #v1); CONST_ID(kwarg_ids[2], #v2); CONST_ID(kwarg_ids[3], #v3); } \
|
49
|
+
__KWARGS_CALL(4, scan, required); \
|
50
|
+
VALUE v0 = kwargs[0]; VALUE v1 = kwargs[1]; VALUE v2 = kwargs[2]; VALUE v3 = kwargs[3];
|
51
|
+
|
52
|
+
#define __KWARGS_5(scan, required, v0, v1, v2, v3, v4) \
|
53
|
+
__KWARGS_DECL(5) { CONST_ID(kwarg_ids[0], #v0); CONST_ID(kwarg_ids[1], #v1); CONST_ID(kwarg_ids[2], #v2); CONST_ID(kwarg_ids[3], #v3); CONST_ID(kwarg_ids[4], #v4); } \
|
54
|
+
__KWARGS_CALL(5, scan, required); \
|
55
|
+
VALUE v0 = kwargs[0]; VALUE v1 = kwargs[1]; VALUE v2 = kwargs[2]; VALUE v3 = kwargs[3]; VALUE v4 = kwargs[4];
|
56
|
+
|
57
|
+
#define __KWARGS_(N, scan, required, ...) __CAT(__KWARGS_, N)(scan, required, __VA_ARGS__)
|
58
|
+
#define KWARGS(scan, required, ...) __KWARGS_(__NARG(__VA_ARGS__), scan, required, __VA_ARGS__)
|
59
|
+
|
60
|
+
#define __CONST_IDS_1(pre, v0) static ID pre##v0; if (!pre##v0) { CONST_ID(pre##v0, #v0); }
|
61
|
+
#define __CONST_IDS_2(pre, v0, v1) static ID pre##v0, pre##v1; if (!pre##v0) { CONST_ID(pre##v0, #v0); CONST_ID(pre##v1, #v1); }
|
62
|
+
#define __CONST_IDS_3(pre, v0, v1, v2) static ID pre##v0, pre##v1, pre##v2; if (!pre##v0) { CONST_ID(pre##v0, #v0); CONST_ID(pre##v1, #v1); CONST_ID(pre##v2, #v2); }
|
63
|
+
#define __CONST_IDS_4(pre, v0, v1, v2, v3) static ID pre##v0, pre##v1, pre##v2, pre##v3; if (!pre##v0) { CONST_ID(pre##v0, #v0); CONST_ID(pre##v1, #v1); CONST_ID(pre##v2, #v2); CONST_ID(pre##v3, #v3); }
|
64
|
+
#define __CONST_IDS_5(pre, v0, v1, v2, v3, v4) static ID pre##v0, pre##v1, pre##v2, pre##v3, pre##v4; if (!pre##v0) { CONST_ID(pre##v0, #v0); CONST_ID(pre##v1, #v1); CONST_ID(pre##v2, #v2); CONST_ID(pre##v3, #v3); CONST_ID(pre##v4, #v4); }
|
65
|
+
#define __CONST_IDS_6(pre, v0, v1, v2, v3, v4, v5) static ID pre##v0, pre##v1, pre##v2, pre##v3, pre##v4, pre##v5; if (!pre##v0) { CONST_ID(pre##v0, #v0); CONST_ID(pre##v1, #v1); CONST_ID(pre##v2, #v2); CONST_ID(pre##v3, #v3); CONST_ID(pre##v4, #v4); CONST_ID(pre##v5, #v5); }
|
66
|
+
#define __CONST_IDS_7(pre, v0, v1, v2, v3, v4, v5, v6) static ID pre##v0, pre##v1, pre##v2, pre##v3, pre##v4, pre##v5, pre##v6; if (!pre##v0) { CONST_ID(pre##v0, #v0); CONST_ID(pre##v1, #v1); CONST_ID(pre##v2, #v2); CONST_ID(pre##v3, #v3); CONST_ID(pre##v4, #v4); CONST_ID(pre##v5, #v5); CONST_ID(pre##v6, #v6); }
|
67
|
+
#define __CONST_IDS_8(pre, v0, v1, v2, v3, v4, v5, v6, v7) static ID pre##v0, pre##v1, pre##v2, pre##v3, pre##v4, pre##v5, pre##v6, pre##v7; if (!pre##v0) { CONST_ID(pre##v0, #v0); CONST_ID(pre##v1, #v1); CONST_ID(pre##v2, #v2); CONST_ID(pre##v3, #v3); CONST_ID(pre##v4, #v4); CONST_ID(pre##v5, #v5); CONST_ID(pre##v6, #v6); CONST_ID(pre##v7, #v7); }
|
68
|
+
#define __CONST_IDS_9(pre, v0, v1, v2, v3, v4, v5, v6, v7, v8) static ID pre##v0, pre##v1, pre##v2, pre##v3, pre##v4, pre##v5, pre##v6, pre##v7, pre##v8; if (!pre##v0) { CONST_ID(pre##v0, #v0); CONST_ID(pre##v1, #v1); CONST_ID(pre##v2, #v2); CONST_ID(pre##v3, #v3); CONST_ID(pre##v4, #v4); CONST_ID(pre##v5, #v5); CONST_ID(pre##v6, #v6); CONST_ID(pre##v7, #v7); CONST_ID(pre##v8, #v8); }
|
69
|
+
#define __CONST_IDS_10(pre, v0, v1, v2, v3, v4, v5, v6, v7, v8, v9) static ID v0, pre##v1, pre##v2, pre##v3, pre##v4, pre##v5, pre##v6, pre##v7, pre##v8, pre##v9; if (!pre##v0) { CONST_ID(pre##v0, #v0); CONST_ID(pre##v1, #v1); CONST_ID(pre##v2, #v2); CONST_ID(pre##v3, #v3); CONST_ID(pre##v4, #v4); CONST_ID(pre##v5, #v5); CONST_ID(pre##v6, #v6); CONST_ID(pre##v7, #v7); CONST_ID(pre##v8, #v8); CONST_ID(pre##v9, #v9); }
|
70
|
+
#define __CONST_IDS_(N, pre, ...) __CAT(__CONST_IDS_, N)(pre, __VA_ARGS__)
|
71
|
+
#define CONST_IDS(pre, ...) __CONST_IDS_(__NARG(__VA_ARGS__), pre, __VA_ARGS__)
|
@@ -0,0 +1,524 @@
|
|
1
|
+
module ConcurrentSHM
|
2
|
+
# A channel for asynchronously passing data between processes, safely, via
|
3
|
+
# shared memory spaces.
|
4
|
+
class Channel
|
5
|
+
# Raised when `#send` is called on a closed channel.
|
6
|
+
class ClosedWriteError
|
7
|
+
def initialize
|
8
|
+
super("Write to closed channel")
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
# An unbuffered channel.
|
13
|
+
class Unbuffered < Channel
|
14
|
+
# An unbuffered channel with zero-width packets.
|
15
|
+
class Empty < Unbuffered
|
16
|
+
# Allocates an unbuffered, zero-width channel.
|
17
|
+
# @see Channel.new
|
18
|
+
def self.new(shm, offset: 0, autosize: true)
|
19
|
+
alloc(shm, 2, offset: offset, autosize: autosize) do |body|
|
20
|
+
@state = body[0].as_intptr
|
21
|
+
@closed = body[1].as_intptr
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Send a zero-width packet over the channel. Blocks if there are no receivers.
|
26
|
+
# @return [nil]
|
27
|
+
# @raise [ClosedWriteError] if the channel is closed
|
28
|
+
def send
|
29
|
+
do_send
|
30
|
+
end
|
31
|
+
|
32
|
+
# Receive a zero-width packet over the channel. Blocks if there are no senders.
|
33
|
+
# @return [nil]
|
34
|
+
def recv
|
35
|
+
do_recv
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# An unbuffered channel with fixed-width packets.
|
40
|
+
class Fixed < Unbuffered
|
41
|
+
# Allocates an unbuffered, fixed-width channel with the specified width.
|
42
|
+
# @see Channel.new
|
43
|
+
# @raise [RangeError] if the width is less than 1
|
44
|
+
def self.new(shm, width:, offset: 0, autosize: true)
|
45
|
+
raise RangeError, "Width must be > 0" unless width > 0
|
46
|
+
|
47
|
+
alloc(shm, 2 + width, offset: offset, autosize: autosize) do |body|
|
48
|
+
@state = body[0].as_intptr
|
49
|
+
@closed = body[1].as_intptr
|
50
|
+
@data = body[2..]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Send a fixed-width packet over the channel. Blocks if there are no receivers.
|
55
|
+
# @param data [String] the packet
|
56
|
+
# @return [nil]
|
57
|
+
# @raise [ClosedWriteError] if the channel is closed
|
58
|
+
# @raise [RangeError] if the packet size exceeds the channel width
|
59
|
+
def send(data)
|
60
|
+
raise RangeError, "Data is wider than channel" if data.size > @data.size
|
61
|
+
|
62
|
+
do_send do
|
63
|
+
@data.write(data)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Receive a fixed-width packet over the channel. Blocks if there are no senders.
|
68
|
+
# @return [String] the packet
|
69
|
+
def recv
|
70
|
+
do_recv do
|
71
|
+
@data.read
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
# An unbuffered channel with variable-width packets.
|
77
|
+
class Variable < Unbuffered
|
78
|
+
# Allocates an unbuffered, variable-width channel with the specified
|
79
|
+
# capacity.
|
80
|
+
# @see Channel.new
|
81
|
+
# @raise [RangeError] if the capacity is less than 1 or greater than 2<sup>32</sup>-1
|
82
|
+
def self.new(shm, capacity:, offset: 0, autosize: true)
|
83
|
+
raise RangeError, "Capacity must be > 0" unless capacity > 0
|
84
|
+
cbytes = bytes_for(capacity) { raise RangeError, "Capacity must be less than 2^32" }
|
85
|
+
|
86
|
+
alloc(shm, cbytes + 2 + capacity, offset: offset, autosize: autosize) do |body|
|
87
|
+
@width = body[0, cbytes].as_uintptr
|
88
|
+
@state = body[cbytes].as_intptr
|
89
|
+
@closed = body[cbytes+1].as_intptr
|
90
|
+
@data = body[cbytes+2..]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Send a variable-width packet over the channel. Blocks if there are no receivers.
|
95
|
+
# @param data [String] the packet
|
96
|
+
# @return [nil]
|
97
|
+
# @raise [ClosedWriteError] if the channel is closed
|
98
|
+
# @raise [RangeError] if the packet size exceeds the channel capacity
|
99
|
+
def send(data)
|
100
|
+
raise RangeError, "Data is wider than channel" if data.size > @data.size
|
101
|
+
|
102
|
+
do_send do
|
103
|
+
@width[] = data.bytesize + 1
|
104
|
+
@data.write(data)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Receive a variable-width packet over the channel. Blocks if there are no senders.
|
109
|
+
# @return [String] the packet
|
110
|
+
def recv
|
111
|
+
do_recv do
|
112
|
+
w = @width[] - 1
|
113
|
+
@width[] = 0
|
114
|
+
@data[0...w].read
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
private
|
120
|
+
|
121
|
+
EMPTY = 0
|
122
|
+
FULL = +1
|
123
|
+
DONE = -1
|
124
|
+
private_constant :EMPTY, :FULL, :DONE
|
125
|
+
|
126
|
+
def do_send
|
127
|
+
locked do
|
128
|
+
raise ClosedWriteError unless transition(@state, from: EMPTY, to: FULL)
|
129
|
+
yield if block_given?
|
130
|
+
|
131
|
+
raise ClosedWriteError unless transition(@state, from: DONE, to: EMPTY)
|
132
|
+
return nil
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
def do_recv
|
137
|
+
locked do
|
138
|
+
return :closed unless transition(@state, from: FULL, to: DONE)
|
139
|
+
yield if block_given?
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
# A buffered channel with a buffer depth of 1.
|
145
|
+
class SingleBuffered < Channel
|
146
|
+
# A single-buffered channel with zero-width packets.
|
147
|
+
class Empty < SingleBuffered
|
148
|
+
# Allocates a single-buffered, zero-width channel.
|
149
|
+
# @see Channel.new
|
150
|
+
def self.new(shm, offset: 0, autosize: true)
|
151
|
+
alloc(shm, 2, offset: offset, autosize: autosize) do |body|
|
152
|
+
@state = body[0].as_intptr
|
153
|
+
@closed = body[1].as_intptr
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# (see Buffered::Empty#send)
|
158
|
+
def send
|
159
|
+
do_send
|
160
|
+
end
|
161
|
+
|
162
|
+
# (see Buffered::Empty#recv)
|
163
|
+
def recv
|
164
|
+
do_recv
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
# A single-buffered channel with fixed-width packets.
|
169
|
+
class Fixed < SingleBuffered
|
170
|
+
# Allocates a single-unbuffered, fixed-width channel with the specified
|
171
|
+
# width.
|
172
|
+
# @see Channel.new
|
173
|
+
# @raise [RangeError] if width is less than 1
|
174
|
+
def self.new(shm, width:, offset: 0, autosize: true)
|
175
|
+
raise RangeError, "Width must be > 0" unless width > 0
|
176
|
+
|
177
|
+
alloc(shm, 2 + width, offset: offset, autosize: autosize) do |body|
|
178
|
+
@state = body[0].as_intptr
|
179
|
+
@closed = body[1].as_intptr
|
180
|
+
@data = body[2..]
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
# (see Buffered::Fixed#send)
|
185
|
+
def send(data)
|
186
|
+
raise RangeError, "Data is wider than channel" if data.size > @data.size
|
187
|
+
|
188
|
+
do_send do
|
189
|
+
@data.write(data)
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
# (see Buffered::Fixed#recv)
|
194
|
+
def recv
|
195
|
+
do_recv do
|
196
|
+
@data.read
|
197
|
+
end
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
# A single-buffered channel with variable-width packets.
|
202
|
+
class Variable < SingleBuffered
|
203
|
+
# Allocates a single-buffered, variable-width channel with the specified
|
204
|
+
# capacity.
|
205
|
+
# @see Channel.new
|
206
|
+
# @raise [RangeError] if capacity is less than 1 or greater than 2<sup>32</sup>-1
|
207
|
+
def self.new(shm, capacity:, offset: 0, autosize: true)
|
208
|
+
raise RangeError, "Capacity must be > 0" unless capacity > 0
|
209
|
+
cbytes = bytes_for(capacity) { raise RangeError, "Capacity must be less than 2^32" }
|
210
|
+
|
211
|
+
alloc(shm, cbytes + 1 + capacity, offset: offset, autosize: autosize) do |body|
|
212
|
+
@width = body[0, cbytes].as_uintptr
|
213
|
+
@closed = body[cbytes].as_intptr
|
214
|
+
@data = body[cbytes+1..]
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
# Send a variable-width packet over the channel. Blocks if the buffer is full.
|
219
|
+
# @param data [String] the packet
|
220
|
+
# @return [nil]
|
221
|
+
# @raise [ClosedWriteError] if the channel is closed
|
222
|
+
# @raise [RangeError] if the packet size exceeds the channel capacity
|
223
|
+
def send(data)
|
224
|
+
raise RangeError, "Data is wider than channel" if data.size > @data.size
|
225
|
+
|
226
|
+
do_send do
|
227
|
+
@width[] = data.bytesize
|
228
|
+
@data.write(data)
|
229
|
+
end
|
230
|
+
end
|
231
|
+
|
232
|
+
# Receive a variable-width packet over the channel. Blocks if the buffer is empty.
|
233
|
+
# @return [String] the packet
|
234
|
+
def recv
|
235
|
+
do_recv do
|
236
|
+
@data[0...@width[]].read
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
private
|
242
|
+
|
243
|
+
EMPTY = 0
|
244
|
+
FULL = 1
|
245
|
+
private_constant :EMPTY, :FULL
|
246
|
+
|
247
|
+
def do_send
|
248
|
+
locked do
|
249
|
+
raise ClosedWriteError unless transition(@state, from: EMPTY, to: FULL)
|
250
|
+
yield if block_given?
|
251
|
+
return nil
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
def do_recv
|
256
|
+
locked do
|
257
|
+
return :closed unless transition(@state, from: FULL, to: EMPTY)
|
258
|
+
yield if block_given?
|
259
|
+
end
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
# A buffered channel.
|
264
|
+
class Buffered < Channel
|
265
|
+
# A buffered channel with zero-width packets.
|
266
|
+
class Empty < Buffered
|
267
|
+
# Allocates a buffered, zero-width channel.
|
268
|
+
# @see Channel.new
|
269
|
+
# @raise [RangeError] if depth is less than 1 or greater than 2<sup>32</sup>-1
|
270
|
+
def self.new(shm, depth:, offset: 0, autosize: true)
|
271
|
+
raise RangeError, "Depth must be > 0" unless depth > 0
|
272
|
+
dbytes = bytes_for(depth) { raise RangeError, "Depth must be less than 2^32" }
|
273
|
+
|
274
|
+
alloc(shm, dbytes*2 + 1, offset: offset, autosize: autosize) do |body|
|
275
|
+
@depth = depth
|
276
|
+
@read = body[0, dbytes].as_uintptr
|
277
|
+
@written = body[dbytes, dbytes].as_uintptr
|
278
|
+
@closed = body[dbytes*2].as_intptr
|
279
|
+
end
|
280
|
+
end
|
281
|
+
|
282
|
+
# Send a zero-width packet over the channel. Blocks if the buffer is full.
|
283
|
+
# @return [nil]
|
284
|
+
# @raise [ClosedWriteError] if the channel is closed
|
285
|
+
def send
|
286
|
+
do_send
|
287
|
+
end
|
288
|
+
|
289
|
+
# Receive a zero-width packet over the channel. Blocks if the buffer is empty.
|
290
|
+
# @return [nil]
|
291
|
+
def recv
|
292
|
+
do_recv
|
293
|
+
end
|
294
|
+
end
|
295
|
+
|
296
|
+
# A buffered channel with fixed-width packets.
|
297
|
+
class Fixed < Buffered
|
298
|
+
# Allocates a buffered, fixed-width channel with the specified width.
|
299
|
+
# @see Channel.new
|
300
|
+
# @raise [RangeError] if width is less than 1 or depth is less than 1 or greater than 2<sup>32</sup>-1
|
301
|
+
def self.new(shm, depth:, width:, offset: 0, autosize: true)
|
302
|
+
raise RangeError, "Width must be > 0" unless width > 0
|
303
|
+
raise RangeError, "Depth must be > 0" unless depth > 0
|
304
|
+
dbytes = bytes_for(depth) { raise RangeError, "Depth must be less than 2^32" }
|
305
|
+
|
306
|
+
alloc(shm, dbytes*2 + 1 + depth*width, offset: offset, autosize: autosize) do |body|
|
307
|
+
@depth, @width = depth,
|
308
|
+
@read = body[0, dbytes].as_uintptr
|
309
|
+
@written = body[dbytes, dbytes].as_uintptr
|
310
|
+
@closed = body[dbytes*2].as_intptr
|
311
|
+
|
312
|
+
data = body[dbytes*2+1..]
|
313
|
+
@data = (0...@depth).map { |i| data[i*width, width] }
|
314
|
+
end
|
315
|
+
end
|
316
|
+
|
317
|
+
# Send a fixed-width packet over the channel. Blocks if the buffer is full.
|
318
|
+
# @param data [String] the packet
|
319
|
+
# @return [nil]
|
320
|
+
# @raise [ClosedWriteError] if the channel is closed
|
321
|
+
# @raise [RangeError] if the packet size exceeds the channel width
|
322
|
+
def send(data)
|
323
|
+
raise RangeError, "Data is wider than channel" if data.size > @data.size
|
324
|
+
|
325
|
+
do_send do |i|
|
326
|
+
@data[i].write(data)
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
# Receive a fixed-width packet over the channel. Blocks if the buffer is empty.
|
331
|
+
# @return [String] the packet
|
332
|
+
def recv
|
333
|
+
do_recv do |i|
|
334
|
+
@data[i].read
|
335
|
+
end
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
private
|
340
|
+
|
341
|
+
def do_send
|
342
|
+
locked do
|
343
|
+
raise ClosedWriteError unless wait_until { @written[] < @depth }
|
344
|
+
|
345
|
+
i = @written[]
|
346
|
+
w = (i + 1) % @depth
|
347
|
+
@written[] = w == @read[] ? @depth : w
|
348
|
+
@cond.signal
|
349
|
+
yield(i) if block_given?
|
350
|
+
nil
|
351
|
+
end
|
352
|
+
end
|
353
|
+
|
354
|
+
def do_recv
|
355
|
+
locked do
|
356
|
+
return :closed unless wait_until { @read[] != @written[] }
|
357
|
+
|
358
|
+
r = @read[]
|
359
|
+
@read[] = (r + 1) % @depth
|
360
|
+
@written[] = r if @written[] == @depth
|
361
|
+
@cond.signal
|
362
|
+
yield(r) if block_given?
|
363
|
+
end
|
364
|
+
end
|
365
|
+
end
|
366
|
+
|
367
|
+
class << self
|
368
|
+
# (see .alloc)
|
369
|
+
# Allocates a channel in a shared memory space at the specified offset,
|
370
|
+
# optionally initializing the space to the required size. The class of the
|
371
|
+
# returned channel depends on the width and depth.
|
372
|
+
#
|
373
|
+
# | Depth | Width | Behavior
|
374
|
+
# |-|-|-
|
375
|
+
# | 0 | 0 | {Unbuffered::Empty Unbuffered, zero-width}
|
376
|
+
# | 0 | Positive | {Unbuffered::Fixed Unbuffered, fixed-width}
|
377
|
+
# | 0 | Negative | {Unbuffered::Variable Unbuffered, variable-width}
|
378
|
+
# | 1 | 0 | {SingleBuffered::Empty Single-buffered, zero-width}
|
379
|
+
# | 1 | Positive | {SingleBuffered::Fixed Single-buffered, fixed-width}
|
380
|
+
# | 1 | Negative | {SingleBuffered::Variable Single-buffered, variable-width}
|
381
|
+
# | 2+ | 0 | {Buffered::Empty Buffered, zero-width}
|
382
|
+
# | 2+ | Positive | {Buffered::Fixed Buffered, fixed-width}
|
383
|
+
# | 2+ | Negative | Not supported (buffered, variable-width)
|
384
|
+
#
|
385
|
+
# When allocating a variable-width channel, `width` is inverted and passed
|
386
|
+
# as `capacity`.
|
387
|
+
#
|
388
|
+
# @param shm [SharedMemory] the shared memory space
|
389
|
+
# @param offset [Integer] the offset to place the channel at in the shared memory space
|
390
|
+
# @param autosize [Boolean] whether to initialize the space to the required size
|
391
|
+
# @param depth [Integer] the channel buffer depth
|
392
|
+
# @param width [Integer] the channel data width
|
393
|
+
# @return [Channel] the channel
|
394
|
+
# @raise [ArgumentError] if depth is not an integer, width is not an integer, or depth is negative
|
395
|
+
# @raise [RangeError] if the offset is not a multiple of 16, or the shared memory space is not large enough
|
396
|
+
def new(shm, depth:, width:, offset: 0, autosize: true)
|
397
|
+
raise ArgumentError, "Depth is not an integer" unless depth.is_a?(Integer)
|
398
|
+
raise ArgumentError, "Width is not an integer" unless width.is_a?(Integer)
|
399
|
+
raise ArgumentError, "Depth must be positive" if depth < 0
|
400
|
+
|
401
|
+
args = { offset: offset, autosize: autosize }
|
402
|
+
|
403
|
+
if depth == 0
|
404
|
+
if width < 0
|
405
|
+
Unbuffered::Variable.new(shm, capacity: -width, **args)
|
406
|
+
elsif width > 0
|
407
|
+
Unbuffered::Fixed.new(shm, width: width, **args)
|
408
|
+
else
|
409
|
+
Unbuffered::Empty.new(shm, **args)
|
410
|
+
end
|
411
|
+
|
412
|
+
elsif depth == 1
|
413
|
+
if width < 0
|
414
|
+
SingleBuffered::Variable.new(shm, capacity: -width, **args)
|
415
|
+
elsif width > 0
|
416
|
+
SingleBuffered::Fixed.new(shm, width: width, **args)
|
417
|
+
else
|
418
|
+
SingleBuffered::Empty.new(shm, **args)
|
419
|
+
end
|
420
|
+
|
421
|
+
else
|
422
|
+
if width < 0
|
423
|
+
raise "Buffered variable-width mode is not supported"
|
424
|
+
elsif width > 0
|
425
|
+
Buffered::Fixed.new(shm, depth: depth, width: width, **args)
|
426
|
+
else
|
427
|
+
Buffered::Empty.new(shm, depth: depth, **args)
|
428
|
+
end
|
429
|
+
end
|
430
|
+
end
|
431
|
+
|
432
|
+
private
|
433
|
+
|
434
|
+
def mu_size
|
435
|
+
ConcurrentSHM.size_of(Mutex)
|
436
|
+
end
|
437
|
+
|
438
|
+
def cond_size
|
439
|
+
ConcurrentSHM.size_of(Condition)
|
440
|
+
end
|
441
|
+
|
442
|
+
def alloc(shm, needed, offset:, autosize:, &block)
|
443
|
+
raise RangeError, "Offset must be aligned to a 16-byte boundary" if offset % 16 != 0
|
444
|
+
|
445
|
+
needed += mu_size + cond_size
|
446
|
+
shm.size = offset + needed if autosize && shm.size == 0
|
447
|
+
raise RangeError, "Shared memory is too small: size=#{shm.size} but channel needs #{needed} at offset #{offset}" if shm.size < offset + needed
|
448
|
+
|
449
|
+
r = Region.map(shm, offset, needed)
|
450
|
+
mu = Mutex.new(r[0, mu_size])
|
451
|
+
cond = Condition.new(r[mu_size, cond_size])
|
452
|
+
body = r[mu_size + cond_size..]
|
453
|
+
|
454
|
+
# Call Object.new, because Channel.new has been overwritten
|
455
|
+
ch = Channel.superclass.method(:new).unbind.bind(self).call
|
456
|
+
ch.instance_variable_set(:@mu, mu)
|
457
|
+
ch.instance_variable_set(:@cond, cond)
|
458
|
+
|
459
|
+
# Always retain a reference to the mapped region so it doesn't get garbage collected
|
460
|
+
ch.instance_variable_set(:@mapping, r)
|
461
|
+
|
462
|
+
# Implementation-specific initialization
|
463
|
+
ch.instance_exec(body, &block)
|
464
|
+
|
465
|
+
# Clear status
|
466
|
+
ch.instance_variable_get(:@closed)[] = 0
|
467
|
+
|
468
|
+
ch
|
469
|
+
end
|
470
|
+
|
471
|
+
def bytes_for(v)
|
472
|
+
if v < 1 << 8
|
473
|
+
1
|
474
|
+
elsif v < 1 << 16
|
475
|
+
2
|
476
|
+
elsif v < 1 << 32
|
477
|
+
4
|
478
|
+
else
|
479
|
+
yield
|
480
|
+
end
|
481
|
+
end
|
482
|
+
end
|
483
|
+
|
484
|
+
# Close the channel.
|
485
|
+
# @return [nil]
|
486
|
+
# @raise [RuntimeError] if the channel is already closed
|
487
|
+
def close
|
488
|
+
locked do
|
489
|
+
raise "Already closed" if closed?
|
490
|
+
|
491
|
+
@cond.broadcast
|
492
|
+
@closed[] = 1
|
493
|
+
end
|
494
|
+
end
|
495
|
+
|
496
|
+
private
|
497
|
+
|
498
|
+
def closed?
|
499
|
+
@closed[] == 1
|
500
|
+
end
|
501
|
+
|
502
|
+
def locked
|
503
|
+
@mu.lock
|
504
|
+
yield
|
505
|
+
ensure
|
506
|
+
@mu.unlock
|
507
|
+
end
|
508
|
+
|
509
|
+
def wait_until
|
510
|
+
loop do
|
511
|
+
return true if yield
|
512
|
+
return false if closed?
|
513
|
+
@cond.wait(@mu)
|
514
|
+
end
|
515
|
+
end
|
516
|
+
|
517
|
+
def transition(value, from:, to:)
|
518
|
+
return false unless wait_until { value[] == from }
|
519
|
+
value[] = to
|
520
|
+
@cond.signal
|
521
|
+
true
|
522
|
+
end
|
523
|
+
end
|
524
|
+
end
|