3
3
4
4
import io .netty .buffer .ByteBuf ;
5
5
import io .netty .buffer .ByteBufOutputStream ;
6
+ import io .netty .buffer .PooledByteBufAllocator ;
6
7
import io .netty .channel .ChannelHandlerContext ;
7
8
import io .netty .handler .codec .ByteToMessageDecoder ;
9
+ import io .netty .handler .codec .DecoderException ;
8
10
import org .apache .logging .log4j .LogManager ;
9
11
import org .apache .logging .log4j .Logger ;
10
12
14
16
import java .util .HashMap ;
15
17
import java .util .List ;
16
18
import java .util .Map ;
19
+ import java .util .concurrent .TimeUnit ;
17
20
import java .util .zip .Inflater ;
18
21
import java .util .zip .InflaterOutputStream ;
19
22
20
23
21
24
public class BeatsParser extends ByteToMessageDecoder {
22
25
private final static Logger logger = LogManager .getLogger (BeatsParser .class );
26
+ private final static long maxDirectMemory = io .netty .util .internal .PlatformDependent .maxDirectMemory ();
23
27
24
28
private Batch batch ;
25
29
@@ -45,15 +49,18 @@ private enum States {
45
49
private int requiredBytes = 0 ;
46
50
private int sequence = 0 ;
47
51
private boolean decodingCompressedBuffer = false ;
52
+ private long usedDirectMemory ;
53
+ private boolean closeCalled = false ;
48
54
49
55
@ Override
50
56
protected void decode (ChannelHandlerContext ctx , ByteBuf in , List <Object > out ) throws InvalidFrameProtocolException , IOException {
51
- if (!hasEnoughBytes (in )) {
52
- if (decodingCompressedBuffer ){
57
+ if (!hasEnoughBytes (in )) {
58
+ if (decodingCompressedBuffer ) {
53
59
throw new InvalidFrameProtocolException ("Insufficient bytes in compressed content to decode: " + currentState );
54
60
}
55
61
return ;
56
62
}
63
+ usedDirectMemory = ((PooledByteBufAllocator ) ctx .alloc ()).metric ().usedDirectMemory ();
57
64
58
65
switch (currentState ) {
59
66
case READ_HEADER : {
@@ -182,6 +189,13 @@ protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) t
182
189
183
190
case READ_COMPRESSED_FRAME : {
184
191
logger .trace ("Running: READ_COMPRESSED_FRAME" );
192
+
193
+ if (usedDirectMemory + requiredBytes > maxDirectMemory * 0.90 ) {
194
+ ctx .channel ().config ().setAutoRead (false );
195
+ ctx .close ();
196
+ closeCalled = true ;
197
+ throw new IOException ("not enough memory to decompress this from " + ctx .channel ().id ());
198
+ }
185
199
inflateCompressedFrame (ctx , in , (buffer ) -> {
186
200
transition (States .READ_HEADER );
187
201
@@ -192,16 +206,20 @@ protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) t
192
206
}
193
207
} finally {
194
208
decodingCompressedBuffer = false ;
209
+ ctx .channel ().config ().setAutoRead (false );
210
+ ctx .channel ().eventLoop ().schedule (() -> {
211
+ ctx .channel ().config ().setAutoRead (true );
212
+ }, 5 , TimeUnit .MILLISECONDS );
195
213
transition (States .READ_HEADER );
196
214
}
197
215
});
198
216
break ;
199
217
}
200
218
case READ_JSON : {
201
219
logger .trace ("Running: READ_JSON" );
202
- ((V2Batch )batch ).addMessage (sequence , in , requiredBytes );
203
- if (batch .isComplete ()) {
204
- if (logger .isTraceEnabled ()) {
220
+ ((V2Batch ) batch ).addMessage (sequence , in , requiredBytes );
221
+ if (batch .isComplete ()) {
222
+ if (logger .isTraceEnabled ()) {
205
223
logger .trace ("Sending batch size: " + this .batch .size () + ", windowSize: " + batch .getBatchSize () + " , seq: " + sequence );
206
224
}
207
225
out .add (batch );
@@ -260,6 +278,62 @@ private void batchComplete() {
260
278
batch = null ;
261
279
}
262
280
281
+ @ Override
282
+ public void channelRead (ChannelHandlerContext ctx , Object msg ) throws Exception {
283
+ //System.out.println("channelRead(" + ctx.channel().isActive() + ": " + ctx.channel().id() + ":" + currentState + ":" + decodingCompressedBuffer);
284
+ if (closeCalled ) {
285
+ ((ByteBuf ) msg ).release ();
286
+ //if(batch != null) batch.release();
287
+ return ;
288
+ }
289
+ usedDirectMemory = ((PooledByteBufAllocator ) ctx .alloc ()).metric ().usedDirectMemory ();
290
+
291
+ // If we're just beginning a new frame on this channel,
292
+ // don't accumulate more data for 25 ms if usage of direct memory is above 20%
293
+ //
294
+ // The goal here is to avoid thundering herd: many beats connecting and sending data
295
+ // at the same time. As some channels progress to other states they'll use more memory
296
+ // but also give it back once a full batch is read.
297
+ if ((!decodingCompressedBuffer ) && (this .currentState != States .READ_COMPRESSED_FRAME )) {
298
+ if (usedDirectMemory > (maxDirectMemory * 0.40 )) {
299
+ ctx .channel ().config ().setAutoRead (false );
300
+ //System.out.println("pausing reads on " + ctx.channel().id());
301
+ ctx .channel ().eventLoop ().schedule (() -> {
302
+ //System.out.println("resuming reads on " + ctx.channel().id());
303
+ ctx .channel ().config ().setAutoRead (true );
304
+ }, 200 , TimeUnit .MILLISECONDS );
305
+ } else {
306
+ //System.out.println("no need to pause reads on " + ctx.channel().id());
307
+ }
308
+ } else if (usedDirectMemory > maxDirectMemory * 0.90 ) {
309
+ ctx .channel ().config ().setAutoRead (false );
310
+ ctx .close ();
311
+ closeCalled = true ;
312
+ ((ByteBuf ) msg ).release ();
313
+ if (batch != null ) batch .release ();
314
+ throw new IOException ("about to explode, cut them all down " + ctx .channel ().id ());
315
+ }
316
+ super .channelRead (ctx , msg );
317
+ }
318
+
319
+ @ Override
320
+ public void exceptionCaught (ChannelHandlerContext ctx , Throwable cause ) throws Exception {
321
+ System .out .println (cause .getClass ().toString () + ":" + ctx .channel ().id ().toString () + ":" + this .currentState + "|" + cause .getMessage ());
322
+ if (cause instanceof DecoderException ) {
323
+ ctx .channel ().config ().setAutoRead (false );
324
+ if (!closeCalled ) ctx .close ();
325
+ } else if (cause instanceof OutOfMemoryError ) {
326
+ cause .printStackTrace ();
327
+ ctx .channel ().config ().setAutoRead (false );
328
+ if (!closeCalled ) ctx .close ();
329
+ } else if (cause instanceof IOException ) {
330
+ ctx .channel ().config ().setAutoRead (false );
331
+ if (!closeCalled ) ctx .close ();
332
+ } else {
333
+ super .exceptionCaught (ctx , cause );
334
+ }
335
+ }
336
+
263
337
@ FunctionalInterface
264
338
private interface CheckedConsumer <T > {
265
339
void accept (T t ) throws IOException ;
0 commit comments