rswim 2.0.0 → 2.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b9a936a878b5bdbfd6503ac3693577d73a894d77d6d25a9b5794354595cb4b3b
4
- data.tar.gz: 806de89b6fb5e6fdb4af90ba60d71c0af839a41a34dbfcd8f5ae40155a25856d
3
+ metadata.gz: 2cdad1327bf5ba2e7fd3a2d975b7f0ad507d99952b9c2b2d01269f4f3b813f25
4
+ data.tar.gz: 2050c00ddb9eec045103645d269683ad0454d664d92026f6849f80428e1483ff
5
5
  SHA512:
6
- metadata.gz: 439d9eac5fa4ce3d610190ae6f7d1e39fdebad50a699b37678d28d2c3f2c2d51917e9feedbea03f39ccd2e47f70a703fff36a2be425d79b211881025168fd8dc
7
- data.tar.gz: 6736caa12593b49b06956580f1698ac009a8202d3f71a6473e9b68b35eb8cf7dee5071266f7fe19cd07396405509c4836a33257c1ec65984de02838605165bfa
6
+ metadata.gz: 17a26dc6da80bc10db656b7127f2e72c7bb77d962eec38fc63830aa5b5d4642c8e3650fb8c4d2b860efac2e1eaa12c590c98f31dc1e4092adec31018c8b558bf
7
+ data.tar.gz: 9563514c494f896b54098b80b1636a20323a84d07b5f9b689577e592592edc6ae480449fcf7020ae1ad79b72084a7d087967983f9b2229a712de9d4712e95d8e
data/CHANGELOG.md CHANGED
@@ -1,2 +1,3 @@
1
1
  # 1.0.0 Complete implementation for UDP plus simple, human readable serialisation of messages
2
2
  # 2.0.0 Piggyback custom state on the liveness propagation mechanism using `RSwim::Node#append_custom_state`
3
+ # 2.1.0 Use non-blocking I/O by means of `Fiber.shedule` with the scheduler provided by the Async gem
data/Gemfile.lock CHANGED
@@ -1,17 +1,25 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- rswim (2.0.0)
4
+ rswim (2.1.0)
5
+ async (~> 1.30)
5
6
  slop (~> 4.9)
6
7
  zeitwerk (~> 2.2)
7
8
 
8
9
  GEM
9
10
  remote: https://rubygems.org/
10
11
  specs:
12
+ async (1.30.1)
13
+ console (~> 1.10)
14
+ nio4r (~> 2.3)
15
+ timers (~> 4.1)
11
16
  byebug (11.1.3)
12
17
  coderay (1.1.3)
18
+ console (1.13.1)
19
+ fiber-local
13
20
  diff-lcs (1.4.4)
14
21
  ffi (1.15.4)
22
+ fiber-local (1.0.0)
15
23
  formatador (0.3.0)
16
24
  fuubar (2.5.1)
17
25
  rspec-core (~> 3.0)
@@ -36,6 +44,7 @@ GEM
36
44
  lumberjack (1.2.8)
37
45
  method_source (1.0.0)
38
46
  nenv (0.3.0)
47
+ nio4r (2.5.8)
39
48
  notiffany (0.1.3)
40
49
  nenv (~> 0.1)
41
50
  shellany (~> 0.0)
@@ -63,6 +72,7 @@ GEM
63
72
  shellany (0.0.1)
64
73
  slop (4.9.1)
65
74
  thor (1.1.0)
75
+ timers (4.3.3)
66
76
  zeitwerk (2.4.2)
67
77
 
68
78
  PLATFORMS
data/bin/async_loop ADDED
@@ -0,0 +1,37 @@
1
+ #!/usr/bin/env ruby --jit
2
+ require 'bundler/setup'
3
+ require 'async'
4
+ require 'rswim'
5
+
6
+ PORT = 4545
7
+
8
+ @stuff = []
9
+
10
+ def read
11
+ if @stuff.empty?
12
+ sleep 2
13
+ end
14
+ Array.new(@stuff.size) { @stuff.pop }
15
+ end
16
+
17
+ puts "Ready\n"
18
+ begin
19
+ # Run node (blocking)
20
+ Async do
21
+ Fiber.schedule do
22
+ in_s = UDPSocket.new
23
+ my_host = "localhost" # Socket.ip_address_list.find(&:ipv4_private?).ip_address
24
+
25
+ in_s.bind(my_host, PORT)
26
+ loop do
27
+ text, sender = in_s.recvfrom(10_000)
28
+ puts "received #{text} from network"
29
+ @stuff << text
30
+ end
31
+ end
32
+ loop { puts "Read: #{read }" }
33
+ end
34
+ rescue Interrupt
35
+ puts "\nShutting down gracefully"
36
+ end
37
+ puts "\nDone"
data/lib/rswim/agent.rb CHANGED
@@ -2,7 +2,48 @@
2
2
 
3
3
  module RSwim
4
4
  module Agent
5
- class Base
5
+ # Non-blocking agent that is explicitly advanced
6
+ class PushBased
7
+ def initialize(node_member_id, seed_member_ids, t_ms, r_ms)
8
+ @state = new_protocol_state(node_member_id, seed_member_ids, t_ms, r_ms)
9
+ end
10
+
11
+ def subscribe(&block)
12
+ @state.subscribe(&block)
13
+ end
14
+
15
+ def append_custom_state(key, value)
16
+ @state.append_custom_state(key, value)
17
+ end
18
+
19
+ def run
20
+ @t0 = monotonic_seconds
21
+ end
22
+
23
+ def advance(messages)
24
+ raise 'not running' if @t0.nil?
25
+
26
+ t1 = monotonic_seconds
27
+ elapsed_seconds = t1 - @t0
28
+ @t0 = t1
29
+ @state.advance(messages, elapsed_seconds)
30
+ end
31
+
32
+ protected
33
+
34
+ def new_protocol_state(node_member_id, seed_member_ids, t_ms, r_ms)
35
+ ProtocolState.new(node_member_id, seed_member_ids, t_ms, r_ms)
36
+ end
37
+
38
+ private
39
+
40
+ def monotonic_seconds
41
+ Process.clock_gettime(Process::CLOCK_MONOTONIC)
42
+ end
43
+ end
44
+
45
+ # Blocking agent that pops messages from pipe, processes them and pauses before repeating
46
+ class PullBased
6
47
  def initialize(pipe, node_member_id, seed_member_ids, t_ms, r_ms)
7
48
  @pipe = pipe
8
49
  @state = new_protocol_state(node_member_id, seed_member_ids, t_ms, r_ms)
@@ -39,7 +80,7 @@ module RSwim
39
80
  end
40
81
  end
41
82
 
42
- class SleepBased < Base
83
+ class SleepBased < PullBased
43
84
  def initialize(pipe, node_member_id, seed_member_ids, t_ms, r_ms, sleep_time_seconds = 0.1)
44
85
  super(pipe, node_member_id, seed_member_ids, t_ms, r_ms)
45
86
  @sleep_time_seconds = sleep_time_seconds
@@ -55,7 +96,7 @@ module RSwim
55
96
  end
56
97
  end
57
98
 
58
- class FiberBased < Base
99
+ class FiberBased < PullBased
59
100
  def run
60
101
  @f = Fiber.new { super }
61
102
  end
@@ -0,0 +1,67 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSwim
4
+ module Integration
5
+ module UDP
6
+ class Sender
7
+ def initialize(port, out_q)
8
+ @out_q = out_q
9
+ @port = port
10
+ @out_s = UDPSocket.new
11
+ end
12
+
13
+ def run
14
+ Async do
15
+ loop do
16
+ wire_messages = @out_q.pop
17
+ wire_messages.each do |(host, wire_message)|
18
+ logger.debug "about to send message to #{host} on port #{@port}"
19
+ Fiber.schedule do
20
+ @out_s.send(wire_message, 0, host, @port)
21
+ rescue StandardError => e
22
+ logger.debug("Error while sending: #{e}")
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+
29
+ private
30
+
31
+ def logger
32
+ @_logger ||= RSwim::Logger.new(self.class, $stderr)
33
+ end
34
+ end
35
+
36
+ class IOLoop < RSwim::IOLoop
37
+ def initialize(agent, serializer, deserializer, directory, sleep_time_seconds, my_host, port)
38
+ super(agent, serializer, deserializer, directory, sleep_time_seconds)
39
+ @my_host = my_host
40
+ @port = port
41
+ end
42
+
43
+ protected
44
+
45
+ def before_run
46
+ @in_s = UDPSocket.new
47
+ @in_s.bind(@my_host, @port)
48
+ @out_q = Queue.new
49
+ Thread.new { Sender.new(@port, @out_q).run }.abort_on_exception = true
50
+ end
51
+
52
+ def read
53
+ text, sender = @in_s.recvfrom(10_000)
54
+ [sender[3], text]
55
+ rescue StandardError => e
56
+ logger.debug("Error while receiving: #{e}")
57
+ end
58
+
59
+ def send(wire_messages)
60
+ @out_q << wire_messages
61
+ rescue StandardError => e
62
+ logger.debug("Error while sending: #{e}")
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -1,56 +1,21 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'socket'
4
-
5
3
  module RSwim
6
4
  module Integration
7
- module Udp
5
+ module UDP
8
6
  # Node implementation that sends and listens using UDP
9
7
  class Node < RSwim::Node
10
8
  def initialize(my_host, seed_hosts, port, t_ms, r_ms)
9
+ @port = port
11
10
  my_host ||= Socket.ip_address_list.find(&:ipv4_private?).ip_address
12
11
  super(my_host, seed_hosts, t_ms, r_ms)
13
- @port = port
14
12
  end
15
13
 
16
14
  protected
17
15
 
18
- def before_start
19
- @in_s = UDPSocket.new
20
- @out_s = UDPSocket.new
21
- @in_s.bind(@my_host, @port)
22
- logger.info "node listening on UDP port #{@port}"
23
- Thread.new { receive }.abort_on_exception = true
24
- Thread.new { send }.abort_on_exception = true
25
- end
26
-
27
- private
28
-
29
- def send
30
- loop do
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}")
38
- end
39
- logger.info 'node no longer receiving'
40
- end
41
-
42
- def receive
43
- loop do
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}")
52
- end
53
- logger.info 'node no longer receiving'
16
+ def create_io_loop
17
+ raise 'bad setup' if @port.nil?
18
+ IOLoop.new(@agent, @serializer, @deserializer, @directory, @sleep_time_seconds, @my_host, @port)
54
19
  end
55
20
  end
56
21
  end
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RSwim
4
+ class IOLoop
5
+ def initialize(agent, serializer, deserializer, directory, sleep_time_seconds)
6
+ @agent = agent
7
+ @serializer = serializer
8
+ @deserializer = deserializer
9
+ @directory = directory
10
+ @sleep_time_seconds = sleep_time_seconds
11
+ @read_buffer = []
12
+ end
13
+
14
+ def run
15
+ before_run
16
+ Async do
17
+ start_producer
18
+ loop do
19
+ in_messages = consume_read_buffer
20
+ logger.debug "advancing agent with #{in_messages.size} messages"
21
+ out_messages = @agent.advance(in_messages)
22
+ wire_messages = out_messages.map do |message|
23
+ wire_message = @serializer.serialize(message)
24
+ host = @directory.host(message.to)
25
+ [host, wire_message]
26
+ end
27
+ logger.debug "sending #{wire_messages.size} messages from agent to other hosts"
28
+ send(wire_messages)
29
+ rescue StandardError => e
30
+ logger.debug("Error in I/O loop: #{e}")
31
+ end
32
+ logger.info 'node no longer receiving'
33
+ end
34
+ end
35
+
36
+ protected
37
+
38
+ def before_run; end
39
+
40
+ def read
41
+ raise 'implemented in subclass'
42
+ end
43
+
44
+ def send(_wire_messages)
45
+ raise 'implemented in subclass'
46
+ end
47
+
48
+ def logger
49
+ @_logger ||= RSwim::Logger.new(self.class, $stderr)
50
+ end
51
+
52
+ private
53
+
54
+ def start_producer
55
+ Fiber.schedule do
56
+ loop do
57
+ logger.debug 'about to recieve message'
58
+ sender_host, wire_message = read
59
+ continue if wire_message.nil?
60
+ logger.debug "Read #{wire_message} from host #{sender_host}"
61
+ message = @deserializer.deserialize(sender_host, wire_message)
62
+ unless message.nil?
63
+ logger.debug "Wire message deserialized to #{message}"
64
+ @read_buffer << message
65
+ end
66
+ end
67
+ end
68
+ end
69
+
70
+ def consume_read_buffer
71
+ if @read_buffer.empty?
72
+ logger.debug "sleeping for #{@sleep_time_seconds} seconds while waiting for buffered messages"
73
+ sleep @sleep_time_seconds
74
+ end
75
+ Array.new(@read_buffer.size) { @read_buffer.pop }
76
+ end
77
+ end
78
+ end
data/lib/rswim/node.rb CHANGED
@@ -1,22 +1,23 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'socket'
4
-
5
3
  module RSwim
6
4
  class Node
5
+ def self.udp(my_host, seed_hosts, port, t_ms = T_MS, r_ms = R_MS)
6
+ Integration::UDP::Node.new(my_host, seed_hosts, port, t_ms, r_ms)
7
+ end
8
+
7
9
  def initialize(my_host, seed_hosts, t_ms, r_ms)
8
10
  @my_host = my_host
9
11
  @directory = Directory.new
10
12
  @my_id = @directory.id(@my_host)
11
13
  @deserializer = Integration::Deserializer.new(@directory, @my_id)
12
14
  @serializer = Integration::Serializer.new(@directory)
13
- @pipe = RSwim::Pipe.simple
14
- seed_ids = seed_hosts.map { |host| @directory.id(host) }
15
- @agent = RSwim::Agent::SleepBased.new(@pipe, @my_id, seed_ids, t_ms, r_ms)
16
- end
17
-
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)
15
+ @seed_ids = seed_hosts.map { |host| @directory.id(host) }
16
+ @t_ms = t_ms
17
+ @r_ms = r_ms
18
+ @agent = RSwim::Agent::PushBased.new(@my_id, @seed_ids, t_ms, r_ms)
19
+ @sleep_time_seconds = r_ms / 1_000
20
+ @io_loop = create_io_loop
20
21
  end
21
22
 
22
23
  def subscribe(&block)
@@ -30,18 +31,17 @@ module RSwim
30
31
  @agent.append_custom_state(key, value)
31
32
  end
32
33
 
33
- # blocks until interrupted
34
34
  def start
35
35
  logger.info 'starting node'
36
- before_start
37
36
  @agent.run
37
+ @io_loop.run
38
38
  rescue StandardError => e
39
39
  logger.error("Node failed: #{e}")
40
40
  end
41
41
 
42
42
  protected
43
43
 
44
- def before_start
44
+ def create_io_loop
45
45
  raise 'must be implemented in subclass'
46
46
  end
47
47
 
data/lib/rswim/pipe.rb CHANGED
@@ -24,8 +24,7 @@ module RSwim
24
24
  attr_reader :q_in, :q_out
25
25
 
26
26
  def initialize
27
- @q_in, @q_out = 2.times.map { Queue.new }
28
- super(@q_in, @q_out)
27
+ super(Queue.new, Queue.new)
29
28
  end
30
29
  end
31
30
  end
data/lib/rswim/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RSwim
4
- VERSION = '2.0.0'
4
+ VERSION = '2.1.0'
5
5
  end
data/lib/rswim.rb CHANGED
@@ -1,8 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'logger'
4
+ require 'socket'
4
5
  require 'zeitwerk'
5
- require 'byebug'
6
+ require 'async'
6
7
 
7
8
  # frozen_string_literal: true
8
9
 
@@ -10,6 +11,8 @@ class MyInflector < Zeitwerk::Inflector
10
11
  def camelize(basename, _abspath)
11
12
  case basename
12
13
  when 'rswim' then 'RSwim'
14
+ when 'udp' then 'UDP'
15
+ when 'io_loop' then 'IOLoop'
13
16
  else super
14
17
  end
15
18
  end
@@ -29,5 +32,4 @@ module RSwim
29
32
  R_MS = 10_000
30
33
 
31
34
  class Error < StandardError; end
32
- # Your code goes here...
33
35
  end
data/rswim.gemspec CHANGED
@@ -30,7 +30,8 @@ Gem::Specification.new do |spec|
30
30
 
31
31
  spec.add_dependency 'zeitwerk', '~> 2.2'
32
32
  spec.add_dependency 'slop', '~> 4.9'
33
-
33
+ spec.add_dependency 'async', '~> 1.30'
34
+
34
35
  spec.add_development_dependency "bundler", ">= 2.2.10"
35
36
  spec.add_development_dependency "rake", "~> 12.0"
36
37
  spec.add_development_dependency "rspec", "~> 3.0"
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: 2.0.0
4
+ version: 2.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Erik Madsen
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-10-05 00:00:00.000000000 Z
11
+ date: 2021-10-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: zeitwerk
@@ -38,6 +38,20 @@ dependencies:
38
38
  - - "~>"
39
39
  - !ruby/object:Gem::Version
40
40
  version: '4.9'
41
+ - !ruby/object:Gem::Dependency
42
+ name: async
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.30'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.30'
41
55
  - !ruby/object:Gem::Dependency
42
56
  name: bundler
43
57
  requirement: !ruby/object:Gem::Requirement
@@ -142,6 +156,7 @@ files:
142
156
  - LICENSE.txt
143
157
  - README.md
144
158
  - Rakefile
159
+ - bin/async_loop
145
160
  - bin/console
146
161
  - bin/run_node
147
162
  - bin/setup
@@ -150,7 +165,9 @@ files:
150
165
  - lib/rswim/directory.rb
151
166
  - lib/rswim/integration/deserializer.rb
152
167
  - lib/rswim/integration/serializer.rb
168
+ - lib/rswim/integration/udp/io_loop.rb
153
169
  - lib/rswim/integration/udp/node.rb
170
+ - lib/rswim/io_loop.rb
154
171
  - lib/rswim/logger.rb
155
172
  - lib/rswim/member/ack_responder.rb
156
173
  - lib/rswim/member/base.rb