Skip to content

Commit 04b8ebe

Browse files
committed
test: add failing spec for JDK HttpClient thread leak
1 parent 8db55f3 commit 04b8ebe

1 file changed

Lines changed: 60 additions & 0 deletions

File tree

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package io.micronaut.http.client.jdk
2+
3+
import io.micronaut.context.ApplicationContext
4+
import io.micronaut.context.annotation.Requires
5+
import io.micronaut.http.annotation.Controller
6+
import io.micronaut.http.annotation.Get
7+
import io.micronaut.http.client.annotation.Client
8+
import io.micronaut.runtime.server.EmbeddedServer
9+
import spock.lang.AutoCleanup
10+
import spock.lang.Shared
11+
import spock.lang.Specification
12+
13+
import java.util.regex.Pattern
14+
15+
class JdkBlockingHttpClientThreadLeakSpec extends Specification {
16+
17+
private static final Pattern JDK_HTTP_CLIENT_SELECTOR = Pattern.compile(/HttpClient-\d+-SelectorManager/)
18+
19+
@Shared @AutoCleanup
20+
EmbeddedServer server = ApplicationContext.run(EmbeddedServer, [
21+
'spec.name': 'JdkBlockingHttpClientThreadLeakSpec'
22+
])
23+
24+
@Shared @AutoCleanup
25+
ApplicationContext clientCtx = ApplicationContext.run([
26+
'spec.name': 'JdkBlockingHttpClientThreadLeakSpec',
27+
'micronaut.http.services.leakspec.url': server.URL.toString()
28+
])
29+
30+
def "synchronous declarative @Client does not spawn a new JDK HttpClient per call"() {
31+
given:
32+
LeakClient client = clientCtx.getBean(LeakClient)
33+
int baseline = countSelectorThreads()
34+
35+
when:
36+
500.times { client.ping() }
37+
38+
then: 'thread count is measured immediately, before GC can reap leaked clients'
39+
int after = countSelectorThreads()
40+
(after - baseline) <= 1
41+
}
42+
43+
private static int countSelectorThreads() {
44+
Thread.getAllStackTraces().keySet().count { JDK_HTTP_CLIENT_SELECTOR.matcher(it.name).matches() } as int
45+
}
46+
47+
@Requires(property = 'spec.name', value = 'JdkBlockingHttpClientThreadLeakSpec')
48+
@Client('leakspec')
49+
static interface LeakClient {
50+
@Get('/leak/ping')
51+
String ping()
52+
}
53+
54+
@Requires(property = 'spec.name', value = 'JdkBlockingHttpClientThreadLeakSpec')
55+
@Controller('/leak')
56+
static class LeakController {
57+
@Get('/ping')
58+
String ping() { 'pong' }
59+
}
60+
}

0 commit comments

Comments
 (0)