activerecord-bogacs 0.5.1 → 0.7.1

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.
@@ -1,5 +1,8 @@
1
1
  require 'active_record/version'
2
2
 
3
+ require 'concurrent/atomic/atomic_boolean'
4
+
5
+ require 'active_record/connection_adapters/adapter_compat'
3
6
  require 'active_record/bogacs/pool_support'
4
7
  require 'active_record/bogacs/thread_safe'
5
8
 
@@ -16,17 +19,17 @@ module ActiveRecord
16
19
  attr_reader :size, :spec
17
20
 
18
21
  def initialize(spec)
19
- @connected = nil
22
+ super()
20
23
 
21
24
  @spec = spec
22
25
  @size = nil
23
26
  @automatic_reconnect = nil
27
+ @lock_thread = false
24
28
 
25
- @reserved_connections = ThreadSafe::Map.new #:initial_capacity => @size
26
- end
29
+ @thread_cached_conns = ThreadSafe::Map.new
27
30
 
28
- # @private replacement for attr_reader :connections
29
- def connections; @reserved_connections.values end
31
+ @connected = ::Concurrent::AtomicBoolean.new
32
+ end
30
33
 
31
34
  # @private attr_reader :reaper
32
35
  def reaper; end
@@ -34,26 +37,32 @@ module ActiveRecord
34
37
  # @private
35
38
  def checkout_timeout; end
36
39
 
40
+ # @override
41
+ # def connection_cache_key(owner_thread = Thread.current)
42
+ # owner_thread
43
+ # end
44
+
37
45
  # Retrieve the connection associated with the current thread, or call
38
46
  # #checkout to obtain one if necessary.
39
47
  #
40
48
  # #connection can be called any number of times; the connection is
41
49
  # held in a hash keyed by the thread id.
42
50
  def connection
43
- @reserved_connections[current_connection_id] ||= checkout
51
+ connection_id = connection_cache_key(current_thread)
52
+ @thread_cached_conns[connection_id] ||= checkout
44
53
  end
45
54
 
46
55
  # Is there an open connection that is being used for the current thread?
47
56
  def active_connection?
48
- conn = @reserved_connections[current_connection_id]
49
- conn ? conn.in_use? : false
57
+ connection_id = connection_cache_key(current_thread)
58
+ @thread_cached_conns[connection_id]
50
59
  end
51
60
 
52
61
  # Signal that the thread is finished with the current connection.
53
62
  # #release_connection releases the connection-thread association
54
63
  # and returns the connection to the pool.
55
- def release_connection(with_id = current_connection_id)
56
- conn = @reserved_connections.delete(with_id)
64
+ def release_connection(owner_thread = Thread.current)
65
+ conn = @thread_cached_conns.delete(connection_cache_key(owner_thread))
57
66
  checkin conn if conn
58
67
  end
59
68
 
@@ -61,41 +70,68 @@ module ActiveRecord
61
70
  # exists checkout a connection, yield it to the block, and checkin the
62
71
  # connection when finished.
63
72
  def with_connection
64
- connection_id = current_connection_id
65
- fresh_connection = true unless active_connection?
66
- yield connection
73
+ connection_id = connection_cache_key
74
+ unless conn = @thread_cached_conns[connection_id]
75
+ conn = connection
76
+ fresh_connection = true
77
+ end
78
+ yield conn
67
79
  ensure
68
- release_connection(connection_id) if fresh_connection
80
+ release_connection if fresh_connection
69
81
  end
70
82
 
71
83
  # Returns true if a connection has already been opened.
72
- def connected?; @connected end
84
+ def connected?; @connected.true? end
85
+
86
+ # @private replacement for attr_reader :connections
87
+ def connections; @thread_cached_conns.values end
73
88
 
74
89
  # Disconnects all connections in the pool, and clears the pool.
75
90
  def disconnect!
76
91
  synchronize do
77
- @connected = false
78
-
79
- connections = @reserved_connections.values
80
- @reserved_connections.clear
92
+ @connected.make_false
81
93
 
94
+ connections = @thread_cached_conns.values
95
+ @thread_cached_conns.clear
82
96
  connections.each do |conn|
83
- checkin conn
97
+ if conn.in_use?
98
+ conn.steal!
99
+ checkin conn
100
+ end
84
101
  conn.disconnect!
85
102
  end
86
103
  end
87
104
  end
88
105
 
106
+ def discard! # :nodoc:
107
+ synchronize do
108
+ return if discarded?
109
+ @connected.make_false
110
+
111
+ connections.each do |conn|
112
+ conn.discard!
113
+ end
114
+ @thread_cached_conns = nil
115
+ end
116
+ end
117
+
118
+ def discarded? # :nodoc:
119
+ @thread_cached_conns.nil?
120
+ end
121
+
89
122
  # Clears the cache which maps classes.
90
123
  def clear_reloadable_connections!
91
124
  synchronize do
92
- @connected = false
125
+ @connected.make_false
93
126
 
94
- connections = @reserved_connections.values
95
- @reserved_connections.clear
127
+ connections = @thread_cached_conns.values
128
+ @thread_cached_conns.clear
96
129
 
97
130
  connections.each do |conn|
98
- checkin conn
131
+ if conn.in_use?
132
+ conn.steal!
133
+ checkin conn
134
+ end
99
135
  conn.disconnect! if conn.requires_reloading?
100
136
  end
101
137
  end
@@ -107,7 +143,7 @@ module ActiveRecord
107
143
  def verify_active_connections!
108
144
  synchronize do
109
145
  clear_stale_cached_connections!
110
- @reserved_connections.values.each(&:verify!)
146
+ @thread_cached_conns.values.each(&:verify!)
111
147
  end
112
148
  end if ActiveRecord::VERSION::MAJOR < 4
113
149
 
@@ -115,12 +151,12 @@ module ActiveRecord
115
151
  # are no longer alive.
116
152
  # @private AR 3.2 compatibility
117
153
  def clear_stale_cached_connections!
118
- keys = Thread.list.find_all { |t| t.alive? }.map(&:object_id)
119
- keys = @reserved_connections.keys - keys
154
+ keys = Thread.list.find_all { |t| t.alive? }.map { |t| connection_cache_key(t) }
155
+ keys = @thread_cached_conns.keys - keys
120
156
  keys.each do |key|
121
- if conn = @reserved_connections[key]
122
- checkin conn, false # no release
123
- @reserved_connections.delete(key)
157
+ if conn = @thread_cached_conns[key]
158
+ checkin conn, true # no release
159
+ @thread_cached_conns.delete(key)
124
160
  end
125
161
  end
126
162
  end if ActiveRecord::VERSION::MAJOR < 4
@@ -131,12 +167,12 @@ module ActiveRecord
131
167
  # @return [ActiveRecord::ConnectionAdapters::AbstractAdapter]
132
168
  # @raise [ActiveRecord::ConnectionTimeoutError] no connection can be obtained from the pool
133
169
  def checkout
134
- #synchronize do
135
- conn = checkout_new_connection # acquire_connection
170
+ conn = checkout_new_connection # acquire_connection
171
+ synchronize do
136
172
  conn.lease
137
173
  _run_checkout_callbacks(conn) # checkout_and_verify(conn)
138
- conn
139
- #end
174
+ end
175
+ conn
140
176
  end
141
177
 
142
178
  # Check-in a database connection back into the pool, indicating that you
@@ -145,13 +181,9 @@ module ActiveRecord
145
181
  # @param conn [ActiveRecord::ConnectionAdapters::AbstractAdapter] connection
146
182
  # object, which was obtained earlier by calling #checkout on this pool
147
183
  # @see #checkout
148
- def checkin(conn, do_release = true)
149
- release(conn) if do_release
150
- #synchronize do
151
- _run_checkin_callbacks(conn)
152
- #release conn
153
- #@available.add conn
154
- #end
184
+ def checkin(conn, released = nil)
185
+ release(conn) unless released
186
+ _run_checkin_callbacks(conn)
155
187
  end
156
188
 
157
189
  # Remove a connection from the connection pool. The connection will
@@ -165,6 +197,21 @@ module ActiveRecord
165
197
  # we do not really manage the connection pool - nothing to do ...
166
198
  end
167
199
 
200
+ def flush(minimum_idle = nil)
201
+ # we do not really manage the connection pool
202
+ end
203
+
204
+ def flush!
205
+ reap
206
+ flush(-1)
207
+ end
208
+
209
+ def stat
210
+ {
211
+ connections: connections.size
212
+ }
213
+ end
214
+
168
215
  private
169
216
 
170
217
  # @raise [ActiveRecord::ConnectionTimeoutError]
@@ -181,34 +228,30 @@ module ActiveRecord
181
228
  checkout_new_connection
182
229
  end
183
230
 
184
- def release(conn, owner = nil)
185
- thread_id = owner.object_id unless owner.nil?
231
+ def release(conn, owner = conn.owner)
232
+ thread_id = connection_cache_key(owner) unless owner.nil?
186
233
 
187
234
  thread_id ||=
188
- if @reserved_connections[conn_id = current_connection_id] == conn
235
+ if @thread_cached_conns[conn_id = connection_cache_key].equal?(conn)
189
236
  conn_id
190
237
  else
191
- connections = @reserved_connections
192
- connections.keys.find { |k| connections[k] == conn }
238
+ connections = @thread_cached_conns
239
+ connections.keys.find { |k| connections[k].equal?(conn) }
193
240
  end
194
241
 
195
- @reserved_connections.delete thread_id if thread_id
242
+ @thread_cached_conns.delete_pair(thread_id, conn) if thread_id
196
243
  end
197
244
 
198
245
  def checkout_new_connection
199
- # NOTE: automatic reconnect seems to make no sense for us!
200
- #raise ConnectionNotEstablished unless @automatic_reconnect
201
-
246
+ # NOTE: automatic reconnect makes no sense for us!
202
247
  begin
203
248
  conn = new_connection
204
- rescue ConnectionTimeoutError => e
205
- raise e
206
249
  rescue => e
207
- raise ConnectionTimeoutError, e.message if timeout_error?(e)
250
+ raise ConnectionTimeoutError, e.message if timeout_error?(e) && !e.is_a?(ConnectionTimeoutError)
208
251
  raise e
209
252
  end
253
+ @connected.make_true
210
254
  conn.pool = self
211
- synchronize { @connected = true } if @connected != true
212
255
  conn
213
256
  end
214
257
 
@@ -1,19 +1,33 @@
1
+ require 'active_record/connection_adapters/abstract/query_cache'
1
2
 
2
3
  module ActiveRecord
3
4
  module Bogacs
4
5
  module PoolSupport
5
6
 
6
- #def self.included(base)
7
- #base.send :include, ThreadSafe::Synchronized
8
- #end
7
+ def self.included(base)
8
+ base.send :include, ActiveRecord::ConnectionAdapters::QueryCache::ConnectionPoolConfiguration
9
+ end if ActiveRecord::ConnectionAdapters::QueryCache.const_defined? :ConnectionPoolConfiguration
10
+
11
+ attr_accessor :schema_cache
12
+
13
+ def lock_thread=(lock_thread)
14
+ if lock_thread
15
+ @lock_thread = Thread.current
16
+ else
17
+ @lock_thread = nil
18
+ end
19
+ end if ActiveRecord::VERSION::MAJOR > 4
9
20
 
10
21
  def new_connection
11
- Base.send(spec.adapter_method, spec.config)
22
+ conn = Base.send(spec.adapter_method, spec.config)
23
+ conn.schema_cache = schema_cache.dup if schema_cache && conn.respond_to?(:schema_cache=)
24
+ conn
12
25
  end
13
26
 
14
- def current_connection_id
15
- # NOTE: possible fiber work-around on JRuby ?!
16
- Base.connection_id ||= Thread.current.object_id
27
+ # @override (previously named current_connection_id)
28
+ # @private connection_cache_key for AR (5.2) compatibility
29
+ def connection_cache_key(owner_thread = Thread.current)
30
+ owner_thread.object_id
17
31
  end
18
32
 
19
33
  # @note Method not part of the pre 4.0 API (does no exist).
@@ -37,6 +51,10 @@ module ActiveRecord
37
51
 
38
52
  private
39
53
 
54
+ def current_thread
55
+ @lock_thread || Thread.current
56
+ end
57
+
40
58
  if ActiveRecord::VERSION::STRING > '4.2'
41
59
 
42
60
  def _run_checkin_callbacks(conn)
@@ -89,4 +107,4 @@ module ActiveRecord
89
107
 
90
108
  end
91
109
  end
92
- end
110
+ end
@@ -0,0 +1,17 @@
1
+ module ActiveRecord
2
+ module Bogacs
3
+ class Railtie < Rails::Railtie
4
+
5
+ initializer 'active_record.bogacs', :before => 'active_record.initialize_database' do |_|
6
+ ActiveSupport.on_load :active_record do
7
+ require 'active_record/bogacs'
8
+
9
+ # support for auto-configuring FalsePool (when config[:pool] set to false) :
10
+ require 'active_record/bogacs/connection_handler'
11
+ ActiveRecord::Base.default_connection_handler = ConnectionHandler.new
12
+ end
13
+ end
14
+
15
+ end
16
+ end
17
+ end
@@ -1,7 +1,8 @@
1
1
  require 'active_record/version'
2
2
  require 'active_record/connection_adapters/abstract/connection_pool'
3
3
 
4
- require 'thread'
4
+ require 'concurrent/atomic/atomic_reference'
5
+ require 'concurrent/thread_safe/util/cheap_lockable.rb'
5
6
 
6
7
  require 'active_record/bogacs/thread_safe'
7
8
  require 'active_record/bogacs/pool_support'
@@ -13,17 +14,11 @@ require 'active_record/bogacs/pool_support'
13
14
  #
14
15
  module ActiveRecord
15
16
  module Bogacs
16
- class ShareablePool < ConnectionAdapters::ConnectionPool # NOTE: maybe do not override?!
17
- include PoolSupport
17
+ class ShareablePool < DefaultPool
18
18
 
19
- if ActiveRecord::Bogacs::ThreadSafe.load_cheap_lockable(false)
20
- include ThreadSafe::CheapLockable
21
- else
22
- alias_method :cheap_synchronize, :synchronize
23
- end
19
+ include ::Concurrent::ThreadSafe::Util::CheapLockable
24
20
 
25
- ActiveRecord::Bogacs::ThreadSafe.load_atomic_reference
26
- AtomicReference = ThreadSafe::AtomicReference
21
+ AtomicReference = ::Concurrent::AtomicReference
27
22
 
28
23
  DEFAULT_SHARED_POOL = 0.25 # only allow 25% of the pool size to be shared
29
24
  MAX_THREAD_SHARING = 5 # not really a strict limit but should hold
@@ -32,38 +27,35 @@ module ActiveRecord
32
27
 
33
28
  # @override
34
29
  def initialize(spec)
35
- super(spec) # ConnectionPool#initialize
30
+ super(spec)
36
31
  shared_size = spec.config[:shared_pool]
37
32
  shared_size = shared_size ? shared_size.to_f : DEFAULT_SHARED_POOL
38
33
  # size 0.0 - 1.0 assumes percentage of the pool size
39
34
  shared_size = ( @size * shared_size ).round if shared_size <= 1.0
40
35
  @shared_size = shared_size.to_i
41
- @shared_connections = ThreadSafe::Map.new # :initial_capacity => @shared_size, :concurrency_level => 20
36
+ @shared_connections = ThreadSafe::Map.new # initial_capacity: @shared_size
42
37
  end
43
38
 
44
39
  # @override
45
40
  def connection
46
- Thread.current[shared_connection_key] || begin # super - simplified :
47
- super # @reserved_connections.compute_if_absent(current_connection_id) { checkout }
48
- end
41
+ current_thread[shared_connection_key] || super
49
42
  end
50
43
 
51
44
  # @override
52
45
  def active_connection?
53
- if shared_conn = Thread.current[shared_connection_key]
54
- return shared_conn.in_use?
55
- end
56
- super_active_connection? current_connection_id
46
+ return true if current_thread[shared_connection_key]
47
+ has_active_connection? # super
57
48
  end
58
49
 
59
50
  # @override called from ConnectionManagement middle-ware (when finished)
60
- def release_connection(with_id = current_connection_id)
61
- if reserved_conn = @reserved_connections[with_id]
51
+ def release_connection(owner_thread = Thread.current)
52
+ conn_id = connection_cache_key(owner_thread)
53
+ if reserved_conn = @thread_cached_conns.delete(conn_id)
62
54
  if shared_count = @shared_connections[reserved_conn]
63
55
  cheap_synchronize do # lock due #get_shared_connection ... not needed ?!
64
56
  # NOTE: the other option is to not care about shared here at all ...
65
57
  if shared_count.get == 0 # releasing a shared connection
66
- release_shared_connection(reserved_conn)
58
+ release_shared_connection(reserved_conn, owner_thread)
67
59
  #else return false
68
60
  end
69
61
  end
@@ -75,18 +67,18 @@ module ActiveRecord
75
67
 
76
68
  # @override
77
69
  def disconnect!
78
- cheap_synchronize { @shared_connections.clear; super }
70
+ synchronize { @shared_connections.clear; super }
79
71
  end
80
72
 
81
73
  # @override
82
74
  def clear_reloadable_connections!
83
- cheap_synchronize { @shared_connections.clear; super }
75
+ synchronize { @shared_connections.clear; super }
84
76
  end
85
77
 
86
78
  # @override
87
79
  # @note called from #reap thus the pool should work with reaper
88
80
  def remove(conn)
89
- cheap_synchronize { @shared_connections.delete(conn); super }
81
+ synchronize { @shared_connections.delete(conn); super }
90
82
  end
91
83
 
92
84
  # # Return any checked-out connections back to the pool by threads that
@@ -105,14 +97,14 @@ module ActiveRecord
105
97
 
106
98
  # Custom API :
107
99
 
108
- def release_shared_connection(connection)
100
+ def release_shared_connection(connection, owner_thread = Thread.current)
109
101
  shared_conn_key = shared_connection_key
110
- if connection == Thread.current[shared_conn_key]
111
- Thread.current[shared_conn_key] = nil
102
+ if connection == owner_thread[shared_conn_key]
103
+ owner_thread[shared_conn_key] = nil
112
104
  end
113
105
 
114
106
  @shared_connections.delete(connection)
115
- checkin connection
107
+ shared_checkin connection # synchronized
116
108
  end
117
109
 
118
110
  def with_shared_connection
@@ -125,9 +117,8 @@ module ActiveRecord
125
117
 
126
118
  start = Time.now if DEBUG
127
119
  begin
128
- connection_id = current_connection_id
129
120
  # if there's a 'regular' connection on the thread use it as super
130
- if super_active_connection?(connection_id) # for current thread
121
+ if has_active_connection? # for current thread
131
122
  connection = self.connection # do not mark as shared
132
123
  DEBUG && debug("with_shared_conn 10 got active = #{connection.to_s}")
133
124
  # otherwise if we have a shared connection - use that one :
@@ -135,7 +126,8 @@ module ActiveRecord
135
126
  emulated_checkout(connection); shared = true
136
127
  DEBUG && debug("with_shared_conn 20 got shared = #{connection.to_s}")
137
128
  else
138
- cheap_synchronize do
129
+ shared = true
130
+ synchronize do
139
131
  # check shared again as/if threads end up sync-ing up here :
140
132
  if connection = get_shared_connection
141
133
  emulated_checkout(connection)
@@ -148,7 +140,6 @@ module ActiveRecord
148
140
  DEBUG && debug("with_shared_conn 30 acq shared = #{connection.to_s}")
149
141
  end
150
142
  end
151
- shared = true
152
143
  end
153
144
 
154
145
  Thread.current[shared_conn_key] = connection if shared
@@ -156,23 +147,17 @@ module ActiveRecord
156
147
  DEBUG && debug("with_shared_conn obtaining a connection took #{(Time.now - start) * 1000}ms")
157
148
  yield connection
158
149
  ensure
159
- Thread.current[shared_conn_key] = nil # if shared
160
- rem_shared_connection(connection) if shared
150
+ Thread.current[shared_conn_key] = nil if shared
151
+ rem_shared_connection(connection) if shared && connection
161
152
  end
162
153
  end
163
154
 
164
155
  private
165
156
 
166
- def super_active_connection?(connection_id = current_connection_id)
167
- (@reserved_connections.get(connection_id) || ( return false )).in_use?
157
+ def has_active_connection? # super.active_connection?
158
+ @thread_cached_conns.fetch(connection_cache_key(current_thread), nil)
168
159
  end
169
160
 
170
- def super_active_connection?(connection_id = current_connection_id)
171
- synchronize do
172
- (@reserved_connections[connection_id] || ( return false )).in_use?
173
- end
174
- end if ActiveRecord::VERSION::MAJOR < 4
175
-
176
161
  def acquire_connection_no_wait?
177
162
  synchronize do
178
163
  @connections.size < @size || @available.send(:can_remove_no_wait?)
@@ -247,12 +232,22 @@ module ActiveRecord
247
232
 
248
233
  def emulated_checkin(connection)
249
234
  # NOTE: not sure we'd like to run `run_callbacks :checkin {}` here ...
250
- connection.expire
235
+ connection.expire if connection.owner.equal? Thread.current
251
236
  end
252
237
 
253
238
  def emulated_checkout(connection)
254
239
  # NOTE: not sure we'd like to run `run_callbacks :checkout {}` here ...
255
- connection.lease; # connection.verify! auto-reconnect should do this
240
+ connection.lease unless connection.in_use? # connection.verify! auto-reconnect should do this
241
+ end
242
+
243
+ def shared_checkin(conn)
244
+ #conn.lock.synchronize do
245
+ synchronize do
246
+ _run_checkin_callbacks(conn) if conn.owner.equal? Thread.current
247
+
248
+ @available.add conn
249
+ end
250
+ #end
256
251
  end
257
252
 
258
253
  def shared_connection_key
@@ -269,7 +264,7 @@ module ActiveRecord
269
264
  when 'true' then ActiveRecord::Base.logger
270
265
  else File.expand_path(debug)
271
266
  end
272
- require 'logger'; Logger.new log_dev
267
+ log_dev.respond_to?(:debug) ? log_dev : (require 'logger'; Logger.new log_dev)
273
268
  else nil
274
269
  end
275
270
  end
@@ -1,33 +1,29 @@
1
+ require 'concurrent/thread_safe/util/cheap_lockable.rb'
2
+
1
3
  # @note Inpspired by thread_safe gem's Util::CheapLockable mixin.
2
4
  module ActiveRecord::Bogacs
3
5
  module ThreadSafe
4
- engine = defined?(RUBY_ENGINE) && RUBY_ENGINE
5
- if engine == 'rbx'
6
+ if defined? JRUBY_VERSION
6
7
  module Synchronized
7
- # Making use of the Rubinius' ability to lock via object headers to avoid
8
- # the overhead of the extra Mutex objects.
9
- def synchronize
10
- ::Rubinius.lock(self)
11
- begin
12
- yield
13
- ensure
14
- ::Rubinius.unlock(self)
8
+ if defined? ::JRuby::Util.synchronized # since JRuby 9.3
9
+ def synchronize
10
+ ::JRuby::Util.synchronized(self) { yield }
11
+ end
12
+ else
13
+ require 'jruby'
14
+ def synchronize
15
+ ::JRuby.reference0(self).synchronized { yield }
15
16
  end
16
17
  end
17
18
  end
18
- elsif engine == 'jruby'
19
+ else
20
+ require 'concurrent/thread_safe/util/cheap_lockable.rb'
21
+
19
22
  module Synchronized
20
- require 'jruby'
21
- # Use Java's native synchronized (this) { wait(); notifyAll(); } to avoid
22
- # the overhead of the extra Mutex objects
23
- def synchronize
24
- ::JRuby.reference0(self).synchronized { yield }
25
- end
23
+ include ::Concurrent::ThreadSafe::Util::CheapLockable
24
+ alias_method :synchronize, :cheap_synchronize
25
+ public :synchronize
26
26
  end
27
- else
28
- require 'thread'; require 'monitor'
29
- # on MRI fallback to built-in MonitorMixin
30
- Synchronized = MonitorMixin
31
27
  end
32
28
  end
33
- end
29
+ end