activerecord-bogacs 0.1.0

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