celluloid-io 0.13.0.pre → 0.13.0.pre2

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