activerecord-bogacs 0.1.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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 63fff373845781b28c456f85fe34f88f4bf70a50
4
+ data.tar.gz: 6b3a8ce661500ec191eff3405d6d3e92d9d90c9e
5
+ SHA512:
6
+ metadata.gz: ba7c559f09102200a324592448dd7f283ac2b604dd1cdbc5f8e3a0309c07181ebcfbb7fbb576dfa3421cff9faf1cb2c3c5907b64c519c3dbe6da8ab039daaa01
7
+ data.tar.gz: bfc98ebdacaab29b8d73be82da0327af06bab79d861426c538106de8918bd3ca90581bde24112d1ce4cce76bb3027d186d14ff9f3f017a7205a262696b8c5657
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ .bundle
3
+ .config
4
+ .yardoc
5
+ Gemfile.lock
6
+ InstalledFiles
7
+ _yardoc
8
+ coverage
9
+ doc/
10
+ lib/bundler/man
11
+ pkg
12
+ rdoc
13
+ spec/reports
14
+ test/jars
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
@@ -0,0 +1,26 @@
1
+ language: ruby
2
+ jdk:
3
+ - openjdk6
4
+ - oraclejdk7
5
+ - oraclejdk8
6
+ rvm:
7
+ - jruby
8
+ #- jruby-head
9
+ #- jruby-18mode
10
+ #- jruby-19mode
11
+ #- 2.1.0
12
+ before_install:
13
+ - ((jruby -v | grep 1.8.7) && jruby --1.9 -S gem update --system 2.1.11) || true
14
+ before_script:
15
+ - echo "JRUBY_OPTS: $JRUBY_OPTS"
16
+ - export JRUBY_OPTS="--server -Xcext.enabled=false -Xcompile.invokedynamic=false"
17
+ - export JAVA_OPTS="$JAVA_OPTS" # -Xmx600M
18
+ - echo "JAVA_OPTS: $JAVA_OPTS"
19
+ script:
20
+ - bundle exec rake tomcat:jndi:download tomcat:jdbc:download tomcat:dbcp:download
21
+ - bundle exec rake db:create:mysql db:create:postgresql
22
+ env:
23
+ - JRUBY_OPTS="--1.8 $JRUBY_OPTS" AR_ADAPTER=mysql
24
+ - JRUBY_OPTS="$JRUBY_OPTS" AR_ADAPTER=postgresql AR_VERSION="~> 3.2.18"
25
+ - JRUBY_OPTS="--1.8 $JRUBY_OPTS" AR_ADAPTER=mysql AR_VERSION="~> 3.2.18"
26
+ - JRUBY_OPTS="$JRUBY_OPTS" AR_ADAPTER=postgresql
data/Gemfile ADDED
@@ -0,0 +1,33 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ if version = ENV['AR_VERSION']
6
+ if version.index('/') && ::File.exist?(version)
7
+ gem 'activerecord', :path => version
8
+ elsif version =~ /^[0-9abcdef]+$/
9
+ gem 'activerecord', :github => 'rails/rails', :ref => version
10
+ elsif version.index('.').nil?
11
+ gem 'activerecord', :github => 'rails/rails', :branch => version
12
+ else
13
+ gem 'activerecord', version, :require => nil
14
+ end
15
+ else
16
+ gem 'activerecord', :require => nil
17
+ end
18
+
19
+ gem 'activerecord-jdbc-adapter', :require => nil, :platform => :jruby
20
+
21
+ #gem 'thread_safe', :require => nil # "optional" - we can roll without it
22
+
23
+ if defined?(JRUBY_VERSION) && JRUBY_VERSION < '1.7.0'
24
+ gem 'jruby-openssl', :platform => :jruby
25
+ end
26
+
27
+ group :test do
28
+ gem 'jdbc-mysql', :require => nil, :platform => :jruby
29
+ gem 'jdbc-postgres', :require => nil, :platform => :jruby
30
+
31
+ gem 'mysql2', :require => nil, :platform => :mri
32
+ gem 'pg', :require => nil, :platform => :mri
33
+ end
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013-2014 [Karol Bucek](http://kares.org)
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,124 @@
1
+ # ActiveRecord::Bogacs
2
+
3
+ ActiveRecord pooling "hacks" ... in a relaxed 'spa' fashion!
4
+
5
+ ![Bogacs][0]
6
+
7
+ Bogács is a village in Borsod-Abaúj-Zemplén county, Hungary.
8
+
9
+ **WiP: don't even thing about putting this on production (TEST IT OUT FIRST)!**
10
+
11
+ ## Install
12
+
13
+ add this line to your application's *Gemfile*:
14
+
15
+ gem 'activerecord-bogacs', :require => 'active_record/bogacs'
16
+
17
+ ... or install it yourself as:
18
+
19
+ $ gem install activerecord-bogacs
20
+
21
+ ## Setup
22
+
23
+ Bogacs' pools rely on a small monkey-patch that allows to change the AR pool.
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 pool class at
26
+ the bottom of your *application.rb* e.g. :
27
+
28
+ ```ruby
29
+ Bundler.require(*Rails.groups)
30
+
31
+ module MyApp
32
+ class Application < Rails::Application
33
+ # config.middleware.delete 'ActiveRecord::QueryCache'
34
+ end
35
+ end
36
+
37
+ # sample configuration using the "false" pool
38
+ if Rails.env.production?
39
+ pool_class = ActiveRecord::Bogacs::FalsePool
40
+ ActiveRecord::ConnectionAdapters::ConnectionHandler.connection_pool_class = pool_class
41
+ end
42
+ ```
43
+
44
+ pools are expected to work with older ActiveRecord versions, if not let us know.
45
+
46
+ ### Default Pool
47
+
48
+ Meant as a back-port for users stuck with old Rails versions (< 4.0) on production,
49
+ facing potential (pool related) concurrency bugs e.g. with high-loads under JRuby.
50
+
51
+ Based on pool code from 4.x (which works much better than any previous version),
52
+ with a few minor tunings and extensions such as `pool_initial: 0.5` which allows
53
+ to specify how many connections to prefill when the pool is created.
54
+
55
+ ### False Pool
56
+
57
+ The false pool won't do any actual pooling, it is assumes that an underlying pool
58
+ is configured. But it does maintain a cache of AR connections mapped to threads.
59
+ Ignores all pool related configuration `pool: 42`, `checkout_timeout: 2.5` etc.
60
+
61
+ **NOTE:** be sure to configure an underlying pool e.g. with Trinidad (using the
62
+ default Tomcat JDBC pool) :
63
+
64
+ ```yaml
65
+ ---
66
+ http: # true
67
+ port: 3000
68
+ # ...
69
+ extensions:
70
+ mysql_dbpool:
71
+ url: jdbc:mysql:///my_production
72
+ username: root
73
+ jndi: jdbc/BogacsDB
74
+ initialSize: <%= ENV['POOL_INITIAL'] || 25 %> # connections created on start
75
+ maxActive: <%= ENV['POOL_SIZE'] || 100 %> # default 100 (AR pool: size)
76
+ maxIdle: <%= ENV['POOL_SIZE'] || 100 %> # max connections kept in the pool
77
+ minIdle: <%= ENV['POOL_SIZE'] || 50 %>
78
+ # idle connections are checked periodically (if enabled) and connections
79
+ # that been idle for longer than minEvictableIdleTimeMillis will be released
80
+ minEvictableIdleTimeMillis: <%= 3 * 60 * 1000 %> # default 60s
81
+ # AR checkout_timeout: 5
82
+ maxWait: <%= (( ENV['POOL_TIMEOUT'] || 5.0 ).to_f * 1000).to_i %> # default 30s
83
+ ```
84
+
85
+ ActiveRecord-JDBC adapter allows you to lookup connection from JNDI using the
86
+ following configuration :
87
+
88
+ ```
89
+ production:
90
+ adapter: mysql2
91
+ jndi: java:/comp/env/jdbc/BogacsDB
92
+ ```
93
+
94
+ **NOTE:** using the `FalsePool` there's nothing to configure (in *database.yml*)
95
+ that is `no_pool: 5` or `checkout_timeout: 5` defaults!
96
+
97
+ ### Shareable Pool
98
+
99
+ This pool allows for a database connection to be "shared" among threads, this is
100
+ very **dangerous** normally. You will need to understand the underlying driver's
101
+ connection whether it is thread-safe.
102
+
103
+ You'll need to manually declare blocks that run with a shared connection (**make
104
+ sure** only read operations happen within such blocks) similar to the built-in
105
+ `with_connection` e.g. :
106
+
107
+ ```ruby
108
+ cache_fetch( [ 'user', user_id ] ) do
109
+ ActiveRecord::Base.with_shared_connection { User.find(user_id) }
110
+ end
111
+ ```
112
+
113
+ The pool will only share connections among such blocks and only if it runs out
114
+ of all connections (until than it will always prefer checking out a connection
115
+ just like a "regular" pool).
116
+
117
+ Only tested with ActiveRecord-JDBC-Adapter using the official Postgres' driver.
118
+
119
+ ## Copyright
120
+
121
+ Copyright (c) 2014 [Karol Bucek](http://kares.org).
122
+ See LICENSE (http://en.wikipedia.org/wiki/MIT_License) for details.
123
+
124
+ [0]: http://res.cloudinary.com/kares/image/upload/c_scale,h_600,w_800/v1406451696/bogacs.jpg
@@ -0,0 +1,167 @@
1
+ require 'bundler/gem_tasks'
2
+
3
+ require 'rake/testtask'
4
+ Rake::TestTask.new do |t|
5
+ t.pattern = "test/**/*_test.rb"
6
+ end
7
+
8
+ task :default => :test
9
+
10
+ desc "Creates a (test) MySQL database"
11
+ task 'db:create:mysql' do
12
+ fail "could not create database: mysql executable not found" unless mysql = _which('mysql')
13
+ ENV['Rake'] = true; ENV['AR_ADAPTER'] ||= 'mysql'
14
+ load File.expand_path('test/test_helper.rb', File.dirname(__FILE__))
15
+
16
+ script = "DROP DATABASE IF EXISTS `#{AR_CONFIG[:database]}`;"
17
+ script << "CREATE DATABASE `#{AR_CONFIG[:database]}` DEFAULT CHARACTER SET `utf8` COLLATE `utf8_general_ci`;"
18
+ if AR_CONFIG[:username]
19
+ script << "GRANT ALL PRIVILEGES ON `#{AR_CONFIG[:database]}`.* TO #{AR_CONFIG[:username]}@localhost;"
20
+ script << "SET PASSWORD FOR #{AR_CONFIG[:username]}@localhost = PASSWORD('#{AR_CONFIG[:password]}');"
21
+ end
22
+ params = { '-u' => 'root' }
23
+ if ENV['DATABASE_YML']
24
+ require 'yaml'
25
+ password = YAML.load(File.new(ENV['DATABASE_YML']))["production"]["password"]
26
+ params['--password'] = password
27
+ end
28
+ puts "Creating MySQL database: #{AR_CONFIG[:database]}"
29
+ sh "cat #{_sql_script(script).path} | #{mysql} #{params.to_a.join(' ')}", :verbose => $VERBOSE
30
+ puts "... run tests with MySQL using: `rake test AR_ADAPTER=mysql`"
31
+ end
32
+
33
+ desc "Creates a (test) PostgreSQL database"
34
+ task 'db:create:postgresql' do
35
+ fail 'could not create database: psql executable not found' unless psql = _which('psql')
36
+ fail 'could not create database: missing "postgres" role' unless PostgresHelper.postgres_role?
37
+ ENV['Rake'] = true; ENV['AR_ADAPTER'] ||= 'postgresql'
38
+ load File.expand_path('test/test_helper.rb', File.dirname(__FILE__))
39
+
40
+ script = "DROP DATABASE IF EXISTS #{AR_CONFIG[:database]};"
41
+ if pg_user = AR_CONFIG[:username] || ENV['PGUSER'] || ENV_JAVA['user.name']
42
+ script << "DROP USER IF EXISTS #{pg_user};"
43
+ pg_password = AR_CONFIG[:password] || ENV['PGPASSWORD']
44
+ script << "CREATE USER #{pg_user} CREATEDB SUPERUSER LOGIN PASSWORD '#{pg_password}';"
45
+ end
46
+ script << "CREATE DATABASE #{AR_CONFIG[:database]} OWNER #{pg_user || 'postgres'};"
47
+ params = { '-U' => ENV['PSQL_USER'] || 'postgres' }; params['-q'] = nil unless $VERBOSE
48
+ puts "Creating PostgreSQL database: #{AR_CONFIG[:database]}"
49
+ sh "cat #{_sql_script(script).path} | #{psql} #{params.to_a.join(' ')}", :verbose => $VERBOSE
50
+ puts "... run tests with PostgreSQL using: `rake test AR_ADAPTER=postgresql`"
51
+ end
52
+
53
+ def _sql_script(content, name = '_sql_script')
54
+ require 'tempfile'
55
+ script = Tempfile.new(name)
56
+ script.puts content
57
+ yield(script) if block_given?
58
+ script.close
59
+ at_exit { script.unlink }
60
+ script
61
+ end
62
+
63
+ def _which(cmd)
64
+ exts = ENV['PATHEXT'] ? ENV['PATHEXT'].split(';') : ['']
65
+ ( ENV['PATH'] || '' ).split(File::PATH_SEPARATOR).each do |path|
66
+ exts.each do |ext|
67
+ exe = File.join(path, "#{cmd}#{ext}")
68
+ return exe if File.executable? exe
69
+ end
70
+ end
71
+ nil
72
+ end
73
+
74
+ namespace :tomcat do
75
+
76
+ tomcat_maven_repo = 'http://repo2.maven.org/maven2/org/apache/tomcat'
77
+ download_dir = File.expand_path('test/jars', File.dirname(__FILE__))
78
+ version_default = '7.0.54'
79
+
80
+ [ 'tomcat-jdbc', 'tomcat-dbcp' ].each do |tomcat_pool|
81
+ namespace tomcat_pool.sub('tomcat-', '') do # rake tomcat:dbcp:download
82
+
83
+ tomcat_pool_jar = "#{tomcat_pool}.jar"
84
+
85
+ task :download, :version do |_,args| # rake tomcat:jdbc:download[7.0.54]
86
+ version = args[:version] || version_default
87
+
88
+ uri = "#{tomcat_maven_repo}/#{tomcat_pool}/#{version}/#{tomcat_pool}-#{version}.jar"
89
+
90
+ require 'open-uri'; require 'tmpdir'
91
+
92
+ temp_dir = File.join(Dir.tmpdir, (Time.now.to_f * 1000).to_i.to_s)
93
+ FileUtils.mkdir temp_dir
94
+
95
+ Dir.chdir(temp_dir) do
96
+ FileUtils.mkdir download_dir unless File.exist?(download_dir)
97
+ puts "downloading #{uri}"
98
+ file = open(uri)
99
+ FileUtils.cp file.path, File.join(download_dir, tomcat_pool_jar)
100
+ end
101
+
102
+ FileUtils.rm_r temp_dir
103
+ end
104
+
105
+ task :check do
106
+ jar_path = File.join(download_dir, tomcat_pool_jar)
107
+ unless File.exist?(jar_path)
108
+ Rake::Task["#{tomcat_pool.sub('-',':')}:download"].invoke
109
+ end
110
+ end
111
+
112
+ task :clear do
113
+ jar_path = File.join(download_dir, tomcat_pool_jar)
114
+ rm jar_path if File.exist?(jar_path)
115
+ end
116
+
117
+ end
118
+ end
119
+
120
+ namespace 'jndi' do # contains a FS JNDI impl (for tests)
121
+
122
+ catalina_jar = 'tomcat-catalina.jar'; juli_jar = 'tomcat-juli.jar'
123
+
124
+ task :download, :version do |_,args|
125
+ version = args[:version] || version_default
126
+
127
+ catalina_uri = "#{tomcat_maven_repo}/tomcat-catalina/#{version}/tomcat-catalina-#{version}.jar"
128
+ juli_uri = "#{tomcat_maven_repo}/tomcat-juli/#{version}/tomcat-juli-#{version}.jar"
129
+
130
+ require 'open-uri'; require 'tmpdir'
131
+
132
+ temp_dir = File.join(Dir.tmpdir, (Time.now.to_f * 1000).to_i.to_s)
133
+ FileUtils.mkdir temp_dir
134
+
135
+ downloads = Hash.new
136
+ downloads[catalina_jar] = catalina_uri
137
+ downloads[juli_jar] = juli_uri
138
+
139
+ Dir.chdir(temp_dir) do
140
+ FileUtils.mkdir download_dir unless File.exist?(download_dir)
141
+ downloads.each do |jar, uri|
142
+ puts "downloading #{uri}"
143
+ file = open(uri)
144
+ FileUtils.cp file.path, File.join(download_dir, jar)
145
+ end
146
+ end
147
+
148
+ FileUtils.rm_r temp_dir
149
+ end
150
+
151
+ task :check do
152
+ jar_path = File.join(download_dir, catalina_jar)
153
+ unless File.exist?(jar_path)
154
+ Rake::Task['tomcat:jndi:download'].invoke
155
+ end
156
+ end
157
+
158
+ task :clear do
159
+ jar_path = File.join(download_dir, catalina_jar)
160
+ rm jar_path if File.exist?(jar_path)
161
+ jar_path = File.join(download_dir, juli_jar)
162
+ rm jar_path if File.exist?(jar_path)
163
+ end
164
+
165
+ end
166
+
167
+ end
@@ -0,0 +1,26 @@
1
+ # coding: utf-8
2
+
3
+ Gem::Specification.new do |gem|
4
+ gem.name = 'activerecord-bogacs'
5
+
6
+ path = File.expand_path('lib/active_record/bogacs/version.rb', File.dirname(__FILE__))
7
+ gem.version = File.read(path).match( /.*VERSION\s*=\s*['"](.*)['"]/m )[1]
8
+
9
+ gem.authors = ['Karol Bucek']
10
+ gem.email = ['self@kares.org']
11
+ gem.description = %q{(experimental) alternatives for ActiveRecord::ConnectionAdapters::ConnectionPool}
12
+ gem.summary = %q{A small body of still water, usually fresh ... for ActiveRecord!}
13
+ gem.homepage = "http://github.com/kares/activerecord-bogacs"
14
+ #gem.licenses = ['MIT']
15
+
16
+ gem.files = `git ls-files`.split($/)
17
+ gem.executables = gem.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ gem.test_files = gem.files.grep(%r{^test/})
19
+ gem.require_paths = ["lib"]
20
+
21
+ gem.add_runtime_dependency 'atomic', '~> 1.1'
22
+ gem.add_runtime_dependency 'thread_safe', '~> 0.3'
23
+
24
+ gem.add_development_dependency 'rake', '~> 10.3'
25
+ gem.add_development_dependency 'test-unit', '~> 2.5'
26
+ end
@@ -0,0 +1,55 @@
1
+ require 'active_record/bogacs/version'
2
+
3
+ require 'active_record'
4
+ require 'active_record/version'
5
+ require 'active_record/connection_adapters/abstract/connection_pool'
6
+
7
+ module ActiveRecord
8
+ module Bogacs
9
+ autoload :DefaultPool, 'active_record/bogacs/default_pool'
10
+ autoload :FalsePool, 'active_record/bogacs/false_pool'
11
+ autoload :ShareablePool, 'active_record/bogacs/shareable_pool'
12
+ end
13
+ autoload :SharedConnection, 'active_record/shared_connection'
14
+ end
15
+
16
+ # NOTE: needs explicit configuration - before connection gets established e.g.
17
+ #
18
+ # klass = ActiveRecord::Bogacs::FalsePool
19
+ # ActiveRecord::ConnectionAdapters::ConnectionHandler.connection_pool_class = klass
20
+ #
21
+ module ActiveRecord
22
+ module ConnectionAdapters
23
+ # @private there's no other way to change the pool class to use but to patch :(
24
+ ConnectionHandler.class_eval do
25
+
26
+ @@connection_pool_class = ConnectionAdapters::ConnectionPool
27
+
28
+ def connection_pool_class; @@connection_pool_class end
29
+ def self.connection_pool_class=(klass); @@connection_pool_class = klass end
30
+
31
+ if ActiveRecord::VERSION::MAJOR > 3 # 4.x
32
+
33
+ def establish_connection(owner, spec)
34
+ @class_to_pool.clear
35
+ owner_to_pool[owner.name] = connection_pool_class.new(spec)
36
+ end
37
+
38
+ elsif ActiveRecord::VERSION::MAJOR == 3 && ActiveRecord::VERSION::MINOR == 2
39
+
40
+ def establish_connection(name, spec)
41
+ @class_to_pool[name] =
42
+ ( @connection_pools[spec] ||= connection_pool_class.new(spec) )
43
+ end
44
+
45
+ else # 2.3/3.0/3.1
46
+
47
+ def establish_connection(name, spec)
48
+ @connection_pools[name] = connection_pool_class.new(spec)
49
+ end
50
+
51
+ end
52
+
53
+ end
54
+ end
55
+ end