arya-pandemic 0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +99 -0
- data/Rakefile +14 -0
- data/lib/pandemic.rb +40 -0
- data/lib/pandemic/client_side/cluster_connection.rb +129 -0
- data/lib/pandemic/client_side/config.rb +33 -0
- data/lib/pandemic/client_side/connection.rb +31 -0
- data/lib/pandemic/client_side/connection_proxy.rb +15 -0
- data/lib/pandemic/client_side/pandemize.rb +17 -0
- data/lib/pandemic/connection_pool.rb +117 -0
- data/lib/pandemic/mutex_counter.rb +24 -0
- data/lib/pandemic/server_side/client.rb +86 -0
- data/lib/pandemic/server_side/config.rb +55 -0
- data/lib/pandemic/server_side/handler.rb +27 -0
- data/lib/pandemic/server_side/peer.rb +203 -0
- data/lib/pandemic/server_side/request.rb +72 -0
- data/lib/pandemic/server_side/server.rb +231 -0
- data/lib/pandemic/util.rb +26 -0
- data/pandemic.gemspec +31 -0
- metadata +91 -0
@@ -0,0 +1,231 @@
|
|
1
|
+
module Pandemic
|
2
|
+
module ServerSide
|
3
|
+
class Server
|
4
|
+
include Util
|
5
|
+
class StopServer < Exception; end
|
6
|
+
class << self
|
7
|
+
def boot
|
8
|
+
Config.load
|
9
|
+
# Process.setrlimit(Process::RLIMIT_NOFILE, 4096) # arbitrary high number of max file descriptors.
|
10
|
+
server = self.new
|
11
|
+
set_signal_traps(server)
|
12
|
+
server
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
def set_signal_traps(server)
|
17
|
+
interrupt_tries = 0
|
18
|
+
Signal.trap(Signal.list["INT"]) do
|
19
|
+
interrupt_tries += 1
|
20
|
+
if interrupt_tries == 1
|
21
|
+
server.stop
|
22
|
+
else
|
23
|
+
exit
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
attr_reader :host, :port, :running
|
29
|
+
def initialize
|
30
|
+
@host, @port = host_port(Config.bind_to)
|
31
|
+
|
32
|
+
@clients = []
|
33
|
+
@total_clients = 0
|
34
|
+
@clients_mutex = Mutex.new
|
35
|
+
|
36
|
+
@peers = {}
|
37
|
+
@servers = Config.servers
|
38
|
+
@servers.each do |peer|
|
39
|
+
next if peer == Config.bind_to # not a peer, it's itself
|
40
|
+
@peers[peer] = Peer.new(peer, self)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def handler=(handler)
|
45
|
+
@handler = handler
|
46
|
+
end
|
47
|
+
|
48
|
+
def start
|
49
|
+
raise "You must specify a handler" unless @handler
|
50
|
+
|
51
|
+
debug("Listening")
|
52
|
+
@listener = TCPServer.new(@host, @port)
|
53
|
+
@running = true
|
54
|
+
@running_since = Time.now
|
55
|
+
|
56
|
+
@peers.values.each { |peer| peer.connect }
|
57
|
+
|
58
|
+
@listener_thread = Thread.new do
|
59
|
+
begin
|
60
|
+
while @running
|
61
|
+
begin
|
62
|
+
conn = @listener.accept
|
63
|
+
Thread.new(conn) { |c| handle_connection(c) }
|
64
|
+
rescue Errno::ECONNABORTED, Errno::EINTR
|
65
|
+
debug("Connection accepted aborted")
|
66
|
+
conn.close if conn && !conn.closed?
|
67
|
+
end
|
68
|
+
end
|
69
|
+
rescue StopServer
|
70
|
+
info("Stopping server")
|
71
|
+
@listener.close if @listener
|
72
|
+
@peers.values.each { |p| p.disconnect }
|
73
|
+
@clients.each {|c| c.close }
|
74
|
+
rescue Exception => e
|
75
|
+
warn("Unhandled exception in server listening thread: #{e.inspect}")
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def stop
|
81
|
+
@running = false
|
82
|
+
@listener_thread.raise(StopServer)
|
83
|
+
end
|
84
|
+
|
85
|
+
def handle_connection(connection)
|
86
|
+
begin
|
87
|
+
connection.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1) if Socket.constants.include?('TCP_NODELAY')
|
88
|
+
|
89
|
+
identification = connection.gets.strip
|
90
|
+
info("Incoming connection from #{connection.peeraddr.values_at(3,1).join(":")} (#{identification})")
|
91
|
+
if identification =~ /^SERVER ([a-zA-Z0-9.]+:[0-9]+)$/
|
92
|
+
debug("Recognized as peer")
|
93
|
+
host, port = host_port($1)
|
94
|
+
matching_peer = @peers.values.detect { |peer| [peer.host, peer.port] == [host, port] }
|
95
|
+
debug("Found matching peer")
|
96
|
+
matching_peer.add_incoming_connection(connection) unless matching_peer.nil?
|
97
|
+
elsif identification =~ /^CLIENT$/
|
98
|
+
debug("Recognized as client")
|
99
|
+
@clients_mutex.synchronize do
|
100
|
+
@clients << Client.new(connection, self).listen
|
101
|
+
end
|
102
|
+
elsif identification =~ /^stats$/
|
103
|
+
debug("Stats request received")
|
104
|
+
print_stats(connection)
|
105
|
+
else
|
106
|
+
debug("Unrecognized connection. Closing.")
|
107
|
+
connection.close # i dunno you
|
108
|
+
end
|
109
|
+
rescue Exception => e
|
110
|
+
warn("Unhandled exception in handle connection method: #{e.inspect}")
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def handle_client_request(request)
|
115
|
+
info("Handling client request")
|
116
|
+
map = @handler.map(request, connection_statuses)
|
117
|
+
request.max_responses = map.size
|
118
|
+
debug("Sending client request to #{map.size} handlers (#{request.hash})")
|
119
|
+
|
120
|
+
map.each do |peer, body|
|
121
|
+
if @peers[peer]
|
122
|
+
@peers[peer].client_request(request, body)
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
if map[signature]
|
127
|
+
debug("Processing #{request.hash}")
|
128
|
+
Thread.new do
|
129
|
+
begin
|
130
|
+
request.add_response(self.process(map[signature]))
|
131
|
+
rescue Exception => e
|
132
|
+
warn("Unhandled exception in local processing: #{e.inspect}")
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
debug("Waiting for responses")
|
138
|
+
request.wait_for_responses
|
139
|
+
|
140
|
+
debug("Done waiting for responses, calling reduce")
|
141
|
+
@handler.reduce(request)
|
142
|
+
end
|
143
|
+
|
144
|
+
def process(body)
|
145
|
+
@handler.process(body)
|
146
|
+
end
|
147
|
+
|
148
|
+
def signature
|
149
|
+
"#{@host}:#{@port}"
|
150
|
+
end
|
151
|
+
|
152
|
+
def connection_statuses
|
153
|
+
@servers.inject({}) do |statuses, server|
|
154
|
+
if server == signature
|
155
|
+
statuses[server] = :self
|
156
|
+
else
|
157
|
+
statuses[server] = @peers[server].connected? ? :connected : :disconnected
|
158
|
+
end
|
159
|
+
statuses
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def client_closed(client)
|
164
|
+
@clients_mutex.synchronize do
|
165
|
+
@clients.delete(client)
|
166
|
+
end
|
167
|
+
end
|
168
|
+
|
169
|
+
private
|
170
|
+
def print_stats(connection)
|
171
|
+
begin
|
172
|
+
stats = collect_stats
|
173
|
+
str = []
|
174
|
+
str << "Uptime: #{stats[:uptime]}"
|
175
|
+
str << "Number of Threads: #{stats[:num_threads]}"
|
176
|
+
str << "Connected Clients: #{stats[:connected_clients]}"
|
177
|
+
str << "Clients Ever: #{stats[:total_clients]}"
|
178
|
+
str << "Connected Peers: #{stats[:connected_peers]}"
|
179
|
+
str << "Disconnected Peers: #{stats[:disconnected_peers]}"
|
180
|
+
str << "Total Requests: #{stats[:num_requests]}"
|
181
|
+
str << "Pending Requests: #{stats[:pending_requests]}"
|
182
|
+
str << "Late Responses: #{stats[:late_responses]}"
|
183
|
+
connection.puts(str.join("\n"))
|
184
|
+
end while (s = connection.gets) && (s.strip == "stats" || s.strip == "")
|
185
|
+
connection.close if connection && !connection.closed?
|
186
|
+
end
|
187
|
+
|
188
|
+
def debug(msg)
|
189
|
+
logger.debug("Server #{signature}") {msg}
|
190
|
+
end
|
191
|
+
|
192
|
+
def info(msg)
|
193
|
+
logger.info("Server #{signature}") {msg}
|
194
|
+
end
|
195
|
+
|
196
|
+
def warn(msg)
|
197
|
+
logger.warn("Server #{signature}") {msg}
|
198
|
+
end
|
199
|
+
|
200
|
+
def collect_stats
|
201
|
+
results = {}
|
202
|
+
results[:num_threads] = Thread.list.size
|
203
|
+
results[:connected_clients], results[:total_clients] = \
|
204
|
+
@clients_mutex.synchronize { [@clients.size, @total_clients] }
|
205
|
+
|
206
|
+
results[:connected_peers], results[:disconnected_peers] = \
|
207
|
+
connection_statuses.inject([0,0]) do |counts, (server,status)|
|
208
|
+
if status == :connected
|
209
|
+
counts[0] += 1
|
210
|
+
elsif status == :disconnected
|
211
|
+
counts[1] += 1
|
212
|
+
end
|
213
|
+
counts
|
214
|
+
end
|
215
|
+
|
216
|
+
results[:num_requests] = Request.total_request_count
|
217
|
+
results[:late_responses] = Request.total_late_responses
|
218
|
+
|
219
|
+
results[:pending_requests] = @clients_mutex.synchronize do
|
220
|
+
@clients.inject(0) do |pending, client|
|
221
|
+
pending + (client.received_requests - client.responded_requests)
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
results[:uptime] = Time.now - @running_since
|
226
|
+
results
|
227
|
+
end
|
228
|
+
|
229
|
+
end
|
230
|
+
end
|
231
|
+
end
|
@@ -0,0 +1,26 @@
|
|
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
|
+
$logger
|
9
|
+
end
|
10
|
+
|
11
|
+
def bm(title, &block)
|
12
|
+
@times ||= Hash.new(0)
|
13
|
+
begin
|
14
|
+
start = Time.now.to_f
|
15
|
+
yield block
|
16
|
+
ensure
|
17
|
+
@times[title] += (Time.now.to_f - start)
|
18
|
+
$stdout.puts("#{title} #{@times[title]}")
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def with_mutex(obj)
|
23
|
+
obj.extend(MonitorMixin)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/pandemic.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = %q{pandemic}
|
5
|
+
s.version = "0.2"
|
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-03-10}
|
10
|
+
s.description = %q{Distribute MapReduce to any of the workers and it will spread, like a pandemic.}
|
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/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/request.rb", "lib/pandemic/server_side/server.rb", "lib/pandemic/util.rb", "lib/pandemic.rb", "README.markdown"]
|
13
|
+
s.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/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/request.rb", "lib/pandemic/server_side/server.rb", "lib/pandemic/util.rb", "lib/pandemic.rb", "Rakefile", "README.markdown", "Manifest", "pandemic.gemspec"]
|
14
|
+
s.has_rdoc = true
|
15
|
+
s.homepage = %q{}
|
16
|
+
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Pandemic", "--main", "README.markdown"]
|
17
|
+
s.require_paths = ["lib"]
|
18
|
+
s.rubyforge_project = %q{pandemic}
|
19
|
+
s.rubygems_version = %q{1.3.1}
|
20
|
+
s.summary = %q{Distribute MapReduce to any of the workers and it will spread, like a pandemic.}
|
21
|
+
|
22
|
+
if s.respond_to? :specification_version then
|
23
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
24
|
+
s.specification_version = 2
|
25
|
+
|
26
|
+
if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
|
27
|
+
else
|
28
|
+
end
|
29
|
+
else
|
30
|
+
end
|
31
|
+
end
|
metadata
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: arya-pandemic
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: "0.2"
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Arya Asemanfar
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
|
12
|
+
date: 2009-03-10 00:00:00 -07:00
|
13
|
+
default_executable:
|
14
|
+
dependencies: []
|
15
|
+
|
16
|
+
description: Distribute MapReduce to any of the workers and it will spread, like a pandemic.
|
17
|
+
email: aryaasemanfar@gmail.com
|
18
|
+
executables: []
|
19
|
+
|
20
|
+
extensions: []
|
21
|
+
|
22
|
+
extra_rdoc_files:
|
23
|
+
- lib/pandemic/client_side/cluster_connection.rb
|
24
|
+
- lib/pandemic/client_side/config.rb
|
25
|
+
- lib/pandemic/client_side/connection.rb
|
26
|
+
- lib/pandemic/client_side/connection_proxy.rb
|
27
|
+
- lib/pandemic/client_side/pandemize.rb
|
28
|
+
- lib/pandemic/connection_pool.rb
|
29
|
+
- lib/pandemic/mutex_counter.rb
|
30
|
+
- lib/pandemic/server_side/client.rb
|
31
|
+
- lib/pandemic/server_side/config.rb
|
32
|
+
- lib/pandemic/server_side/handler.rb
|
33
|
+
- lib/pandemic/server_side/peer.rb
|
34
|
+
- lib/pandemic/server_side/request.rb
|
35
|
+
- lib/pandemic/server_side/server.rb
|
36
|
+
- lib/pandemic/util.rb
|
37
|
+
- lib/pandemic.rb
|
38
|
+
- README.markdown
|
39
|
+
files:
|
40
|
+
- lib/pandemic/client_side/cluster_connection.rb
|
41
|
+
- lib/pandemic/client_side/config.rb
|
42
|
+
- lib/pandemic/client_side/connection.rb
|
43
|
+
- lib/pandemic/client_side/connection_proxy.rb
|
44
|
+
- lib/pandemic/client_side/pandemize.rb
|
45
|
+
- lib/pandemic/connection_pool.rb
|
46
|
+
- lib/pandemic/mutex_counter.rb
|
47
|
+
- lib/pandemic/server_side/client.rb
|
48
|
+
- lib/pandemic/server_side/config.rb
|
49
|
+
- lib/pandemic/server_side/handler.rb
|
50
|
+
- lib/pandemic/server_side/peer.rb
|
51
|
+
- lib/pandemic/server_side/request.rb
|
52
|
+
- lib/pandemic/server_side/server.rb
|
53
|
+
- lib/pandemic/util.rb
|
54
|
+
- lib/pandemic.rb
|
55
|
+
- Rakefile
|
56
|
+
- README.markdown
|
57
|
+
- Manifest
|
58
|
+
- pandemic.gemspec
|
59
|
+
has_rdoc: true
|
60
|
+
homepage: ""
|
61
|
+
post_install_message:
|
62
|
+
rdoc_options:
|
63
|
+
- --line-numbers
|
64
|
+
- --inline-source
|
65
|
+
- --title
|
66
|
+
- Pandemic
|
67
|
+
- --main
|
68
|
+
- README.markdown
|
69
|
+
require_paths:
|
70
|
+
- lib
|
71
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - ">="
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: "0"
|
76
|
+
version:
|
77
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: "1.2"
|
82
|
+
version:
|
83
|
+
requirements: []
|
84
|
+
|
85
|
+
rubyforge_project: pandemic
|
86
|
+
rubygems_version: 1.2.0
|
87
|
+
signing_key:
|
88
|
+
specification_version: 2
|
89
|
+
summary: Distribute MapReduce to any of the workers and it will spread, like a pandemic.
|
90
|
+
test_files: []
|
91
|
+
|