evt 0.1.4 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 666a665e9a03c15a364613ceac651e6e71becfa51b780b81d6cb56652ad6489b
4
- data.tar.gz: 0c21649125a1d4f6c4002b8d8da2e235d98763cde42bc8ae2f0aaac3353a98f5
3
+ metadata.gz: 792dc614f6216687c600f101b8fac515a71fc0249c4ca6af3f880cb71ee17eff
4
+ data.tar.gz: c64b75645bd2adb22a3947c7b239c9cde05a8fb1ac42a6e08f2cfe653bc20a84
5
5
  SHA512:
6
- metadata.gz: 3b6fe6c7ee071b3da47e4ba66d581d18a1e1e3df3a34e072588cdd1d2b1a0816d1b85ebf6fe89190fefee6c0bc2d8d8df9b30031625febbe02062bb6db366c58
7
- data.tar.gz: d42b01b897e8d673060a5ed5ff5ffee6156cb63b3ad2e7e777044fa674e1ac59e53fc60167231745512cbfe1b31c4fc51b74d2a5331876ea63d8984fbe3311f8
6
+ metadata.gz: '016090b4ba576eb10896099d3879445d3c65d3b8f6d9475731786639282c5e7f37ba1029e6d1201b165f9bfbf20193fb13017b5ea6af90792b60989f33691899'
7
+ data.tar.gz: 83d15ffca0e28b5c96f241197bbd97eabe1a935e01f71c6e4bb94a2eb9ad413148b2318d1b55a4df4b11444e989508b32cd7da9f1b19b839512d13b92b7034b9
@@ -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
@@ -9,3 +9,4 @@
9
9
  /*.gem
10
10
  /lib/*.bundle
11
11
  /lib/*.so
12
+ Gemfile.lock
data/Gemfile CHANGED
@@ -3,5 +3,5 @@ source "https://rubygems.org"
3
3
  # Specify your gem's dependencies in evt.gemspec
4
4
  gemspec
5
5
 
6
- gem "rake", "~> 12.0"
6
+ gem "rake", "~> 13.0"
7
7
  gem "minitest", "~> 5.0"
data/README.md CHANGED
@@ -1,10 +1,30 @@
1
- # evt
1
+ # Evt
2
2
 
3
- [![Build Status](https://travis-ci.org/dsh0416/evt.svg?branch=master)](https://travis-ci.org/dsh0416/evt)
3
+ The Event Library that designed for Ruby 3.0.
4
4
 
5
- A Handcrafted Low-Level Event Handler designed as Ruby 3 Scheduler.
5
+ **This gem is still under development, APIs and features are not stable. Advices and PRs are highly welcome.**
6
6
 
7
- Supports `epoll`, `kqueue`, IOCP (WIP), and Ruby `select` fallback.
7
+ ![CI Tests](https://github.com/dsh0416/evt/workflows/CI%20Tests/badge.svg)
8
+
9
+ ## Features
10
+
11
+
12
+
13
+ ### IO Backend Support
14
+
15
+ | | Linux | Windows | macOS | FreeBSD |
16
+ | --------------- | ----------- | ------------| ----------- | ----------- |
17
+ | io_uring | ✅ (See 1) | ❌ | ❌ | ❌ |
18
+ | epoll | ✅ (See 2) | ❌ | ❌ | ❌ |
19
+ | kqueue | ❌ | ❌ | ✅ (⚠️ See 5) | ✅ |
20
+ | IOCP | ❌ | ❌ (⚠️See 3) | ❌ | ❌ |
21
+ | Ruby (`select`) | ✅ Fallback | ✅ (⚠️See 4) | ✅ Fallback | ✅ Fallback |
22
+
23
+ 1. when liburing is installed
24
+ 2. when kernel version >= 2.6.8
25
+ 3. WOULD NOT WORK until `FILE_FLAG_OVERLAPPED` is included in I/O initialization process.
26
+ 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
+ 5. `kqueue` performance in Darwin is very poor. **MAY BE DISABLED IN THE FUTURE.**
8
28
 
9
29
  ## Install
10
30
 
@@ -18,17 +38,34 @@ gem install evt
18
38
  require 'evt'
19
39
 
20
40
  rd, wr = IO.pipe
21
- Thread.current.scheduler = Evt::Scheduler.new
41
+ scheduler = Evt::Scheduler.new
22
42
 
23
- hit = 0
24
- fiber = Fiber.new do
25
- scheduler.wait_readable(rd)
26
- hit += 1
43
+ Fiber.set_scheduler scheduler
44
+
45
+ Fiber.schedule do
46
+ message = rd.read(20)
47
+ puts message
48
+ rd.close
49
+ end
50
+
51
+ Fiber.schedule do
52
+ wr.write("Hello World")
53
+ wr.close
27
54
  end
28
55
 
29
- wr.write('Hello World')
30
- fiber.resume
31
- Thread.current.scheduler.run
56
+ scheduler.run
32
57
 
33
- puts hit # => 1
58
+ # "Hello World"
34
59
  ```
60
+
61
+ ## Roadmap
62
+
63
+ - [x] Support epoll/kqueue/select
64
+ - [x] Upgrade to the latest Scheduler API
65
+ - [x] Support io_uring
66
+ - [x] Support iov features of io_uring
67
+ - [x] Support IOCP (**NOT ENABLED YET**)
68
+ - [x] Setup tests with Ruby 3
69
+ - [ ] Support IOCP with iov features
70
+ - [ ] Setup more tests for production purpose
71
+ - [ ] Documentation for usages
@@ -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.7.1'
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']
@@ -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
@@ -1,218 +1,34 @@
1
+ #ifndef EVT_C
2
+ #define EVT_C
3
+
1
4
  #include "evt.h"
2
5
 
3
6
  void Init_evt_ext()
4
7
  {
5
8
  Evt = rb_define_module("Evt");
6
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);
7
12
  rb_define_singleton_method(Scheduler, "backend", method_scheduler_backend, 0);
8
13
  rb_define_method(Scheduler, "init_selector", method_scheduler_init, 0);
9
14
  rb_define_method(Scheduler, "register", method_scheduler_register, 2);
10
15
  rb_define_method(Scheduler, "deregister", method_scheduler_deregister, 1);
11
16
  rb_define_method(Scheduler, "wait", method_scheduler_wait, 0);
12
- }
13
-
14
-
15
- #if defined(__linux__) // TODO: Do more checks for using epoll
16
- #include <sys/epoll.h>
17
- #define EPOLL_MAX_EVENTS 64
18
-
19
- VALUE method_scheduler_init(VALUE self) {
20
- rb_iv_set(self, "@epfd", INT2NUM(epoll_create(1))); // Size of epoll is ignored after Linux 2.6.8.
21
- return Qnil;
22
- }
23
-
24
- VALUE method_scheduler_register(VALUE self, VALUE io, VALUE interest) {
25
- struct epoll_event event;
26
- ID id_fileno = rb_intern("fileno");
27
- int epfd = NUM2INT(rb_iv_get(self, "@epfd"));
28
- int fd = NUM2INT(rb_funcall(io, id_fileno, 0));
29
- int ruby_interest = NUM2INT(interest);
30
- int readable = NUM2INT(rb_const_get(rb_cIO, rb_intern("WAIT_READABLE")));
31
- int writable = NUM2INT(rb_const_get(rb_cIO, rb_intern("WAIT_WRITABLE")));
32
-
33
- if (ruby_interest & readable) {
34
- event.events |= EPOLLIN;
35
- } else if (ruby_interest & writable) {
36
- event.events |= EPOLLOUT;
37
- }
38
- event.data.ptr = (void*) io;
39
-
40
- epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event);
41
- return Qnil;
42
- }
43
-
44
- VALUE method_scheduler_deregister(VALUE self, VALUE io) {
45
- ID id_fileno = rb_intern("fileno");
46
- int epfd = NUM2INT(rb_iv_get(self, "@epfd"));
47
- int fd = NUM2INT(rb_funcall(io, id_fileno, 0));
48
- epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL); // Require Linux 2.6.9 for NULL event.
49
- return Qnil;
50
- }
51
-
52
- VALUE method_scheduler_wait(VALUE self) {
53
- int n, epfd, i, event_flag, timeout;
54
- VALUE next_timeout, obj_io, readables, writables, result;
55
- ID id_next_timeout = rb_intern("next_timeout");
56
- ID id_push = rb_intern("push");
57
-
58
- epfd = NUM2INT(rb_iv_get(self, "@epfd"));
59
- next_timeout = rb_funcall(self, id_next_timeout, 0);
60
- readables = rb_ary_new();
61
- writables = rb_ary_new();
62
-
63
- if (next_timeout == Qnil) {
64
- timeout = -1;
65
- } else {
66
- timeout = NUM2INT(next_timeout);
67
- }
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, timeout);
72
- // TODO: 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
-
93
- VALUE method_scheduler_backend() {
94
- return rb_str_new_cstr("epoll");
95
- }
96
- #elif defined(__FreeBSD__) || defined(__NetBSD__) || defined(__APPLE__)
97
- #include <sys/event.h>
98
- #define KQUEUE_MAX_EVENTS 64
99
-
100
- VALUE method_scheduler_init(VALUE self) {
101
- rb_iv_set(self, "@kq", INT2NUM(kqueue()));
102
- return Qnil;
103
- }
104
-
105
- VALUE method_scheduler_register(VALUE self, VALUE io, VALUE interest) {
106
- struct kevent event;
107
- u_short event_flags = 0;
108
- ID id_fileno = rb_intern("fileno");
109
- int kq = NUM2INT(rb_iv_get(self, "@kq"));
110
- int fd = NUM2INT(rb_funcall(io, id_fileno, 0));
111
- int ruby_interest = NUM2INT(interest);
112
- int readable = NUM2INT(rb_const_get(rb_cIO, rb_intern("WAIT_READABLE")));
113
- int writable = NUM2INT(rb_const_get(rb_cIO, rb_intern("WAIT_WRITABLE")));
114
-
115
- if (ruby_interest & readable) {
116
- event_flags |= EVFILT_READ;
117
- } else if (ruby_interest & writable) {
118
- event_flags |= EVFILT_WRITE;
119
- }
120
-
121
- EV_SET(&event, fd, event_flags, EV_ADD|EV_ENABLE, 0, 0, (void*) io);
122
- kevent(kq, &event, 1, NULL, 0, NULL); // TODO: Check the return value
123
- return Qnil;
124
- }
125
17
 
126
- VALUE method_scheduler_deregister(VALUE self, VALUE io) {
127
- struct kevent event;
128
- ID id_fileno = rb_intern("fileno");
129
- int kq = NUM2INT(rb_iv_get(self, "@kq"));
130
- int fd = NUM2INT(rb_funcall(io, id_fileno, 0));
131
- EV_SET(&event, fd, 0, EV_DELETE, 0, 0, NULL);
132
- kevent(kq, &event, 1, NULL, 0, NULL); // TODO: Check the return value
133
- return Qnil;
134
- }
135
-
136
- VALUE method_scheduler_wait(VALUE self) {
137
- int n, kq, i;
138
- u_short event_flags = 0;
139
-
140
- struct kevent* events; // Event Triggered
141
- struct timespec timeout;
142
- VALUE next_timeout, obj_io, readables, writables, result;
143
- ID id_next_timeout = rb_intern("next_timeout");
144
- ID id_push = rb_intern("push");
145
-
146
- kq = NUM2INT(rb_iv_get(self, "@kq"));
147
- next_timeout = rb_funcall(self, id_next_timeout, 0);
148
- readables = rb_ary_new();
149
- writables = rb_ary_new();
150
-
151
- events = (struct kevent*) xmalloc(sizeof(struct kevent) * KQUEUE_MAX_EVENTS);
152
-
153
- if (next_timeout == Qnil || NUM2INT(next_timeout) == -1) {
154
- n = kevent(kq, NULL, 0, events, KQUEUE_MAX_EVENTS, NULL);
155
- } else {
156
- timeout.tv_sec = next_timeout / 1000;
157
- timeout.tv_nsec = next_timeout % 1000 * 1000 * 1000;
158
- n = kevent(kq, NULL, 0, events, KQUEUE_MAX_EVENTS, &timeout);
159
- }
160
-
161
- // TODO: Check if n >= 0
162
- for (i = 0; i < n; i++) {
163
- event_flags = events[i].filter;
164
- if (event_flags & EVFILT_READ) {
165
- obj_io = (VALUE) events[i].udata;
166
- rb_funcall(readables, id_push, 1, obj_io);
167
- } else if (event_flags & EVFILT_WRITE) {
168
- obj_io = (VALUE) events[i].udata;
169
- rb_funcall(writables, id_push, 1, obj_io);
170
- }
171
- }
172
-
173
- result = rb_ary_new2(2);
174
- rb_ary_store(result, 0, readables);
175
- rb_ary_store(result, 1, writables);
176
-
177
- xfree(events);
178
- return result;
179
- }
180
-
181
- VALUE method_scheduler_backend() {
182
- return rb_str_new_cstr("kqueue");
183
- }
184
- #else
185
- // Fallback to IO.select
186
- VALUE method_scheduler_init(VALUE self) {
187
- return Qnil;
188
- }
189
-
190
- VALUE method_scheduler_register(VALUE self, VALUE io, VALUE interest) {
191
- return Qnil;
192
- }
193
-
194
- VALUE method_scheduler_deregister(VALUE self, VALUE io) {
195
- return Qnil;
196
- }
197
-
198
- VALUE method_scheduler_wait(VALUE self) {
199
- // return IO.select(@readable.keys, @writable.keys, [], next_timeout)
200
- VALUE readable, writable, readable_keys, writable_keys, next_timeout;
201
- ID id_select = rb_intern("select");
202
- ID id_keys = rb_intern("keys");
203
- ID id_next_timeout = rb_intern("next_timeout");
204
-
205
- readable = rb_iv_get(self, "@readable");
206
- writable = rb_iv_get(self, "@writable");
207
-
208
- readable_keys = rb_funcall(readable, id_keys, 0);
209
- writable_keys = rb_funcall(writable, id_keys, 0);
210
- next_timeout = rb_funcall(self, id_next_timeout, 0);
211
-
212
- return rb_funcall(rb_cIO, id_select, 4, readable_keys, writable_keys, rb_ary_new(), next_timeout);
213
- }
214
-
215
- VALUE method_scheduler_backend() {
216
- return rb_str_new_cstr("ruby");
217
- }
218
- #endif
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