001package io.jstach.rainbowgum; 002 003import java.util.Collections; 004import java.util.IdentityHashMap; 005import java.util.List; 006import java.util.concurrent.CopyOnWriteArrayList; 007import java.util.concurrent.atomic.AtomicBoolean; 008import java.util.concurrent.locks.ReentrantReadWriteLock; 009 010/** 011 * A component that has a start and stop. 012 */ 013public interface LogLifecycle extends AutoCloseable { 014 015 /** 016 * Special event message that serves as a poison pill for shutdown. For now this is 017 * internal. 018 */ 019 static final String SHUTDOWN = "#SHUTDOWN#"; 020 021 /** 022 * Starts a component. 023 * @param config log config. 024 */ 025 public void start(LogConfig config); 026 027 @Override 028 public void close(); 029 030} 031 032interface Shutdownable { 033 034 public void shutdown(); 035 036} 037 038final class ShutdownManager { 039 040 private static final ReentrantReadWriteLock staticLock = new ReentrantReadWriteLock(); 041 042 private static CopyOnWriteArrayList<AutoCloseable> shutdownHooks = new CopyOnWriteArrayList<>(); 043 044 // we do not need the newer VarHandle because there is only one of these guys 045 private static AtomicBoolean shutdownHookRegistered = new AtomicBoolean(false); 046 047 static void addShutdownHook(AutoCloseable hook) { 048 staticLock.writeLock().lock(); 049 try { 050 if (shutdownHookRegistered.compareAndSet(false, true)) { 051 var thread = new Thread(() -> { 052 runShutdownHooks(); 053 }); 054 thread.setName("rainbowgum-shutdown"); 055 Runtime.getRuntime().addShutdownHook(thread); 056 } 057 shutdownHooks.add(hook); 058 } 059 finally { 060 staticLock.writeLock().unlock(); 061 } 062 } 063 064 static void removeShutdownHook(AutoCloseable hook) { 065 staticLock.writeLock().lock(); 066 try { 067 shutdownHooks.removeIf(h -> h == hook); 068 } 069 finally { 070 staticLock.writeLock().unlock(); 071 } 072 073 } 074 075 /* 076 * This is for unit testing. 077 */ 078 static List<AutoCloseable> shutdownHooks() { 079 return List.copyOf(shutdownHooks); 080 } 081 082 private static void runShutdownHooks() { 083 /* 084 * We do not lock here since we are in the shutdown thread luckily shutdownHooks 085 * is thread safe 086 */ 087 var found = Collections.newSetFromMap(new IdentityHashMap<>()); 088 for (var hook : shutdownHooks) { 089 try { 090 if (found.add(hook)) { 091 if (hook instanceof Shutdownable shut) { 092 shut.shutdown(); 093 } 094 else { 095 hook.close(); 096 } 097 } 098 } 099 catch (Exception e) { 100 MetaLog.error(ShutdownManager.class, e); 101 } 102 } 103 // Help the GC or whatever final cleanup is going on 104 shutdownHooks.clear(); 105 } 106 107}