backport 0.3.0 → 1.2.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/.rubocop.yml +2 -0
- data/.travis.yml +21 -18
- data/CHANGELOG.md +19 -0
- data/Gemfile +6 -6
- data/README.md +80 -80
- data/backport.gemspec +28 -27
- data/lib/backport.rb +23 -9
- data/lib/backport/adapter.rb +6 -4
- data/lib/backport/client.rb +58 -28
- data/lib/backport/machine.rb +20 -9
- data/lib/backport/server/base.rb +8 -0
- data/lib/backport/server/connectable.rb +9 -7
- data/lib/backport/server/interval.rb +24 -6
- data/lib/backport/server/stdio.rb +10 -0
- data/lib/backport/server/tcpip.rb +27 -23
- data/lib/backport/version.rb +1 -1
- metadata +4 -19
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: bb7787742f0eda4aab85f3b7cd388f69ad1870a4298f4b6f4862577f198685f0
|
4
|
+
data.tar.gz: 00c7263e5c3dc511f3a13a12d2e5cf7335d9c2feab66b63430ae363975284b73
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: cc68f707447563a776948af2e86cfd2d683659fe11ce81ee21d37536e7fc5818935ff9e763a4fcb4d4022460cf3088c14f830fa1faefb9577aff959a5a6e0c3a
|
7
|
+
data.tar.gz: 14be66a01490aaa143da303aac0f9f0c3061ea4dbad0e284f4e07a463fc5dd6b807b677b3f70f1ce8191312b3b12d1429bb8e87d883bf3ea61e9bc8fcd91eee9
|
data/.rubocop.yml
CHANGED
data/.travis.yml
CHANGED
@@ -1,18 +1,21 @@
|
|
1
|
-
---
|
2
|
-
sudo: false
|
3
|
-
language: ruby
|
4
|
-
|
5
|
-
|
6
|
-
- 2.
|
7
|
-
- 2.
|
8
|
-
- 2.
|
9
|
-
- 2.
|
10
|
-
- 2.
|
11
|
-
- 2.
|
12
|
-
- jruby-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
1
|
+
---
|
2
|
+
sudo: false
|
3
|
+
language: ruby
|
4
|
+
rvm:
|
5
|
+
- 2.1
|
6
|
+
- 2.2
|
7
|
+
- 2.3
|
8
|
+
- 2.4
|
9
|
+
- 2.5
|
10
|
+
- 2.6
|
11
|
+
- 2.7
|
12
|
+
- jruby-head
|
13
|
+
matrix:
|
14
|
+
include:
|
15
|
+
- rvm: 2.6
|
16
|
+
os: osx
|
17
|
+
allow_failures:
|
18
|
+
- rvm: jruby-head
|
19
|
+
|
20
|
+
before_install: gem install bundler -v 1.17.2
|
21
|
+
script: rspec --format documentation
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,22 @@
|
|
1
|
+
## 1.2.0 - June 13, 2021
|
2
|
+
- Improved handling of multiple machines
|
3
|
+
- Rescue StandardError instead of Exception
|
4
|
+
|
5
|
+
## 1.1.2 - August 4, 2019
|
6
|
+
- Rescue Errno::ENOTSOCK
|
7
|
+
|
8
|
+
## 1.1.1 - May 28, 2019
|
9
|
+
- Interval server synchronizes updates and ready state
|
10
|
+
- Spec and CI changes
|
11
|
+
|
12
|
+
## 1.1.0 - May 27, 2019
|
13
|
+
- Interval server uses threads for timing
|
14
|
+
- Servers use observer patterns to reduce polling
|
15
|
+
|
16
|
+
## 1.0.0 - February 19, 2019
|
17
|
+
- Renamed Adapter#sending to Adapter#receiving
|
18
|
+
- Travis tests up to Ruby 2.6
|
19
|
+
|
1
20
|
## 0.3.0 - January 10, 2019
|
2
21
|
- Basic logging
|
3
22
|
- Differentiate between "expected" and "unexpected" exceptions in Tcpip
|
data/Gemfile
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
source "https://rubygems.org"
|
2
|
-
|
3
|
-
git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
|
4
|
-
|
5
|
-
# Specify your gem's dependencies in backport.gemspec
|
6
|
-
gemspec
|
1
|
+
source "https://rubygems.org"
|
2
|
+
|
3
|
+
git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
|
4
|
+
|
5
|
+
# Specify your gem's dependencies in backport.gemspec
|
6
|
+
gemspec
|
data/README.md
CHANGED
@@ -1,80 +1,80 @@
|
|
1
|
-
# Backport
|
2
|
-
|
3
|
-
A pure Ruby library for event-driven IO.
|
4
|
-
|
5
|
-
This library is designed with portability as the highest priority, which is why it's written in pure Ruby. Consider [EventMachine](https://github.com/eventmachine/eventmachine) if you need a solution that's faster, more mature, and scalable.
|
6
|
-
|
7
|
-
## Installation
|
8
|
-
|
9
|
-
Install the gem:
|
10
|
-
|
11
|
-
```
|
12
|
-
gem install backport
|
13
|
-
```
|
14
|
-
|
15
|
-
Or add it to your application's Gemfile:
|
16
|
-
|
17
|
-
```ruby
|
18
|
-
gem 'backport'
|
19
|
-
```
|
20
|
-
|
21
|
-
## Usage
|
22
|
-
|
23
|
-
### Examples
|
24
|
-
|
25
|
-
A simple echo server:
|
26
|
-
|
27
|
-
```ruby
|
28
|
-
require 'backport'
|
29
|
-
|
30
|
-
module MyAdapter
|
31
|
-
def opening
|
32
|
-
puts "Opening a connection"
|
33
|
-
end
|
34
|
-
|
35
|
-
def closing
|
36
|
-
puts "Closing a connection"
|
37
|
-
end
|
38
|
-
|
39
|
-
def
|
40
|
-
write "Client sent: #{data}"
|
41
|
-
end
|
42
|
-
end
|
43
|
-
|
44
|
-
Backport.run do
|
45
|
-
Backport.prepare_tcp_server(host: 'localhost', port: 8000, adapter: MyAdapter)
|
46
|
-
end
|
47
|
-
```
|
48
|
-
|
49
|
-
An interval server that runs once per second:
|
50
|
-
|
51
|
-
```ruby
|
52
|
-
require 'backport'
|
53
|
-
|
54
|
-
Backport.run do
|
55
|
-
Backport.prepare_interval 1 do
|
56
|
-
puts "tick"
|
57
|
-
end
|
58
|
-
end
|
59
|
-
```
|
60
|
-
|
61
|
-
### Using Adapters
|
62
|
-
|
63
|
-
Backport servers that handle client connections, such as TCP servers, use an
|
64
|
-
adapter to provide an application interface to the client. Developers can
|
65
|
-
provide their own adapter implementations in two ways: a Ruby module that will
|
66
|
-
be used to extend a Backport::Adapter object, or a class that extends
|
67
|
-
Backport::Adapter. In either case, the adapter should provide the following
|
68
|
-
methods:
|
69
|
-
|
70
|
-
* `opening`: A callback triggered when the client connection is accepted
|
71
|
-
* `closing`: A callback triggered when the client connection is closed
|
72
|
-
* `
|
73
|
-
|
74
|
-
Backport::Adapter also provides the following methods:
|
75
|
-
|
76
|
-
* `write(data)`: Send raw data to the client
|
77
|
-
* `write_line(data)`: Send a line of data to the client
|
78
|
-
* `close`: Disconnect the client from the server
|
79
|
-
* `closed?`: True if the connection is closed
|
80
|
-
* `remote`: A hash of data about the client, e.g., the remote IP address
|
1
|
+
# Backport
|
2
|
+
|
3
|
+
A pure Ruby library for event-driven IO.
|
4
|
+
|
5
|
+
This library is designed with portability as the highest priority, which is why it's written in pure Ruby. Consider [EventMachine](https://github.com/eventmachine/eventmachine) if you need a solution that's faster, more mature, and scalable.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Install the gem:
|
10
|
+
|
11
|
+
```
|
12
|
+
gem install backport
|
13
|
+
```
|
14
|
+
|
15
|
+
Or add it to your application's Gemfile:
|
16
|
+
|
17
|
+
```ruby
|
18
|
+
gem 'backport'
|
19
|
+
```
|
20
|
+
|
21
|
+
## Usage
|
22
|
+
|
23
|
+
### Examples
|
24
|
+
|
25
|
+
A simple echo server:
|
26
|
+
|
27
|
+
```ruby
|
28
|
+
require 'backport'
|
29
|
+
|
30
|
+
module MyAdapter
|
31
|
+
def opening
|
32
|
+
puts "Opening a connection"
|
33
|
+
end
|
34
|
+
|
35
|
+
def closing
|
36
|
+
puts "Closing a connection"
|
37
|
+
end
|
38
|
+
|
39
|
+
def receiving data
|
40
|
+
write "Client sent: #{data}"
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
Backport.run do
|
45
|
+
Backport.prepare_tcp_server(host: 'localhost', port: 8000, adapter: MyAdapter)
|
46
|
+
end
|
47
|
+
```
|
48
|
+
|
49
|
+
An interval server that runs once per second:
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
require 'backport'
|
53
|
+
|
54
|
+
Backport.run do
|
55
|
+
Backport.prepare_interval 1 do
|
56
|
+
puts "tick"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
```
|
60
|
+
|
61
|
+
### Using Adapters
|
62
|
+
|
63
|
+
Backport servers that handle client connections, such as TCP servers, use an
|
64
|
+
adapter to provide an application interface to the client. Developers can
|
65
|
+
provide their own adapter implementations in two ways: a Ruby module that will
|
66
|
+
be used to extend a Backport::Adapter object, or a class that extends
|
67
|
+
Backport::Adapter. In either case, the adapter should provide the following
|
68
|
+
methods:
|
69
|
+
|
70
|
+
* `opening`: A callback triggered when the client connection is accepted
|
71
|
+
* `closing`: A callback triggered when the client connection is closed
|
72
|
+
* `receiving(data)`: A callback triggered when the server receives data from the client
|
73
|
+
|
74
|
+
Backport::Adapter also provides the following methods:
|
75
|
+
|
76
|
+
* `write(data)`: Send raw data to the client
|
77
|
+
* `write_line(data)`: Send a line of data to the client
|
78
|
+
* `close`: Disconnect the client from the server
|
79
|
+
* `closed?`: True if the connection is closed
|
80
|
+
* `remote`: A hash of data about the client, e.g., the remote IP address
|
data/backport.gemspec
CHANGED
@@ -1,27 +1,28 @@
|
|
1
|
-
|
2
|
-
lib = File.expand_path("../lib", __FILE__)
|
3
|
-
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
-
require "backport/version"
|
5
|
-
|
6
|
-
Gem::Specification.new do |spec|
|
7
|
-
spec.name = "backport"
|
8
|
-
spec.version = Backport::VERSION
|
9
|
-
spec.authors = ["Fred Snyder"]
|
10
|
-
spec.email = ["fsnyder@castwide.com"]
|
11
|
-
|
12
|
-
spec.summary = %q{A pure Ruby library for event-driven IO}
|
13
|
-
spec.homepage = "http://github.com/castwide/backport"
|
14
|
-
spec.license = "MIT"
|
15
|
-
|
16
|
-
# Specify which files should be added to the gem when it is released.
|
17
|
-
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
18
|
-
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
19
|
-
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
20
|
-
end
|
21
|
-
spec.require_paths = ["lib"]
|
22
|
-
|
23
|
-
spec.
|
24
|
-
|
25
|
-
spec.add_development_dependency "
|
26
|
-
spec.add_development_dependency "
|
27
|
-
|
1
|
+
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require "backport/version"
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "backport"
|
8
|
+
spec.version = Backport::VERSION
|
9
|
+
spec.authors = ["Fred Snyder"]
|
10
|
+
spec.email = ["fsnyder@castwide.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{A pure Ruby library for event-driven IO}
|
13
|
+
spec.homepage = "http://github.com/castwide/backport"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
# Specify which files should be added to the gem when it is released.
|
17
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
18
|
+
spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
|
19
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
20
|
+
end
|
21
|
+
spec.require_paths = ["lib"]
|
22
|
+
|
23
|
+
spec.required_ruby_version = '>= 2.1'
|
24
|
+
|
25
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
26
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
27
|
+
spec.add_development_dependency "simplecov", "~> 0.14"
|
28
|
+
end
|
data/lib/backport.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
require
|
1
|
+
require 'backport/version'
|
2
2
|
require 'logger'
|
3
3
|
|
4
4
|
# An event-driven IO library.
|
@@ -15,7 +15,7 @@ module Backport
|
|
15
15
|
# @param adapter [Adapter]
|
16
16
|
# @return [void]
|
17
17
|
def prepare_stdio_server adapter: Adapter
|
18
|
-
|
18
|
+
machines.last.prepare Backport::Server::Stdio.new(adapter: adapter)
|
19
19
|
end
|
20
20
|
|
21
21
|
# Prepare a TCP server to run in Backport.
|
@@ -25,7 +25,7 @@ module Backport
|
|
25
25
|
# @param adapter [Adapter]
|
26
26
|
# @return [void]
|
27
27
|
def prepare_tcp_server host: 'localhost', port: 1117, adapter: Adapter
|
28
|
-
|
28
|
+
machines.last.prepare Backport::Server::Tcpip.new(host: host, port: port, adapter: adapter)
|
29
29
|
end
|
30
30
|
|
31
31
|
# Prepare an interval server to run in Backport.
|
@@ -33,7 +33,7 @@ module Backport
|
|
33
33
|
# @param period [Float] Seconds between intervals
|
34
34
|
# @return [void]
|
35
35
|
def prepare_interval period, &block
|
36
|
-
|
36
|
+
machines.last.prepare Backport::Server::Interval.new(period, &block)
|
37
37
|
end
|
38
38
|
|
39
39
|
# Run the Backport machine. The provided block will be executed before the
|
@@ -48,25 +48,39 @@ module Backport
|
|
48
48
|
#
|
49
49
|
# @return [void]
|
50
50
|
def run &block
|
51
|
+
machine = Machine.new
|
52
|
+
machines.push machine
|
51
53
|
machine.run &block
|
54
|
+
machines.delete machine
|
52
55
|
end
|
53
56
|
|
54
|
-
# Stop
|
57
|
+
# Stop all running Backport machines.
|
58
|
+
#
|
59
|
+
# For more accurate control, consider stopping the machine
|
60
|
+
# from the self reference in Machine#run, e.g.:
|
61
|
+
#
|
62
|
+
# ```
|
63
|
+
# Backport.run do |machine|
|
64
|
+
# # ...
|
65
|
+
# machine.stop
|
66
|
+
# end
|
67
|
+
# ```
|
55
68
|
#
|
56
69
|
# @return [void]
|
57
70
|
def stop
|
58
|
-
|
71
|
+
machines.last.stop unless machines.empty?
|
59
72
|
end
|
60
73
|
|
74
|
+
# @return [Logger]
|
61
75
|
def logger
|
62
76
|
@logger ||= Logger.new(STDERR, level: Logger::WARN, progname: 'Backport')
|
63
77
|
end
|
64
78
|
|
65
79
|
private
|
66
80
|
|
67
|
-
# @return [Machine]
|
68
|
-
def
|
69
|
-
@
|
81
|
+
# @return [Array<Machine>]
|
82
|
+
def machines
|
83
|
+
@machines ||= []
|
70
84
|
end
|
71
85
|
end
|
72
86
|
end
|
data/lib/backport/adapter.rb
CHANGED
@@ -40,13 +40,13 @@ module Backport
|
|
40
40
|
# @return [void]
|
41
41
|
def closing; end
|
42
42
|
|
43
|
-
# A callback triggered when the
|
44
|
-
# and/or modules should override this method to provide their
|
45
|
-
# functionality.
|
43
|
+
# A callback triggered when the server receives data from the client.
|
44
|
+
# Subclasses and/or modules should override this method to provide their
|
45
|
+
# own functionality.
|
46
46
|
#
|
47
47
|
# @param data [String]
|
48
48
|
# @return [void]
|
49
|
-
def
|
49
|
+
def receiving(data); end
|
50
50
|
|
51
51
|
# Send data to the client.
|
52
52
|
#
|
@@ -76,9 +76,11 @@ module Backport
|
|
76
76
|
# The server is responsible for implementation details like closing the
|
77
77
|
# client's socket.
|
78
78
|
#
|
79
|
+
# @return [void]
|
79
80
|
def close
|
80
81
|
return if closed?
|
81
82
|
_data[:closed] = true
|
83
|
+
_data[:on_close].call unless _data[:on_close].nil?
|
82
84
|
closing
|
83
85
|
end
|
84
86
|
end
|
data/lib/backport/client.rb
CHANGED
@@ -1,18 +1,29 @@
|
|
1
|
+
require 'observer'
|
2
|
+
|
1
3
|
module Backport
|
2
4
|
# A client connected to a connectable Backport server.
|
3
5
|
#
|
4
6
|
class Client
|
7
|
+
include Observable
|
8
|
+
|
5
9
|
# @return [Adapter]
|
6
10
|
attr_reader :adapter
|
7
11
|
|
12
|
+
# @param input [IO]
|
13
|
+
# @param output [IO]
|
14
|
+
# @param adapter [Class, Module]
|
15
|
+
# @param remote [Hash]
|
8
16
|
def initialize input, output, adapter, remote = {}
|
9
17
|
@in = input
|
10
18
|
@out = output
|
19
|
+
@mutex = Mutex.new
|
11
20
|
@adapter = make_adapter(adapter, remote)
|
12
21
|
@stopped = true
|
13
22
|
@buffer = ''
|
14
23
|
end
|
15
24
|
|
25
|
+
# True if the client is stopped.
|
26
|
+
#
|
16
27
|
def stopped?
|
17
28
|
@stopped ||= false
|
18
29
|
end
|
@@ -23,15 +34,19 @@ module Backport
|
|
23
34
|
# callback. The server is responsible for implementation details like
|
24
35
|
# closing the client's socket.
|
25
36
|
#
|
37
|
+
# @return [void]
|
26
38
|
def stop
|
27
39
|
return if stopped?
|
28
40
|
@adapter.closing
|
29
41
|
@stopped = true
|
42
|
+
changed
|
43
|
+
notify_observers self
|
30
44
|
end
|
31
45
|
|
32
46
|
# Start running the client. This method will start the thread that reads
|
33
47
|
# client input from IO.
|
34
48
|
#
|
49
|
+
# @return [void]
|
35
50
|
def start
|
36
51
|
return unless stopped?
|
37
52
|
@stopped = false
|
@@ -41,13 +56,18 @@ module Backport
|
|
41
56
|
# @deprecated Prefer #start to #run for non-blocking client/server methods
|
42
57
|
alias run start
|
43
58
|
|
44
|
-
#
|
59
|
+
# Handle a tick from the server. This method will check for client input
|
60
|
+
# and update the adapter accordingly, or stop the client if the adapter is
|
61
|
+
# closed.
|
45
62
|
#
|
46
|
-
# @
|
47
|
-
def
|
48
|
-
|
63
|
+
# @return [void]
|
64
|
+
def tick
|
65
|
+
input = read
|
66
|
+
@adapter.receiving input unless input.nil?
|
49
67
|
end
|
50
68
|
|
69
|
+
private
|
70
|
+
|
51
71
|
# Read the client input. Return nil if the input buffer is empty.
|
52
72
|
#
|
53
73
|
# @return [String, nil]
|
@@ -60,38 +80,48 @@ module Backport
|
|
60
80
|
return tmp unless tmp.empty?
|
61
81
|
end
|
62
82
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
83
|
+
# @param mod_cls [Module, Class] The Adapter module or class
|
84
|
+
# @param remote [Hash] Remote client data
|
85
|
+
# @return [Adapter]
|
86
|
+
def make_adapter mod_cls, remote
|
87
|
+
if mod_cls.is_a?(Class) && mod_cls <= Backport::Adapter
|
88
|
+
@adapter = mod_cls.new(@out, remote)
|
89
|
+
elsif mod_cls.class == Module
|
69
90
|
@adapter = Adapter.new(@out, remote)
|
70
|
-
@adapter.extend
|
91
|
+
@adapter.extend mod_cls
|
71
92
|
else
|
72
|
-
raise TypeError, "#{
|
93
|
+
raise TypeError, "#{mod_cls} is not a valid Backport adapter"
|
73
94
|
end
|
74
95
|
end
|
75
96
|
|
76
|
-
|
77
|
-
|
78
|
-
end
|
97
|
+
# @return [Mutex]
|
98
|
+
attr_reader :mutex
|
79
99
|
|
100
|
+
# Start the thread that checks the input IO for client data.
|
101
|
+
#
|
102
|
+
# @return [void]
|
80
103
|
def run_input_thread
|
81
104
|
Thread.new do
|
82
|
-
until stopped?
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
105
|
+
read_input until stopped?
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
# Read input from the client.
|
110
|
+
#
|
111
|
+
# @return [void]
|
112
|
+
def read_input
|
113
|
+
begin
|
114
|
+
@in.flush
|
115
|
+
chars = @in.sysread(255)
|
116
|
+
rescue EOFError, IOError, Errno::ECONNRESET, Errno::ENOTSOCK
|
117
|
+
chars = nil
|
118
|
+
end
|
119
|
+
if chars.nil?
|
120
|
+
stop
|
121
|
+
else
|
122
|
+
mutex.synchronize { @buffer.concat chars }
|
123
|
+
changed
|
124
|
+
notify_observers self
|
95
125
|
end
|
96
126
|
end
|
97
127
|
end
|
data/lib/backport/machine.rb
CHANGED
@@ -4,18 +4,20 @@ module Backport
|
|
4
4
|
class Machine
|
5
5
|
def initialize
|
6
6
|
@stopped = true
|
7
|
+
@mutex = Mutex.new
|
7
8
|
end
|
8
9
|
|
9
10
|
# Run the machine. If a block is provided, it gets executed before the
|
10
11
|
# maching starts its main loop. The main loop blocks program execution
|
11
12
|
# until the machine is stopped.
|
12
13
|
#
|
14
|
+
# @yieldparam [self]
|
13
15
|
# @return [void]
|
14
16
|
def run
|
15
17
|
return unless stopped?
|
16
18
|
servers.clear
|
17
19
|
@stopped = false
|
18
|
-
yield if block_given?
|
20
|
+
yield self if block_given?
|
19
21
|
run_server_thread
|
20
22
|
end
|
21
23
|
|
@@ -41,6 +43,7 @@ module Backport
|
|
41
43
|
# @param server [Server::Base]
|
42
44
|
# @return [void]
|
43
45
|
def prepare server
|
46
|
+
server.add_observer self
|
44
47
|
servers.push server
|
45
48
|
server.start unless stopped?
|
46
49
|
end
|
@@ -50,20 +53,28 @@ module Backport
|
|
50
53
|
@servers ||= []
|
51
54
|
end
|
52
55
|
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
56
|
+
# @param server [Server::Base]
|
57
|
+
# @return [void]
|
58
|
+
def update server
|
59
|
+
if server.stopped?
|
60
|
+
servers.delete server
|
61
|
+
stop if servers.empty?
|
62
|
+
else
|
63
|
+
mutex.synchronize { server.tick }
|
64
|
+
end
|
57
65
|
end
|
58
66
|
|
59
67
|
private
|
60
68
|
|
69
|
+
# @return [Mutex]
|
70
|
+
attr_reader :mutex
|
71
|
+
|
72
|
+
# Start the thread that updates servers via the #tick method.
|
73
|
+
#
|
74
|
+
# @return [void]
|
61
75
|
def run_server_thread
|
62
76
|
servers.map(&:start)
|
63
|
-
until stopped?
|
64
|
-
tick
|
65
|
-
sleep 0.001
|
66
|
-
end
|
77
|
+
sleep 0.1 until stopped?
|
67
78
|
end
|
68
79
|
end
|
69
80
|
end
|
data/lib/backport/server/base.rb
CHANGED
@@ -1,11 +1,16 @@
|
|
1
|
+
require 'observer'
|
2
|
+
|
1
3
|
module Backport
|
2
4
|
module Server
|
3
5
|
# An extendable server class that provides basic start/stop functionality
|
4
6
|
# and common callbacks.
|
5
7
|
#
|
6
8
|
class Base
|
9
|
+
include Observable
|
10
|
+
|
7
11
|
# Start the server.
|
8
12
|
#
|
13
|
+
# @return [void]
|
9
14
|
def start
|
10
15
|
return if started?
|
11
16
|
starting
|
@@ -14,10 +19,13 @@ module Backport
|
|
14
19
|
|
15
20
|
# Stop the server.
|
16
21
|
#
|
22
|
+
# @return [void]
|
17
23
|
def stop
|
18
24
|
return if stopped?
|
19
25
|
stopping
|
20
26
|
@started = false
|
27
|
+
changed
|
28
|
+
notify_observers self
|
21
29
|
end
|
22
30
|
|
23
31
|
def started?
|
@@ -5,17 +5,12 @@ module Backport
|
|
5
5
|
# Connectable servers check clients for incoming data on each tick.
|
6
6
|
#
|
7
7
|
module Connectable
|
8
|
-
|
9
|
-
clients.each do |client|
|
10
|
-
input = client.read
|
11
|
-
client.sending input unless input.nil?
|
12
|
-
end
|
13
|
-
end
|
14
|
-
|
8
|
+
# @return [void]
|
15
9
|
def starting
|
16
10
|
clients.map(&:run)
|
17
11
|
end
|
18
12
|
|
13
|
+
# @return [void]
|
19
14
|
def stopping
|
20
15
|
clients.map(&:stop)
|
21
16
|
end
|
@@ -24,6 +19,13 @@ module Backport
|
|
24
19
|
def clients
|
25
20
|
@clients ||= []
|
26
21
|
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
# @return [Mutex]
|
26
|
+
def mutex
|
27
|
+
@mutex ||= Mutex.new
|
28
|
+
end
|
27
29
|
end
|
28
30
|
end
|
29
31
|
end
|
@@ -9,18 +9,36 @@ module Backport
|
|
9
9
|
def initialize period, &block
|
10
10
|
@period = period
|
11
11
|
@block = block
|
12
|
-
@
|
12
|
+
@ready = false
|
13
|
+
@mutex = Mutex.new
|
13
14
|
end
|
14
15
|
|
15
16
|
def starting
|
16
|
-
@
|
17
|
+
@ready = false
|
18
|
+
run_ready_thread
|
17
19
|
end
|
18
20
|
|
19
21
|
def tick
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
22
|
+
return unless @ready
|
23
|
+
@mutex.synchronize do
|
24
|
+
@block.call self
|
25
|
+
@ready = false
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
# @return [void]
|
32
|
+
def run_ready_thread
|
33
|
+
Thread.new do
|
34
|
+
until stopped?
|
35
|
+
sleep @period
|
36
|
+
break if stopped?
|
37
|
+
@mutex.synchronize { @ready = true }
|
38
|
+
changed
|
39
|
+
notify_observers self
|
40
|
+
end
|
41
|
+
end
|
24
42
|
end
|
25
43
|
end
|
26
44
|
end
|
@@ -5,12 +5,22 @@ module Backport
|
|
5
5
|
class Stdio < Base
|
6
6
|
include Connectable
|
7
7
|
|
8
|
+
# @param input [IO]
|
9
|
+
# @param output [IO]
|
10
|
+
# @param adapter [Module, Class]
|
8
11
|
def initialize input: STDIN, output: STDOUT, adapter: Adapter
|
9
12
|
@in = input
|
10
13
|
@out = output
|
11
14
|
@out.binmode
|
12
15
|
@adapter = adapter
|
13
16
|
clients.push Client.new(input, output, adapter)
|
17
|
+
clients.last.add_observer self
|
18
|
+
end
|
19
|
+
|
20
|
+
# @param client [Client]
|
21
|
+
# @return [void]
|
22
|
+
def update client
|
23
|
+
client.tick
|
14
24
|
end
|
15
25
|
end
|
16
26
|
end
|
@@ -8,26 +8,16 @@ module Backport
|
|
8
8
|
class Tcpip < Base
|
9
9
|
include Connectable
|
10
10
|
|
11
|
+
# @param host [String]
|
12
|
+
# @param port [Integer]
|
13
|
+
# @param adapter [Module, Class]
|
14
|
+
# @param socket_class [Class]
|
11
15
|
def initialize host: 'localhost', port: 1117, adapter: Adapter, socket_class: TCPServer
|
12
16
|
@socket = socket_class.new(host, port)
|
13
17
|
@adapter = adapter
|
14
18
|
@stopped = false
|
15
19
|
end
|
16
20
|
|
17
|
-
def tick
|
18
|
-
mutex.synchronize do
|
19
|
-
clients.each do |client|
|
20
|
-
if client.adapter.closed?
|
21
|
-
client.stop
|
22
|
-
next
|
23
|
-
end
|
24
|
-
input = client.read
|
25
|
-
client.sending input unless input.nil?
|
26
|
-
end
|
27
|
-
clients.delete_if(&:stopped?)
|
28
|
-
end
|
29
|
-
end
|
30
|
-
|
31
21
|
def starting
|
32
22
|
start_accept_thread
|
33
23
|
end
|
@@ -37,8 +27,8 @@ module Backport
|
|
37
27
|
return if socket.closed?
|
38
28
|
begin
|
39
29
|
socket.shutdown Socket::SHUT_RDWR
|
40
|
-
rescue Errno::ENOTCONN, IOError =>
|
41
|
-
Backport.logger.info "Minor exception while stopping server [#{
|
30
|
+
rescue Errno::ENOTCONN, IOError => err
|
31
|
+
Backport.logger.info "Minor exception while stopping server [#{err.class}] #{err.message}"
|
42
32
|
end
|
43
33
|
socket.close
|
44
34
|
end
|
@@ -52,7 +42,7 @@ module Backport
|
|
52
42
|
result = nil
|
53
43
|
mutex.synchronize do
|
54
44
|
begin
|
55
|
-
conn = socket.
|
45
|
+
conn = socket.accept
|
56
46
|
addr = conn.addr(true)
|
57
47
|
data = {
|
58
48
|
family: addr[0],
|
@@ -61,14 +51,21 @@ module Backport
|
|
61
51
|
address: addr[3]
|
62
52
|
}
|
63
53
|
clients.push Client.new(conn, conn, @adapter, data)
|
54
|
+
this = self
|
55
|
+
clients.last.adapter._data[:on_close] = Proc.new {
|
56
|
+
conn.close
|
57
|
+
changed
|
58
|
+
notify_observers this
|
59
|
+
}
|
60
|
+
clients.last.add_observer self
|
64
61
|
clients.last.run
|
65
62
|
result = clients.last
|
66
|
-
rescue IO::WaitReadable, Errno::EAGAIN
|
63
|
+
rescue IO::WaitReadable, Errno::EAGAIN
|
67
64
|
# ignore
|
68
65
|
rescue Errno::ENOTSOCK, IOError => e
|
69
66
|
Backport.logger.info "Server stopped with minor exception [#{e.class}] #{e.message}"
|
70
67
|
stop
|
71
|
-
rescue
|
68
|
+
rescue StandardError => e
|
72
69
|
Backport.logger.warn "Server stopped with major exception [#{e.class}] #{e.message}"
|
73
70
|
stop
|
74
71
|
end
|
@@ -76,15 +73,22 @@ module Backport
|
|
76
73
|
result
|
77
74
|
end
|
78
75
|
|
76
|
+
# @param client [Client]
|
77
|
+
# @return [void]
|
78
|
+
def update client
|
79
|
+
if client.stopped?
|
80
|
+
clients.delete client
|
81
|
+
else
|
82
|
+
client.tick
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
79
86
|
private
|
80
87
|
|
81
88
|
# @return [TCPSocket]
|
82
89
|
attr_reader :socket
|
83
90
|
|
84
|
-
|
85
|
-
@mutex ||= Mutex.new
|
86
|
-
end
|
87
|
-
|
91
|
+
# @return [void]
|
88
92
|
def start_accept_thread
|
89
93
|
Thread.new do
|
90
94
|
until stopped?
|
data/lib/backport/version.rb
CHANGED
metadata
CHANGED
@@ -1,29 +1,15 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: backport
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 1.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Fred Snyder
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-06-13 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
-
- !ruby/object:Gem::Dependency
|
14
|
-
name: bundler
|
15
|
-
requirement: !ruby/object:Gem::Requirement
|
16
|
-
requirements:
|
17
|
-
- - "~>"
|
18
|
-
- !ruby/object:Gem::Version
|
19
|
-
version: '1.17'
|
20
|
-
type: :development
|
21
|
-
prerelease: false
|
22
|
-
version_requirements: !ruby/object:Gem::Requirement
|
23
|
-
requirements:
|
24
|
-
- - "~>"
|
25
|
-
- !ruby/object:Gem::Version
|
26
|
-
version: '1.17'
|
27
13
|
- !ruby/object:Gem::Dependency
|
28
14
|
name: rake
|
29
15
|
requirement: !ruby/object:Gem::Requirement
|
@@ -108,15 +94,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
108
94
|
requirements:
|
109
95
|
- - ">="
|
110
96
|
- !ruby/object:Gem::Version
|
111
|
-
version: '
|
97
|
+
version: '2.1'
|
112
98
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
113
99
|
requirements:
|
114
100
|
- - ">="
|
115
101
|
- !ruby/object:Gem::Version
|
116
102
|
version: '0'
|
117
103
|
requirements: []
|
118
|
-
|
119
|
-
rubygems_version: 2.7.6
|
104
|
+
rubygems_version: 3.1.2
|
120
105
|
signing_key:
|
121
106
|
specification_version: 4
|
122
107
|
summary: A pure Ruby library for event-driven IO
|