# ProtectedTimer.java

    /*
    ** Module   : ProtectedTimer.java
    ** Abstract : Timer implemetation which is protected from out of memory errors
    **            during task execution.
    **
    ** Copyright (c) 2004-2023, Golden Code Development Corporation.
    **
    ** -#- -I- --Date-- --JPRM-- ----------------Description---------------------
    ** 001 SVL 20090720   @43236 Created initial version. OOME-protected timer
    *                            implementation.
    ** 002 GBB 20230512          Logging methods replaced by CentralLogger/ConversionStatus.
    */
    /*
    ** This program is free software: you can redistribute it and/or modify
    ** it under the terms of the GNU Affero General Public License as
    ** published by the Free Software Foundation, either version 3 of the
    ** License, or (at your option) any later version.
    **
    ** This program is distributed in the hope that it will be useful,
    ** but WITHOUT ANY WARRANTY; without even the implied warranty of
    ** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    ** GNU Affero General Public License for more details.
    **
    ** You may find a copy of the GNU Affero GPL version 3 at the following
    ** location: https://www.gnu.org/licenses/agpl-3.0.en.html
    **
    ** Additional terms under GNU Affero GPL version 3 section 7:
    **
    **   Under Section 7 of the GNU Affero GPL version 3, the following additional
    **   terms apply to the works covered under the License.  These additional terms
    **   are non-permissive additional terms allowed under Section 7 of the GNU
    **   Affero GPL version 3 and may not be removed by you.
    **
    **   0. Attribution Requirement.
    **
    **     You must preserve all legal notices or author attributions in the covered
    **     work or Appropriate Legal Notices displayed by works containing the covered
    **     work.  You may not remove from the covered work any author or developer
    **     credit already included within the covered work.
    **
    **   1. No License To Use Trademarks.
    **
    **     This license does not grant any license or rights to use the trademarks
    **     Golden Code, FWD, any Golden Code or FWD logo, or any other trademarks
    **     of Golden Code Development Corporation. You are not authorized to use the
    **     name Golden Code, FWD, or the names of any author or contributor, for
    **     publicity purposes without written authorization.
    **
    **   2. No Misrepresentation of Affiliation.
    **
    **     You may not represent yourself as Golden Code Development Corporation or FWD.
    **
    **     You may not represent yourself for publicity purposes as associated with
    **     Golden Code Development Corporation, FWD, or any author or contributor to
    **     the covered work, without written authorization.
    **
    **   3. No Misrepresentation of Source or Origin.
    **
    **     You may not represent the covered work as solely your work.  All modified
    **     versions of the covered work must be marked in a reasonable way to make it
    **     clear that the modified work is not originating from Golden Code Development
    **     Corporation or FWD.  All modified versions must contain the notices of
    **     attribution required in this license.
    */

    package com.goldencode.p2j.util;

    import com.goldencode.p2j.util.logging.*;

    import java.util.concurrent.*;

    /**
     * A timer facility which protects against out-of-memory errors.  Like
     * <code>java.util.Timer</code>, tasks are scheduled for future execution in a
     * background thread, either for one-time execution, or repeated execution at
     * regular intervals.
     * <p>
     * Each <code>ProtectedTimer</code> object launches a single background
     * thread on which all of the timer's tasks are executed sequentially.  If
     * tasks are not scheduled to repeat, <code>schedulingDone()</code> should be
     * invoked after all required tasks has been scheduled.  This will cause the
     * background thread stop after all tasks has been completed or cancelled.
     * Otherwise the background thread will continue running indefinitely (for a
     * non-daemon thread) or until the JVM exits (for a daemon thread).
     * <p>
     * This implementation is protected from out of memory errors.  If an
     * <code>OutOfMemoryError</code> is thrown on the background thread, it is
     * caught and logged if possible, but processing is allowed to continue.
     * If a task is running at the time of the error, there is no guarantee it has
     * completed its work (protected tasks themselves should handle out of memory
     * errors in a way appropriate to their implementations).  The existing
     * schedule is honored, however (to the extent possible), since the background
     * thread keeps running.
     * <p>
     * This class is thread-safe:  multiple threads can share a single
     * <code>ProtectedTimer</code> object without the need for external
     * synchronization.
     */
    public class ProtectedTimer
    {
       /** Logger */
       private static final CentralLogger LOG = CentralLogger.get(ProtectedTimer.class);

       /** The variable used to generate sequential timer numbers. */
       private static int timerNumber = 0;

       /** Background thread for running scheduled tasks. */
       private final TimerThread timerThread = new TimerThread();

       /** Error message to be logged in the event of OutOfMemoryError */
       private final String oomeMessage;

       /** Queue of the scheduled tasks. */
       private DelayQueue<ProtectedTimerTask> queue =
          new DelayQueue<ProtectedTimerTask>();

       /**
        * Constructs a protected timer with non-daemon background thread.
        */
       public ProtectedTimer()
       {
          this(false);
       }

       /**
        * Constructs a protected timer.
        *
        * @param   isDaemon
        *          <code>true</code> if the background timer thread should be run
        *          as a daemon.
        */
       public ProtectedTimer(boolean isDaemon)
       {
          this("ProtectedTimer-" + getNextTimerNumber(), isDaemon);
       }

       /**
        * Constructs a protected timer.
        *
        * @param   threadName
        *          Name of the timer's background thread.
        * @param   isDaemon
        *          <code>true</code> if the background timer thread should be run
        *          as a daemon.
        */
       public ProtectedTimer(String threadName, boolean isDaemon)
       {
          oomeMessage = "OutOfMemoryError in protected timer " + threadName;
          timerThread.setName(threadName);
          timerThread.setDaemon(isDaemon);
          timerThread.start();
       }

       /**
        * Return next sequential unique timer number.
        *
        * @return  See above.
        */
       private static synchronized int getNextTimerNumber()
       {
          return timerNumber++;
       }

       /**
        * Schedules a non-repeating task to the timer.
        *
        * @param   task
        *          Task to be scheduled.
        * @param   initialDelay
        *          Delay before execution of the task.
        */
       public void schedule(ProtectedTimerTask task, long initialDelay)
       {
          schedule(task, initialDelay, 0);
       }

       /**
        * Schedules a repeating task to the timer.
        *
        * @param   task
        *          Task to be scheduled.
        * @param   initialDelay
        *          Delay before execution of the task.
        * @param   periodicalDelay
        *          Interval between the subsequent executions of the task.
        */
       public void schedule(ProtectedTimerTask task,
                            long initialDelay,
                            long periodicalDelay)
       {
          synchronized (timerThread)
          {
             if (!timerThread.newTasksMayBeScheduled)
                throw new IllegalStateException("Timer is not active!");

             task.initialSchedule(initialDelay, periodicalDelay);
             queue.add(task);
          }
       }

       /**
        * Immediately stops all scheduled tasks and background timer thread.
        */
       public void cancel()
       {
          synchronized (timerThread)
          {
             timerThread.newTasksMayBeScheduled = false;
             timerThread.interrupt();
          }
       }

       /**
        * Should be called if the background timer thread should exit after all
        * scheduled tasks has been completed or cancelled. Makes the background
        * thread immediately exit if there are no scheduled tasks.
        */
       public void schedulingDone()
       {
          synchronized (timerThread)
          {
             timerThread.newTasksMayBeScheduled = false;
             if (queue.size() == 0)
             {
                timerThread.interrupt();
             }
          }
       }

       /**
        * Background timer thread.
        */
       private class TimerThread
       extends Thread
       {
          /** Indicates whether new tasks may be scheduled to this thread. */
          boolean newTasksMayBeScheduled = true;

          /**
           * Main thread loop that performs sequential task executions.
           */
          public void run()
          {
             ProtectedTimerTask task = null;

             while (true)
             {
                try
                {
                   if (task != null && task.reschedule())
                   {
                      queue.add(task);
                      task = null;
                   }

                   synchronized (this)
                   {
                      if (!newTasksMayBeScheduled && queue.size() == 0)
                      {
                         break;
                      }
                   }

                   // blocking
                   task = queue.take();

                   if (!task.isCancelled())
                   {
                      task.run();
                   }
                }
                catch (InterruptedException e)
                {
                   break;
                }
                catch (OutOfMemoryError e)
                {
                   // Just log the error. Let the tasks to be executed into their
                   // scheduled time.

                   try
                   {
                      LOG.severe(oomeMessage);
                   }
                   catch (OutOfMemoryError err)
                   {
                      // Ignore.
                   }
                }
             }
          }
       }
    }