View Javadoc
1   /*
2    * junixsocket
3    *
4    * Copyright 2009-2024 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 = new ShutdownThread(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    interface ShutdownHook {
76      /**
77       * Called upon Runtime shutdown.
78       *
79       * When you implement this method, make sure to check that the given Thread matches the current
80       * thread, e.g.: <code>
81       * if (thread != Thread.currentThread() || !(thread instanceof ShutdownThread)) {
82       * throw new IllegalStateException("Illegal caller"); }
83       * </code>
84       *
85       * @param thread The current Thread.
86       * @throws Exception Most likely ignored
87       */
88      @SuppressFBWarnings("THROWS_METHOD_THROWS_CLAUSE_BASIC_EXCEPTION")
89      void onRuntimeShutdown(Thread thread) throws Exception;
90    }
91  
92    /**
93     * The Thread that will be called upon Runtime shutdown.
94     *
95     * @author Christian Kohlschütter
96     */
97    static final class ShutdownThread extends Thread {
98      private final WeakReference<ShutdownHook> ref;
99  
100     ShutdownThread(WeakReference<ShutdownHook> ref) {
101       super();
102       this.ref = ref;
103     }
104 
105     @Override
106     public void run() {
107       ShutdownHook hook = ref.get();
108       ref.clear();
109       try {
110         if (hook != null) {
111           hook.onRuntimeShutdown(this);
112         }
113       } catch (Exception e) {
114         // ignore
115       }
116     }
117   }
118 }