This is an automated email from the ASF dual-hosted git repository. dsmiley pushed a commit to branch branch_10x in repository https://gitbox.apache.org/repos/asf/solr.git
commit b05147d5c30c8ef655833d29e1e878debee24b07 Author: Andrzej BiaĆecki <[email protected]> AuthorDate: Thu Sep 10 18:37:49 2026 +0200 SOLR-18422: Don't use System.nanoTime as absolute timestamp in CrossDC Producer. (#4888) (cherry picked from commit ccb0d010efc6defb4e77bb2cd842be2a32fca710) --- changelog/unreleased/solr-18422.yml | 8 +++++ .../solr/crossdc/common/MirroredSolrRequest.java | 5 +-- .../common/MirroredSolrRequestSerializerTest.java | 41 ++++++++++++++++++++++ 3 files changed, 52 insertions(+), 2 deletions(-) diff --git a/changelog/unreleased/solr-18422.yml b/changelog/unreleased/solr-18422.yml new file mode 100644 index 00000000000..6deba72b1df --- /dev/null +++ b/changelog/unreleased/solr-18422.yml @@ -0,0 +1,8 @@ +title: Don't use System.nanoTime as absolute timestamp in CrossDC producer. + +type: fixed +authors: + - name: Andrzej Bialecki +links: + - name: SOLR-18422 + url: https://issues.apache.org/jira/browse/SOLR-18422 diff --git a/solr/modules/cross-dc/src/java/org/apache/solr/crossdc/common/MirroredSolrRequest.java b/solr/modules/cross-dc/src/java/org/apache/solr/crossdc/common/MirroredSolrRequest.java index 4d29239adbe..d30e64aae60 100644 --- a/solr/modules/cross-dc/src/java/org/apache/solr/crossdc/common/MirroredSolrRequest.java +++ b/solr/modules/cross-dc/src/java/org/apache/solr/crossdc/common/MirroredSolrRequest.java @@ -36,6 +36,7 @@ import org.apache.solr.common.params.SolrParams; import org.apache.solr.common.util.ContentStream; import org.apache.solr.common.util.ContentStreamBase; import org.apache.solr.common.util.NamedList; +import org.apache.solr.common.util.TimeSource; /** * Class to encapsulate a mirrored Solr request. This adds a timestamp and #attempts to the request @@ -179,7 +180,7 @@ public class MirroredSolrRequest<T extends SolrResponse> { private long submitTimeNanos = 0; public MirroredSolrRequest(final SolrRequest<T> solrRequest) { - this(Type.UPDATE, 1, solrRequest, System.nanoTime()); + this(Type.UPDATE, 1, solrRequest, TimeSource.CURRENT_TIME.getTimeNs()); } public MirroredSolrRequest(final Type type, final SolrRequest<T> solrRequest) { @@ -187,7 +188,7 @@ public class MirroredSolrRequest<T extends SolrResponse> { } public MirroredSolrRequest(final Type type, final int attempt, final SolrRequest<T> solrRequest) { - this(type, attempt, solrRequest, System.nanoTime()); + this(type, attempt, solrRequest, TimeSource.CURRENT_TIME.getTimeNs()); } public MirroredSolrRequest( diff --git a/solr/modules/cross-dc/src/test/org/apache/solr/crossdc/common/MirroredSolrRequestSerializerTest.java b/solr/modules/cross-dc/src/test/org/apache/solr/crossdc/common/MirroredSolrRequestSerializerTest.java index 598ef314846..494a0812a74 100644 --- a/solr/modules/cross-dc/src/test/org/apache/solr/crossdc/common/MirroredSolrRequestSerializerTest.java +++ b/solr/modules/cross-dc/src/test/org/apache/solr/crossdc/common/MirroredSolrRequestSerializerTest.java @@ -29,6 +29,7 @@ import org.apache.solr.common.SolrInputDocument; import org.apache.solr.common.params.ModifiableSolrParams; import org.apache.solr.common.params.ShardParams; import org.apache.solr.common.params.SolrParams; +import org.apache.solr.common.util.TimeSource; import org.junit.BeforeClass; import org.junit.Test; import org.mockito.Mockito; @@ -177,6 +178,46 @@ public class MirroredSolrRequestSerializerTest extends SolrTestCase { assertEquals(List.of("field:value"), deserializedReq.getDeleteQuery()); } + @Test + public void testDefaultSubmitTime() { + // The timestamp must be comparable across JVMs (e.g. producer and consumer processes), so it + // has to be derived from a wall clock (System.currentTimeMillis() via TimeSource.CURRENT_TIME) + // rather than System.nanoTime(), which is only meaningful within a single JVM's lifetime. + long before = TimeSource.CURRENT_TIME.getTimeNs(); + MirroredSolrRequest<?> mirroredRequest = new MirroredSolrRequest<>(new UpdateRequest()); + long after = TimeSource.CURRENT_TIME.getTimeNs(); + + assertTrue( + "submitTimeNanos should be a wall-clock nanosecond timestamp, got " + + mirroredRequest.getSubmitTimeNanos(), + mirroredRequest.getSubmitTimeNanos() >= before + && mirroredRequest.getSubmitTimeNanos() <= after); + } + + @Test + public void testSubmitTimeNanosRoundTrip() { + // MirroredSolrRequest(Type, int attempt, SolrRequest) omits submitTimeNanos arg and must + // fall back to the same wall-clock-based timestamp. + MirroredSolrRequestSerializer serializer = new MirroredSolrRequestSerializer(); + UpdateRequest req = new UpdateRequest(); + req.deleteById("1"); + + long before = TimeSource.CURRENT_TIME.getTimeNs(); + MirroredSolrRequest<?> mirroredRequest = + new MirroredSolrRequest<>(MirroredSolrRequest.Type.UPDATE, 3, req); + long after = TimeSource.CURRENT_TIME.getTimeNs(); + + byte[] data = serializer.serialize("test", mirroredRequest); + MirroredSolrRequest<?> deserialized = serializer.deserialize("test", data); + + assertEquals(3, deserialized.getAttempt()); + assertEquals(mirroredRequest.getSubmitTimeNanos(), deserialized.getSubmitTimeNanos()); + assertTrue( + "submitTimeNanos should be a wall-clock nanosecond timestamp, got " + + deserialized.getSubmitTimeNanos(), + deserialized.getSubmitTimeNanos() >= before && deserialized.getSubmitTimeNanos() <= after); + } + /** * Confirms the waste pattern from {@link MirroredSolrRequestSerializer#serialize(String, * MirroredSolrRequest)} testing both small and large documents around the 8192 bytes boundary.
