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 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