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 +4 -4
- data/CHANGELOG.md +1 -0
- data/Gemfile.lock +11 -1
- data/bin/async_loop +37 -0
- data/lib/rswim/agent.rb +44 -3
- data/lib/rswim/integration/udp/io_loop.rb +67 -0
- data/lib/rswim/integration/udp/node.rb +5 -40
- data/lib/rswim/io_loop.rb +78 -0
- data/lib/rswim/node.rb +12 -12
- data/lib/rswim/pipe.rb +1 -2
- data/lib/rswim/version.rb +1 -1
- data/lib/rswim.rb +4 -2
- data/rswim.gemspec +2 -1
- metadata +19 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2cdad1327bf5ba2e7fd3a2d975b7f0ad507d99952b9c2b2d01269f4f3b813f25
|
4
|
+
data.tar.gz: 2050c00ddb9eec045103645d269683ad0454d664d92026f6849f80428e1483ff
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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.
|
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
|
-
|
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 <
|
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 <
|
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
|
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
|
19
|
-
|
20
|
-
@
|
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
|
-
@
|
14
|
-
|
15
|
-
@
|
16
|
-
|
17
|
-
|
18
|
-
|
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
|
44
|
+
def create_io_loop
|
45
45
|
raise 'must be implemented in subclass'
|
46
46
|
end
|
47
47
|
|
data/lib/rswim/pipe.rb
CHANGED
data/lib/rswim/version.rb
CHANGED
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 '
|
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.
|
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-
|
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
|