umlaut 3.0.0beta9 → 3.0.0beta10
Sign up to get free protection for your applications and to get access to all the features.
- 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
|