rswim 2.0.0 → 2.1.0

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