001/*
002 * JGrapes Event Driven Framework
003 * Copyright (C) 2024 Michael N. Lipp
004 * 
005 * This program is free software; you can redistribute it and/or modify it 
006 * under the terms of the GNU Affero General Public License as published by 
007 * the Free Software Foundation; either version 3 of the License, or 
008 * (at your option) any later version.
009 * 
010 * This program is distributed in the hope that it will be useful, but 
011 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
012 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License 
013 * for more details.
014 * 
015 * You should have received a copy of the GNU Affero General Public License along 
016 * with this program; if not, see <http://www.gnu.org/licenses/>.
017 */
018
019package org.jgrapes.io.util;
020
021import java.lang.ref.ReferenceQueue;
022import java.lang.ref.WeakReference;
023import java.util.HashSet;
024import java.util.Set;
025
026import org.jgrapes.core.Components;
027
028/**
029 * Cleans up threads if some object has been garbage collected.
030 * 
031 * Sometimes it is necessary to create a thread that delivers data
032 * to a synchronous consumer. This thread keeps the consumer running
033 * until the consumer expects no more input and thus terminates
034 * the thread.
035 * 
036 * Due to an error condition it may happen, however, that the terminating
037 * event never occurs and the thread runs forever. As a possible remedy,
038 * this class allows the thread to be associated with the lifetime of an
039 * arbitrary object. When the object is garbage collected, the thread is
040 * terminated automatically.
041 * 
042 * @since 2.8.0
043 */
044public final class ThreadCleaner {
045
046    private static Set<RefWithThread> watched = new HashSet<>();
047    private static ReferenceQueue<Object> abandoned
048        = new ReferenceQueue<>();
049
050    private ThreadCleaner() {
051        // Utility class
052    }
053
054    /**
055     * Weak references to an object that interrupts the associated
056     * thread if the object has been garbage collected.
057     *
058     * @param <T> the generic type
059     */
060    private static class RefWithThread extends WeakReference<Object> {
061        public final Thread watched;
062
063        /**
064         * Creates a new instance.
065         *
066         * @param referent the referent
067         * @param thread the thread
068         */
069        public RefWithThread(Object referent, Thread thread) {
070            super(referent, abandoned);
071            watched = thread;
072        }
073    }
074
075    static {
076        (Components.useVirtualThreads() ? Thread.ofVirtual()
077            : Thread.ofPlatform()).name("ThreadCleaner").start(() -> {
078                while (true) {
079                    try {
080                        ThreadCleaner.RefWithThread ref
081                            = (ThreadCleaner.RefWithThread) abandoned.remove();
082                        ref.watched.interrupt();
083                        watched.remove(ref);
084                    } catch (InterruptedException e) { // NOPMD
085                        // Nothing to do
086                    }
087                }
088            });
089    }
090
091    /**
092     * Watch the referent and terminate the thread if it is
093     * garbage collected.
094     *
095     * @param referent the referent
096     * @param thread the thread
097     */
098    public static void watch(Object referent, Thread thread) {
099        watched.add(new RefWithThread(referent, thread));
100    }
101}