evt 0.2.0 → 0.3.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/build.yml +21 -0
- data/.github/workflows/test.yml +11 -22
- data/README.md +27 -8
- data/Rakefile +1 -0
- data/evt.gemspec +4 -3
- data/ext/evt/epoll.h +7 -9
- data/ext/evt/evt.c +32 -21
- data/ext/evt/evt.h +27 -19
- data/ext/evt/extconf.rb +16 -5
- data/ext/evt/iocp.h +4 -8
- data/ext/evt/kqueue.h +6 -21
- data/ext/evt/select.h +2 -14
- data/ext/evt/uring.h +18 -24
- data/lib/evt.rb +9 -0
- data/lib/evt/backends/bundled.rb +177 -0
- data/lib/evt/backends/epoll.rb +27 -0
- data/lib/evt/backends/iocp.rb +33 -0
- data/lib/evt/backends/kqueue.rb +27 -0
- data/lib/evt/backends/select.rb +27 -0
- data/lib/evt/backends/uring.rb +35 -0
- data/lib/evt/scheduler.rb +27 -112
- data/lib/evt/version.rb +1 -1
- metadata +25 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 40b96a960c2027db56a915a8c4d6e20f8d29fd9f195003ef1a93cf9fad13a151
|
4
|
+
data.tar.gz: d6f99c7c134954a47b68c56f9a68f1227ed9ac90cefedefbef33a9c8978730aa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 75dbf1e519db25b8a0f63958cb2578fe852f12374eaed0a5a3b9595b30315ad92971477db2b27ed1bd2db6bd051553fa93eaa41263ed45c08c57ace0b43117fd
|
7
|
+
data.tar.gz: 905988d2a76fed454cd42b3acc014d5ddce044ecdf918352d8a6ede58213b3006dda7588a7d5889c6a4b16b23cce0a14308d71e4ba02430efcfe0fcdfa8f38c6
|
@@ -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,15 +1,16 @@
|
|
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
|
-

|
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)
|
9
|
+
[](https://rubygems.org/gems/evt)
|
10
|
+
[](https://rubygems.org/gems/evt)
|
8
11
|
|
9
12
|
## Features
|
10
13
|
|
11
|
-
|
12
|
-
|
13
14
|
### IO Backend Support
|
14
15
|
|
15
16
|
| | Linux | Windows | macOS | FreeBSD |
|
@@ -18,14 +19,33 @@ The Event Library that designed for Ruby 3.0.
|
|
18
19
|
| epoll | ✅ (See 2) | ❌ | ❌ | ❌ |
|
19
20
|
| kqueue | ❌ | ❌ | ✅ (⚠️ See 5) | ✅ |
|
20
21
|
| IOCP | ❌ | ❌ (⚠️See 3) | ❌ | ❌ |
|
21
|
-
| Ruby (`select`) | ✅ Fallback | ✅ (⚠️See 4) | ✅ Fallback | ✅ Fallback |
|
22
|
+
| Ruby (`IO.select`) | ✅ Fallback | ✅ (⚠️See 4) | ✅ Fallback | ✅ Fallback |
|
22
23
|
|
23
24
|
1. when liburing is installed
|
24
|
-
2. when kernel version >= 2.6.
|
25
|
+
2. when kernel version >= 2.6.9
|
25
26
|
3. WOULD NOT WORK until `FILE_FLAG_OVERLAPPED` is included in I/O initialization process.
|
26
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).
|
27
28
|
5. `kqueue` performance in Darwin is very poor. **MAY BE DISABLED IN THE FUTURE.**
|
28
29
|
|
30
|
+
### Benchmark
|
31
|
+
|
32
|
+
The benchmark is running under `v0.2.2` version. See [evt-server-benchmark](https://github.com/dsh0416/evt-server-benchmark) 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
|
+
|
38
|
+
| OS | CPU | Memory | Backend | req/s |
|
39
|
+
| ----- | ----------- | ------ | ---------------------- | -------- |
|
40
|
+
| Linux | Ryzen 2700x | 64GB | epoll | 54680.08 |
|
41
|
+
| Linux | Ryzen 2700x | 64GB | io_uring | 50245.53 |
|
42
|
+
| Linux | Ryzen 2700x | 64GB | IO.select (using poll) | 44159.23 |
|
43
|
+
| macOS | i7-6820HQ | 16GB | kqueue | 37855.53 |
|
44
|
+
| macOS | i7-6820HQ | 16GB | IO.select (using poll) | 28293.36 |
|
45
|
+
|
46
|
+
The benchmark uses an invalid parser, and `wrk` is very error-sensitive. The benchmark can't close the connection properly.
|
47
|
+
Use a valid parser, recent updates to my [midori](https://github.com/midori-rb/midori.rb) is able to use Ruby scheduler, which could achives 247k+ req/s on single thread with `kqueue` and 647k+ req/s with `epoll`.
|
48
|
+
|
29
49
|
## Install
|
30
50
|
|
31
51
|
```bash
|
@@ -53,8 +73,6 @@ Fiber.schedule do
|
|
53
73
|
wr.close
|
54
74
|
end
|
55
75
|
|
56
|
-
scheduler.run
|
57
|
-
|
58
76
|
# "Hello World"
|
59
77
|
```
|
60
78
|
|
@@ -66,6 +84,7 @@ scheduler.run
|
|
66
84
|
- [x] Support iov features of io_uring
|
67
85
|
- [x] Support IOCP (**NOT ENABLED YET**)
|
68
86
|
- [x] Setup tests with Ruby 3
|
87
|
+
- [x] Selectable backend compilation by environment variable
|
69
88
|
- [ ] Support IOCP with iov features
|
70
89
|
- [ ] Setup more tests for production purpose
|
71
90
|
- [ ] 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,11 @@ 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'
|
27
28
|
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"));
|
@@ -31,7 +31,7 @@ VALUE method_scheduler_register(VALUE self, VALUE io, VALUE interest) {
|
|
31
31
|
return Qnil;
|
32
32
|
}
|
33
33
|
|
34
|
-
VALUE
|
34
|
+
VALUE method_scheduler_epoll_deregister(VALUE self, VALUE io) {
|
35
35
|
ID id_fileno = rb_intern("fileno");
|
36
36
|
int epfd = NUM2INT(rb_iv_get(self, "@epfd"));
|
37
37
|
int fd = NUM2INT(rb_funcall(io, id_fileno, 0));
|
@@ -39,7 +39,7 @@ VALUE method_scheduler_deregister(VALUE self, VALUE io) {
|
|
39
39
|
return Qnil;
|
40
40
|
}
|
41
41
|
|
42
|
-
VALUE
|
42
|
+
VALUE method_scheduler_epoll_wait(VALUE self) {
|
43
43
|
int n, epfd, i, event_flag, timeout;
|
44
44
|
VALUE next_timeout, obj_io, readables, writables, result;
|
45
45
|
ID id_next_timeout = rb_intern("next_timeout");
|
@@ -56,7 +56,7 @@ VALUE method_scheduler_wait(VALUE self) {
|
|
56
56
|
timeout = NUM2INT(next_timeout);
|
57
57
|
}
|
58
58
|
|
59
|
-
struct epoll_event
|
59
|
+
struct epoll_event events[EPOLL_MAX_EVENTS];
|
60
60
|
|
61
61
|
n = epoll_wait(epfd, events, EPOLL_MAX_EVENTS, timeout);
|
62
62
|
if (n < 0) {
|
@@ -79,12 +79,10 @@ VALUE method_scheduler_wait(VALUE self) {
|
|
79
79
|
result = rb_ary_new2(2);
|
80
80
|
rb_ary_store(result, 0, readables);
|
81
81
|
rb_ary_store(result, 1, writables);
|
82
|
-
|
83
|
-
xfree(events);
|
84
82
|
return result;
|
85
83
|
}
|
86
84
|
|
87
|
-
VALUE
|
85
|
+
VALUE method_scheduler_epoll_backend(VALUE klass) {
|
88
86
|
return rb_str_new_cstr("epoll");
|
89
87
|
}
|
90
88
|
#endif
|
data/ext/evt/evt.c
CHANGED
@@ -5,30 +5,41 @@
|
|
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_deregister", method_scheduler_epoll_deregister, 1);
|
28
|
+
rb_define_method(Bundled, "epoll_wait", method_scheduler_epoll_wait, 0);
|
21
29
|
#endif
|
30
|
+
#if HAVE_SYS_EVENT_H
|
31
|
+
rb_define_singleton_method(Bundled, "kqueue_backend", method_scheduler_kqueue_backend, 0);
|
32
|
+
rb_define_method(Bundled, "kqueue_init_selector", method_scheduler_kqueue_init, 0);
|
33
|
+
rb_define_method(Bundled, "kqueue_register", method_scheduler_kqueue_register, 2);
|
34
|
+
rb_define_method(Bundled, "kqueue_wait", method_scheduler_kqueue_wait, 0);
|
35
|
+
#endif
|
36
|
+
rb_define_singleton_method(Bundled, "select_backend", method_scheduler_select_backend, 0);
|
37
|
+
rb_define_method(Bundled, "select_wait", method_scheduler_select_wait, 0);
|
22
38
|
}
|
23
39
|
|
24
|
-
#
|
25
|
-
|
26
|
-
#
|
27
|
-
|
28
|
-
#
|
29
|
-
#include "kqueue.h"
|
30
|
-
#elif HAVE_WINDOWS_H
|
31
|
-
#include "select.h"
|
32
|
-
// #include "iocp.h"
|
33
|
-
#endif
|
40
|
+
#include "uring.h"
|
41
|
+
#include "epoll.h"
|
42
|
+
#include "kqueue.h"
|
43
|
+
// #include "iocp.h"
|
44
|
+
#include "select.h"
|
34
45
|
#endif
|
data/ext/evt/evt.h
CHANGED
@@ -4,29 +4,22 @@
|
|
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_deregister(VALUE self, VALUE io);
|
17
|
+
VALUE method_scheduler_uring_wait(VALUE self);
|
18
|
+
VALUE method_scheduler_uring_backend(VALUE klass);
|
19
|
+
VALUE method_scheduler_uring_io_read(VALUE self, VALUE io, VALUE buffer, VALUE offset, VALUE length);
|
20
|
+
VALUE method_scheduler_uring_io_write(VALUE self, VALUE io, VALUE buffer, VALUE offset, VALUE length);
|
29
21
|
|
22
|
+
#include <liburing.h>
|
30
23
|
#define URING_ENTRIES 64
|
31
24
|
#define URING_MAX_EVENTS 64
|
32
25
|
|
@@ -49,13 +42,26 @@ VALUE method_scheduler_io_write(VALUE io, VALUE buffer, VALUE offset, VALUE leng
|
|
49
42
|
.data = NULL,
|
50
43
|
.flags = RUBY_TYPED_FREE_IMMEDIATELY,
|
51
44
|
};
|
52
|
-
#
|
45
|
+
#endif
|
46
|
+
#if HAVE_SYS_EPOLL_H
|
47
|
+
VALUE method_scheduler_epoll_init(VALUE self);
|
48
|
+
VALUE method_scheduler_epoll_register(VALUE self, VALUE io, VALUE interest);
|
49
|
+
VALUE method_scheduler_epoll_deregister(VALUE self, VALUE io);
|
50
|
+
VALUE method_scheduler_epoll_wait(VALUE self);
|
51
|
+
VALUE method_scheduler_epoll_backend(VALUE klass);
|
53
52
|
#include <sys/epoll.h>
|
54
53
|
#define EPOLL_MAX_EVENTS 64
|
55
|
-
#
|
54
|
+
#endif
|
55
|
+
#if HAVE_SYS_EVENT_H
|
56
|
+
VALUE method_scheduler_kqueue_init(VALUE self);
|
57
|
+
VALUE method_scheduler_kqueue_register(VALUE self, VALUE io, VALUE interest);
|
58
|
+
VALUE method_scheduler_kqueue_deregister(VALUE self, VALUE io);
|
59
|
+
VALUE method_scheduler_kqueue_wait(VALUE self);
|
60
|
+
VALUE method_scheduler_kqueue_backend(VALUE klass);
|
56
61
|
#include <sys/event.h>
|
57
62
|
#define KQUEUE_MAX_EVENTS 64
|
58
|
-
#
|
63
|
+
#endif
|
64
|
+
#if HAVE_WINDOWS_H
|
59
65
|
// #include <Windows.h>
|
60
66
|
// #define IOCP_MAX_EVENTS 64
|
61
67
|
|
@@ -79,4 +85,6 @@ VALUE method_scheduler_io_write(VALUE io, VALUE buffer, VALUE offset, VALUE leng
|
|
79
85
|
// .flags = RUBY_TYPED_FREE_IMMEDIATELY,
|
80
86
|
// };
|
81
87
|
#endif
|
88
|
+
VALUE method_scheduler_select_wait(VALUE self);
|
89
|
+
VALUE method_scheduler_select_backend(VALUE klass);
|
82
90
|
#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 {
|
@@ -70,7 +57,6 @@ VALUE method_scheduler_wait(VALUE self) {
|
|
70
57
|
// TODO: Check if n >= 0
|
71
58
|
for (i = 0; i < n; i++) {
|
72
59
|
event_flags = events[i].filter;
|
73
|
-
printf("event flags: %d\n", event_flags);
|
74
60
|
if (event_flags & EVFILT_READ) {
|
75
61
|
obj_io = (VALUE) events[i].udata;
|
76
62
|
rb_funcall(readables, id_push, 1, obj_io);
|
@@ -86,11 +72,10 @@ VALUE method_scheduler_wait(VALUE self) {
|
|
86
72
|
rb_ary_store(result, 0, readables);
|
87
73
|
rb_ary_store(result, 1, writables);
|
88
74
|
|
89
|
-
xfree(events);
|
90
75
|
return result;
|
91
76
|
}
|
92
77
|
|
93
|
-
VALUE
|
78
|
+
VALUE method_scheduler_kqueue_backend(VALUE klass) {
|
94
79
|
return rb_str_new_cstr("kqueue");
|
95
80
|
}
|
96
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;
|
@@ -90,27 +85,27 @@ VALUE method_scheduler_wait(VALUE self) {
|
|
90
85
|
data = (struct uring_data*) io_uring_cqe_get_data(cqes[i]);
|
91
86
|
poll_events = data->poll_mask;
|
92
87
|
obj_io = data->io;
|
93
|
-
if (
|
88
|
+
if (data->is_poll) {
|
89
|
+
if (poll_events & POLL_IN) {
|
90
|
+
rb_funcall(readables, id_push, 1, obj_io);
|
91
|
+
}
|
92
|
+
|
93
|
+
if (poll_events & POLL_OUT) {
|
94
|
+
rb_funcall(writables, id_push, 1, obj_io);
|
95
|
+
}
|
96
|
+
} else {
|
94
97
|
rb_funcall(iovs, id_push, 1, obj_io);
|
95
98
|
}
|
96
|
-
|
97
|
-
if (poll_events & POLL_IN) {
|
98
|
-
rb_funcall(readables, id_push, 1, obj_io);
|
99
|
-
}
|
100
|
-
|
101
|
-
if (poll_events & POLL_OUT) {
|
102
|
-
rb_funcall(writables, id_push, 1, obj_io);
|
103
|
-
}
|
104
|
-
xfree(data);
|
99
|
+
io_uring_cqe_seen(ring, cqes[i]);
|
105
100
|
}
|
106
101
|
|
107
102
|
if (ret == 0) {
|
108
103
|
if (next_timeout != Qnil && NUM2INT(next_timeout) != -1) {
|
109
104
|
// sleep
|
110
105
|
time = next_timeout / 1000;
|
111
|
-
rb_funcall(rb_mKernel, id_sleep, 1,
|
106
|
+
rb_funcall(rb_mKernel, id_sleep, 1, rb_float_new(time));
|
112
107
|
} else {
|
113
|
-
rb_funcall(rb_mKernel, id_sleep, 1,
|
108
|
+
rb_funcall(rb_mKernel, id_sleep, 1, rb_float_new(0.001)); // To avoid infinite loop
|
114
109
|
}
|
115
110
|
}
|
116
111
|
|
@@ -122,7 +117,7 @@ VALUE method_scheduler_wait(VALUE self) {
|
|
122
117
|
return result;
|
123
118
|
}
|
124
119
|
|
125
|
-
VALUE
|
120
|
+
VALUE method_scheduler_uring_io_read(VALUE self, VALUE io, VALUE buffer, VALUE offset, VALUE length) {
|
126
121
|
struct io_uring* ring;
|
127
122
|
struct uring_data *data;
|
128
123
|
char* read_buffer;
|
@@ -152,7 +147,6 @@ VALUE method_scheduler_io_read(VALUE self, VALUE io, VALUE buffer, VALUE offset,
|
|
152
147
|
io_uring_submit(ring);
|
153
148
|
|
154
149
|
VALUE result = rb_str_new(read_buffer, strlen(read_buffer));
|
155
|
-
xfree(read_buffer);
|
156
150
|
if (buffer != Qnil) {
|
157
151
|
rb_str_append(buffer, result);
|
158
152
|
}
|
@@ -161,7 +155,7 @@ VALUE method_scheduler_io_read(VALUE self, VALUE io, VALUE buffer, VALUE offset,
|
|
161
155
|
return result;
|
162
156
|
}
|
163
157
|
|
164
|
-
VALUE
|
158
|
+
VALUE method_scheduler_uring_io_write(VALUE self, VALUE io, VALUE buffer, VALUE offset, VALUE length) {
|
165
159
|
struct io_uring* ring;
|
166
160
|
struct uring_data *data;
|
167
161
|
char* write_buffer;
|
@@ -193,7 +187,7 @@ VALUE method_scheduler_io_write(VALUE self, VALUE io, VALUE buffer, VALUE offset
|
|
193
187
|
return length;
|
194
188
|
}
|
195
189
|
|
196
|
-
VALUE
|
190
|
+
VALUE method_scheduler_uring_backend(VALUE klass) {
|
197
191
|
return rb_str_new_cstr("liburing");
|
198
192
|
}
|
199
193
|
|
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,177 @@
|
|
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 |io|
|
53
|
+
fiber = @iovs.delete(io)
|
54
|
+
fiber&.resume
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
collect
|
59
|
+
|
60
|
+
if @waiting.any?
|
61
|
+
time = current_time
|
62
|
+
waiting, @waiting = @waiting, {}
|
63
|
+
|
64
|
+
waiting.each do |fiber, timeout|
|
65
|
+
if timeout <= time
|
66
|
+
fiber.resume
|
67
|
+
else
|
68
|
+
@waiting[fiber] = timeout
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
if @ready.any?
|
74
|
+
ready = nil
|
75
|
+
|
76
|
+
@lock.synchronize do
|
77
|
+
ready, @ready = @ready, []
|
78
|
+
end
|
79
|
+
|
80
|
+
ready.each do |fiber|
|
81
|
+
fiber.resume
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def current_time
|
88
|
+
Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
89
|
+
end
|
90
|
+
|
91
|
+
# Wait for the given file descriptor to match the specified events within
|
92
|
+
# the specified timeout.
|
93
|
+
# @parameter event [Integer] A bit mask of `IO::READABLE`,
|
94
|
+
# `IO::WRITABLE` and `IO::PRIORITY`.
|
95
|
+
# @parameter timeout [Numeric] The amount of time to wait for the event in seconds.
|
96
|
+
# @returns [Integer] The subset of events that are ready.
|
97
|
+
def io_wait(io, events, duration)
|
98
|
+
# TODO: IO::PRIORITY
|
99
|
+
@readable[io] = Fiber.current unless (events & IO::READABLE).zero?
|
100
|
+
@writable[io] = Fiber.current unless (events & IO::WRITABLE).zero?
|
101
|
+
self.register(io, events)
|
102
|
+
Fiber.yield
|
103
|
+
self.deregister(io)
|
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
|
154
|
+
if @collect_counter < COLLECT_COUNTER_MAX
|
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
|
+
end
|
169
|
+
|
170
|
+
# Intercept the creation of a non-blocking fiber.
|
171
|
+
# @returns [Fiber]
|
172
|
+
def fiber(&block)
|
173
|
+
fiber = Fiber.new(blocking: false, &block)
|
174
|
+
fiber.resume
|
175
|
+
fiber
|
176
|
+
end
|
177
|
+
end
|
@@ -0,0 +1,27 @@
|
|
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 deregister(io)
|
21
|
+
epoll_deregister(io)
|
22
|
+
end
|
23
|
+
|
24
|
+
def wait
|
25
|
+
epoll_wait
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,33 @@
|
|
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 deregister(io)
|
19
|
+
# Placeholder
|
20
|
+
end
|
21
|
+
|
22
|
+
def io_read(io, buffer, offset, length)
|
23
|
+
# Placeholder
|
24
|
+
end
|
25
|
+
|
26
|
+
def io_write(io, buffer, offset, length)
|
27
|
+
# Placeholder
|
28
|
+
end
|
29
|
+
|
30
|
+
def wait
|
31
|
+
# Placeholder
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,27 @@
|
|
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 deregister(io)
|
21
|
+
# Kqueue running under one-shot mode, no need to deregister
|
22
|
+
end
|
23
|
+
|
24
|
+
def wait
|
25
|
+
kqueue_wait
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,27 @@
|
|
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 deregister(io)
|
21
|
+
# Select is stateless
|
22
|
+
end
|
23
|
+
|
24
|
+
def wait
|
25
|
+
select_wait
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,35 @@
|
|
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, register)
|
18
|
+
end
|
19
|
+
|
20
|
+
def deregister(io)
|
21
|
+
# io_uring running under one-shot mode, no need to deregister
|
22
|
+
end
|
23
|
+
|
24
|
+
def io_read(io, buffer, offset, length)
|
25
|
+
uring_io_read(io, buffer, offset, length)
|
26
|
+
end
|
27
|
+
|
28
|
+
def io_write(io, buffer, offset, length)
|
29
|
+
uring_io_write(io, buffer, offset, length)
|
30
|
+
end
|
31
|
+
|
32
|
+
def wait
|
33
|
+
uring_wait
|
34
|
+
end
|
35
|
+
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.
|
4
|
+
version: 0.3.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-12-
|
11
|
+
date: 2020-12-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rake-compiler
|
@@ -24,6 +24,20 @@ 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
|
27
41
|
description: A low-level Event Handler designed for Ruby 3 Scheduler for better performance
|
28
42
|
email:
|
29
43
|
- dsh0416@gmail.com
|
@@ -32,6 +46,7 @@ extensions:
|
|
32
46
|
- ext/evt/extconf.rb
|
33
47
|
extra_rdoc_files: []
|
34
48
|
files:
|
49
|
+
- ".github/workflows/build.yml"
|
35
50
|
- ".github/workflows/test.yml"
|
36
51
|
- ".gitignore"
|
37
52
|
- CODE_OF_CONDUCT.md
|
@@ -49,6 +64,12 @@ files:
|
|
49
64
|
- ext/evt/select.h
|
50
65
|
- ext/evt/uring.h
|
51
66
|
- lib/evt.rb
|
67
|
+
- lib/evt/backends/bundled.rb
|
68
|
+
- lib/evt/backends/epoll.rb
|
69
|
+
- lib/evt/backends/iocp.rb
|
70
|
+
- lib/evt/backends/kqueue.rb
|
71
|
+
- lib/evt/backends/select.rb
|
72
|
+
- lib/evt/backends/uring.rb
|
52
73
|
- lib/evt/scheduler.rb
|
53
74
|
- lib/evt/version.rb
|
54
75
|
homepage: https://github.com/dsh0416/evt
|
@@ -65,7 +86,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
65
86
|
requirements:
|
66
87
|
- - ">="
|
67
88
|
- !ruby/object:Gem::Version
|
68
|
-
version:
|
89
|
+
version: 3.0.0.rc1
|
69
90
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
91
|
requirements:
|
71
92
|
- - ">="
|
@@ -75,5 +96,5 @@ requirements: []
|
|
75
96
|
rubygems_version: 3.2.2
|
76
97
|
signing_key:
|
77
98
|
specification_version: 4
|
78
|
-
summary:
|
99
|
+
summary: The Event Library that designed for Ruby 3.0 Fiber Scheluer.
|
79
100
|
test_files: []
|