rswim 1.0.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.
- checksums.yaml +7 -0
- data/.gitignore +17 -0
- data/.rspec +3 -0
- data/.ruby-version +1 -0
- data/.travis.yml +6 -0
- data/CHANGELOG.md +1 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +79 -0
- data/Guardfile +42 -0
- data/LICENSE.txt +21 -0
- data/README.md +72 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/run_node +30 -0
- data/bin/setup +8 -0
- data/lib/rswim.rb +33 -0
- data/lib/rswim/agent.rb +69 -0
- data/lib/rswim/directory.rb +27 -0
- data/lib/rswim/integration/deserializer.rb +51 -0
- data/lib/rswim/integration/serializer.rb +29 -0
- data/lib/rswim/integration/udp/node.rb +61 -0
- data/lib/rswim/logger.rb +31 -0
- data/lib/rswim/member/ack_responder.rb +22 -0
- data/lib/rswim/member/base.rb +11 -0
- data/lib/rswim/member/forwarding_state/base.rb +33 -0
- data/lib/rswim/member/forwarding_state/forwarding_ack.rb +26 -0
- data/lib/rswim/member/forwarding_state/ready.rb +24 -0
- data/lib/rswim/member/health_state/alive.rb +31 -0
- data/lib/rswim/member/health_state/base.rb +71 -0
- data/lib/rswim/member/health_state/confirmed.rb +31 -0
- data/lib/rswim/member/health_state/suspected.rb +32 -0
- data/lib/rswim/member/me.rb +45 -0
- data/lib/rswim/member/peer.rb +85 -0
- data/lib/rswim/member/transmission_state/awaiting_ack.rb +38 -0
- data/lib/rswim/member/transmission_state/base.rb +48 -0
- data/lib/rswim/member/transmission_state/off.rb +21 -0
- data/lib/rswim/member/transmission_state/ready.rb +27 -0
- data/lib/rswim/member/transmission_state/sending_ping.rb +30 -0
- data/lib/rswim/member/transmission_state/sending_ping_request.rb +31 -0
- data/lib/rswim/member_pool.rb +114 -0
- data/lib/rswim/message.rb +18 -0
- data/lib/rswim/node.rb +50 -0
- data/lib/rswim/pipe.rb +32 -0
- data/lib/rswim/protocol_state.rb +65 -0
- data/lib/rswim/status_report.rb +29 -0
- data/lib/rswim/update_entry.rb +17 -0
- data/lib/rswim/version.rb +5 -0
- data/log/.keep +0 -0
- data/rswim.gemspec +37 -0
- data/tmp/.keep +0 -0
- metadata +195 -0
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RSwim
|
4
|
+
module Member
|
5
|
+
class Me < Base
|
6
|
+
def initialize(id)
|
7
|
+
super
|
8
|
+
@ack_responder = AckResponder.new(id)
|
9
|
+
@incarnation_number = 0
|
10
|
+
@propagation_count = 0
|
11
|
+
end
|
12
|
+
|
13
|
+
def schedule_ack(member_id)
|
14
|
+
@ack_responder.schedule_ack(member_id)
|
15
|
+
end
|
16
|
+
|
17
|
+
def prepare_output
|
18
|
+
@ack_responder.prepare_output
|
19
|
+
end
|
20
|
+
|
21
|
+
def update_suspicion(status, incarnation_number)
|
22
|
+
if status != :alive && incarnation_number == @incarnation_number
|
23
|
+
@incarnation_number += 1
|
24
|
+
|
25
|
+
# making sure to get priority in being propagated
|
26
|
+
@propagation_count = -10
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def increment_propagation_count
|
31
|
+
@propagation_count += 1
|
32
|
+
end
|
33
|
+
|
34
|
+
def prepare_update_entry
|
35
|
+
UpdateEntry.new(@id, :alive, @incarnation_number, @propagation_count)
|
36
|
+
end
|
37
|
+
|
38
|
+
def update(elapsed_seconds); end
|
39
|
+
|
40
|
+
def can_be_pinged?
|
41
|
+
false
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RSwim
|
4
|
+
module Member
|
5
|
+
class Peer < Base
|
6
|
+
def initialize(id, node_member_id, member_pool)
|
7
|
+
super(id)
|
8
|
+
@member_pool = member_pool
|
9
|
+
@transmission_state = TransmissionState::Ready.new(id, node_member_id, member_pool)
|
10
|
+
@health_state = HealthState::Alive.new(id, member_pool)
|
11
|
+
@forwarding_state = ForwardingState::Ready.new(id, node_member_id)
|
12
|
+
end
|
13
|
+
|
14
|
+
## Messages
|
15
|
+
|
16
|
+
# send a ping message to this peer
|
17
|
+
def ping!
|
18
|
+
@transmission_state.enqueue_ping
|
19
|
+
end
|
20
|
+
|
21
|
+
# send ping request to this peer trying to reach target with target_id
|
22
|
+
def ping_request!(target_id)
|
23
|
+
@transmission_state.enqueue_ping_request(target_id)
|
24
|
+
end
|
25
|
+
|
26
|
+
# send a ping message to this peer on behalf of source with source_id
|
27
|
+
def ping_from!(source_id)
|
28
|
+
@transmission_state.enqueue_ping_from(source_id)
|
29
|
+
end
|
30
|
+
|
31
|
+
|
32
|
+
## Callbacks
|
33
|
+
|
34
|
+
# call this when you received ack from member
|
35
|
+
def replied_with_ack
|
36
|
+
@transmission_state.member_replied_with_ack
|
37
|
+
end
|
38
|
+
|
39
|
+
def replied_in_time
|
40
|
+
update_suspicion(:alive)
|
41
|
+
end
|
42
|
+
|
43
|
+
def failed_to_reply
|
44
|
+
@health_state.member_failed_to_reply
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
## Commands
|
49
|
+
|
50
|
+
def halt
|
51
|
+
@transmission_state = TransmissionState::Off.new(@id)
|
52
|
+
end
|
53
|
+
|
54
|
+
def forward_ack
|
55
|
+
@forwarding_state.forward_ack_to_member
|
56
|
+
end
|
57
|
+
|
58
|
+
def update(elapsed_seconds)
|
59
|
+
@transmission_state = @transmission_state.advance(elapsed_seconds)
|
60
|
+
@forwarding_state = @forwarding_state.advance(elapsed_seconds)
|
61
|
+
@health_state = @health_state.advance(elapsed_seconds)
|
62
|
+
end
|
63
|
+
|
64
|
+
def prepare_output
|
65
|
+
[@transmission_state, @forwarding_state].flat_map(&:prepare_output)
|
66
|
+
end
|
67
|
+
|
68
|
+
def prepare_update_entry
|
69
|
+
@health_state.update_entry
|
70
|
+
end
|
71
|
+
|
72
|
+
def increment_propagation_count
|
73
|
+
@health_state.increment_propagation_count
|
74
|
+
end
|
75
|
+
|
76
|
+
def update_suspicion(status, incarnation_number=nil)
|
77
|
+
@health_state = @health_state.update_suspicion(status, incarnation_number)
|
78
|
+
end
|
79
|
+
|
80
|
+
def can_be_pinged?
|
81
|
+
@health_state.can_be_pinged?
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RSwim
|
4
|
+
module Member
|
5
|
+
module TransmissionState
|
6
|
+
class AwaitingAck < Base
|
7
|
+
def initialize(id, node_member_id, member_pool, source_ids, target_ids)
|
8
|
+
super
|
9
|
+
@life_time_seconds = 0
|
10
|
+
@done = false
|
11
|
+
end
|
12
|
+
|
13
|
+
def member_replied_with_ack
|
14
|
+
if @source_ids.include?(@id)
|
15
|
+
@member_pool.member_replied_in_time(@id)
|
16
|
+
end
|
17
|
+
@source_ids.each { |i| @member_pool.forward_ack_to(i) unless i == @id }
|
18
|
+
@source_ids.clear
|
19
|
+
@done = true
|
20
|
+
end
|
21
|
+
|
22
|
+
def advance(elapsed_seconds)
|
23
|
+
@life_time_seconds += elapsed_seconds
|
24
|
+
if @done
|
25
|
+
Ready.new(@id, @node_member_id, @member_pool, @source_ids, @target_ids)
|
26
|
+
elsif @life_time_seconds > R_MS / 1000.0
|
27
|
+
if @source_ids.include?(@id)
|
28
|
+
@member_pool.member_failed_to_reply(@id)
|
29
|
+
end
|
30
|
+
Ready.new(@id, @node_member_id, @member_pool, @source_ids, @target_ids)
|
31
|
+
else
|
32
|
+
self
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RSwim
|
4
|
+
module Member
|
5
|
+
module TransmissionState
|
6
|
+
class Base
|
7
|
+
def initialize(id, node_member_id, member_pool, source_ids, target_ids)
|
8
|
+
@member_pool = member_pool
|
9
|
+
@id = id
|
10
|
+
@node_member_id = node_member_id
|
11
|
+
@source_ids = source_ids
|
12
|
+
@target_ids = target_ids
|
13
|
+
logger.debug("Member with id #{id} entered new state: #{self.class}")
|
14
|
+
end
|
15
|
+
|
16
|
+
def member_replied_with_ack; end
|
17
|
+
|
18
|
+
def advance(_elapsed_seconds)
|
19
|
+
self
|
20
|
+
end
|
21
|
+
|
22
|
+
def prepare_output
|
23
|
+
[]
|
24
|
+
end
|
25
|
+
|
26
|
+
def enqueue_ping
|
27
|
+
@source_ids << @id
|
28
|
+
end
|
29
|
+
|
30
|
+
def enqueue_ping_from(source_id)
|
31
|
+
@source_ids << source_id
|
32
|
+
end
|
33
|
+
|
34
|
+
def enqueue_ping_request(target_id)
|
35
|
+
@target_ids << target_id
|
36
|
+
end
|
37
|
+
|
38
|
+
protected
|
39
|
+
|
40
|
+
def logger
|
41
|
+
@_logger ||= begin
|
42
|
+
RSwim::Logger.new("Node #{@node_member_id}", STDERR)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RSwim
|
4
|
+
module Member
|
5
|
+
module TransmissionState
|
6
|
+
class Off < Base
|
7
|
+
def initialize(id)
|
8
|
+
super(id, nil, nil, [], [])
|
9
|
+
end
|
10
|
+
|
11
|
+
def member_replied_with_ack
|
12
|
+
logger.debug("out of order ack from member #{@id}")
|
13
|
+
end
|
14
|
+
|
15
|
+
def advance(_elapsed_seconds)
|
16
|
+
self
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RSwim
|
4
|
+
module Member
|
5
|
+
module TransmissionState
|
6
|
+
class Ready < Base
|
7
|
+
def initialize(id, node_member_id, member_pool, source_ids = [], target_ids = [])
|
8
|
+
super
|
9
|
+
end
|
10
|
+
|
11
|
+
def member_replied_with_ack
|
12
|
+
logger.debug("out of order ack from member #{@id}")
|
13
|
+
end
|
14
|
+
|
15
|
+
def advance(_elapsed_seconds)
|
16
|
+
if !@source_ids.empty?
|
17
|
+
SendingPing.new(@id, @node_member_id, @member_pool, @source_ids, @target_ids)
|
18
|
+
elsif !@target_ids.empty?
|
19
|
+
SendingPingRequest.new(@id, @node_member_id, @member_pool, @source_ids, @target_ids)
|
20
|
+
else
|
21
|
+
self
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RSwim
|
4
|
+
module Member
|
5
|
+
module TransmissionState
|
6
|
+
class SendingPing < Base
|
7
|
+
def initialize(id, node_member_id, member_pool, source_ids, target_ids)
|
8
|
+
super
|
9
|
+
@done = false
|
10
|
+
end
|
11
|
+
|
12
|
+
def member_replied_with_ack
|
13
|
+
logger.debug("out of order ack from member #{@id}")
|
14
|
+
end
|
15
|
+
|
16
|
+
def advance(_elapsed_seconds)
|
17
|
+
if @done then AwaitingAck.new(@id, @node_member_id, @member_pool, @source_ids, @target_ids)
|
18
|
+
else self
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def prepare_output
|
23
|
+
@done = true
|
24
|
+
message = Message.new(@id, @node_member_id, :ping)
|
25
|
+
[message]
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RSwim
|
4
|
+
module Member
|
5
|
+
module TransmissionState
|
6
|
+
class SendingPingRequest < Base
|
7
|
+
def initialize(id, node_member_id, member_pool, source_ids, target_ids)
|
8
|
+
super
|
9
|
+
@done = false
|
10
|
+
end
|
11
|
+
|
12
|
+
def member_replied_with_ack
|
13
|
+
logger.debug("out of order ack from member #{@id}")
|
14
|
+
end
|
15
|
+
|
16
|
+
def advance(_elapsed_seconds)
|
17
|
+
if @done then AwaitingAck.new(@id, @node_member_id, @member_pool, @source_ids, @target_ids)
|
18
|
+
else self
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def prepare_output
|
23
|
+
@done = true
|
24
|
+
target_id = @target_ids.shift
|
25
|
+
message = Message.new(@id, @node_member_id, :ping_req, target_id: target_id)
|
26
|
+
[message]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RSwim
|
4
|
+
class MemberPool
|
5
|
+
def initialize(node_member_id, seed_member_ids)
|
6
|
+
seed_member_ids -= [node_member_id]
|
7
|
+
@node_member_id = node_member_id
|
8
|
+
@me = Member::Me.new(node_member_id)
|
9
|
+
@members = { node_member_id => @me }
|
10
|
+
seed_member_ids.each { |id| member(id) }
|
11
|
+
@subscribers = []
|
12
|
+
end
|
13
|
+
|
14
|
+
def update_member(message)
|
15
|
+
updates = message.payload[:updates]
|
16
|
+
update_suspicions(updates) unless updates.nil?
|
17
|
+
|
18
|
+
sender = member(message.from) # NB: records member if not seen before
|
19
|
+
case message.type
|
20
|
+
when :ping
|
21
|
+
@me.schedule_ack(message.from)
|
22
|
+
when :ack
|
23
|
+
sender.replied_with_ack
|
24
|
+
when :ping_req
|
25
|
+
target_id = message.payload[:target_id]
|
26
|
+
member(target_id).ping_from!(message.from)
|
27
|
+
else
|
28
|
+
raise 'bad message type'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def update_members(elapsed_seconds)
|
33
|
+
@members.values.each { |m| m.update(elapsed_seconds) }
|
34
|
+
end
|
35
|
+
|
36
|
+
def status_report
|
37
|
+
StatusReport.print(@node_member_id, @members)
|
38
|
+
end
|
39
|
+
|
40
|
+
def subscribe(&block)
|
41
|
+
@subscribers << block
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
def prepare_output
|
46
|
+
update_entries = @members.map { |_k, member| member.prepare_update_entry }
|
47
|
+
# .select { |entry| entry.propagation_count < 5 }
|
48
|
+
.sort_by { |entry| entry.propagation_count } # sort ascending!
|
49
|
+
.take(15) # TODO: constant
|
50
|
+
|
51
|
+
update_entries.each do |entry|
|
52
|
+
publish(entry.member_id, entry.status) if entry.propagation_count.zero?
|
53
|
+
member(entry.member_id).increment_propagation_count
|
54
|
+
end
|
55
|
+
|
56
|
+
ms = @members.values.flat_map(&:prepare_output)
|
57
|
+
ms.each { |message| message.payload[:updates] = update_entries }
|
58
|
+
ms
|
59
|
+
end
|
60
|
+
|
61
|
+
def send_ping_to_random_healthy_member
|
62
|
+
ms = @members.values.select(&:can_be_pinged?)
|
63
|
+
return if ms.empty?
|
64
|
+
|
65
|
+
index = ms.one? ? 0 : rand(ms.size)
|
66
|
+
member = ms[index]
|
67
|
+
member.ping!
|
68
|
+
end
|
69
|
+
|
70
|
+
def send_ping_request_to_k_members(target_id)
|
71
|
+
@members.inject([]) { |acc, (id, m)| id != target_id && m.can_be_pinged? ? (acc << m) : acc }
|
72
|
+
.take(K)
|
73
|
+
.each { |m| m.ping_request!(target_id) }
|
74
|
+
end
|
75
|
+
|
76
|
+
def forward_ack_to(member_id)
|
77
|
+
member(member_id).forward_ack
|
78
|
+
end
|
79
|
+
|
80
|
+
def halt_member(member_id)
|
81
|
+
member(member_id).halt
|
82
|
+
end
|
83
|
+
|
84
|
+
def remove_member(member_id)
|
85
|
+
raise 'boom' if member_id == @node_member_id
|
86
|
+
@members.delete(member_id)
|
87
|
+
end
|
88
|
+
|
89
|
+
def member_replied_in_time(member_id)
|
90
|
+
member(member_id).replied_in_time
|
91
|
+
end
|
92
|
+
|
93
|
+
def member_failed_to_reply(member_id)
|
94
|
+
member(member_id).failed_to_reply
|
95
|
+
end
|
96
|
+
|
97
|
+
private
|
98
|
+
|
99
|
+
def publish(member_id, status)
|
100
|
+
@subscribers.each { |s| s.call(member_id, status) }
|
101
|
+
end
|
102
|
+
|
103
|
+
def update_suspicions(updates)
|
104
|
+
updates.each do |u|
|
105
|
+
member(u.member_id).update_suspicion(u.status, u.incarnation_number)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def member(id)
|
110
|
+
raise 'boom' if id.nil?
|
111
|
+
@members[id] ||= Member::Peer.new(id, @node_member_id, self)
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|