pandemic 0.5.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (38) hide show
  1. data/MIT-LICENSE +22 -0
  2. data/Manifest +27 -0
  3. data/README.markdown +133 -0
  4. data/Rakefile +14 -0
  5. data/examples/client/client.rb +17 -0
  6. data/examples/client/constitution.txt +865 -0
  7. data/examples/client/pandemic_client.yml +3 -0
  8. data/examples/server/pandemic_server.yml +3 -0
  9. data/examples/server/word_count_server.rb +47 -0
  10. data/lib/pandemic.rb +42 -0
  11. data/lib/pandemic/client_side/cluster_connection.rb +194 -0
  12. data/lib/pandemic/client_side/config.rb +34 -0
  13. data/lib/pandemic/client_side/connection.rb +40 -0
  14. data/lib/pandemic/client_side/connection_proxy.rb +15 -0
  15. data/lib/pandemic/client_side/pandemize.rb +17 -0
  16. data/lib/pandemic/connection_pool.rb +194 -0
  17. data/lib/pandemic/mutex_counter.rb +50 -0
  18. data/lib/pandemic/requests_per_second.rb +31 -0
  19. data/lib/pandemic/server_side/client.rb +123 -0
  20. data/lib/pandemic/server_side/config.rb +74 -0
  21. data/lib/pandemic/server_side/handler.rb +31 -0
  22. data/lib/pandemic/server_side/peer.rb +211 -0
  23. data/lib/pandemic/server_side/processor.rb +90 -0
  24. data/lib/pandemic/server_side/request.rb +92 -0
  25. data/lib/pandemic/server_side/server.rb +285 -0
  26. data/lib/pandemic/util.rb +15 -0
  27. data/pandemic.gemspec +37 -0
  28. data/test/client_test.rb +87 -0
  29. data/test/connection_pool_test.rb +133 -0
  30. data/test/functional_test.rb +57 -0
  31. data/test/handler_test.rb +31 -0
  32. data/test/mutex_counter_test.rb +73 -0
  33. data/test/peer_test.rb +48 -0
  34. data/test/processor_test.rb +33 -0
  35. data/test/server_test.rb +171 -0
  36. data/test/test_helper.rb +24 -0
  37. data/test/util_test.rb +21 -0
  38. metadata +141 -0
@@ -0,0 +1,15 @@
1
+ module Pandemic
2
+ module Util
3
+ def host_port(str)
4
+ [str[/^[^:]+/], str[/[0-9]+$/].to_i]
5
+ end
6
+
7
+ def logger
8
+ $pandemic_logger
9
+ end
10
+
11
+ def with_mutex(obj)
12
+ obj.extend(MonitorMixin)
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,37 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{pandemic}
5
+ s.version = "0.5.4"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 1.2") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Arya Asemanfar"]
9
+ s.date = %q{2009-09-07}
10
+ s.description = %q{A framework for distributing work for real-time services and offline tasks.}
11
+ s.email = %q{aryaasemanfar@gmail.com}
12
+ s.extra_rdoc_files = ["lib/pandemic/client_side/cluster_connection.rb", "lib/pandemic/client_side/config.rb", "lib/pandemic/client_side/connection.rb", "lib/pandemic/client_side/connection_proxy.rb", "lib/pandemic/client_side/pandemize.rb", "lib/pandemic/connection_pool.rb", "lib/pandemic/mutex_counter.rb", "lib/pandemic/requests_per_second.rb", "lib/pandemic/server_side/client.rb", "lib/pandemic/server_side/config.rb", "lib/pandemic/server_side/handler.rb", "lib/pandemic/server_side/peer.rb", "lib/pandemic/server_side/processor.rb", "lib/pandemic/server_side/request.rb", "lib/pandemic/server_side/server.rb", "lib/pandemic/util.rb", "lib/pandemic.rb", "README.markdown"]
13
+ s.files = ["examples/client/client.rb", "examples/client/constitution.txt", "examples/client/pandemic_client.yml", "examples/server/pandemic_server.yml", "examples/server/word_count_server.rb", "lib/pandemic/client_side/cluster_connection.rb", "lib/pandemic/client_side/config.rb", "lib/pandemic/client_side/connection.rb", "lib/pandemic/client_side/connection_proxy.rb", "lib/pandemic/client_side/pandemize.rb", "lib/pandemic/connection_pool.rb", "lib/pandemic/mutex_counter.rb", "lib/pandemic/requests_per_second.rb", "lib/pandemic/server_side/client.rb", "lib/pandemic/server_side/config.rb", "lib/pandemic/server_side/handler.rb", "lib/pandemic/server_side/peer.rb", "lib/pandemic/server_side/processor.rb", "lib/pandemic/server_side/request.rb", "lib/pandemic/server_side/server.rb", "lib/pandemic/util.rb", "lib/pandemic.rb", "Manifest", "MIT-LICENSE", "pandemic.gemspec", "Rakefile", "README.markdown", "test/client_test.rb", "test/connection_pool_test.rb", "test/functional_test.rb", "test/handler_test.rb", "test/mutex_counter_test.rb", "test/peer_test.rb", "test/processor_test.rb", "test/server_test.rb", "test/test_helper.rb", "test/util_test.rb"]
14
+ s.homepage = %q{https://github.com/arya/pandemic/}
15
+ s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Pandemic", "--main", "README.markdown"]
16
+ s.require_paths = ["lib"]
17
+ s.rubyforge_project = %q{pandemic}
18
+ s.rubygems_version = %q{1.3.4}
19
+ s.summary = %q{A framework for distributing work for real-time services and offline tasks.}
20
+ s.test_files = ["test/client_test.rb", "test/connection_pool_test.rb", "test/functional_test.rb", "test/handler_test.rb", "test/mutex_counter_test.rb", "test/peer_test.rb", "test/processor_test.rb", "test/server_test.rb", "test/test_helper.rb", "test/util_test.rb"]
21
+
22
+ if s.respond_to? :specification_version then
23
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
24
+ s.specification_version = 3
25
+
26
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
27
+ s.add_development_dependency(%q<shoulda>, [">= 0"])
28
+ s.add_development_dependency(%q<mocha>, [">= 0"])
29
+ else
30
+ s.add_dependency(%q<shoulda>, [">= 0"])
31
+ s.add_dependency(%q<mocha>, [">= 0"])
32
+ end
33
+ else
34
+ s.add_dependency(%q<shoulda>, [">= 0"])
35
+ s.add_dependency(%q<mocha>, [">= 0"])
36
+ end
37
+ end
@@ -0,0 +1,87 @@
1
+ require 'test_helper'
2
+
3
+ class ClientTest < Test::Unit::TestCase
4
+ include TestHelper
5
+
6
+ context "with a client object" do
7
+ setup do
8
+ @server = mock()
9
+ @server.stubs(:client_closed)
10
+ @server.stubs(:handle_client_request)
11
+ @server.expects(:running).at_least_once.returns(true).then.returns(false)
12
+ @connection = mock()
13
+ @connection.stubs(:write)
14
+ @connection.stubs(:flush)
15
+ @connection.stubs(:peeraddr).returns(['','','',''])
16
+ @connection.expects(:nil?).returns(false).at_least_once
17
+ @client = Pandemic::ServerSide::Client.new(@connection, @server)
18
+ end
19
+
20
+ should "read size from the connection" do
21
+ @connection.expects(:gets).returns("5\n")
22
+ @connection.expects(:read).with(5)
23
+ @server.expects(:client_closed).with(@client)
24
+ @client.listen
25
+ wait_for_threads
26
+ end
27
+
28
+ should "read body from the connection" do
29
+ @connection.expects(:gets).returns("5\n")
30
+ @connection.expects(:read).with(5).returns("hello")
31
+ @server.expects(:client_closed).with(@client)
32
+ @client.listen
33
+ wait_for_threads
34
+ end
35
+
36
+ should "call handle request body on server" do
37
+ @connection.expects(:gets).returns("5\n")
38
+ @connection.expects(:read).with(5).returns("hello")
39
+
40
+ request = mock()
41
+ Pandemic::ServerSide::Request.expects(:new).returns(request)
42
+ @server.expects(:handle_client_request).with(request).returns(nil)
43
+ @connection.stubs(:write)
44
+ @connection.stubs(:flush)
45
+
46
+ @server.expects(:client_closed).with(@client)
47
+ @client.listen
48
+ wait_for_threads
49
+ end
50
+
51
+ should "write response back to client" do
52
+ @connection.expects(:gets).returns("5\n")
53
+ @connection.expects(:read).with(5).returns("hello")
54
+
55
+ request = mock()
56
+ response = "olleh"
57
+ Pandemic::ServerSide::Request.expects(:new).returns(request)
58
+ @server.expects(:handle_client_request).with(request).returns(response)
59
+
60
+ @connection.expects(:write).with("5\n#{response}")
61
+ @connection.expects(:flush)
62
+
63
+ @server.expects(:client_closed).with(@client)
64
+ @client.listen
65
+ wait_for_threads
66
+ end
67
+
68
+ should "close the connection on nil response" do
69
+ @connection.expects(:gets).returns(nil)
70
+ @connection.expects(:close)
71
+
72
+ @server.expects(:client_closed).with(@client)
73
+ @client.listen
74
+ wait_for_threads
75
+ end
76
+
77
+ should "close the connection on disconnect" do
78
+ @connection.expects(:gets).raises(Pandemic::ServerSide::Client::DisconnectClient)
79
+ @connection.expects(:closed?).returns(false)
80
+ @connection.expects(:close)
81
+
82
+ @server.expects(:client_closed).with(@client)
83
+ @client.listen
84
+ wait_for_threads
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,133 @@
1
+ require 'test_helper'
2
+
3
+ class ConnectionPoolTest < Test::Unit::TestCase
4
+ include TestHelper
5
+ context "without a create connection block" do
6
+ setup do
7
+ @connection_pool = Pandemic::ConnectionPool.new
8
+ end
9
+
10
+ should "raise an exception when trying to connect" do
11
+ assert_raises Pandemic::ConnectionPool::CreateConnectionUndefinedException do
12
+ @connection_pool.connect
13
+ end
14
+ end
15
+ end
16
+
17
+ context "with a connection pool" do
18
+ setup do
19
+ @connection_pool = Pandemic::ConnectionPool.new
20
+ @connection_creator = mock()
21
+ @connection_destroyer = mock()
22
+ end
23
+
24
+ should "call create connection after its defined" do
25
+ @connection_creator.expects(:create).once
26
+ @connection_pool.create_connection do
27
+ @connection_creator.create
28
+ end
29
+ end
30
+
31
+ should "call destroyer on disconnect" do
32
+ @connection_creator.expects(:create).once
33
+ @connection_destroyer.expects(:destroy).once
34
+ @connection_pool.create_connection do
35
+ @connection_creator.create
36
+ conn = mock()
37
+ conn.expects(:closed?).returns(false).at_least(0)
38
+ conn
39
+ end
40
+
41
+ @connection_pool.destroy_connection do
42
+ @connection_destroyer.destroy
43
+ end
44
+
45
+ @connection_pool.disconnect
46
+ end
47
+
48
+ end
49
+
50
+ context "with a max connections of 2" do
51
+ setup do
52
+ @connection_pool = Pandemic::ConnectionPool.new(:max_connections => 2, :timeout => 0.01)
53
+ @connection_creator = mock()
54
+ end
55
+
56
+ should "raise timeout exception when no connections available" do
57
+ @connection_creator.expects(:create).twice
58
+ @connection_pool.create_connection do
59
+ @connection_creator.create
60
+ conn = mock()
61
+ conn.expects(:closed?).returns(false).at_least(0)
62
+ conn
63
+ end
64
+
65
+ assert_raises Pandemic::ConnectionPool::TimedOutWaitingForConnectionException do
66
+ @connection_pool.with_connection do |conn1|
67
+ @connection_pool.with_connection do |conn2|
68
+ @connection_pool.with_connection do |conn3|
69
+ fail("there should only be two connections")
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+
76
+ should "should checkin a connection and allow someone check the same one out" do
77
+ @connection_creator.expects(:create).twice
78
+ @connection_pool.create_connection do
79
+ @connection_creator.create
80
+ conn = mock()
81
+ conn.expects(:closed?).returns(false).at_least(0)
82
+ conn
83
+ end
84
+
85
+ @connection_pool.with_connection do |conn1|
86
+ conn2, conn3 = nil, nil
87
+
88
+ @connection_pool.with_connection do |conn|
89
+ conn2 = conn
90
+ end
91
+ @connection_pool.with_connection do |conn|
92
+ conn3 = conn
93
+ end
94
+
95
+ assert_equal conn2, conn3
96
+ end
97
+ end
98
+
99
+ should "should checkin connection even if there is an exception" do
100
+ @connection_creator.expects(:create).once
101
+ @connection_pool.create_connection do
102
+ @connection_creator.create
103
+ conn = mock()
104
+ conn.expects(:closed?).returns(false).at_least(0)
105
+ conn
106
+ end
107
+ before = @connection_pool.available_count
108
+ begin
109
+ @connection_pool.with_connection do |conn1|
110
+ raise TestException
111
+ end
112
+ rescue TestException
113
+ end
114
+ after = @connection_pool.available_count
115
+
116
+ assert_equal before, after
117
+ end
118
+ end
119
+
120
+ context "with a min connections of 2" do
121
+ setup do
122
+ @connection_pool = Pandemic::ConnectionPool.new(:min_connections => 2)
123
+ @connection_creator = mock()
124
+ end
125
+
126
+ should "call create connection twice" do
127
+ @connection_creator.expects(:create).twice
128
+ @connection_pool.create_connection do
129
+ @connection_creator.create
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,57 @@
1
+ require 'test_helper'
2
+
3
+ class FunctionalTest < Test::Unit::TestCase
4
+ include TestHelper
5
+ should "work" do
6
+ ignore_threads = Thread.list
7
+ ARGV.replace(["-i", "0", "-c", "test/pandemic_server.yml"]) # :(
8
+ Pandemic::ClientSide::Config.config_path = "test/pandemic_client.yml"
9
+ server = epidemic!
10
+ server.handler = Class.new(Pandemic::ServerSide::Handler) do
11
+ def process(body)
12
+ body.reverse
13
+ end
14
+ end
15
+ server.start
16
+
17
+ client = Class.new do
18
+ include Pandemize
19
+ end.new
20
+ client.extend(Pandemize)
21
+ assert_equal "dlrow olleh", client.pandemic.request("hello world")
22
+ client.pandemic.shutdown
23
+ server.stop
24
+ wait_for_threads(ignore_threads)
25
+ end
26
+
27
+ should "work with multiple peers" do
28
+ ignore_threads = Thread.list
29
+ handler = Class.new(Pandemic::ServerSide::Handler) do
30
+ def process(body)
31
+ body.reverse
32
+ end
33
+ end
34
+
35
+ ARGV.replace(["-i", "0", "-c", "test/pandemic_server.yml"]) # :(
36
+ server = epidemic!
37
+ server.handler = handler
38
+ server.start
39
+
40
+ ARGV.replace(["-i", "1", "-c", "test/pandemic_server.yml"]) # :(
41
+ server2 = epidemic!
42
+ server2.handler = handler
43
+ server2.start
44
+
45
+ Pandemic::ClientSide::Config.config_path = "test/pandemic_client.yml"
46
+
47
+ client = Class.new do
48
+ include Pandemize
49
+ end.new
50
+ client.extend(Pandemize)
51
+ assert_equal "raboofraboof", client.pandemic.request("foobar")
52
+ client.pandemic.shutdown
53
+ server.stop
54
+ server2.stop
55
+ wait_for_threads(ignore_threads)
56
+ end
57
+ end
@@ -0,0 +1,31 @@
1
+ require 'test_helper'
2
+
3
+ class HandlerTest < Test::Unit::TestCase
4
+ include TestHelper
5
+
6
+ context "with a request object" do
7
+ setup do
8
+ @request = mock()
9
+ @servers = {
10
+ 1 => :self,
11
+ 2 => :disconnected,
12
+ 3 => :connected
13
+ }
14
+ @handler = Pandemic::ServerSide::Handler.new
15
+ end
16
+
17
+ should "concatenate all the results" do
18
+ @request.expects(:responses).once.returns(%w{1 2 3})
19
+ assert_equal "123", @handler.reduce(@request)
20
+ end
21
+
22
+ should "map to all non-disconnected nodes" do
23
+ @request.expects(:body).twice.returns("123")
24
+ map = @handler.partition(@request, @servers)
25
+ # see setup for @servers
26
+ assert_equal 2, map.size
27
+ assert_equal "123", map[1]
28
+ assert_equal "123", map[3]
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,73 @@
1
+ require 'test_helper'
2
+
3
+ class MutexCounterTest < Test::Unit::TestCase
4
+ context "with a new counter" do
5
+ setup do
6
+ @counter = Pandemic::MutexCounter.new
7
+ end
8
+
9
+ should "start at 0" do
10
+ assert_equal 0, @counter.value
11
+ end
12
+
13
+ should "increment to 1 after one call to inc" do
14
+ assert_equal 0, @counter.value
15
+ assert_equal 1, @counter.inc
16
+ assert_equal 1, @counter.value
17
+ end
18
+
19
+ should "decrement to 0 after one call to inc" do
20
+ assert_equal 0, @counter.value
21
+ assert_equal 1, @counter.inc
22
+ assert_equal 1, @counter.value
23
+ assert_equal 0, @counter.decr
24
+ assert_equal 0, @counter.value
25
+ end
26
+
27
+ should "only decrement to 0" do
28
+ assert_equal 0, @counter.value
29
+ assert_equal 1, @counter.inc
30
+ assert_equal 1, @counter.value
31
+ assert_equal 0, @counter.decr
32
+ assert_equal 0, @counter.value
33
+ assert_equal 0, @counter.decr
34
+ assert_equal 0, @counter.value
35
+ end
36
+
37
+ should "be thread safe" do
38
+ # Not exactly a perfect test, but I'm not sure how to actually test
39
+ # this without putting some code in the counter for this reason.
40
+ threads = []
41
+ 5.times { threads << Thread.new { 100.times { @counter.inc }}}
42
+ threads.each {|t| t.join } # make sure they're all done
43
+ assert_equal 500, @counter.value
44
+ end
45
+ end
46
+
47
+ context "with a max of 10" do
48
+ setup do
49
+ @counter = Pandemic::MutexCounter.new(10)
50
+ end
51
+
52
+ should "cycle from 1 to 10" do
53
+ expected = (1..10).to_a + [1]
54
+ actual = (1..11).collect { @counter.inc }
55
+ assert_equal expected, actual
56
+ end
57
+
58
+ should "have the correct 'real_total'" do
59
+ 11.times { @counter.inc }
60
+ assert_equal 1, @counter.value
61
+ assert_equal 11, @counter.real_total
62
+ end
63
+
64
+ should "maintain correct 'real_total' with decrement" do
65
+ 11.times { @counter.inc }
66
+ assert_equal 1, @counter.value
67
+ assert_equal 11, @counter.real_total
68
+ assert_equal 0, @counter.decr
69
+ assert_equal 10, @counter.real_total
70
+ end
71
+
72
+ end
73
+ end
@@ -0,0 +1,48 @@
1
+ require 'test_helper'
2
+
3
+ class PeerTest < Test::Unit::TestCase
4
+ include TestHelper
5
+
6
+ should "initialize a new connection pool" do
7
+ connection_pool = mock()
8
+ Pandemic::ConnectionPool.expects(:new).returns(connection_pool)
9
+ connection_pool.expects(:create_connection)
10
+
11
+ server = mock()
12
+ peer = Pandemic::ServerSide::Peer.new("localhost:4000", server)
13
+ end
14
+
15
+ should "create a tcp socket" do
16
+ connection_pool = mock()
17
+ Pandemic::ConnectionPool.expects(:new).returns(connection_pool)
18
+ connection_pool.expects(:create_connection).yields
19
+ TCPSocket.expects(:new).with("localhost", 4000)
20
+
21
+ server = mock()
22
+ peer = Pandemic::ServerSide::Peer.new("localhost:4000", server)
23
+ end
24
+
25
+ context "with conn. pool" do
26
+ setup do
27
+ @connection_pool = mock()
28
+ Pandemic::ConnectionPool.expects(:new).returns(@connection_pool)
29
+ @connection_pool.expects(:create_connection)
30
+
31
+ @server = mock()
32
+ @peer = Pandemic::ServerSide::Peer.new("localhost:4000", @server)
33
+ end
34
+
35
+ should "send client request to peer connection" do
36
+ request, body = stub(:hash => "asdasdfadsf"), "hello world"
37
+ @connection_pool.stubs(:available_count => 1, :connections_count => 1)
38
+ conn = mock()
39
+ @connection_pool.expects(:with_connection).yields(conn)
40
+
41
+ conn.stubs(:closed? => false)
42
+ conn.expects(:write).with("P#{request.hash}#{[body.size].pack('N')}#{body}")
43
+ conn.expects(:flush)
44
+
45
+ @peer.client_request(request, body)
46
+ end
47
+ end
48
+ end