    001/*
    002 * Licensed to the Apache Software Foundation (ASF) under one or more
    003 *  contributor license agreements.  See the NOTICE file distributed with
    004 *  this work for additional information regarding copyright ownership.
    005 *  The ASF licenses this file to You under the Apache License, Version 2.0
    006 *  (the "License"); you may not use this file except in compliance with
    007 *  the License.  You may obtain a copy of the License at
    008 *
    009 *      http://www.apache.org/licenses/LICENSE-2.0
    010 *
    011 *  Unless required by applicable law or agreed to in writing, software
    012 *  distributed under the License is distributed on an "AS IS" BASIS,
    013 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    014 *  See the License for the specific language governing permissions and
    015 *  limitations under the License.
    016 */
    017
    018package org.apache.commons.exec;
    019
    020import java.time.Duration;
    021import java.util.Vector;
    022import java.util.concurrent.ThreadFactory;
    023import java.util.function.Supplier;
    024
    025/**
    026 * Generalization of {@code ExecuteWatchdog}.
    027 *
    028 * @see org.apache.commons.exec.ExecuteWatchdog
    029 */
    030public class Watchdog implements Runnable {
    031
    032    /**
    033     * Builds ExecuteWatchdog instances.
    034     *
    035     * @since 1.4.0
    036     */
    037    public static final class Builder implements Supplier<Watchdog> {
    038
    039        private ThreadFactory threadFactory;
    040        private Duration timeout;
    041
    042        /**
    043         * Creates a new configured ExecuteWatchdog.
    044         *
    045         * @return a new configured ExecuteWatchdog.
    046         */
    047        @Override
    048        public Watchdog get() {
    049            return new Watchdog(threadFactory, timeout);
    050        }
    051
    052        /**
    053         * Sets the thread factory.
    054         *
    055         * @param threadFactory the thread factory.
    056         * @return this.
    057         */
    058        public Builder setThreadFactory(final ThreadFactory threadFactory) {
    059            this.threadFactory = threadFactory;
    060            return this;
    061        }
    062
    063        /**
    064         * Sets the timeout duration.
    065         *
    066         * @param timeout the timeout duration.
    067         * @return this.
    068         */
    069        public Builder setTimeout(final Duration timeout) {
    070            this.timeout = timeout;
    071            return this;
    072        }
    073
    074    }
    075
    076    /**
    077     * Creates a new builder.
    078     *
    079     * @return a new builder.
    080     * @since 1.4.0
    081     */
    082    public static Builder builder() {
    083        return new Builder();
    084    }
    085
    086    private final Vector<TimeoutObserver> observers = new Vector<>(1);
    087
    088    private final long timeoutMillis;
    089
    090    private boolean stopped;
    091
    092    /**
    093     * The thread factory.
    094     */
    095    private final ThreadFactory threadFactory;
    096
    097    /**
    098     * Constructs a new instance.
    099     *
    100     * @param timeoutMillis the timeout duration.
    101     * @deprecated Use {@link Builder#get()}.
    102     */
    103    @Deprecated
    104    public Watchdog(final long timeoutMillis) {
    105        this(null, Duration.ofMillis(timeoutMillis));
    106    }
    107
    108    /**
    109     * Constructs a new instance.
    110     *
    111     * @param threadFactory the thread factory.
    112     * @param timeout       the timeout duration.
    113     */
    114    private Watchdog(final ThreadFactory threadFactory, final Duration timeout) {
    115        if (timeout.isNegative() || Duration.ZERO.equals(timeout)) {
    116            throw new IllegalArgumentException("timeout must not be less than 1.");
    117        }
    118        this.timeoutMillis = timeout.toMillis();
    119        this.threadFactory = threadFactory;
    120    }
    121
    122    /**
    123     * Adds a TimeoutObserver.
    124     *
    125     * @param to a TimeoutObserver to add.
    126     */
    127    public void addTimeoutObserver(final TimeoutObserver to) {
    128        observers.addElement(to);
    129    }
    130
    131    /**
    132     * Fires a timeout occurred event for each observer.
    133     */
    134    protected final void fireTimeoutOccured() {
    135        observers.forEach(o -> o.timeoutOccured(this));
    136    }
    137
    138    /**
    139     * Removes a TimeoutObserver.
    140     *
    141     * @param to a TimeoutObserver to remove.
    142     */
    143    public void removeTimeoutObserver(final TimeoutObserver to) {
    144        observers.removeElement(to);
    145    }
    146
    147    @Override
    148    public void run() {
    149        final long startTimeMillis = System.currentTimeMillis();
    150        boolean isWaiting;
    151        synchronized (this) {
    152            long timeLeftMillis = timeoutMillis - (System.currentTimeMillis() - startTimeMillis);
    153            isWaiting = timeLeftMillis > 0;
    154            while (!stopped && isWaiting) {
    155                try {
    156                    wait(timeLeftMillis);
    157                } catch (final InterruptedException ignore) {
    158                    // ignore
    159                }
    160                timeLeftMillis = timeoutMillis - (System.currentTimeMillis() - startTimeMillis);
    161                isWaiting = timeLeftMillis > 0;
    162            }
    163        }
    164
    165        // notify the listeners outside of the synchronized block (see EXEC-60)
    166        if (!isWaiting) {
    167            fireTimeoutOccured();
    168        }
    169    }
    170
    171    /**
    172     * Starts a new thread.
    173     */
    174    public synchronized void start() {
    175        stopped = false;
    176        ThreadUtil.newThread(threadFactory, this, "CommonsExecWatchdog-", true).start();
    177    }
    178
    179    /**
    180     * Requests a thread stop.
    181     */
    182    public synchronized void stop() {
    183        stopped = true;
    184        notifyAll();
    185    }
    186
    187}