Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for ActiveSupport classes in Sidekiq::Worker methods #2104

Open
wants to merge 9 commits into
base: main
Choose a base branch
from

Conversation

iMacTia
Copy link

@iMacTia iMacTia commented Dec 2, 2024

Motivation

The currently tapioca-generated signatures for Sidekiq::Worker methods do not support ActiveSupport classes.
But reading the code comments and testing the behaviour at runtime, the methods should indeed be able to support them.

"+interval+ must be a timestamp, numeric or something that
acts numeric (like an activesupport time interval).

Implementation

ActiveSupport classes have been added to #perform_at and #perform_in methods, keeping their semantic difference despite these being aliased:

  • Add ActiveSupport::TimeWithZone as an allowed type to #perform_at
  • Add ActiveSupport::Duration as an allowed type to #perform_in

This is based on the assumption that projects using Sidekiq are most likely Rails application and therefore have access to ActiveSupport. If we don't want to rely on that assumption, the implementation will need to be tweaked to check if the ActiveSupport classes are defined when generating the signatures.

Tests

No new tests, just amended the existing tests to make sure the signatures are generated correctly

…and #perform_in methods

As stated in the [code docs](https://github.com/sidekiq/sidekiq/blob/e411ffc78713099a90b348930d0b664d2ee41279/lib/sidekiq/job.rb#L256-L261): "+interval+ must be a timestamp, numeric or something that acts numeric (like an activesupport time interval)."
@iMacTia iMacTia requested a review from a team as a code owner December 2, 2024 13:56
@KaanOzkan
Copy link
Contributor

This is based on the assumption that projects using Sidekiq are most likely Rails application and therefore have access to ActiveSupport.

I don't think we can assume that. It doesn't depend on Rails and this will lead to type checking errors.

@amomchilov amomchilov changed the title Add support for ActiveSupport classes in Sidekiq::Worker methods Add support for ActiveSupport classes in Sidekiq::Worker methods Dec 2, 2024
Copy link
Contributor

@amomchilov amomchilov left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @iMacTia, thanks for contributing this!

As Kaan mentioned, we can't assume that ActiveSupport and/or Rails are available. Luckily, this check already exists in ActiveSupportTimeExt. Could you please modify your PR to use that logic, and approach the appropriate time type?

ConstantType = type_member { { fixed: T.class_of(::Time) } }
sig { override.void }
def decorate
return unless constant.respond_to?(:zone)
root.create_path(constant) do |mod|
return_type = if ::Time.zone
"::ActiveSupport::TimeWithZone"
else
"::Time"
end

@iMacTia
Copy link
Author

iMacTia commented Dec 2, 2024

Thank you both for reviewing, I figured this might be an issue hence why I pointed it out in the description.
No problem, I'll add the conditional rendering and update the tests to test both 👍

@iMacTia
Copy link
Author

iMacTia commented Dec 3, 2024

@KaanOzkan @amomchilov I'm hitting a bit of a bump with testing both scenarios (ActiveSupport being bundled or not). Unfortunately I'm not as proficient with Minitest as I am with RSpec, so I may be missing something obvious here, but since we require "active_support" in the bin/test file I'm finding it hard to test what happens when `ActiveSupport doesn't exist.

I think I found a working solution in my latest commit, but I'm not very proud of the remove_const/const_set dance, and not sure if there's a more idiomatic way of achieving the same in Minitest?

If you have any suggestion I'm happy to make amendments or if it works and you don't mind we could just merge it as-is 🙂

@iMacTia
Copy link
Author

iMacTia commented Dec 4, 2024

All tests except for one (unrelated?) are currently passing on my branch 🎉
Let me know if there's anything else missing here, I'd love to get this merged (and released?) sooner rather than later as to avoid monkey-patching on our side 🙏

sig { void }
def backup_activesupport
Object.const_set(:ActiveSupportBackup, Object.send(:remove_const, :ActiveSupport)) # rubocop:disable RSpec/RemoveConst
end
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Another way is to use SpecWithProject similar to CLI tests. You'll be running it in a test gem so it should be a matter of requiring and not requiring ActiveSupport. But this way will be slower.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Soooo which approach do you think we should be using in this case?
I got it working with the current approach, but it's your gem so it's really up to you how you'd like this to be tested 🙂

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have a strong opinion, if after_teardown doesn't run somehow we probably have more important issues than not resetting ActiveSupport. I'll leave it to @amomchilov

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

None of this is needed, you can include Tapioca::Helpers::Test::Isolation into this test class to make each of the tests run in a forked subprocess, each with its own clean copy of the object space.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aaaah now we're talking! I'll give that a try, thank you @paracycle!

Copy link
Author

@iMacTia iMacTia Dec 10, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I also couldn't figure out how to run a single or a subset of tests, that would be quite useful because right now I can only run the whole test suite which takes quite some time 😞

Never mind, I finally found bin/test

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK it wasn't super straightforward because active_support is actually required in bin/test (apparently for Rails 6 support?) and so it's already part of the object space at the time of forking. That means I still need to undefine and unload it, but at least I don't need to put it back after the tests have run 👍

I grabbed some code from pipeline_spec.rb to do that and tests are passing locally 🙌

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants