uringmachine 0.5.1 → 0.6.1
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 +4 -4
- data/.github/workflows/test.yml +1 -1
- data/CHANGELOG.md +8 -0
- data/TODO.md +4 -0
- data/examples/bm_http_parse.rb +149 -0
- data/examples/bm_queue.rb +111 -0
- data/ext/um/extconf.rb +1 -1
- data/ext/um/um.c +94 -3
- data/ext/um/um.h +9 -6
- data/ext/um/um_class.c +6 -0
- data/lib/uringmachine/actor.rb +52 -0
- data/lib/uringmachine/version.rb +1 -1
- data/lib/uringmachine.rb +2 -2
- data/test/run.rb +5 -0
- data/test/test_actor.rb +63 -0
- data/test/test_um.rb +80 -13
- data/uringmachine.gemspec +2 -2
- metadata +12 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 602ceb6207baea6b38287928500c73df48258ca2f8cf843f02b45fa65c591e3e
|
4
|
+
data.tar.gz: 78b2bc7a274b03e2ae51510e213be0d23911c4db102ad4ab7eee435f915bb5c0
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c0dcbb93493c9778cf4ac12769f88611364d4e190c9e9dfa666543643e3aa3b44fa01cc49cf1ea2a1ba4798c4893b75e8b47e5b1eeac23b48bc5a6d84046e458
|
7
|
+
data.tar.gz: 5b012fcc3e7fd1a6d4e68aae58b9d099f19d5a89670772cdfdd6ef4ac76ed9781f533fd576e2b19b02c7774ba31d3ce6ee06dc590e04ce07a62e3c7fb2394191
|
data/.github/workflows/test.yml
CHANGED
data/CHANGELOG.md
CHANGED
data/TODO.md
CHANGED
@@ -0,0 +1,149 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/inline'
|
4
|
+
|
5
|
+
gemfile do
|
6
|
+
source 'https://rubygems.org'
|
7
|
+
gem 'uringmachine', path: '..'
|
8
|
+
gem 'benchmark-ips'
|
9
|
+
gem 'http_parser.rb'
|
10
|
+
end
|
11
|
+
|
12
|
+
require 'benchmark/ips'
|
13
|
+
require 'uringmachine'
|
14
|
+
require 'http/parser'
|
15
|
+
|
16
|
+
$machine = UM.new
|
17
|
+
|
18
|
+
HTTP_MSG = "GET /foo/bar HTTP/1.1\r\nServer: foobar.com\r\nFoo: bar\r\n\r\n"
|
19
|
+
|
20
|
+
$count = 0
|
21
|
+
|
22
|
+
def parse_http_parser
|
23
|
+
current_fiber = Fiber.current
|
24
|
+
$count += 1
|
25
|
+
r, w = IO.pipe
|
26
|
+
parser = Http::Parser.new
|
27
|
+
$machine.spin do
|
28
|
+
buffer = +''
|
29
|
+
loop do
|
30
|
+
res = $machine.read(r.fileno, buffer, 512)
|
31
|
+
break if res == 0
|
32
|
+
parser << buffer
|
33
|
+
end
|
34
|
+
rescue Exception => e
|
35
|
+
$machine.schedule(current_fiber, e)
|
36
|
+
# puts e.backtrace.join("\n")
|
37
|
+
# exit!
|
38
|
+
end
|
39
|
+
parser.on_message_complete = -> do
|
40
|
+
headers = parser.headers
|
41
|
+
headers['method'] = parser.http_method.downcase
|
42
|
+
headers['path'] = parser.request_url
|
43
|
+
headers['protocol'] = parser.http_version
|
44
|
+
$machine.schedule(current_fiber, headers)
|
45
|
+
end
|
46
|
+
|
47
|
+
$machine.write(w.fileno, HTTP_MSG)
|
48
|
+
$machine.yield
|
49
|
+
ensure
|
50
|
+
$machine.close(r.fileno)
|
51
|
+
$machine.close(w.fileno)
|
52
|
+
end
|
53
|
+
|
54
|
+
require 'stringio'
|
55
|
+
|
56
|
+
RE_REQUEST_LINE = /^([a-z]+)\s+([^\s]+)\s+(http\/[0-9\.]{1,3})/i
|
57
|
+
RE_HEADER_LINE = /^([a-z0-9\-]+)\:\s+(.+)/i
|
58
|
+
|
59
|
+
def get_line(fd, sio, buffer)
|
60
|
+
while true
|
61
|
+
line = sio.gets(chomp: true)
|
62
|
+
return line if line
|
63
|
+
|
64
|
+
res = $machine.read(fd, buffer, 1024, -1)
|
65
|
+
return nil if res == 0
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
def get_request_line(fd, sio, buffer)
|
70
|
+
line = get_line(fd, sio, buffer)
|
71
|
+
|
72
|
+
m = line.match(RE_REQUEST_LINE)
|
73
|
+
return nil if !m
|
74
|
+
|
75
|
+
{
|
76
|
+
'method' => m[1].downcase,
|
77
|
+
'path' => m[2],
|
78
|
+
'protocol' => m[3].downcase
|
79
|
+
}
|
80
|
+
end
|
81
|
+
|
82
|
+
def parse_headers(fd)
|
83
|
+
buffer = String.new('', capacity: 4096)
|
84
|
+
sio = StringIO.new(buffer)
|
85
|
+
|
86
|
+
headers = get_request_line(fd, sio, buffer)
|
87
|
+
return nil if !headers
|
88
|
+
|
89
|
+
while true
|
90
|
+
line = get_line(fd, sio, buffer)
|
91
|
+
break if line.empty?
|
92
|
+
|
93
|
+
m = line.match(RE_HEADER_LINE)
|
94
|
+
raise "Invalid header" if !m
|
95
|
+
|
96
|
+
headers[m[1]] = m[2]
|
97
|
+
end
|
98
|
+
|
99
|
+
headers
|
100
|
+
end
|
101
|
+
|
102
|
+
def parse_http_stringio
|
103
|
+
current_fiber = Fiber.current
|
104
|
+
r, w = IO.pipe
|
105
|
+
|
106
|
+
$machine.spin do
|
107
|
+
headers = parse_headers(r.fileno)
|
108
|
+
$machine.schedule(current_fiber, headers)
|
109
|
+
rescue Exception => e
|
110
|
+
p e
|
111
|
+
puts e.backtrace.join("\n")
|
112
|
+
exit!
|
113
|
+
end
|
114
|
+
|
115
|
+
$machine.write(w.fileno, HTTP_MSG)
|
116
|
+
$machine.yield
|
117
|
+
ensure
|
118
|
+
$machine.close(r.fileno)
|
119
|
+
$machine.close(w.fileno)
|
120
|
+
end
|
121
|
+
|
122
|
+
# p parse_http_parser
|
123
|
+
# p parse_http_stringio
|
124
|
+
# exit
|
125
|
+
|
126
|
+
GC.disable
|
127
|
+
|
128
|
+
def alloc_count
|
129
|
+
count0 = ObjectSpace.count_objects[:TOTAL]
|
130
|
+
yield
|
131
|
+
count1 = ObjectSpace.count_objects[:TOTAL]
|
132
|
+
count1 - count0
|
133
|
+
end
|
134
|
+
|
135
|
+
X = 100
|
136
|
+
p(
|
137
|
+
alloc_http_parser: alloc_count { X.times { parse_http_parser } },
|
138
|
+
alloc_stringio: alloc_count { X.times { parse_http_stringio } }
|
139
|
+
)
|
140
|
+
exit
|
141
|
+
|
142
|
+
Benchmark.ips do |x|
|
143
|
+
x.config(:time => 5, :warmup => 3)
|
144
|
+
|
145
|
+
x.report("http_parser") { parse_http_parser }
|
146
|
+
x.report("homegrown") { parse_http_stringio }
|
147
|
+
|
148
|
+
x.compare!
|
149
|
+
end
|
@@ -0,0 +1,111 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/inline'
|
4
|
+
|
5
|
+
gemfile do
|
6
|
+
source 'https://rubygems.org'
|
7
|
+
gem 'uringmachine', path: '..'
|
8
|
+
gem 'benchmark-ips'
|
9
|
+
end
|
10
|
+
|
11
|
+
require 'benchmark/ips'
|
12
|
+
require 'uringmachine'
|
13
|
+
|
14
|
+
COUNT = 1000
|
15
|
+
NUM_PRODUCERS = 2
|
16
|
+
NUM_CONSUMERS = 10
|
17
|
+
|
18
|
+
def run_threads
|
19
|
+
queue = Queue.new
|
20
|
+
done = Queue.new
|
21
|
+
|
22
|
+
NUM_PRODUCERS.times do
|
23
|
+
Thread.new do
|
24
|
+
COUNT.times { queue << rand(1000) }
|
25
|
+
done << true
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
total = 0
|
30
|
+
NUM_CONSUMERS.times do
|
31
|
+
Thread.new do
|
32
|
+
loop do
|
33
|
+
item = queue.shift
|
34
|
+
break if item.nil?
|
35
|
+
|
36
|
+
total += item
|
37
|
+
end
|
38
|
+
done << true
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# wait for producers
|
43
|
+
NUM_PRODUCERS.times { done.shift }
|
44
|
+
|
45
|
+
# stop and wait for consumers
|
46
|
+
NUM_CONSUMERS.times do
|
47
|
+
queue << nil
|
48
|
+
done.shift
|
49
|
+
end
|
50
|
+
|
51
|
+
total
|
52
|
+
end
|
53
|
+
|
54
|
+
def run_um
|
55
|
+
machine = UM.new
|
56
|
+
queue = UM::Queue.new
|
57
|
+
done = UM::Queue.new
|
58
|
+
|
59
|
+
NUM_PRODUCERS.times do
|
60
|
+
machine.spin do
|
61
|
+
COUNT.times { machine.push(queue, rand(1000)) }
|
62
|
+
machine.push(done, true)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
total = 0
|
67
|
+
NUM_CONSUMERS.times do
|
68
|
+
machine.spin do
|
69
|
+
loop do
|
70
|
+
item = machine.shift(queue)
|
71
|
+
break if item.nil?
|
72
|
+
|
73
|
+
total += item
|
74
|
+
end
|
75
|
+
machine.push(done, true)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
# wait for producers
|
80
|
+
NUM_PRODUCERS.times { machine.shift(done) }
|
81
|
+
|
82
|
+
# stop and wait for consumers
|
83
|
+
NUM_CONSUMERS.times do
|
84
|
+
machine.push(queue, nil)
|
85
|
+
machine.shift(done)
|
86
|
+
end
|
87
|
+
|
88
|
+
total
|
89
|
+
end
|
90
|
+
|
91
|
+
|
92
|
+
# puts "running"
|
93
|
+
# res = run_threads
|
94
|
+
# p threads: res
|
95
|
+
|
96
|
+
# 100.times {
|
97
|
+
# res = run_um
|
98
|
+
# p fibers: res
|
99
|
+
# }
|
100
|
+
|
101
|
+
|
102
|
+
# __END__
|
103
|
+
|
104
|
+
Benchmark.ips do |x|
|
105
|
+
x.config(:time => 5, :warmup => 2)
|
106
|
+
|
107
|
+
x.report("threads") { run_threads }
|
108
|
+
x.report("UM") { run_um }
|
109
|
+
|
110
|
+
x.compare!
|
111
|
+
end
|
data/ext/um/extconf.rb
CHANGED
data/ext/um/um.c
CHANGED
@@ -88,8 +88,9 @@ static inline void um_process_cqe(struct um *machine, struct io_uring_cqe *cqe)
|
|
88
88
|
op->result.flags = cqe->flags;
|
89
89
|
}
|
90
90
|
|
91
|
-
if (
|
92
|
-
|
91
|
+
if (op->flags & OP_F_ASYNC) return;
|
92
|
+
|
93
|
+
um_runqueue_push(machine, op);
|
93
94
|
}
|
94
95
|
|
95
96
|
// copied from liburing/queue.c
|
@@ -174,8 +175,28 @@ inline VALUE process_runqueue_op(struct um *machine, struct um_op *op) {
|
|
174
175
|
inline VALUE um_fiber_switch(struct um *machine) {
|
175
176
|
while (true) {
|
176
177
|
struct um_op *op = um_runqueue_shift(machine);
|
177
|
-
if (op)
|
178
|
+
if (op) {
|
179
|
+
// in case of a snooze, we need to prevent a situation where completions
|
180
|
+
// are not processed because the runqueue is never empty. Theoretically,
|
181
|
+
// we can still have a situation where multiple fibers are all doing a
|
182
|
+
// snooze repeatedly, which can prevent completions from being processed.
|
183
|
+
|
184
|
+
// is the op a snooze op and is this the same fiber as the current one?
|
185
|
+
if (unlikely(op->kind == OP_SCHEDULE && op->fiber == rb_fiber_current())) {
|
186
|
+
// are there any pending ops (i.e. waiting for completion)?
|
187
|
+
if (machine->pending_count > 0) {
|
188
|
+
// if yes, process completions, get runqueue head, put original op
|
189
|
+
// back on runqueue.
|
190
|
+
um_wait_for_and_process_ready_cqes(machine);
|
191
|
+
struct um_op *op2 = um_runqueue_shift(machine);
|
192
|
+
if (likely(op2 && op2 != op)) {
|
193
|
+
um_runqueue_push(machine, op);
|
194
|
+
op = op2;
|
195
|
+
}
|
196
|
+
}
|
197
|
+
}
|
178
198
|
return process_runqueue_op(machine, op);
|
199
|
+
}
|
179
200
|
|
180
201
|
um_wait_for_and_process_ready_cqes(machine);
|
181
202
|
}
|
@@ -216,6 +237,8 @@ inline void um_prep_op(struct um *machine, struct um_op *op, enum op_kind kind)
|
|
216
237
|
case OP_ACCEPT_MULTISHOT:
|
217
238
|
case OP_READ_MULTISHOT:
|
218
239
|
case OP_RECV_MULTISHOT:
|
240
|
+
case OP_TIMEOUT_MULTISHOT:
|
241
|
+
case OP_SLEEP_MULTISHOT:
|
219
242
|
op->flags |= OP_F_MULTISHOT;
|
220
243
|
default:
|
221
244
|
}
|
@@ -297,6 +320,33 @@ VALUE um_sleep(struct um *machine, double duration) {
|
|
297
320
|
return raise_if_exception(ret);
|
298
321
|
}
|
299
322
|
|
323
|
+
// VALUE um_periodically(struct um *machine, double interval) {
|
324
|
+
// struct um_op op;
|
325
|
+
// VALUE ret = Qnil;
|
326
|
+
// um_prep_op(machine, &op, OP_SLEEP_MULTISHOT);
|
327
|
+
// op.ts = um_double_to_timespec(interval);
|
328
|
+
// op.flags |= OP_F_MULTISHOT;
|
329
|
+
// struct io_uring_sqe *sqe = um_get_sqe(machine, &op);
|
330
|
+
// io_uring_prep_timeout(sqe, &op.ts, 0, IORING_TIMEOUT_MULTISHOT);
|
331
|
+
|
332
|
+
// while (true) {
|
333
|
+
// ret = um_fiber_switch(machine);
|
334
|
+
|
335
|
+
// if (!um_op_completed_p(&op)) {
|
336
|
+
// um_cancel_and_wait(machine, &op);
|
337
|
+
// break;
|
338
|
+
// }
|
339
|
+
// else {
|
340
|
+
// if (op.result.res != -ETIME) um_raise_on_error_result(op.result.res);
|
341
|
+
// ret = DBL2NUM(interval);
|
342
|
+
// }
|
343
|
+
// }
|
344
|
+
|
345
|
+
// RB_GC_GUARD(ret);
|
346
|
+
// return raise_if_exception(ret);
|
347
|
+
|
348
|
+
// }
|
349
|
+
|
300
350
|
inline VALUE um_read(struct um *machine, int fd, VALUE buffer, int maxlen, int buffer_offset) {
|
301
351
|
struct um_op op;
|
302
352
|
um_prep_op(machine, &op, OP_READ);
|
@@ -701,3 +751,44 @@ VALUE um_recv_each(struct um *machine, int fd, int bgid, int flags) {
|
|
701
751
|
struct op_ctx ctx = { .machine = machine, .op = &op, .fd = fd, .bgid = bgid, .read_buf = NULL, .flags = flags };
|
702
752
|
return rb_ensure(read_recv_each_begin, (VALUE)&ctx, multishot_ensure, (VALUE)&ctx);
|
703
753
|
}
|
754
|
+
|
755
|
+
VALUE periodically_begin(VALUE arg) {
|
756
|
+
struct op_ctx *ctx = (struct op_ctx *)arg;
|
757
|
+
struct io_uring_sqe *sqe = um_get_sqe(ctx->machine, ctx->op);
|
758
|
+
io_uring_prep_timeout(sqe, &ctx->ts, 0, IORING_TIMEOUT_MULTISHOT);
|
759
|
+
|
760
|
+
while (true) {
|
761
|
+
VALUE ret = um_fiber_switch(ctx->machine);
|
762
|
+
if (!um_op_completed_p(ctx->op))
|
763
|
+
return raise_if_exception(ret);
|
764
|
+
|
765
|
+
int more = false;
|
766
|
+
struct um_op_result *result = &ctx->op->result;
|
767
|
+
while (result) {
|
768
|
+
more = (result->flags & IORING_CQE_F_MORE);
|
769
|
+
if (result->res < 0 && result->res != -ETIME) {
|
770
|
+
um_op_multishot_results_clear(ctx->machine, ctx->op);
|
771
|
+
return Qnil;
|
772
|
+
}
|
773
|
+
rb_yield(Qnil);
|
774
|
+
result = result->next;
|
775
|
+
}
|
776
|
+
um_op_multishot_results_clear(ctx->machine, ctx->op);
|
777
|
+
if (more)
|
778
|
+
ctx->op->flags &= ~OP_F_COMPLETED;
|
779
|
+
else
|
780
|
+
break;
|
781
|
+
}
|
782
|
+
|
783
|
+
return Qnil;
|
784
|
+
}
|
785
|
+
|
786
|
+
VALUE um_periodically(struct um *machine, double interval) {
|
787
|
+
struct um_op op;
|
788
|
+
um_prep_op(machine, &op, OP_SLEEP_MULTISHOT);
|
789
|
+
op.ts = um_double_to_timespec(interval);
|
790
|
+
|
791
|
+
struct op_ctx ctx = { .machine = machine, .op = &op, .ts = op.ts, .read_buf = NULL };
|
792
|
+
return rb_ensure(periodically_begin, (VALUE)&ctx, multishot_ensure, (VALUE)&ctx);
|
793
|
+
}
|
794
|
+
|
data/ext/um/um.h
CHANGED
@@ -43,14 +43,16 @@ enum op_kind {
|
|
43
43
|
|
44
44
|
OP_ACCEPT_MULTISHOT,
|
45
45
|
OP_READ_MULTISHOT,
|
46
|
-
OP_RECV_MULTISHOT
|
46
|
+
OP_RECV_MULTISHOT,
|
47
|
+
OP_TIMEOUT_MULTISHOT,
|
48
|
+
OP_SLEEP_MULTISHOT
|
47
49
|
};
|
48
50
|
|
49
|
-
#define OP_F_COMPLETED (1U << 0)
|
50
|
-
#define OP_F_TRANSIENT (1U << 1)
|
51
|
-
#define OP_F_ASYNC (1U << 2)
|
52
|
-
#define OP_F_IGNORE_CANCELED (1U << 3)
|
53
|
-
#define OP_F_MULTISHOT (1U << 4)
|
51
|
+
#define OP_F_COMPLETED (1U << 0) // op is completed (set on each CQE for multishot ops)
|
52
|
+
#define OP_F_TRANSIENT (1U << 1) // op is heap allocated
|
53
|
+
#define OP_F_ASYNC (1U << 2) // op belongs to an AsyncOp
|
54
|
+
#define OP_F_IGNORE_CANCELED (1U << 3) // CQE with -ECANCEL should be ignored
|
55
|
+
#define OP_F_MULTISHOT (1U << 4) // op is multishot
|
54
56
|
|
55
57
|
struct um_op_result {
|
56
58
|
__s32 res;
|
@@ -199,6 +201,7 @@ void um_schedule(struct um *machine, VALUE fiber, VALUE value);
|
|
199
201
|
VALUE um_timeout(struct um *machine, VALUE interval, VALUE class);
|
200
202
|
|
201
203
|
VALUE um_sleep(struct um *machine, double duration);
|
204
|
+
VALUE um_periodically(struct um *machine, double interval);
|
202
205
|
VALUE um_read(struct um *machine, int fd, VALUE buffer, int maxlen, int buffer_offset);
|
203
206
|
VALUE um_read_each(struct um *machine, int fd, int bgid);
|
204
207
|
VALUE um_write(struct um *machine, int fd, VALUE str, int len);
|
data/ext/um/um_class.c
CHANGED
@@ -91,6 +91,11 @@ VALUE UM_sleep(VALUE self, VALUE duration) {
|
|
91
91
|
return um_sleep(machine, NUM2DBL(duration));
|
92
92
|
}
|
93
93
|
|
94
|
+
VALUE UM_periodically(VALUE self, VALUE interval) {
|
95
|
+
struct um *machine = um_get_machine(self);
|
96
|
+
return um_periodically(machine, NUM2DBL(interval));
|
97
|
+
}
|
98
|
+
|
94
99
|
VALUE UM_read(int argc, VALUE *argv, VALUE self) {
|
95
100
|
struct um *machine = um_get_machine(self);
|
96
101
|
VALUE fd;
|
@@ -329,6 +334,7 @@ void Init_UM(void) {
|
|
329
334
|
rb_define_method(cUM, "read", UM_read, -1);
|
330
335
|
rb_define_method(cUM, "read_each", UM_read_each, 2);
|
331
336
|
rb_define_method(cUM, "sleep", UM_sleep, 1);
|
337
|
+
rb_define_method(cUM, "periodically", UM_periodically, 1);
|
332
338
|
rb_define_method(cUM, "write", UM_write, -1);
|
333
339
|
|
334
340
|
rb_define_method(cUM, "waitpid", UM_waitpid, 2);
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class UringMachine
|
4
|
+
def spin_actor(mod, *a, **k)
|
5
|
+
target = Object.new.extend(mod)
|
6
|
+
mailbox = UM::Queue.new
|
7
|
+
actor = spin(nil, Actor) { actor.run(self, target, mailbox) }
|
8
|
+
target.setup(*a, **k)
|
9
|
+
snooze
|
10
|
+
actor
|
11
|
+
end
|
12
|
+
|
13
|
+
class Actor < Fiber
|
14
|
+
def run(machine, target, mailbox)
|
15
|
+
@machine = machine
|
16
|
+
@target = target
|
17
|
+
@mailbox = mailbox
|
18
|
+
while (msg = machine.shift(mailbox))
|
19
|
+
process_message(msg)
|
20
|
+
end
|
21
|
+
ensure
|
22
|
+
@target.teardown if @target.respond_to?(:teardown)
|
23
|
+
end
|
24
|
+
|
25
|
+
def cast(sym, *a, **k)
|
26
|
+
self << [:cast, nil, sym, a, k]
|
27
|
+
self
|
28
|
+
end
|
29
|
+
|
30
|
+
def call(sym, *a, **k)
|
31
|
+
self << [:call, Fiber.current, sym, a, k]
|
32
|
+
@machine.yield
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def process_message(msg)
|
38
|
+
type, fiber, sym, args, kwargs = msg
|
39
|
+
case type
|
40
|
+
when :cast
|
41
|
+
@target.send(sym, *args, **kwargs)
|
42
|
+
when :call
|
43
|
+
res = @target.send(sym, *args, **kwargs)
|
44
|
+
@machine.schedule(fiber, res)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def <<(msg)
|
49
|
+
@machine.push(@mailbox, msg)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/lib/uringmachine/version.rb
CHANGED
data/lib/uringmachine.rb
CHANGED
@@ -12,8 +12,8 @@ class UringMachine
|
|
12
12
|
@@fiber_map
|
13
13
|
end
|
14
14
|
|
15
|
-
def spin(value = nil, &block)
|
16
|
-
f =
|
15
|
+
def spin(value = nil, fiber_class = Fiber, &block)
|
16
|
+
f = fiber_class.new do |resume_value|
|
17
17
|
block.(resume_value)
|
18
18
|
rescue Exception => e
|
19
19
|
STDERR.puts "Unhandled fiber exception: #{e.inspect}"
|
data/test/run.rb
ADDED
data/test/test_actor.rb
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'helper'
|
4
|
+
require 'socket'
|
5
|
+
require 'uringmachine/actor'
|
6
|
+
|
7
|
+
class ActorTest < UMBaseTest
|
8
|
+
module Counter
|
9
|
+
def setup
|
10
|
+
@count = 0
|
11
|
+
end
|
12
|
+
|
13
|
+
def incr
|
14
|
+
@count += 1
|
15
|
+
end
|
16
|
+
|
17
|
+
def get
|
18
|
+
@count
|
19
|
+
end
|
20
|
+
|
21
|
+
def reset
|
22
|
+
@count = 0
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def test_basic_actor_functionality
|
27
|
+
actor = @machine.spin_actor(Counter)
|
28
|
+
|
29
|
+
assert_kind_of Fiber, actor
|
30
|
+
|
31
|
+
assert_equal 0, actor.call(:get)
|
32
|
+
assert_equal 1, actor.call(:incr)
|
33
|
+
assert_equal actor, actor.cast(:incr)
|
34
|
+
assert_equal 2, actor.call(:get)
|
35
|
+
assert_equal actor, actor.cast(:reset)
|
36
|
+
assert_equal 0, actor.call(:get)
|
37
|
+
end
|
38
|
+
|
39
|
+
module Counter2
|
40
|
+
def setup(count)
|
41
|
+
@count = count
|
42
|
+
end
|
43
|
+
|
44
|
+
def incr
|
45
|
+
@count += 1
|
46
|
+
end
|
47
|
+
|
48
|
+
def get
|
49
|
+
@count
|
50
|
+
end
|
51
|
+
|
52
|
+
def reset
|
53
|
+
@count = 0
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
def test_actor_with_args
|
59
|
+
actor = @machine.spin_actor(Counter2, 43)
|
60
|
+
|
61
|
+
assert_equal 43, actor.call(:get)
|
62
|
+
end
|
63
|
+
end
|
data/test/test_um.rb
CHANGED
@@ -212,6 +212,57 @@ class SleepTest < UMBaseTest
|
|
212
212
|
end
|
213
213
|
end
|
214
214
|
|
215
|
+
class PeriodicallyTest < UMBaseTest
|
216
|
+
class Cancel < StandardError; end
|
217
|
+
|
218
|
+
def test_periodically
|
219
|
+
count = 0
|
220
|
+
cancel = 0
|
221
|
+
|
222
|
+
t0 = monotonic_clock
|
223
|
+
assert_equal 0, machine.pending_count
|
224
|
+
begin
|
225
|
+
machine.periodically(0.01) do
|
226
|
+
count += 1
|
227
|
+
raise Cancel if count >= 5
|
228
|
+
end
|
229
|
+
rescue Cancel
|
230
|
+
cancel = 1
|
231
|
+
end
|
232
|
+
machine.snooze
|
233
|
+
assert_equal 0, machine.pending_count
|
234
|
+
t1 = monotonic_clock
|
235
|
+
assert_in_range 0.05..0.09, t1 - t0
|
236
|
+
assert_equal 5, count
|
237
|
+
assert_equal 1, cancel
|
238
|
+
end
|
239
|
+
|
240
|
+
def test_periodically_with_timeout
|
241
|
+
count = 0
|
242
|
+
cancel = 0
|
243
|
+
|
244
|
+
t0 = monotonic_clock
|
245
|
+
assert_equal 0, machine.pending_count
|
246
|
+
begin
|
247
|
+
machine.timeout(0.05, Cancel) do
|
248
|
+
machine.periodically(0.01) do
|
249
|
+
count += 1
|
250
|
+
raise Cancel if count >= 5
|
251
|
+
end
|
252
|
+
end
|
253
|
+
rescue Cancel
|
254
|
+
cancel = 1
|
255
|
+
end
|
256
|
+
machine.snooze
|
257
|
+
assert_equal 0, machine.pending_count
|
258
|
+
t1 = monotonic_clock
|
259
|
+
assert_in_range 0.05..0.08, t1 - t0
|
260
|
+
assert_in_range 4..6, count
|
261
|
+
assert_equal 1, cancel
|
262
|
+
|
263
|
+
end
|
264
|
+
end
|
265
|
+
|
215
266
|
class ReadTest < UMBaseTest
|
216
267
|
def test_read
|
217
268
|
r, w = IO.pipe
|
@@ -274,6 +325,26 @@ class ReadTest < UMBaseTest
|
|
274
325
|
assert_equal 3, result
|
275
326
|
assert_equal 'foobar', buffer
|
276
327
|
end
|
328
|
+
|
329
|
+
def test_read_with_string_io
|
330
|
+
require 'stringio'
|
331
|
+
|
332
|
+
buffer = +'foo'
|
333
|
+
sio = StringIO.new(buffer)
|
334
|
+
|
335
|
+
r, w = IO.pipe
|
336
|
+
w << 'bar'
|
337
|
+
|
338
|
+
result = machine.read(r.fileno, buffer, 100, -1)
|
339
|
+
assert_equal 3, result
|
340
|
+
assert_equal 'foobar', sio.read
|
341
|
+
|
342
|
+
w << 'baz'
|
343
|
+
|
344
|
+
result = machine.read(r.fileno, buffer, 100, -1)
|
345
|
+
assert_equal 3, result
|
346
|
+
assert_equal 'baz', sio.read
|
347
|
+
end
|
277
348
|
end
|
278
349
|
|
279
350
|
class ReadEachTest < UMBaseTest
|
@@ -846,32 +917,28 @@ class QueueTest < UMBaseTest
|
|
846
917
|
q = UM::Queue.new
|
847
918
|
buf = []
|
848
919
|
|
849
|
-
|
920
|
+
machine.spin do
|
850
921
|
buf << [1, machine.pop(q)]
|
851
|
-
machine.yield
|
852
922
|
end
|
853
923
|
|
854
|
-
machine.
|
855
|
-
|
856
|
-
f2 = Fiber.new do
|
924
|
+
machine.spin do
|
857
925
|
buf << [2, machine.pop(q)]
|
858
|
-
machine.yield
|
859
926
|
end
|
860
927
|
|
861
|
-
machine.schedule(f2, nil)
|
862
|
-
|
863
928
|
machine.snooze
|
864
929
|
assert_equal [], buf
|
930
|
+
assert_equal 2, machine.pending_count
|
865
931
|
|
866
932
|
machine.push(q, :foo)
|
867
933
|
assert_equal 1, q.count
|
868
|
-
machine.
|
934
|
+
machine.snooze
|
935
|
+
assert_equal 1, machine.pending_count
|
869
936
|
assert_equal [[1, :foo]], buf
|
870
937
|
|
871
938
|
machine.push(q, :bar)
|
872
939
|
assert_equal 1, q.count
|
873
940
|
|
874
|
-
machine.
|
941
|
+
machine.snooze
|
875
942
|
assert_equal [[1, :foo], [2, :bar]], buf
|
876
943
|
assert_equal 0, q.count
|
877
944
|
end
|
@@ -898,7 +965,7 @@ class QueueTest < UMBaseTest
|
|
898
965
|
end
|
899
966
|
machine.schedule(f2, nil)
|
900
967
|
|
901
|
-
|
968
|
+
machine.snooze
|
902
969
|
|
903
970
|
assert_equal [[1, :bar], [2, :foo]], buf.sort
|
904
971
|
assert_equal 0, q.count
|
@@ -925,12 +992,12 @@ class QueueTest < UMBaseTest
|
|
925
992
|
end
|
926
993
|
machine.schedule(f2, nil)
|
927
994
|
|
928
|
-
machine.
|
995
|
+
machine.snooze
|
929
996
|
|
930
997
|
assert_equal [[1, :foo]], buf
|
931
998
|
machine.push(q, :bar)
|
932
999
|
|
933
|
-
machine.
|
1000
|
+
machine.snooze
|
934
1001
|
assert_equal [[1, :foo], [2, :bar]], buf
|
935
1002
|
end
|
936
1003
|
|
data/uringmachine.gemspec
CHANGED
@@ -20,8 +20,8 @@ Gem::Specification.new do |s|
|
|
20
20
|
s.require_paths = ["lib"]
|
21
21
|
s.required_ruby_version = '>= 3.3'
|
22
22
|
|
23
|
-
s.add_development_dependency 'rake-compiler', '1.2.
|
24
|
-
s.add_development_dependency 'minitest', '5.25.
|
23
|
+
s.add_development_dependency 'rake-compiler', '1.2.9'
|
24
|
+
s.add_development_dependency 'minitest', '5.25.4'
|
25
25
|
s.add_development_dependency 'http_parser.rb', '0.8.0'
|
26
26
|
s.add_development_dependency 'benchmark-ips', '2.14.0'
|
27
27
|
s.add_development_dependency 'localhost', '1.3.1'
|
metadata
CHANGED
@@ -1,14 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: uringmachine
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Sharon Rosner
|
8
|
-
autorequire:
|
9
8
|
bindir: bin
|
10
9
|
cert_chain: []
|
11
|
-
date:
|
10
|
+
date: 2025-04-23 00:00:00.000000000 Z
|
12
11
|
dependencies:
|
13
12
|
- !ruby/object:Gem::Dependency
|
14
13
|
name: rake-compiler
|
@@ -16,28 +15,28 @@ dependencies:
|
|
16
15
|
requirements:
|
17
16
|
- - '='
|
18
17
|
- !ruby/object:Gem::Version
|
19
|
-
version: 1.2.
|
18
|
+
version: 1.2.9
|
20
19
|
type: :development
|
21
20
|
prerelease: false
|
22
21
|
version_requirements: !ruby/object:Gem::Requirement
|
23
22
|
requirements:
|
24
23
|
- - '='
|
25
24
|
- !ruby/object:Gem::Version
|
26
|
-
version: 1.2.
|
25
|
+
version: 1.2.9
|
27
26
|
- !ruby/object:Gem::Dependency
|
28
27
|
name: minitest
|
29
28
|
requirement: !ruby/object:Gem::Requirement
|
30
29
|
requirements:
|
31
30
|
- - '='
|
32
31
|
- !ruby/object:Gem::Version
|
33
|
-
version: 5.25.
|
32
|
+
version: 5.25.4
|
34
33
|
type: :development
|
35
34
|
prerelease: false
|
36
35
|
version_requirements: !ruby/object:Gem::Requirement
|
37
36
|
requirements:
|
38
37
|
- - '='
|
39
38
|
- !ruby/object:Gem::Version
|
40
|
-
version: 5.25.
|
39
|
+
version: 5.25.4
|
41
40
|
- !ruby/object:Gem::Dependency
|
42
41
|
name: http_parser.rb
|
43
42
|
requirement: !ruby/object:Gem::Requirement
|
@@ -80,7 +79,6 @@ dependencies:
|
|
80
79
|
- - '='
|
81
80
|
- !ruby/object:Gem::Version
|
82
81
|
version: 1.3.1
|
83
|
-
description:
|
84
82
|
email: sharon@noteflakes.com
|
85
83
|
executables: []
|
86
84
|
extensions:
|
@@ -98,6 +96,8 @@ files:
|
|
98
96
|
- README.md
|
99
97
|
- Rakefile
|
100
98
|
- TODO.md
|
99
|
+
- examples/bm_http_parse.rb
|
100
|
+
- examples/bm_queue.rb
|
101
101
|
- examples/bm_snooze.rb
|
102
102
|
- examples/bm_sqlite.rb
|
103
103
|
- examples/bm_write.rb
|
@@ -129,12 +129,15 @@ files:
|
|
129
129
|
- ext/um/um_sync.c
|
130
130
|
- ext/um/um_utils.c
|
131
131
|
- lib/uringmachine.rb
|
132
|
+
- lib/uringmachine/actor.rb
|
132
133
|
- lib/uringmachine/dns_resolver.rb
|
133
134
|
- lib/uringmachine/ssl.rb
|
134
135
|
- lib/uringmachine/ssl/context_builder.rb
|
135
136
|
- lib/uringmachine/version.rb
|
136
137
|
- supressions/ruby.supp
|
137
138
|
- test/helper.rb
|
139
|
+
- test/run.rb
|
140
|
+
- test/test_actor.rb
|
138
141
|
- test/test_async_op.rb
|
139
142
|
- test/test_ssl.rb
|
140
143
|
- test/test_um.rb
|
@@ -439,7 +442,6 @@ metadata:
|
|
439
442
|
source_code_uri: https://github.com/digital-fabric/uringmachine
|
440
443
|
documentation_uri: https://www.rubydoc.info/gems/uringmachine
|
441
444
|
changelog_uri: https://github.com/digital-fabric/uringmachine/blob/master/CHANGELOG.md
|
442
|
-
post_install_message:
|
443
445
|
rdoc_options:
|
444
446
|
- "--title"
|
445
447
|
- UringMachine
|
@@ -458,8 +460,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
458
460
|
- !ruby/object:Gem::Version
|
459
461
|
version: '0'
|
460
462
|
requirements: []
|
461
|
-
rubygems_version: 3.
|
462
|
-
signing_key:
|
463
|
+
rubygems_version: 3.6.2
|
463
464
|
specification_version: 4
|
464
465
|
summary: A lean, mean io_uring machine
|
465
466
|
test_files: []
|