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