rswim 1.0.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +1 -0
- data/.ruby-version +1 -1
- data/CHANGELOG.md +1 -0
- data/Gemfile.lock +27 -25
- data/README.md +15 -3
- data/bin/run_node +39 -10
- data/lib/rswim/agent.rb +10 -2
- data/lib/rswim/integration/deserializer.rb +18 -12
- data/lib/rswim/integration/serializer.rb +9 -3
- data/lib/rswim/integration/udp/node.rb +21 -24
- data/lib/rswim/member/base.rb +1 -0
- data/lib/rswim/member/health_state/alive.rb +19 -3
- data/lib/rswim/member/health_state/base.rb +9 -41
- data/lib/rswim/member/health_state/confirmed.rb +10 -2
- data/lib/rswim/member/health_state/suspected.rb +21 -3
- data/lib/rswim/member/me.rb +24 -11
- data/lib/rswim/member/peer.rb +55 -12
- data/lib/rswim/member/transmission_state/off.rb +2 -2
- data/lib/rswim/member_pool.rb +25 -12
- data/lib/rswim/message.rb +4 -0
- data/lib/rswim/node.rb +13 -11
- data/lib/rswim/protocol_state.rb +11 -1
- data/lib/rswim/update_entry.rb +8 -4
- data/lib/rswim/version.rb +1 -1
- data/rswim.gemspec +4 -1
- metadata +25 -11
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b9a936a878b5bdbfd6503ac3693577d73a894d77d6d25a9b5794354595cb4b3b
|
4
|
+
data.tar.gz: 806de89b6fb5e6fdb4af90ba60d71c0af839a41a34dbfcd8f5ae40155a25856d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 439d9eac5fa4ce3d610190ae6f7d1e39fdebad50a699b37678d28d2c3f2c2d51917e9feedbea03f39ccd2e47f70a703fff36a2be425d79b211881025168fd8dc
|
7
|
+
data.tar.gz: 6736caa12593b49b06956580f1698ac009a8202d3f71a6473e9b68b35eb8cf7dee5071266f7fe19cd07396405509c4836a33257c1ec65984de02838605165bfa
|
data/.gitignore
CHANGED
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
3.0.2
|
data/CHANGELOG.md
CHANGED
data/Gemfile.lock
CHANGED
@@ -1,7 +1,8 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
rswim (
|
4
|
+
rswim (2.0.0)
|
5
|
+
slop (~> 4.9)
|
5
6
|
zeitwerk (~> 2.2)
|
6
7
|
|
7
8
|
GEM
|
@@ -10,18 +11,18 @@ GEM
|
|
10
11
|
byebug (11.1.3)
|
11
12
|
coderay (1.1.3)
|
12
13
|
diff-lcs (1.4.4)
|
13
|
-
ffi (1.
|
14
|
-
formatador (0.
|
15
|
-
fuubar (2.5.
|
14
|
+
ffi (1.15.4)
|
15
|
+
formatador (0.3.0)
|
16
|
+
fuubar (2.5.1)
|
16
17
|
rspec-core (~> 3.0)
|
17
18
|
ruby-progressbar (~> 1.4)
|
18
|
-
guard (2.
|
19
|
+
guard (2.18.0)
|
19
20
|
formatador (>= 0.2.4)
|
20
21
|
listen (>= 2.7, < 4.0)
|
21
22
|
lumberjack (>= 1.0.12, < 2.0)
|
22
23
|
nenv (~> 0.1)
|
23
24
|
notiffany (~> 0.0)
|
24
|
-
pry (>= 0.
|
25
|
+
pry (>= 0.13.0)
|
25
26
|
shellany (~> 0.0)
|
26
27
|
thor (>= 0.18.1)
|
27
28
|
guard-compat (1.2.1)
|
@@ -29,7 +30,7 @@ GEM
|
|
29
30
|
guard (~> 2.1)
|
30
31
|
guard-compat (~> 1.1)
|
31
32
|
rspec (>= 2.99.0, < 4.0)
|
32
|
-
listen (3.
|
33
|
+
listen (3.7.0)
|
33
34
|
rb-fsevent (~> 0.10, >= 0.10.3)
|
34
35
|
rb-inotify (~> 0.9, >= 0.9.10)
|
35
36
|
lumberjack (1.2.8)
|
@@ -38,36 +39,37 @@ GEM
|
|
38
39
|
notiffany (0.1.3)
|
39
40
|
nenv (~> 0.1)
|
40
41
|
shellany (~> 0.0)
|
41
|
-
pry (0.
|
42
|
+
pry (0.14.1)
|
42
43
|
coderay (~> 1.1)
|
43
44
|
method_source (~> 1.0)
|
44
45
|
rake (12.3.3)
|
45
|
-
rb-fsevent (0.
|
46
|
+
rb-fsevent (0.11.0)
|
46
47
|
rb-inotify (0.10.1)
|
47
48
|
ffi (~> 1.0)
|
48
|
-
rspec (3.
|
49
|
-
rspec-core (~> 3.
|
50
|
-
rspec-expectations (~> 3.
|
51
|
-
rspec-mocks (~> 3.
|
52
|
-
rspec-core (3.
|
53
|
-
rspec-support (~> 3.
|
54
|
-
rspec-expectations (3.
|
49
|
+
rspec (3.10.0)
|
50
|
+
rspec-core (~> 3.10.0)
|
51
|
+
rspec-expectations (~> 3.10.0)
|
52
|
+
rspec-mocks (~> 3.10.0)
|
53
|
+
rspec-core (3.10.1)
|
54
|
+
rspec-support (~> 3.10.0)
|
55
|
+
rspec-expectations (3.10.1)
|
55
56
|
diff-lcs (>= 1.2.0, < 2.0)
|
56
|
-
rspec-support (~> 3.
|
57
|
-
rspec-mocks (3.
|
57
|
+
rspec-support (~> 3.10.0)
|
58
|
+
rspec-mocks (3.10.2)
|
58
59
|
diff-lcs (>= 1.2.0, < 2.0)
|
59
|
-
rspec-support (~> 3.
|
60
|
-
rspec-support (3.
|
61
|
-
ruby-progressbar (1.
|
60
|
+
rspec-support (~> 3.10.0)
|
61
|
+
rspec-support (3.10.2)
|
62
|
+
ruby-progressbar (1.11.0)
|
62
63
|
shellany (0.0.1)
|
63
|
-
|
64
|
-
|
64
|
+
slop (4.9.1)
|
65
|
+
thor (1.1.0)
|
66
|
+
zeitwerk (2.4.2)
|
65
67
|
|
66
68
|
PLATFORMS
|
67
69
|
ruby
|
68
70
|
|
69
71
|
DEPENDENCIES
|
70
|
-
bundler (
|
72
|
+
bundler (>= 2.2.10)
|
71
73
|
byebug
|
72
74
|
fuubar (~> 2.5)
|
73
75
|
guard-rspec (~> 4.7)
|
@@ -76,4 +78,4 @@ DEPENDENCIES
|
|
76
78
|
rswim!
|
77
79
|
|
78
80
|
BUNDLED WITH
|
79
|
-
2.
|
81
|
+
2.2.22
|
data/README.md
CHANGED
@@ -4,7 +4,8 @@ RSwim is a Ruby implementation of the SWIM gossip protocol, a mechanism for disc
|
|
4
4
|
|
5
5
|
It is an implementation inspired by the original [SWIM: Scalable Weakly-consistent Infection-style Process Group Membership Protocol](https://www.cs.cornell.edu/projects/Quicksilver/public_pdfs/SWIM.pdf) paper by Abhinandan Das, Indranil Gupta, Ashish Motivala.
|
6
6
|
|
7
|
-
The implementation is kept intentionally simple and limited to the features described in the paper.
|
7
|
+
The implementation is kept intentionally simple and limited to the features described in the paper, except for the addition in version 2.0.0 of the ability to piggyback custom state on the liveness propagation mechanism, see `RSwim::Node#append_custom_state`
|
8
|
+
|
8
9
|
No attempts have been made to address known security issues such as Byzantine attacks.
|
9
10
|
|
10
11
|
Currently RSwim runs on UDP with a custom, human readable serialization format.
|
@@ -27,6 +28,7 @@ Or install it yourself as:
|
|
27
28
|
$ gem install rswim
|
28
29
|
|
29
30
|
## Usage
|
31
|
+
To try out a small demo script, execute `bin/run_node --help` for more information.
|
30
32
|
|
31
33
|
Example:
|
32
34
|
```ruby
|
@@ -43,9 +45,19 @@ Example:
|
|
43
45
|
node = RSwim::Node.udp(nil, seed_hosts, port)
|
44
46
|
|
45
47
|
# Subscribe to updates
|
46
|
-
node.subscribe do |host, status|
|
47
|
-
puts "Update: #{host} entered state #{status}"
|
48
|
+
node.subscribe do |host, status, custom_state|
|
49
|
+
puts "Update: #{host} entered liveness state #{status} with custom state #{custom_state}"
|
48
50
|
end
|
51
|
+
|
52
|
+
# Periodically append new state for publishing
|
53
|
+
Thread.new do
|
54
|
+
uptime = 0
|
55
|
+
loop do
|
56
|
+
sleep(5)
|
57
|
+
uptime += 5
|
58
|
+
node.append_custom_state(:uptime_seconds, uptime)
|
59
|
+
end
|
60
|
+
end.abort_on_exception = true
|
49
61
|
|
50
62
|
puts "Ready\n"
|
51
63
|
begin
|
data/bin/run_node
CHANGED
@@ -1,30 +1,59 @@
|
|
1
1
|
#!/usr/bin/env ruby --jit
|
2
|
-
require
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'slop'
|
3
4
|
require 'rswim'
|
4
|
-
puts "Ruby version: #{RUBY_VERSION}"
|
5
5
|
|
6
6
|
PORT = 4545
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
8
|
+
opts = Slop.parse do |o|
|
9
|
+
o.array '-s', '--seeds', 'a comma separated list of seed nodes'
|
10
|
+
o.bool '-d', '--debug', 'turn on debug logging'
|
11
|
+
o.on '--help' do
|
12
|
+
puts o
|
13
|
+
exit
|
14
|
+
end
|
15
|
+
o.on '-v', '--version' do
|
16
|
+
puts RSwim::VERSION
|
17
|
+
exit
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
puts "Ruby version: #{RUBY_VERSION}"
|
22
|
+
|
23
|
+
RSwim::Logger.level = ::Logger::DEBUG if opts.debug?
|
24
|
+
seed_hosts = opts[:seeds]
|
11
25
|
abort 'EOF' if seed_hosts.nil?
|
12
|
-
puts "Operating with no seed nodes" if seed_hosts.empty?
|
13
26
|
|
14
|
-
|
27
|
+
if seed_hosts.empty?
|
28
|
+
puts 'Operating with no seed nodes'
|
29
|
+
else
|
30
|
+
seed_hosts.each { |h| puts "Seed node: #{h}" }
|
31
|
+
end
|
32
|
+
|
33
|
+
puts 'Starting node'
|
15
34
|
|
16
35
|
# Instantiate node, setting my_host to nil to auto detect host IP.
|
17
|
-
node = RSwim::Node.udp(nil, seed_hosts, PORT)
|
36
|
+
node = RSwim::Node.udp(nil, seed_hosts, PORT, 3_500, 1_000)
|
18
37
|
|
19
38
|
# Subscribe to updates
|
20
|
-
node.subscribe do |host, status|
|
21
|
-
puts "Update: #{host} entered state #{status}"
|
39
|
+
node.subscribe do |host, status, custom_state|
|
40
|
+
puts "Update: #{host} entered liveness state #{status} with custom state #{custom_state}"
|
22
41
|
end
|
23
42
|
|
43
|
+
Thread.new do
|
44
|
+
uptime = 0
|
45
|
+
loop do
|
46
|
+
sleep(5)
|
47
|
+
uptime += 5
|
48
|
+
node.append_custom_state(:uptime_seconds, uptime)
|
49
|
+
end
|
50
|
+
end.abort_on_exception = true
|
51
|
+
|
24
52
|
puts "Ready\n"
|
25
53
|
begin
|
26
54
|
# Run node (blocking)
|
27
55
|
node.start
|
28
56
|
rescue Interrupt
|
57
|
+
puts "\nShutting down gracefully"
|
29
58
|
end
|
30
59
|
puts "\nDone"
|
data/lib/rswim/agent.rb
CHANGED
@@ -5,7 +5,7 @@ module RSwim
|
|
5
5
|
class Base
|
6
6
|
def initialize(pipe, node_member_id, seed_member_ids, t_ms, r_ms)
|
7
7
|
@pipe = pipe
|
8
|
-
@state =
|
8
|
+
@state = new_protocol_state(node_member_id, seed_member_ids, t_ms, r_ms)
|
9
9
|
end
|
10
10
|
|
11
11
|
def subscribe(&block)
|
@@ -20,8 +20,16 @@ module RSwim
|
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
|
+
def append_custom_state(key, value)
|
24
|
+
@state.append_custom_state(key, value)
|
25
|
+
end
|
26
|
+
|
23
27
|
protected
|
24
28
|
|
29
|
+
def new_protocol_state(node_member_id, seed_member_ids, t_ms, r_ms)
|
30
|
+
ProtocolState.new(node_member_id, seed_member_ids, t_ms, r_ms)
|
31
|
+
end
|
32
|
+
|
25
33
|
def pause
|
26
34
|
raise 'implement this in a subclass'
|
27
35
|
end
|
@@ -32,7 +40,7 @@ module RSwim
|
|
32
40
|
end
|
33
41
|
|
34
42
|
class SleepBased < Base
|
35
|
-
def initialize(pipe, node_member_id, seed_member_ids, sleep_time_seconds = 0.1
|
43
|
+
def initialize(pipe, node_member_id, seed_member_ids, t_ms, r_ms, sleep_time_seconds = 0.1)
|
36
44
|
super(pipe, node_member_id, seed_member_ids, t_ms, r_ms)
|
37
45
|
@sleep_time_seconds = sleep_time_seconds
|
38
46
|
end
|
@@ -26,26 +26,32 @@ module RSwim
|
|
26
26
|
protected
|
27
27
|
|
28
28
|
def logger
|
29
|
-
@_logger ||=
|
30
|
-
RSwim::Logger.new(self.class, STDERR)
|
31
|
-
end
|
29
|
+
@_logger ||= RSwim::Logger.new(self.class, $stderr)
|
32
30
|
end
|
33
31
|
|
34
32
|
private
|
35
33
|
|
36
34
|
def parse_updates(lines)
|
37
35
|
lines.map do |l|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
36
|
+
# host status incarnation_number
|
37
|
+
host, status, incarnation_number, *pairs = l.strip.split(' ')
|
38
|
+
id = @directory.id(host)
|
39
|
+
custom_state = parse_custom_state(pairs)
|
40
|
+
|
41
|
+
UpdateEntry.new(id, status.to_sym, incarnation_number.to_i, custom_state)
|
42
|
+
rescue StandardError => e
|
43
|
+
logger.debug("Failed to parse line `#{l}`: #{e}")
|
44
|
+
nil
|
47
45
|
end.tap(&:compact!)
|
48
46
|
end
|
47
|
+
|
48
|
+
def parse_custom_state(pairs)
|
49
|
+
pairs.each_slice(2).map do |(key, value)|
|
50
|
+
raise 'bad custom state' if !key.end_with?(':') || value.nil?
|
51
|
+
|
52
|
+
[key[0..-2].to_sym, value]
|
53
|
+
end.to_h
|
54
|
+
end
|
49
55
|
end
|
50
56
|
end
|
51
57
|
end
|
@@ -10,9 +10,11 @@ module RSwim
|
|
10
10
|
def serialize(message)
|
11
11
|
l1 = message.type.to_s.gsub(/_/, '-')
|
12
12
|
l1 << " #{@directory.host(message.payload[:target_id])}" if message.type == :ping_req
|
13
|
+
|
13
14
|
message.payload[:updates].to_a.each do |update|
|
14
15
|
# host status incarnation_number
|
15
16
|
l1 << "\n#{@directory.host(update.member_id)} #{update.status} #{update.incarnation_number}"
|
17
|
+
l1 << " #{serialize_custom_state(update.custom_state)}" unless update.custom_state.empty?
|
16
18
|
end
|
17
19
|
l1
|
18
20
|
end
|
@@ -20,9 +22,13 @@ module RSwim
|
|
20
22
|
protected
|
21
23
|
|
22
24
|
def logger
|
23
|
-
@_logger ||=
|
24
|
-
|
25
|
-
|
25
|
+
@_logger ||= RSwim::Logger.new(self.class, STDERR)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def serialize_custom_state(custom_state)
|
31
|
+
custom_state.map { |k, v| "#{k}: #{v}" }.join(' ')
|
26
32
|
end
|
27
33
|
end
|
28
34
|
end
|
@@ -5,10 +5,11 @@ require 'socket'
|
|
5
5
|
module RSwim
|
6
6
|
module Integration
|
7
7
|
module Udp
|
8
|
+
# Node implementation that sends and listens using UDP
|
8
9
|
class Node < RSwim::Node
|
9
|
-
def initialize(my_host, seed_hosts, port)
|
10
|
-
my_host ||= Socket.ip_address_list.find
|
11
|
-
super(my_host, seed_hosts)
|
10
|
+
def initialize(my_host, seed_hosts, port, t_ms, r_ms)
|
11
|
+
my_host ||= Socket.ip_address_list.find(&:ipv4_private?).ip_address
|
12
|
+
super(my_host, seed_hosts, t_ms, r_ms)
|
12
13
|
@port = port
|
13
14
|
end
|
14
15
|
|
@@ -19,39 +20,35 @@ module RSwim
|
|
19
20
|
@out_s = UDPSocket.new
|
20
21
|
@in_s.bind(@my_host, @port)
|
21
22
|
logger.info "node listening on UDP port #{@port}"
|
22
|
-
Thread.new { receive }.
|
23
|
-
Thread.new { send }.
|
23
|
+
Thread.new { receive }.abort_on_exception = true
|
24
|
+
Thread.new { send }.abort_on_exception = true
|
24
25
|
end
|
25
26
|
|
26
27
|
private
|
27
28
|
|
28
29
|
def send
|
29
30
|
loop do
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
logger.debug("Error while sending: #{e}")
|
38
|
-
end
|
31
|
+
message = @pipe.q_out.pop
|
32
|
+
wire_message = @serializer.serialize(message)
|
33
|
+
host = @directory.host(message.to)
|
34
|
+
logger.debug "about to send message to #{host}"
|
35
|
+
@out_s.send(wire_message, 0, host, @port)
|
36
|
+
rescue StandardError => e
|
37
|
+
logger.debug("Error while sending: #{e}")
|
39
38
|
end
|
40
39
|
logger.info 'node no longer receiving'
|
41
40
|
end
|
42
41
|
|
43
42
|
def receive
|
44
43
|
loop do
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
logger.debug("Error while receiving: #{e}")
|
54
|
-
end
|
44
|
+
logger.debug 'about to recieve message'
|
45
|
+
text, sender = @in_s.recvfrom(10_000)
|
46
|
+
|
47
|
+
message = @deserializer.deserialize(sender[3], text)
|
48
|
+
logger.debug "received #{message}"
|
49
|
+
@pipe.q_in << message
|
50
|
+
rescue StandardError => e
|
51
|
+
logger.debug("Error while receiving: #{e}")
|
55
52
|
end
|
56
53
|
logger.info 'node no longer receiving'
|
57
54
|
end
|
data/lib/rswim/member/base.rb
CHANGED
@@ -4,20 +4,32 @@ module RSwim
|
|
4
4
|
module Member
|
5
5
|
module HealthState
|
6
6
|
class Alive < Base
|
7
|
-
def initialize(id,
|
7
|
+
def initialize(id, node_member_id, member_pool, must_propagate: false)
|
8
8
|
super
|
9
9
|
@failed_to_reply = false
|
10
10
|
end
|
11
11
|
|
12
12
|
def advance(_elapsed_seconds)
|
13
13
|
if @failed_to_reply
|
14
|
-
|
15
|
-
Suspected.new(@id, @member_pool, ue, true)
|
14
|
+
Suspected.new(@id, @node_member_id, @member_pool, must_propagate: true, send_ping_request: true)
|
16
15
|
else
|
17
16
|
self
|
18
17
|
end
|
19
18
|
end
|
20
19
|
|
20
|
+
def update_suspicion(status, old_incarnation_number, gossip_incarnation_number)
|
21
|
+
case status
|
22
|
+
when :confirmed then Confirmed.new(@id, @node_member_id, @member_pool)
|
23
|
+
when :suspected
|
24
|
+
if gossip_incarnation_number >= old_incarnation_number
|
25
|
+
Suspected.new(@id, @node_member_id, @member_pool, send_ping_request: false)
|
26
|
+
else
|
27
|
+
self
|
28
|
+
end
|
29
|
+
when :alive then self
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
21
33
|
def member_failed_to_reply
|
22
34
|
@failed_to_reply = true
|
23
35
|
end
|
@@ -25,6 +37,10 @@ module RSwim
|
|
25
37
|
def can_be_pinged?
|
26
38
|
true
|
27
39
|
end
|
40
|
+
|
41
|
+
def status
|
42
|
+
:alive
|
43
|
+
end
|
28
44
|
end
|
29
45
|
end
|
30
46
|
end
|
@@ -4,56 +4,26 @@ module RSwim
|
|
4
4
|
module Member
|
5
5
|
module HealthState
|
6
6
|
class Base
|
7
|
-
attr_reader :
|
7
|
+
attr_reader :propagation_count
|
8
8
|
|
9
|
-
def initialize(id, member_pool,
|
9
|
+
def initialize(id, node_member_id, member_pool, must_propagate:)
|
10
10
|
@member_pool = member_pool
|
11
11
|
@id = id
|
12
|
-
@
|
12
|
+
@node_member_id = node_member_id
|
13
|
+
@propagation_count = must_propagate ? -2 : 0
|
13
14
|
logger.debug("Member with id #{id} entered new state: #{self.class}")
|
14
15
|
end
|
15
16
|
|
16
|
-
def
|
17
|
-
|
17
|
+
def increment_propagation_count
|
18
|
+
@propagation_count += 1
|
18
19
|
end
|
19
20
|
|
20
|
-
def
|
21
|
-
|
22
|
-
s0 = @update_entry.status
|
23
|
-
i0 = @update_entry.incarnation_number
|
24
|
-
case status
|
25
|
-
when :confirmed
|
26
|
-
if (s0 == :confirmed)
|
27
|
-
self
|
28
|
-
else
|
29
|
-
ue = UpdateEntry.new(@id, status, incarnation_number, 0)
|
30
|
-
Confirmed.new(@id, @member_pool, ue)
|
31
|
-
end
|
32
|
-
when :suspected
|
33
|
-
if (s0 == :suspected && incarnation_number > i0) ||
|
34
|
-
(s0 == :alive && incarnation_number >= i0)
|
35
|
-
ue = UpdateEntry.new(@id, status, incarnation_number, 0)
|
36
|
-
Suspected.new(@id, @member_pool, ue, false)
|
37
|
-
else
|
38
|
-
self
|
39
|
-
end
|
40
|
-
when :alive
|
41
|
-
if (s0 == :suspected && incarnation_number > i0) ||
|
42
|
-
(s0 == :alive && incarnation_number > i0)
|
43
|
-
ue = UpdateEntry.new(@id, status, incarnation_number, 0)
|
44
|
-
Alive.new(@id, @member_pool, ue)
|
45
|
-
else
|
46
|
-
self
|
47
|
-
end
|
48
|
-
end
|
21
|
+
def advance(_elapsed_seconds)
|
22
|
+
self
|
49
23
|
end
|
50
24
|
|
51
25
|
def member_failed_to_reply; end
|
52
26
|
|
53
|
-
def increment_propagation_count
|
54
|
-
@update_entry.increment_propagation_count
|
55
|
-
end
|
56
|
-
|
57
27
|
def can_be_pinged?
|
58
28
|
false
|
59
29
|
end
|
@@ -61,9 +31,7 @@ module RSwim
|
|
61
31
|
protected
|
62
32
|
|
63
33
|
def logger
|
64
|
-
@_logger ||=
|
65
|
-
RSwim::Logger.new("unknown node", STDERR)
|
66
|
-
end
|
34
|
+
@_logger ||= RSwim::Logger.new("Node #{@node_member_id}", STDERR)
|
67
35
|
end
|
68
36
|
end
|
69
37
|
end
|
@@ -4,7 +4,7 @@ module RSwim
|
|
4
4
|
module Member
|
5
5
|
module HealthState
|
6
6
|
class Confirmed < Base
|
7
|
-
def initialize(id, member_pool,
|
7
|
+
def initialize(id, node_member_id, member_pool, must_propagate: false)
|
8
8
|
super
|
9
9
|
@member_halted = false
|
10
10
|
@member_removed = false
|
@@ -17,7 +17,7 @@ module RSwim
|
|
17
17
|
@member_pool.halt_member(@id)
|
18
18
|
@member_halted = true
|
19
19
|
end
|
20
|
-
|
20
|
+
|
21
21
|
if !@member_removed && @life_time_seconds > 10
|
22
22
|
@member_pool.remove_member(@id)
|
23
23
|
@member_removed = true
|
@@ -25,6 +25,14 @@ module RSwim
|
|
25
25
|
|
26
26
|
self
|
27
27
|
end
|
28
|
+
|
29
|
+
def update_suspicion(_status, _old_incarnation_number, _gossip_incarnation_number)
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
def status
|
34
|
+
:confirmed
|
35
|
+
end
|
28
36
|
end
|
29
37
|
end
|
30
38
|
end
|
@@ -4,8 +4,8 @@ module RSwim
|
|
4
4
|
module Member
|
5
5
|
module HealthState
|
6
6
|
class Suspected < Base
|
7
|
-
def initialize(id,
|
8
|
-
super(id, member_pool,
|
7
|
+
def initialize(id, node_member_id, member_pool, send_ping_request:, must_propagate: false)
|
8
|
+
super(id, node_member_id, member_pool, must_propagate: must_propagate)
|
9
9
|
@ping_request_sent = !send_ping_request
|
10
10
|
@life_time_seconds = 0
|
11
11
|
end
|
@@ -17,15 +17,33 @@ module RSwim
|
|
17
17
|
@ping_request_sent = true
|
18
18
|
end
|
19
19
|
if @life_time_seconds > 60
|
20
|
-
|
20
|
+
# TODO: make sure to propagate this information with priority
|
21
|
+
Confirmed.new(@id, @node_member_id, @member_pool, must_propagate: true)
|
21
22
|
else
|
22
23
|
self
|
23
24
|
end
|
24
25
|
end
|
25
26
|
|
27
|
+
def update_suspicion(status, old_incarnation_number, gossip_incarnation_number)
|
28
|
+
case status
|
29
|
+
when :confirmed then Confirmed.new(@id, @node_member_id, @member_pool)
|
30
|
+
when :suspected then self
|
31
|
+
when :alive
|
32
|
+
if gossip_incarnation_number > old_incarnation_number
|
33
|
+
Alive.new(@id, @node_member_id, @member_pool)
|
34
|
+
else
|
35
|
+
self
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
26
40
|
def can_be_pinged?
|
27
41
|
true
|
28
42
|
end
|
43
|
+
|
44
|
+
def status
|
45
|
+
:suspected
|
46
|
+
end
|
29
47
|
end
|
30
48
|
end
|
31
49
|
end
|
data/lib/rswim/member/me.rb
CHANGED
@@ -2,12 +2,17 @@
|
|
2
2
|
|
3
3
|
module RSwim
|
4
4
|
module Member
|
5
|
+
# Member behaviour of local node ("me")
|
5
6
|
class Me < Base
|
6
7
|
def initialize(id)
|
7
8
|
super
|
8
9
|
@ack_responder = AckResponder.new(id)
|
9
|
-
@incarnation_number = 0
|
10
10
|
@propagation_count = 0
|
11
|
+
@custom_state = {}
|
12
|
+
end
|
13
|
+
|
14
|
+
def increment_propagation_count
|
15
|
+
@propagation_count += 1
|
11
16
|
end
|
12
17
|
|
13
18
|
def schedule_ack(member_id)
|
@@ -18,21 +23,20 @@ module RSwim
|
|
18
23
|
@ack_responder.prepare_output
|
19
24
|
end
|
20
25
|
|
21
|
-
def
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
# making sure to get priority in being propagated
|
26
|
-
@propagation_count = -10
|
27
|
-
end
|
26
|
+
def append_custom_state(key, value)
|
27
|
+
@custom_state[key] = value
|
28
|
+
propagate_change
|
28
29
|
end
|
29
30
|
|
30
|
-
def
|
31
|
-
@
|
31
|
+
def incorporate_gossip(update_entry)
|
32
|
+
if update_entry.incarnation_number >= @incarnation_number &&
|
33
|
+
(update_entry.status != :alive || update_entry.custom_state != @custom_state)
|
34
|
+
propagate_change
|
35
|
+
end
|
32
36
|
end
|
33
37
|
|
34
38
|
def prepare_update_entry
|
35
|
-
UpdateEntry.new(@id, :alive, @incarnation_number, @propagation_count)
|
39
|
+
UpdateEntry.new(@id, :alive, @incarnation_number, @custom_state, @propagation_count)
|
36
40
|
end
|
37
41
|
|
38
42
|
def update(elapsed_seconds); end
|
@@ -40,6 +44,15 @@ module RSwim
|
|
40
44
|
def can_be_pinged?
|
41
45
|
false
|
42
46
|
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def propagate_change
|
51
|
+
@incarnation_number += 1
|
52
|
+
|
53
|
+
# making sure to get priority in being propagated
|
54
|
+
@propagation_count = -10
|
55
|
+
end
|
43
56
|
end
|
44
57
|
end
|
45
58
|
end
|
data/lib/rswim/member/peer.rb
CHANGED
@@ -6,9 +6,11 @@ module RSwim
|
|
6
6
|
def initialize(id, node_member_id, member_pool)
|
7
7
|
super(id)
|
8
8
|
@member_pool = member_pool
|
9
|
+
@node_member_id = node_member_id
|
9
10
|
@transmission_state = TransmissionState::Ready.new(id, node_member_id, member_pool)
|
10
|
-
@health_state = HealthState::Alive.new(id, member_pool)
|
11
|
+
@health_state = HealthState::Alive.new(id, node_member_id, member_pool)
|
11
12
|
@forwarding_state = ForwardingState::Ready.new(id, node_member_id)
|
13
|
+
@custom_state_holder = CustomStateHolder.new(id, node_member_id)
|
12
14
|
end
|
13
15
|
|
14
16
|
## Messages
|
@@ -28,8 +30,7 @@ module RSwim
|
|
28
30
|
@transmission_state.enqueue_ping_from(source_id)
|
29
31
|
end
|
30
32
|
|
31
|
-
|
32
|
-
## Callbacks
|
33
|
+
# # Callbacks
|
33
34
|
|
34
35
|
# call this when you received ack from member
|
35
36
|
def replied_with_ack
|
@@ -44,11 +45,10 @@ module RSwim
|
|
44
45
|
@health_state.member_failed_to_reply
|
45
46
|
end
|
46
47
|
|
47
|
-
|
48
48
|
## Commands
|
49
49
|
|
50
50
|
def halt
|
51
|
-
@transmission_state = TransmissionState::Off.new(@id)
|
51
|
+
@transmission_state = TransmissionState::Off.new(@id, @node_member_id)
|
52
52
|
end
|
53
53
|
|
54
54
|
def forward_ack
|
@@ -61,25 +61,68 @@ module RSwim
|
|
61
61
|
@health_state = @health_state.advance(elapsed_seconds)
|
62
62
|
end
|
63
63
|
|
64
|
+
def increment_propagation_count
|
65
|
+
@health_state.increment_propagation_count
|
66
|
+
@custom_state_holder.increment_propagation_count
|
67
|
+
end
|
68
|
+
|
64
69
|
def prepare_output
|
65
70
|
[@transmission_state, @forwarding_state].flat_map(&:prepare_output)
|
66
71
|
end
|
67
72
|
|
68
73
|
def prepare_update_entry
|
69
|
-
@health_state.
|
70
|
-
|
71
|
-
|
72
|
-
def increment_propagation_count
|
73
|
-
@health_state.increment_propagation_count
|
74
|
+
pc = [@health_state, @custom_state_holder].map!(&:propagation_count).min
|
75
|
+
UpdateEntry.new(@id, @health_state.status, @incarnation_number, @custom_state_holder.state, pc)
|
74
76
|
end
|
75
77
|
|
76
|
-
def
|
77
|
-
|
78
|
+
def incorporate_gossip(update_entry)
|
79
|
+
update_custom_state(update_entry.custom_state, update_entry.incarnation_number)
|
80
|
+
update_suspicion(update_entry.status, update_entry.incarnation_number)
|
81
|
+
@incarnation_number = update_entry.incarnation_number if update_entry.incarnation_number > @incarnation_number
|
78
82
|
end
|
79
83
|
|
80
84
|
def can_be_pinged?
|
81
85
|
@health_state.can_be_pinged?
|
82
86
|
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def update_custom_state(new_state, incarnation_number)
|
91
|
+
should_update = new_state != @custom_state_holder.state && incarnation_number > @incarnation_number
|
92
|
+
@custom_state_holder.state = new_state if should_update
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
def update_suspicion(status, incarnation_number = nil)
|
97
|
+
old_incarnation_number = @incarnation_number
|
98
|
+
incarnation_number ||= @incarnation_number
|
99
|
+
@health_state = @health_state.update_suspicion(status, old_incarnation_number, incarnation_number)
|
100
|
+
end
|
101
|
+
|
102
|
+
class CustomStateHolder
|
103
|
+
attr_reader :propagation_count, :state
|
104
|
+
|
105
|
+
def initialize(id, node_member_id)
|
106
|
+
@id = id
|
107
|
+
@node_member_id = node_member_id
|
108
|
+
@state = {}
|
109
|
+
@propagation_count = 0
|
110
|
+
end
|
111
|
+
|
112
|
+
def state=(arg)
|
113
|
+
@state = arg
|
114
|
+
@propagation_count = 0
|
115
|
+
logger.debug("Member with id #{@id} updated custom state: #{@state}")
|
116
|
+
end
|
117
|
+
|
118
|
+
def increment_propagation_count
|
119
|
+
@propagation_count += 1
|
120
|
+
end
|
121
|
+
|
122
|
+
def logger
|
123
|
+
@_logger ||= RSwim::Logger.new("Node #{@node_member_id}", $stderr)
|
124
|
+
end
|
125
|
+
end
|
83
126
|
end
|
84
127
|
end
|
85
128
|
end
|
data/lib/rswim/member_pool.rb
CHANGED
@@ -11,9 +11,13 @@ module RSwim
|
|
11
11
|
@subscribers = []
|
12
12
|
end
|
13
13
|
|
14
|
+
def append_custom_state(key, value)
|
15
|
+
@me.append_custom_state(key, value)
|
16
|
+
end
|
17
|
+
|
14
18
|
def update_member(message)
|
15
19
|
updates = message.payload[:updates]
|
16
|
-
|
20
|
+
incorporate_gossip(updates) unless updates.nil?
|
17
21
|
|
18
22
|
sender = member(message.from) # NB: records member if not seen before
|
19
23
|
case message.type
|
@@ -41,19 +45,19 @@ module RSwim
|
|
41
45
|
@subscribers << block
|
42
46
|
end
|
43
47
|
|
44
|
-
|
45
48
|
def prepare_output
|
49
|
+
ms = @members.values.flat_map(&:prepare_output)
|
50
|
+
return ms if ms.empty?
|
51
|
+
|
46
52
|
update_entries = @members.map { |_k, member| member.prepare_update_entry }
|
47
|
-
|
48
|
-
.sort_by { |entry| entry.propagation_count } # sort ascending!
|
53
|
+
.sort_by(&:propagation_count) # sort ascending!
|
49
54
|
.take(15) # TODO: constant
|
50
55
|
|
51
56
|
update_entries.each do |entry|
|
52
|
-
publish(entry
|
57
|
+
publish(entry) if entry.propagation_count.zero?
|
53
58
|
member(entry.member_id).increment_propagation_count
|
54
59
|
end
|
55
60
|
|
56
|
-
ms = @members.values.flat_map(&:prepare_output)
|
57
61
|
ms.each { |message| message.payload[:updates] = update_entries }
|
58
62
|
ms
|
59
63
|
end
|
@@ -62,8 +66,7 @@ module RSwim
|
|
62
66
|
ms = @members.values.select(&:can_be_pinged?)
|
63
67
|
return if ms.empty?
|
64
68
|
|
65
|
-
|
66
|
-
member = ms[index]
|
69
|
+
member = random_member(ms)
|
67
70
|
member.ping!
|
68
71
|
end
|
69
72
|
|
@@ -83,6 +86,7 @@ module RSwim
|
|
83
86
|
|
84
87
|
def remove_member(member_id)
|
85
88
|
raise 'boom' if member_id == @node_member_id
|
89
|
+
|
86
90
|
@members.delete(member_id)
|
87
91
|
end
|
88
92
|
|
@@ -94,20 +98,29 @@ module RSwim
|
|
94
98
|
member(member_id).failed_to_reply
|
95
99
|
end
|
96
100
|
|
101
|
+
protected
|
102
|
+
|
103
|
+
def random_member(members)
|
104
|
+
index = members.one? ? 0 : rand(members.size)
|
105
|
+
members[index]
|
106
|
+
end
|
107
|
+
|
97
108
|
private
|
98
109
|
|
99
|
-
def publish(
|
100
|
-
@subscribers.each { |s| s.call(
|
110
|
+
def publish(update_entry)
|
111
|
+
@subscribers.each { |s| s.call(update_entry) }
|
101
112
|
end
|
102
113
|
|
103
|
-
def
|
114
|
+
def incorporate_gossip(updates)
|
104
115
|
updates.each do |u|
|
105
|
-
member(u.member_id)
|
116
|
+
m = member(u.member_id)
|
117
|
+
m.incorporate_gossip(u)
|
106
118
|
end
|
107
119
|
end
|
108
120
|
|
109
121
|
def member(id)
|
110
122
|
raise 'boom' if id.nil?
|
123
|
+
|
111
124
|
@members[id] ||= Member::Peer.new(id, @node_member_id, self)
|
112
125
|
end
|
113
126
|
end
|
data/lib/rswim/message.rb
CHANGED
data/lib/rswim/node.rb
CHANGED
@@ -4,7 +4,7 @@ require 'socket'
|
|
4
4
|
|
5
5
|
module RSwim
|
6
6
|
class Node
|
7
|
-
def initialize(my_host, seed_hosts)
|
7
|
+
def initialize(my_host, seed_hosts, t_ms, r_ms)
|
8
8
|
@my_host = my_host
|
9
9
|
@directory = Directory.new
|
10
10
|
@my_id = @directory.id(@my_host)
|
@@ -12,27 +12,31 @@ module RSwim
|
|
12
12
|
@serializer = Integration::Serializer.new(@directory)
|
13
13
|
@pipe = RSwim::Pipe.simple
|
14
14
|
seed_ids = seed_hosts.map { |host| @directory.id(host) }
|
15
|
-
@agent = RSwim::Agent::SleepBased.new(@pipe, @my_id, seed_ids)
|
15
|
+
@agent = RSwim::Agent::SleepBased.new(@pipe, @my_id, seed_ids, t_ms, r_ms)
|
16
16
|
end
|
17
17
|
|
18
|
-
def self.udp(my_host, seed_hosts, port)
|
19
|
-
Integration::Udp::Node.new(my_host, seed_hosts, port)
|
18
|
+
def self.udp(my_host, seed_hosts, port, t_ms = T_MS, r_ms = R_MS)
|
19
|
+
Integration::Udp::Node.new(my_host, seed_hosts, port, t_ms, r_ms)
|
20
20
|
end
|
21
21
|
|
22
22
|
def subscribe(&block)
|
23
|
-
@agent.subscribe do |
|
24
|
-
host = @directory.host(
|
25
|
-
block.call(host, status)
|
23
|
+
@agent.subscribe do |update_entry|
|
24
|
+
host = @directory.host(update_entry.member_id)
|
25
|
+
block.call(host, update_entry.status, update_entry.custom_state)
|
26
26
|
end
|
27
27
|
end
|
28
28
|
|
29
|
+
def append_custom_state(key, value)
|
30
|
+
@agent.append_custom_state(key, value)
|
31
|
+
end
|
32
|
+
|
29
33
|
# blocks until interrupted
|
30
34
|
def start
|
31
35
|
logger.info 'starting node'
|
32
36
|
before_start
|
33
37
|
@agent.run
|
34
38
|
rescue StandardError => e
|
35
|
-
logger.
|
39
|
+
logger.error("Node failed: #{e}")
|
36
40
|
end
|
37
41
|
|
38
42
|
protected
|
@@ -42,9 +46,7 @@ module RSwim
|
|
42
46
|
end
|
43
47
|
|
44
48
|
def logger
|
45
|
-
@_logger ||=
|
46
|
-
RSwim::Logger.new(self.class, STDERR)
|
47
|
-
end
|
49
|
+
@_logger ||= RSwim::Logger.new(self.class, $stderr)
|
48
50
|
end
|
49
51
|
end
|
50
52
|
end
|
data/lib/rswim/protocol_state.rb
CHANGED
@@ -5,7 +5,7 @@ module RSwim
|
|
5
5
|
def initialize(node_member_id, seed_member_ids, t_ms, r_ms)
|
6
6
|
@t_ms = t_ms
|
7
7
|
@r_ms = r_ms
|
8
|
-
@member_pool =
|
8
|
+
@member_pool = new_member_pool(node_member_id, seed_member_ids)
|
9
9
|
@node_member_id = node_member_id
|
10
10
|
@t = @r = 1
|
11
11
|
end
|
@@ -14,6 +14,10 @@ module RSwim
|
|
14
14
|
@member_pool.subscribe(&block)
|
15
15
|
end
|
16
16
|
|
17
|
+
def append_custom_state(key, value)
|
18
|
+
@member_pool.append_custom_state(key, value)
|
19
|
+
end
|
20
|
+
|
17
21
|
def advance(input_messages, elapsed_seconds)
|
18
22
|
@t += elapsed_seconds * 1000
|
19
23
|
@t = 0 if @t >= @t_ms
|
@@ -50,6 +54,12 @@ module RSwim
|
|
50
54
|
output_messages
|
51
55
|
end
|
52
56
|
|
57
|
+
protected
|
58
|
+
|
59
|
+
def new_member_pool(node_member_id, seed_member_ids)
|
60
|
+
MemberPool.new(node_member_id, seed_member_ids)
|
61
|
+
end
|
62
|
+
|
53
63
|
private
|
54
64
|
|
55
65
|
def logger
|
data/lib/rswim/update_entry.rb
CHANGED
@@ -1,17 +1,21 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
module RSwim
|
3
4
|
class UpdateEntry
|
4
|
-
attr_reader :member_id, :status, :incarnation_number, :propagation_count
|
5
|
+
attr_reader :member_id, :status, :incarnation_number, :propagation_count, :custom_state
|
5
6
|
|
6
|
-
def initialize(member_id, status, incarnation_number, propagation_count = 0)
|
7
|
+
def initialize(member_id, status, incarnation_number, custom_state, propagation_count = 0)
|
7
8
|
@member_id = member_id
|
8
9
|
@status = status
|
9
10
|
@incarnation_number = incarnation_number
|
11
|
+
@custom_state = custom_state
|
10
12
|
@propagation_count = propagation_count
|
11
13
|
end
|
12
14
|
|
13
|
-
def
|
14
|
-
|
15
|
+
def ==(other)
|
16
|
+
%i[member_id status incarnation_number propagation_count custom_state].all? do |a|
|
17
|
+
send(a) == other.send(a)
|
18
|
+
end
|
15
19
|
end
|
16
20
|
end
|
17
21
|
end
|
data/lib/rswim/version.rb
CHANGED
data/rswim.gemspec
CHANGED
@@ -26,9 +26,12 @@ Gem::Specification.new do |spec|
|
|
26
26
|
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
27
27
|
spec.require_paths = ["lib"]
|
28
28
|
|
29
|
+
spec.required_ruby_version = '>= 3.0.0'
|
30
|
+
|
29
31
|
spec.add_dependency 'zeitwerk', '~> 2.2'
|
32
|
+
spec.add_dependency 'slop', '~> 4.9'
|
30
33
|
|
31
|
-
spec.add_development_dependency "bundler", "
|
34
|
+
spec.add_development_dependency "bundler", ">= 2.2.10"
|
32
35
|
spec.add_development_dependency "rake", "~> 12.0"
|
33
36
|
spec.add_development_dependency "rspec", "~> 3.0"
|
34
37
|
spec.add_development_dependency 'guard-rspec', '~> 4.7'
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: rswim
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 2.0.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Erik Madsen
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-10-05 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: zeitwerk
|
@@ -25,19 +25,33 @@ dependencies:
|
|
25
25
|
- !ruby/object:Gem::Version
|
26
26
|
version: '2.2'
|
27
27
|
- !ruby/object:Gem::Dependency
|
28
|
-
name:
|
28
|
+
name: slop
|
29
29
|
requirement: !ruby/object:Gem::Requirement
|
30
30
|
requirements:
|
31
31
|
- - "~>"
|
32
32
|
- !ruby/object:Gem::Version
|
33
|
-
version:
|
34
|
-
type: :
|
33
|
+
version: '4.9'
|
34
|
+
type: :runtime
|
35
35
|
prerelease: false
|
36
36
|
version_requirements: !ruby/object:Gem::Requirement
|
37
37
|
requirements:
|
38
38
|
- - "~>"
|
39
39
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
40
|
+
version: '4.9'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: bundler
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: 2.2.10
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: 2.2.10
|
41
55
|
- !ruby/object:Gem::Dependency
|
42
56
|
name: rake
|
43
57
|
requirement: !ruby/object:Gem::Requirement
|
@@ -173,7 +187,7 @@ metadata:
|
|
173
187
|
homepage_uri: https://github.com/beatmadsen/rswim
|
174
188
|
source_code_uri: https://github.com/beatmadsen/rswim
|
175
189
|
changelog_uri: https://github.com/beatmadsen/rswim/blob/master/CHANGELOG.md
|
176
|
-
post_install_message:
|
190
|
+
post_install_message:
|
177
191
|
rdoc_options: []
|
178
192
|
require_paths:
|
179
193
|
- lib
|
@@ -181,15 +195,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
181
195
|
requirements:
|
182
196
|
- - ">="
|
183
197
|
- !ruby/object:Gem::Version
|
184
|
-
version:
|
198
|
+
version: 3.0.0
|
185
199
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
186
200
|
requirements:
|
187
201
|
- - ">="
|
188
202
|
- !ruby/object:Gem::Version
|
189
203
|
version: '0'
|
190
204
|
requirements: []
|
191
|
-
rubygems_version: 3.
|
192
|
-
signing_key:
|
205
|
+
rubygems_version: 3.2.22
|
206
|
+
signing_key:
|
193
207
|
specification_version: 4
|
194
208
|
summary: Ruby implementation of the SWIM gossip protocol
|
195
209
|
test_files: []
|