Now that we require Ruby 3.1, we can assume `Process._fork` is
defined on MRI, hence we can trust that our decorator will
reliably detect forks so we no longer need to check the if
the pid changed in critical spots.
Using Symbol#name allows to hit two birds with one stone.
First it will return a pre-existing string, so will save
one allocation per key.
Second, that string will be already interned, so it will
save the internal `Hash` implementation the work of looking
up the interned strings table to deduplicate the key.
```
ruby 3.3.0 (2023-12-25 revision 5124f9ac75) [x86_64-darwin21]
Warming up --------------------------------------
to_s 17.768k i/100ms
cond 23.703k i/100ms
Calculating -------------------------------------
to_s 169.830k (±10.4%) i/s - 852.864k in 5.088377s
cond 236.803k (± 7.9%) i/s - 1.185M in 5.040945s
Comparison:
to_s: 169830.3 i/s
cond: 236803.4 i/s - 1.39x faster
```
```ruby
require 'bundler/inline'
gemfile do
source 'https://rubygems.org'
gem 'benchmark-ips', require: false
end
HASH = {
first_name: nil,
last_name: nil,
country: nil,
profession: nil,
language: nil,
hobby: nil,
pet: nil,
longer_name: nil,
occupation: nil,
mailing_address: nil,
}.freeze
require 'benchmark/ips'
Benchmark.ips do |x|
x.report("to_s") { HASH.transform_keys(&:to_s) }
x.report("cond") { HASH.transform_keys { |k| Symbol === k ? k.name : k.to_s } }
x.compare!(order: :baseline)
end
```
The introduction of the block argument means that `Object#with` can now
accept a `Symbol#to_proc` as the block argument:
```ruby
client.with(timeout: 5_000) do |c|
c.get("/commits")
end
```
Now that we dropped support for Ruby 2.7, we no longer
need to check if variables are defined before accessing them
to avoid the undefined variable warning.
Now that we no longer support Ruby 2.7, many `ruby2_keyword` calls
can be eliminated.
The ones that are left could be eliminated but would end up substantially
slower or more compliacated so I left them for now.
When `parallelize` is used, and the ActiveSupport::Testing::ParallelizeExecutor
is being set, we can't call `parallelize_me!` ourselves since the
parallel executor might not be set to run tests in parallel.
In fact, maybe we should stop calling `parallelize_me!` altogether here
and rely on `parallelize` to do the right thing, but this might break
test classes that are using `ActiveSupport::Testing::Isolation` outside
without calling `parallelize`.
This changes the default number of workers when parallelizing tests to
be based on logical core count instead of physical core count. This may
provide a performance boost for parallelized tests.
This also reverts #50545 in favor of using the default worker count.
- tweak opening paragraph to specifically mention that the Redis backing
a Cache should not be the Redis backing Active Job
- add code blocks to Redis::Distributed since it uses the class syntax
- expand on ways to point Cache at a Redis in initialization
- add an rdoc link for #fetch and a code block for `-amount`
ActiveSupport::Deprecation used to `include Singleton` which is why the require is here, but it was removed in 9812641891f1c9ba4c3f8ffb8549ec26fc2668c4 (June 5 2023). Leaving the require seems to have been an oversight.
However, module ActiveJob::Serializers::ObjectSerializer does require Singleton and was relying on AS:Deprecation to require it. This PR just moves the require to the place it's actually used.
- Link to Deprecation::Behavior in configuring guide
The current list of options for `config.active_support.deprecation`
was missing the newly added `:report` option. Instead of adding the
missing option and continuing to keep 4 different lists of the same
options in sync, I opted to replace the list with a link to the
options in the Behavior API docs. This had the additional advantage of
giving more information about all of the options which was not
mentioned in the Configuring guide.
- Use symbols for Behavior options
It felt to me like naming the options did not make it explicit that
those were the symbols to pass to `#behavior=`, but by adding the `:`
that becomes more clear.
- Add some API links
There were a few references to `behavior=`, but we may as well link to
the actual method.
Fix: https://github.com/rails/rails/pull/50136
Using delegate saves on the `method_missing + public_send` combo.
I chose to delegate to all the methods I saw called in Rails own test
suite, but there is likely a handful more candidates for explicit delegation.
But also this rely on a new (private) parameter in `Module#delegate` to
provide the expected delegated type and use that to define delegators
with the extact required signature. This saves on array and hash allocations
caused by splatting.
In some ways it's a continuation of https://github.com/rails/rails/pull/46875
Note that I didn't make the new `as:` parameter public, as I fear it's
a bit too brittle to be used. For the same reason I'm considering reverting
the optimized path behavior on `to: :class` and requiring to explictly pass
`as: self` for that optimized path.
Co-Authored-By: Alexandre Terrasa <alexandre.terrasa@shopify.com>
This is a continuation of https://github.com/rails/rails/pull/46875
The behavior of looking up the class method when `to: :class` is passed
is a bit error prone because it silently degrades.
By passing the expected owner of the delegated method, we can be more
strict, and also generate a delegator in a module rather than having
to do it at inclusion time.
I made this argument private API because we want it in Rails, but
I'm worried it might be a bit too sharp for public API. I can
be convinced otherwise though.
method cause an Internal Server Error due to a TypeError.
In order to display the extraced source of a syntax error, we try
to locate the node id for the backtrace location. The call to
RubyVM::AbstractSyntaxTree.node_id_for_backtrace_location is expecting
a Thread::Backtrace::Location object, but we are passing a
SourceMapLocation instead.
This commit does two things:
1) it addresses the issue by making sure that we are always passing
a Thread::Backtrace::Location instead
2) it allows the development view to show the extracted source fragment
There is a bug in the current implementation of #travel_to:
it remembers a timezone of its argument, and all stubbed methods start
returning results in that remembered timezone. However, the expected
behaviour is to return results in a system timezone.
It can lead to bugs in tests like this one:
https://github.com/faker-ruby/faker/issues/2861
The CI env var was recently [changed][1] to be specific for the
application being tested instead of the framework tests. Because of
this, these lines which should have been true before are now false.
[1]: 1f0262aa2b768b14de5e6ef29d0e547628f570ee
Updates `MemoryStore#write_entry` to pass a `nil` `namespace` to
`exist?`, which expects a _name_ rather than a an already "normalized"
_key_. This fixes a bug where `unless_exist` would overwrite any
existing entry if a `namespace` was used.
Right now if the subprocess exit uncleanly, it straight out bring the
parent down with it because it will fail to parse the (likely empty)
Marshal payload:
```
<internal:marshal>:34:in `load': marshal data too short (ArgumentError)
from 3.3.0+0/bundler/gems/rails-488a7ce18880/activesupport/lib/active_support/testing/isolation.rb:23:in `run'
from 3.3.0+0/gems/minitest-5.20.0/lib/minitest.rb:1094:in `run_one_method'
from 3.3.0+0/gems/ci-queue-0.38.0/lib/minitest/queue.rb:179:in `block in run'
from 3.3.0+0/gems/ci-queue-0.38.0/lib/minitest/queue.rb:168:in `with_timestamps'
from 3.3.0+0/gems/ci-queue-0.38.0/lib/minitest/queue.rb:178:in `run'
from 3.3.0+0/gems/ci-queue-0.38.0/lib/minitest/queue.rb:229:in `block in run_from_queue'
from 3.3.0+0/gems/ci-queue-0.38.0/lib/ci/queue/redis/worker.rb:55:in `poll'
from 3.3.0+0/gems/ci-queue-0.38.0/lib/minitest/queue.rb:228:in `run_from_queue'
from 3.3.0+0/gems/ci-queue-0.38.0/lib/minitest/queue.rb:213:in `__run'
from 3.3.0+0/gems/minitest-5.20.0/lib/minitest.rb:162:in `run'
from 3.3.0+0/gems/minitest-5.20.0/lib/minitest.rb:86:in `block in autorun'
- XXXX::XXXXTest#test_xxxxx - /tmp/bundle/ruby/3.3.0+0/bundler/gems/rails-488a7ce18880/activesupport/lib/active_support/testing/isolation.rb:52:in `write': closed stream (IOError)
from 3.3.0+0/bundler/gems/rails-488a7ce18880/activesupport/lib/active_support/testing/isolation.rb:52:in `puts'
from 3.3.0+0/bundler/gems/rails-488a7ce18880/activesupport/lib/active_support/testing/isolation.rb:52:in `block (2 levels) in run_in_isolation'
from 3.3.0+0/bundler/gems/rails-488a7ce18880/activesupport/lib/active_support/testing/isolation.rb:32:in `fork'
from 3.3.0+0/bundler/gems/rails-488a7ce18880/activesupport/lib/active_support/testing/isolation.rb:32:in `block in run_in_isolation'
from 3.3.0+0/bundler/gems/rails-488a7ce18880/activesupport/lib/active_support/testing/isolation.rb:28:in `pipe'
from 3.3.0+0/bundler/gems/rails-488a7ce18880/activesupport/lib/active_support/testing/isolation.rb:28:in `run_in_isolation'
from 3.3.0+0/bundler/gems/rails-488a7ce18880/activesupport/lib/active_support/testing/isolation.rb:19:in `run'
from 3.3.0+0/gems/minitest-5.20.0/lib/minitest.rb:1094:in `run_one_method'
from 3.3.0+0/gems/ci-queue-0.38.0/lib/minitest/queue.rb:179:in `block in run'
from 3.3.0+0/gems/ci-queue-0.38.0/lib/minitest/queue.rb:168:in `with_timestamps'
from 3.3.0+0/gems/ci-queue-0.38.0/lib/minitest/queue.rb:178:in `run'
from 3.3.0+0/gems/ci-queue-0.38.0/lib/minitest/queue.rb:229:in `block in run_from_queue'
from 3.3.0+0/gems/ci-queue-0.38.0/lib/ci/queue/redis/worker.rb:55:in `poll'
from 3.3.0+0/gems/ci-queue-0.38.0/lib/minitest/queue.rb:228:in `run_from_queue'
from 3.3.0+0/gems/ci-queue-0.38.0/lib/minitest/queue.rb:213:in `__run'
from 3.3.0+0/gems/minitest-5.20.0/lib/minitest.rb:162:in `run'
from 3.3.0+0/gems/minitest-5.20.0/lib/minitest.rb:86:in `block in autorun'
```
This breaks the Minitest contract that `run_one_method` shouldn't raise
ever, and return a `Minitest::Result`.
By properly checking the sub process status, we can turn this crash into
a test failure, allowing the original test process to go on.
It's a common useful pattern for situation where something isn't
supposed to happen, but if it does we can recover from it.
So in such situation you don't want such issue to be hidden
in development or test, as it's likely a bug, but do not want to
fail a request if it happens in production.
In other words, it behaves like `#record` in development and test
environments, and like `raise` in production.
Fix: https://github.com/rails/rails/pull/49638
Fix: https://github.com/rails/rails/pull/49339
Co-Authored-By: Andrew Novoselac <andrew.novoselac@shopify.com>
Co-Authored-By: Dustin Brown <dbrown9@gmail.com>
The return value was not specified before. Now it returns `true` on a
successful write, `nil` if there was an error talking to the cache
backend, and `false` if the write failed for another reason (e.g. the
key already exists and `unless_exist: true` was passed).
Fix: #50047
`Backtrace::Location` instance for code generated with eval always
have their `absolute_path` set to `nil`. So if absolute path is nil
we should fallback to checking `#path`.
Co-Authored-By: fatkodima <fatkodima123@gmail.com>
Previously, the `Cache::Store` instrumentation would call
`normalize_key` when adding a key to the log. However, this resulted in
the logged key not always matching the actual key written/read from the
cache:
```irb
irb(main):004> cache.write("foo", "bar", namespace: "baz")
D, [2023-11-10T12:44:59.286362 #169586] DEBUG -- : Cache write: baz:foo ({:compress=>false, :compress_threshold=>1024, :namespace=>"baz"})
=> true
irb(main):005> cache.delete("foo", namespace: "baz")
D, [2023-11-10T12:45:03.071300 #169586] DEBUG -- : Cache delete: foo
=> true
```
In this example, `#write` would correctly log that the key written to
was `baz:foo` because the `namespace` option would be passed to the
`instrument` method. However, other methods like `#delete` would log
that the `foo` key was deleted because the `namespace` option was _not_
passed to `instrument`.
This commit fixes the issue by making the caller responsible for passing
the correct key to `#instrument`. This allows `normalize_key` to be
removed from the log generation which both prevents the key from being
normalized a second time and removes the need to pass the full options
hash into `#instrument`.
Co-authored-by: Jonathan Hefner <jonathan@hefner.pro>
On the first call it can be a bit slow because it needs to load
a bunch of data. It's also better to do it as part of boot
so that some of that data is shared via Copy-on-Write
Follow up to previous commit where these procs were allows to be shared
between subclasses. This change allocates the procs as they are first
declared.
Because `#present?` always resolve to `Object#present?`, it's
an extremely polymorphic method, and inline cache hits are low.
In addition, it requires an extra call to `self.blank?` which is
an overhead.
By specializing `present?` on common types, we avoid both of these
slow-downs:
```
ruby 3.2.2 (2023-03-30 revision e51014f9c0) [arm64-darwin22]
Warming up --------------------------------------
present? 198.028k i/100ms
opt_present? 565.521k i/100ms
Calculating -------------------------------------
present? 2.087M (± 8.8%) i/s - 10.297M in 5.028398s
opt_present? 5.584M (± 8.6%) i/s - 27.711M in 5.023852s
Comparison:
present?: 2086621.6 i/s
opt_present?: 5584373.5 i/s - 2.68x faster
```
```
ruby 3.2.2 (2023-03-30 revision e51014f9c0) +YJIT [arm64-darwin22]
Warming up --------------------------------------
present? 819.792k i/100ms
opt_present? 1.047M i/100ms
Calculating -------------------------------------
present? 12.192M (± 8.8%) i/s - 60.665M in 5.050622s
opt_present? 16.540M (± 8.2%) i/s - 82.676M in 5.059029s
Comparison:
present?: 12192047.5 i/s
opt_present?: 16539689.6 i/s - 1.36x faster
```
```ruby
require 'bundler/inline'
gemfile do
source 'https://rubygems.org'
gem 'benchmark-ips'
gem 'activesupport'
end
require 'active_support/all'
class Object
def opt_present?
respond_to?(:empty?) ? !empty? : !!self
end
end
class NilClass
def opt_present?
false
end
end
class FalseClass
def opt_present?
false
end
end
class TrueClass
def opt_present?
true
end
end
class Array
def opt_present?
!empty?
end
end
class Hash
def opt_present?
!empty?
end
end
class Symbol
def opt_present?
!empty?
end
end
class String
def opt_present?
!blank?
end
end
class Numeric # :nodoc:
def opt_present?
true
end
end
class Time # :nodoc:
def opt_present?
true
end
end
array = []
hash = {}
time = Time.now
puts RUBY_DESCRIPTION
Benchmark.ips do |x|
x.report("present?") do
true.present?
false.present?
1.present?
1.0.present?
array.present?
hash.present?
:foo.present?
time.present?
end
x.report("opt_present?") do
true.opt_present?
false.opt_present?
1.opt_present?
1.0.opt_present?
array.opt_present?
hash.opt_present?
:foo.opt_present?
time.opt_present?
end
x.compare!(order: :baseline)
end
```
The `@` prefix is always stripped, so might as well not require it.
For backward compatibility reasons we still handle the prefix for now,
but we eagerly strip it and emit a deprecation.
Prior to this commit, `String#downcase_first` and `String#upcase_first`
would return a frozen string when called on an empty string:
```ruby
# BEFORE
"foo".downcase_first.frozen? # => false
"".downcase_first.frozen? # => true
# AFTER
"foo".downcase_first.frozen? # => false
"".downcase_first.frozen? # => false
```
For better or worse, the Rails guide settled on double quotes
and a large part of the community also use rubocop which enforce
them by default.
So we might as well try to follow that style when providing code
snippets in the documentation or error messages.
Fix: https://github.com/rails/rails/issues/49822
I certainly didn't get them all, but consistency should be significantly
improved.
For SEO purposes, there should only be one `<h1>` per page, and the edge
version of SDoc will provide one using the module name if none are
present in documentation.
The reason of removing dup 5 years ago in 6da99b4e99 was to decrease memory allocations, so, it makes sense to only dup options when we know they will be overwritten.
We had a few cases of tests being skipped accidentally on CI
hence not bein ran for a long time.
Skipping make sense when running the test suite locally, e.g.
you may not have Redis or some other dependency running.
But on CI, a test not being ran should be considered an error.
Previously in https://github.com/rails/rails/pull/43282, which shipped
with Rails 7.0, we added the guarantee that if an exception occurred
within an ActiveSupport::Notificaitons subscriber that the remaining
subscribers would still be run.
This was broken in https://github.com/rails/rails/pull/44469, where the
different types of subscribers were broken into groups by type for
performance. Although we would guard exceptions and allways run all (or
none) of the same group, that didn't work cross-group with different
types of subscriber.
Any classes that don't implement #blank? will fall back to Object's
implementation, which checks whether #empty? is defined before either
using #empty? or implicit truthiness. Since checking whether #empty? is
defined is expensive, some core classes (Array/Hash) alias #blank?
directly to #empty? to skip the respond_to? check.
This commit applies the alias optimization to Symbol. #blank? is called
on Symbols for many Active Record query methods (select, includes,
group, order, joins, etc.) so it seems reasonable to define the #blank?
alias on Symbol as well.
ActiveSupport::Callbacks often ended up with empty Arrays: on callbacks
without a conditional, and on callback sequences which had no before
and/or after callbacks.
Previously, callbacks which were shared with subclasses would not share
between them the procs generated in Filters::Before and Filters::After.
This was also lazily generated so would cause memory growth after an
application is booted (and not sharable between workers via CoW).
This was because we would rebuild the objects used to invoke the
callbacks via CallbackChain#compile, so any difference in the callback
chain would result in all of the callback procs being rebuilt.
This commit changes before and after callbacks (but not around!) to be
shared between all subclasses of where it was defined. This is done by
changing Filters::Before and Filters::After to plain classes which
respond to call instead of generating procs (which wasn't strictly
necessary but was easier to implement, and also results in simpler
objects which use less memory). These objects avoid referencing and tied
to a specific callback sequence and so can be memoized and reused.
This has the most impact on applications with many Controllers, and many
callbacks in the ApplicationController (or similar).
I also took this opportunity to merge together all the different forms
of procs generated (halting, halting_and_conditional, conditional,
simple) into one form with if statements. There isn't any significant
performance benefit from the specialization previously being done.
It used to be stdlib but is being extracted in modern rubies.
Overall its usefulness is dubious. In all cases it is included in
Rails, it's only for the `synchronize` method, but end up exposing
a dozen other useless methods.
In the end just using a Mutex is clearer and simpler.
In some cases we can even get away with a single mutex in a constant.
Data encoded using a non-String purpose and `use_message_serializer_for_metadata == false` was incorrectly decoded,
triggering a "mismatched purpose" error during decode.
Fix this by ensuring that we compare both sides as a String.
e.g.:
```
zzak@mbp16 railties % bin/test test/application/assets_test.rb
Run options: --seed 32028
.............../Users/zzak/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/mail-2.8.1/lib/mail/parsers/content_location_parser.rb:592: warning: assigned but unused variable - disp_type_s
I, [2023-10-15T10:38:33.005925 #97662] INFO -- : [0ca46786-853f-4740-9e71-f644c4893502] Started GET "/assets/demo.js" for 127.0.0.1 at 2023-10-15 10:38:33 +0900
E, [2023-10-15T10:38:33.006641 #97662] ERROR -- : [0ca46786-853f-4740-9e71-f644c4893502]
[0ca46786-853f-4740-9e71-f644c4893502] ActionController::RoutingError (No route matches [GET] "/assets/demo.js"):
[0ca46786-853f-4740-9e71-f644c4893502]
../Users/zzak/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/mail-2.8.1/lib/mail/parsers/content_location_parser.rb:592: warning: assigned but unused variable - disp_type_s
..../Users/zzak/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/mail-2.8.1/lib/mail/parsers/content_location_parser.rb:592: warning: assigned but unused variable - disp_type_s
I, [2023-10-15T10:38:33.973335 #97660] INFO -- : [fa848d82-0d80-4b23-925b-73fa5d0f47d4] Started GET "/posts?debug_assets=true" for 127.0.0.1 at 2023-10-15 10:38:33 +0900
I, [2023-10-15T10:38:33.975021 #97660] INFO -- : [fa848d82-0d80-4b23-925b-73fa5d0f47d4] Processing by PostsController#index as HTML
I, [2023-10-15T10:38:33.975209 #97660] INFO -- : [fa848d82-0d80-4b23-925b-73fa5d0f47d4] Parameters: {"debug_assets"=>"true"}
I, [2023-10-15T10:38:33.977560 #97660] INFO -- : [fa848d82-0d80-4b23-925b-73fa5d0f47d4] Completed 200 OK in 2ms (Views: 1.9ms | ActiveRecord: 0.0ms | Allocations: 1266)
......./Users/zzak/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0/gems/mail-2.8.1/lib/mail/parsers/content_location_parser.rb:592: warning: assigned but unused variable - disp_type_s
.
Finished in 4.842481s, 5.9887 runs/s, 23.3351 assertions/s.
29 runs, 113 assertions, 0 failures, 0 errors, 0 skips
```
For example:
https://buildkite.com/rails/rails/builds/94132#0186806f-8a23-4d07-ae8f-3b937f8517ae/1162-1171
See also #47484
Fix: https://github.com/rails/rails/pull/49563
The semantic_logger gems doesn't behave exactly like stdlib logger
in that `SemanticLogger#level` returns a Symbol while stdlib `Logger#level`
returns an Integer.
Because of this we can't simply compare integers, we have to use the
various `#{level}?` methods.
Currently, when a method is called on Rails.logger, the BroadcastLogger will find the loggers that will respond to that method. However, when the method has keyword arguments, they are passed as regular arguments and will throw an ArgumentError. This adds keyword argument support by double splatting hash args.
```
class CustomLogger
def foo(bar:)
true
end
end
Rails.logger.foo(bar: "baz")
```
Expected: `true`
Actual: `wrong number of arguments (given 1, expected 0) (ArgumentError)`