DanaDanger-equity 0.1

Sign up to get free protection for your applications and to get access to all the features.
data/bin/equity ADDED
@@ -0,0 +1,138 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # equity 0.1 - simple queueing software load balancer
4
+ #
5
+ # Usage: equity [-d] <listen-port> [<node-address>:]<node-port> ...
6
+ #
7
+ # The -d option turns on debug mode. Equity will remain in the foreground and
8
+ # print status messages.
9
+ #
10
+ # Node addresses default to localhost.
11
+ #
12
+
13
+ require 'socket'
14
+ require 'equity/node'
15
+
16
+ # Prints a debug message if debugging is enabled.
17
+ def debug(socket, message)
18
+ return unless $debugging
19
+ print "[#{socket.object_id.to_s(16)}] " if socket
20
+ puts message
21
+ end
22
+
23
+
24
+ # Process arguments and instantiate nodes.
25
+ $debugging = !!ARGV.delete('-d')
26
+
27
+ if ARGV.length < 2
28
+ STDERR.puts 'equity 0.1'
29
+ STDERR.puts 'Usage: equity [-d] <listen-port> [<node-address>:]<node-port> ...'
30
+ STDERR.print "\n"
31
+ STDERR.puts 'The -d option turns on debug mode. Equity will remain in the foreground and'
32
+ STDERR.puts 'print status messages.'
33
+ STDERR.print "\n"
34
+ STDERR.puts 'Node addresses default to localhost.'
35
+ exit(64) # EX_USAGE
36
+ end
37
+
38
+ $listen_port = ARGV.shift.to_i
39
+
40
+ $nodes = []
41
+ ARGV.each do |node_spec|
42
+ address, port = node_spec.split(':', 2)
43
+ if port.nil?
44
+ port = address
45
+ address = 'localhost'
46
+ end
47
+ $nodes << Equity::Node.new(address, port)
48
+ end
49
+
50
+ # Daemonize.
51
+ unless $debugging
52
+ fork && exit
53
+ Process.setsid
54
+ trap 'SIGHUP', 'IGNORE'
55
+ fork && exit
56
+ Dir.chdir '/tmp'
57
+ File.umask 0000
58
+ ObjectSpace.each_object(IO) do |io|
59
+ unless [STDIN, STDOUT, STDERR].include?(io)
60
+ io.close rescue nil
61
+ end
62
+ end
63
+ STDIN.reopen "/dev/null"
64
+ STDOUT.reopen "/dev/null", "a"
65
+ STDERR.reopen STDOUT
66
+ end
67
+
68
+ # Start up server.
69
+ $client_queue = []
70
+
71
+ $server = TCPServer.new(nil, $listen_port)
72
+ $server.listen(5)
73
+
74
+ # Set up SIGINT handler to print node counters.
75
+ trap 'SIGINT', Proc.new {
76
+ debug(nil, "\nNode Counters")
77
+ $nodes.each do |node|
78
+ debug(nil, "#{node} - #{node.counter} connections")
79
+ end
80
+ exit
81
+ }
82
+
83
+ puts "Ready on port #{$listen_port}" if $debugging
84
+
85
+ # Loop forever.
86
+ while true
87
+ # Wait for one or more sockets to be readable.
88
+ sockets = [$server, $nodes.collect {|n| n.sockets}]
89
+ sockets.flatten!
90
+ selected = select(sockets, nil, nil, 5)
91
+ if selected
92
+ selected[0].each do |socket|
93
+ if socket == $server
94
+ # Incoming connection. Accept it and queue it.
95
+ client = $server.accept
96
+ $client_queue << client
97
+ debug(client, 'connection accepted')
98
+ else
99
+ # Data received. Transfer it to the socket's mate.
100
+ node = $nodes.find {|n| n.owns_socket?(socket)}
101
+ next if node.nil?
102
+ begin
103
+ data = socket.recvfrom(65536)[0]
104
+ raise Errno::EPIPE if data.empty?
105
+ socket.mate.write(data)
106
+ rescue Errno::EPIPE, Errno::ECONNRESET
107
+ # Connection closed. Disconnect this node.
108
+ node.disconnect
109
+ debug(socket, 'disconnected')
110
+ end
111
+ end
112
+ end
113
+ end
114
+
115
+ # Dequeue as many clients as possible, showing favor to nodes that have
116
+ # handled the fewest clients.
117
+ while !$client_queue.empty?
118
+ client = $client_queue.shift
119
+ found_a_node = false
120
+ sorted_notes = $nodes.sort {|a,b| a.counter <=> b.counter}
121
+ sorted_notes.each do |node|
122
+ next if node.connected?
123
+ begin
124
+ node.connect(client)
125
+ rescue
126
+ debug(client, "connection to #{node} failed")
127
+ next
128
+ end
129
+ debug(client, "assigned to #{node}")
130
+ found_a_node = true
131
+ break
132
+ end
133
+ unless found_a_node
134
+ $client_queue.unshift(client)
135
+ break
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,54 @@
1
+ require 'equity/socket_pairing'
2
+
3
+ module Equity
4
+ # Represents a cluster node.
5
+ class Node
6
+ attr_reader :counter
7
+
8
+ def initialize(address, port)
9
+ @client = nil
10
+ @counter = 0
11
+ @address = address.dup
12
+ @port = port.to_i
13
+ end
14
+
15
+ def connect(client)
16
+ raise(RuntimeError, 'node is already connected') if connected?
17
+ begin
18
+ @client = client
19
+ @server = TCPSocket.new(@address, @port)
20
+ @server.extend SocketPairing
21
+ @server.pair_with(client)
22
+ @counter += 1
23
+ @server
24
+ rescue Exception => e
25
+ @client = nil
26
+ @server = nil
27
+ raise e
28
+ end
29
+ end
30
+
31
+ def disconnect
32
+ @client.close
33
+ @server.close
34
+ @client = nil
35
+ @server = nil
36
+ end
37
+
38
+ def connected?
39
+ !!@client
40
+ end
41
+
42
+ def sockets
43
+ connected? ? [@client, @server] : []
44
+ end
45
+
46
+ def owns_socket?(socket)
47
+ connected? && ((socket == @client) || (socket == @server))
48
+ end
49
+
50
+ def to_s
51
+ "#{@address}:#{@port}"
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,15 @@
1
+ module Equity
2
+ # Enables pairing of sockets, so a client socket can be linked to a server
3
+ # socket.
4
+ module SocketPairing
5
+ attr_reader :mate
6
+
7
+ def pair_with(other_socket, one_way = false)
8
+ @mate = other_socket
9
+ unless one_way
10
+ other_socket.extend SocketPairing
11
+ other_socket.pair_with(self, true)
12
+ end
13
+ end
14
+ end
15
+ end
metadata ADDED
@@ -0,0 +1,55 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: DanaDanger-equity
3
+ version: !ruby/object:Gem::Version
4
+ version: "0.1"
5
+ platform: ruby
6
+ authors:
7
+ - DanaDanger
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2008-08-13 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description:
17
+ email:
18
+ executables:
19
+ - equity
20
+ extensions: []
21
+
22
+ extra_rdoc_files: []
23
+
24
+ files:
25
+ - bin/equity
26
+ - lib/equity/node.rb
27
+ - lib/equity/socket_pairing.rb
28
+ has_rdoc: false
29
+ homepage: http://github.com/DanaDanger/equity
30
+ post_install_message:
31
+ rdoc_options: []
32
+
33
+ require_paths:
34
+ - lib
35
+ required_ruby_version: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: "0"
40
+ version:
41
+ required_rubygems_version: !ruby/object:Gem::Requirement
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ version: "0"
46
+ version:
47
+ requirements: []
48
+
49
+ rubyforge_project:
50
+ rubygems_version: 1.2.0
51
+ signing_key:
52
+ specification_version: 2
53
+ summary: Queueing software load balancer.
54
+ test_files: []
55
+