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.procedure2;
019
020import static org.junit.jupiter.api.Assertions.assertEquals;
021import static org.junit.jupiter.api.Assertions.assertThrows;
022
023import java.lang.Thread.UncaughtExceptionHandler;
024import java.util.Set;
025import java.util.concurrent.TimeUnit;
026import org.apache.hadoop.hbase.HBaseCommonTestingUtil;
027import org.apache.hadoop.hbase.testclassification.MasterTests;
028import org.apache.hadoop.hbase.testclassification.SmallTests;
029import org.junit.jupiter.api.AfterEach;
030import org.junit.jupiter.api.BeforeEach;
031import org.junit.jupiter.api.Tag;
032import org.junit.jupiter.api.Test;
033
034/**
035 * Make sure the {@link UncaughtExceptionHandler} will be called when there are unchecked exceptions
036 * thrown in the task.
037 * <p/>
038 * See HBASE-21875 and HBASE-21890 for more details.
039 */
040@Tag(MasterTests.TAG)
041@Tag(SmallTests.TAG)
042public class TestRemoteProcedureDispatcherUncaughtExceptionHandler {
043
044  private static HBaseCommonTestingUtil UTIL = new HBaseCommonTestingUtil();
045
046  private static final class ExceptionHandler implements UncaughtExceptionHandler {
047
048    private Throwable error;
049
050    @Override
051    public synchronized void uncaughtException(Thread t, Throwable e) {
052      this.error = e;
053      notifyAll();
054    }
055
056    public synchronized void get() throws Throwable {
057      while (error == null) {
058        wait();
059      }
060      throw error;
061    }
062  }
063
064  private static final class Dispatcher extends RemoteProcedureDispatcher<Void, Integer> {
065
066    private final UncaughtExceptionHandler handler;
067
068    public Dispatcher(UncaughtExceptionHandler handler) {
069      super(UTIL.getConfiguration());
070      this.handler = handler;
071    }
072
073    @Override
074    protected UncaughtExceptionHandler getUncaughtExceptionHandler() {
075      return handler;
076    }
077
078    @Override
079    protected void remoteDispatch(Integer key, Set<RemoteProcedure> operations) {
080    }
081
082    @Override
083    protected void abortPendingOperations(Integer key, Set<RemoteProcedure> operations) {
084    }
085  }
086
087  private ExceptionHandler handler;
088
089  private Dispatcher dispatcher;
090
091  @BeforeEach
092  public void setUp() {
093    handler = new ExceptionHandler();
094    dispatcher = new Dispatcher(handler);
095    dispatcher.start();
096  }
097
098  @AfterEach
099  public void tearDown() {
100    dispatcher.stop();
101    dispatcher = null;
102    handler = null;
103  }
104
105  @Test
106  public void testSubmit() throws Throwable {
107    String message = "inject error";
108    dispatcher.submitTask(new Runnable() {
109
110      @Override
111      public void run() {
112        throw new RuntimeException(message);
113      }
114    });
115    RuntimeException exception = assertThrows(RuntimeException.class, () -> handler.get());
116    assertEquals(message, exception.getMessage());
117  }
118
119  @Test
120  public void testDelayedSubmit() throws Throwable {
121    String message = "inject error";
122    dispatcher.submitTask(new Runnable() {
123
124      @Override
125      public void run() {
126        throw new RuntimeException(message);
127      }
128    }, 100, TimeUnit.MILLISECONDS);
129    RuntimeException exception = assertThrows(RuntimeException.class, () -> handler.get());
130    assertEquals(message, exception.getMessage());
131  }
132}