View Javadoc
1   /*
2    * junixsocket
3    *
4    * Copyright 2009-2026 Christian Kohlschütter
5    *
6    * Licensed under the Apache License, Version 2.0 (the "License");
7    * you may not use this file except in compliance with the License.
8    * 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.newsclub.net.unix.rmi;
19  
20  import java.lang.ref.WeakReference;
21  import java.util.ArrayList;
22  import java.util.List;
23  
24  import com.kohlschutter.annotations.compiletime.SuppressFBWarnings;
25  
26  /**
27   * Simplifies handling shutdown hooks.
28   *
29   * @author Christian Kohlschütter
30   */
31  final class ShutdownHookSupport {
32    private static final List<Thread> HOOKS = ("true".equals(System.getProperty(
33        "org.newsclub.net.unix.rmi.collect-shutdown-hooks", "false"))) ? new ArrayList<>() : null;
34  
35    /**
36     * Registers a shutdown hook to be executed upon Runtime shutdown.
37     *
38     * NOTE: Only a weak reference to the hook is stored.
39     *
40     * @param hook The hook to register.
41     * @return The thread, to be used with #removeShutdownHook
42     */
43    public static Thread addWeakShutdownHook(ShutdownHook hook) {
44      Thread t = newShutdownThread(new WeakReference<>(hook));
45      Runtime.getRuntime().addShutdownHook(t);
46      if (HOOKS != null) {
47        synchronized (HOOKS) {
48          HOOKS.add(t);
49        }
50      }
51      return t;
52    }
53  
54    // only for unit testing
55    @SuppressFBWarnings({"RU_INVOKE_RUN"})
56    @SuppressWarnings("DoNotCall" /* errorprone */)
57    static void runHooks() {
58      if (HOOKS != null) {
59        List<Thread> list;
60        synchronized (HOOKS) {
61          list = new ArrayList<>(HOOKS);
62          HOOKS.clear();
63        }
64        for (Thread t : list) {
65          t.run(); // NOPMD -- code coverage fails if we call .start()
66        }
67      }
68    }
69  
70    /**
71     * Something that wants to be called upon Runtime shutdown.
72     *
73     * @author Christian Kohlschütter
74     */
75    @FunctionalInterface
76    interface ShutdownHook {
77      /**
78       * Called upon Runtime shutdown.
79       *
80       * When you implement this method, make sure to check that the given Thread matches the current
81       * thread, e.g.: <code>
82       * if (thread != Thread.currentThread()) {
83       * throw new IllegalStateException("Illegal caller"); }
84       * </code>
85       *
86       * @param thread The current Thread.
87       * @throws Exception Most likely ignored
88       */
89      @SuppressFBWarnings("THROWS_METHOD_THROWS_CLAUSE_BASIC_EXCEPTION")
90      void onRuntimeShutdown(Thread thread) throws Exception;
91    }
92  
93    /**
94     * Create a Thread that will be called upon Runtime shutdown.
95     *
96     * @param ref The shutdown hook (weak reference).
97     */
98    public static Thread newShutdownThread(WeakReference<ShutdownHook> ref) {
99      return new Thread(() -> {
100       ShutdownHook hook = ref.get();
101       ref.clear();
102       try {
103         if (hook != null) {
104           hook.onRuntimeShutdown(Thread.currentThread());
105         }
106       } catch (Exception e) {
107         // ignore
108       }
109     }, "ShutdownHook");
110   }
111 }