Commit Graph

974 Commits

Author SHA1 Message Date
Rafael Mendonça França 994bce4e32
Merge PR #45698 2022-11-17 22:54:30 +00:00
Sam Bostock 1bd522037e
Ensure pipe is closed after test run
`Forking.run_in_isolation` opens two ends of a pipe. The fork process closes
the read end, writes to it, and then terminates (which presumably closes the
file descriptors on its end). The parent process closes the write end, reads
from it, and returns, never closing the read end.

This results in an accumulation of open file descriptors, which can cause
errors if the limit is reached.

One approach to fixing this would be to simply close the read end of the pipe
in the parent process. However, it is more idiomatic to open the pipe given a
block, which automatically closes the pipe after the block exits.
2022-11-04 16:49:36 -04:00
Jonathan Hefner b191a65850 Fix Time#change and #advance around the end of DST
Prior to this commit, when `Time#change` or `Time#advance` constructed a
time inside the final stretch of Daylight Saving Time (DST), the non-DST
offset would always be chosen for local times:

  ```ruby
  # DST ended just before 2021-11-07 2:00:00 AM in US/Eastern.
  ENV["TZ"] = "US/Eastern"

  time = Time.local(2021, 11, 07, 00, 59, 59) + 1
  # => 2021-11-07 01:00:00 -0400
  time.change(day: 07)
  # => 2021-11-07 01:00:00 -0500
  time.advance(seconds: 0)
  # => 2021-11-07 01:00:00 -0500

  time = Time.local(2021, 11, 06, 01, 00, 00)
  # => 2021-11-06 01:00:00 -0400
  time.change(day: 07)
  # => 2021-11-07 01:00:00 -0500
  time.advance(days: 1)
  # => 2021-11-07 01:00:00 -0500
  ```

And the DST offset would always be chosen for times with a `TimeZone`
object:

  ```ruby
  Time.zone = "US/Eastern"

  time = Time.new(2021, 11, 07, 02, 00, 00, Time.zone) - 3600
  # => 2021-11-07 01:00:00 -0500
  time.change(day: 07)
  # => 2021-11-07 01:00:00 -0400
  time.advance(seconds: 0)
  # => 2021-11-07 01:00:00 -0400

  time = Time.new(2021, 11, 8, 01, 00, 00, Time.zone)
  # => 2021-11-08 01:00:00 -0500
  time.change(day: 07)
  # => 2021-11-07 01:00:00 -0400
  time.advance(days: -1)
  # => 2021-11-07 01:00:00 -0400
  ```

This commit fixes `Time#change` and `Time#advance` to choose the offset
that matches the original time's offset when possible:

  ```ruby
  ENV["TZ"] = "US/Eastern"

  time = Time.local(2021, 11, 07, 00, 59, 59) + 1
  # => 2021-11-07 01:00:00 -0400
  time.change(day: 07)
  # => 2021-11-07 01:00:00 -0400
  time.advance(seconds: 0)
  # => 2021-11-07 01:00:00 -0400

  time = Time.local(2021, 11, 06, 01, 00, 00)
  # => 2021-11-06 01:00:00 -0400
  time.change(day: 07)
  # => 2021-11-07 01:00:00 -0400
  time.advance(days: 1)
  # => 2021-11-07 01:00:00 -0400

  Time.zone = "US/Eastern"

  time = Time.new(2021, 11, 07, 02, 00, 00, Time.zone) - 3600
  # => 2021-11-07 01:00:00 -0500
  time.change(day: 07)
  # => 2021-11-07 01:00:00 -0500
  time.advance(seconds: 0)
  # => 2021-11-07 01:00:00 -0500

  time = Time.new(2021, 11, 8, 01, 00, 00, Time.zone)
  # => 2021-11-08 01:00:00 -0500
  time.change(day: 07)
  # => 2021-11-07 01:00:00 -0500
  time.advance(days: -1)
  # => 2021-11-07 01:00:00 -0500
  ```

It's worth noting that there is a somewhat dubious case when calling
`advance` with a mix of calendar and clock units (e.g. months + hours).
`advance` adds calendar units first, then adds clock units.  So a
non-DST time may first be advanced to a date within DST before any clock
units are applied.  For example:

  ```ruby
  # DST began on 2021-03-14 in US/Eastern.
  Time.zone = "US/Eastern"

  non_dst = Time.new(2021, 03, 07, 00, 00, 00, Time.zone)
  # => 2021-03-07 00:00:00 -0500

  non_dst.advance(months: 8, hours: 1)
  # => 2021-11-07 01:00:00 -0400
  ```

One could argue that the expected result is `2021-11-07 01:00:00 -0500`.
However, the difference between that and the result for `hours: 0` is
greater than 1 hour:

  ```ruby
  adjusted_result = non_dst.advance(months: 8, hours: 1) + 3600
  # => 2021-11-07 01:00:00 -0500

  adjusted_result - non_dst.advance(months: 8, hours: 0)
  # => 7200.0
  ```

Which might also be unexpected.

Furthermore, it may be difficult (or expensive) to apply such an
adjustment in a consistent way.  For example, the result for `hours: 2`
does have the expected `-0500` offset, so it might seem no adjustment is
necessary, but if we didn't adjust it too, it would conflict with the
adjusted `hours: 1` result:

  ```ruby
  non_dst.advance(months: 8, hours: 2)
  # => 2021-11-07 01:00:00 -0500
  ```

Therefore, the approach in this commit (which produces a `-0400` offset
for `hours: 1`) seems like a reasonable compromise.

Fixes #45055.
Closes #45556.
Closes #46248.

Co-authored-by: Kevin Hall <bigtoe416@yahoo.com>
Co-authored-by: Takayoshi Nishida <takayoshi.nishida@gmail.com>
2022-10-22 14:58:03 -05:00
Jean Boussier 4af6d50d44 MemoryStore: preserve entry ttl when incrementing
Fix: https://github.com/rails/rails/issues/46301

When incrementing in Memcached or Redis, we use `raw` values, so
only the backing store TTL counts, and is preserved.

So for consistency we need to emulate this in MemoryStore by
re-using the existing entry's `expires_at`.
2022-10-22 12:33:41 +02:00
Martin Spickermann 1a9b887c0c Allow ErrorReporter to handle several error classes
`Rails.error.handle` and `Rails.error.record` are able to filter by list of serveral error classes now, for example like this:

```ruby
Rails.error.handle(ArgumentError, TypeError) do
  [1, 2, 3].first(x) # where `x` might be `-4` or `'4'`
end
```
2022-10-21 13:21:36 +02:00
Alex Ghiculescu be155f135a Support using a method name to define the `ActiveSupport::CurrentAttributes.resets` callback
Previously you could only use a block, which was inconsistent with other callback APIs in Rails. Now, you can use a block or a method name. The block API is still recommended in the docs.

```ruby
class Current < ActiveSupport::CurrentAttributes
  resets { Time.zone = nil }
  resets :clear_time_zone
end
```

This also works for `before_reset`.

```ruby
class Current < ActiveSupport::CurrentAttributes
  before_reset { Time.zone = nil }
  before_reset :clear_time_zone
end
```
2022-10-05 11:05:58 -05:00
Jonathan Hefner c267be43d3 Move CHANGELOG entry for #44179 to railties [ci-skip]
The CHANGELOG entry for #44179 originally focused on
`ActiveSupport::MessageVerifiers` and `ActiveSupport::MessageEncryptors`,
and made sense in `activesupport/CHANGELOG.md`.  However, prior to
merge, the focus changed to `Rails.application.message_verifiers`.
Therefore, the CHANGELOG entry should be in `railties/CHANGELOG.md`.
2022-09-29 12:08:14 -05:00
Jean Boussier 1aa24639f7 Filter reloaded classes in Class#subclasses and Class#descendants core exts
When calling `#descendants` on a non-reloadable class with reloadable
descendants, it can return classes that were unloaded but not yet
garbage collected.

`DescendantsTracker` have been dealing with this for a long time
but the core_ext versions of these methods were never made reloader
aware.

This also refactor `DescendantsTracker` to not be so different when
there is no native `#subclasses` method.
2022-09-28 12:08:58 +02:00
Jean Boussier a6dbfcdc23 `Rails.error.report` now marks errors as reported to avoid reporting them twice
This is done by setting a special instance variable on the exception.

Another implementation could be to use an `ObjectSpace::WeakMap` as
a weak reference set, but I figured 3rd party error reporting libraries
may want to inspect wether the error was reported.
2022-09-26 17:58:08 +02:00
Jonathan Hefner baade55c69 Add Rails.application.message_verifiers
Currently, `Rails.application.message_verifier(name)` returns a
`MessageVerifier` instance using a secret derived from the given `name`.
Instances created this way always generate messages using the default
options for `MessageVerifier.new`.  Also, if there are older options to
rotate for message verification, each instance created this way must be
configured individually.  In cases where Rails itself uses
`Rails.application.message_verifier` (e.g. in Active Storage),
discovering the name of the instance may require digging through
Rails' source code.

To alleviate these issues, this commit adds a `MessageVerifiers` factory
class, which acts as a central point for configuring and creating
`MessageVerifier` instances.  Options passed to `MessageVerifiers#rotate`
will be applied to `MessageVerifier` instances that `MessageVerifiers#[]`
creates.  So the following:

  ```ruby
  foo_verifier = Rails.application.message_verifier(:foo)
  foo_verifier.rotate(old_options)
  bar_verifier = Rails.application.message_verifier(:bar)
  bar_verifier.rotate(old_options)
  ```

Can be rewritten as:

  ```ruby
  Rails.application.message_verifiers.rotate(old_options)
  foo_verifier = Rails.application.message_verifiers[:foo]
  bar_verifier = Rails.application.message_verifiers[:bar]
  ```

Additionally, `Rails.application.message_verifiers` supports a
`:secret_key_base` option, so old `secret_key_base` values can be
rotated:

  ```ruby
  Rails.application.message_verifiers.rotate(secret_key_base: "old secret_key_base")
  ```

`MessageVerifiers` memoizes `MessageVerifier` instances.  This also
allows `MessageVerifier` instances to be overridden entirely:

  ```ruby
  Rails.application.message_verifiers[:foo] = ActiveSupport::MessageVerifier.new(other_secret)
  ```

For parity, this commit also adds a `MessageEncryptors` factory class,
which fulfills the same role for `MessageEncryptor` instances.
2022-09-22 16:21:31 -05:00
Jean Boussier f33a1b902d Add `assert_error_reported` and `assert_no_error_reported`
Allows to easily asserts an error happened but was handled

```ruby
report = assert_error_reported(IOError) do
  # ...
end
assert_equal "Oops", report.error.message
assert_equal "admin", report.context[:section]
assert_equal :warning, report.severity
assert_predicate report, :handled?
```
2022-09-16 10:04:26 +02:00
Jonathan Hefner ff57f6a1fc Pass deprecator to AS::Deprecation callbacks
This commit adds support for 3-arity `ActiveSupport::Deprecation`
behavior callbacks, which receive the deprecator instance as their third
argument.  This allows such callbacks to change their behavior based on
the deprecator's state.  For example, based on the deprecator's `debug`
flag.

This commit also fixes the `:stderr` and `:log` behaviors to check
the given deprecator's `debug` flag, instead of the global
`ActiveSupport::Deprecation.debug` flag.

Note that 2-arity and 4-arity callbacks will continue to behave the
same as before.  However, callbacks with -1 and -2 arity (i.e. with
splat args) will now receive the deprecator as their third argument,
instead of the deprecation horizon and gem name.
2022-09-15 16:31:33 -05:00
Jonathan Hefner 2540f94e82 Fix disallowed_warnings for non-global deprecators
Prior to this commit, the globally configured
`ActiveSupport::Deprecation.disallowed_warnings` affected all
deprecators, and each deprecator's `#disallowed_warnings` had no effect.
(Though the same was not true for `ActiveSupport::Deprecation.allow`,
which would only affect warnings from `ActiveSupport::Deprecation.warn`,
not warnings from non-global deprecators.)

This commit fixes `disallowed_warnings` to only affect the deprecator
on which it is configured.
2022-09-13 15:43:41 -05:00
Gannon McGibbon 6016f9ef31 Add italic and underline support to `ActiveSupport::LogSubscriber#color`
Previously, only bold text was supported via a positional argument.
This allows for bold, italic, and underline options to be specified
for colored logs.

```ruby
info color("Hello world!", :red, bold: true, underline: true)
```
2022-09-09 22:55:33 -05:00
Mark Schneider 69b550fc88 Add String#downcase_first method
Rails 5 added String#upcase_first, which converts the first letter of a
string to uppercase (returning a duplicate).  This commit adds the
corollary method, converting the first letter to lowercase.
2022-09-08 12:57:31 -04:00
Jonathan Hefner 38a2cfe9fe Freeze thread_mattr_accessor default values
This provides a basic level of protection against different threads
trying to mutate a shared default object.  It is not a bulletproof
solution, because the default may contain nested non-frozen objects, but
it should cover common cases.
2022-09-01 15:41:32 -05:00
Trevor Turk 0ae0ab7e3f Add raise_on_invalid_cache_expiration_time config to ActiveSupport::Cache::Store 2022-08-30 10:10:28 -05:00
Jean Boussier b84b04b33f Pass options accessor to Cache#fetch block
Closes: https://github.com/rails/rails/pull/45080

This is a surprisingly common use case when dealing with APIs
with short lived authentication tokens.

This implementation is very much the same as the original one
by Andrii, except that we yield an object with a restricted interface
instead of yielding the options hash.

The reason is that not all options can be changed between the read
and the write, so by restricting the interface we avoid surprising
users.

Co-Authored-By: Andrii Gladkyi <arg@arg.zone>
2022-08-30 12:11:20 +02:00
Jean Boussier 39413de44c Revert "Merge pull request #45842 from trevorturk/validate-expires-at"
This reverts commit 3d84e1298b, reversing
changes made to cc138468ca.

As discussed with the author in https://github.com/rails/rails/pull/45892
2022-08-29 10:08:02 +02:00
Thierry Deo 25faa5466b Fix `thread_mattr_accessor` `default` option behavior
This makes the value supplied to the `default` option of
`thread_mattr_accessor` to be set in descendant classes
as well as in any new Thread that starts.

Previously, the `default` value provided was set only at the
moment of defining the attribute writer, which would cause
the attribute to be uninitialized in descendants and in other threads.

For instance:

  ```ruby
  class Processor
    thread_mattr_accessor :mode, default: :smart
  end

  class SubProcessor < Processor
  end

  SubProcessor.mode # => :smart (was `nil` prior to this commit)

  Thread.new do
    Processor.mode # => :smart (was `nil` prior to this commit)
  end.join
  ```

If a non-`nil` default has been specified, there is a small (~7%)
performance decrease when reading non-`nil` values, and a larger (~45%)
performance decrease when reading `nil` values.

Benchmark script:

  ```ruby
  # frozen_string_literal: true
  require "benchmark/ips"
  require "active_support"
  require "active_support/core_ext/module/attribute_accessors_per_thread"

  class MyClass
    thread_mattr_accessor :default_value, default: "default"
    thread_mattr_accessor :string_value, default: "default"
    thread_mattr_accessor :nil_value, default: "default"
  end

  MyClass.string_value = "string"
  MyClass.nil_value = nil

  Benchmark.ips do |x|
    x.report("default_value") { MyClass.default_value }
    x.report("string_value") { MyClass.string_value }
    x.report("nil_value") { MyClass.nil_value }
  end
  ```

Before this commit:

  ```
         default_value      2.075M (± 0.7%) i/s -     10.396M in   5.010585s
          string_value      2.103M (± 0.7%) i/s -     10.672M in   5.074624s
             nil_value      1.777M (± 0.9%) i/s -      8.924M in   5.023058s
  ```

After this commit:

  ```
         default_value      2.008M (± 0.7%) i/s -     10.187M in   5.072990s
          string_value      1.967M (± 0.7%) i/s -      9.891M in   5.028570s
             nil_value      1.144M (± 0.5%) i/s -      5.770M in   5.041630s
  ```

If no default or a `nil` default is specified, there is no performance
impact.

Fixes #43312.

Co-authored-by: Jonathan Hefner <jonathan@hefner.pro>
2022-08-25 11:30:58 -05:00
Jean Boussier 341d627a57 RedisCacheStore: don't pass url if it's nil
In 5.0 it will prevent `$REDIS_URL` from being used.
2022-08-22 09:01:27 +02:00
Trevor Turk 7eeb4af7c8 Log a warning if ActiveSupport::Cache is given an expiration in the past 2022-08-18 17:04:21 -05:00
Daniel Alfaro 1344031d7e Add `skip_nil:` support to `ActiveSupport::Cache::Store#fetch_multi` 2022-07-20 17:02:25 -05:00
Hartley McGuire 81c5c9971a
Fix extra changelog entry added in 959d46e (#45604)
The :urlsafe option was renamed to :url_safe in 7094d0f and the correct
entry is still below
2022-07-14 17:03:07 -07:00
matt swanson 959d46ef87
Add `quarter` method to date/time (#45009)
Co-authored-by: David Heinemeier Hansson <david@hey.com>
2022-07-14 16:43:52 -07:00
Hartley McGuire 5458571254
Add missing CHANGELOG authors
Ref 44a2971
Ref 9f0b8eb
2022-07-11 01:43:32 -04:00
Jonathan Hefner 7094d0fc43 Rename :urlsafe option to :url_safe
Although Ruby provides `Base64.urlsafe_encode64` and
`Base64.urlsafe_decode64` methods, the technical term is "URL-safe",
with "URL" and "safe" as separate words.

For better readability, this commit renames the `:urlsafe` option for
`MessageEncryptor` and `MessageVerifier` to `:url_safe`.
2022-07-08 15:36:24 -05:00
Ryo Nakamura 6f9bb2cc0f Fix NoMethodError on custom ActiveSupport::Deprecation behavior
with some additional changes made later:

1. Flip the condition for backward compatibility
   Co-authored-by: Jonathan Hefner <jonathan@hefner.pro>

2. Improve custom behavior test
   Co-authored-by: Jonathan Hefner <jonathan@hefner.pro>

3. Fix indentation
2022-07-06 06:41:41 +09:00
Jonathan Hefner d9823326e8 Support :urlsafe option for MessageEncryptor
This adds a `:urlsafe` option to the `MessageEncryptor` constructor.
When enabled, this option ensures that messages use a URL-safe encoding.
This matches the `MessageVerifier` `:urlsafe` option added in #45419.
2022-06-29 16:00:16 -05:00
Shouichi Kamiya 09c3f36a96 Add urlsafe option to MessageVerifier initializer
MessageVerifier uses Base64.strict_encode64 and generated strings are
not urlsafe. Though the goal is to make MessageVerifier generated
strings urlsafe, We can not simply switch to Base64.urlsafe_encode64
because it will be a breaking change. Thus, as a first step, urlsafe
option is added to the MessageVerifier initializer.
2022-06-21 21:40:35 +09:00
fatkodima 799b5c1df4 Enable connection pooling by default for MemCacheStore and `RedisCacheStore` 2022-06-07 11:40:17 +03:00
fatkodima 358556b971 Add `force:` support to `ActiveSupport::Cache::Store#fetch_multi` 2022-05-25 03:19:12 +03:00
fatkodima 059d1d16ab Deprecate `:pool_size` and `:pool_timeout` options for configuring connection pooling in cache stores 2022-05-18 00:10:04 +03:00
Andrej Blagojević f48bf3975f Add initial value support to MemCacheStore #increment and #decrement 2022-05-17 18:32:52 +00:00
Joey Paris 98244c1d4b Squash commits 2022-05-15 11:45:22 +01:00
Alan Savage ad7b40c4f7 Fix MemoryStore#write(name, val, unless_exist: true) with expired entry 2022-05-11 11:54:03 -07:00
Jean Boussier c9a2bc284c Error reporting API: Add a source attribute
Ref: https://github.com/rails/rails/pull/43625#issuecomment-1109595539

Some users may not be interested by some internal errors.
By providing a `source` attribute we allow to easilly filter
these errors out.
2022-05-05 10:57:21 +02:00
Alvaro Martin Fraguas 649516ce0f
Fix and add protections for XSS in names.
Add the method ERB::Util.xml_name_escape to escape dangerous characters
in names of tags and names of attributes, following the specification of
XML.

Use that method in the tag helpers of ActionView::Helpers. Rename the option
:escape_attributes to :escape, to simplify by applying the option to the whole
tag.
2022-04-26 12:34:42 -07:00
Steven Harman 3b012a5254
Respect the formatter keyword arg on init
The stdlib Logger::new allows passing a :formatter keyword argument to
set the logger's formatter. ActiveSupport::Logger::new ignores this
argument by always setting the formatter to an instance of
SimpleFormatter. Instead, we should only set it when none is yet set.
2022-04-22 19:55:36 -04:00
Andrew White 1943962a10
Deprecate preserving the pre-Ruby 2.4 behavior of `to_time`
With Ruby 2.4+ the default for +to_time+ changed from converting to the
local system time to preserving the offset of the receiver. At the time
Rails supported older versions of Ruby so a compatibility layer was
added to assist in the migration process. From Rails 5.0 new applications
have defaulted to the Ruby 2.4+ behavior and since Rails 7.0 now only
supports Ruby 2.7+ this compatibility layer can be safely removed.

To minimize any noise generated the deprecation warning only appears when
the setting is configured to `false` as that is the only scenario where
the removal of the compatibility layer has any effect.
2022-03-20 10:07:59 +00:00
Jean Boussier 0ac4c04143 `Pathname.blank?` only returns true for `Pathname.new("")`
Fix: https://github.com/rails/rails/issues/44452

We don't ignore blank path strings because as surprising as it is, they
are valid paths:

```ruby
>> path = Pathname.new(" ")
=> #<Pathname: >
>> path.write("test")
=> 4
>> path.exist?
=> true
```

Previously it would end up calling `Pathname#empty?` which returned true
if the path existed and was an empty directory or file.

That behavior was unlikely to be expected.
2022-02-19 09:54:58 +01:00
John Hawthorn 9f0b8eb584 Deprecate Event#{children,parent_of} 2022-02-17 08:20:01 -08:00
Zack 5d0c2b0dd2 Change MessageEncryptor default serializer to JSON for Rails 7.1
These changes include adding a hybrid serializer class
named JsonWithMarshalFallback in order for existing apps
to have an upgrade path from Marshal to JSON.
2022-02-07 12:19:36 -05:00
David Heinemeier Hansson fdb98d218e
Add TestCase#stub_const (#44294)
* Add TestCase#stub_const

* Note the concurrency issue

* Changelog entry
2022-02-01 12:20:06 +01:00
Stephen Sugden d5b65c082e Use YAML.unsafe_load for encrypted configuration
Fix: https://github.com/rails/rails/pull/44063
2022-01-26 11:53:19 +01:00
Daniel Pepper 44a2971e5c
atomic write race condition 2022-01-16 16:54:58 -08:00
Jean Boussier 68e9df0257 Ruby 3.1: Handle `Class#subclasses` existing without `Class#descendants`
Since `#subclasses` was introduced a bit after `#descendants`, the feature testing
code assumed that if the former was present, the later would be too.

However it was decided to revert `#descendants` for now, but to keep `#subclasses`
https://bugs.ruby-lang.org/issues/14394#note-33

Since this was totally unexpected, the `#descendants` core_ext and `DescendantsTracker#descendants`
are broken on Active Support 7.0.0 / Ruby 3.1.0-dev.

We now test the existence of both methods to handle this new situation. Since
for now it's planned for `#descendants` to make it back in Ruby 3.2, we keep
checking for it.
2021-12-21 11:41:56 +01:00
Rafael Mendonça França a35a380c2c
Remove CHANGELOG that is included in 7.0 2021-12-10 19:03:45 +00:00
Caleb Buxton ffc1e5f889
fix: equivalent negative durations add to the same time (#43795)
* bug: illustrate negative durations don't add to the same time

* fix: equivalent negative durations add to the same time

Co-authored-by: Caleb <me@cpb.ca>
Co-authored-by: Braden Staudacher <braden.staudacher@chime.com>

* Updates CHANGELOG with fix to `ActiveSupport::Duration.build`
2021-12-10 14:03:04 -05:00
Rafael Mendonça França 83d85b2207
Start Rails 7.1 development 2021-12-07 15:52:30 +00:00