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.
@@ -0,0 +1,64 @@
1
+ require_relative 'queue'
2
+
3
+ module LightIO::Library
4
+ class SizedQueue < LightIO::Library::Queue
5
+ attr_accessor :max
6
+
7
+ def initialize(max)
8
+ raise ArgumentError, 'queue size must be positive' unless max > 0
9
+ super()
10
+ @max = max
11
+ @enqueue_waiters = []
12
+ end
13
+
14
+ def push(object)
15
+ raise ClosedQueueError, "queue closed" if @close
16
+ if size >= max
17
+ future = LightIO::Future.new
18
+ @enqueue_waiters << future
19
+ future.value
20
+ end
21
+ super
22
+ self
23
+ end
24
+
25
+ alias enq push
26
+ alias << push
27
+
28
+ def pop(non_block=false)
29
+ result = super
30
+ check_release_enqueue_waiter
31
+ result
32
+ end
33
+
34
+ alias deq pop
35
+ alias shift pop
36
+
37
+ def clear
38
+ result = super
39
+ check_release_enqueue_waiter
40
+ result
41
+ end
42
+
43
+ def max=(value)
44
+ @max = value
45
+ check_release_enqueue_waiter if size < max
46
+ end
47
+
48
+ def num_waiting
49
+ super + @enqueue_waiters.size
50
+ end
51
+
52
+ private
53
+ def check_release_enqueue_waiter
54
+ if @enqueue_waiters.any?
55
+ future = LightIO::Future.new
56
+ LightIO::IOloop.current.add_callback {
57
+ @enqueue_waiters.shift.transfer
58
+ future.transfer
59
+ }
60
+ future.value
61
+ end
62
+ end
63
+ end
64
+ end
@@ -65,9 +65,11 @@ module LightIO::Library
65
65
  class BasicSocket < IO
66
66
  include LightIO::Wrap::IOWrapper
67
67
  wrap ::BasicSocket
68
-
69
68
  wrap_blocking_methods :recv, :recvmsg, :sendmsg
70
69
 
70
+ extend Forwardable
71
+ def_delegators :@io_watcher, :wait, :wait_writable
72
+
71
73
  def shutdown(*args)
72
74
  # close watcher before io shutdown
73
75
  @io_watcher.close
@@ -99,13 +101,40 @@ module LightIO::Library
99
101
  @io.sys_accept
100
102
  end
101
103
 
102
- # bind addr?
104
+ Option = ::Socket::Option
105
+ UDPSource = ::Socket::UDPSource
106
+ SocketError = ::SocketError
107
+
108
+ class Ifaddr
109
+ include LightIO::Wrap::Wrapper
110
+ wrap ::Socket
111
+
112
+ def addr
113
+ @io.addr && Addrinfo._wrap(@io.addr)
114
+ end
115
+
116
+ def broadaddr
117
+ @io.broadaddr && Addrinfo._wrap(@io.broadaddr)
118
+ end
119
+
120
+ def dstaddr
121
+ @io.dstaddr && Addrinfo._wrap(@io.dstaddr)
122
+ end
123
+
124
+ def netmask
125
+ @io.netmask && Addrinfo._wrap(@io.netmask)
126
+ end
127
+ end
103
128
 
104
129
  class << self
105
130
  ## implement ::Socket class methods
106
- wrap_methods_run_in_threads_pool :getaddrinfo, :gethostbyaddr, :gethostbyname, :gethostname, :getifaddrs,
131
+ wrap_methods_run_in_threads_pool :getaddrinfo, :gethostbyaddr, :gethostbyname, :gethostname,
107
132
  :getnameinfo, :getservbyname
108
133
 
134
+ def getifaddrs
135
+ raw_class.getifaddrs.map {|ifaddr| Ifaddr._wrap(ifaddr)}
136
+ end
137
+
109
138
  def socketpair(domain, type, protocol)
110
139
  raw_class.socketpair(domain, type, protocol).map {|s| _wrap(s)}
111
140
  end
@@ -0,0 +1,295 @@
1
+ require 'thread'
2
+ require_relative 'mutex'
3
+ require_relative 'queue'
4
+
5
+ module LightIO::Library
6
+ class ThreadGroup
7
+ include LightIO::Wrap::Wrapper
8
+ wrap ::ThreadGroup
9
+
10
+ Default = ThreadGroup._wrap(::ThreadGroup::Default)
11
+
12
+ def add(thread)
13
+ if @io.enclosed?
14
+ raise ThreadError, "can't move from the enclosed thread group"
15
+ elsif thread.is_a?(LightIO::Library::Thread)
16
+ # let thread decide how to add to group
17
+ thread.send(:add_to_group, self)
18
+ else
19
+ @io.add(thread)
20
+ end
21
+ self
22
+ end
23
+
24
+ def list
25
+ @io.list + threads
26
+ end
27
+
28
+ private
29
+ def threads
30
+ @threads ||= []
31
+ end
32
+ end
33
+
34
+
35
+ class Thread
36
+ RAW_THREAD = ::Thread
37
+
38
+ # constants
39
+ ThreadError = ::ThreadError
40
+ Queue = LightIO::Library::Queue
41
+ Backtrace = ::Thread::Backtrace
42
+ SizedQueue = LightIO::Library::SizedQueue
43
+
44
+ @current_thread = nil
45
+
46
+ module FallbackHelper
47
+ module ClassMethods
48
+ def fallback_method(obj, method, warning_text)
49
+ define_method method do |*args|
50
+ warn warning_text
51
+ obj.public_send method, *args
52
+ end
53
+ end
54
+
55
+ def fallback_thread_class_methods(*methods)
56
+ methods.each {|m| fallback_method(RAW_THREAD.main, m, "This method is fallback to native Thread class,"\
57
+ " it may cause unexpected behaviour,"\
58
+ " open issues on https://github.com/socketry/lightio/issues"\
59
+ " if this behaviour not approach you purpose")}
60
+ end
61
+ end
62
+
63
+ include ClassMethods
64
+
65
+ def fallback_main_thread_methods(*methods)
66
+ methods.each {|m| fallback_method(main, m, "This method is fallback to native main thread,"\
67
+ " it may cause unexpected behaviour,"\
68
+ " open issues on https://github.com/socketry/lightio/issues"\
69
+ " if this behaviour not approach you purpose")}
70
+ end
71
+
72
+ def self.included(base)
73
+ base.send :extend, ClassMethods
74
+ end
75
+ end
76
+
77
+ class << self
78
+ extend Forwardable
79
+ def_delegators :'LightIO::Library::Thread::RAW_THREAD', :DEBUG, :DEBUG=
80
+
81
+ include FallbackHelper
82
+ fallback_thread_class_methods :abort_on_exception, :abort_on_exception=
83
+
84
+ def fork(*args, &blk)
85
+ obj = allocate
86
+ obj.send(:init_core, *args, &blk)
87
+ obj
88
+ end
89
+
90
+ alias start fork
91
+
92
+ def kill(thr)
93
+ thr.kill
94
+ end
95
+
96
+ def current
97
+ return main if LightIO::Core::LightFiber.is_root?(Fiber.current)
98
+ @current_thread || RAW_THREAD.current
99
+ end
100
+
101
+ def exclusive(&blk)
102
+ @thread_mutex.synchronize(&blk)
103
+ end
104
+
105
+ def list
106
+ thread_list = []
107
+ threads.keys.each {|id|
108
+ begin
109
+ thr = ObjectSpace._id2ref(id)
110
+ unless thr.alive?
111
+ # manually remove thr from threads
112
+ thr.kill
113
+ next
114
+ end
115
+ thread_list << thr
116
+ rescue RangeError
117
+ # mean object is recycled
118
+ # just wait ruby GC call finalizer to remove it from threads
119
+ next
120
+ end
121
+ }
122
+ thread_list
123
+ end
124
+
125
+ def pass
126
+ LightIO::Beam.pass
127
+ end
128
+
129
+ alias stop pass
130
+
131
+ def finalizer(object_id)
132
+ proc {threads.delete(object_id)}
133
+ end
134
+
135
+ def main
136
+ RAW_THREAD.main
137
+ end
138
+
139
+ private
140
+
141
+ # threads and threads variables
142
+ def threads
143
+ @threads ||= {}
144
+ end
145
+ end
146
+
147
+ extend Forwardable
148
+
149
+ def initialize(*args, &blk)
150
+ init_core(*args, &blk)
151
+ end
152
+
153
+ @thread_mutex = LightIO::Library::Mutex.new
154
+ def_delegators :@beam, :alive?, :value
155
+
156
+ fallback_main_thread_methods :abort_on_exception,
157
+ :abort_on_exception=,
158
+ :handle_interrupt,
159
+ :pending_interrupt,
160
+ :add_trace_func,
161
+ :backtrace,
162
+ :backtrace_locations,
163
+ :priority,
164
+ :priority=,
165
+ :safe_level
166
+
167
+ def kill
168
+ @beam.kill && self
169
+ end
170
+
171
+ alias exit kill
172
+ alias terminate kill
173
+
174
+ def status
175
+ if Thread.current == self
176
+ 'run'
177
+ elsif alive?
178
+ @beam.error.nil? ? 'sleep' : 'abouting'
179
+ else
180
+ @beam.error.nil? ? false : nil
181
+ end
182
+ end
183
+
184
+ def thread_variables
185
+ thread_values.keys
186
+ end
187
+
188
+ def thread_variable_get(name)
189
+ thread_values[name.to_sym]
190
+ end
191
+
192
+ def thread_variable_set(name, value)
193
+ thread_values[name.to_sym] = value
194
+ end
195
+
196
+ def thread_variable?(key)
197
+ thread_values.key?(key)
198
+ end
199
+
200
+ def [](name)
201
+ fiber_values[name.to_sym]
202
+ end
203
+
204
+ def []=(name, val)
205
+ fiber_values[name.to_sym] = val
206
+ end
207
+
208
+ def group
209
+ @group
210
+ end
211
+
212
+ def inspect
213
+ "#<LightIO::Library::Thread:0x00#{object_id.to_s(16)} #{status}>"
214
+ end
215
+
216
+ def join(limit=nil)
217
+ @beam.join(limit) && self
218
+ end
219
+
220
+ def key?(sym)
221
+ fiber_values.has_key?(sym)
222
+ end
223
+
224
+ def keys
225
+ fiber_values.keys
226
+ end
227
+
228
+ def raise(exception, message=nil, backtrace=nil)
229
+ @beam.raise(LightIO::Beam::BeamError.new(exception), message, backtrace)
230
+ end
231
+
232
+ def run
233
+ Kernel.raise ThreadError, 'killed thread' unless alive?
234
+ Thread.pass
235
+ end
236
+
237
+ alias wakeup run
238
+
239
+ def stop?
240
+ !alive? || status == 'sleep'
241
+ end
242
+
243
+ private
244
+ def init_core(*args, &blk)
245
+ @beam = LightIO::Beam.new(*args, &blk)
246
+ @beam.on_dead = proc {on_dead}
247
+ @beam.on_transfer = proc {|from, to| on_transfer(from, to)}
248
+ # register this thread
249
+ thread_values
250
+ # add self to ThreadGroup::Default
251
+ add_to_group(ThreadGroup::Default)
252
+ # remove thread and thread variables
253
+ ObjectSpace.define_finalizer(self, self.class.finalizer(self.object_id))
254
+ end
255
+
256
+ # add self to thread group
257
+ def add_to_group(group)
258
+ # remove from old group
259
+ remove_from_group
260
+ @group = group
261
+ @group.send(:threads) << self
262
+ end
263
+
264
+ # remove thread from group when dead
265
+ def remove_from_group
266
+ @group.send(:threads).delete(self) if @group
267
+ end
268
+
269
+ def on_dead
270
+ # release references
271
+ remove_from_group
272
+ end
273
+
274
+ def on_transfer(from, to)
275
+ Thread.instance_variable_set(:@current_thread, self)
276
+ end
277
+
278
+ def thread_values
279
+ Thread.send(:threads)[object_id] ||= {}
280
+ end
281
+
282
+ def fibers_and_values
283
+ @fibers_and_values ||= {}
284
+ end
285
+
286
+ def fiber_values
287
+ beam_or_fiber = LightIO::Beam.current
288
+ # only consider non-root fiber
289
+ if !beam_or_fiber.instance_of?(::Fiber) || LightIO::LightFiber.is_root?(beam_or_fiber)
290
+ beam_or_fiber = @beam
291
+ end
292
+ fibers_and_values[beam_or_fiber] ||= {}
293
+ end
294
+ end
295
+ end
@@ -0,0 +1,59 @@
1
+ require 'thwait'
2
+
3
+ module LightIO::Library
4
+ class ThreadsWait
5
+ ErrNoWaitingThread = ::ThreadsWait::ErrNoWaitingThread
6
+ ErrNoFinishedThread = ::ThreadsWait::ErrNoFinishedThread
7
+
8
+ attr_reader :threads
9
+
10
+ def initialize(*threads)
11
+ @threads = threads
12
+ end
13
+
14
+ def all_waits
15
+ until empty?
16
+ thr = next_wait
17
+ yield thr if block_given?
18
+ end
19
+ end
20
+
21
+ def empty?
22
+ @threads.empty?
23
+ end
24
+
25
+ def finished?
26
+ @threads.any? {|thr| !thr.alive?}
27
+ end
28
+
29
+ def join(*threads)
30
+ join_nowait(*threads)
31
+ next_wait
32
+ end
33
+
34
+ def join_nowait(*threads)
35
+ @threads.concat(threads)
36
+ end
37
+
38
+ def next_wait(nonblock=nil)
39
+ raise ThreadsWait::ErrNoWaitingThread, 'No threads for waiting.' if empty?
40
+ @threads.each do |thr|
41
+ if thr.alive? && nonblock
42
+ next
43
+ elsif thr.alive?
44
+ thr.join
45
+ end
46
+ # thr should dead
47
+ @threads.delete(thr)
48
+ return thr
49
+ end
50
+ raise ThreadsWait::ErrNoFinishedThread, 'No finished threads.'
51
+ end
52
+
53
+ class << self
54
+ def all_waits(*threads, &blk)
55
+ new(*threads).all_waits(&blk)
56
+ end
57
+ end
58
+ end
59
+ end
@@ -1,8 +1,11 @@
1
1
  require_relative 'library/queue'
2
+ require_relative 'library/sized_queue'
2
3
  require_relative 'library/kernel_ext'
3
4
  require_relative 'library/timeout'
4
5
  require_relative 'library/io'
5
6
  require_relative 'library/socket'
7
+ require_relative 'library/thread'
8
+ require_relative 'library/threads_wait'
6
9
 
7
10
  module LightIO
8
11
  # Library include modules can cooperative with LightIO::Beam
@@ -1,3 +1,3 @@
1
1
  module LightIO
2
- VERSION = "0.2.2"
2
+ VERSION = "0.3.0"
3
3
  end
@@ -21,12 +21,17 @@ module LightIO::Watchers
21
21
  @io = io
22
22
  @ioloop = LightIO::Core::IOloop.current
23
23
  @waiting = false
24
- # NIO monitor
25
- @monitor = @ioloop.add_io_wait(@io, interests) {callback_on_waiting}
26
24
  ObjectSpace.define_finalizer(self, self.class.finalizer(@monitor))
27
25
  @error = nil
28
26
  end
29
27
 
28
+ # NIO::Monitor
29
+ def monitor(interests=:rw)
30
+ @monitor ||= begin
31
+ @ioloop.add_io_wait(@io, interests) {callback_on_waiting}
32
+ end
33
+ end
34
+
30
35
  class << self
31
36
  def finalizer(monitor)
32
37
  proc {monitor.close if monitor && !monitor.close?}
@@ -34,8 +39,19 @@ module LightIO::Watchers
34
39
  end
35
40
 
36
41
  extend Forwardable
37
- def_delegators :@monitor, :interests, :interests=, :closed?, :readable?, :writable?, :writeable?
42
+ def_delegators :monitor, :interests, :interests=, :closed?
43
+
44
+ def readable?
45
+ check_monitor_read
46
+ monitor.readable?
47
+ end
38
48
 
49
+ def writable?
50
+ check_monitor_write
51
+ monitor.writable?
52
+ end
53
+
54
+ alias :writeable? :writable?
39
55
 
40
56
  # Blocking until io is readable
41
57
  # @param [Numeric] timeout return nil after timeout seconds, otherwise return self
@@ -53,9 +69,10 @@ module LightIO::Watchers
53
69
 
54
70
  def wait(timeout=nil, mode=:read)
55
71
  LightIO::Timeout.timeout(timeout) do
56
- interests = {read: :r, write: :w, read_write: :rw}[mode]
57
- self.interests = interests
58
- wait_in_ioloop
72
+ check_monitor(mode)
73
+ in_waiting(mode) do
74
+ wait_in_ioloop
75
+ end
59
76
  self
60
77
  end
61
78
  rescue Timeout::Error
@@ -64,7 +81,7 @@ module LightIO::Watchers
64
81
 
65
82
  def close
66
83
  return if closed?
67
- @monitor.close
84
+ monitor.close
68
85
  @error = IOError.new('closed stream')
69
86
  callback_on_waiting
70
87
  end
@@ -80,34 +97,70 @@ module LightIO::Watchers
80
97
  end
81
98
 
82
99
  private
100
+ def check_monitor(mode)
101
+ case mode
102
+ when :read
103
+ check_monitor_read
104
+ when :write
105
+ check_monitor_write
106
+ when :read_write
107
+ check_monitor_read_write
108
+ else
109
+ raise ArgumentError, "get unknown value #{mode}"
110
+ end
111
+ end
112
+
113
+ def check_monitor_read
114
+ if monitor(:r).interests == :w
115
+ monitor.interests = :rw
116
+ end
117
+ end
118
+
119
+ def check_monitor_write
120
+ if monitor(:w).interests == :r
121
+ monitor.interests = :rw
122
+ end
123
+ end
124
+
125
+ def check_monitor_read_write
126
+ if monitor(:rw).interests != :rw
127
+ monitor.interests = :rw
128
+ end
129
+ end
83
130
 
84
131
  # Blocking until io interests is satisfied
85
132
  def wait_in_ioloop
86
133
  raise LightIO::Error, "Watchers::IO can't cross threads" if @ioloop != LightIO::Core::IOloop.current
87
134
  raise EOFError, "can't wait closed IO watcher" if @monitor.closed?
88
- @waiting = true
89
135
  @ioloop.wait(self)
136
+ end
137
+
138
+ def in_waiting(mode)
139
+ @waiting = mode
140
+ yield
90
141
  @waiting = false
91
142
  end
92
143
 
93
144
  def callback_on_waiting
94
145
  # only call callback on waiting
95
- return unless @waiting && io_is_ready?
146
+ return unless io_is_ready?
96
147
  if @error
97
148
  # if error occurred in io waiting, send it to callback, see IOloop#wait
98
- callback.call(LightIO::Core::Beam::BeamError.new(@error))
149
+ callback&.call(LightIO::Core::Beam::BeamError.new(@error))
99
150
  else
100
- callback.call
151
+ callback&.call
101
152
  end
102
153
  end
103
154
 
104
155
  def io_is_ready?
105
- if interests == :r
156
+ return false unless @waiting
157
+ return true if closed?
158
+ if @waiting == :r
106
159
  readable?
107
- elsif interests == :w
108
- writeable?
160
+ elsif @waiting == :w
161
+ writable?
109
162
  else
110
- readable? || writeable?
163
+ readable? || writable?
111
164
  end
112
165
  end
113
166
  end
data/lib/lightio/wrap.rb CHANGED
@@ -85,26 +85,16 @@ module LightIO::Wrap
85
85
  #
86
86
  # @param [Symbol] method method name, example: wait_nonblock
87
87
  # @param [args] args arguments pass to method
88
- def wait_nonblock(method, *args, exception_symbol: true)
88
+ def wait_nonblock(method, *args)
89
89
  loop do
90
- begin
91
- result = if RUBY_VERSION > "2.3" && exception_symbol
92
- @io.__send__(method, *args, exception: false)
93
- else
94
- @io.__send__(method, *args)
95
- end
96
- case result
97
- when :wait_readable
98
- @io_watcher.wait_readable
99
- when :wait_writable
100
- @io_watcher.wait_writable
101
- else
102
- return result
103
- end
104
- rescue RAW_IO::WaitReadable
105
- @io_watcher.wait_readable
106
- rescue RAW_IO::WaitWritable
107
- @io_watcher.wait_writable
90
+ result = @io.__send__(method, *args, exception: false)
91
+ case result
92
+ when :wait_readable
93
+ @io_watcher.wait_readable
94
+ when :wait_writable
95
+ @io_watcher.wait_writable
96
+ else
97
+ return result
108
98
  end
109
99
  end
110
100
  end
@@ -115,14 +105,14 @@ module LightIO::Wrap
115
105
  # wrap blocking method with "#{method}_nonblock"
116
106
  #
117
107
  # @param [Symbol] method method name, example: wait
118
- def wrap_blocking_method(method, exception_symbol: true)
108
+ def wrap_blocking_method(method)
119
109
  define_method method do |*args|
120
- wait_nonblock(:"#{method}_nonblock", *args, exception_symbol: exception_symbol)
110
+ wait_nonblock(:"#{method}_nonblock", *args)
121
111
  end
122
112
  end
123
113
 
124
- def wrap_blocking_methods(*methods, exception_symbol: true)
125
- methods.each {|m| wrap_blocking_method(m, exception_symbol: exception_symbol)}
114
+ def wrap_blocking_methods(*methods)
115
+ methods.each {|m| wrap_blocking_method(m)}
126
116
  end
127
117
  end
128
118
 
data/lightio.gemspec CHANGED
@@ -21,7 +21,7 @@ Gem::Specification.new do |spec|
21
21
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
22
  spec.require_paths = ["lib"]
23
23
 
24
- spec.add_runtime_dependency "nio4r", "~> 2.1"
24
+ spec.add_runtime_dependency "nio4r", "~> 2.2"
25
25
  spec.add_development_dependency "bundler", "~> 1.16"
26
26
  spec.add_development_dependency "rake", "~> 10.0"
27
27
  spec.add_development_dependency "rspec", "~> 3.0"