uringmachine 1.0.4 → 1.0.6

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: dea49e5d6f13c7919ccaa9765428bfc81058ad1fab0b3f1df07d0fd60a5b1cd3
4
- data.tar.gz: 3cd06ca52b40e891807b589bc7d7f97cfc67be2d491e5855a9624de959f160a1
3
+ metadata.gz: 1c37bb6e6971036cefa78d76efbb4425f843087b4b4f5e24fe16e695920746b8
4
+ data.tar.gz: 144e109b5dfa199adda69387c7229c78393f3ee8b0a45f545db76934f896ef2c
5
5
  SHA512:
6
- metadata.gz: 06ef57453cc0986a0b047b9d91ab5add06cf64145d5fd17ba5d85edf1b01f1bde0cea060d3a54bc87cadc9fc4c0137350ad704b0f0c1421f51c9ef9158eed8c5
7
- data.tar.gz: 65a8701e871902c7e726cdaebacebef4bde5c111b6a3393b92d40a9b5a3005535f240cb30574f34fa41048206430b8faa41203d23a2fab603285c88eb9e766b4
6
+ metadata.gz: 3d19ae0d9c4056c691a8b9c93901f759e2b149ebd275d8a5d1e0dcfc33e2308eef0e583541b2ce90b69d55efc20b4ab0c7415f4e44d0c9753b07e733bc965e90
7
+ data.tar.gz: 697653d6d9120bcbece655bf530e1e87e9fc461e2052bd20b8666f344440ee11898b569c3d98a7477c39dd11bdf6ff03fc6b1e08f953c257a84fb0aea58a882f
data/CHANGELOG.md CHANGED
@@ -1,3 +1,11 @@
1
+ # 1.0.6 2026-10-01
2
+
3
+ - Add `UM#file_read` convenience method
4
+
5
+ # 1.0.5 2026-10-01
6
+
7
+ - Add support for regular files in UM::IO with :file mode
8
+
1
9
  # 1.0.4 2026-08-24
2
10
 
3
11
  - Add `IO#read_uint_be` method
data/TODO.md CHANGED
@@ -1,4 +1,46 @@
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
19
+
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
2
44
 
3
45
  - IO methods:
4
46
  ```ruby
@@ -23,30 +65,20 @@
23
65
  raise if stream_id & 0x80000000
24
66
  ```
25
67
 
26
- - SSL kTLS:
27
- - discussion: https://share.gemini.google/tPWiFpr3bWeF
28
- - API:
29
-
30
- ```ruby
31
- machine.ssl_setup_ktls(fd)
32
-
33
- # then we can just use normal send/recv
34
- machine.send(fd, "foo!")
35
- ```
36
-
37
- - SSL custom BIO:
38
- - release GVL before calling `SSL_read`, `SSL_write`
39
- - reacquire GVL (`rb_thread_call_with_gvl`) in `um_bio_read`, `um_bio_write`
40
-
41
- - Add `IO#http_xxx` methods
42
- - `#http_read_request_headers()`
43
- - `#http_read_body(content_length)` (-1 means chunked TE)
68
+ - Add `IO#http1_xxx` methods
69
+ - `#http1_read_request_headers()`
70
+ - `#http1_read_body(content_length)` (-1 means chunked TE)
44
71
 
45
72
  - Add tests for support for Set in `machine#await`
46
73
  - Add tests for support for Set, Array in `machine#join`
47
- - Add `UM#read_file` for reading entire file
74
+ - Add `UM#read_file` for reading entire file
48
75
  - Add `UM#write_file` for writing entire file
49
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
+
50
82
  ## Balancing I/O with the runqueue
51
83
 
52
84
  - in some cases where there are many entries in the runqueue, this can
@@ -109,6 +141,7 @@
109
141
  - select on fibers:
110
142
  - select fibers that are done
111
143
  - select first done fiber
144
+ - we might want to rename `#select` to `#fdselect`
112
145
 
113
146
  ## ops still not implemented
114
147
 
@@ -131,36 +164,3 @@
131
164
  # ['1.1.1.1:80', '2.2.2.2:80']
132
165
  tcp_connect_he(*addrs)
133
166
  ```
134
-
135
- ## Character scanning in UM::IO
136
-
137
- ```c
138
- // bitmaps for character types can be generated with a bit of Ruby:
139
- //
140
- // def t(r); (0..255).map { [it].pack('c') =~ r ? 1 : 0 }; end
141
- // def tn(r); (0..255).map { [it].pack('c') =~ r ? 0 : 1 }; end
142
- // def u64(bits); bits.reverse.join.to_i(2); end
143
- // def p(a); a.each_slice(64).map { u64(it) }; end
144
-
145
- // usage:
146
- //
147
- // p(t(/[a-zA-Z0-9]/)).map { format('%016X', it) }
148
-
149
-
150
- // /[a-zA-Z0-9]/
151
- uint64_t alpha_numeric[] = [
152
- 0x000000000000FFC0,
153
- 0x7FFFFFE07FFFFFE0,
154
- 0x0000000000000000,
155
- 0x0000000000000000
156
- ];
157
-
158
- // HTTP method: /[a-zA-Z]/ (3-12 characters)
159
- // header-key: /[a-zA-Z\-]/ ()
160
- // path: /^($/
161
-
162
- // check if character is in bitmap
163
- inline int test_char(char c, uint64 *bitmap) {
164
- return bitmap[c / 64] & (1UL << (c % 64));
165
- }
166
- ```
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
  };
data/ext/um/um_io.c CHANGED
@@ -157,7 +157,7 @@ inline void io_handle_enobufs(struct um_io *io) {
157
157
  io->op = NULL;
158
158
  }
159
159
 
160
- inline void io_await_segments(struct um_io *io) {
160
+ static inline void io_await_segments(struct um_io *io) {
161
161
  if (unlikely(!io->op)) io_multishot_op_start(io);
162
162
 
163
163
  if (!OP_CQE_SEEN_P(io->op)) {
@@ -208,11 +208,54 @@ int io_get_more_segments_ssl(struct um_io *io) {
208
208
  return 1;
209
209
  }
210
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
+
211
252
  int io_get_more_segments(struct um_io *io) {
212
253
  switch (io->mode) {
213
254
  case IO_FD:
214
255
  case IO_SOCKET:
215
256
  return io_get_more_segments_bp(io);
257
+ case IO_FILE:
258
+ return io_get_more_segments_file(io);
216
259
  case IO_SSL:
217
260
  return io_get_more_segments_ssl(io);
218
261
  default:
@@ -438,14 +481,15 @@ VALUE io_read(struct um_io *io, VALUE out_buffer, ssize_t len, size_t inc, int s
438
481
 
439
482
  while (true) {
440
483
  size_t segment_len = current->len - pos;
441
- if (abs_len && segment_len > remaining_len) {
484
+ if (abs_len && segment_len > (size_t)remaining_len) {
442
485
  segment_len = remaining_len;
443
486
  }
444
487
  total_len += segment_len;
445
488
  if (abs_len) {
446
489
  remaining_len -= segment_len;
447
- if (!remaining_len)
490
+ if (!remaining_len) {
448
491
  return io_consume_string(io, out_buffer, total_len, inc, safe_inc);
492
+ }
449
493
  }
450
494
 
451
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
  }
@@ -427,7 +432,8 @@ void Init_IO(void) {
427
432
 
428
433
  eIORESPError = rb_define_class_under(cIO, "RESPError", rb_eStandardError);
429
434
 
430
- SYM_fd = ID2SYM(rb_intern("fd"));
431
- 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"));
432
438
  SYM_ssl = ID2SYM(rb_intern("ssl"));
433
439
  }
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class UringMachine
4
- VERSION = '1.0.4'
4
+ VERSION = '1.0.6'
5
5
  end
data/lib/uringmachine.rb CHANGED
@@ -50,7 +50,7 @@ class UringMachine
50
50
  end
51
51
 
52
52
  # Runs the given block in the given fiber. This method is used to run fibers
53
- # indirectly.
53
+ # indirectly. See FiberScheduler#fiber
54
54
  #
55
55
  # @param fiber [Fiber] fiber
56
56
  # @param block [Proc] block to run
@@ -202,9 +202,10 @@ class UringMachine
202
202
  # the type of target and how it is read from:
203
203
  #
204
204
  # - :fd - read from the given fd using the buffer pool (default mode)
205
+ # - :file - read from a regular file
205
206
  # - :socket - receive from the given socket fd using the buffer pool
206
207
  # - :ssl - read from the given SSL socket
207
- #
208
+ #
208
209
  # If a block is given, the block will be called with the IO instance as
209
210
  # argument and the method will return the block's return value.
210
211
  #
@@ -220,6 +221,17 @@ class UringMachine
220
221
  res
221
222
  end
222
223
 
224
+ # call-seq:
225
+ # machine.file_read(fn) -> data
226
+ #
227
+ # Reads and returns the content of the given filename, with a maximum length of 1GB.
228
+ #
229
+ # @param fn [String] filename
230
+ # @return [String] file content
231
+ def file_read(fn)
232
+ open(fn, O_RDONLY) { io(it, :file).read(-(1 << 30)) }
233
+ end
234
+
223
235
  # Creates, binds and sets up a TCP socket for listening on the given host and
224
236
  # port.
225
237
  #
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
@@ -715,6 +716,23 @@ class IOModeTest < UMBaseTest
715
716
  sock2&.close rescue nil
716
717
  end
717
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
+
718
736
  def test_io_socket_mode_non_socket
719
737
  r, w = UM.pipe
720
738
  machine.write(w, 'foobar')
data/test/test_um.rb CHANGED
@@ -2,6 +2,7 @@
2
2
 
3
3
  require_relative 'helper'
4
4
  require 'socket'
5
+ require 'tempfile'
5
6
 
6
7
  class UringMachineTest < Minitest::Test
7
8
  def test_kernel_version
@@ -3520,6 +3521,23 @@ class IOMethodTest < UMBaseTest
3520
3521
  end
3521
3522
  end
3522
3523
 
3524
+ class FileHelperMethodsTest < UMBaseTest
3525
+ def setup
3526
+ super
3527
+ @fn = Tempfile.new('uringmachine_test_io_from_file').path
3528
+ end
3529
+
3530
+ def test_file_read
3531
+ IO.write(@fn, 'foobar' * 321)
3532
+ buf = machine.file_read(@fn)
3533
+ assert_equal 'foobar' * 321, buf
3534
+ end
3535
+
3536
+ def test_file_read_bad_fn
3537
+ assert_raises(Errno::ENOENT) { machine.file_read(@fn + '-missing') }
3538
+ end
3539
+ end
3540
+
3523
3541
  class TCPHelperMethodsTest < UMBaseTest
3524
3542
  def setup
3525
3543
  super
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.4
4
+ version: 1.0.6
5
5
  platform: ruby
6
6
  authors:
7
7
  - Sharon Rosner