lightio 0.3.2 → 0.4.0.pre
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/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
|
[](https://coveralls.io/github/socketry/lightio?branch=master)
|
7
7
|
[](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
|