arya-pandemic 0.2
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/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
|
+
|