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}