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;
19
20 import java.io.Closeable;
21 import java.io.IOException;
22 import java.lang.ref.Cleaner;
23 import java.util.Objects;
24
25 import org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement;
26
27 import com.kohlschutter.annotations.compiletime.SuppressFBWarnings;
28
29 /**
30 * This wrapper (along with the Java 8-specific counterpart in src/main/java8) allows us to
31 * implement cleanup logic for objects that are garbage-collectable/no longer reachable.
32 *
33 * <p>
34 * Usage:
35 * <ol>
36 * <li>Create a subclass of CleanableState and attach it as a private field to the object you want
37 * to be cleaned up. You may call that field {@code cleanableState}.</li>
38 * <li>Define all resources that need to be cleaned up in this subclass (instead of the observed
39 * object itself).</li>
40 * <li>Make sure to not refer the observed object instance itself, as that will create a reference
41 * cycle and prevent proper cleanup.</li>
42 * <li>Implement the {@link #doClean()} method to perform all the necessary cleanup steps.</li>
43 * <li>If the observed class implements {@code close()}, it's a good practice to have it just call
44 * {@code cleanableState.runCleaner()}.</li>
45 * </ol>
46 * <p>
47 * Exceptions thrown upon doClean() are either thrown via {@link #close()} when invoked directly,
48 * or, if cleaned during Garbage collection, to the exception handler specified in the constructor
49 * or (if none specified) to the default uncaught exception handler.
50 * <p>
51 * Implementation details:
52 * <ul>
53 * <li>In Java 9 or later, {@link Cleaner} is used under the hood.</li>
54 * <li>In Java 8 or earlier, {@link #finalize()} calls {@link #doClean()} directly.</li>
55 * </ul>
56 *
57 * @author Christian Kohlschütter
58 */
59 @IgnoreJRERequirement // see src/main/java8
60 public abstract class CleanableState implements Closeable {
61 /**
62 * A default exception handler: Calls {@link Thread#getDefaultUncaughtExceptionHandler()} with the
63 * stacktrace and information about the current thread.
64 */
65 public static final AFConsumer<Throwable> DEFAULT_EXCEPTION_HANDLER = (t) -> Thread
66 .getDefaultUncaughtExceptionHandler().uncaughtException(Thread.currentThread(), t);
67
68 private static final Cleaner CLEANER = Cleaner.create();
69 private final Cleaner.Cleanable cleanable;
70 private AFConsumer<Throwable> exceptionHandler;
71 private AFConsumer<Throwable> exceptionHandlerCurrent;
72 private IOException exceptionUponClose = null;
73
74 /**
75 * Creates a state object to be used as an implementation detail of the specified observed
76 * instance, using the {@link #DEFAULT_EXCEPTION_HANDLER}.
77 *
78 * @param observed The observed instance (the outer class referencing this
79 * {@link CleanableState}).
80 */
81 @SuppressFBWarnings("CT_CONSTRUCTOR_THROW")
82 protected CleanableState(Object observed) {
83 this(observed, DEFAULT_EXCEPTION_HANDLER);
84 }
85
86 /**
87 * Creates a state object to be used as an implementation detail of the specified observed
88 * instance, using a custom exception handler.
89 *
90 * @param observed The observed instance (the outer class referencing this
91 * {@link CleanableState}).
92 * @param exceptionHandler The exception handler.
93 */
94 @SuppressFBWarnings("CT_CONSTRUCTOR_THROW")
95 protected CleanableState(Object observed, AFConsumer<Throwable> exceptionHandler) {
96 Objects.requireNonNull(exceptionHandler);
97
98 this.exceptionHandler = exceptionHandler;
99 this.exceptionHandlerCurrent = exceptionHandler;
100 this.cleanable = CLEANER.register(observed, () -> doClean1());
101 }
102
103 /**
104 * Explicitly the cleanup code defined in {@link #doClean()}. This is best be called from a
105 * {@code close()} method in the observed class.
106 */
107 public final void runCleaner() {
108 cleanable.clean();
109 }
110
111 private void doClean1() {
112 try {
113 doClean();
114 } catch (Throwable t) { // NOPMD
115 exceptionHandlerCurrent.accept(t);
116 }
117 }
118
119 /**
120 * Performs the actual cleanup. Be sure to always clean up whenever possible, either by tracking
121 * potential exceptions or by using try-finally to ensure proper cleanup.
122 *
123 * @throws IOException on error.
124 */
125 protected abstract void doClean() throws IOException;
126
127 /**
128 * Checks if we're being called from within {@link #close()}.
129 *
130 * @return {@code true} if being called from within {@link #close()}.
131 */
132 protected boolean inClose() {
133 return exceptionHandlerCurrent != exceptionHandler; // NOPMD
134 }
135
136 @Override
137 public final void close() throws IOException {
138 this.exceptionHandlerCurrent = (t) -> {
139 IOException exc = exceptionUponClose;
140 if (exc != null) {
141 exc.addSuppressed(t);
142 } else if (t instanceof IOException) {
143 exceptionUponClose = (IOException) t;
144 } else {
145 exceptionUponClose = new IOException();
146 exceptionUponClose.addSuppressed(t);
147 }
148 };
149 runCleaner();
150 this.exceptionHandlerCurrent = exceptionHandler;
151
152 if (exceptionUponClose != null) {
153 throw exceptionUponClose;
154 }
155 }
156 }