celluloid-io 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +17 -0
- data/.rspec +4 -0
- data/.travis.yml +8 -0
- data/CHANGES.md +3 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +22 -0
- data/README.md +80 -0
- data/Rakefile +7 -0
- data/celluloid-io.gemspec +22 -0
- data/lib/celluloid/io.rb +27 -0
- data/lib/celluloid/io/mailbox.rb +72 -0
- data/lib/celluloid/io/reactor.rb +63 -0
- data/lib/celluloid/io/version.rb +5 -0
- data/lib/celluloid/io/waker.rb +43 -0
- data/spec/celluloid/io/actor_spec.rb +5 -0
- data/spec/celluloid/io/mailbox_spec.rb +5 -0
- data/spec/celluloid/io/waker_spec.rb +34 -0
- data/spec/spec_helper.rb +4 -0
- metadata +101 -0
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/CHANGES.md
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2011 Tony Arcieri
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
Celluloid::IO
|
2
|
+
=============
|
3
|
+
[](http://travis-ci.org/tarcieri/celluloid-io)
|
4
|
+
|
5
|
+
You don't have to choose between threaded and evented IO! Celluloid::IO provides
|
6
|
+
a simple and easy way to wait for IO events inside of a Celluloid actor, which
|
7
|
+
runs in its own thread. Any Ruby IO object can be registered and monitored.
|
8
|
+
It's a somewhat similar idea to Ruby event frameworks like EventMachine and
|
9
|
+
Cool.io, but Celluloid actors automatically wrap up all IO in Fibers,
|
10
|
+
resulting in a synchronous API that's duck type compatible with existing IO
|
11
|
+
objects.
|
12
|
+
|
13
|
+
Unlike EventMachine, you can make as many Celluloid::IO actors as you wish,
|
14
|
+
each running their own event loop independently from the others. Using many
|
15
|
+
actors allows your program to scale across multiple CPU cores on Ruby
|
16
|
+
implementations which don't have a GIL, such as JRuby and Rubinius.
|
17
|
+
|
18
|
+
Like Celluloid::IO? [Join the Google Group](http://groups.google.com/group/celluloid-ruby)
|
19
|
+
|
20
|
+
Supported Platforms
|
21
|
+
-------------------
|
22
|
+
|
23
|
+
Celluloid works on Ruby 1.9.2+, JRuby 1.6 (in 1.9 mode), and Rubinius 2.0. JRuby
|
24
|
+
or Rubinius are the preferred platforms as they support true hardware-level
|
25
|
+
parallelism when running Ruby code, whereas MRI/YARV is constrained by a global
|
26
|
+
interpreter lock (GIL).
|
27
|
+
|
28
|
+
To use JRuby in 1.9 mode, you'll need to pass the "--1.9" command line option
|
29
|
+
to the JRuby executable, or set the "JRUBY_OPTS=--1.9" environment variable.
|
30
|
+
|
31
|
+
Celluloid works on Rubinius in either 1.8 or 1.9 mode.
|
32
|
+
|
33
|
+
Usage
|
34
|
+
-----
|
35
|
+
|
36
|
+
To use Celluloid::IO, define a normal Ruby class that includes Celluloid::IO:
|
37
|
+
|
38
|
+
require 'celluloid/io'
|
39
|
+
|
40
|
+
class MyServer
|
41
|
+
include Celluloid::IO
|
42
|
+
|
43
|
+
# Bind a TCP server to the given host and port
|
44
|
+
def initialize(host, port)
|
45
|
+
@server = TCPServer.new host, port
|
46
|
+
run!
|
47
|
+
end
|
48
|
+
|
49
|
+
# Run the TCP server event loop
|
50
|
+
def run
|
51
|
+
while true
|
52
|
+
wait_readable(@server)
|
53
|
+
on_connect @server.accept
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Terminate this server
|
58
|
+
def terminate
|
59
|
+
@server.close
|
60
|
+
super
|
61
|
+
end
|
62
|
+
|
63
|
+
# Called whenever a new connection is opened
|
64
|
+
def on_connect(connection)
|
65
|
+
connection.close
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
Contributing to Celluloid::IO
|
70
|
+
-----------------------------
|
71
|
+
|
72
|
+
* Fork Celluloid on github
|
73
|
+
* Make your changes and send me a pull request
|
74
|
+
* If I like them I'll merge them and give you commit access to my repository
|
75
|
+
|
76
|
+
License
|
77
|
+
-------
|
78
|
+
|
79
|
+
Copyright (c) 2011 Tony Arcieri. Distributed under the MIT License. See
|
80
|
+
LICENSE.txt for further details.
|
data/Rakefile
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/celluloid/io/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Tony Arcieri"]
|
6
|
+
gem.email = ["tony.arcieri@gmail.com"]
|
7
|
+
gem.description = "Evented IO for Celluloid actors"
|
8
|
+
gem.summary = "Celluloid::IO allows you to monitor multiple IO objects within a Celluloid actor"
|
9
|
+
gem.homepage = "http://github.com/tarcieri/celluloid-io"
|
10
|
+
|
11
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
12
|
+
gem.files = `git ls-files`.split("\n")
|
13
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
14
|
+
gem.name = "celluloid-io"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = Celluloid::IO::VERSION
|
17
|
+
|
18
|
+
gem.add_development_dependency('celluloid', '>= 0.7.0')
|
19
|
+
|
20
|
+
gem.add_development_dependency('rake')
|
21
|
+
gem.add_development_dependency('rspec', ['>= 2.7.0'])
|
22
|
+
end
|
data/lib/celluloid/io.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'celluloid/io/version'
|
2
|
+
|
3
|
+
require 'celluloid'
|
4
|
+
require 'celluloid/io/mailbox'
|
5
|
+
require 'celluloid/io/reactor'
|
6
|
+
require 'celluloid/io/waker'
|
7
|
+
|
8
|
+
module Celluloid
|
9
|
+
# Actors with evented IO support
|
10
|
+
module IO
|
11
|
+
def self.included(klass)
|
12
|
+
klass.send :include, Celluloid
|
13
|
+
klass.use_mailbox Celluloid::IO::Mailbox
|
14
|
+
end
|
15
|
+
|
16
|
+
# Wait for the given IO object to become readable
|
17
|
+
def wait_readable(io, &block)
|
18
|
+
# Law of demeter be damned!
|
19
|
+
current_actor.mailbox.reactor.wait_readable(io, &block)
|
20
|
+
end
|
21
|
+
|
22
|
+
# Wait for the given IO object to become writeable
|
23
|
+
def wait_writeable(io, &block)
|
24
|
+
current_actor.mailbox.reactor.wait_writeable(io, &block)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Celluloid
|
2
|
+
module IO
|
3
|
+
# An alternative implementation of Celluloid::Mailbox using Wakers
|
4
|
+
class Mailbox < Celluloid::Mailbox
|
5
|
+
attr_reader :reactor, :waker
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@messages = []
|
9
|
+
@lock = Mutex.new
|
10
|
+
@waker = Waker.new
|
11
|
+
@reactor = Reactor.new(@waker)
|
12
|
+
end
|
13
|
+
|
14
|
+
# Add a message to the Mailbox
|
15
|
+
def <<(message)
|
16
|
+
@lock.synchronize do
|
17
|
+
@messages << message
|
18
|
+
@waker.signal
|
19
|
+
end
|
20
|
+
nil
|
21
|
+
rescue DeadWakerError
|
22
|
+
raise MailboxError, "dead recipient"
|
23
|
+
end
|
24
|
+
|
25
|
+
# Add a high-priority system event to the Mailbox
|
26
|
+
def system_event(event)
|
27
|
+
@lock.synchronize do
|
28
|
+
@messages.unshift event
|
29
|
+
|
30
|
+
begin
|
31
|
+
@waker.signal
|
32
|
+
rescue DeadWakerError
|
33
|
+
# Silently fail if messages are sent to dead actors
|
34
|
+
end
|
35
|
+
end
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
|
39
|
+
# Receive a message from the Mailbox
|
40
|
+
def receive(timeout = nil, &block)
|
41
|
+
message = nil
|
42
|
+
|
43
|
+
begin
|
44
|
+
if timeout
|
45
|
+
now = Time.now
|
46
|
+
wait_until ||= now + timeout
|
47
|
+
wait_interval = wait_until - now
|
48
|
+
return if wait_interval < 0
|
49
|
+
else
|
50
|
+
wait_interval = nil
|
51
|
+
end
|
52
|
+
|
53
|
+
@reactor.run_once(wait_interval) do
|
54
|
+
@waker.wait
|
55
|
+
message = next_message(&block)
|
56
|
+
end
|
57
|
+
end until message
|
58
|
+
|
59
|
+
message
|
60
|
+
rescue IOError, DeadWakerError
|
61
|
+
shutdown # force shutdown of the mailbox
|
62
|
+
raise MailboxShutdown, "mailbox shutdown called during receive"
|
63
|
+
end
|
64
|
+
|
65
|
+
# Cleanup any IO objects this Mailbox may be using
|
66
|
+
def shutdown
|
67
|
+
@waker.cleanup
|
68
|
+
super
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module Celluloid
|
2
|
+
module IO
|
3
|
+
# React to external I/O events. This is kinda sorta supposed to resemble the
|
4
|
+
# Reactor design pattern.
|
5
|
+
class Reactor
|
6
|
+
def initialize(waker)
|
7
|
+
@waker = waker
|
8
|
+
@readers = {}
|
9
|
+
@writers = {}
|
10
|
+
end
|
11
|
+
|
12
|
+
# Wait for the given IO object to become readable
|
13
|
+
def wait_readable(io)
|
14
|
+
wait_for_io io, @readers
|
15
|
+
end
|
16
|
+
|
17
|
+
# Wait for the given IO object to become writeable
|
18
|
+
def wait_writeable(io)
|
19
|
+
wait_for_io io, @writers
|
20
|
+
end
|
21
|
+
|
22
|
+
# Run the reactor, waiting for events, and calling the given block if
|
23
|
+
# the reactor is awoken by the waker
|
24
|
+
def run_once(timeout = nil)
|
25
|
+
readers, writers = select(@readers.keys << @waker.io, @writers.keys, [], timeout)
|
26
|
+
return unless readers
|
27
|
+
|
28
|
+
yield if readers.include? @waker.io
|
29
|
+
|
30
|
+
[[readers, @readers], [writers, @writers]].each do |ios, registered|
|
31
|
+
ios.each do |io|
|
32
|
+
task = registered.delete io
|
33
|
+
task.resume if task
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
#######
|
39
|
+
private
|
40
|
+
#######
|
41
|
+
|
42
|
+
def wait_for_io(io, set)
|
43
|
+
# zomg ugly type conversion :(
|
44
|
+
unless io.is_a?(IO)
|
45
|
+
if IO.respond_to? :try_convert
|
46
|
+
io = IO.try_convert(io)
|
47
|
+
elsif io.respond_to? :to_io
|
48
|
+
io = io.to_io
|
49
|
+
else raise TypeError, "can't convert #{io.class} into IO"
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
if set.has_key? io
|
54
|
+
raise ArgumentError, "another method is already waiting on #{io.inspect}"
|
55
|
+
else
|
56
|
+
set[io] = Task.current
|
57
|
+
end
|
58
|
+
|
59
|
+
Task.suspend
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Celluloid
|
2
|
+
module IO
|
3
|
+
class DeadWakerError < StandardError; end # You can't wake the dead
|
4
|
+
|
5
|
+
# Wakes up sleepy threads so that they can check their mailbox
|
6
|
+
# Works like a ConditionVariable, except it's implemented as an IO object so
|
7
|
+
# that it can be multiplexed alongside other IO objects.
|
8
|
+
class Waker
|
9
|
+
PAYLOAD = "\0" # the payload doesn't matter. each byte is a signal
|
10
|
+
def initialize
|
11
|
+
@receiver, @sender = ::IO.pipe
|
12
|
+
end
|
13
|
+
|
14
|
+
# Wakes up the thread that is waiting for this Waker
|
15
|
+
def signal
|
16
|
+
@sender << PAYLOAD
|
17
|
+
nil
|
18
|
+
rescue IOError, Errno::EPIPE, Errno::EBADF
|
19
|
+
raise DeadWakerError, "waker is already dead"
|
20
|
+
end
|
21
|
+
|
22
|
+
# Wait for another thread to signal this Waker
|
23
|
+
def wait
|
24
|
+
byte = @receiver.read(1)
|
25
|
+
raise DeadWakerError, "can't wait on a dead waker" unless byte == PAYLOAD
|
26
|
+
rescue IOError, RuntimeError
|
27
|
+
raise DeadWakerError, "can't wait on a dead waker"
|
28
|
+
end
|
29
|
+
|
30
|
+
# Return the IO object which will be readable when this Waker is signaled
|
31
|
+
def io
|
32
|
+
@receiver
|
33
|
+
end
|
34
|
+
|
35
|
+
# Clean up the IO objects associated with this waker
|
36
|
+
def cleanup
|
37
|
+
@receiver.close rescue nil
|
38
|
+
@sender.close rescue nil
|
39
|
+
nil
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Celluloid::IO::Waker do
|
4
|
+
it "blocks until awoken" do
|
5
|
+
waker = Celluloid::IO::Waker.new
|
6
|
+
thread = Thread.new do
|
7
|
+
waker.wait
|
8
|
+
:done
|
9
|
+
end
|
10
|
+
|
11
|
+
# Assert that the thread can't be joined at this point
|
12
|
+
thread.join(0).should be_nil
|
13
|
+
|
14
|
+
waker.signal
|
15
|
+
thread.value.should == :done
|
16
|
+
end
|
17
|
+
|
18
|
+
it "returns an IO object that can be multiplexed with IO.select" do
|
19
|
+
waker = Celluloid::IO::Waker.new
|
20
|
+
waker.io.should be_an_instance_of(IO)
|
21
|
+
|
22
|
+
thread = Thread.new do
|
23
|
+
readable, _, _ = select [waker.io]
|
24
|
+
waker.wait
|
25
|
+
:done
|
26
|
+
end
|
27
|
+
|
28
|
+
# Assert that the thread can't be joined at this point
|
29
|
+
thread.join(0).should be_nil
|
30
|
+
|
31
|
+
waker.signal
|
32
|
+
thread.value.should == :done
|
33
|
+
end
|
34
|
+
end
|
data/spec/spec_helper.rb
ADDED
metadata
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: celluloid-io
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.7.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Tony Arcieri
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2011-12-29 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: celluloid
|
16
|
+
requirement: &70176481988080 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 0.7.0
|
22
|
+
type: :development
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70176481988080
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: rake
|
27
|
+
requirement: &70176481987440 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :development
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70176481987440
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rspec
|
38
|
+
requirement: &70176481986860 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: 2.7.0
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70176481986860
|
47
|
+
description: Evented IO for Celluloid actors
|
48
|
+
email:
|
49
|
+
- tony.arcieri@gmail.com
|
50
|
+
executables: []
|
51
|
+
extensions: []
|
52
|
+
extra_rdoc_files: []
|
53
|
+
files:
|
54
|
+
- .gitignore
|
55
|
+
- .rspec
|
56
|
+
- .travis.yml
|
57
|
+
- CHANGES.md
|
58
|
+
- Gemfile
|
59
|
+
- LICENSE.txt
|
60
|
+
- README.md
|
61
|
+
- Rakefile
|
62
|
+
- celluloid-io.gemspec
|
63
|
+
- lib/celluloid/io.rb
|
64
|
+
- lib/celluloid/io/mailbox.rb
|
65
|
+
- lib/celluloid/io/reactor.rb
|
66
|
+
- lib/celluloid/io/version.rb
|
67
|
+
- lib/celluloid/io/waker.rb
|
68
|
+
- spec/celluloid/io/actor_spec.rb
|
69
|
+
- spec/celluloid/io/mailbox_spec.rb
|
70
|
+
- spec/celluloid/io/waker_spec.rb
|
71
|
+
- spec/spec_helper.rb
|
72
|
+
homepage: http://github.com/tarcieri/celluloid-io
|
73
|
+
licenses: []
|
74
|
+
post_install_message:
|
75
|
+
rdoc_options: []
|
76
|
+
require_paths:
|
77
|
+
- lib
|
78
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
79
|
+
none: false
|
80
|
+
requirements:
|
81
|
+
- - ! '>='
|
82
|
+
- !ruby/object:Gem::Version
|
83
|
+
version: '0'
|
84
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
85
|
+
none: false
|
86
|
+
requirements:
|
87
|
+
- - ! '>='
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
requirements: []
|
91
|
+
rubyforge_project:
|
92
|
+
rubygems_version: 1.8.10
|
93
|
+
signing_key:
|
94
|
+
specification_version: 3
|
95
|
+
summary: Celluloid::IO allows you to monitor multiple IO objects within a Celluloid
|
96
|
+
actor
|
97
|
+
test_files:
|
98
|
+
- spec/celluloid/io/actor_spec.rb
|
99
|
+
- spec/celluloid/io/mailbox_spec.rb
|
100
|
+
- spec/celluloid/io/waker_spec.rb
|
101
|
+
- spec/spec_helper.rb
|