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

Ensure Concurrency Keys are string-like and return a better error when they cannot be cast to a string #791

Merged
merged 3 commits into from
Jan 27, 2023

Conversation

Earlopain
Copy link
Collaborator

Closes #784

If you pass something like a Hash or Array as the concurrency key, the resulting stack trace doesn't make it very clear what you did wrong:

   TypeError:
     can't cast Hash
   # ./app/models/good_job/lockable.rb:196:in `advisory_lock_key'
   # ./lib/good_job/active_job_extensions/concurrency.rb:51:in `block (2 levels) in <module:Concurrency>'
   # ./spec/lib/good_job/active_job_extensions/concurrency_spec.rb:157:in `block (4 levels) in <top (required)>'
   # ./spec/support/reset_rails_queue_adapter.rb:12:in `block (2 levels) in <top (required)>'
   # ./spec/support/reset_good_job.rb:19:in `block (2 levels) in <top (required)>'
   # ./spec/support/database_cleaner.rb:14:in `block (3 levels) in <top (required)>'
   # /usr/local/rvm/gems/default/gems/database_cleaner-core-2.0.1/lib/database_cleaner/strategy.rb:30:in `cleaning'
   # /usr/local/rvm/gems/default/gems/database_cleaner-core-2.0.1/lib/database_cleaner/cleaners.rb:34:in `block (2 levels) in cleaning'
   # /usr/local/rvm/gems/default/gems/database_cleaner-core-2.0.1/lib/database_cleaner/cleaners.rb:35:in `cleaning'
   # ./spec/support/database_cleaner.rb:13:in `block (2 levels) in <top (required)>'

This made me believe that I'm not allowed to pass a Hash to the job itself. Now the error is much clearer:

 ArgumentError:
       Concurrency key must be a String, was Hash

This is technically a breaking change. Some types like Number, Time and Symbol were being cast to a String by rails automatically, this now raises an error. The readme says that the concurrency key must be a String, but it would be best for this to go in the next major version if accepted.

Copy link
Owner

@bensheldon bensheldon left a comment

Choose a reason for hiding this comment

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

@Earlopain Thinking about this, it's too much of a breaking change: objects that previously could be coerced to a string will start raising exceptions.

Simply doing the coercion would avoid the issue you had of the value failing to cast (though still leaves open it not being understood that a complex object is being stringified)

Comment on lines 121 to 125
raise ArgumentError, "Concurrency key must be a String, was #{key.class}" unless key.is_a?(String)

key
Copy link
Owner

Choose a reason for hiding this comment

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

Suggested change
raise ArgumentError, "Concurrency key must be a String, was #{key.class}" unless key.is_a?(String)
key
key.to_s

@Earlopain
Copy link
Collaborator Author

@Earlopain Thinking about this, it's too much of a breaking change: objects that previously could be coerced to a string will start raising exceptions.

Simply doing the coercion would avoid the issue you had of the value failing to cast (though still leaves open it not being understood that a complex object is being stringified)

I don't think doing to_s would be a good idea considering that you can pass model instances to jobs. It trades one issue for another:

class ExampleJob < ApplicationJob
  include GoodJob::ActiveJobExtensions::Concurrency
  good_job_control_concurrency_with(perform_limit: 1, key: -> { arguments.first })

  def perform(arg)
    sleep 60
  end
end

ExampleJob.perform_later(ExampleModel.first)
# key: "#<ExampleModel:0x00007f892b76bea0>"
ExampleJob.perform_later(ExampleModel.first)
# key: "#<ExampleModel:0x00007f892d66ee38>"

Both jobs would execute in parallel.

I agree on it being a breaking change. I have no issues with this being put on the backlog for v4, whenever that might be.

@bensheldon
Copy link
Owner

Hmmm. I don't want to require it to be is_a?(String) because there are plenty of other classes that I think should be acceptable: String, Symbol, Numeric, Date/Time....

...though maybe that isn't as infinite as I feared and it would be fine to typecheck those.

I also wonder if it would be possible to catch the bad cast exception and raise an easier to understand error.

@Earlopain
Copy link
Collaborator Author

I like the approach of just catching the error. I changed the commit to do just that.

@Earlopain Earlopain changed the title Require the concurrency key to be a string Return a useful error when concurrency key can't be cast Jan 9, 2023
@bensheldon bensheldon changed the title Return a useful error when concurrency key can't be cast Ensure Concurrency Keys are string-like and return a better error when they cannot be cast Jan 23, 2023
@bensheldon bensheldon added the bug Something isn't working label Jan 23, 2023
@bensheldon bensheldon changed the title Ensure Concurrency Keys are string-like and return a better error when they cannot be cast Ensure Concurrency Keys are string-like and return a better error when they cannot be cast to a string Jan 27, 2023
@bensheldon bensheldon merged commit 7750afe into bensheldon:main Jan 27, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Concurrency key doesn't handle Hash: TypeError (can't cast Hash)
2 participants