activerecord-bogacs 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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