View Javadoc

1   /**
2    *
3    * Licensed to the Apache Software Foundation (ASF) under one
4    * or more contributor license agreements.  See the NOTICE file
5    * distributed with this work for additional information
6    * regarding copyright ownership.  The ASF licenses this file
7    * to you under the Apache License, Version 2.0 (the
8    * "License"); you may not use this file except in compliance
9    * with the License.  You may obtain a copy of the License at
10   *
11   *     http://www.apache.org/licenses/LICENSE-2.0
12   *
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   */
19  package org.apache.hadoop.hbase.regionserver.handler;
20  
21  import java.io.IOException;
22  import java.util.concurrent.atomic.AtomicBoolean;
23  
24  import org.apache.commons.logging.Log;
25  import org.apache.commons.logging.LogFactory;
26  import org.apache.hadoop.hbase.classification.InterfaceAudience;
27  import org.apache.hadoop.hbase.HRegionInfo;
28  import org.apache.hadoop.hbase.HTableDescriptor;
29  import org.apache.hadoop.hbase.Server;
30  import org.apache.hadoop.hbase.coordination.OpenRegionCoordination;
31  import org.apache.hadoop.hbase.executor.EventHandler;
32  import org.apache.hadoop.hbase.executor.EventType;
33  import org.apache.hadoop.hbase.protobuf.generated.RegionServerStatusProtos.RegionStateTransition.TransitionCode;
34  import org.apache.hadoop.hbase.regionserver.HRegion;
35  import org.apache.hadoop.hbase.regionserver.RegionServerAccounting;
36  import org.apache.hadoop.hbase.regionserver.RegionServerServices;
37  import org.apache.hadoop.hbase.regionserver.RegionServerServices.PostOpenDeployContext;
38  import org.apache.hadoop.hbase.util.CancelableProgressable;
39  import org.apache.hadoop.hbase.util.ConfigUtil;
40  /**
41   * Handles opening of a region on a region server.
42   * <p>
43   * This is executed after receiving an OPEN RPC from the master or client.
44   */
45  @InterfaceAudience.Private
46  @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="JLM_JSR166_UTILCONCURRENT_MONITORENTER",
47    justification="Use of an atomic type both as monitor and condition variable is intended")
48  public class OpenRegionHandler extends EventHandler {
49    private static final Log LOG = LogFactory.getLog(OpenRegionHandler.class);
50  
51    protected final RegionServerServices rsServices;
52  
53    private final HRegionInfo regionInfo;
54    private final HTableDescriptor htd;
55    private final long masterSystemTime;
56  
57    private OpenRegionCoordination coordination;
58    private OpenRegionCoordination.OpenRegionDetails ord;
59  
60    private final boolean useZKForAssignment;
61  
62    public OpenRegionHandler(final Server server,
63        final RegionServerServices rsServices, HRegionInfo regionInfo,
64        HTableDescriptor htd, long masterSystemTime, OpenRegionCoordination coordination,
65        OpenRegionCoordination.OpenRegionDetails ord) {
66      this(server, rsServices, regionInfo, htd, EventType.M_RS_OPEN_REGION,
67          masterSystemTime, coordination, ord);
68    }
69  
70    protected OpenRegionHandler(final Server server,
71        final RegionServerServices rsServices, final HRegionInfo regionInfo,
72        final HTableDescriptor htd, EventType eventType, long masterSystemTime,
73        OpenRegionCoordination coordination, OpenRegionCoordination.OpenRegionDetails ord) {
74      super(server, eventType);
75      this.rsServices = rsServices;
76      this.regionInfo = regionInfo;
77      this.htd = htd;
78      this.coordination = coordination;
79      this.ord = ord;
80      useZKForAssignment = ConfigUtil.useZKForAssignment(server.getConfiguration());
81      this.masterSystemTime = masterSystemTime;
82    }
83  
84    public HRegionInfo getRegionInfo() {
85      return regionInfo;
86    }
87  
88    @Override
89    public void process() throws IOException {
90      boolean openSuccessful = false;
91      boolean transitionedToOpening = false;
92      final String regionName = regionInfo.getRegionNameAsString();
93      HRegion region = null;
94  
95      try {
96        if (this.server.isStopped() || this.rsServices.isStopping()) {
97          return;
98        }
99        final String encodedName = regionInfo.getEncodedName();
100 
101       // 3 different difficult situations can occur
102       // 1) The opening was cancelled. This is an expected situation
103       // 2) The region was hijacked, we no longer have the znode
104       // 3) The region is now marked as online while we're suppose to open. This would be a bug.
105 
106       // Check that this region is not already online
107       if (this.rsServices.getFromOnlineRegions(encodedName) != null) {
108         LOG.error("Region " + encodedName +
109             " was already online when we started processing the opening. " +
110             "Marking this new attempt as failed");
111         return;
112       }
113 
114       // Check that we're still supposed to open the region and transition.
115       // If fails, just return.  Someone stole the region from under us.
116       // Calling transitionFromOfflineToOpening initializes this.version.
117       if (!isRegionStillOpening()){
118         LOG.error("Region " + encodedName + " opening cancelled");
119         return;
120       }
121 
122       if (useZKForAssignment
123           && !coordination.transitionFromOfflineToOpening(regionInfo, ord)) {
124         LOG.warn("Region was hijacked? Opening cancelled for encodedName=" + encodedName);
125         // This is a desperate attempt: the znode is unlikely to be ours. But we can't do more.
126         return;
127       }
128       transitionedToOpening = true;
129       // Open region.  After a successful open, failures in subsequent
130       // processing needs to do a close as part of cleanup.
131       region = openRegion();
132       if (region == null) {
133         return;
134       }
135 
136       boolean failed = true;
137       if (isRegionStillOpening() && (!useZKForAssignment ||
138            coordination.tickleOpening(ord, regionInfo, rsServices, "post_region_open"))) {
139         if (updateMeta(region, masterSystemTime)) {
140           failed = false;
141         }
142       }
143       if (failed || this.server.isStopped() ||
144           this.rsServices.isStopping()) {
145         return;
146       }
147 
148       if (!isRegionStillOpening() ||
149           (useZKForAssignment && !coordination.transitionToOpened(region, ord))) {
150         // If we fail to transition to opened, it's because of one of two cases:
151         //    (a) we lost our ZK lease
152         // OR (b) someone else opened the region before us
153         // OR (c) someone cancelled the open
154         // In all cases, we try to transition to failed_open to be safe.
155         return;
156       }
157 
158       // We have a znode in the opened state now. We can't really delete it as the master job.
159       // Transitioning to failed open would create a race condition if the master has already
160       // acted the transition to opened.
161       // Cancelling the open is dangerous, because we would have a state where the master thinks
162       // the region is opened while the region is actually closed. It is a dangerous state
163       // to be in. For this reason, from now on, we're not going back. There is a message in the
164       // finally close to let the admin knows where we stand.
165 
166 
167       // Successful region open, and add it to OnlineRegions
168       this.rsServices.addToOnlineRegions(region);
169       openSuccessful = true;
170 
171       // Done!  Successful region open
172       LOG.debug("Opened " + regionName + " on " +
173         this.server.getServerName());
174 
175 
176     } finally {
177       // Do all clean up here
178       if (!openSuccessful) {
179         doCleanUpOnFailedOpen(region, transitionedToOpening, ord);
180       }
181       final Boolean current = this.rsServices.getRegionsInTransitionInRS().
182           remove(this.regionInfo.getEncodedNameAsBytes());
183 
184       // Let's check if we have met a race condition on open cancellation....
185       // A better solution would be to not have any race condition.
186       // this.rsServices.getRegionsInTransitionInRS().remove(
187       //  this.regionInfo.getEncodedNameAsBytes(), Boolean.TRUE);
188       // would help, but we would still have a consistency issue to manage with
189       // 1) this.rsServices.addToOnlineRegions(region);
190       // 2) the ZK state.
191       if (openSuccessful) {
192         if (current == null) { // Should NEVER happen, but let's be paranoid.
193           LOG.error("Bad state: we've just opened a region that was NOT in transition. Region="
194               + regionName);
195         } else if (Boolean.FALSE.equals(current)) { // Can happen, if we're
196                                                     // really unlucky.
197           LOG.error("Race condition: we've finished to open a region, while a close was requested "
198               + " on region=" + regionName + ". It can be a critical error, as a region that"
199               + " should be closed is now opened. Closing it now");
200           cleanupFailedOpen(region);
201         }
202       }
203     }
204   }
205 
206   private void doCleanUpOnFailedOpen(HRegion region, boolean transitionedToOpening,
207                                      OpenRegionCoordination.OpenRegionDetails ord)
208       throws IOException {
209     if (transitionedToOpening) {
210       try {
211         if (region != null) {
212           cleanupFailedOpen(region);
213         }
214       } finally {
215         if (!useZKForAssignment) {
216           rsServices.reportRegionStateTransition(TransitionCode.FAILED_OPEN, regionInfo);
217         } else {
218           // Even if cleanupFailed open fails we need to do this transition
219           // See HBASE-7698
220           coordination.tryTransitionFromOpeningToFailedOpen(regionInfo, ord);
221         }
222       }
223     } else if (!useZKForAssignment) {
224       rsServices.reportRegionStateTransition(TransitionCode.FAILED_OPEN, regionInfo);
225     } else {
226       // If still transition to OPENING is not done, we need to transition znode
227       // to FAILED_OPEN
228       coordination.tryTransitionFromOfflineToFailedOpen(this.rsServices, regionInfo, ord);
229     }
230   }
231 
232   /**
233    * Update ZK or META.  This can take a while if for example the
234    * hbase:meta is not available -- if server hosting hbase:meta crashed and we are
235    * waiting on it to come back -- so run in a thread and keep updating znode
236    * state meantime so master doesn't timeout our region-in-transition.
237    * Caller must cleanup region if this fails.
238    */
239   boolean updateMeta(final HRegion r, long masterSystemTime) {
240     if (this.server.isStopped() || this.rsServices.isStopping()) {
241       return false;
242     }
243     // Object we do wait/notify on.  Make it boolean.  If set, we're done.
244     // Else, wait.
245     final AtomicBoolean signaller = new AtomicBoolean(false);
246     PostOpenDeployTasksThread t = new PostOpenDeployTasksThread(r,
247       this.server, this.rsServices, signaller, masterSystemTime);
248     t.start();
249     // Post open deploy task:
250     //   meta => update meta location in ZK
251     //   other region => update meta
252     // It could fail if ZK/meta is not available and
253     // the update runs out of retries.
254     long now = System.currentTimeMillis();
255     long lastUpdate = now;
256     boolean tickleOpening = true;
257     while (!signaller.get() && t.isAlive() && !this.server.isStopped() &&
258         !this.rsServices.isStopping() && isRegionStillOpening()) {
259       long elapsed = now - lastUpdate;
260       if (elapsed > 120000) { // 2 minutes, no need to tickleOpening too often
261         // Only tickle OPENING if postOpenDeployTasks is taking some time.
262         lastUpdate = now;
263         if (useZKForAssignment) {
264           tickleOpening = coordination.tickleOpening(
265             ord, regionInfo, rsServices, "post_open_deploy");
266         }
267       }
268       synchronized (signaller) {
269         try {
270           // Wait for 10 seconds, so that server shutdown
271           // won't take too long if this thread happens to run.
272           if (!signaller.get()) signaller.wait(10000);
273         } catch (InterruptedException e) {
274           // Go to the loop check.
275         }
276       }
277       now = System.currentTimeMillis();
278     }
279     // Is thread still alive?  We may have left above loop because server is
280     // stopping or we timed out the edit.  Is so, interrupt it.
281     if (t.isAlive()) {
282       if (!signaller.get()) {
283         // Thread still running; interrupt
284         LOG.debug("Interrupting thread " + t);
285         t.interrupt();
286       }
287       try {
288         t.join();
289       } catch (InterruptedException ie) {
290         LOG.warn("Interrupted joining " +
291           r.getRegionInfo().getRegionNameAsString(), ie);
292         Thread.currentThread().interrupt();
293       }
294     }
295 
296     // Was there an exception opening the region?  This should trigger on
297     // InterruptedException too.  If so, we failed.  Even if tickle opening fails
298     // then it is a failure.
299     return ((!Thread.interrupted() && t.getException() == null) && tickleOpening);
300   }
301 
302   /**
303    * Thread to run region post open tasks. Call {@link #getException()} after the thread finishes
304    * to check for exceptions running
305    * {@link RegionServerServices#postOpenDeployTasks(PostOpenDeployContext)}
306    */
307   static class PostOpenDeployTasksThread extends Thread {
308     private Throwable exception = null;
309     private final Server server;
310     private final RegionServerServices services;
311     private final HRegion region;
312     private final AtomicBoolean signaller;
313     private final long masterSystemTime;
314 
315     PostOpenDeployTasksThread(final HRegion region, final Server server,
316         final RegionServerServices services, final AtomicBoolean signaller, long masterSystemTime) {
317       super("PostOpenDeployTasks:" + region.getRegionInfo().getEncodedName());
318       this.setDaemon(true);
319       this.server = server;
320       this.services = services;
321       this.region = region;
322       this.signaller = signaller;
323       this.masterSystemTime = masterSystemTime;
324     }
325 
326     @Override
327     public void run() {
328       try {
329         this.services.postOpenDeployTasks(new PostOpenDeployContext(region, masterSystemTime));
330       } catch (Throwable e) {
331         String msg = "Exception running postOpenDeployTasks; region=" +
332           this.region.getRegionInfo().getEncodedName();
333         this.exception = e;
334         if (e instanceof IOException
335             && isRegionStillOpening(region.getRegionInfo(), services)) {
336           server.abort(msg, e);
337         } else {
338           LOG.warn(msg, e);
339         }
340       }
341       // We're done.  Set flag then wake up anyone waiting on thread to complete.
342       this.signaller.set(true);
343       synchronized (this.signaller) {
344         this.signaller.notify();
345       }
346     }
347 
348     /**
349      * @return Null or the run exception; call this method after thread is done.
350      */
351     Throwable getException() {
352       return this.exception;
353     }
354   }
355 
356   /**
357    * @return Instance of HRegion if successful open else null.
358    */
359   HRegion openRegion() {
360     HRegion region = null;
361     try {
362       // Instantiate the region.  This also periodically tickles OPENING
363       // state so master doesn't timeout this region in transition.
364       region = HRegion.openHRegion(this.regionInfo, this.htd,
365         this.rsServices.getWAL(this.regionInfo),
366         this.server.getConfiguration(),
367         this.rsServices,
368         new CancelableProgressable() {
369           @Override
370           public boolean progress() {
371             if (useZKForAssignment) {
372               // if tickle failed, we need to cancel opening region.
373               return coordination.tickleOpening(ord, regionInfo,
374                 rsServices, "open_region_progress");
375             }
376             if (!isRegionStillOpening()) {
377               LOG.warn("Open region aborted since it isn't opening any more");
378               return false;
379             }
380             return true;
381           }
382         });
383     } catch (Throwable t) {
384       // We failed open. Our caller will see the 'null' return value
385       // and transition the node back to FAILED_OPEN. If that fails,
386       // we rely on the Timeout Monitor in the master to reassign.
387       LOG.error(
388           "Failed open of region=" + this.regionInfo.getRegionNameAsString()
389               + ", starting to roll back the global memstore size.", t);
390       // Decrease the global memstore size.
391       if (this.rsServices != null) {
392         RegionServerAccounting rsAccounting =
393           this.rsServices.getRegionServerAccounting();
394         if (rsAccounting != null) {
395           rsAccounting.rollbackRegionReplayEditsSize(this.regionInfo.getRegionName());
396         }
397       }
398     }
399     return region;
400   }
401 
402   void cleanupFailedOpen(final HRegion region) throws IOException {
403     if (region != null) {
404       byte[] encodedName = regionInfo.getEncodedNameAsBytes();
405       try {
406         rsServices.getRegionsInTransitionInRS().put(encodedName,Boolean.FALSE);
407         this.rsServices.removeFromOnlineRegions(region, null);
408         region.close();
409       } finally {
410         rsServices.getRegionsInTransitionInRS().remove(encodedName);
411       }
412     }
413   }
414 
415   private static boolean isRegionStillOpening(
416       HRegionInfo regionInfo, RegionServerServices rsServices) {
417     byte[] encodedName = regionInfo.getEncodedNameAsBytes();
418     Boolean action = rsServices.getRegionsInTransitionInRS().get(encodedName);
419     return Boolean.TRUE.equals(action); // true means opening for RIT
420   }
421 
422   private boolean isRegionStillOpening() {
423     return isRegionStillOpening(regionInfo, rsServices);
424   }
425 }