Skip to content

Commit 8c92a39

Browse files
JCBC-2146 fix for lock duration when specified as sub-second rounds to 0
Issue :- When a lock duration is specified in sub-second units, the fractional part is lost during conversion to the binary protocol format, resulting in the lock duration being effectively rounded down to zero Fix :- To address the issue of sub-second lock durations being ignored, the fix involves rounding up the lock duration to the nearest whole second whenever fractional seconds (milliseconds, nanoseconds) are present. This ensures that the lock is held for at least one second, even if the specified duration is less than a full second. Change-Id: Id16cb52d6233a8011b0dc30a175038ab0aa586a2 Reviewed-on: https://review.couchbase.org/c/couchbase-jvm-clients/+/227612 Reviewed-by: David Nault <[email protected]> Tested-by: Build Bot <[email protected]>
1 parent c7d2a6f commit 8c92a39

File tree

3 files changed

+79
-2
lines changed

3 files changed

+79
-2
lines changed

core-io/src/main/java/com/couchbase/client/core/msg/kv/GetAndLockRequest.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
import com.couchbase.client.core.retry.RetryStrategy;
2828
import com.couchbase.client.core.deps.io.netty.buffer.ByteBuf;
2929
import com.couchbase.client.core.deps.io.netty.buffer.ByteBufAllocator;
30-
import com.couchbase.client.core.util.Bytes;
30+
import com.couchbase.client.core.util.CbDurations;
3131

3232
import java.time.Duration;
3333

@@ -60,7 +60,8 @@ public ByteBuf encode(ByteBufAllocator alloc, int opaque, KeyValueChannelContext
6060

6161
try {
6262
key = encodedKeyWithCollection(alloc, ctx);
63-
extras = alloc.buffer(Integer.BYTES).writeInt((int) lockFor.getSeconds());
63+
long secondsToLock = CbDurations.getSecondsCeil(lockFor);
64+
extras = alloc.buffer(Integer.BYTES).writeInt(Math.toIntExact(secondsToLock));
6465

6566
return MemcacheProtocol.request(alloc, Opcode.GET_AND_LOCK, noDatatype(),
6667
partition(), opaque, noCas(), extras, key, noBody());
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
/*
2+
* Copyright (c) 2025 Couchbase, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.couchbase.client.core.util;
18+
19+
import com.couchbase.client.core.annotation.Stability;
20+
21+
import java.time.Duration;
22+
23+
@Stability.Internal
24+
public final class CbDurations {
25+
private CbDurations() {
26+
27+
}
28+
29+
/**
30+
* Converts a Duration to seconds, rounding up if there are any partial seconds.
31+
* @param duration the duration to convert
32+
* @return the duration in seconds, rounded up if there are any partial seconds
33+
*/
34+
public static long getSecondsCeil(final Duration duration) {
35+
return duration.getNano() == 0
36+
? duration.getSeconds()
37+
: Math.addExact(duration.getSeconds(), 1L);
38+
}
39+
}
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package com.couchbase.client.core.util;
2+
3+
import org.junit.jupiter.api.Test;
4+
5+
import java.time.Duration;
6+
7+
import static org.junit.jupiter.api.Assertions.assertEquals;
8+
import static org.junit.jupiter.api.Assertions.assertThrows;
9+
10+
11+
public class CbDurationsTest {
12+
13+
@Test
14+
void shouldReturnSameValueForWholeSeconds() {
15+
assertEquals(0, CbDurations.getSecondsCeil(Duration.ZERO));
16+
assertEquals(1, CbDurations.getSecondsCeil(Duration.ofSeconds(1)));
17+
assertEquals(5, CbDurations.getSecondsCeil(Duration.ofSeconds(5)));
18+
assertEquals(Integer.MAX_VALUE,
19+
CbDurations.getSecondsCeil(Duration.ofSeconds(Integer.MAX_VALUE)));
20+
}
21+
22+
@Test
23+
void shouldRoundUpForPartialSeconds() {
24+
assertEquals(1, CbDurations.getSecondsCeil(Duration.ofMillis(1)));
25+
assertEquals(1, CbDurations.getSecondsCeil(Duration.ofMillis(999)));
26+
assertEquals(2, CbDurations.getSecondsCeil(Duration.ofMillis(1001)));
27+
assertEquals(2, CbDurations.getSecondsCeil(Duration.ofNanos(1_000_000_001)));
28+
}
29+
30+
@Test
31+
void shouldHandleMixedValues() {
32+
assertEquals(3, CbDurations.getSecondsCeil(
33+
Duration.ofSeconds(2).plusMillis(500)));
34+
assertEquals(3, CbDurations.getSecondsCeil(
35+
Duration.ofSeconds(2).plusNanos(1)));
36+
}
37+
}

0 commit comments

Comments
 (0)