Skip to content

Commit 4ac8274

Browse files
committed
fix: address review feedback on exposition name claiming and test names
- Fix race condition: use putIfAbsent for atomic exposition name claiming with proper rollback on failure - Extract claimExpositionNames/releaseExpositionNames for single rollback path - Rename Info test methods to reflect that _info suffix is now allowed Signed-off-by: Gregor Zeitlinger <gregor.zeitlinger@grafana.com>
1 parent 0787661 commit 4ac8274

2 files changed

Lines changed: 68 additions & 45 deletions

File tree

prometheus-metrics-model/src/main/java/io/prometheus/metrics/model/registry/PrometheusRegistry.java

Lines changed: 66 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -208,55 +208,78 @@ private void validateRegistration(
208208
final String helpForValidation = help;
209209
final Unit unitForValidation = unit;
210210

211-
// Check exposition name collisions before modifying any state.
212211
Set<String> expositionNames = computeExpositionNames(prometheusName, type);
213-
for (String expositionName : expositionNames) {
214-
String owner = expositionNameOwners.get(expositionName);
215-
if (owner != null && !owner.equals(prometheusName)) {
216-
throw new IllegalArgumentException(
217-
"'"
218-
+ prometheusName
219-
+ "' and '"
220-
+ owner
221-
+ "' have conflicting exposition name: '"
222-
+ expositionName
223-
+ "'");
224-
}
212+
String claimError = claimExpositionNames(expositionNames, prometheusName);
213+
if (claimError != null) {
214+
throw new IllegalArgumentException(claimError);
225215
}
226216

227-
registered.compute(
228-
prometheusName,
229-
(n, existingInfo) -> {
230-
if (existingInfo == null) {
231-
return RegistrationInfo.of(type, names, helpForValidation, unitForValidation);
232-
} else {
233-
if (existingInfo.getType() != type) {
234-
throw new IllegalArgumentException(
235-
prometheusName
236-
+ ": Conflicting metric types. Existing: "
237-
+ existingInfo.getType()
238-
+ ", new: "
239-
+ type);
240-
}
241-
// Check label set first; only mutate help/unit after validation passes.
242-
if (!existingInfo.addLabelSet(names)) {
243-
throw new IllegalArgumentException(
244-
prometheusName + ": duplicate metric name with identical label schema " + names);
245-
}
246-
// Roll back label schema if metadata validation fails
247-
try {
248-
existingInfo.validateMetadata(helpForValidation, unitForValidation);
249-
} catch (IllegalArgumentException e) {
250-
existingInfo.removeLabelSet(names);
251-
throw e;
217+
boolean success = false;
218+
try {
219+
registered.compute(
220+
prometheusName,
221+
(n, existingInfo) -> {
222+
if (existingInfo == null) {
223+
return RegistrationInfo.of(type, names, helpForValidation, unitForValidation);
224+
} else {
225+
if (existingInfo.getType() != type) {
226+
throw new IllegalArgumentException(
227+
prometheusName
228+
+ ": Conflicting metric types. Existing: "
229+
+ existingInfo.getType()
230+
+ ", new: "
231+
+ type);
232+
}
233+
// Check label set first; only mutate help/unit after validation passes.
234+
if (!existingInfo.addLabelSet(names)) {
235+
throw new IllegalArgumentException(
236+
prometheusName
237+
+ ": duplicate metric name with identical label schema "
238+
+ names);
239+
}
240+
// Roll back label schema if metadata validation fails
241+
try {
242+
existingInfo.validateMetadata(helpForValidation, unitForValidation);
243+
} catch (IllegalArgumentException e) {
244+
existingInfo.removeLabelSet(names);
245+
throw e;
246+
}
247+
return existingInfo;
252248
}
253-
return existingInfo;
254-
}
255-
});
249+
});
250+
success = true;
251+
} finally {
252+
if (!success) {
253+
releaseExpositionNames(expositionNames, prometheusName);
254+
}
255+
}
256+
}
257+
258+
/**
259+
* Atomically claims exposition names for the given metric. Returns null on success, or an error
260+
* message on conflict. Rolls back any partially claimed names on failure.
261+
*/
262+
@Nullable
263+
private String claimExpositionNames(Set<String> expositionNames, String prometheusName) {
264+
for (String expositionName : expositionNames) {
265+
String existingOwner = expositionNameOwners.putIfAbsent(expositionName, prometheusName);
266+
if (existingOwner != null && !existingOwner.equals(prometheusName)) {
267+
releaseExpositionNames(expositionNames, prometheusName);
268+
return "'"
269+
+ prometheusName
270+
+ "' and '"
271+
+ existingOwner
272+
+ "' have conflicting exposition name: '"
273+
+ expositionName
274+
+ "'";
275+
}
276+
}
277+
return null;
278+
}
256279

257-
// Registration succeeded — claim exposition names.
280+
private void releaseExpositionNames(Set<String> expositionNames, String owner) {
258281
for (String expositionName : expositionNames) {
259-
expositionNameOwners.put(expositionName, prometheusName);
282+
expositionNameOwners.remove(expositionName, owner);
260283
}
261284
}
262285

prometheus-metrics-model/src/test/java/io/prometheus/metrics/model/snapshots/InfoSnapshotTest.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,13 +60,13 @@ void testDataImmutable() {
6060
}
6161

6262
@Test
63-
void testNameMustNotIncludeSuffix() {
63+
void testNameMayIncludeSuffix() {
6464
InfoSnapshot snapshot = InfoSnapshot.builder().name("jvm_info").build();
6565
assertThat(snapshot.getMetadata().getPrometheusName()).isEqualTo("jvm_info");
6666
}
6767

6868
@Test
69-
void testNameMustNotIncludeSuffixDot() {
69+
void testNameMayIncludeSuffixDot() {
7070
InfoSnapshot snapshot = InfoSnapshot.builder().name("jvm.info").build();
7171
assertThat(snapshot.getMetadata().getPrometheusName()).isEqualTo("jvm_info");
7272
}

0 commit comments

Comments
 (0)