Skip to content

[server] Four classes override equals() without hashCode() #3973

Description

@vbhanuchander-lang

Search before asking

  • I searched in the issues and found nothing similar.

Fluss version

main (development)

Please describe the bug 🐞

Summary

Follow-up to #3951, which covers the same defect in the record-batch classes. Four more classes
in fluss-server override equals() with value semantics but do not override hashCode(). In
each case the superclass is Object and equals() does not delegate to super.equals(), so
hashCode() is the identity hash and byte-for-byte equal instances hash differently, breaking
the general contract of Object.hashCode():

"If two objects are equal according to the equals(Object) method, then calling the
hashCode method on each of the two objects must produce the same integer result."
java.lang.Object.hashCode(), Java SE 11 API

The four:

Class equals() compares
IsrState.CommittedIsrState (IsrState.java:85) isr
IsrState.PendingExpandIsrState (IsrState.java:171) newInSyncReplicaId, sentLeaderAndIsr, lastCommittedState
IsrState.PendingShrinkIsrState (IsrState.java:244) outOfSyncReplicaIds, sentLeaderAndIsr, lastCommittedState
RemoteLogManifestHandle (RemoteLogManifestHandle.java:49) remoteLogManifestPath, remoteLogEndOffset

Why these four belong together

PendingExpandIsrState.equals() and PendingShrinkIsrState.equals() both compare
lastCommittedState, which is a CommittedIsrState. A correct hashCode() for either pending
state therefore has to fold in CommittedIsrState.hashCode() — so fixing the pending states
without also fixing CommittedIsrState would still leave the composed hash identity-derived and
inconsistent. They have to be done as one change.

LeaderAndIsr, the other field these two compare, already implements both equals() and
hashCode() consistently, so composing it is safe.

Impact

Any HashSet/HashMap keyed on these types would fail to find an equal instance. I checked and
nothing keys them today, so this is a latent trap rather than an active failure. The ISR states
in particular are compared frequently in the replica code, and equality already works — it is
only hashing that is broken, which is exactly the kind of thing that bites silently when someone
later reaches for a Set.

The Checkstyle EqualsHashCode module is not enabled in tools/maven/checkstyle.xml, which is
why CI does not flag any of this.

Solution

Add hashCode() to each class over exactly the fields its own equals() already compares,
using Objects.hash(...) to match the existing idiom in LeaderAndIsr in the same module.

One thing I deliberately did not change, and would like a maintainer's read on:
CommittedIsrState holds both isr and standbyReplicas, but its equals() compares only
isr. I kept hashCode() consistent with the equals() that is there today — Objects.hash(isr)
rather than widening equals(), since changing equality semantics for ISR state is a behavioural
change that deserves its own discussion. If the omission of standbyReplicas from equals() is
itself unintended, I am happy to open a separate issue for it.

Scope

Two further cases came out of the same sweep and are not included, for reasons worth stating:

  • TieringSourceEnumeratorState (fluss-flink-common) also has no hashCode(), but its problem
    is larger than a missing hash: equals() is implemented as
    this.toString().equals(that.toString()) against any non-null argument, so it is asymmetric and
    will accept unrelated types. Adding a hashCode() alone would only half-fix it, so it needs its
    own issue.
  • WriteResultForBucket and its subclasses, and the ModifyColumn* classes in the Flink 1.18/1.19
    modules, inherit a hashCode() from a base class that hashes a subset of the fields their
    equals() compares. That is legal — merely a weaker hash — so I have left them alone.

Are you willing to submit a PR?

  • I'm willing to submit a PR!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions