diff --git a/build.gradle b/build.gradle
index 68525a4230..75002c0143 100644
--- a/build.gradle
+++ b/build.gradle
@@ -64,6 +64,8 @@ configure(allprojects) { project ->
dependency "io.reactivex:rxjava-reactive-streams:1.2.1"
dependency "io.reactivex.rxjava2:rxjava:2.2.17"
+ dependency "io.projectreactor.tools:blockhound:1.0.2.RELEASE"
+
dependency "com.caucho:hessian:4.0.62"
dependency "com.fasterxml:aalto-xml:1.2.2"
dependency("com.fasterxml.woodstox:woodstox-core:6.0.3") {
diff --git a/spring-core/spring-core.gradle b/spring-core/spring-core.gradle
index 94327d342e..d88536a3fe 100644
--- a/spring-core/spring-core.gradle
+++ b/spring-core/spring-core.gradle
@@ -43,6 +43,7 @@ dependencies {
compile(files(objenesisRepackJar))
compile(project(":spring-jcl"))
compileOnly(project(":kotlin-coroutines"))
+ compileOnly("io.projectreactor.tools:blockhound")
optional("net.sf.jopt-simple:jopt-simple")
optional("org.aspectj:aspectjweaver")
optional("org.jetbrains.kotlin:kotlin-reflect")
@@ -60,6 +61,7 @@ dependencies {
testCompile("javax.xml.bind:jaxb-api")
testCompile("com.fasterxml.woodstox:woodstox-core")
testCompile(project(":kotlin-coroutines"))
+ testCompile("io.projectreactor.tools:blockhound")
testFixturesImplementation("com.google.code.findbugs:jsr305")
testFixturesImplementation("io.projectreactor:reactor-test")
testFixturesImplementation("org.assertj:assertj-core")
diff --git a/spring-core/src/main/java/org/springframework/core/ReactiveAdapterRegistry.java b/spring-core/src/main/java/org/springframework/core/ReactiveAdapterRegistry.java
index 48c7512e62..9b237c9a09 100644
--- a/spring-core/src/main/java/org/springframework/core/ReactiveAdapterRegistry.java
+++ b/spring-core/src/main/java/org/springframework/core/ReactiveAdapterRegistry.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2019 the original author or authors.
+ * Copyright 2002-2020 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -29,12 +29,15 @@ import io.reactivex.Flowable;
import kotlinx.coroutines.CompletableDeferredKt;
import kotlinx.coroutines.Deferred;
import org.reactivestreams.Publisher;
+import reactor.blockhound.BlockHound;
+import reactor.blockhound.integration.BlockHoundIntegration;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import rx.RxReactiveStreams;
import org.springframework.lang.Nullable;
import org.springframework.util.ClassUtils;
+import org.springframework.util.ConcurrentReferenceHashMap;
import org.springframework.util.ReflectionUtils;
/**
@@ -354,4 +357,32 @@ public class ReactiveAdapterRegistry {
);
}
}
+
+
+ /**
+ * {@code BlockHoundIntegration} for spring-core classes.
+ *
Whitelists the following:
+ *
+ *
Reading class info via {@link LocalVariableTableParameterNameDiscoverer}.
+ *
Locking within {@link ConcurrentReferenceHashMap}.
+ *
+ * @since 5.2.4
+ */
+ public static class SpringCoreBlockHoundIntegration implements BlockHoundIntegration {
+
+ @Override
+ public void applyTo(BlockHound.Builder builder) {
+
+ // Avoid hard references potentially anywhere in spring-core (no need for structural dependency)
+
+ builder.allowBlockingCallsInside(
+ "org.springframework.core.LocalVariableTableParameterNameDiscoverer", "inspectClass");
+
+ String className = "org.springframework.util.ConcurrentReferenceHashMap$Segment";
+ builder.allowBlockingCallsInside(className, "doTask");
+ builder.allowBlockingCallsInside(className, "clear");
+ builder.allowBlockingCallsInside(className, "restructure");
+ }
+ }
+
}
diff --git a/spring-core/src/main/resources/META-INF/services/reactor.blockhound.integration.BlockHoundIntegration b/spring-core/src/main/resources/META-INF/services/reactor.blockhound.integration.BlockHoundIntegration
new file mode 100644
index 0000000000..5a35c69bac
--- /dev/null
+++ b/spring-core/src/main/resources/META-INF/services/reactor.blockhound.integration.BlockHoundIntegration
@@ -0,0 +1,15 @@
+# Copyright 2002-2020 the original author or authors.
+#
+# 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.
+
+org.springframework.core.ReactiveAdapterRegistry$SpringCoreBlockHoundIntegration
\ No newline at end of file
diff --git a/spring-core/src/test/java/org/springframework/core/SpringCoreBlockHoundIntegrationTests.java b/spring-core/src/test/java/org/springframework/core/SpringCoreBlockHoundIntegrationTests.java
new file mode 100644
index 0000000000..2d8648335c
--- /dev/null
+++ b/spring-core/src/test/java/org/springframework/core/SpringCoreBlockHoundIntegrationTests.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2002-2020 the original author or authors.
+ *
+ * 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 org.springframework.core;
+
+import java.lang.reflect.Method;
+import java.util.Map;
+import java.util.concurrent.CompletableFuture;
+
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+import reactor.blockhound.BlockHound;
+import reactor.core.scheduler.Schedulers;
+
+import org.springframework.tests.sample.objects.TestObject;
+import org.springframework.util.ConcurrentReferenceHashMap;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/**
+ * Tests to verify the spring-core BlockHound integration rules.
+ *
+ * @author Rossen Stoyanchev
+ * @since 5.2.4
+ */
+public class SpringCoreBlockHoundIntegrationTests {
+
+
+ @BeforeAll
+ static void setUp() {
+ BlockHound.install();
+ }
+
+
+ @Test
+ void blockHoundIsInstalled() {
+ assertThatThrownBy(() -> testNonBlockingTask(() -> Thread.sleep(10)))
+ .hasMessageContaining("Blocking call!");
+ }
+
+ @Test
+ void localVariableTableParameterNameDiscoverer() {
+ testNonBlockingTask(() -> {
+ Method setName = TestObject.class.getMethod("setName", String.class);
+ String[] names = new LocalVariableTableParameterNameDiscoverer().getParameterNames(setName);
+ assertThat(names).isEqualTo(new String[] {"name"});
+ });
+ }
+
+ @Test
+ void concurrentReferenceHashMap() {
+ int size = 10000;
+ Map map = new ConcurrentReferenceHashMap<>(size);
+
+ CompletableFuture