uringmachine 1.0.2 → 1.0.5

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6230878feb2403c55f9e13ab254813d68b761691b4fdebd57f283a9ecfed7740
4
- data.tar.gz: a507437c50dc56d5f04e9fcfc545d0d9a5ddd09357ce9bfef758bac36fd695f6
3
+ metadata.gz: 8c4ce5de22052f74a5386dafe18ff4f021db995b6a6b120516a98e0f81783222
4
+ data.tar.gz: 64b51a3e8c2e1d5392c6cd8c4c995c09b506de554a537bad14a3074702fb0926
5
5
  SHA512:
6
- metadata.gz: bfa96e81a0165b32ec90cfe4a958b1412dfdb7bcee27b5afdff62d8b7b95e972aee7ce012d7a19bcbe425d5a0ef33e7abe89279862ba5e260629e50c8f0d64d7
7
- data.tar.gz: 399e5b9c3338325db9ac508b4a4ca4a108798c6417887eb1353120582c646d2722a616a6bcc232167b7c8bee1383aca4776610bea96f47d538a824075999408e
6
+ metadata.gz: 458534cc0ceec394b4dcf32829f62708ac1c68a8e7adf71f4bb48fe00195594805fe22ecc990edc38790b44e9609eeee00ca0c537bc316136e00bb3e87b69577
7
+ data.tar.gz: d955545beb0ba009b85ae3fa11619bf3a8869ad97474dd041e57d2c903555418e40e22eae2707a4e936563196162cec197ba55f516fb475cdb6f71da62fb9e87
data/CHANGELOG.md CHANGED
@@ -1,3 +1,16 @@
1
+ # 1.0.5 2026-10-01
2
+
3
+ - Add support for regular files in UM::IO with :file mode
4
+
5
+ # 1.0.4 2026-08-24
6
+
7
+ - Add `IO#read_uint_be` method
8
+
9
+ # 1.0.3 2026-08-24
10
+
11
+ - Add `IO#read_int_be` method
12
+ - Don't schedule fiber for async multishot ops (fixes potential segfault when using UM::IO)
13
+
1
14
  # 1.0.2 2026-05-25
2
15
 
3
16
  - Fix DNS resolver
data/TODO.md CHANGED
@@ -1,14 +1,84 @@
1
- ## immediate
1
+ ## Character scanning in UM::IO
2
+
3
+ - Refactor character scanning
4
+ - functions/macros for iterating over segments
5
+
6
+ - SIMD?
7
+ - prep work for SIMD character scanning
8
+ - we'll probably need a bit of helper code for dealing with SIMD
9
+ - a good overview here: https://mitchellh.com/writing/everyone-should-know-simd
10
+ - SIMD prep work should be done once before iterating over segments
11
+
12
+ ```c
13
+ // bitmaps for character types can be generated with a bit of Ruby:
14
+ //
15
+ // def t(r); (0..255).map { [it].pack('c') =~ r ? 1 : 0 }; end
16
+ // def tn(r); (0..255).map { [it].pack('c') =~ r ? 0 : 1 }; end
17
+ // def u64(bits); bits.reverse.join.to_i(2); end
18
+ // def p(a); a.each_slice(64).map { u64(it) }; end
2
19
 
3
- - Add `IO#http_xxx` methods
4
- - `#http_read_request_headers()`
5
- - `#http_read_body(content_length)` (-1 means chunked TE)
20
+ // usage:
21
+ //
22
+ // p(t(/[a-zA-Z0-9]/)).map { format('%016X', it) }
23
+
24
+
25
+ // /[a-zA-Z0-9]/
26
+ uint64_t alpha_numeric[] = [
27
+ 0x000000000000FFC0,
28
+ 0x7FFFFFE07FFFFFE0,
29
+ 0x0000000000000000,
30
+ 0x0000000000000000
31
+ ];
32
+
33
+ // HTTP method: /[a-zA-Z]/ (3-12 characters)
34
+ // header-key: /[a-zA-Z\-]/ ()
35
+ // path: /^($/
36
+
37
+ // check if character is in bitmap
38
+ inline int test_char(char c, uint64 *bitmap) {
39
+ return bitmap[c / 64] & (1UL << (c % 64));
40
+ }
41
+ ```
42
+
43
+ ## IO methods
44
+
45
+ - IO methods:
46
+ ```ruby
47
+ io.read_int_be(length)
48
+ io.read_uint_be(length)
49
+ io.read_int_le(length)
50
+ io.read_uint_le(length)
51
+ ```
52
+
53
+ - It might be interesting to have a DSL where we can define a frame/header structure:
54
+
55
+ ```ruby
56
+ http2_transform = UM::IO.transform do
57
+ length uint(24)
58
+ type uint(8)
59
+ flags uint(8)
60
+ stream_id uint(32)
61
+ end
62
+
63
+ frame = io.read(http2_transform)
64
+ raise if frame[:length] > MAX_LENGTH
65
+ raise if stream_id & 0x80000000
66
+ ```
67
+
68
+ - Add `IO#http1_xxx` methods
69
+ - `#http1_read_request_headers()`
70
+ - `#http1_read_body(content_length)` (-1 means chunked TE)
6
71
 
7
72
  - Add tests for support for Set in `machine#await`
8
73
  - Add tests for support for Set, Array in `machine#join`
9
- - Add `UM#read_file` for reading entire file
74
+ - Add `UM#read_file` for reading entire file
10
75
  - Add `UM#write_file` for writing entire file
11
76
 
77
+ ## SSL custom BIO:
78
+
79
+ - release GVL before calling `SSL_read`, `SSL_write`
80
+ - reacquire GVL (`rb_thread_call_with_gvl`) in `um_bio_read`, `um_bio_write`
81
+
12
82
  ## Balancing I/O with the runqueue
13
83
 
14
84
  - in some cases where there are many entries in the runqueue, this can
@@ -71,6 +141,7 @@
71
141
  - select on fibers:
72
142
  - select fibers that are done
73
143
  - select first done fiber
144
+ - we might want to rename `#select` to `#fdselect`
74
145
 
75
146
  ## ops still not implemented
76
147
 
@@ -84,21 +155,6 @@
84
155
  - madvise
85
156
  - getxattr / setxattr
86
157
 
87
- ## actors
88
-
89
- When doing a `call`, we need to provide a mailbox for the response. can this be
90
- automatic?
91
-
92
- ## Syntax / pattern for launching/supervising multiple operations
93
-
94
- Select (see above):
95
-
96
- ```ruby
97
- # select
98
- machine.join_select(*fibers) #=> [result, fiber]
99
- machine.shift_select(*queues) #=> [result, queue]
100
- ```
101
-
102
158
  ## Other abstractions
103
159
 
104
160
  - Happy eyeballs connect
@@ -108,36 +164,3 @@ machine.shift_select(*queues) #=> [result, queue]
108
164
  # ['1.1.1.1:80', '2.2.2.2:80']
109
165
  tcp_connect_he(*addrs)
110
166
  ```
111
-
112
- ## Character scanning in UM::IO
113
-
114
- ```c
115
- // bitmaps for character types can be generated with a bit of Ruby:
116
- //
117
- // def t(r); (0..255).map { [it].pack('c') =~ r ? 1 : 0 }; end
118
- // def tn(r); (0..255).map { [it].pack('c') =~ r ? 0 : 1 }; end
119
- // def u64(bits); bits.reverse.join.to_i(2); end
120
- // def p(a); a.each_slice(64).map { u64(it) }; end
121
-
122
- // usage:
123
- //
124
- // p(t(/[a-zA-Z0-9]/)).map { format('%016X', it) }
125
-
126
-
127
- // /[a-zA-Z0-9]/
128
- uint64_t alpha_numeric[] = [
129
- 0x000000000000FFC0,
130
- 0x7FFFFFE07FFFFFE0,
131
- 0x0000000000000000,
132
- 0x0000000000000000
133
- ];
134
-
135
- // HTTP method: /[a-zA-Z]/ (3-12 characters)
136
- // header-key: /[a-zA-Z\-]/ ()
137
- // path: /^($/
138
-
139
- // check if character is in bitmap
140
- inline int test_char(char c, uint64 *bitmap) {
141
- return bitmap[c / 64] & (1UL << (c % 64));
142
- }
143
- ```
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/setup'
4
+
5
+ require 'uringmachine'
6
+ require 'uringmachine/fiber_scheduler'
7
+ require 'irb'
8
+
9
+ @machine = UringMachine.new
10
+ @scheduler = UM::FiberScheduler.new(@machine)
11
+ Fiber.set_scheduler @scheduler
12
+
13
+ f = @machine.spin { IRB.start }
14
+ @machine.join(f)
data/ext/um/um.c CHANGED
@@ -192,7 +192,7 @@ static inline void um_process_cqe(struct um *machine, struct io_uring_cqe *cqe)
192
192
  um_op_multishot_results_push(machine, op, cqe->res, cqe->flags);
193
193
 
194
194
  op->flags |= done ? (OP_F_CQE_SEEN | OP_F_CQE_DONE) : OP_F_CQE_SEEN;
195
- if (!OP_SCHEDULED_P(op)) um_schedule_op(machine, op);
195
+ if (!(op->flags & (OP_F_SCHEDULED | OP_F_ASYNC))) um_schedule_op(machine, op);
196
196
  }
197
197
  else {
198
198
  // single shot
data/ext/um/um.h CHANGED
@@ -83,6 +83,7 @@ enum um_io_mode {
83
83
  IO_FD,
84
84
  IO_SOCKET,
85
85
  IO_SSL,
86
+ IO_FILE,
86
87
  IO_STRING,
87
88
  IO_IO_BUFFER
88
89
  };
@@ -426,6 +427,8 @@ VALUE io_read(struct um_io *io, VALUE out_buffer, ssize_t len, size_t inc, int s
426
427
  VALUE io_read_to_delim(struct um_io *io, VALUE out_buffer, VALUE delim, ssize_t maxlen);
427
428
  void io_skip(struct um_io *io, size_t inc, int safe_inc);
428
429
  void io_read_each(struct um_io *io);
430
+ VALUE io_read_int_be(struct um_io *io, size_t len);
431
+ VALUE io_read_uint_be(struct um_io *io, size_t len);
429
432
  size_t io_write_raw(struct um_io *io, const char *buffer, size_t len);
430
433
  VALUE io_writev(struct um_io *io, int argc, VALUE *argv);
431
434
  VALUE resp_read(struct um_io *io, VALUE out_buffer);
data/ext/um/um_io.c CHANGED
@@ -74,13 +74,12 @@ void um_io_cleanup(struct um_io *io) {
74
74
  um_segment_checkin(io->machine, io->head);
75
75
  io->head = next;
76
76
  }
77
+ io->tail = NULL;
77
78
  io->pending_bytes = 0;
78
79
  }
79
80
 
80
81
  // returns true if case of ENOBUFS error, sets more to true if more data forthcoming
81
- inline int io_process_segments(
82
- struct um_io *io, size_t *total_bytes, int *more) {
83
-
82
+ inline int io_process_segments(struct um_io *io, size_t *total_bytes, int *more) {
84
83
  *more = 0;
85
84
  struct um_op_result *result = &io->op->result;
86
85
  io->op->flags &= ~OP_F_CQE_SEEN;
@@ -125,6 +124,7 @@ void io_clear(struct um_io *io) {
125
124
  um_segment_checkin(io->machine, io->head);
126
125
  io->head = next;
127
126
  }
127
+ io->tail = NULL;
128
128
  io->pending_bytes = 0;
129
129
 
130
130
  if (io->working_buffer) {
@@ -133,7 +133,31 @@ void io_clear(struct um_io *io) {
133
133
  }
134
134
  }
135
135
 
136
- inline void io_await_segments(struct um_io *io) {
136
+ inline void io_handle_enobufs(struct um_io *io) {
137
+ // we want to restart only if in case we don't already have lots of pending
138
+ // bytes in the stream. This heuristic might need a closer look to determine
139
+ // if this is the correct behavior.
140
+ int should_restart = io->pending_bytes < (io->machine->bp_buffer_size * 4);
141
+
142
+ // fprintf(stderr, "%p enobufs total: %ld pending: %ld threshold: %ld bc: %d (same: %d, restart: %d)\n",
143
+ // io,
144
+ // total_bytes, io->pending_bytes, io->machine->bp_commit_level,
145
+ // io->machine->bp_buffer_count,
146
+ // same_threshold, should_restart
147
+ // );
148
+
149
+ if (should_restart) {
150
+ // If multiple IO ops are happening at the same time, they'll all get
151
+ // ENOBUFS! We track the buffer pool commit level in the op in order to
152
+ // prevent running bp_handle_enobufs() more than once.
153
+ if (io->op->bp_commit_level == io->machine->bp_commit_level)
154
+ bp_handle_enobufs(io->machine);
155
+ }
156
+ um_op_release(io->machine, io->op);
157
+ io->op = NULL;
158
+ }
159
+
160
+ static inline void io_await_segments(struct um_io *io) {
137
161
  if (unlikely(!io->op)) io_multishot_op_start(io);
138
162
 
139
163
  if (!OP_CQE_SEEN_P(io->op)) {
@@ -156,40 +180,12 @@ int io_get_more_segments_bp(struct um_io *io) {
156
180
  io_await_segments(io);
157
181
  enobufs = io_process_segments(io, &total_bytes, &more);
158
182
  um_op_multishot_results_clear(io->machine, io->op);
159
- if (unlikely(enobufs)) {
160
- int should_restart = io->pending_bytes < (io->machine->bp_buffer_size * 4);
161
- // int same_threshold = io->op->bp_commit_level == io->machine->bp_commit_level;
162
-
163
- // fprintf(stderr, "%p enobufs total: %ld pending: %ld threshold: %ld bc: %d (same: %d, restart: %d)\n",
164
- // io,
165
- // total_bytes, io->pending_bytes, io->machine->bp_commit_level,
166
- // io->machine->bp_buffer_count,
167
- // same_threshold, should_restart
168
- // );
169
-
170
- // If multiple IO ops are happening at the same time, they'll all
171
- // get ENOBUFS! We track the commit threshold in the op in order to
172
- // prevent running bp_handle_enobufs() more than once.
173
-
174
- if (should_restart) {
175
- if (io->op->bp_commit_level == io->machine->bp_commit_level)
176
- bp_handle_enobufs(io->machine);
177
-
178
- um_op_release(io->machine, io->op);
179
- io->op = NULL;
180
- }
181
- else {
182
- um_op_release(io->machine, io->op);
183
- io->op = NULL;
184
- }
185
183
 
186
- if (total_bytes) return total_bytes;
187
- }
188
- else {
189
- if (more)
190
- io->op->flags &= ~OP_F_CQE_SEEN;
191
- if (total_bytes || io->eof) return total_bytes;
192
- }
184
+ if (unlikely(enobufs))
185
+ io_handle_enobufs(io);
186
+ else if (more)
187
+ io->op->flags &= ~OP_F_CQE_SEEN;
188
+ if (total_bytes || io->eof) return total_bytes;
193
189
  }
194
190
  }
195
191
 
@@ -212,11 +208,54 @@ int io_get_more_segments_ssl(struct um_io *io) {
212
208
  return 1;
213
209
  }
214
210
 
211
+ int io_get_more_segments_file(struct um_io *io) {
212
+ if (!io->working_buffer)
213
+ io->working_buffer = bp_buffer_checkout(io->machine);
214
+
215
+ char *ptr = io->working_buffer->buf + io->working_buffer->pos;
216
+ size_t maxlen = io->working_buffer->len - io->working_buffer->pos;
217
+
218
+ struct um_op *op = um_op_acquire(io->machine);
219
+ um_prep_op(io->machine, op, OP_READ, 2, 0);
220
+ struct io_uring_sqe *sqe = um_get_sqe(io->machine, op);
221
+ io_uring_prep_read(sqe, io->fd, ptr, maxlen, -1);
222
+
223
+ VALUE ret = um_yield(io->machine);
224
+ int has_more = 0;
225
+
226
+ if (likely(um_verify_op_completion(io->machine, op, true))) {
227
+ int res = op->result.res;
228
+
229
+ if (res == 0) {
230
+ bp_buffer_checkin(io->machine, io->working_buffer);
231
+ io->working_buffer = NULL;
232
+ goto done;
233
+ }
234
+
235
+ struct um_segment *segment = bp_buffer_consume(io->machine, io->working_buffer, res);
236
+ io_add_segment(io, segment);
237
+
238
+ if ((size_t)res == maxlen) {
239
+ bp_buffer_checkin(io->machine, io->working_buffer);
240
+ io->working_buffer = NULL;
241
+ }
242
+ else has_more = 1;
243
+ }
244
+ um_op_release(io->machine, op);
245
+
246
+ done:
247
+ RAISE_IF_EXCEPTION(ret);
248
+ RB_GC_GUARD(ret);
249
+ return has_more;
250
+ }
251
+
215
252
  int io_get_more_segments(struct um_io *io) {
216
253
  switch (io->mode) {
217
254
  case IO_FD:
218
255
  case IO_SOCKET:
219
256
  return io_get_more_segments_bp(io);
257
+ case IO_FILE:
258
+ return io_get_more_segments_file(io);
220
259
  case IO_SSL:
221
260
  return io_get_more_segments_ssl(io);
222
261
  default:
@@ -254,9 +293,7 @@ inline void io_skip(struct um_io *io, size_t inc, int safe_inc) {
254
293
  io->pending_bytes -= inc_len;
255
294
  if (io->pos == io->head->len) {
256
295
  io_shift_head(io);
257
- if (inc && safe_inc && !io->head) {
258
- if (!io_get_more_segments(io)) break;
259
- }
296
+ if (inc && safe_inc && !io->head && !io_get_more_segments(io)) break;
260
297
  }
261
298
  }
262
299
  }
@@ -285,6 +322,58 @@ inline void io_read_each(struct um_io *io) {
285
322
  RB_GC_GUARD(buffer);
286
323
  }
287
324
 
325
+ VALUE io_read_int_be(struct um_io *io, size_t len) {
326
+ if (unlikely(io->eof && !io->head)) return Qnil;
327
+ if (!io->tail && !io_get_more_segments(io)) return Qnil;
328
+
329
+ long value = 0;
330
+ struct um_segment *current = io->head;
331
+
332
+ while (true) {
333
+ size_t segment_len = current->len - io->pos;
334
+ int pos = 0;
335
+ while (segment_len > 0 && len > 0) {
336
+ value = value * 0x100 + *(unsigned char *)(current->ptr + io->pos + pos);
337
+ len--;
338
+ segment_len--;
339
+ pos++;
340
+ }
341
+ if (pos > 0) io_skip(io, pos, true);
342
+
343
+ if (len == 0) return LONG2NUM(value);
344
+
345
+ if (!io->head && !io_get_more_segments(io))
346
+ return Qnil;
347
+ current = io->head;
348
+ }
349
+ }
350
+
351
+ VALUE io_read_uint_be(struct um_io *io, size_t len) {
352
+ if (unlikely(io->eof && !io->head)) return Qnil;
353
+ if (!io->tail && !io_get_more_segments(io)) return Qnil;
354
+
355
+ unsigned long value = 0;
356
+ struct um_segment *current = io->head;
357
+
358
+ while (true) {
359
+ size_t segment_len = current->len - io->pos;
360
+ int pos = 0;
361
+ while (segment_len > 0 && len > 0) {
362
+ value = value * 0x100 + *(unsigned char *)(current->ptr + io->pos + pos);
363
+ len--;
364
+ segment_len--;
365
+ pos++;
366
+ }
367
+ if (pos > 0) io_skip(io, pos, true);
368
+
369
+ if (len == 0) return ULONG2NUM(value);
370
+
371
+ if (!io->head && !io_get_more_segments(io))
372
+ return Qnil;
373
+ current = io->head;
374
+ }
375
+ }
376
+
288
377
  inline void io_copy(struct um_io *io, char *dest, size_t len) {
289
378
  while (len) {
290
379
  char *segment_ptr = io->head->ptr + io->pos;
@@ -316,7 +405,7 @@ VALUE io_consume_string(struct um_io *io, VALUE out_buffer, size_t len, size_t i
316
405
  char *dest = RSTRING_PTR(str);
317
406
 
318
407
  io_copy(io, dest, len);
319
- io_skip(io, inc, safe_inc);
408
+ if (inc) io_skip(io, inc, safe_inc);
320
409
  return str;
321
410
  RB_GC_GUARD(str);
322
411
  }
@@ -392,14 +481,15 @@ VALUE io_read(struct um_io *io, VALUE out_buffer, ssize_t len, size_t inc, int s
392
481
 
393
482
  while (true) {
394
483
  size_t segment_len = current->len - pos;
395
- if (abs_len && segment_len > remaining_len) {
484
+ if (abs_len && segment_len > (size_t)remaining_len) {
396
485
  segment_len = remaining_len;
397
486
  }
398
487
  total_len += segment_len;
399
488
  if (abs_len) {
400
489
  remaining_len -= segment_len;
401
- if (!remaining_len)
490
+ if (!remaining_len) {
402
491
  return io_consume_string(io, out_buffer, total_len, inc, safe_inc);
492
+ }
403
493
  }
404
494
 
405
495
  if (!current->next) {
data/ext/um/um_io_class.c CHANGED
@@ -4,6 +4,7 @@ VALUE cIO;
4
4
  VALUE eIORESPError;
5
5
 
6
6
  VALUE SYM_fd;
7
+ VALUE SYM_file;
7
8
  VALUE SYM_socket;
8
9
  VALUE SYM_ssl;
9
10
 
@@ -87,6 +88,7 @@ static inline void io_set_target(struct um_io *conn, VALUE target, enum um_io_mo
87
88
  conn->mode = mode;
88
89
  switch (mode) {
89
90
  case IO_FD:
91
+ case IO_FILE:
90
92
  case IO_SOCKET:
91
93
  conn->fd = NUM2INT(target);
92
94
  return;
@@ -109,6 +111,8 @@ static inline void io_setup(struct um_io *conn, VALUE target, VALUE mode) {
109
111
  }
110
112
  else if (mode == SYM_fd)
111
113
  io_set_target(conn, target, IO_FD);
114
+ else if (mode == SYM_file)
115
+ io_set_target(conn, target, IO_FILE);
112
116
  else if (mode == SYM_socket)
113
117
  io_set_target(conn, target, IO_SOCKET);
114
118
  else if (mode == SYM_ssl)
@@ -157,10 +161,11 @@ VALUE IO_initialize(int argc, VALUE *argv, VALUE self) {
157
161
  VALUE IO_mode(VALUE self) {
158
162
  struct um_io *conn = um_get_io(self);
159
163
  switch (conn->mode) {
160
- case IO_FD: return SYM_fd;
161
- case IO_SOCKET: return SYM_socket;
162
- case IO_SSL: return SYM_ssl;
163
- default: return Qnil;
164
+ case IO_FD: return SYM_fd;
165
+ case IO_FILE: return SYM_file;
166
+ case IO_SOCKET: return SYM_socket;
167
+ case IO_SSL: return SYM_ssl;
168
+ default: return Qnil;
164
169
  }
165
170
  return Qnil;
166
171
  }
@@ -242,6 +247,42 @@ VALUE IO_read_each(VALUE self) {
242
247
  return self;
243
248
  }
244
249
 
250
+ #define MAX_INT_SIZE 8
251
+
252
+ /* call-seq:
253
+ * conn.read_int_be(len) -> int
254
+ *
255
+ * Reads a signed integer from the target in big endian notation. The len
256
+ * parameter indicates the integer field length in bytes, with an upper limit of
257
+ * 8 bytes. If EOF is encountered before enough bytes are read, returns nil.
258
+ *
259
+ * @param len [Integer] number of bytes to read
260
+ * @return [Integer, nil] integer value or nil if EOF
261
+ */
262
+ VALUE IO_read_int_be(VALUE self, VALUE len) {
263
+ struct um_io *conn = um_get_io(self);
264
+ size_t len_i = NUM2UINT(len);
265
+ if (unlikely(len_i > MAX_INT_SIZE)) rb_raise(eUMError, "Invalid length given");
266
+ return io_read_int_be(conn, len_i);
267
+ }
268
+
269
+ /* call-seq:
270
+ * conn.read_uint_be(len) -> int
271
+ *
272
+ * Reads an unsigned integer from the target in big endian notation. The len
273
+ * parameter indicates the integer field length in bytes, with an upper limit
274
+ * of 8 bytes. If EOF is encountered before enough bytes are read, returns nil.
275
+ *
276
+ * @param len [Integer] number of bytes to read
277
+ * @return [Integer, nil] integer value or nil if EOF
278
+ */
279
+ VALUE IO_read_uint_be(VALUE self, VALUE len) {
280
+ struct um_io *conn = um_get_io(self);
281
+ size_t len_i = NUM2UINT(len);
282
+ if (unlikely(len_i > MAX_INT_SIZE)) rb_raise(eUMError, "Invalid length given");
283
+ return io_read_uint_be(conn, len_i);
284
+ }
285
+
245
286
  /* call-seq:
246
287
  * conn.write(*bufs) -> len
247
288
  *
@@ -375,6 +416,9 @@ void Init_IO(void) {
375
416
  rb_define_method(cIO, "skip", IO_skip, 1);
376
417
  rb_define_method(cIO, "read_each", IO_read_each, 0);
377
418
 
419
+ rb_define_method(cIO, "read_int_be", IO_read_int_be, 1);
420
+ rb_define_method(cIO, "read_uint_be", IO_read_uint_be, 1);
421
+
378
422
  rb_define_method(cIO, "write", IO_write, -1);
379
423
 
380
424
  rb_define_method(cIO, "resp_read", IO_resp_read, 0);
@@ -388,7 +432,8 @@ void Init_IO(void) {
388
432
 
389
433
  eIORESPError = rb_define_class_under(cIO, "RESPError", rb_eStandardError);
390
434
 
391
- SYM_fd = ID2SYM(rb_intern("fd"));
392
- SYM_socket = ID2SYM(rb_intern("socket"));
435
+ SYM_fd = ID2SYM(rb_intern("fd"));
436
+ SYM_file = ID2SYM(rb_intern("file"));
437
+ SYM_socket = ID2SYM(rb_intern("socket"));
393
438
  SYM_ssl = ID2SYM(rb_intern("ssl"));
394
439
  }
data/ext/um/um_op.c CHANGED
@@ -156,9 +156,8 @@ inline void um_op_multishot_results_push(struct um *machine, struct um_op *op, _
156
156
  result->res = res;
157
157
  result->flags = flags;
158
158
  result->next = NULL;
159
- if (op->flags & OP_F_BUFFER_POOL && res >= 0) {
159
+ if (op->flags & OP_F_BUFFER_POOL && res >= 0)
160
160
  result->segment = bp_get_op_result_segment(machine, op, res, flags);
161
- }
162
161
 
163
162
  op->multishot_result_tail = result;
164
163
  op->multishot_result_count++;
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class UringMachine
4
- VERSION = '1.0.2'
4
+ VERSION = '1.0.5'
5
5
  end
data/test/test_io.rb CHANGED
@@ -4,6 +4,7 @@ require_relative 'helper'
4
4
  require 'securerandom'
5
5
  require 'openssl'
6
6
  require 'localhost/authority'
7
+ require 'tempfile'
7
8
 
8
9
  class IOBaseTest < UMBaseTest
9
10
  attr_reader :conn
@@ -305,6 +306,22 @@ class IOTest < IOBaseTest
305
306
  machine.terminate(f)
306
307
  machine.join(f)
307
308
  end
309
+
310
+ def test_io_read_segfault
311
+ machine.write(@wfd, "abcd")
312
+ conn.read(4)
313
+
314
+ machine.write(@wfd, "\x00\x01\x05\xdf")
315
+ conn.read(4)
316
+
317
+ f = machine.spin do
318
+ machine.write(@wfd, "\xde")
319
+ machine.snooze
320
+ machine.write(@wfd, "\x90\x03")
321
+ end
322
+ value = conn.read(3)
323
+ assert_equal "\xde\x90\x03".b, value
324
+ end
308
325
  end
309
326
 
310
327
  class IOWriteTest < UMBaseTest
@@ -699,6 +716,23 @@ class IOModeTest < UMBaseTest
699
716
  sock2&.close rescue nil
700
717
  end
701
718
 
719
+ def test_io_file_mode
720
+ fn = Tempfile.new('uringmachine_test_io_from_file').path
721
+ IO.write(fn, (0..9).to_a.join)
722
+ fd = machine.open(fn, UM::O_RDONLY)
723
+ io = machine.io(fd, :file)
724
+ buf = io.read(3)
725
+ assert_equal '012', buf
726
+
727
+ buf = io.read(5)
728
+ assert_equal '34567', buf
729
+
730
+ buf = io.read(-4)
731
+ assert_equal '89', buf
732
+ ensure
733
+ machine.close(fd) if fd
734
+ end
735
+
702
736
  def test_io_socket_mode_non_socket
703
737
  r, w = UM.pipe
704
738
  machine.write(w, 'foobar')
@@ -806,3 +840,113 @@ class IOByteCountsTest < IOBaseTest
806
840
  assert_equal 0, conn.pending
807
841
  end
808
842
  end
843
+
844
+ class IOIntTest < IOBaseTest
845
+ def test_io_read_int_be
846
+ machine.write(@wfd, "abcd")
847
+ conn.read(4)
848
+
849
+ machine.write(@wfd, "\x00\x01\x05\xdf")
850
+ value = conn.read_int_be(2)
851
+ assert_equal 0x0001, value
852
+ value = conn.read_int_be(2)
853
+ assert_equal 0x05df, value
854
+
855
+ machine.write(@wfd, "\x00\x01\x05\xdf")
856
+ value = conn.read_int_be(4)
857
+ assert_equal 0x000105df, value
858
+
859
+ machine.write(@wfd, "\xde\xfa")
860
+ machine.snooze
861
+ machine.write(@wfd, "\xdd\x03")
862
+ machine.snooze
863
+ value = conn.read_int_be(4)
864
+ assert_equal 0xdefadd03, value
865
+
866
+ machine.write(@wfd, "\xed\xcb\xa9")
867
+ value = conn.read_int_be(3)
868
+ assert_equal 0xedcba9, value
869
+
870
+ f = machine.spin do
871
+ machine.write(@wfd, "\xde")
872
+ machine.snooze
873
+ machine.write(@wfd, "\x90\x03")
874
+ end
875
+ value = conn.read_int_be(3)
876
+ machine.join(f)
877
+ assert_equal 0xde9003, value
878
+
879
+ machine.write(@wfd, "\xde\xfa")
880
+ machine.close(@wfd)
881
+ assert_nil conn.read_int_be(4)
882
+ end
883
+
884
+ def test_io_read_int_be_invalid_length
885
+ machine.write(@wfd, (1..8).to_a.pack('C*'))
886
+ assert_raises(UM::Error) { conn.read_int_be(-1) }
887
+ assert_raises(UM::Error) { conn.read_int_be(20) }
888
+ assert_raises(UM::Error) { conn.read_int_be(9) }
889
+
890
+ # 8 is my limit on Schnitzengruben
891
+ value = conn.read_int_be(8)
892
+ assert_equal 0x0102030405060708, value
893
+ end
894
+
895
+ def test_io_read_uint_be
896
+ machine.write(@wfd, "abcd")
897
+ conn.read(4)
898
+
899
+ machine.write(@wfd, "\x00\x01\x05\xdf")
900
+ value = conn.read_uint_be(2)
901
+ assert_equal 0x0001, value
902
+ value = conn.read_uint_be(2)
903
+ assert_equal 0x05df, value
904
+
905
+ machine.write(@wfd, "\x00\x01\x05\xdf")
906
+ value = conn.read_uint_be(4)
907
+ assert_equal 0x000105df, value
908
+
909
+ machine.write(@wfd, "\xde\xfa")
910
+ machine.snooze
911
+ machine.write(@wfd, "\xdd\x03")
912
+ machine.snooze
913
+ value = conn.read_uint_be(4)
914
+ assert_equal 0xdefadd03, value
915
+
916
+ machine.write(@wfd, "\xed\xcb\xa9")
917
+ value = conn.read_uint_be(3)
918
+ assert_equal 0xedcba9, value
919
+
920
+ f = machine.spin do
921
+ machine.write(@wfd, "\xde")
922
+ machine.snooze
923
+ machine.write(@wfd, "\x90\x03")
924
+ end
925
+ value = conn.read_uint_be(3)
926
+ machine.join(f)
927
+ assert_equal 0xde9003, value
928
+
929
+ machine.write(@wfd, "\xff" * 8)
930
+ value = conn.read_uint_be(8)
931
+ assert_equal 0xffffffffffffffff, value
932
+
933
+ machine.write(@wfd, "\x80\x00\x00\x00\x00\x00\x00\x00")
934
+ value = conn.read_uint_be(8)
935
+ assert_equal 0x8000000000000000, value
936
+
937
+ machine.write(@wfd, "\xde\xfa")
938
+ machine.close(@wfd)
939
+ assert_nil conn.read_uint_be(4)
940
+ end
941
+
942
+ def test_io_read_uint_be_invalid_length
943
+ machine.write(@wfd, (0xf1..0xf8).to_a.pack('C*'))
944
+ assert_raises(UM::Error) { conn.read_uint_be(-1) }
945
+ assert_raises(UM::Error) { conn.read_uint_be(20) }
946
+ assert_raises(UM::Error) { conn.read_uint_be(9) }
947
+
948
+ # 8 is my limit on Schnitzengruben
949
+ value = conn.read_uint_be(8)
950
+ assert_equal 0xf1f2f3f4f5f6f7f8, value
951
+ end
952
+ end
data/test/test_um.rb CHANGED
@@ -3277,6 +3277,23 @@ class FileWatchTest < UMBaseTest
3277
3277
  name: 'foo.txt'
3278
3278
  }
3279
3279
  ], events
3280
+
3281
+ fn = File.join(@root, '.bar')
3282
+ IO.write(fn, 'barbar')
3283
+
3284
+ events = machine.inotify_get_events(fd)
3285
+ assert_equal [
3286
+ {
3287
+ wd: wd,
3288
+ mask: UM::IN_CREATE,
3289
+ name: '.bar'
3290
+ },
3291
+ {
3292
+ wd: wd,
3293
+ mask: UM::IN_CLOSE_WRITE,
3294
+ name: '.bar'
3295
+ }
3296
+ ], events
3280
3297
  end
3281
3298
 
3282
3299
  def write_file(fn, data)
@@ -3386,6 +3403,25 @@ class FileWatchTest < UMBaseTest
3386
3403
  { mask: UM::IN_CREATE, fn: File.join(@root, 'bar/baz.txt') },
3387
3404
  { mask: UM::IN_CLOSE_WRITE, fn: File.join(@root, 'bar/baz.txt') },
3388
3405
  ], events
3406
+
3407
+ fn = File.join(@root, 'bar/.dotfile')
3408
+ write_file(fn, 'dotdot')
3409
+ machine.sleep(0.05)
3410
+ assert_equal [
3411
+ { mask: UM::IN_CLOSE_WRITE, fn: File.join(@root, 'foo.txt') },
3412
+ { mask: UM::IN_CLOSE_WRITE, fn: File.join(@root, 'foo/baz.txt') },
3413
+ { mask: UM::IN_CREATE | UM::IN_ISDIR, fn: File.join(@root, 'bar') },
3414
+ { mask: UM::IN_CREATE, fn: File.join(@root, 'bar/baz.txt') },
3415
+ { mask: UM::IN_CLOSE_WRITE, fn: File.join(@root, 'bar/baz.txt') },
3416
+ { mask: UM::IN_DELETE, fn: File.join(@root, 'foo/baz.txt') },
3417
+ { mask: UM::IN_DELETE, fn: File.join(@root, 'bar/baz.txt') },
3418
+ { mask: UM::IN_DELETE | UM::IN_ISDIR, fn: File.join(@root, 'bar') },
3419
+ { mask: UM::IN_CREATE | UM::IN_ISDIR, fn: File.join(@root, 'bar') },
3420
+ { mask: UM::IN_CREATE, fn: File.join(@root, 'bar/baz.txt') },
3421
+ { mask: UM::IN_CLOSE_WRITE, fn: File.join(@root, 'bar/baz.txt') },
3422
+ { mask: UM::IN_CREATE, fn: File.join(@root, 'bar/.dotfile') },
3423
+ { mask: UM::IN_CLOSE_WRITE, fn: File.join(@root, 'bar/.dotfile') },
3424
+ ], events
3389
3425
  ensure
3390
3426
  machine.schedule(f, UM::Terminate.new)
3391
3427
  machine.join(f)
@@ -3397,22 +3433,26 @@ class SetChildSubreaperTest < Minitest::Test
3397
3433
  r, w = IO.pipe
3398
3434
  UM.pr_set_child_subreaper(true)
3399
3435
 
3400
- child_pid = fork {
3436
+ child_pid = fork do
3401
3437
  r2, w2 = IO.pipe
3402
- pid = fork {
3438
+ pid = fork do
3403
3439
  r.close
3404
3440
  w.close
3405
3441
  w2.close
3406
3442
  r2.read
3407
3443
  r2.close
3408
3444
  sleep(0.01)
3409
- }
3445
+ ensure
3446
+ exit!
3447
+ end
3410
3448
  w << pid
3411
3449
  w.close
3412
3450
  r2.close
3413
3451
  w2 << 'done'
3414
3452
  w2.close
3415
- }
3453
+ ensure
3454
+ exit!
3455
+ end
3416
3456
  Process.wait(child_pid)
3417
3457
 
3418
3458
  w.close
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: uringmachine
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.2
4
+ version: 1.0.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sharon Rosner
@@ -103,6 +103,7 @@ files:
103
103
  - examples/http_server.rb
104
104
  - examples/inout.rb
105
105
  - examples/io_uring_simple.c
106
+ - examples/irb_fiber.rb
106
107
  - examples/nc.rb
107
108
  - examples/nc_ssl.rb
108
109
  - examples/pg.rb