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

Fixup have_enqueued_job matcher on job retries #2573

Merged
merged 4 commits into from
Apr 28, 2023

Conversation

ojab
Copy link
Contributor

@ojab ojab commented Feb 14, 2022

Previously we were checking only job counts, so if one job was
performed and one job was added - matcher failed. Check by unique
id (#hash) instead.
We cannot use job['job_id'] here, because job retains job_id on retry.

This works only with rails >= 6.1, because retry_on was introduced in 6.0 and apparently test queue adapter is broken for retries in 6.0: it adds executed job back with added exception_executions along with actual retried job. So dunno how to proceed here, please advise /shrug

Test CI run: https://github.com/ojab/rspec-rails/runs/5183906574?check_suite_focus=true

Also:
fixes #2668

@ojab ojab force-pushed the fixup_have_enqueued_job_on_retries branch 2 times, most recently from f6316ca to ab03c3d Compare February 14, 2022 12:47
.github/workflows/ci.yml Outdated Show resolved Hide resolved
rspec-rails.gemspec Outdated Show resolved Hide resolved
@ojab ojab marked this pull request as ready for review February 14, 2022 13:08
@ojab
Copy link
Contributor Author

ojab commented Feb 14, 2022

hmm.. Actually looking @ CI, 6.0 is green, so probably failure is rails-6.0 <-> ruby-3.1 compat locally. And 5.2 fails with unknown keyword queue. so retry_on is there.
I'll check tomorrow and I guess would get green CI without any workarounds 😅

@ojab ojab marked this pull request as draft February 14, 2022 21:36
@ojab ojab force-pushed the fixup_have_enqueued_job_on_retries branch 2 times, most recently from f6b2620 to be0b3b7 Compare February 15, 2022 11:29
@ojab ojab marked this pull request as ready for review February 15, 2022 11:29
@ojab
Copy link
Contributor Author

ojab commented Feb 15, 2022

It became more complicated due to rails-6.0 pushing jobs with the same #hash to the queue, but CI is green https://github.com/ojab/rspec-rails/runs/5198727860?check_suite_focus=true
Cleaned up, still two additional commits: first fixing rspec dependencies version in gemspec, second restricting rails-5.2 version for ruby-2.2 compat (filled rails/rails#44435 about it).

@ojab ojab requested review from pirj and JonRowe February 15, 2022 11:31
@JonRowe
Copy link
Member

JonRowe commented Feb 16, 2022

We've merged build fixes for Rails and updated the minimum versions so this should be ok to be rebased without your ci changes

@ojab ojab force-pushed the fixup_have_enqueued_job_on_retries branch from be0b3b7 to c308f61 Compare February 16, 2022 09:26
@ojab
Copy link
Contributor Author

ojab commented Feb 16, 2022

🙇 done

Copy link
Member

@pirj pirj left a comment

Choose a reason for hiding this comment

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

Thanks!

lib/rspec/rails/matchers/active_job.rb Outdated Show resolved Hide resolved
@ojab ojab force-pushed the fixup_have_enqueued_job_on_retries branch 2 times, most recently from 1de9d7d to 0cd7393 Compare February 16, 2022 10:10
@ojab
Copy link
Contributor Author

ojab commented Oct 27, 2022

😿

@ojab ojab force-pushed the fixup_have_enqueued_job_on_retries branch from 0cd7393 to 30ba268 Compare October 27, 2022 09:32
@pirj pirj force-pushed the fixup_have_enqueued_job_on_retries branch 2 times, most recently from 2625570 to 304f624 Compare October 27, 2022 19:54
@pirj

This comment was marked as outdated.


check(in_block_jobs)
in_block_jobs = queue_adapter.enqueued_jobs.each_with_object({}) do |job, jobs|
Copy link
Member

Choose a reason for hiding this comment

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

It appears to me that storing by the hash key is not necessary.
I've added a couple more jobs of the same kind, and they have a different job_id assigned, resulting in a different hash.

As a result, count is always a 1.

Below, a -= 1 and a further .positive? with Array.new and a compact is basically subtraction of previously enqueued jobs from all enqueued jobs.

I dared pushing a commit that compares with a Set#-.

Can you write a failing spec to prove this approach is insufficient?

@pirj pirj removed the request for review from JonRowe October 27, 2022 21:23
Copy link
Member

@pirj pirj left a comment

Choose a reason for hiding this comment

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

Needs a test that would fail without changes, so that changes would make it green.

@pirj
Copy link
Member

pirj commented Oct 28, 2022

The previous implementation was:

  1. Save the number of currently enqueued jobs
  2. Run the block passed to expect { ... } that can:
  • perform previously scheduled jobs and remove them from enqueued
  • enqueue more jobs
  1. Remove the number of previously enqueued jobs from the list of currently enqueued jobs

Of course, since the matcher is scoped to one specific job class, it's quite an edge case.

This (previous, before the PR's) implementation was not considering jobs that were at the bottom of the list, and moved to the top of the list, e.g.:

- job with args [1]
- job with args [2]
+ job with args [1]
+ job with args [2]

And expect { ... }.to have_enqueued_jobs would fail because it would think that no jobs were (re-)enqueued.

Equally, if the order of jobs would change, or if some jobs succeed, or if more jobs succeed than fail and a re-scheduled, the expectation would fail.

I believe we can leave some of those cases for follow-up improvements, but one I'd love to see is:

# some setup
expect { ... }.to have_enqueued_job(MyJob)

that would fail if we just compare the counts of matching jobs.

It is unfair to do so with counts because yes, one job was performed, and one enqueued.
If you scope it to the :retry queue, the expectation would pass. Because there were no jobs on that queue, and now there's one. But this is unfair, too, as it works fine with the previous count-only implementation, too.

How about an example with a job that was on :retry queue already and got re-enqueued again?

@JonRowe
Copy link
Member

JonRowe commented Nov 10, 2022

This looks fine to me, but I defer to @pirj as we do want a failing spec this fixes :)

@pirj
Copy link
Member

pirj commented Dec 1, 2022

Previously we were checking only job counts, so if one job was performed and one job was added - matcher failed.

This is an incorrect assumption.

The "before" for the matcher is everything prior to what is inside the expect { ... } block.
I am not aware of an option to perform jobs with the test adapter that were enqueued previously.
queue_adapter.perform_enqueued_jobs = true and perform_enqueued_jobs { ... } do not perform previously enqueued jobs.

So if a job is enqueued, performed and schedules itself all inside expect { ... }, all previously enqueued jobs are out of equation with

in_block_jobs = queue_adapter.enqueued_jobs.drop(original_enqueued_jobs_count)

and all the jobs that are left enqueued (e.g. when perform_enqueued_jobs = true but perform_enqueued_at_jobs = false) like in the case with job retries, are still detected. This is what your newly added example shows, as it is passing just fine with the current implementation.

Even though I'd love to merge my shiny refactoring involving Set that considers if the same job gets re-enqueued, I have no good idea how it can affect a real-world spec.

I failed to write an example that would break the existing implementation, and with this I'm more inclined to close this PR.

Please do not hesitate to reopen if you can write a failing spec.

@pirj pirj closed this Dec 1, 2022
@pirj
Copy link
Member

pirj commented Apr 18, 2023

Actually, a spec might be possible to write with a direct non-block usage of perform_enqueued_jobs as here.

@pirj pirj reopened this Apr 18, 2023
@pirj
Copy link
Member

pirj commented Apr 19, 2023

@ojab Does this look correct to you?
Please accept my apologies for nitpicking and ignorance of the block-less form of perform_enqueued_jobs that was needed to reproduce the issue.

@pirj pirj requested a review from JonRowe April 19, 2023 06:15
@ojab
Copy link
Contributor Author

ojab commented Apr 19, 2023

@pirj oh, right, sorry for bogus spec. I'll update the PR tomorrow to use non-block form and will check that it fails properly in master.

@ojab
Copy link
Contributor Author

ojab commented Apr 20, 2023

Checked the commit where monkeypatch was introduced into local codebase, it's

retried_job.perform_later

expect  { perform_enqueued_jobs }.to 

so yeah, spec is indeed doesn't mirror what was broken 🤦
Changed/verified that it breaks with rails-6.1 on main and works with changes in this PR/pushed.

@ojab ojab force-pushed the fixup_have_enqueued_job_on_retries branch from 8b111e9 to a9d666a Compare April 20, 2023 09:54
@pirj
Copy link
Member

pirj commented Apr 21, 2023

Which monkey patch?

How does this work?
We set the adapter to perform jobs, then schedule a job.
The job should immediately execute, fail and be re-scheduled for later, right?
What does the second perform_enqueued_jobs do, performs it once again, and then it is re-scheduled once again?

        queue_adapter.perform_enqueued_jobs = true
      end

      it "passes with reenqueued job" do
        time = Time.current.change(usec: 0)

        retried_job.perform_later

        travel_to time do
          expect  { perform_enqueued_jobs }.to

I strongly prefer the solution with Set, as it's significantly simpler.
Does it solve the same issue?

ojab and others added 4 commits April 28, 2023 23:52
Previously we were checking only job counts, so if one job was
performed and one job was added - matcher failed. Check by unique
id (`#hash`) instead.
We cannot use `job['job_id']` here, because job retains `job_id` on
retry.

Also we need to count jobs before & after because multiple
`#perform_later` in rails-6.0 create jobs with the same `#hash`, at
least in some cases, using `:test` AJ adapter.
@pirj pirj force-pushed the fixup_have_enqueued_job_on_retries branch from a9d666a to 47f15d2 Compare April 28, 2023 20:55
@pirj pirj merged commit f598b53 into rspec:main Apr 28, 2023
@pirj
Copy link
Member

pirj commented Apr 28, 2023

Thank you, @ojab !
I went with my implementation approach for the fix. If you feel that yours is superior, please don't hesitate to open a PR with a failing spec.

JonRowe pushed a commit that referenced this pull request May 4, 2023
Fixup `have_enqueued_job` matcher on job retries
@JonRowe
Copy link
Member

JonRowe commented May 4, 2023

Released in 6.0.2

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.

New ActiveJob is not counted when other job executed
3 participants