// Licensed to the Software Freedom Conservancy (SFC) under one
// or more contributor license agreements.  See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership.  The SFC licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License.  You may obtain a copy of the License at
//
//   http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied.  See the License for the
// specific language governing permissions and limitations
// under the License.

package org.openqa.selenium.concurrent;

import static java.util.concurrent.TimeUnit.MILLISECONDS;

import java.time.Duration;
import java.util.Objects;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;

public class Regularly {

  private final ScheduledExecutorService executor;

  public Regularly(String name) {
    Objects.requireNonNull(name, "Name must be set");

    this.executor = Executors.newScheduledThreadPool(1, r -> new Thread(r, name));
  }

  public void submit(Runnable task, Duration successPeriod, Duration retryPeriod) {
    Objects.requireNonNull(task, "Task to schedule must be set.");
    Objects.requireNonNull(successPeriod, "Success period must be set.");
    Objects.requireNonNull(retryPeriod, "Retry period must be set.");

    executor.schedule(new RetryingRunnable(task, successPeriod.toMillis(), retryPeriod.toMillis()), 0, MILLISECONDS);
  }

  public void shutdown() {
    executor.shutdown();
  }

  private class RetryingRunnable implements Runnable {

    private final Runnable delegate;
    private final long successPeriod;
    private final long retryPeriod;

    public RetryingRunnable(Runnable delegate, long successPeriod, long retryPeriod) {
      this.delegate = delegate;
      this.successPeriod = successPeriod;
      this.retryPeriod = retryPeriod;
    }

    @Override
    public void run() {
      try {
        delegate.run();
        executor.schedule(this, successPeriod, MILLISECONDS);
      } catch (Exception e) {
        executor.schedule(this, retryPeriod, MILLISECONDS);
      }
    }
  }
}

@@ -924,16 +924,94 @@
 ration;%0A
+import java.util.ArrayList;%0Aimport java.util.Iterator;%0Aimport java.util.List;%0A
 import j
@@ -1120,16 +1120,66 @@
 Service;
+%0Aimport java.util.concurrent.atomic.AtomicBoolean;
 %0A%0Apublic
@@ -1248,16 +1248,95 @@
 xecutor;
+%0A  private final List%3CRetryingRunnable%3E scheduledRunnables = new ArrayList%3C%3E();
 %0A%0A  publ
@@ -1810,52 +1810,79 @@
     
-executor.schedule(new RetryingRunnable(task,
+RetryingRunnable runnable = new RetryingRunnable(%0A        task,%0A       
  suc
@@ -1903,16 +1903,24 @@
 illis(),
+%0A       
  retryPe
@@ -1939,84 +1939,584 @@
 s())
-, 0, MILLISECONDS);%0A  %7D%0A%0A  public void shutdown() %7B%0A    executor.shutdown();
+;%0A%0A    synchronized (scheduledRunnables) %7B%0A      scheduledRunnables.add(runnable);%0A    %7D%0A%0A    executor.schedule(runnable, 0, MILLISECONDS);%0A  %7D%0A%0A  public void shutdown() %7B%0A    executor.shutdown();%0A  %7D%0A%0A  public void remove(Runnable runnable) %7B%0A    synchronized (scheduledRunnables) %7B%0A      Iterator%3CRetryingRunnable%3E iterator = scheduledRunnables.iterator();%0A      while (iterator.hasNext()) %7B%0A        RetryingRunnable compareWith = iterator.next();%0A        if (compareWith.equals(runnable)) %7B%0A          iterator.remove();%0A          compareWith.stop();%0A        %7D%0A      %7D%0A    %7D
 %0A  %7D
@@ -2683,16 +2683,81 @@
 yPeriod;
+%0A    private final AtomicBoolean stop = new AtomicBoolean(false);
 %0A%0A    pu
@@ -2990,24 +2990,73 @@
 oid run() %7B%0A
+      if (stop.get()) %7B%0A        return;%0A      %7D%0A%0A
       try %7B%0A
@@ -3240,19 +3240,78 @@
      %7D%0A    %7D
+%0A%0A    public void stop() %7B%0A      this.stop.set(true);%0A    %7D
 %0A  %7D%0A%7D%0A

Allow tasks to no longer be run regularly