celluloid-io 0.13.0.pre → 0.13.0.pre2

Sign up to get free protection for your applications and to get access to all the features.
data/.coveralls.yml ADDED
@@ -0,0 +1 @@
1
+ service-name: travis-pro
data/.travis.yml CHANGED
@@ -1,6 +1,7 @@
1
1
  script: rake ci
2
2
  rvm:
3
3
  - 1.9.3
4
+ - 2.0.0
4
5
  - ruby-head
5
6
  - jruby-19mode
6
7
  - rbx-19mode
@@ -8,8 +9,10 @@ rvm:
8
9
 
9
10
  matrix:
10
11
  allow_failures:
12
+ - rvm: 2.0.0 # not ready for primetime :(
11
13
  - rvm: ruby-head
12
14
  - rvm: jruby-head
15
+ - rvm: rbx-19mode
13
16
 
14
17
  notifications:
15
18
  irc: "irc.freenode.org#celluloid"
data/CHANGES.md CHANGED
@@ -1,7 +1,11 @@
1
- 0.13.0.pre
2
- ----------
1
+ 0.13.0.pre2
2
+ -----------
3
+ * Support for many, many more IO methods, particularly line-oriented
4
+ methods like #gets, #readline, and #readlines
3
5
  * Initial SSL support via Celluloid::IO::SSLSocket and
4
6
  Celluloid::IO::SSLServer
7
+ * Concurrent writes between tasks of the same actor are now coordinated
8
+ using Celluloid::Conditions instead of signals
5
9
  * Celluloid 0.13 compatibility fixes
6
10
 
7
11
  0.12.0
data/Gemfile CHANGED
@@ -1,6 +1,5 @@
1
- source :rubygems
2
-
3
- #gem 'celluloid', :git => 'git://github.com/celluloid/celluloid'
4
-
5
- # Specify your gem's dependencies in celluloid-io.gemspec
1
+ source 'https://rubygems.org'
6
2
  gemspec
3
+
4
+ gem 'coveralls', require: false
5
+ gem 'celluloid', github: 'celluloid/celluloid'
data/README.md CHANGED
@@ -1,8 +1,10 @@
1
1
  ![Celluloid](https://github.com/celluloid/celluloid-io/raw/master/logo.png)
2
2
  =============
3
+ [![Gem Version](https://badge.fury.io/rb/celluloid-io.png)](http://rubygems.org/gems/celluloid-io)
3
4
  [![Build Status](https://secure.travis-ci.org/celluloid/celluloid-io.png?branch=master)](http://travis-ci.org/celluloid/celluloid-io)
4
5
  [![Dependency Status](https://gemnasium.com/celluloid/celluloid-io.png)](https://gemnasium.com/celluloid/celluloid-io)
5
- [![Code Climate](https://codeclimate.com/badge.png)](https://codeclimate.com/github/celluloid/celluloid-io)
6
+ [![Code Climate](https://codeclimate.com/github/celluloid/celluloid-io.png)](https://codeclimate.com/github/celluloid/celluloid-io)
7
+ [![Coverage Status](https://coveralls.io/repos/celluloid/celluloid-io/badge.png?branch=master)](https://coveralls.io/r/celluloid/celluloid-io)
6
8
 
7
9
  You don't have to choose between threaded and evented IO! Celluloid::IO
8
10
  provides an event-driven IO system for building fast, scalable network
@@ -162,15 +164,13 @@ classes instead of the core Ruby TCPSocket and TCPServer classes.
162
164
  Status
163
165
  ------
164
166
 
165
- The rudiments of TCPServer and TCPSocket are in place and ready to use. It is now
167
+ The rudiments of TCPServer TCPSocket, and UNIXSocket are in place and ready to use. It is now
166
168
  fully nonblocking, including DNS resolution, which effectively makes Celluloid::IO
167
169
  feature complete as a nonblocking I/O system.
168
170
 
169
171
  Basic UDPSocket support is in place. On JRuby, recvfrom makes a blocking call
170
172
  as the underlying recvfrom_nonblock call is not supported by JRuby.
171
173
 
172
- No UNIXSocket support yet, sorry (patches welcome!)
173
-
174
174
  Contributing to Celluloid::IO
175
175
  -----------------------------
176
176
 
data/benchmarks/actor.rb CHANGED
@@ -2,14 +2,14 @@
2
2
 
3
3
  require 'rubygems'
4
4
  require 'bundler/setup'
5
- require 'celluloid'
5
+ require 'celluloid/io'
6
6
  require 'benchmark/ips'
7
7
 
8
8
  class ExampleActor
9
9
  include Celluloid::IO
10
10
 
11
11
  def initialize
12
- @condition = Condition.new
12
+ @condition = Celluloid::Condition.new
13
13
  end
14
14
 
15
15
  def example_method; end
@@ -24,13 +24,13 @@ class ExampleActor
24
24
  end
25
25
 
26
26
  example_actor = ExampleActor.new
27
- mailbox = Celluloid::Mailbox.new
27
+ mailbox = Celluloid::IO::Mailbox.new
28
28
 
29
29
  latch_in, latch_out = Queue.new, Queue.new
30
30
  latch = Thread.new do
31
31
  while true
32
32
  n = latch_in.pop
33
- for i in 0..n; mailbox.receive; end
33
+ for i in 0...n; mailbox.receive; end
34
34
  latch_out << :done
35
35
  end
36
36
  end
@@ -49,9 +49,12 @@ Benchmark.ips do |ips|
49
49
  waiter.value
50
50
  end
51
51
 
52
+ # Deadlocking o_O
53
+ =begin
52
54
  ips.report("messages") do |n|
53
55
  latch_in << n
54
- for i in 0..n; mailbox << :message; end
56
+ for i in 0...n; mailbox << :message; end
55
57
  latch_out.pop
56
58
  end
59
+ =end
57
60
  end
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
+ #
3
+ # Run this as: bundle exec examples/echo_server.rb
2
4
 
3
5
  require 'bundler/setup'
4
6
  require 'celluloid/io'
@@ -12,7 +14,7 @@ class EchoServer
12
14
  # Since we included Celluloid::IO, we're actually making a
13
15
  # Celluloid::IO::TCPServer here
14
16
  @server = TCPServer.new(host, port)
15
- run!
17
+ async.run
16
18
  end
17
19
 
18
20
  def finalize
@@ -20,7 +22,7 @@ class EchoServer
20
22
  end
21
23
 
22
24
  def run
23
- loop { handle_connection! @server.accept }
25
+ loop { async.handle_connection @server.accept }
24
26
  end
25
27
 
26
28
  def handle_connection(socket)
@@ -10,10 +10,11 @@ class EchoUNIXServer
10
10
  puts "*** start server #{socket_path}"
11
11
  @socket_path = socket_path
12
12
  @server = UNIXServer.open(socket_path)
13
+ async.run
13
14
  end
14
15
 
15
16
  def run
16
- loop { handle_connection! @server.accept }
17
+ loop { async.handle_connection @server.accept }
17
18
  end
18
19
 
19
20
  def handle_connection(socket)
@@ -39,5 +40,6 @@ class EchoUNIXServer
39
40
 
40
41
  end
41
42
 
42
- s = EchoUNIXServer.new("/tmp/sock_test")
43
- s.run
43
+ supervisor = EchoUNIXServer.supervise("/tmp/sock_test")
44
+ trap("INT") { supervisor.terminate; exit }
45
+ sleep
@@ -25,7 +25,7 @@ module Celluloid
25
25
  ssl = Celluloid::IO::SSLSocket.new(sock, @ctx)
26
26
  ssl.accept if @start_immediately
27
27
  ssl
28
- rescue SSLError
28
+ rescue OpenSSL::SSL::SSLError
29
29
  sock.close
30
30
  raise
31
31
  end
@@ -3,14 +3,14 @@ require 'openssl'
3
3
  module Celluloid
4
4
  module IO
5
5
  # SSLSocket with Celluloid::IO support
6
- class SSLSocket
7
- include CommonMethods
6
+ class SSLSocket < Stream
8
7
  extend Forwardable
9
8
 
10
9
  def_delegators :@socket, :read_nonblock, :write_nonblock, :close, :closed?,
11
10
  :cert, :cipher, :client_ca, :peer_cert, :peer_cert_chain, :verify_result
12
11
 
13
12
  def initialize(io, ctx = OpenSSL::SSL::SSLContext.new)
13
+ super()
14
14
  @context = ctx
15
15
  @socket = OpenSSL::SSL::SSLSocket.new(::IO.try_convert(io), @context)
16
16
  end
@@ -0,0 +1,422 @@
1
+ # Partially adapted from Ruby's OpenSSL::Buffering
2
+ # Originally from the 'OpenSSL for Ruby 2' project
3
+ # Copyright (C) 2001 GOTOU YUUZOU <gotoyuzo@notwork.org>
4
+ # All rights reserved.
5
+ #
6
+ # This program is licenced under the same licence as Ruby.
7
+
8
+ module Celluloid
9
+ module IO
10
+ # Base class of all streams in Celluloid::IO
11
+ class Stream
12
+ include Enumerable
13
+
14
+ # The "sync mode" of the stream
15
+ #
16
+ # See IO#sync for full details.
17
+ attr_accessor :sync
18
+
19
+ # Default size to read from or write to the stream for buffer operations
20
+ BLOCK_SIZE = 1024*16
21
+
22
+ def initialize
23
+ @eof = false
24
+ @sync = true # FIXME: hax
25
+ @read_buffer = ''.force_encoding(Encoding::ASCII_8BIT)
26
+ @write_buffer = ''.force_encoding(Encoding::ASCII_8BIT)
27
+
28
+ @read_latch = Latch.new
29
+ @write_latch = Latch.new
30
+ end
31
+
32
+ # Are we inside of a Celluloid::IO actor?
33
+ def evented?
34
+ actor = Thread.current[:celluloid_actor]
35
+ actor && actor.mailbox.is_a?(Celluloid::IO::Mailbox)
36
+ end
37
+
38
+ # Wait until the current object is readable
39
+ def wait_readable
40
+ if evented?
41
+ Celluloid.current_actor.wait_readable(self.to_io)
42
+ else
43
+ Kernel.select([self.to_io])
44
+ end
45
+ end
46
+
47
+ # Wait until the current object is writable
48
+ def wait_writable
49
+ if evented?
50
+ Celluloid.current_actor.wait_writable(self.to_io)
51
+ else
52
+ Kernel.select([], [self.to_io])
53
+ end
54
+ end
55
+
56
+ # System read via the nonblocking subsystem
57
+ def sysread(length = nil, buffer = nil)
58
+ buffer ||= ''.force_encoding(Encoding::ASCII_8BIT)
59
+
60
+ @read_latch.synchronize do
61
+ begin
62
+ read_nonblock(length, buffer)
63
+ rescue ::IO::WaitReadable
64
+ wait_readable
65
+ retry
66
+ end
67
+ end
68
+
69
+ buffer
70
+ end
71
+
72
+ # System write via the nonblocking subsystem
73
+ def syswrite(string)
74
+ length = string.length
75
+ total_written = 0
76
+
77
+ remaining = string
78
+
79
+ @write_latch.synchronize do
80
+ while total_written < length
81
+ begin
82
+ written = write_nonblock(remaining)
83
+ rescue ::IO::WaitWritable
84
+ wait_writable
85
+ retry
86
+ rescue EOFError
87
+ return total_written
88
+ end
89
+
90
+ total_written += written
91
+
92
+ # FIXME: mutating the original buffer here. Seems bad.
93
+ remaining.slice!(0, written) if written < remaining.length
94
+ end
95
+ end
96
+
97
+ total_written
98
+ end
99
+
100
+ # Reads +size+ bytes from the stream. If +buf+ is provided it must
101
+ # reference a string which will receive the data.
102
+ #
103
+ # See IO#read for full details.
104
+ def read(size=nil, buf=nil)
105
+ if size == 0
106
+ if buf
107
+ buf.clear
108
+ return buf
109
+ else
110
+ return ""
111
+ end
112
+ end
113
+
114
+ until @eof
115
+ break if size && size <= @read_buffer.size
116
+ fill_rbuff
117
+ break unless size
118
+ end
119
+
120
+ ret = consume_rbuff(size) || ""
121
+
122
+ if buf
123
+ buf.replace(ret)
124
+ ret = buf
125
+ end
126
+
127
+ (size && ret.empty?) ? nil : ret
128
+ end
129
+
130
+ # Reads at most +maxlen+ bytes from the stream. If +buf+ is provided it
131
+ # must reference a string which will receive the data.
132
+ #
133
+ # See IO#readpartial for full details.
134
+ def readpartial(maxlen, buf=nil)
135
+ if maxlen == 0
136
+ if buf
137
+ buf.clear
138
+ return buf
139
+ else
140
+ return ""
141
+ end
142
+ end
143
+
144
+ if @read_buffer.empty?
145
+ begin
146
+ return sysread(maxlen, buf)
147
+ rescue Errno::EAGAIN
148
+ retry
149
+ end
150
+ end
151
+
152
+ ret = consume_rbuff(maxlen)
153
+
154
+ if buf
155
+ buf.replace(ret)
156
+ ret = buf
157
+ end
158
+
159
+ raise EOFError if ret.empty?
160
+ ret
161
+ end
162
+
163
+ # Reads the next "line+ from the stream. Lines are separated by +eol+. If
164
+ # +limit+ is provided the result will not be longer than the given number of
165
+ # bytes.
166
+ #
167
+ # +eol+ may be a String or Regexp.
168
+ #
169
+ # Unlike IO#gets the line read will not be assigned to +$_+.
170
+ #
171
+ # Unlike IO#gets the separator must be provided if a limit is provided.
172
+ def gets(eol=$/, limit=nil)
173
+ idx = @read_buffer.index(eol)
174
+
175
+ until @eof
176
+ break if idx
177
+ fill_rbuff
178
+ idx = @read_buffer.index(eol)
179
+ end
180
+
181
+ if eol.is_a?(Regexp)
182
+ size = idx ? idx+$&.size : nil
183
+ else
184
+ size = idx ? idx+eol.size : nil
185
+ end
186
+
187
+ if limit and limit >= 0
188
+ size = [size, limit].min
189
+ end
190
+
191
+ consume_rbuff(size)
192
+ end
193
+
194
+ # Executes the block for every line in the stream where lines are separated
195
+ # by +eol+.
196
+ #
197
+ # See also #gets
198
+ def each(eol=$/)
199
+ while line = self.gets(eol)
200
+ yield line
201
+ end
202
+ end
203
+ alias each_line each
204
+
205
+ # Reads lines from the stream which are separated by +eol+.
206
+ #
207
+ # See also #gets
208
+ def readlines(eol=$/)
209
+ ary = []
210
+ ary << line while line = self.gets(eol)
211
+ ary
212
+ end
213
+
214
+ # Reads a line from the stream which is separated by +eol+.
215
+ #
216
+ # Raises EOFError if at end of file.
217
+ def readline(eol=$/)
218
+ raise EOFError if eof?
219
+ gets(eol)
220
+ end
221
+
222
+ # Reads one character from the stream. Returns nil if called at end of
223
+ # file.
224
+ def getc
225
+ read(1)
226
+ end
227
+
228
+ # Calls the given block once for each byte in the stream.
229
+ def each_byte # :yields: byte
230
+ yield(c.ord) while c = getc
231
+ end
232
+
233
+ # Reads a one-character string from the stream. Raises an EOFError at end
234
+ # of file.
235
+ def readchar
236
+ raise EOFError if eof?
237
+ getc
238
+ end
239
+
240
+ # Pushes character +c+ back onto the stream such that a subsequent buffered
241
+ # character read will return it.
242
+ #
243
+ # Unlike IO#getc multiple bytes may be pushed back onto the stream.
244
+ #
245
+ # Has no effect on unbuffered reads (such as #sysread).
246
+ def ungetc(c)
247
+ @read_buffer[0,0] = c.chr
248
+ end
249
+
250
+ # Returns true if the stream is at file which means there is no more data to
251
+ # be read.
252
+ def eof?
253
+ fill_rbuff if !@eof && @read_buffer.empty?
254
+ @eof && @read_buffer.empty?
255
+ end
256
+ alias eof eof?
257
+
258
+ # Writes +s+ to the stream. If the argument is not a string it will be
259
+ # converted using String#to_s. Returns the number of bytes written.
260
+ def write(s)
261
+ do_write(s)
262
+ s.bytesize
263
+ end
264
+
265
+ # Writes +s+ to the stream. +s+ will be converted to a String using
266
+ # String#to_s.
267
+ def << (s)
268
+ do_write(s)
269
+ self
270
+ end
271
+
272
+ # Writes +args+ to the stream along with a record separator.
273
+ #
274
+ # See IO#puts for full details.
275
+ def puts(*args)
276
+ s = ""
277
+ if args.empty?
278
+ s << "\n"
279
+ end
280
+
281
+ args.each do |arg|
282
+ s << arg.to_s
283
+ if $/ && /\n\z/ !~ s
284
+ s << "\n"
285
+ end
286
+ end
287
+
288
+ do_write(s)
289
+ nil
290
+ end
291
+
292
+ # Writes +args+ to the stream.
293
+ #
294
+ # See IO#print for full details.
295
+ def print(*args)
296
+ s = ""
297
+ args.each { |arg| s << arg.to_s }
298
+ do_write(s)
299
+ nil
300
+ end
301
+
302
+ # Formats and writes to the stream converting parameters under control of
303
+ # the format string.
304
+ #
305
+ # See Kernel#sprintf for format string details.
306
+ def printf(s, *args)
307
+ do_write(s % args)
308
+ nil
309
+ end
310
+
311
+ # Flushes buffered data to the stream.
312
+ def flush
313
+ osync = @sync
314
+ @sync = true
315
+ do_write ""
316
+ return self
317
+ ensure
318
+ @sync = osync
319
+ end
320
+
321
+ # Closes the stream and flushes any unwritten data.
322
+ def close
323
+ flush rescue nil
324
+ sysclose
325
+ end
326
+
327
+ #######
328
+ private
329
+ #######
330
+
331
+ # Fills the buffer from the underlying stream
332
+ def fill_rbuff
333
+ begin
334
+ @read_buffer << sysread(BLOCK_SIZE)
335
+ rescue Errno::EAGAIN
336
+ retry
337
+ rescue EOFError
338
+ @eof = true
339
+ end
340
+ end
341
+
342
+ # Consumes +size+ bytes from the buffer
343
+ def consume_rbuff(size=nil)
344
+ if @read_buffer.empty?
345
+ nil
346
+ else
347
+ size = @read_buffer.size unless size
348
+ ret = @read_buffer[0, size]
349
+ @read_buffer[0, size] = ""
350
+ ret
351
+ end
352
+ end
353
+
354
+ # Writes +s+ to the buffer. When the buffer is full or #sync is true the
355
+ # buffer is flushed to the underlying stream.
356
+ def do_write(s)
357
+ @write_buffer << s
358
+ @write_buffer.force_encoding(Encoding::BINARY)
359
+ @sync ||= false
360
+
361
+ if @sync or @write_buffer.size > BLOCK_SIZE or idx = @write_buffer.rindex($/)
362
+ remain = idx ? idx + $/.size : @write_buffer.length
363
+ nwritten = 0
364
+
365
+ while remain > 0
366
+ str = @write_buffer[nwritten,remain]
367
+ begin
368
+ nwrote = syswrite(str)
369
+ rescue Errno::EAGAIN
370
+ retry
371
+ end
372
+ remain -= nwrote
373
+ nwritten += nwrote
374
+ end
375
+
376
+ @write_buffer[0,nwritten] = ""
377
+ end
378
+ end
379
+
380
+ # Perform an operation exclusively, uncontested by other tasks
381
+ class Latch
382
+ def initialize
383
+ @owner = nil
384
+ @waiters = 0
385
+ @condition = Celluloid::Condition.new
386
+ end
387
+
388
+ # Synchronize an operation across all tasks in the current actor
389
+ def synchronize
390
+ actor = Thread.current[:celluloid_actor]
391
+ return yield unless actor
392
+
393
+ # Silently acquire ownership at the actor level. This method should be
394
+ # replaced with an ownership system similar to conditions if this code
395
+ # is ever extracted into Celluloid itself
396
+ if @condition.owner != actor.proxy
397
+ @condition.owner = actor.proxy
398
+ @waiters = 0
399
+ @owner = nil
400
+ end
401
+
402
+ if @owner || @waiters > 0
403
+ @waiters += 1
404
+ @condition.wait
405
+ @waiters -= 1
406
+ end
407
+
408
+ @owner = Task.current
409
+
410
+ begin
411
+ ret = yield
412
+ ensure
413
+ @owner = nil
414
+ @condition.signal if @waiters > 0
415
+ end
416
+
417
+ ret
418
+ end
419
+ end
420
+ end
421
+ end
422
+ end
@@ -4,25 +4,49 @@ require 'resolv'
4
4
  module Celluloid
5
5
  module IO
6
6
  # TCPSocket with combined blocking and evented support
7
- class TCPSocket
8
- include CommonMethods
7
+ class TCPSocket < Stream
9
8
  extend Forwardable
10
9
 
11
10
  def_delegators :@socket, :read_nonblock, :write_nonblock, :close, :closed?
12
11
  def_delegators :@socket, :addr, :peeraddr, :setsockopt
13
12
 
13
+ # Open a TCP socket, yielding it to the given block and closing it
14
+ # automatically when done (if a block is given)
15
+ def self.open(*args, &block)
16
+ sock = new(*args)
17
+
18
+ if block_given?
19
+ begin
20
+ yield sock
21
+ ensure
22
+ sock.close
23
+ end
24
+ end
25
+
26
+ sock
27
+ end
28
+
14
29
  # Convert a Ruby TCPSocket into a Celluloid::IO::TCPSocket
30
+ # DEPRECATED: to be removed in a future release
15
31
  def self.from_ruby_socket(ruby_socket)
16
- # Some hax here, but whatever ;)
17
- socket = allocate
18
- socket.instance_variable_set(:@socket, ruby_socket)
19
- socket
32
+ new(ruby_socket)
20
33
  end
21
34
 
22
35
  # Opens a TCP connection to remote_host on remote_port. If local_host
23
36
  # and local_port are specified, then those parameters are used on the
24
37
  # local end to establish the connection.
25
- def initialize(remote_host, remote_port, local_host = nil, local_port = nil)
38
+ def initialize(remote_host, remote_port = nil, local_host = nil, local_port = nil)
39
+ super()
40
+
41
+ # Allow users to pass in a Ruby TCPSocket directly
42
+ if remote_host.is_a? ::TCPSocket
43
+ @addr = nil
44
+ @socket = remote_host
45
+ return
46
+ elsif remote_port.nil?
47
+ raise ArgumentError, "wrong number of arguments (1 for 2)"
48
+ end
49
+
26
50
  # Is it an IPv4 address?
27
51
  begin
28
52
  @addr = Resolv::IPv4.create(remote_host)
@@ -3,27 +3,32 @@ require 'socket'
3
3
  module Celluloid
4
4
  module IO
5
5
  # UNIXSocket with combined blocking and evented support
6
- class UNIXSocket
7
- include CommonMethods
6
+ class UNIXSocket < Stream
8
7
  extend Forwardable
9
8
 
10
9
  def_delegators :@socket, :read_nonblock, :write_nonblock, :close, :closed?, :readline, :puts, :addr
11
10
 
12
- # Convert a Ruby UNIXSocket into a Celluloid::IO::UNIXSocket
13
- def self.from_ruby_socket(ruby_socket)
14
- # Some hax here, but whatever ;)
15
- socket = allocate
16
- socket.instance_variable_set(:@socket, ruby_socket)
17
- socket
18
- end
19
-
20
11
  # Open a UNIX connection.
21
12
  def self.open(socket_path, &block)
22
13
  self.new(socket_path, &block)
23
14
  end
24
15
 
16
+ # Convert a Ruby UNIXSocket into a Celluloid::IO::UNIXSocket
17
+ # DEPRECATED: to be removed in a future release
18
+ def self.from_ruby_socket(ruby_socket)
19
+ new(ruby_socket)
20
+ end
21
+
25
22
  # Open a UNIX connection.
26
23
  def initialize(socket_path, &block)
24
+ super()
25
+
26
+ # Allow users to pass in a Ruby UNIXSocket directly
27
+ if socket_path.is_a? ::UNIXSocket
28
+ @socket = socket_path
29
+ return
30
+ end
31
+
27
32
  # FIXME: not doing non-blocking connect
28
33
  @socket = if block
29
34
  ::UNIXSocket.open(socket_path, &block)
@@ -1,5 +1,5 @@
1
1
  module Celluloid
2
2
  module IO
3
- VERSION = "0.13.0.pre"
3
+ VERSION = "0.13.0.pre2"
4
4
  end
5
5
  end
data/lib/celluloid/io.rb CHANGED
@@ -2,10 +2,10 @@ require 'forwardable'
2
2
  require 'celluloid/io/version'
3
3
 
4
4
  require 'celluloid'
5
- require 'celluloid/io/common_methods'
6
5
  require 'celluloid/io/dns_resolver'
7
6
  require 'celluloid/io/mailbox'
8
7
  require 'celluloid/io/reactor'
8
+ require 'celluloid/io/stream'
9
9
 
10
10
  require 'celluloid/io/tcp_server'
11
11
  require 'celluloid/io/tcp_socket'
@@ -14,7 +14,23 @@ describe Celluloid::IO::SSLSocket do
14
14
  end
15
15
  end
16
16
 
17
- let(:client) { TCPSocket.new example_addr, example_ssl_port }
17
+ let(:client) do
18
+ remaining_attempts = 3
19
+
20
+ begin
21
+ TCPSocket.new example_addr, example_ssl_port
22
+ rescue Errno::ECONNREFUSED
23
+ # HAX: sometimes this fails to connect? o_O
24
+ # This is quite likely due to the Thread.pass style spinlocks for startup
25
+ raise if remaining_attempts < 1
26
+ remaining_attempts -= 1
27
+
28
+ # Seems gimpy, but sleep and retry
29
+ sleep 0.1
30
+ retry
31
+ end
32
+ end
33
+
18
34
  let(:ssl_client) { Celluloid::IO::SSLSocket.new client, client_context }
19
35
 
20
36
  let(:server_cert) { OpenSSL::X509::Certificate.new fixture_dir.join("server.crt").read }
@@ -31,6 +47,7 @@ describe Celluloid::IO::SSLSocket do
31
47
  let(:server_thread) do
32
48
  Thread.new { ssl_server.accept }.tap do |thread|
33
49
  Thread.pass while thread.status && thread.status != "sleep"
50
+ thread.join unless thread.status
34
51
  end
35
52
  end
36
53
 
@@ -38,6 +55,7 @@ describe Celluloid::IO::SSLSocket do
38
55
  let(:raw_server_thread) do
39
56
  Thread.new { celluloid_server.accept }.tap do |thread|
40
57
  Thread.pass while thread.status && thread.status != "sleep"
58
+ thread.join unless thread.status
41
59
  end
42
60
  end
43
61
 
@@ -160,13 +178,14 @@ describe Celluloid::IO::SSLSocket do
160
178
  end
161
179
 
162
180
  def with_ssl_sockets
163
- thread = server_thread
181
+ server_thread
164
182
  ssl_client.connect
165
183
 
166
184
  begin
167
- ssl_peer = thread.value
185
+ ssl_peer = server_thread.value
168
186
  yield ssl_client, ssl_peer
169
187
  ensure
188
+ server_thread.join
170
189
  ssl_server.close
171
190
  ssl_client.close
172
191
  ssl_peer.close
@@ -174,16 +193,17 @@ describe Celluloid::IO::SSLSocket do
174
193
  end
175
194
 
176
195
  def with_raw_sockets
177
- server_thread = raw_server_thread
178
- raw_client = client
196
+ raw_server_thread
197
+ client
179
198
 
180
199
  begin
181
- raw_peer = server_thread.value
182
- yield raw_client, raw_peer
200
+ peer = raw_server_thread.value
201
+ yield client, peer
183
202
  ensure
203
+ raw_server_thread.join
184
204
  celluloid_server.close
185
- raw_client.close
186
- raw_peer.close
205
+ client.close
206
+ peer.close
187
207
  end
188
208
  end
189
209
  end
@@ -45,10 +45,10 @@ describe Celluloid::IO::TCPSocket do
45
45
  end
46
46
  end
47
47
 
48
- it "reads data in ASCII-8BIT encoding" do
48
+ it "reads data in binary encoding" do
49
49
  with_connected_sockets do |subject, peer|
50
50
  peer << payload
51
- within_io_actor { subject.read(payload.size).encoding }.should eq Encoding::ASCII_8BIT
51
+ within_io_actor { subject.read(payload.size).encoding }.should eq Encoding::BINARY
52
52
  end
53
53
  end
54
54
 
@@ -59,10 +59,10 @@ describe Celluloid::IO::TCPSocket do
59
59
  end
60
60
  end
61
61
 
62
- it "reads partial data in ASCII-8BIT encoding" do
62
+ it "reads partial data in binary encoding" do
63
63
  with_connected_sockets do |subject, peer|
64
64
  peer << payload * 2
65
- within_io_actor { subject.readpartial(payload.size).encoding }.should eq Encoding::ASCII_8BIT
65
+ within_io_actor { subject.readpartial(payload.size).encoding }.should eq Encoding::BINARY
66
66
  end
67
67
  end
68
68
 
@@ -90,7 +90,7 @@ describe Celluloid::IO::TCPSocket do
90
90
  end
91
91
 
92
92
  it "raises IOError when active sockets are closed across threads" do
93
- pending "not implemented :("
93
+ pending "not implemented"
94
94
 
95
95
  with_connected_sockets do |subject, peer|
96
96
  actor = ExampleActor.new
@@ -50,10 +50,10 @@ describe Celluloid::IO::UNIXSocket do
50
50
  end
51
51
  end
52
52
 
53
- it "reads data in ASCII-8BIT encoding" do
53
+ it "reads data in binary encoding" do
54
54
  with_connected_unix_sockets do |subject, peer|
55
55
  peer << payload
56
- within_io_actor { subject.read(payload.size).encoding }.should eq Encoding::ASCII_8BIT
56
+ within_io_actor { subject.read(payload.size).encoding }.should eq Encoding::BINARY
57
57
  end
58
58
  end
59
59
 
@@ -64,10 +64,10 @@ describe Celluloid::IO::UNIXSocket do
64
64
  end
65
65
  end
66
66
 
67
- it "reads partial data in ASCII-8BIT encoding" do
67
+ it "reads partial data in binary encoding" do
68
68
  with_connected_unix_sockets do |subject, peer|
69
69
  peer << payload * 2
70
- within_io_actor { subject.readpartial(payload.size).encoding }.should eq Encoding::ASCII_8BIT
70
+ within_io_actor { subject.readpartial(payload.size).encoding }.should eq Encoding::BINARY
71
71
  end
72
72
  end
73
73
 
data/spec/spec_helper.rb CHANGED
@@ -2,10 +2,19 @@ require 'rubygems'
2
2
  require 'bundler/setup'
3
3
  require 'celluloid/io'
4
4
  require 'celluloid/rspec'
5
+ require 'coveralls'
6
+ Coveralls.wear!
5
7
 
6
8
  logfile = File.open(File.expand_path("../../log/test.log", __FILE__), 'a')
7
9
  Celluloid.logger = Logger.new(logfile)
8
10
 
11
+ # FIXME: Hax until test termination can be cleaned up
12
+ module Celluloid
13
+ class << self
14
+ def shutdown; end # hax: noop!
15
+ end
16
+ end
17
+
9
18
  class ExampleActor
10
19
  include Celluloid::IO
11
20
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: celluloid-io
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.13.0.pre
4
+ version: 0.13.0.pre2
5
5
  prerelease: 7
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-02-14 00:00:00.000000000 Z
12
+ date: 2013-03-17 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: celluloid
@@ -130,6 +130,7 @@ executables: []
130
130
  extensions: []
131
131
  extra_rdoc_files: []
132
132
  files:
133
+ - .coveralls.yml
133
134
  - .gitignore
134
135
  - .rspec
135
136
  - .travis.yml
@@ -146,12 +147,12 @@ files:
146
147
  - examples/echo_unix_client.rb
147
148
  - examples/echo_unix_server.rb
148
149
  - lib/celluloid/io.rb
149
- - lib/celluloid/io/common_methods.rb
150
150
  - lib/celluloid/io/dns_resolver.rb
151
151
  - lib/celluloid/io/mailbox.rb
152
152
  - lib/celluloid/io/reactor.rb
153
153
  - lib/celluloid/io/ssl_server.rb
154
154
  - lib/celluloid/io/ssl_socket.rb
155
+ - lib/celluloid/io/stream.rb
155
156
  - lib/celluloid/io/tcp_server.rb
156
157
  - lib/celluloid/io/tcp_socket.rb
157
158
  - lib/celluloid/io/udp_socket.rb
@@ -191,7 +192,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
191
192
  version: '0'
192
193
  segments:
193
194
  - 0
194
- hash: -662471971010224791
195
+ hash: -1245022308244925467
195
196
  required_rubygems_version: !ruby/object:Gem::Requirement
196
197
  none: false
197
198
  requirements:
@@ -1,146 +0,0 @@
1
- module Celluloid
2
- module IO
3
- # Common implementations of methods originall from the IO class
4
- module CommonMethods
5
- # Are we inside of a Celluloid::IO actor?
6
- def evented?
7
- actor = Thread.current[:celluloid_actor]
8
- actor && actor.mailbox.is_a?(Celluloid::IO::Mailbox)
9
- end
10
-
11
- # Wait until the current object is readable
12
- def wait_readable
13
- if evented?
14
- Celluloid.current_actor.wait_readable(self.to_io)
15
- else
16
- Kernel.select([self.to_io])
17
- end
18
- end
19
-
20
- # Wait until the current object is writable
21
- def wait_writable
22
- if evented?
23
- Celluloid.current_actor.wait_writable(self.to_io)
24
- else
25
- Kernel.select([], [self.to_io])
26
- end
27
- end
28
-
29
- # Request exclusive control for a particular operation
30
- # Type should be one of :r (read) or :w (write)
31
- def acquire_ownership(type)
32
- return unless Thread.current[:celluloid_actor]
33
-
34
- case type
35
- when :r
36
- ivar = :@read_owner
37
- when :w
38
- ivar = :@write_owner
39
- else raise ArgumentError, "invalid ownership type: #{type}"
40
- end
41
-
42
- # Celluloid needs a better API here o_O
43
- Thread.current[:celluloid_actor].wait(self) while instance_variable_defined?(ivar) && instance_variable_get(ivar)
44
- instance_variable_set(ivar, Task.current)
45
- end
46
-
47
- # Release ownership for a particular operation
48
- # Type should be one of :r (read) or :w (write)
49
- def release_ownership(type)
50
- return unless Thread.current[:celluloid_actor]
51
-
52
- case type
53
- when :r
54
- ivar = :@read_owner
55
- when :w
56
- ivar = :@write_owner
57
- else raise ArgumentError, "invalid ownership type: #{type}"
58
- end
59
-
60
- raise "not owner" unless instance_variable_defined?(ivar) && instance_variable_get(ivar) == Task.current
61
- instance_variable_set(ivar, nil)
62
- Thread.current[:celluloid_actor].signal(self)
63
- end
64
-
65
- def read(length = nil, buffer = nil)
66
- buffer ||= ''.force_encoding(Encoding::ASCII_8BIT)
67
- remaining = length
68
-
69
- acquire_ownership :r
70
- begin
71
- if length
72
- until remaining.zero?
73
- begin
74
- str = readpartial(remaining)
75
- rescue EOFError
76
- return if length == remaining
77
- return buffer
78
- end
79
-
80
- buffer << str
81
- remaining -= str.length
82
- end
83
- else
84
- while true
85
- begin
86
- buffer << read_nonblock(Socket::SO_RCVBUF)
87
- rescue Errno::EAGAIN, EOFError
88
- return buffer
89
- end
90
- end
91
- end
92
- ensure
93
- release_ownership :r
94
- end
95
-
96
- buffer
97
- end
98
-
99
- def readpartial(length, buffer = nil)
100
- buffer ||= ''.force_encoding(Encoding::ASCII_8BIT)
101
-
102
- begin
103
- read_nonblock(length, buffer)
104
- rescue ::IO::WaitReadable
105
- wait_readable
106
- retry
107
- end
108
-
109
- buffer
110
- end
111
-
112
- def write(string)
113
- length = string.length
114
- total_written = 0
115
-
116
- remaining = string
117
- acquire_ownership :w
118
-
119
- begin
120
- while total_written < length
121
- begin
122
- written = write_nonblock(remaining)
123
- rescue ::IO::WaitWritable
124
- wait_writable
125
- retry
126
- rescue EOFError
127
- return total_written
128
- end
129
-
130
- total_written += written
131
- remaining.slice!(0, written) if written < remaining.length
132
- end
133
- ensure
134
- release_ownership :w
135
- end
136
-
137
- total_written
138
- end
139
-
140
- def <<(string)
141
- write string
142
- self
143
- end
144
- end
145
- end
146
- end