uringmachine 0.29.2 → 0.30.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6151ac8411facd0ef37fa66a7cae623d2137f335bbe1e178529cb0a76c9f56c3
4
- data.tar.gz: 5918fde9ea4ba936760a3d402207bd6fd5806337b005cd3633ab1cf0f8584ed0
3
+ metadata.gz: a97099f1d89b2333b8be056c91acabad63fad122ce4c802a666e4abb10aca93b
4
+ data.tar.gz: '095878ee7df374be5dc87b74ea636b3def2b611006c76c7d86ae7c00ad064f47'
5
5
  SHA512:
6
- metadata.gz: 0d77a7f37251930db32ec8023b1cb60e5595348544b279b175d39a528dbe782a703c75f6ebde5b2dd84379ec12ae83decf47f11f0b692e4975a36f146b4bc975
7
- data.tar.gz: 190d3d88d32f91241c3c9ed3fc674d5318fcf630e64d5d5fdecdc7a86b6e62f03786190a021f4828e6a40022ccbf301a3cffdf0311ff35bc747889d9f00fbb9b
6
+ metadata.gz: 339349fd4116011334517f124201a1cf425762c1bafe33ba61fa3eaf175fa5064512f780f8354f8fc317515bab6c4914cb41fba2022d23c17aac7e08d18cad7b
7
+ data.tar.gz: 02c7b30e4143f07382f2788a9f242e6f051f57b438fc3ecc63153a13f318bbbab69e5239add2af31e277c68b21483be60bd542863c8f7aa358d7fa1b0c0793e9
data/CHANGELOG.md CHANGED
@@ -1,3 +1,9 @@
1
+ # 0.30.0 2026-03-23
2
+
3
+ - Add `Stream#each`
4
+ - Add `UM#splice`, `UM#tee`, `UM#fsync`
5
+ - Add `UM#tcp_listen`, `UM#tcp_connect`
6
+
1
7
  # 0.29.2 2026-03-15
2
8
 
3
9
  - Add `Stream#consumed`, `Stream#pending`
data/TODO.md CHANGED
@@ -1,67 +1,10 @@
1
1
  ## immediate
2
2
 
3
- - Add support for returning a value on timeout
4
-
5
- Since to do this safely we need to actually raise an exception that wraps the
6
- value, rescue it and return the value, we might want a separate method that
7
- wraps `#timeout`:
8
-
9
- ```ruby
10
- TimeoutValueError < StandardError
11
-
12
- def timeout_with_value(interval, value, &block)
13
- timeout_error = TimeoutValueError
14
- timeout(interval, timeout_error, &block)
15
- rescue TimeoutValueError => e
16
- raise if e != timeout_error
17
-
18
- value
19
- end
20
- ```
21
-
22
3
  - Add tests for support for Set in `machine#await`
23
4
  - Add tests for support for Set, Array in `machine#join`
24
5
  - Add `#read_file` for reading entire file
25
6
  - Add `#write_file` for writing entire file
26
7
 
27
- - (?) Fix all futex value (Queue, Mutex) to be properly aligned
28
-
29
- <<<<<<< HEAD
30
- =======
31
- ## Buffer rings - automatic management
32
-
33
- - Take the buffer_pool branch, rewrite it
34
- - Allow multiple stream modes:
35
- - :buffer_pool - uses buffer rings
36
- - :ssl - read from an SSL connection (`SSLSocket`)
37
- - :io - read from an `IO`
38
-
39
- The API will look something like:
40
-
41
- ```ruby
42
- # The mode is selected automatically according to the given target
43
-
44
- stream = UM::Stream.new(machine, fd) # buffer_pool mode (read)
45
- stream = UM::Stream.new(machine, fd, :recv) # buffer_pool mode (recv)
46
- stream = UM::Stream.new(machine, ssl_sock) # SSLSocket mode
47
- stream = UM::Stream.new(machine, conn) # IO mode
48
- stream = UM::Stream.new(machine, str) # string mode
49
- stream = UM::Stream.new(machine, io_buf) # IO:Buffer mode
50
- ```
51
-
52
- This can be very useful in testing of stuff such as protocol implementations:
53
-
54
- ```ruby
55
- stream = UM::Stream.new(machine, "GET /foo HTTP/1.1\r\nHost: bar.com\r\n")
56
- ```
57
-
58
- So basically the stream is tied to a machine, and that means it can only be used
59
- on the thread with which the machine is associated. It is not thread-safe. (This
60
- is incidentally true also for most of the UringMachine instance methods!)
61
-
62
- Continued discussion in docs/design/buffer_pool.md
63
-
64
- >>>>>>> 04d9eb7 (Docs)
65
8
  ## Balancing I/O with the runqueue
66
9
 
67
10
  - in some cases where there are many entries in the runqueue, this can
@@ -89,6 +32,8 @@ Continued discussion in docs/design/buffer_pool.md
89
32
  debouncer = machine.debounce { }
90
33
  ```
91
34
 
35
+ - happy eyeballs algo for TCP connect
36
+
92
37
  - read multiple files
93
38
 
94
39
  ```ruby
@@ -99,12 +44,31 @@ Continued discussion in docs/design/buffer_pool.md
99
44
  machine.read_files(*fns) #=> { fn1:, fn2:, fn3:, ...}
100
45
  ```
101
46
 
47
+ - more generally, a DSL for expressing batch operations:
48
+
49
+ ```ruby
50
+ result = machine.batch do |b|
51
+ fns.each { b[it] = read_file(b, it) }
52
+ end
53
+ #=> { fn1 => data1, fn2 => data2, ... }
54
+
55
+ # we can also imagine performing operations in sequence using linking:
56
+ result = machine.batch {
57
+ m.
58
+ }
59
+
60
+ end
61
+ ```
62
+
102
63
  ## polyvalent select
103
64
 
104
65
  - select on multiple queues (ala Go)
105
66
  - select on mixture of queues and fds
67
+ - select on fibers:
68
+ - select fibers that are done
69
+ - select first done fiber
106
70
 
107
- ## ops
71
+ ## ops still not implemented
108
72
 
109
73
  - splice / - tee
110
74
  - sendto
@@ -123,31 +87,7 @@ Continued discussion in docs/design/buffer_pool.md
123
87
  When doing a `call`, we need to provide a mailbox for the response. can this be
124
88
  automatic?
125
89
 
126
- ## streams
127
-
128
- We're still missing:
129
-
130
- - limit on line length in `get_line`
131
- - ability to supply buffer to `get_line` and `get_string`
132
- - allow read to eof, maybe with `read_to_eof`
133
-
134
- For the sake of performance, simplicity and explicitness, we change the API as
135
- follows:
136
-
137
- ```ruby
138
- stream.get_line(buf, limit)
139
- # the defaults:
140
- stream.get_line(nil, -1)
141
-
142
- stream.get_string(len, buf)
143
- # defaults:
144
- stream.get_string(len, nil)
145
-
146
- # and
147
- stream.read_to_eof(buf)
148
- # defaults:
149
- stream.read_to_eof(nil)
150
- ```
90
+ ##
151
91
 
152
92
  ## Syntax / pattern for launching/supervising multiple operations
153
93
 
@@ -166,6 +106,5 @@ machine.shift_select(*queues) #=> [result, queue]
166
106
  ```ruby
167
107
  # addrs: [['1.1.1.1', 80], ['2.2.2.2', 80]]
168
108
  # ['1.1.1.1:80', '2.2.2.2:80']
169
- tcp_connect_happy_eyeballs(*addrs)
109
+ tcp_connect_he(*addrs)
170
110
  ```
171
-
@@ -0,0 +1,128 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './common'
4
+ require 'socket'
5
+ require 'openssl'
6
+ require 'localhost/authority'
7
+
8
+ GROUPS = 48
9
+ ITERATIONS = 5000
10
+
11
+ SIZE = 1 << 14
12
+ DATA = '*' * SIZE
13
+
14
+ class UMBenchmark
15
+ def server_ctx
16
+ @server_ctx ||= Localhost::Authority.fetch.server_context
17
+ end
18
+
19
+ def ssl_wrap(sock, ctx)
20
+ OpenSSL::SSL::SSLSocket.new(sock, ctx).tap { it.sync_close = true }
21
+ end
22
+
23
+ def ssl_socketpair(machine)
24
+ sock1, sock2 = Socket.socketpair(:AF_UNIX, :SOCK_STREAM, 0)
25
+ ssl1 = ssl_wrap(sock1, server_ctx)
26
+ ssl2 = ssl_wrap(sock2, OpenSSL::SSL::SSLContext.new)
27
+
28
+ if !machine
29
+ t = Thread.new { ssl1.accept rescue nil }
30
+ ssl2.connect
31
+ t.join
32
+ else
33
+ machine.ssl_set_bio(ssl1)
34
+ machine.ssl_set_bio(ssl2)
35
+ f = machine.spin { ssl1.accept rescue nil }
36
+ ssl2.connect
37
+ machine.join(f)
38
+ end
39
+ [ssl1, ssl2]
40
+ end
41
+
42
+ def do_threads(threads, ios)
43
+ GROUPS.times do
44
+ r, w = ssl_socketpair(nil)
45
+ threads << Thread.new do
46
+ ITERATIONS.times { w.write(DATA) }
47
+ w.close
48
+ end
49
+ threads << Thread.new do
50
+ ITERATIONS.times { r.readpartial(SIZE) }
51
+ r.close
52
+ end
53
+ end
54
+ end
55
+
56
+ def do_thread_pool(thread_pool, ios)
57
+ GROUPS.times do
58
+ r, w = ssl_socketpair(nil)
59
+ r.sync = true
60
+ w.sync = true
61
+ ios << r << w
62
+ ITERATIONS.times {
63
+ thread_pool.queue { w.write(DATA) }
64
+ thread_pool.queue { r.readpartial(SIZE) }
65
+ }
66
+ end
67
+ end
68
+
69
+ def do_scheduler(scheduler, ios)
70
+ GROUPS.times do
71
+ r, w = ssl_socketpair(nil)
72
+ r.sync = true
73
+ w.sync = true
74
+ Fiber.schedule do
75
+ ITERATIONS.times { w.write(DATA) }
76
+ w.close
77
+ end
78
+ Fiber.schedule do
79
+ ITERATIONS.times { r.readpartial(SIZE) }
80
+ r.close
81
+ end
82
+ end
83
+ end
84
+
85
+ def do_scheduler_x(div, scheduler, ios)
86
+ (GROUPS/div).times do
87
+ r, w = ssl_socketpair(nil)
88
+ r.sync = true
89
+ w.sync = true
90
+ Fiber.schedule do
91
+ ITERATIONS.times { w.write(DATA) }
92
+ w.close
93
+ end
94
+ Fiber.schedule do
95
+ ITERATIONS.times { r.readpartial(SIZE) }
96
+ r.close
97
+ end
98
+ end
99
+ end
100
+
101
+ def do_um(machine, fibers, fds)
102
+ GROUPS.times do
103
+ r, w = ssl_socketpair(machine)
104
+ fibers << machine.spin do
105
+ ITERATIONS.times { machine.ssl_write(w, DATA, SIZE) }
106
+ machine.close_async(w)
107
+ end
108
+ fibers << machine.spin do
109
+ ITERATIONS.times { machine.ssl_read(r, +'', SIZE) }
110
+ machine.close_async(r)
111
+ end
112
+ end
113
+ end
114
+
115
+ def do_um_x(div, machine, fibers, fds)
116
+ (GROUPS/div).times do
117
+ r, w = ssl_socketpair(machine)
118
+ fibers << machine.spin do
119
+ ITERATIONS.times { machine.ssl_write(w, DATA, SIZE) }
120
+ machine.close_async(w)
121
+ end
122
+ fibers << machine.spin do
123
+ ITERATIONS.times { machine.ssl_read(r, +'', SIZE) }
124
+ machine.close_async(r)
125
+ end
126
+ end
127
+ end
128
+ end
@@ -0,0 +1,76 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative './common'
4
+ require 'securerandom'
5
+
6
+ C = ENV['C']&.to_i || 50
7
+ I = 10
8
+ puts "C=#{C}"
9
+
10
+ class UMBenchmark
11
+ CONTAINER_NAME = "redis-#{SecureRandom.hex}"
12
+
13
+ def start_redis_server
14
+ `docker run --name #{CONTAINER_NAME} -d -p 6379:6379 redis:latest`
15
+ end
16
+
17
+ def stop_redis_server
18
+ `docker stop #{CONTAINER_NAME}`
19
+ end
20
+
21
+ def create_redis_conn(retries = 0)
22
+ Redis.new
23
+ rescue
24
+ if retries < 3
25
+ sleep 0.5
26
+ create_redis_conn(retries + 1)
27
+ else
28
+ raise
29
+ end
30
+ end
31
+
32
+ def query_redis(conn)
33
+ conn.set('abc', 'def')
34
+ p conn.get('abc')
35
+ end
36
+
37
+ def with_container
38
+ start_redis_server
39
+ sleep 0.5
40
+ yield
41
+ rescue Exception => e
42
+ p e
43
+ p e.backtrace
44
+ ensure
45
+ stop_redis_server
46
+ end
47
+
48
+ def benchmark
49
+ with_container {
50
+ Benchmark.bm { run_benchmarks(it) }
51
+ }
52
+ end
53
+
54
+ # def do_threads(threads, ios)
55
+ # C.times.map do
56
+ # threads << Thread.new do
57
+ # conn = create_redis_conn
58
+ # I.times { query_redis(conn) }
59
+ # ensure
60
+ # conn.close
61
+ # end
62
+ # end
63
+ # end
64
+
65
+ def do_scheduler(scheduler, ios)
66
+ return if !scheduler.is_a?(UM::FiberScheduler)
67
+ C.times do
68
+ Fiber.schedule do
69
+ conn = create_redis_conn
70
+ I.times { query_redis(conn) }
71
+ ensure
72
+ conn.close
73
+ end
74
+ end
75
+ end
76
+ end
data/benchmark/common.rb CHANGED
@@ -9,6 +9,7 @@ gemfile do
9
9
  gem 'io-event'
10
10
  gem 'async'
11
11
  gem 'pg'
12
+ gem 'redis'
12
13
  gem 'gvltools'
13
14
  gem 'openssl'
14
15
  gem 'localhost'
@@ -62,7 +62,7 @@ end
62
62
 
63
63
  require 'stringio'
64
64
 
65
- RE_REQUEST_LINE = /^([a-z]+)\s+([^\s]+)\s+(http\/[0-9\.]{1,3})/i
65
+ RE_REQUEST_LINE = /^([a-z]+)\s+([^\s]+)\s+(http\/1\.1)/i
66
66
  RE_HEADER_LINE = /^([a-z0-9\-]+)\:\s+(.+)/i
67
67
 
68
68
  def get_line(fd, sio, buffer)
@@ -137,7 +137,7 @@ def stream_parse_headers(fd)
137
137
  return nil if !headers
138
138
 
139
139
  while true
140
- line = stream.get_line(buf, 0)
140
+ line = stream.get_line(0)
141
141
  break if line.empty?
142
142
 
143
143
  m = line.match(RE_HEADER_LINE)
@@ -150,7 +150,7 @@ def stream_parse_headers(fd)
150
150
  end
151
151
 
152
152
  def stream_get_request_line(stream, buf)
153
- line = stream.get_line(buf, 0)
153
+ line = stream.get_line(0)
154
154
 
155
155
  m = line.match(RE_REQUEST_LINE)
156
156
  return nil if !m
data/ext/um/um.c CHANGED
@@ -1285,6 +1285,57 @@ VALUE um_statx(struct um *machine, int dirfd, VALUE path, int flags, unsigned in
1285
1285
  return statx_to_hash(&stat);
1286
1286
  }
1287
1287
 
1288
+ VALUE um_splice(struct um *machine, int in_fd, int out_fd, uint nbytes) {
1289
+ struct um_op *op = um_op_acquire(machine);
1290
+ um_prep_op(machine, op, OP_SPLICE, 2, 0);
1291
+ struct io_uring_sqe *sqe = um_get_sqe(machine, op);
1292
+ io_uring_prep_splice(sqe, in_fd, -1, out_fd, -1, nbytes, 0);
1293
+
1294
+ VALUE ret = um_yield(machine);
1295
+
1296
+ if (likely(um_verify_op_completion(machine, op, false))) ret = INT2NUM(op->result.res);
1297
+ um_op_release(machine, op);
1298
+
1299
+ RAISE_IF_EXCEPTION(ret);
1300
+ RB_GC_GUARD(ret);
1301
+
1302
+ return ret;
1303
+ }
1304
+
1305
+ VALUE um_tee(struct um *machine, int in_fd, int out_fd, uint nbytes) {
1306
+ struct um_op *op = um_op_acquire(machine);
1307
+ um_prep_op(machine, op, OP_TEE, 2, 0);
1308
+ struct io_uring_sqe *sqe = um_get_sqe(machine, op);
1309
+ io_uring_prep_tee(sqe, in_fd, out_fd, nbytes, 0);
1310
+
1311
+ VALUE ret = um_yield(machine);
1312
+
1313
+ if (likely(um_verify_op_completion(machine, op, false))) ret = INT2NUM(op->result.res);
1314
+ um_op_release(machine, op);
1315
+
1316
+ RAISE_IF_EXCEPTION(ret);
1317
+ RB_GC_GUARD(ret);
1318
+
1319
+ return ret;
1320
+ }
1321
+
1322
+ VALUE um_fsync(struct um *machine, int fd) {
1323
+ struct um_op *op = um_op_acquire(machine);
1324
+ um_prep_op(machine, op, OP_FSYNC, 2, 0);
1325
+ struct io_uring_sqe *sqe = um_get_sqe(machine, op);
1326
+ io_uring_prep_fsync(sqe, fd, 0);
1327
+
1328
+ VALUE ret = um_yield(machine);
1329
+
1330
+ if (likely(um_verify_op_completion(machine, op, false))) ret = INT2NUM(op->result.res);
1331
+ um_op_release(machine, op);
1332
+
1333
+ RAISE_IF_EXCEPTION(ret);
1334
+ RB_GC_GUARD(ret);
1335
+
1336
+ return ret;
1337
+ }
1338
+
1288
1339
  /*******************************************************************************
1289
1340
  multishot ops
1290
1341
  *******************************************************************************/
data/ext/um/um.h CHANGED
@@ -48,6 +48,9 @@ enum um_op_kind {
48
48
  OP_CLOSE,
49
49
  OP_CLOSE_ASYNC,
50
50
  OP_STATX,
51
+ OP_SPLICE,
52
+ OP_TEE,
53
+ OP_FSYNC,
51
54
 
52
55
  OP_ACCEPT,
53
56
  OP_RECV,
@@ -161,7 +164,7 @@ struct um_op {
161
164
  struct iovec *iovecs; // used for vectorized write/send
162
165
  siginfo_t siginfo; // used for waitid
163
166
  int int_value; // used for getsockopt
164
- size_t bp_commit_level; // buffer pool commit threshold
167
+ size_t bp_commit_level; // buffer pool commit level
165
168
  };
166
169
  };
167
170
 
@@ -325,7 +328,6 @@ void um_op_list_compact(struct um *machine, struct um_op *head);
325
328
  void um_op_multishot_results_push(struct um *machine, struct um_op *op, __s32 res, __u32 flags);
326
329
  void um_op_multishot_results_clear(struct um *machine, struct um_op *op);
327
330
 
328
- struct um_segment *um_segment_alloc(struct um *machine);
329
331
  void um_segment_free(struct um *machine, struct um_segment *segment);
330
332
 
331
333
  void um_runqueue_push(struct um *machine, struct um_op *op);
@@ -391,6 +393,9 @@ VALUE um_waitid_status(struct um *machine, int idtype, int id, int options);
391
393
  #endif
392
394
 
393
395
  VALUE um_statx(struct um *machine, int dirfd, VALUE path, int flags, unsigned int mask);
396
+ VALUE um_splice(struct um *machine, int in_fd, int out_fd, uint nbytes);
397
+ VALUE um_tee(struct um *machine, int in_fd, int out_fd, uint nbytes);
398
+ VALUE um_fsync(struct um *machine, int fd);
394
399
 
395
400
  VALUE um_accept(struct um *machine, int fd);
396
401
  VALUE um_accept_each(struct um *machine, int fd);
@@ -437,6 +442,7 @@ void stream_clear(struct um_stream *stream);
437
442
  VALUE stream_get_line(struct um_stream *stream, VALUE buf, size_t maxlen);
438
443
  VALUE stream_get_string(struct um_stream *stream, VALUE out_buffer, ssize_t len, size_t inc, int safe_inc);
439
444
  void stream_skip(struct um_stream *stream, size_t inc, int safe_inc);
445
+ void stream_each(struct um_stream *stream);
440
446
  VALUE resp_decode(struct um_stream *stream, VALUE out_buffer);
441
447
  void resp_encode(struct um_write_buffer *buf, VALUE obj);
442
448
  void resp_encode_cmd(struct um_write_buffer *buf, int argc, VALUE *argv);
@@ -454,9 +460,9 @@ void um_sidecar_signal_wait(struct um *machine);
454
460
  void um_sidecar_signal_wake(struct um *machine);
455
461
 
456
462
  void um_ssl_set_bio(struct um *machine, VALUE ssl_obj);
457
- int um_ssl_read(struct um *machine, VALUE ssl, VALUE buf, int maxlen);
458
- int um_ssl_read_raw(struct um *machine, VALUE ssl_obj, char *ptr, int maxlen);
459
- int um_ssl_write(struct um *machine, VALUE ssl, VALUE buf, int len);
463
+ int um_ssl_read(struct um *machine, VALUE ssl, VALUE buf, size_t maxlen);
464
+ int um_ssl_read_raw(struct um *machine, VALUE ssl_obj, char *ptr, size_t maxlen);
465
+ int um_ssl_write(struct um *machine, VALUE ssl, VALUE buf, size_t len);
460
466
 
461
467
  void bp_setup(struct um *machine);
462
468
  void bp_teardown(struct um *machine);
@@ -18,7 +18,7 @@ inline struct um_buffer *buffer_alloc(struct um *machine) {
18
18
  return buffer;
19
19
  }
20
20
 
21
- inline struct um_buffer *bp_buffer_checkout(struct um *machine) {
21
+ struct um_buffer *bp_buffer_checkout(struct um *machine) {
22
22
  struct um_buffer *buffer = machine->bp_buffer_freelist;
23
23
  if (buffer) {
24
24
  struct um_buffer *next = buffer->next;
@@ -50,13 +50,13 @@ inline void buffer_free(struct um *machine, struct um_buffer *buffer) {
50
50
  }
51
51
  }
52
52
 
53
- inline void bp_buffer_checkin(struct um *machine, struct um_buffer *buffer) {
53
+ void bp_buffer_checkin(struct um *machine, struct um_buffer *buffer) {
54
54
  assert(buffer->ref_count > 0);
55
55
  buffer->ref_count--;
56
56
  if (!buffer->ref_count) buffer_free(machine, buffer);
57
57
  }
58
58
 
59
- inline void bp_discard_buffer_freelist(struct um *machine) {
59
+ void bp_discard_buffer_freelist(struct um *machine) {
60
60
  while (machine->bp_buffer_freelist) {
61
61
  struct um_buffer *buffer = machine->bp_buffer_freelist;
62
62
  struct um_buffer *next = buffer->next;
@@ -69,7 +69,7 @@ inline void bp_discard_buffer_freelist(struct um *machine) {
69
69
  }
70
70
  }
71
71
 
72
- inline void bp_setup(struct um *machine) {
72
+ void bp_setup(struct um *machine) {
73
73
  int ret;
74
74
  machine->bp_br = io_uring_setup_buf_ring(&machine->ring, BP_BR_ENTRIES, BP_BGID, IOU_PBUF_RING_INC, &ret);
75
75
  if (unlikely(!machine->bp_br)) rb_syserr_fail(ret, strerror(ret));
@@ -85,7 +85,7 @@ inline void bp_setup(struct um *machine) {
85
85
  machine->bp_total_commited = 0;
86
86
  }
87
87
 
88
- inline void bp_teardown(struct um *machine) {
88
+ void bp_teardown(struct um *machine) {
89
89
  bp_discard_buffer_freelist(machine);
90
90
  for (int i = 0; i < BP_BR_ENTRIES; i++) {
91
91
  struct um_buffer *buffer = machine->bp_commited_buffers[i];
@@ -162,7 +162,7 @@ inline int should_commit_more_p(struct um *machine) {
162
162
  (machine->bp_total_commited < machine->bp_commit_level);
163
163
  }
164
164
 
165
- inline void bp_ensure_commit_level(struct um *machine) {
165
+ void bp_ensure_commit_level(struct um *machine) {
166
166
  if (machine->bp_total_commited > (machine->bp_commit_level / 2))
167
167
  return;
168
168
 
@@ -179,7 +179,7 @@ inline void bp_ensure_commit_level(struct um *machine) {
179
179
  // size.
180
180
  }
181
181
 
182
- inline void bp_handle_enobufs(struct um *machine) {
182
+ void bp_handle_enobufs(struct um *machine) {
183
183
  if (unlikely(machine->bp_commit_level >= BP_MAX_COMMIT_LEVEL))
184
184
  rb_raise(eUMError, "Buffer starvation");
185
185
 
@@ -189,7 +189,7 @@ inline void bp_handle_enobufs(struct um *machine) {
189
189
  bp_discard_buffer_freelist(machine);
190
190
  }
191
191
 
192
- inline struct um_segment *um_segment_alloc(struct um *machine) {
192
+ inline struct um_segment *segment_alloc(struct um *machine) {
193
193
  if (machine->segment_freelist) {
194
194
  struct um_segment *segment = machine->segment_freelist;
195
195
  machine->segment_freelist = segment->next;
@@ -208,14 +208,14 @@ inline struct um_segment *um_segment_alloc(struct um *machine) {
208
208
  return batch;
209
209
  }
210
210
 
211
- inline void um_segment_free(struct um *machine, struct um_segment *segment) {
211
+ void um_segment_free(struct um *machine, struct um_segment *segment) {
212
212
  segment->next = machine->segment_freelist;
213
213
  machine->segment_freelist = segment;
214
214
  machine->metrics.segments_free++;
215
215
  }
216
216
 
217
- inline struct um_segment *bp_buffer_consume(struct um *machine, struct um_buffer *buffer, size_t len) {
218
- struct um_segment *segment = um_segment_alloc(machine);
217
+ struct um_segment *bp_buffer_consume(struct um *machine, struct um_buffer *buffer, size_t len) {
218
+ struct um_segment *segment = segment_alloc(machine);
219
219
  segment->ptr = buffer->buf + buffer->pos;
220
220
  segment->len = len;
221
221
  segment->buffer = buffer;
data/ext/um/um_class.c CHANGED
@@ -534,6 +534,60 @@ VALUE UM_statx(VALUE self, VALUE dirfd, VALUE path, VALUE flags, VALUE mask) {
534
534
  return um_statx(machine, NUM2INT(dirfd), path, NUM2INT(flags), NUM2UINT(mask));
535
535
  }
536
536
 
537
+ /* call-seq:
538
+ * machine.splice(in_fd, out_fd, nbytes) -> len
539
+ *
540
+ * Splices bytes from in_fd to out_fd. At least one of the given fds must be a
541
+ * pipe.
542
+ *
543
+ * - https://www.man7.org/linux/man-pages/man2/splice.2.html
544
+ * - https://www.man7.org/linux/man-pages/man3/io_uring_prep_splice.3.html
545
+ *
546
+ * @param in_fd [Integer] fd to splice from
547
+ * @param out_fd [Integer] fd to splice to
548
+ * @param nbytes [Integer] number of bytes to splice
549
+ * @return [Integer] number of bytes spliced
550
+ */
551
+ VALUE UM_splice(VALUE self, VALUE in_fd, VALUE out_fd, VALUE nbytes) {
552
+ struct um *machine = um_get_machine(self);
553
+ return um_splice(machine, NUM2INT(in_fd), NUM2INT(out_fd), NUM2UINT(nbytes));
554
+ }
555
+
556
+ /* call-seq:
557
+ * machine.tee(in_fd, out_fd, nbytes) -> len
558
+ *
559
+ * Duplicates bytes from in_fd to out_fd. At least one of the given fds must be
560
+ * a pipe.
561
+ *
562
+ * - https://www.man7.org/linux/man-pages/man2/tee.2.html
563
+ * - https://www.man7.org/linux/man-pages/man3/io_uring_prep_tee.3.html
564
+ *
565
+ * @param in_fd [Integer] fd to copy from
566
+ * @param out_fd [Integer] fd to copy to
567
+ * @param nbytes [Integer] number of bytes to duplicate
568
+ * @return [Integer] number of bytes duplicated
569
+ */
570
+ VALUE UM_tee(VALUE self, VALUE in_fd, VALUE out_fd, VALUE nbytes) {
571
+ struct um *machine = um_get_machine(self);
572
+ return um_tee(machine, NUM2INT(in_fd), NUM2INT(out_fd), NUM2UINT(nbytes));
573
+ }
574
+
575
+ /* call-seq:
576
+ * machine.fsync(fd) -> 0
577
+ *
578
+ * Flushes all modified file data to the storage device.
579
+ *
580
+ * - https://www.man7.org/linux/man-pages/man2/fsync.2.html
581
+ * - https://www.man7.org/linux/man-pages/man3/io_uring_prep_fsync.3.html
582
+ *
583
+ * @param fd [Integer] fd
584
+ * @return [Integer] 0 if successful
585
+ */
586
+ VALUE UM_fsync(VALUE self, VALUE fd) {
587
+ struct um *machine = um_get_machine(self);
588
+ return um_fsync(machine, NUM2INT(fd));
589
+ }
590
+
537
591
  /* call-seq:
538
592
  * machine.close(fd) -> 0
539
593
  *
@@ -1480,6 +1534,9 @@ void Init_UM(void) {
1480
1534
  rb_define_method(cUM, "writev", UM_writev, -1);
1481
1535
  rb_define_method(cUM, "write_async", UM_write_async, -1);
1482
1536
  rb_define_method(cUM, "statx", UM_statx, 4);
1537
+ rb_define_method(cUM, "splice", UM_splice, 3);
1538
+ rb_define_method(cUM, "tee", UM_tee, 3);
1539
+ rb_define_method(cUM, "fsync", UM_fsync, 1);
1483
1540
 
1484
1541
  rb_define_method(cUM, "poll", UM_poll, 2);
1485
1542
  rb_define_method(cUM, "select", UM_select, 3);