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}