lightio 0.2.2 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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"