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 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
- * Removed case gem usage in Revactor internals
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
@@ -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
- # Spawn the server
22
- server = Actor.spawn do
23
- clients = {}
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
- # A proc to broadcast a message to all connected clients. If the server
26
- # were encapsulated into an object this could be a method
27
- broadcast = proc do |msg|
28
- clients.keys.each { |client| client << T[:write, msg] }
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
- # Server's main loop. The server handles incoming messages from the
32
- # client managers and dispatches them to other client managers.
33
- loop do
34
- Actor.receive do |filter|
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
- server << T[:register, Actor.current, nickname]
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
- filter.when(T[:tcp, sock]) do |_, _, message|
76
- server << T[:say, Actor.current, message]
77
- sock.active = :once
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
- filter.when(T[:tcp_closed, sock]) do
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) }
@@ -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.3' unless defined? Revactor::VERSION
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 delegator tcp http_client
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
@@ -4,7 +4,6 @@
4
4
  # See file LICENSE for details
5
5
  #++
6
6
 
7
- require File.dirname(__FILE__) + '/../revactor'
8
7
  require 'thread'
9
8
  require 'fiber'
10
9
 
@@ -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
@@ -4,9 +4,7 @@
4
4
  # See file LICENSE for details
5
5
  #++
6
6
 
7
- require File.dirname(__FILE__) + '/../revactor'
8
7
  require 'uri'
9
-
10
8
 
11
9
  module Revactor
12
10
  # Thrown for all HTTP-specific errors
@@ -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.(@queue.delete_at matched_index)
79
+ action.call @queue.delete_at(matched_index)
82
80
  end
83
81
 
84
82
  # Is the mailbox empty?
@@ -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
@@ -5,7 +5,6 @@
5
5
  #++
6
6
 
7
7
  require 'thread'
8
- require File.dirname(__FILE__) + '/../revactor'
9
8
 
10
9
  class Actor
11
10
  # The Actor Scheduler maintains a run queue of actors with outstanding
@@ -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: false,
350
- controller: Actor.current
347
+ :active => false,
348
+ :controller => Actor.current
351
349
  }.merge(options)
352
350
 
353
351
  @active, @controller = opts[:active], opts[:controller]
@@ -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.3"
5
+ s.version = "0.1.4"
6
6
  s.authors = "Tony Arcieri"
7
7
  s.email = "tony@medioh.com"
8
- s.date = "2008-1-29"
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.3
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-01-29 00:00:00 -07:00
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/delegator.rb
46
+ - lib/revactor/actorize.rb
38
47
  - lib/revactor/filters
39
48
  - lib/revactor/filters/line.rb
40
49
  - lib/revactor/filters/packet.rb
@@ -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