nyara 0.0.1.pre.3 → 0.0.1.pre.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/ext/event.c +39 -2
- data/ext/inc/epoll.h +5 -4
- data/ext/inc/kqueue.h +9 -5
- data/ext/inc/version.inc +1 -1
- data/ext/nyara.c +2 -0
- data/ext/request.c +2 -0
- data/ext/request.h +2 -0
- data/lib/nyara/controller.rb +11 -1
- data/lib/nyara/nyara.rb +1 -1
- data/nyara.gemspec +1 -1
- data/rakefile +3 -3
- data/readme.md +17 -89
- data/spec/performance_spec.rb +4 -1
- data/{hello.rb → tools/hello.rb} +1 -1
- metadata +4 -4
- /data/lib/nyara/{patch_tcp_socket.rb → patches/tcp_socket.rb} +0 -0
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7dc7b625bcf917f1fc957ca42953345df87faa40
|
4
|
+
data.tar.gz: f7c7669fed83475cb1626dec024e30eed7cf76ee
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e76227e8c9e4f4dc292b32e1cc72e11525e91a5b6f54784d4f7f6a63fba4b126f5fea81070cf793723908d82182faac1e3a47d3835903a99426fb82cb85e93b1
|
7
|
+
data.tar.gz: 592183cbad4bb90c3090a752832a2e7d26138e07b38bf2e7cb94544046619c3f6f620c566e21119f80f8c99c0f360c0be49f0303b51b19b6ac25a0c4a564c407
|
data/ext/event.c
CHANGED
@@ -21,6 +21,7 @@ static void loop_body(int fd, int etype);
|
|
21
21
|
static int qfd;
|
22
22
|
|
23
23
|
#define MAX_RECEIVE_DATA 65536
|
24
|
+
// * 4
|
24
25
|
static char received_data[MAX_RECEIVE_DATA];
|
25
26
|
extern http_parser_settings nyara_request_parse_settings;
|
26
27
|
|
@@ -36,6 +37,7 @@ static ID id_not_found;
|
|
36
37
|
static VALUE sym_term_close;
|
37
38
|
static VALUE sym_writing;
|
38
39
|
static VALUE sym_reading;
|
40
|
+
static VALUE sym_sleep;
|
39
41
|
static Request* curr_request;
|
40
42
|
|
41
43
|
static void _set_nonblock(int fd) {
|
@@ -59,6 +61,9 @@ static VALUE _fiber_func(VALUE _, VALUE args) {
|
|
59
61
|
static void _handle_request(VALUE request) {
|
60
62
|
Request* p;
|
61
63
|
Data_Get_Struct(request, Request, p);
|
64
|
+
if (p->sleeping) {
|
65
|
+
return;
|
66
|
+
}
|
62
67
|
curr_request = p;
|
63
68
|
|
64
69
|
// read and parse data
|
@@ -115,8 +120,8 @@ static void _handle_request(VALUE request) {
|
|
115
120
|
// do nothing
|
116
121
|
} else if (state == sym_reading) {
|
117
122
|
// do nothing
|
118
|
-
} else {
|
119
|
-
//
|
123
|
+
} else if (state == sym_sleep) {
|
124
|
+
// do nothing
|
120
125
|
}
|
121
126
|
}
|
122
127
|
|
@@ -178,6 +183,34 @@ static VALUE ext_run_queue(VALUE _, VALUE v_fd) {
|
|
178
183
|
return Qnil;
|
179
184
|
}
|
180
185
|
|
186
|
+
static VALUE ext_request_sleep(VALUE _, VALUE request) {
|
187
|
+
Request* p;
|
188
|
+
Data_Get_Struct(request, Request, p);
|
189
|
+
|
190
|
+
VALUE* v_fds = RARRAY_PTR(p->watched_fds);
|
191
|
+
long v_fds_len = RARRAY_LEN(p->watched_fds);
|
192
|
+
for (long i = 0; i < v_fds_len; i++) {
|
193
|
+
DEL_E(FIX2INT(v_fds[i]));
|
194
|
+
}
|
195
|
+
DEL_E(p->fd);
|
196
|
+
p->sleeping = true;
|
197
|
+
return Qnil;
|
198
|
+
}
|
199
|
+
|
200
|
+
static VALUE ext_request_wakeup(VALUE _, VALUE request) {
|
201
|
+
// NOTE should not use current_request
|
202
|
+
Request* p;
|
203
|
+
Data_Get_Struct(request, Request, p);
|
204
|
+
|
205
|
+
VALUE* v_fds = RARRAY_PTR(p->watched_fds);
|
206
|
+
long v_fds_len = RARRAY_LEN(p->watched_fds);
|
207
|
+
for (long i = 0; i < v_fds_len; i++) {
|
208
|
+
ADD_E(FIX2INT(v_fds[i]), ETYPE_CONNECT);
|
209
|
+
}
|
210
|
+
ADD_E(p->fd, ETYPE_HANDLE_REQUEST);
|
211
|
+
return Qnil;
|
212
|
+
}
|
213
|
+
|
181
214
|
static VALUE ext_set_nonblock(VALUE _, VALUE v_fd) {
|
182
215
|
int fd = FIX2INT(v_fd);
|
183
216
|
_set_nonblock(fd);
|
@@ -273,10 +306,14 @@ void Init_event(VALUE ext) {
|
|
273
306
|
sym_term_close = ID2SYM(rb_intern("term_close"));
|
274
307
|
sym_writing = ID2SYM(rb_intern("writing"));
|
275
308
|
sym_reading = ID2SYM(rb_intern("reading"));
|
309
|
+
sym_sleep = ID2SYM(rb_intern("sleep"));
|
276
310
|
|
277
311
|
rb_define_singleton_method(ext, "init_queue", ext_init_queue, 0);
|
278
312
|
rb_define_singleton_method(ext, "run_queue", ext_run_queue, 1);
|
279
313
|
|
314
|
+
rb_define_singleton_method(ext, "request_sleep", ext_request_sleep, 0);
|
315
|
+
rb_define_singleton_method(ext, "request_wakeup", ext_request_wakeup, 0);
|
316
|
+
|
280
317
|
// fd operations
|
281
318
|
rb_define_singleton_method(ext, "set_nonblock", ext_set_nonblock, 1);
|
282
319
|
rb_define_singleton_method(ext, "fd_watch", ext_fd_watch, 1);
|
data/ext/inc/epoll.h
CHANGED
@@ -8,6 +8,9 @@ static struct epoll_event qevents[MAX_E];
|
|
8
8
|
|
9
9
|
static void ADD_E(int fd, uint64_t etype) {
|
10
10
|
struct epoll_event e;
|
11
|
+
// not using edge trigger flag EPOLLET
|
12
|
+
// because edge trigger only fire once when fd is readable/writable
|
13
|
+
// but the event may not be consumed in our handler
|
11
14
|
e.events = EPOLLIN | EPOLLOUT;
|
12
15
|
e.data.u64 = (etype << 32) | (uint64_t)fd;
|
13
16
|
|
@@ -20,8 +23,7 @@ static void ADD_E(int fd, uint64_t etype) {
|
|
20
23
|
# endif
|
21
24
|
}
|
22
25
|
|
23
|
-
// either epoll or kqueue removes the event watch from queue when fd closed
|
24
|
-
// seems this is not required in epoll?
|
26
|
+
// NOTE either epoll or kqueue removes the event watch from queue when fd closed
|
25
27
|
static void DEL_E(int fd) {
|
26
28
|
struct epoll_event e;
|
27
29
|
e.events = EPOLLIN | EPOLLOUT;
|
@@ -38,8 +40,7 @@ static void DEL_E(int fd) {
|
|
38
40
|
static void INIT_E() {
|
39
41
|
qfd = epoll_create(10); // size not important
|
40
42
|
if (qfd == -1) {
|
41
|
-
|
42
|
-
exit(-1);
|
43
|
+
rb_sys_fail("epoll_create(2)");
|
43
44
|
}
|
44
45
|
}
|
45
46
|
|
data/ext/inc/kqueue.h
CHANGED
@@ -16,8 +16,9 @@ static struct kevent qevents[MAX_E];
|
|
16
16
|
|
17
17
|
static void ADD_E(int fd, uint64_t etype) {
|
18
18
|
struct kevent e;
|
19
|
+
// without EV_CLEAR, it is level-triggered
|
20
|
+
// http://www.cs.helsinki.fi/linux/linux-kernel/2001-38/0547.html
|
19
21
|
EV_SET(&e, fd, EVFILT_READ | EVFILT_WRITE, EV_ADD, 0, 0, (void*)etype);
|
20
|
-
// todo timeout
|
21
22
|
# ifdef NDEBUG
|
22
23
|
kevent(qfd, &e, 1, NULL, 0, NULL);
|
23
24
|
# else
|
@@ -26,7 +27,7 @@ static void ADD_E(int fd, uint64_t etype) {
|
|
26
27
|
# endif
|
27
28
|
}
|
28
29
|
|
29
|
-
static void
|
30
|
+
static void DEL_E_WITH_FILTER(int fd, int filter) {
|
30
31
|
struct kevent e;
|
31
32
|
EV_SET(&e, fd, filter, EV_DELETE, 0, 0, NULL);
|
32
33
|
# ifdef NDEBUG
|
@@ -37,11 +38,14 @@ static void DEL_E(int fd, int filter) {
|
|
37
38
|
# endif
|
38
39
|
}
|
39
40
|
|
41
|
+
static void DEL_E(int fd) {
|
42
|
+
DEL_E_WITH_FILTER(fd, EVFILT_READ | EVFILT_WRITE);
|
43
|
+
}
|
44
|
+
|
40
45
|
static void INIT_E() {
|
41
46
|
qfd = kqueue();
|
42
47
|
if (qfd == -1) {
|
43
|
-
|
44
|
-
exit(-1);
|
48
|
+
rb_sys_fail("kqueue(2)");
|
45
49
|
}
|
46
50
|
}
|
47
51
|
|
@@ -61,7 +65,7 @@ static void LOOP_E() {
|
|
61
65
|
if (qevents[i].flags & EV_EOF) {
|
62
66
|
// EV_EOF is set if the read side of the socket is shutdown
|
63
67
|
// the event can keep flipping back to consume cpu if we don't remove it
|
64
|
-
|
68
|
+
DEL_E_WITH_FILTER(fd, qevents[i].filter);
|
65
69
|
}
|
66
70
|
if (qevents[i].filter & (EVFILT_READ | EVFILT_WRITE)) {
|
67
71
|
loop_body(fd, (int)qevents[i].udata);
|
data/ext/inc/version.inc
CHANGED
@@ -1 +1 @@
|
|
1
|
-
#define NYARA_VERSION "0.0.1
|
1
|
+
#define NYARA_VERSION "0.0.1"
|
data/ext/nyara.c
CHANGED
data/ext/request.c
CHANGED
data/ext/request.h
CHANGED
data/lib/nyara/controller.rb
CHANGED
@@ -314,7 +314,17 @@ module Nyara
|
|
314
314
|
|
315
315
|
# Resume action after +seconds+
|
316
316
|
def sleep seconds
|
317
|
-
|
317
|
+
seconds = seconds.to_f
|
318
|
+
raise ArgumentError, 'bad sleep seconds' if seconds < 0
|
319
|
+
|
320
|
+
# NOTE request_wake requires request as param, so this method can not be generalized to Fiber.sleep
|
321
|
+
|
322
|
+
Ext.request_sleep self # place sleep actions before wake
|
323
|
+
Thread.new do
|
324
|
+
sleep seconds
|
325
|
+
Ext.request_wakeup self
|
326
|
+
end
|
327
|
+
Fiber.yield :sleep # see event.c for the handler
|
318
328
|
end
|
319
329
|
|
320
330
|
# One shot render, and terminate the action.
|
data/lib/nyara/nyara.rb
CHANGED
data/nyara.gemspec
CHANGED
data/rakefile
CHANGED
@@ -11,7 +11,7 @@ desc "code generate"
|
|
11
11
|
task :gen => [status_file, version_file]
|
12
12
|
|
13
13
|
desc "generate #{status_file}"
|
14
|
-
file status_file do
|
14
|
+
file status_file => __FILE__ do
|
15
15
|
puts "generating: #{status_file}"
|
16
16
|
require "nokogiri"
|
17
17
|
require "open-uri"
|
@@ -28,13 +28,13 @@ file status_file do
|
|
28
28
|
end
|
29
29
|
|
30
30
|
desc "generate #{version_file}"
|
31
|
-
file version_file => 'nyara.gemspec' do
|
31
|
+
file version_file => ['nyara.gemspec', __FILE__] do
|
32
32
|
puts "generating: #{version_file}"
|
33
33
|
lines = File.readlines('nyara.gemspec')
|
34
34
|
version = nil
|
35
35
|
lines.each do |line|
|
36
36
|
if line =~ /s\.version =/
|
37
|
-
version = line[/\d+(\.\d+)*(\.pre)?/]
|
37
|
+
version = line[/\d+(\.\d+)*(\.pre\d+)?/]
|
38
38
|
break
|
39
39
|
end
|
40
40
|
end
|
data/readme.md
CHANGED
@@ -1,14 +1,20 @@
|
|
1
|
-
Not Yet Another Ruby Async web framework and server.
|
1
|
+
Not Yet Another Ruby Async web framework and server.
|
2
|
+
|
3
|
+
[![Build Status](https://travis-ci.org/luikore/nyara.png)](https://travis-ci.org/luikore/nyara)
|
2
4
|
|
3
5
|
- Evented IO while API remains synchrony
|
4
|
-
- Prefork production server
|
6
|
+
- Prefork production server, mixing a bit blocking operations won't block other users
|
5
7
|
- Sinatra-like http method and scanf-like http path and path helper
|
6
8
|
- Request format matcher with just `case ... when`
|
7
9
|
- Easy to stream the view with `Fiber.yield`
|
8
10
|
|
9
11
|
# Getting started
|
10
12
|
|
11
|
-
|
13
|
+
Requirement
|
14
|
+
|
15
|
+
- BSD/Linux/Mac OS X
|
16
|
+
- Ruby 2.0.0-p195 or higher (due to some syntax issues, doesn't work on 2.0.0-p0)
|
17
|
+
- GCC4.5+ or Clang
|
12
18
|
|
13
19
|
Install
|
14
20
|
|
@@ -31,95 +37,17 @@ And start server
|
|
31
37
|
ruby nyahaha.rb
|
32
38
|
```
|
33
39
|
|
34
|
-
#
|
35
|
-
|
36
|
-
After cloning
|
37
|
-
|
38
|
-
```bash
|
39
|
-
git submodule update --init
|
40
|
-
bundle
|
41
|
-
rake gen
|
42
|
-
rake gem
|
43
|
-
```
|
44
|
-
|
45
|
-
If you have cloned the repo once, and want to update code
|
46
|
-
|
47
|
-
```bash
|
48
|
-
git pull --recurse-submodules
|
49
|
-
git submodule foreach git fetch
|
50
|
-
```
|
51
|
-
|
52
|
-
# Testing
|
53
|
-
|
54
|
-
Simply run the test
|
55
|
-
|
56
|
-
```bash
|
57
|
-
rspec -c
|
58
|
-
```
|
59
|
-
|
60
|
-
Test in GC.stress mode
|
61
|
-
|
62
|
-
```bash
|
63
|
-
rspec -c -f d
|
64
|
-
```
|
65
|
-
|
66
|
-
With coverage (generates *coverage/index.html*)
|
67
|
-
|
68
|
-
```bash
|
69
|
-
COVERAGE=1 rspec -c
|
70
|
-
```
|
71
|
-
|
72
|
-
# Why fast
|
73
|
-
|
74
|
-
### Solid http parsers written in C
|
75
|
-
|
76
|
-
Nyara uses two evented parsers:
|
77
|
-
|
78
|
-
- [http_parser](https://github.com/joyent/http-parser) with chunked encoding support
|
79
|
-
- [multipart-parser-c](https://github.com/iafonov/multipart-parser-c) (todo)
|
80
|
-
|
81
|
-
And implemented the following in addition:
|
82
|
-
|
83
|
-
- RFC2616 compliant `Accept*` parser
|
84
|
-
- MIME type matcher
|
85
|
-
- Url-encoded parser for path / query / request body
|
86
|
-
|
87
|
-
### Decisive routing on header complete
|
88
|
-
|
89
|
-
To support HTTP methods like PUT, DELETE for archaic browsers, a technique called **method override** is used, and the HTTP method can be overriden by a request param (usually named `_method`). In Rack the param may rest in request body, so it needs to parse the whole body before routing to a desired action. In Nyara the param is always in request path query, and routing starts when header completes. So server can do a lot of things before a huge file completely uploaded and provide better user experience.
|
90
|
-
|
91
|
-
### Thin evented IO layer built for BSD or Linux
|
92
|
-
|
93
|
-
Nyara is only for systems with kqueue or epoll (maybe iocp in the future). Manufactural event queue is a waste of time.
|
94
|
-
|
95
|
-
### Solve sequential problems with Fiber
|
96
|
-
|
97
|
-
The common caveats of an evented framework is: mutual callbacks must be used to ensure the order of operations. In eventmachine, sent data buffers are copied and chained in a deque to flush, and `close_connection_after_writing` must be called to ensure that all data are written before close.
|
98
|
-
|
99
|
-
While in Nyara, the data sending is much simpler: we send them directly, if there are remaining bytes, the action fiber is paused. When the socket is ready again, we wake up the fiber to continue the send action. So a lot of duplications, memory copy and schedule are avoided and `close` is the `close`.
|
100
|
-
|
101
|
-
### More stable memory management
|
102
|
-
|
103
|
-
To make better user experience, you may tune the server to stop GC while doing request, and start GC again after every serveral requests. But by doing so you are increasing the probability of OOM: there are cases when `malloc` or `new` fails to get memory while the GC stopped by you can release some. With C-ext this can be partly fixed with Ruby's `ALLOC` and `ALLOC_N`, which can make GC release some memory when `malloc` fails. But with C++ this becomes a bit messy: you need to redefine `new` operators.
|
104
|
-
|
105
|
-
In Nyara, the use of C++ memory allocation is limited to boot time (the use of C++ may possibly removed in the future) so your server has less chance to be quit by a silent `OutOfMemoryException`.
|
106
|
-
|
107
|
-
### Shared buffer in layout rendering
|
108
|
-
|
109
|
-
Consider you have a page with nested layout: `layout1` encloses `layout2`, and `layout2` contains `page`.
|
110
|
-
|
111
|
-
When rendering `layout2`, the output string of `page` becomes an element inside the array buffer of `layout2`, then the output of `page` is duplicated in the output of `layout2`. When rendering `layout1`, the output of `layout2` is duplicated so a string containing the output of `page` is duplicated, again.
|
112
|
-
|
113
|
-
In Nyara, nested templates of Slim, ERB or Haml share the same output buffer, so the duplication is greatly reduced.
|
40
|
+
# Documentation
|
114
41
|
|
115
|
-
|
42
|
+
- [Manual](https://github.com/luikore/nyara/wiki/Manual)
|
43
|
+
- [API doc](http://rubydoc.info/github/luikore/nyara/master/frames)
|
44
|
+
- [Building from source](https://github.com/luikore/nyara/wiki/Building)
|
116
45
|
|
117
|
-
|
46
|
+
# Caveats
|
118
47
|
|
119
|
-
-
|
120
|
-
-
|
121
|
-
- layout rendering vs tilt
|
48
|
+
- *Nyara* is not based on [rack](https://github.com/rack/rack).
|
49
|
+
- *Nyara* is not compatible with [eventmachine](https://github.com/eventmachine/eventmachine). It won't work if you add gems like [em-synchrony](https://github.com/igrigorik/em-synchrony).
|
122
50
|
|
123
51
|
# License
|
124
52
|
|
125
|
-
BSD, see copying
|
53
|
+
BSD, see [copying](https://github.com/luikore/nyara/blob/master/copying)
|
data/spec/performance_spec.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require_relative "spec_helper"
|
2
|
+
unless ENV['SKIP_PERFORMANCE']
|
2
3
|
|
3
4
|
# run benchmarks in performance/, each output is a hash dumped with Marshal
|
4
5
|
#
|
@@ -18,7 +19,7 @@ describe 'performance' do
|
|
18
19
|
|
19
20
|
it "[parse_accept_value] faster than sinatra" do
|
20
21
|
res = bm 'parse_accept_value'
|
21
|
-
assert res[:nyara] * 1.
|
22
|
+
assert res[:nyara] * 1.7 < res[:sinatra], res.inspect
|
22
23
|
end
|
23
24
|
|
24
25
|
it "[parse_param] faster than parse in pure ruby" do
|
@@ -31,3 +32,5 @@ describe 'performance' do
|
|
31
32
|
assert res[:nyara] * 1.1 < res[:tilt], res.inspect
|
32
33
|
end
|
33
34
|
end
|
35
|
+
|
36
|
+
end # unless
|
data/{hello.rb → tools/hello.rb}
RENAMED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: nyara
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.1.pre.
|
4
|
+
version: 0.0.1.pre.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Zete Lui
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-06-
|
11
|
+
date: 2013-06-26 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: Fast, slim and fuzzy ruby web framework + server, based on preforked
|
14
14
|
event queue and Fiber. NO rack NOR eventmachine are used.
|
@@ -26,7 +26,6 @@ files:
|
|
26
26
|
- example/hello.rb
|
27
27
|
- example/stream.rb
|
28
28
|
- ext/extconf.rb
|
29
|
-
- hello.rb
|
30
29
|
- lib/nyara/config.rb
|
31
30
|
- lib/nyara/controller.rb
|
32
31
|
- lib/nyara/cookie.rb
|
@@ -36,7 +35,7 @@ files:
|
|
36
35
|
- lib/nyara/hashes/param_hash.rb
|
37
36
|
- lib/nyara/mime_types.rb
|
38
37
|
- lib/nyara/nyara.rb
|
39
|
-
- lib/nyara/
|
38
|
+
- lib/nyara/patches/tcp_socket.rb
|
40
39
|
- lib/nyara/request.rb
|
41
40
|
- lib/nyara/route.rb
|
42
41
|
- lib/nyara/route_entry.rb
|
@@ -64,6 +63,7 @@ files:
|
|
64
63
|
- spec/spec_helper.rb
|
65
64
|
- spec/view_spec.rb
|
66
65
|
- tools/bench-cookie.rb
|
66
|
+
- tools/hello.rb
|
67
67
|
- ext/http-parser/http_parser.h
|
68
68
|
- ext/inc/epoll.h
|
69
69
|
- ext/inc/kqueue.h
|
File without changes
|