uringmachine 0.30.0 → 0.31.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.
@@ -0,0 +1,394 @@
1
+ #include "um.h"
2
+
3
+ VALUE cConnection;
4
+ VALUE eConnectionRESPError;
5
+
6
+ VALUE SYM_fd;
7
+ VALUE SYM_socket;
8
+ VALUE SYM_ssl;
9
+
10
+ inline int connection_has_target_obj_p(struct um_connection *conn) {
11
+ switch (conn->mode) {
12
+ case CONNECTION_SSL:
13
+ case CONNECTION_STRING:
14
+ case CONNECTION_IO_BUFFER:
15
+ return true;
16
+ default:
17
+ return false;
18
+ }
19
+ }
20
+
21
+ inline void connection_mark_segments(struct um_connection *conn) {
22
+ struct um_segment *curr = conn->head;
23
+ while (curr) {
24
+ // rb_gc_mark_movable(curr->obj);
25
+ curr = curr->next;
26
+ }
27
+ }
28
+
29
+ inline void connection_compact_segments(struct um_connection *conn) {
30
+ struct um_segment *curr = conn->head;
31
+ while (curr) {
32
+ // curr->obj = rb_gc_location(curr->obj);
33
+ curr = curr->next;
34
+ }
35
+ }
36
+
37
+ static void Connection_mark(void *ptr) {
38
+ struct um_connection *conn = ptr;
39
+ rb_gc_mark_movable(conn->self);
40
+ rb_gc_mark_movable(conn->machine->self);
41
+
42
+ if (connection_has_target_obj_p(conn)) {
43
+ rb_gc_mark_movable(conn->target);
44
+ connection_mark_segments(conn);
45
+ }
46
+ }
47
+
48
+ static void Connection_compact(void *ptr) {
49
+ struct um_connection *conn = ptr;
50
+ conn->self = rb_gc_location(conn->self);
51
+
52
+ if (connection_has_target_obj_p(conn)) {
53
+ conn->target = rb_gc_location(conn->target);
54
+ connection_compact_segments(conn);
55
+ }
56
+ }
57
+
58
+ static void Connection_free(void *ptr) {
59
+ struct um_connection *conn = ptr;
60
+ connection_clear(conn);
61
+ }
62
+
63
+ static const rb_data_type_t Connection_type = {
64
+ .wrap_struct_name = "UringMachine::Connection",
65
+ .function = {
66
+ .dmark = Connection_mark,
67
+ .dfree = Connection_free,
68
+ .dsize = NULL,
69
+ .dcompact = Connection_compact
70
+ },
71
+ .flags = RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED | RUBY_TYPED_EMBEDDABLE
72
+ };
73
+
74
+ static VALUE Connection_allocate(VALUE klass) {
75
+ struct um_connection *conn;
76
+ VALUE self = TypedData_Make_Struct(klass, struct um_connection, &Connection_type, conn);
77
+ return self;
78
+ }
79
+
80
+ static inline struct um_connection *um_get_connection(VALUE self) {
81
+ struct um_connection *conn;
82
+ TypedData_Get_Struct(self, struct um_connection, &Connection_type, conn);
83
+ return conn;
84
+ }
85
+
86
+ static inline void connection_set_target(struct um_connection *conn, VALUE target, enum um_connection_mode mode) {
87
+ conn->mode = mode;
88
+ switch (mode) {
89
+ case CONNECTION_FD:
90
+ case CONNECTION_SOCKET:
91
+ conn->fd = NUM2INT(target);
92
+ return;
93
+ case CONNECTION_SSL:
94
+ conn->target = target;
95
+ um_ssl_set_bio(conn->machine, target);
96
+ return;
97
+ default:
98
+ rb_raise(eUMError, "Invalid connection mode");
99
+ }
100
+ }
101
+
102
+ static inline void connection_setup(struct um_connection *conn, VALUE target, VALUE mode) {
103
+ conn->working_buffer = NULL;
104
+ if (NIL_P(mode)) {
105
+ if (TYPE(target) == T_DATA)
106
+ connection_set_target(conn, target, CONNECTION_SSL);
107
+ else
108
+ connection_set_target(conn, target, CONNECTION_FD);
109
+ }
110
+ else if (mode == SYM_fd)
111
+ connection_set_target(conn, target, CONNECTION_FD);
112
+ else if (mode == SYM_socket)
113
+ connection_set_target(conn, target, CONNECTION_SOCKET);
114
+ else if (mode == SYM_ssl)
115
+ connection_set_target(conn, target, CONNECTION_SSL);
116
+ else
117
+ rb_raise(eUMError, "Invalid connection mode");
118
+ }
119
+
120
+ /* call-seq:
121
+ * UM::Stream.new(machine, fd, mode = nil) -> conn
122
+ * machine.connection(fd, mode = nil) -> conn
123
+ * machine.connection(fd, mode = nil) { |conn| ... }
124
+ *
125
+ * Initializes a new connection with the given UringMachine instance, target and
126
+ * optional mode. The target maybe a file descriptor, or an instance of
127
+ * OpenSSL::SSL::SSLSocket. In case of an SSL socket, the mode should be :ssl.
128
+ *
129
+ * @param machine [UringMachine] UringMachine instance
130
+ * @param target [integer, OpenSSL::SSL::SSLSocket] connection target: file descriptor or SSL socket
131
+ * @param mode [Symbol] optional connection mode: :fd, :socket, :ssl
132
+ * @return [void]
133
+ */
134
+ VALUE Connection_initialize(int argc, VALUE *argv, VALUE self) {
135
+ VALUE machine;
136
+ VALUE target;
137
+ VALUE mode;
138
+ rb_scan_args(argc, argv, "21", &machine, &target, &mode);
139
+
140
+ struct um_connection *conn = um_get_connection(self);
141
+ memset(conn, 0, sizeof(struct um_connection));
142
+
143
+ RB_OBJ_WRITE(self, &conn->self, self);
144
+ conn->machine = um_get_machine(machine);
145
+ connection_setup(conn, target, mode);
146
+
147
+ return self;
148
+ }
149
+
150
+ /* call-seq:
151
+ * conn.mode -> mode
152
+ *
153
+ * Returns the connection mode.
154
+ *
155
+ * @return [Symbol] connection mode
156
+ */
157
+ VALUE Connection_mode(VALUE self) {
158
+ struct um_connection *conn = um_get_connection(self);
159
+ switch (conn->mode) {
160
+ case CONNECTION_FD: return SYM_fd;
161
+ case CONNECTION_SOCKET: return SYM_socket;
162
+ case CONNECTION_SSL: return SYM_ssl;
163
+ default: return Qnil;
164
+ }
165
+ return Qnil;
166
+ }
167
+
168
+ /* call-seq:
169
+ * conn.read_line(limit) -> str
170
+ *
171
+ * Reads from the string until a newline character is encountered. Returns the
172
+ * line without the newline delimiter. If limit is 0, the line length is not
173
+ * limited. If no newline delimiter is found before EOF, returns nil.
174
+ *
175
+ * @param limit [integer] maximum line length (0 means no limit)
176
+ * @return [String, nil] read data or nil
177
+ */
178
+ VALUE Connection_read_line(VALUE self, VALUE limit) {
179
+ struct um_connection *conn = um_get_connection(self);
180
+ return connection_read_line(conn, Qnil, NUM2ULONG(limit));
181
+ }
182
+
183
+ /* call-seq:
184
+ * conn.read(len) -> str
185
+ *
186
+ * Reads len bytes from the conn. If len is 0, reads all available bytes. If
187
+ * len is negative, reads up to -len available bytes. If len is positive and eof
188
+ * is encountered before len bytes are read, returns nil.
189
+ *
190
+ * @param len [integer] number of bytes to read
191
+ * @return [String, nil] read data or nil
192
+ */
193
+ VALUE Connection_read(VALUE self, VALUE len) {
194
+ struct um_connection *conn = um_get_connection(self);
195
+ return connection_read(conn, Qnil, NUM2LONG(len), 0, false);
196
+ }
197
+
198
+ /* call-seq:
199
+ * conn.read_to_delim(delim, limit) -> str
200
+ *
201
+ * Reads from the string until a the given delimiter is encountered. Returns the
202
+ * line without the delimiter. If limit is 0, the length is not limited. If a
203
+ * delimiter is not found before EOF and limit is 0 or greater, returns nil.
204
+ *
205
+ * If no delimiter is found before EOF and limit is negative, returns the
206
+ * buffered data up to EOF or until the absolute-value length limit is reached.
207
+ *
208
+ * The `delim` parameter must be a single byte string.
209
+ *
210
+ * @param delim [String] delimiter (single byte) @param limit [integer] maximum
211
+ * line length (0 means no limit) @return [String, nil] read data or nil
212
+ */
213
+ VALUE Connection_read_to_delim(VALUE self, VALUE delim, VALUE limit) {
214
+ struct um_connection *conn = um_get_connection(self);
215
+ return connection_read_to_delim(conn, Qnil, delim, NUM2LONG(limit));
216
+ }
217
+
218
+ /* call-seq:
219
+ * conn.skip(len) -> len
220
+ *
221
+ * Skips len bytes in the conn.
222
+ *
223
+ * @param len [integer] number of bytes to skip
224
+ * @return [Integer] len
225
+ */
226
+ VALUE Connection_skip(VALUE self, VALUE len) {
227
+ struct um_connection *conn = um_get_connection(self);
228
+ connection_skip(conn, NUM2LONG(len), true);
229
+ return len;
230
+ }
231
+
232
+ /* call-seq:
233
+ * conn.read_each { |data| } -> conn
234
+ *
235
+ * Reads from the target, passing each chunk to the given block.
236
+ *
237
+ * @return [UringMachine::Connection] conn
238
+ */
239
+ VALUE Connection_read_each(VALUE self) {
240
+ struct um_connection *conn = um_get_connection(self);
241
+ connection_read_each(conn);
242
+ return self;
243
+ }
244
+
245
+ /* call-seq:
246
+ * conn.write(*bufs) -> len
247
+ *
248
+ * Writes to the connection, ensuring that all data has been written before
249
+ * returning the total number of bytes written.
250
+ *
251
+ * @param bufs [Array<String, IO::Buffer>] data to write
252
+ * @return [Integer] total bytes written
253
+ */
254
+ VALUE Connection_write(int argc, VALUE *argv, VALUE self) {
255
+ struct um_connection *conn = um_get_connection(self);
256
+ return connection_writev(conn, argc, argv);
257
+ }
258
+
259
+ /* call-seq:
260
+ * conn.resp_read -> obj
261
+ *
262
+ * Decodes an object from a RESP (Redis protocol) message.
263
+ *
264
+ * @return [any] decoded object
265
+ */
266
+ VALUE Connection_resp_read(VALUE self) {
267
+ struct um_connection *conn = um_get_connection(self);
268
+ VALUE out_buffer = rb_utf8_str_new_literal("");
269
+ VALUE obj = resp_read(conn, out_buffer);
270
+ RB_GC_GUARD(out_buffer);
271
+ return obj;
272
+ }
273
+
274
+ /* call-seq:
275
+ * conn.resp_write(obj) -> conn
276
+ *
277
+ * Writes the given object using RESP (Redis protocol) to the connection target.
278
+ * Returns the number of bytes written.
279
+ *
280
+ * @param obj [any] object to write
281
+ * @return [Integer] total bytes written
282
+ */
283
+ VALUE Connection_resp_write(VALUE self, VALUE obj) {
284
+ struct um_connection *conn = um_get_connection(self);
285
+
286
+ VALUE str = rb_str_new(NULL, 0);
287
+ struct um_write_buffer buf;
288
+ write_buffer_init(&buf, str);
289
+ rb_str_modify(str);
290
+ resp_encode(&buf, obj);
291
+ write_buffer_update_len(&buf);
292
+
293
+ size_t len = connection_write_raw(conn, buf.ptr, buf.len);
294
+ RB_GC_GUARD(str);
295
+ return ULONG2NUM(len);
296
+ }
297
+
298
+ /* call-seq:
299
+ * conn.resp_encode(obj) -> string
300
+ *
301
+ * Encodes an object into a RESP (Redis protocol) message.
302
+ *
303
+ * @param str [String] string buffer
304
+ * @param obj [any] object to be encoded
305
+ * @return [String] str
306
+ */
307
+ VALUE Connection_resp_encode(VALUE self, VALUE str, VALUE obj) {
308
+ struct um_write_buffer buf;
309
+ write_buffer_init(&buf, str);
310
+ rb_str_modify(str);
311
+ resp_encode(&buf, obj);
312
+ write_buffer_update_len(&buf);
313
+ return str;
314
+ }
315
+
316
+ /* call-seq:
317
+ * conn.eof? -> bool
318
+ *
319
+ * Returns true if connection has reached EOF.
320
+ *
321
+ * @return [bool] EOF reached
322
+ */
323
+ VALUE Connection_eof_p(VALUE self) {
324
+ struct um_connection *conn = um_get_connection(self);
325
+ return conn->eof ? Qtrue : Qfalse;
326
+ }
327
+
328
+ /* call-seq:
329
+ * conn.consumed -> int
330
+ *
331
+ * Returns the total number of bytes consumed from the conn.
332
+ *
333
+ * @return [Integer] total bytes consumed
334
+ */
335
+ VALUE Connection_consumed(VALUE self) {
336
+ struct um_connection *conn = um_get_connection(self);
337
+ return LONG2NUM(conn->consumed_bytes);
338
+ }
339
+
340
+ /* call-seq:
341
+ * conn.pending -> int
342
+ *
343
+ * Returns the number of bytes available for reading.
344
+ *
345
+ * @return [Integer] bytes available
346
+ */
347
+ VALUE Connection_pending(VALUE self) {
348
+ struct um_connection *conn = um_get_connection(self);
349
+ return LONG2NUM(conn->pending_bytes);
350
+ }
351
+
352
+ /* call-seq:
353
+ * conn.clear -> conn
354
+ *
355
+ * Clears all available bytes and stops any ongoing read operation.
356
+ *
357
+ * @return [UM::Stream] self
358
+ */
359
+ VALUE Connection_clear(VALUE self) {
360
+ struct um_connection *conn = um_get_connection(self);
361
+ connection_clear(conn);
362
+ return self;
363
+ }
364
+
365
+ void Init_Stream(void) {
366
+ cConnection = rb_define_class_under(cUM, "Connection", rb_cObject);
367
+ rb_define_alloc_func(cConnection, Connection_allocate);
368
+
369
+ rb_define_method(cConnection, "initialize", Connection_initialize, -1);
370
+ rb_define_method(cConnection, "mode", Connection_mode, 0);
371
+
372
+ rb_define_method(cConnection, "read_line", Connection_read_line, 1);
373
+ rb_define_method(cConnection, "read", Connection_read, 1);
374
+ rb_define_method(cConnection, "read_to_delim", Connection_read_to_delim, 2);
375
+ rb_define_method(cConnection, "skip", Connection_skip, 1);
376
+ rb_define_method(cConnection, "read_each", Connection_read_each, 0);
377
+
378
+ rb_define_method(cConnection, "write", Connection_write, -1);
379
+
380
+ rb_define_method(cConnection, "resp_read", Connection_resp_read, 0);
381
+ rb_define_method(cConnection, "resp_write", Connection_resp_write, 1);
382
+ rb_define_singleton_method(cConnection, "resp_encode", Connection_resp_encode, 2);
383
+
384
+ rb_define_method(cConnection, "eof?", Connection_eof_p, 0);
385
+ rb_define_method(cConnection, "consumed", Connection_consumed, 0);
386
+ rb_define_method(cConnection, "pending", Connection_pending, 0);
387
+ rb_define_method(cConnection, "clear", Connection_clear, 0);
388
+
389
+ eConnectionRESPError = rb_define_class_under(cConnection, "RESPError", rb_eStandardError);
390
+
391
+ SYM_fd = ID2SYM(rb_intern("fd"));
392
+ SYM_socket = ID2SYM(rb_intern("socket"));
393
+ SYM_ssl = ID2SYM(rb_intern("ssl"));
394
+ }
data/ext/um/um_ssl.c CHANGED
@@ -8,14 +8,14 @@
8
8
  static int um_bio_read(BIO *bio, char *buf, int blen)
9
9
  {
10
10
  struct um *machine = (struct um *)BIO_get_ex_data(bio, IDX_BIO_DATA_MACHINE);
11
- long fd = (long)BIO_get_ex_data(bio, IDX_BIO_DATA_FD);
11
+ int fd = (int)(long)BIO_get_ex_data(bio, IDX_BIO_DATA_FD);
12
12
  return (int)um_read_raw(machine, fd, buf, blen);
13
13
  }
14
14
 
15
15
  static int um_bio_write(BIO *bio, const char *buf, int blen)
16
16
  {
17
17
  struct um *machine = (struct um *)BIO_get_ex_data(bio, IDX_BIO_DATA_MACHINE);
18
- long fd = (long)BIO_get_ex_data(bio, IDX_BIO_DATA_FD);
18
+ int fd = (int)(long)BIO_get_ex_data(bio, IDX_BIO_DATA_FD);
19
19
  return (int)um_write_raw(machine, fd, buf, blen);
20
20
  }
21
21
 
@@ -105,3 +105,38 @@ int um_ssl_write(struct um *machine, VALUE ssl_obj, VALUE buf, size_t len) {
105
105
 
106
106
  return ret;
107
107
  }
108
+
109
+ int um_ssl_write_raw(struct um *machine, VALUE ssl_obj, const char *buffer, size_t len) {
110
+ SSL *ssl = RTYPEDDATA_GET_DATA(ssl_obj);
111
+ if (unlikely(!len)) return INT2NUM(0);
112
+
113
+ int ret = SSL_write(ssl, buffer, (int)len);
114
+ if (ret <= 0) rb_raise(eUMError, "Failed to write");
115
+
116
+ return ret;
117
+ }
118
+
119
+ int um_ssl_write_all(struct um *machine, VALUE ssl_obj, VALUE buf) {
120
+ SSL *ssl = RTYPEDDATA_GET_DATA(ssl_obj);
121
+ const char *base;
122
+ size_t size;
123
+ um_get_buffer_bytes_for_writing(buf, (const void **)&base, &size, true);
124
+
125
+ size_t left = size;
126
+ while (left) {
127
+ int ret = SSL_write(ssl, base, (int)left);
128
+ if (ret <= 0) rb_raise(eUMError, "Failed to write");
129
+
130
+ left -= ret;
131
+ base += ret;
132
+ }
133
+
134
+ return size;
135
+ }
136
+
137
+ int um_ssl_writev(struct um *machine, VALUE ssl, int argc, VALUE *argv) {
138
+ size_t total = 0;
139
+ for (int i = 0; i < argc; i++)
140
+ total += um_ssl_write_all(machine, ssl, argv[i]);
141
+ return total;
142
+ }
data/ext/um/um_utils.c CHANGED
@@ -159,7 +159,7 @@ int um_setup_buffer_ring(struct um *machine, unsigned size, unsigned count) {
159
159
  return bg_id;
160
160
  }
161
161
 
162
- inline VALUE um_get_string_from_buffer_ring(struct um *machine, int bgid, __s32 result, __u32 flags) {
162
+ inline VALUE um_read_from_buffer_ring(struct um *machine, int bgid, __s32 result, __u32 flags) {
163
163
  if (!result) return Qnil;
164
164
 
165
165
  unsigned buf_idx = flags >> IORING_CQE_BUFFER_SHIFT;
@@ -244,6 +244,8 @@ scheduler.join
244
244
  - Added more low-level methods for performing I/O operations supported by
245
245
  io_uring: `splice`, `tee`, `fsync`.
246
246
 
247
+ - Added RDoc [documentation](https://www.rubydoc.info/gems/uringmachine).
248
+
247
249
  ### Benchmarking
248
250
 
249
251
  - I did extensive benchmarking comparing different solutions for performing
@@ -121,7 +121,7 @@ Ruby I/O layer. Some interesting warts in the Ruby `IO` implementation:
121
121
  ```ruby
122
122
  def io_write(io, buffer, length, offset)
123
123
  reset_nonblock(io)
124
- @machine.write(io.fileno, buffer.get_string)
124
+ @machine.write(io.fileno, buffer.read)
125
125
  rescue Errno::EINTR
126
126
  retry
127
127
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  class UringMachine
4
- VERSION = '0.30.0'
4
+ VERSION = '0.31.0'
5
5
  end
data/lib/uringmachine.rb CHANGED
@@ -195,28 +195,28 @@ class UringMachine
195
195
  end
196
196
 
197
197
  # call-seq:
198
- # machine.stream(fd, mode = nil) -> stream
199
- # machine.stream(fd, mode = nil) { |stream| }
198
+ # machine.connection(fd, mode = nil) -> conn
199
+ # machine.connection(fd, mode = nil) { |conn| }
200
200
  #
201
- # Creates a stream for reading from the given target fd or other object. The
202
- # mode indicates the type of target and how it is read from:
201
+ # Creates a connection for reading from the given target fd or other object.
202
+ # The mode indicates the type of target and how it is read from:
203
203
  #
204
- # - :bp_read - read from the given fd using the buffer pool (default mode)
205
- # - :bp_recv - receive from the given socket fd using the buffer pool
204
+ # - :fd - read from the given fd using the buffer pool (default mode)
205
+ # - :socket - receive from the given socket fd using the buffer pool
206
206
  # - :ssl - read from the given SSL connection
207
207
  #
208
- # If a block is given, the block will be called with the stream object and the
209
- # method will return the block's return value.
208
+ # If a block is given, the block will be called with the connection object and
209
+ # the method will return the block's return value.
210
210
  #
211
211
  # @param target [Integer, OpenSSL::SSL::SSLSocket] fd or ssl connection
212
- # @param mode [Symbol, nil] stream mode
213
- # @return [UringMachine::Stream] stream object
214
- def stream(target, mode = nil)
215
- stream = UM::Stream.new(self, target, mode)
216
- return stream if !block_given?
217
-
218
- res = yield(stream)
219
- stream.clear
212
+ # @param mode [Symbol, nil] connection mode
213
+ # @return [UringMachine::Stream] connection object
214
+ def connection(target, mode = nil)
215
+ conn = UM::Connection.new(self, target, mode)
216
+ return conn if !block_given?
217
+
218
+ res = yield(conn)
219
+ conn.clear
220
220
  res
221
221
  end
222
222