umlaut 3.0.0beta9 → 3.0.0beta10
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/active_record_patch/connection_pool.rb +397 -384
- data/app/controllers/search_methods/sfx4.rb +2 -2
- data/app/models/collection.rb +59 -57
- data/app/models/service_response.rb +11 -0
- data/app/models/service_wave.rb +1 -1
- data/db/migrate/02_umlaut_add_service_response_index.rb +9 -0
- data/lib/service_adaptors/hathi_trust.rb +1 -1
- data/lib/tasks/umlaut_migrate_permalinks.rake +1 -1
- data/lib/umlaut.rb +2 -2
- data/lib/umlaut/version.rb +1 -1
- metadata +4 -4
- data/app/models/#Untitled-1# +0 -0
@@ -2,12 +2,14 @@
|
|
2
2
|
#
|
3
3
|
# ActiveRecord's ConnectionPool in Rails 3.2.3 allows threads to 'steal'
|
4
4
|
# connections from each other, so some threads get starved out.
|
5
|
+
#
|
5
6
|
# This monkey patch uses an implementation from https://github.com/rails/rails/pull/6492
|
6
|
-
# that ensures 'fair' queue in ConnectionPool.
|
7
|
+
# that ensures 'fair' queue in ConnectionPool.
|
8
|
+
#
|
9
|
+
# It's actually a weird hybrid which ALSO maintains the clear_stale_cached_connections!
|
10
|
+
# behavior to reclaim leaked orphaned connections, and calls that method
|
11
|
+
# in checkout when pool has no avail connections.
|
7
12
|
#
|
8
|
-
# Can be removed if/when we are on an AR that incorporates above patch
|
9
|
-
# or equivalent.
|
10
|
-
#
|
11
13
|
# This file referenced from an initializer in our main engine
|
12
14
|
# class, that loads it to monkey patch AR.
|
13
15
|
#
|
@@ -20,6 +22,8 @@ unless ENV["NO_AR_PATCH"]
|
|
20
22
|
require 'active_record'
|
21
23
|
ActiveRecord::ConnectionAdapters::ConnectionPool
|
22
24
|
|
25
|
+
# Unload it so we can redefine it completely
|
26
|
+
ActiveRecord::ConnectionAdapters.send(:remove_const, :ConnectionPool)
|
23
27
|
|
24
28
|
# Some require's our new definition will need
|
25
29
|
require 'thread'
|
@@ -27,427 +31,436 @@ unless ENV["NO_AR_PATCH"]
|
|
27
31
|
require 'set'
|
28
32
|
require 'active_support/core_ext/module/deprecation'
|
29
33
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
module ConnectionAdapters
|
37
|
-
# Connection pool base class for managing Active Record database
|
38
|
-
# connections.
|
39
|
-
#
|
40
|
-
# == Introduction
|
41
|
-
#
|
42
|
-
# A connection pool synchronizes thread access to a limited number of
|
43
|
-
# database connections. The basic idea is that each thread checks out a
|
44
|
-
# database connection from the pool, uses that connection, and checks the
|
45
|
-
# connection back in. ConnectionPool is completely thread-safe, and will
|
46
|
-
# ensure that a connection cannot be used by two threads at the same time,
|
47
|
-
# as long as ConnectionPool's contract is correctly followed. It will also
|
48
|
-
# handle cases in which there are more threads than connections: if all
|
49
|
-
# connections have been checked out, and a thread tries to checkout a
|
50
|
-
# connection anyway, then ConnectionPool will wait until some other thread
|
51
|
-
# has checked in a connection.
|
52
|
-
#
|
53
|
-
# == Obtaining (checking out) a connection
|
54
|
-
#
|
55
|
-
# Connections can be obtained and used from a connection pool in several
|
56
|
-
# ways:
|
57
|
-
#
|
58
|
-
# 1. Simply use ActiveRecord::Base.connection as with Active Record 2.1 and
|
59
|
-
# earlier (pre-connection-pooling). Eventually, when you're done with
|
60
|
-
# the connection(s) and wish it to be returned to the pool, you call
|
61
|
-
# ActiveRecord::Base.clear_active_connections!. This will be the
|
62
|
-
# default behavior for Active Record when used in conjunction with
|
63
|
-
# Action Pack's request handling cycle.
|
64
|
-
# 2. Manually check out a connection from the pool with
|
65
|
-
# ActiveRecord::Base.connection_pool.checkout. You are responsible for
|
66
|
-
# returning this connection to the pool when finished by calling
|
67
|
-
# ActiveRecord::Base.connection_pool.checkin(connection).
|
68
|
-
# 3. Use ActiveRecord::Base.connection_pool.with_connection(&block), which
|
69
|
-
# obtains a connection, yields it as the sole argument to the block,
|
70
|
-
# and returns it to the pool after the block completes.
|
71
|
-
#
|
72
|
-
# Connections in the pool are actually AbstractAdapter objects (or objects
|
73
|
-
# compatible with AbstractAdapter's interface).
|
74
|
-
#
|
75
|
-
# == Options
|
76
|
-
#
|
77
|
-
# There are several connection-pooling-related options that you can add to
|
78
|
-
# your database connection configuration:
|
34
|
+
# And completely redefine ConnectionPool
|
35
|
+
|
36
|
+
class ActiveRecord::ConnectionAdapters::ConnectionPool
|
37
|
+
# Threadsafe, fair, FIFO queue. Meant to be used by ConnectionPool
|
38
|
+
# with which it shares a Monitor. But could be a generic Queue.
|
79
39
|
#
|
80
|
-
#
|
81
|
-
#
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
# * +dead_connection_timeout+: number of seconds from last checkout
|
89
|
-
# after which the Reaper will consider a connection reapable. (default
|
90
|
-
# 5 seconds).
|
91
|
-
class ConnectionPool
|
92
|
-
# Threadsafe, fair, FIFO queue. Meant to be used by ConnectionPool
|
93
|
-
# with which it shares a Monitor. But could be a generic Queue.
|
94
|
-
#
|
95
|
-
# The Queue in stdlib's 'thread' could replace this class except
|
96
|
-
# stdlib's doesn't support waiting with a timeout.
|
97
|
-
class Queue
|
98
|
-
def initialize(lock = Monitor.new)
|
99
|
-
@lock = lock
|
100
|
-
@cond = @lock.new_cond
|
101
|
-
@num_waiting = 0
|
102
|
-
@queue = []
|
103
|
-
end
|
104
|
-
|
105
|
-
# Test if any threads are currently waiting on the queue.
|
106
|
-
def any_waiting?
|
107
|
-
synchronize do
|
108
|
-
@num_waiting > 0
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
|
-
# Return the number of threads currently waiting on this
|
113
|
-
# queue.
|
114
|
-
def num_waiting
|
115
|
-
synchronize do
|
116
|
-
@num_waiting
|
117
|
-
end
|
118
|
-
end
|
119
|
-
|
120
|
-
# Add +element+ to the queue. Never blocks.
|
121
|
-
def add(element)
|
122
|
-
synchronize do
|
123
|
-
@queue.push element
|
124
|
-
@cond.signal
|
125
|
-
end
|
126
|
-
end
|
127
|
-
|
128
|
-
# If +element+ is in the queue, remove and return it, or nil.
|
129
|
-
def delete(element)
|
130
|
-
synchronize do
|
131
|
-
@queue.delete(element)
|
132
|
-
end
|
133
|
-
end
|
134
|
-
|
135
|
-
# Remove all elements from the queue.
|
136
|
-
def clear
|
137
|
-
synchronize do
|
138
|
-
@queue.clear
|
139
|
-
end
|
140
|
-
end
|
141
|
-
|
142
|
-
# Remove the head of the queue.
|
143
|
-
#
|
144
|
-
# If +timeout+ is not given, remove and return the head the
|
145
|
-
# queue if the number of available elements is strictly
|
146
|
-
# greater than the number of threads currently waiting (that
|
147
|
-
# is, don't jump ahead in line). Otherwise, return nil.
|
148
|
-
#
|
149
|
-
# If +timeout+ is given, block if it there is no element
|
150
|
-
# available, waiting up to +timeout+ seconds for an element to
|
151
|
-
# become available.
|
152
|
-
#
|
153
|
-
# Raises:
|
154
|
-
# - ConnectionTimeoutError if +timeout+ is given and no element
|
155
|
-
# becomes available after +timeout+ seconds,
|
156
|
-
def poll(timeout = nil)
|
157
|
-
synchronize do
|
158
|
-
if timeout
|
159
|
-
no_wait_poll || wait_poll(timeout)
|
160
|
-
else
|
161
|
-
no_wait_poll
|
162
|
-
end
|
163
|
-
end
|
164
|
-
end
|
165
|
-
|
166
|
-
private
|
167
|
-
|
168
|
-
def synchronize(&block)
|
169
|
-
@lock.synchronize(&block)
|
170
|
-
end
|
171
|
-
|
172
|
-
# Test if the queue currently contains any elements.
|
173
|
-
def any?
|
174
|
-
!@queue.empty?
|
175
|
-
end
|
176
|
-
|
177
|
-
# A thread can remove an element from the queue without
|
178
|
-
# waiting if an only if the number of currently available
|
179
|
-
# connections is strictly greater than the number of waiting
|
180
|
-
# threads.
|
181
|
-
def can_remove_no_wait?
|
182
|
-
@queue.size > @num_waiting
|
183
|
-
end
|
184
|
-
|
185
|
-
# Removes and returns the head of the queue if possible, or nil.
|
186
|
-
def remove
|
187
|
-
@queue.shift
|
188
|
-
end
|
189
|
-
|
190
|
-
# Remove and return the head the queue if the number of
|
191
|
-
# available elements is strictly greater than the number of
|
192
|
-
# threads currently waiting. Otherwise, return nil.
|
193
|
-
def no_wait_poll
|
194
|
-
remove if can_remove_no_wait?
|
195
|
-
end
|
196
|
-
|
197
|
-
# Waits on the queue up to +timeout+ seconds, then removes and
|
198
|
-
# returns the head of the queue.
|
199
|
-
def wait_poll(timeout)
|
200
|
-
@num_waiting += 1
|
201
|
-
|
202
|
-
t0 = Time.now
|
203
|
-
elapsed = 0
|
204
|
-
loop do
|
205
|
-
@cond.wait(timeout - elapsed)
|
206
|
-
|
207
|
-
return remove if any?
|
208
|
-
|
209
|
-
elapsed = Time.now - t0
|
210
|
-
raise ConnectionTimeoutError if elapsed >= timeout
|
211
|
-
end
|
212
|
-
ensure
|
213
|
-
@num_waiting -= 1
|
214
|
-
end
|
215
|
-
end
|
216
|
-
|
217
|
-
|
218
|
-
include MonitorMixin
|
219
|
-
|
220
|
-
attr_accessor :automatic_reconnect, :checkout_timeout, :dead_connection_timeout
|
221
|
-
attr_reader :spec, :connections, :size, :reaper
|
222
|
-
|
223
|
-
# Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification
|
224
|
-
# object which describes database connection information (e.g. adapter,
|
225
|
-
# host name, username, password, etc), as well as the maximum size for
|
226
|
-
# this ConnectionPool.
|
227
|
-
#
|
228
|
-
# The default ConnectionPool maximum size is 5.
|
229
|
-
def initialize(spec)
|
230
|
-
super()
|
231
|
-
|
232
|
-
@spec = spec
|
233
|
-
|
234
|
-
# The cache of reserved connections mapped to threads
|
235
|
-
@reserved_connections = {}
|
236
|
-
|
237
|
-
@checkout_timeout = spec.config[:checkout_timeout] || 5
|
238
|
-
|
239
|
-
|
240
|
-
# default max pool size to 5
|
241
|
-
@size = (spec.config[:pool] && spec.config[:pool].to_i) || 5
|
242
|
-
|
243
|
-
@connections = []
|
244
|
-
@automatic_reconnect = true
|
245
|
-
|
246
|
-
@available = Queue.new self
|
40
|
+
# The Queue in stdlib's 'thread' could replace this class except
|
41
|
+
# stdlib's doesn't support waiting with a timeout.
|
42
|
+
class Queue
|
43
|
+
def initialize(lock = Monitor.new)
|
44
|
+
@lock = lock
|
45
|
+
@cond = @lock.new_cond
|
46
|
+
@num_waiting = 0
|
47
|
+
@queue = []
|
247
48
|
end
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
# #checkout to obtain one if necessary.
|
252
|
-
#
|
253
|
-
# #connection can be called any number of times; the connection is
|
254
|
-
# held in a hash keyed by the thread id.
|
255
|
-
def connection
|
49
|
+
|
50
|
+
# Test if any threads are currently waiting on the queue.
|
51
|
+
def any_waiting?
|
256
52
|
synchronize do
|
257
|
-
@
|
53
|
+
@num_waiting > 0
|
258
54
|
end
|
259
55
|
end
|
260
|
-
|
261
|
-
#
|
262
|
-
|
56
|
+
|
57
|
+
# Return the number of threads currently waiting on this
|
58
|
+
# queue.
|
59
|
+
def num_waiting
|
263
60
|
synchronize do
|
264
|
-
@
|
265
|
-
return false
|
266
|
-
}.in_use?
|
61
|
+
@num_waiting
|
267
62
|
end
|
268
63
|
end
|
269
|
-
|
270
|
-
#
|
271
|
-
|
272
|
-
# and returns the connection to the pool.
|
273
|
-
def release_connection(with_id = current_connection_id)
|
64
|
+
|
65
|
+
# Add +element+ to the queue. Never blocks.
|
66
|
+
def add(element)
|
274
67
|
synchronize do
|
275
|
-
|
276
|
-
|
68
|
+
@queue.push element
|
69
|
+
@cond.signal
|
277
70
|
end
|
278
71
|
end
|
279
|
-
|
280
|
-
# If
|
281
|
-
|
282
|
-
# connection when finished.
|
283
|
-
def with_connection
|
284
|
-
connection_id = current_connection_id
|
285
|
-
fresh_connection = true unless active_connection?
|
286
|
-
yield connection
|
287
|
-
ensure
|
288
|
-
release_connection(connection_id) if fresh_connection
|
289
|
-
end
|
290
|
-
|
291
|
-
# Returns true if a connection has already been opened.
|
292
|
-
def connected?
|
293
|
-
synchronize { @connections.any? }
|
294
|
-
end
|
295
|
-
|
296
|
-
# Disconnects all connections in the pool, and clears the pool.
|
297
|
-
def disconnect!
|
72
|
+
|
73
|
+
# If +element+ is in the queue, remove and return it, or nil.
|
74
|
+
def delete(element)
|
298
75
|
synchronize do
|
299
|
-
@
|
300
|
-
@connections.each do |conn|
|
301
|
-
checkin conn
|
302
|
-
conn.disconnect!
|
303
|
-
end
|
304
|
-
@connections = []
|
305
|
-
@available.clear
|
76
|
+
@queue.delete(element)
|
306
77
|
end
|
307
78
|
end
|
308
|
-
|
309
|
-
#
|
310
|
-
def
|
79
|
+
|
80
|
+
# Remove all elements from the queue.
|
81
|
+
def clear
|
311
82
|
synchronize do
|
312
|
-
@
|
313
|
-
@connections.each do |conn|
|
314
|
-
checkin conn
|
315
|
-
conn.disconnect! if conn.requires_reloading?
|
316
|
-
end
|
317
|
-
@connections.delete_if do |conn|
|
318
|
-
conn.requires_reloading?
|
319
|
-
end
|
320
|
-
@available.clear
|
321
|
-
@connections.each do |conn|
|
322
|
-
@available.add conn
|
323
|
-
end
|
83
|
+
@queue.clear
|
324
84
|
end
|
325
85
|
end
|
326
|
-
|
327
|
-
|
328
|
-
# Check-out a database connection from the pool, indicating that you want
|
329
|
-
# to use it. You should call #checkin when you no longer need this.
|
330
|
-
#
|
331
|
-
# This is done by either returning and leasing existing connection, or by
|
332
|
-
# creating a new connection and leasing it.
|
86
|
+
|
87
|
+
# Remove the head of the queue.
|
333
88
|
#
|
334
|
-
# If
|
335
|
-
# number of
|
336
|
-
#
|
89
|
+
# If +timeout+ is not given, remove and return the head the
|
90
|
+
# queue if the number of available elements is strictly
|
91
|
+
# greater than the number of threads currently waiting (that
|
92
|
+
# is, don't jump ahead in line). Otherwise, return nil.
|
337
93
|
#
|
338
|
-
#
|
94
|
+
# If +timeout+ is given, block if it there is no element
|
95
|
+
# available, waiting up to +timeout+ seconds for an element to
|
96
|
+
# become available.
|
339
97
|
#
|
340
98
|
# Raises:
|
341
|
-
# - ConnectionTimeoutError
|
342
|
-
|
343
|
-
|
344
|
-
conn = acquire_connection
|
345
|
-
conn.lease
|
346
|
-
checkout_and_verify(conn)
|
347
|
-
end
|
348
|
-
end
|
349
|
-
|
350
|
-
# Check-in a database connection back into the pool, indicating that you
|
351
|
-
# no longer need this connection.
|
352
|
-
#
|
353
|
-
# +conn+: an AbstractAdapter object, which was obtained by earlier by
|
354
|
-
# calling +checkout+ on this pool.
|
355
|
-
def checkin(conn)
|
99
|
+
# - ConnectionTimeoutError if +timeout+ is given and no element
|
100
|
+
# becomes available after +timeout+ seconds,
|
101
|
+
def poll(timeout = nil)
|
356
102
|
synchronize do
|
357
|
-
|
358
|
-
|
103
|
+
if timeout
|
104
|
+
no_wait_poll || wait_poll(timeout)
|
105
|
+
else
|
106
|
+
no_wait_poll
|
359
107
|
end
|
360
|
-
|
361
|
-
release conn
|
362
|
-
|
363
|
-
@available.add conn
|
364
108
|
end
|
365
109
|
end
|
366
|
-
|
367
|
-
|
368
|
-
# remain open and active but will no longer be managed by this pool.
|
369
|
-
def remove(conn)
|
110
|
+
|
111
|
+
def num_available
|
370
112
|
synchronize do
|
371
|
-
@
|
372
|
-
@available.delete conn
|
373
|
-
|
374
|
-
# FIXME: we might want to store the key on the connection so that removing
|
375
|
-
# from the reserved hash will be a little easier.
|
376
|
-
release conn
|
377
|
-
|
378
|
-
@available.add checkout_new_connection if @available.any_waiting?
|
113
|
+
@queue.size
|
379
114
|
end
|
380
115
|
end
|
381
|
-
|
382
|
-
|
383
|
-
|
116
|
+
|
384
117
|
private
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
#
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
|
405
|
-
|
406
|
-
|
118
|
+
|
119
|
+
def synchronize(&block)
|
120
|
+
@lock.synchronize(&block)
|
121
|
+
end
|
122
|
+
|
123
|
+
# Test if the queue currently contains any elements.
|
124
|
+
def any?
|
125
|
+
!@queue.empty?
|
126
|
+
end
|
127
|
+
|
128
|
+
# A thread can remove an element from the queue without
|
129
|
+
# waiting if an only if the number of currently available
|
130
|
+
# connections is strictly greater than the number of waiting
|
131
|
+
# threads.
|
132
|
+
def can_remove_no_wait?
|
133
|
+
@queue.size > @num_waiting
|
134
|
+
end
|
135
|
+
|
136
|
+
# Removes and returns the head of the queue if possible, or nil.
|
137
|
+
def remove
|
138
|
+
@queue.shift
|
139
|
+
end
|
140
|
+
|
141
|
+
# Remove and return the head the queue if the number of
|
142
|
+
# available elements is strictly greater than the number of
|
143
|
+
# threads currently waiting. Otherwise, return nil.
|
144
|
+
def no_wait_poll
|
145
|
+
remove if can_remove_no_wait?
|
146
|
+
end
|
147
|
+
|
148
|
+
# Waits on the queue up to +timeout+ seconds, then removes and
|
149
|
+
# returns the head of the queue.
|
150
|
+
def wait_poll(timeout)
|
151
|
+
@num_waiting += 1
|
152
|
+
|
153
|
+
t0 = Time.now
|
154
|
+
elapsed = 0
|
155
|
+
loop do
|
156
|
+
@cond.wait(timeout - elapsed)
|
157
|
+
|
158
|
+
return remove if any?
|
159
|
+
|
160
|
+
elapsed = Time.now - t0
|
161
|
+
raise ActiveRecord::ConnectionTimeoutError if elapsed >= timeout
|
162
|
+
end
|
163
|
+
ensure
|
164
|
+
@num_waiting -= 1
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
# Every +frequency+ seconds, the reaper will call +reap+ on +pool+.
|
169
|
+
# A reaper instantiated with a nil frequency will never reap the
|
170
|
+
# connection pool.
|
171
|
+
#
|
172
|
+
# Configure the frequency by setting "reaping_frequency" in your
|
173
|
+
# database yaml file.
|
174
|
+
class Reaper
|
175
|
+
attr_reader :pool, :frequency
|
176
|
+
|
177
|
+
def initialize(pool, frequency)
|
178
|
+
@pool = pool
|
179
|
+
@frequency = frequency
|
180
|
+
end
|
181
|
+
|
182
|
+
def run
|
183
|
+
return unless frequency
|
184
|
+
Thread.new(frequency, pool) { |t, p|
|
185
|
+
while true
|
186
|
+
sleep t
|
187
|
+
p.reap
|
407
188
|
end
|
189
|
+
}
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
include MonitorMixin
|
194
|
+
|
195
|
+
attr_accessor :automatic_reconnect, :checkout_timeout, :dead_connection_timeout
|
196
|
+
attr_reader :spec, :connections, :size, :reaper
|
197
|
+
|
198
|
+
# Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification
|
199
|
+
# object which describes database connection information (e.g. adapter,
|
200
|
+
# host name, username, password, etc), as well as the maximum size for
|
201
|
+
# this ConnectionPool.
|
202
|
+
#
|
203
|
+
# The default ConnectionPool maximum size is 5.
|
204
|
+
def initialize(spec)
|
205
|
+
super()
|
206
|
+
|
207
|
+
@spec = spec
|
208
|
+
|
209
|
+
# The cache of reserved connections mapped to threads
|
210
|
+
@reserved_connections = {}
|
211
|
+
|
212
|
+
@checkout_timeout = spec.config[:checkout_timeout] || 5
|
213
|
+
@dead_connection_timeout = spec.config[:dead_connection_timeout]
|
214
|
+
@reaper = Reaper.new self, spec.config[:reaping_frequency]
|
215
|
+
@reaper.run
|
216
|
+
|
217
|
+
# default max pool size to 5
|
218
|
+
@size = (spec.config[:pool] && spec.config[:pool].to_i) || 5
|
219
|
+
|
220
|
+
@connections = []
|
221
|
+
@automatic_reconnect = true
|
222
|
+
|
223
|
+
@available = Queue.new self
|
224
|
+
end
|
225
|
+
|
226
|
+
# Hack for tests to be able to add connections. Do not call outside of tests
|
227
|
+
def insert_connection_for_test!(c) #:nodoc:
|
228
|
+
synchronize do
|
229
|
+
@connections << c
|
230
|
+
@available.add c
|
231
|
+
end
|
232
|
+
end
|
233
|
+
|
234
|
+
# Retrieve the connection associated with the current thread, or call
|
235
|
+
# #checkout to obtain one if necessary.
|
236
|
+
#
|
237
|
+
# #connection can be called any number of times; the connection is
|
238
|
+
# held in a hash keyed by the thread id.
|
239
|
+
def connection
|
240
|
+
synchronize do
|
241
|
+
@reserved_connections[current_connection_id] ||= checkout
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
# Is there an open connection that is being used for the current thread?
|
246
|
+
def active_connection?
|
247
|
+
synchronize do
|
248
|
+
@reserved_connections.fetch(current_connection_id) {
|
249
|
+
return false
|
250
|
+
}.in_use?
|
251
|
+
end
|
252
|
+
end
|
253
|
+
|
254
|
+
# Signal that the thread is finished with the current connection.
|
255
|
+
# #release_connection releases the connection-thread association
|
256
|
+
# and returns the connection to the pool.
|
257
|
+
def release_connection(with_id = current_connection_id)
|
258
|
+
synchronize do
|
259
|
+
conn = @reserved_connections.delete(with_id)
|
260
|
+
checkin conn if conn
|
261
|
+
end
|
262
|
+
end
|
263
|
+
|
264
|
+
# If a connection already exists yield it to the block. If no connection
|
265
|
+
# exists checkout a connection, yield it to the block, and checkin the
|
266
|
+
# connection when finished.
|
267
|
+
def with_connection
|
268
|
+
connection_id = current_connection_id
|
269
|
+
fresh_connection = true unless active_connection?
|
270
|
+
yield connection
|
271
|
+
ensure
|
272
|
+
release_connection(connection_id) if fresh_connection
|
273
|
+
end
|
274
|
+
|
275
|
+
# Returns true if a connection has already been opened.
|
276
|
+
def connected?
|
277
|
+
synchronize { @connections.any? }
|
278
|
+
end
|
279
|
+
|
280
|
+
# Disconnects all connections in the pool, and clears the pool.
|
281
|
+
def disconnect!
|
282
|
+
synchronize do
|
283
|
+
@reserved_connections = {}
|
284
|
+
@connections.each do |conn|
|
285
|
+
checkin conn
|
286
|
+
conn.disconnect!
|
408
287
|
end
|
288
|
+
@connections = []
|
289
|
+
@available.clear
|
409
290
|
end
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
291
|
+
end
|
292
|
+
|
293
|
+
# Clears the cache which maps classes.
|
294
|
+
def clear_reloadable_connections!
|
295
|
+
synchronize do
|
296
|
+
@reserved_connections = {}
|
297
|
+
@connections.each do |conn|
|
298
|
+
checkin conn
|
299
|
+
conn.disconnect! if conn.requires_reloading?
|
300
|
+
end
|
301
|
+
@connections.delete_if do |conn|
|
302
|
+
conn.requires_reloading?
|
303
|
+
end
|
304
|
+
@available.clear
|
305
|
+
@connections.each do |conn|
|
306
|
+
@available.add conn
|
418
307
|
end
|
419
|
-
|
420
|
-
@reserved_connections.delete thread_id if thread_id
|
421
308
|
end
|
422
|
-
|
423
|
-
|
424
|
-
|
309
|
+
end
|
310
|
+
|
311
|
+
# clear_stale_cached imp from Rails 3.2, still using Threads.
|
312
|
+
# Yes, we've created a monster.
|
313
|
+
# Return any checked-out connections back to the pool by threads that
|
314
|
+
# are no longer alive.
|
315
|
+
def clear_stale_cached_connections!
|
316
|
+
keys = @reserved_connections.keys - Thread.list.find_all { |t|
|
317
|
+
t.alive?
|
318
|
+
}.map { |thread| thread.object_id }
|
319
|
+
keys.each do |key|
|
320
|
+
conn = @reserved_connections[key]
|
321
|
+
ActiveSupport::Deprecation.warn(<<-eowarn) if conn.in_use?
|
322
|
+
Database connections will not be closed automatically, please close your
|
323
|
+
database connection at the end of the thread by calling `close` on your
|
324
|
+
connection. For example: ActiveRecord::Base.connection.close
|
325
|
+
eowarn
|
326
|
+
checkin conn
|
327
|
+
@reserved_connections.delete(key)
|
425
328
|
end
|
426
|
-
|
427
|
-
|
428
|
-
|
329
|
+
end
|
330
|
+
|
331
|
+
# Check-out a database connection from the pool, indicating that you want
|
332
|
+
# to use it. You should call #checkin when you no longer need this.
|
333
|
+
#
|
334
|
+
# This is done by either returning and leasing existing connection, or by
|
335
|
+
# creating a new connection and leasing it.
|
336
|
+
#
|
337
|
+
# If all connections are leased and the pool is at capacity (meaning the
|
338
|
+
# number of currently leased connections is greater than or equal to the
|
339
|
+
# size limit set), an ActiveRecord::PoolFullError exception will be raised.
|
340
|
+
#
|
341
|
+
# Returns: an AbstractAdapter object.
|
342
|
+
#
|
343
|
+
# Raises:
|
344
|
+
# - PoolFullError: no connection can be obtained from the pool.
|
345
|
+
def checkout
|
346
|
+
synchronize do
|
347
|
+
conn = acquire_connection
|
348
|
+
conn.lease
|
349
|
+
checkout_and_verify(conn)
|
429
350
|
end
|
430
|
-
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
351
|
+
end
|
352
|
+
|
353
|
+
# Check-in a database connection back into the pool, indicating that you
|
354
|
+
# no longer need this connection.
|
355
|
+
#
|
356
|
+
# +conn+: an AbstractAdapter object, which was obtained by earlier by
|
357
|
+
# calling +checkout+ on this pool.
|
358
|
+
def checkin(conn)
|
359
|
+
synchronize do
|
360
|
+
conn.run_callbacks :checkin do
|
361
|
+
conn.expire
|
362
|
+
end
|
363
|
+
|
364
|
+
release conn
|
365
|
+
|
366
|
+
@available.add conn
|
438
367
|
end
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
368
|
+
end
|
369
|
+
|
370
|
+
# Remove a connection from the connection pool. The connection will
|
371
|
+
# remain open and active but will no longer be managed by this pool.
|
372
|
+
def remove(conn)
|
373
|
+
synchronize do
|
374
|
+
@connections.delete conn
|
375
|
+
@available.delete conn
|
376
|
+
|
377
|
+
# FIXME: we might want to store the key on the connection so that removing
|
378
|
+
# from the reserved hash will be a little easier.
|
379
|
+
release conn
|
380
|
+
|
381
|
+
@available.add checkout_new_connection if @available.any_waiting?
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
# Removes dead connections from the pool. A dead connection can occur
|
386
|
+
# if a programmer forgets to close a connection at the end of a thread
|
387
|
+
# or a thread dies unexpectedly.
|
388
|
+
def reap
|
389
|
+
synchronize do
|
390
|
+
stale = Time.now - @dead_connection_timeout
|
391
|
+
connections.dup.each do |conn|
|
392
|
+
remove conn if conn.in_use? && stale > conn.last_use && !conn.active?
|
443
393
|
end
|
444
|
-
c
|
445
394
|
end
|
446
395
|
end
|
447
|
-
|
448
|
-
|
396
|
+
|
397
|
+
private
|
398
|
+
|
399
|
+
# Acquire a connection by one of 1) immediately removing one
|
400
|
+
# from the queue of available connections, 2) creating a new
|
401
|
+
# connection if the pool is not at capacity, 3) waiting on the
|
402
|
+
# queue for a connection to become available.
|
403
|
+
#
|
404
|
+
# Raises:
|
405
|
+
# - PoolFullError if a connection could not be acquired (FIXME:
|
406
|
+
# why not ConnectionTimeoutError?
|
407
|
+
def acquire_connection
|
408
|
+
if conn = @available.poll
|
409
|
+
conn
|
410
|
+
elsif @connections.size < @size
|
411
|
+
checkout_new_connection
|
412
|
+
else
|
413
|
+
clear_stale_cached_connections!
|
414
|
+
|
415
|
+
t0 = Time.now
|
416
|
+
|
417
|
+
Rails.logger.info("POLLED_CHECKOUT: num avail connections: #{@available.num_available}; num waiting: #{@available.num_waiting}; total connections: #{@connections.size}")
|
418
|
+
begin
|
419
|
+
@available.poll(@checkout_timeout)
|
420
|
+
rescue ActiveRecord::ConnectionTimeoutError
|
421
|
+
msg = 'could not obtain a database connection within %0.3f seconds (waited %0.3f seconds)' %
|
422
|
+
[@checkout_timeout, Time.now - t0]
|
423
|
+
raise ActiveRecord::ConnectionTimeoutError, msg
|
424
|
+
end
|
425
|
+
end
|
426
|
+
end
|
427
|
+
|
428
|
+
def release(conn)
|
429
|
+
thread_id = if @reserved_connections[current_connection_id] == conn
|
430
|
+
current_connection_id
|
431
|
+
else
|
432
|
+
@reserved_connections.keys.find { |k|
|
433
|
+
@reserved_connections[k] == conn
|
434
|
+
}
|
435
|
+
end
|
436
|
+
|
437
|
+
@reserved_connections.delete thread_id if thread_id
|
438
|
+
end
|
439
|
+
|
440
|
+
def new_connection
|
441
|
+
ActiveRecord::Base.send(spec.adapter_method, spec.config)
|
442
|
+
end
|
443
|
+
|
444
|
+
def current_connection_id #:nodoc:
|
445
|
+
ActiveRecord::Base.connection_id ||= Thread.current.object_id
|
446
|
+
end
|
447
|
+
|
448
|
+
def checkout_new_connection
|
449
|
+
raise ConnectionNotEstablished unless @automatic_reconnect
|
450
|
+
|
451
|
+
c = new_connection
|
452
|
+
c.pool = self
|
453
|
+
@connections << c
|
454
|
+
c
|
455
|
+
end
|
456
|
+
|
457
|
+
def checkout_and_verify(c)
|
458
|
+
c.run_callbacks :checkout do
|
459
|
+
c.verify!
|
460
|
+
end
|
461
|
+
c
|
462
|
+
end
|
449
463
|
end
|
450
|
-
end
|
451
464
|
|
452
465
|
end
|
453
466
|
|
@@ -156,8 +156,8 @@ module SearchMethods
|
|
156
156
|
|
157
157
|
extra_info_xml = Nokogiri::XML( sfx_obj["EXTRA_INFO_XML"] )
|
158
158
|
|
159
|
-
# Put SFX object id in rft.object_id, that's what SFX does.
|
160
|
-
ctx.referent.set_metadata('object_id', sfx_obj["OBJECT_ID"])
|
159
|
+
# Put SFX object id in rft.object_id, that's what SFX does.
|
160
|
+
ctx.referent.set_metadata('object_id', sfx_obj["OBJECT_ID"].to_s )
|
161
161
|
ctx.referent.set_metadata("jtitle", sfx_obj["TITLE_DISPLAY"] || "Unknown Title")
|
162
162
|
|
163
163
|
issn = extra_info_xml.search("item[key=issn]").text
|
data/app/models/collection.rb
CHANGED
@@ -60,7 +60,7 @@ class Collection
|
|
60
60
|
# Then actually executes the services that are dispatchable (queued).
|
61
61
|
def dispatch_services!
|
62
62
|
queued_service_ids = prepare_for_dispatch!
|
63
|
-
|
63
|
+
|
64
64
|
dispatch_foreground!(queued_service_ids)
|
65
65
|
|
66
66
|
dispatch_background!(queued_service_ids)
|
@@ -152,68 +152,70 @@ class Collection
|
|
152
152
|
def prepare_for_dispatch!
|
153
153
|
# Go through currently dispatched services, looking for timed out
|
154
154
|
# services -- services still in progress that have taken too long,
|
155
|
-
# as well as service responses that are too old to be used.
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
155
|
+
# as well as service responses that are too old to be used.
|
156
|
+
queued_service_ids = []
|
157
|
+
DispatchedService.transaction do
|
158
|
+
umlaut_request.dispatched_services.each do | ds |
|
159
|
+
# go through dispatched_services and set stil in progress but too long to failed temporary
|
160
|
+
if ( (ds.status == DispatchedService::InProgress ||
|
161
|
+
ds.status == DispatchedService::Queued ) &&
|
162
|
+
(Time.now - ds.updated_at) > self.background_service_timeout)
|
163
|
+
|
164
|
+
ds.store_exception( Exception.new("background service timed out (took longer than #{self.background_service_timeout} to run); thread assumed dead.")) unless ds.exception_info
|
165
|
+
# Fail it temporary, it'll be run again.
|
166
|
+
ds.status = DispatchedService::FailedTemporary
|
167
|
+
ds.save!
|
168
|
+
logger.warn("Background service timed out, thread assumed dead. #{umlaut_request.id} / #{ds.service_id}")
|
169
|
+
end
|
170
|
+
|
171
|
+
# go through dispatched_services and delete:
|
172
|
+
# 1) old completed dispatches, too old to use.
|
173
|
+
# 2) failedtemporary dispatches that are older than our resurrection time
|
174
|
+
# -> And all responses associated with those dispatches.
|
175
|
+
# After being deleted, they'll end up re-queued.
|
176
|
+
if ( (ds.completed? && completed_dispatch_expired?(ds) ) ||
|
177
|
+
( ds.status == DispatchedService::FailedTemporary &&
|
178
|
+
(Time.now - ds.updated_at) > self.requeue_failedtemporary_services
|
179
|
+
)
|
177
180
|
)
|
178
|
-
|
181
|
+
|
182
|
+
# Need to expire. Delete all the service responses, and
|
183
|
+
# the DispatchedService record, and service will be automatically
|
184
|
+
# run again.
|
185
|
+
serv_id = ds.service_id
|
179
186
|
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
umlaut_request.service_responses.each do |response|
|
186
|
-
if response.service_id == serv_id
|
187
|
-
umlaut_request.service_responses.delete(response)
|
188
|
-
response.destroy
|
187
|
+
umlaut_request.service_responses.each do |response|
|
188
|
+
if response.service_id == serv_id
|
189
|
+
umlaut_request.service_responses.delete(response)
|
190
|
+
response.destroy
|
191
|
+
end
|
189
192
|
end
|
193
|
+
|
194
|
+
umlaut_request.dispatched_services.delete(ds)
|
195
|
+
ds.destroy
|
190
196
|
end
|
197
|
+
end
|
191
198
|
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
queued_service_ids.push(service_id) if found.status == DispatchedService::Queued
|
211
|
-
else
|
212
|
-
umlaut_request.new_dispatch_object!(service_id, DispatchedService::Queued).save!
|
213
|
-
queued_service_ids.push(service_id)
|
214
|
-
end
|
199
|
+
# Queue any services without a dispatch marker at all, keeping
|
200
|
+
# track of queued services, already existing or newly created.
|
201
|
+
|
202
|
+
# Just in case, we're going to refetch dispatched_services from the db,
|
203
|
+
# in case some other http request or background service updated things
|
204
|
+
# recently.
|
205
|
+
umlaut_request.dispatched_services.reset
|
206
|
+
|
207
|
+
self.get_service_definitions.each do |service|
|
208
|
+
service_id = service['service_id']
|
209
|
+
# use in-memory #to_a search, don't go to db each time!
|
210
|
+
if found = umlaut_request.dispatched_services.to_a.find {|s| s.service_id == service_id}
|
211
|
+
queued_service_ids.push(service_id) if found.status == DispatchedService::Queued
|
212
|
+
else
|
213
|
+
umlaut_request.new_dispatch_object!(service_id, DispatchedService::Queued).save!
|
214
|
+
queued_service_ids.push(service_id)
|
215
|
+
end
|
216
|
+
end
|
215
217
|
end
|
216
|
-
|
218
|
+
|
217
219
|
return queued_service_ids
|
218
220
|
end
|
219
221
|
|
@@ -109,6 +109,17 @@ class ServiceResponse < ActiveRecord::Base
|
|
109
109
|
self.service.view_data_from_service_type(self)
|
110
110
|
end
|
111
111
|
|
112
|
+
|
113
|
+
def service_data
|
114
|
+
# Fix weird-ass char encoding bug with AR serialize and hashes.
|
115
|
+
# https://github.com/rails/rails/issues/6538
|
116
|
+
data = super
|
117
|
+
if data.kind_of? Hash
|
118
|
+
data.values.each {|v| v.force_encoding "UTF-8" if v.respond_to? :force_encoding }
|
119
|
+
end
|
120
|
+
return data
|
121
|
+
end
|
122
|
+
|
112
123
|
# Should take a ServiceTypeValue object, or symbol name of
|
113
124
|
# ServiceTypeValue object.
|
114
125
|
def service_type_value=(value)
|
data/app/models/service_wave.rb
CHANGED
@@ -95,7 +95,7 @@ class ServiceWave
|
|
95
95
|
|
96
96
|
# Log it too, although experience shows it may never make it to the
|
97
97
|
# log for mysterious reasons.
|
98
|
-
Rails.logger.error(TermColor.color("Umlaut: Threaded service raised exception.", :red, true) + "Service: #{service.service_id}, #{e}\n #{clean_backtrace(e).join("\n ")}")
|
98
|
+
Rails.logger.error(TermColor.color("Umlaut: Threaded service raised exception.", :red, true) + " Service: #{service.service_id}, #{e.class} #{e.message}\n #{clean_backtrace(e).join("\n ")}")
|
99
99
|
|
100
100
|
# And stick it in a thread variable too
|
101
101
|
Thread.current[:exception] = e
|
@@ -131,7 +131,7 @@ class HathiTrust < Service
|
|
131
131
|
isbn = isbn.gsub(/[\-\[\]]/, '') unless isbn.blank?
|
132
132
|
|
133
133
|
oclcnum = get_identifier(:info, "oclcnum", rft)
|
134
|
-
oclcnum =
|
134
|
+
oclcnum = oclcnum.gsub(/[\-\[\]]/, '') unless oclcnum.blank?
|
135
135
|
|
136
136
|
lccn = get_lccn(rft)
|
137
137
|
lccn = lccn.gsub(/[\-\[\]]/, '') unless lccn.blank?
|
@@ -39,7 +39,7 @@ namespace :umlaut do
|
|
39
39
|
# dynamically create a model for Permalinks in old
|
40
40
|
# db, set up with connection info to old db.
|
41
41
|
OldPermalink = Class.new(ActiveRecord::Base) do
|
42
|
-
self.
|
42
|
+
self.table_name = "permalinks"
|
43
43
|
# just to be safe
|
44
44
|
def read_only?
|
45
45
|
true
|
data/lib/umlaut.rb
CHANGED
@@ -38,9 +38,9 @@ module Umlaut
|
|
38
38
|
|
39
39
|
# Patch with fixed 'fair' version of ConnectionPool, see
|
40
40
|
# active_record_patch/connection_pool.rb
|
41
|
-
initializer("#{engine_name}.patch_connection_pool", :before => "active_record.initialize_database") do |app|
|
41
|
+
#initializer("#{engine_name}.patch_connection_pool", :before => "active_record.initialize_database") do |app|
|
42
42
|
load File.join(self.root, "active_record_patch", "connection_pool.rb")
|
43
|
-
end
|
43
|
+
#end
|
44
44
|
|
45
45
|
|
46
46
|
|
data/lib/umlaut/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: umlaut
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 3.0.
|
4
|
+
version: 3.0.0beta10
|
5
5
|
prerelease: 5
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-05-
|
12
|
+
date: 2012-05-30 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rails
|
@@ -318,7 +318,6 @@ files:
|
|
318
318
|
- app/models/referent_value.rb
|
319
319
|
- app/models/service_type_value.rb
|
320
320
|
- app/models/service_store.rb
|
321
|
-
- app/models/#Untitled-1#
|
322
321
|
- app/models/sfx_db.rb
|
323
322
|
- app/models/collection.rb
|
324
323
|
- app/models/dispatched_service.rb
|
@@ -377,6 +376,7 @@ files:
|
|
377
376
|
- app/helpers/resolve_helper.rb
|
378
377
|
- app/helpers/search_helper.rb
|
379
378
|
- db/seeds.rb
|
379
|
+
- db/migrate/02_umlaut_add_service_response_index.rb
|
380
380
|
- db/migrate/01_umlaut_init.rb
|
381
381
|
- db/orig_fixed_data/service_type_values.yml
|
382
382
|
- lib/opensearch_query.rb
|
@@ -558,7 +558,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
558
558
|
version: '0'
|
559
559
|
segments:
|
560
560
|
- 0
|
561
|
-
hash:
|
561
|
+
hash: 2903597674342542131
|
562
562
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
563
563
|
none: false
|
564
564
|
requirements:
|
data/app/models/#Untitled-1#
DELETED
Binary file
|