revactor 0.1.3 → 0.1.4
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/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
|