`[].all?(Array)` returns `true`. Thus when an empty `args` array was
passed to `assert_called_with`, `expect` would not be called on the mock
object, eventually leading to an "unmocked method" error.
This commit allows an empty array to be passed as `args`, which behaves
the same as passing `[[]]`.
ActiveSupport::MessageVerifier#verified is causing signed_message to be
split twice: first inside #valid_message? and then inside #verified.
This is ultimately unnecessary.
We can avoid String#split all together by calculating the indexes of the
data and the digest in the payload. This increases the resistance of the
solution against ill-formed payloads that don't include the separator.
Ruby 3.1 is going to introduce an [optimization][] that makes interpolation
of some types of objects faster, unless there is a custom implementation
of to_s. Since Rails is overriding `to_s` for a bunch of core classes it
means that this optimization in Rails applications will be disabled.
In order to allow Rails applications to use this optimization in
the future we are deprecating all the reasons we override `to_s` in
those core classes in favor of using `to_formatted_s`.
[optimization]: b08dacfea3
Ruby 3.1 introduced an optimization to string interpolation for some
core classes in b08dacfea3.
But since we override `to_s` in some of those core classes to add behavior
like `to_s(:db)`, all Rails applications will not be able to take advantage
of that improvement.
Since we can use the `to_formatted_s` alias for the Rails specific behavior
it is best for us to deprecate the `to_s` core extension and allow Rails
applications to get the proformace improvement.
This commit starts removing all the `to_s(:db)` calls inside the framework
so we can deprecate the core extension in the next commit.
Example failure: https://buildkite.com/rails/rails/builds/82905#80d6c6ec-943d-4ba3-b360-1ef6c4aa5d89/1012-1022
The test designates the event end time as 0.01 seconds (i.e. 10
milliseconds) after the start time. It then asserts that the event
duration is 10 ± 0.0001 milliseconds. This sometimes fails due to
floating point precision errors.
This commit changes the assertion to instead check that the duration is
within 1% of the expected value.
This reverts commit 0181f0edd5.
Users of Active Support should always require the top level file of
the framework before requiring anything else inside it, so we don't
need to require that top level file here.
Previously I assumed it was useless, however I was wrong.
The method is called by the reloader to give the illusion that
the GC is precise. Meaning a class that will be unloaded is
immediately made invisible without waiting for it to be garbage collected.
This is easy to do up to Ruby 3.0 because `DescendantTracker` keeps
a map of all tracked classes.
However on 3.1 we need to use the inverse strategy, we keep a WeakMap
of all the classes we cleared, and we filter the return value of `descendants`
and `subclasses`.
Since `clear` is private API and is only used when reloading is enabled,
to reduce the performance impact in production mode, we entirely remove
this behavior when `config.cache_classes` is enabled.
By doing so we avoid a lot of extra work.
```
baseline: 4092604.4 i/s
opt-method: 693204.7 i/s - 5.90x (± 0.00) slower
method: 614761.0 i/s - 6.66x (± 0.00) slower
```
Baseline is calling `run_callbacks` with no callbacks registered.
Full benchmark: https://gist.github.com/casperisfine/837a7a665c6b232dadcf980d73694748
Ref: https://github.com/rails/rails/issues/43472
We swallow these exceptions because it makes sense for a cache to
fallback to a cache miss in case of transcient failures.
However there's value in reporting these to the application
owners so that they can be alerted that something undesirable happened.
* This works whether the Ruby implementation raises or uses a Hash with
a default of 0 to represent unknown keys (like TruffleRuby) and so
this is more portable.
* Also avoids an extra exception on JRuby.
This module has been soft deprecated for a long time, but since
it was used internally it wasn't throwing deprecation warnings.
Now we can throw a deprecation warning.
Ref: https://github.com/rails/rails/pull/43596
This allow users to declare wether their unit of work is isolated by
fibers or by threads.
`PerThreadRegistry` and `thread_mattr_accessor` were intentionally left
out as they require documentation change. I'll submit them in separate
pull requests.
Many places in Active Support and Rails in general use `Thread.current#[]`
to store "request (or job) local data". This often cause problems with
`Enumerator` because it runs in a different fiber.
On the other hand, some places migrated to `Thread#thread_variable_get`
which cause issues with fiber based servers (`falcon`).
Based on this, I believe the isolation level should be an application
configuration.
For backward compatibility it could ship with `:fiber` isolation as a default
but longer term :thread would make more sense as it would work fine for
all deployment targets except falcon.
Ref: https://github.com/rails/rails/pull/38905
Ref: https://github.com/rails/rails/pull/39428
Ref: https://github.com/rails/rails/pull/34495
(and possibly many others)
Fix: https://github.com/rails/rails/issues/43472
The reporter is held by the executor, but the `Rails` module provides a
nicer `Rails.error` shortcut.
For ease of use, two block based specialized methods are exposed.
`handle`, which swallow errors and forward them to the subscribers:
```ruby
Rails.error.handle do
1 + '1' # raises TypeError
end
1 + 1 # This will be executed
```
`record`, which forward the errors to the subscribes but let it
continue rewinding the call stack:
```ruby
Rails.error.record do
1 + '1' # raises TypeError
end
1 + 1 # This won't be executed.
```
For cases where the blocked based API isn't suitable, the lower level
`report` method can be used:
```ruby
Rails.error.report(error, handled: true / false)
```
Since the test are skipped when Redis | Memcache are not reacheable two
instance variables are not initialized properly but used on teardown
blocks, having warnings about undeclared variables:
- instance variable @cache not initialized (redis cache store)
- instance variable @_stores not initialized (memcache store)
Historically `Date._iso8601(nil)` returns `{}`.
But in these versions it raises a `TypeError` changing the exception type
of `ActiveSupport::TimeZone#iso8601(nil)`, which may break some applications.
I'm working on a standardized error reporting interface
(https://github.com/rails/rails/issues/43472) and it has the same need
for a `context` than Active Record's query logs.
Just like query logs, when reporting an error you want to know what the
current controller or job is, etc.
So by extracting it we can allow both API to use the same store.
Ref: https://github.com/rails/rails/pull/43282
Ref: https://github.com/rails/rails/pull/43561
It can be legitimate for subscriber to want to bubble up some exception
to the caller, so wrapping it change the exception class which might break
the calling code expecting a specific error.
We can minimize this by only using InstrumentationSubscriberError
when more than one subscriber raised.
The bulk of the optimization is to generate code rather than use
`define_method` with a closure.
```
Warming up --------------------------------------
original 207.468k i/100ms
code-generator 340.849k i/100ms
Calculating -------------------------------------
original 2.127M (± 1.1%) i/s - 10.788M in 5.073860s
code-generator 3.426M (± 0.9%) i/s - 17.383M in 5.073965s
Comparison:
code-generator: 3426241.0 i/s
original: 2126539.2 i/s - 1.61x (± 0.00) slower
```
```ruby
require 'benchmark/ips'
require 'active_support/all'
class Original < ActiveSupport::CurrentAttributes
attribute :foo
end
class CodeGen < ActiveSupport::CurrentAttributes
class << self
def attribute(*names)
ActiveSupport::CodeGenerator.batch(generated_attribute_methods, __FILE__, __LINE__) do |owner|
names.each do |name|
owner.define_cached_method(name, namespace: :current_attributes) do |batch|
batch <<
"def #{name}" <<
"attributes[:#{name}]" <<
"end"
end
owner.define_cached_method("#{name}=", namespace: :current_attributes) do |batch|
batch <<
"def #{name}=(value)" <<
"attributes[:#{name}] = value" <<
"end"
end
end
end
ActiveSupport::CodeGenerator.batch(singleton_class, __FILE__, __LINE__) do |owner|
names.each do |name|
owner.define_cached_method(name, namespace: :current_attributes_delegation) do |batch|
batch <<
"def #{name}" <<
"instance.#{name}" <<
"end"
end
owner.define_cached_method("#{name}=", namespace: :current_attributes_delegation) do |batch|
batch <<
"def #{name}=(value)" <<
"instance.#{name} = value" <<
"end"
end
end
end
end
end
attribute :foo
end
Benchmark.ips do |x|
x.report('original') { Original.foo }
x.report('code-generator') { CodeGen.foo }
x.compare!
end
```
It's `Rails.application.executor.wrap` that is responsible for
clearing request/job local state such as `CurrentAttributes`.
Instead of including an ad hoc helper to clear `CurrentAttributes` it's
better to run the executor so we properly clear other states as well.
However it means all executor hooks now need to be re-entrant.
It's `Rails.application.executor.wrap` that is responsible for
clearing request/job local state such as `CurrentAttributes`.
Instead of including an ad hoc helper to clear `CurrentAttributes` it's
better to run the executor so we properly clear other states as well.
Updates the `Digest::UUID.uuid_from_hash` to return the correct UUID values
for namespace IDs that are different from the ones provided on `Digest::UUID`.
- The new behavior will be enabled by setting the
`config.active_support.use_rfc4122_namespaced_uuids` option to `true`
and is the default for new apps.
- The old behavior is the default for upgraded apps and will output a
deprecation warning every time a value that is different than one of
the constants defined on the `Digest::UUID` extension is used as the
namespace ID.
Fixes#37681.
Unless you are on a Ruby older than 2.3 (or some very old JRuby)
`Concurrent.monotonic_time` is just `Process.clock_gettime(Process::CLOCK_MONOTONIC)`.
So might as well skip the extra method call, and more importantly
it allows in many cases to pass the scale as second argument and save some
floating point multiplication.
This module will be a private module in Active Support, this way
if we need to change the behavior of translate in controllers or
views don't forget to change in the other one.
The word "Crazy" has long been associated with mental illness. While
there may be other dictionary definitions, it's difficult for some of us
to separate the word from the stigmatization, gaslighting, and bullying
that often comes along with it.
This commit replaces instances of the word with various alternatives. I
find most of these more focused and descriptive than what we had before.
Since the module isn't included anywhere and is purely static
there iss no point using `mattr_accessor`, which define instance
accessors and other extra overhead.
Regular `attr_accessor` does the job just fine.
Use Kernel::Float(..., exceptions:false) instead of a rescue block in
ActionView::Helpers::NumberHelper and
ActiveSupport::NumberHelper::NumberConverter to slightly improve
performance.
Also remove documentation that incorrectly states
ActiveSupport::NumberHelper supports the `raise:` option.
The descendants tracker is a generic tracker that you can use anywhere.
In particular, outside Rails applications.
Should provide API to clear only a subset of classes, but in my view
should know nothing about autoloading. That is a concern of client code.
require_dependency has now one single definition, and its implementation
does not depend on Zeitwerk or even applications. It only depends on
having some autoload paths in place.
We can test that in the AS test suite.
Without this fix, the delegation will raise the ArgumentError:
```
% bin/test -w test/per_thread_registry_test.rb
Running 1 tests in a single process (parallelization threshold is 50)
Run options: --seed 23992
# Running:
E
Error:
PerThreadRegistryTest#test_method_missing_with_kwargs:
ArgumentError: wrong number of arguments (given 1, expected 0; required keyword: x)
/Users/kamipo/src/github.com/rails/rails/activesupport/test/per_thread_registry_test.rb:9:in `foo'
/Users/kamipo/src/github.com/rails/rails/activesupport/lib/active_support/per_thread_registry.rb:55:in `foo'
/Users/kamipo/src/github.com/rails/rails/activesupport/lib/active_support/per_thread_registry.rb:57:in `method_missing'
/Users/kamipo/src/github.com/rails/rails/activesupport/test/per_thread_registry_test.rb:13:in `test_method_missing_with_kwargs'
```
Followup: https://github.com/rails/rails/pull/42833
The previous fix wasn't working in practice because the LocalCache
middleware doesn't use `with_local_cache` but directly set a
regular `LocalStore` instance in the registry.
So instead we redecorate it on every access. It may cause some extra
allocations, but since it only happens in 6.1 mode, it's not as
much of a concern.
The test for CurrentAttributes using thread-local variables leaks
an instance in Thread.current.
Usually instances are reset in the test helper but the objects remain:
activesupport/lib/active_support/current_attributes/test_helper.rb:11
In this situation we use clear_all to make sure we don't leave instances
behind when the configuration is changed.
assert_not_equal is an alias for refute_equal that is defined only on
the class ActiveSupport::TestCase. This commit ensures
ActiveSupport::Testing::Assertions#assert_changes doesn't depends on
ActiveSupport::TestCase to work.
Setting `remove_deprecated_time_with_zone_name` didn't work because
the value is checked in a framework initialiser, which runs before application initialiser.
This PR moves the setting inside the `config.after_initialize` block.
I've also added 2 tests for this behaviour.
Fixes#42820
Sometimes it can be very strange or long have to define default values
for config accessors as blocks, specially when config is a simple value
like 1, true, :symbol. This commit adds the ability to specify those
values by just passing a ` default` option when defining the accessor.
It also makes config accessor's interface similar to other Rails
methods like `class_attribute`, which also has the instance_reader,
instance_writer and instance_accessor options.
Since `LocalCache` store needs to call `dup_value!` on write (to avoid
mutating the original value), we end up serializing each cache entry twice.
Once for the local cache, and a second time for the actual backend. So the
write performance is quite bad.
So the idea here is than rather than to store `Entry` instances, the local
cache now instead store whatever payload was sent to the real backend.
This means that we now only serialize the `Entry` once, and if the cache
store was configured with an optimized coder, it will be used for the local
cache too.
Current Rails `main`:
```
fetch in rails 7.0.0.alpha
52.423 (± 1.9%) i/s - 265.000 in 5.058089s
write in rails 7.0.0.alpha
12.412 (± 0.0%) i/s - 62.000 in 5.005204s
```
Current Rails `main` with local cache disabled:
```
fetch in rails 7.0.0.alpha
52.047 (± 3.8%) i/s - 260.000 in 5.000138s
write in rails 7.0.0.alpha
25.513 (± 0.0%) i/s - 128.000 in 5.018942s
```
This branch:
```
fetch in rails 7.0.0.alpha
50.259 (± 4.0%) i/s - 255.000 in 5.085783s
write in rails 7.0.0.alpha
25.805 (± 0.0%) i/s - 130.000 in 5.039486s
```
So essentially, the local cache overhead on write has been eliminated.
Benchmark:
```
require "bundler/inline"
gemfile(true) do
source "https://rubygems.org"
gem "rails", github: "rails/rails", branch: "main"
gem 'benchmark-ips'
gem "mysql2"
gem "pry"
end
require "active_record"
require "logger"
require 'benchmark/ips'
ActiveRecord::Base.establish_connection(adapter: "mysql2", database: 'test', host: 'localhost', user: 'root', password: '')
ActiveRecord::Base.logger = Logger.new(nil)
ActiveRecord::Schema.define do
create_table :users, force: true do |t|
t.string :name
t.integer :phone
end
end
class ApplicationRecord < ActiveRecord::Base
self.abstract_class = true
end
class User < ApplicationRecord
end
1_000.times { |i| User.create(name: "test #{i}") }
cache = ActiveSupport::Cache::FileStore.new(ARGV[0] || '/tmp/rails-cache')
cache.clear
unless ENV['DISABLE_LOCAL_CACHE']
ActiveSupport::Cache::Strategy::LocalCache::LocalCacheRegistry.set_cache_for(
cache.middleware.local_cache_key,
ActiveSupport::Cache::Strategy::LocalCache::LocalStore.new
)
end
h = {}
h = User.last(Integer(ENV.fetch('SIZE', 1000))).each { |u| h[u.id] = u }
puts "== Benchmarking read_entry code and write_entry code in rails #{Rails.version}"
Benchmark.ips do |x|
x.report("fetch in rails #{Rails.version}") {
cache.fetch('key', compress: false) { h }
}
x.report("write in rails #{Rails.version}") {
cache.write("key+#{Time.now}", h, compress: false) { h }
}
end
```
This adds an additional method to configure the parallelization
threshold. Before this, the only way of configuring the threshold was
via an option:
```
config.active_support.test_parallelization_minimum_number_of_tests
```
- Related to https://github.com/rails/rails/pull/42761
- Used `parallelized?` method instead of calling a method `should_parallelize?` to figure out if parallezation is enabled.
- Fixed semantics of the test name corresponding to the change
- Updated test name as per the code review suggestion.
Parallelizing tests has a cost in terms of database setup and fixture
loading. This change makes Rails disable parallelization when the number
of tests is below a configurable threshold.
When running tests in parallel each process gets its own database
instance. On each execution, each process will update each database
schema (if needed) and load all the fixtures. This can be very expensive
for non trivial datasets.
As an example, for HEY, when running a single file with 18 tests,
running tests in parallel in my box adds an overhead of 13 seconds
versus not parallelizing them. Of course parallelizing is totally worthy
when there are many tests to run, but not when running just a few tests.
The threshold is configurable via
config.active_support.test_parallelization_minimum_number_of_tests,
which is 30 50 by default.
This also adds some tracing to know how tests are being executed:
When in parallel:
```
Running 2829 tests in parallel in 8 processes
```
When not in parallel:
```
Running 15 tests in a single process (parallelization threshold is 30)
```
Class variables are confusing and can be slow. In this case we were just
using the class variable as a global to implement Fiber-local variables
on top of. Instead we can use Thread#[] directly to store our Fiber-locals.
Follow up to https://github.com/rails/rails/pull/37313
- Adds regression tests
- Logs a warning in cases where assertions are nested in a way that's likely to be confusing
Date._iso8601 will attempt to parse certain args as ordinal dates eg. '21087' => 2021-03-28. Add ActiveSupport::TimeZone.iso8601 support for these
ordinal values to be consistent with Date._iso8601 and raise ArgumentError where the day of year is invalid.
Previously this function would fail to apply the negative format
correctly if the value being rounded was 0.5 exactly (modulo
precision).
This fixes the logic to use `>= 0.5` instead of `> 0.5`
This is a more-complete version of the fix in #37865 which originally
addressed #37846.
Also see context at #39350 with respect to alternate input formats.
Closes#42577
Same bug as https://github.com/rails/rails/pull/42559, but a very different fix due to https://github.com/rails/rails/pull/42025. To recap, the issue:
- While using the `dalli_store`, you set any value in the Rails cache with no expiry.
- You change to the `mem_cache_store`.
- You upgrade to Rails 7.
- You try to read the same cache key you set in step 1, and it crashes on read.
https://github.com/rails/rails/pull/42025 was backward compatible with entries written using the `mem_cache_store`, but *not* the `dalli_store` which did not use `ActiveSupport::Cache::Entry`. This PR attempts to fix that.
Previously calling ActiveSupport::Inflector::Inflections.clear(:acronyms)
reset the instance variable to an empty Array, which is not the correct
default. The next time an acronym is added a TypeError is thrown.
Because we re-use the message generated by the `Time` instance
it causes `TimeWithZone` to still pretend being a `Time` instance
even with `remove_deprecated_time_with_zone_name = true` which is
confusing.
Manually formats the seconds. This avoids using the %g formatter which can convert large numbers or very small fractions into scientific notation which is not supported by iso8601
Ruby master ships with Psych 4.0.0 which makes `YAML.load`
defaults to safe mode (https://github.com/ruby/psych/pull/487).
However since these YAML files are trustworthy sources
we can parse them with `unsafe_load`.