activerecord-bogacs 0.5.1 → 0.6.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.
- checksums.yaml +4 -4
- data/LICENSE.txt +1 -1
- data/README.md +37 -17
- data/lib/active_record/bogacs.rb +6 -2
- data/lib/active_record/bogacs/connection_handler.rb +36 -0
- data/lib/active_record/bogacs/railtie.rb +17 -0
- data/lib/active_record/bogacs/thread_safe.rb +27 -4
- data/lib/active_record/bogacs/validator.rb +1 -1
- data/lib/active_record/bogacs/version.rb +1 -1
- data/lib/active_record/connection_adapters/adapter_compat.rb +52 -22
- data/lib/activerecord-bogacs.rb +1 -0
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a11630875cf14ee4c1ea37721a04a1966eeb488874d23c833075e42c2e435852
|
4
|
+
data.tar.gz: 77016cc2628013c4a1791403fcbf65ad488f8db4f6a091a9659e1e0b523c7e38
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 89a482ca85813ef8b769355a0a42ffe0317024022c0da7ad337084029ad455976865199806b6f79fbd0b79c1288ec7f6a1654ec9aea66ec07b544b28329f5dcc
|
7
|
+
data.tar.gz: 624c0fe63b5ff31ef51abeb2dd537fe3ef2bddad81f03cd94df38fb7aafea3b93635a844554bb7597ec08fc551215c016e1c1ebcb3817230220455919db1ade3
|
data/LICENSE.txt
CHANGED
data/README.md
CHANGED
@@ -6,7 +6,7 @@ ActiveRecord (all-year) pooling "alternatives" ... in a relaxed 'spa' fashion.
|
|
6
6
|
|
7
7
|
Bogács is a village in Borsod-Abaúj-Zemplén county, Hungary.
|
8
8
|
|
9
|
-
**
|
9
|
+
**NOTE: do not put this on production if you do not understand the consequences!**
|
10
10
|
|
11
11
|
## Install
|
12
12
|
|
@@ -22,8 +22,8 @@ add this line to your application's *Gemfile*:
|
|
22
22
|
|
23
23
|
Bogacs' pools rely on a small monkey-patch that allows to change the AR pool.
|
24
24
|
The desired pool class needs to be set before `establish_connection` happens,
|
25
|
-
thus an initializer won't work, you might consider setting the
|
26
|
-
the bottom of your *application.rb* e.g. :
|
25
|
+
thus an initializer (under Rails) won't work, you might consider setting the
|
26
|
+
pool class at the bottom of your *application.rb* e.g. :
|
27
27
|
|
28
28
|
```ruby
|
29
29
|
Bundler.require(*Rails.groups)
|
@@ -34,19 +34,39 @@ module MyApp
|
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
37
|
-
# sample AR-Bogacs setup using the "
|
37
|
+
# sample AR-Bogacs setup using the "default" pool :
|
38
38
|
if Rails.env.production?
|
39
|
-
pool_class = ActiveRecord::Bogacs::
|
39
|
+
pool_class = ActiveRecord::Bogacs::DefaultPool
|
40
40
|
ActiveRecord::ConnectionAdapters::ConnectionHandler.connection_pool_class = pool_class
|
41
41
|
end
|
42
42
|
```
|
43
43
|
|
44
|
-
|
44
|
+
Alternatively, for `FalsePool`, there's a configuration convention (no need to path
|
45
|
+
and set the connection_pool_class) :
|
46
|
+
```yaml
|
47
|
+
production:
|
48
|
+
adapter: mysql2
|
49
|
+
<% if $servlet_context %>
|
50
|
+
jndi: java:comp/env/jdbc/mydb
|
51
|
+
pool: false # use AR::Bogacs::FalsePool
|
52
|
+
<% else %>
|
53
|
+
port: <%= ENV['DATABASE_PORT'] || 3306 %>
|
54
|
+
database: mydb
|
55
|
+
# ...
|
56
|
+
<% end %>
|
57
|
+
```
|
58
|
+
|
59
|
+
This works only as long as one doesn't set a custom connection handler, since
|
60
|
+
*Bogacs* only sets up its custom `ActiveRecord::Base.default_connection_handler`.
|
61
|
+
|
62
|
+
Pools are expected to work with older ActiveRecord versions: 3.x as well as 4.x.
|
45
63
|
|
46
64
|
### [Default Pool][2]
|
47
65
|
|
48
|
-
Meant as a back-port for users stuck with old Rails versions (< 4.0)
|
49
|
-
facing potential (pool related) concurrency bugs e.g. with
|
66
|
+
Meant, primarily, as a back-port for users stuck with old Rails versions (< 4.0)
|
67
|
+
on production, facing potential (pool related) concurrency bugs e.g. with highly
|
68
|
+
concurrent loads under JRuby, although it also enhances some of the thread locking
|
69
|
+
issues present in 4.x's pool.
|
50
70
|
|
51
71
|
Based on pool code from 4.x (which works much better than any previous version),
|
52
72
|
with a few minor tunings and extensions such as `pool_initial: 0.5` which allows
|
@@ -56,7 +76,7 @@ to specify how many connections to initialize in advance when the pool is create
|
|
56
76
|
|
57
77
|
The false pool won't do any actual pooling, it is assumed that an underlying pool
|
58
78
|
is configured. Still, it does maintain a hash of AR connections mapped to threads.
|
59
|
-
Ignores pool related
|
79
|
+
Ignores pool related configuration such as `pool: 42` or `checkout_timeout: 2.5`.
|
60
80
|
|
61
81
|
**NOTE:** be sure to configure an underlying pool e.g. with Trinidad (using the
|
62
82
|
default Tomcat JDBC pool) :
|
@@ -70,7 +90,7 @@ default Tomcat JDBC pool) :
|
|
70
90
|
mysql_dbpool:
|
71
91
|
url: jdbc:mysql:///my_production
|
72
92
|
username: root
|
73
|
-
jndi: jdbc/
|
93
|
+
jndi: jdbc/MyDB
|
74
94
|
initialSize: <%= ENV['POOL_INITIAL'] || 25 %> # connections created on start
|
75
95
|
maxActive: <%= ENV['POOL_SIZE'] || 100 %> # default 100 (AR pool: size)
|
76
96
|
maxIdle: <%= ENV['POOL_SIZE'] || 100 %> # max connections kept in the pool
|
@@ -88,7 +108,7 @@ following configuration :
|
|
88
108
|
```
|
89
109
|
production:
|
90
110
|
adapter: mysql2
|
91
|
-
jndi: java:/comp/env/jdbc/
|
111
|
+
jndi: java:/comp/env/jdbc/MyDB
|
92
112
|
```
|
93
113
|
|
94
114
|
**NOTE:** when using `FalsePool` there's nothing to configure (in *database.yml*)!
|
@@ -97,7 +117,7 @@ production:
|
|
97
117
|
|
98
118
|
This pool allows for a database connection to be "shared" among threads, this is
|
99
119
|
very **dangerous** normally. You will need to understand the underlying driver's
|
100
|
-
connection whether
|
120
|
+
connection implementation (whether its thread-safe).
|
101
121
|
|
102
122
|
You'll need to manually declare blocks that run with a shared connection (**make
|
103
123
|
sure** only read operations happen within such blocks) similar to the built-in
|
@@ -109,15 +129,15 @@ cache_fetch( [ 'user', user_id ] ) do
|
|
109
129
|
end
|
110
130
|
```
|
111
131
|
|
112
|
-
The pool
|
113
|
-
|
114
|
-
just like
|
132
|
+
The pool "might" share connections among such blocks but only if it runs out of
|
133
|
+
all connections (pool size is reached), until than it will always prefer checking
|
134
|
+
out a connection just like `with_connection` does.
|
115
135
|
|
116
|
-
|
136
|
+
Tested with ActiveRecord-JDBC-Adapter using the official Postgres' driver (< 42).
|
117
137
|
|
118
138
|
## Copyright
|
119
139
|
|
120
|
-
Copyright (c)
|
140
|
+
Copyright (c) 2018 [Karol Bucek](http://kares.org).
|
121
141
|
See LICENSE (http://en.wikipedia.org/wiki/MIT_License) for details.
|
122
142
|
|
123
143
|
[0]: http://res.cloudinary.com/kares/image/upload/c_scale,h_600,w_800/v1406451696/bogacs.jpg
|
data/lib/active_record/bogacs.rb
CHANGED
@@ -1,6 +1,10 @@
|
|
1
|
-
require 'active_record'
|
2
1
|
|
3
2
|
require 'active_record/bogacs/version'
|
4
3
|
require 'active_record/bogacs/autoload'
|
5
4
|
|
6
|
-
|
5
|
+
if defined?(Rails::Railtie)
|
6
|
+
require 'active_record/bogacs/railtie'
|
7
|
+
else
|
8
|
+
require 'active_record'
|
9
|
+
require 'active_record/connection_adapters/pool_class'
|
10
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'active_record/version'
|
2
|
+
|
3
|
+
require 'active_record/connection_adapters/abstract/connection_pool'
|
4
|
+
|
5
|
+
module ActiveRecord
|
6
|
+
module Bogacs
|
7
|
+
class ConnectionHandler < ConnectionAdapters::ConnectionHandler
|
8
|
+
|
9
|
+
if ActiveRecord::VERSION::MAJOR < 5
|
10
|
+
|
11
|
+
def establish_connection(owner, spec)
|
12
|
+
@class_to_pool.clear
|
13
|
+
if spec.config[:pool].eql? false
|
14
|
+
owner_to_pool[owner.name] = ActiveRecord::Bogacs::FalsePool.new(spec)
|
15
|
+
else # super
|
16
|
+
owner_to_pool[owner.name] = ActiveRecord::ConnectionAdapters::ConnectionPool.new(spec)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
else
|
21
|
+
|
22
|
+
def establish_connection(config) # (spec) in 5.0
|
23
|
+
pool = super
|
24
|
+
spec = pool.spec
|
25
|
+
if spec.config[:pool].eql? false
|
26
|
+
owner_to_pool[owner.name] = ActiveRecord::Bogacs::FalsePool.new(spec)
|
27
|
+
else
|
28
|
+
pool
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
end
|
36
|
+
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
|
@@ -9,8 +9,8 @@ module ActiveRecord
|
|
9
9
|
begin
|
10
10
|
require 'thread_safe'
|
11
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"
|
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
14
|
raise e
|
15
15
|
end
|
16
16
|
end
|
@@ -58,8 +58,8 @@ module ActiveRecord
|
|
58
58
|
require 'thread_safe'
|
59
59
|
rescue
|
60
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"
|
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
63
|
raise e
|
64
64
|
end
|
65
65
|
end
|
@@ -71,6 +71,29 @@ module ActiveRecord
|
|
71
71
|
end
|
72
72
|
end
|
73
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
|
+
|
74
97
|
end
|
75
98
|
end
|
76
99
|
end
|
@@ -116,7 +116,7 @@ module ActiveRecord
|
|
116
116
|
logger && logger.info("[validator] connection ##{conn.object_id} failed to validate: #{e.inspect}")
|
117
117
|
end
|
118
118
|
|
119
|
-
# TODO support
|
119
|
+
# TODO support seconds_idle - only validate if certain amount since use passed
|
120
120
|
|
121
121
|
logger && logger.debug("[validator] found non-active connection ##{conn.object_id} - removing from pool")
|
122
122
|
pool.remove_without_owner conn # not active - remove
|
@@ -8,38 +8,42 @@ module ActiveRecord
|
|
8
8
|
|
9
9
|
if method_defined? :owner # >= 4.2
|
10
10
|
|
11
|
-
|
11
|
+
if ActiveRecord::VERSION::STRING > '5.2'
|
12
12
|
|
13
|
-
|
13
|
+
# THIS IS OUR COMPATIBILITY BASE-LINE
|
14
14
|
|
15
|
-
|
16
|
-
|
15
|
+
elsif ActiveRecord::VERSION::MAJOR > 4
|
16
|
+
|
17
|
+
# @private added @idle_since
|
18
|
+
# this method must only be called while holding connection pool's mutex
|
19
|
+
def expire
|
17
20
|
if in_use?
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
msg += "it is already in use by a different thread: #{@owner}. Current thread: #{Thread.current}."
|
21
|
+
if @owner != Thread.current
|
22
|
+
raise ActiveRecordError, "Cannot expire connection, " \
|
23
|
+
"it is owned by a different thread: #{@owner}. " \
|
24
|
+
"Current thread: #{Thread.current}."
|
23
25
|
end
|
24
|
-
raise ActiveRecordError, msg
|
25
|
-
end
|
26
26
|
|
27
|
-
|
27
|
+
@owner = nil; @idle_since = monotonic_time
|
28
|
+
else
|
29
|
+
raise ActiveRecordError, "Cannot expire connection, it is not currently leased."
|
30
|
+
end
|
28
31
|
end
|
29
32
|
|
30
33
|
else
|
31
34
|
|
32
|
-
# @private removed synchronization
|
35
|
+
# @private removed synchronization
|
33
36
|
def lease
|
34
|
-
|
35
|
-
|
36
|
-
# NOTE: could do a warning if 4.2.x cases do not end up here ...
|
37
|
-
end
|
38
|
-
else
|
39
|
-
@owner = Thread.current; @last_use = Time.now
|
37
|
+
unless in_use?
|
38
|
+
@owner = Thread.current
|
40
39
|
end
|
41
40
|
end
|
42
41
|
|
42
|
+
# @private added @idle_since
|
43
|
+
def expire
|
44
|
+
@owner = nil; @idle_since = monotonic_time
|
45
|
+
end
|
46
|
+
|
43
47
|
end
|
44
48
|
|
45
49
|
else
|
@@ -69,7 +73,7 @@ module ActiveRecord
|
|
69
73
|
end
|
70
74
|
|
71
75
|
def expire
|
72
|
-
@in_use = false; @owner = nil
|
76
|
+
@in_use = false; @owner = nil; @idle_since = monotonic_time
|
73
77
|
end
|
74
78
|
|
75
79
|
else
|
@@ -83,13 +87,39 @@ module ActiveRecord
|
|
83
87
|
end
|
84
88
|
|
85
89
|
def expire
|
86
|
-
@owner = nil
|
90
|
+
@owner = nil; @idle_since = monotonic_time
|
87
91
|
end
|
88
92
|
|
89
93
|
end
|
90
94
|
|
91
95
|
end
|
92
96
|
|
97
|
+
unless method_defined? :seconds_idle # >= 5.2
|
98
|
+
|
99
|
+
if ActiveRecord::Bogacs::ThreadSafe.load_monotonic_clock(false)
|
100
|
+
include ActiveRecord::Bogacs::ThreadSafe
|
101
|
+
|
102
|
+
def monotonic_time; MONOTONIC_CLOCK.get_time end
|
103
|
+
private :monotonic_time
|
104
|
+
|
105
|
+
else
|
106
|
+
|
107
|
+
def monotonic_time; nil end
|
108
|
+
private :monotonic_time
|
109
|
+
|
110
|
+
warn "activerecord-bogacs failed to load 'concurrent-ruby', '~> 1.0', seconds_idle won't work" if $VERBOSE
|
111
|
+
|
112
|
+
end
|
113
|
+
|
114
|
+
# Seconds since this connection was returned to the pool
|
115
|
+
def seconds_idle # :nodoc:
|
116
|
+
return 0 if in_use?
|
117
|
+
time = monotonic_time
|
118
|
+
time - ( @idle_since || time )
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
|
93
123
|
end
|
94
124
|
end
|
95
|
-
end
|
125
|
+
end
|
@@ -0,0 +1 @@
|
|
1
|
+
require 'active_record/bogacs' # auto-loading with gem '...' declarations
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: activerecord-bogacs
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.6.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Karol Bucek
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-05-07 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
requirement: !ruby/object:Gem::Requirement
|
@@ -68,9 +68,11 @@ files:
|
|
68
68
|
- activerecord-bogacs.gemspec
|
69
69
|
- lib/active_record/bogacs.rb
|
70
70
|
- lib/active_record/bogacs/autoload.rb
|
71
|
+
- lib/active_record/bogacs/connection_handler.rb
|
71
72
|
- lib/active_record/bogacs/default_pool.rb
|
72
73
|
- lib/active_record/bogacs/false_pool.rb
|
73
74
|
- lib/active_record/bogacs/pool_support.rb
|
75
|
+
- lib/active_record/bogacs/railtie.rb
|
74
76
|
- lib/active_record/bogacs/reaper.rb
|
75
77
|
- lib/active_record/bogacs/shareable_pool.rb
|
76
78
|
- lib/active_record/bogacs/thread_safe.rb
|
@@ -80,6 +82,7 @@ files:
|
|
80
82
|
- lib/active_record/connection_adapters/adapter_compat.rb
|
81
83
|
- lib/active_record/connection_adapters/pool_class.rb
|
82
84
|
- lib/active_record/shared_connection.rb
|
85
|
+
- lib/activerecord-bogacs.rb
|
83
86
|
- test/active_record/bogacs/default_pool_test.rb
|
84
87
|
- test/active_record/bogacs/false_pool_test.rb
|
85
88
|
- test/active_record/bogacs/reaper_test.rb
|