001/**
002 * The MIT License (MIT)
003 *
004 * Copyright (c) 2018 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.loop;
025
026import sun.misc.Contended;
027
028import java.util.Objects;
029import java.util.concurrent.ThreadFactory;
030import java.util.concurrent.TimeUnit;
031import java.util.concurrent.atomic.AtomicInteger;
032import java.util.function.BooleanSupplier;
033import java.util.function.Function;
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 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    private static final int TERMINATED = 4;
045
046    private final Function<BooleanSupplier, Runnable> mainRunnableFactory;
047    private final Function<BooleanSupplier, Runnable> shutdownRunnableFactory;
048    private final Thread thread;
049    @Contended
050    private final AtomicInteger state = new AtomicInteger(RUNNING);
051
052    /**
053     * Constructor for shutdownable thread; it is recommended to use the static start(..) methods instead.
054     *
055     * @param mainRunnableFactory       the factory for the main runnable;
056     *                                  the <i>{@link #isRunning}</i> condition is passed to the factory as lambda
057     * @param shutdownRunnableFactory   the factory for the shutdown phase runnable;
058     *                                  the <i>{@link #isShutdownRunning}</i> condition is passed to the factory as lambda
059     * @param threadFactory             the factory to provide the thread
060     */
061    protected ShutdownableThread(final Function<BooleanSupplier, Runnable> mainRunnableFactory,
062                                 final Function<BooleanSupplier, Runnable> shutdownRunnableFactory,
063                                 final ThreadFactory threadFactory) {
064        this.mainRunnableFactory = Objects.requireNonNull(mainRunnableFactory);
065        this.shutdownRunnableFactory = Objects.requireNonNull(shutdownRunnableFactory);
066        this.thread = threadFactory.newThread(this::run);
067        thread.start();
068    }
069
070    /**
071     * Creates, starts and returns a new shutdownable thread.
072     *
073     * @param mainRunnableFactory       the factory for the main runnable;
074     *                                  the <i>{@link #isRunning}</i> condition is passed to the factory as lambda
075     * @param shutdownRunnableFactory   the factory for the shutdown phase runnable;
076     *                                  the <i>{@link #isShutdownRunning}</i> condition is passed to the factory as lambda
077     * @param threadFactory             the factory to provide the thread
078     * @return the newly created and started shutdownable thread
079     */
080    public static ShutdownableThread start(final Function<BooleanSupplier, Runnable> mainRunnableFactory,
081                                           final Function<BooleanSupplier, Runnable> shutdownRunnableFactory,
082                                           final ThreadFactory threadFactory) {
083        return new ShutdownableThread(mainRunnableFactory, shutdownRunnableFactory, threadFactory);
084    }
085
086    private void run() {
087        final Runnable main = mainRunnableFactory.apply(this::isRunning);
088        final Runnable shutdown = shutdownRunnableFactory.apply(this::isShutdownRunning);
089        main.run();
090        shutdown.run();
091        notifyTerminated();
092    }
093
094    @Override
095    public void shutdown() {
096        state.compareAndSet(RUNNING, SHUTDOWN);
097    }
098
099    @Override
100    public void shutdownNow() {
101        final int shutdownAndNow = SHUTDOWN | SHUTDOWN_NOW;
102        if (!state.compareAndSet(RUNNING, shutdownAndNow)) {
103            state.compareAndSet(SHUTDOWN, shutdownAndNow);
104        }
105    }
106
107    private void notifyTerminated() {
108        if (!state.compareAndSet(SHUTDOWN, SHUTDOWN | TERMINATED)) {
109            state.compareAndSet(SHUTDOWN | SHUTDOWN_NOW, SHUTDOWN | SHUTDOWN_NOW | TERMINATED);
110        }
111    }
112
113    private boolean isRunning() {
114        return (state.get() & SHUTDOWN) == 0;
115    }
116
117    private boolean isShutdownRunning() {
118        return (state.get() & SHUTDOWN_NOW) == 0;
119    }
120
121    @Override
122    public boolean isShutdown() {
123        return (state.get() & SHUTDOWN) != 0;
124    }
125
126    @Override
127    public boolean isTerminated() {
128        return (state.get() & TERMINATED) != 0;
129    }
130
131    @Override
132    public boolean awaitTermination(final long timeout, final TimeUnit unit) {
133        if (timeout < 0) {
134            throw new IllegalArgumentException("timeout value is negative: " + timeout);
135        }
136        if (isTerminated()) {
137            return true;
138        }
139        if (timeout == 0) {
140            return isTerminated();
141        }
142        final long millis = unit.toMillis(timeout);
143        final long nanos = unit.toNanos(timeout - unit.convert(millis, TimeUnit.MILLISECONDS));
144        try {
145            if (nanos == 0) {
146                thread.join(millis);
147            } else {
148                thread.join(millis, (int) nanos);
149            }
150        } catch (final InterruptedException e) {
151            throw new IllegalStateException("Join interrupted for thread " + thread);
152        }
153        return isTerminated();
154    }
155
156    /**
157     * Returns the name of the thread that was created with the thread factory passed to the constructor.
158     *
159     * @return the service thread's name
160     * @see Thread#getName()
161     */
162    @Override
163    public String toString() {
164        return thread.getName();
165    }
166}