revactor 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGES ADDED
@@ -0,0 +1,3 @@
1
+ 0.1.0:
2
+
3
+ * Initial release
data/LICENSE ADDED
@@ -0,0 +1,58 @@
1
+ Ruby is copyrighted free software by Yukihiro Matsumoto <matz@netlab.co.jp>.
2
+ You can redistribute it and/or modify it under either the terms of the GPL
3
+ (see COPYING.txt file), or the conditions below:
4
+
5
+ 1. You may make and give away verbatim copies of the source form of the
6
+ software without restriction, provided that you duplicate all of the
7
+ original copyright notices and associated disclaimers.
8
+
9
+ 2. You may modify your copy of the software in any way, provided that
10
+ you do at least ONE of the following:
11
+
12
+ a) place your modifications in the Public Domain or otherwise
13
+ make them Freely Available, such as by posting said
14
+ modifications to Usenet or an equivalent medium, or by allowing
15
+ the author to include your modifications in the software.
16
+
17
+ b) use the modified software only within your corporation or
18
+ organization.
19
+
20
+ c) rename any non-standard executables so the names do not conflict
21
+ with standard executables, which must also be provided.
22
+
23
+ d) make other distribution arrangements with the author.
24
+
25
+ 3. You may distribute the software in object code or executable
26
+ form, provided that you do at least ONE of the following:
27
+
28
+ a) distribute the executables and library files of the software,
29
+ together with instructions (in the manual page or equivalent)
30
+ on where to get the original distribution.
31
+
32
+ b) accompany the distribution with the machine-readable source of
33
+ the software.
34
+
35
+ c) give non-standard executables non-standard names, with
36
+ instructions on where to get the original software distribution.
37
+
38
+ d) make other distribution arrangements with the author.
39
+
40
+ 4. You may modify and include the part of the software into any other
41
+ software (possibly commercial). But some files in the distribution
42
+ are not written by the author, so that they are not under this terms.
43
+
44
+ They are gc.c(partly), utils.c(partly), regex.[ch], st.[ch] and some
45
+ files under the ./missing directory. See each file for the copying
46
+ condition.
47
+
48
+ 5. The scripts and library files supplied as input to or produced as
49
+ output from the software do not automatically fall under the
50
+ copyright of the software, but belong to whomever generated them,
51
+ and may be sold commercially, and may be aggregated with this
52
+ software.
53
+
54
+ 6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
55
+ IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
56
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
57
+ PURPOSE.
58
+
data/README ADDED
@@ -0,0 +1,338 @@
1
+ = Revactor
2
+
3
+ Revactor is an Actor model implementation for Ruby 1.9 built on top of the
4
+ Rev high performance event library. Revactor is primarily designed for
5
+ writing Erlang-like network services and tools.
6
+
7
+ You can load Revactor into your Ruby 1.9 application with:
8
+
9
+ require 'revactor'
10
+
11
+ If you'd like to learn more about the Actor model, more information is
12
+ available on the Revactor web site:
13
+
14
+ http://revactor.org/philosophy
15
+
16
+ More information about Rev is available on Rubyforge:
17
+
18
+ http://rev.rubyforge.org/rdoc
19
+
20
+ == Anatomy
21
+
22
+ Revactor is built out of several parts which interoperate to let you build
23
+ network servers painlessly while still guaranteeing correct operation:
24
+
25
+ * Actors - Actors are the main concurrency primitive used by Revactor.
26
+
27
+ * Revactor::TCP - This module provides an API duck typed to the Ruby
28
+ Sockets API. However, rather than blocking calls actually blocking,
29
+ they defer to the underlying event loop. Actor-focused means of
30
+ receiving data are also provided.
31
+
32
+ * Filters - Applied to all incoming data directly when it's received, Filters
33
+ can preprocess or postprocess data before it's even delivered to an Actor.
34
+ This is useful for handling protocol framing or other streaming transforms.
35
+
36
+ * Behaviors - These are patterns for implementing Actors which accomplish
37
+ specific tasks. Right now only the Server behavior is supported.
38
+
39
+ == Actors
40
+
41
+ Actors add mailboxes to Ruby's Fiber mechanism. They multitask cooperatively,
42
+ meaning that many of the worries surrounding threaded programming disappear.
43
+ Any sequence of operations you do in an Actor are executed in the order
44
+ you specify. You don't (generally) have to worry about another Actor doing
45
+ something in the background as you frob a particular data structure.
46
+
47
+ Unfortunately, in Ruby 1.9, Actors are not first-class citizens. This means
48
+ you will need to jump from the non-Actor world to the Actor world before you
49
+ can do anything with Actors. This is accomplished by running Actor.start:
50
+
51
+ # Not in Actor world
52
+ Actor.start do
53
+ # In Actor world, yay
54
+ ...
55
+ end
56
+ # Won't get called until all Actors have processes their entire mailbox
57
+ # and aren't waiting for any events.
58
+
59
+ Ideally, what you place in Actor.start is a small startup routine which
60
+ spawns all the Actors your application needs to get started and nothing else.
61
+
62
+ Once you're in Actor world, you can begin making Actors and sending them
63
+ events. You create Actors with Actor.spawn:
64
+
65
+ myactor = Actor.spawn { puts "I'm an Actor!" }
66
+
67
+ When you spawn an Actor it's scheduled to run after the current Actor either
68
+ completes or calls Actor.receive. Speaking of which, Actor.receive is used
69
+ to receive messages:
70
+
71
+ myactor = Actor.spawn do
72
+ Actor.receive do |filter|
73
+ filter.when(:dog) { puts "I got a dog!" }
74
+ end
75
+ end
76
+
77
+ You can send a message to an actor using its #send method or <<
78
+
79
+ Calling:
80
+
81
+ myactor << :dog
82
+
83
+ prints:
84
+
85
+ "Yay, I got a dog!"
86
+
87
+ == Mailboxes
88
+
89
+ So, Actors can receive messages. But where do those messages go? The answer
90
+ is every Actor has a mailbox. The mailbox is sort of like a message queue,
91
+ but you don't have to read it sequentially. You can apply filters to it, and
92
+ change the filterset at any time.
93
+
94
+ When you call Actor.receive, it yields a filter object and lets you register
95
+ message patterns you're interested in, then it sleeps and waits for messages.
96
+ Each time the current actor receives a message, it's scanned by the filter,
97
+ and if a match occurs the appropriate action is given.
98
+
99
+ Matching is performed by the Filter#when method, which takes a pattern to match
100
+ against a message and a block to call if the message matches. The pattern is
101
+ compared to the message using ===, the same thing Ruby uses for case statements.
102
+ You can think of the filter as a big case statement.
103
+
104
+ Understanding the === statement is a bit strange, so here's a short guide:
105
+
106
+ You can pass #when a class to match against the message. Obviously passing it
107
+ Object will match all messages.
108
+
109
+ You can pass #when a regexp to match against the message.
110
+
111
+ Revactor installs the Case gem by default. This is useful for matching against
112
+ messages stored in Arrays, or in fixed-size arrays called Tuples. Case can
113
+ be used as follows:
114
+
115
+ filter.when(Case[:foobar, Object, Object]) { ... }
116
+
117
+ This will look for messages which are Arrays or Tuples with three members,
118
+ whose first member is the symbol :foobar. As you can probably guess, Case[]
119
+ matches against an Array or Tuple with the same number of members, and
120
+ matches each member of the given tuple with ===. Once again, Object is a
121
+ wildcard, so the other members of the message can be anything.
122
+
123
+ Want more complex pattern matching? Case lets you use a block to match any
124
+ member by using a guard, ala:
125
+
126
+ filter.when(Case[:foobar, Case.guard { |n| n > 100 }, Object]) { ... }
127
+
128
+ This will look for an Array / Tuple with three members, whose first member is
129
+ the symbol :foobar and whose second member is greater than 100.
130
+
131
+ You can also specify how long you wish to wait for a message before timing out.
132
+ This is accomplished with Filter#after:
133
+
134
+ filter.after(0.5) { raise 'it timed out ;_;' }
135
+
136
+ The #after method takes a duration in seconds to wait (in the above example it
137
+ waits a half second) before the receive operation times out.
138
+
139
+ Actor.receive returns whatever value the evaluated action returned. This means
140
+ you don't have to depend on side effects to extract values from receive,
141
+ instead you can just interpret its return value.
142
+
143
+ == Revactor::TCP
144
+
145
+ The TCP module lets you perform TCP operations on top of the Actor model. For
146
+ those of you familiar with Erlang, it implements something akin to gen_tcp.
147
+ Everyone else, read on!
148
+
149
+ Perhaps the best part of Revactor::TCP is you don't really need to know
150
+ anything about the Actor model to use it. For the most part it's duck typed
151
+ to the Ruby Socket API and can operate as a drop-in replacement.
152
+
153
+ To make an outgoing connection, call:
154
+
155
+ sock = Revactor::TCP.connect(host, port)
156
+
157
+ This will resolve the hostname for host (if it's not an IPv4 or IPv6 address),
158
+ make the connection, and return a socket to it. The best part is: this call
159
+ will "block" until the connection is established, and raise exceptions if the
160
+ connection fails. It works just like the Sockets API.
161
+
162
+ However, it's not actually blocking. Underneath this call is using the
163
+ Actor.receive method to wait for events. This means other Actors can run in
164
+ the background while the current one is waiting for a connection.
165
+
166
+ Furthermore, the Actor making this call can receive other events and they
167
+ will remain undisturbed in the mailbox. The connect method filters for
168
+ messages specifically related to making an outgoing connection.
169
+
170
+ To listen for incoming connections, there's a complimentary method:
171
+
172
+ listener = Revactor::TCP.listen(addr, port)
173
+
174
+ This will listen for incoming connections on the given address and port. It
175
+ returns a listen socket with a #accept method:
176
+
177
+ sock = listener.accept
178
+
179
+ Like TCP.connect, this method will block waiting for connections, but in
180
+ actuality is calling Actor.receive waiting for messages related to incoming
181
+ connections.
182
+
183
+ Now that you have a handle on a Revactor TCP socket, there's several ways you
184
+ can begin using it. The first is using a standard imperative sockets API:
185
+
186
+ data = sock.read(1024)
187
+
188
+ This call will "block" until it reads a kilobyte from the socket. However,
189
+ you may not be interested in a specific amount of data, just whenever data
190
+ is available on the socket. In that case, you can just call the #read method
191
+ without any argument:
192
+
193
+ data = sock.read
194
+
195
+ There's also a corresponding command to write to the socket. Like read this
196
+ command will also "block" until all data has been written out to the socket:
197
+
198
+ sock.write data
199
+
200
+ For Actors that want to deal with both incoming TCP data and messages from
201
+ other Actors, Revactor's TCP sockets also support an approach called
202
+ active mode. Active mode automatically delivers incoming data as a message
203
+ to what's known as the Socket's controller. You can assign the Socket's
204
+ controller whenever you want:
205
+
206
+ sock.controller = Actor.current
207
+
208
+ Once you've done this, you can turn on active mode to begin receiving messages:
209
+
210
+ sock.active = true
211
+ Actor.receive do |filter|
212
+ filter.when(Case[:tcp, sock, Object]) do |_, _, data|
213
+ ...
214
+ end
215
+
216
+ filter.when(Case[:somethingelse, Object]) do |_, message|
217
+ ...
218
+ end
219
+ end
220
+
221
+ (note: _ is an idiom which means ignore/discard a variable)
222
+
223
+ With active mode, the controller will receive all data as quickly as it can be
224
+ read off the socket. If the Actor processing incoming message can't process
225
+ them as quickly as they're being read, then they'll begin piling up in the
226
+ mailbox until the controller is able to catch up (if ever).
227
+
228
+ In order to prevent this from happening, sockets can be set active once:
229
+
230
+ sock.active = :once
231
+
232
+ This means read the next incoming message, then fall back to active = false.
233
+ The underlying system will stop monitoring the socket for incoming data,
234
+ and you're free to spend as much time as you'd like handling it. Once
235
+ you're ready for the next message, just set active to :once again.
236
+
237
+ == Filters
238
+
239
+ Not to be confused with Mailbox filters, Revactor's TCP sockets can each have
240
+ a filter chain. Filters are specified when a connection is created:
241
+
242
+ sock = Revactor::TCP.connect('irc.efnet.org', 6667, :filter => :line)
243
+
244
+ Filters transform data as it's read or written off the wire. In this case
245
+ we're connecting to an IRC server, and the IRC protocol is framed using a
246
+ newline delimiter.
247
+
248
+ The line filter will scan incoming messages for a newline, and buffer until
249
+ it encounters one. When it finds one, it will reassemble the entire message
250
+ from the buffer and deliver it to you in one fell swoop.
251
+
252
+ With the line filter on, receiving messages off IRC is easy:
253
+
254
+ message = sock.read
255
+
256
+ This will provide you with the entire next message, with the newline delimiter
257
+ already removed.
258
+
259
+ If the filter name is a symbol, Revactor will look under its filters directory
260
+ for a class of the cooresponding name. Alternatively you can pass the name of
261
+ a class you created yourself which responds to the methods encode and decode:
262
+
263
+ sock = Revactor::TCP.connect(host, port, :filter => MyFilter)
264
+
265
+ Filter chains can be specified by passing an array:
266
+
267
+ sock = Revactor::TCP.connect(host, port, :filter => [MyFilter, :line])
268
+
269
+ You can pass arguments to your filter's initialize method by passing an array
270
+ with a class name as a member of a filter chain:
271
+
272
+ sock = Revactor::TCP.connect(host, port, :filter => [[Myfilter, 42], :line])
273
+
274
+ In addition to the line filter, Revactor bundles a :packet filter. This filter
275
+ constructs messages with a length prefix that specifies the size of the
276
+ remaining message. This is a simple and straightforward way to frame
277
+ discrete messages on top of a streaming protocol like TCP, and is used for,
278
+ among other things, DRb.
279
+
280
+ == Behaviors
281
+
282
+ Behaviors are kind of like design patterns for Actors. Often you just want
283
+ an Actor to hold some state and service calls which query or mutate that state.
284
+ This behavior is wrapped up as a Server.
285
+
286
+ To begin implementing a server, look at Revactor::Behavior::Server, which is
287
+ a module demonstrating the API. Servers are implemented using a set of
288
+ callbacks which are called in response to certain events.
289
+
290
+ Servers should implement a number of principles, including transactional
291
+ semantics, however due to the side effect potential of mutable state in
292
+ Ruby this isn't possible to achieve.
293
+
294
+ Future versions of Revactor may attempt to address this, and also change the
295
+ present implementation (which is effectively a carbon copy of Erlang's
296
+ gen_server) to be more Ruby-like.
297
+
298
+ == Mongrel
299
+
300
+ Revactor includes complete support for running Mongrel on top of Actors and
301
+ Revactor::TCP. The implementation monkeypatches two methods in the
302
+ Mongrel::HttpServer class. Initial benchmarks show better throughput
303
+ and concurrency than threaded Mongrel running on Ruby 1.8 or 1.9.
304
+
305
+ To use Mongrel on top of Revactor, just:
306
+
307
+ require 'revactor/mongrel'
308
+
309
+ Then call Mongrel within an Actor.start block. The semantics are the same.
310
+ The only difference is that Actors, not Threads, are being used for
311
+ concurrency.
312
+
313
+ == Roadmap
314
+
315
+ Revactor is still in its infancy. Erlang and its Open Telcom Platform are an
316
+ extremely feature rich platform, and many features can be borrowed and
317
+ incorporated into Revactor.
318
+
319
+ The first and foremost feature to be added is the concept of linked Actors.
320
+ This idea is somewhat difficult to explain for the uninitiated, but the general
321
+ concept is that interdependent Actors are linked in a graph. When one Actor
322
+ dies, it kills all linked Actors is well. However, special Actors trap the
323
+ exit messages from the Actors they're linked with. These Actors are generally
324
+ supervisors and restart any linked Actor graphs which crash.
325
+
326
+ Next on the agenda is implementing DRb. This should be possible simply by
327
+ monkeypatching the existing DRb implementation to run on top of Revactor::TCP.
328
+ Once DRb has been implemented it should be fairly trivial to implement
329
+ distributed Actor networks over TCP using DRb as the underlying message
330
+ passing protocol.
331
+
332
+ Long term items include implementation of more Filters, Behaviors, and protocol
333
+ modules. These include an HTTP client (subclassed from the client in Rev),
334
+ an HTTP server adapter (using the Mongrel parser), and the gen_fsm behavior from
335
+ Erlang. Additional areas of concern are addressing the problems of mutable
336
+ state in conjunction with Server and FSM behaviors. A possible solution is
337
+ to implement a tuple which stores immutable collections of primitive types
338
+ like numbers, strings, and symbols, but this approach is likely to be slow.
data/Rakefile ADDED
@@ -0,0 +1,48 @@
1
+ require 'rake'
2
+ require 'rake/rdoctask'
3
+ require 'rake/gempackagetask'
4
+ load 'revactor.gemspec'
5
+
6
+ # Default Rake task
7
+ task :default => :rdoc
8
+
9
+ # RDoc
10
+ Rake::RDocTask.new(:rdoc) do |task|
11
+ task.rdoc_dir = 'doc'
12
+ task.title = 'Revactor'
13
+ task.options = %w(--title Revactor --main README --line-numbers)
14
+ task.rdoc_files.include('bin/**/*.rb')
15
+ task.rdoc_files.include('lib/**/*.rb')
16
+ task.rdoc_files.include('README')
17
+ end
18
+
19
+ # Gem
20
+ Rake::GemPackageTask.new(GEMSPEC) do |pkg|
21
+ pkg.need_tar = true
22
+ end
23
+
24
+ # RSpec
25
+ begin
26
+ require 'spec/rake/spectask'
27
+
28
+ SPECS = FileList['spec/**/*_spec.rb']
29
+
30
+ Spec::Rake::SpecTask.new(:spec) do |task|
31
+ task.spec_files = SPECS
32
+ end
33
+
34
+ namespace :spec do
35
+ Spec::Rake::SpecTask.new(:print) do |task|
36
+ task.spec_files = SPECS
37
+ task.spec_opts="-f s".split
38
+ end
39
+
40
+ Spec::Rake::SpecTask.new(:rcov) do |task|
41
+ task.spec_files = SPECS
42
+ task.rcov = true
43
+ task.rcov_opts = ['--exclude', 'spec']
44
+ end
45
+ end
46
+
47
+ rescue LoadError
48
+ end
@@ -0,0 +1,39 @@
1
+ # An example echo server, written using Revactor::TCP
2
+ # This implementation creates a new actor for each
3
+ # incoming connection.
4
+
5
+ require File.dirname(__FILE__) + '/../lib/revactor'
6
+
7
+ HOST = 'localhost'
8
+ PORT = 4321
9
+
10
+ # Before we can begin using actors we have to call Actor.start
11
+ # Future versions of Revactor will hopefully eliminate this
12
+ Actor.start do
13
+ # Create a new listener socket on the given host and port
14
+ listener = Revactor::TCP.listen(HOST, PORT)
15
+ puts "Listening on #{HOST}:#{PORT}"
16
+
17
+ # Begin receiving connections
18
+ loop do
19
+ # Accept an incoming connection and start a new Actor
20
+ # to handle it
21
+ Actor.spawn(listener.accept) do |sock|
22
+ puts "#{sock.remote_addr}:#{sock.remote_port} connected"
23
+
24
+ # Begin echoing received data
25
+ loop do
26
+ begin
27
+ # Write everything we read
28
+ sock.write sock.read
29
+ rescue EOFError
30
+ puts "#{sock.remote_addr}:#{sock.remote_port} disconnected"
31
+
32
+ # Break (and exit the current actor) if the connection
33
+ # is closed, just like with a normal Ruby socket
34
+ break
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,24 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'cgi'
4
+ require File.dirname(__FILE__) + '/../lib/revactor'
5
+
6
+ Actor.start do
7
+ term = ARGV[0] || 'foobar'
8
+ sock = Revactor::TCP.connect("www.google.com", 80)
9
+
10
+ sock.write [
11
+ "GET /search?q=#{CGI.escape(term)} HTTP/1.0",
12
+ "Host: www.google.com",
13
+ "\r\n"
14
+ ].join("\r\n")
15
+
16
+ loop do
17
+ begin
18
+ STDOUT.write sock.read
19
+ STDOUT.flush
20
+ rescue EOFError
21
+ break
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require File.dirname(__FILE__) + '/../lib/revactor/mongrel'
4
+
5
+ ADDR = '127.0.0.1'
6
+ PORT = 8080
7
+
8
+ Actor.start do
9
+ server = Mongrel::HttpServer.new(ADDR, PORT)
10
+ server.register '/', Mongrel::DirHandler.new(".")
11
+ server.run
12
+
13
+ puts "Running on #{ADDR}:#{PORT}"
14
+ end