From 31d9a8a5c7b58e727923052b763c856fd55881e6 Mon Sep 17 00:00:00 2001 From: Emmanuel T Odeke Date: Wed, 12 Feb 2025 17:57:21 +0200 Subject: [PATCH 1/4] chore(x-goog-spanner-request-id): commit foundation This change adds in the bases to bring in the functionality for the "x-goog-spanner-request-id" that'll be used for debugging without the limits of trace continuity and sampling. Updates #3537 Address code review comments --- .../cloud/spanner/XGoogSpannerRequestId.java | 84 +++++++++++++++++++ .../spanner/XGoogSpannerRequestIdTest.java | 54 ++++++++++++ 2 files changed, 138 insertions(+) create mode 100644 google-cloud-spanner/src/main/java/com/google/cloud/spanner/XGoogSpannerRequestId.java create mode 100644 google-cloud-spanner/src/test/java/com/google/cloud/spanner/XGoogSpannerRequestIdTest.java diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/XGoogSpannerRequestId.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/XGoogSpannerRequestId.java new file mode 100644 index 00000000000..94221f4ea28 --- /dev/null +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/XGoogSpannerRequestId.java @@ -0,0 +1,84 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed 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 com.google.cloud.spanner; + +import com.google.api.core.InternalApi; +import com.google.common.annotations.VisibleForTesting; +import java.security.SecureRandom; +import java.util.Objects; + +@InternalApi +public class XGoogSpannerRequestId { + // 1. Generate the random process Id singleton. + @VisibleForTesting static String RAND_PROCESS_ID = XGoogSpannerRequestId.generateRandProcessId(); + + @VisibleForTesting + static final long VERSION = 1; // The version of the specification being implemented. + + private final long nthClientId; + private final long nthChannelId; + private final long nthRequest; + private long attempt; + + XGoogSpannerRequestId(long nthClientId, long nthChannelId, long nthRequest, long attempt) { + this.nthClientId = nthClientId; + this.nthChannelId = nthChannelId; + this.nthRequest = nthRequest; + this.attempt = attempt; + } + + public static XGoogSpannerRequestId of( + long nthClientId, long nthChannelId, long nthRequest, long attempt) { + return new XGoogSpannerRequestId(nthClientId, nthChannelId, nthRequest, attempt); + } + + private static String generateRandProcessId() { + return String.format("%08x", new SecureRandom().nextInt()); + } + + @Override + public String toString() { + return String.format( + "%d.%s.%d.%d.%d.%d", + XGoogSpannerRequestId.VERSION, + XGoogSpannerRequestId.RAND_PROCESS_ID, + this.nthClientId, + this.nthChannelId, + this.nthRequest, + this.attempt); + } + + @Override + public boolean equals(Object other) { + // instanceof for a null object returns false. + if (!(other instanceof XGoogSpannerRequestId)) { + return false; + } + + XGoogSpannerRequestId otherReqId = (XGoogSpannerRequestId) (other); + + return Objects.equals(this.nthClientId, otherReqId.nthClientId) + && Objects.equals(this.nthChannelId, otherReqId.nthChannelId) + && Objects.equals(this.nthRequest, otherReqId.nthRequest) + && Objects.equals(this.attempt, otherReqId.attempt); + } + + @Override + public int hashCode() { + return Objects.hash(this.nthClientId, this.nthChannelId, this.nthRequest, this.attempt); + } +} diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/XGoogSpannerRequestIdTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/XGoogSpannerRequestIdTest.java new file mode 100644 index 00000000000..30dbf909b28 --- /dev/null +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/XGoogSpannerRequestIdTest.java @@ -0,0 +1,54 @@ +/* + * Copyright 2025 Google LLC + * + * Licensed 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 + * + * https://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 com.google.cloud.spanner; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertTrue; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class XGoogSpannerRequestIdTest { + private static final Pattern REGEX_RAND_PROCESS_ID = + Pattern.compile("1.([0-9a-z]{8})(\\.\\d+){3}\\.(\\d+)$"); + + @Test + public void testEquals() { + XGoogSpannerRequestId reqID1 = XGoogSpannerRequestId.of(1, 1, 1, 1); + XGoogSpannerRequestId reqID2 = XGoogSpannerRequestId.of(1, 1, 1, 1); + assertEquals(reqID1, reqID2); + assertEquals(reqID1, reqID1); + assertEquals(reqID2, reqID2); + + XGoogSpannerRequestId reqID3 = XGoogSpannerRequestId.of(1, 1, 1, 2); + assertNotEquals(reqID1, reqID3); + assertNotEquals(reqID3, reqID1); + assertEquals(reqID3, reqID3); + } + + @Test + public void testEnsureHexadecimalFormatForRandProcessID() { + String str = XGoogSpannerRequestId.of(1, 2, 3, 4).toString(); + Matcher m = XGoogSpannerRequestIdTest.REGEX_RAND_PROCESS_ID.matcher(str); + assertTrue(m.matches()); + } +} From 4fea875fa39000c515c11500905a4a327ecc699e Mon Sep 17 00:00:00 2001 From: Emmanuel T Odeke Date: Sun, 16 Feb 2025 01:31:46 +0200 Subject: [PATCH 2/4] RandProcessId MUST be 64-bits and not 32-bits --- .../java/com/google/cloud/spanner/XGoogSpannerRequestId.java | 3 ++- .../com/google/cloud/spanner/XGoogSpannerRequestIdTest.java | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/XGoogSpannerRequestId.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/XGoogSpannerRequestId.java index 94221f4ea28..1b3be4233b5 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/XGoogSpannerRequestId.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/XGoogSpannerRequestId.java @@ -47,7 +47,8 @@ public static XGoogSpannerRequestId of( } private static String generateRandProcessId() { - return String.format("%08x", new SecureRandom().nextInt()); + // Expecting to use 64-bits of randomness to avoid clashes. + return String.format("%016x", new SecureRandom().nextInt(64)); } @Override diff --git a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/XGoogSpannerRequestIdTest.java b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/XGoogSpannerRequestIdTest.java index 30dbf909b28..12c9213c7dc 100644 --- a/google-cloud-spanner/src/test/java/com/google/cloud/spanner/XGoogSpannerRequestIdTest.java +++ b/google-cloud-spanner/src/test/java/com/google/cloud/spanner/XGoogSpannerRequestIdTest.java @@ -29,7 +29,7 @@ @RunWith(JUnit4.class) public class XGoogSpannerRequestIdTest { private static final Pattern REGEX_RAND_PROCESS_ID = - Pattern.compile("1.([0-9a-z]{8})(\\.\\d+){3}\\.(\\d+)$"); + Pattern.compile("1.([0-9a-z]{16})(\\.\\d+){3}\\.(\\d+)$"); @Test public void testEquals() { From 4c260a8588415c57513f939e2b17f0d8e23246ce Mon Sep 17 00:00:00 2001 From: Emmanuel T Odeke Date: Sun, 16 Feb 2025 16:06:50 +0200 Subject: [PATCH 3/4] Use BigInteger to generate the 64-bit integer --- .../java/com/google/cloud/spanner/XGoogSpannerRequestId.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/XGoogSpannerRequestId.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/XGoogSpannerRequestId.java index 1b3be4233b5..e239056effb 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/XGoogSpannerRequestId.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/XGoogSpannerRequestId.java @@ -18,6 +18,7 @@ import com.google.api.core.InternalApi; import com.google.common.annotations.VisibleForTesting; +import java.math.BigInteger; import java.security.SecureRandom; import java.util.Objects; @@ -48,7 +49,8 @@ public static XGoogSpannerRequestId of( private static String generateRandProcessId() { // Expecting to use 64-bits of randomness to avoid clashes. - return String.format("%016x", new SecureRandom().nextInt(64)); + BigInteger bigInt = new BigInteger(64, new SecureRandom()); + return String.format("%016x", bigInt); } @Override From 331e06ef27abe48259da7c05bcc55ac6624005bd Mon Sep 17 00:00:00 2001 From: Emmanuel T Odeke Date: Sun, 16 Feb 2025 16:10:24 +0200 Subject: [PATCH 4/4] Make RAND_PROCESS_ID static final too --- .../java/com/google/cloud/spanner/XGoogSpannerRequestId.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/XGoogSpannerRequestId.java b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/XGoogSpannerRequestId.java index e239056effb..4f6c0114750 100644 --- a/google-cloud-spanner/src/main/java/com/google/cloud/spanner/XGoogSpannerRequestId.java +++ b/google-cloud-spanner/src/main/java/com/google/cloud/spanner/XGoogSpannerRequestId.java @@ -25,7 +25,8 @@ @InternalApi public class XGoogSpannerRequestId { // 1. Generate the random process Id singleton. - @VisibleForTesting static String RAND_PROCESS_ID = XGoogSpannerRequestId.generateRandProcessId(); + @VisibleForTesting + static final String RAND_PROCESS_ID = XGoogSpannerRequestId.generateRandProcessId(); @VisibleForTesting static final long VERSION = 1; // The version of the specification being implemented.