001/**
002 * The MIT License (MIT)
003 *
004 * Copyright (c) 2019 nobark (tools4j), Marco Terzer, Anton Anufriev
005 *
006 * Permission is hereby granted, free of charge, to any person obtaining a copy
007 * of this software and associated documentation files (the "Software"), to deal
008 * in the Software without restriction, including without limitation the rights
009 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
010 * copies of the Software, and to permit persons to whom the Software is
011 * furnished to do so, subject to the following conditions:
012 *
013 * The above copyright notice and this permission notice shall be included in all
014 * copies or substantial portions of the Software.
015 *
016 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
017 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
018 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
019 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
020 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
021 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
022 * SOFTWARE.
023 */
024package org.tools4j.nobark.run;
025
026import java.util.Collections;
027import java.util.List;
028import java.util.Objects;
029import java.util.concurrent.ThreadFactory;
030import java.util.concurrent.TimeUnit;
031import java.util.concurrent.atomic.AtomicInteger;
032
033import sun.misc.Contended;
034
035/**
036 * A thread that performs a main {@link java.lang.Runnable runnable} in a new thread and another shutdown runnable the
037 * graceful {@link #shutdown} phase of the thread.  The thread is started immediately upon construction.
038 */
039public class ShutdownableThread implements ThreadLike, Shutdownable {
040
041    private static final int RUNNING = 0;
042    private static final int SHUTDOWN = 1;
043    private static final int SHUTDOWN_NOW = 2;
044
045    private final RunnableFactory mainRunnableFactory;
046    private final RunnableFactory shutdownRunnableFactory;
047    private final Thread thread;
048    @Contended
049    private final AtomicInteger state = new AtomicInteger(RUNNING);
050
051    /**
052     * Constructor for shutdownable thread; it is recommended to use the static start(..) methods instead.
053     *
054     * @param mainRunnableFactory       the factory for the main runnable;
055     *                                  the <i>{@link #keepMainRunning}</i> condition is passed to the factory as lambda
056     * @param shutdownRunnableFactory   the factory for the shutdown phase runnable;
057     *                                  the <i>{@link #keepShutdownRunning}</i> condition is passed to the factory as lambda
058     * @param threadFactory             the factory to provide the thread
059     */
060    protected ShutdownableThread(final RunnableFactory mainRunnableFactory,
061                                 final RunnableFactory shutdownRunnableFactory,
062                                 final ThreadFactory threadFactory) {
063        this.mainRunnableFactory = Objects.requireNonNull(mainRunnableFactory);
064        this.shutdownRunnableFactory = Objects.requireNonNull(shutdownRunnableFactory);
065        this.thread = threadFactory.newThread(this::run);
066        thread.start();
067    }
068
069    /**
070     * Creates, starts and returns a new shutdownable thread.
071     *
072     * @param mainRunnableFactory       the factory for the main runnable;
073     *                                  the <i>{@link #keepMainRunning}</i> condition is passed to the factory as lambda
074     * @param shutdownRunnableFactory   the factory for the shutdown phase runnable;
075     *                                  the <i>{@link #keepShutdownRunning}</i> condition is passed to the factory as lambda
076     * @param threadFactory             the factory to provide the thread
077     * @return the newly created and started shutdownable thread
078     */
079    public static ShutdownableThread start(final RunnableFactory mainRunnableFactory,
080                                           final RunnableFactory shutdownRunnableFactory,
081                                           final ThreadFactory threadFactory) {
082        return new ShutdownableThread(mainRunnableFactory, shutdownRunnableFactory, threadFactory);
083    }
084
085    private void run() {
086        final Runnable main = mainRunnableFactory.create(this::keepMainRunning);
087        final Runnable shutdown = shutdownRunnableFactory.create(this::keepShutdownRunning);
088        main.run();
089        shutdown.run();
090    }
091
092    @Override
093    public void shutdown() {
094        state.compareAndSet(RUNNING, SHUTDOWN);
095    }
096
097
098    @Override
099    public List<Runnable> shutdownNow() {
100        final int shutdownAndNow = SHUTDOWN | SHUTDOWN_NOW;
101        if (!state.compareAndSet(RUNNING, shutdownAndNow)) {
102            state.compareAndSet(SHUTDOWN, shutdownAndNow);
103        }
104        return Collections.emptyList();
105    }
106
107    private boolean keepMainRunning() {
108        return (state.get() & SHUTDOWN) == 0;
109    }
110
111    private boolean keepShutdownRunning() {
112        return (state.get() & SHUTDOWN_NOW) == 0;
113    }
114
115    @Override
116    public boolean isShutdown() {
117        return (state.get() & SHUTDOWN) != 0;
118    }
119
120    @Override
121    public boolean isTerminated() {
122        return threadState() == Thread.State.TERMINATED;
123    }
124
125    @Override
126    public boolean awaitTermination(final long timeout, final TimeUnit unit) {
127        if (timeout < 0) {
128            throw new IllegalArgumentException("timeout value is negative: " + timeout);
129        }
130        if (isTerminated()) {
131            return true;
132        }
133        final long millis = unit.toMillis(timeout);
134        final long nanos = unit.toNanos(timeout - unit.convert(millis, TimeUnit.MILLISECONDS));
135        try {
136            if (nanos == 0) {
137                thread.join(millis);
138            } else {
139                thread.join(millis, (int) nanos);
140            }
141        } catch (final InterruptedException e) {
142            throw new IllegalStateException("Join interrupted for thread " + thread);
143        }
144        return isTerminated();
145    }
146
147    @Override
148    public Thread.State threadState() {
149        return thread.getState();
150    }
151
152    /**
153     * Calls {@link #shutdown()}.
154     */
155    @Override
156    public void stop() {
157        shutdown();
158    }
159
160    /**
161     * Delegates to {@link #awaitTermination(long, TimeUnit)}.
162     */
163    @Override
164    public void join(final long millis) {
165        awaitTermination(millis, TimeUnit.MILLISECONDS);
166    }
167
168    /**
169     * Returns the name of the thread that was created with the thread factory passed to the constructor.
170     *
171     * @return the service thread's name
172     * @see Thread#getName()
173     */
174    @Override
175    public String toString() {
176        return thread.getName();
177    }
178}