/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 org.apache.hadoop.hive.llap; import io.netty.buffer.ByteBuf; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import java.io.IOException; import java.io.OutputStream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * OutputStream to write to the Netty Channel */ public class ChannelOutputStream extends OutputStream { private static final Logger LOG = LoggerFactory.getLogger(ChannelOutputStream.class); private ChannelHandlerContext chc; private int bufSize; private String id; private ByteBuf buf; private byte[] singleByte = new byte[1]; private boolean closed = false; private final Object writeMonitor = new Object(); private final int maxPendingWrites; private volatile int pendingWrites = 0; private ChannelFutureListener writeListener = new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) { pendingWrites--; if (future.isCancelled()) { LOG.error("Write cancelled on ID " + id); } else if (!future.isSuccess()) { LOG.error("Write error on ID " + id, future.cause()); } synchronized (writeMonitor) { writeMonitor.notifyAll(); } } }; private ChannelFutureListener closeListener = new ChannelFutureListener() { @Override public void operationComplete(ChannelFuture future) { if (future.isCancelled()) { LOG.error("Close cancelled on ID " + id); } else if (!future.isSuccess()) { LOG.error("Close failed on ID " + id, future.cause()); } } }; public ChannelOutputStream(ChannelHandlerContext chc, String id, int bufSize, int maxOutstandingWrites) { this.chc = chc; this.id = id; this.bufSize = bufSize; this.buf = chc.alloc().buffer(bufSize); this.maxPendingWrites = maxOutstandingWrites; } @Override public void write(int b) throws IOException { singleByte[0] = (byte) b; write(singleByte, 0, 1); } @Override public void write(byte[] b) throws IOException { write(b, 0, b.length); } @Override public void write(byte[] b, int off, int len) throws IOException { int currentOffset = off; int bytesRemaining = len; while (bytesRemaining + buf.readableBytes() > bufSize) { int iterationLen = bufSize - buf.readableBytes(); writeInternal(b, currentOffset, iterationLen); currentOffset += iterationLen; bytesRemaining -= iterationLen; } if (bytesRemaining > 0) { writeInternal(b, currentOffset, bytesRemaining); } } @Override public void flush() throws IOException { if (buf.isReadable()) { writeToChannel(); } chc.flush(); } @Override public void close() throws IOException { if (closed) { throw new IOException("Already closed: " + id); } try { flush(); } catch (IOException err) { LOG.error("Error flushing stream before close", err); } closed = true; // Wait for all writes to finish before we actually close. waitForWritesToFinish(0); try { chc.close().addListener(closeListener); } finally { buf.release(); buf = null; chc = null; closed = true; } } private void waitForWritesToFinish(int desiredWriteCount) throws IOException { synchronized (writeMonitor) { // to prevent spurious wake up while (pendingWrites > desiredWriteCount) { try { writeMonitor.wait(); } catch (InterruptedException ie) { throw new IOException("Interrupted while waiting for write operations to finish for " + id); } } } } private void writeToChannel() throws IOException { if (closed) { throw new IOException("Already closed: " + id); } // Wait if we have exceeded our max pending write count waitForWritesToFinish(maxPendingWrites - 1); pendingWrites++; chc.writeAndFlush(buf.copy()).addListener(writeListener); buf.clear(); } private void writeInternal(byte[] b, int off, int len) throws IOException { if (closed) { throw new IOException("Already closed: " + id); } buf.writeBytes(b, off, len); if (buf.readableBytes() >= bufSize) { writeToChannel(); } } }