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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e60501b7bca5cc445161d405a11e4a7d688de116
4
- data.tar.gz: 6068963377b9805a4cc93090d6d14eaba2df280a
3
+ metadata.gz: a88720041be21bfca13e5e2f38e63218661e81b1
4
+ data.tar.gz: 280863b2d6dee03a5f433c560137cf3a7f21cd7e
5
5
  SHA512:
6
- metadata.gz: c9e29908ede0af65b6b8a6d86d7ed88f85de30e6275c7a34917ef3ed3e444ac6df2567de2392b9859daabae6158ead27dc2be4b13669adb0070aa895cae3d8ee
7
- data.tar.gz: 391014cc0762ba84b948e9a18efe76377a61dcc6135926eec7c543a93b0921d9e707b52a1b260173a4286a90822716e264c4973c5238f85c2b4da24b0a875235
6
+ metadata.gz: 9faf2d2f8cdbf5a54b012d534c059858c98425054a9698ed3ea93dd1a57947d3e1fc169e1acaf00ffed983b713b34e308b550681c08f939554f0294cf90c0718
7
+ data.tar.gz: 4a596272e41a00d3bd879b5fa9a4532f05a191a834df4ed187a6557becbb7a25e02cbce94fff1cf4cdf52b8e16f4436d44e6f11129b78756476b9a9fef779a83
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- lightio (0.3.2)
4
+ lightio (0.4.0.pre)
5
5
  nio4r (~> 2.2)
6
6
 
7
7
  GEM
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
- The intent of LightIO is to provide ruby stdlib compatible modules, that user can use these modules instead stdlib, to gain the benefits of IO event loop without care any details about react or async programming.
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"]
@@ -1,5 +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 LightIO Libraries API
4
+ # look LightIO::Library namespace to find more
3
5
 
4
6
  require 'lightio'
5
7
 
@@ -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 how to use ruby 'raw' socket with LightIO
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
@@ -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,
@@ -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)
@@ -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
- unless RAW_THREAD.current.thread_variable?(key)
56
- RAW_THREAD.current.thread_variable_set(key, IOloop.new)
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
- RAW_THREAD.current.thread_variable_get(key)
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
@@ -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
@@ -0,0 +1,9 @@
1
+ module LightIO::Library
2
+ class File < LightIO::Library::IO
3
+ include Base
4
+ include LightIO::Wrap::IOWrapper
5
+
6
+ mock ::File
7
+ extend LightIO::Module::File::ClassMethods
8
+ end
9
+ end
@@ -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
- wrap ::IO
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? ? '' : 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
- @io.getc
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
- @io.eof?
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
- @io_watcher.wait_readable
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
@@ -1,5 +1,8 @@
1
1
  module LightIO::Library
2
2
  class Queue
3
+ extend Base::MockMethods
4
+ mock ::Queue
5
+
3
6
  def initialize
4
7
  @queue = []
5
8
  @waiters = []
@@ -97,4 +100,4 @@ module LightIO::Library
97
100
  @waiters.size
98
101
  end
99
102
  end
100
- end
103
+ end
@@ -2,6 +2,9 @@ require_relative 'queue'
2
2
 
3
3
  module LightIO::Library
4
4
  class SizedQueue < LightIO::Library::Queue
5
+ extend Base::MockMethods
6
+ mock ::SizedQueue
7
+
5
8
  attr_accessor :max
6
9
 
7
10
  def initialize(max)