io_unblock 0.1.1 → 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,10 +1,10 @@
1
1
  require 'thread'
2
- require "io_unblock/version"
3
-
4
- module IoUnblock
5
- class IoUnblockError < StandardError; end
6
- end
7
-
2
+ require 'socket'
3
+ require 'forwardable'
4
+ require 'io_unblock/version'
5
+ require 'io_unblock/errors'
8
6
  require 'io_unblock/delegation'
9
7
  require 'io_unblock/buffer'
10
8
  require 'io_unblock/stream'
9
+ require 'io_unblock/tcp_socket'
10
+
@@ -9,16 +9,16 @@ module IoUnblock
9
9
  @mutex = Mutex.new
10
10
  end
11
11
 
12
- def push bytes, cb
13
- synched { @buffer.push [bytes, cb] }
12
+ def push bytes, cb, cb_args
13
+ synched { @buffer.push [bytes, cb, cb_args] }
14
14
  end
15
15
 
16
16
  def pop
17
17
  synched { @buffer.pop }
18
18
  end
19
19
 
20
- def unshift bytes, cb
21
- synched { @buffer.unshift [bytes, cb] }
20
+ def unshift bytes, cb, cb_args
21
+ synched { @buffer.unshift [bytes, cb, cb_args] }
22
22
  end
23
23
 
24
24
  def shift
@@ -0,0 +1,4 @@
1
+ module IoUnblock
2
+ class IoUnblockError < StandardError; end
3
+ end
4
+
@@ -54,35 +54,55 @@ module IoUnblock
54
54
  end
55
55
 
56
56
  def stop
57
- @s_mutex.synchronize do
58
- if @running
59
- @running = false
60
- @processor.join
61
- end
57
+ if @processor == Thread.current
58
+ stop_inside
59
+ else
60
+ stop_outside
62
61
  end
63
62
  self
64
63
  end
65
64
 
66
65
  # The callback triggered here will be invoked only when all bytes
67
66
  # have been written.
68
- def write bytes, &cb
69
- @w_buff.push bytes, cb
67
+ def write bytes, *cb_args, &cb
68
+ @w_buff.push bytes, cb, cb_args
70
69
  self
71
70
  end
71
+
72
+ def alive?
73
+ @processor && @processor.alive?
74
+ end
72
75
 
73
76
  private
74
- def trigger_callbacks named, *args, &other
75
- other && other.call(*args)
76
- @callbacks.key?(named) && @callbacks[named].call(*args)
77
+ def stop_inside
78
+ @running = false
79
+ end
80
+
81
+ def stop_outside
82
+ @s_mutex.synchronize do
83
+ if @running
84
+ @running = false
85
+ @processor.join
86
+ end
87
+ end
88
+ end
89
+
90
+ def guard_callback named, cb, *args
91
+ cb && cb.call(*args)
77
92
  rescue Exception => ex
78
93
  if named == :callback_failed
79
94
  warn "Exception raised in callback_failed handler: #{ex}"
80
- ex.backtrace.each { |b| warn b }
95
+ warn "From: #{ex.backtrace.first}"
81
96
  else
82
97
  trigger_callbacks :callback_failed, ex, named
83
98
  end
84
99
  end
85
100
 
101
+ def trigger_callbacks named, *args, &other
102
+ guard_callback named, other, *args
103
+ guard_callback named, @callbacks[named], *args
104
+ end
105
+
86
106
  def flush_and_close
87
107
  _write while connected? && @w_buff.buffered?
88
108
  io_close
@@ -109,25 +129,26 @@ private
109
129
  def _write
110
130
  written = 0
111
131
  while written < MAX_BYTES_PER_WRITE
112
- bytes, cb = @w_buff.shift
132
+ bytes, cb, cb_args = @w_buff.shift
113
133
  break unless bytes
114
134
  begin
115
135
  w = io_write bytes
116
136
  rescue Errno::EINTR, Errno::EAGAIN, Errno::EWOULDBLOCK
117
137
  # writing will either block, or cannot otherwise be completed,
118
138
  # put data back and try again some other day
119
- @w_buff.unshift bytes, cb
139
+ @w_buff.unshift bytes, cb, cb_args
120
140
  break
121
141
  rescue Exception
122
142
  force_close $!
123
143
  break
124
144
  end
125
145
  written += w
146
+ trigger_callbacks :wrote, bytes, w
126
147
  if w < bytes.size
127
- @w_buff.unshift bytes[w..-1], cb
128
- trigger_callbacks :wrote, bytes, w
148
+ @w_buff.unshift bytes[w..-1], cb, cb_args
129
149
  else
130
- trigger_callbacks :wrote, bytes, w, &cb
150
+ # A separate callback invocation so we can pass custom args
151
+ guard_callback :wrote, cb, bytes, w, *cb_args
131
152
  end
132
153
  end
133
154
  end
@@ -0,0 +1,16 @@
1
+ module IoUnblock
2
+ class TcpSocket
3
+ extend Forwardable
4
+
5
+ def_delegators :@stream, :start, :stop, :callbacks, :write,
6
+ :running?, :connected?, :alive?, :select_delay, :select_delay=
7
+ attr_reader :host, :port
8
+
9
+ def initialize host, port, callbacks=nil
10
+ @host = host
11
+ @port = port
12
+ @socket = ::TCPSocket.new host, port
13
+ @stream = Stream.new @socket, callbacks
14
+ end
15
+ end
16
+ end
@@ -1,3 +1,3 @@
1
1
  module IoUnblock
2
- VERSION = "0.1.1"
2
+ VERSION = "0.1.2"
3
3
  end
@@ -6,41 +6,41 @@ describe IoUnblock::Buffer do
6
6
  end
7
7
 
8
8
  it "should be empty when nothing is buffered" do
9
- @buffer.push :a, 1
9
+ @buffer.push :a, 1, 'hello'
10
10
  @buffer.pop
11
11
  @buffer.empty?.must_equal true
12
12
  @buffer.buffered?.must_equal false
13
13
  end
14
14
 
15
15
  it "should not be empty when stuff is buffered" do
16
- @buffer.push :a, 1
16
+ @buffer.push :a, 1, 'hello'
17
17
  @buffer.empty?.must_equal false
18
18
  @buffer.buffered?.must_equal true
19
19
  end
20
20
 
21
21
  it "should push to the end of the buffer" do
22
- @buffer.push :a, 1
23
- @buffer.push :b, 2
24
- @buffer.first.must_equal [:a, 1]
25
- @buffer.last.must_equal [:b, 2]
22
+ @buffer.push :a, 1, 'hello'
23
+ @buffer.push :b, 2, 'world'
24
+ @buffer.first.must_equal [:a, 1, 'hello']
25
+ @buffer.last.must_equal [:b, 2, 'world']
26
26
  end
27
27
 
28
28
  it "should unshift to the beginning of the buffer" do
29
- @buffer.unshift :a, 1
30
- @buffer.unshift :b, 2
31
- @buffer.last.must_equal [:a, 1]
32
- @buffer.first.must_equal [:b, 2]
29
+ @buffer.unshift :a, 1, 'hello'
30
+ @buffer.unshift :b, 2, 'world'
31
+ @buffer.first.must_equal [:b, 2, 'world']
32
+ @buffer.last.must_equal [:a, 1, 'hello']
33
33
  end
34
34
 
35
35
  it "should shift from the beginning" do
36
- @buffer.push :a, 1
37
- @buffer.push :b, 2
38
- @buffer.shift.must_equal [:a, 1]
36
+ @buffer.push :a, 1, 'hello'
37
+ @buffer.push :b, 2, 'world'
38
+ @buffer.shift.must_equal [:a, 1, 'hello']
39
39
  end
40
40
 
41
41
  it "should pop from the end" do
42
- @buffer.push :a, 1
43
- @buffer.push :b, 2
44
- @buffer.pop.must_equal [:b, 2]
42
+ @buffer.push :a, 1, 'hello'
43
+ @buffer.push :b, 2, 'world'
44
+ @buffer.pop.must_equal [:b, 2, 'world']
45
45
  end
46
46
  end
@@ -1,8 +1,19 @@
1
1
  require File.expand_path("../../spec_helper.rb", __FILE__)
2
2
 
3
3
  describe IoUnblock::Stream do
4
+ include Stubz
4
5
  def dummy_io; @dummy_io ||= DummyIO.new; end
5
6
  def stream; @stream ||= IoUnblock::Stream.new dummy_io; end
7
+
8
+ # Watch for Thread exceptions
9
+ before do
10
+ @th_abort = Thread.abort_on_exception
11
+ Thread.abort_on_exception = true
12
+ end
13
+
14
+ after do
15
+ Thread.abort_on_exception = @th_abort
16
+ end
6
17
 
7
18
  it "raises an exception if started twice" do
8
19
  stream.start
@@ -179,6 +190,14 @@ describe IoUnblock::Stream do
179
190
  called_with.must_equal [ ['lo', 2] ]
180
191
  end
181
192
 
193
+ it "triggers the given callback after writing the full string with params" do
194
+ dummy_io.max_write = 3
195
+ stream.start
196
+ stream.write('hello', 'turbo', 'is', 'me', &callback)
197
+ stream.stop
198
+ called_with.must_equal [ ['lo', 2, 'turbo', 'is', 'me'] ]
199
+ end
200
+
182
201
  it "is not connected when failed is triggered" do
183
202
  is_connected = true
184
203
  cb_stream = callback_stream(
@@ -202,13 +221,29 @@ describe IoUnblock::Stream do
202
221
  end
203
222
 
204
223
  it "warns when triggering callback_failed raises an exception" do
224
+ warn_lines = []
205
225
  cb_err = RuntimeError.new 'failback!'
206
226
  cb_stream = callback_stream(
207
227
  :callback_failed => lambda { |*_| raise cb_err }
208
228
  )
229
+ stub(cb_stream, :warn) { |w| warn_lines << w }
209
230
  cb_stream.start
210
231
  cb_stream.write('another test') { raise cb_err }
211
232
  cb_stream.stop
233
+ warn_lines.first.must_equal 'Exception raised in callback_failed handler: failback!'
234
+ /\AFrom: /.must_match warn_lines.last
235
+ end
236
+
237
+ it "stops properly when called within the processor thread" do
238
+ stopped_called = false
239
+ cb_stream = callback_stream(
240
+ :looped => lambda { |s| sleep 0.25; s.stop },
241
+ :stopped => lambda { |s| stopped_called = true }
242
+ )
243
+ cb_stream.start
244
+ Thread.pass until cb_stream.running?
245
+ Thread.pass while cb_stream.alive?
246
+ stopped_called.must_equal true
212
247
  end
213
248
  end
214
249
  end
@@ -0,0 +1,70 @@
1
+ require File.expand_path("../../spec_helper.rb", __FILE__)
2
+
3
+ describe IoUnblock::TcpSocket do
4
+ include Stubz
5
+
6
+ def tcp_socket
7
+ @tcp_socket ||= IoUnblock::TcpSocket.new('host.name', 33, {:call => :back})
8
+ end
9
+
10
+ before do
11
+ @socket = MiniTest::Mock.new
12
+ @stream = MiniTest::Mock.new
13
+ stub(::TCPSocket, :new, @socket)
14
+ stub(::IoUnblock::Stream, :new, @stream)
15
+ end
16
+
17
+ describe "attribs" do
18
+ it "has a host" do
19
+ tcp_socket.host.must_equal 'host.name'
20
+ end
21
+
22
+ it "has a port" do
23
+ tcp_socket.port.must_equal 33
24
+ end
25
+ end
26
+
27
+ describe "delegation to the stream" do
28
+ it "delegates start" do
29
+ @stream.expect(:start, nil, [:some, :args])
30
+ tcp_socket.start :some, :args
31
+ @stream.verify
32
+ end
33
+
34
+ it "delegates stop" do
35
+ @stream.expect(:stop, nil, ['moar cowbell'])
36
+ tcp_socket.stop 'moar cowbell'
37
+ @stream.verify
38
+ end
39
+
40
+ it "delegates callbacks" do
41
+ @stream.expect(:callbacks, {:a => :hash})
42
+ tcp_socket.callbacks.must_equal({:a => :hash})
43
+ @stream.verify
44
+ end
45
+
46
+ it "delegates running?" do
47
+ @stream.expect(:running?, :generally_a_bool)
48
+ tcp_socket.running?.must_equal :generally_a_bool
49
+ @stream.verify
50
+ end
51
+
52
+ it "delegates connected?" do
53
+ @stream.expect(:connected?, :again_a_bool)
54
+ tcp_socket.connected?.must_equal :again_a_bool
55
+ @stream.verify
56
+ end
57
+
58
+ it "delegates select_delay" do
59
+ @stream.expect(:select_delay, 48)
60
+ tcp_socket.select_delay.must_equal 48
61
+ @stream.verify
62
+ end
63
+
64
+ it "delegates select_delay=" do
65
+ @stream.expect(:select_delay=, 19, [19])
66
+ tcp_socket.select_delay = 19
67
+ @stream.verify
68
+ end
69
+ end
70
+ end
@@ -11,74 +11,10 @@ begin
11
11
  require 'minitest/emoji'
12
12
  rescue LoadError
13
13
  end
14
- require 'io_unblock'
15
- require 'stringio'
16
14
 
17
- # Used to test IO stuff, using stringio objects for the write and
18
- # read stream.
19
- class DummyIO
20
- attr_reader :closed, :w_stream, :r_stream, :raised_write, :raised_read
21
- alias :closed? :closed
22
- alias :raised_write? :raised_write
23
- alias :raised_read? :raised_read
24
- attr_accessor :readable, :writeable
25
- attr_accessor :write_delay, :read_delay
26
- attr_accessor :max_write, :max_read
27
- attr_accessor :raise_read, :raise_write
28
- alias :readable? :readable
29
- alias :writeable? :writeable
30
-
31
- def initialize *args, &block
32
- @r_stream = StringIO.new
33
- @w_stream = StringIO.new
34
- @readable = @writeable = true
35
- @read_delay = @write_delay = 0
36
- @max_write = 0
37
- @max_read = 0
38
- @closed = false
39
- @raise_read = nil
40
- @raise_write = nil
41
- @raised_write = false
42
- @raised_read = false
43
- end
44
-
45
- def close
46
- @closed = true
47
- @w_stream.close
48
- @r_stream.close
49
- end
50
-
51
- def write_nonblock bytes
52
- sleep(@write_delay) if @write_delay > 0
53
- do_raise_write
54
- if @max_write > 0 && bytes.size > @max_write
55
- @w_stream.write bytes[0...@max_write]
56
- else
57
- @w_stream.write bytes
58
- end
59
- end
60
-
61
- def read_nonblock len
62
- sleep(@read_delay) if @read_delay > 0
63
- do_raise_read
64
- if @max_read > 0 && len > @max_read
65
- @r_stream.read @max_read
66
- else
67
- @r_stream.read len
68
- end
69
- end
15
+ Dir[File.expand_path('../support/*.rb', __FILE__)].each do |r|
16
+ require r
17
+ end
70
18
 
71
- def do_raise_write
72
- if @raise_write
73
- @raised_write = true
74
- raise @raise_write
75
- end
76
- end
19
+ require 'io_unblock'
77
20
 
78
- def do_raise_read
79
- if @raise_read
80
- @raised_read = true
81
- raise @raise_read
82
- end
83
- end
84
- end
@@ -0,0 +1,70 @@
1
+ require 'stringio'
2
+
3
+ # Used to test IO stuff, using stringio objects for the write and
4
+ # read stream.
5
+ class DummyIO
6
+ attr_reader :closed, :w_stream, :r_stream, :raised_write, :raised_read
7
+ alias :closed? :closed
8
+ alias :raised_write? :raised_write
9
+ alias :raised_read? :raised_read
10
+ attr_accessor :readable, :writeable
11
+ attr_accessor :write_delay, :read_delay
12
+ attr_accessor :max_write, :max_read
13
+ attr_accessor :raise_read, :raise_write
14
+ alias :readable? :readable
15
+ alias :writeable? :writeable
16
+
17
+ def initialize *args, &block
18
+ @r_stream = StringIO.new
19
+ @w_stream = StringIO.new
20
+ @readable = @writeable = true
21
+ @read_delay = @write_delay = 0
22
+ @max_write = 0
23
+ @max_read = 0
24
+ @closed = false
25
+ @raise_read = nil
26
+ @raise_write = nil
27
+ @raised_write = false
28
+ @raised_read = false
29
+ end
30
+
31
+ def close
32
+ @closed = true
33
+ @w_stream.close
34
+ @r_stream.close
35
+ end
36
+
37
+ def write_nonblock bytes
38
+ sleep(@write_delay) if @write_delay > 0
39
+ do_raise_write
40
+ if @max_write > 0 && bytes.size > @max_write
41
+ @w_stream.write bytes[0...@max_write]
42
+ else
43
+ @w_stream.write bytes
44
+ end
45
+ end
46
+
47
+ def read_nonblock len
48
+ sleep(@read_delay) if @read_delay > 0
49
+ do_raise_read
50
+ if @max_read > 0 && len > @max_read
51
+ @r_stream.read @max_read
52
+ else
53
+ @r_stream.read len
54
+ end
55
+ end
56
+
57
+ def do_raise_write
58
+ if @raise_write
59
+ @raised_write = true
60
+ raise @raise_write
61
+ end
62
+ end
63
+
64
+ def do_raise_read
65
+ if @raise_read
66
+ @raised_read = true
67
+ raise @raise_read
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,49 @@
1
+ module Stubz
2
+ def self.included base
3
+ base.__send__(:before) { _ensure_stubs_ }
4
+ base.__send__(:after) { _reset_stubs_ }
5
+ end
6
+
7
+ def stub_instance(klass, meth, *val, &block)
8
+ _do_stub_ klass, meth, *val, &block
9
+ end
10
+
11
+ def stub(obj, meth, *val, &block)
12
+ _do_stub_ get_meta_class(obj), meth, *val, &block
13
+ end
14
+
15
+ def get_meta_class klass
16
+ class << klass; self; end
17
+ end
18
+
19
+ def _do_stub_ klass, meth, *val, &block
20
+ if klass.method_defined? meth
21
+ aliased = "_stub_#{meth}_#{Time.now.to_i}_"
22
+ klass.send(:alias_method, aliased, meth)
23
+ else
24
+ aliased = nil
25
+ end
26
+ if block
27
+ klass.send(:define_method, meth, &block)
28
+ else
29
+ klass.send(:define_method, meth) do |*_|
30
+ val.first
31
+ end
32
+ end
33
+ @stubbies << [klass, meth, aliased]
34
+ end
35
+
36
+ def _ensure_stubs_
37
+ @stubbies = []
38
+ end
39
+
40
+ def _reset_stubs_
41
+ @stubbies.each do |(klass, meth, aliased)|
42
+ klass.send(:remove_method, meth)
43
+ if aliased
44
+ klass.send(:alias_method, meth, aliased)
45
+ klass.send(:remove_method, aliased)
46
+ end
47
+ end
48
+ end
49
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: io_unblock
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-03-13 00:00:00.000000000Z
12
+ date: 2012-03-15 00:00:00.000000000Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: minitest
16
- requirement: &2161186640 !ruby/object:Gem::Requirement
16
+ requirement: &2160167540 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :development
23
23
  prerelease: false
24
- version_requirements: *2161186640
24
+ version_requirements: *2160167540
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rake
27
- requirement: &2161185800 !ruby/object:Gem::Requirement
27
+ requirement: &2160165920 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,7 +32,7 @@ dependencies:
32
32
  version: '0'
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *2161185800
35
+ version_requirements: *2160165920
36
36
  description: Non-blocking IO reads/writes wrapped in a thread
37
37
  email:
38
38
  - ian.eccles@gmail.com
@@ -50,12 +50,17 @@ files:
50
50
  - lib/io_unblock.rb
51
51
  - lib/io_unblock/buffer.rb
52
52
  - lib/io_unblock/delegation.rb
53
+ - lib/io_unblock/errors.rb
53
54
  - lib/io_unblock/stream.rb
55
+ - lib/io_unblock/tcp_socket.rb
54
56
  - lib/io_unblock/version.rb
55
57
  - spec/io_unblock/buffer_spec.rb
56
58
  - spec/io_unblock/delegation_spec.rb
57
59
  - spec/io_unblock/stream_spec.rb
60
+ - spec/io_unblock/tcp_socket_spec.rb
58
61
  - spec/spec_helper.rb
62
+ - spec/support/dummy_io.rb
63
+ - spec/support/stubz.rb
59
64
  homepage: https://github.com/iande/io_unblock
60
65
  licenses: []
61
66
  post_install_message:
@@ -84,5 +89,8 @@ test_files:
84
89
  - spec/io_unblock/buffer_spec.rb
85
90
  - spec/io_unblock/delegation_spec.rb
86
91
  - spec/io_unblock/stream_spec.rb
92
+ - spec/io_unblock/tcp_socket_spec.rb
87
93
  - spec/spec_helper.rb
94
+ - spec/support/dummy_io.rb
95
+ - spec/support/stubz.rb
88
96
  has_rdoc: