Skip to content

Commit b63388a

Browse files
committed
Merge branch '1.13.x'
2 parents 1c886f8 + c61b1c3 commit b63388a

File tree

2 files changed

+51
-2
lines changed

2 files changed

+51
-2
lines changed

concurrency-tests/src/jcstress/java/io/micrometer/concurrencytests/MeterRegistryConcurrencyTest.java

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,13 @@
1515
*/
1616
package io.micrometer.concurrencytests;
1717

18+
import io.micrometer.core.Issue;
1819
import io.micrometer.core.instrument.Counter;
1920
import io.micrometer.core.instrument.MeterRegistry;
2021
import io.micrometer.core.instrument.simple.SimpleMeterRegistry;
2122
import org.openjdk.jcstress.annotations.*;
2223
import org.openjdk.jcstress.infra.results.LL_Result;
24+
import org.openjdk.jcstress.infra.results.L_Result;
2325
import org.openjdk.jcstress.infra.results.Z_Result;
2426

2527
public class MeterRegistryConcurrencyTest {
@@ -145,4 +147,45 @@ public void arbiter(LL_Result r) {
145147

146148
}
147149

150+
/*
151+
* Verify the fix for a ConcurrentModificationException that was being thrown in the
152+
* tested scenario. The late registration of a MeterFilter causes iteration of the
153+
* KeySet of the preFilterIdToMeterMap to add them to the stalePreFilterIds set. This
154+
* iteration could happen at the same time a new meter is being registered, thus added
155+
* to the preFilterIdToMeterMap, modifying it while iterating over its KeySet.
156+
*/
157+
@Issue("gh-5489")
158+
@JCStressTest
159+
@Outcome(id = "OK", expect = Expect.ACCEPTABLE, desc = "No exception")
160+
@Outcome(expect = Expect.FORBIDDEN, desc = "Exception thrown")
161+
@State
162+
public static class ConfigureLateMeterFilterWithNewMeterRegister {
163+
164+
MeterRegistry registry = new SimpleMeterRegistry();
165+
166+
public ConfigureLateMeterFilterWithNewMeterRegister() {
167+
// registry not empty so the MeterFilter is treated as late configuration
168+
registry.counter("c1");
169+
}
170+
171+
@Actor
172+
public void actor1() {
173+
// adds a new meter to preMap, a concurrent modification if not guarded
174+
registry.counter("c2");
175+
}
176+
177+
@Actor
178+
public void actor2(L_Result r) {
179+
try {
180+
// iterates over preMap keys to add all to the staleIds
181+
registry.config().commonTags("common", "tag");
182+
r.r1 = "OK";
183+
}
184+
catch (Throwable e) {
185+
r.r1 = e.getClass().getSimpleName();
186+
}
187+
}
188+
189+
}
190+
148191
}

micrometer-core/src/main/java/io/micrometer/core/instrument/MeterRegistry.java

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,11 @@ public abstract class MeterRegistry {
108108
*/
109109
private final Map<Id, Meter> preFilterIdToMeterMap = new HashMap<>();
110110

111-
// not thread safe; only needed when MeterFilter configured after Meters registered
111+
/**
112+
* Only needed when MeterFilter configured after Meters registered. Write/remove
113+
* guarded by meterMapLock, remove in {@link #unmarkStaleId(Id)} and other operations
114+
* unguarded
115+
*/
112116
private final Set<Id> stalePreFilterIds = new HashSet<>();
113117

114118
/**
@@ -829,7 +833,9 @@ public Config commonTags(String... tags) {
829833
public synchronized Config meterFilter(MeterFilter filter) {
830834
if (!meterMap.isEmpty()) {
831835
logWarningAboutLateFilter();
832-
stalePreFilterIds.addAll(preFilterIdToMeterMap.keySet());
836+
synchronized (meterMapLock) {
837+
stalePreFilterIds.addAll(preFilterIdToMeterMap.keySet());
838+
}
833839
}
834840
MeterFilter[] newFilters = new MeterFilter[filters.length + 1];
835841
System.arraycopy(filters, 0, newFilters, 0, filters.length);

0 commit comments

Comments
 (0)