rgossip 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README +43 -0
- data/lib/rgossip/client.rb +103 -0
- data/lib/rgossip/gossipper.rb +57 -0
- data/lib/rgossip/node.rb +54 -0
- data/lib/rgossip/nodes.rb +42 -0
- data/lib/rgossip/receiver.rb +96 -0
- data/lib/rgossip/timer.rb +52 -0
- data/lib/rgossip.rb +43 -0
- metadata +85 -0
data/README
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
= rgossip
|
2
|
+
|
3
|
+
== Description
|
4
|
+
|
5
|
+
Basic implementation of a gossip protocol.
|
6
|
+
|
7
|
+
This is a porting of Java implementation.
|
8
|
+
|
9
|
+
see http://code.google.com/p/gossip-protocol-java/
|
10
|
+
|
11
|
+
== Install
|
12
|
+
|
13
|
+
gem install rgossip
|
14
|
+
|
15
|
+
== Example
|
16
|
+
|
17
|
+
require 'rubygems'
|
18
|
+
require 'rgossip'
|
19
|
+
|
20
|
+
#RGossip.debug = true
|
21
|
+
#RGossip.porting = 20870 # default: 100870(udp)
|
22
|
+
|
23
|
+
gossip = RGossip::Client.new ['10.150.174.161', '10.150.185.250', '10.150.174.30']
|
24
|
+
# RGossip::Client#initialize(initial_nodes = [], address = nil, data = nil)
|
25
|
+
|
26
|
+
gossip.data = 'node-1'
|
27
|
+
gossip.start
|
28
|
+
#gossip.join
|
29
|
+
|
30
|
+
loop do
|
31
|
+
case gets
|
32
|
+
when /list/i
|
33
|
+
gossip.each do |address, data|
|
34
|
+
puts "#{address}: #{data}"
|
35
|
+
# (example output)
|
36
|
+
# 10.150.174.161: node-1
|
37
|
+
# 10.150.174.30: node-3
|
38
|
+
# 10.150.185.250: node-2
|
39
|
+
end
|
40
|
+
when /^add\s+(.+)$/
|
41
|
+
gossip.add_node $1
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
module RGossip
|
2
|
+
class Client
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
attr_reader :node_list
|
6
|
+
attr_reader :dead_list
|
7
|
+
attr_reader :my_node
|
8
|
+
|
9
|
+
def initialize(initial_nodes = [], address = nil, data = nil)
|
10
|
+
raise 'too large data' if data && data.length > RGossip.bufsiz
|
11
|
+
|
12
|
+
@address = address || IPSocket.getaddress(Socket.gethostname)
|
13
|
+
|
14
|
+
RGossip.log("Initialize Client: initial_nodes=#{initial_nodes.inspect} address: #{@address} data: #{data.inspect}")
|
15
|
+
|
16
|
+
@node_list = Nodes.new
|
17
|
+
@dead_list = Nodes.new
|
18
|
+
@my_node = Node.new(@node_list, @dead_list, @address, data)
|
19
|
+
@my_node.update_timestamp
|
20
|
+
@node_list << @my_node
|
21
|
+
|
22
|
+
initial_nodes.uniq.each do |i|
|
23
|
+
@node_list << Node.new(@node_list, @dead_list, i, nil)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def start
|
28
|
+
return if @running
|
29
|
+
RGossip.log("Start Client: address=#{@address}")
|
30
|
+
|
31
|
+
@gossiper = Gossiper.new(@my_node, @node_list)
|
32
|
+
@receiver = Receiver.new(@my_node, @node_list, @dead_list)
|
33
|
+
|
34
|
+
@node_list.each do |node|
|
35
|
+
if node.address != @my_node.address
|
36
|
+
node.start_timer
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
@gossiper.start
|
41
|
+
@receiver.start
|
42
|
+
@running = true
|
43
|
+
end
|
44
|
+
|
45
|
+
def stop
|
46
|
+
return unless @running
|
47
|
+
RGossip.log("Stop Client")
|
48
|
+
|
49
|
+
@gossiper.stop
|
50
|
+
@receiver.stop
|
51
|
+
@running = true
|
52
|
+
|
53
|
+
@gossiper = nil
|
54
|
+
@receiver = nil
|
55
|
+
end
|
56
|
+
|
57
|
+
def join
|
58
|
+
@gossiper.join
|
59
|
+
@receiver.join
|
60
|
+
end
|
61
|
+
|
62
|
+
def running?
|
63
|
+
!!@running
|
64
|
+
end
|
65
|
+
|
66
|
+
def data=(v)
|
67
|
+
@node_list.synchronize {
|
68
|
+
@my_node.data = v
|
69
|
+
}
|
70
|
+
end
|
71
|
+
|
72
|
+
def add_node(address)
|
73
|
+
@node_list.synchronize {
|
74
|
+
raise 'node is exist' if @node_list.any? {|i| i.address == address }
|
75
|
+
@node_list << Node.new(@node_list, @dead_list, address, nil)
|
76
|
+
}
|
77
|
+
end
|
78
|
+
|
79
|
+
def delete_node(address)
|
80
|
+
raise 'cannot delete my node' if @my_node.address == address
|
81
|
+
|
82
|
+
@node_list.synchronize {
|
83
|
+
@node_list.reject! do |i|
|
84
|
+
i.address == address
|
85
|
+
end
|
86
|
+
}
|
87
|
+
end
|
88
|
+
|
89
|
+
def clear_dead_list
|
90
|
+
@dead_list.synchronize {
|
91
|
+
@dead_list.clear
|
92
|
+
}
|
93
|
+
end
|
94
|
+
|
95
|
+
def each
|
96
|
+
@node_list.synchronize {
|
97
|
+
@node_list.each do |node|
|
98
|
+
yield([node.address, node.data])
|
99
|
+
end
|
100
|
+
}
|
101
|
+
end
|
102
|
+
end # Client
|
103
|
+
end # RGossip
|
@@ -0,0 +1,57 @@
|
|
1
|
+
class Gossiper
|
2
|
+
@@interval = 0.1
|
3
|
+
def self.interval; @@interval; end
|
4
|
+
def self.interval=(v); @@interval = v; end
|
5
|
+
|
6
|
+
def initialize(my_node, node_list)
|
7
|
+
@my_node = my_node
|
8
|
+
@node_list = node_list
|
9
|
+
@running = true
|
10
|
+
end
|
11
|
+
|
12
|
+
def start
|
13
|
+
RGossip.log("Start Gossiper: interval=#{@@interval}")
|
14
|
+
|
15
|
+
@thread = Thread.start {
|
16
|
+
begin
|
17
|
+
sock = UDPSocket.open
|
18
|
+
|
19
|
+
while @running
|
20
|
+
@node_list.synchronize { gossip(sock) }
|
21
|
+
sleep(@@interval)
|
22
|
+
end
|
23
|
+
ensure
|
24
|
+
sock.close
|
25
|
+
end
|
26
|
+
}
|
27
|
+
end
|
28
|
+
|
29
|
+
def stop
|
30
|
+
RGossip.log("Stop Gossiper")
|
31
|
+
|
32
|
+
@running = false
|
33
|
+
end
|
34
|
+
|
35
|
+
def join
|
36
|
+
@thread.join
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
def gossip(sock)
|
41
|
+
@my_node.update_timestamp
|
42
|
+
|
43
|
+
dest = @node_list.choice_without(@my_node)
|
44
|
+
return unless dest
|
45
|
+
|
46
|
+
RGossip.log("Gossip: destination=#{dest.address}")
|
47
|
+
|
48
|
+
@node_list.serialize_to_chunks.each do |chunk|
|
49
|
+
begin
|
50
|
+
sock.send(chunk, 0, dest.address, RGossip.port)
|
51
|
+
rescue Exception => e
|
52
|
+
raise e unless RGossip.error_handler
|
53
|
+
RGossip.error_handler.call(e)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end # Gossiper
|
57
|
+
end # RGossip
|
data/lib/rgossip/node.rb
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
module RGossip
|
2
|
+
class Node
|
3
|
+
attr_reader :address
|
4
|
+
attr_accessor :timestamp
|
5
|
+
attr_accessor :data
|
6
|
+
|
7
|
+
@@lifetime = 10
|
8
|
+
def self.lifetime; @@lifetime; end
|
9
|
+
def self.lifetime=(v); @@lifetime = v; end
|
10
|
+
|
11
|
+
def initialize(node_list, dead_list, address, data, timestamp = nil)
|
12
|
+
@node_list = node_list
|
13
|
+
@dead_list = dead_list
|
14
|
+
@address = address
|
15
|
+
@data = data
|
16
|
+
@timestamp = timestamp || ""
|
17
|
+
|
18
|
+
@timer = Timer.new(@@lifetime) do
|
19
|
+
RGossip.log("Timeout Node: address=#{@address}")
|
20
|
+
|
21
|
+
@node_list.synchronize {
|
22
|
+
@node_list.reject! do |i|
|
23
|
+
i.address == @address
|
24
|
+
end
|
25
|
+
}
|
26
|
+
|
27
|
+
@dead_list.synchronize {
|
28
|
+
@dead_list << self
|
29
|
+
}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def update_timestamp
|
34
|
+
now = Time.now
|
35
|
+
@timestamp = "#{now.tv_sec}#{now.tv_usec}"
|
36
|
+
end
|
37
|
+
|
38
|
+
def to_a
|
39
|
+
[@address, @timestamp, @data]
|
40
|
+
end
|
41
|
+
|
42
|
+
def start_timer
|
43
|
+
RGossip.log("Start Timer: address=#{@address}")
|
44
|
+
|
45
|
+
@timer.start
|
46
|
+
end
|
47
|
+
|
48
|
+
def reset_timer
|
49
|
+
RGossip.log("Reset Timer: address=#{@address}")
|
50
|
+
|
51
|
+
@timer.reset
|
52
|
+
end
|
53
|
+
end # Node
|
54
|
+
end # RGossip
|
@@ -0,0 +1,42 @@
|
|
1
|
+
module RGossip
|
2
|
+
class Nodes < Array
|
3
|
+
def initialize(ary = [])
|
4
|
+
super(ary)
|
5
|
+
@mutex = Mutex.new
|
6
|
+
end
|
7
|
+
|
8
|
+
def synchronize
|
9
|
+
@mutex.synchronize { yield }
|
10
|
+
end
|
11
|
+
|
12
|
+
def choice_without(node)
|
13
|
+
node_list = self.select {|i| i.address != node.address }
|
14
|
+
node_list.empty? ? nil : node_list[rand(node_list.size)]
|
15
|
+
end
|
16
|
+
|
17
|
+
def serialize_to_chunks
|
18
|
+
chunks = []
|
19
|
+
nodes = []
|
20
|
+
datasum = ''
|
21
|
+
|
22
|
+
self.sort_by { rand }.each do |node|
|
23
|
+
packed = node.to_a.to_msgpack
|
24
|
+
|
25
|
+
if (datasum + packed).length <= RGossip.bufsiz
|
26
|
+
nodes << node
|
27
|
+
datasum << packed
|
28
|
+
else
|
29
|
+
chunks << nodes.map {|i| i.to_a }.to_msgpack
|
30
|
+
nodes.clear
|
31
|
+
datasum.clear
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
unless nodes.empty?
|
36
|
+
chunks << nodes.map {|i| i.to_a }.to_msgpack
|
37
|
+
end
|
38
|
+
|
39
|
+
return chunks
|
40
|
+
end
|
41
|
+
end # Nodes
|
42
|
+
end # RGossip
|
@@ -0,0 +1,96 @@
|
|
1
|
+
module RGossip
|
2
|
+
class Receiver
|
3
|
+
@@timeout = 3
|
4
|
+
def self.timeout; @@timeout; end
|
5
|
+
def self.timeout=(v); @@timeout = v; end
|
6
|
+
|
7
|
+
def initialize(my_node, node_list, dead_list)
|
8
|
+
@my_node = my_node
|
9
|
+
@node_list = node_list
|
10
|
+
@dead_list = dead_list
|
11
|
+
@running = true
|
12
|
+
end
|
13
|
+
|
14
|
+
def start
|
15
|
+
RGossip.log("Start Receiver: port=#{RGossip.port}")
|
16
|
+
|
17
|
+
@thread = Thread.start {
|
18
|
+
begin
|
19
|
+
sock = UDPSocket.open
|
20
|
+
sock.bind(@my_node.address, RGossip.port)
|
21
|
+
|
22
|
+
while @running
|
23
|
+
receive(sock)
|
24
|
+
end
|
25
|
+
ensure
|
26
|
+
sock.close
|
27
|
+
end
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
def stop
|
32
|
+
RGossip.log("Stop Receiver")
|
33
|
+
|
34
|
+
@running = false
|
35
|
+
end
|
36
|
+
|
37
|
+
def join
|
38
|
+
@thread.join
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
def receive(sock)
|
43
|
+
RGossip.log("Receive: timeout=#{@@timeout}")
|
44
|
+
|
45
|
+
begin
|
46
|
+
return unless select([sock], [], [], @@timeout)
|
47
|
+
message, (afam, port, host, ip) = sock.recvfrom(RGossip.bufsiz * RGossip.allowance)
|
48
|
+
|
49
|
+
RGossip.log("Receive from #{host}(#{ip})")
|
50
|
+
|
51
|
+
recv_nodes = MessagePack.unpack(message)
|
52
|
+
|
53
|
+
@node_list.synchronize {
|
54
|
+
merge_lists(recv_nodes)
|
55
|
+
}
|
56
|
+
rescue Exception => e
|
57
|
+
raise e unless RGossip.error_handler
|
58
|
+
RGossip.error_handler.call(e)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def merge_lists(recv_nodes)
|
63
|
+
recv_nodes.each do |address, timestamp, data|
|
64
|
+
next if address == @my_node.address
|
65
|
+
|
66
|
+
if (node = @node_list.find {|i| i.address == address })
|
67
|
+
if timestamp > node.timestamp
|
68
|
+
RGossip.log("Update Node: address=#{address} timestamp=#{timestamp}")
|
69
|
+
|
70
|
+
node.timestamp = timestamp
|
71
|
+
node.data = data
|
72
|
+
node.reset_timer
|
73
|
+
end
|
74
|
+
elsif (index = @dead_list.synchronize { @dead_list.index {|i| i.address == address } })
|
75
|
+
RGossip.log("Come back Node: address=#{address} timestamp=#{timestamp}")
|
76
|
+
|
77
|
+
@dead_list.synchronize {
|
78
|
+
node = @dead_list[index]
|
79
|
+
|
80
|
+
if timestamp > node.timestamp
|
81
|
+
@dead_list.delete_at(index)
|
82
|
+
@node_list << node
|
83
|
+
node.start_timer
|
84
|
+
end
|
85
|
+
}
|
86
|
+
else
|
87
|
+
RGossip.log("Add Node: address=#{address} timestamp=#{timestamp}")
|
88
|
+
|
89
|
+
node = Node.new(@node_list, @dead_list, address, data, timestamp)
|
90
|
+
@node_list << node
|
91
|
+
node.start_timer
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end # Receiver
|
96
|
+
end # RGossip
|
@@ -0,0 +1,52 @@
|
|
1
|
+
module RGossip
|
2
|
+
class Timer
|
3
|
+
def initialize(timeout, &block)
|
4
|
+
@timeout = timeout
|
5
|
+
@block = block
|
6
|
+
@mutex = Mutex.new
|
7
|
+
@reset = false
|
8
|
+
end
|
9
|
+
|
10
|
+
def start
|
11
|
+
@mutex.synchronize {
|
12
|
+
if_is_thread_alive? { @thread.kill }
|
13
|
+
@reset = false
|
14
|
+
|
15
|
+
@thread = Thread.start {
|
16
|
+
loop do
|
17
|
+
sleep @timeout
|
18
|
+
|
19
|
+
if @reset
|
20
|
+
@reset = false
|
21
|
+
retry
|
22
|
+
end
|
23
|
+
|
24
|
+
@block.call
|
25
|
+
break
|
26
|
+
end
|
27
|
+
}
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
def reset
|
32
|
+
@mutex.synchronize {
|
33
|
+
if_is_thread_alive? do
|
34
|
+
begin
|
35
|
+
@reset = true
|
36
|
+
@thread.run
|
37
|
+
ensure
|
38
|
+
@reset = false
|
39
|
+
end
|
40
|
+
end
|
41
|
+
}
|
42
|
+
rescue ThreadError
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
def if_is_thread_alive?
|
47
|
+
if @thread and @thread.alive?
|
48
|
+
yield
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end # Timer
|
52
|
+
end # RGossip
|
data/lib/rgossip.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'msgpack'
|
3
|
+
require 'socket'
|
4
|
+
require 'thread'
|
5
|
+
|
6
|
+
require 'rgossip/client'
|
7
|
+
require 'rgossip/node'
|
8
|
+
require 'rgossip/nodes'
|
9
|
+
require 'rgossip/gossipper'
|
10
|
+
require 'rgossip/receiver'
|
11
|
+
require 'rgossip/timer'
|
12
|
+
|
13
|
+
module RGossip
|
14
|
+
@@port = 10870
|
15
|
+
def self.port; @@port; end
|
16
|
+
def self.port=(v); @@port = v; end
|
17
|
+
|
18
|
+
@@bufsiz = 512
|
19
|
+
def self.bufsiz; @@bufsiz; end
|
20
|
+
def self.bufsiz=(v); @@bufsiz = v; end
|
21
|
+
|
22
|
+
@@allowance = 3
|
23
|
+
def self.allowance; @@allowance; end
|
24
|
+
def self.allowance=(v); @@allowance = v; end
|
25
|
+
|
26
|
+
@@error_handler = lambda {|e| $stderr.puts((["#{e.class}: #{e.message}"] + (e.backtrace || [])).join("\n\tfrom ")) }
|
27
|
+
def self.error_handler; @@error_handler; end
|
28
|
+
def self.error_handler=(v); @@error_handler = v; end
|
29
|
+
|
30
|
+
@@debug = false
|
31
|
+
def self.debug; @@debug; end
|
32
|
+
def self.debug=(v); @@debug = v; end
|
33
|
+
|
34
|
+
@@debug_logger = lambda {|message| $stderr.puts "[#{Time.now.strftime '%x %X'} ##{$$}] #{message}" }
|
35
|
+
def self.debug_logger; @@debug_logger; end
|
36
|
+
def self.debug_logger=(v); @@debug_logger = v; end
|
37
|
+
|
38
|
+
def self.log(message)
|
39
|
+
if self.debug
|
40
|
+
self.debug_logger.call(message)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
metadata
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rgossip
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- winebarrel
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-05-22 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: msgpack
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
hash: 3
|
29
|
+
segments:
|
30
|
+
- 0
|
31
|
+
version: "0"
|
32
|
+
type: :runtime
|
33
|
+
version_requirements: *id001
|
34
|
+
description:
|
35
|
+
email: sgwr_dts@yahoo.co.jp
|
36
|
+
executables: []
|
37
|
+
|
38
|
+
extensions: []
|
39
|
+
|
40
|
+
extra_rdoc_files: []
|
41
|
+
|
42
|
+
files:
|
43
|
+
- README
|
44
|
+
- lib/rgossip/timer.rb
|
45
|
+
- lib/rgossip/gossipper.rb
|
46
|
+
- lib/rgossip/client.rb
|
47
|
+
- lib/rgossip/receiver.rb
|
48
|
+
- lib/rgossip/nodes.rb
|
49
|
+
- lib/rgossip/node.rb
|
50
|
+
- lib/rgossip.rb
|
51
|
+
homepage: https://bitbucket.org/winebarrel/rgossip
|
52
|
+
licenses: []
|
53
|
+
|
54
|
+
post_install_message:
|
55
|
+
rdoc_options: []
|
56
|
+
|
57
|
+
require_paths:
|
58
|
+
- lib
|
59
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
60
|
+
none: false
|
61
|
+
requirements:
|
62
|
+
- - ">="
|
63
|
+
- !ruby/object:Gem::Version
|
64
|
+
hash: 3
|
65
|
+
segments:
|
66
|
+
- 0
|
67
|
+
version: "0"
|
68
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
69
|
+
none: false
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
hash: 3
|
74
|
+
segments:
|
75
|
+
- 0
|
76
|
+
version: "0"
|
77
|
+
requirements: []
|
78
|
+
|
79
|
+
rubyforge_project:
|
80
|
+
rubygems_version: 1.8.1
|
81
|
+
signing_key:
|
82
|
+
specification_version: 3
|
83
|
+
summary: Basic implementation of a gossip protocol. This is a porting of Java implementation. see http://code.google.com/p/gossip-protocol-java/
|
84
|
+
test_files: []
|
85
|
+
|