revactor 0.1.3 → 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES +5 -1
- data/examples/chat_server.rb +72 -53
- data/lib/revactor.rb +4 -4
- data/lib/revactor/actor.rb +0 -1
- data/lib/revactor/actorize.rb +76 -0
- data/lib/revactor/http_client.rb +0 -2
- data/lib/revactor/mailbox.rb +1 -3
- data/lib/revactor/mongrel.rb +2 -2
- data/lib/revactor/scheduler.rb +0 -1
- data/lib/revactor/tcp.rb +2 -4
- data/revactor.gemspec +3 -2
- metadata +12 -3
- data/lib/revactor/delegator.rb +0 -70
data/CHANGES
CHANGED
@@ -1,6 +1,10 @@
|
|
1
|
+
0.1.4:
|
2
|
+
|
3
|
+
* Fix bungled 0.1.3 release :(
|
4
|
+
|
1
5
|
0.1.3:
|
2
6
|
|
3
|
-
*
|
7
|
+
* Added the Actorize module for easily using objects as Actors
|
4
8
|
|
5
9
|
* Add a guaranteed blocking idle state for the Actor scheduler, fixing a bug
|
6
10
|
where an idle root Actor would spin on the scheduler when calling receive
|
data/examples/chat_server.rb
CHANGED
@@ -14,80 +14,99 @@ PORT = 4321
|
|
14
14
|
|
15
15
|
# Open a listen socket. All traffic on new connections will be run through
|
16
16
|
# the "line" filter, so incoming messages are delimited by newlines.
|
17
|
-
|
18
17
|
listener = Actor::TCP.listen(HOST, PORT, :filter => :line)
|
19
18
|
puts "Listening on #{HOST}:#{PORT}"
|
20
19
|
|
21
|
-
#
|
22
|
-
|
23
|
-
|
20
|
+
# The ClientConenction class handles all network interaction with clients
|
21
|
+
# This includes the initial handshake (getting a nickname), processing
|
22
|
+
# incoming messages, and writing out enqueued messages
|
23
|
+
class ClientConnection
|
24
|
+
# Add .spawn and .spawn_link methods to the singleton
|
25
|
+
extend Actorize
|
24
26
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
27
|
+
def initialize(dispatcher, sock)
|
28
|
+
@dispatcher, @sock = dispatcher, sock
|
29
|
+
puts "#{sock.remote_addr}:#{sock.remote_port} connected"
|
30
|
+
|
31
|
+
handshake
|
32
|
+
message_loop
|
33
|
+
rescue EOFError
|
34
|
+
puts "#{sock.remote_addr}:#{sock.remote_port} disconnected"
|
35
|
+
@dispatcher << T[:disconnected, Actor.current]
|
29
36
|
end
|
30
37
|
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
Actor.
|
35
|
-
filter.when(T[:register]) do |_, client, nickname|
|
36
|
-
clients[client] = nickname
|
37
|
-
broadcast.call "*** #{nickname} joined"
|
38
|
-
client << T[:write, "*** Users: " + clients.values.join(', ')]
|
39
|
-
end
|
40
|
-
|
41
|
-
filter.when(T[:say]) do |_, client, msg|
|
42
|
-
nickname = clients[client]
|
43
|
-
broadcast.call "<#{nickname}> #{msg}"
|
44
|
-
end
|
45
|
-
|
46
|
-
filter.when(T[:disconnected]) do |_, client|
|
47
|
-
nickname = clients.delete client
|
48
|
-
broadcast.call "*** #{nickname} left" if nickname
|
49
|
-
end
|
50
|
-
end
|
38
|
+
def handshake
|
39
|
+
@sock.write "Please enter a nickname:"
|
40
|
+
nickname = @sock.read
|
41
|
+
@dispatcher << T[:register, Actor.current, nickname]
|
51
42
|
end
|
52
|
-
end
|
53
|
-
|
54
|
-
# The main loop handles incoming connections
|
55
|
-
loop do
|
56
|
-
# Spawn a new actor for each incoming connection
|
57
|
-
Actor.spawn(listener.accept) do |sock|
|
58
|
-
puts "#{sock.remote_addr}:#{sock.remote_port} connected"
|
59
|
-
|
60
|
-
# Connection handshaking
|
61
|
-
sock.write "Please enter a nickname:"
|
62
|
-
nickname = sock.read
|
63
43
|
|
64
|
-
|
65
|
-
|
44
|
+
def message_loop
|
66
45
|
# Flip the socket into asynchronous "active" mode
|
67
46
|
# This means the Actor can receive messages from
|
68
47
|
# the socket alongside other events.
|
69
|
-
sock.controller = Actor.current
|
70
|
-
sock.active = :once
|
71
|
-
|
48
|
+
@sock.controller = Actor.current
|
49
|
+
@sock.active = :once
|
50
|
+
|
72
51
|
# Main message loop
|
73
52
|
loop do
|
74
53
|
Actor.receive do |filter|
|
75
|
-
|
76
|
-
|
77
|
-
|
54
|
+
# Handle incoming TCP traffic. The line filter ensures that all
|
55
|
+
# incoming traffic is filtered down to CRLF-delimited lines of text
|
56
|
+
filter.when(T[:tcp, @sock]) do |_, _, message|
|
57
|
+
@dispatcher << T[:line, Actor.current, message]
|
58
|
+
@sock.active = :once
|
78
59
|
end
|
79
60
|
|
61
|
+
# Write a message to the client's socket
|
80
62
|
filter.when(T[:write]) do |_, message|
|
81
|
-
sock.write message
|
63
|
+
@sock.write message
|
82
64
|
end
|
83
|
-
|
84
|
-
|
65
|
+
|
66
|
+
# Indicates the client's connection has closed
|
67
|
+
filter.when(T[:tcp_closed, @sock]) do
|
85
68
|
raise EOFError
|
86
69
|
end
|
87
70
|
end
|
88
71
|
end
|
89
|
-
rescue EOFError
|
90
|
-
puts "#{sock.remote_addr}:#{sock.remote_port} disconnected"
|
91
|
-
server << T[:disconnected, Actor.current]
|
92
72
|
end
|
93
73
|
end
|
74
|
+
|
75
|
+
class Dispatcher
|
76
|
+
extend Actorize
|
77
|
+
|
78
|
+
def initialize
|
79
|
+
@clients = {}
|
80
|
+
run
|
81
|
+
end
|
82
|
+
|
83
|
+
def run
|
84
|
+
loop do
|
85
|
+
Actor.receive do |filter|
|
86
|
+
filter.when(T[:register]) do |_, client, nickname|
|
87
|
+
@clients[client] = nickname
|
88
|
+
broadcast "*** #{nickname} joined"
|
89
|
+
client << T[:write, "*** Users: " + @clients.values.join(', ')]
|
90
|
+
end
|
91
|
+
|
92
|
+
filter.when(T[:line]) do |_, client, msg|
|
93
|
+
nickname = @clients[client]
|
94
|
+
broadcast "<#{nickname}> #{msg}"
|
95
|
+
end
|
96
|
+
|
97
|
+
filter.when(T[:disconnected]) do |_, client|
|
98
|
+
nickname = @clients.delete client
|
99
|
+
broadcast "*** #{nickname} left" if nickname
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# Broadcast a message to all connected clients
|
106
|
+
def broadcast(message)
|
107
|
+
@clients.keys.each { |client| client << T[:write, message] }
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
dispatcher = Dispatcher.spawn
|
112
|
+
loop { ClientConnection.spawn(dispatcher, listener.accept) }
|
data/lib/revactor.rb
CHANGED
@@ -5,6 +5,7 @@
|
|
5
5
|
#++
|
6
6
|
|
7
7
|
require 'rev'
|
8
|
+
require 'case'
|
8
9
|
|
9
10
|
# This is mostly in hopes of a bright future with Rubinius
|
10
11
|
# The recommended container for all datagrams sent between
|
@@ -26,13 +27,13 @@ end
|
|
26
27
|
T = Tuple unless defined? T
|
27
28
|
|
28
29
|
module Revactor
|
29
|
-
Revactor::VERSION = '0.1.
|
30
|
+
Revactor::VERSION = '0.1.4' unless defined? Revactor::VERSION
|
30
31
|
def self.version() VERSION end
|
31
32
|
end
|
32
33
|
|
33
34
|
%w{
|
34
|
-
actor scheduler mailbox
|
35
|
-
filters/line filters/packet
|
35
|
+
actor scheduler mailbox tcp http_client
|
36
|
+
filters/line filters/packet actorize
|
36
37
|
}.each do |file|
|
37
38
|
require File.dirname(__FILE__) + '/revactor/' + file
|
38
39
|
end
|
@@ -41,6 +42,5 @@ end
|
|
41
42
|
class Actor
|
42
43
|
Actor::TCP = Revactor::TCP unless defined? Actor::TCP
|
43
44
|
Actor::Filter = Revactor::Filter unless defined? Actor::Filter
|
44
|
-
Actor::Delegator = Revactor::Delegator unless defined? Actor::Delegator
|
45
45
|
Actor::HttpClient = Revactor::HttpClient unless defined? Actor::HttpClient
|
46
46
|
end
|
data/lib/revactor/actor.rb
CHANGED
@@ -0,0 +1,76 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (C)2007 Tony Arcieri
|
3
|
+
# You can redistribute this under the terms of the Ruby license
|
4
|
+
# See file LICENSE for details
|
5
|
+
#++
|
6
|
+
|
7
|
+
# Any class can be "actorized" by extending it with the Actorize module:
|
8
|
+
#
|
9
|
+
# class MyClass
|
10
|
+
# extend Actorize
|
11
|
+
# end
|
12
|
+
#
|
13
|
+
# This will create MyClass.spawn and MyClass.spawn_link instance methods.
|
14
|
+
# Now you can create instances of your class which run inside Actors:
|
15
|
+
#
|
16
|
+
# actor = MyClass.spawn
|
17
|
+
#
|
18
|
+
# Actorize defines method_missing for the Actor object, and will delegate
|
19
|
+
# any method calls to the Actor to MyClass's call method. This method
|
20
|
+
# should be defined with the following signature:
|
21
|
+
#
|
22
|
+
# class MyClass
|
23
|
+
# extend Actorize
|
24
|
+
#
|
25
|
+
# def self.call(actor, meth, *args)
|
26
|
+
# ...
|
27
|
+
# end
|
28
|
+
# end
|
29
|
+
#
|
30
|
+
# The call method receives the actor the call was made on, the method that
|
31
|
+
# was invoked, and the arguments that were passed. You can then write a
|
32
|
+
# simple RPC mechanism to send a message to the actor and receive a response:
|
33
|
+
#
|
34
|
+
# actor << [:call, Actor.current, meth, *args]
|
35
|
+
# Actor.receive do |filter|
|
36
|
+
# filter.when(T[:call_reply, actor]) { |_, _, response| response }
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# Using this approach, you can mix the synchronous approach of Objects with
|
40
|
+
# the asynchronous approach of Actors, and effectively duck type Actors
|
41
|
+
# to Objects.
|
42
|
+
#
|
43
|
+
module Actorize
|
44
|
+
def spawn(*args)
|
45
|
+
_actorize Actor.spawn(*args, &method(:new))
|
46
|
+
end
|
47
|
+
|
48
|
+
def spawn_link(*args)
|
49
|
+
_actorize Actor.spawn_link(*args, &method(:new))
|
50
|
+
end
|
51
|
+
|
52
|
+
#######
|
53
|
+
private
|
54
|
+
#######
|
55
|
+
|
56
|
+
def _actorize(actor)
|
57
|
+
actor.extend InstanceMethods
|
58
|
+
actor.instance_variable_set(:@_class, self)
|
59
|
+
actor
|
60
|
+
end
|
61
|
+
|
62
|
+
module InstanceMethods
|
63
|
+
def method_missing(*args, &block)
|
64
|
+
return super unless @_class.respond_to?(:call)
|
65
|
+
@_class.call(self, *args, &block)
|
66
|
+
end
|
67
|
+
|
68
|
+
def remote_class
|
69
|
+
@_class
|
70
|
+
end
|
71
|
+
|
72
|
+
def inspect
|
73
|
+
"#<#{self.class}(#{remote_class}):0x#{object_id.to_s(16)}>"
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
data/lib/revactor/http_client.rb
CHANGED
data/lib/revactor/mailbox.rb
CHANGED
@@ -4,8 +4,6 @@
|
|
4
4
|
# See file LICENSE for details
|
5
5
|
#++
|
6
6
|
|
7
|
-
require File.dirname(__FILE__) + '/../revactor'
|
8
|
-
|
9
7
|
class Actor
|
10
8
|
# Actor mailbox. For purposes of efficiency the mailbox also handles
|
11
9
|
# suspending and resuming an actor when no messages match its filter set.
|
@@ -78,7 +76,7 @@ class Actor
|
|
78
76
|
end
|
79
77
|
|
80
78
|
# Otherwise we matched a message, so process it with the action
|
81
|
-
action.
|
79
|
+
action.call @queue.delete_at(matched_index)
|
82
80
|
end
|
83
81
|
|
84
82
|
# Is the mailbox empty?
|
data/lib/revactor/mongrel.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
require File.dirname(__FILE__) + '/../revactor'
|
2
1
|
require 'mongrel'
|
3
|
-
|
2
|
+
require File.dirname(__FILE__) + '/../revactor'
|
3
|
+
|
4
4
|
class Revactor::TCP::Socket
|
5
5
|
# Monkeypatched readpartial routine inserted whenever Revactor's mongrel.rb
|
6
6
|
# is loaded. The value passed to this method is ignored, so it is not
|
data/lib/revactor/scheduler.rb
CHANGED
data/lib/revactor/tcp.rb
CHANGED
@@ -4,8 +4,6 @@
|
|
4
4
|
# See file LICENSE for details
|
5
5
|
#++
|
6
6
|
|
7
|
-
require File.dirname(__FILE__) + '/../revactor'
|
8
|
-
|
9
7
|
module Revactor
|
10
8
|
# The TCP module holds all Revactor functionality related to the
|
11
9
|
# Transmission Control Protocol, including drop-in replacements
|
@@ -346,8 +344,8 @@ module Revactor
|
|
346
344
|
def initialize(host, port, options = {})
|
347
345
|
super(host, port)
|
348
346
|
opts = {
|
349
|
-
active
|
350
|
-
controller
|
347
|
+
:active => false,
|
348
|
+
:controller => Actor.current
|
351
349
|
}.merge(options)
|
352
350
|
|
353
351
|
@active, @controller = opts[:active], opts[:controller]
|
data/revactor.gemspec
CHANGED
@@ -2,10 +2,10 @@ require 'rubygems'
|
|
2
2
|
|
3
3
|
GEMSPEC = Gem::Specification.new do |s|
|
4
4
|
s.name = "revactor"
|
5
|
-
s.version = "0.1.
|
5
|
+
s.version = "0.1.4"
|
6
6
|
s.authors = "Tony Arcieri"
|
7
7
|
s.email = "tony@medioh.com"
|
8
|
-
s.date = "2008-
|
8
|
+
s.date = "2008-3-28"
|
9
9
|
s.summary = "Revactor is an Actor implementation for writing high performance concurrent programs"
|
10
10
|
s.platform = Gem::Platform::RUBY
|
11
11
|
s.required_ruby_version = '>= 1.9.0'
|
@@ -15,6 +15,7 @@ GEMSPEC = Gem::Specification.new do |s|
|
|
15
15
|
|
16
16
|
# Dependencies
|
17
17
|
s.add_dependency("rev", ">= 0.2.0")
|
18
|
+
s.add_dependency("case", ">= 0.4")
|
18
19
|
|
19
20
|
# RubyForge info
|
20
21
|
s.homepage = "http://revactor.org"
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: revactor
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.4
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Tony Arcieri
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2008-
|
12
|
+
date: 2008-03-28 00:00:00 -06:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -21,6 +21,15 @@ dependencies:
|
|
21
21
|
- !ruby/object:Gem::Version
|
22
22
|
version: 0.2.0
|
23
23
|
version:
|
24
|
+
- !ruby/object:Gem::Dependency
|
25
|
+
name: case
|
26
|
+
version_requirement:
|
27
|
+
version_requirements: !ruby/object:Gem::Requirement
|
28
|
+
requirements:
|
29
|
+
- - ">="
|
30
|
+
- !ruby/object:Gem::Version
|
31
|
+
version: "0.4"
|
32
|
+
version:
|
24
33
|
description:
|
25
34
|
email: tony@medioh.com
|
26
35
|
executables: []
|
@@ -34,7 +43,7 @@ extra_rdoc_files:
|
|
34
43
|
files:
|
35
44
|
- lib/revactor
|
36
45
|
- lib/revactor/actor.rb
|
37
|
-
- lib/revactor/
|
46
|
+
- lib/revactor/actorize.rb
|
38
47
|
- lib/revactor/filters
|
39
48
|
- lib/revactor/filters/line.rb
|
40
49
|
- lib/revactor/filters/packet.rb
|
data/lib/revactor/delegator.rb
DELETED
@@ -1,70 +0,0 @@
|
|
1
|
-
#--
|
2
|
-
# Copyright (C)2007 Tony Arcieri
|
3
|
-
# You can redistribute this under the terms of the Ruby license
|
4
|
-
# See file LICENSE for details
|
5
|
-
#++
|
6
|
-
|
7
|
-
require File.dirname(__FILE__) + '/../revactor'
|
8
|
-
|
9
|
-
# A Delegator whose delegate runs in a separate Actor. This allows
|
10
|
-
# an easy method for constructing synchronous calls to a separate Actor
|
11
|
-
# which cause the current Actor to block until they complete.
|
12
|
-
class Revactor::Delegator
|
13
|
-
# Create a new delegator for the given object
|
14
|
-
def initialize(obj)
|
15
|
-
@obj = obj
|
16
|
-
@running = true
|
17
|
-
@actor = Actor.spawn(&method(:start))
|
18
|
-
end
|
19
|
-
|
20
|
-
# Stop the delegator Actor
|
21
|
-
def stop
|
22
|
-
@actor << :stop
|
23
|
-
nil
|
24
|
-
end
|
25
|
-
|
26
|
-
# Send a message to the Actor delegate
|
27
|
-
def send(meth, *args, &block)
|
28
|
-
@actor << T[:call, Actor.current, meth, args, block]
|
29
|
-
Actor.receive do |filter|
|
30
|
-
filter.when(Case[:call_reply, @actor, Object]) { |_, _, reply| reply }
|
31
|
-
filter.when(Case[:call_error, @actor, Object]) { |_, _, ex| raise ex }
|
32
|
-
end
|
33
|
-
end
|
34
|
-
|
35
|
-
alias_method :method_missing, :send
|
36
|
-
|
37
|
-
#########
|
38
|
-
protected
|
39
|
-
#########
|
40
|
-
|
41
|
-
# Start the server
|
42
|
-
def start
|
43
|
-
loop do
|
44
|
-
Actor.receive do |filter|
|
45
|
-
filter.when(:stop) { |_| return }
|
46
|
-
filter.when(Object) { |message| handle_message(message) }
|
47
|
-
end
|
48
|
-
end
|
49
|
-
end
|
50
|
-
|
51
|
-
# Dispatch the incoming message to the appropriate handler
|
52
|
-
def handle_message(message)
|
53
|
-
case message.first
|
54
|
-
when :call then handle_call(message)
|
55
|
-
else @obj.__send__(:on_message, message) if @obj.respond_to? :on_message
|
56
|
-
end
|
57
|
-
end
|
58
|
-
|
59
|
-
# Wrapper for calling the provided object's handle_call method
|
60
|
-
def handle_call(message)
|
61
|
-
_, from, meth, args, block = message
|
62
|
-
|
63
|
-
begin
|
64
|
-
result = @obj.__send__(meth, *args, &block)
|
65
|
-
from << T[:call_reply, Actor.current, result]
|
66
|
-
rescue => ex
|
67
|
-
from << T[:call_error, Actor.current, ex]
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|