rswim 1.0.0

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