lightio 0.3.2 → 0.4.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/README.md +5 -3
- data/Rakefile +9 -1
- data/examples/echo_server.rb +2 -0
- data/examples/echo_server_with_raw_socket.rb +2 -1
- data/examples/monkey_patch.rb +26 -0
- data/lib/lightio.rb +3 -0
- data/lib/lightio/core/beam.rb +1 -1
- data/lib/lightio/core/ioloop.rb +11 -5
- data/lib/lightio/library.rb +2 -0
- data/lib/lightio/library/base.rb +96 -0
- data/lib/lightio/library/file.rb +9 -0
- data/lib/lightio/library/io.rb +14 -85
- data/lib/lightio/library/queue.rb +4 -1
- data/lib/lightio/library/sized_queue.rb +3 -0
- data/lib/lightio/library/socket.rb +93 -127
- data/lib/lightio/library/thread.rb +153 -122
- data/lib/lightio/library/threads_wait.rb +7 -8
- data/lib/lightio/module.rb +11 -0
- data/lib/lightio/module/base.rb +40 -0
- data/lib/lightio/module/file.rb +10 -0
- data/lib/lightio/module/io.rb +88 -0
- data/lib/lightio/module/socket.rb +165 -0
- data/lib/lightio/module/thread.rb +76 -0
- data/lib/lightio/module/threads_wait.rb +13 -0
- data/lib/lightio/monkey.rb +150 -0
- data/lib/lightio/raw_proxy.rb +24 -0
- data/lib/lightio/version.rb +1 -1
- data/lib/lightio/wrap.rb +16 -63
- data/lightio.gemspec +2 -0
- metadata +17 -6
- data/lib/lightio/library/mutex.rb +0 -93
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a88720041be21bfca13e5e2f38e63218661e81b1
|
4
|
+
data.tar.gz: 280863b2d6dee03a5f433c560137cf3a7f21cd7e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 9faf2d2f8cdbf5a54b012d534c059858c98425054a9698ed3ea93dd1a57947d3e1fc169e1acaf00ffed983b713b34e308b550681c08f939554f0294cf90c0718
|
7
|
+
data.tar.gz: 4a596272e41a00d3bd879b5fa9a4532f05a191a834df4ed187a6557becbb7a25e02cbce94fff1cf4cdf52b8e16f4436d44e6f11129b78756476b9a9fef779a83
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -6,9 +6,11 @@
|
|
6
6
|
[![Coverage Status](https://coveralls.io/repos/github/socketry/lightio/badge.svg?branch=master)](https://coveralls.io/github/socketry/lightio?branch=master)
|
7
7
|
[![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/jjyr/lightio/blob/master/LICENSE.txt)
|
8
8
|
|
9
|
-
LightIO is a ruby networking library, that combines ruby fiber and fast IO event loop.
|
9
|
+
LightIO is a ruby concurrency networking library, that combines ruby fiber and fast IO event loop.
|
10
10
|
|
11
|
-
|
11
|
+
* **Simple**, LightIO provide same API with ruby stdlib, from low level to high level, use `LightIO::Thread` to start green threads, use `LightIO::Socket` `LightIO::TCPServer` for IO operations.
|
12
|
+
* **Transparent**, LightIO encourage user to write multi threads and blocking-IO style code, LightIO will transfer actual IO operations to inner IO loop.
|
13
|
+
* **Monkey patch**(experiment), LightIO provide reversible monkey patch, call `LightIO::Monkey.patch_all!` to apply, it will let LightIO inner loop to take over all IO operations, and patch threads to light weight fibers.
|
12
14
|
|
13
15
|
See [Wiki](https://github.com/jjyr/lightio/wiki) and [Roadmap](https://github.com/jjyr/lightio/wiki/Current-status-and-roadmap) to get more information.
|
14
16
|
|
@@ -59,7 +61,7 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/jjyr/l
|
|
59
61
|
|
60
62
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
61
63
|
|
62
|
-
Copyright, 2017, by [Jiang Jinyang](http://justjjy.com/)
|
64
|
+
Copyright, 2017-2018, by [Jiang Jinyang](http://justjjy.com/)
|
63
65
|
|
64
66
|
## Code of Conduct
|
65
67
|
|
data/Rakefile
CHANGED
@@ -1,6 +1,14 @@
|
|
1
1
|
require "bundler/gem_tasks"
|
2
2
|
require "rspec/core/rake_task"
|
3
3
|
|
4
|
-
RSpec::Core::RakeTask.new(:spec)
|
4
|
+
RSpec::Core::RakeTask.new(:"spec:library") do |t|
|
5
|
+
t.exclude_pattern = 'spec/**/monkey_spec.rb'
|
6
|
+
end
|
7
|
+
|
8
|
+
RSpec::Core::RakeTask.new(:"spec:monkey_patch") do |t|
|
9
|
+
t.rspec_opts = "-r monkey_patch.rb --tag ~skip_monkey_patch"
|
10
|
+
end
|
5
11
|
|
6
12
|
task :default => :spec
|
13
|
+
|
14
|
+
task :spec => [:"spec:library", :"spec:monkey_patch"]
|
data/examples/echo_server.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# Example from https://github.com/socketry/nio4r/blob/master/examples/echo_server.rb
|
2
2
|
# rewrite it in lightio for demonstrate
|
3
|
-
# this example demonstrate
|
3
|
+
# this example demonstrate LightIO low-level API
|
4
|
+
# how to use ruby 'raw'(unpatched) socket with LightIO
|
4
5
|
|
5
6
|
require 'lightio'
|
6
7
|
require 'socket'
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'lightio'
|
2
|
+
|
3
|
+
# apply monkey patch as early as possible
|
4
|
+
# after monkey patch, it just normal ruby code
|
5
|
+
LightIO::Monkey.patch_all!
|
6
|
+
|
7
|
+
|
8
|
+
TCPServer.open('localhost', 3000) do |server|
|
9
|
+
while (socket = server.accept)
|
10
|
+
_, port, host = socket.peeraddr
|
11
|
+
puts "accept connection from #{host}:#{port}"
|
12
|
+
|
13
|
+
# Don't worry, Thread.new create green threads, it cost very light
|
14
|
+
Thread.new(socket) do |socket|
|
15
|
+
data = nil
|
16
|
+
begin
|
17
|
+
socket.write(data) while (data = socket.readpartial(4096))
|
18
|
+
rescue EOFError
|
19
|
+
_, port, host = socket.peeraddr
|
20
|
+
puts "*** #{host}:#{port} disconnected"
|
21
|
+
socket.close
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
data/lib/lightio.rb
CHANGED
@@ -1,10 +1,13 @@
|
|
1
1
|
# LightIO
|
2
2
|
require 'lightio/version'
|
3
3
|
require 'lightio/errors'
|
4
|
+
require 'lightio/raw_proxy'
|
4
5
|
require 'lightio/core'
|
5
6
|
require 'lightio/watchers'
|
6
7
|
require 'lightio/wrap'
|
8
|
+
require 'lightio/module'
|
7
9
|
require 'lightio/library'
|
10
|
+
require 'lightio/monkey'
|
8
11
|
|
9
12
|
# LightIO provide light-weight executor: LightIO::Beam and batch io libraries,
|
10
13
|
# view LightIO::Core::Beam to learn how to concurrent programming with Beam,
|
data/lib/lightio/core/beam.rb
CHANGED
@@ -45,7 +45,7 @@ module LightIO::Core
|
|
45
45
|
# @param [Proc] blk block to execute
|
46
46
|
# @return [Beam]
|
47
47
|
def initialize(*args, &blk)
|
48
|
-
raise Error, "must be called with a block" unless blk
|
48
|
+
raise LightIO::Error, "must be called with a block" unless blk
|
49
49
|
super() {
|
50
50
|
begin
|
51
51
|
@value = yield(*args)
|
data/lib/lightio/core/ioloop.rb
CHANGED
@@ -7,8 +7,6 @@ module LightIO::Core
|
|
7
7
|
# IOloop handle io waiting and schedule beams, user do not supposed to directly use this class
|
8
8
|
class IOloop
|
9
9
|
|
10
|
-
RAW_THREAD = ::Thread
|
11
|
-
|
12
10
|
def initialize
|
13
11
|
@fiber = Fiber.new {run}
|
14
12
|
@backend = Backend::NIO.new
|
@@ -48,15 +46,23 @@ module LightIO::Core
|
|
48
46
|
@fiber.transfer
|
49
47
|
end
|
50
48
|
|
49
|
+
THREAD_PROXY = ::LightIO::RawProxy.new(::Thread,
|
50
|
+
methods: [:current],
|
51
|
+
instance_methods: [:thread_variable_get, :thread_variable_set, :thread_variable?])
|
52
|
+
|
51
53
|
class << self
|
52
54
|
# return current ioloop or create new one
|
53
55
|
def current
|
54
56
|
key = :"lightio.ioloop"
|
55
|
-
|
56
|
-
|
57
|
+
current_thread = THREAD_PROXY.send(:current)
|
58
|
+
unless THREAD_PROXY.instance_send(current_thread, :thread_variable?, key)
|
59
|
+
THREAD_PROXY.instance_send(current_thread, :thread_variable_set, key, IOloop.new)
|
57
60
|
end
|
58
|
-
|
61
|
+
THREAD_PROXY.instance_send(current_thread, :thread_variable_get, key)
|
59
62
|
end
|
60
63
|
end
|
61
64
|
end
|
65
|
+
|
66
|
+
# Initialize IOloop
|
67
|
+
IOloop.current
|
62
68
|
end
|
data/lib/lightio/library.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
|
+
require_relative 'library/base'
|
1
2
|
require_relative 'library/queue'
|
2
3
|
require_relative 'library/sized_queue'
|
3
4
|
require_relative 'library/kernel_ext'
|
4
5
|
require_relative 'library/timeout'
|
5
6
|
require_relative 'library/io'
|
7
|
+
require_relative 'library/file'
|
6
8
|
require_relative 'library/socket'
|
7
9
|
require_relative 'library/thread'
|
8
10
|
require_relative 'library/threads_wait'
|
@@ -0,0 +1,96 @@
|
|
1
|
+
module LightIO::Library
|
2
|
+
module Base
|
3
|
+
module MockMethods
|
4
|
+
protected
|
5
|
+
def mock(klass)
|
6
|
+
@mock_klass = klass
|
7
|
+
define_alias_methods
|
8
|
+
define_method_missing(singleton_class, @mock_klass)
|
9
|
+
define_instance_method_missing(self, :@obj)
|
10
|
+
define_mock_methods
|
11
|
+
extend_class_methods
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :mock_klass
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def define_alias_methods
|
19
|
+
class_methods_module = LightIO::Module.const_get("#{mock_klass}::ClassMethods") rescue nil
|
20
|
+
return unless class_methods_module
|
21
|
+
methods = class_methods_module.instance_methods(false).select {|method| mock_klass.respond_to?(method)}
|
22
|
+
methods.each do |method|
|
23
|
+
origin_method_name = "origin_#{method}"
|
24
|
+
mock_klass.singleton_class.__send__(:alias_method, origin_method_name, method)
|
25
|
+
mock_klass.singleton_class.__send__(:protected, origin_method_name)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def define_method_missing(base, target_var)
|
30
|
+
base.send(:define_method, :method_missing) {|*args| target_var.__send__(*args)}
|
31
|
+
base.send(:define_method, :respond_to_missing?) {|method, *| target_var.respond_to?(method)}
|
32
|
+
end
|
33
|
+
|
34
|
+
def define_instance_method_missing(base, target_var)
|
35
|
+
base.send(:define_method, :method_missing) {|*args| instance_variable_get(target_var).__send__(*args)}
|
36
|
+
base.send(:define_method, :respond_to_missing?) {|method, *| instance_variable_get(target_var).respond_to?(method)}
|
37
|
+
end
|
38
|
+
|
39
|
+
def define_mock_methods
|
40
|
+
define_method :is_a? do |klass|
|
41
|
+
mock_klass = self.class.__send__(:call_method_from_ancestors, :mock_klass)
|
42
|
+
return super(klass) unless mock_klass
|
43
|
+
mock_klass <= klass || super(klass)
|
44
|
+
end
|
45
|
+
|
46
|
+
alias_method :kind_of?, :is_a?
|
47
|
+
|
48
|
+
define_method :instance_of? do |klass|
|
49
|
+
mock_klass = self.class.__send__(:mock_klass)
|
50
|
+
return super(klass) unless mock_klass
|
51
|
+
mock_klass == klass || super(klass)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def call_method_from_ancestors(method)
|
56
|
+
__send__(method) || begin
|
57
|
+
self.ancestors.each do |klass|
|
58
|
+
result = klass.__send__(method)
|
59
|
+
break result if result
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def extend_class_methods
|
65
|
+
class_methods_module = LightIO::Module.const_get("#{mock_klass}::ClassMethods")
|
66
|
+
self.__send__ :extend, class_methods_module
|
67
|
+
rescue NameError
|
68
|
+
nil
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
module ClassMethods
|
73
|
+
def _wrap(obj)
|
74
|
+
if obj.instance_of? self
|
75
|
+
obj
|
76
|
+
else
|
77
|
+
mock_obj = allocate
|
78
|
+
mock_obj.instance_variable_set(:@obj, obj)
|
79
|
+
mock_obj
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
def initialize(*args)
|
85
|
+
obj = self.class.send(:call_method_from_ancestors, :mock_klass).send(:origin_new, *args)
|
86
|
+
@obj = obj
|
87
|
+
end
|
88
|
+
|
89
|
+
class << self
|
90
|
+
def included(base)
|
91
|
+
base.send :extend, MockMethods
|
92
|
+
base.send :extend, ClassMethods
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
data/lib/lightio/library/io.rb
CHANGED
@@ -1,8 +1,10 @@
|
|
1
|
-
require 'forwardable'
|
2
1
|
module LightIO::Library
|
3
2
|
class IO
|
3
|
+
include Base
|
4
4
|
include LightIO::Wrap::IOWrapper
|
5
|
-
|
5
|
+
|
6
|
+
mock ::IO
|
7
|
+
extend LightIO::Module::IO::ClassMethods
|
6
8
|
|
7
9
|
wrap_blocking_methods :read, :write
|
8
10
|
|
@@ -19,7 +21,7 @@ module LightIO::Library
|
|
19
21
|
return outbuf
|
20
22
|
end
|
21
23
|
else
|
22
|
-
return length.nil? ?
|
24
|
+
return length.nil? ? outbuf : nil
|
23
25
|
end
|
24
26
|
end
|
25
27
|
end
|
@@ -40,7 +42,7 @@ module LightIO::Library
|
|
40
42
|
|
41
43
|
def getc
|
42
44
|
wait_readable
|
43
|
-
@
|
45
|
+
@obj.getc
|
44
46
|
end
|
45
47
|
|
46
48
|
def readline(*args)
|
@@ -71,7 +73,7 @@ module LightIO::Library
|
|
71
73
|
|
72
74
|
def eof
|
73
75
|
wait_readable
|
74
|
-
@
|
76
|
+
@obj.eof
|
75
77
|
end
|
76
78
|
|
77
79
|
alias eof? eof
|
@@ -95,16 +97,16 @@ module LightIO::Library
|
|
95
97
|
$_ = s
|
96
98
|
end
|
97
99
|
|
98
|
-
def close(*args)
|
99
|
-
# close watcher before io closed
|
100
|
-
@io_watcher.close
|
101
|
-
@io.close(*args)
|
102
|
-
end
|
103
|
-
|
104
100
|
def to_io
|
105
101
|
self
|
106
102
|
end
|
107
103
|
|
104
|
+
def close(*args)
|
105
|
+
# close watcher before io closed
|
106
|
+
io_watcher.close
|
107
|
+
@obj.close
|
108
|
+
end
|
109
|
+
|
108
110
|
private
|
109
111
|
def wait_readable
|
110
112
|
# if IO is already readable, continue wait_readable may block it forever
|
@@ -115,80 +117,7 @@ module LightIO::Library
|
|
115
117
|
ungetbyte(b)
|
116
118
|
return
|
117
119
|
end
|
118
|
-
|
119
|
-
end
|
120
|
-
|
121
|
-
class << self
|
122
|
-
def open(*args)
|
123
|
-
io = self.new(*args)
|
124
|
-
return io unless block_given?
|
125
|
-
begin
|
126
|
-
yield io
|
127
|
-
ensure
|
128
|
-
io.close if io.respond_to? :close
|
129
|
-
end
|
130
|
-
end
|
131
|
-
|
132
|
-
def pipe(*args)
|
133
|
-
r, w = raw_class.pipe
|
134
|
-
if block_given?
|
135
|
-
begin
|
136
|
-
return yield r, w
|
137
|
-
ensure
|
138
|
-
w.close
|
139
|
-
r.close
|
140
|
-
end
|
141
|
-
end
|
142
|
-
[IO._wrap(r), IO._wrap(w)]
|
143
|
-
end
|
144
|
-
|
145
|
-
def select(read_fds, write_fds=nil, _except_fds=nil, timeout=nil)
|
146
|
-
timer = timeout && Time.now
|
147
|
-
read_fds ||= []
|
148
|
-
write_fds ||= []
|
149
|
-
loop do
|
150
|
-
# make sure io registered, then clear io watcher status
|
151
|
-
read_fds.each {|fd| get_io_watcher(fd).tap {|io| io.readable?; io.clear_status}}
|
152
|
-
write_fds.each {|fd| get_io_watcher(fd).tap {|io| io.writable?; io.clear_status}}
|
153
|
-
# run ioloop once
|
154
|
-
LightIO.sleep 0
|
155
|
-
r_fds = read_fds.select {|fd|
|
156
|
-
io = convert_to_io(fd)
|
157
|
-
io.closed? ? raise(IOError, 'closed stream') : get_io_watcher(io).readable?
|
158
|
-
}
|
159
|
-
w_fds = write_fds.select {|fd|
|
160
|
-
io = convert_to_io(fd)
|
161
|
-
io.closed? ? raise(IOError, 'closed stream') : get_io_watcher(io).writable?
|
162
|
-
}
|
163
|
-
e_fds = []
|
164
|
-
if r_fds.empty? && w_fds.empty?
|
165
|
-
if timeout && Time.now - timer > timeout
|
166
|
-
return nil
|
167
|
-
end
|
168
|
-
else
|
169
|
-
return [r_fds, w_fds, e_fds]
|
170
|
-
end
|
171
|
-
end
|
172
|
-
end
|
173
|
-
|
174
|
-
private
|
175
|
-
def convert_to_io(io)
|
176
|
-
unless io.respond_to?(:to_io)
|
177
|
-
raise TypeError, "no implicit conversion of #{io.class} into IO"
|
178
|
-
end
|
179
|
-
to_io = io.to_io
|
180
|
-
unless to_io.is_a?(IO)
|
181
|
-
raise TypeError, "can't convert #{io.class} to IO (#{io.class}#to_io gives #{to_io.class})"
|
182
|
-
end
|
183
|
-
to_io
|
184
|
-
end
|
185
|
-
|
186
|
-
def get_io_watcher(io)
|
187
|
-
unless io.is_a?(IO)
|
188
|
-
io = convert_to_io(io)
|
189
|
-
end
|
190
|
-
io.instance_variable_get(:@io_watcher)
|
191
|
-
end
|
120
|
+
io_watcher.wait_readable
|
192
121
|
end
|
193
122
|
end
|
194
123
|
end
|