1
+ package io .servicetalk .grpc ;
2
+
3
+ import com .apple .servicetalkleak .Message ;
4
+ import com .apple .servicetalkleak .ServiceTalkLeak ;
5
+ import io .netty .buffer .ByteBufUtil ;
6
+ import io .servicetalk .concurrent .api .Publisher ;
7
+ import io .servicetalk .concurrent .api .Single ;
8
+ import io .servicetalk .grpc .api .GrpcServiceContext ;
9
+ import io .servicetalk .grpc .api .GrpcStatusCode ;
10
+ import io .servicetalk .grpc .api .GrpcStatusException ;
11
+ import io .servicetalk .grpc .netty .GrpcClients ;
12
+ import io .servicetalk .grpc .netty .GrpcServers ;
13
+ import io .servicetalk .http .netty .HttpProtocolConfigs ;
14
+ import io .servicetalk .http .netty .SpliceFlatStreamToMetaSingle ;
15
+ import io .servicetalk .logging .api .LogLevel ;
16
+ import io .servicetalk .transport .api .HostAndPort ;
17
+ import io .servicetalk .transport .api .IoExecutor ;
18
+ import io .servicetalk .transport .netty .internal .NettyIoExecutors ;
19
+ import org .junit .jupiter .api .Test ;
20
+ import org .slf4j .Logger ;
21
+ import org .slf4j .LoggerFactory ;
22
+
23
+ import static io .servicetalk .concurrent .api .internal .BlockingUtils .blockingInvocation ;
24
+ import static org .junit .jupiter .api .Assertions .assertFalse ;
25
+
26
+ public class LeakRepro {
27
+
28
+ private static final Logger LOGGER = LoggerFactory .getLogger (LeakRepro .class );
29
+
30
+ static boolean leakDetected = false ;
31
+
32
+ static {
33
+ System .setProperty ("io.netty.leakDetection.level" , "paranoid" );
34
+ ByteBufUtil .setLeakListener ((type , records ) -> {
35
+ leakDetected = true ;
36
+ LOGGER .error ("ByteBuf leak detected!" );
37
+ });
38
+ }
39
+
40
+ IoExecutor serverExecutor = NettyIoExecutors .createIoExecutor (1 , "server" );
41
+ IoExecutor clientExecutor = NettyIoExecutors .createIoExecutor (1 , "client" );
42
+
43
+ @ SuppressWarnings ("resource" )
44
+ @ Test
45
+ public void testLeak () throws Exception {
46
+ GrpcServers .forPort (8888 )
47
+ .initializeHttp (b -> b
48
+ .ioExecutor (serverExecutor )
49
+ .executor (serverExecutor ))
50
+ .listenAndAwait (new ServiceTalkLeak .ServiceTalkLeakService () {
51
+ @ Override
52
+ public Publisher <Message > rpc (GrpcServiceContext ctx , Publisher <Message > request ) {
53
+ Publisher <Message > response = splice (request )
54
+ .flatMapPublisher (pair -> {
55
+ LOGGER .info ("Initial message: " + pair .head );
56
+ return Publisher .failed (new GrpcStatusException (GrpcStatusCode .INVALID_ARGUMENT .status ()));
57
+ });
58
+ return response ;
59
+ }
60
+ });
61
+
62
+ ServiceTalkLeak .ServiceTalkLeakClient client = GrpcClients .forAddress (HostAndPort .of ("127.0.0.1" , 8888 ))
63
+ .initializeHttp (b -> b
64
+ .protocols (HttpProtocolConfigs .h2 ().enableFrameLogging ("CLIENT" , LogLevel .INFO , () -> true ).build ())
65
+ .ioExecutor (clientExecutor )
66
+ .executor (clientExecutor ))
67
+ .build (new ServiceTalkLeak .ClientFactory ());
68
+
69
+ for (int i = 0 ; i < 10 ; i ++) {
70
+ LOGGER .info ("Iteration {}" , i );
71
+ blockingInvocation (
72
+ client .rpc (
73
+ Publisher .from (
74
+ Message .newBuilder ().setValue ("first message" ).build (),
75
+ Message .newBuilder ().setValue ("second message (which leaks)" ).build ()))
76
+ .ignoreElements ()
77
+ .onErrorComplete ());
78
+
79
+ System .gc ();
80
+ System .runFinalization ();
81
+ }
82
+
83
+ assertFalse (leakDetected );
84
+ }
85
+
86
+ private static Single <Pair > splice (Publisher <Message > request ) {
87
+ return request .liftSyncToSingle (new SpliceFlatStreamToMetaSingle <>(Pair ::new ));
88
+ }
89
+
90
+ private static final class Pair {
91
+ final Message head ;
92
+ final Publisher <Message > stream ;
93
+
94
+ public Pair (Message head , Publisher <Message > stream ) {
95
+ this .head = head ;
96
+ this .stream = stream ;
97
+ }
98
+ }
99
+ }
0 commit comments