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.
@@ -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
+