View Javadoc

1   /**
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *     http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing, software
13   * distributed under the License is distributed on an "AS IS" BASIS,
14   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15   * See the License for the specific language governing permissions and
16   * limitations under the License.
17   */
18  package org.apache.hadoop.hbase.io;
19  
20  import java.io.IOException;
21  import java.io.InputStream;
22  import java.lang.reflect.InvocationTargetException;
23  import java.lang.reflect.Method;
24  
25  import org.apache.commons.io.IOUtils;
26  import org.apache.commons.logging.Log;
27  import org.apache.commons.logging.LogFactory;
28  import org.apache.hadoop.fs.FSDataInputStream;
29  import org.apache.hadoop.fs.FileSystem;
30  import org.apache.hadoop.fs.Path;
31  import org.apache.hadoop.hbase.fs.HFileSystem;
32  
33  import com.google.common.annotations.VisibleForTesting;
34  
35  /**
36   * Wrapper for input stream(s) that takes care of the interaction of FS and HBase checksums,
37   * as well as closing streams. Initialization is not thread-safe, but normal operation is;
38   * see method comments.
39   */
40  public class FSDataInputStreamWrapper {
41    private static final Log LOG = LogFactory.getLog(FSDataInputStreamWrapper.class);
42    private static final boolean isLogTraceEnabled = LOG.isTraceEnabled();
43  
44    private final HFileSystem hfs;
45    private final Path path;
46    private final FileLink link;
47    private final boolean doCloseStreams;
48  
49    /** Two stream handles, one with and one without FS-level checksum.
50     * HDFS checksum setting is on FS level, not single read level, so you have to keep two
51     * FS objects and two handles open to interleave different reads freely, which is very sad.
52     * This is what we do:
53     * 1) First, we need to read the trailer of HFile to determine checksum parameters.
54     *  We always use FS checksum to do that, so ctor opens {@link #stream}.
55     * 2.1) After that, if HBase checksum is not used, we'd just always use {@link #stream};
56     * 2.2) If HBase checksum can be used, we'll open {@link #streamNoFsChecksum},
57     *  and close {@link #stream}. User MUST call prepareForBlockReader for that to happen;
58     *  if they don't, (2.1) will be the default.
59     * 3) The users can call {@link #shouldUseHBaseChecksum()}, and pass its result to
60     *  {@link #getStream(boolean)} to get stream (if Java had out/pointer params we could
61     *  return both in one call). This stream is guaranteed to be set.
62     * 4) The first time HBase checksum fails, one would call {@link #fallbackToFsChecksum(int)}.
63     * That will take lock, and open {@link #stream}. While this is going on, others will
64     * continue to use the old stream; if they also want to fall back, they'll also call
65     * {@link #fallbackToFsChecksum(int)}, and block until {@link #stream} is set.
66     * 5) After some number of checksumOk() calls, we will go back to using HBase checksum.
67     * We will have 2 handles; however we presume checksums fail so rarely that we don't care.
68     */
69    private volatile FSDataInputStream stream = null;
70    private volatile FSDataInputStream streamNoFsChecksum = null;
71    private final Object streamNoFsChecksumFirstCreateLock = new Object();
72  
73    // The configuration states that we should validate hbase checksums
74    private boolean useHBaseChecksumConfigured;
75  
76    // Record the current state of this reader with respect to
77    // validating checkums in HBase. This is originally set the same
78    // value as useHBaseChecksumConfigured, but can change state as and when
79    // we encounter checksum verification failures.
80    private volatile boolean useHBaseChecksum;
81  
82    // In the case of a checksum failure, do these many succeeding
83    // reads without hbase checksum verification.
84    private volatile int hbaseChecksumOffCount = -1;
85  
86    private Boolean instanceOfCanUnbuffer = null;
87    // Using reflection to get org.apache.hadoop.fs.CanUnbuffer#unbuffer method to avoid compilation
88    // errors against Hadoop pre 2.6.4 and 2.7.1 versions.
89    private Method unbuffer = null;
90  
91    public FSDataInputStreamWrapper(FileSystem fs, Path path) throws IOException {
92      this(fs, null, path, false);
93    }
94  
95    public FSDataInputStreamWrapper(FileSystem fs, Path path, boolean dropBehind) throws IOException {
96      this(fs, null, path, dropBehind);
97    }
98  
99    public FSDataInputStreamWrapper(FileSystem fs, FileLink link) throws IOException {
100     this(fs, link, null, false);
101   }
102   public FSDataInputStreamWrapper(FileSystem fs, FileLink link,
103                                   boolean dropBehind) throws IOException {
104     this(fs, link, null, dropBehind);
105   }
106 
107   private FSDataInputStreamWrapper(FileSystem fs, FileLink link,
108                                    Path path, boolean dropBehind) throws IOException {
109     assert (path == null) != (link == null);
110     this.path = path;
111     this.link = link;
112     this.doCloseStreams = true;
113     // If the fs is not an instance of HFileSystem, then create an instance of HFileSystem
114     // that wraps over the specified fs. In this case, we will not be able to avoid
115     // checksumming inside the filesystem.
116     this.hfs = (fs instanceof HFileSystem) ? (HFileSystem)fs : new HFileSystem(fs);
117 
118     // Initially we are going to read the tail block. Open the reader w/FS checksum.
119     this.useHBaseChecksumConfigured = this.useHBaseChecksum = false;
120     this.stream = (link != null) ? link.open(hfs) : hfs.open(path);
121     try {
122       this.stream.setDropBehind(dropBehind);
123     } catch (Exception e) {
124       // Skipped.
125     }
126   }
127 
128 
129   /**
130    * Prepares the streams for block reader. NOT THREAD SAFE. Must be called once, after any
131    * reads finish and before any other reads start (what happens in reality is we read the
132    * tail, then call this based on what's in the tail, then read blocks).
133    * @param forceNoHBaseChecksum Force not using HBase checksum.
134    */
135   public void prepareForBlockReader(boolean forceNoHBaseChecksum) throws IOException {
136     if (hfs == null) return;
137     assert this.stream != null && !this.useHBaseChecksumConfigured;
138     boolean useHBaseChecksum =
139         !forceNoHBaseChecksum && hfs.useHBaseChecksum() && (hfs.getNoChecksumFs() != hfs);
140 
141     if (useHBaseChecksum) {
142       FileSystem fsNc = hfs.getNoChecksumFs();
143       this.streamNoFsChecksum = (link != null) ? link.open(fsNc) : fsNc.open(path);
144       this.useHBaseChecksumConfigured = this.useHBaseChecksum = useHBaseChecksum;
145       // Close the checksum stream; we will reopen it if we get an HBase checksum failure.
146       this.stream.close();
147       this.stream = null;
148     }
149   }
150 
151   /** For use in tests. */
152   @VisibleForTesting
153   public FSDataInputStreamWrapper(FSDataInputStream fsdis) {
154     this(fsdis, fsdis);
155   }
156 
157   /** For use in tests. */
158   @VisibleForTesting
159   public FSDataInputStreamWrapper(FSDataInputStream fsdis, FSDataInputStream noChecksum) {
160     doCloseStreams = false;
161     stream = fsdis;
162     streamNoFsChecksum = noChecksum;
163     path = null;
164     link = null;
165     hfs = null;
166     useHBaseChecksumConfigured = useHBaseChecksum = false;
167   }
168 
169   /**
170    * @return Whether we are presently using HBase checksum.
171    */
172   public boolean shouldUseHBaseChecksum() {
173     return this.useHBaseChecksum;
174   }
175 
176   /**
177    * Get the stream to use. Thread-safe.
178    * @param useHBaseChecksum must be the value that shouldUseHBaseChecksum has returned
179    *  at some point in the past, otherwise the result is undefined.
180    */
181   public FSDataInputStream getStream(boolean useHBaseChecksum) {
182     return useHBaseChecksum ? this.streamNoFsChecksum : this.stream;
183   }
184 
185   /**
186    * Read from non-checksum stream failed, fall back to FS checksum. Thread-safe.
187    * @param offCount For how many checksumOk calls to turn off the HBase checksum.
188    */
189   public FSDataInputStream fallbackToFsChecksum(int offCount) throws IOException {
190     // checksumOffCount is speculative, but let's try to reset it less.
191     boolean partOfConvoy = false;
192     if (this.stream == null) {
193       synchronized (streamNoFsChecksumFirstCreateLock) {
194         partOfConvoy = (this.stream != null);
195         if (!partOfConvoy) {
196           this.stream = (link != null) ? link.open(hfs) : hfs.open(path);
197         }
198       }
199     }
200     if (!partOfConvoy) {
201       this.useHBaseChecksum = false;
202       this.hbaseChecksumOffCount = offCount;
203     }
204     return this.stream;
205   }
206 
207   /** Report that checksum was ok, so we may ponder going back to HBase checksum. */
208   public void checksumOk() {
209     if (this.useHBaseChecksumConfigured && !this.useHBaseChecksum
210         && (this.hbaseChecksumOffCount-- < 0)) {
211       // The stream we need is already open (because we were using HBase checksum in the past).
212       assert this.streamNoFsChecksum != null;
213       this.useHBaseChecksum = true;
214     }
215   }
216 
217   /** Close stream(s) if necessary. */
218   public void close() throws IOException {
219     if (!doCloseStreams) {
220       return;
221     }
222     // we do not care about the close exception as it is for reading, no data loss issue.
223     IOUtils.closeQuietly(streamNoFsChecksum);
224     IOUtils.closeQuietly(stream);
225   }
226 
227   public HFileSystem getHfs() {
228     return this.hfs;
229   }
230 
231   /**
232    * This will free sockets and file descriptors held by the stream only when the stream implements
233    * org.apache.hadoop.fs.CanUnbuffer. NOT THREAD SAFE. Must be called only when all the clients
234    * using this stream to read the blocks have finished reading. If by chance the stream is
235    * unbuffered and there are clients still holding this stream for read then on next client read
236    * request a new socket will be opened by Datanode without client knowing about it and will serve
237    * its read request. Note: If this socket is idle for some time then the DataNode will close the
238    * socket and the socket will move into CLOSE_WAIT state and on the next client request on this
239    * stream, the current socket will be closed and a new socket will be opened to serve the
240    * requests.
241    */
242   @SuppressWarnings({ "rawtypes" })
243   public void unbuffer() {
244     FSDataInputStream stream = this.getStream(this.shouldUseHBaseChecksum());
245     if (stream != null) {
246       InputStream wrappedStream = stream.getWrappedStream();
247       // CanUnbuffer interface was added as part of HDFS-7694 and the fix is available in Hadoop
248       // 2.6.4+ and 2.7.1+ versions only so check whether the stream object implements the
249       // CanUnbuffer interface or not and based on that call the unbuffer api.
250       final Class<? extends InputStream> streamClass = wrappedStream.getClass();
251       if (this.instanceOfCanUnbuffer == null) {
252         // To ensure we compute whether the stream is instance of CanUnbuffer only once.
253         this.instanceOfCanUnbuffer = false;
254         Class<?>[] streamInterfaces = streamClass.getInterfaces();
255         for (Class c : streamInterfaces) {
256           if (c.getCanonicalName().toString().equals("org.apache.hadoop.fs.CanUnbuffer")) {
257             try {
258               this.unbuffer = streamClass.getDeclaredMethod("unbuffer");
259             } catch (NoSuchMethodException | SecurityException e) {
260               if (isLogTraceEnabled) {
261                 LOG.trace("Failed to find 'unbuffer' method in class " + streamClass
262                     + " . So there may be a TCP socket connection "
263                     + "left open in CLOSE_WAIT state.", e);
264               }
265               return;
266             }
267             this.instanceOfCanUnbuffer = true;
268             break;
269           }
270         }
271       }
272       if (this.instanceOfCanUnbuffer) {
273         try {
274           this.unbuffer.invoke(wrappedStream);
275         } catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
276           if (isLogTraceEnabled) {
277             LOG.trace("Failed to invoke 'unbuffer' method in class " + streamClass
278                 + " . So there may be a TCP socket connection left open in CLOSE_WAIT state.", e);
279           }
280         }
281       } else {
282         if (isLogTraceEnabled) {
283           LOG.trace("Failed to find 'unbuffer' method in class " + streamClass
284               + " . So there may be a TCP socket connection "
285               + "left open in CLOSE_WAIT state. For more details check "
286               + "https://issues.apache.org/jira/browse/HBASE-9393");
287         }
288       }
289     }
290   }
291 }