DanaDanger-equity 0.1

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/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
+