Skip to content

Fix UniqueOpts.ExcludeKind being silently ignored by isEmpty() - #1404

Merged
brandur merged 2 commits into
riverqueue:masterfrom
JackDanger:fix/uniqueopts-isempty-excludekind
Sep 29, 2026
Merged

brandur merged 2 commits into
riverqueue:masterfrom
JackDanger:fix/uniqueopts-isempty-excludekind

Conversation

@JackDanger

@JackDanger JackDanger commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

UniqueOpts.ExcludeKind is silently ignored on master. The public isEmpty() never looks at it — despite its own doc comment warning that every new option must be added there — while the copy of the same opts in internal/dbunique (dbunique.UniqueOpts.IsEmpty()) does count it. So UniqueOpts{ExcludeKind: true} inserts jobs with no unique key at all, and when a worker also carries unique opts, those silently override the caller's instead.

Two commits: first the failing test (TestUniqueOpts_isEmpty asserts the public and internal isEmpty agree for every field combination), then the fix — just the isEmpty() one-liner. Acceptance tests pin the insert-params path around it.

After review, the corner config {ExcludeKind: true} with nothing else set stays valid: it now applies uniqueness across all jobs (a whole-table key, since ByState defaults to UniqueOptsByStateDefault() when unset) instead of being silently ignored.

Reproduce on master with just the test commit:

go test . -run 'TestUniqueOpts_isEmpty' -count=1

(the acceptance tests in the branch stay green on master too — the twin-parity assertion is the one that fails.)

@JackDanger
JackDanger force-pushed the fix/uniqueopts-isempty-excludekind branch 3 times, most recently from 6fb21b7 to e797f26 Compare September 28, 2026 07:36
@brandur

brandur commented Sep 28, 2026

Copy link
Copy Markdown
Contributor

@JackDanger Thanks!!

Could you sign the CLA please?
https://github.com/riverqueue/rivercla

A small bug in the new validation is that this shape of UniqueOpts would become invalid:

UniqueOpts{
    ExcludeKind: true,
    ByState:     rivertype.UniqueOptsByStateDefault(),
}

We could modify your logic to also allow ByState, but that would ban this form of UniqueOpts, which is the same as the UniqueOpts above, and which should probably stay allowed:

UniqueOpts{ExcludeKind: true}

I wonder if maybe what we should do here is keep the isEmpty fix, but back out the new validation logic and just leave as is. Thoughts?

@JackDanger
JackDanger force-pushed the fix/uniqueopts-isempty-excludekind branch from e797f26 to 6eeeb85 Compare September 28, 2026 17:56
@brandur

brandur commented Sep 28, 2026

Copy link
Copy Markdown
Contributor

Thanks Jack!

@bgentry Can I get your opinion on this one as well? I suggested to Jack that we drop validation in favor of allowing UniqueOpts{ExcludeKind: true}. He made that change. I think it does make sense logically, but I suppose I could see it as a slight footgun in that it'd allow you to force all your jobs to share a single basically a single uniqueness key.

@JackDanger

Copy link
Copy Markdown
Contributor Author

signed! riverqueue/rivercla#36

I'm looking for ways to make this a little more elegant and I appreciate the review and feedback 🙇

@brandur

brandur commented Sep 29, 2026

Copy link
Copy Markdown
Contributor

@JackDanger Thanks :)

Ugh, I hate to do this, but I was thinking about this more tonight, and I think what you had originally was probably better. Allowing UniqueOpts{ExcludeKind: true} just feels like too much an accident waiting to happen.

What do you think about resurrecting a validation like this?

if o.ExcludeKind && !o.ByArgs && !o.ByQueue && o.ByPeriod == 0 {
    return errors.New("UniqueOpts.ExcludeKind requires ByArgs, ByQueue, or ByPeriod")
}

And in case we ever get a complaint about it, we could reevaluate.

Sorry about the flip flop :/ I should have thought harder about this when I suggested the original change.

The public UniqueOpts.isEmpty() does not consider ExcludeKind, against the
warning in its own doc comment, while dbunique.UniqueOpts.IsEmpty() in
internal/dbunique does. A UniqueOpts with only ExcludeKind set is therefore
treated as unset by insertParamsFromConfigArgsAndOptions, and uniqueness
silently never applies.

- TestUniqueOpts_isEmpty now asserts both isEmpty implementations agree
  for every field permutation.
- TestUniqueOpts_validateExcludeKind asserts an ExcludeKind-only config
  is rejected (the resulting key would be constant across the whole
  table).
- Two subtests in TestInsertParamsFromJobArgsAndOptions cover rejection
  via the real insert-params path and key equality across kinds with
  ByArgs+ExcludeKind.

These tests fail until the isEmpty()/validate() fix lands.
The public UniqueOpts.isEmpty() never considered ExcludeKind — against the
explicit warning in its own doc comment — even though d977e10 added the
field alongside the same option inside internal/dbunique, whose IsEmpty()
does consider it. Callers passing only UniqueOpts{ExcludeKind: true}
therefore had no unique key computed at all: jobs were inserted with no
dedup, silently, while the option's doc promised uniqueness "across all
jobs regardless of kind".

- isEmpty() now counts ExcludeKind, matching dbunique.UniqueOpts.IsEmpty().
- validate() rejects the ExcludeKind-only combination: the unique key is
  built from kind/args/queue/period, so kind excluded and nothing else
  included leaves no key to dedupe by. The combination is safe to
  re-evaluate if a justified use shows up, per review.
- The UniqueOpts.ExcludeKind doc comment states the combination
  requirement.

Signed-off-by: Jack Danger <github@jackcanty.com>
@JackDanger
JackDanger force-pushed the fix/uniqueopts-isempty-excludekind branch from 6eeeb85 to 8e3ef79 Compare September 29, 2026 05:10
@JackDanger

Copy link
Copy Markdown
Contributor Author

Thanks Brandur, addressed in 8e3ef79

@brandur brandur left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks!

@brandur
brandur marked this pull request as ready for review September 29, 2026 05:25
@brandur
brandur merged commit bb1ef22 into riverqueue:master Sep 29, 2026
12 checks passed
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.

2 participants