arya-pandemic 0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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
+