dat-tcp 0.7.0 → 0.8.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.
data/Gemfile CHANGED
@@ -2,5 +2,5 @@ source "https://rubygems.org"
2
2
 
3
3
  gemspec
4
4
 
5
- gem 'rake'
6
- gem 'pry', "~> 0.9.0"
5
+ gem 'rake', "~> 10.4.0"
6
+ gem 'pry', "~> 0.9.0"
data/dat-tcp.gemspec CHANGED
@@ -19,7 +19,8 @@ Gem::Specification.new do |gem|
19
19
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
20
20
  gem.require_paths = ["lib"]
21
21
 
22
- gem.add_dependency("dat-worker-pool", ["~> 0.5"])
22
+ gem.add_dependency("dat-worker-pool", ["~> 0.6"])
23
23
 
24
- gem.add_development_dependency('assert', ['~> 2.15'])
24
+ gem.add_development_dependency("assert", ["~> 2.15"])
25
+ gem.add_development_dependency("scmd", ["~> 3.0"])
25
26
  end
data/lib/dat-tcp.rb CHANGED
@@ -3,38 +3,44 @@ require 'socket'
3
3
  require 'thread'
4
4
 
5
5
  require 'dat-tcp/version'
6
- require 'dat-tcp/logger'
6
+ require 'dat-tcp/worker'
7
7
 
8
8
  module DatTCP
9
9
 
10
10
  class Server
11
11
 
12
- attr_reader :worker_start_procs, :worker_shutdown_procs
13
- attr_reader :worker_sleep_procs, :worker_wakeup_procs
14
- attr_reader :logger
15
- private :logger
16
-
17
- def initialize(config = nil, &serve_proc)
18
- config ||= {}
19
- @backlog_size = config[:backlog_size] || 1024
20
- @debug = config[:debug] || false
21
- @min_workers = config[:min_workers] || 2
22
- @max_workers = config[:max_workers] || 4
23
- @shutdown_timeout = config[:shutdown_timeout] || 15
12
+ DEFAULT_BACKLOG_SIZE = 1024
13
+ DEFAULT_SHUTDOWN_TIMEOUT = 15
14
+ DEFAULT_NUM_WORKERS = 2
15
+
16
+ SIGNAL = '.'.freeze
17
+
18
+ def initialize(worker_class, options = nil)
19
+ if !worker_class.kind_of?(Class) || !worker_class.include?(DatTCP::Worker)
20
+ raise ArgumentError, "worker class must include `#{DatTCP::Worker}`"
21
+ end
22
+
23
+ options ||= {}
24
+ @backlog_size = options[:backlog_size] || DEFAULT_BACKLOG_SIZE
25
+ @shutdown_timeout = options[:shutdown_timeout] || DEFAULT_SHUTDOWN_TIMEOUT
26
+
24
27
  @signal_reader, @signal_writer = IO.pipe
25
- @serve_proc = serve_proc || raise(ArgumentError, "no block given")
26
28
 
27
- @worker_start_procs = []
28
- @worker_shutdown_procs = []
29
- @worker_sleep_procs = []
30
- @worker_wakeup_procs = []
29
+ @logger_proxy = if options[:logger]
30
+ LoggerProxy.new(options[:logger])
31
+ else
32
+ NullLoggerProxy.new
33
+ end
31
34
 
32
- @logger = DatTCP::Logger.new(@debug)
35
+ @worker_pool = DatWorkerPool.new(worker_class, {
36
+ :num_workers => (options[:num_workers] || DEFAULT_NUM_WORKERS),
37
+ :logger => options[:logger],
38
+ :worker_params => options[:worker_params]
39
+ })
33
40
 
34
- @tcp_server = nil
35
- @work_loop_thread = nil
36
- @worker_pool = nil
37
- @signal = Signal.new(:stop)
41
+ @tcp_server = nil
42
+ @thread = nil
43
+ @state = State.new(:stop)
38
44
  end
39
45
 
40
46
  def ip
@@ -50,7 +56,7 @@ module DatTCP
50
56
  end
51
57
 
52
58
  def client_file_descriptors
53
- @worker_pool ? @worker_pool.work_items.map(&:fileno) : []
59
+ @worker_pool.work_items.map(&:fileno)
54
60
  end
55
61
 
56
62
  def listening?
@@ -58,11 +64,11 @@ module DatTCP
58
64
  end
59
65
 
60
66
  def running?
61
- !!(@work_loop_thread && @work_loop_thread.alive?)
67
+ !!(@thread && @thread.alive?)
62
68
  end
63
69
 
64
70
  def listen(*args)
65
- @signal.set :listen
71
+ @state.set :listen
66
72
  @tcp_server = TCPServer.build(*args)
67
73
  raise ArgumentError, "takes ip and port or file descriptor" if !@tcp_server
68
74
  yield @tcp_server if block_given?
@@ -74,159 +80,104 @@ module DatTCP
74
80
  @tcp_server = nil
75
81
  end
76
82
 
77
- def start(client_file_descriptors = nil)
83
+ def start(passed_client_fds = nil)
78
84
  raise NotListeningError.new unless listening?
79
- @signal.set :start
80
- @work_loop_thread = Thread.new{ work_loop(client_file_descriptors) }
85
+ @state.set :run
86
+ @thread = Thread.new{ work_loop(passed_client_fds) }
81
87
  end
82
88
 
83
89
  def pause(wait = false)
84
- @signal_writer.write_nonblock('p')
90
+ return unless self.running?
91
+ @state.set :pause
92
+ wakeup_thread
85
93
  wait_for_shutdown if wait
86
94
  end
87
95
 
88
96
  def stop(wait = false)
89
- @signal_writer.write_nonblock('s')
97
+ return unless self.running?
98
+ @state.set :stop
99
+ wakeup_thread
90
100
  wait_for_shutdown if wait
91
101
  end
92
102
 
93
103
  def halt(wait = false)
94
- @signal_writer.write_nonblock('h')
104
+ return unless self.running?
105
+ @state.set :halt
106
+ wakeup_thread
95
107
  wait_for_shutdown if wait
96
108
  end
97
109
 
98
- def on_worker_start(&block); @worker_start_procs << block; end
99
- def on_worker_shutdown(&block); @worker_shutdown_procs << block; end
100
- def on_worker_sleep(&block); @worker_sleep_procs << block; end
101
- def on_worker_wakeup(&block); @worker_wakeup_procs << block; end
102
-
103
110
  def inspect
104
111
  reference = '0x0%x' % (self.object_id << 1)
105
112
  "#<#{self.class}:#{reference}".tap do |s|
106
113
  s << " @ip=#{ip.inspect} @port=#{port.inspect}"
107
- s << " @work_loop_status=#{@work_loop_thread.status.inspect}" if running?
108
114
  s << ">"
109
115
  end
110
116
  end
111
117
 
112
118
  private
113
119
 
114
- def serve(socket)
115
- @serve_proc.call(socket)
120
+ def work_loop(passed_client_fds)
121
+ setup(passed_client_fds)
122
+ accept_client_connections while @state.run?
123
+ rescue StandardError => exception
124
+ @state.set :stop
125
+ log{ "An error occurred while running the server, exiting" }
126
+ log{ "#{exception.class}: #{exception.message}" }
127
+ (exception.backtrace || []).each{ |l| log{ l } }
116
128
  ensure
117
- socket.close rescue false
129
+ teardown
118
130
  end
119
131
 
120
- def work_loop(client_file_descriptors = nil)
121
- logger.info "Starting work loop..."
122
- @worker_pool = build_worker_pool
123
- add_client_sockets_from_fds client_file_descriptors
132
+ def setup(passed_client_fds)
124
133
  @worker_pool.start
125
- process_inputs while @signal.start?
126
- logger.info "Stopping work loop..."
127
- shutdown_worker_pool unless @signal.halt?
128
- rescue StandardError => exception
129
- logger.error "Exception occurred, stopping server!"
130
- logger.error "#{exception.class}: #{exception.message}"
131
- logger.error exception.backtrace.join("\n")
132
- ensure
133
- unless @signal.pause?
134
- logger.info "Closing TCP server connection"
135
- stop_listen
134
+ (passed_client_fds || []).each do |fd|
135
+ @worker_pool.push TCPSocket.for_fd(fd)
136
136
  end
137
- clear_thread
138
- logger.info "Stopped work loop"
139
137
  end
140
138
 
141
- def build_worker_pool
142
- wp = DatWorkerPool.new(
143
- @min_workers,
144
- @max_workers
145
- ){ |socket| serve(socket) }
139
+ def accept_client_connections
140
+ ready_inputs, _, _ = IO.select([@tcp_server, @signal_reader])
146
141
 
147
- # add any configured callbacks
148
- self.worker_start_procs.each do |cb|
149
- wp.on_worker_start(&cb)
142
+ if ready_inputs.include?(@tcp_server)
143
+ @worker_pool.push @tcp_server.accept
150
144
  end
151
- self.worker_shutdown_procs.each do |cb|
152
- wp.on_worker_shutdown(&cb)
153
- end
154
- self.worker_sleep_procs.each do |cb|
155
- wp.on_worker_sleep(&cb)
156
- end
157
- self.worker_wakeup_procs.each do |cb|
158
- wp.on_worker_wakeup(&cb)
159
- end
160
-
161
- wp
162
- end
163
145
 
164
- def add_client_sockets_from_fds(file_descriptors)
165
- (file_descriptors || []).each do |file_descriptor|
166
- @worker_pool.add_work TCPSocket.for_fd(file_descriptor)
146
+ if ready_inputs.include?(@signal_reader)
147
+ @signal_reader.read_nonblock(SIGNAL.bytesize)
167
148
  end
168
149
  end
169
150
 
170
- def process_inputs
171
- ready_inputs, _, _ = IO.select([ @tcp_server, @signal_reader ])
172
- accept_connection if ready_inputs.include?(@tcp_server)
173
- process_signal if ready_inputs.include?(@signal_reader)
174
- end
175
-
176
- def accept_connection
177
- @worker_pool.add_work @tcp_server.accept
178
- end
179
-
180
- def process_signal
181
- @signal.send @signal_reader.read_nonblock(1)
182
- end
151
+ def teardown
152
+ unless @state.pause?
153
+ log{ "Stop listening for connections, closing TCP socket" }
154
+ self.stop_listen
155
+ end
183
156
 
184
- def shutdown_worker_pool
185
- logger.info "Shutting down worker pool"
186
- @worker_pool.shutdown(@shutdown_timeout)
157
+ timeout = @state.halt? ? 0 : @shutdown_timeout
158
+ @worker_pool.shutdown(timeout)
159
+ ensure
160
+ @thread = nil
187
161
  end
188
162
 
189
- def clear_thread
190
- @work_loop_thread = nil
163
+ def wakeup_thread
164
+ @signal_writer.write_nonblock(SIGNAL)
191
165
  end
192
166
 
193
167
  def wait_for_shutdown
194
- @work_loop_thread.join if @work_loop_thread
168
+ @thread.join if @thread
195
169
  end
196
170
 
197
- class Signal
198
- def initialize(value)
199
- @value = value
200
- @mutex = Mutex.new
201
- end
202
-
203
- def s; set :stop; end
204
- def h; set :halt; end
205
- def p; set :pause; end
206
-
207
- def set(value)
208
- @mutex.synchronize{ @value = value }
209
- end
210
-
211
- def listen?
212
- @mutex.synchronize{ @value == :listen }
213
- end
214
-
215
- def start?
216
- @mutex.synchronize{ @value == :start }
217
- end
218
-
219
- def pause?
220
- @mutex.synchronize{ @value == :pause }
221
- end
222
-
223
- def stop?
224
- @mutex.synchronize{ @value == :stop }
225
- end
171
+ def log(&message_block)
172
+ @logger_proxy.log(&message_block)
173
+ end
226
174
 
227
- def halt?
228
- @mutex.synchronize{ @value == :halt }
229
- end
175
+ class State < DatWorkerPool::LockedObject
176
+ def listen?; self.value == :listen; end
177
+ def run?; self.value == :run; end
178
+ def pause?; self.value == :pause; end
179
+ def stop?; self.value == :stop; end
180
+ def halt?; self.value == :halt; end
230
181
  end
231
182
 
232
183
  module TCPServer
@@ -263,6 +214,16 @@ module DatTCP
263
214
  end
264
215
  end
265
216
 
217
+ class LoggerProxy < Struct.new(:logger)
218
+ def log(&message_block)
219
+ self.logger.debug("[DTCP] #{message_block.call}")
220
+ end
221
+ end
222
+
223
+ class NullLoggerProxy
224
+ def log(&block); end
225
+ end
226
+
266
227
  end
267
228
 
268
229
  class NotListeningError < RuntimeError
@@ -1,45 +1,49 @@
1
- require 'dat-tcp/logger'
1
+ require 'dat-tcp'
2
+ require 'dat-tcp/worker'
2
3
 
3
4
  module DatTCP
4
5
 
5
6
  class ServerSpy
6
7
 
8
+ attr_reader :worker_class
9
+ attr_reader :options, :backlog_size, :shutdown_timeout
10
+ attr_reader :num_workers, :logger, :worker_params
7
11
  attr_reader :ip, :port, :file_descriptor
8
12
  attr_reader :client_file_descriptors
9
- attr_reader :logger
10
- attr_reader :worker_start_procs, :worker_shutdown_procs
11
- attr_reader :worker_sleep_procs, :worker_wakeup_procs
12
13
  attr_reader :waiting_for_pause, :waiting_for_stop, :waiting_for_halt
13
14
  attr_accessor :listen_called, :start_called
14
15
  attr_accessor :stop_listen_called, :pause_called
15
16
  attr_accessor :stop_called, :halt_called
16
17
 
17
- attr_accessor :serve_proc
18
+ def initialize(worker_class, options = nil)
19
+ @worker_class = worker_class
20
+ if !@worker_class.kind_of?(Class) || !@worker_class.include?(DatTCP::Worker)
21
+ raise ArgumentError, "worker class must include `#{DatTCP::Worker}`"
22
+ end
18
23
 
19
- def initialize
20
- @ip = nil
21
- @port = nil
22
- @file_descriptor = nil
24
+ server_ns = DatTCP::Server
25
+ @options = options || {}
26
+ @backlog_size = @options[:backlog_size] || server_ns::DEFAULT_BACKLOG_SIZE
27
+ @shutdown_timeout = @options[:shutdown_timeout] || server_ns::DEFAULT_SHUTDOWN_TIMEOUT
28
+ @num_workers = (@options[:num_workers] || server_ns::DEFAULT_NUM_WORKERS).to_i
29
+ @logger = @options[:logger]
30
+ @worker_params = @options[:worker_params]
31
+
32
+ @ip = nil
33
+ @port = nil
34
+ @file_descriptor = nil
23
35
  @client_file_descriptors = []
24
- @logger = DatTCP::Logger::Null.new
25
-
26
- @worker_start_procs = []
27
- @worker_shutdown_procs = []
28
- @worker_sleep_procs = []
29
- @worker_wakeup_procs = []
30
36
 
31
37
  @waiting_for_pause = nil
32
- @waiting_for_stop = nil
33
- @waiting_for_halt = nil
38
+ @waiting_for_stop = nil
39
+ @waiting_for_halt = nil
34
40
 
35
- @listen_called = false
41
+ @listen_called = false
36
42
  @stop_listen_called = false
37
- @start_called = false
38
- @pause_called = false
39
- @stop_called = false
40
- @halt_called = false
41
-
42
- @serve_proc = proc{ }
43
+ @start_called = false
44
+ @pause_called = false
45
+ @stop_called = false
46
+ @halt_called = false
43
47
  end
44
48
 
45
49
  def listening?
@@ -64,8 +68,8 @@ module DatTCP
64
68
  @stop_listen_called = true
65
69
  end
66
70
 
67
- def start(client_file_descriptors = nil)
68
- @client_file_descriptors = client_file_descriptors || []
71
+ def start(passed_client_fds = nil)
72
+ @client_file_descriptors = passed_client_fds || []
69
73
  @start_called = true
70
74
  end
71
75
 
@@ -84,11 +88,6 @@ module DatTCP
84
88
  @halt_called = true
85
89
  end
86
90
 
87
- def on_worker_start(&block); @worker_start_procs << block; end
88
- def on_worker_shutdown(&block); @worker_shutdown_procs << block; end
89
- def on_worker_sleep(&block); @worker_sleep_procs << block; end
90
- def on_worker_wakeup(&block); @worker_wakeup_procs << block; end
91
-
92
91
  end
93
92
 
94
93
  end
@@ -1,3 +1,3 @@
1
1
  module DatTCP
2
- VERSION = "0.7.0"
2
+ VERSION = "0.8.0"
3
3
  end
@@ -0,0 +1,26 @@
1
+ require 'dat-worker-pool/worker'
2
+
3
+ module DatTCP
4
+
5
+ module Worker
6
+
7
+ def self.included(klass)
8
+ klass.class_eval do
9
+ include DatWorkerPool::Worker
10
+
11
+ end
12
+ end
13
+
14
+ module TestHelpers
15
+
16
+ def self.included(klass)
17
+ klass.class_eval do
18
+ include DatWorkerPool::Worker::TestHelpers
19
+ end
20
+ end
21
+
22
+ end
23
+
24
+ end
25
+
26
+ end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dat-tcp
3
3
  version: !ruby/object:Gem::Version
4
- hash: 3
4
+ hash: 63
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 7
8
+ - 8
9
9
  - 0
10
- version: 0.7.0
10
+ version: 0.8.0
11
11
  platform: ruby
12
12
  authors:
13
13
  - Collin Redding
@@ -16,7 +16,7 @@ autorequire:
16
16
  bindir: bin
17
17
  cert_chain: []
18
18
 
19
- date: 2015-09-08 00:00:00 Z
19
+ date: 2015-11-30 00:00:00 Z
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
22
22
  requirement: &id001 !ruby/object:Gem::Requirement
@@ -24,11 +24,11 @@ dependencies:
24
24
  requirements:
25
25
  - - ~>
26
26
  - !ruby/object:Gem::Version
27
- hash: 1
27
+ hash: 7
28
28
  segments:
29
29
  - 0
30
- - 5
31
- version: "0.5"
30
+ - 6
31
+ version: "0.6"
32
32
  type: :runtime
33
33
  name: dat-worker-pool
34
34
  version_requirements: *id001
@@ -48,6 +48,21 @@ dependencies:
48
48
  name: assert
49
49
  version_requirements: *id002
50
50
  prerelease: false
51
+ - !ruby/object:Gem::Dependency
52
+ requirement: &id003 !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ~>
56
+ - !ruby/object:Gem::Version
57
+ hash: 7
58
+ segments:
59
+ - 3
60
+ - 0
61
+ version: "3.0"
62
+ type: :development
63
+ name: scmd
64
+ version_requirements: *id003
65
+ prerelease: false
51
66
  description: A generic threaded TCP server API. It is designed for use as a base for application servers.
52
67
  email:
53
68
  - collin.redding@me.com
@@ -63,9 +78,9 @@ files:
63
78
  - Rakefile
64
79
  - dat-tcp.gemspec
65
80
  - lib/dat-tcp.rb
66
- - lib/dat-tcp/logger.rb
67
81
  - lib/dat-tcp/server_spy.rb
68
82
  - lib/dat-tcp/version.rb
83
+ - lib/dat-tcp/worker.rb
69
84
  homepage: https://github.com/redding/dat-tcp
70
85
  licenses:
71
86
  - MIT
@@ -1,36 +0,0 @@
1
- # DatTCP's logger module acts as a generator for either a debug or null logger.
2
- # This allows the server and workers to always assume they have some logger
3
- # object and not have to worry about conditionally checking if a logger is
4
- # present. The null logger takes all messages and does nothing with them. When
5
- # debug mode is turned off, this logger is used, which keeps the server from
6
- # logging. The debug logger uses an instance of ruby's standard logger and
7
- # writes to STDOUT.
8
- #
9
- require 'logger'
10
-
11
- module DatTCP::Logger
12
-
13
- def self.new(debug)
14
- !!debug ? DatTCP::Logger::Debug.new : DatTCP::Logger::Null.new
15
- end
16
-
17
- module Debug
18
-
19
- def self.new
20
- ::Logger.new(STDOUT).tap do |logger|
21
- logger.progname = "[#{self.name}]"
22
- logger.datetime_format = "%m/%d/%Y %H:%M:%S%p "
23
- end
24
- end
25
-
26
- end
27
-
28
- class Null
29
-
30
- Logger::Severity.constants.each do |name|
31
- define_method(name.downcase){|*args| } # no-op
32
- end
33
-
34
- end
35
-
36
- end