Skip to content

Commit 60eb231

Browse files
authored
[Java] Relax cuVS version matching (#1544)
As discussed during a sync, we want to relax version matching/checking between cuvs-java and the cuVS C library. The rationale is that the C API should be pretty stable, and the cuvs-java bindings to the C API should not be affected by the cuVS version, provided that the API does not change. For these reasons, we do still want to check that - the C library version is at least as recent as the Java module - the C library is not "too far" in the future; we do not want unlimited forward compatibility, we want to be able to change the C API at some point (after e.g. a deprecation cycle). This PR introduces these checks, allowing cuvs-java to start and bind with C libraries up to 3 more cuVS releases in the future. Authors: - Lorenzo Dematté (https://github.com/ldematte) - MithunR (https://github.com/mythrocks) Approvers: - Chris Hegarty (https://github.com/ChrisHegarty) - MithunR (https://github.com/mythrocks) URL: #1544
1 parent 54ae46a commit 60eb231

File tree

2 files changed

+236
-8
lines changed

2 files changed

+236
-8
lines changed

java/cuvs-java/src/main/java22/com/nvidia/cuvs/spi/JDKProvider.java

Lines changed: 97 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,73 @@
3434

3535
final class JDKProvider implements CuVSProvider {
3636

37+
record CuVSVersion(short major, short minor, short patch) {
38+
private static final int MAX_VERSION_DISTANCE = 3;
39+
40+
static CuVSVersion fromString(String versionString) {
41+
var tokens = versionString.split("\\.");
42+
final short major = parseToken(tokens, 0);
43+
final short minor = parseToken(tokens, 1);
44+
final short patch = parseToken(tokens, 2);
45+
if (major == 0 || minor == 0) {
46+
return null;
47+
}
48+
return new CuVSVersion(major, minor, patch);
49+
}
50+
51+
private static short parseToken(String[] tokens, int index) {
52+
if (index < tokens.length) {
53+
try {
54+
return Short.parseShort(tokens[index]);
55+
} catch (NumberFormatException _) {
56+
}
57+
}
58+
return 0;
59+
}
60+
61+
static boolean isCuVSMoreRecent(CuVSVersion mavenVersion, CuVSVersion cuvsVersion) {
62+
return (cuvsVersion.major == mavenVersion.major && cuvsVersion.minor >= mavenVersion.minor)
63+
|| cuvsVersion.major > mavenVersion.major;
64+
}
65+
66+
static boolean isCuVSWithinMaxRange(CuVSVersion mavenVersion, CuVSVersion cuvsVersion)
67+
throws ProviderInitializationException {
68+
var maxVersionFromSyspropString = System.getProperty("cuvs.max_version");
69+
70+
final CuVSVersion maxVersion;
71+
if (maxVersionFromSyspropString != null) {
72+
maxVersion = fromString(maxVersionFromSyspropString);
73+
if (maxVersion == null) {
74+
throw new ProviderInitializationException(
75+
"System property 'cuvs.max_version' is not a valid cuVS version: "
76+
+ maxVersionFromSyspropString);
77+
}
78+
} else {
79+
maxVersion = addReleases(mavenVersion, MAX_VERSION_DISTANCE);
80+
}
81+
82+
return cuvsVersion.major < maxVersion.major
83+
|| (cuvsVersion.major == maxVersion.major && cuvsVersion.minor <= maxVersion.minor);
84+
}
85+
86+
private static CuVSVersion addReleases(CuVSVersion currentVersion, int numberOfReleases) {
87+
short candidateMinor = (short) (currentVersion.minor + numberOfReleases * 2);
88+
short releaseMinor = (short) (candidateMinor % 12);
89+
short releaseMajor = (short) (currentVersion.major + (candidateMinor / 12));
90+
if (releaseMinor == 0) {
91+
releaseMinor = 12;
92+
releaseMajor -= 1;
93+
}
94+
95+
return new CuVSVersion(releaseMajor, releaseMinor, (short) 0);
96+
}
97+
98+
@Override
99+
public String toString() {
100+
return String.format(Locale.ROOT, "%02d.%02d.%d", major, minor, patch);
101+
}
102+
}
103+
37104
private static final MethodHandle createNativeDataset$mh;
38105
private static final MethodHandle createNativeDatasetWithStrides$mh;
39106

@@ -75,8 +142,6 @@ private JDKProvider() {}
75142
static CuVSProvider create() throws ProviderInitializationException {
76143
NativeDependencyLoader.loadLibraries();
77144

78-
var mavenVersion = readCuVSVersionFromManifest();
79-
80145
try (var localArena = Arena.ofConfined()) {
81146
var majorPtr = localArena.allocate(uint16_t);
82147
var minorPtr = localArena.allocate(uint16_t);
@@ -86,17 +151,41 @@ static CuVSProvider create() throws ProviderInitializationException {
86151
var minor = minorPtr.get(uint16_t, 0);
87152
var patch = patchPtr.get(uint16_t, 0);
88153

89-
var cuvsVersionString = String.format(Locale.ROOT, "%02d.%02d.%d", major, minor, patch);
90-
if (mavenVersion != null && !cuvsVersionString.equals(mavenVersion)) {
154+
var mavenVersionString = readCuVSVersionFromManifest();
155+
checkCuVSVersionMatching(mavenVersionString, major, minor, patch);
156+
}
157+
return new JDKProvider();
158+
}
159+
160+
static void checkCuVSVersionMatching(
161+
String mavenVersionString, short major, short minor, short patch)
162+
throws ProviderInitializationException {
163+
var mavenVersion = CuVSVersion.fromString(mavenVersionString);
164+
var cuvsVersion = new CuVSVersion(major, minor, patch);
165+
166+
if (mavenVersion != null) {
167+
if (!CuVSVersion.isCuVSMoreRecent(mavenVersion, cuvsVersion)) {
168+
throw new ProviderInitializationException(
169+
String.format(
170+
Locale.ROOT,
171+
"""
172+
Version mismatch: outdated libcuvs_c (libcuvs_c [%s], cuvs-java version [%s]).\
173+
Please upgrade your libcuvs_c installation to match at lease the cuvs-java\
174+
version.\
175+
""",
176+
cuvsVersion,
177+
mavenVersion));
178+
}
179+
if (!CuVSVersion.isCuVSWithinMaxRange(mavenVersion, cuvsVersion)) {
91180
throw new ProviderInitializationException(
92181
String.format(
93182
Locale.ROOT,
94-
"libcuvs_c version mismatch: expected [%s], found [%s]",
95-
mavenVersion,
96-
cuvsVersionString));
183+
"Version mismatch: unsupported libcuvs_c (libcuvs_c [%s], cuvs-java version [%s]). "
184+
+ "Please upgrade your software, or install a previous version of libcuvs_c.",
185+
cuvsVersion,
186+
mavenVersion));
97187
}
98188
}
99-
return new JDKProvider();
100189
}
101190

102191
/**
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
/*
2+
* SPDX-FileCopyrightText: Copyright (c) 2025, NVIDIA CORPORATION.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
package com.nvidia.cuvs.spi;
6+
7+
import static com.carrotsearch.randomizedtesting.RandomizedTest.assumeTrue;
8+
import static org.junit.Assert.assertEquals;
9+
import static org.junit.Assert.assertThrows;
10+
11+
import com.nvidia.cuvs.CuVSTestCase;
12+
import java.lang.invoke.MethodHandles;
13+
import java.lang.invoke.MethodType;
14+
import org.junit.Before;
15+
import org.junit.Test;
16+
17+
public class CuVSProviderIT extends CuVSTestCase {
18+
19+
@Before
20+
public void setup() {
21+
assumeTrue("not supported on " + System.getProperty("os.name"), isLinuxAmd64());
22+
// Clear sysprop from previous runs/command line
23+
System.clearProperty("cuvs.max_version");
24+
}
25+
26+
@Test
27+
public void testSameVersionCheck() {
28+
try {
29+
checkCuVSVersionMatching("25.12.0", 25, 12, 0);
30+
} catch (ProviderInitializationException e) {
31+
throw new AssertionError(e);
32+
}
33+
}
34+
35+
@Test
36+
public void testPatchLevelNotConsidered() {
37+
try {
38+
checkCuVSVersionMatching("25.12.0", 25, 12, 1);
39+
checkCuVSVersionMatching("25.12.1", 25, 12, 0);
40+
checkCuVSVersionMatching("25.12", 25, 12, 0);
41+
} catch (ProviderInitializationException e) {
42+
throw new AssertionError(e);
43+
}
44+
}
45+
46+
@Test
47+
public void testInvalidVersionsNotConsidered() {
48+
try {
49+
checkCuVSVersionMatching("abc", 25, 12, 0);
50+
checkCuVSVersionMatching("0.0.0", 25, 12, 0);
51+
} catch (ProviderInitializationException e) {
52+
throw new AssertionError(e);
53+
}
54+
}
55+
56+
@Test
57+
public void testPastVersionCheck() {
58+
var ex =
59+
assertThrows(
60+
ProviderInitializationException.class,
61+
() -> checkCuVSVersionMatching("25.12.0", 25, 10, 0));
62+
63+
assertEquals(
64+
"""
65+
Version mismatch: outdated libcuvs_c (libcuvs_c [25.10.0], cuvs-java version [25.12.0]). \
66+
Please upgrade your libcuvs_c installation to match at lease the cuvs-java version.\
67+
""",
68+
ex.getMessage());
69+
}
70+
71+
@Test
72+
public void testSupportedFutureVersionCheck() {
73+
try {
74+
checkCuVSVersionMatching("25.12.0", 26, 2, 0);
75+
checkCuVSVersionMatching("26.02.0", 26, 8, 0);
76+
checkCuVSVersionMatching("26.02.0", 26, 8, 1);
77+
} catch (ProviderInitializationException e) {
78+
throw new AssertionError(e);
79+
}
80+
}
81+
82+
@Test
83+
public void testUnsupportedFutureVersionCheck() {
84+
var ex =
85+
assertThrows(
86+
ProviderInitializationException.class,
87+
() -> checkCuVSVersionMatching("25.12.0", 26, 10, 0));
88+
89+
assertEquals(
90+
"""
91+
Version mismatch: unsupported libcuvs_c (libcuvs_c [26.10.0], cuvs-java version [25.12.0]). \
92+
Please upgrade your software, or install a previous version of libcuvs_c.\
93+
""",
94+
ex.getMessage());
95+
}
96+
97+
@Test
98+
public void testMaxVersionOverride() {
99+
try {
100+
checkCuVSVersionMatching("25.12.0", 26, 4, 0);
101+
System.setProperty("cuvs.max_version", "26.02.0");
102+
103+
var ex =
104+
assertThrows(
105+
ProviderInitializationException.class,
106+
() -> checkCuVSVersionMatching("25.12.0", 26, 4, 0));
107+
108+
assertEquals(
109+
"""
110+
Version mismatch: unsupported libcuvs_c (libcuvs_c [26.04.0], cuvs-java version [25.12.0]). \
111+
Please upgrade your software, or install a previous version of libcuvs_c.\
112+
""",
113+
ex.getMessage());
114+
System.setProperty("cuvs.max_version", "26.12.0");
115+
checkCuVSVersionMatching("25.12.0", 26, 12, 0);
116+
} catch (ProviderInitializationException e) {
117+
throw new AssertionError(e);
118+
}
119+
}
120+
121+
static void checkCuVSVersionMatching(String mavenVersionString, int major, int minor, int patch)
122+
throws ProviderInitializationException {
123+
try {
124+
var cls = Class.forName("com.nvidia.cuvs.spi.JDKProvider");
125+
var ctr =
126+
MethodHandles.lookup()
127+
.findStatic(
128+
cls,
129+
"checkCuVSVersionMatching",
130+
MethodType.methodType(
131+
void.class, String.class, short.class, short.class, short.class));
132+
ctr.invoke(mavenVersionString, (short) major, (short) minor, (short) patch);
133+
} catch (ProviderInitializationException e) {
134+
throw e;
135+
} catch (Throwable e) {
136+
throw new AssertionError(e);
137+
}
138+
}
139+
}

0 commit comments

Comments
 (0)