Skip to content

Commit a7f09af

Browse files
authored
Fix Conflict for NonBlocking Interface in Reactor Integration (#6037)
Motivation: The `reactor.core.scheduler.NonBlocking` interface was introduced to `armeria-core` in #1665 to make `Schedulers.isInNonBlockingThread()` return `true` for an Armeria `EventLoop`. However, a conflict arises when building Java modules because the `NonBlocking` interface clashes with Reactor's own definition. Modifications: - Moved `NonBlocking` to `com.linecorp.armeria.common` to resolve the module conflict while retaining its utility for identifying non-blocking threads. - Updated to call `Schedulers.registerNonBlockingThreadPredicate` if Reactor is available in the classpath. - Adjusted `CoreBlockHoundIntegration` to update the non-blocking thread predicate. Result: - Resolved the Java module conflict with the `NonBlocking` interface.
1 parent d3ecb50 commit a7f09af

File tree

11 files changed

+78
-18
lines changed

11 files changed

+78
-18
lines changed

core/build.gradle

+3-2
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,9 @@ dependencies {
139139
// JUnit Pioneer
140140
testImplementation libs.junit.pioneer
141141

142+
// Reactor for registering EventLoop as a non blocking thread.
143+
optionalImplementation libs.reactor.core
144+
142145
// Reactive Streams
143146
api libs.reactivestreams
144147
testImplementation libs.reactivestreams.tck
@@ -217,8 +220,6 @@ if (tasks.findByName('trimShadedJar')) {
217220
tasks.trimShadedJar.configure {
218221
// Keep all classes under com.linecorp.armeria, except the internal ones.
219222
keep "class !com.linecorp.armeria.internal.shaded.**,com.linecorp.armeria.** { *; }"
220-
// Keep the 'NonBlocking' tag interface.
221-
keep "class reactor.core.scheduler.NonBlocking { *; }"
222223
// Do not optimize the dependencies that access some fields via sun.misc.Unsafe or reflection only.
223224
keep "class com.linecorp.armeria.internal.shaded.caffeine.** { *; }"
224225
keep "class com.linecorp.armeria.internal.shaded.jctools.** { *; }"

core/src/main/java/com/linecorp/armeria/client/ClientFactory.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636

3737
import com.linecorp.armeria.client.endpoint.EndpointGroup;
3838
import com.linecorp.armeria.common.FlagsProvider;
39+
import com.linecorp.armeria.common.NonBlocking;
3940
import com.linecorp.armeria.common.Scheme;
4041
import com.linecorp.armeria.common.SerializationFormat;
4142
import com.linecorp.armeria.common.SessionProtocol;
@@ -49,7 +50,6 @@
4950
import io.micrometer.core.instrument.MeterRegistry;
5051
import io.netty.channel.EventLoop;
5152
import io.netty.channel.EventLoopGroup;
52-
import reactor.core.scheduler.NonBlocking;
5353

5454
/**
5555
* Creates and manages clients.

core/src/main/java/com/linecorp/armeria/client/HttpChannelPool.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,7 @@
4747
import com.linecorp.armeria.client.proxy.Socks4ProxyConfig;
4848
import com.linecorp.armeria.client.proxy.Socks5ProxyConfig;
4949
import com.linecorp.armeria.common.ClosedSessionException;
50+
import com.linecorp.armeria.common.NonBlocking;
5051
import com.linecorp.armeria.common.SerializationFormat;
5152
import com.linecorp.armeria.common.SessionProtocol;
5253
import com.linecorp.armeria.common.annotation.Nullable;
@@ -76,7 +77,6 @@
7677
import io.netty.util.AttributeKey;
7778
import io.netty.util.concurrent.Future;
7879
import io.netty.util.concurrent.Promise;
79-
import reactor.core.scheduler.NonBlocking;
8080

8181
final class HttpChannelPool implements AsyncCloseable {
8282

core/src/main/java/com/linecorp/armeria/client/HttpClientFactory.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import com.linecorp.armeria.client.proxy.ProxyConfigSelector;
4343
import com.linecorp.armeria.client.redirect.RedirectConfig;
4444
import com.linecorp.armeria.common.Http1HeaderNaming;
45+
import com.linecorp.armeria.common.NonBlocking;
4546
import com.linecorp.armeria.common.RequestContext;
4647
import com.linecorp.armeria.common.Scheme;
4748
import com.linecorp.armeria.common.SerializationFormat;
@@ -71,7 +72,6 @@
7172
import io.netty.handler.ssl.SslContextBuilder;
7273
import io.netty.resolver.AddressResolverGroup;
7374
import io.netty.util.concurrent.FutureListener;
74-
import reactor.core.scheduler.NonBlocking;
7575

7676
/**
7777
* A {@link ClientFactory} that creates an HTTP client.

core/src/main/java/com/linecorp/armeria/common/CommonPools.java

+8
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,14 @@ public final class CommonPools {
4141
MoreMeterBinders
4242
.eventLoopMetrics(WORKER_GROUP, new MeterIdPrefix("armeria.netty.common"))
4343
.bindTo(Flags.meterRegistry());
44+
45+
try {
46+
Class.forName("reactor.core.scheduler.Schedulers",
47+
true, CommonPools.class.getClassLoader());
48+
ReactorNonBlockingUtil.registerEventLoopAsNonBlocking();
49+
} catch (ClassNotFoundException e) {
50+
// Do nothing.
51+
}
4452
}
4553

4654
/**

core/src/main/java/com/linecorp/armeria/common/CoreBlockHoundIntegration.java

+2
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@
3030
public final class CoreBlockHoundIntegration implements BlockHoundIntegration {
3131
@Override
3232
public void applyTo(Builder builder) {
33+
builder.nonBlockingThreadPredicate(predicate -> predicate.or(NonBlocking.class::isInstance));
34+
3335
// short locks
3436
builder.allowBlockingCallsInside("com.linecorp.armeria.client.HttpClientFactory",
3537
"pool");

core/src/main/java/reactor/core/scheduler/NonBlocking.java renamed to core/src/main/java/com/linecorp/armeria/common/NonBlocking.java

+11-2
Original file line numberDiff line numberDiff line change
@@ -13,9 +13,18 @@
1313
* License for the specific language governing permissions and limitations
1414
* under the License.
1515
*/
16-
package reactor.core.scheduler;
16+
package com.linecorp.armeria.common;
1717

1818
/**
19-
* A dummy interface that makes Project Reactor recognize Armeria's event loop threads as non-blocking.
19+
* An interface that indicates a non-blocking thread. You can use this interface to check if the current
20+
* thread is a non-blocking thread. For example:
21+
* <pre>{@code
22+
* if (Thread.currentThread() instanceof NonBlocking) {
23+
* // Avoid blocking operations.
24+
* closeable.closeAsync();
25+
* } else {
26+
* closeable.close();
27+
* }
28+
* }</pre>
2029
*/
2130
public interface NonBlocking {}
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2019 LINE Corporation
2+
* Copyright 2024 LINE Corporation
33
*
44
* LINE Corporation licenses this file to you under the Apache License,
55
* version 2.0 (the "License"); you may not use this file except in compliance
@@ -13,11 +13,15 @@
1313
* License for the specific language governing permissions and limitations
1414
* under the License.
1515
*/
16+
package com.linecorp.armeria.common;
1617

17-
/**
18-
* Provides a dummy interface that makes Project Reactor recognize Armeria's event loop threads as non-blocking.
19-
*/
20-
@NonNullByDefault
21-
package reactor.core.scheduler;
18+
import reactor.core.scheduler.Schedulers;
19+
20+
final class ReactorNonBlockingUtil {
21+
22+
static void registerEventLoopAsNonBlocking() {
23+
Schedulers.registerNonBlockingThreadPredicate(NonBlocking.class::isInstance);
24+
}
2225

23-
import com.linecorp.armeria.common.annotation.NonNullByDefault;
26+
private ReactorNonBlockingUtil() {}
27+
}

core/src/main/java/com/linecorp/armeria/common/util/EventLoopCheckingFuture.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,9 @@
3131
import com.google.common.collect.MapMaker;
3232

3333
import com.linecorp.armeria.common.Flags;
34+
import com.linecorp.armeria.common.NonBlocking;
3435
import com.linecorp.armeria.common.annotation.Nullable;
3536

36-
import reactor.core.scheduler.NonBlocking;
37-
3837
/**
3938
* A {@link CompletableFuture} that warns the user if they call a method that blocks the event loop.
4039
*/

core/src/main/java/com/linecorp/armeria/internal/common/util/EventLoopThread.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,15 +15,15 @@
1515
*/
1616
package com.linecorp.armeria.internal.common.util;
1717

18+
import com.linecorp.armeria.common.NonBlocking;
1819
import com.linecorp.armeria.common.annotation.Nullable;
1920

2021
import io.netty.util.concurrent.FastThreadLocal;
2122
import io.netty.util.concurrent.FastThreadLocalThread;
22-
import reactor.core.scheduler.NonBlocking;
2323

2424
/**
2525
* An event loop thread with support for {@link TemporaryThreadLocals}, Netty {@link FastThreadLocal} and
26-
* Project Reactor {@link NonBlocking}.
26+
* {@link NonBlocking} interface.
2727
*/
2828
public final class EventLoopThread extends FastThreadLocalThread implements NonBlocking {
2929

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright 2024 LINE Corporation
3+
*
4+
* LINE Corporation licenses this file to you under the Apache License,
5+
* version 2.0 (the "License"); you may not use this file except in compliance
6+
* with the License. You may obtain a copy of the License at:
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13+
* License for the specific language governing permissions and limitations
14+
* under the License.
15+
*/
16+
package com.linecorp.armeria.common.reactor3;
17+
18+
import static org.assertj.core.api.Assertions.assertThat;
19+
20+
import org.junit.jupiter.api.Test;
21+
22+
import com.linecorp.armeria.common.CommonPools;
23+
24+
import io.netty.util.concurrent.Future;
25+
import reactor.core.scheduler.Schedulers;
26+
27+
final class EventLoopNonBlockingTest {
28+
29+
/**
30+
* Verifies that the current thread is registered a non-blocking thread via {@code ReactorNonBlockingUtil}.
31+
*/
32+
@Test
33+
void checkEventLoopNonBlocking() throws Exception {
34+
final Future<Boolean> submit = CommonPools.workerGroup().submit(Schedulers::isInNonBlockingThread);
35+
assertThat(submit.get()).isTrue();
36+
}
37+
}

0 commit comments

Comments
 (0)