22
22
import io .netty .channel .ChannelHandlerContext ;
23
23
import io .netty .channel .ChannelInboundHandlerAdapter ;
24
24
import java .time .zone .ZoneRulesException ;
25
+ import java .util .ArrayDeque ;
26
+ import java .util .Queue ;
25
27
import java .util .concurrent .CompletableFuture ;
26
28
import java .util .concurrent .CompletionException ;
27
29
import java .util .concurrent .CompletionStage ;
28
30
import java .util .concurrent .Executor ;
29
31
import java .util .concurrent .Executors ;
30
32
import java .util .function .BiFunction ;
33
+ import java .util .function .Consumer ;
31
34
import neo4j .org .testkit .backend .CustomDriverError ;
32
35
import neo4j .org .testkit .backend .FrontendError ;
33
36
import neo4j .org .testkit .backend .TestkitState ;
@@ -48,6 +51,7 @@ public class TestkitRequestProcessorHandler extends ChannelInboundHandlerAdapter
48
51
// Some requests require multiple threads
49
52
private final Executor requestExecutorService = Executors .newFixedThreadPool (10 );
50
53
private Channel channel ;
54
+ private ResponseQueueHanlder responseQueueHanlder ;
51
55
52
56
public TestkitRequestProcessorHandler (BackendMode backendMode , Logging logging ) {
53
57
switch (backendMode ) {
@@ -64,22 +68,22 @@ public TestkitRequestProcessorHandler(BackendMode backendMode, Logging logging)
64
68
@ Override
65
69
public void channelRegistered (ChannelHandlerContext ctx ) throws Exception {
66
70
channel = ctx .channel ();
71
+ responseQueueHanlder = new ResponseQueueHanlder (channel ::writeAndFlush );
67
72
super .channelRegistered (ctx );
68
73
}
69
74
70
75
@ Override
71
76
public void channelRead (ChannelHandlerContext ctx , Object msg ) {
72
77
// Processing is done in a separate thread to avoid blocking EventLoop because some testing logic, like
73
78
// resolvers support, is blocking.
79
+ responseQueueHanlder .setResponseReadyAndDispatchFirst ();
74
80
requestExecutorService .execute (() -> {
75
81
try {
76
82
var request = (TestkitRequest ) msg ;
77
- var responseStage = processorImpl .apply (request , testkitState );
78
- responseStage .whenComplete ((response , throwable ) -> {
79
- if (throwable != null ) {
80
- ctx .writeAndFlush (createErrorResponse (throwable ));
81
- } else if (response != null ) {
82
- ctx .writeAndFlush (response );
83
+ processorImpl .apply (request , testkitState ).whenComplete ((response , throwable ) -> {
84
+ var testkitResponse = throwable != null ? createErrorResponse (throwable ) : response ;
85
+ if (testkitResponse != null ) {
86
+ responseQueueHanlder .offerAndDispatchFirst (testkitResponse );
83
87
}
84
88
});
85
89
} catch (Throwable throwable ) {
@@ -101,7 +105,8 @@ private static CompletionStage<TestkitResponse> wrapSyncRequest(
101
105
102
106
@ Override
103
107
public void exceptionCaught (ChannelHandlerContext ctx , Throwable cause ) {
104
- ctx .writeAndFlush (createErrorResponse (cause ));
108
+ var response = createErrorResponse (cause );
109
+ responseQueueHanlder .offerAndDispatchFirst (response );
105
110
}
106
111
107
112
private TestkitResponse createErrorResponse (Throwable throwable ) {
@@ -165,7 +170,7 @@ private void writeAndFlush(TestkitResponse response) {
165
170
if (channel == null ) {
166
171
throw new IllegalStateException ("Called before channel is initialized" );
167
172
}
168
- channel . writeAndFlush (response );
173
+ responseQueueHanlder . offerAndDispatchFirst (response );
169
174
}
170
175
171
176
public enum BackendMode {
@@ -174,4 +179,34 @@ public enum BackendMode {
174
179
REACTIVE_LEGACY ,
175
180
REACTIVE
176
181
}
182
+
183
+ private static class ResponseQueueHanlder {
184
+ private final Consumer <TestkitResponse > responseWriter ;
185
+ private final Queue <TestkitResponse > responseQueue = new ArrayDeque <>();
186
+ private boolean responseReady ;
187
+
188
+ ResponseQueueHanlder (Consumer <TestkitResponse > responseWriter ) {
189
+ this .responseWriter = responseWriter ;
190
+ }
191
+
192
+ synchronized void setResponseReadyAndDispatchFirst () {
193
+ responseReady = true ;
194
+ dispatchFirst ();
195
+ }
196
+
197
+ synchronized void offerAndDispatchFirst (TestkitResponse response ) {
198
+ responseQueue .offer (response );
199
+ if (responseReady ) {
200
+ dispatchFirst ();
201
+ }
202
+ }
203
+
204
+ private synchronized void dispatchFirst () {
205
+ var response = responseQueue .poll ();
206
+ if (response != null ) {
207
+ responseReady = false ;
208
+ responseWriter .accept (responseQueue .poll ());
209
+ }
210
+ }
211
+ }
177
212
}
0 commit comments