evt 0.2.1 → 0.3.2
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/build.yml +21 -0
- data/.github/workflows/test.yml +11 -22
- data/README.md +25 -10
- data/Rakefile +1 -0
- data/evt.gemspec +5 -3
- data/ext/evt/epoll.h +8 -16
- data/ext/evt/evt.c +31 -21
- data/ext/evt/evt.h +24 -19
- data/ext/evt/extconf.rb +16 -5
- data/ext/evt/iocp.h +4 -8
- data/ext/evt/kqueue.h +6 -20
- data/ext/evt/select.h +2 -14
- data/ext/evt/uring.h +23 -37
- data/lib/evt.rb +9 -0
- data/lib/evt/backends/bundled.rb +181 -0
- data/lib/evt/backends/epoll.rb +23 -0
- data/lib/evt/backends/iocp.rb +29 -0
- data/lib/evt/backends/kqueue.rb +23 -0
- data/lib/evt/backends/select.rb +26 -0
- data/lib/evt/backends/uring.rb +31 -0
- data/lib/evt/scheduler.rb +27 -112
- data/lib/evt/version.rb +1 -1
- metadata +39 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 945c1a802be8b4e03b88c44138a139367288409b27c83a6f2904d0ecc94d2a40
|
|
4
|
+
data.tar.gz: 7b88e16f4a082f536c5b0eb9a7f68e67e6e1f5ab4586dbe771f7c57b9004447a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: f042da98ff5f85de3b4fce59a2726f2f4c46a1da8817abb524d700cd0413fc4118cb6c0a766a69abdc7e4ae5f0acdd35bc653b0c33116239c1e776f4c3218f23
|
|
7
|
+
data.tar.gz: 1824a4935d712e62268b9cad01f52c445ccecfcaa859b11fd93749f28ef3f259370b011af395ae269c9c03544b7ab5c1bb6ad3afd18cc8eea4a6d576e151ff76
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
name: Build
|
|
2
|
+
on:
|
|
3
|
+
pull_request:
|
|
4
|
+
push:
|
|
5
|
+
branches:
|
|
6
|
+
- master
|
|
7
|
+
jobs:
|
|
8
|
+
build:
|
|
9
|
+
runs-on: ubuntu-20.04
|
|
10
|
+
steps:
|
|
11
|
+
- uses: actions/checkout@v2
|
|
12
|
+
- uses: ruby/setup-ruby@master
|
|
13
|
+
with:
|
|
14
|
+
ruby-version: '3.0'
|
|
15
|
+
bundler-cache: false
|
|
16
|
+
- name: Install Dependencies
|
|
17
|
+
run: |
|
|
18
|
+
gem install bundler
|
|
19
|
+
bundle install --jobs 4 --retry 3
|
|
20
|
+
- name: Build
|
|
21
|
+
run: rake build
|
data/.github/workflows/test.yml
CHANGED
|
@@ -12,13 +12,13 @@ jobs:
|
|
|
12
12
|
fail-fast: false
|
|
13
13
|
matrix:
|
|
14
14
|
include:
|
|
15
|
-
- { os: ubuntu-20.04, ruby: '3.0' }
|
|
16
|
-
- { os: ubuntu-20.04, ruby: ruby-
|
|
17
|
-
- { os: macos-11.0, ruby: '3.0' }
|
|
18
|
-
- { os: macos-11.0, ruby: ruby-
|
|
19
|
-
- { os: windows-2019, ruby: mingw }
|
|
20
|
-
- { os: windows-2019, ruby: mswin }
|
|
21
|
-
name: ${{ matrix.os }} ${{ matrix.ruby }}
|
|
15
|
+
- { os: ubuntu-20.04, ruby: '3.0', backend: epoll }
|
|
16
|
+
- { os: ubuntu-20.04, ruby: '3.0', backend: ruby, disable-epoll: 1 }
|
|
17
|
+
- { os: macos-11.0, ruby: '3.0', backend: kqueue }
|
|
18
|
+
- { os: macos-11.0, ruby: '3.0', backend: ruby, disable-kqueue: 1 }
|
|
19
|
+
- { os: windows-2019, ruby: mingw, backend: ruby }
|
|
20
|
+
- { os: windows-2019, ruby: mswin, backend: ruby }
|
|
21
|
+
name: test ${{ matrix.os }} ${{ matrix.ruby }} ${{ matrix.backend }}
|
|
22
22
|
runs-on: ${{ matrix.os }}
|
|
23
23
|
timeout-minutes: 5
|
|
24
24
|
steps:
|
|
@@ -32,20 +32,9 @@ jobs:
|
|
|
32
32
|
gem install bundler
|
|
33
33
|
bundle install --jobs 4 --retry 3
|
|
34
34
|
- name: Compile
|
|
35
|
+
env:
|
|
36
|
+
DISABLE_EPOLL: ${{ matrix.disable-epoll }}
|
|
37
|
+
DISABLE_KQUEUE: ${{ matrix.disable-kqueue }}
|
|
35
38
|
run: rake compile
|
|
36
39
|
- 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
|
|
40
|
+
run: rake test
|
data/README.md
CHANGED
|
@@ -1,33 +1,49 @@
|
|
|
1
1
|
# Evt
|
|
2
2
|
|
|
3
|
-
The Event Library that designed for Ruby 3.0.
|
|
3
|
+
The Event Library that designed for Ruby 3.0 Fiber Scheduler.
|
|
4
4
|
|
|
5
5
|
**This gem is still under development, APIs and features are not stable. Advices and PRs are highly welcome.**
|
|
6
6
|
|
|
7
|
-
[](https://github.com/dsh0416/evt/actions?query=workflow%3A%
|
|
7
|
+
[](https://github.com/dsh0416/evt/actions?query=workflow%3A%22Build%22)
|
|
8
|
+
[](https://github.com/dsh0416/evt/actions?query=workflow%3A%22CI+Tests%22)
|
|
8
9
|
[](https://rubygems.org/gems/evt)
|
|
9
10
|
[](https://rubygems.org/gems/evt)
|
|
10
11
|
|
|
11
12
|
## Features
|
|
12
13
|
|
|
13
|
-
|
|
14
|
-
|
|
15
14
|
### IO Backend Support
|
|
16
15
|
|
|
17
16
|
| | Linux | Windows | macOS | FreeBSD |
|
|
18
17
|
| --------------- | ----------- | ------------| ----------- | ----------- |
|
|
19
|
-
| io_uring |
|
|
18
|
+
| io_uring | ⚠️ (See 1) | ❌ | ❌ | ❌ |
|
|
20
19
|
| epoll | ✅ (See 2) | ❌ | ❌ | ❌ |
|
|
21
20
|
| kqueue | ❌ | ❌ | ✅ (⚠️ See 5) | ✅ |
|
|
22
21
|
| IOCP | ❌ | ❌ (⚠️See 3) | ❌ | ❌ |
|
|
23
|
-
| Ruby (`select`) | ✅ Fallback | ✅ (⚠️See 4) | ✅ Fallback | ✅ Fallback |
|
|
22
|
+
| Ruby (`IO.select`) | ✅ Fallback | ✅ (⚠️See 4) | ✅ Fallback | ✅ Fallback |
|
|
24
23
|
|
|
25
|
-
1. when liburing is installed
|
|
26
|
-
2. when kernel version >= 2.6.
|
|
24
|
+
1. when liburing is installed. (Currently fixing)
|
|
25
|
+
2. when kernel version >= 2.6.9
|
|
27
26
|
3. WOULD NOT WORK until `FILE_FLAG_OVERLAPPED` is included in I/O initialization process.
|
|
28
27
|
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
28
|
5. `kqueue` performance in Darwin is very poor. **MAY BE DISABLED IN THE FUTURE.**
|
|
30
29
|
|
|
30
|
+
### Benchmark
|
|
31
|
+
|
|
32
|
+
The benchmark is running under `v0.3.1` version. See `example.rb` in [midori](https://github.com/midori-rb/midori.rb) for test code, the test is running under a single-thread server.
|
|
33
|
+
|
|
34
|
+
The test command is `wrk -t4 -c8192 -d30s http://localhost:3001`.
|
|
35
|
+
|
|
36
|
+
All of the systems have set their file descriptor limit to maximum.
|
|
37
|
+
On systems raising "Fiber unable to allocate memory", `sudo sysctl -w vm.max_map_count=1000000` is set.
|
|
38
|
+
|
|
39
|
+
| OS | CPU | Memory | Backend | req/s |
|
|
40
|
+
| ----- | ----------- | ------ | ---------------------- | --------------|
|
|
41
|
+
| Linux | Ryzen 2700x | 64GB | epoll | 1853259.47 |
|
|
42
|
+
| Linux | Ryzen 2700x | 64GB | io_uring | require fixes |
|
|
43
|
+
| Linux | Ryzen 2700x | 64GB | IO.select (using poll) | 1636849.15 |
|
|
44
|
+
| macOS | i7-6820HQ | 16GB | kqueue | 247370.37 |
|
|
45
|
+
| macOS | i7-6820HQ | 16GB | IO.select (using poll) | 323391.38 |
|
|
46
|
+
|
|
31
47
|
## Install
|
|
32
48
|
|
|
33
49
|
```bash
|
|
@@ -55,8 +71,6 @@ Fiber.schedule do
|
|
|
55
71
|
wr.close
|
|
56
72
|
end
|
|
57
73
|
|
|
58
|
-
scheduler.run
|
|
59
|
-
|
|
60
74
|
# "Hello World"
|
|
61
75
|
```
|
|
62
76
|
|
|
@@ -68,6 +82,7 @@ scheduler.run
|
|
|
68
82
|
- [x] Support iov features of io_uring
|
|
69
83
|
- [x] Support IOCP (**NOT ENABLED YET**)
|
|
70
84
|
- [x] Setup tests with Ruby 3
|
|
85
|
+
- [x] Selectable backend compilation by environment variable
|
|
71
86
|
- [ ] Support IOCP with iov features
|
|
72
87
|
- [ ] Setup more tests for production purpose
|
|
73
88
|
- [ ] Documentation for usages
|
data/Rakefile
CHANGED
data/evt.gemspec
CHANGED
|
@@ -6,11 +6,11 @@ Gem::Specification.new do |spec|
|
|
|
6
6
|
spec.authors = ["Delton Ding"]
|
|
7
7
|
spec.email = ["dsh0416@gmail.com"]
|
|
8
8
|
|
|
9
|
-
spec.summary = "
|
|
9
|
+
spec.summary = "The Event Library that designed for Ruby 3.0 Fiber Scheluer."
|
|
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 = '>=
|
|
13
|
+
spec.required_ruby_version = '>= 3.0.0.rc1'
|
|
14
14
|
|
|
15
15
|
spec.metadata["homepage_uri"] = spec.homepage
|
|
16
16
|
spec.metadata["source_code_uri"] = "https://github.com/dsh0416/evt"
|
|
@@ -18,10 +18,12 @@ 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|
|
|
21
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|examples|.vscode)/}) }
|
|
22
22
|
end
|
|
23
23
|
spec.require_paths = ["lib"]
|
|
24
24
|
spec.extensions = ['ext/evt/extconf.rb']
|
|
25
25
|
|
|
26
26
|
spec.add_development_dependency 'rake-compiler', '~> 1.0'
|
|
27
|
+
spec.add_development_dependency 'simplecov', '~> 0.20.0'
|
|
28
|
+
spec.add_development_dependency 'minitest-reporters', '~> 1.4'
|
|
27
29
|
end
|
data/ext/evt/epoll.h
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
#ifndef EPOLL_H
|
|
2
|
-
#define
|
|
2
|
+
#define EPOLL_H
|
|
3
3
|
#include "evt.h"
|
|
4
4
|
|
|
5
5
|
#if HAVE_SYS_EPOLL_H
|
|
6
|
-
VALUE
|
|
6
|
+
VALUE method_scheduler_epoll_init(VALUE self) {
|
|
7
7
|
rb_iv_set(self, "@epfd", INT2NUM(epoll_create(1))); // Size of epoll is ignored after Linux 2.6.8.
|
|
8
8
|
return Qnil;
|
|
9
9
|
}
|
|
10
10
|
|
|
11
|
-
VALUE
|
|
11
|
+
VALUE method_scheduler_epoll_register(VALUE self, VALUE io, VALUE interest) {
|
|
12
12
|
struct epoll_event event;
|
|
13
13
|
ID id_fileno = rb_intern("fileno");
|
|
14
14
|
int epfd = NUM2INT(rb_iv_get(self, "@epfd"));
|
|
@@ -25,21 +25,15 @@ VALUE method_scheduler_register(VALUE self, VALUE io, VALUE interest) {
|
|
|
25
25
|
event.events |= EPOLLOUT;
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
event.events |=EPOLLONESHOT;
|
|
29
|
+
|
|
28
30
|
event.data.ptr = (void*) io;
|
|
29
31
|
|
|
30
32
|
epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event);
|
|
31
33
|
return Qnil;
|
|
32
34
|
}
|
|
33
35
|
|
|
34
|
-
VALUE
|
|
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) {
|
|
36
|
+
VALUE method_scheduler_epoll_wait(VALUE self) {
|
|
43
37
|
int n, epfd, i, event_flag, timeout;
|
|
44
38
|
VALUE next_timeout, obj_io, readables, writables, result;
|
|
45
39
|
ID id_next_timeout = rb_intern("next_timeout");
|
|
@@ -56,7 +50,7 @@ VALUE method_scheduler_wait(VALUE self) {
|
|
|
56
50
|
timeout = NUM2INT(next_timeout);
|
|
57
51
|
}
|
|
58
52
|
|
|
59
|
-
struct epoll_event
|
|
53
|
+
struct epoll_event events[EPOLL_MAX_EVENTS];
|
|
60
54
|
|
|
61
55
|
n = epoll_wait(epfd, events, EPOLL_MAX_EVENTS, timeout);
|
|
62
56
|
if (n < 0) {
|
|
@@ -79,12 +73,10 @@ VALUE method_scheduler_wait(VALUE self) {
|
|
|
79
73
|
result = rb_ary_new2(2);
|
|
80
74
|
rb_ary_store(result, 0, readables);
|
|
81
75
|
rb_ary_store(result, 1, writables);
|
|
82
|
-
|
|
83
|
-
xfree(events);
|
|
84
76
|
return result;
|
|
85
77
|
}
|
|
86
78
|
|
|
87
|
-
VALUE
|
|
79
|
+
VALUE method_scheduler_epoll_backend(VALUE klass) {
|
|
88
80
|
return rb_str_new_cstr("epoll");
|
|
89
81
|
}
|
|
90
82
|
#endif
|
data/ext/evt/evt.c
CHANGED
|
@@ -5,30 +5,40 @@
|
|
|
5
5
|
|
|
6
6
|
void Init_evt_ext()
|
|
7
7
|
{
|
|
8
|
+
#ifdef HAVE_RB_EXT_RACTOR_SAFE
|
|
9
|
+
rb_ext_ractor_safe(true);
|
|
10
|
+
#endif
|
|
8
11
|
Evt = rb_define_module("Evt");
|
|
9
|
-
|
|
10
|
-
Payload = rb_define_class_under(
|
|
12
|
+
Bundled = rb_define_class_under(Evt, "Bundled", rb_cObject);
|
|
13
|
+
Payload = rb_define_class_under(Bundled, "Payload", rb_cObject);
|
|
11
14
|
Fiber = rb_define_class("Fiber", rb_cObject);
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
rb_define_method(
|
|
15
|
-
rb_define_method(
|
|
16
|
-
rb_define_method(
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
15
|
+
#if HAVE_LIBURING_H
|
|
16
|
+
rb_define_singleton_method(Bundled, "uring_backend", method_scheduler_uring_backend, 0);
|
|
17
|
+
rb_define_method(Bundled, "uring_init_selector", method_scheduler_uring_init, 0);
|
|
18
|
+
rb_define_method(Bundled, "uring_register", method_scheduler_uring_register, 2);
|
|
19
|
+
rb_define_method(Bundled, "uring_wait", method_scheduler_uring_wait, 0);
|
|
20
|
+
rb_define_method(Bundled, "uring_io_read", method_scheduler_uring_io_read, 4);
|
|
21
|
+
rb_define_method(Bundled, "uring_io_write", method_scheduler_uring_io_write, 4);
|
|
22
|
+
#endif
|
|
23
|
+
#if HAVE_SYS_EPOLL_H
|
|
24
|
+
rb_define_singleton_method(Bundled, "epoll_backend", method_scheduler_epoll_backend, 0);
|
|
25
|
+
rb_define_method(Bundled, "epoll_init_selector", method_scheduler_epoll_init, 0);
|
|
26
|
+
rb_define_method(Bundled, "epoll_register", method_scheduler_epoll_register, 2);
|
|
27
|
+
rb_define_method(Bundled, "epoll_wait", method_scheduler_epoll_wait, 0);
|
|
21
28
|
#endif
|
|
29
|
+
#if HAVE_SYS_EVENT_H
|
|
30
|
+
rb_define_singleton_method(Bundled, "kqueue_backend", method_scheduler_kqueue_backend, 0);
|
|
31
|
+
rb_define_method(Bundled, "kqueue_init_selector", method_scheduler_kqueue_init, 0);
|
|
32
|
+
rb_define_method(Bundled, "kqueue_register", method_scheduler_kqueue_register, 2);
|
|
33
|
+
rb_define_method(Bundled, "kqueue_wait", method_scheduler_kqueue_wait, 0);
|
|
34
|
+
#endif
|
|
35
|
+
rb_define_singleton_method(Bundled, "select_backend", method_scheduler_select_backend, 0);
|
|
36
|
+
rb_define_method(Bundled, "select_wait", method_scheduler_select_wait, 0);
|
|
22
37
|
}
|
|
23
38
|
|
|
24
|
-
#
|
|
25
|
-
|
|
26
|
-
#
|
|
27
|
-
|
|
28
|
-
#
|
|
29
|
-
#include "kqueue.h"
|
|
30
|
-
#elif HAVE_WINDOWS_H
|
|
31
|
-
#include "select.h"
|
|
32
|
-
// #include "iocp.h"
|
|
33
|
-
#endif
|
|
39
|
+
#include "uring.h"
|
|
40
|
+
#include "epoll.h"
|
|
41
|
+
#include "kqueue.h"
|
|
42
|
+
// #include "iocp.h"
|
|
43
|
+
#include "select.h"
|
|
34
44
|
#endif
|
data/ext/evt/evt.h
CHANGED
|
@@ -4,29 +4,21 @@
|
|
|
4
4
|
#include <ruby.h>
|
|
5
5
|
|
|
6
6
|
VALUE Evt = Qnil;
|
|
7
|
-
VALUE
|
|
7
|
+
VALUE Bundled = Qnil;
|
|
8
8
|
VALUE Payload = Qnil;
|
|
9
9
|
VALUE Fiber = Qnil;
|
|
10
10
|
|
|
11
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
12
|
|
|
27
13
|
#if HAVE_LIBURING_H
|
|
28
|
-
|
|
14
|
+
VALUE method_scheduler_uring_init(VALUE self);
|
|
15
|
+
VALUE method_scheduler_uring_register(VALUE self, VALUE io, VALUE interest);
|
|
16
|
+
VALUE method_scheduler_uring_wait(VALUE self);
|
|
17
|
+
VALUE method_scheduler_uring_backend(VALUE klass);
|
|
18
|
+
VALUE method_scheduler_uring_io_read(VALUE self, VALUE io, VALUE buffer, VALUE offset, VALUE length);
|
|
19
|
+
VALUE method_scheduler_uring_io_write(VALUE self, VALUE io, VALUE buffer, VALUE offset, VALUE length);
|
|
29
20
|
|
|
21
|
+
#include <liburing.h>
|
|
30
22
|
#define URING_ENTRIES 64
|
|
31
23
|
#define URING_MAX_EVENTS 64
|
|
32
24
|
|
|
@@ -49,13 +41,24 @@ VALUE method_scheduler_io_write(VALUE io, VALUE buffer, VALUE offset, VALUE leng
|
|
|
49
41
|
.data = NULL,
|
|
50
42
|
.flags = RUBY_TYPED_FREE_IMMEDIATELY,
|
|
51
43
|
};
|
|
52
|
-
#
|
|
44
|
+
#endif
|
|
45
|
+
#if HAVE_SYS_EPOLL_H
|
|
46
|
+
VALUE method_scheduler_epoll_init(VALUE self);
|
|
47
|
+
VALUE method_scheduler_epoll_register(VALUE self, VALUE io, VALUE interest);
|
|
48
|
+
VALUE method_scheduler_epoll_wait(VALUE self);
|
|
49
|
+
VALUE method_scheduler_epoll_backend(VALUE klass);
|
|
53
50
|
#include <sys/epoll.h>
|
|
54
51
|
#define EPOLL_MAX_EVENTS 64
|
|
55
|
-
#
|
|
52
|
+
#endif
|
|
53
|
+
#if HAVE_SYS_EVENT_H
|
|
54
|
+
VALUE method_scheduler_kqueue_init(VALUE self);
|
|
55
|
+
VALUE method_scheduler_kqueue_register(VALUE self, VALUE io, VALUE interest);
|
|
56
|
+
VALUE method_scheduler_kqueue_wait(VALUE self);
|
|
57
|
+
VALUE method_scheduler_kqueue_backend(VALUE klass);
|
|
56
58
|
#include <sys/event.h>
|
|
57
59
|
#define KQUEUE_MAX_EVENTS 64
|
|
58
|
-
#
|
|
60
|
+
#endif
|
|
61
|
+
#if HAVE_WINDOWS_H
|
|
59
62
|
// #include <Windows.h>
|
|
60
63
|
// #define IOCP_MAX_EVENTS 64
|
|
61
64
|
|
|
@@ -79,4 +82,6 @@ VALUE method_scheduler_io_write(VALUE io, VALUE buffer, VALUE offset, VALUE leng
|
|
|
79
82
|
// .flags = RUBY_TYPED_FREE_IMMEDIATELY,
|
|
80
83
|
// };
|
|
81
84
|
#endif
|
|
85
|
+
VALUE method_scheduler_select_wait(VALUE self);
|
|
86
|
+
VALUE method_scheduler_select_backend(VALUE klass);
|
|
82
87
|
#endif
|
data/ext/evt/extconf.rb
CHANGED
|
@@ -2,11 +2,22 @@ require 'mkmf'
|
|
|
2
2
|
extension_name = 'evt_ext'
|
|
3
3
|
dir_config(extension_name)
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
def env_defined?(v)
|
|
6
|
+
return false if ENV[v].nil?
|
|
7
|
+
return false if ENV[v].empty?
|
|
8
|
+
return false if ENV[v].upcase == 'FALSE'
|
|
9
|
+
return false if ENV[v] == '0'
|
|
10
|
+
true
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
unless env_defined?('DISABLE_URING')
|
|
14
|
+
have_library('uring')
|
|
15
|
+
have_header('liburing.h')
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
have_header('sys/epoll.h') unless env_defined?('DISABLE_EPOLL')
|
|
19
|
+
have_header('sys/event.h') unless env_defined?('DISABLE_KQUEUE')
|
|
20
|
+
have_header('Windows.h') unless env_defined?('DISABLE_IOCP')
|
|
10
21
|
|
|
11
22
|
create_header
|
|
12
23
|
create_makefile(extension_name)
|
data/ext/evt/iocp.h
CHANGED
|
@@ -11,13 +11,13 @@ size_t iocp_payload_size(const void* data) {
|
|
|
11
11
|
return sizeof(HANDLE);
|
|
12
12
|
}
|
|
13
13
|
|
|
14
|
-
VALUE
|
|
14
|
+
VALUE method_scheduler_iocp_init(VALUE self) {
|
|
15
15
|
HANDLE iocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE, NULL, 0, 0);
|
|
16
16
|
rb_iv_set(self, "@iocp", TypedData_Wrap_Struct(Payload, &type_iocp_payload, iocp));
|
|
17
17
|
return Qnil;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
VALUE
|
|
20
|
+
VALUE method_scheduler_iocp_register(VALUE self, VALUE io, VALUE interest) {
|
|
21
21
|
HANDLE iocp;
|
|
22
22
|
VALUE iocp_obj = rb_iv_get(self, "@iocp");
|
|
23
23
|
struct iocp_data* data;
|
|
@@ -47,11 +47,7 @@ VALUE method_scheduler_register(VALUE self, VALUE io, VALUE interest) {
|
|
|
47
47
|
return Qnil;
|
|
48
48
|
}
|
|
49
49
|
|
|
50
|
-
VALUE
|
|
51
|
-
return Qnil;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
VALUE method_scheduler_wait(VALUE self) {
|
|
50
|
+
VALUE method_scheduler_iocp_wait(VALUE self) {
|
|
55
51
|
ID id_next_timeout = rb_intern("next_timeout");
|
|
56
52
|
ID id_push = rb_intern("push");
|
|
57
53
|
VALUE iocp_obj = rb_iv_get(self, "@iocp");
|
|
@@ -119,7 +115,7 @@ VALUE method_scheduler_wait(VALUE self) {
|
|
|
119
115
|
return result;
|
|
120
116
|
}
|
|
121
117
|
|
|
122
|
-
VALUE
|
|
118
|
+
VALUE method_scheduler_iocp_backend(VALUE klass) {
|
|
123
119
|
return rb_str_new_cstr("iocp");
|
|
124
120
|
}
|
|
125
121
|
#endif
|
data/ext/evt/kqueue.h
CHANGED
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
#if HAVE_SYS_EVENT_H
|
|
6
6
|
|
|
7
|
-
VALUE
|
|
7
|
+
VALUE method_scheduler_kqueue_init(VALUE self) {
|
|
8
8
|
rb_iv_set(self, "@kq", INT2NUM(kqueue()));
|
|
9
9
|
return Qnil;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
VALUE
|
|
12
|
+
VALUE method_scheduler_kqueue_register(VALUE self, VALUE io, VALUE interest) {
|
|
13
13
|
struct kevent event;
|
|
14
14
|
u_short event_flags = 0;
|
|
15
15
|
ID id_fileno = rb_intern("fileno");
|
|
@@ -27,26 +27,15 @@ VALUE method_scheduler_register(VALUE self, VALUE io, VALUE interest) {
|
|
|
27
27
|
event_flags |= EVFILT_WRITE;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
EV_SET(&event, fd, event_flags, EV_ADD|EV_ENABLE, 0, 0, (void*) io);
|
|
30
|
+
EV_SET(&event, fd, event_flags, EV_ADD|EV_ENABLE|EV_ONESHOT, 0, 0, (void*) io);
|
|
31
31
|
kevent(kq, &event, 1, NULL, 0, NULL); // TODO: Check the return value
|
|
32
32
|
return Qnil;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
VALUE
|
|
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) {
|
|
35
|
+
VALUE method_scheduler_kqueue_wait(VALUE self) {
|
|
46
36
|
int n, kq, i;
|
|
47
37
|
u_short event_flags = 0;
|
|
48
|
-
|
|
49
|
-
struct kevent* events; // Event Triggered
|
|
38
|
+
struct kevent events[KQUEUE_MAX_EVENTS];
|
|
50
39
|
struct timespec timeout;
|
|
51
40
|
VALUE next_timeout, obj_io, readables, writables, result;
|
|
52
41
|
ID id_next_timeout = rb_intern("next_timeout");
|
|
@@ -57,8 +46,6 @@ VALUE method_scheduler_wait(VALUE self) {
|
|
|
57
46
|
readables = rb_ary_new();
|
|
58
47
|
writables = rb_ary_new();
|
|
59
48
|
|
|
60
|
-
events = (struct kevent*) xmalloc(sizeof(struct kevent) * KQUEUE_MAX_EVENTS);
|
|
61
|
-
|
|
62
49
|
if (next_timeout == Qnil || NUM2INT(next_timeout) == -1) {
|
|
63
50
|
n = kevent(kq, NULL, 0, events, KQUEUE_MAX_EVENTS, NULL);
|
|
64
51
|
} else {
|
|
@@ -85,11 +72,10 @@ VALUE method_scheduler_wait(VALUE self) {
|
|
|
85
72
|
rb_ary_store(result, 0, readables);
|
|
86
73
|
rb_ary_store(result, 1, writables);
|
|
87
74
|
|
|
88
|
-
xfree(events);
|
|
89
75
|
return result;
|
|
90
76
|
}
|
|
91
77
|
|
|
92
|
-
VALUE
|
|
78
|
+
VALUE method_scheduler_kqueue_backend(VALUE klass) {
|
|
93
79
|
return rb_str_new_cstr("kqueue");
|
|
94
80
|
}
|
|
95
81
|
#endif
|
data/ext/evt/select.h
CHANGED
|
@@ -2,19 +2,7 @@
|
|
|
2
2
|
#define SELECT_H
|
|
3
3
|
#include "evt.h"
|
|
4
4
|
|
|
5
|
-
VALUE
|
|
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) {
|
|
5
|
+
VALUE method_scheduler_select_wait(VALUE self) {
|
|
18
6
|
// return IO.select(@readable.keys, @writable.keys, [], next_timeout)
|
|
19
7
|
VALUE readable, writable, readable_keys, writable_keys, next_timeout;
|
|
20
8
|
ID id_select = rb_intern("select");
|
|
@@ -30,7 +18,7 @@ VALUE method_scheduler_wait(VALUE self) {
|
|
|
30
18
|
return rb_funcall(rb_cIO, id_select, 4, readable_keys, writable_keys, rb_ary_new(), next_timeout);
|
|
31
19
|
}
|
|
32
20
|
|
|
33
|
-
VALUE
|
|
21
|
+
VALUE method_scheduler_select_backend(VALUE klass) {
|
|
34
22
|
return rb_str_new_cstr("ruby");
|
|
35
23
|
}
|
|
36
24
|
#endif
|
data/ext/evt/uring.h
CHANGED
|
@@ -12,7 +12,7 @@ size_t uring_payload_size(const void* data) {
|
|
|
12
12
|
return sizeof(struct io_uring);
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
VALUE
|
|
15
|
+
VALUE method_scheduler_uring_init(VALUE self) {
|
|
16
16
|
int ret;
|
|
17
17
|
struct io_uring* ring;
|
|
18
18
|
ring = xmalloc(sizeof(struct io_uring));
|
|
@@ -24,7 +24,7 @@ VALUE method_scheduler_init(VALUE self) {
|
|
|
24
24
|
return Qnil;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
VALUE
|
|
27
|
+
VALUE method_scheduler_uring_register(VALUE self, VALUE io, VALUE interest) {
|
|
28
28
|
VALUE ring_obj;
|
|
29
29
|
struct io_uring* ring;
|
|
30
30
|
struct io_uring_sqe *sqe;
|
|
@@ -60,12 +60,7 @@ VALUE method_scheduler_register(VALUE self, VALUE io, VALUE interest) {
|
|
|
60
60
|
return Qnil;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
VALUE
|
|
64
|
-
// io_uring runs under oneshot mode. No need to deregister.
|
|
65
|
-
return Qnil;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
VALUE method_scheduler_wait(VALUE self) {
|
|
63
|
+
VALUE method_scheduler_uring_wait(VALUE self) {
|
|
69
64
|
struct io_uring* ring;
|
|
70
65
|
struct io_uring_cqe *cqes[URING_MAX_EVENTS];
|
|
71
66
|
struct uring_data *data;
|
|
@@ -84,6 +79,12 @@ VALUE method_scheduler_wait(VALUE self) {
|
|
|
84
79
|
iovs = rb_ary_new();
|
|
85
80
|
|
|
86
81
|
TypedData_Get_Struct(rb_iv_get(self, "@ring"), struct io_uring, &type_uring_payload, ring);
|
|
82
|
+
|
|
83
|
+
struct __kernel_timespec ts;
|
|
84
|
+
ts.tv_sec = NUM2INT(next_timeout);
|
|
85
|
+
ts.tv_nsec = 0;
|
|
86
|
+
|
|
87
|
+
io_uring_wait_cqe_timeout(ring, cqes, &ts);
|
|
87
88
|
ret = io_uring_peek_batch_cqe(ring, cqes, URING_MAX_EVENTS);
|
|
88
89
|
|
|
89
90
|
for (i = 0; i < ret; i++) {
|
|
@@ -99,18 +100,12 @@ VALUE method_scheduler_wait(VALUE self) {
|
|
|
99
100
|
rb_funcall(writables, id_push, 1, obj_io);
|
|
100
101
|
}
|
|
101
102
|
} else {
|
|
102
|
-
|
|
103
|
+
VALUE v = rb_ary_new2(2);
|
|
104
|
+
rb_ary_store(v, 0, obj_io);
|
|
105
|
+
rb_ary_store(v, 1, obj_io);
|
|
106
|
+
rb_funcall(iovs, id_push, 1, SIZET2NUM(cqes[i]->res));
|
|
103
107
|
}
|
|
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
|
-
}
|
|
108
|
+
io_uring_cqe_seen(ring, cqes[i]);
|
|
114
109
|
}
|
|
115
110
|
|
|
116
111
|
result = rb_ary_new2(3);
|
|
@@ -121,7 +116,7 @@ VALUE method_scheduler_wait(VALUE self) {
|
|
|
121
116
|
return result;
|
|
122
117
|
}
|
|
123
118
|
|
|
124
|
-
VALUE
|
|
119
|
+
VALUE method_scheduler_uring_io_read(VALUE self, VALUE io, VALUE buffer, VALUE offset, VALUE length) {
|
|
125
120
|
struct io_uring* ring;
|
|
126
121
|
struct uring_data *data;
|
|
127
122
|
char* read_buffer;
|
|
@@ -136,30 +131,26 @@ VALUE method_scheduler_io_read(VALUE self, VALUE io, VALUE buffer, VALUE offset,
|
|
|
136
131
|
int fd = NUM2INT(rb_funcall(io, id_fileno, 0));
|
|
137
132
|
|
|
138
133
|
read_buffer = (char*) xmalloc(NUM2SIZET(length));
|
|
139
|
-
struct iovec iov = {
|
|
140
|
-
.iov_base = read_buffer,
|
|
141
|
-
.iov_len = NUM2SIZET(length),
|
|
142
|
-
};
|
|
143
134
|
|
|
144
135
|
data = (struct uring_data*) xmalloc(sizeof(struct uring_data));
|
|
145
136
|
data->is_poll = false;
|
|
146
137
|
data->io = io;
|
|
147
138
|
data->poll_mask = 0;
|
|
148
139
|
|
|
149
|
-
|
|
140
|
+
io_uring_prep_read(sqe, fd, read_buffer, 1, NUM2SIZET(length), NUM2SIZET(offset));
|
|
150
141
|
io_uring_sqe_set_data(sqe, data);
|
|
151
142
|
io_uring_submit(ring);
|
|
152
143
|
|
|
144
|
+
VALUE ret = rb_funcall(Fiber, rb_intern("yield"), 0); // Fiber.yield
|
|
145
|
+
|
|
153
146
|
VALUE result = rb_str_new(read_buffer, strlen(read_buffer));
|
|
154
147
|
if (buffer != Qnil) {
|
|
155
148
|
rb_str_append(buffer, result);
|
|
156
149
|
}
|
|
157
|
-
|
|
158
|
-
rb_funcall(Fiber, rb_intern("yield"), 0); // Fiber.yield
|
|
159
|
-
return result;
|
|
150
|
+
return ret;
|
|
160
151
|
}
|
|
161
152
|
|
|
162
|
-
VALUE
|
|
153
|
+
VALUE method_scheduler_uring_io_write(VALUE self, VALUE io, VALUE buffer, VALUE offset, VALUE length) {
|
|
163
154
|
struct io_uring* ring;
|
|
164
155
|
struct uring_data *data;
|
|
165
156
|
char* write_buffer;
|
|
@@ -174,24 +165,19 @@ VALUE method_scheduler_io_write(VALUE self, VALUE io, VALUE buffer, VALUE offset
|
|
|
174
165
|
int fd = NUM2INT(rb_funcall(io, id_fileno, 0));
|
|
175
166
|
|
|
176
167
|
write_buffer = StringValueCStr(buffer);
|
|
177
|
-
struct iovec iov = {
|
|
178
|
-
.iov_base = write_buffer,
|
|
179
|
-
.iov_len = NUM2SIZET(length),
|
|
180
|
-
};
|
|
181
168
|
|
|
182
169
|
data = (struct uring_data*) xmalloc(sizeof(struct uring_data));
|
|
183
170
|
data->is_poll = false;
|
|
184
171
|
data->io = io;
|
|
185
172
|
data->poll_mask = 0;
|
|
186
173
|
|
|
187
|
-
|
|
174
|
+
io_uring_prep_write(sqe, fd, write_buffer, NUM2SIZET(length), NUM2SIZET(offset));
|
|
188
175
|
io_uring_sqe_set_data(sqe, data);
|
|
189
176
|
io_uring_submit(ring);
|
|
190
|
-
rb_funcall(Fiber, rb_intern("yield"), 0);
|
|
191
|
-
return length;
|
|
177
|
+
return rb_funcall(Fiber, rb_intern("yield"), 0);
|
|
192
178
|
}
|
|
193
179
|
|
|
194
|
-
VALUE
|
|
180
|
+
VALUE method_scheduler_uring_backend(VALUE klass) {
|
|
195
181
|
return rb_str_new_cstr("liburing");
|
|
196
182
|
}
|
|
197
183
|
|
data/lib/evt.rb
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'fiber'
|
|
4
|
+
require 'socket'
|
|
5
|
+
require 'io/nonblock'
|
|
3
6
|
require_relative 'evt/version'
|
|
7
|
+
require_relative 'evt/backends/bundled'
|
|
8
|
+
require_relative 'evt/backends/epoll'
|
|
9
|
+
require_relative 'evt/backends/iocp'
|
|
10
|
+
require_relative 'evt/backends/kqueue'
|
|
11
|
+
require_relative 'evt/backends/select'
|
|
12
|
+
require_relative 'evt/backends/uring'
|
|
4
13
|
require_relative 'evt/scheduler'
|
|
5
14
|
require_relative 'evt_ext'
|
|
6
15
|
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Evt::Bundled
|
|
4
|
+
MAXIMUM_TIMEOUT = 5
|
|
5
|
+
COLLECT_COUNTER_MAX = 16384
|
|
6
|
+
|
|
7
|
+
def initialize
|
|
8
|
+
@readable = {}
|
|
9
|
+
@writable = {}
|
|
10
|
+
@waiting = {}
|
|
11
|
+
@iovs = {}
|
|
12
|
+
|
|
13
|
+
@lock = Mutex.new
|
|
14
|
+
@blocking = 0
|
|
15
|
+
@ready = []
|
|
16
|
+
@collect_counter = 0
|
|
17
|
+
|
|
18
|
+
init_selector
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
attr_reader :readable
|
|
22
|
+
attr_reader :writable
|
|
23
|
+
attr_reader :waiting
|
|
24
|
+
|
|
25
|
+
def next_timeout
|
|
26
|
+
_fiber, timeout = @waiting.min_by{ |key, value| value }
|
|
27
|
+
|
|
28
|
+
if timeout
|
|
29
|
+
offset = timeout - current_time
|
|
30
|
+
return 0 if offset < 0
|
|
31
|
+
return offset if offset < MAXIMUM_TIMEOUT
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
MAXIMUM_TIMEOUT
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def run
|
|
38
|
+
while @readable.any? or @writable.any? or @waiting.any? or @iovs.any? or @blocking.positive?
|
|
39
|
+
readable, writable, iovs = self.wait
|
|
40
|
+
|
|
41
|
+
readable&.each do |io|
|
|
42
|
+
fiber = @readable.delete(io)
|
|
43
|
+
fiber&.resume
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
writable&.each do |io|
|
|
47
|
+
fiber = @writable.delete(io)
|
|
48
|
+
fiber&.resume
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
unless iovs.nil?
|
|
52
|
+
iovs&.each do |v|
|
|
53
|
+
io, ret = v
|
|
54
|
+
fiber = @iovs.delete(io)
|
|
55
|
+
fiber&.resume(ret)
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
collect
|
|
60
|
+
|
|
61
|
+
if @waiting.any?
|
|
62
|
+
time = current_time
|
|
63
|
+
waiting, @waiting = @waiting, {}
|
|
64
|
+
|
|
65
|
+
waiting.each do |fiber, timeout|
|
|
66
|
+
if timeout <= time
|
|
67
|
+
fiber.resume if fiber.is_a? Fiber and fiber.alive?
|
|
68
|
+
else
|
|
69
|
+
@waiting[fiber] = timeout
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
if @ready.any?
|
|
75
|
+
ready = nil
|
|
76
|
+
|
|
77
|
+
@lock.synchronize do
|
|
78
|
+
ready, @ready = @ready, []
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
ready.each do |fiber|
|
|
82
|
+
fiber.resume if fiber.is_a? Fiber and fiber.alive?
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def current_time
|
|
89
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Wait for the given file descriptor to match the specified events within
|
|
93
|
+
# the specified timeout.
|
|
94
|
+
# @parameter event [Integer] A bit mask of `IO::READABLE`,
|
|
95
|
+
# `IO::WRITABLE` and `IO::PRIORITY`.
|
|
96
|
+
# @parameter timeout [Numeric] The amount of time to wait for the event in seconds.
|
|
97
|
+
# @returns [Integer] The subset of events that are ready.
|
|
98
|
+
def io_wait(io, events, duration)
|
|
99
|
+
# TODO: IO::PRIORITY
|
|
100
|
+
@readable[io] = Fiber.current unless (events & IO::READABLE).zero?
|
|
101
|
+
@writable[io] = Fiber.current unless (events & IO::WRITABLE).zero?
|
|
102
|
+
self.register(io, events)
|
|
103
|
+
Fiber.yield
|
|
104
|
+
true
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Sleep the current task for the specified duration, or forever if not
|
|
108
|
+
# specified.
|
|
109
|
+
# @param duration [Numeric] The amount of time to sleep in seconds.
|
|
110
|
+
def kernel_sleep(duration = nil)
|
|
111
|
+
self.block(:sleep, duration)
|
|
112
|
+
true
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Block the calling fiber.
|
|
116
|
+
# @parameter blocker [Object] What we are waiting on, informational only.
|
|
117
|
+
# @parameter timeout [Numeric | Nil] The amount of time to wait for in seconds.
|
|
118
|
+
# @returns [Boolean] Whether the blocking operation was successful or not.
|
|
119
|
+
def block(blocker, timeout = nil)
|
|
120
|
+
if timeout
|
|
121
|
+
@waiting[Fiber.current] = current_time + timeout
|
|
122
|
+
begin
|
|
123
|
+
Fiber.yield
|
|
124
|
+
ensure
|
|
125
|
+
@waiting.delete(Fiber.current)
|
|
126
|
+
end
|
|
127
|
+
else
|
|
128
|
+
@blocking += 1
|
|
129
|
+
begin
|
|
130
|
+
Fiber.yield
|
|
131
|
+
ensure
|
|
132
|
+
@blocking -= 1
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Unblock the specified fiber.
|
|
138
|
+
# @parameter blocker [Object] What we are waiting on, informational only.
|
|
139
|
+
# @parameter fiber [Fiber] The fiber to unblock.
|
|
140
|
+
# @reentrant Thread safe.
|
|
141
|
+
def unblock(blocker, fiber)
|
|
142
|
+
@lock.synchronize do
|
|
143
|
+
@ready << fiber
|
|
144
|
+
end
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Invoked when the thread exits.
|
|
148
|
+
def close
|
|
149
|
+
self.run
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Collect closed streams in readables and writables
|
|
153
|
+
def collect(force=false)
|
|
154
|
+
if @collect_counter < COLLECT_COUNTER_MAX and !force
|
|
155
|
+
@collect_counter += 1
|
|
156
|
+
return
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
@collect_counter = 0
|
|
160
|
+
|
|
161
|
+
@readable.keys.each do |io|
|
|
162
|
+
@readable.delete(io) if io.closed?
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
@writable.keys.each do |io|
|
|
166
|
+
@writable.delete(io) if io.closed?
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
@iovs.keys.each do |io|
|
|
170
|
+
@iovs.delete(io) if io.closed?
|
|
171
|
+
end
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# Intercept the creation of a non-blocking fiber.
|
|
175
|
+
# @returns [Fiber]
|
|
176
|
+
def fiber(&block)
|
|
177
|
+
fiber = Fiber.new(blocking: false, &block)
|
|
178
|
+
fiber.resume
|
|
179
|
+
fiber
|
|
180
|
+
end
|
|
181
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Evt::Epoll < Evt::Bundled
|
|
4
|
+
def self.available?
|
|
5
|
+
self.respond_to?(:epoll_backend)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def self.backend
|
|
9
|
+
self.epoll_backend
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def init_selector
|
|
13
|
+
epoll_init_selector
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def register(io, interest)
|
|
17
|
+
epoll_register(io, interest)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def wait
|
|
21
|
+
epoll_wait
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Evt::Iocp < Evt::Bundled
|
|
4
|
+
##
|
|
5
|
+
# IOCP is totally disabled for now
|
|
6
|
+
def self.available?
|
|
7
|
+
false
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def init_selector
|
|
11
|
+
# Placeholder
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def register(io, interest)
|
|
15
|
+
# Placeholder
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def io_read(io, buffer, offset, length)
|
|
19
|
+
# Placeholder
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def io_write(io, buffer, offset, length)
|
|
23
|
+
# Placeholder
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def wait
|
|
27
|
+
# Placeholder
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Evt::Kqueue < Evt::Bundled
|
|
4
|
+
def self.available?
|
|
5
|
+
self.respond_to?(:kqueue_backend)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def self.backend
|
|
9
|
+
self.kqueue_backend
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def init_selector
|
|
13
|
+
kqueue_init_selector
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def register(io, interest)
|
|
17
|
+
kqueue_register(io, interest)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def wait
|
|
21
|
+
kqueue_wait
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Evt::Select < Evt::Bundled
|
|
4
|
+
def self.available?
|
|
5
|
+
self.respond_to?(:select_backend)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def self.backend
|
|
9
|
+
self.select_backend
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def init_selector
|
|
13
|
+
# Select is stateless
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def register(io, interest)
|
|
17
|
+
# Select is stateless
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def wait
|
|
21
|
+
select_wait
|
|
22
|
+
rescue Errno::EBADF => _
|
|
23
|
+
collect(true)
|
|
24
|
+
return [], []
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
class Evt::Uring < Evt::Bundled
|
|
4
|
+
def self.available?
|
|
5
|
+
self.respond_to?(:uring_backend)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def self.backend
|
|
9
|
+
self.uring_backend
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def init_selector
|
|
13
|
+
uring_init_selector
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def register(io, interest)
|
|
17
|
+
uring_register(io, interest)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def io_read(io, buffer, offset, length)
|
|
21
|
+
uring_io_read(io, buffer, offset, length)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def io_write(io, buffer, offset, length)
|
|
25
|
+
uring_io_write(io, buffer, offset, length)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def wait
|
|
29
|
+
uring_wait
|
|
30
|
+
end
|
|
31
|
+
end
|
data/lib/evt/scheduler.rb
CHANGED
|
@@ -1,121 +1,36 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
##
|
|
4
|
+
# The major class for Ruby Fiber Scheduler
|
|
5
|
+
# @example
|
|
6
|
+
# scheduler = Evt::Scheduler.new
|
|
7
|
+
# Fiber.set_scheduler scheduler
|
|
8
|
+
# scheduler.run
|
|
7
9
|
class Evt::Scheduler
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
attr_reader :writable
|
|
24
|
-
attr_reader :waiting
|
|
25
|
-
|
|
26
|
-
def next_timeout
|
|
27
|
-
_fiber, timeout = @waiting.min_by{|key, value| value}
|
|
28
|
-
|
|
29
|
-
if timeout
|
|
30
|
-
offset = timeout - current_time
|
|
31
|
-
offset < 0 ? 0 : offset
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def run
|
|
36
|
-
while @readable.any? or @writable.any? or @waiting.any? or @iovs.any? or @locking.positive?
|
|
37
|
-
readable, writable, iovs = self.wait
|
|
38
|
-
|
|
39
|
-
readable&.each do |io|
|
|
40
|
-
fiber = @readable.delete(io)
|
|
41
|
-
fiber&.resume
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
writable&.each do |io|
|
|
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
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
if @waiting.any?
|
|
57
|
-
time = current_time
|
|
58
|
-
waiting = @waiting
|
|
59
|
-
@waiting = {}
|
|
60
|
-
|
|
61
|
-
waiting.each do |fiber, timeout|
|
|
62
|
-
if timeout <= time
|
|
63
|
-
fiber.resume
|
|
64
|
-
else
|
|
65
|
-
@waiting[fiber] = timeout
|
|
66
|
-
end
|
|
67
|
-
end
|
|
68
|
-
end
|
|
69
|
-
|
|
70
|
-
if @ready.any?
|
|
71
|
-
ready = nil
|
|
72
|
-
|
|
73
|
-
@lock.synchronize do
|
|
74
|
-
ready, @ready = @ready, []
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
ready.each do |fiber|
|
|
78
|
-
fiber.resume
|
|
79
|
-
end
|
|
10
|
+
class << self
|
|
11
|
+
BACKENDS = [
|
|
12
|
+
Evt::Uring,
|
|
13
|
+
Evt::Epoll,
|
|
14
|
+
Evt::Kqueue,
|
|
15
|
+
Evt::Iocp,
|
|
16
|
+
Evt::Select,
|
|
17
|
+
].freeze
|
|
18
|
+
|
|
19
|
+
##
|
|
20
|
+
# Returns the fastest possible scheduler
|
|
21
|
+
# Use the backend scheduler directly if you want to choose it yourself
|
|
22
|
+
def new
|
|
23
|
+
BACKENDS.each do |backend|
|
|
24
|
+
return backend.new if backend.available?
|
|
80
25
|
end
|
|
81
26
|
end
|
|
82
|
-
end
|
|
83
|
-
|
|
84
|
-
def current_time
|
|
85
|
-
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
86
|
-
end
|
|
87
|
-
|
|
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?
|
|
91
|
-
self.register(io, events)
|
|
92
|
-
Fiber.yield
|
|
93
|
-
self.deregister(io)
|
|
94
|
-
true
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
def kernel_sleep(duration = nil)
|
|
98
|
-
@waiting[Fiber.current] = current_time + duration if duration.nil?
|
|
99
|
-
Fiber.yield
|
|
100
|
-
true
|
|
101
|
-
end
|
|
102
|
-
|
|
103
|
-
def mutex_lock(mutex)
|
|
104
|
-
@locking += 1
|
|
105
|
-
Fiber.yield
|
|
106
|
-
ensure
|
|
107
|
-
@locking -= 1
|
|
108
|
-
end
|
|
109
27
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
28
|
+
##
|
|
29
|
+
# Returns all available backends on this machine
|
|
30
|
+
def availables
|
|
31
|
+
BACKENDS.filter do |backend|
|
|
32
|
+
backend.available?
|
|
33
|
+
end
|
|
113
34
|
end
|
|
114
35
|
end
|
|
115
|
-
|
|
116
|
-
def fiber(&block)
|
|
117
|
-
fiber = Fiber.new(blocking: false, &block)
|
|
118
|
-
fiber.resume
|
|
119
|
-
fiber
|
|
120
|
-
end
|
|
121
36
|
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.2
|
|
4
|
+
version: 0.3.2
|
|
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-12-
|
|
11
|
+
date: 2020-12-23 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rake-compiler
|
|
@@ -24,6 +24,34 @@ dependencies:
|
|
|
24
24
|
- - "~>"
|
|
25
25
|
- !ruby/object:Gem::Version
|
|
26
26
|
version: '1.0'
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: simplecov
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - "~>"
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: 0.20.0
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - "~>"
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: 0.20.0
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: minitest-reporters
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - "~>"
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '1.4'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - "~>"
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '1.4'
|
|
27
55
|
description: A low-level Event Handler designed for Ruby 3 Scheduler for better performance
|
|
28
56
|
email:
|
|
29
57
|
- dsh0416@gmail.com
|
|
@@ -32,6 +60,7 @@ extensions:
|
|
|
32
60
|
- ext/evt/extconf.rb
|
|
33
61
|
extra_rdoc_files: []
|
|
34
62
|
files:
|
|
63
|
+
- ".github/workflows/build.yml"
|
|
35
64
|
- ".github/workflows/test.yml"
|
|
36
65
|
- ".gitignore"
|
|
37
66
|
- CODE_OF_CONDUCT.md
|
|
@@ -49,6 +78,12 @@ files:
|
|
|
49
78
|
- ext/evt/select.h
|
|
50
79
|
- ext/evt/uring.h
|
|
51
80
|
- lib/evt.rb
|
|
81
|
+
- lib/evt/backends/bundled.rb
|
|
82
|
+
- lib/evt/backends/epoll.rb
|
|
83
|
+
- lib/evt/backends/iocp.rb
|
|
84
|
+
- lib/evt/backends/kqueue.rb
|
|
85
|
+
- lib/evt/backends/select.rb
|
|
86
|
+
- lib/evt/backends/uring.rb
|
|
52
87
|
- lib/evt/scheduler.rb
|
|
53
88
|
- lib/evt/version.rb
|
|
54
89
|
homepage: https://github.com/dsh0416/evt
|
|
@@ -65,7 +100,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
65
100
|
requirements:
|
|
66
101
|
- - ">="
|
|
67
102
|
- !ruby/object:Gem::Version
|
|
68
|
-
version:
|
|
103
|
+
version: 3.0.0.rc1
|
|
69
104
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
70
105
|
requirements:
|
|
71
106
|
- - ">="
|
|
@@ -75,5 +110,5 @@ requirements: []
|
|
|
75
110
|
rubygems_version: 3.2.2
|
|
76
111
|
signing_key:
|
|
77
112
|
specification_version: 4
|
|
78
|
-
summary:
|
|
113
|
+
summary: The Event Library that designed for Ruby 3.0 Fiber Scheluer.
|
|
79
114
|
test_files: []
|