revactor 0.1.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/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