diff options
-rw-r--r-- | activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb | 3 | ||||
-rw-r--r-- | activerecord/test/cases/migration_test.rb | 36 | ||||
-rw-r--r-- | activesupport/lib/active_support/buffered_logger.rb | 44 | ||||
-rw-r--r-- | activesupport/lib/active_support/cache/file_store.rb | 21 | ||||
-rw-r--r-- | activesupport/test/buffered_logger_test.rb | 53 | ||||
-rw-r--r-- | activesupport/test/caching_test.rb | 34 | ||||
-rw-r--r-- | railties/guides/source/caching_with_rails.textile | 6 |
7 files changed, 185 insertions, 12 deletions
diff --git a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb index 1932a849ee..e0e957a12c 100644 --- a/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb +++ b/activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb @@ -413,6 +413,8 @@ module ActiveRecord self.limit = options[:limit] if options.include?(:limit) self.default = options[:default] if include_default self.null = options[:null] if options.include?(:null) + self.precision = options[:precision] if options.include?(:precision) + self.scale = options[:scale] if options.include?(:scale) end end end @@ -467,6 +469,7 @@ module ActiveRecord @definition.column(column_name, column.type, :limit => column.limit, :default => column.default, + :precision => column.precision, :scale => column.scale, :null => column.null) end @definition.primary_key(primary_key(from)) if primary_key(from) diff --git a/activerecord/test/cases/migration_test.rb b/activerecord/test/cases/migration_test.rb index b3f1785f44..49944eced9 100644 --- a/activerecord/test/cases/migration_test.rb +++ b/activerecord/test/cases/migration_test.rb @@ -518,6 +518,42 @@ if ActiveRecord::Base.connection.supports_migrations? assert_equal 7, wealth_column.scale end + # Test SQLite adapter specifically for decimal types with precision and scale + # attributes, since these need to be maintained in schema but aren't actually + # used in SQLite itself + if current_adapter?(:SQLite3Adapter) + def test_change_column_with_new_precision_and_scale + Person.delete_all + Person.connection.add_column 'people', 'wealth', :decimal, :precision => 9, :scale => 7 + Person.reset_column_information + + Person.connection.change_column 'people', 'wealth', :decimal, :precision => 12, :scale => 8 + Person.reset_column_information + + wealth_column = Person.columns_hash['wealth'] + assert_equal 12, wealth_column.precision + assert_equal 8, wealth_column.scale + end + + def test_change_column_preserve_other_column_precision_and_scale + Person.delete_all + Person.connection.add_column 'people', 'last_name', :string + Person.connection.add_column 'people', 'wealth', :decimal, :precision => 9, :scale => 7 + Person.reset_column_information + + wealth_column = Person.columns_hash['wealth'] + assert_equal 9, wealth_column.precision + assert_equal 7, wealth_column.scale + + Person.connection.change_column 'people', 'last_name', :string, :null => false + Person.reset_column_information + + wealth_column = Person.columns_hash['wealth'] + assert_equal 9, wealth_column.precision + assert_equal 7, wealth_column.scale + end + end + def test_native_types Person.delete_all Person.connection.add_column "people", "last_name", :string diff --git a/activesupport/lib/active_support/buffered_logger.rb b/activesupport/lib/active_support/buffered_logger.rb index 26412cd7f4..136e245859 100644 --- a/activesupport/lib/active_support/buffered_logger.rb +++ b/activesupport/lib/active_support/buffered_logger.rb @@ -25,22 +25,28 @@ module ActiveSupport # Silences the logger for the duration of the block. def silence(temporary_level = ERROR) if silencer + old_logger_level = @tmp_levels[Thread.current] begin - old_logger_level, self.level = level, temporary_level + @tmp_levels[Thread.current] = temporary_level yield self ensure - self.level = old_logger_level + if old_logger_level + @tmp_levels[Thread.current] = old_logger_level + else + @tmp_levels.delete(Thread.current) + end end else yield self end end - attr_accessor :level + attr_writer :level attr_reader :auto_flushing def initialize(log, level = DEBUG) @level = level + @tmp_levels = {} @buffer = Hash.new { |h,k| h[k] = [] } @auto_flushing = 1 @guard = Mutex.new @@ -62,8 +68,12 @@ module ActiveSupport end end + def level + @tmp_levels[Thread.current] || @level + end + def add(severity, message = nil, progname = nil, &block) - return if @level > severity + return if level > severity message = (message || (block && block.call) || progname).to_s # If a newline is necessary then create a new message ending with a newline. # Ensures that the original message is not mutated. @@ -84,7 +94,7 @@ module ActiveSupport end # end def #{severity.downcase}? # def debug? - #{severity} >= @level # DEBUG >= @level + #{severity} >= level # DEBUG >= @level end # end EOT end @@ -105,13 +115,15 @@ module ActiveSupport def flush @guard.synchronize do - buffer.each do |content| - @log.write(content) - end + write_buffer(buffer) # Important to do this even if buffer was empty or else @buffer will # accumulate empty arrays for each request where nothing was logged. clear_buffer + + # Clear buffers associated with dead threads or else spawned threads + # that don't call flush will result in a memory leak. + flush_dead_buffers end end @@ -133,5 +145,21 @@ module ActiveSupport def clear_buffer @buffer.delete(Thread.current) end + + # Find buffers created by threads that are no longer alive and flush them to the log + # in order to prevent memory leaks from spawned threads. + def flush_dead_buffers #:nodoc: + @buffer.keys.reject{|thread| thread.alive?}.each do |thread| + buffer = @buffer[thread] + write_buffer(buffer) + @buffer.delete(thread) + end + end + + def write_buffer(buffer) + buffer.each do |content| + @log.write(content) + end + end end end diff --git a/activesupport/lib/active_support/cache/file_store.rb b/activesupport/lib/active_support/cache/file_store.rb index b431041b76..bc5d94b5a7 100644 --- a/activesupport/lib/active_support/cache/file_store.rb +++ b/activesupport/lib/active_support/cache/file_store.rb @@ -26,11 +26,26 @@ module ActiveSupport FileUtils.rm_r(root_dirs.collect{|f| File.join(cache_path, f)}) end + # Cleanup the cache by removing old entries. By default this will delete entries + # that haven't been accessed in one day. You can change this behavior by passing + # in a +not_accessed_in+ option. Any entry not accessed in that number of seconds + # in the past will be deleted. Alternatively, you can pass in +:expired_only+ with + # +true+ to only delete expired entries. def cleanup(options = nil) options = merged_options(options) - each_key(options) do |key| - entry = read_entry(key, options) - delete_entry(key, options) if entry && entry.expired? + expired_only = options[:expired_only] + timestamp = Time.now - (options[:not_accessed_in] || 1.day.to_i) + search_dir(cache_path) do |fname| + if expired_only + key = file_path_key(fname) + entry = read_entry(key, options) + delete_entry(key, options) if entry && entry.expired? + else + if File.atime(fname) <= timestamp + key = file_path_key(fname) + delete_entry(key, options) + end + end end end diff --git a/activesupport/test/buffered_logger_test.rb b/activesupport/test/buffered_logger_test.rb index 21049d685b..8699862d9e 100644 --- a/activesupport/test/buffered_logger_test.rb +++ b/activesupport/test/buffered_logger_test.rb @@ -198,4 +198,57 @@ class BufferedLoggerTest < Test::Unit::TestCase end assert byte_string.include?(BYTE_STRING) end + + def test_silence_only_current_thread + @logger.auto_flushing = true + run_thread_a = false + + a = Thread.new do + while !run_thread_a do + sleep(0.001) + end + @logger.info("x") + run_thread_a = false + end + + @logger.silence do + run_thread_a = true + @logger.info("a") + while run_thread_a do + sleep(0.001) + end + end + + a.join + + assert @output.string.include?("x") + assert !@output.string.include?("a") + end + + def test_flush_dead_buffers + @logger.auto_flushing = false + + a = Thread.new do + @logger.info("a") + end + + keep_running = true + b = Thread.new do + @logger.info("b") + while keep_running + sleep(0.001) + end + end + + @logger.info("x") + a.join + @logger.flush + + + assert @output.string.include?("x") + assert @output.string.include?("a") + assert !@output.string.include?("b") + + keep_running = false + end end diff --git a/activesupport/test/caching_test.rb b/activesupport/test/caching_test.rb index cb5362525f..1b2f6d061c 100644 --- a/activesupport/test/caching_test.rb +++ b/activesupport/test/caching_test.rb @@ -557,6 +557,29 @@ class FileStoreTest < ActiveSupport::TestCase key = @cache_with_pathname.send(:key_file_path, "views/index?id=1") assert_equal "views/index?id=1", @cache_with_pathname.send(:file_path_key, key) end + + def test_cleanup_with_not_accessed_in + @cache.write(1, "aaaaaaaaaa") + @cache.write(2, "bbbbbbbbbb") + @cache.write(3, "cccccccccc") + sleep(2) + @cache.read(2) + @cache.cleanup(:not_accessed_in => 1) + assert_equal false, @cache.exist?(1) + assert_equal true, @cache.exist?(2) + assert_equal false, @cache.exist?(3) + end + + def test_cleanup_with_expired_only + @cache.write(1, "aaaaaaaaaa", :expires_in => 0.001) + @cache.write(2, "bbbbbbbbbb") + @cache.write(3, "cccccccccc", :expires_in => 0.001) + sleep(0.002) + @cache.cleanup(:expired_only => 0.001) + assert_equal false, @cache.exist?(1) + assert_equal true, @cache.exist?(2) + assert_equal false, @cache.exist?(3) + end # Because file systems have a maximum filename size, filenames > max size should be split in to directories # If filename is 'AAAAB', where max size is 4, the returned path should be AAAA/B @@ -646,6 +669,17 @@ class MemoryStoreTest < ActiveSupport::TestCase assert_equal true, @cache.exist?(2) assert_equal false, @cache.exist?(1) end + + def test_cleanup_removes_expired_entries + @cache.write(1, "aaaaaaaaaa", :expires_in => 0.001) + @cache.write(2, "bbbbbbbbbb") + @cache.write(3, "cccccccccc", :expires_in => 0.001) + sleep(0.002) + @cache.cleanup + assert_equal false, @cache.exist?(1) + assert_equal true, @cache.exist?(2) + assert_equal false, @cache.exist?(3) + end end uses_memcached 'memcached backed store' do diff --git a/railties/guides/source/caching_with_rails.textile b/railties/guides/source/caching_with_rails.textile index 4273d0dd64..721c791a33 100644 --- a/railties/guides/source/caching_with_rails.textile +++ b/railties/guides/source/caching_with_rails.textile @@ -289,7 +289,11 @@ ActionController::Base.cache_store = :file_store, "/path/to/cache/directory" With this cache store, multiple server processes on the same host can share a cache. Servers processes running on different hosts could share a cache by using a shared file system, but that set up would not be ideal and is not recommended. The cache store is appropriate for low to medium traffic sites that are served off one or two hosts. -Note that the cache will grow until the disk is full unless you periodically clear out old entries. +Note that the cache will grow until the disk is full unless you periodically clear out old entries. You can call +ActiveSupport::Cache::FileStore#cleanup+ to remove entries older than a specified time. + +<ruby> +Rails.cache.cleanup(:not_accessed_in => 2.days) +</ruby> h4. ActiveSupport::Cache::MemCacheStore |