-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add BlockHound SPI support via ServiceLoader (#1682)
Add BlockHound SPI's implementation. By keeping it next to core, we control how we intercept the tasks (e.g., `onScheduleHook`) and can also apply internal optimizations in future. BlockHound's built-in Reactor integration will be adjusted to not apply anything if Reactor's version is 3.3 or higher. The integration class must be public to to the ServiceLoader use, but it is excluded from javadoc and documented inline as "do not consider public".
- Loading branch information
1 parent
7090063
commit ad07981
Showing
6 changed files
with
193 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
17 changes: 17 additions & 0 deletions
17
...e/src/blockHoundTest/java/reactor/core/scheduler/ReactorBlockHoundIntegrationSPITest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package reactor.core.scheduler; | ||
|
||
import java.util.ServiceLoader; | ||
|
||
import org.junit.Test; | ||
import reactor.blockhound.integration.BlockHoundIntegration; | ||
|
||
import static org.assertj.core.api.Assertions.assertThat; | ||
|
||
public class ReactorBlockHoundIntegrationSPITest { | ||
|
||
@Test | ||
public void shouldSupportServiceLoader() { | ||
assertThat(ServiceLoader.load(BlockHoundIntegration.class)) | ||
.hasAtLeastOneElementOfType(ReactorBlockHoundIntegration.class); | ||
} | ||
} |
87 changes: 87 additions & 0 deletions
87
...core/src/blockHoundTest/java/reactor/core/scheduler/ReactorBlockHoundIntegrationTest.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,87 @@ | ||
/* | ||
* Copyright (c) 2019-Present Pivotal Software Inc, All Rights Reserved. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package reactor.core.scheduler; | ||
|
||
import java.time.Duration; | ||
import java.util.concurrent.CompletableFuture; | ||
import java.util.concurrent.TimeUnit; | ||
import java.util.function.Consumer; | ||
|
||
import org.assertj.core.api.Assertions; | ||
import org.junit.Rule; | ||
import org.junit.Test; | ||
import org.junit.rules.Timeout; | ||
import reactor.blockhound.BlockHound; | ||
import reactor.core.publisher.Mono; | ||
import reactor.core.scheduler.ReactorBlockHoundIntegration; | ||
import reactor.core.scheduler.Schedulers; | ||
|
||
public class ReactorBlockHoundIntegrationTest { | ||
|
||
static { | ||
// Use the builder to load only our integration to avoid false positives | ||
BlockHound.builder() | ||
.with(new ReactorBlockHoundIntegration()) | ||
.install(); | ||
} | ||
|
||
@Rule | ||
public Timeout timeout = new Timeout(1, TimeUnit.SECONDS); | ||
|
||
@Test | ||
public void shouldDetectBlockingCalls() { | ||
expectBlockingCall("java.lang.Thread.sleep", future -> { | ||
Schedulers.parallel() | ||
.schedule(() -> { | ||
try { | ||
Thread.sleep(10); | ||
future.complete(null); | ||
} | ||
catch (Throwable e) { | ||
future.completeExceptionally(e); | ||
} | ||
}); | ||
}); | ||
} | ||
|
||
@Test | ||
public void shouldDetectBlockingCallsOnSubscribe() { | ||
expectBlockingCall("java.lang.Thread.yield", future -> { | ||
Mono.fromRunnable(Thread::yield) | ||
.subscribeOn(Schedulers.parallel()) | ||
.subscribe(future::complete, future::completeExceptionally); | ||
}); | ||
} | ||
|
||
@Test | ||
public void shouldDetectBlockingCallsInOperators() { | ||
expectBlockingCall("java.lang.Thread.yield", future -> { | ||
Mono.delay(Duration.ofMillis(10)) | ||
.doOnNext(__ -> Thread.yield()) | ||
.subscribe(future::complete, future::completeExceptionally); | ||
}); | ||
} | ||
|
||
void expectBlockingCall(String desc, Consumer<CompletableFuture<Object>> callable) { | ||
Assertions | ||
.assertThatThrownBy(() -> { | ||
CompletableFuture<Object> future = new CompletableFuture<>(); | ||
callable.accept(future); | ||
future.join(); | ||
}) | ||
.hasMessageContaining("Blocking call! " + desc); | ||
} | ||
} |
59 changes: 59 additions & 0 deletions
59
reactor-core/src/main/java/reactor/core/scheduler/ReactorBlockHoundIntegration.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
/* | ||
* Copyright (c) 2019-Present Pivotal Software Inc, All Rights Reserved. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package reactor.core.scheduler; | ||
|
||
import reactor.blockhound.BlockHound; | ||
import reactor.blockhound.integration.BlockHoundIntegration; | ||
|
||
import java.util.concurrent.ScheduledThreadPoolExecutor; | ||
|
||
/** | ||
* {@link BlockHoundIntegration} with Reactor's scheduling mechanism. | ||
* Wraps every scheduled {@link Runnable} with a noop {@link Wrapper}, so that it can be | ||
* detected as an entry point of the non-blocking call stack. | ||
* | ||
* WARNING: this class is not intended to be public, but {@link java.util.ServiceLoader} | ||
* requires it to be so. Public visibility DOES NOT make it part of the public API. | ||
* | ||
* @since 3.3.0 | ||
*/ | ||
public final class ReactorBlockHoundIntegration implements BlockHoundIntegration { | ||
|
||
@Override | ||
public void applyTo(BlockHound.Builder builder) { | ||
builder.nonBlockingThreadPredicate(current -> current.or(NonBlocking.class::isInstance)); | ||
|
||
// `ScheduledThreadPoolExecutor$DelayedWorkQueue.offer` parks the Thread with Unsafe#park. | ||
builder.allowBlockingCallsInside(ScheduledThreadPoolExecutor.class.getName(), "scheduleAtFixedRate"); | ||
|
||
Schedulers.onScheduleHook("BlockHound", Wrapper::new); | ||
builder.disallowBlockingCallsInside(Wrapper.class.getName(), "run"); | ||
} | ||
|
||
static final class Wrapper implements Runnable { | ||
|
||
final Runnable delegate; | ||
|
||
Wrapper(Runnable delegate) { | ||
this.delegate = delegate; | ||
} | ||
|
||
@Override | ||
public void run() { | ||
delegate.run(); | ||
} | ||
} | ||
} |
14 changes: 14 additions & 0 deletions
14
...src/main/resources/META-INF/services/reactor.blockhound.integration.BlockHoundIntegration
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
# Copyright (c) 2019-Present Pivotal Software Inc, All Rights Reserved. | ||
# | ||
# Licensed under the Apache License, Version 2.0 (the "License"); | ||
# you may not use this file except in compliance with the License. | ||
# You may obtain a copy of the License at | ||
# | ||
# https://www.apache.org/licenses/LICENSE-2.0 | ||
# | ||
# Unless required by applicable law or agreed to in writing, software | ||
# distributed under the License is distributed on an "AS IS" BASIS, | ||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
# See the License for the specific language governing permissions and | ||
# limitations under the License. | ||
reactor.core.scheduler.ReactorBlockHoundIntegration |