ever 0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.github/workflows/test.yml +29 -0
- data/.gitignore +57 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +3 -0
- data/Gemfile.lock +25 -0
- data/LICENSE +21 -0
- data/README.md +167 -0
- data/Rakefile +18 -0
- data/ever.gemspec +26 -0
- data/examples/http_server.rb +124 -0
- data/ext/ever/ever.h +49 -0
- data/ext/ever/ever_ext.c +12 -0
- data/ext/ever/extconf.rb +22 -0
- data/ext/ever/libev.c +2 -0
- data/ext/ever/libev.h +11 -0
- data/ext/ever/loop.c +257 -0
- data/ext/ever/watcher.c +137 -0
- data/ext/libev/Changes +548 -0
- data/ext/libev/LICENSE +37 -0
- data/ext/libev/README +59 -0
- data/ext/libev/README.embed +3 -0
- data/ext/libev/ev.c +5279 -0
- data/ext/libev/ev.h +856 -0
- data/ext/libev/ev_epoll.c +296 -0
- data/ext/libev/ev_kqueue.c +224 -0
- data/ext/libev/ev_linuxaio.c +642 -0
- data/ext/libev/ev_poll.c +156 -0
- data/ext/libev/ev_port.c +192 -0
- data/ext/libev/ev_select.c +316 -0
- data/ext/libev/ev_vars.h +215 -0
- data/ext/libev/ev_win32.c +162 -0
- data/ext/libev/ev_wrap.h +216 -0
- data/ext/libev/test_libev_win32.c +123 -0
- data/lib/ever/version.rb +5 -0
- data/lib/ever.rb +3 -0
- data/test/helper.rb +13 -0
- data/test/run.rb +0 -0
- data/test/test_loop.rb +87 -0
- metadata +131 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 4ba6404ba736a8c3c519cba46a8bf8a9df85ecc4a9532790c15821bff12f348e
|
4
|
+
data.tar.gz: 162398e3f59e7a638cf7de58b47e0a05c746f4be49e46f62bbd23b60305a1f77
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a3e8236c119bdb97b0c5aa0b26f60cbc40f740201218de11494b2368e0c60e7ab3dc4b0235751b903483dc441fcc55f07bc9da61e6c06e5a26135656e909cfc8
|
7
|
+
data.tar.gz: aeef774232228911357f7c472a937695f319615d31f0bf11d93554177c3b184a32003b4489a5193ca803d90dbb67cfda5a64417e6c6045be11f73bc98d341b21
|
@@ -0,0 +1,29 @@
|
|
1
|
+
name: Tests
|
2
|
+
|
3
|
+
on: [push, pull_request]
|
4
|
+
|
5
|
+
jobs:
|
6
|
+
build:
|
7
|
+
strategy:
|
8
|
+
fail-fast: false
|
9
|
+
matrix:
|
10
|
+
os: [ubuntu-latest]
|
11
|
+
ruby: [2.6, 2.7, 3.0]
|
12
|
+
|
13
|
+
name: >-
|
14
|
+
${{matrix.os}}, ${{matrix.ruby}}
|
15
|
+
|
16
|
+
runs-on: ${{matrix.os}}
|
17
|
+
steps:
|
18
|
+
- uses: actions/checkout@v1
|
19
|
+
- uses: actions/setup-ruby@v1
|
20
|
+
with:
|
21
|
+
ruby-version: ${{matrix.ruby}}
|
22
|
+
- name: Install dependencies
|
23
|
+
run: |
|
24
|
+
gem install bundler
|
25
|
+
bundle install
|
26
|
+
- name: Compile C-extension
|
27
|
+
run: bundle exec rake compile
|
28
|
+
- name: Run tests
|
29
|
+
run: bundle exec rake test
|
data/.gitignore
ADDED
@@ -0,0 +1,57 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
/.config
|
4
|
+
/coverage/
|
5
|
+
/InstalledFiles
|
6
|
+
/pkg/
|
7
|
+
/spec/reports/
|
8
|
+
/spec/examples.txt
|
9
|
+
/test/tmp/
|
10
|
+
/test/version_tmp/
|
11
|
+
/tmp/
|
12
|
+
|
13
|
+
# Used by dotenv library to load environment variables.
|
14
|
+
# .env
|
15
|
+
|
16
|
+
# Ignore Byebug command history file.
|
17
|
+
.byebug_history
|
18
|
+
|
19
|
+
## Specific to RubyMotion:
|
20
|
+
.dat*
|
21
|
+
.repl_history
|
22
|
+
build/
|
23
|
+
*.bridgesupport
|
24
|
+
build-iPhoneOS/
|
25
|
+
build-iPhoneSimulator/
|
26
|
+
|
27
|
+
## Specific to RubyMotion (use of CocoaPods):
|
28
|
+
#
|
29
|
+
# We recommend against adding the Pods directory to your .gitignore. However
|
30
|
+
# you should judge for yourself, the pros and cons are mentioned at:
|
31
|
+
# https://guides.cocoapods.org/using/using-cocoapods.html#should-i-check-the-pods-directory-into-source-control
|
32
|
+
#
|
33
|
+
# vendor/Pods/
|
34
|
+
|
35
|
+
## Documentation cache and generated files:
|
36
|
+
/.yardoc/
|
37
|
+
/_yardoc/
|
38
|
+
/doc/
|
39
|
+
/rdoc/
|
40
|
+
|
41
|
+
## Environment normalization:
|
42
|
+
/.bundle/
|
43
|
+
/vendor/bundle
|
44
|
+
/lib/bundler/man/
|
45
|
+
|
46
|
+
# for a library or gem, you might want to ignore these files since the code is
|
47
|
+
# intended to run in multiple environments; otherwise, check them in:
|
48
|
+
# Gemfile.lock
|
49
|
+
# .ruby-version
|
50
|
+
# .ruby-gemset
|
51
|
+
|
52
|
+
# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
|
53
|
+
.rvmrc
|
54
|
+
|
55
|
+
# Used by RuboCop. Remote config files pulled in from inherit_from directive.
|
56
|
+
# .rubocop-https?--*
|
57
|
+
*.so
|
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
ever (0.1)
|
5
|
+
|
6
|
+
GEM
|
7
|
+
remote: https://rubygems.org/
|
8
|
+
specs:
|
9
|
+
http_parser.rb (0.7.0)
|
10
|
+
minitest (5.14.4)
|
11
|
+
rake (13.0.6)
|
12
|
+
rake-compiler (1.1.1)
|
13
|
+
rake
|
14
|
+
|
15
|
+
PLATFORMS
|
16
|
+
x86_64-linux
|
17
|
+
|
18
|
+
DEPENDENCIES
|
19
|
+
ever!
|
20
|
+
http_parser.rb (= 0.7.0)
|
21
|
+
minitest (= 5.14.4)
|
22
|
+
rake-compiler (= 1.1.1)
|
23
|
+
|
24
|
+
BUNDLED WITH
|
25
|
+
2.2.26
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
MIT License
|
2
|
+
|
3
|
+
Copyright (c) 2021 Digital Fabric
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
13
|
+
copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,167 @@
|
|
1
|
+
# Ever - a callback-less event reactor for Ruby
|
2
|
+
|
3
|
+
[![Gem Version](https://badge.fury.io/rb/ever.svg)](http://rubygems.org/gems/ever)
|
4
|
+
[![Ever Test](https://github.com/digital-fabric/ever/workflows/Tests/badge.svg)](https://github.com/digital-fabric/ever/actions?query=workflow%3ATests)
|
5
|
+
[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/digital-fabric/ever/blob/master/LICENSE)
|
6
|
+
|
7
|
+
Ever is a [libev](http://pod.tst.eu/http://cvs.schmorp.de/libev/ev.pod)-based event reactor for Ruby with a callback-less design. Events are emitted to an application-provided block inside a tight loop without registering and invoking callbacks.
|
8
|
+
|
9
|
+
## Features
|
10
|
+
|
11
|
+
- Simple, minimalistic API
|
12
|
+
- Zero dependencies
|
13
|
+
- Callback-less API for getting events
|
14
|
+
- Events for I/O readiness
|
15
|
+
- Events for one-shot or recurring timers
|
16
|
+
- Cross-thread signalling and emitting of events
|
17
|
+
|
18
|
+
## Rationale
|
19
|
+
|
20
|
+
I'm planning to add a compatibility mode to [Tipi](https://github.com/digital-fabric/tipi), a new [Polyphony](https://github.com/digital-fabric/polyphony)-based web server for Ruby. In this mode, Tipi will not be using Polyphony, but will employ multiple worker threads for handling concurrent requests. The problem is that we have X number of threads that need to be able to deal with Y number of concurrent connections.
|
21
|
+
|
22
|
+
After coming up with a bunch of different ideas for how to achieve this, I settled on the following design:
|
23
|
+
|
24
|
+
- The main thread runs a libev-based event reactor, and deals with accepting connections and distributing events.
|
25
|
+
- One or more worker threads wait for jobs to execute.
|
26
|
+
- When a new connection is accepted, the main thread starts watching for I/O readiness.
|
27
|
+
- When a connection is ready for reading, the main thread puts the connection on the job queue.
|
28
|
+
- A worker thread pulls the connection from the job queue and tries to read an incoming request. If the request is not complete, the connection is watched again for read readiness.
|
29
|
+
- When the request is complete, the worker threads continues to run the Rack app, gets the response, and tries to write the response. If the response cannot be written, the connection is watched for write readiness.
|
30
|
+
- When the response has been written, the connection is watched again for read readiness in preparation for the next request.
|
31
|
+
|
32
|
+
(A working sketch for this design is included [here as an example](https://github.com/digital-fabric/ever/blob/main/examples/http_server.rb).)
|
33
|
+
|
34
|
+
What's interesting about this design is that any number of worker threads can (theoretically) handle any number of concurrent requests, since each worker thread is not tied to a specific connection, but rather work on each connection in the queue as it becomes ready (for reading or writing).
|
35
|
+
|
36
|
+
## Installing
|
37
|
+
|
38
|
+
If you're using bundler just add it to your `Gemfile`:
|
39
|
+
|
40
|
+
```ruby
|
41
|
+
source 'https://rubygems.org'
|
42
|
+
|
43
|
+
gem 'ever'
|
44
|
+
```
|
45
|
+
|
46
|
+
You can then run `bundle install` to install it. Otherwise, just run `gem install ever`.
|
47
|
+
|
48
|
+
## Usage
|
49
|
+
|
50
|
+
Start by creating an instance of Ever::Loop:
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
require 'ever'
|
54
|
+
|
55
|
+
evloop = Ever::Loop.new
|
56
|
+
```
|
57
|
+
|
58
|
+
### Setting up event watchers
|
59
|
+
|
60
|
+
All events are identified using an application-provided key. This means that your app should provide a unique key for each event you wish to watch. To watch for I/O readiness, use `Loop#watch_io(key, io, read_write, oneshot)` where:
|
61
|
+
|
62
|
+
- `key`: unique event key (this can be *any* value, and in many cases you can just use the `IO` instance.)
|
63
|
+
- `io`: `IO` instance to watch.
|
64
|
+
- `read_write`: `false` for read, `true` for write.
|
65
|
+
- `oneshot`: `true` for one-shot event monitoring, `false` otherwise.
|
66
|
+
|
67
|
+
Example:
|
68
|
+
|
69
|
+
```ruby
|
70
|
+
result = socket.read_nonblock(16384, exception: false)
|
71
|
+
case result
|
72
|
+
when :wait_readable
|
73
|
+
evloop.watch_io(socket, socket, false, true)
|
74
|
+
else
|
75
|
+
...
|
76
|
+
end
|
77
|
+
```
|
78
|
+
|
79
|
+
To setup up timers, use `Loop#watch_timer(key, duration, interval)` where:
|
80
|
+
|
81
|
+
- `key`: unique event key
|
82
|
+
- `duration`: timer duration in seconds.
|
83
|
+
- `interval`: recurring interval in seconds. `0` for a one-shot timer.
|
84
|
+
|
85
|
+
```ruby
|
86
|
+
evloop.watch_timer(:timer, 1, 1)
|
87
|
+
evloop.each do |key|
|
88
|
+
case key
|
89
|
+
when :timer
|
90
|
+
puts "Got timer event"
|
91
|
+
end
|
92
|
+
end
|
93
|
+
```
|
94
|
+
|
95
|
+
### Stopping watchers
|
96
|
+
|
97
|
+
To stop a specific watcher, use `Loop#unwatch(key)` and provide the key previously provided to `#watch_io` or `#watch_timer`:
|
98
|
+
|
99
|
+
```ruby
|
100
|
+
evloop.watch_timer(:timer, 1, 1)
|
101
|
+
count = 0
|
102
|
+
evloop.each do |key|
|
103
|
+
case key
|
104
|
+
when :timer
|
105
|
+
puts "Got timer event"
|
106
|
+
count += 1
|
107
|
+
evloop.unwatch(:timer) if count == 10
|
108
|
+
end
|
109
|
+
end
|
110
|
+
```
|
111
|
+
|
112
|
+
### Processing events
|
113
|
+
|
114
|
+
To process events as they happen, use `Loop#each`, which will block waiting for events and will yield events as they happen. The application-provided block will be called with the event key for each event:
|
115
|
+
|
116
|
+
```ruby
|
117
|
+
evloop.each do |key|
|
118
|
+
distribute_event(key)
|
119
|
+
end
|
120
|
+
```
|
121
|
+
|
122
|
+
Alternatively you can use `Loop#next_event` to process events one by one, or using a custom loop. Note that while this method can block, it can also return `nil` in case no event was generated.
|
123
|
+
|
124
|
+
### Emitting custom events
|
125
|
+
|
126
|
+
You can emit events using `Loop#emit(key)`. In case the event loop is currently polling for events, it immediately return and the emitted event will be available.
|
127
|
+
|
128
|
+
### Signalling the event loop
|
129
|
+
|
130
|
+
You can signal the event loop in order to stop it from blocking by using `Loop#signal`.
|
131
|
+
|
132
|
+
### Stopping the event loop
|
133
|
+
|
134
|
+
An event loop that is currently blocking on `Loop#each` can be stopped using `Loop#stop` or by calling `Loop#emit(:stop)`.
|
135
|
+
|
136
|
+
### Signal handling
|
137
|
+
|
138
|
+
The created event loop will not trap signals by itself. You can setup signal traps and emit events that tell the app what to do. Here's an example:
|
139
|
+
|
140
|
+
```ruby
|
141
|
+
evloop = Ever::Loop.new
|
142
|
+
trap('SIGINT') { evloop.stop }
|
143
|
+
evloop.each { |key| handle_event(key) }
|
144
|
+
```
|
145
|
+
|
146
|
+
## API Summary
|
147
|
+
|
148
|
+
|Method|Description|
|
149
|
+
|------|-----------|
|
150
|
+
|`Loop.new()`|create a new event loop.|
|
151
|
+
|`Loop#each(&block)`|Handle events in an infinite loop.|
|
152
|
+
|`Loop#next_event()`|Wait for an event and return its key.|
|
153
|
+
|`Loop#watch_io(key, io, read_write, oneshot)`|Watch an IO instance for readiness.|
|
154
|
+
|`Loop#watch_timer(key, duration, interval)`|Setup a one-shot/recurring timer.|
|
155
|
+
|`Loop#unwatch(key)`|Stop watching specific event key.|
|
156
|
+
|`Loop#emit(key)`|Emit a custom event.|
|
157
|
+
|`Loop#signal()`|Signal the event loop, causing it to break if currently blocking.|
|
158
|
+
|`Loop#stop()`|Stop an event loop currently blocking in `#each`.|
|
159
|
+
|
160
|
+
## Performance
|
161
|
+
|
162
|
+
I did not yet explore all the performance implications of this new design, but [a sketch I made for an HTTP server](https://github.com/digital-fabric/ever/blob/main/examples/http_server.rb) shows it performing consistently at >60000 reqs/seconds on my development machine.
|
163
|
+
|
164
|
+
## Contributing
|
165
|
+
|
166
|
+
Issues and pull requests will be gladly accepted. If you have found this gem
|
167
|
+
useful, please let me know.
|
data/Rakefile
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/gem_tasks'
|
4
|
+
require 'rake/clean'
|
5
|
+
|
6
|
+
require 'rake/extensiontask'
|
7
|
+
Rake::ExtensionTask.new('ever_ext') do |ext|
|
8
|
+
ext.ext_dir = 'ext/ever'
|
9
|
+
end
|
10
|
+
|
11
|
+
task :recompile => [:clean, :compile]
|
12
|
+
task :default => [:compile, :test]
|
13
|
+
|
14
|
+
task :test do
|
15
|
+
exec 'ruby test/test_loop.rb'
|
16
|
+
end
|
17
|
+
|
18
|
+
CLEAN.include '**/*.o', '**/*.so', '**/*.so.*', '**/*.a', '**/*.bundle', '**/*.jar', 'pkg', 'tmp'
|
data/ever.gemspec
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
require_relative './lib/ever/version'
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = 'ever'
|
5
|
+
s.version = Ever::VERSION
|
6
|
+
s.licenses = ['MIT']
|
7
|
+
s.summary = 'Callback-less event reactor for Ruby'
|
8
|
+
s.author = 'Sharon Rosner'
|
9
|
+
s.email = 'sharon@noteflakes.com'
|
10
|
+
s.files = `git ls-files`.split
|
11
|
+
s.homepage = 'https://digital-fabric.github.io/ever'
|
12
|
+
s.metadata = {
|
13
|
+
"source_code_uri" => "https://github.com/digital-fabric/ever",
|
14
|
+
"homepage_uri" => "https://github.com/digital-fabric/ever",
|
15
|
+
"changelog_uri" => "https://github.com/digital-fabric/ever/blob/master/CHANGELOG.md"
|
16
|
+
}
|
17
|
+
s.rdoc_options = ["--title", "ever", "--main", "README.md"]
|
18
|
+
s.extra_rdoc_files = ["README.md"]
|
19
|
+
s.extensions = ["ext/ever/extconf.rb"]
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
s.required_ruby_version = '>= 2.6'
|
22
|
+
|
23
|
+
s.add_development_dependency 'rake-compiler', '1.1.1'
|
24
|
+
s.add_development_dependency 'minitest', '5.14.4'
|
25
|
+
s.add_development_dependency 'http_parser.rb', '0.7.0'
|
26
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler/setup'
|
4
|
+
require 'ever'
|
5
|
+
require 'http/parser'
|
6
|
+
require 'socket'
|
7
|
+
|
8
|
+
class Connection
|
9
|
+
attr_reader :io, :parser, :request_complete,
|
10
|
+
:request_headers, :request_body
|
11
|
+
attr_accessor :response
|
12
|
+
|
13
|
+
def initialize(io)
|
14
|
+
@io = io
|
15
|
+
@parser = Http::Parser.new(self)
|
16
|
+
setup_read_request
|
17
|
+
end
|
18
|
+
|
19
|
+
def setup_read_request
|
20
|
+
@request_complete = nil
|
21
|
+
@request_headers = nil
|
22
|
+
@request_body = +''
|
23
|
+
end
|
24
|
+
|
25
|
+
def on_headers_complete(headers)
|
26
|
+
@request_headers = headers
|
27
|
+
end
|
28
|
+
|
29
|
+
def on_body(chunk)
|
30
|
+
@request_body << chunk
|
31
|
+
end
|
32
|
+
|
33
|
+
def on_message_complete
|
34
|
+
@request_complete = true
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
$job_queue = Queue.new
|
39
|
+
$evloop = Ever::Loop.new
|
40
|
+
|
41
|
+
worker = Thread.new do
|
42
|
+
while (job = $job_queue.shift)
|
43
|
+
handle_connection(job)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def handle_connection(conn)
|
48
|
+
if !conn.request_complete
|
49
|
+
handle_read_request(conn)
|
50
|
+
else
|
51
|
+
handle_write_response(conn)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def handle_read_request(conn)
|
56
|
+
result = conn.io.read_nonblock(16384, exception: false)
|
57
|
+
case result
|
58
|
+
when :wait_readable
|
59
|
+
$evloop.emit([:watch_io, conn, false, true])
|
60
|
+
when :wait_writable
|
61
|
+
$evloop.emit([:watch_io, conn, true, true])
|
62
|
+
when nil
|
63
|
+
$evloop.emit([:close, conn])
|
64
|
+
else
|
65
|
+
conn.parser << result
|
66
|
+
if conn.request_complete
|
67
|
+
conn.response = handle_request(conn.request_headers, conn.request_body)
|
68
|
+
handle_write_response(conn)
|
69
|
+
else
|
70
|
+
$evloop.emit([:watch_io, conn, false, true])
|
71
|
+
end
|
72
|
+
end
|
73
|
+
rescue HTTP::Parser::Error, SystemCallError, IOError
|
74
|
+
$evloop.emit([:close, conn])
|
75
|
+
end
|
76
|
+
|
77
|
+
def handle_request(headers, body)
|
78
|
+
response_body = "Hello, world!"
|
79
|
+
"HTTP/1.1 200 OK\nContent-Length: #{response_body.bytesize}\n\n#{response_body}"
|
80
|
+
end
|
81
|
+
|
82
|
+
def handle_write_response(conn)
|
83
|
+
result = conn.io.write_nonblock(conn.response, exception: false)
|
84
|
+
case result
|
85
|
+
when :wait_readable
|
86
|
+
$evloop.emit([:watch_io, conn, false, true])
|
87
|
+
when :wait_writable
|
88
|
+
$evloop.emit([:watch_io, conn, true, true])
|
89
|
+
when nil
|
90
|
+
$evloop.emit([:close, conn])
|
91
|
+
else
|
92
|
+
conn.setup_read_request
|
93
|
+
$evloop.emit([:watch_io, conn, false, true])
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
def setup_connection(io)
|
98
|
+
conn = Connection.new(io)
|
99
|
+
$evloop.emit([:watch_io, conn, false, true])
|
100
|
+
end
|
101
|
+
|
102
|
+
server = TCPServer.new('0.0.0.0', 1234)
|
103
|
+
puts "Listening on port 1234..."
|
104
|
+
trap('SIGINT') { $evloop.stop }
|
105
|
+
$evloop.watch_io(:accept, server, false, false)
|
106
|
+
|
107
|
+
$evloop.each do |event|
|
108
|
+
case event
|
109
|
+
when :accept
|
110
|
+
socket = server.accept
|
111
|
+
setup_connection(socket)
|
112
|
+
when Connection
|
113
|
+
$job_queue << event
|
114
|
+
when Array
|
115
|
+
cmd = event[0]
|
116
|
+
case cmd
|
117
|
+
when :watch_io
|
118
|
+
$evloop.watch_io(event[1], event[1].io, event[2], event[3])
|
119
|
+
when :close
|
120
|
+
conn = event[1]
|
121
|
+
conn.io.close
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|