dat-tcp 0.3.1 → 0.4.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/dat-tcp.gemspec +1 -1
- data/lib/dat-tcp.rb +12 -6
- data/lib/dat-tcp/version.rb +1 -1
- metadata +9 -10
- data/lib/dat-tcp/worker_pool.rb +0 -224
data/dat-tcp.gemspec
CHANGED
@@ -18,7 +18,7 @@ Gem::Specification.new do |gem|
|
|
18
18
|
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
19
19
|
gem.require_paths = ["lib"]
|
20
20
|
|
21
|
-
gem.add_dependency("
|
21
|
+
gem.add_dependency("dat-worker-pool", ["~> 0.1"])
|
22
22
|
|
23
23
|
gem.add_development_dependency('assert', ['~> 2.0'])
|
24
24
|
gem.add_development_dependency('assert-mocha', ['~> 1.0'])
|
data/lib/dat-tcp.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
|
+
require 'dat-worker-pool'
|
1
2
|
require 'ostruct'
|
2
3
|
require 'socket'
|
3
4
|
require 'thread'
|
4
5
|
|
5
6
|
require 'dat-tcp/version'
|
6
7
|
require 'dat-tcp/logger'
|
7
|
-
require 'dat-tcp/worker_pool'
|
8
8
|
|
9
9
|
module DatTCP
|
10
10
|
|
@@ -103,7 +103,7 @@ module DatTCP
|
|
103
103
|
end
|
104
104
|
|
105
105
|
def client_file_descriptors
|
106
|
-
@worker_pool ? @worker_pool.
|
106
|
+
@worker_pool ? @worker_pool.work_items.map(&:fileno) : []
|
107
107
|
end
|
108
108
|
|
109
109
|
def listening?
|
@@ -114,8 +114,14 @@ module DatTCP
|
|
114
114
|
!!@work_loop_thread
|
115
115
|
end
|
116
116
|
|
117
|
-
# This method should be overwritten to handle new connections
|
118
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)
|
119
125
|
end
|
120
126
|
|
121
127
|
# Hooks
|
@@ -156,10 +162,10 @@ module DatTCP
|
|
156
162
|
def work_loop(client_file_descriptors = nil)
|
157
163
|
self.logger.info "Starting work loop..."
|
158
164
|
pool_args = [ @min_workers, @max_workers, @debug ]
|
159
|
-
@worker_pool =
|
165
|
+
@worker_pool = DatWorkerPool.new(*pool_args){|socket| serve(socket) }
|
160
166
|
self.enqueue_file_descriptors(client_file_descriptors || [])
|
161
167
|
while @state.run?
|
162
|
-
@worker_pool.
|
168
|
+
@worker_pool.add_work self.accept_connection
|
163
169
|
end
|
164
170
|
self.logger.info "Stopping work loop..."
|
165
171
|
shutdown_worker_pool if !@state.halt?
|
@@ -175,7 +181,7 @@ module DatTCP
|
|
175
181
|
|
176
182
|
def enqueue_file_descriptors(file_descriptors)
|
177
183
|
file_descriptors.each do |file_descriptor|
|
178
|
-
@worker_pool.
|
184
|
+
@worker_pool.add_work TCPSocket.for_fd(file_descriptor)
|
179
185
|
end
|
180
186
|
end
|
181
187
|
|
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: 15
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
|
-
-
|
9
|
-
-
|
10
|
-
version: 0.
|
8
|
+
- 4
|
9
|
+
- 0
|
10
|
+
version: 0.4.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: 2013-
|
19
|
+
date: 2013-07-15 00:00:00 Z
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
22
22
|
prerelease: false
|
@@ -25,13 +25,13 @@ dependencies:
|
|
25
25
|
requirements:
|
26
26
|
- - ~>
|
27
27
|
- !ruby/object:Gem::Version
|
28
|
-
hash:
|
28
|
+
hash: 9
|
29
29
|
segments:
|
30
|
+
- 0
|
30
31
|
- 1
|
31
|
-
|
32
|
-
version: "1.2"
|
32
|
+
version: "0.1"
|
33
33
|
requirement: *id001
|
34
|
-
name:
|
34
|
+
name: dat-worker-pool
|
35
35
|
type: :runtime
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
prerelease: false
|
@@ -80,7 +80,6 @@ files:
|
|
80
80
|
- lib/dat-tcp.rb
|
81
81
|
- lib/dat-tcp/logger.rb
|
82
82
|
- lib/dat-tcp/version.rb
|
83
|
-
- lib/dat-tcp/worker_pool.rb
|
84
83
|
homepage: https://github.com/redding/dat-tcp
|
85
84
|
licenses: []
|
86
85
|
|
data/lib/dat-tcp/worker_pool.rb
DELETED
@@ -1,224 +0,0 @@
|
|
1
|
-
require 'system_timer'
|
2
|
-
require 'thread'
|
3
|
-
|
4
|
-
require 'dat-tcp/logger'
|
5
|
-
|
6
|
-
module DatTCP
|
7
|
-
|
8
|
-
TimeoutError = Class.new(RuntimeError)
|
9
|
-
|
10
|
-
class WorkerPool
|
11
|
-
attr_reader :logger, :spawned
|
12
|
-
|
13
|
-
def initialize(min = 0, max = 1, debug = false, &serve_proc)
|
14
|
-
@min_workers = min
|
15
|
-
@max_workers = max
|
16
|
-
@debug = debug
|
17
|
-
@logger = DatTCP::Logger.new(@debug)
|
18
|
-
@serve_proc = serve_proc
|
19
|
-
|
20
|
-
@queue = DatTCP::Queue.new
|
21
|
-
@workers_waiting = DatTCP::WorkersWaiting.new
|
22
|
-
|
23
|
-
@mutex = Mutex.new
|
24
|
-
@workers = []
|
25
|
-
@spawned = 0
|
26
|
-
|
27
|
-
@min_workers.times{ self.spawn_worker }
|
28
|
-
end
|
29
|
-
|
30
|
-
def connections
|
31
|
-
@queue.items
|
32
|
-
end
|
33
|
-
|
34
|
-
def waiting
|
35
|
-
@workers_waiting.count
|
36
|
-
end
|
37
|
-
|
38
|
-
# Check if all workers are busy before adding the connection. When the
|
39
|
-
# connection is added, a worker will stop waiting (if it was idle). Because
|
40
|
-
# of that, we can't reliably check if all workers are busy. We might think
|
41
|
-
# all workers are busy because we just woke up a sleeping worker to serve
|
42
|
-
# this connection. Then we would spawn a worker to do nothing.
|
43
|
-
def enqueue_connection(socket)
|
44
|
-
return if !socket
|
45
|
-
new_worker_needed = all_workers_are_busy?
|
46
|
-
@queue.push socket
|
47
|
-
self.spawn_worker if new_worker_needed && havent_reached_max_workers?
|
48
|
-
end
|
49
|
-
|
50
|
-
# Shutdown each worker and then the queue. Shutting down the queue will
|
51
|
-
# signal any workers waiting on it to wake up, so they can start shutting
|
52
|
-
# down. If a worker is processing a connection, then it will be joined and
|
53
|
-
# allowed to finish.
|
54
|
-
# **NOTE** Any connections that are on the queue are not served.
|
55
|
-
def shutdown(timeout)
|
56
|
-
begin
|
57
|
-
SystemTimer.timeout(timeout, DatTCP::TimeoutError) do
|
58
|
-
@workers.each(&:shutdown)
|
59
|
-
@queue.shutdown
|
60
|
-
|
61
|
-
# use this pattern instead of `each` -- we don't want to call `join` on
|
62
|
-
# every worker (especially if they are shutting down on their own), we
|
63
|
-
# just want to make sure that any who haven't had a chance to finish
|
64
|
-
# get to (this is safe, otherwise you might get a dead thread in the
|
65
|
-
# `each`).
|
66
|
-
@workers.first.join until @workers.empty?
|
67
|
-
end
|
68
|
-
rescue DatTCP::TimeoutError => exception
|
69
|
-
exception.message.replace "Timed out shutting down the server"
|
70
|
-
@debug ? raise(exception) : self.logger.error(exception.message)
|
71
|
-
end
|
72
|
-
end
|
73
|
-
|
74
|
-
# public, because workers need to call it for themselves
|
75
|
-
def despawn_worker(worker)
|
76
|
-
@mutex.synchronize do
|
77
|
-
@spawned -= 1
|
78
|
-
@workers.delete worker
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
protected
|
83
|
-
|
84
|
-
def spawn_worker
|
85
|
-
@mutex.synchronize do
|
86
|
-
worker = DatTCP::Worker.new(self, @queue, @workers_waiting) do |socket|
|
87
|
-
self.serve_socket(socket)
|
88
|
-
end
|
89
|
-
@workers << worker
|
90
|
-
@spawned += 1
|
91
|
-
worker
|
92
|
-
end
|
93
|
-
end
|
94
|
-
|
95
|
-
def serve_socket(socket)
|
96
|
-
begin
|
97
|
-
@serve_proc.call(socket)
|
98
|
-
rescue Exception => exception
|
99
|
-
self.logger.error "Exception raised while serving connection!"
|
100
|
-
self.logger.error "#{exception.class}: #{exception.message}"
|
101
|
-
self.logger.error exception.backtrace.join("\n")
|
102
|
-
ensure
|
103
|
-
socket.close rescue false
|
104
|
-
end
|
105
|
-
end
|
106
|
-
|
107
|
-
def all_workers_are_busy?
|
108
|
-
@workers_waiting.count <= 0
|
109
|
-
end
|
110
|
-
|
111
|
-
def havent_reached_max_workers?
|
112
|
-
@mutex.synchronize do
|
113
|
-
@spawned < @max_workers
|
114
|
-
end
|
115
|
-
end
|
116
|
-
|
117
|
-
end
|
118
|
-
|
119
|
-
class Queue
|
120
|
-
|
121
|
-
def initialize
|
122
|
-
@items = []
|
123
|
-
@shutdown = false
|
124
|
-
@mutex = Mutex.new
|
125
|
-
@condition_variable = ConditionVariable.new
|
126
|
-
end
|
127
|
-
|
128
|
-
def items
|
129
|
-
@mutex.synchronize{ @items }
|
130
|
-
end
|
131
|
-
|
132
|
-
# Add the connection and wake up the first worker (the `signal`) that's
|
133
|
-
# waiting (because of `wait_for_new_connection`)
|
134
|
-
def push(socket)
|
135
|
-
raise "Unable to add connection while shutting down" if @shutdown
|
136
|
-
@mutex.synchronize do
|
137
|
-
@items << socket
|
138
|
-
@condition_variable.signal
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
142
|
-
def pop
|
143
|
-
@mutex.synchronize{ @items.pop }
|
144
|
-
end
|
145
|
-
|
146
|
-
def empty?
|
147
|
-
@mutex.synchronize{ @items.empty? }
|
148
|
-
end
|
149
|
-
|
150
|
-
# wait to be signaled by `push`
|
151
|
-
def wait_for_new_connection
|
152
|
-
@mutex.synchronize{ @condition_variable.wait(@mutex) }
|
153
|
-
end
|
154
|
-
|
155
|
-
# wake up any workers who are idle (because of `wait_for_new_connection`)
|
156
|
-
def shutdown
|
157
|
-
@shutdown = true
|
158
|
-
@mutex.synchronize{ @condition_variable.broadcast }
|
159
|
-
end
|
160
|
-
|
161
|
-
end
|
162
|
-
|
163
|
-
class Worker
|
164
|
-
|
165
|
-
def initialize(pool, queue, workers_waiting, &block)
|
166
|
-
@pool = pool
|
167
|
-
@queue = queue
|
168
|
-
@workers_waiting = workers_waiting
|
169
|
-
@block = block
|
170
|
-
@shutdown = false
|
171
|
-
@thread = Thread.new{ work_loop }
|
172
|
-
end
|
173
|
-
|
174
|
-
def shutdown
|
175
|
-
@shutdown = true
|
176
|
-
end
|
177
|
-
|
178
|
-
def join
|
179
|
-
@thread.join if @thread
|
180
|
-
end
|
181
|
-
|
182
|
-
protected
|
183
|
-
|
184
|
-
def work_loop
|
185
|
-
loop do
|
186
|
-
self.wait_for_work
|
187
|
-
break if @shutdown
|
188
|
-
@block.call @queue.pop
|
189
|
-
end
|
190
|
-
ensure
|
191
|
-
@pool.despawn_worker(self)
|
192
|
-
end
|
193
|
-
|
194
|
-
# Wait for a connection to serve by checking if the queue is empty.
|
195
|
-
def wait_for_work
|
196
|
-
while @queue.empty?
|
197
|
-
return if @shutdown
|
198
|
-
@workers_waiting.increment
|
199
|
-
@queue.wait_for_new_connection
|
200
|
-
@workers_waiting.decrement
|
201
|
-
end
|
202
|
-
end
|
203
|
-
|
204
|
-
end
|
205
|
-
|
206
|
-
class WorkersWaiting
|
207
|
-
attr_reader :count
|
208
|
-
|
209
|
-
def initialize
|
210
|
-
@mutex = Mutex.new
|
211
|
-
@count = 0
|
212
|
-
end
|
213
|
-
|
214
|
-
def increment
|
215
|
-
@mutex.synchronize{ @count += 1 }
|
216
|
-
end
|
217
|
-
|
218
|
-
def decrement
|
219
|
-
@mutex.synchronize{ @count -= 1 }
|
220
|
-
end
|
221
|
-
|
222
|
-
end
|
223
|
-
|
224
|
-
end
|