dat-tcp 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
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("SystemTimer", ["~> 1.2"])
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.connections.map(&:fileno) : []
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 = DatTCP::WorkerPool.new(*pool_args){|socket| serve(socket) }
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.enqueue_connection self.accept_connection
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.enqueue_connection TCPSocket.for_fd(file_descriptor)
184
+ @worker_pool.add_work TCPSocket.for_fd(file_descriptor)
179
185
  end
180
186
  end
181
187
 
@@ -1,3 +1,3 @@
1
1
  module DatTCP
2
- VERSION = "0.3.1"
2
+ VERSION = "0.4.0"
3
3
  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: 17
4
+ hash: 15
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
- - 3
9
- - 1
10
- version: 0.3.1
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-04-02 00:00:00 Z
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: 11
28
+ hash: 9
29
29
  segments:
30
+ - 0
30
31
  - 1
31
- - 2
32
- version: "1.2"
32
+ version: "0.1"
33
33
  requirement: *id001
34
- name: SystemTimer
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
 
@@ -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