pandemic 0.5.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.
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