24
24
import com .mongodb .MongoSocketOpenException ;
25
25
import com .mongodb .MongoSocketReadTimeoutException ;
26
26
import com .mongodb .ServerAddress ;
27
+ import com .mongodb .annotations .ThreadSafe ;
27
28
import com .mongodb .connection .AsyncCompletionHandler ;
28
29
import com .mongodb .connection .SocketSettings ;
29
30
import com .mongodb .connection .SslSettings ;
30
31
import com .mongodb .connection .Stream ;
32
+ import com .mongodb .lang .Nullable ;
31
33
import io .netty .bootstrap .Bootstrap ;
32
34
import io .netty .buffer .ByteBufAllocator ;
33
35
import io .netty .buffer .CompositeByteBuf ;
34
36
import io .netty .buffer .PooledByteBufAllocator ;
35
37
import io .netty .channel .Channel ;
36
38
import io .netty .channel .ChannelFuture ;
37
39
import io .netty .channel .ChannelFutureListener ;
38
- import io .netty .channel .ChannelHandler ;
39
40
import io .netty .channel .ChannelHandlerContext ;
41
+ import io .netty .channel .ChannelInboundHandlerAdapter ;
40
42
import io .netty .channel .ChannelInitializer ;
41
43
import io .netty .channel .ChannelOption ;
44
+ import io .netty .channel .ChannelPipeline ;
42
45
import io .netty .channel .EventLoopGroup ;
43
46
import io .netty .channel .SimpleChannelInboundHandler ;
44
47
import io .netty .channel .socket .SocketChannel ;
45
48
import io .netty .handler .ssl .SslHandler ;
46
49
import io .netty .handler .timeout .ReadTimeoutException ;
47
- import io .netty .util .concurrent .EventExecutor ;
48
50
import org .bson .ByteBuf ;
49
51
50
52
import javax .net .ssl .SSLContext ;
58
60
import java .util .List ;
59
61
import java .util .Queue ;
60
62
import java .util .concurrent .CountDownLatch ;
63
+ import java .util .concurrent .Future ;
64
+ import java .util .concurrent .ScheduledFuture ;
61
65
66
+ import static com .mongodb .assertions .Assertions .isTrueArgument ;
62
67
import static com .mongodb .internal .connection .SslHelper .enableHostNameVerification ;
63
68
import static com .mongodb .internal .connection .SslHelper .enableSni ;
64
69
import static java .util .concurrent .TimeUnit .MILLISECONDS ;
65
70
66
71
/**
67
72
* A Stream implementation based on Netty 4.0.
73
+ * Just like it is for the {@link java.nio.channels.AsynchronousSocketChannel},
74
+ * concurrent pending<sup>1</sup> readers
75
+ * (whether {@linkplain #read(int, int) synchronous} or {@linkplain #readAsync(int, AsyncCompletionHandler) asynchronous})
76
+ * are not supported by {@link NettyStream}.
77
+ * However, this class does not have a fail-fast mechanism checking for such situations.
78
+ * <hr>
79
+ * <sup>1</sup>We cannot simply say that read methods are not allowed be run concurrently because strictly speaking they are allowed,
80
+ * as explained below.
81
+ * <pre>{@code
82
+ * NettyStream stream = ...;
83
+ * stream.readAsync(1, new AsyncCompletionHandler<ByteBuf>() {//inv1
84
+ * @Override
85
+ * public void completed(ByteBuf o) {
86
+ * stream.readAsync(//inv2
87
+ * 1, ...);//ret2
88
+ * }
89
+ *
90
+ * @Override
91
+ * public void failed(Throwable t) {
92
+ * }
93
+ * });//ret1
94
+ * }</pre>
95
+ * Arrows on the diagram below represent happens-before relations.
96
+ * <pre>{@code
97
+ * int1 -> inv2 -> ret2
98
+ * \--------> ret1
99
+ * }</pre>
100
+ * As shown on the diagram, the method {@link #readAsync(int, AsyncCompletionHandler)} runs concurrently with
101
+ * itself in the example above. However, there are no concurrent pending readers because the second operation
102
+ * is invoked after the first operation has completed reading despite the method has not returned yet.
68
103
*/
69
104
final class NettyStream implements Stream {
70
- private static final String READ_HANDLER_NAME = "ReadTimeoutHandler" ;
105
+ private static final byte NO_SCHEDULE_TIME = 0 ;
71
106
private final ServerAddress address ;
72
107
private final SocketSettings settings ;
73
108
private final SslSettings sslSettings ;
74
109
private final EventLoopGroup workerGroup ;
75
110
private final Class <? extends SocketChannel > socketChannelClass ;
76
111
private final ByteBufAllocator allocator ;
77
112
78
- private volatile boolean isClosed ;
113
+ private boolean isClosed ;
79
114
private volatile Channel channel ;
80
115
81
116
private final LinkedList <io .netty .buffer .ByteBuf > pendingInboundBuffers = new LinkedList <io .netty .buffer .ByteBuf >();
82
- private volatile PendingReader pendingReader ;
83
- private volatile Throwable pendingException ;
117
+ /* The fields pendingReader, pendingException are always written/read inside synchronized blocks
118
+ * that use the same NettyStream object, so they can be plain.*/
119
+ private PendingReader pendingReader ;
120
+ private Throwable pendingException ;
121
+ /* The fields readTimeoutTask, readTimeoutMillis are each written only in the ChannelInitializer.initChannel method
122
+ * (in addition to the write of the default value and the write by variable initializers),
123
+ * and read only when NettyStream users read data, or Netty event loop handles incoming data.
124
+ * Since actions done by the ChannelInitializer.initChannel method
125
+ * are ordered (in the happens-before order) before user read actions and before event loop actions that handle incoming data,
126
+ * these fields can be plain.*/
127
+ @ Nullable
128
+ private ReadTimeoutTask readTimeoutTask ;
129
+ private long readTimeoutMillis = NO_SCHEDULE_TIME ;
84
130
85
131
NettyStream (final ServerAddress address , final SocketSettings settings , final SslSettings sslSettings , final EventLoopGroup workerGroup ,
86
132
final Class <? extends SocketChannel > socketChannelClass , final ByteBufAllocator allocator ) {
@@ -143,6 +189,7 @@ private void initializeChannel(final AsyncCompletionHandler<Void> handler, final
143
189
bootstrap .handler (new ChannelInitializer <SocketChannel >() {
144
190
@ Override
145
191
public void initChannel (final SocketChannel ch ) {
192
+ ChannelPipeline pipeline = ch .pipeline ();
146
193
if (sslSettings .isEnabled ()) {
147
194
SSLEngine engine = getSslContext ().createSSLEngine (address .getHost (), address .getPort ());
148
195
engine .setUseClientMode (true );
@@ -152,13 +199,20 @@ public void initChannel(final SocketChannel ch) {
152
199
enableHostNameVerification (sslParameters );
153
200
}
154
201
engine .setSSLParameters (sslParameters );
155
- ch . pipeline () .addFirst ("ssl" , new SslHandler (engine , false ));
202
+ pipeline .addFirst ("ssl" , new SslHandler (engine , false ));
156
203
}
204
+
157
205
int readTimeout = settings .getReadTimeout (MILLISECONDS );
158
- if (readTimeout > 0 ) {
159
- ch .pipeline ().addLast (READ_HANDLER_NAME , new ReadTimeoutHandler (readTimeout ));
206
+ if (readTimeout > NO_SCHEDULE_TIME ) {
207
+ readTimeoutMillis = readTimeout ;
208
+ /* We need at least one handler before (in the inbound evaluation order) the InboundBufferHandler,
209
+ * so that we can fire exception events (they are inbound events) using its context and the InboundBufferHandler
210
+ * receives them. SslHandler is not always present, so adding a NOOP handler.*/
211
+ pipeline .addLast (new ChannelInboundHandlerAdapter ());
212
+ readTimeoutTask = new ReadTimeoutTask (pipeline .lastContext ());
160
213
}
161
- ch .pipeline ().addLast (new InboundBufferHandler ());
214
+
215
+ pipeline .addLast (new InboundBufferHandler ());
162
216
}
163
217
});
164
218
final ChannelFuture channelFuture = bootstrap .connect (nextAddress );
@@ -184,9 +238,10 @@ public boolean supportsAdditionalTimeout() {
184
238
}
185
239
186
240
@ Override
187
- public ByteBuf read (final int numBytes , final int additionalTimeout ) throws IOException {
241
+ public ByteBuf read (final int numBytes , final int additionalTimeoutMillis ) throws IOException {
242
+ isTrueArgument ("additionalTimeoutMillis must not be negative" , additionalTimeoutMillis >= 0 );
188
243
FutureAsyncCompletionHandler <ByteBuf > future = new FutureAsyncCompletionHandler <ByteBuf >();
189
- readAsync (numBytes , future , additionalTimeout );
244
+ readAsync (numBytes , future , combinedTimeout ( readTimeoutMillis , additionalTimeoutMillis ) );
190
245
return future .get ();
191
246
}
192
247
@@ -211,18 +266,27 @@ public void operationComplete(final ChannelFuture future) throws Exception {
211
266
212
267
@ Override
213
268
public void readAsync (final int numBytes , final AsyncCompletionHandler <ByteBuf > handler ) {
214
- readAsync (numBytes , handler , 0 );
269
+ readAsync (numBytes , handler , readTimeoutMillis );
215
270
}
216
271
217
- private void readAsync (final int numBytes , final AsyncCompletionHandler <ByteBuf > handler , final int additionalTimeout ) {
218
- scheduleReadTimeout (additionalTimeout );
272
+ /**
273
+ * @param numBytes Must be equal to {@link #pendingReader}{@code .numBytes} when called by a Netty channel handler.
274
+ * @param handler Must be equal to {@link #pendingReader}{@code .handler} when called by a Netty channel handler.
275
+ * @param readTimeoutMillis Must be equal to {@link #NO_SCHEDULE_TIME} when called by a Netty channel handler.
276
+ * Timeouts may be scheduled only by the public read methods. Taking into account that concurrent pending
277
+ * readers are not allowed, there must not be a situation when threads attempt to schedule a timeout
278
+ * before the previous one is either cancelled or completed.
279
+ */
280
+ private void readAsync (final int numBytes , final AsyncCompletionHandler <ByteBuf > handler , final long readTimeoutMillis ) {
219
281
ByteBuf buffer = null ;
220
282
Throwable exceptionResult = null ;
221
283
synchronized (this ) {
222
284
exceptionResult = pendingException ;
223
285
if (exceptionResult == null ) {
224
286
if (!hasBytesAvailable (numBytes )) {
225
- pendingReader = new PendingReader (numBytes , handler );
287
+ if (pendingReader == null ) {//called by a public read method
288
+ pendingReader = new PendingReader (numBytes , handler , scheduleReadTimeout (readTimeoutTask , readTimeoutMillis ));
289
+ }
226
290
} else {
227
291
CompositeByteBuf composite = allocator .compositeBuffer (pendingInboundBuffers .size ());
228
292
int bytesNeeded = numBytes ;
@@ -245,13 +309,16 @@ private void readAsync(final int numBytes, final AsyncCompletionHandler<ByteBuf>
245
309
buffer = new NettyByteBuf (composite ).flip ();
246
310
}
247
311
}
312
+ if (!(exceptionResult == null && buffer == null )//the read operation has completed
313
+ && pendingReader != null ) {//we need to clear the pending reader
314
+ cancel (pendingReader .timeout );
315
+ this .pendingReader = null ;
316
+ }
248
317
}
249
318
if (exceptionResult != null ) {
250
- disableReadTimeout ();
251
319
handler .failed (exceptionResult );
252
320
}
253
321
if (buffer != null ) {
254
- disableReadTimeout ();
255
322
handler .completed (buffer );
256
323
}
257
324
}
@@ -275,14 +342,12 @@ private void handleReadResponse(final io.netty.buffer.ByteBuf buffer, final Thro
275
342
} else {
276
343
pendingException = t ;
277
344
}
278
- if (pendingReader != null ) {
279
- localPendingReader = pendingReader ;
280
- pendingReader = null ;
281
- }
345
+ localPendingReader = pendingReader ;
282
346
}
283
347
284
348
if (localPendingReader != null ) {
285
- readAsync (localPendingReader .numBytes , localPendingReader .handler );
349
+ //timeouts may be scheduled only by the public read methods
350
+ readAsync (localPendingReader .numBytes , localPendingReader .handler , NO_SCHEDULE_TIME );
286
351
}
287
352
}
288
353
@@ -358,10 +423,14 @@ public void exceptionCaught(final ChannelHandlerContext ctx, final Throwable t)
358
423
private static final class PendingReader {
359
424
private final int numBytes ;
360
425
private final AsyncCompletionHandler <ByteBuf > handler ;
426
+ @ Nullable
427
+ private final ScheduledFuture <?> timeout ;
361
428
362
- private PendingReader (final int numBytes , final AsyncCompletionHandler <ByteBuf > handler ) {
429
+ private PendingReader (
430
+ final int numBytes , final AsyncCompletionHandler <ByteBuf > handler , @ Nullable final ScheduledFuture <?> timeout ) {
363
431
this .numBytes = numBytes ;
364
432
this .handler = handler ;
433
+ this .timeout = timeout ;
365
434
}
366
435
}
367
436
@@ -445,47 +514,52 @@ public void operationComplete(final ChannelFuture future) {
445
514
}
446
515
}
447
516
448
- private void scheduleReadTimeout (final int additionalTimeout ) {
449
- adjustTimeout (false , additionalTimeout );
517
+ private static void cancel (@ Nullable final Future <?> f ) {
518
+ if (f != null ) {
519
+ f .cancel (false );
520
+ }
450
521
}
451
522
452
- private void disableReadTimeout () {
453
- adjustTimeout (true , 0 );
523
+ private static long combinedTimeout (final long timeout , final int additionalTimeout ) {
524
+ if (timeout == NO_SCHEDULE_TIME ) {
525
+ return NO_SCHEDULE_TIME ;
526
+ } else {
527
+ return Math .addExact (timeout , additionalTimeout );
528
+ }
454
529
}
455
530
456
- private void adjustTimeout (final boolean disable , final int additionalTimeout ) {
457
- if (isClosed ) {
458
- return ;
459
- }
460
- ChannelHandler timeoutHandler = channel .pipeline ().get (READ_HANDLER_NAME );
461
- if (timeoutHandler != null ) {
462
- final ReadTimeoutHandler readTimeoutHandler = (ReadTimeoutHandler ) timeoutHandler ;
463
- final ChannelHandlerContext handlerContext = channel .pipeline ().context (timeoutHandler );
464
- EventExecutor executor = handlerContext .executor ();
465
-
466
- if (disable ) {
467
- if (executor .inEventLoop ()) {
468
- readTimeoutHandler .removeTimeout (handlerContext );
469
- } else {
470
- executor .submit (new Runnable () {
471
- @ Override
472
- public void run () {
473
- readTimeoutHandler .removeTimeout (handlerContext );
474
- }
475
- });
476
- }
477
- } else {
478
- if (executor .inEventLoop ()) {
479
- readTimeoutHandler .scheduleTimeout (handlerContext , additionalTimeout );
480
- } else {
481
- executor .submit (new Runnable () {
482
- @ Override
483
- public void run () {
484
- readTimeoutHandler .scheduleTimeout (handlerContext , additionalTimeout );
485
- }
486
- });
487
- }
531
+ private static ScheduledFuture <?> scheduleReadTimeout (@ Nullable final ReadTimeoutTask readTimeoutTask , final long timeoutMillis ) {
532
+ if (timeoutMillis == NO_SCHEDULE_TIME ) {
533
+ return null ;
534
+ } else {
535
+ //assert readTimeoutTask != null : "readTimeoutTask must be initialized if read timeouts are enabled";
536
+ return readTimeoutTask .schedule (timeoutMillis );
537
+ }
538
+ }
539
+
540
+ @ ThreadSafe
541
+ private static final class ReadTimeoutTask implements Runnable {
542
+ private final ChannelHandlerContext ctx ;
543
+
544
+ private ReadTimeoutTask (final ChannelHandlerContext timeoutChannelHandlerContext ) {
545
+ ctx = timeoutChannelHandlerContext ;
546
+ }
547
+
548
+ @ Override
549
+ public void run () {
550
+ try {
551
+ if (ctx .channel ().isOpen ()) {
552
+ ctx .fireExceptionCaught (ReadTimeoutException .INSTANCE );
553
+ ctx .close ();
488
554
}
555
+ } catch (final Throwable t ) {
556
+ ctx .fireExceptionCaught (t );
489
557
}
558
+ }
559
+
560
+ private ScheduledFuture <?> schedule (final long timeoutMillis ) {
561
+ //assert timeoutMillis > 0 : timeoutMillis;
562
+ return ctx .executor ().schedule (this , timeoutMillis , MILLISECONDS );
563
+ }
490
564
}
491
565
}
0 commit comments