uringmachine 0.1 → 0.3

Sign up to get free protection for your applications and to get access to all the features.
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