From d729b6e939025e339a60587b1125813a3a9e3514 Mon Sep 17 00:00:00 2001
From: matthew_swift <matthew_swift@localhost>
Date: Mon, 20 Apr 2009 14:44:30 +0000
Subject: [PATCH] Fix issue 3179: Thread left in the system after shutdown is called.

---
 opendj-sdk/opends/src/server/org/opends/server/util/TimeThread.java |  496 ++++++++++++++++++++++++++++++++++---------------------
 1 files changed, 306 insertions(+), 190 deletions(-)

diff --git a/opendj-sdk/opends/src/server/org/opends/server/util/TimeThread.java b/opendj-sdk/opends/src/server/org/opends/server/util/TimeThread.java
index 38bcd0b..27399b4 100644
--- a/opendj-sdk/opends/src/server/org/opends/server/util/TimeThread.java
+++ b/opendj-sdk/opends/src/server/org/opends/server/util/TimeThread.java
@@ -28,154 +28,138 @@
 
 
 
+import static org.opends.server.loggers.debug.DebugLogger.*;
+
 import java.text.SimpleDateFormat;
 import java.util.Calendar;
 import java.util.Date;
 import java.util.GregorianCalendar;
 import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
 import java.util.TimeZone;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ThreadFactory;
+import java.util.concurrent.TimeUnit;
 
 import org.opends.server.api.DirectoryThread;
-
-import static org.opends.server.loggers.debug.DebugLogger.*;
 import org.opends.server.loggers.debug.DebugTracer;
 import org.opends.server.schema.GeneralizedTimeSyntax;
 import org.opends.server.types.DebugLogLevel;
-import static org.opends.server.util.ServerConstants.*;
+
 
 
 /**
- * This class defines a thread that will wake up periodically, get the current
- * time, and store various representations of it.  Note that only limited
- * debugging will be performed in this class due to the frequency with which it
- * will be called.
+ * This class provides an application-wide timing service. It provides
+ * the ability to retrieve the current time in various different formats
+ * and resolutions.
  */
 @org.opends.server.types.PublicAPI(
-     stability=org.opends.server.types.StabilityLevel.UNCOMMITTED,
-     mayInstantiate=false,
-     mayExtend=false,
-     mayInvoke=true)
+    stability = org.opends.server.types.StabilityLevel.UNCOMMITTED,
+    mayInstantiate = false,
+    mayExtend = false,
+    mayInvoke = true)
 public final class TimeThread
-       extends DirectoryThread
 {
-  /**
-   * The tracer object for the debug logger.
-   */
-  private static final DebugTracer TRACER = getTracer();
-
-
 
   /**
-   * The singleton instance for this time thread.
+   * Timer job.
    */
-  private static final TimeThread threadInstance = new TimeThread();
-
-
-
-  // The calendar holding the current time.
-  private static GregorianCalendar calendar;
-
-  // A set of arbitrary formatters that should be maintained by this time
-  // thread.
-  private static CopyOnWriteArrayList<SimpleDateFormat> userDefinedFormatters;
-
-  // A set of abitrary formatted times, mapped from format string to the
-  // corresponding formatted time representation.
-  private static ConcurrentHashMap<String,String> userDefinedTimeStrings;
-
-  // The date for this time thread.
-  private static Date date;
-
-  // The current time in HHmm form as an integer.
-  private static int hourAndMinute;
-
-  // The current time in milliseconds since the epoch.
-  private static volatile long time;
-
-  // The current time in nanoseconds.
-  private static volatile long nanoTime;
-
-  // The date formatter that will be used to obtain the local timestamp.
-  private static SimpleDateFormat localTimestampFormatter;
-
-  // The date formatter that will be used to obtain the GMT timestamp.
-  private static SimpleDateFormat gmtTimestampFormatter;
-
-  // The timestamp for this time thread in the generalized time format.
-  private static String generalizedTime;
-
-  // The timestamp for this time thread in the local time zone.
-  private static String localTimestamp;
-
-  // The timestamp for this time thread in GMT.
-  private static String gmtTimestamp;
-
-
-
-  /**
-   * Creates a new instance of this time thread and starts it.
-   */
-  private TimeThread()
+  private static final class TimeInfo implements Runnable
   {
-    super("Time Thread");
 
-    setDaemon(true);
+    // The calendar holding the current time.
+    private GregorianCalendar calendar;
 
-    userDefinedFormatters  = new CopyOnWriteArrayList<SimpleDateFormat>();
-    userDefinedTimeStrings = new ConcurrentHashMap<String,String>();
+    // The date for this time thread.
+    private Date date;
 
-    TimeZone utcTimeZone = TimeZone.getTimeZone(TIME_ZONE_UTC);
+    // The timestamp for this time thread in the generalized time
+    // format.
+    private String generalizedTime;
 
-    gmtTimestampFormatter = new SimpleDateFormat(DATE_FORMAT_GMT_TIME);
-    gmtTimestampFormatter.setTimeZone(utcTimeZone);
+    // The timestamp for this time thread in GMT.
+    private String gmtTimestamp;
 
-    localTimestampFormatter = new SimpleDateFormat(DATE_FORMAT_LOCAL_TIME);
+    // The date formatter that will be used to obtain the GMT
+    // timestamp.
+    private final SimpleDateFormat gmtTimestampFormatter;
 
-    calendar        = new GregorianCalendar();
-    date            = calendar.getTime();
-    time            = date.getTime();
-    nanoTime        = System.nanoTime();
-    generalizedTime = GeneralizedTimeSyntax.format(date);
-    localTimestamp  = localTimestampFormatter.format(date);
-    gmtTimestamp    = gmtTimestampFormatter.format(date);
-    hourAndMinute   = (calendar.get(Calendar.HOUR_OF_DAY) * 100) +
-                      calendar.get(Calendar.MINUTE);
+    // The current time in HHmm form as an integer.
+    private int hourAndMinute;
 
-    start();
-  }
+    // The timestamp for this time thread in the local time zone.
+    private String localTimestamp;
+
+    // The date formatter that will be used to obtain the local
+    // timestamp.
+    private final SimpleDateFormat localTimestampFormatter;
+
+    // The current time in nanoseconds.
+    private volatile long nanoTime;
+
+    // The current time in milliseconds since the epoch.
+    private volatile long time;
+
+    // A set of arbitrary formatters that should be maintained by this
+    // time thread.
+    private final List<SimpleDateFormat> userDefinedFormatters;
+
+    // A set of abitrary formatted times, mapped from format string to
+    // the corresponding formatted time representation.
+    private final Map<String, String> userDefinedTimeStrings;
 
 
 
-  /**
-   * Operates in a loop, getting the current time and then sleeping briefly
-   * before checking again.
-   */
-  @Override
-  public void run()
-  {
-    while (true)
+    /**
+     * Create a new job with the specified delay.
+     */
+    public TimeInfo()
+    {
+      userDefinedFormatters =
+          new CopyOnWriteArrayList<SimpleDateFormat>();
+      userDefinedTimeStrings = new ConcurrentHashMap<String, String>();
+
+      TimeZone utcTimeZone = TimeZone.getTimeZone("UTC");
+
+      gmtTimestampFormatter = new SimpleDateFormat("yyyyMMddHHmmss'Z'");
+      gmtTimestampFormatter.setTimeZone(utcTimeZone);
+
+      localTimestampFormatter =
+          new SimpleDateFormat("dd/MMM/yyyy:HH:mm:ss Z");
+
+      // Populate initial values.
+      run();
+    }
+
+
+
+    /**
+     * {@inheritDoc}
+     */
+    public void run()
     {
       try
       {
-        calendar        = new GregorianCalendar();
-        date            = calendar.getTime();
-        time            = date.getTime();
-        nanoTime        = System.nanoTime();
+        calendar = new GregorianCalendar();
+        date = calendar.getTime();
+        time = date.getTime();
+        nanoTime = System.nanoTime();
         generalizedTime = GeneralizedTimeSyntax.format(date);
-        localTimestamp  = localTimestampFormatter.format(date);
-        gmtTimestamp    = gmtTimestampFormatter.format(date);
-        hourAndMinute   = (calendar.get(Calendar.HOUR_OF_DAY) * 100) +
-                          calendar.get(Calendar.MINUTE);
+        localTimestamp = localTimestampFormatter.format(date);
+        gmtTimestamp = gmtTimestampFormatter.format(date);
+        hourAndMinute =
+            calendar.get(Calendar.HOUR_OF_DAY) * 100
+                + calendar.get(Calendar.MINUTE);
 
         for (SimpleDateFormat format : userDefinedFormatters)
         {
-          userDefinedTimeStrings.put(format.toPattern(),
-                                     format.format(date));
+          userDefinedTimeStrings.put(format.toPattern(), format
+              .format(date));
         }
-
-        Thread.sleep(200);
       }
       catch (Exception e)
       {
@@ -187,136 +171,203 @@
     }
   }
 
+  /**
+   * Thread factory used by the scheduled execution service.
+   */
+  private static final class TimeThreadFactory implements
+      ThreadFactory
+  {
+
+    /**
+     * {@inheritDoc}
+     */
+    public Thread newThread(Runnable r)
+    {
+      Thread t = new DirectoryThread(r, "Time Thread");
+      t.setDaemon(true);
+      return t;
+    }
+
+  }
+
+
+
+  // The singleton instance.
+  private static TimeThread INSTANCE = new TimeThread();
+
+  // The tracer object for the debug logger.
+  private static final DebugTracer TRACER = getTracer();
+
 
 
   /**
-   * Retrieves a <CODE>Calendar</CODE> containing the time at the last update.
+   * Retrieves a <CODE>Calendar</CODE> containing the time at the last
+   * update.
    *
-   * @return  A <CODE>Calendar</CODE> containing the time at the last update.
+   * @return A <CODE>Calendar</CODE> containing the time at the last
+   *         update.
+   * @throws IllegalStateException
+   *           If the time service has not been started.
    */
-  public static Calendar getCalendar()
+  public static Calendar getCalendar() throws IllegalStateException
   {
-    return calendar;
+    checkState();
+    return INSTANCE.timeInfo.calendar;
   }
 
 
 
   /**
-   * Retrieves a <CODE>Date</CODE> containing the time at the last update.
+   * Retrieves a <CODE>Date</CODE> containing the time at the last
+   * update.
    *
-   * @return  A <CODE>Date</CODE> containing the time at the last update.
+   * @return A <CODE>Date</CODE> containing the time at the last update.
+   * @throws IllegalStateException
+   *           If the time service has not been started.
    */
-  public static Date getDate()
+  public static Date getDate() throws IllegalStateException
   {
-    return date;
+    checkState();
+    return INSTANCE.timeInfo.date;
   }
 
 
 
   /**
-   * Retrieves the time in milliseconds since the epoch at the last update.
+   * Retrieves a string containing a normalized representation of the
+   * current time in a generalized time format. The timestamp will look
+   * like "20050101000000.000Z".
    *
-   * @return  The time in milliseconds since the epoch at the last update.
+   * @return A string containing a normalized representation of the
+   *         current time in a generalized time format.
+   * @throws IllegalStateException
+   *           If the time service has not been started.
    */
-  public static long getTime()
+  public static String getGeneralizedTime() throws IllegalStateException
   {
-    return time;
-  }
-
-  /**
-   * Retrieves the time in nanoseconds from the most precise available system
-   * timer. The value retured represents nanoseconds since some fixed but
-   * arbitrary time.
-   *
-   * @return The time in nanoseconds from some fixed but arbitrary time.
-   */
-  public static long getNanoTime()
-  {
-    return nanoTime;
+    checkState();
+    return INSTANCE.timeInfo.generalizedTime;
   }
 
 
 
   /**
-   * Retrieves a string containing a normalized representation of the current
-   * time in a generalized time format.  The timestamp will look like
-   * "20050101000000.000Z".
+   * Retrieves a string containing the current time in GMT. The
+   * timestamp will look like "20050101000000Z".
    *
-   * @return  A string containing a normalized representation of the current
-   *          time in a generalized time format.
+   * @return A string containing the current time in GMT.
+   * @throws IllegalStateException
+   *           If the time service has not been started.
    */
-  public static String getGeneralizedTime()
+  public static String getGMTTime() throws IllegalStateException
   {
-    return generalizedTime;
-  }
-
-
-
-  /**
-   * Retrieves a string containing the current time in the local time zone.  The
-   * timestamp format will look like "01/Jan/2005:00:00:00 -0600".
-   *
-   * @return  A string containing the current time in the local time zone.
-   */
-  public static String getLocalTime()
-  {
-    return localTimestamp;
-  }
-
-
-
-  /**
-   * Retrieves a string containing the current time in GMT.  The timestamp will
-   * look like "20050101000000Z".
-   *
-   * @return  A string containing the current time in GMT.
-   */
-  public static String getGMTTime()
-  {
-    return gmtTimestamp;
+    checkState();
+    return INSTANCE.timeInfo.gmtTimestamp;
   }
 
 
 
   /**
    * Retrieves an integer containing the time in HHmm format at the last
-   * update.  It will be calculated as "(hourOfDay*100) + minuteOfHour".
+   * update. It will be calculated as "(hourOfDay*100) + minuteOfHour".
    *
-   * @return  An integer containing the time in HHmm format at the last update.
+   * @return An integer containing the time in HHmm format at the last
+   *         update.
+   * @throws IllegalStateException
+   *           If the time service has not been started.
    */
-  public static int getHourAndMinute()
+  public static int getHourAndMinute() throws IllegalStateException
   {
-    return hourAndMinute;
+    checkState();
+    return INSTANCE.timeInfo.hourAndMinute;
   }
 
 
 
   /**
-   * Retrieves the current time formatted using the given format string.  The
-   * first time this method is used with a given format string, it will be used
-   * to create a formatter that will generate the time string.  That formatter
-   * will then be put into a list so that it will be maintained automatically
-   * for future use.
+   * Retrieves a string containing the current time in the local time
+   * zone. The timestamp format will look like
+   * "01/Jan/2005:00:00:00 -0600".
    *
-   * @param  formatString  The string that defines the format of the time string
-   *                       to retrieve.
+   * @return A string containing the current time in the local time
+   *         zone.
+   * @throws IllegalStateException
+   *           If the time service has not been started.
+   */
+  public static String getLocalTime() throws IllegalStateException
+  {
+    checkState();
+    return INSTANCE.timeInfo.localTimestamp;
+  }
+
+
+
+  /**
+   * Retrieves the time in nanoseconds from the most precise available
+   * system timer. The value retured represents nanoseconds since some
+   * fixed but arbitrary time.
    *
-   * @return  The formatted time string.
+   * @return The time in nanoseconds from some fixed but arbitrary time.
+   * @throws IllegalStateException
+   *           If the time service has not been started.
+   */
+  public static long getNanoTime() throws IllegalStateException
+  {
+    checkState();
+    return INSTANCE.timeInfo.nanoTime;
+  }
+
+
+
+  /**
+   * Retrieves the time in milliseconds since the epoch at the last
+   * update.
    *
-   * @throws  IllegalArgumentException  If the provided format string is
-   *                                    invalid.
+   * @return The time in milliseconds since the epoch at the last
+   *         update.
+   * @throws IllegalStateException
+   *           If the time service has not been started.
+   */
+  public static long getTime() throws IllegalStateException
+  {
+    checkState();
+    return INSTANCE.timeInfo.time;
+  }
+
+
+
+  /**
+   * Retrieves the current time formatted using the given format string.
+   * <p>
+   * The first time this method is used with a given format string, it
+   * will be used to create a formatter that will generate the time
+   * string. That formatter will then be put into a list so that it will
+   * be maintained automatically for future use.
+   *
+   * @param formatString
+   *          The string that defines the format of the time string to
+   *          retrieve.
+   * @return The formatted time string.
+   * @throws IllegalArgumentException
+   *           If the provided format string is invalid.
+   * @throws IllegalStateException
+   *           If the time service has not been started.
    */
   public static String getUserDefinedTime(String formatString)
-         throws IllegalArgumentException
+      throws IllegalArgumentException, IllegalStateException
   {
-    String timeString = userDefinedTimeStrings.get(formatString);
+    checkState();
+
+    String timeString =
+        INSTANCE.timeInfo.userDefinedTimeStrings.get(formatString);
 
     if (timeString == null)
     {
       SimpleDateFormat formatter = new SimpleDateFormat(formatString);
-      timeString = formatter.format(date);
-      userDefinedTimeStrings.put(formatString, timeString);
-      userDefinedFormatters.add(formatter);
+      timeString = formatter.format(INSTANCE.timeInfo.date);
+      INSTANCE.timeInfo.userDefinedTimeStrings.put(formatString,
+          timeString);
+      INSTANCE.timeInfo.userDefinedFormatters.add(formatter);
     }
 
     return timeString;
@@ -325,16 +376,24 @@
 
 
   /**
-   * Removes the user-defined time formatter from this time thread so that it
-   * will no longer be maintained.  This is a safe operation because if the
-   * same format string is used for multiple purposes then it will be added back
-   * the next time a time is requested with the given format.
+   * Removes the user-defined time formatter from this time thread so
+   * that it will no longer be maintained. This is a safe operation
+   * because if the same format string is used for multiple purposes
+   * then it will be added back the next time a time is requested with
+   * the given format.
    *
-   * @param  formatString  The format string for the date formatter to remove.
+   * @param formatString
+   *          The format string for the date formatter to remove.
+   * @throws IllegalStateException
+   *           If the time service has not been started.
    */
   public static void removeUserDefinedFormatter(String formatString)
+    throws IllegalStateException
   {
-    Iterator<SimpleDateFormat> iterator = userDefinedFormatters.iterator();
+    checkState();
+
+    Iterator<SimpleDateFormat> iterator =
+        INSTANCE.timeInfo.userDefinedFormatters.iterator();
     while (iterator.hasNext())
     {
       SimpleDateFormat format = iterator.next();
@@ -344,7 +403,64 @@
       }
     }
 
-    userDefinedTimeStrings.remove(formatString);
+    INSTANCE.timeInfo.userDefinedTimeStrings.remove(formatString);
+  }
+
+
+
+  /**
+   * Starts the time service if it has not already been started.
+   */
+  public static void start()
+  {
+    if (INSTANCE == null)
+    {
+      INSTANCE = new TimeThread();
+    }
+  }
+
+
+
+  /**
+   * Stops the time service if it has not already been stopped.
+   */
+  public static void stop()
+  {
+    if (INSTANCE != null)
+    {
+      INSTANCE.scheduler.shutdown();
+      INSTANCE = null;
+    }
+  }
+
+
+
+  // Ensures that the time service has been started.
+  private static void checkState() throws IllegalStateException
+  {
+    if (INSTANCE == null)
+    {
+      throw new IllegalStateException("Time service not started");
+    }
+  }
+
+
+
+  // The scheduler.
+  private final ScheduledExecutorService scheduler =
+      Executors.newSingleThreadScheduledExecutor(new TimeThreadFactory());
+
+  // The current time information.
+  private final TimeInfo timeInfo = new TimeInfo();
+
+
+
+  /**
+   * Creates a new instance of this time service and starts it.
+   */
+  private TimeThread()
+  {
+    this.scheduler.scheduleWithFixedDelay(timeInfo, 0, 200,
+        TimeUnit.MILLISECONDS);
   }
 }
-

--
Gitblit v1.10.0