diff options
15 files changed, 117 insertions, 224 deletions
diff --git a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb index 450ef69744..c6699737b4 100644 --- a/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb +++ b/activerecord/lib/active_record/connection_adapters/abstract/connection_pool.rb @@ -2,6 +2,7 @@ require 'thread' require 'monitor' require 'set' require 'active_support/core_ext/module/deprecation' +require 'timeout' module ActiveRecord # Raised when a connection could not be obtained within the connection @@ -91,6 +92,21 @@ module ActiveRecord attr_accessor :automatic_reconnect, :timeout attr_reader :spec, :connections, :size, :reaper + class Latch # :nodoc: + def initialize + @mutex = Mutex.new + @cond = ConditionVariable.new + end + + def release + @mutex.synchronize { @cond.broadcast } + end + + def await + @mutex.synchronize { @cond.wait @mutex } + end + end + # Creates a new ConnectionPool object. +spec+ is a ConnectionSpecification # object which describes database connection information (e.g. adapter, # host name, username, password, etc), as well as the maximum size for @@ -112,25 +128,9 @@ module ActiveRecord # default max pool size to 5 @size = (spec.config[:pool] && spec.config[:pool].to_i) || 5 + @latch = Latch.new @connections = [] @automatic_reconnect = true - - # connections available to be checked out - @available = [] - - # number of threads waiting to check out a connection - @num_waiting = 0 - - # signal threads waiting - @cond = new_cond - end - - # Hack for tests to be able to add connections. Do not call outside of tests - def insert_connection_for_test!(c) - synchronize do - @connections << c - @available << c - end end # Retrieve the connection associated with the current thread, or call @@ -188,7 +188,6 @@ module ActiveRecord conn.disconnect! end @connections = [] - @available = [] end end @@ -203,9 +202,6 @@ module ActiveRecord @connections.delete_if do |conn| conn.requires_reloading? end - @available.delete_if do |conn| - conn.requires_reloading? - end end end @@ -229,19 +225,23 @@ module ActiveRecord # Raises: # - PoolFullError: no connection can be obtained from the pool. def checkout - synchronize do - conn = nil - - if @num_waiting == 0 - conn = acquire_connection - end + loop do + # Checkout an available connection + synchronize do + # Try to find a connection that hasn't been leased, and lease it + conn = connections.find { |c| c.lease } + + # If all connections were leased, and we have room to expand, + # create a new connection and lease it. + if !conn && connections.size < size + conn = checkout_new_connection + conn.lease + end - unless conn - conn = wait_until(@timeout) { acquire_connection } + return checkout_and_verify(conn) if conn end - conn.lease - checkout_and_verify(conn) + Timeout.timeout(@timeout, PoolFullError) { @latch.await } end end @@ -257,10 +257,8 @@ module ActiveRecord end release conn - - @available.unshift conn - @cond.signal end + @latch.release end # Remove a connection from the connection pool. The connection will @@ -268,14 +266,12 @@ module ActiveRecord def remove(conn) synchronize do @connections.delete conn - @available.delete conn # FIXME: we might want to store the key on the connection so that removing # from the reserved hash will be a little easier. release conn - - @cond.signal # can make a new connection now end + @latch.release end # Removes dead connections from the pool. A dead connection can occur @@ -287,60 +283,12 @@ module ActiveRecord connections.dup.each do |conn| remove conn if conn.in_use? && stale > conn.last_use && !conn.active? end - @cond.broadcast # may violate fairness end + @latch.release end private - # Take an available connection or, if possible, create a new - # one, or nil. - # - # Monitor must be held while calling this method. - # - # Returns: a newly acquired connection. - def acquire_connection - if @available.any? - @available.pop - elsif connections.size < size - checkout_new_connection - end - end - - # Wait on +@cond+ until the block returns non-nil. Note that - # unlike MonitorMixin::ConditionVariable#wait_until, this method - # does not test the block before the first wait period. - # - # Monitor must be held when calling this method. - # - # +timeout+: Integer timeout in seconds - # - # Returns: the result of the block - # - # Raises: - # - PoolFullError: timeout elapsed before +&block+ returned a connection - def wait_until(timeout, &block) - @num_waiting += 1 - begin - t0 = Time.now - loop do - elapsed = Time.now - t0 - if elapsed >= timeout - msg = 'could not obtain a database connection within %0.3f seconds (waited %0.3f seconds)' % - [timeout, elapsed] - raise PoolFullError, msg - end - - @cond.wait(timeout - elapsed) - - conn = yield - return conn if conn - end - ensure - @num_waiting -= 1 - end - end - def release(conn) thread_id = if @reserved_connections[current_connection_id] == conn current_connection_id diff --git a/activerecord/lib/active_record/store.rb b/activerecord/lib/active_record/store.rb index ce2ea85ef9..fdd82b489a 100644 --- a/activerecord/lib/active_record/store.rb +++ b/activerecord/lib/active_record/store.rb @@ -1,3 +1,5 @@ +require 'active_support/core_ext/hash/indifferent_access' + module ActiveRecord # Store gives you a thin wrapper around serialize for the purpose of storing hashes in a single column. # It's like a simple key/value store backed into your record when you don't care about being able to @@ -13,9 +15,6 @@ module ActiveRecord # You can set custom coder to encode/decode your serialized attributes to/from different formats. # JSON, YAML, Marshal are supported out of the box. Generally it can be any wrapper that provides +load+ and +dump+. # - # String keys should be used for direct access to virtual attributes because of most of the coders do not - # distinguish symbols and strings as keys. - # # Examples: # # class User < ActiveRecord::Base @@ -23,8 +22,12 @@ module ActiveRecord # end # # u = User.new(color: 'black', homepage: '37signals.com') - # u.color # Accessor stored attribute - # u.settings['country'] = 'Denmark' # Any attribute, even if not specified with an accessor + # u.color # Accessor stored attribute + # u.settings[:country] = 'Denmark' # Any attribute, even if not specified with an accessor + # + # # There is no difference between strings and symbols for accessing custom attributes + # u.settings[:country] # => 'Denmark' + # u.settings['country'] # => 'Denmark' # # # Add additional accessors to an existing store through store_accessor # class SuperUser < User @@ -35,24 +38,38 @@ module ActiveRecord module ClassMethods def store(store_attribute, options = {}) - serialize store_attribute, options.fetch(:coder, Hash) + serialize store_attribute, options.fetch(:coder, ActiveSupport::HashWithIndifferentAccess) store_accessor(store_attribute, options[:accessors]) if options.has_key? :accessors end def store_accessor(store_attribute, *keys) keys.flatten.each do |key| define_method("#{key}=") do |value| - send("#{store_attribute}=", {}) unless send(store_attribute).is_a?(Hash) - send(store_attribute)[key.to_s] = value + initialize_store_attribute(store_attribute) + send(store_attribute)[key] = value send("#{store_attribute}_will_change!") end define_method(key) do - send("#{store_attribute}=", {}) unless send(store_attribute).is_a?(Hash) - send(store_attribute)[key.to_s] + initialize_store_attribute(store_attribute) + send(store_attribute)[key] end end end end + + private + def initialize_store_attribute(store_attribute) + case attribute = send(store_attribute) + when ActiveSupport::HashWithIndifferentAccess + # Already initialized. Do nothing. + when Hash + # Initialized as a Hash. Convert to indifferent access. + send :"#{store_attribute}=", attribute.with_indifferent_access + else + # Uninitialized. Set to an indifferent hash. + send :"#{store_attribute}=", ActiveSupport::HashWithIndifferentAccess.new + end + end end end diff --git a/activerecord/test/cases/connection_adapters/abstract_adapter_test.rb b/activerecord/test/cases/connection_adapters/abstract_adapter_test.rb index 3e3d6e2769..7dc6e8afcb 100644 --- a/activerecord/test/cases/connection_adapters/abstract_adapter_test.rb +++ b/activerecord/test/cases/connection_adapters/abstract_adapter_test.rb @@ -36,7 +36,7 @@ module ActiveRecord def test_close pool = ConnectionPool.new(ConnectionSpecification.new({}, nil)) - pool.insert_connection_for_test! adapter + pool.connections << adapter adapter.pool = pool # Make sure the pool marks the connection in use diff --git a/activerecord/test/cases/connection_pool_test.rb b/activerecord/test/cases/connection_pool_test.rb index b1905515d3..8dc9f761c2 100644 --- a/activerecord/test/cases/connection_pool_test.rb +++ b/activerecord/test/cases/connection_pool_test.rb @@ -200,121 +200,6 @@ module ActiveRecord end.join end - # The connection pool is "fair" if threads waiting for - # connections receive them the order in which they began - # waiting. This ensures that we don't timeout one HTTP request - # even while well under capacity in a multi-threaded environment - # such as a Java servlet container. - # - # We don't need strict fairness: if two connections become - # available at the same time, it's fine of two threads that were - # waiting acquire the connections out of order. - # - # Thus this test prepares waiting threads and then trickles in - # available connections slowly, ensuring the wakeup order is - # correct in this case. - # - # Try a few times since it might work out just by chance. - def test_checkout_fairness - 4.times { setup; do_checkout_fairness } - end - - def do_checkout_fairness - expected = (1..@pool.size).to_a.freeze - # check out all connections so our threads start out waiting - conns = expected.map { @pool.checkout } - mutex = Mutex.new - order = [] - errors = [] - - threads = expected.map do |i| - t = Thread.new { - begin - @pool.checkout # never checked back in - mutex.synchronize { order << i } - rescue => e - mutex.synchronize { errors << e } - end - } - Thread.pass until t.status == "sleep" - t - end - - # this should wake up the waiting threads one by one in order - conns.each { |conn| @pool.checkin(conn); sleep 0.1 } - - threads.each(&:join) - - raise errors.first if errors.any? - - assert_equal(expected, order) - end - - # As mentioned in #test_checkout_fairness, we don't care about - # strict fairness. This test creates two groups of threads: - # group1 whose members all start waiting before any thread in - # group2. Enough connections are checked in to wakeup all - # group1 threads, and the fact that only group1 and no group2 - # threads acquired a connection is enforced. - # - # Try a few times since it might work out just by chance. - def test_checkout_fairness_by_group - 4.times { setup; do_checkout_fairness_by_group } - end - - def do_checkout_fairness_by_group - @pool.instance_variable_set(:@size, 10) - # take all the connections - conns = (1..10).map { @pool.checkout } - mutex = Mutex.new - successes = [] # threads that successfully got a connection - errors = [] - - make_thread = proc do |i| - t = Thread.new { - begin - @pool.checkout # never checked back in - mutex.synchronize { successes << i } - rescue => e - mutex.synchronize { errors << e } - end - } - Thread.pass until t.status == "sleep" - t - end - - # all group1 threads start waiting before any in group2 - group1 = (1..5).map(&make_thread) - group2 = (6..10).map(&make_thread) - - # checkin n connections back to the pool - checkin = proc do |n| - n.times do - c = conns.pop - @pool.checkin(c) - end - end - - checkin.call(group1.size) # should wake up all group1 - - loop do - sleep 0.1 - break if mutex.synchronize { (successes.size + errors.size) == group1.size } - end - - winners = mutex.synchronize { successes.dup } - checkin.call(group2.size) # should wake up everyone remaining - - group1.each(&:join) - group2.each(&:join) - - assert_equal((1..group1.size).to_a, winners.sort) - - if errors.any? - raise errors.first - end - end - def test_automatic_reconnect= pool = ConnectionPool.new ActiveRecord::Base.connection_pool.spec assert pool.automatic_reconnect diff --git a/activerecord/test/cases/store_test.rb b/activerecord/test/cases/store_test.rb index e1d0f1f799..3a5d84df9f 100644 --- a/activerecord/test/cases/store_test.rb +++ b/activerecord/test/cases/store_test.rb @@ -41,6 +41,40 @@ class StoreTest < ActiveRecord::TestCase assert_equal false, @john.remember_login end + test "preserve store attributes data in HashWithIndifferentAccess format without any conversion" do + @john.json_data = HashWithIndifferentAccess.new(:height => 'tall', 'weight' => 'heavy') + @john.height = 'low' + assert_equal true, @john.json_data.instance_of?(HashWithIndifferentAccess) + assert_equal 'low', @john.json_data[:height] + assert_equal 'low', @john.json_data['height'] + assert_equal 'heavy', @john.json_data[:weight] + assert_equal 'heavy', @john.json_data['weight'] + end + + test "convert store attributes from Hash to HashWithIndifferentAccess saving the data and access attributes indifferently" do + @john.json_data = { :height => 'tall', 'weight' => 'heavy' } + assert_equal true, @john.json_data.instance_of?(Hash) + assert_equal 'tall', @john.json_data[:height] + assert_equal nil, @john.json_data['height'] + assert_equal nil, @john.json_data[:weight] + assert_equal 'heavy', @john.json_data['weight'] + @john.height = 'low' + assert_equal true, @john.json_data.instance_of?(HashWithIndifferentAccess) + assert_equal 'low', @john.json_data[:height] + assert_equal 'low', @john.json_data['height'] + assert_equal 'heavy', @john.json_data[:weight] + assert_equal 'heavy', @john.json_data['weight'] + end + + test "convert store attributes from any format other than Hash or HashWithIndifferent access losing the data" do + @john.json_data = "somedata" + @john.height = 'low' + assert_equal true, @john.json_data.instance_of?(HashWithIndifferentAccess) + assert_equal 'low', @john.json_data[:height] + assert_equal 'low', @john.json_data['height'] + assert_equal false, @john.json_data.delete_if { |k, v| k == 'height' }.any? + end + test "reading store attributes through accessors encoded with JSON" do assert_equal 'tall', @john.height assert_nil @john.weight diff --git a/activesupport/lib/active_support.rb b/activesupport/lib/active_support.rb index 8f018dcbc6..56d6676961 100644 --- a/activesupport/lib/active_support.rb +++ b/activesupport/lib/active_support.rb @@ -25,6 +25,7 @@ require 'securerandom' require "active_support/dependencies/autoload" require "active_support/version" require "active_support/logger" +require "active_support/lazy_load_hooks" module ActiveSupport extend ActiveSupport::Autoload diff --git a/activesupport/lib/active_support/dependencies/autoload.rb b/activesupport/lib/active_support/dependencies/autoload.rb index a1626ebeba..4045db3232 100644 --- a/activesupport/lib/active_support/dependencies/autoload.rb +++ b/activesupport/lib/active_support/dependencies/autoload.rb @@ -1,5 +1,4 @@ require "active_support/inflector/methods" -require "active_support/lazy_load_hooks" module ActiveSupport module Autoload diff --git a/railties/lib/rails/generators/actions.rb b/railties/lib/rails/generators/actions.rb index 6cd2ea2bbd..92f40b9e31 100644 --- a/railties/lib/rails/generators/actions.rb +++ b/railties/lib/rails/generators/actions.rb @@ -27,7 +27,7 @@ module Rails log :gemfile, message options.each do |option, value| - parts << ":#{option} => #{value.inspect}" + parts << "#{option}: #{value.inspect}" end in_root do diff --git a/railties/lib/rails/generators/rails/app/templates/config/routes.rb b/railties/lib/rails/generators/rails/app/templates/config/routes.rb index 286e93c3cf..303e47877f 100644 --- a/railties/lib/rails/generators/rails/app/templates/config/routes.rb +++ b/railties/lib/rails/generators/rails/app/templates/config/routes.rb @@ -1,6 +1,10 @@ <%= app_const %>.routes.draw do # The priority is based upon order of creation: # first created -> highest priority. + + # You can have the root of your site routed with "root" + # just remember to delete public/index.html. + # root :to => 'welcome#index' # Sample of regular route: # get 'products/:id' => 'catalog#view' @@ -46,9 +50,6 @@ # resources :products # end - # You can have the root of your site routed with "root" - # just remember to delete public/index.html. - # root :to => 'welcome#index' # See how all your routes lay out with "rake routes" end
\ No newline at end of file diff --git a/railties/lib/rails/generators/rails/controller/templates/controller.rb b/railties/lib/rails/generators/rails/controller/templates/controller.rb index 9b04192126..ece6bbba3b 100644 --- a/railties/lib/rails/generators/rails/controller/templates/controller.rb +++ b/railties/lib/rails/generators/rails/controller/templates/controller.rb @@ -1,5 +1,5 @@ <% if namespaced? -%> -require "<%= namespaced_file_path %>/application_controller" +require_dependency "<%= namespaced_file_path %>/application_controller" <% end -%> <% module_namespacing do -%> diff --git a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb index 722e37e20b..7088367462 100644 --- a/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb +++ b/railties/lib/rails/generators/rails/plugin_new/plugin_new_generator.rb @@ -139,7 +139,7 @@ task :default => :test gemfile_in_app_path = File.join(rails_app_path, "Gemfile") if File.exist? gemfile_in_app_path - entry = "gem '#{name}', :path => '#{relative_path}'" + entry = "gem '#{name}', path: '#{relative_path}'" append_file gemfile_in_app_path, entry end end diff --git a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb index 5cd31bf2ee..0294bde582 100644 --- a/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb +++ b/railties/lib/rails/generators/rails/scaffold_controller/templates/controller.rb @@ -1,5 +1,5 @@ <% if namespaced? -%> -require "<%= namespaced_file_path %>/application_controller" +require_dependency "<%= namespaced_file_path %>/application_controller" <% end -%> <% module_namespacing do -%> diff --git a/railties/test/generators/actions_test.rb b/railties/test/generators/actions_test.rb index a8c8fcd5b7..bc086c5986 100644 --- a/railties/test/generators/actions_test.rb +++ b/railties/test/generators/actions_test.rb @@ -67,6 +67,14 @@ class ActionsTest < Rails::Generators::TestCase assert_file 'Gemfile', /^gem "rspec-rails"$/ end + def test_gem_should_include_options + run_generator + + action :gem, 'rspec', github: 'dchelimsky/rspec', tag: '1.2.9.rc1' + + assert_file 'Gemfile', /gem "rspec", github: "dchelimsky\/rspec", tag: "1\.2\.9\.rc1"/ + end + def test_gem_group_should_wrap_gems_in_a_group run_generator diff --git a/railties/test/generators/namespaced_generators_test.rb b/railties/test/generators/namespaced_generators_test.rb index 903465392d..09169ef2d2 100644 --- a/railties/test/generators/namespaced_generators_test.rb +++ b/railties/test/generators/namespaced_generators_test.rb @@ -21,7 +21,7 @@ class NamespacedControllerGeneratorTest < NamespacedGeneratorTestCase def test_namespaced_controller_skeleton_is_created run_generator assert_file "app/controllers/test_app/account_controller.rb", - /require "test_app\/application_controller"/, + /require_dependency "test_app\/application_controller"/, /module TestApp/, / class AccountController < ApplicationController/ @@ -234,7 +234,7 @@ class NamespacedScaffoldGeneratorTest < NamespacedGeneratorTestCase # Controller assert_file "app/controllers/test_app/product_lines_controller.rb", - /require "test_app\/application_controller"/, + /require_dependency "test_app\/application_controller"/, /module TestApp/, /class ProductLinesController < ApplicationController/ diff --git a/railties/test/generators/plugin_new_generator_test.rb b/railties/test/generators/plugin_new_generator_test.rb index 51374e5f3f..58740978aa 100644 --- a/railties/test/generators/plugin_new_generator_test.rb +++ b/railties/test/generators/plugin_new_generator_test.rb @@ -279,7 +279,7 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase run_generator [destination_root] - assert_file gemfile_path, /gem 'bukkits', :path => 'tmp\/bukkits'/ + assert_file gemfile_path, /gem 'bukkits', path: 'tmp\/bukkits'/ ensure Object.send(:remove_const, 'APP_PATH') FileUtils.rm gemfile_path @@ -294,7 +294,7 @@ class PluginNewGeneratorTest < Rails::Generators::TestCase run_generator [destination_root, "--skip-gemfile-entry"] assert_file gemfile_path do |contents| - assert_no_match(/gem 'bukkits', :path => 'tmp\/bukkits'/, contents) + assert_no_match(/gem 'bukkits', path: 'tmp\/bukkits'/, contents) end ensure Object.send(:remove_const, 'APP_PATH') |