Skip to content

Commit 5979b09

Browse files
committed
Use the new Vertx local context storage
1 parent 5d72d9b commit 5979b09

File tree

7 files changed

+177
-47
lines changed

7 files changed

+177
-47
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package io.quarkus.vertx.core.runtime;
2+
3+
import java.util.concurrent.atomic.AtomicReferenceArray;
4+
import java.util.function.Supplier;
5+
6+
import io.vertx.core.Context;
7+
import io.vertx.core.spi.context.storage.AccessMode;
8+
9+
final class QuarkusAccessModes {
10+
11+
/**
12+
* Beware this {@link AccessMode#getOrCreate(AtomicReferenceArray, int, Supplier)} because, differently from
13+
* {@link io.vertx.core.spi.context.storage.ContextLocal#get(Context, Supplier)},
14+
* is not suitable to be used with {@link io.vertx.core.spi.context.storage.ContextLocal#get(Context, AccessMode, Supplier)}
15+
* with the same guarantees of atomicity i.e. the supplier can get called more than once by different racing threads!
16+
*/
17+
public static final AccessMode ACQUIRE_RELEASE = new AccessMode() {
18+
@Override
19+
public Object get(AtomicReferenceArray<Object> locals, int idx) {
20+
return locals.getAcquire(idx);
21+
}
22+
23+
@Override
24+
public void put(AtomicReferenceArray<Object> locals, int idx, Object value) {
25+
// This is still ensuring visibility across threads and happens-before,
26+
// but won't impose setVolatile total ordering i.e. StoreLoad barriers after write
27+
// to make it faster
28+
locals.setRelease(idx, value);
29+
}
30+
31+
@Override
32+
public Object getOrCreate(AtomicReferenceArray<Object> locals, int idx, Supplier<Object> initialValueSupplier) {
33+
Object value = locals.getAcquire(idx);
34+
if (value == null) {
35+
value = initialValueSupplier.get();
36+
locals.setRelease(idx, value);
37+
}
38+
return value;
39+
}
40+
};
41+
42+
}

extensions/vertx/runtime/src/main/java/io/quarkus/vertx/core/runtime/VertxCoreRecorder.java

+1-29
Original file line numberDiff line numberDiff line change
@@ -13,12 +13,10 @@
1313
import java.util.HashSet;
1414
import java.util.List;
1515
import java.util.Locale;
16-
import java.util.Map;
1716
import java.util.Optional;
1817
import java.util.Set;
1918
import java.util.UUID;
2019
import java.util.concurrent.CompletableFuture;
21-
import java.util.concurrent.ConcurrentMap;
2220
import java.util.concurrent.CountDownLatch;
2321
import java.util.concurrent.ExecutorService;
2422
import java.util.concurrent.ThreadFactory;
@@ -46,7 +44,6 @@
4644
import io.quarkus.vertx.core.runtime.config.ClusterConfiguration;
4745
import io.quarkus.vertx.core.runtime.config.EventBusConfiguration;
4846
import io.quarkus.vertx.core.runtime.config.VertxConfiguration;
49-
import io.quarkus.vertx.core.runtime.context.VertxContextSafetyToggle;
5047
import io.quarkus.vertx.mdc.provider.LateBoundMDCProvider;
5148
import io.quarkus.vertx.runtime.VertxCurrentContextFactory;
5249
import io.vertx.core.AsyncResult;
@@ -584,15 +581,7 @@ public void runWith(Runnable task, Object context) {
584581
// The CDI contexts must not be propagated
585582
// First test if VertxCurrentContextFactory is actually used
586583
if (currentContextFactory != null) {
587-
List<String> keys = currentContextFactory.keys();
588-
ConcurrentMap<Object, Object> local = vertxContext.localContextData();
589-
if (containsScopeKey(keys, local)) {
590-
// Duplicate the context, copy the data, remove the request context
591-
vertxContext = vertxContext.duplicate();
592-
vertxContext.localContextData().putAll(local);
593-
keys.forEach(vertxContext.localContextData()::remove);
594-
VertxContextSafetyToggle.setContextSafe(vertxContext, true);
595-
}
584+
vertxContext = currentContextFactory.duplicateContextIfContainsAnyCreatedScopeKeys(vertxContext);
596585
}
597586
vertxContext.beginDispatch();
598587
try {
@@ -604,23 +593,6 @@ public void runWith(Runnable task, Object context) {
604593
task.run();
605594
}
606595
}
607-
608-
private boolean containsScopeKey(List<String> keys, Map<Object, Object> localContextData) {
609-
if (keys.isEmpty()) {
610-
return false;
611-
}
612-
if (keys.size() == 1) {
613-
// Very often there will be only one key used
614-
return localContextData.containsKey(keys.get(0));
615-
} else {
616-
for (String key : keys) {
617-
if (localContextData.containsKey(key)) {
618-
return true;
619-
}
620-
}
621-
}
622-
return false;
623-
}
624596
};
625597
}
626598

extensions/vertx/runtime/src/main/java/io/quarkus/vertx/core/runtime/VertxLocalsHelper.java

+23-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
import io.smallrye.common.vertx.VertxContext;
44
import io.vertx.core.impl.ContextInternal;
5+
import io.vertx.core.impl.ContextLocalImpl;
6+
import io.vertx.core.spi.context.storage.ContextLocal;
57

68
public class VertxLocalsHelper {
79

@@ -22,6 +24,11 @@ public static void throwOnRootContextAccess() {
2224
public static <T> T getLocal(ContextInternal context, Object key) {
2325
if (VertxContext.isDuplicatedContext(context)) {
2426
// We are on a duplicated context, allow accessing the locals
27+
if (key instanceof ContextLocalImpl<?>) {
28+
var localKey = (ContextLocal<T>) key;
29+
return (T) context.getLocal(localKey, QuarkusAccessModes.ACQUIRE_RELEASE);
30+
}
31+
assert !(key instanceof ContextLocal<?>);
2532
return (T) context.localContextData().get(key);
2633
} else {
2734
throw new UnsupportedOperationException(ILLEGAL_ACCESS_TO_LOCAL_CONTEXT);
@@ -31,7 +38,13 @@ public static <T> T getLocal(ContextInternal context, Object key) {
3138
public static void putLocal(ContextInternal context, Object key, Object value) {
3239
if (VertxContext.isDuplicatedContext(context)) {
3340
// We are on a duplicated context, allow accessing the locals
34-
context.localContextData().put(key, value);
41+
if (key instanceof ContextLocalImpl<?>) {
42+
var localKey = (ContextLocal<Object>) key;
43+
context.putLocal(localKey, QuarkusAccessModes.ACQUIRE_RELEASE, value);
44+
} else {
45+
assert !(key instanceof ContextLocal<?>);
46+
context.localContextData().put(key, value);
47+
}
3548
} else {
3649
throw new UnsupportedOperationException(ILLEGAL_ACCESS_TO_LOCAL_CONTEXT);
3750
}
@@ -40,6 +53,15 @@ public static void putLocal(ContextInternal context, Object key, Object value) {
4053
public static boolean removeLocal(ContextInternal context, Object key) {
4154
if (VertxContext.isDuplicatedContext(context)) {
4255
// We are on a duplicated context, allow accessing the locals
56+
if (key instanceof ContextLocalImpl<?>) {
57+
var localKey = (ContextLocal<Object>) key;
58+
if (localKey == null) {
59+
return false;
60+
}
61+
context.removeLocal(localKey, QuarkusAccessModes.ACQUIRE_RELEASE);
62+
return true;
63+
}
64+
assert !(key instanceof ContextLocal<?>);
4365
return context.localContextData().remove(key) != null;
4466
} else {
4567
throw new UnsupportedOperationException(ILLEGAL_ACCESS_TO_LOCAL_CONTEXT);

extensions/vertx/runtime/src/main/java/io/quarkus/vertx/core/runtime/context/VertxContextSafetyToggle.java

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
package io.quarkus.vertx.core.runtime.context;
22

3+
import static io.quarkus.vertx.runtime.storage.QuarkusLocalStorageKeyVertxServiceProvider.ACCESS_TOGGLE_KEY;
4+
35
import io.smallrye.common.vertx.VertxContext;
46
import io.vertx.core.Context;
57
import io.vertx.core.Vertx;
8+
import io.vertx.core.spi.context.storage.ContextLocal;
69

710
/**
811
* This is meant for other extensions to integrate with, to help
@@ -39,7 +42,6 @@
3942
*/
4043
public final class VertxContextSafetyToggle {
4144

42-
private static final Object ACCESS_TOGGLE_KEY = new Object();
4345
public static final String UNRESTRICTED_BY_DEFAULT_PROPERTY = "io.quarkus.vertx.core.runtime.context.VertxContextSafetyToggle.UNRESTRICTED_BY_DEFAULT";
4446

4547
/**
@@ -50,6 +52,12 @@ public final class VertxContextSafetyToggle {
5052
private static final boolean UNRESTRICTED_BY_DEFAULT = Boolean.getBoolean(UNRESTRICTED_BY_DEFAULT_PROPERTY);
5153
private static final boolean FULLY_DISABLED = Boolean.getBoolean(FULLY_DISABLE_PROPERTY);
5254

55+
public static ContextLocal<Boolean> registerAccessToggleKey() {
56+
if (FULLY_DISABLED)
57+
return null;
58+
return ContextLocal.registerLocal(Boolean.class);
59+
}
60+
5361
/**
5462
* Verifies if the current Vert.x context was flagged as safe
5563
* to be accessed by components which expect non-concurrent

extensions/vertx/runtime/src/main/java/io/quarkus/vertx/runtime/VertxCurrentContextFactory.java

+78-16
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
package io.quarkus.vertx.runtime;
22

3+
import static io.quarkus.vertx.runtime.storage.QuarkusLocalStorageKeyVertxServiceProvider.REQUEST_SCOPED_LOCAL_KEY;
4+
35
import java.lang.annotation.Annotation;
4-
import java.util.Collections;
56
import java.util.List;
7+
import java.util.concurrent.ConcurrentMap;
68
import java.util.concurrent.CopyOnWriteArrayList;
9+
import java.util.concurrent.atomic.AtomicBoolean;
10+
11+
import jakarta.enterprise.context.RequestScoped;
712

813
import io.netty.util.concurrent.FastThreadLocal;
914
import io.quarkus.arc.CurrentContext;
@@ -14,23 +19,31 @@
1419
import io.smallrye.common.vertx.VertxContext;
1520
import io.vertx.core.Context;
1621
import io.vertx.core.Vertx;
22+
import io.vertx.core.impl.ContextInternal;
1723

1824
public class VertxCurrentContextFactory implements CurrentContextFactory {
1925

2026
private static final String LOCAL_KEY_PREFIX = "io.quarkus.vertx.cdi-current-context";
2127

2228
private final List<String> keys;
23-
private final List<String> unmodifiableKeys;
29+
private final AtomicBoolean requestScopedKeyCreated;
2430

2531
public VertxCurrentContextFactory() {
2632
// There will be only a few mutative operations max
2733
this.keys = new CopyOnWriteArrayList<>();
28-
// We do not want to allocate a new object for each VertxCurrentContextFactory#keys() invocation
29-
this.unmodifiableKeys = Collections.unmodifiableList(keys);
34+
this.requestScopedKeyCreated = new AtomicBoolean();
3035
}
3136

3237
@Override
3338
public <T extends InjectableContext.ContextState> CurrentContext<T> create(Class<? extends Annotation> scope) {
39+
if (scope == RequestScoped.class) {
40+
if (!requestScopedKeyCreated.compareAndSet(false, true)) {
41+
throw new IllegalStateException(
42+
"Multiple current contexts for the same scope are not supported. Current context for "
43+
+ scope + " already exists!");
44+
}
45+
return new VertxCurrentContext<T>(REQUEST_SCOPED_LOCAL_KEY);
46+
}
3447
String key = LOCAL_KEY_PREFIX + scope.getName();
3548
if (keys.contains(key)) {
3649
throw new IllegalStateException(
@@ -41,20 +54,52 @@ public <T extends InjectableContext.ContextState> CurrentContext<T> create(Class
4154
return new VertxCurrentContext<>(key);
4255
}
4356

44-
/**
45-
*
46-
* @return an unmodifiable list of used keys
47-
*/
48-
public List<String> keys() {
49-
return unmodifiableKeys;
57+
public ContextInternal duplicateContextIfContainsAnyCreatedScopeKeys(ContextInternal vertxContext) {
58+
if (!containsAnyCreatedScopeKeys(vertxContext)) {
59+
return vertxContext;
60+
}
61+
// Duplicate the context, copy the data, remove the request context
62+
var duplicateCtx = vertxContext.duplicate();
63+
// TODO this is not copying any ContextLocal<?> from the original context to the new one!
64+
var duplicateCtxData = duplicateCtx.localContextData();
65+
duplicateCtxData.putAll(vertxContext.localContextData());
66+
keys.forEach(duplicateCtxData::remove);
67+
if (requestScopedKeyCreated.get()) {
68+
duplicateCtx.removeLocal(REQUEST_SCOPED_LOCAL_KEY);
69+
}
70+
VertxContextSafetyToggle.setContextSafe(duplicateCtx, true);
71+
return duplicateCtx;
72+
}
73+
74+
private boolean containsAnyCreatedScopeKeys(ContextInternal vertxContext) {
75+
boolean requestScopedKeyCreated = this.requestScopedKeyCreated.get();
76+
if (requestScopedKeyCreated && vertxContext.getLocal(REQUEST_SCOPED_LOCAL_KEY) != null) {
77+
return true;
78+
}
79+
if (keys.isEmpty()) {
80+
return false;
81+
}
82+
ConcurrentMap<Object, Object> local = vertxContext.localContextData();
83+
if (keys.size() == 1) {
84+
// Very often there will be only one key used
85+
return local.containsKey(keys.get(0));
86+
} else {
87+
for (String key : keys) {
88+
if (local.containsKey(key)) {
89+
return true;
90+
}
91+
}
92+
}
93+
return false;
5094
}
5195

5296
private static final class VertxCurrentContext<T extends ContextState> implements CurrentContext<T> {
5397

54-
private final String key;
55-
private final FastThreadLocal<T> fallback = new FastThreadLocal<>();
98+
// It allows to use both ContextLocalImpl and String keys
99+
private final Object key;
100+
private volatile FastThreadLocal<T> fallback;
56101

57-
private VertxCurrentContext(String key) {
102+
private VertxCurrentContext(Object key) {
58103
this.key = key;
59104
}
60105

@@ -64,7 +109,24 @@ public T get() {
64109
if (context != null && VertxContext.isDuplicatedContext(context)) {
65110
return context.getLocal(key);
66111
}
67-
return fallback.get();
112+
return fallback().get();
113+
}
114+
115+
private FastThreadLocal<T> fallback() {
116+
var fallback = this.fallback;
117+
if (fallback == null) {
118+
fallback = getOrCreateFallback();
119+
}
120+
return fallback;
121+
}
122+
123+
private synchronized FastThreadLocal<T> getOrCreateFallback() {
124+
var fallback = this.fallback;
125+
if (fallback == null) {
126+
fallback = new FastThreadLocal<>();
127+
this.fallback = fallback;
128+
}
129+
return fallback;
68130
}
69131

70132
@Override
@@ -80,7 +142,7 @@ public void set(T state) {
80142
}
81143

82144
} else {
83-
fallback.set(state);
145+
fallback().set(state);
84146
}
85147
}
86148

@@ -91,7 +153,7 @@ public void remove() {
91153
// NOOP - the DC should not be shared.
92154
// context.removeLocal(key);
93155
} else {
94-
fallback.remove();
156+
fallback().remove();
95157
}
96158
}
97159

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package io.quarkus.vertx.runtime.storage;
2+
3+
import io.quarkus.arc.InjectableContext;
4+
import io.quarkus.vertx.core.runtime.context.VertxContextSafetyToggle;
5+
import io.vertx.core.impl.VertxBuilder;
6+
import io.vertx.core.spi.VertxServiceProvider;
7+
import io.vertx.core.spi.context.storage.ContextLocal;
8+
9+
/**
10+
* This provider exists with the sole purpose of reliably get the optimized local keys created before
11+
* the Vertx instance is created.
12+
*/
13+
public class QuarkusLocalStorageKeyVertxServiceProvider implements VertxServiceProvider {
14+
15+
public static final ContextLocal<Boolean> ACCESS_TOGGLE_KEY = VertxContextSafetyToggle.registerAccessToggleKey();
16+
public static final ContextLocal<InjectableContext.ContextState> REQUEST_SCOPED_LOCAL_KEY = ContextLocal
17+
.registerLocal(InjectableContext.ContextState.class);
18+
19+
@Override
20+
public void init(VertxBuilder builder) {
21+
22+
}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
io.quarkus.vertx.runtime.storage.QuarkusLocalStorageKeyVertxServiceProvider

0 commit comments

Comments
 (0)