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.
@@ -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,4 @@
1
+ require_relative 'concurrent-shm/gem'
2
+ require_relative 'concurrent-shm/channel'
3
+ require_relative 'concurrent-shm/cshm'
4
+ require_relative 'concurrent-shm/int_ptr'
@@ -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