    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 *      https://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         * Constructs a new instance.
    044         */
    045        public Builder() {
    046            // empty
    047        }
    048
    049        /**
    050         * Creates a new configured ExecuteWatchdog.
    051         *
    052         * @return a new configured ExecuteWatchdog.
    053         */
    054        @Override
    055        public Watchdog get() {
    056            return new Watchdog(threadFactory, timeout);
    057        }
    058
    059        /**
    060         * Sets the thread factory.
    061         *
    062         * @param threadFactory the thread factory.
    063         * @return {@code this} instance.
    064         */
    065        public Builder setThreadFactory(final ThreadFactory threadFactory) {
    066            this.threadFactory = threadFactory;
    067            return this;
    068        }
    069
    070        /**
    071         * Sets the timeout duration.
    072         *
    073         * @param timeout the timeout duration.
    074         * @return {@code this} instance.
    075         */
    076        public Builder setTimeout(final Duration timeout) {
    077            this.timeout = timeout;
    078            return this;
    079        }
    080
    081    }
    082
    083    /**
    084     * Creates a new builder.
    085     *
    086     * @return a new builder.
    087     * @since 1.4.0
    088     */
    089    public static Builder builder() {
    090        return new Builder();
    091    }
    092
    093    private final Vector<TimeoutObserver> observers = new Vector<>(1);
    094
    095    private final long timeoutMillis;
    096
    097    private boolean stopped;
    098
    099    /**
    100     * The thread factory.
    101     */
    102    private final ThreadFactory threadFactory;
    103
    104    /**
    105     * Constructs a new instance.
    106     *
    107     * @param timeoutMillis the timeout duration.
    108     * @deprecated Use {@link Builder#get()}.
    109     */
    110    @Deprecated
    111    public Watchdog(final long timeoutMillis) {
    112        this(null, Duration.ofMillis(timeoutMillis));
    113    }
    114
    115    /**
    116     * Constructs a new instance.
    117     *
    118     * @param threadFactory the thread factory.
    119     * @param timeout       the timeout duration.
    120     */
    121    private Watchdog(final ThreadFactory threadFactory, final Duration timeout) {
    122        if (timeout.isNegative() || Duration.ZERO.equals(timeout)) {
    123            throw new IllegalArgumentException("timeout must not be less than 1.");
    124        }
    125        this.timeoutMillis = timeout.toMillis();
    126        this.threadFactory = threadFactory;
    127    }
    128
    129    /**
    130     * Adds a TimeoutObserver.
    131     *
    132     * @param to a TimeoutObserver to add.
    133     */
    134    public void addTimeoutObserver(final TimeoutObserver to) {
    135        observers.addElement(to);
    136    }
    137
    138    /**
    139     * Fires a timeout occurred event for each observer.
    140     */
    141    protected final void fireTimeoutOccured() {
    142        observers.forEach(o -> o.timeoutOccured(this));
    143    }
    144
    145    /**
    146     * Removes a TimeoutObserver.
    147     *
    148     * @param to a TimeoutObserver to remove.
    149     */
    150    public void removeTimeoutObserver(final TimeoutObserver to) {
    151        observers.removeElement(to);
    152    }
    153
    154    @Override
    155    public void run() {
    156        final long startTimeMillis = System.currentTimeMillis();
    157        boolean isWaiting;
    158        synchronized (this) {
    159            long timeLeftMillis = timeoutMillis - (System.currentTimeMillis() - startTimeMillis);
    160            isWaiting = timeLeftMillis > 0;
    161            while (!stopped && isWaiting) {
    162                try {
    163                    wait(timeLeftMillis);
    164                } catch (final InterruptedException ignore) {
    165                    // ignore
    166                }
    167                timeLeftMillis = timeoutMillis - (System.currentTimeMillis() - startTimeMillis);
    168                isWaiting = timeLeftMillis > 0;
    169            }
    170        }
    171
    172        // notify the listeners outside of the synchronized block (see EXEC-60)
    173        if (!isWaiting) {
    174            fireTimeoutOccured();
    175        }
    176    }
    177
    178    /**
    179     * Starts a new thread.
    180     */
    181    public synchronized void start() {
    182        stopped = false;
    183        ThreadUtil.newThread(threadFactory, this, "CommonsExecWatchdog-", true).start();
    184    }
    185
    186    /**
    187     * Requests a thread stop.
    188     */
    189    public synchronized void stop() {
    190        stopped = true;
    191        notifyAll();
    192    }
    193
    194}