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.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.rspec +3 -0
  4. data/.ruby-version +1 -0
  5. data/.travis.yml +6 -0
  6. data/CHANGELOG.md +1 -0
  7. data/Gemfile +4 -0
  8. data/Gemfile.lock +79 -0
  9. data/Guardfile +42 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.md +72 -0
  12. data/Rakefile +6 -0
  13. data/bin/console +14 -0
  14. data/bin/run_node +30 -0
  15. data/bin/setup +8 -0
  16. data/lib/rswim.rb +33 -0
  17. data/lib/rswim/agent.rb +69 -0
  18. data/lib/rswim/directory.rb +27 -0
  19. data/lib/rswim/integration/deserializer.rb +51 -0
  20. data/lib/rswim/integration/serializer.rb +29 -0
  21. data/lib/rswim/integration/udp/node.rb +61 -0
  22. data/lib/rswim/logger.rb +31 -0
  23. data/lib/rswim/member/ack_responder.rb +22 -0
  24. data/lib/rswim/member/base.rb +11 -0
  25. data/lib/rswim/member/forwarding_state/base.rb +33 -0
  26. data/lib/rswim/member/forwarding_state/forwarding_ack.rb +26 -0
  27. data/lib/rswim/member/forwarding_state/ready.rb +24 -0
  28. data/lib/rswim/member/health_state/alive.rb +31 -0
  29. data/lib/rswim/member/health_state/base.rb +71 -0
  30. data/lib/rswim/member/health_state/confirmed.rb +31 -0
  31. data/lib/rswim/member/health_state/suspected.rb +32 -0
  32. data/lib/rswim/member/me.rb +45 -0
  33. data/lib/rswim/member/peer.rb +85 -0
  34. data/lib/rswim/member/transmission_state/awaiting_ack.rb +38 -0
  35. data/lib/rswim/member/transmission_state/base.rb +48 -0
  36. data/lib/rswim/member/transmission_state/off.rb +21 -0
  37. data/lib/rswim/member/transmission_state/ready.rb +27 -0
  38. data/lib/rswim/member/transmission_state/sending_ping.rb +30 -0
  39. data/lib/rswim/member/transmission_state/sending_ping_request.rb +31 -0
  40. data/lib/rswim/member_pool.rb +114 -0
  41. data/lib/rswim/message.rb +18 -0
  42. data/lib/rswim/node.rb +50 -0
  43. data/lib/rswim/pipe.rb +32 -0
  44. data/lib/rswim/protocol_state.rb +65 -0
  45. data/lib/rswim/status_report.rb +29 -0
  46. data/lib/rswim/update_entry.rb +17 -0
  47. data/lib/rswim/version.rb +5 -0
  48. data/log/.keep +0 -0
  49. data/rswim.gemspec +37 -0
  50. data/tmp/.keep +0 -0
  51. 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