uringmachine 0.1 → 0.3

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/ext/um/um_op.c CHANGED
@@ -1,5 +1,58 @@
1
1
  #include "um.h"
2
2
 
3
+ inline struct um_result_entry *um_result_checkout(struct um *machine) {
4
+ if (machine->result_freelist) {
5
+ struct um_result_entry *entry = machine->result_freelist;
6
+ machine->result_freelist = entry->next;
7
+ return entry;
8
+ }
9
+
10
+ struct um_result_entry *entry = malloc(sizeof(struct um_result_entry));
11
+ return entry;
12
+ }
13
+
14
+ inline void um_result_checkin(struct um *machine, struct um_result_entry *entry) {
15
+ entry->next = machine->result_freelist;
16
+ machine->result_freelist = entry;
17
+ }
18
+
19
+ inline void um_op_result_cleanup(struct um *machine, struct um_op *op) {
20
+ struct um_result_entry *entry = op->results_head;
21
+ while (entry) {
22
+ struct um_result_entry *next = entry->next;
23
+ um_result_checkin(machine, entry);
24
+ entry = next;
25
+ }
26
+ op->results_head = op->results_tail = NULL;
27
+ }
28
+
29
+ inline void um_op_result_push(struct um *machine, struct um_op *op, int result, int flags) {
30
+ struct um_result_entry *entry = um_result_checkout(machine);
31
+ entry->next = 0;
32
+ entry->result = result;
33
+ entry->flags = flags;
34
+ if (op->results_tail) {
35
+ op->results_tail->next = entry;
36
+ op->results_tail = entry;
37
+ }
38
+ else {
39
+ op->results_head = op->results_tail = entry;
40
+ }
41
+ }
42
+
43
+ inline int um_op_result_shift(struct um *machine, struct um_op *op, int *result, int *flags) {
44
+ if (!op->results_head) return 0;
45
+
46
+ struct um_result_entry *entry = op->results_head;
47
+ *result = entry->result;
48
+ *flags = entry->flags;
49
+ op->results_head = entry->next;
50
+ if (!op->results_head)
51
+ op->results_tail = NULL;
52
+ um_result_checkin(machine, entry);
53
+ return 1;
54
+ }
55
+
3
56
  inline void um_op_clear(struct um_op *op) {
4
57
  memset(op, 0, sizeof(struct um_op));
5
58
  op->fiber = op->resume_value = Qnil;
@@ -8,9 +61,9 @@ inline void um_op_clear(struct um_op *op) {
8
61
  inline struct um_op *um_op_checkout(struct um *machine) {
9
62
  machine->pending_count++;
10
63
 
11
- if (machine->freelist_head) {
12
- struct um_op *op = machine->freelist_head;
13
- machine->freelist_head = op->next;
64
+ if (machine->op_freelist) {
65
+ struct um_op *op = machine->op_freelist;
66
+ machine->op_freelist = op->next;
14
67
  um_op_clear(op);
15
68
  return op;
16
69
  }
@@ -21,10 +74,12 @@ inline struct um_op *um_op_checkout(struct um *machine) {
21
74
  }
22
75
 
23
76
  inline void um_op_checkin(struct um *machine, struct um_op *op) {
77
+ um_op_result_cleanup(machine, op);
78
+
24
79
  machine->pending_count--;
25
80
 
26
- op->next = machine->freelist_head;
27
- machine->freelist_head = op;
81
+ op->next = machine->op_freelist;
82
+ machine->op_freelist = op;
28
83
  }
29
84
 
30
85
  inline struct um_op *um_runqueue_find_by_fiber(struct um *machine, VALUE fiber) {
@@ -78,10 +133,19 @@ inline struct um_op *um_runqueue_shift(struct um *machine) {
78
133
  return op;
79
134
  }
80
135
 
81
- inline void um_free_linked_list(struct um_op *op) {
136
+ inline void um_free_op_linked_list(struct um *machine, struct um_op *op) {
82
137
  while (op) {
83
138
  struct um_op *next = op->next;
139
+ um_op_result_cleanup(machine, op);
84
140
  free(op);
85
141
  op = next;
86
142
  }
87
143
  }
144
+
145
+ inline void um_free_result_linked_list(struct um *machine, struct um_result_entry *entry) {
146
+ while (entry) {
147
+ struct um_result_entry *next = entry->next;
148
+ free(entry);
149
+ entry = next;
150
+ }
151
+ }
data/ext/um/um_utils.c CHANGED
@@ -15,9 +15,59 @@ inline int um_value_is_exception_p(VALUE v) {
15
15
  return rb_obj_is_kind_of(v, rb_eException) == Qtrue;
16
16
  }
17
17
 
18
- VALUE um_raise_exception(VALUE e) {
18
+ inline VALUE um_raise_exception(VALUE e) {
19
19
  static ID ID_raise = 0;
20
20
  if (!ID_raise) ID_raise = rb_intern("raise");
21
21
 
22
22
  return rb_funcall(rb_mKernel, ID_raise, 1, e);
23
23
  }
24
+
25
+ inline void um_raise_on_system_error(int result) {
26
+ if (unlikely(result < 0)) rb_syserr_fail(-result, strerror(-result));
27
+ }
28
+
29
+ inline void * um_prepare_read_buffer(VALUE buffer, unsigned len, int ofs) {
30
+ unsigned current_len = RSTRING_LEN(buffer);
31
+ if (ofs < 0) ofs = current_len + ofs + 1;
32
+ unsigned new_len = len + (unsigned)ofs;
33
+
34
+ if (current_len < new_len)
35
+ rb_str_modify_expand(buffer, new_len);
36
+ else
37
+ rb_str_modify(buffer);
38
+ return RSTRING_PTR(buffer) + ofs;
39
+ }
40
+
41
+ static inline void adjust_read_buffer_len(VALUE buffer, int result, int ofs) {
42
+ rb_str_modify(buffer);
43
+ unsigned len = result > 0 ? (unsigned)result : 0;
44
+ unsigned current_len = RSTRING_LEN(buffer);
45
+ if (ofs < 0) ofs = current_len + ofs + 1;
46
+ rb_str_set_len(buffer, len + (unsigned)ofs);
47
+ }
48
+
49
+ inline void um_update_read_buffer(struct um *machine, VALUE buffer, int buffer_offset, int result, int flags) {
50
+ if (!result) return;
51
+
52
+ adjust_read_buffer_len(buffer, result, buffer_offset);
53
+ }
54
+
55
+ inline VALUE get_string_from_buffer_ring(struct um *machine, int bgid, int result, int flags) {
56
+ if (!result) return Qnil;
57
+
58
+ unsigned buf_idx = flags >> IORING_CQE_BUFFER_SHIFT;
59
+ struct buf_ring_descriptor *desc = machine->buffer_rings + bgid;
60
+ char *src = desc->buf_base + desc->buf_size * buf_idx;
61
+ // TODO: add support for UTF8
62
+ // buf = rd->utf8_encoding ? rb_utf8_str_new(src, cqe->res) : rb_str_new(src, cqe->res);
63
+ VALUE buf = rb_str_new(src, result);
64
+
65
+ // add buffer back to buffer ring
66
+ io_uring_buf_ring_add(
67
+ desc->br, src, desc->buf_size, buf_idx, io_uring_buf_ring_mask(desc->buf_count), 0
68
+ );
69
+ io_uring_buf_ring_advance(desc->br, 1);
70
+
71
+ RB_GC_GUARD(buf);
72
+ return buf;
73
+ }
@@ -1,3 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- UM_VERSION = '0.1'
3
+ class UringMachine
4
+ VERSION = '0.3'
5
+ end
data/lib/uringmachine.rb CHANGED
@@ -3,6 +3,3 @@
3
3
  require_relative './um_ext'
4
4
 
5
5
  UM = UringMachine
6
-
7
- class UringMachine
8
- end
data/test/helper.rb CHANGED
@@ -3,8 +3,12 @@
3
3
  require 'bundler/setup'
4
4
  require_relative './coverage' if ENV['COVERAGE']
5
5
  require 'uringmachine'
6
+ require 'socket'
6
7
  require 'minitest/autorun'
7
8
 
9
+ STDOUT.sync = true
10
+ STDERR.sync = true
11
+
8
12
  module ::Kernel
9
13
  def debug(**h)
10
14
  k, v = h.first
@@ -46,18 +50,6 @@ module Minitest::Assertions
46
50
  end
47
51
  end
48
52
 
49
- class IOURingBaseTest < Minitest::Test
50
- attr_accessor :ring
51
-
52
- def setup
53
- @ring = IOU::Ring.new
54
- end
55
-
56
- def teardown
57
- ring.close
58
- end
59
- end
60
-
61
53
  class UMBaseTest < Minitest::Test
62
54
  attr_accessor :machine
63
55
 
@@ -66,5 +58,6 @@ class UMBaseTest < Minitest::Test
66
58
  end
67
59
 
68
60
  def teardown
61
+ # @machine&.cleanup
69
62
  end
70
63
  end
data/test/test_um.rb CHANGED
@@ -3,16 +3,6 @@
3
3
  require_relative 'helper'
4
4
  require 'socket'
5
5
 
6
- class SleepTest < UMBaseTest
7
- def test_sleep
8
- t0 = monotonic_clock
9
- res = machine.sleep(0.1)
10
- t1 = monotonic_clock
11
- assert_in_range 0.09..0.13, t1 - t0
12
- assert_equal 0.1, res
13
- end
14
- end
15
-
16
6
  class SchedulingTest < UMBaseTest
17
7
  def test_schedule_and_yield
18
8
  buf = []
@@ -119,8 +109,8 @@ class SchedulingTest < UMBaseTest
119
109
 
120
110
  def test_timeout_with_raising_block
121
111
  e = nil
122
- v = begin
123
- machine.timeout(0.01, TOError) do
112
+ begin
113
+ machine.timeout(0.1, TOError) do
124
114
  raise 'hi'
125
115
  end
126
116
  rescue => e
@@ -135,7 +125,7 @@ class SchedulingTest < UMBaseTest
135
125
  end
136
126
 
137
127
  def test_timeout_with_nothing_blocking
138
- v = machine.timeout(0.01, TOError) { 42 }
128
+ v = machine.timeout(0.1, TOError) { 42 }
139
129
 
140
130
  assert_equal 42, v
141
131
 
@@ -166,3 +156,259 @@ class SchedulingTest < UMBaseTest
166
156
  assert_equal [3], buf
167
157
  end
168
158
  end
159
+
160
+ class SleepTest < UMBaseTest
161
+ def test_sleep
162
+ t0 = monotonic_clock
163
+ res = machine.sleep(0.1)
164
+ t1 = monotonic_clock
165
+ assert_in_range 0.09..0.13, t1 - t0
166
+ assert_equal 0.1, res
167
+ end
168
+ end
169
+
170
+ class ReadTest < UMBaseTest
171
+ def test_read
172
+ r, w = IO.pipe
173
+ w << 'foobar'
174
+
175
+ buf = +''
176
+ res = machine.read(r.fileno, buf, 3)
177
+ assert_equal 3, res
178
+ assert_equal 'foo', buf
179
+
180
+ buf = +''
181
+ res = machine.read(r.fileno, buf, 128)
182
+ assert_equal 3, res
183
+ assert_equal 'bar', buf
184
+
185
+ w.close
186
+ buf = +''
187
+ res = machine.read(r.fileno, buf, 128)
188
+ assert_equal 0, res
189
+ assert_equal '', buf
190
+ end
191
+
192
+ def test_read_bad_fd
193
+ _r, w = IO.pipe
194
+
195
+ assert_raises(Errno::EBADF) do
196
+ machine.read(w.fileno, +'', 8192)
197
+ end
198
+ end
199
+
200
+ def test_read_with_buffer_offset
201
+ buffer = +'foo'
202
+ r, w = IO.pipe
203
+ w << 'bar'
204
+
205
+ result = machine.read(r.fileno, buffer, 100, buffer.bytesize)
206
+ assert_equal 3, result
207
+ assert_equal 'foobar', buffer
208
+ end
209
+
210
+ def test_read_with_negative_buffer_offset
211
+ buffer = +'foo'
212
+
213
+ r, w = IO.pipe
214
+ w << 'bar'
215
+
216
+ result = machine.read(r.fileno, buffer, 100, -1)
217
+ assert_equal 3, result
218
+ assert_equal 'foobar', buffer
219
+
220
+ buffer = +'foogrr'
221
+
222
+ r, w = IO.pipe
223
+ w << 'bar'
224
+
225
+ result = machine.read(r.fileno, buffer, 100, -4)
226
+ assert_equal 3, result
227
+ assert_equal 'foobar', buffer
228
+ end
229
+ end
230
+
231
+ class ReadEachTest < UMBaseTest
232
+ def test_read_each
233
+ r, w = IO.pipe
234
+ bufs = []
235
+
236
+ bgid = machine.setup_buffer_ring(4096, 1024)
237
+ assert_equal 0, bgid
238
+
239
+ f = Fiber.new do
240
+ w << 'foo'
241
+ machine.snooze
242
+ w << 'bar'
243
+ machine.snooze
244
+ w << 'baz'
245
+ machine.snooze
246
+ w.close
247
+ machine.yield
248
+ end
249
+ machine.schedule(f, nil)
250
+
251
+ machine.read_each(r.fileno, bgid) do |buf|
252
+ bufs << buf
253
+ end
254
+
255
+ assert_equal ['foo', 'bar', 'baz'], bufs
256
+ assert_equal 0, machine.pending_count
257
+ end
258
+
259
+ # send once and close write fd
260
+ def test_read_each_raising_1
261
+ r, w = IO.pipe
262
+
263
+ bgid = machine.setup_buffer_ring(4096, 1024)
264
+ assert_equal 0, bgid
265
+
266
+ w << 'foo'
267
+ w.close
268
+
269
+ e = nil
270
+ begin
271
+ machine.read_each(r.fileno, bgid) do |buf|
272
+ raise 'hi'
273
+ end
274
+ rescue => e
275
+ end
276
+
277
+ assert_kind_of RuntimeError, e
278
+ assert_equal 'hi', e.message
279
+ assert_equal 0, machine.pending_count
280
+ end
281
+
282
+ # send once and leave write fd open
283
+ def test_read_each_raising_2
284
+ r, w = IO.pipe
285
+
286
+ bgid = machine.setup_buffer_ring(4096, 1024)
287
+ assert_equal 0, bgid
288
+
289
+ w << 'foo'
290
+
291
+ e = nil
292
+ begin
293
+ machine.read_each(r.fileno, bgid) do |buf|
294
+ raise 'hi'
295
+ end
296
+ rescue => e
297
+ end
298
+
299
+ assert_kind_of RuntimeError, e
300
+ assert_equal 'hi', e.message
301
+
302
+ # since the write fd is still open, the read_each impl is supposed to cancel
303
+ # the op, which is done asynchronously.
304
+ assert_equal 1, machine.pending_count
305
+ machine.snooze
306
+ assert_equal 0, machine.pending_count
307
+ end
308
+
309
+ # send twice
310
+ def test_read_each_raising_3
311
+ r, w = IO.pipe
312
+
313
+ bgid = machine.setup_buffer_ring(4096, 1024)
314
+ assert_equal 0, bgid
315
+
316
+ w << 'foo'
317
+ w << 'bar'
318
+
319
+ e = nil
320
+ begin
321
+ machine.read_each(r.fileno, bgid) do |buf|
322
+ raise 'hi'
323
+ end
324
+ rescue => e
325
+ end
326
+
327
+ assert_kind_of RuntimeError, e
328
+ assert_equal 'hi', e.message
329
+
330
+ # since the write fd is still open, the read_each impl is supposed to cancel
331
+ # the op, which is done asynchronously.
332
+ assert_equal 1, machine.pending_count
333
+ machine.snooze
334
+ assert_equal 0, machine.pending_count
335
+ end
336
+ end
337
+
338
+ class WriteTest < UMBaseTest
339
+ def test_write
340
+ r, w = IO.pipe
341
+
342
+ machine.write(w.fileno, 'foo')
343
+ assert_equal 'foo', r.readpartial(3)
344
+
345
+ machine.write(w.fileno, 'bar', 2)
346
+ assert_equal 'ba', r.readpartial(3)
347
+ end
348
+
349
+ def test_write_bad_fd
350
+ r, _w = IO.pipe
351
+
352
+ assert_raises(Errno::EBADF) do
353
+ machine.write(r.fileno, 'foo')
354
+ end
355
+ end
356
+ end
357
+
358
+ class AcceptTest < UMBaseTest
359
+ def setup
360
+ super
361
+ @port = 9000 + rand(1000)
362
+ @server = TCPServer.open('127.0.0.1', @port)
363
+ end
364
+
365
+ def teardown
366
+ @server&.close
367
+ super
368
+ end
369
+
370
+ def test_accept
371
+ conn = TCPSocket.new('127.0.0.1', @port)
372
+
373
+ fd = machine.accept(@server.fileno)
374
+ assert_kind_of Integer, fd
375
+ assert fd > 0
376
+
377
+ machine.write(fd, 'foo')
378
+ buf = conn.readpartial(3)
379
+
380
+ assert_equal 'foo', buf
381
+ end
382
+ end
383
+
384
+ class AcceptEachTest < UMBaseTest
385
+ def setup
386
+ super
387
+ @port = 9000 + rand(1000)
388
+ @server = TCPServer.open('127.0.0.1', @port)
389
+ end
390
+
391
+ def teardown
392
+ @server&.close
393
+ super
394
+ end
395
+
396
+ def test_accept_each
397
+ conns = 3.times.map { TCPSocket.new('127.0.0.1', @port) }
398
+
399
+ count = 0
400
+ machine.accept_each(@server.fileno) do |fd|
401
+ machine.write(fd, (count += 1).to_s)
402
+ break if count == 3
403
+ end
404
+
405
+ assert_equal 3, count
406
+ assert_equal 1, machine.pending_count
407
+ machine.snooze
408
+ assert_equal 0, machine.pending_count
409
+
410
+ assert_equal '1', conns[0].readpartial(3)
411
+ assert_equal '2', conns[1].readpartial(3)
412
+ assert_equal '3', conns[2].readpartial(3)
413
+ end
414
+ end
data/uringmachine.gemspec CHANGED
@@ -2,7 +2,7 @@ require_relative './lib/uringmachine/version'
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = 'uringmachine'
5
- s.version = UM_VERSION
5
+ s.version = UringMachine::VERSION
6
6
  s.licenses = ['MIT']
7
7
  s.summary = 'A lean, mean io_uring machine'
8
8
  s.author = 'Sharon Rosner'
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: uringmachine
3
3
  version: !ruby/object:Gem::Version
4
- version: '0.1'
4
+ version: '0.3'
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sharon Rosner
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-10-03 00:00:00.000000000 Z
11
+ date: 2024-10-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rake-compiler
@@ -91,9 +91,6 @@ files:
91
91
  - examples/http_server_multishot.rb
92
92
  - examples/http_server_simpler.rb
93
93
  - ext/um/extconf.rb
94
- - ext/um/iou.h
95
- - ext/um/op_ctx.c
96
- - ext/um/ring.c
97
94
  - ext/um/um.c
98
95
  - ext/um/um.h
99
96
  - ext/um/um_class.c
@@ -103,7 +100,6 @@ files:
103
100
  - lib/uringmachine.rb
104
101
  - lib/uringmachine/version.rb
105
102
  - test/helper.rb
106
- - test/test_iou.rb
107
103
  - test/test_um.rb
108
104
  - uringmachine.gemspec
109
105
  - vendor/liburing/.github/actions/codespell/stopwords
data/ext/um/iou.h DELETED
@@ -1,101 +0,0 @@
1
- #ifndef IOU_H
2
- #define IOU_H
3
-
4
- #include "ruby.h"
5
- #include <liburing.h>
6
-
7
- // debugging
8
- #define OBJ_ID(obj) (NUM2LONG(rb_funcall(obj, rb_intern("object_id"), 0)))
9
- #define INSPECT(str, obj) { printf(str); VALUE s = rb_funcall(obj, rb_intern("inspect"), 0); printf(": %s\n", StringValueCStr(s)); }
10
- #define CALLER() rb_funcall(rb_mKernel, rb_intern("caller"), 0)
11
- #define TRACE_CALLER() INSPECT("caller: ", CALLER())
12
- #define TRACE_FREE(ptr) //printf("Free %p %s:%d\n", ptr, __FILE__, __LINE__)
13
-
14
- // branching
15
- #ifndef unlikely
16
- #define unlikely(cond) __builtin_expect(!!(cond), 0)
17
- #endif
18
-
19
- #ifndef likely
20
- #define likely(cond) __builtin_expect(!!(cond), 1)
21
- #endif
22
-
23
- struct buf_ring_descriptor {
24
- struct io_uring_buf_ring *br;
25
- size_t br_size;
26
- // struct io_uring_buf_ring *buf_ring;
27
- unsigned buf_count;
28
- unsigned buf_size;
29
- char *buf_base;
30
- // size_t buf_ring_size;
31
- };
32
-
33
- #define BUFFER_RING_MAX_COUNT 10
34
-
35
- typedef struct IOURing_t {
36
- struct io_uring ring;
37
- unsigned int ring_initialized;
38
- unsigned int op_counter;
39
- unsigned int unsubmitted_sqes;
40
- VALUE pending_ops;
41
-
42
- struct buf_ring_descriptor brs[BUFFER_RING_MAX_COUNT];
43
- unsigned int br_counter;
44
- } IOURing_t;
45
-
46
- struct sa_data {
47
- struct sockaddr addr;
48
- socklen_t len;
49
- };
50
-
51
- struct read_data {
52
- VALUE buffer;
53
- int buffer_offset;
54
- unsigned bg_id;
55
- int utf8_encoding;
56
- };
57
-
58
- enum op_type {
59
- OP_accept,
60
- OP_cancel,
61
- OP_close,
62
- OP_emit,
63
- OP_nop,
64
- OP_read,
65
- OP_timeout,
66
- OP_write
67
- };
68
-
69
- typedef struct OpCtx_t {
70
- enum op_type type;
71
- VALUE spec;
72
- VALUE proc;
73
- union {
74
- struct __kernel_timespec ts;
75
- struct sa_data sa;
76
- struct read_data rd;
77
- } data;
78
- int stop_signal;
79
- } OpCtx_t;
80
-
81
- extern VALUE mIOU;
82
- extern VALUE cOpCtx;
83
-
84
- enum op_type OpCtx_type_get(VALUE self);
85
- void OpCtx_type_set(VALUE self, enum op_type type);
86
-
87
- VALUE OpCtx_spec_get(VALUE self);
88
- VALUE OpCtx_proc_get(VALUE self);
89
-
90
- struct __kernel_timespec *OpCtx_ts_get(VALUE self);
91
- void OpCtx_ts_set(VALUE self, VALUE value);
92
-
93
- struct sa_data *OpCtx_sa_get(VALUE self);
94
-
95
- struct read_data *OpCtx_rd_get(VALUE self);
96
- void OpCtx_rd_set(VALUE self, VALUE buffer, int buffer_offset, unsigned bg_id, int utf8_encoding);
97
-
98
- int OpCtx_stop_signal_p(VALUE self);
99
- void OpCtx_stop_signal_set(VALUE self);
100
-
101
- #endif // IOU_H