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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9693d9fb93dc1efb03c85d3abe258cd54e2fa4bca03e8bc2f64a45a56783269a
4
- data.tar.gz: a7abdaa602ccbfda748cdace2af071af73651ecff42663ac237def501260f457
3
+ metadata.gz: bb7787742f0eda4aab85f3b7cd388f69ad1870a4298f4b6f4862577f198685f0
4
+ data.tar.gz: 00c7263e5c3dc511f3a13a12d2e5cf7335d9c2feab66b63430ae363975284b73
5
5
  SHA512:
6
- metadata.gz: bbc92f9838a571f7cb9ea71334961cf34013a41e1010147ec4f54d4c5efde68c37b4f7cb9f0d9d52ad8ebe23ac9fa83e24568d690044ed200d838a1cb2c50358
7
- data.tar.gz: a5cdf05eb09d40bc352fd07ed2d23b85107875551c9718e0145e7abdf92f670ff784e99b50b8daf5879c7a0cbe92dca1d8ca4b3a42ed73a482610085fd56e986
6
+ metadata.gz: cc68f707447563a776948af2e86cfd2d683659fe11ce81ee21d37536e7fc5818935ff9e763a4fcb4d4022460cf3088c14f830fa1faefb9577aff959a5a6e0c3a
7
+ data.tar.gz: 14be66a01490aaa143da303aac0f9f0c3061ea4dbad0e284f4e07a463fc5dd6b807b677b3f70f1ce8191312b3b12d1429bb8e87d883bf3ea61e9bc8fcd91eee9
data/.rubocop.yml CHANGED
@@ -2,3 +2,5 @@ Style/MethodDefParentheses:
2
2
  Enabled: false
3
3
  Layout/EmptyLineAfterGuardClause:
4
4
  Enabled: false
5
+ Style/StringLiterals:
6
+ Enabled: false
data/.travis.yml CHANGED
@@ -1,18 +1,21 @@
1
- ---
2
- sudo: false
3
- language: ruby
4
- cache: bundler
5
- rvm:
6
- - 2.1
7
- - 2.2
8
- - 2.3
9
- - 2.4
10
- - 2.5.1
11
- - 2.5.3
12
- - jruby-9.0.5.0
13
- - jruby-9.1.16.0
14
- matrix:
15
- include:
16
- - rvm: 2.4
17
- os: osx
18
- before_install: gem install bundler -v 1.17.2
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 sending 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
- * `sending(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
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.add_development_dependency "bundler", "~> 1.17"
24
- spec.add_development_dependency "rake", "~> 10.0"
25
- spec.add_development_dependency "rspec", "~> 3.0"
26
- spec.add_development_dependency "simplecov", "~> 0.14"
27
- end
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 "backport/version"
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
- machine.prepare Backport::Server::Stdio.new(adapter: adapter)
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
- machine.prepare Backport::Server::Tcpip.new(host: host, port: port, adapter: adapter)
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
- machine.prepare Backport::Server::Interval.new(period, &block)
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 the Backport machine.
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
- machine.stop
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 machine
69
- @machine ||= Machine.new
81
+ # @return [Array<Machine>]
82
+ def machines
83
+ @machines ||= []
70
84
  end
71
85
  end
72
86
  end
@@ -40,13 +40,13 @@ module Backport
40
40
  # @return [void]
41
41
  def closing; end
42
42
 
43
- # A callback triggered when the client sends data to the server. Subclasses
44
- # and/or modules should override this method to provide their own
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 sending(data); end
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
@@ -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
- # Notify the adapter that the client is sending data.
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
- # @param data [String]
47
- def sending data
48
- @adapter.sending data
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
- private
64
-
65
- def make_adapter cls_mod, remote
66
- if cls_mod.is_a?(Class) && cls_mod <= Backport::Adapter
67
- @adapter = cls_mod.new(@out, remote)
68
- elsif cls_mod.class == Module
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 cls_mod
91
+ @adapter.extend mod_cls
71
92
  else
72
- raise TypeError, "#{cls_mod} is not a valid Backport adapter"
93
+ raise TypeError, "#{mod_cls} is not a valid Backport adapter"
73
94
  end
74
95
  end
75
96
 
76
- def mutex
77
- @mutex ||= Mutex.new
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
- @in.flush
84
- begin
85
- chars = @in.sysread(255)
86
- rescue EOFError, Errno::ECONNRESET
87
- chars = nil
88
- end
89
- if chars.nil?
90
- stop
91
- break
92
- end
93
- mutex.synchronize { @buffer.concat chars }
94
- end
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
@@ -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
- def tick
54
- servers.delete_if(&:stopped?)
55
- stop if servers.empty?
56
- servers.each(&:tick)
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
@@ -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
- def tick
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
- @last_time = Time.now
12
+ @ready = false
13
+ @mutex = Mutex.new
13
14
  end
14
15
 
15
16
  def starting
16
- @last_time = Time.now
17
+ @ready = false
18
+ run_ready_thread
17
19
  end
18
20
 
19
21
  def tick
20
- now = Time.now
21
- return unless now - @last_time >= @period
22
- @block.call self
23
- @last_time = now
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 => e
41
- Backport.logger.info "Minor exception while stopping server [#{e.class}] #{e.message}"
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.accept_nonblock
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 => e
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 Exception => e
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
- def mutex
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?
@@ -1,3 +1,3 @@
1
1
  module Backport
2
- VERSION = '0.3.0'.freeze
2
+ VERSION = '1.2.0'.freeze
3
3
  end
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: 0.3.0
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: 2019-01-10 00:00:00.000000000 Z
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: '0'
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
- rubyforge_project:
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