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