001/**
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing, software
013 * distributed under the License is distributed on an "AS IS" BASIS,
014 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018package org.apache.hadoop.hbase.regionserver.wal;
019
020import java.io.IOException;
021import java.io.InterruptedIOException;
022import java.io.OutputStream;
023import java.nio.ByteBuffer;
024import java.util.concurrent.CompletableFuture;
025import java.util.concurrent.ExecutionException;
026import java.util.function.Consumer;
027import org.apache.hadoop.fs.FileSystem;
028import org.apache.hadoop.fs.Path;
029import org.apache.hadoop.hbase.Cell;
030import org.apache.hadoop.hbase.io.ByteBufferWriter;
031import org.apache.hadoop.hbase.io.asyncfs.AsyncFSOutput;
032import org.apache.hadoop.hbase.io.asyncfs.AsyncFSOutputHelper;
033import org.apache.hadoop.hbase.util.CommonFSUtils.StreamLacksCapabilityException;
034import org.apache.hadoop.hbase.wal.AsyncFSWALProvider;
035import org.apache.hadoop.hbase.wal.WAL.Entry;
036import org.apache.yetus.audience.InterfaceAudience;
037import org.slf4j.Logger;
038import org.slf4j.LoggerFactory;
039
040import org.apache.hbase.thirdparty.com.google.common.base.Throwables;
041import org.apache.hbase.thirdparty.io.netty.channel.Channel;
042import org.apache.hbase.thirdparty.io.netty.channel.EventLoopGroup;
043
044import org.apache.hadoop.hbase.shaded.protobuf.generated.WALProtos.WALHeader;
045import org.apache.hadoop.hbase.shaded.protobuf.generated.WALProtos.WALTrailer;
046
047/**
048 * AsyncWriter for protobuf-based WAL.
049 */
050@InterfaceAudience.Private
051public class AsyncProtobufLogWriter extends AbstractProtobufLogWriter
052    implements AsyncFSWALProvider.AsyncWriter {
053
054  private static final Logger LOG = LoggerFactory.getLogger(AsyncProtobufLogWriter.class);
055
056  private final EventLoopGroup eventLoopGroup;
057
058  private final Class<? extends Channel> channelClass;
059
060  private AsyncFSOutput output;
061
062  private static final class OutputStreamWrapper extends OutputStream
063      implements ByteBufferWriter {
064
065    private final AsyncFSOutput out;
066
067    private final byte[] oneByteBuf = new byte[1];
068
069    @Override
070    public void write(int b) throws IOException {
071      oneByteBuf[0] = (byte) b;
072      write(oneByteBuf);
073    }
074
075    public OutputStreamWrapper(AsyncFSOutput out) {
076      this.out = out;
077    }
078
079    @Override
080    public void write(ByteBuffer b, int off, int len) throws IOException {
081      ByteBuffer bb = b.duplicate();
082      bb.position(off);
083      bb.limit(off + len);
084      out.write(bb);
085    }
086
087    @Override
088    public void writeInt(int i) throws IOException {
089      out.writeInt(i);
090    }
091
092    @Override
093    public void write(byte[] b, int off, int len) throws IOException {
094      out.write(b, off, len);
095    }
096
097    @Override
098    public void close() throws IOException {
099      out.close();
100    }
101  }
102
103  private OutputStream asyncOutputWrapper;
104
105  public AsyncProtobufLogWriter(EventLoopGroup eventLoopGroup,
106      Class<? extends Channel> channelClass) {
107    this.eventLoopGroup = eventLoopGroup;
108    this.channelClass = channelClass;
109  }
110
111  /*
112   * @return class name which is recognized by hbase-1.x to avoid ProtobufLogReader throwing error:
113   *   IOException: Got unknown writer class: AsyncProtobufLogWriter
114   */
115  @Override
116  protected String getWriterClassName() {
117    return "ProtobufLogWriter";
118  }
119
120  @Override
121  public void append(Entry entry) {
122    int buffered = output.buffered();
123    entry.setCompressionContext(compressionContext);
124    try {
125      entry.getKey().
126        getBuilder(compressor).setFollowingKvCount(entry.getEdit().size()).build()
127          .writeDelimitedTo(asyncOutputWrapper);
128    } catch (IOException e) {
129      throw new AssertionError("should not happen", e);
130    }
131    try {
132      for (Cell cell : entry.getEdit().getCells()) {
133        cellEncoder.write(cell);
134      }
135    } catch (IOException e) {
136      throw new AssertionError("should not happen", e);
137    }
138    length.addAndGet(output.buffered() - buffered);
139  }
140
141  @Override
142  public CompletableFuture<Long> sync() {
143    return output.flush(false);
144  }
145
146  @Override
147  public synchronized void close() throws IOException {
148    if (this.output == null) {
149      return;
150    }
151    try {
152      writeWALTrailer();
153      output.close();
154    } catch (Exception e) {
155      LOG.warn("normal close failed, try recover", e);
156      output.recoverAndClose(null);
157    }
158    this.output = null;
159  }
160
161  public AsyncFSOutput getOutput() {
162    return this.output;
163  }
164
165  @Override
166  protected void initOutput(FileSystem fs, Path path, boolean overwritable, int bufferSize,
167      short replication, long blockSize) throws IOException, StreamLacksCapabilityException {
168    this.output = AsyncFSOutputHelper.createOutput(fs, path, overwritable, false, replication,
169        blockSize, eventLoopGroup, channelClass);
170    this.asyncOutputWrapper = new OutputStreamWrapper(output);
171  }
172
173  private long write(Consumer<CompletableFuture<Long>> action) throws IOException {
174    CompletableFuture<Long> future = new CompletableFuture<>();
175    action.accept(future);
176    try {
177      return future.get().longValue();
178    } catch (InterruptedException e) {
179      InterruptedIOException ioe = new InterruptedIOException();
180      ioe.initCause(e);
181      throw ioe;
182    } catch (ExecutionException e) {
183      Throwables.propagateIfPossible(e.getCause(), IOException.class);
184      throw new RuntimeException(e.getCause());
185    }
186  }
187
188  @Override
189  protected long writeMagicAndWALHeader(byte[] magic, WALHeader header) throws IOException {
190    return write(future -> {
191      output.write(magic);
192      try {
193        header.writeDelimitedTo(asyncOutputWrapper);
194      } catch (IOException e) {
195        // should not happen
196        throw new AssertionError(e);
197      }
198      output.flush(false).whenComplete((len, error) -> {
199        if (error != null) {
200          future.completeExceptionally(error);
201        } else {
202          future.complete(len);
203        }
204      });
205    });
206  }
207
208  @Override
209  protected long writeWALTrailerAndMagic(WALTrailer trailer, byte[] magic) throws IOException {
210    return write(future -> {
211      try {
212        trailer.writeTo(asyncOutputWrapper);
213      } catch (IOException e) {
214        // should not happen
215        throw new AssertionError(e);
216      }
217      output.writeInt(trailer.getSerializedSize());
218      output.write(magic);
219      output.flush(false).whenComplete((len, error) -> {
220        if (error != null) {
221          future.completeExceptionally(error);
222        } else {
223          future.complete(len);
224        }
225      });
226    });
227  }
228
229  @Override
230  protected OutputStream getOutputStreamForCellEncoder() {
231    return asyncOutputWrapper;
232  }
233}