rgossip 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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
@@ -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
+