lightio 0.2.2 → 0.3.0

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 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