You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@kafka.apache.org by ch...@apache.org on 2026/09/09 03:50:46 UTC
(kafka) branch trunk updated: MINOR: Exclude expiryTimestamp from TokenInformation.hashCode (#23098)
This is an automated email from the ASF dual-hosted git repository.
chia7712 pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/kafka.git
The following commit(s) were added to refs/heads/trunk by this push:
new 5c297fd8b33 MINOR: Exclude expiryTimestamp from TokenInformation.hashCode (#23098)
5c297fd8b33 is described below
commit 5c297fd8b338ea03ea607a820af0cb3442cd4a46
Author: Hüseyin Kutsi Balcı <ba...@gmail.com>
AuthorDate: Wed Sep 9 06:50:40 2026 +0300
MINOR: Exclude expiryTimestamp from TokenInformation.hashCode (#23098)
TokenInformation.equals() compares six fields but hashCode() also
includes expiryTimestamp, so two equal instances can have different hash
codes, violating the Object.hashCode contract. expiryTimestamp is also
the only mutable field.
This removes expiryTimestamp from hashCode() to match equals(). Note
that setExpiryTimestamp is not called anywhere in KRaft, so this has no
behavioural impact inside Kafka; it only fixes the published contract of
a public class.
Adds TokenInformationTest.
Co-Authored-By: Claude Sonnet 4.6 <cl...@anthropic.com>
Reviewers: Chia-Ping Tsai <ch...@gmail.com>, Ismael Juma
<is...@juma.me.uk>
---
.../token/delegation/TokenInformation.java | 2 +-
.../token/delegation/TokenInformationTest.java | 89 ++++++++++++++++++++++
2 files changed, 90 insertions(+), 1 deletion(-)
diff --git a/clients/src/main/java/org/apache/kafka/common/security/token/delegation/TokenInformation.java b/clients/src/main/java/org/apache/kafka/common/security/token/delegation/TokenInformation.java
index 844280c1084..12c78ab15c7 100644
--- a/clients/src/main/java/org/apache/kafka/common/security/token/delegation/TokenInformation.java
+++ b/clients/src/main/java/org/apache/kafka/common/security/token/delegation/TokenInformation.java
@@ -147,6 +147,6 @@ public class TokenInformation {
@Override
public int hashCode() {
- return Objects.hash(owner, tokenRequester, renewers, issueTimestamp, maxTimestamp, expiryTimestamp, tokenId);
+ return Objects.hash(owner, tokenRequester, renewers, issueTimestamp, maxTimestamp, tokenId);
}
}
diff --git a/clients/src/test/java/org/apache/kafka/common/security/token/delegation/TokenInformationTest.java b/clients/src/test/java/org/apache/kafka/common/security/token/delegation/TokenInformationTest.java
new file mode 100644
index 00000000000..2167a8fdc62
--- /dev/null
+++ b/clients/src/test/java/org/apache/kafka/common/security/token/delegation/TokenInformationTest.java
@@ -0,0 +1,89 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.kafka.common.security.token.delegation;
+
+import org.apache.kafka.common.security.auth.KafkaPrincipal;
+
+import org.junit.jupiter.api.Test;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+public class TokenInformationTest {
+
+ private static final KafkaPrincipal OWNER = new KafkaPrincipal(KafkaPrincipal.USER_TYPE, "owner");
+ private static final long ISSUE_TIMESTAMP = 1000L;
+ private static final long MAX_TIMESTAMP = 9000L;
+
+ private static TokenInformation tokenWithExpiry(long expiryTimestamp) {
+ return new TokenInformation("tokenId", OWNER, OWNER, List.of(OWNER),
+ ISSUE_TIMESTAMP, MAX_TIMESTAMP, expiryTimestamp);
+ }
+
+ @Test
+ public void testEqualsAndHashCode() {
+ TokenInformation tokenInformation = tokenWithExpiry(5000L);
+ TokenInformation same = tokenWithExpiry(5000L);
+
+ assertEquals(tokenInformation, same);
+ assertEquals(tokenInformation.hashCode(), same.hashCode());
+ }
+
+ @Test
+ public void testHashCodeIgnoresExpiryTimestampLikeEquals() {
+ // equals() deliberately leaves expiryTimestamp out: renewing a token does not
+ // make it a different token. hashCode() has to leave it out for the same reason,
+ // otherwise two instances can be equal and still hash differently.
+ TokenInformation tokenInformation = tokenWithExpiry(5000L);
+ TokenInformation renewed = tokenWithExpiry(6000L);
+
+ assertEquals(tokenInformation, renewed);
+ assertEquals(tokenInformation.hashCode(), renewed.hashCode());
+ }
+
+ @Test
+ public void testEqualInstancesCollapseInHashSet() {
+ TokenInformation tokenInformation = tokenWithExpiry(5000L);
+ TokenInformation renewed = tokenWithExpiry(6000L);
+
+ Set<TokenInformation> tokens = new HashSet<>();
+ tokens.add(tokenInformation);
+
+ assertTrue(tokens.contains(renewed));
+ tokens.add(renewed);
+ assertEquals(1, tokens.size());
+ }
+
+ @Test
+ public void testHashCodeIsStableAcrossRenewal() {
+ // setExpiryTimestamp is what renewal calls, so it can run on an instance that
+ // is already an element of a hash-based collection.
+ TokenInformation tokenInformation = tokenWithExpiry(5000L);
+ Set<TokenInformation> tokens = new HashSet<>();
+ tokens.add(tokenInformation);
+
+ int hashCodeBeforeRenewal = tokenInformation.hashCode();
+ tokenInformation.setExpiryTimestamp(7000L);
+
+ assertEquals(hashCodeBeforeRenewal, tokenInformation.hashCode());
+ assertTrue(tokens.contains(tokenInformation));
+ }
+}