/* * Copyright (C) 2015 SoftIndex LLC. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.datakernel.stream.processor; import io.datakernel.bytebuf.ByteBuf; import io.datakernel.bytebuf.ByteBufPool; import io.datakernel.eventloop.Eventloop; import io.datakernel.exception.ParseException; import io.datakernel.jmx.JmxAttribute; import io.datakernel.jmx.ValueStats; import io.datakernel.serializer.BufferSerializer; import io.datakernel.stream.AbstractStreamTransformer_1_1; import io.datakernel.stream.StreamDataReceiver; import io.datakernel.util.MemSize; import java.util.ArrayDeque; import static java.lang.Math.min; /** * Represent deserializer which deserializes data from ByteBuffer to some type. Is a {@link AbstractStreamTransformer_1_1} * which receives ByteBufs and streams specified type. * * @param <T> original type of data */ public final class StreamBinaryDeserializer<T> extends AbstractStreamTransformer_1_1<ByteBuf, T> { private final BufferSerializer<T> valueSerializer; private int maxMessageSize = (int) (StreamBinarySerializer.MAX_SIZE.get()); private int buffersToSuspend = 2; private InputConsumer inputConsumer; private OutputProducer outputProducer; public interface Inspector extends AbstractStreamTransformer_1_1.Inspector { void onInput(ByteBuf buf); void onOutput(); } public static class JmxInspector<T> extends AbstractStreamTransformer_1_1.JmxInspector implements Inspector { private static final double SMOOTHING_WINDOW = ValueStats.SMOOTHING_WINDOW_1_MINUTE; private final ValueStats inputBufs = ValueStats.create(SMOOTHING_WINDOW); private long outputItems; @Override public void onInput(ByteBuf buf) { inputBufs.recordValue(buf.readRemaining()); } @Override public void onOutput() { outputItems++; } @JmxAttribute public ValueStats getInputBufs() { return inputBufs; } @JmxAttribute public long getOutputItems() { return outputItems; } } // region creators private StreamBinaryDeserializer(Eventloop eventloop, BufferSerializer<T> valueSerializer) { super(eventloop); this.valueSerializer = valueSerializer; rebuild(); } private void rebuild() { if (outputProducer != null) outputProducer.buf.recycle(); inputConsumer = new InputConsumer(); outputProducer = new OutputProducer(new ArrayDeque<ByteBuf>(), maxMessageSize, valueSerializer, buffersToSuspend); } @Override protected AbstractInputConsumer getInputImpl() { return inputConsumer; } @Override protected AbstractOutputProducer getOutputImpl() { return outputProducer; } /** * Creates a new instance of this class with default size of byte buffer pool - 16 * * @param eventloop event loop in which serializer will run * @param valueSerializer specified BufferSerializer for this type */ public static <T> StreamBinaryDeserializer<T> create(Eventloop eventloop, BufferSerializer<T> valueSerializer) { return new StreamBinaryDeserializer<>(eventloop, valueSerializer); } public StreamBinaryDeserializer<T> withBuffersToSuspend(int buffersToSuspend) { this.buffersToSuspend = buffersToSuspend; rebuild(); return this; } public StreamBinaryDeserializer<T> withMaxMessageSize(int maxMessageSize) { this.maxMessageSize = maxMessageSize; rebuild(); return this; } public StreamBinaryDeserializer<T> withMaxMessageSize(MemSize maxMessageSize) { return withMaxMessageSize((int) maxMessageSize.get()); } public StreamBinaryDeserializer<T> withInspector(Inspector inspector) { super.inspector = inspector; rebuild(); return this; } // endregion private final class InputConsumer extends AbstractInputConsumer { @Override protected void onUpstreamEndOfStream() { outputProducer.produce(); } @Override public StreamDataReceiver<ByteBuf> getDataReceiver() { return outputProducer; } } private final class OutputProducer extends AbstractOutputProducer implements StreamDataReceiver<ByteBuf> { private final ArrayDeque<ByteBuf> byteBufs; public static final int MAX_HEADER_BYTES = 3; private final int maxMessageSize; public static final int INITIAL_BUFFER_SIZE = 10; private final BufferSerializer<T> valueSerializer; private final int buffersToSuspend; private ByteBuf buf; private int dataSize; private final Inspector inspector = (Inspector) StreamBinaryDeserializer.this.inspector; private OutputProducer(ArrayDeque<ByteBuf> byteBufs, int maxMessageSize, BufferSerializer<T> valueSerializer, int buffersToSuspend) { this.byteBufs = byteBufs; this.maxMessageSize = maxMessageSize; this.valueSerializer = valueSerializer; this.buffersToSuspend = buffersToSuspend; this.buf = ByteBufPool.allocate(OutputProducer.INITIAL_BUFFER_SIZE); } @Override public void onData(ByteBuf buf) { if (inspector != null) inspector.onInput(buf); this.byteBufs.offer(buf); outputProducer.produce(); if (this.byteBufs.size() == this.buffersToSuspend) { inputConsumer.suspend(); } } @Override protected void onDownstreamSuspended() { inputConsumer.suspend(); } @Override protected void onDownstreamResumed() { inputConsumer.resume(); resumeProduce(); } @Override protected void doProduce() { try { while (isStatusReady()) { ByteBuf nextBuf = byteBufs.peek(); if (nextBuf == null) break; while (isStatusReady() && nextBuf.readRemaining() > 0) { if (dataSize == 0) { // read message header: if (!buf.canRead() && nextBuf.readRemaining() >= MAX_HEADER_BYTES) { int sizeLen = tryReadSize(nextBuf.array(), nextBuf.readPosition()); nextBuf.moveReadPosition(sizeLen); if (sizeLen > MAX_HEADER_BYTES) throw new ParseException("Parsed size length > MAX_HEADER_BYTES"); buf.rewind(); } else { int readSize = min(nextBuf.readRemaining(), MAX_HEADER_BYTES - buf.readRemaining()); buf = ByteBufPool.append(buf, nextBuf.array(), nextBuf.readPosition(), readSize); nextBuf.moveReadPosition(readSize); int sizeLen = tryReadSize(buf.array(), buf.readPosition()); if (sizeLen > buf.readRemaining()) { // Read past last position - incomplete varint in buffer, waiting for more bytes dataSize = 0; break; } int unreadSize = buf.readRemaining() - sizeLen; nextBuf.moveReadPosition(-unreadSize); buf.rewind(); } if (dataSize >= maxMessageSize) throw new ParseException("Parsed data size > message size"); } // read message body: T item; if (!buf.canRead() && nextBuf.readRemaining() >= dataSize) { int initialHead = nextBuf.readPosition(); try { item = valueSerializer.deserialize(nextBuf); } catch (Exception e) { throw new ParseException("Cannot deserialize stream ", e); } if ((nextBuf.readPosition() - initialHead) != dataSize) throw new ParseException("Deserialized size != parsed data size"); dataSize = 0; } else { int readSize = min(nextBuf.readRemaining(), dataSize - buf.readRemaining()); buf = ByteBufPool.append(buf, nextBuf.array(), nextBuf.readPosition(), readSize); nextBuf.moveReadPosition(readSize); if (buf.readRemaining() != dataSize) break; try { item = valueSerializer.deserialize(buf); } catch (Exception e) { throw new ParseException("Cannot finish deserialization", e); } if (buf.canRead()) throw new ParseException("Deserialized size != parsed data size"); dataSize = 0; buf.rewind(); } if (inspector != null) inspector.onOutput(); downstreamDataReceiver.onData(item); } if (getProducerStatus().isClosed()) return; if (nextBuf.canRead()) { return; } ByteBuf poolBuffer = byteBufs.poll(); assert poolBuffer == nextBuf; nextBuf.recycle(); } if (byteBufs.isEmpty()) { if (inputConsumer.getConsumerStatus().isClosed()) { outputProducer.sendEndOfStream(); } else { if (!isStatusReady()) { resumeProduce(); } } } } catch (ParseException e) { closeWithError(e); } } @Override protected void doCleanup() { recycleBufs(); } private int tryReadSize(byte[] buf, int off) { byte b = buf[off]; if (b >= 0) { dataSize = b; return 1; } dataSize = b & 0x7f; b = buf[off + 1]; if (b >= 0) { dataSize |= (b << 7); return 2; } dataSize |= ((b & 0x7f) << 7); b = buf[off + 2]; if (b >= 0) { dataSize |= (b << 14); return 3; } dataSize = Integer.MAX_VALUE; return Integer.MAX_VALUE; } public void recycleBufs() { if (buf != null) { buf.recycle(); buf = null; } for (ByteBuf byteBuf : byteBufs) { byteBuf.recycle(); } byteBufs.clear(); } } }