Skip to content

Commit c06e604

Browse files
committed
Address Observability Thread Safety
Closes gh-12829
1 parent acf4872 commit c06e604

File tree

2 files changed

+107
-58
lines changed

2 files changed

+107
-58
lines changed

web/src/main/java/org/springframework/security/web/ObservationFilterChainDecorator.java

Lines changed: 56 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,8 @@
1818

1919
import java.io.IOException;
2020
import java.util.ArrayList;
21-
import java.util.Arrays;
22-
import java.util.Iterator;
2321
import java.util.List;
22+
import java.util.concurrent.atomic.AtomicInteger;
2423
import java.util.concurrent.atomic.AtomicReference;
2524

2625
import io.micrometer.common.KeyValues;
@@ -227,49 +226,38 @@ default Observation after() {
227226

228227
class SimpleAroundFilterObservation implements AroundFilterObservation {
229228

230-
private final Iterator<Observation> observations;
229+
private final ObservationReference before;
231230

232-
private final Observation before;
231+
private final ObservationReference after;
233232

234-
private final Observation after;
235-
236-
private final AtomicReference<Observation.Scope> currentScope = new AtomicReference<>(null);
233+
private final AtomicReference<ObservationReference> reference = new AtomicReference<>(
234+
ObservationReference.NOOP);
237235

238236
SimpleAroundFilterObservation(Observation before, Observation after) {
239-
this.before = before;
240-
this.after = after;
241-
this.observations = Arrays.asList(before, after).iterator();
237+
this.before = new ObservationReference(before);
238+
this.after = new ObservationReference(after);
242239
}
243240

244241
@Override
245242
public void start() {
246-
if (this.observations.hasNext()) {
247-
stop();
248-
Observation observation = this.observations.next();
249-
observation.start();
250-
Observation.Scope scope = observation.openScope();
251-
this.currentScope.set(scope);
243+
if (this.reference.compareAndSet(ObservationReference.NOOP, this.before)) {
244+
this.before.start();
245+
return;
246+
}
247+
if (this.reference.compareAndSet(this.before, this.after)) {
248+
this.before.stop();
249+
this.after.start();
252250
}
253251
}
254252

255253
@Override
256254
public void error(Throwable ex) {
257-
Observation.Scope scope = this.currentScope.get();
258-
if (scope == null) {
259-
return;
260-
}
261-
scope.close();
262-
scope.getCurrentObservation().error(ex);
255+
this.reference.get().error(ex);
263256
}
264257

265258
@Override
266259
public void stop() {
267-
Observation.Scope scope = this.currentScope.getAndSet(null);
268-
if (scope == null) {
269-
return;
270-
}
271-
scope.close();
272-
scope.getCurrentObservation().stop();
260+
this.reference.get().stop();
273261
}
274262

275263
@Override
@@ -304,12 +292,50 @@ public FilterChain wrap(FilterChain chain) {
304292

305293
@Override
306294
public Observation before() {
307-
return this.before;
295+
return this.before.observation;
308296
}
309297

310298
@Override
311299
public Observation after() {
312-
return this.after;
300+
return this.after.observation;
301+
}
302+
303+
private static final class ObservationReference {
304+
305+
private static final ObservationReference NOOP = new ObservationReference(Observation.NOOP);
306+
307+
private final AtomicInteger state = new AtomicInteger(0);
308+
309+
private final Observation observation;
310+
311+
private volatile Observation.Scope scope;
312+
313+
private ObservationReference(Observation observation) {
314+
this.observation = observation;
315+
this.scope = Observation.Scope.NOOP;
316+
}
317+
318+
private void start() {
319+
if (this.state.compareAndSet(0, 1)) {
320+
this.observation.start();
321+
this.scope = this.observation.openScope();
322+
}
323+
}
324+
325+
private void error(Throwable error) {
326+
if (this.state.get() == 1) {
327+
this.scope.close();
328+
this.scope.getCurrentObservation().error(error);
329+
}
330+
}
331+
332+
private void stop() {
333+
if (this.state.compareAndSet(1, 2)) {
334+
this.scope.close();
335+
this.scope.getCurrentObservation().stop();
336+
}
337+
}
338+
313339
}
314340

315341
}

web/src/main/java/org/springframework/security/web/server/ObservationWebFilterChainDecorator.java

Lines changed: 51 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -17,10 +17,9 @@
1717
package org.springframework.security.web.server;
1818

1919
import java.util.ArrayList;
20-
import java.util.Arrays;
21-
import java.util.Iterator;
2220
import java.util.List;
2321
import java.util.ListIterator;
22+
import java.util.concurrent.atomic.AtomicInteger;
2423
import java.util.concurrent.atomic.AtomicReference;
2524

2625
import io.micrometer.common.KeyValues;
@@ -253,46 +252,38 @@ default Observation after() {
253252

254253
class SimpleAroundWebFilterObservation implements AroundWebFilterObservation {
255254

256-
private final Iterator<Observation> observations;
255+
private final ObservationReference before;
257256

258-
private final Observation before;
257+
private final ObservationReference after;
259258

260-
private final Observation after;
261-
262-
private final AtomicReference<Observation> currentObservation = new AtomicReference<>(null);
259+
private final AtomicReference<ObservationReference> currentObservation = new AtomicReference<>(
260+
ObservationReference.NOOP);
263261

264262
SimpleAroundWebFilterObservation(Observation before, Observation after) {
265-
this.before = before;
266-
this.after = after;
267-
this.observations = Arrays.asList(before, after).iterator();
263+
this.before = new ObservationReference(before);
264+
this.after = new ObservationReference(after);
268265
}
269266

270267
@Override
271268
public void start() {
272-
if (this.observations.hasNext()) {
273-
stop();
274-
Observation observation = this.observations.next();
275-
observation.start();
276-
this.currentObservation.set(observation);
269+
if (this.currentObservation.compareAndSet(ObservationReference.NOOP, this.before)) {
270+
this.before.start();
271+
return;
272+
}
273+
if (this.currentObservation.compareAndSet(this.before, this.after)) {
274+
this.before.stop();
275+
this.after.start();
277276
}
278277
}
279278

280279
@Override
281280
public void error(Throwable ex) {
282-
Observation observation = this.currentObservation.get();
283-
if (observation == null) {
284-
return;
285-
}
286-
observation.error(ex);
281+
this.currentObservation.get().error(ex);
287282
}
288283

289284
@Override
290285
public void stop() {
291-
Observation observation = this.currentObservation.getAndSet(null);
292-
if (observation == null) {
293-
return;
294-
}
295-
observation.stop();
286+
this.currentObservation.get().stop();
296287
}
297288

298289
@Override
@@ -329,12 +320,44 @@ public WebFilter wrap(WebFilter filter) {
329320

330321
@Override
331322
public Observation before() {
332-
return this.before;
323+
return this.before.observation;
333324
}
334325

335326
@Override
336327
public Observation after() {
337-
return this.after;
328+
return this.after.observation;
329+
}
330+
331+
private static final class ObservationReference {
332+
333+
private static final ObservationReference NOOP = new ObservationReference(Observation.NOOP);
334+
335+
private final AtomicInteger state = new AtomicInteger(0);
336+
337+
private final Observation observation;
338+
339+
private ObservationReference(Observation observation) {
340+
this.observation = observation;
341+
}
342+
343+
private void start() {
344+
if (this.state.compareAndSet(0, 1)) {
345+
this.observation.start();
346+
}
347+
}
348+
349+
private void error(Throwable ex) {
350+
if (this.state.get() == 1) {
351+
this.observation.error(ex);
352+
}
353+
}
354+
355+
private void stop() {
356+
if (this.state.compareAndSet(1, 2)) {
357+
this.observation.stop();
358+
}
359+
}
360+
338361
}
339362

340363
}

0 commit comments

Comments
 (0)