lightio 0.2.2 → 0.3.0

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: d9a17b0dab156ad387faf4b53a3688a8f92c86a8
4
- data.tar.gz: a266e67454df145079d044db7da58e69e7093c3a
3
+ metadata.gz: 2da560fd469b39e6d55ee4acbe7ef0bae81602ae
4
+ data.tar.gz: 665f6fc976e45793695b3daa53810096f7db88a6
5
5
  SHA512:
6
- metadata.gz: b016a8993bd4527250104ba72b97b7a95599d6f47986cc8f65e964a0d792976ad68b7d53e24c9881f52e851641f62c23e3decec84b21699e9a5a0a8c8042b4db
7
- data.tar.gz: 64f5822c5e76447125e8ef795444fc8fe94841700e23e55eb475083f25cad527b9d0d89f8610e3c7da4b32b3d3a4a16bacfc83f81d78f1cd3a77cd7a60cc4579
6
+ metadata.gz: b1dbaf889b0b2649f110786e75aba70b42a02c0b887f3c9e5aff27bc5ee8049ffaec5ca911765492ddbff05ca24e773d0dcbff6859e05b40f1e64b571f0bad8c
7
+ data.tar.gz: e3bf2ff085faf5c582d6e57be7c9aff14a144f614b259720c86663d4fa71f23da895499152d4746fe5f28212984c0367520e2d0c9e06adfb8fb04aeb3461908c
data/.travis.yml CHANGED
@@ -2,9 +2,9 @@ sudo: false
2
2
  language: ruby
3
3
  rvm:
4
4
  - jruby-9.1.15.0 # latest stable
5
- - 2.2.7
6
5
  - 2.3.4
7
6
  - 2.4.1
7
+ - 2.5.0
8
8
  - ruby-head
9
9
 
10
10
  env:
@@ -16,4 +16,5 @@ matrix:
16
16
  allow_failures:
17
17
  - rvm: ruby-head
18
18
  - rvm: jruby-9.1.15.0
19
+ - rvm: 2.5.0
19
20
  fast_finish: true
data/Gemfile CHANGED
@@ -5,6 +5,12 @@ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
5
5
  # Specify your gem's dependencies in lightio.gemspec
6
6
  gemspec
7
7
 
8
+ group :development do
9
+ gem 'guard'
10
+ gem 'guard-rspec'
11
+ gem 'guard-bundler'
12
+ end
13
+
8
14
  group :test do
9
15
  gem 'coveralls', require: false
10
16
  end
data/Gemfile.lock CHANGED
@@ -1,12 +1,13 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- lightio (0.2.2)
5
- nio4r (~> 2.1)
4
+ lightio (0.3.0)
5
+ nio4r (~> 2.2)
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
9
9
  specs:
10
+ coderay (1.1.1)
10
11
  coveralls (0.8.21)
11
12
  json (>= 1.8, < 3)
12
13
  simplecov (~> 0.14.1)
@@ -15,9 +16,46 @@ GEM
15
16
  tins (~> 1.6)
16
17
  diff-lcs (1.3)
17
18
  docile (1.1.5)
19
+ ffi (1.9.18)
20
+ formatador (0.2.5)
21
+ guard (2.14.1)
22
+ formatador (>= 0.2.4)
23
+ listen (>= 2.7, < 4.0)
24
+ lumberjack (~> 1.0)
25
+ nenv (~> 0.1)
26
+ notiffany (~> 0.0)
27
+ pry (>= 0.9.12)
28
+ shellany (~> 0.0)
29
+ thor (>= 0.18.1)
30
+ guard-bundler (2.1.0)
31
+ bundler (~> 1.0)
32
+ guard (~> 2.2)
33
+ guard-compat (~> 1.1)
34
+ guard-compat (1.2.1)
35
+ guard-rspec (4.7.3)
36
+ guard (~> 2.1)
37
+ guard-compat (~> 1.1)
38
+ rspec (>= 2.99.0, < 4.0)
18
39
  json (2.1.0)
19
- nio4r (2.1.0)
40
+ listen (3.1.5)
41
+ rb-fsevent (~> 0.9, >= 0.9.4)
42
+ rb-inotify (~> 0.9, >= 0.9.7)
43
+ ruby_dep (~> 1.2)
44
+ lumberjack (1.0.12)
45
+ method_source (0.8.2)
46
+ nenv (0.3.0)
47
+ nio4r (2.2.0)
48
+ notiffany (0.1.1)
49
+ nenv (~> 0.1)
50
+ shellany (~> 0.0)
51
+ pry (0.10.4)
52
+ coderay (~> 1.1.0)
53
+ method_source (~> 0.8.1)
54
+ slop (~> 3.4)
20
55
  rake (10.1.0)
56
+ rb-fsevent (0.10.2)
57
+ rb-inotify (0.9.10)
58
+ ffi (>= 0.5.0, < 2)
21
59
  rspec (3.7.0)
22
60
  rspec-core (~> 3.7.0)
23
61
  rspec-expectations (~> 3.7.0)
@@ -31,11 +69,14 @@ GEM
31
69
  diff-lcs (>= 1.2.0, < 2.0)
32
70
  rspec-support (~> 3.7.0)
33
71
  rspec-support (3.7.0)
72
+ ruby_dep (1.5.0)
73
+ shellany (0.0.1)
34
74
  simplecov (0.14.1)
35
75
  docile (~> 1.1.0)
36
76
  json (>= 1.8, < 3)
37
77
  simplecov-html (~> 0.10.0)
38
78
  simplecov-html (0.10.2)
79
+ slop (3.6.0)
39
80
  term-ansicolor (1.6.0)
40
81
  tins (~> 1.0)
41
82
  thor (0.19.4)
@@ -47,6 +88,9 @@ PLATFORMS
47
88
  DEPENDENCIES
48
89
  bundler (~> 1.16)
49
90
  coveralls
91
+ guard
92
+ guard-bundler
93
+ guard-rspec
50
94
  lightio!
51
95
  rake (~> 10.0)
52
96
  rspec (~> 3.0)
data/Guardfile ADDED
@@ -0,0 +1,10 @@
1
+ directories %w(. lib spec) \
2
+ .select {|d| Dir.exists?(d) ? d : UI.warning("Directory #{d} does not exist")}
3
+
4
+ guard :bundler do
5
+ watch('Gemfile')
6
+ end
7
+
8
+ guard :rspec, all_after_pass: false, all_on_start: false, failed_mode: :keep, cmd: 'bundle exec rspec' do
9
+ watch(%r{^(lib|spec)/(.+?)(_spec)?\.rb}) {|m| "spec/#{m[2]}_spec.rb"}
10
+ end
data/README.md CHANGED
@@ -38,7 +38,7 @@ Or install it yourself as:
38
38
  The following documentations is also usable:
39
39
 
40
40
  * [Basic usage](https://github.com/socketry/lightio/wiki/Basic-Usage)
41
- * [YARD documentation](http://www.rubydoc.info/gems/lightio/frames)
41
+ * [YARD documentation](http://www.rubydoc.info/github/socketry/lightio/master)
42
42
  * [Examples](/examples)
43
43
 
44
44
  ## Discussion
data/bin/_guard-core ADDED
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application '_guard-core' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
12
+ load(bundle_binstub) if File.file?(bundle_binstub)
13
+
14
+ require "pathname"
15
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
16
+ Pathname.new(__FILE__).realpath)
17
+
18
+ require "rubygems"
19
+ require "bundler/setup"
20
+
21
+ load Gem.bin_path("guard", "_guard-core")
data/bin/guard ADDED
@@ -0,0 +1,21 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ #
5
+ # This file was generated by Bundler.
6
+ #
7
+ # The application 'guard' is installed as part of a gem, and
8
+ # this file is here to facilitate running it.
9
+ #
10
+
11
+ bundle_binstub = File.expand_path("../bundle", __FILE__)
12
+ load(bundle_binstub) if File.file?(bundle_binstub)
13
+
14
+ require "pathname"
15
+ ENV["BUNDLE_GEMFILE"] ||= File.expand_path("../../Gemfile",
16
+ Pathname.new(__FILE__).realpath)
17
+
18
+ require "rubygems"
19
+ require "bundler/setup"
20
+
21
+ load Gem.bin_path("guard", "guard")
@@ -55,7 +55,7 @@ module LightIO::Core
55
55
  @running = false
56
56
  @timers = Timers.new
57
57
  @callbacks = []
58
- @selector = ::NIO::Selector.new
58
+ @selector = ::NIO::Selector.new(env_backend)
59
59
  end
60
60
 
61
61
  def run
@@ -98,6 +98,15 @@ module LightIO::Core
98
98
  raise
99
99
  end
100
100
 
101
+ def backend
102
+ @selector.backend
103
+ end
104
+
105
+ def env_backend
106
+ key = 'LIGHTIO_BACKEND'.freeze
107
+ ENV.has_key?(key) ? ENV[key].to_sym : nil
108
+ end
109
+
101
110
  private
102
111
 
103
112
  def run_timers
@@ -112,7 +121,10 @@ module LightIO::Core
112
121
  end
113
122
 
114
123
  def run_callbacks
115
- while (callback = @callbacks.shift)
124
+ # prevent 'add new callbacks' during callback call, new callbacks will run in next turn
125
+ callbacks = @callbacks
126
+ @callbacks = []
127
+ while (callback = callbacks.shift)
116
128
  callback.call
117
129
  end
118
130
  end
@@ -1,3 +1,5 @@
1
+ require 'forwardable'
2
+
1
3
  module LightIO::Core
2
4
  # Beam is light-weight executor, provide thread-like interface
3
5
  #
@@ -20,6 +22,9 @@ module LightIO::Core
20
22
  # special class for simulate Thread#raise for Beam
21
23
  class BeamError
22
24
  attr_reader :error, :parent
25
+ extend Forwardable
26
+
27
+ def_delegators :@error, :message, :backtrace
23
28
 
24
29
  def initialize(error)
25
30
  @error = error
@@ -27,6 +32,9 @@ module LightIO::Core
27
32
  end
28
33
  end
29
34
 
35
+ attr_reader :error
36
+ attr_accessor :on_dead
37
+
30
38
  # Create a new beam
31
39
  #
32
40
  # Beam is light-weight executor, provide thread-like interface
@@ -72,9 +80,9 @@ module LightIO::Core
72
80
  #
73
81
  # @param [Numeric] limit wait limit seconds if limit > 0, return nil if beam still alive, else return beam self
74
82
  # @return [Beam, nil]
75
- def join(limit=0)
83
+ def join(limit=nil)
76
84
  # try directly get result
77
- if !alive? || limit <= 0
85
+ if !alive? || limit.nil? || limit <= 0
78
86
  # call value to raise error
79
87
  value
80
88
  return self
@@ -106,10 +114,18 @@ module LightIO::Core
106
114
 
107
115
  # Fiber not provide raise method, so we have to simulate one
108
116
  # @param [BeamError] error currently only support raise BeamError
109
- def raise(error)
110
- super unless error.is_a?(BeamError)
117
+ def raise(error, message=nil, backtrace=nil)
118
+ unless error.is_a?(BeamError)
119
+ message ||= error.respond_to?(:message) ? error.message : nil
120
+ backtrace ||= error.respond_to?(:backtrace) ? error.backtrace : nil
121
+ super(error, message, backtrace)
122
+ end
111
123
  self.parent = error.parent if error.parent
112
- raise error.error
124
+ if Beam.current == self
125
+ raise(error.error, message, backtrace)
126
+ else
127
+ @error ||= error
128
+ end
113
129
  end
114
130
 
115
131
  class << self
@@ -130,6 +146,7 @@ module LightIO::Core
130
146
  # mark beam as dead
131
147
  def dead
132
148
  @alive = false
149
+ on_dead.call(self) if on_dead
133
150
  end
134
151
 
135
152
  # Beam transfer back to parent after schedule
@@ -1,36 +1,21 @@
1
1
  require 'lightio/core/backend/nio'
2
+ require 'forwardable'
3
+
2
4
  module LightIO::Core
3
5
  # IOloop like a per-threaded EventMachine (cause fiber cannot resume cross threads)
4
6
  #
5
7
  # IOloop handle io waiting and schedule beams, user do not supposed to directly use this class
6
8
  class IOloop
7
9
 
10
+ RAW_THREAD = ::Thread
11
+
8
12
  def initialize
9
13
  @fiber = Fiber.new {run}
10
14
  @backend = Backend::NIO.new
11
15
  end
12
16
 
13
- # should never invoke explicitly
14
- def run
15
- # start io loop and never return...
16
- @backend.run
17
- end
18
-
19
- def add_timer(timer)
20
- @backend.add_timer(timer)
21
- end
22
-
23
- def add_callback(&blk)
24
- @backend.add_callback(&blk)
25
- end
26
-
27
- def add_io_wait(io, interests, &blk)
28
- @backend.add_io_wait(io, interests, &blk)
29
- end
30
-
31
- def cancel_io_wait(io)
32
- @backend.cancel_io_wait(io)
33
- end
17
+ extend Forwardable
18
+ def_delegators :@backend, :run, :add_timer, :add_callback, :add_io_wait, :cancel_io_wait, :backend
34
19
 
35
20
  # Wait a watcher, watcher can be a timer or socket.
36
21
  # see LightIO::Watchers module for detail
@@ -44,15 +29,18 @@ module LightIO::Core
44
29
  # wait until watcher is ok
45
30
  # then do work
46
31
  response_id, err = future.value
32
+ current_beam = LightIO::Core::Beam.current
47
33
  if response_id != id
48
- raise InvalidTransferError, "expect #{id}, but get #{result}"
34
+ raise LightIO::InvalidTransferError, "expect #{id}, but get #{response_id}"
49
35
  elsif err
50
36
  # if future return a err
51
37
  # simulate Thread#raise to Beam , that we can shutdown beam blocking by socket accepting
52
38
  # transfer back to which beam occur this err
53
39
  # not sure this is a right way to do it
54
- LightIO::Core::Beam.current.raise(err)
40
+ current_beam.raise(err) if current_beam.is_a?(LightIO::Core::Beam)
55
41
  end
42
+ # check beam error after wait
43
+ current_beam.send(:check_and_raise_error) if current_beam.is_a?(LightIO::Core::Beam)
56
44
  end
57
45
 
58
46
  def transfer
@@ -63,10 +51,10 @@ module LightIO::Core
63
51
  # return current ioloop or create new one
64
52
  def current
65
53
  key = :"lightio.ioloop"
66
- unless Thread.current.thread_variable?(key)
67
- Thread.current.thread_variable_set(key, IOloop.new)
54
+ unless RAW_THREAD.current.thread_variable?(key)
55
+ RAW_THREAD.current.thread_variable_set(key, IOloop.new)
68
56
  end
69
- Thread.current.thread_variable_get(key)
57
+ RAW_THREAD.current.thread_variable_get(key)
70
58
  end
71
59
  end
72
60
  end
@@ -6,10 +6,24 @@ module LightIO::Core
6
6
  # SHOULD NOT BE USED DIRECTLY
7
7
  class LightFiber < Fiber
8
8
  attr_reader :ioloop
9
+ attr_accessor :on_transfer
10
+
11
+ ROOT_FIBER = Fiber.current
9
12
 
10
13
  def initialize(ioloop: IOloop.current, &blk)
11
14
  @ioloop = ioloop
12
15
  super(&blk)
13
16
  end
17
+
18
+ def transfer
19
+ on_transfer.call(LightFiber.current, self) if on_transfer
20
+ super
21
+ end
22
+
23
+ class << self
24
+ def is_root?(fiber)
25
+ ROOT_FIBER == fiber
26
+ end
27
+ end
14
28
  end
15
29
  end
@@ -4,10 +4,7 @@ module LightIO::Library
4
4
  include LightIO::Wrap::IOWrapper
5
5
  wrap ::IO
6
6
 
7
- extend Forwardable
8
- def_delegators :@io_watcher, :wait, :wait_readable, :wait_writable
9
-
10
- wrap_blocking_methods :read, :write, exception_symbol: false
7
+ wrap_blocking_methods :read, :write
11
8
 
12
9
  alias_method :<<, :write
13
10
 
@@ -16,23 +13,88 @@ module LightIO::Library
16
13
  (outbuf ||= "").clear
17
14
  loop do
18
15
  readlen = length.nil? ? 4096 : length - outbuf.size
19
- begin
20
- outbuf << wait_nonblock(:read_nonblock, readlen, exception_symbol: false)
16
+ if (data = wait_nonblock(:read_nonblock, readlen))
17
+ outbuf << data
21
18
  if length == outbuf.size
22
19
  return outbuf
23
20
  end
24
- rescue EOFError
25
- return outbuf
21
+ else
22
+ return length.nil? ? '' : nil
26
23
  end
27
24
  end
28
25
  end
29
26
 
30
27
  def readpartial(maxlen, outbuf=nil)
31
28
  (outbuf ||= "").clear
32
- outbuf << wait_nonblock(:read_nonblock, maxlen, exception_symbol: false)
29
+ if (data = wait_nonblock(:read_nonblock, maxlen))
30
+ outbuf << data
31
+ else
32
+ raise EOFError, 'end of file reached'
33
+ end
33
34
  outbuf
34
35
  end
35
36
 
37
+ def getbyte
38
+ read(1)
39
+ end
40
+
41
+ def getc
42
+ wait_readable
43
+ @io.getc
44
+ end
45
+
46
+ def readline(*args)
47
+ line = gets(*args)
48
+ raise EOFError, 'end of file reached' if line.nil?
49
+ line
50
+ end
51
+
52
+ def readlines(*args)
53
+ result = []
54
+ until eof?
55
+ result << readline(*args)
56
+ end
57
+ result
58
+ end
59
+
60
+ def readchar
61
+ c = getc
62
+ raise EOFError, 'end of file reached' if c.nil?
63
+ c
64
+ end
65
+
66
+ def readbyte
67
+ b = getbyte
68
+ raise EOFError, 'end of file reached' if b.nil?
69
+ b
70
+ end
71
+
72
+ def eof
73
+ wait_readable
74
+ @io.eof?
75
+ end
76
+
77
+ alias eof? eof
78
+
79
+ def gets(*args)
80
+ raise ArgumentError, "wrong number of arguments (given #{args.size}, expected 0..2)" if args.size > 2
81
+ return nil if eof?
82
+ sep = $/
83
+ if args[0].is_a?(Numeric)
84
+ limit = args.shift
85
+ else
86
+ sep = args.shift if args.size > 0
87
+ limit = args.shift if args.first.is_a?(Numeric)
88
+ end
89
+ s = ''
90
+ while (c = getc)
91
+ s << c
92
+ break if limit && s.size == limit
93
+ break if c == sep
94
+ end
95
+ $_ = s
96
+ end
97
+
36
98
  def close(*args)
37
99
  # close watcher before io closed
38
100
  @io_watcher.close
@@ -43,6 +105,19 @@ module LightIO::Library
43
105
  self
44
106
  end
45
107
 
108
+ private
109
+ def wait_readable
110
+ # if IO is already readable, continue wait_readable may block it forever
111
+ # so use getbyte detect this situation
112
+ # Maybe move getc and gets to thread pool is a good idea
113
+ b = getbyte
114
+ if b
115
+ ungetbyte(b)
116
+ return
117
+ end
118
+ @io_watcher.wait_readable
119
+ end
120
+
46
121
  class << self
47
122
  def open(*args)
48
123
  io = self.new(*args)
@@ -65,7 +140,7 @@ module LightIO::Library
65
140
  end
66
141
 
67
142
  def select(read_fds, write_fds=nil, _except_fds=nil, timeout=nil)
68
- timer = 0
143
+ timer = timeout && Time.now
69
144
  # run once ioloop
70
145
  LightIO.sleep 0
71
146
  loop do
@@ -73,10 +148,8 @@ module LightIO::Library
73
148
  w_fds = (write_fds || []).select {|fd| fd.closed? ? raise(IOError, 'closed stream') : fd.instance_variable_get(:@io_watcher).writable?}
74
149
  e_fds = []
75
150
  if r_fds.empty? && w_fds.empty?
76
- interval = 0.1
77
- LightIO.sleep interval
78
- timer += interval
79
- if timeout && timer > timeout
151
+ LightIO.sleep 0
152
+ if timeout && Time.now - timer > timeout
80
153
  return nil
81
154
  end
82
155
  else
@@ -7,8 +7,8 @@ module LightIO::Library
7
7
  LightIO::IOloop.current.transfer
8
8
  end
9
9
  duration = duration[0]
10
- if duration.zero? && LightIO::Beam.current.respond_to?(:pass)
11
- LightIO::Beam.current.pass
10
+ if duration.zero?
11
+ LightIO::Beam.pass
12
12
  return
13
13
  end
14
14
  timer = LightIO::Watchers::Timer.new duration
@@ -0,0 +1,62 @@
1
+ require_relative 'queue'
2
+
3
+ module LightIO::Library
4
+ class Thread
5
+ class Mutex
6
+ def initialize
7
+ @queue = Queue.new
8
+ @queue << true
9
+ @locked_thread = nil
10
+ end
11
+
12
+ def lock
13
+ raise ThreadError, "deadlock; recursive locking" if owner?
14
+ @queue.pop
15
+ @locked_thread = LightIO::Thread.current
16
+ self
17
+ end
18
+
19
+ def unlock
20
+ raise ThreadError, "Attempt to unlock a mutex which is not locked" unless owner?
21
+ @locked_thread = nil
22
+ @queue << true
23
+ self
24
+ end
25
+
26
+ def locked?
27
+ !@locked_thread.nil?
28
+ end
29
+
30
+ def owner?
31
+ @locked_thread == LightIO::Thread.current
32
+ end
33
+
34
+ def sleep(timeout=nil)
35
+ unlock
36
+ LightIO.sleep(timeout)
37
+ lock
38
+ end
39
+
40
+ def synchronize
41
+ raise ThreadError, 'must be called with a block' unless block_given?
42
+ lock
43
+ begin
44
+ yield
45
+ ensure
46
+ unlock
47
+ end
48
+ end
49
+
50
+ def try_lock
51
+ if @locked_thread.nil?
52
+ lock
53
+ true
54
+ else
55
+ false
56
+ end
57
+ end
58
+ end
59
+ end
60
+
61
+ Mutex = Thread::Mutex
62
+ end
@@ -27,10 +27,10 @@ module LightIO::Library
27
27
  # Pushes the given +object+ to the queue.
28
28
  def push(object)
29
29
  raise ClosedQueueError, "queue closed" if @close
30
- if @waiters.any?
30
+ if (waiter = @waiters.shift)
31
31
  future = LightIO::Future.new
32
32
  LightIO::IOloop.current.add_callback {
33
- @waiters.shift.transfer(object)
33
+ waiter.transfer(object)
34
34
  future.transfer
35
35
  }
36
36
  future.value