activerecord-bogacs 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
@@ -40,20 +43,21 @@ module ActiveRecord
40
43
  # #connection can be called any number of times; the connection is
41
44
  # held in a hash keyed by the thread id.
42
45
  def connection
43
- @reserved_connections[current_connection_id] ||= checkout
46
+ connection_id = current_connection_id(current_thread)
47
+ @thread_cached_conns[connection_id] ||= checkout
44
48
  end
45
49
 
46
50
  # Is there an open connection that is being used for the current thread?
47
51
  def active_connection?
48
- conn = @reserved_connections[current_connection_id]
49
- conn ? conn.in_use? : false
52
+ connection_id = current_connection_id(current_thread)
53
+ @thread_cached_conns[connection_id]
50
54
  end
51
55
 
52
56
  # Signal that the thread is finished with the current connection.
53
57
  # #release_connection releases the connection-thread association
54
58
  # and returns the connection to the pool.
55
- def release_connection(with_id = current_connection_id)
56
- conn = @reserved_connections.delete(with_id)
59
+ def release_connection(owner_thread = Thread.current)
60
+ conn = @thread_cached_conns.delete(current_connection_id(owner_thread))
57
61
  checkin conn if conn
58
62
  end
59
63
 
@@ -62,40 +66,63 @@ module ActiveRecord
62
66
  # connection when finished.
63
67
  def with_connection
64
68
  connection_id = current_connection_id
65
- fresh_connection = true unless active_connection?
66
- yield connection
69
+ unless conn = @thread_cached_conns[connection_id]
70
+ conn = connection
71
+ fresh_connection = true
72
+ end
73
+ yield conn
67
74
  ensure
68
- release_connection(connection_id) if fresh_connection
75
+ release_connection if fresh_connection
69
76
  end
70
77
 
71
78
  # Returns true if a connection has already been opened.
72
- def connected?; @connected end
79
+ def connected?; @connected.true? end
80
+
81
+ # @private replacement for attr_reader :connections
82
+ def connections; @thread_cached_conns.values end
73
83
 
74
84
  # Disconnects all connections in the pool, and clears the pool.
75
85
  def disconnect!
76
86
  synchronize do
77
- @connected = false
78
-
79
- connections = @reserved_connections.values
80
- @reserved_connections.clear
87
+ @connected.make_false
81
88
 
89
+ connections = @thread_cached_conns.values
90
+ @thread_cached_conns.clear
82
91
  connections.each do |conn|
83
- checkin conn
92
+ if conn.in_use?
93
+ conn.steal!
94
+ checkin conn
95
+ end
84
96
  conn.disconnect!
85
97
  end
86
98
  end
87
99
  end
88
100
 
101
+ def discard! # :nodoc:
102
+ synchronize do
103
+ return if @thread_cached_conns.nil? # already discarded
104
+ @connected.make_false
105
+
106
+ connections.each do |conn|
107
+ conn.discard!
108
+ end
109
+ @thread_cached_conns = nil
110
+ end
111
+ end
112
+
89
113
  # Clears the cache which maps classes.
90
114
  def clear_reloadable_connections!
91
115
  synchronize do
92
- @connected = false
116
+ @connected.make_false
93
117
 
94
- connections = @reserved_connections.values
95
- @reserved_connections.clear
118
+ connections = @thread_cached_conns.values
119
+ @thread_cached_conns.clear
96
120
 
97
121
  connections.each do |conn|
98
- checkin conn
122
+ if conn.in_use?
123
+ conn.steal!
124
+ checkin conn
125
+ end
99
126
  conn.disconnect! if conn.requires_reloading?
100
127
  end
101
128
  end
@@ -107,7 +134,7 @@ module ActiveRecord
107
134
  def verify_active_connections!
108
135
  synchronize do
109
136
  clear_stale_cached_connections!
110
- @reserved_connections.values.each(&:verify!)
137
+ @thread_cached_conns.values.each(&:verify!)
111
138
  end
112
139
  end if ActiveRecord::VERSION::MAJOR < 4
113
140
 
@@ -115,12 +142,12 @@ module ActiveRecord
115
142
  # are no longer alive.
116
143
  # @private AR 3.2 compatibility
117
144
  def clear_stale_cached_connections!
118
- keys = Thread.list.find_all { |t| t.alive? }.map(&:object_id)
119
- keys = @reserved_connections.keys - keys
145
+ keys = Thread.list.find_all { |t| t.alive? }.map { |t| current_connection_id(t) }
146
+ keys = @thread_cached_conns.keys - keys
120
147
  keys.each do |key|
121
- if conn = @reserved_connections[key]
122
- checkin conn, false # no release
123
- @reserved_connections.delete(key)
148
+ if conn = @thread_cached_conns[key]
149
+ checkin conn, true # no release
150
+ @thread_cached_conns.delete(key)
124
151
  end
125
152
  end
126
153
  end if ActiveRecord::VERSION::MAJOR < 4
@@ -131,12 +158,12 @@ module ActiveRecord
131
158
  # @return [ActiveRecord::ConnectionAdapters::AbstractAdapter]
132
159
  # @raise [ActiveRecord::ConnectionTimeoutError] no connection can be obtained from the pool
133
160
  def checkout
134
- #synchronize do
135
- conn = checkout_new_connection # acquire_connection
161
+ conn = checkout_new_connection # acquire_connection
162
+ synchronize do
136
163
  conn.lease
137
164
  _run_checkout_callbacks(conn) # checkout_and_verify(conn)
138
- conn
139
- #end
165
+ end
166
+ conn
140
167
  end
141
168
 
142
169
  # Check-in a database connection back into the pool, indicating that you
@@ -145,13 +172,9 @@ module ActiveRecord
145
172
  # @param conn [ActiveRecord::ConnectionAdapters::AbstractAdapter] connection
146
173
  # object, which was obtained earlier by calling #checkout on this pool
147
174
  # @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
175
+ def checkin(conn, released = nil)
176
+ release(conn) unless released
177
+ _run_checkin_callbacks(conn)
155
178
  end
156
179
 
157
180
  # Remove a connection from the connection pool. The connection will
@@ -165,6 +188,21 @@ module ActiveRecord
165
188
  # we do not really manage the connection pool - nothing to do ...
166
189
  end
167
190
 
191
+ def flush(minimum_idle = nil)
192
+ # we do not really manage the connection pool
193
+ end
194
+
195
+ def flush!
196
+ reap
197
+ flush(-1)
198
+ end
199
+
200
+ def stat
201
+ {
202
+ connections: connections.size
203
+ }
204
+ end
205
+
168
206
  private
169
207
 
170
208
  # @raise [ActiveRecord::ConnectionTimeoutError]
@@ -181,34 +219,30 @@ module ActiveRecord
181
219
  checkout_new_connection
182
220
  end
183
221
 
184
- def release(conn, owner = nil)
185
- thread_id = owner.object_id unless owner.nil?
222
+ def release(conn, owner = conn.owner)
223
+ thread_id = current_connection_id(owner) unless owner.nil?
186
224
 
187
225
  thread_id ||=
188
- if @reserved_connections[conn_id = current_connection_id] == conn
226
+ if @thread_cached_conns[conn_id = current_connection_id].equal?(conn)
189
227
  conn_id
190
228
  else
191
- connections = @reserved_connections
192
- connections.keys.find { |k| connections[k] == conn }
229
+ connections = @thread_cached_conns
230
+ connections.keys.find { |k| connections[k].equal?(conn) }
193
231
  end
194
232
 
195
- @reserved_connections.delete thread_id if thread_id
233
+ @thread_cached_conns.delete_pair(thread_id, conn) if thread_id
196
234
  end
197
235
 
198
236
  def checkout_new_connection
199
- # NOTE: automatic reconnect seems to make no sense for us!
200
- #raise ConnectionNotEstablished unless @automatic_reconnect
201
-
237
+ # NOTE: automatic reconnect makes no sense for us!
202
238
  begin
203
239
  conn = new_connection
204
- rescue ConnectionTimeoutError => e
205
- raise e
206
240
  rescue => e
207
- raise ConnectionTimeoutError, e.message if timeout_error?(e)
241
+ raise ConnectionTimeoutError, e.message if timeout_error?(e) && !e.is_a?(ConnectionTimeoutError)
208
242
  raise e
209
243
  end
244
+ @connected.make_true
210
245
  conn.pool = self
211
- synchronize { @connected = true } if @connected != true
212
246
  conn
213
247
  end
214
248
 
@@ -1,20 +1,34 @@
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
+ # @private
28
+ def current_connection_id(owner_thread = Thread.current)
29
+ owner_thread.object_id
17
30
  end
31
+ alias_method :connection_cache_key, :current_connection_id # for AR (5.2) compatibility
18
32
 
19
33
  # @note Method not part of the pre 4.0 API (does no exist).
20
34
  def remove(conn)
@@ -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
@@ -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'
@@ -16,14 +17,9 @@ module ActiveRecord
16
17
  class ShareablePool < ConnectionAdapters::ConnectionPool # NOTE: maybe do not override?!
17
18
  include PoolSupport
18
19
 
19
- if ActiveRecord::Bogacs::ThreadSafe.load_cheap_lockable(false)
20
- include ThreadSafe::CheapLockable
21
- else
22
- alias_method :cheap_synchronize, :synchronize
23
- end
20
+ include ThreadSafe::Synchronized
24
21
 
25
- ActiveRecord::Bogacs::ThreadSafe.load_atomic_reference
26
- AtomicReference = ThreadSafe::AtomicReference
22
+ AtomicReference = ::Concurrent::AtomicReference
27
23
 
28
24
  DEFAULT_SHARED_POOL = 0.25 # only allow 25% of the pool size to be shared
29
25
  MAX_THREAD_SHARING = 5 # not really a strict limit but should hold
@@ -60,7 +56,7 @@ module ActiveRecord
60
56
  def release_connection(with_id = current_connection_id)
61
57
  if reserved_conn = @reserved_connections[with_id]
62
58
  if shared_count = @shared_connections[reserved_conn]
63
- cheap_synchronize do # lock due #get_shared_connection ... not needed ?!
59
+ synchronize do # lock due #get_shared_connection ... not needed ?!
64
60
  # NOTE: the other option is to not care about shared here at all ...
65
61
  if shared_count.get == 0 # releasing a shared connection
66
62
  release_shared_connection(reserved_conn)
@@ -75,18 +71,18 @@ module ActiveRecord
75
71
 
76
72
  # @override
77
73
  def disconnect!
78
- cheap_synchronize { @shared_connections.clear; super }
74
+ synchronize { @shared_connections.clear; super }
79
75
  end
80
76
 
81
77
  # @override
82
78
  def clear_reloadable_connections!
83
- cheap_synchronize { @shared_connections.clear; super }
79
+ synchronize { @shared_connections.clear; super }
84
80
  end
85
81
 
86
82
  # @override
87
83
  # @note called from #reap thus the pool should work with reaper
88
84
  def remove(conn)
89
- cheap_synchronize { @shared_connections.delete(conn); super }
85
+ synchronize { @shared_connections.delete(conn); super }
90
86
  end
91
87
 
92
88
  # # Return any checked-out connections back to the pool by threads that
@@ -135,7 +131,7 @@ module ActiveRecord
135
131
  emulated_checkout(connection); shared = true
136
132
  DEBUG && debug("with_shared_conn 20 got shared = #{connection.to_s}")
137
133
  else
138
- cheap_synchronize do
134
+ synchronize do
139
135
  # check shared again as/if threads end up sync-ing up here :
140
136
  if connection = get_shared_connection
141
137
  emulated_checkout(connection)
@@ -218,7 +214,7 @@ module ActiveRecord
218
214
  end
219
215
 
220
216
  # we did as much as could without a lock - now sync due possible release
221
- cheap_synchronize do # TODO although this likely might be avoided ...
217
+ synchronize do # TODO although this likely might be avoided ...
222
218
  # should try again if possibly the same connection got released :
223
219
  unless least_count = @shared_connections[least_shared]
224
220
  DEBUG && debug(" get_shared_conn retry (connection got released)")
@@ -238,7 +234,7 @@ module ActiveRecord
238
234
  def rem_shared_connection(connection)
239
235
  if shared_count = @shared_connections[connection]
240
236
  # shared_count.update { |v| v - 1 } # NOTE: likely fine without lock!
241
- cheap_synchronize do # give it back to the pool
237
+ synchronize do # give it back to the pool
242
238
  shared_count.update { |v| v - 1 } # might give it back if :
243
239
  release_shared_connection(connection) if shared_count.get == 0
244
240
  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
@@ -2,98 +2,11 @@ module ActiveRecord
2
2
  module Bogacs
3
3
  module ThreadSafe
4
4
 
5
- #def self.load_map
6
- begin
7
- require 'concurrent/map.rb'
8
- rescue LoadError => e
9
- begin
10
- require 'thread_safe'
11
- rescue
12
- warn "activerecord-bogacs needs gem 'concurrent-ruby', '~> 1.0' (or the old 'thread_safe' gem)" +
13
- " please install or add it to your Gemfile"
14
- raise e
15
- end
16
- end
17
- #end
18
-
19
- #load_map # always pre-load thread_safe
20
-
21
- if defined? ::Concurrent::Map
22
- Map = ::Concurrent::Map
23
- else
24
- Map = ::ThreadSafe::Cache
25
- end
5
+ require 'concurrent/map.rb'
6
+ Map = ::Concurrent::Map
26
7
 
27
8
  autoload :Synchronized, 'active_record/bogacs/thread_safe/synchronized'
28
9
 
29
- def self.load_atomic_reference
30
- return const_get :AtomicReference if const_defined? :AtomicReference
31
-
32
- begin
33
- require 'concurrent/atomic/atomic_reference.rb'
34
- rescue LoadError => e
35
- begin
36
- require 'atomic'
37
- rescue LoadError
38
- warn "shareable pool needs gem 'concurrent-ruby', '>= 0.9.1' (or the old 'atomic' gem) " <<
39
- "please install or add it to your Gemfile"
40
- raise e
41
- end
42
- end
43
-
44
- if defined? ::Concurrent::AtomicReference
45
- const_set :AtomicReference, ::Concurrent::AtomicReference
46
- else
47
- const_set :AtomicReference, ::Atomic
48
- end
49
- end
50
-
51
- def self.load_cheap_lockable(required = true)
52
- return const_get :CheapLockable if const_defined? :CheapLockable
53
-
54
- begin
55
- require 'concurrent/thread_safe/util/cheap_lockable.rb'
56
- rescue LoadError => e
57
- begin
58
- require 'thread_safe'
59
- rescue
60
- return nil unless required
61
- warn "activerecord-bogacs needs gem 'concurrent-ruby', '~> 1.0' (or the old 'thread_safe' gem)" +
62
- " please install or add it to your Gemfile"
63
- raise e
64
- end
65
- end
66
-
67
- if defined? ::Concurrent::ThreadSafe::Util::CheapLockable
68
- const_set :CheapLockable, ::Concurrent::ThreadSafe::Util::CheapLockable
69
- else
70
- const_set :CheapLockable, ::ThreadSafe::Util::CheapLockable
71
- end
72
- end
73
-
74
-
75
- def self.load_monotonic_clock(required = true)
76
- return const_get :MONOTONIC_CLOCK if const_defined? :MONOTONIC_CLOCK
77
-
78
- begin
79
- require 'concurrent/utility/monotonic_time.rb'
80
- rescue LoadError => e
81
- return nil unless required
82
- warn "activerecord-bogacs needs gem 'concurrent-ruby', '~> 1.0'" +
83
- " please install or add it to your Gemfile"
84
- raise e
85
- end
86
-
87
- if defined? ::Concurrent.monotonic_time
88
- const_set :MONOTONIC_CLOCK, ::Concurrent.const_get(:GLOBAL_MONOTONIC_CLOCK)
89
- end
90
- end
91
-
92
- # @note Only works when {@link #load_monotonic_time} succeeds.
93
- #def self.monotonic_time
94
- # MONOTONIC_CLOCK.get_time # java.lang.System.nanoTime() / 1_000_000_000.0
95
- #end
96
-
97
10
  end
98
11
  end
99
- end
12
+ end
@@ -1,10 +1,5 @@
1
- begin
2
- require 'concurrent/executors'
3
- require 'concurrent/timer_task'
4
- rescue LoadError => e
5
- warn "activerecord-bogacs' validator feature needs gem 'concurrent-ruby', please install or add it to your Gemfile"
6
- raise e
7
- end
1
+ require 'concurrent/executors'
2
+ require 'concurrent/timer_task'
8
3
 
9
4
  require 'active_record/connection_adapters/adapter_compat'
10
5
  require 'active_record/bogacs/thread_safe'
@@ -81,20 +76,19 @@ module ActiveRecord
81
76
  connections = pool.connections.dup
82
77
  connections.map! do |conn|
83
78
  if conn
84
- owner = conn.owner
85
- if conn.in_use? # we'll do a pool.sync-ed check ...
86
- if owner && ! owner.alive? # stale-conn (reaping)
79
+ if owner = conn.owner
80
+ if owner.alive?
81
+ nil # owner.alive? ... do not touch
82
+ else # stale-conn (reaping)
87
83
  pool.remove conn # remove is synchronized
88
84
  conn.disconnect! rescue nil
89
85
  nil
90
- elsif ! owner # NOTE: this is likely a nasty bug
91
- logger && logger.warn("[validator] found in-use connection ##{conn.object_id} without owner - removing from pool")
92
- pool.remove_without_owner conn # synchronized
93
- conn.disconnect! rescue nil
94
- nil
95
- else
96
- nil # owner.alive? ... do not touch
97
86
  end
87
+ elsif conn.in_use? # no owner? (likely a nasty bug)
88
+ logger && logger.warn("[validator] found in-use connection ##{conn.object_id} without owner - removing from pool")
89
+ pool.remove_without_owner conn # synchronized
90
+ conn.disconnect! rescue nil
91
+ nil
98
92
  else
99
93
  conn # conn not in-use - candidate for validation
100
94
  end
@@ -170,7 +164,8 @@ module ActiveRecord
170
164
 
171
165
  def release_without_owner(conn)
172
166
  if owner_id = cached_conn_owner_id(conn)
173
- thread_cached_conns.delete owner_id; return true
167
+ thread_cached_conns.delete owner_id
168
+ return true
174
169
  end
175
170
  end
176
171
 
@@ -179,4 +174,4 @@ module ActiveRecord
179
174
  end
180
175
 
181
176
  end
182
- end
177
+ end
@@ -1,5 +1,5 @@
1
1
  module ActiveRecord
2
2
  module Bogacs
3
- VERSION = '0.6.0'
3
+ VERSION = '0.7.0'
4
4
  end
5
5
  end