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}