evt 0.1.1 → 0.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.github/workflows/test.yml +51 -0
- data/.gitignore +1 -0
- data/Gemfile +1 -1
- data/README.md +73 -2
- data/evt.gemspec +2 -2
- data/ext/evt/epoll.h +91 -0
- data/ext/evt/evt.c +30 -118
- data/ext/evt/evt.h +82 -0
- data/ext/evt/extconf.rb +9 -2
- data/ext/evt/iocp.h +126 -0
- data/ext/evt/kqueue.h +96 -0
- data/ext/evt/select.h +36 -0
- data/ext/evt/uring.h +199 -0
- data/lib/evt.rb +3 -3
- data/lib/evt/scheduler.rb +52 -101
- data/lib/evt/version.rb +3 -1
- metadata +11 -6
- data/.travis.yml +0 -6
- data/Gemfile.lock +0 -24
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 9466e3fd37da9807dc59525feebfe9252489a460a83111febf95973ff77fee56
|
4
|
+
data.tar.gz: 531808277a49049dc156144294346730a457d717699e091348125086dca529de
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c12a241295309fd31bf21bc4957374a636ff046b6aa1c5f670113f0cdeedb3df74f974145ba6f19f9e442d5f8d9ae373d70bfee6ae417dc09e9b620d57bcc78f
|
7
|
+
data.tar.gz: b3a369c7640d4770a89e7d37166930a757ca25266132fbd0544f2f0a065670de1c13578ea4cf7fc43ad1fb2c2020757b20cc972e85c23cab720bb229103fb070
|
@@ -0,0 +1,51 @@
|
|
1
|
+
name: CI Tests
|
2
|
+
on:
|
3
|
+
pull_request:
|
4
|
+
push:
|
5
|
+
branches:
|
6
|
+
- master
|
7
|
+
schedule:
|
8
|
+
- cron: '0 7 * * SUN'
|
9
|
+
jobs:
|
10
|
+
test:
|
11
|
+
strategy:
|
12
|
+
fail-fast: false
|
13
|
+
matrix:
|
14
|
+
include:
|
15
|
+
- { os: ubuntu-20.04, ruby: '3.0' }
|
16
|
+
- { os: ubuntu-20.04, ruby: ruby-head }
|
17
|
+
- { os: macos-11.0, ruby: '3.0' }
|
18
|
+
- { os: macos-11.0, ruby: ruby-head }
|
19
|
+
- { os: windows-2019, ruby: mingw }
|
20
|
+
- { os: windows-2019, ruby: mswin }
|
21
|
+
name: ${{ matrix.os }} ${{ matrix.ruby }}
|
22
|
+
runs-on: ${{ matrix.os }}
|
23
|
+
timeout-minutes: 5
|
24
|
+
steps:
|
25
|
+
- uses: actions/checkout@v2
|
26
|
+
- uses: ruby/setup-ruby@master
|
27
|
+
with:
|
28
|
+
ruby-version: ${{ matrix.ruby }}
|
29
|
+
bundler-cache: false
|
30
|
+
- name: Install Dependencies
|
31
|
+
run: |
|
32
|
+
gem install bundler
|
33
|
+
bundle install --jobs 4 --retry 3
|
34
|
+
- name: Compile
|
35
|
+
run: rake compile
|
36
|
+
- name: Test
|
37
|
+
run: rake
|
38
|
+
build:
|
39
|
+
runs-on: ubuntu-20.04
|
40
|
+
steps:
|
41
|
+
- uses: actions/checkout@v2
|
42
|
+
- uses: ruby/setup-ruby@master
|
43
|
+
with:
|
44
|
+
ruby-version: '3.0'
|
45
|
+
bundler-cache: false
|
46
|
+
- name: Install Dependencies
|
47
|
+
run: |
|
48
|
+
gem install bundler
|
49
|
+
bundle install --jobs 4 --retry 3
|
50
|
+
- name: Build
|
51
|
+
run: gem build evt.gemspec
|
data/.gitignore
CHANGED
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,2 +1,73 @@
|
|
1
|
-
#
|
2
|
-
|
1
|
+
# Evt
|
2
|
+
|
3
|
+
The Event Library that designed for Ruby 3.0.
|
4
|
+
|
5
|
+
**This gem is still under development, APIs and features are not stable. Advices and PRs are highly welcome.**
|
6
|
+
|
7
|
+
[![CI Tests](https://github.com/dsh0416/evt/workflows/CI%20Tests/badge.svg)](https://github.com/dsh0416/evt/actions?query=workflow%3A%22CI+Tests%22)
|
8
|
+
[![Gem Version](https://badge.fury.io/rb/evt.svg)](https://rubygems.org/gems/evt)
|
9
|
+
[![Downloads](https://ruby-gem-downloads-badge.herokuapp.com/evt?type=total)](https://rubygems.org/gems/evt)
|
10
|
+
|
11
|
+
## Features
|
12
|
+
|
13
|
+
|
14
|
+
|
15
|
+
### IO Backend Support
|
16
|
+
|
17
|
+
| | Linux | Windows | macOS | FreeBSD |
|
18
|
+
| --------------- | ----------- | ------------| ----------- | ----------- |
|
19
|
+
| io_uring | ✅ (See 1) | ❌ | ❌ | ❌ |
|
20
|
+
| epoll | ✅ (See 2) | ❌ | ❌ | ❌ |
|
21
|
+
| kqueue | ❌ | ❌ | ✅ (⚠️ See 5) | ✅ |
|
22
|
+
| IOCP | ❌ | ❌ (⚠️See 3) | ❌ | ❌ |
|
23
|
+
| Ruby (`select`) | ✅ Fallback | ✅ (⚠️See 4) | ✅ Fallback | ✅ Fallback |
|
24
|
+
|
25
|
+
1. when liburing is installed
|
26
|
+
2. when kernel version >= 2.6.8
|
27
|
+
3. WOULD NOT WORK until `FILE_FLAG_OVERLAPPED` is included in I/O initialization process.
|
28
|
+
4. Some I/Os are not able to be nonblock under Windows. See [Scheduler Docs](https://docs.ruby-lang.org/en/master/doc/scheduler_md.html#label-IO).
|
29
|
+
5. `kqueue` performance in Darwin is very poor. **MAY BE DISABLED IN THE FUTURE.**
|
30
|
+
|
31
|
+
## Install
|
32
|
+
|
33
|
+
```bash
|
34
|
+
gem install evt
|
35
|
+
```
|
36
|
+
|
37
|
+
## Usage
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
require 'evt'
|
41
|
+
|
42
|
+
rd, wr = IO.pipe
|
43
|
+
scheduler = Evt::Scheduler.new
|
44
|
+
|
45
|
+
Fiber.set_scheduler scheduler
|
46
|
+
|
47
|
+
Fiber.schedule do
|
48
|
+
message = rd.read(20)
|
49
|
+
puts message
|
50
|
+
rd.close
|
51
|
+
end
|
52
|
+
|
53
|
+
Fiber.schedule do
|
54
|
+
wr.write("Hello World")
|
55
|
+
wr.close
|
56
|
+
end
|
57
|
+
|
58
|
+
scheduler.run
|
59
|
+
|
60
|
+
# "Hello World"
|
61
|
+
```
|
62
|
+
|
63
|
+
## Roadmap
|
64
|
+
|
65
|
+
- [x] Support epoll/kqueue/select
|
66
|
+
- [x] Upgrade to the latest Scheduler API
|
67
|
+
- [x] Support io_uring
|
68
|
+
- [x] Support iov features of io_uring
|
69
|
+
- [x] Support IOCP (**NOT ENABLED YET**)
|
70
|
+
- [x] Setup tests with Ruby 3
|
71
|
+
- [ ] Support IOCP with iov features
|
72
|
+
- [ ] Setup more tests for production purpose
|
73
|
+
- [ ] Documentation for usages
|
data/evt.gemspec
CHANGED
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
|
|
10
10
|
spec.description = "A low-level Event Handler designed for Ruby 3 Scheduler for better performance"
|
11
11
|
spec.homepage = "https://github.com/dsh0416/evt"
|
12
12
|
spec.license = 'BSD-3-Clause'
|
13
|
-
spec.required_ruby_version = '>= 2.
|
13
|
+
spec.required_ruby_version = '>= 2.8.0.dev'
|
14
14
|
|
15
15
|
spec.metadata["homepage_uri"] = spec.homepage
|
16
16
|
spec.metadata["source_code_uri"] = "https://github.com/dsh0416/evt"
|
@@ -18,7 +18,7 @@ Gem::Specification.new do |spec|
|
|
18
18
|
# Specify which files should be added to the gem when it is released.
|
19
19
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
20
20
|
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
21
|
-
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
21
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|.vscode)/}) }
|
22
22
|
end
|
23
23
|
spec.require_paths = ["lib"]
|
24
24
|
spec.extensions = ['ext/evt/extconf.rb']
|
data/ext/evt/epoll.h
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
#ifndef EPOLL_H
|
2
|
+
#define EPOLL_G
|
3
|
+
#include "evt.h"
|
4
|
+
|
5
|
+
#if HAVE_SYS_EPOLL_H
|
6
|
+
VALUE method_scheduler_init(VALUE self) {
|
7
|
+
rb_iv_set(self, "@epfd", INT2NUM(epoll_create(1))); // Size of epoll is ignored after Linux 2.6.8.
|
8
|
+
return Qnil;
|
9
|
+
}
|
10
|
+
|
11
|
+
VALUE method_scheduler_register(VALUE self, VALUE io, VALUE interest) {
|
12
|
+
struct epoll_event event;
|
13
|
+
ID id_fileno = rb_intern("fileno");
|
14
|
+
int epfd = NUM2INT(rb_iv_get(self, "@epfd"));
|
15
|
+
int fd = NUM2INT(rb_funcall(io, id_fileno, 0));
|
16
|
+
int ruby_interest = NUM2INT(interest);
|
17
|
+
int readable = NUM2INT(rb_const_get(rb_cIO, rb_intern("READABLE")));
|
18
|
+
int writable = NUM2INT(rb_const_get(rb_cIO, rb_intern("WRITABLE")));
|
19
|
+
|
20
|
+
if (ruby_interest & readable) {
|
21
|
+
event.events |= EPOLLIN;
|
22
|
+
}
|
23
|
+
|
24
|
+
if (ruby_interest & writable) {
|
25
|
+
event.events |= EPOLLOUT;
|
26
|
+
}
|
27
|
+
|
28
|
+
event.data.ptr = (void*) io;
|
29
|
+
|
30
|
+
epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event);
|
31
|
+
return Qnil;
|
32
|
+
}
|
33
|
+
|
34
|
+
VALUE method_scheduler_deregister(VALUE self, VALUE io) {
|
35
|
+
ID id_fileno = rb_intern("fileno");
|
36
|
+
int epfd = NUM2INT(rb_iv_get(self, "@epfd"));
|
37
|
+
int fd = NUM2INT(rb_funcall(io, id_fileno, 0));
|
38
|
+
epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL); // Require Linux 2.6.9 for NULL event.
|
39
|
+
return Qnil;
|
40
|
+
}
|
41
|
+
|
42
|
+
VALUE method_scheduler_wait(VALUE self) {
|
43
|
+
int n, epfd, i, event_flag, timeout;
|
44
|
+
VALUE next_timeout, obj_io, readables, writables, result;
|
45
|
+
ID id_next_timeout = rb_intern("next_timeout");
|
46
|
+
ID id_push = rb_intern("push");
|
47
|
+
|
48
|
+
epfd = NUM2INT(rb_iv_get(self, "@epfd"));
|
49
|
+
next_timeout = rb_funcall(self, id_next_timeout, 0);
|
50
|
+
readables = rb_ary_new();
|
51
|
+
writables = rb_ary_new();
|
52
|
+
|
53
|
+
if (next_timeout == Qnil) {
|
54
|
+
timeout = -1;
|
55
|
+
} else {
|
56
|
+
timeout = NUM2INT(next_timeout);
|
57
|
+
}
|
58
|
+
|
59
|
+
struct epoll_event* events = (struct epoll_event*) xmalloc(sizeof(struct epoll_event) * EPOLL_MAX_EVENTS);
|
60
|
+
|
61
|
+
n = epoll_wait(epfd, events, EPOLL_MAX_EVENTS, timeout);
|
62
|
+
if (n < 0) {
|
63
|
+
rb_raise(rb_eIOError, "unable to call epoll_wait");
|
64
|
+
}
|
65
|
+
|
66
|
+
for (i = 0; i < n; i++) {
|
67
|
+
event_flag = events[i].events;
|
68
|
+
if (event_flag & EPOLLIN) {
|
69
|
+
obj_io = (VALUE) events[i].data.ptr;
|
70
|
+
rb_funcall(readables, id_push, 1, obj_io);
|
71
|
+
}
|
72
|
+
|
73
|
+
if (event_flag & EPOLLOUT) {
|
74
|
+
obj_io = (VALUE) events[i].data.ptr;
|
75
|
+
rb_funcall(writables, id_push, 1, obj_io);
|
76
|
+
}
|
77
|
+
}
|
78
|
+
|
79
|
+
result = rb_ary_new2(2);
|
80
|
+
rb_ary_store(result, 0, readables);
|
81
|
+
rb_ary_store(result, 1, writables);
|
82
|
+
|
83
|
+
xfree(events);
|
84
|
+
return result;
|
85
|
+
}
|
86
|
+
|
87
|
+
VALUE method_scheduler_backend(VALUE klass) {
|
88
|
+
return rb_str_new_cstr("epoll");
|
89
|
+
}
|
90
|
+
#endif
|
91
|
+
#endif
|
data/ext/evt/evt.c
CHANGED
@@ -1,122 +1,34 @@
|
|
1
|
-
#
|
1
|
+
#ifndef EVT_C
|
2
|
+
#define EVT_C
|
2
3
|
|
3
|
-
|
4
|
-
|
5
|
-
void Init_evt_ext();
|
6
|
-
VALUE method_scheduler_init(VALUE self);
|
7
|
-
VALUE method_scheduler_register(VALUE self, VALUE io, VALUE interest);
|
8
|
-
VALUE method_scheduler_deregister(VALUE self, VALUE io);
|
9
|
-
VALUE method_scheduler_wait(VALUE self);
|
4
|
+
#include "evt.h"
|
10
5
|
|
11
6
|
void Init_evt_ext()
|
12
7
|
{
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
#
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
event.events |= EPOLLIN;
|
41
|
-
} else if (ruby_interest & writable) {
|
42
|
-
event.events |= EPOLLOUT;
|
43
|
-
}
|
44
|
-
event.data.ptr = (void*) io;
|
45
|
-
|
46
|
-
epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event);
|
47
|
-
return Qnil;
|
48
|
-
}
|
49
|
-
|
50
|
-
VALUE method_scheduler_deregister(VALUE self, VALUE io) {
|
51
|
-
ID id_fileno = rb_intern("fileno");
|
52
|
-
int epfd = NUM2INT(rb_iv_get(self, "@epfd"));
|
53
|
-
int fd = NUM2INT(rb_funcall(io, id_fileno, 0));
|
54
|
-
epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL); // Require Linux 2.6.9 for NULL event.
|
55
|
-
return Qnil;
|
56
|
-
}
|
57
|
-
|
58
|
-
VALUE method_scheduler_wait(VALUE self) {
|
59
|
-
int n, epfd, i, event_flag;
|
60
|
-
VALUE next_timeout, obj_io, readables, writables, result;
|
61
|
-
ID id_next_timeout = rb_intern("next_timeout");
|
62
|
-
ID id_push = rb_intern("push");
|
63
|
-
|
64
|
-
epfd = NUM2INT(rb_iv_get(self, "@epfd"));
|
65
|
-
next_timeout = rb_funcall(self, id_next_timeout, 0);
|
66
|
-
readables = rb_ary_new();
|
67
|
-
writables = rb_ary_new();
|
68
|
-
|
69
|
-
struct epoll_event* events = (struct epoll_event*) xmalloc(sizeof(struct epoll_event) * EPOLL_MAX_EVENTS);
|
70
|
-
|
71
|
-
n = epoll_wait(epfd, events, EPOLL_MAX_EVENTS, next_timeout);
|
72
|
-
// Check if n > 0
|
73
|
-
|
74
|
-
for (i = 0; i < n; i++) {
|
75
|
-
event_flag = events[i].events;
|
76
|
-
if (event_flag & EPOLLIN) {
|
77
|
-
obj_io = (VALUE) events[i].data.ptr;
|
78
|
-
rb_funcall(readables, id_push, 1, obj_io);
|
79
|
-
} else if (event_flag & EPOLLOUT) {
|
80
|
-
obj_io = (VALUE) events[i].data.ptr;
|
81
|
-
rb_funcall(writables, id_push, 1, obj_io);
|
82
|
-
}
|
83
|
-
}
|
84
|
-
|
85
|
-
result = rb_ary_new2(2);
|
86
|
-
rb_ary_store(result, 0, readables);
|
87
|
-
rb_ary_store(result, 1, writables);
|
88
|
-
|
89
|
-
xfree(events);
|
90
|
-
return result;
|
91
|
-
}
|
92
|
-
#else
|
93
|
-
// Fallback to IO.select
|
94
|
-
VALUE method_scheduler_init(VALUE self) {
|
95
|
-
return Qnil;
|
96
|
-
}
|
97
|
-
|
98
|
-
VALUE method_scheduler_register(VALUE self, VALUE io, VALUE interest) {
|
99
|
-
return Qnil;
|
100
|
-
}
|
101
|
-
|
102
|
-
VALUE method_scheduler_deregister(VALUE self, VALUE io) {
|
103
|
-
return Qnil;
|
104
|
-
}
|
105
|
-
|
106
|
-
VALUE method_scheduler_wait(VALUE self) {
|
107
|
-
// return IO.select(@readable.keys, @writable.keys, [], next_timeout)
|
108
|
-
VALUE readable, writable, readable_keys, writable_keys, next_timeout;
|
109
|
-
ID id_select = rb_intern("select");
|
110
|
-
ID id_keys = rb_intern("keys");
|
111
|
-
ID id_next_timeout = rb_intern("next_timeout");
|
112
|
-
|
113
|
-
readable = rb_iv_get(self, "@readable");
|
114
|
-
writable = rb_iv_get(self, "@writable");
|
115
|
-
|
116
|
-
readable_keys = rb_funcall(readable, id_keys, 0);
|
117
|
-
writable_keys = rb_funcall(writable, id_keys, 0);
|
118
|
-
next_timeout = rb_funcall(self, id_next_timeout, 0);
|
119
|
-
|
120
|
-
return rb_funcall(rb_cIO, id_select, 4, readable_keys, writable_keys, rb_ary_new(), next_timeout);
|
121
|
-
}
|
122
|
-
#endif
|
8
|
+
Evt = rb_define_module("Evt");
|
9
|
+
Scheduler = rb_define_class_under(Evt, "Scheduler", rb_cObject);
|
10
|
+
Payload = rb_define_class_under(Scheduler, "Payload", rb_cObject);
|
11
|
+
Fiber = rb_define_class("Fiber", rb_cObject);
|
12
|
+
rb_define_singleton_method(Scheduler, "backend", method_scheduler_backend, 0);
|
13
|
+
rb_define_method(Scheduler, "init_selector", method_scheduler_init, 0);
|
14
|
+
rb_define_method(Scheduler, "register", method_scheduler_register, 2);
|
15
|
+
rb_define_method(Scheduler, "deregister", method_scheduler_deregister, 1);
|
16
|
+
rb_define_method(Scheduler, "wait", method_scheduler_wait, 0);
|
17
|
+
|
18
|
+
#if HAVELIBURING_H
|
19
|
+
rb_define_method(Scheduler, "io_read", method_scheduler_io_read, 4);
|
20
|
+
rb_define_method(Scheduler, "io_write", method_scheduler_io_read, 4);
|
21
|
+
#endif
|
22
|
+
}
|
23
|
+
|
24
|
+
#if HAVE_LIBURING_H
|
25
|
+
#include "uring.h"
|
26
|
+
#elif HAVE_SYS_EPOLL_H
|
27
|
+
#include "epoll.h"
|
28
|
+
#elif HAVE_SYS_EVENT_H
|
29
|
+
#include "kqueue.h"
|
30
|
+
#elif HAVE_WINDOWS_H
|
31
|
+
#include "select.h"
|
32
|
+
// #include "iocp.h"
|
33
|
+
#endif
|
34
|
+
#endif
|
data/ext/evt/evt.h
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
#ifndef EVT_H
|
2
|
+
#define EVT_H
|
3
|
+
|
4
|
+
#include <ruby.h>
|
5
|
+
|
6
|
+
VALUE Evt = Qnil;
|
7
|
+
VALUE Scheduler = Qnil;
|
8
|
+
VALUE Payload = Qnil;
|
9
|
+
VALUE Fiber = Qnil;
|
10
|
+
|
11
|
+
void Init_evt_ext();
|
12
|
+
VALUE method_scheduler_init(VALUE self);
|
13
|
+
VALUE method_scheduler_register(VALUE self, VALUE io, VALUE interest);
|
14
|
+
VALUE method_scheduler_deregister(VALUE self, VALUE io);
|
15
|
+
VALUE method_scheduler_wait(VALUE self);
|
16
|
+
VALUE method_scheduler_backend(VALUE klass);
|
17
|
+
#if HAVE_LIBURING_H
|
18
|
+
VALUE method_scheduler_io_read(VALUE self, VALUE io, VALUE buffer, VALUE offset, VALUE length);
|
19
|
+
VALUE method_scheduler_io_write(VALUE self, VALUE io, VALUE buffer, VALUE offset, VALUE length);
|
20
|
+
#endif
|
21
|
+
|
22
|
+
#if HAV_WINDOWS_H
|
23
|
+
VALUE method_scheduler_io_read(VALUE io, VALUE buffer, VALUE offset, VALUE length);
|
24
|
+
VALUE method_scheduler_io_write(VALUE io, VALUE buffer, VALUE offset, VALUE length);
|
25
|
+
#endif
|
26
|
+
|
27
|
+
#if HAVE_LIBURING_H
|
28
|
+
#include <liburing.h>
|
29
|
+
|
30
|
+
#define URING_ENTRIES 64
|
31
|
+
#define URING_MAX_EVENTS 64
|
32
|
+
|
33
|
+
struct uring_data {
|
34
|
+
bool is_poll;
|
35
|
+
short poll_mask;
|
36
|
+
VALUE io;
|
37
|
+
};
|
38
|
+
|
39
|
+
void uring_payload_free(void* data);
|
40
|
+
size_t uring_payload_size(const void* data);
|
41
|
+
|
42
|
+
static const rb_data_type_t type_uring_payload = {
|
43
|
+
.wrap_struct_name = "uring_payload",
|
44
|
+
.function = {
|
45
|
+
.dmark = NULL,
|
46
|
+
.dfree = uring_payload_free,
|
47
|
+
.dsize = uring_payload_size,
|
48
|
+
},
|
49
|
+
.data = NULL,
|
50
|
+
.flags = RUBY_TYPED_FREE_IMMEDIATELY,
|
51
|
+
};
|
52
|
+
#elif HAVE_SYS_EPOLL_H
|
53
|
+
#include <sys/epoll.h>
|
54
|
+
#define EPOLL_MAX_EVENTS 64
|
55
|
+
#elif HAVE_SYS_EVENT_H
|
56
|
+
#include <sys/event.h>
|
57
|
+
#define KQUEUE_MAX_EVENTS 64
|
58
|
+
#elif HAVE_WINDOWS_H
|
59
|
+
// #include <Windows.h>
|
60
|
+
// #define IOCP_MAX_EVENTS 64
|
61
|
+
|
62
|
+
// struct iocp_data {
|
63
|
+
// VALUE io;
|
64
|
+
// bool is_poll;
|
65
|
+
// int interest;
|
66
|
+
// };
|
67
|
+
|
68
|
+
// void iocp_payload_free(void* data);
|
69
|
+
// size_t iocp_payload_size(const void* data);
|
70
|
+
|
71
|
+
// static const rb_data_type_t type_iocp_payload = {
|
72
|
+
// .wrap_struct_name = "iocp_payload",
|
73
|
+
// .function = {
|
74
|
+
// .dmark = NULL,
|
75
|
+
// .dfree = iocp_payload_free,
|
76
|
+
// .dsize = iocp_payload_size,
|
77
|
+
// },
|
78
|
+
// .data = NULL,
|
79
|
+
// .flags = RUBY_TYPED_FREE_IMMEDIATELY,
|
80
|
+
// };
|
81
|
+
#endif
|
82
|
+
#endif
|
data/ext/evt/extconf.rb
CHANGED
@@ -1,5 +1,12 @@
|
|
1
1
|
require 'mkmf'
|
2
2
|
extension_name = 'evt_ext'
|
3
|
-
create_header
|
4
3
|
dir_config(extension_name)
|
5
|
-
|
4
|
+
|
5
|
+
have_library('uring')
|
6
|
+
have_header('liburing.h')
|
7
|
+
have_header('sys/epoll.h')
|
8
|
+
have_header('sys/event.h')
|
9
|
+
have_header('Windows.h')
|
10
|
+
|
11
|
+
create_header
|
12
|
+
create_makefile(extension_name)
|
data/ext/evt/iocp.h
ADDED
@@ -0,0 +1,126 @@
|
|
1
|
+
#ifndef IOCP_H
|
2
|
+
#define IOCP_H
|
3
|
+
#include "evt.h"
|
4
|
+
|
5
|
+
#if HAVE_WINDOWS_H
|
6
|
+
void iocp_payload_free(void* data) {
|
7
|
+
CloseHandle((HANDLE) data);
|
8
|
+
}
|
9
|
+
|
10
|
+
size_t iocp_payload_size(const void* data) {
|
11
|
+
return sizeof(HANDLE);
|
12
|
+
}
|
13
|
+
|
14
|
+
VALUE method_scheduler_init(VALUE self) {
|
15
|
+
HANDLE iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
|
16
|
+
rb_iv_set(self, "@iocp", TypedData_Wrap_Struct(Payload, &type_iocp_payload, iocp));
|
17
|
+
return Qnil;
|
18
|
+
}
|
19
|
+
|
20
|
+
VALUE method_scheduler_register(VALUE self, VALUE io, VALUE interest) {
|
21
|
+
HANDLE iocp;
|
22
|
+
VALUE iocp_obj = rb_iv_get(self, "@iocp");
|
23
|
+
struct iocp_data* data;
|
24
|
+
TypedData_Get_Struct(iocp_obj, HANDLE, &type_iocp_payload, iocp);
|
25
|
+
int fd = NUM2INT(rb_funcallv(io, rb_intern("fileno"), 0, 0));
|
26
|
+
HANDLE io_handler = (HANDLE)rb_w32_get_osfhandle(fd);
|
27
|
+
|
28
|
+
int ruby_interest = NUM2INT(interest);
|
29
|
+
int readable = NUM2INT(rb_const_get(rb_cIO, rb_intern("READABLE")));
|
30
|
+
int writable = NUM2INT(rb_const_get(rb_cIO, rb_intern("WRITABLE")));
|
31
|
+
data = (struct iocp_data*) xmalloc(sizeof(struct iocp_data));
|
32
|
+
data->io = io;
|
33
|
+
data->is_poll = true;
|
34
|
+
data->interest = 0;
|
35
|
+
|
36
|
+
if (ruby_interest & readable) {
|
37
|
+
interest |= readable;
|
38
|
+
}
|
39
|
+
|
40
|
+
if (ruby_interest & writable) {
|
41
|
+
interest |= writable;
|
42
|
+
}
|
43
|
+
|
44
|
+
HANDLE res = CreateIoCompletionPort(io_handler, iocp, (ULONG_PTR) data, 0);
|
45
|
+
printf("IO at address: 0x%08x\n", (void *)data);
|
46
|
+
|
47
|
+
return Qnil;
|
48
|
+
}
|
49
|
+
|
50
|
+
VALUE method_scheduler_deregister(VALUE self, VALUE io) {
|
51
|
+
return Qnil;
|
52
|
+
}
|
53
|
+
|
54
|
+
VALUE method_scheduler_wait(VALUE self) {
|
55
|
+
ID id_next_timeout = rb_intern("next_timeout");
|
56
|
+
ID id_push = rb_intern("push");
|
57
|
+
VALUE iocp_obj = rb_iv_get(self, "@iocp");
|
58
|
+
VALUE next_timeout = rb_funcall(self, id_next_timeout, 0);
|
59
|
+
|
60
|
+
int readable = NUM2INT(rb_const_get(rb_cIO, rb_intern("READABLE")));
|
61
|
+
int writable = NUM2INT(rb_const_get(rb_cIO, rb_intern("WRITABLE")));
|
62
|
+
|
63
|
+
HANDLE iocp;
|
64
|
+
OVERLAPPED_ENTRY lpCompletionPortEntries[IOCP_MAX_EVENTS];
|
65
|
+
ULONG ulNumEntriesRemoved;
|
66
|
+
TypedData_Get_Struct(iocp_obj, HANDLE, &type_iocp_payload, iocp);
|
67
|
+
|
68
|
+
DWORD timeout;
|
69
|
+
if (next_timeout == Qnil) {
|
70
|
+
timeout = 0x5000;
|
71
|
+
} else {
|
72
|
+
timeout = NUM2INT(next_timeout) * 1000; // seconds to milliseconds
|
73
|
+
}
|
74
|
+
|
75
|
+
DWORD NumberOfBytesTransferred;
|
76
|
+
LPOVERLAPPED pOverlapped;
|
77
|
+
ULONG_PTR CompletionKey;
|
78
|
+
|
79
|
+
BOOL res = GetQueuedCompletionStatus(iocp, &NumberOfBytesTransferred, &CompletionKey, &pOverlapped, timeout);
|
80
|
+
// BOOL res = GetQueuedCompletionStatusEx(
|
81
|
+
// iocp, lpCompletionPortEntries, IOCP_MAX_EVENTS, &ulNumEntriesRemoved, timeout, TRUE);
|
82
|
+
|
83
|
+
VALUE result = rb_ary_new2(2);
|
84
|
+
|
85
|
+
VALUE readables = rb_ary_new();
|
86
|
+
VALUE writables = rb_ary_new();
|
87
|
+
|
88
|
+
rb_ary_store(result, 0, readables);
|
89
|
+
rb_ary_store(result, 1, writables);
|
90
|
+
|
91
|
+
if (!result) {
|
92
|
+
return result;
|
93
|
+
}
|
94
|
+
|
95
|
+
printf("--------- Received! ---------\n");
|
96
|
+
printf("Received IO at address: 0x%08x\n", (void *)CompletionKey);
|
97
|
+
printf("dwNumberOfBytesTransferred: %lld\n", NumberOfBytesTransferred);
|
98
|
+
|
99
|
+
// if (ulNumEntriesRemoved > 0) {
|
100
|
+
// printf("Entries: %ld\n", ulNumEntriesRemoved);
|
101
|
+
// }
|
102
|
+
|
103
|
+
// for (ULONG i = 0; i < ulNumEntriesRemoved; i++) {
|
104
|
+
// OVERLAPPED_ENTRY entry = lpCompletionPortEntries[i];
|
105
|
+
|
106
|
+
// struct iocp_data *data = (struct iocp_data*) entry.lpCompletionKey;
|
107
|
+
|
108
|
+
// int interest = data->interest;
|
109
|
+
// VALUE obj_io = data->io;
|
110
|
+
// if (interest & readable) {
|
111
|
+
// rb_funcall(readables, id_push, 1, obj_io);
|
112
|
+
// } else if (interest & writable) {
|
113
|
+
// rb_funcall(writables, id_push, 1, obj_io);
|
114
|
+
// }
|
115
|
+
|
116
|
+
// xfree(data);
|
117
|
+
// }
|
118
|
+
|
119
|
+
return result;
|
120
|
+
}
|
121
|
+
|
122
|
+
VALUE method_scheduler_backend(VALUE klass) {
|
123
|
+
return rb_str_new_cstr("iocp");
|
124
|
+
}
|
125
|
+
#endif
|
126
|
+
#endif
|
data/ext/evt/kqueue.h
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
#ifndef KQUEUE_H
|
2
|
+
#define KQUEUE_H
|
3
|
+
#include "evt.h"
|
4
|
+
|
5
|
+
#if HAVE_SYS_EVENT_H
|
6
|
+
|
7
|
+
VALUE method_scheduler_init(VALUE self) {
|
8
|
+
rb_iv_set(self, "@kq", INT2NUM(kqueue()));
|
9
|
+
return Qnil;
|
10
|
+
}
|
11
|
+
|
12
|
+
VALUE method_scheduler_register(VALUE self, VALUE io, VALUE interest) {
|
13
|
+
struct kevent event;
|
14
|
+
u_short event_flags = 0;
|
15
|
+
ID id_fileno = rb_intern("fileno");
|
16
|
+
int kq = NUM2INT(rb_iv_get(self, "@kq"));
|
17
|
+
int fd = NUM2INT(rb_funcall(io, id_fileno, 0));
|
18
|
+
int ruby_interest = NUM2INT(interest);
|
19
|
+
int readable = NUM2INT(rb_const_get(rb_cIO, rb_intern("READABLE")));
|
20
|
+
int writable = NUM2INT(rb_const_get(rb_cIO, rb_intern("WRITABLE")));
|
21
|
+
|
22
|
+
if (ruby_interest & readable) {
|
23
|
+
event_flags |= EVFILT_READ;
|
24
|
+
}
|
25
|
+
|
26
|
+
if (ruby_interest & writable) {
|
27
|
+
event_flags |= EVFILT_WRITE;
|
28
|
+
}
|
29
|
+
|
30
|
+
EV_SET(&event, fd, event_flags, EV_ADD|EV_ENABLE, 0, 0, (void*) io);
|
31
|
+
kevent(kq, &event, 1, NULL, 0, NULL); // TODO: Check the return value
|
32
|
+
return Qnil;
|
33
|
+
}
|
34
|
+
|
35
|
+
VALUE method_scheduler_deregister(VALUE self, VALUE io) {
|
36
|
+
struct kevent event;
|
37
|
+
ID id_fileno = rb_intern("fileno");
|
38
|
+
int kq = NUM2INT(rb_iv_get(self, "@kq"));
|
39
|
+
int fd = NUM2INT(rb_funcall(io, id_fileno, 0));
|
40
|
+
EV_SET(&event, fd, 0, EV_DELETE, 0, 0, (void*) io);
|
41
|
+
kevent(kq, &event, 1, NULL, 0, (void*) io); // TODO: Check the return value
|
42
|
+
return Qnil;
|
43
|
+
}
|
44
|
+
|
45
|
+
VALUE method_scheduler_wait(VALUE self) {
|
46
|
+
int n, kq, i;
|
47
|
+
u_short event_flags = 0;
|
48
|
+
|
49
|
+
struct kevent* events; // Event Triggered
|
50
|
+
struct timespec timeout;
|
51
|
+
VALUE next_timeout, obj_io, readables, writables, result;
|
52
|
+
ID id_next_timeout = rb_intern("next_timeout");
|
53
|
+
ID id_push = rb_intern("push");
|
54
|
+
|
55
|
+
kq = NUM2INT(rb_iv_get(self, "@kq"));
|
56
|
+
next_timeout = rb_funcall(self, id_next_timeout, 0);
|
57
|
+
readables = rb_ary_new();
|
58
|
+
writables = rb_ary_new();
|
59
|
+
|
60
|
+
events = (struct kevent*) xmalloc(sizeof(struct kevent) * KQUEUE_MAX_EVENTS);
|
61
|
+
|
62
|
+
if (next_timeout == Qnil || NUM2INT(next_timeout) == -1) {
|
63
|
+
n = kevent(kq, NULL, 0, events, KQUEUE_MAX_EVENTS, NULL);
|
64
|
+
} else {
|
65
|
+
timeout.tv_sec = next_timeout / 1000;
|
66
|
+
timeout.tv_nsec = next_timeout % 1000 * 1000 * 1000;
|
67
|
+
n = kevent(kq, NULL, 0, events, KQUEUE_MAX_EVENTS, &timeout);
|
68
|
+
}
|
69
|
+
|
70
|
+
// TODO: Check if n >= 0
|
71
|
+
for (i = 0; i < n; i++) {
|
72
|
+
event_flags = events[i].filter;
|
73
|
+
if (event_flags & EVFILT_READ) {
|
74
|
+
obj_io = (VALUE) events[i].udata;
|
75
|
+
rb_funcall(readables, id_push, 1, obj_io);
|
76
|
+
}
|
77
|
+
|
78
|
+
if (event_flags & EVFILT_WRITE) {
|
79
|
+
obj_io = (VALUE) events[i].udata;
|
80
|
+
rb_funcall(writables, id_push, 1, obj_io);
|
81
|
+
}
|
82
|
+
}
|
83
|
+
|
84
|
+
result = rb_ary_new2(2);
|
85
|
+
rb_ary_store(result, 0, readables);
|
86
|
+
rb_ary_store(result, 1, writables);
|
87
|
+
|
88
|
+
xfree(events);
|
89
|
+
return result;
|
90
|
+
}
|
91
|
+
|
92
|
+
VALUE method_scheduler_backend(VALUE klass) {
|
93
|
+
return rb_str_new_cstr("kqueue");
|
94
|
+
}
|
95
|
+
#endif
|
96
|
+
#endif
|
data/ext/evt/select.h
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
#ifndef SELECT_H
|
2
|
+
#define SELECT_H
|
3
|
+
#include "evt.h"
|
4
|
+
|
5
|
+
VALUE method_scheduler_init(VALUE self) {
|
6
|
+
return Qnil;
|
7
|
+
}
|
8
|
+
|
9
|
+
VALUE method_scheduler_register(VALUE self, VALUE io, VALUE interest) {
|
10
|
+
return Qnil;
|
11
|
+
}
|
12
|
+
|
13
|
+
VALUE method_scheduler_deregister(VALUE self, VALUE io) {
|
14
|
+
return Qnil;
|
15
|
+
}
|
16
|
+
|
17
|
+
VALUE method_scheduler_wait(VALUE self) {
|
18
|
+
// return IO.select(@readable.keys, @writable.keys, [], next_timeout)
|
19
|
+
VALUE readable, writable, readable_keys, writable_keys, next_timeout;
|
20
|
+
ID id_select = rb_intern("select");
|
21
|
+
ID id_next_timeout = rb_intern("next_timeout");
|
22
|
+
|
23
|
+
readable = rb_iv_get(self, "@readable");
|
24
|
+
writable = rb_iv_get(self, "@writable");
|
25
|
+
|
26
|
+
readable_keys = rb_funcall(readable, rb_intern("keys"), 0);
|
27
|
+
writable_keys = rb_funcall(writable, rb_intern("keys"), 0);
|
28
|
+
next_timeout = rb_funcall(self, id_next_timeout, 0);
|
29
|
+
|
30
|
+
return rb_funcall(rb_cIO, id_select, 4, readable_keys, writable_keys, rb_ary_new(), next_timeout);
|
31
|
+
}
|
32
|
+
|
33
|
+
VALUE method_scheduler_backend(VALUE klass) {
|
34
|
+
return rb_str_new_cstr("ruby");
|
35
|
+
}
|
36
|
+
#endif
|
data/ext/evt/uring.h
ADDED
@@ -0,0 +1,199 @@
|
|
1
|
+
#ifndef URING_H
|
2
|
+
#define URING_H
|
3
|
+
#include "evt.h"
|
4
|
+
#if HAVE_LIBURING_H
|
5
|
+
void uring_payload_free(void* data) {
|
6
|
+
// TODO: free the uring_data structs if the payload is freed before all IO responds
|
7
|
+
io_uring_queue_exit((struct io_uring*) data);
|
8
|
+
xfree(data);
|
9
|
+
}
|
10
|
+
|
11
|
+
size_t uring_payload_size(const void* data) {
|
12
|
+
return sizeof(struct io_uring);
|
13
|
+
}
|
14
|
+
|
15
|
+
VALUE method_scheduler_init(VALUE self) {
|
16
|
+
int ret;
|
17
|
+
struct io_uring* ring;
|
18
|
+
ring = xmalloc(sizeof(struct io_uring));
|
19
|
+
ret = io_uring_queue_init(URING_ENTRIES, ring, 0);
|
20
|
+
if (ret < 0) {
|
21
|
+
rb_raise(rb_eIOError, "unable to initalize io_uring");
|
22
|
+
}
|
23
|
+
rb_iv_set(self, "@ring", TypedData_Wrap_Struct(Payload, &type_uring_payload, ring));
|
24
|
+
return Qnil;
|
25
|
+
}
|
26
|
+
|
27
|
+
VALUE method_scheduler_register(VALUE self, VALUE io, VALUE interest) {
|
28
|
+
VALUE ring_obj;
|
29
|
+
struct io_uring* ring;
|
30
|
+
struct io_uring_sqe *sqe;
|
31
|
+
struct uring_data *data;
|
32
|
+
short poll_mask = 0;
|
33
|
+
ID id_fileno = rb_intern("fileno");
|
34
|
+
|
35
|
+
ring_obj = rb_iv_get(self, "@ring");
|
36
|
+
TypedData_Get_Struct(ring_obj, struct io_uring, &type_uring_payload, ring);
|
37
|
+
sqe = io_uring_get_sqe(ring);
|
38
|
+
int fd = NUM2INT(rb_funcall(io, id_fileno, 0));
|
39
|
+
|
40
|
+
int ruby_interest = NUM2INT(interest);
|
41
|
+
int readable = NUM2INT(rb_const_get(rb_cIO, rb_intern("READABLE")));
|
42
|
+
int writable = NUM2INT(rb_const_get(rb_cIO, rb_intern("WRITABLE")));
|
43
|
+
|
44
|
+
if (ruby_interest & readable) {
|
45
|
+
poll_mask |= POLL_IN;
|
46
|
+
}
|
47
|
+
|
48
|
+
if (ruby_interest & writable) {
|
49
|
+
poll_mask |= POLL_OUT;
|
50
|
+
}
|
51
|
+
|
52
|
+
data = (struct uring_data*) xmalloc(sizeof(struct uring_data));
|
53
|
+
data->is_poll = true;
|
54
|
+
data->io = io;
|
55
|
+
data->poll_mask = poll_mask;
|
56
|
+
|
57
|
+
io_uring_prep_poll_add(sqe, fd, poll_mask);
|
58
|
+
io_uring_sqe_set_data(sqe, data);
|
59
|
+
io_uring_submit(ring);
|
60
|
+
return Qnil;
|
61
|
+
}
|
62
|
+
|
63
|
+
VALUE method_scheduler_deregister(VALUE self, VALUE io) {
|
64
|
+
// io_uring runs under oneshot mode. No need to deregister.
|
65
|
+
return Qnil;
|
66
|
+
}
|
67
|
+
|
68
|
+
VALUE method_scheduler_wait(VALUE self) {
|
69
|
+
struct io_uring* ring;
|
70
|
+
struct io_uring_cqe *cqes[URING_MAX_EVENTS];
|
71
|
+
struct uring_data *data;
|
72
|
+
VALUE next_timeout, obj_io, readables, writables, iovs, result;
|
73
|
+
unsigned ret, i;
|
74
|
+
double time = 0.0;
|
75
|
+
short poll_events;
|
76
|
+
|
77
|
+
ID id_next_timeout = rb_intern("next_timeout");
|
78
|
+
ID id_push = rb_intern("push");
|
79
|
+
ID id_sleep = rb_intern("sleep");
|
80
|
+
|
81
|
+
next_timeout = rb_funcall(self, id_next_timeout, 0);
|
82
|
+
readables = rb_ary_new();
|
83
|
+
writables = rb_ary_new();
|
84
|
+
iovs = rb_ary_new();
|
85
|
+
|
86
|
+
TypedData_Get_Struct(rb_iv_get(self, "@ring"), struct io_uring, &type_uring_payload, ring);
|
87
|
+
ret = io_uring_peek_batch_cqe(ring, cqes, URING_MAX_EVENTS);
|
88
|
+
|
89
|
+
for (i = 0; i < ret; i++) {
|
90
|
+
data = (struct uring_data*) io_uring_cqe_get_data(cqes[i]);
|
91
|
+
poll_events = data->poll_mask;
|
92
|
+
obj_io = data->io;
|
93
|
+
if (data->is_poll) {
|
94
|
+
if (poll_events & POLL_IN) {
|
95
|
+
rb_funcall(readables, id_push, 1, obj_io);
|
96
|
+
}
|
97
|
+
|
98
|
+
if (poll_events & POLL_OUT) {
|
99
|
+
rb_funcall(writables, id_push, 1, obj_io);
|
100
|
+
}
|
101
|
+
} else {
|
102
|
+
rb_funcall(iovs, id_push, 1, obj_io);
|
103
|
+
}
|
104
|
+
}
|
105
|
+
|
106
|
+
if (ret == 0) {
|
107
|
+
if (next_timeout != Qnil && NUM2INT(next_timeout) != -1) {
|
108
|
+
// sleep
|
109
|
+
time = next_timeout / 1000;
|
110
|
+
rb_funcall(rb_mKernel, id_sleep, 1, rb_float_new(time));
|
111
|
+
} else {
|
112
|
+
rb_funcall(rb_mKernel, id_sleep, 1, rb_float_new(0.001)); // To avoid infinite loop
|
113
|
+
}
|
114
|
+
}
|
115
|
+
|
116
|
+
result = rb_ary_new2(3);
|
117
|
+
rb_ary_store(result, 0, readables);
|
118
|
+
rb_ary_store(result, 1, writables);
|
119
|
+
rb_ary_store(result, 2, iovs);
|
120
|
+
|
121
|
+
return result;
|
122
|
+
}
|
123
|
+
|
124
|
+
VALUE method_scheduler_io_read(VALUE self, VALUE io, VALUE buffer, VALUE offset, VALUE length) {
|
125
|
+
struct io_uring* ring;
|
126
|
+
struct uring_data *data;
|
127
|
+
char* read_buffer;
|
128
|
+
ID id_fileno = rb_intern("fileno");
|
129
|
+
// @iov[io] = Fiber.current
|
130
|
+
VALUE iovs = rb_iv_get(self, "@iovs");
|
131
|
+
rb_hash_aset(iovs, io, rb_funcall(Fiber, rb_intern("current"), 0));
|
132
|
+
// register
|
133
|
+
VALUE ring_obj = rb_iv_get(self, "@ring");
|
134
|
+
TypedData_Get_Struct(ring_obj, struct io_uring, &type_uring_payload, ring);
|
135
|
+
struct io_uring_sqe *sqe = io_uring_get_sqe(ring);
|
136
|
+
int fd = NUM2INT(rb_funcall(io, id_fileno, 0));
|
137
|
+
|
138
|
+
read_buffer = (char*) xmalloc(NUM2SIZET(length));
|
139
|
+
struct iovec iov = {
|
140
|
+
.iov_base = read_buffer,
|
141
|
+
.iov_len = NUM2SIZET(length),
|
142
|
+
};
|
143
|
+
|
144
|
+
data = (struct uring_data*) xmalloc(sizeof(struct uring_data));
|
145
|
+
data->is_poll = false;
|
146
|
+
data->io = io;
|
147
|
+
data->poll_mask = 0;
|
148
|
+
|
149
|
+
io_uring_prep_readv(sqe, fd, &iov, 1, NUM2SIZET(offset));
|
150
|
+
io_uring_sqe_set_data(sqe, data);
|
151
|
+
io_uring_submit(ring);
|
152
|
+
|
153
|
+
VALUE result = rb_str_new(read_buffer, strlen(read_buffer));
|
154
|
+
if (buffer != Qnil) {
|
155
|
+
rb_str_append(buffer, result);
|
156
|
+
}
|
157
|
+
|
158
|
+
rb_funcall(Fiber, rb_intern("yield"), 0); // Fiber.yield
|
159
|
+
return result;
|
160
|
+
}
|
161
|
+
|
162
|
+
VALUE method_scheduler_io_write(VALUE self, VALUE io, VALUE buffer, VALUE offset, VALUE length) {
|
163
|
+
struct io_uring* ring;
|
164
|
+
struct uring_data *data;
|
165
|
+
char* write_buffer;
|
166
|
+
ID id_fileno = rb_intern("fileno");
|
167
|
+
// @iov[io] = Fiber.current
|
168
|
+
VALUE iovs = rb_iv_get(self, "@iovs");
|
169
|
+
rb_hash_aset(iovs, io, rb_funcall(Fiber, rb_intern("current"), 0));
|
170
|
+
// register
|
171
|
+
VALUE ring_obj = rb_iv_get(self, "@ring");
|
172
|
+
TypedData_Get_Struct(ring_obj, struct io_uring, &type_uring_payload, ring);
|
173
|
+
struct io_uring_sqe *sqe = io_uring_get_sqe(ring);
|
174
|
+
int fd = NUM2INT(rb_funcall(io, id_fileno, 0));
|
175
|
+
|
176
|
+
write_buffer = StringValueCStr(buffer);
|
177
|
+
struct iovec iov = {
|
178
|
+
.iov_base = write_buffer,
|
179
|
+
.iov_len = NUM2SIZET(length),
|
180
|
+
};
|
181
|
+
|
182
|
+
data = (struct uring_data*) xmalloc(sizeof(struct uring_data));
|
183
|
+
data->is_poll = false;
|
184
|
+
data->io = io;
|
185
|
+
data->poll_mask = 0;
|
186
|
+
|
187
|
+
io_uring_prep_writev(sqe, fd, &iov, 1, NUM2SIZET(offset));
|
188
|
+
io_uring_sqe_set_data(sqe, data);
|
189
|
+
io_uring_submit(ring);
|
190
|
+
rb_funcall(Fiber, rb_intern("yield"), 0); // Fiber.yield
|
191
|
+
return length;
|
192
|
+
}
|
193
|
+
|
194
|
+
VALUE method_scheduler_backend(VALUE klass) {
|
195
|
+
return rb_str_new_cstr("liburing");
|
196
|
+
}
|
197
|
+
|
198
|
+
#endif
|
199
|
+
#endif
|
data/lib/evt.rb
CHANGED
data/lib/evt/scheduler.rb
CHANGED
@@ -2,62 +2,55 @@
|
|
2
2
|
|
3
3
|
require 'fiber'
|
4
4
|
require 'socket'
|
5
|
+
require 'io/nonblock'
|
5
6
|
|
6
|
-
|
7
|
-
require 'io/nonblock'
|
8
|
-
rescue LoadError
|
9
|
-
# Ignore.
|
10
|
-
end
|
11
|
-
|
12
|
-
class IO
|
13
|
-
WAIT_READABLE = 1
|
14
|
-
WAIT_WRITABLE = 3
|
15
|
-
end
|
16
|
-
|
17
|
-
class Scheduler
|
7
|
+
class Evt::Scheduler
|
18
8
|
def initialize
|
19
9
|
@readable = {}
|
20
10
|
@writable = {}
|
11
|
+
@iovs = {}
|
21
12
|
@waiting = {}
|
22
|
-
|
13
|
+
|
14
|
+
@lock = Mutex.new
|
15
|
+
@locking = 0
|
16
|
+
@ready = []
|
23
17
|
|
24
18
|
@ios = ObjectSpace::WeakMap.new
|
25
19
|
init_selector
|
26
20
|
end
|
27
21
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
attr :blocking
|
22
|
+
attr_reader :readable
|
23
|
+
attr_reader :writable
|
24
|
+
attr_reader :waiting
|
32
25
|
|
33
26
|
def next_timeout
|
34
27
|
_fiber, timeout = @waiting.min_by{|key, value| value}
|
35
28
|
|
36
29
|
if timeout
|
37
30
|
offset = timeout - current_time
|
38
|
-
|
39
|
-
if offset < 0
|
40
|
-
return 0
|
41
|
-
else
|
42
|
-
return offset
|
43
|
-
end
|
31
|
+
offset < 0 ? 0 : offset
|
44
32
|
end
|
45
33
|
end
|
46
34
|
|
47
35
|
def run
|
48
|
-
while @readable.any? or @writable.any? or @waiting.any?
|
49
|
-
|
50
|
-
readable, writable = self.wait
|
51
|
-
|
52
|
-
# puts "readable: #{readable}" if readable&.any?
|
53
|
-
# puts "writable: #{writable}" if writable&.any?
|
36
|
+
while @readable.any? or @writable.any? or @waiting.any? or @iovs.any? or @locking.positive?
|
37
|
+
readable, writable, iovs = self.wait
|
54
38
|
|
55
39
|
readable&.each do |io|
|
56
|
-
@readable
|
40
|
+
fiber = @readable.delete(io)
|
41
|
+
fiber&.resume
|
57
42
|
end
|
58
43
|
|
59
44
|
writable&.each do |io|
|
60
|
-
@writable
|
45
|
+
fiber = @writable.delete(io)
|
46
|
+
fiber&.resume
|
47
|
+
end
|
48
|
+
|
49
|
+
unless iovs.nil?
|
50
|
+
iovs&.each do |io|
|
51
|
+
fiber = @iovs.delete(io)
|
52
|
+
fiber&.resume
|
53
|
+
end
|
61
54
|
end
|
62
55
|
|
63
56
|
if @waiting.any?
|
@@ -73,98 +66,56 @@ class Scheduler
|
|
73
66
|
end
|
74
67
|
end
|
75
68
|
end
|
76
|
-
end
|
77
|
-
end
|
78
|
-
|
79
|
-
def for_fd(fd)
|
80
|
-
@ios[fd] ||= ::IO.for_fd(fd, autoclose: false)
|
81
|
-
end
|
82
69
|
|
83
|
-
|
84
|
-
|
85
|
-
self.register(io, IO::WAIT_READABLE)
|
86
|
-
Fiber.yield
|
87
|
-
@readable.delete(io)
|
88
|
-
self.deregister(io)
|
89
|
-
return true
|
90
|
-
end
|
70
|
+
if @ready.any?
|
71
|
+
ready = nil
|
91
72
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
)
|
96
|
-
end
|
97
|
-
|
98
|
-
def wait_writable(io)
|
99
|
-
@writable[io] = Fiber.current
|
100
|
-
self.register(io, IO::WAIT_READABLE)
|
101
|
-
Fiber.yield
|
102
|
-
@writable.delete(io)
|
103
|
-
self.deregister(io)
|
104
|
-
return true
|
105
|
-
end
|
73
|
+
@lock.synchronize do
|
74
|
+
ready, @ready = @ready, []
|
75
|
+
end
|
106
76
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
77
|
+
ready.each do |fiber|
|
78
|
+
fiber.resume
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
111
82
|
end
|
112
83
|
|
113
84
|
def current_time
|
114
85
|
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
115
86
|
end
|
116
87
|
|
117
|
-
def
|
118
|
-
@
|
119
|
-
|
120
|
-
Fiber.yield
|
121
|
-
|
122
|
-
return true
|
123
|
-
end
|
124
|
-
|
125
|
-
def wait_any(io, events, duration)
|
126
|
-
unless (events & IO::WAIT_READABLE).zero?
|
127
|
-
@readable[io] = Fiber.current
|
128
|
-
end
|
129
|
-
|
130
|
-
unless (events & IO::WAIT_WRITABLE).zero?
|
131
|
-
@writable[io] = Fiber.current
|
132
|
-
end
|
133
|
-
|
88
|
+
def io_wait(io, events, duration)
|
89
|
+
@readable[io] = Fiber.current unless (events & IO::READABLE).zero?
|
90
|
+
@writable[io] = Fiber.current unless (events & IO::WRITABLE).zero?
|
134
91
|
self.register(io, events)
|
135
|
-
|
136
92
|
Fiber.yield
|
137
|
-
|
138
|
-
@readable.delete(io)
|
139
|
-
@writable.delete(io)
|
140
93
|
self.deregister(io)
|
141
|
-
|
142
|
-
return true
|
94
|
+
true
|
143
95
|
end
|
144
96
|
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
events,
|
150
|
-
duration
|
151
|
-
)
|
97
|
+
def kernel_sleep(duration = nil)
|
98
|
+
@waiting[Fiber.current] = current_time + duration if duration.nil?
|
99
|
+
Fiber.yield
|
100
|
+
true
|
152
101
|
end
|
153
102
|
|
154
|
-
def
|
155
|
-
|
103
|
+
def mutex_lock(mutex)
|
104
|
+
@locking += 1
|
105
|
+
Fiber.yield
|
106
|
+
ensure
|
107
|
+
@locking -= 1
|
156
108
|
end
|
157
109
|
|
158
|
-
def
|
159
|
-
|
160
|
-
|
110
|
+
def mutex_unlock(mutex, fiber)
|
111
|
+
@lock.synchronize do
|
112
|
+
@ready << fiber
|
113
|
+
end
|
161
114
|
end
|
162
115
|
|
163
116
|
def fiber(&block)
|
164
117
|
fiber = Fiber.new(blocking: false, &block)
|
165
|
-
|
166
118
|
fiber.resume
|
167
|
-
|
168
|
-
return fiber
|
119
|
+
fiber
|
169
120
|
end
|
170
121
|
end
|
data/lib/evt/version.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: evt
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Delton Ding
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2020-
|
11
|
+
date: 2020-12-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake-compiler
|
@@ -32,17 +32,22 @@ extensions:
|
|
32
32
|
- ext/evt/extconf.rb
|
33
33
|
extra_rdoc_files: []
|
34
34
|
files:
|
35
|
+
- ".github/workflows/test.yml"
|
35
36
|
- ".gitignore"
|
36
|
-
- ".travis.yml"
|
37
37
|
- CODE_OF_CONDUCT.md
|
38
38
|
- Gemfile
|
39
|
-
- Gemfile.lock
|
40
39
|
- LICENSE
|
41
40
|
- README.md
|
42
41
|
- Rakefile
|
43
42
|
- evt.gemspec
|
43
|
+
- ext/evt/epoll.h
|
44
44
|
- ext/evt/evt.c
|
45
|
+
- ext/evt/evt.h
|
45
46
|
- ext/evt/extconf.rb
|
47
|
+
- ext/evt/iocp.h
|
48
|
+
- ext/evt/kqueue.h
|
49
|
+
- ext/evt/select.h
|
50
|
+
- ext/evt/uring.h
|
46
51
|
- lib/evt.rb
|
47
52
|
- lib/evt/scheduler.rb
|
48
53
|
- lib/evt/version.rb
|
@@ -60,14 +65,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
60
65
|
requirements:
|
61
66
|
- - ">="
|
62
67
|
- !ruby/object:Gem::Version
|
63
|
-
version: 2.
|
68
|
+
version: 2.8.0.dev
|
64
69
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
65
70
|
requirements:
|
66
71
|
- - ">="
|
67
72
|
- !ruby/object:Gem::Version
|
68
73
|
version: '0'
|
69
74
|
requirements: []
|
70
|
-
rubygems_version: 3.
|
75
|
+
rubygems_version: 3.2.2
|
71
76
|
signing_key:
|
72
77
|
specification_version: 4
|
73
78
|
summary: A low-level Event Handler designed for Ruby 3 Scheduler
|
data/.travis.yml
DELETED
data/Gemfile.lock
DELETED
@@ -1,24 +0,0 @@
|
|
1
|
-
PATH
|
2
|
-
remote: .
|
3
|
-
specs:
|
4
|
-
evt (0.1.1)
|
5
|
-
|
6
|
-
GEM
|
7
|
-
remote: https://rubygems.org/
|
8
|
-
specs:
|
9
|
-
minitest (5.14.1)
|
10
|
-
rake (12.3.3)
|
11
|
-
rake-compiler (1.1.1)
|
12
|
-
rake
|
13
|
-
|
14
|
-
PLATFORMS
|
15
|
-
ruby
|
16
|
-
|
17
|
-
DEPENDENCIES
|
18
|
-
evt!
|
19
|
-
minitest (~> 5.0)
|
20
|
-
rake (~> 12.0)
|
21
|
-
rake-compiler (~> 1.0)
|
22
|
-
|
23
|
-
BUNDLED WITH
|
24
|
-
2.2.0.dev
|