1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.logging.log4j.core.util;
19
20 import java.io.Serializable;
21 import java.lang.ref.Reference;
22 import java.lang.ref.SoftReference;
23 import java.lang.ref.WeakReference;
24 import java.util.Collection;
25 import java.util.concurrent.CopyOnWriteArrayList;
26 import java.util.concurrent.Executors;
27 import java.util.concurrent.ThreadFactory;
28 import java.util.concurrent.atomic.AtomicReference;
29
30 import org.apache.logging.log4j.Logger;
31 import org.apache.logging.log4j.core.LifeCycle;
32 import org.apache.logging.log4j.status.StatusLogger;
33
34
35
36
37
38
39
40 public class DefaultShutdownCallbackRegistry implements ShutdownCallbackRegistry, LifeCycle, Runnable, Serializable {
41
42 private static final long serialVersionUID = 1L;
43 protected static final Logger LOGGER = StatusLogger.getLogger();
44
45 private final AtomicReference<State> state = new AtomicReference<State>(State.INITIALIZED);
46 private final ThreadFactory threadFactory;
47 private final Collection<Cancellable> hooks = new CopyOnWriteArrayList<Cancellable>();
48 private Reference<Thread> shutdownHookRef;
49
50
51
52
53 public DefaultShutdownCallbackRegistry() {
54 this(Executors.defaultThreadFactory());
55 }
56
57
58
59
60
61
62 protected DefaultShutdownCallbackRegistry(final ThreadFactory threadFactory) {
63 this.threadFactory = threadFactory;
64 }
65
66
67
68
69 @Override
70 public void run() {
71 if (state.compareAndSet(State.STARTED, State.STOPPING)) {
72 for (final Runnable hook : hooks) {
73 try {
74 hook.run();
75 } catch (final Throwable t) {
76 LOGGER.error(SHUTDOWN_HOOK_MARKER, "Caught exception executing shutdown hook {}", hook, t);
77 }
78 }
79 state.set(State.STOPPED);
80 }
81 }
82
83 @Override
84 public Cancellable addShutdownCallback(final Runnable callback) {
85 if (isStarted()) {
86 final Cancellable receipt = new Cancellable() {
87
88 private final Reference<Runnable> hook = new SoftReference<Runnable>(callback);
89
90 @Override
91 public void cancel() {
92 hook.clear();
93 hooks.remove(this);
94 }
95
96 @Override
97 public void run() {
98 final Runnable hook = this.hook.get();
99 if (hook != null) {
100 hook.run();
101 this.hook.clear();
102 }
103 }
104
105 @Override
106 public String toString() {
107 return String.valueOf(hook.get());
108 }
109 };
110 hooks.add(receipt);
111 return receipt;
112 }
113 throw new IllegalStateException("Cannot add new shutdown hook as this is not started. Current state: " +
114 state.get().name());
115 }
116
117
118
119
120 @Override
121 public void start() {
122 if (state.compareAndSet(State.INITIALIZED, State.STARTING)) {
123 try {
124 addShutdownHook(threadFactory.newThread(this));
125 state.set(State.STARTED);
126 } catch (final Exception e) {
127 LOGGER.catching(e);
128 state.set(State.STOPPED);
129 }
130 }
131 }
132
133 private void addShutdownHook(final Thread thread) {
134 shutdownHookRef = new WeakReference<Thread>(thread);
135 Runtime.getRuntime().addShutdownHook(thread);
136 }
137
138
139
140
141 @Override
142 public void stop() {
143 if (state.compareAndSet(State.STARTED, State.STOPPING)) {
144 try {
145 removeShutdownHook();
146 } finally {
147 state.set(State.STOPPED);
148 }
149 }
150 }
151
152 private void removeShutdownHook() {
153 final Thread shutdownThread = shutdownHookRef.get();
154 if (shutdownThread != null) {
155 Runtime.getRuntime().removeShutdownHook(shutdownThread);
156 shutdownHookRef.enqueue();
157 }
158 }
159
160
161
162
163
164
165 @Override
166 public boolean isStarted() {
167 return state.get() == State.STARTED;
168 }
169
170 @Override
171 public boolean isStopped() {
172 return state.get() == State.STOPPED;
173 }
174
175 }