backport 0.3.0 → 1.2.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: 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