dat-tcp 0.4.0 → 0.5.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 +1 -1
- data/Rakefile +0 -1
- data/dat-tcp.gemspec +3 -3
- data/lib/dat-tcp.rb +130 -155
- data/lib/dat-tcp/version.rb +1 -1
- metadata +20 -35
data/Gemfile
CHANGED
data/Rakefile
CHANGED
data/dat-tcp.gemspec
CHANGED
@@ -12,14 +12,14 @@ Gem::Specification.new do |gem|
|
|
12
12
|
" designed for use as a base for application servers."
|
13
13
|
gem.summary = "A generic threaded TCP server API"
|
14
14
|
gem.homepage = "https://github.com/redding/dat-tcp"
|
15
|
+
gem.license = 'MIT'
|
15
16
|
|
16
17
|
gem.files = `git ls-files -- lib/* Gemfile Rakefile *.gemspec`.split($/)
|
17
18
|
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
18
19
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
19
20
|
gem.require_paths = ["lib"]
|
20
21
|
|
21
|
-
gem.add_dependency("dat-worker-pool", ["~> 0.
|
22
|
+
gem.add_dependency("dat-worker-pool", ["~> 0.3"])
|
22
23
|
|
23
|
-
gem.add_development_dependency('assert',
|
24
|
-
gem.add_development_dependency('assert-mocha', ['~> 1.0'])
|
24
|
+
gem.add_development_dependency('assert', ['~> 2.11'])
|
25
25
|
end
|
data/lib/dat-tcp.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
require 'dat-worker-pool'
|
2
|
-
require 'ostruct'
|
3
2
|
require 'socket'
|
4
3
|
require 'thread'
|
5
4
|
|
@@ -8,86 +7,27 @@ require 'dat-tcp/logger'
|
|
8
7
|
|
9
8
|
module DatTCP
|
10
9
|
|
11
|
-
|
10
|
+
class Server
|
12
11
|
|
13
12
|
attr_reader :logger
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
@
|
19
|
-
@
|
20
|
-
@
|
21
|
-
@
|
22
|
-
@shutdown_timeout = config
|
13
|
+
private :logger
|
14
|
+
|
15
|
+
def initialize(config = nil, &serve_proc)
|
16
|
+
config ||= {}
|
17
|
+
@backlog_size = config[:backlog_size] || 1024
|
18
|
+
@debug = config[:debug] || false
|
19
|
+
@min_workers = config[:min_workers] || 2
|
20
|
+
@max_workers = config[:max_workers] || 4
|
21
|
+
@shutdown_timeout = config[:shutdown_timeout] || 15
|
22
|
+
@signal_reader, @signal_writer = IO.pipe
|
23
|
+
@serve_proc = serve_proc || raise(ArgumentError, "no block given")
|
23
24
|
|
24
25
|
@logger = DatTCP::Logger.new(@debug)
|
25
26
|
|
26
|
-
check_configuration
|
27
|
-
|
28
27
|
@tcp_server = nil
|
29
28
|
@work_loop_thread = nil
|
30
29
|
@worker_pool = nil
|
31
|
-
|
32
|
-
end
|
33
|
-
|
34
|
-
# Socket Options:
|
35
|
-
# * SOL_SOCKET - specifies the protocol layer the option applies to.
|
36
|
-
# SOL_SOCKET is basic socket options (as opposed to
|
37
|
-
# something like IPPROTO_TCP for TCP socket options).
|
38
|
-
# * SO_REUSEADDR - indicates that the rules used in validating addresses
|
39
|
-
# supplied in a bind(2) call should allow reuse of local
|
40
|
-
# addresses. This will allow us to re-bind to a port if we
|
41
|
-
# were shutdown and started right away. This will still
|
42
|
-
# throw an "address in use" if a socket is active on the
|
43
|
-
# port.
|
44
|
-
|
45
|
-
def listen(*args)
|
46
|
-
build_server_method = if args.size == 2
|
47
|
-
:new
|
48
|
-
elsif args.size == 1
|
49
|
-
:for_fd
|
50
|
-
else
|
51
|
-
raise InvalidListenArgsError.new
|
52
|
-
end
|
53
|
-
set_state :listen
|
54
|
-
run_hook 'on_listen'
|
55
|
-
@tcp_server = TCPServer.send(build_server_method, *args)
|
56
|
-
|
57
|
-
@tcp_server.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true)
|
58
|
-
run_hook 'configure_tcp_server', @tcp_server
|
59
|
-
|
60
|
-
@tcp_server.listen(@backlog_size)
|
61
|
-
end
|
62
|
-
|
63
|
-
def run(client_file_descriptors = nil)
|
64
|
-
raise NotListeningError.new if !self.listening?
|
65
|
-
set_state :run
|
66
|
-
run_hook 'on_run'
|
67
|
-
@work_loop_thread = Thread.new{ work_loop(client_file_descriptors) }
|
68
|
-
end
|
69
|
-
|
70
|
-
def pause(wait = true)
|
71
|
-
set_state :pause
|
72
|
-
run_hook 'on_pause'
|
73
|
-
wait_for_shutdown if wait
|
74
|
-
end
|
75
|
-
|
76
|
-
def stop(wait = true)
|
77
|
-
set_state :stop
|
78
|
-
run_hook 'on_stop'
|
79
|
-
wait_for_shutdown if wait
|
80
|
-
end
|
81
|
-
|
82
|
-
def halt(wait = true)
|
83
|
-
set_state :halt
|
84
|
-
run_hook 'on_halt'
|
85
|
-
wait_for_shutdown if wait
|
86
|
-
end
|
87
|
-
|
88
|
-
def stop_listening
|
89
|
-
@tcp_server.close rescue false
|
90
|
-
@tcp_server = nil
|
30
|
+
@signal = Signal.new(:stop)
|
91
31
|
end
|
92
32
|
|
93
33
|
def ip
|
@@ -111,103 +51,107 @@ module DatTCP
|
|
111
51
|
end
|
112
52
|
|
113
53
|
def running?
|
114
|
-
|
115
|
-
end
|
116
|
-
|
117
|
-
def serve(socket)
|
118
|
-
self.serve!(socket)
|
119
|
-
ensure
|
120
|
-
socket.close rescue false
|
121
|
-
end
|
122
|
-
|
123
|
-
# This method should be overwritten to handle new connections
|
124
|
-
def serve!(socket)
|
54
|
+
!!(@work_loop_thread && @work_loop_thread.alive?)
|
125
55
|
end
|
126
56
|
|
127
|
-
|
128
|
-
|
129
|
-
|
57
|
+
def listen(*args)
|
58
|
+
@signal.set :listen
|
59
|
+
@tcp_server = TCPServer.build(*args)
|
60
|
+
raise ArgumentError, "takes ip and port or file descriptor" if !@tcp_server
|
61
|
+
yield @tcp_server if block_given?
|
62
|
+
@tcp_server.listen(@backlog_size)
|
130
63
|
end
|
131
64
|
|
132
|
-
def
|
65
|
+
def stop_listen
|
66
|
+
@tcp_server.close rescue false
|
67
|
+
@tcp_server = nil
|
133
68
|
end
|
134
69
|
|
135
|
-
def
|
70
|
+
def start(client_file_descriptors = nil)
|
71
|
+
raise NotListeningError.new unless listening?
|
72
|
+
@signal.set :start
|
73
|
+
@work_loop_thread = Thread.new{ work_loop(client_file_descriptors) }
|
136
74
|
end
|
137
75
|
|
138
|
-
def
|
76
|
+
def pause(wait = false)
|
77
|
+
@signal_writer.write_nonblock('p')
|
78
|
+
wait_for_shutdown if wait
|
139
79
|
end
|
140
80
|
|
141
|
-
def
|
81
|
+
def stop(wait = false)
|
82
|
+
@signal_writer.write_nonblock('s')
|
83
|
+
wait_for_shutdown if wait
|
142
84
|
end
|
143
85
|
|
144
|
-
def
|
86
|
+
def halt(wait = false)
|
87
|
+
@signal_writer.write_nonblock('h')
|
88
|
+
wait_for_shutdown if wait
|
145
89
|
end
|
146
90
|
|
147
91
|
def inspect
|
148
92
|
reference = '0x0%x' % (self.object_id << 1)
|
149
|
-
"#<#{self.class}:#{reference}".tap do |
|
150
|
-
|
151
|
-
if
|
152
|
-
|
153
|
-
inspect_str << " @ip=#{ip.inspect} @port=#{port.inspect}"
|
154
|
-
end
|
155
|
-
inspect_str << " @work_loop_status=#{@work_loop_thread.status.inspect}" if self.running?
|
156
|
-
inspect_str << ">"
|
93
|
+
"#<#{self.class}:#{reference}".tap do |s|
|
94
|
+
s << " @ip=#{ip.inspect} @port=#{port.inspect}"
|
95
|
+
s << " @work_loop_status=#{@work_loop_thread.status.inspect}" if running?
|
96
|
+
s << ">"
|
157
97
|
end
|
158
98
|
end
|
159
99
|
|
160
|
-
|
100
|
+
private
|
101
|
+
|
102
|
+
def serve(socket)
|
103
|
+
@serve_proc.call(socket)
|
104
|
+
ensure
|
105
|
+
socket.close rescue false
|
106
|
+
end
|
161
107
|
|
162
108
|
def work_loop(client_file_descriptors = nil)
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
self.enqueue_file_descriptors(client_file_descriptors || [])
|
167
|
-
while @state.run?
|
168
|
-
@worker_pool.add_work self.accept_connection
|
109
|
+
logger.info "Starting work loop..."
|
110
|
+
@worker_pool = DatWorkerPool.new(@min_workers, @max_workers) do |socket|
|
111
|
+
serve(socket)
|
169
112
|
end
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
113
|
+
add_client_sockets_from_fds client_file_descriptors
|
114
|
+
process_inputs while @signal.start?
|
115
|
+
logger.info "Stopping work loop..."
|
116
|
+
shutdown_worker_pool unless @signal.halt?
|
117
|
+
rescue StandardError => exception
|
118
|
+
logger.error "Exception occurred, stopping server!"
|
119
|
+
logger.error "#{exception.class}: #{exception.message}"
|
120
|
+
logger.error exception.backtrace.join("\n")
|
176
121
|
ensure
|
177
|
-
|
122
|
+
unless @signal.pause?
|
123
|
+
logger.info "Closing TCP server connection"
|
124
|
+
stop_listen
|
125
|
+
end
|
178
126
|
clear_thread
|
179
|
-
|
127
|
+
logger.info "Stopped work loop"
|
180
128
|
end
|
181
129
|
|
182
|
-
def
|
183
|
-
file_descriptors.each do |file_descriptor|
|
130
|
+
def add_client_sockets_from_fds(file_descriptors)
|
131
|
+
(file_descriptors || []).each do |file_descriptor|
|
184
132
|
@worker_pool.add_work TCPSocket.for_fd(file_descriptor)
|
185
133
|
end
|
186
134
|
end
|
187
135
|
|
188
|
-
|
189
|
-
|
190
|
-
|
136
|
+
def process_inputs
|
137
|
+
ready_inputs, _, _ = IO.select([ @tcp_server, @signal_reader ])
|
138
|
+
accept_connection if ready_inputs.include?(@tcp_server)
|
139
|
+
process_signal if ready_inputs.include?(@signal_reader)
|
140
|
+
end
|
141
|
+
|
191
142
|
def accept_connection
|
192
|
-
|
193
|
-
return @tcp_server.accept if self.connection_ready?
|
194
|
-
end
|
143
|
+
@worker_pool.add_work @tcp_server.accept
|
195
144
|
end
|
196
145
|
|
197
|
-
def
|
198
|
-
|
146
|
+
def process_signal
|
147
|
+
@signal.send @signal_reader.read_nonblock(1)
|
199
148
|
end
|
200
149
|
|
201
150
|
def shutdown_worker_pool
|
202
|
-
|
151
|
+
logger.info "Shutting down worker pool"
|
203
152
|
@worker_pool.shutdown(@shutdown_timeout)
|
204
153
|
end
|
205
154
|
|
206
|
-
def close_connection
|
207
|
-
self.logger.info "Closing TCP server connection..."
|
208
|
-
self.stop_listening
|
209
|
-
end
|
210
|
-
|
211
155
|
def clear_thread
|
212
156
|
@work_loop_thread = nil
|
213
157
|
end
|
@@ -216,50 +160,81 @@ module DatTCP
|
|
216
160
|
@work_loop_thread.join if @work_loop_thread
|
217
161
|
end
|
218
162
|
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
"the maximum number of workers (#{@max_workers})."
|
163
|
+
class Signal
|
164
|
+
def initialize(value)
|
165
|
+
@value = value
|
166
|
+
@mutex = Mutex.new
|
224
167
|
end
|
225
|
-
end
|
226
168
|
|
227
|
-
|
228
|
-
|
229
|
-
|
169
|
+
def s; set :stop; end
|
170
|
+
def h; set :halt; end
|
171
|
+
def p; set :pause; end
|
230
172
|
|
231
|
-
|
232
|
-
|
233
|
-
|
173
|
+
def set(value)
|
174
|
+
@mutex.synchronize{ @value = value }
|
175
|
+
end
|
234
176
|
|
235
|
-
|
177
|
+
def listen?
|
178
|
+
@mutex.synchronize{ @value == :listen }
|
179
|
+
end
|
236
180
|
|
237
|
-
def
|
238
|
-
|
181
|
+
def start?
|
182
|
+
@mutex.synchronize{ @value == :start }
|
239
183
|
end
|
240
184
|
|
241
|
-
|
242
|
-
|
185
|
+
def pause?
|
186
|
+
@mutex.synchronize{ @value == :pause }
|
243
187
|
end
|
244
188
|
|
189
|
+
def stop?
|
190
|
+
@mutex.synchronize{ @value == :stop }
|
191
|
+
end
|
192
|
+
|
193
|
+
def halt?
|
194
|
+
@mutex.synchronize{ @value == :halt }
|
195
|
+
end
|
245
196
|
end
|
246
197
|
|
247
|
-
|
198
|
+
module TCPServer
|
199
|
+
def self.build(*args)
|
200
|
+
case args.size
|
201
|
+
when 2
|
202
|
+
self.new(*args)
|
203
|
+
when 1
|
204
|
+
self.for_fd(*args)
|
205
|
+
end
|
206
|
+
end
|
248
207
|
|
249
|
-
|
208
|
+
def self.new(ip, port)
|
209
|
+
configure(::TCPServer.new(ip, port))
|
210
|
+
end
|
250
211
|
|
251
|
-
|
252
|
-
|
212
|
+
def self.for_fd(file_descriptor)
|
213
|
+
configure(::TCPServer.for_fd(file_descriptor))
|
214
|
+
end
|
215
|
+
|
216
|
+
# `setsockopt` values:
|
217
|
+
# * SOL_SOCKET - specifies the protocol layer the option applies to.
|
218
|
+
# SOL_SOCKET is basic socket options (as opposed to
|
219
|
+
# something like IPPROTO_TCP for TCP socket options).
|
220
|
+
# * SO_REUSEADDR - indicates that the rules used in validating addresses
|
221
|
+
# supplied in a bind(2) call should allow reuse of local
|
222
|
+
# addresses. This will allow us to re-bind to a port if
|
223
|
+
# we were shutdown and started right away. This will
|
224
|
+
# still throw an "address in use" if a socket is active
|
225
|
+
# on the port.
|
226
|
+
def self.configure(tcp_server)
|
227
|
+
tcp_server.setsockopt(Socket::SOL_SOCKET, Socket::SO_REUSEADDR, true)
|
228
|
+
tcp_server
|
229
|
+
end
|
253
230
|
end
|
254
231
|
|
255
232
|
end
|
256
233
|
|
257
234
|
class NotListeningError < RuntimeError
|
258
|
-
|
259
235
|
def initialize
|
260
|
-
super "
|
236
|
+
super "server isn't listening, call `listen` first"
|
261
237
|
end
|
262
|
-
|
263
238
|
end
|
264
239
|
|
265
240
|
end
|
data/lib/dat-tcp/version.rb
CHANGED
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:
|
4
|
+
hash: 11
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
8
|
+
- 5
|
9
9
|
- 0
|
10
|
-
version: 0.
|
10
|
+
version: 0.5.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Collin Redding
|
@@ -16,53 +16,38 @@ autorequire:
|
|
16
16
|
bindir: bin
|
17
17
|
cert_chain: []
|
18
18
|
|
19
|
-
date:
|
19
|
+
date: 2014-06-17 00:00:00 Z
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
22
|
-
|
23
|
-
version_requirements: &id001 !ruby/object:Gem::Requirement
|
22
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
23
|
none: false
|
25
24
|
requirements:
|
26
25
|
- - ~>
|
27
26
|
- !ruby/object:Gem::Version
|
28
|
-
hash:
|
27
|
+
hash: 13
|
29
28
|
segments:
|
30
29
|
- 0
|
31
|
-
-
|
32
|
-
version: "0.
|
33
|
-
|
34
|
-
name: dat-worker-pool
|
30
|
+
- 3
|
31
|
+
version: "0.3"
|
32
|
+
version_requirements: *id001
|
35
33
|
type: :runtime
|
36
|
-
|
34
|
+
name: dat-worker-pool
|
37
35
|
prerelease: false
|
38
|
-
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
38
|
none: false
|
40
39
|
requirements:
|
41
40
|
- - ~>
|
42
41
|
- !ruby/object:Gem::Version
|
43
|
-
hash:
|
42
|
+
hash: 21
|
44
43
|
segments:
|
45
44
|
- 2
|
46
|
-
-
|
47
|
-
version: "2.
|
48
|
-
|
49
|
-
name: assert
|
45
|
+
- 11
|
46
|
+
version: "2.11"
|
47
|
+
version_requirements: *id002
|
50
48
|
type: :development
|
51
|
-
|
49
|
+
name: assert
|
52
50
|
prerelease: false
|
53
|
-
version_requirements: &id003 !ruby/object:Gem::Requirement
|
54
|
-
none: false
|
55
|
-
requirements:
|
56
|
-
- - ~>
|
57
|
-
- !ruby/object:Gem::Version
|
58
|
-
hash: 15
|
59
|
-
segments:
|
60
|
-
- 1
|
61
|
-
- 0
|
62
|
-
version: "1.0"
|
63
|
-
requirement: *id003
|
64
|
-
name: assert-mocha
|
65
|
-
type: :development
|
66
51
|
description: A generic threaded TCP server API. It is designed for use as a base for application servers.
|
67
52
|
email:
|
68
53
|
- collin.redding@me.com
|
@@ -81,8 +66,8 @@ files:
|
|
81
66
|
- lib/dat-tcp/logger.rb
|
82
67
|
- lib/dat-tcp/version.rb
|
83
68
|
homepage: https://github.com/redding/dat-tcp
|
84
|
-
licenses:
|
85
|
-
|
69
|
+
licenses:
|
70
|
+
- MIT
|
86
71
|
post_install_message:
|
87
72
|
rdoc_options: []
|
88
73
|
|
@@ -109,7 +94,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
109
94
|
requirements: []
|
110
95
|
|
111
96
|
rubyforge_project:
|
112
|
-
rubygems_version: 1.8.
|
97
|
+
rubygems_version: 1.8.29
|
113
98
|
signing_key:
|
114
99
|
specification_version: 3
|
115
100
|
summary: A generic threaded TCP server API
|