All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.spf4j.base.ShutdownThread Maven / Gradle / Ivy

Go to download

A continuously growing collection of utilities to measure performance, get better diagnostics, improve performance, or do things more reliably, faster that other open source libraries...

There is a newer version: 8.10.0
Show newest version
/*
 * Copyright (c) 2001-2017, Zoltan Farkas All Rights Reserved.
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 * Additionally licensed with:
 *
 * Licensed 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.spf4j.base;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import javax.annotation.CheckReturnValue;
import org.spf4j.concurrent.CustomThreadFactory;
import org.spf4j.io.NullOutputStream;

/**
 * A shutdown thread that allows tiered shutdown. hooks in each tier will be executed sequentially, in tier level
 * ascending order. hooks on the same tier will be executed in parallel.
 *
 * @author Zoltan Farkas
 */
public final class ShutdownThread {

  public static final long WAIT_FOR_SHUTDOWN_NANOS = TimeUnit.MILLISECONDS.toNanos(
          Integer.getInteger("spf4j.waitForShutdownMillis", 25000));

  private static final ShutdownHooks SHUTDOWN_THREAD = init();

  private ShutdownThread() {
  }

  private static ShutdownHooks init() {
    ShutdownThreadImpl st = new ShutdownThreadImpl(
            Boolean.getBoolean("spf4j.dumpNonDaemonThreadInfoOnShutdown"));
    try {
      java.lang.Runtime.getRuntime().addShutdownHook(st);
      preloadClasses();
      return st;
    } catch (IllegalStateException ex) {
      return new ShutdownHooks() {
        @Override
        public boolean queueHook(final int priority, final Runnable runnable) {
          return false;
        }

        @Override
        public boolean removeQueuedShutdownHook(final Runnable runnable) {
          return false;
        }
      };
    }
  }

  @SuppressFBWarnings("MS_EXPOSE_REP")
  public static ShutdownHooks get() {
    return SHUTDOWN_THREAD;
  }

  /**
   * We do this to make sure we have these classes loaded when shutdown happens. This is to help us get some diagnostic
   * info to the process output, and be resilient to class loading issues.
   */
  private static void preloadClasses() {
    try (PrintStream stream = new PrintStream(NullOutputStream.get(), false, "UTF-8")) {
      RuntimeException rex = new RuntimeException("priming");
      Throwables.writeTo(rex, stream, Throwables.PackageDetail.NONE);
      Throwables.containsNonRecoverable(rex);
    } catch (UnsupportedEncodingException ex) {
      throw new ExceptionInInitializerError(ex);
    }
    try {
      Class.forName(Threads.class.getName());
    } catch (ClassNotFoundException ex) {
      throw new ExceptionInInitializerError(ex);
    }
  }

  /**
   * java.lang.ApplicationShutdownHooks.runHooks, java.lang.Shutdown.runHooks
   */
  private static boolean isHookShutdownRunner(final Thread t) {
    for (StackTraceElement ste : t.getStackTrace()) {
      if ("runHooks".equals(ste.getMethodName()) && ste.getClassName().contains("Shutdown")) {
        return true;
      }
    }
    return false;
  }

  private static final class ShutdownThreadImpl extends Thread implements ShutdownHooks {

    private final SortedMap> rhooks;

    private final boolean dumpNonDaemonThreadInfoOnShutdown;

    private ThreadPoolExecutor shutdownExecutor;

    private volatile boolean isShutdown;

    ShutdownThreadImpl(final boolean dumpNonDaemonThreadInfoOnShutdown) {
      super("spf4j queued shutdown");
      this.rhooks = new TreeMap<>();
      this.dumpNonDaemonThreadInfoOnShutdown = dumpNonDaemonThreadInfoOnShutdown;
      this.shutdownExecutor = null;
      this.isShutdown = false;
    }

    public boolean isDumpNonDaemonThreadInfoOnShutdown() {
      return dumpNonDaemonThreadInfoOnShutdown;
    }

    @Override
    public void run() {
      this.isShutdown = true;
      long deadlineNanos = TimeSource.nanoTime() + WAIT_FOR_SHUTDOWN_NANOS;
      try {
        doRun(deadlineNanos);
        shutDownClean(deadlineNanos);
      } catch (InterruptedException ex) {
        shutDownClean(deadlineNanos);
      } catch (TimeoutException ex) {
        ErrLog.error("Timeout during shutdown executor cleanup", ex);
        shutdownNowExecutor();
      } catch (Exception e) {
        if (org.spf4j.base.Throwables.containsNonRecoverable(e)) {
          XRuntime.get().goDownWithError(e, SysExits.EX_SOFTWARE);
        }
        ErrLog.error("Failure during shutdown", e);
        shutDownClean(deadlineNanos);
      } finally {
        dumpInfoOnRemainingThreads();
      }
    }

    private void shutDownClean(final long deadlineNanos) {
      try {
        shutdownExecutor(deadlineNanos);
      } catch (TimeoutException ex) {
        ErrLog.error("Timeout during shutdown executor cleanup", ex);
      } catch (RuntimeException ex) {
        ErrLog.error("RuntimeException during shutdown executor cleanup", ex);
      } catch (InterruptedException ex) {
        // just terminate
      }
    }

    private ThreadPoolExecutor getOrCreateExecutor() {
      ThreadPoolExecutor tpe = this.shutdownExecutor;
      if (tpe == null) {
        tpe = new ThreadPoolExecutor(1, Integer.MAX_VALUE, 10, TimeUnit.MILLISECONDS,
                new SynchronousQueue(), new CustomThreadFactory("shutdownExecutor", true));
        this.shutdownExecutor = tpe;
      }
      return tpe;
    }

    private void shutdownExecutor(final long deadlineNanos) throws TimeoutException, InterruptedException {
      ThreadPoolExecutor tpe = this.shutdownExecutor;
      if (tpe != null) {
        tpe.shutdown();
        long timeoutNanos = TimeSource.getTimeToDeadlineStrict(deadlineNanos, TimeUnit.NANOSECONDS);
        tpe.awaitTermination(timeoutNanos, TimeUnit.NANOSECONDS);
        List remaining = tpe.shutdownNow();
        if (remaining.size() > 0) {
          ErrLog.error("Remaining tasks: " + remaining);
        }
      }
    }

    private void shutdownNowExecutor() {
      ThreadPoolExecutor tpe = this.shutdownExecutor;
      if (tpe != null) {
        List remaining = tpe.shutdownNow();
        if (remaining.size() > 0) {
          ErrLog.error("Remaining tasks: " + remaining);
        }
      }
    }

    public void doRun(final long deadlineNanos) throws TimeoutException, InterruptedException, Exception {
      Exception rex = null;
      SortedMap> hooks;
      synchronized (rhooks) {
        hooks = new TreeMap<>(rhooks);
        for (Map.Entry> entry : hooks.entrySet()) {
          entry.setValue(new HashSet<>(entry.getValue()));
        }
      }
      for (Map.Entry> runnables : hooks.entrySet()) {
        final Set values = runnables.getValue();
        List> futures = new ArrayList<>(values.size());
        for (Runnable runnable : values) {
          futures.add(getOrCreateExecutor().submit(runnable));
        }
        for (Future future : futures) {
          try {
            long timeoutNanos = TimeSource.getTimeToDeadlineStrict(deadlineNanos, TimeUnit.NANOSECONDS);
            future.get(timeoutNanos, TimeUnit.NANOSECONDS);
          } catch (InterruptedException ex) {
            Thread.currentThread().interrupt();
            if (rex != null) {
              ex.addSuppressed(rex);
            }
            throw ex;
          } catch (ExecutionException | RuntimeException ex) {
            if (rex == null) {
              rex = ex;
            } else {
              rex.addSuppressed(ex);
            }
          } catch (TimeoutException ex) {
            if (rex != null) {
              ex.addSuppressed(rex);
            }
            throw ex;
          }
        }
      }
      if (rex != null) {
        throw rex;
      }
    }

    public void dumpInfoOnRemainingThreads() {
      // print out info on all remaining non daemon threads.
      if (dumpNonDaemonThreadInfoOnShutdown) {
        Thread[] threads = Threads.getThreads();
        Thread current = Thread.currentThread();
        boolean first = true;
        for (Thread thread : threads) {
          if (thread.isAlive() && !thread.isDaemon() && !thread.equals(current)
                  && !thread.getName().contains("DestroyJavaVM")
                  && !isHookShutdownRunner(thread)) {
            if (first) {
              ErrLog.error("Non daemon threads still running:");
              first = false;
            }
            ErrLog.error("Non daemon thread {}, stackTrace = {}", thread, thread.getStackTrace());
          }
        }
      }
    }

    @CheckReturnValue
    @Override
    public boolean queueHook(final int priority, final Runnable runnable) {
      if (this.isShutdown) {
        return false;
      }
      synchronized (this.rhooks) {
        if (this.isShutdown) {
          return false;
        }
        Integer pr = priority;
        Set runnables = this.rhooks.get(pr);
        if (runnables == null) {
          runnables = new HashSet<>(4);
          this.rhooks.put(pr, runnables);
        }
        runnables.add(runnable);
      }
      return true;
    }

    @Override
    public boolean removeQueuedShutdownHook(final Runnable runnable) {
      if (this.equals(Thread.currentThread())) {
        return false;
      }
      synchronized (this.rhooks) {
        for (Set entry : this.rhooks.values()) {
          if (entry.remove(runnable)) {
            return true;
          }
        }
      }
      return false;
    }

  }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy