/* * 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.logfs; import io.datakernel.async.CompletionCallback; import io.datakernel.async.ResultCallback; import io.datakernel.bytebuf.ByteBuf; import io.datakernel.eventloop.Eventloop; import io.datakernel.stream.AbstractStreamTransformer_1_1; import io.datakernel.stream.StreamConsumerDecorator; import io.datakernel.stream.StreamDataReceiver; import io.datakernel.stream.StreamStatus; import org.joda.time.format.DateTimeFormatter; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public final class LogStreamConsumer_ByteBuffer extends StreamConsumerDecorator<ByteBuf> { private final Logger logger = LoggerFactory.getLogger(this.getClass()); private final StreamWriteLog streamWriteLog; private LogStreamConsumer_ByteBuffer(Eventloop eventloop, DateTimeFormatter datetimeFormat, long fileSwitchPeriod, LogFileSystem fileSystem, String logPartition) { this.streamWriteLog = new StreamWriteLog(eventloop, datetimeFormat, fileSwitchPeriod, fileSystem, logPartition); setActualConsumer(streamWriteLog.getInput()); } public static LogStreamConsumer_ByteBuffer create(Eventloop eventloop, DateTimeFormatter datetimeFormat, long fileSwitchPeriod, LogFileSystem fileSystem, String logPartition) { return new LogStreamConsumer_ByteBuffer(eventloop, datetimeFormat, fileSwitchPeriod, fileSystem, logPartition); } public void setCompletionCallback(CompletionCallback completionCallback) { streamWriteLog.inputConsumer.callback = completionCallback; } public void setTag(Object tag) { streamWriteLog.setTag(tag); } private class StreamWriteLog extends AbstractStreamTransformer_1_1<ByteBuf, ByteBuf> { private InputConsumer inputConsumer; private OutputProducer outputProducer; protected StreamWriteLog(Eventloop eventloop, DateTimeFormatter datetimeFormat, long fileSwitchPeriod, LogFileSystem fileSystem, String logPartition) { super(eventloop); outputProducer = new OutputProducer(); inputConsumer = new InputConsumer(datetimeFormat, fileSwitchPeriod, fileSystem, logPartition); } @Override protected AbstractInputConsumer getInputImpl() { return inputConsumer; } @Override protected AbstractOutputProducer getOutputImpl() { return outputProducer; } private class InputConsumer extends AbstractInputConsumer implements StreamDataReceiver<ByteBuf> { private final String logPartition; private long currentPeriod = -1; private LogFile currentLogFile; private final DateTimeFormatter datetimeFormat; private final long fileSwitchPeriod; private final LogFileSystem fileSystem; private boolean createFile; private boolean newFile; private int activeWriters = 0; private CompletionCallback callback; public InputConsumer(DateTimeFormatter datetimeFormat, long fileSwitchPeriod, LogFileSystem fileSystem, String logPartition) { this.logPartition = logPartition; this.datetimeFormat = datetimeFormat; this.fileSwitchPeriod = fileSwitchPeriod; this.fileSystem = fileSystem; } @Override protected void onUpstreamEndOfStream() { logger.trace("{}: upstream producer {} endOfStream.", this, upstreamProducer); if (outputProducer.getDownstream() != null) { outputProducer.sendEndOfStream(); } if (activeWriters == 0 && !createFile) { zeroActiveWriters(); } } @Override public StreamDataReceiver<ByteBuf> getDataReceiver() { return this; } @Override public void onData(ByteBuf buf) { long timestamp = eventloop.currentTimeMillis(); long newPeriod = timestamp / fileSwitchPeriod; outputProducer.send(buf); if (newPeriod != currentPeriod && createFile) newFile = true; if (newPeriod != currentPeriod && !createFile) { currentPeriod = newPeriod; String chunkName = datetimeFormat.print(timestamp); if (currentLogFile == null || !chunkName.equals(currentLogFile.getName())) { createWriteStream(chunkName); } } } private void createWriteStream(final String newChunkName) { createFile = true; fileSystem.makeUniqueLogFile(logPartition, newChunkName, new ResultCallback<LogFile>() { @Override protected void onResult(LogFile result) { createFile = false; ++activeWriters; if (outputProducer.getDownstream() != null) { outputProducer.getDownstream().onProducerEndOfStream(); } currentLogFile = result; ConsumerErrorIgnoring consumerErrorIgnoring = new ConsumerErrorIgnoring(eventloop); fileSystem.write(logPartition, currentLogFile, consumerErrorIgnoring.getOutput(), createCloseCompletionCallback()); outputProducer.streamTo(consumerErrorIgnoring.getInput()); if (getConsumerStatus() == StreamStatus.END_OF_STREAM) { consumerErrorIgnoring.getInput().onProducerEndOfStream(); } if (getConsumerStatus() == StreamStatus.CLOSED_WITH_ERROR) { consumerErrorIgnoring.getInput().onProducerError(getConsumerException()); } if (newFile) { newFile = false; checkPeriod(); } } @Override protected void onException(Exception exception) { createFile = false; logger.error("{}: creating new unique log file with name {} and stream id {} failed.", LogStreamConsumer_ByteBuffer.this, newChunkName, logPartition); eventloop.schedule(1000L, new Runnable() { @Override public void run() { createWriteStream(newChunkName); } }); } }); } private void checkPeriod() { long timestamp = eventloop.currentTimeMillis(); currentPeriod = timestamp / fileSwitchPeriod; String chunkName = datetimeFormat.print(timestamp); if (currentLogFile == null || !chunkName.equals(currentLogFile.getName())) { createWriteStream(chunkName); } } private void zeroActiveWriters() { if (getConsumerStatus() == StreamStatus.END_OF_STREAM) { if (callback != null) { callback.setComplete(); callback = null; } } else if (error != null) { if (callback != null) { callback.setException(getConsumerException()); callback = null; } } else { resume(); } } private CompletionCallback createCloseCompletionCallback() { return new CompletionCallback() { @Override protected void onException(Exception e) { onCompleteOrException(); } @Override protected void onComplete() { onCompleteOrException(); } void onCompleteOrException() { --activeWriters; if (activeWriters == 0) { zeroActiveWriters(); } else { resume(); } } }; } } private class OutputProducer extends AbstractOutputProducer { @Override protected void doCleanup() { for (ByteBuf byteBuf : outputProducer.bufferedList) { byteBuf.recycle(); } outputProducer.bufferedList.clear(); } @Override protected void onDownstreamSuspended() { inputConsumer.suspend(); } @Override protected void onDownstreamResumed() { inputConsumer.resume(); } } } private class ConsumerErrorIgnoring extends AbstractStreamTransformer_1_1<ByteBuf, ByteBuf> { private InputConsumer upstreamConsumer; private OutputProducer downstreamProducer; protected ConsumerErrorIgnoring(Eventloop eventloop) { super(eventloop); upstreamConsumer = new InputConsumer(); downstreamProducer = new OutputProducer(); } @Override protected AbstractInputConsumer getInputImpl() { return upstreamConsumer; } @Override protected AbstractOutputProducer getOutputImpl() { return downstreamProducer; } private class InputConsumer extends AbstractInputConsumer { @Override protected void onUpstreamEndOfStream() { downstreamProducer.sendEndOfStream(); } @Override public StreamDataReceiver<ByteBuf> getDataReceiver() { return downstreamProducer.getDownstreamDataReceiver(); } } private class OutputProducer extends AbstractOutputProducer { @Override protected void onDownstreamSuspended() { upstreamConsumer.suspend(); } @Override protected void onDownstreamResumed() { upstreamConsumer.resume(); } @Override protected void onError(Exception e) { // do nothing } } } }