Package org.xmlBlaster.util

Source Code of org.xmlBlaster.util.WeakObj

/*------------------------------------------------------------------------------
Name:      Timeout.java
Project:   xmlBlaster.org
Copyright: xmlBlaster.org, see xmlBlaster-LICENSE file
Comment:   Allows you be called back after a given delay.
------------------------------------------------------------------------------*/
package org.xmlBlaster.util;

import java.util.TreeMap;
import java.util.NoSuchElementException;
import org.xmlBlaster.util.def.ErrorCode;

/**
* Allows you be called back after a given delay.
* <p />
* Note that this class should be called Timer, but with JDK 1.3 there will be a
* java.util.Timer.
* <p />
* There is a single background thread that is used to execute the
* I_Timeout.timeout() callback. Timer callbacks should complete quickly. If a
* timeout() takes excessive time to complete, it "hogs" the timer's task
* execution thread. This can, in turn, delay the execution of subsequent tasks,
* which may "bunch up" and execute in rapid succession when (and if) the
* offending task finally completes.
* <p />
* This singleton is thread-safe.
* <p />
* This class does not offer real-time guarantees, but usually notifies you
* within ~ 20 milliseconds of the scheduled time.
* <p />
* Adding or removing a timer is good performing, also when huge amounts of
* timers (> 1000) are used.<br />
* Feeding of 10000: 10362 adds/sec and all updates came in 942 millis (600MHz
* Linux PC with Sun JDK 1.3.1) *
* <p />
* TODO: Use a thread pool to dispatch the timeout callbacks.
* <p />
* Example:<br />
*
* <pre>
*  public class MyClass implements I_Timeout {
*    ...
*    Timeout timeout = new Timeout(&quot;TestTimer&quot;);
*    Timestamp timeoutHandle = timeout.addTimeoutListener(this, 4000L, &quot;myTimeout&quot;);
*    ...
*    public void timeout(Object userData) {
*       // userData contains String &quot;myTimeout&quot;
*       System.out.println(&quot;Timeout happened&quot;);
*       ...
*       // If you want to activate the timer again:
*       timeoutHandle = timeout.addTimeoutListener(this, 4000L, &quot;myTimeout&quot;);
*    }
*    ...
*    // if you want to refresh the timer:
*    timeoutHandle = timeout.refreshTimeoutListener(timeoutHandle, 1500L);
*    ...
*  }
* </pre>
*
* Or a short form:
*
* <pre>
* Timeout timeout = new Timeout(&quot;TestTimer&quot;);
*
* Timestamp timeoutHandle = timeout.addTimeoutListener(new I_Timeout() {
*    public void timeout(Object userData) {
*       System.out.println(&quot;Timeout happened&quot;);
*       System.exit(0);
*    }
* }, 2000L, null);
* </pre>
*
* JDK 1.2 or higher only.
*
* @author xmlBlaster@marcelruff.info
* @see org.xmlBlaster.test.classtest.TimeoutTest
*/
public class Timeout extends Thread {
   /** Name for logging output */
   private static String ME = "Timeout";

   /** Sorted map */
   private TreeMap/*<Timestamp, Container>*/ map = null;

   /** Start/Stop the Timeout manager thread */
   private boolean running = true;

   /** On creation wait until thread started */
   private boolean ready = false;

   /** Switch on debugging output */
   private final boolean debug = false;

   /** Hold only weak reference on callback object? */
   private final boolean useWeakReference;

   /** To avoid sync */
   private boolean mapHasNewEntry;

   /**
    * Create a timer thread with a strong reference on the callback objects.
    */
   public Timeout() {
      this("Timeout-Thread", false);
   }

   /**
    * Create a timer thread with a strong reference on the callback objects.
    *
    * @param name
    *           The name of the thread
    */
   public Timeout(String name) {
      this(name, false);
   }

   /**
    * @param name
    *           The name of the thread
    * @param useWeakReference
    *           If true the reference on your I_Timeout implementation is only
    *           weak referenced and may be garbage collected even that we hold a
    *           weak reference.
    */
   public Timeout(String name, boolean useWeakReference) {
      super(name);
      this.useWeakReference = useWeakReference;
      this.map = new TreeMap();
      setDaemon(true);
      start();
      while (!ready) { // We block until our timer thread is ready
         try {
            Thread.sleep(1);
         } catch (InterruptedException e) {
         }
      }
   }

   /**
    * Get number of current used timers.
    *
    * @return The number of active timers
    */
   public int getSize() {
      synchronized (map) {
         return map.size();
      }
   }

   /**
    * Starts the Timeout manager thread.
    */
   public void run() {
      Container container;
      while (running) {
         long delay = 100000; // sleep veeery long
         container = null;
         synchronized (map) {
            try {
               Timestamp nextWakeup = (Timestamp) map.firstKey(); // throws
                                                                  // exception
                                                                  // if empty
               long next = nextWakeup.getMillis();
               long current = System.currentTimeMillis();
               delay = next - current;
               if (delay <= 0) {
                  container = (Container) map.remove(nextWakeup);
                  if (debug) {
                     long time = System.currentTimeMillis();
                     long diff = time - nextWakeup.getMillis();
                     System.out
                           .println("Timeout occurred, calling listener with real time error of "
                                 + diff + " millis");
                  }
               }
            } catch (NoSuchElementException e) {
               if (debug)
                  System.out
                        .println("The listener map is empty, nothing to do.");
            }
            this.mapHasNewEntry = false;
         }

         if (container != null) {
            I_Timeout callback = container.getCallback();
            // System.out.println("useWeakReference=" + useWeakReference + "
            // callback=" + callback);
            if (callback != null) {
               final Object userData = container.getUserData();
               callback.timeout(userData);
            }
            continue;
         }

         if (delay > 0) {
            try {
               synchronized (this) {
                  ready = true; // only needed on thread creation / startup
                  if (!this.mapHasNewEntry) { // If in the sync gap (~5 lines
                                                // higher) a new timer was
                                                // registered we need to loop
                                                // again and recalculate the
                                                // delay
                     wait(delay);
                  }
               }
            } catch (InterruptedException i) {
               // Wakeing up, and check if there is something to do
            }
         }
      }
   }

   /**
    * Add a listener which gets informed after 'delay' milliseconds.
    * <p />
    * After the timeout happened, you are not registered any more. If you want
    * to cycle timeouts, you need to register again.
    * <p />
    *
    * @param listener
    *           Your callback handle (you need to implement this interface).
    * @param delay
    *           The timeout in milliseconds. You can pass 0L and the Timeout
    *           thread will fire immediately, this can be useful to dispatch a
    *           task to the timeoutlistener
    * @param userData
    *           Some arbitrary data you supply, it will be routed back to you
    *           when the timeout occurs through method I_Timeout.timeout().
    * @return A handle which you can use to unregister with
    *         removeTimeoutListener().
    */
   public final Timestamp addTimeoutListener(I_Timeout listener, long delay,
         Object userData) {
      if (listener == null) {
         throw new IllegalArgumentException(ME
               + ": addTimeoutListener() with listener=null");
      }
      Timestamp key = null;
      // if (delay < 1) System.out.println(ME + ": addTimeoutListener with delay
      // = " + delay);
      int nanoCounter = 0;
      long timeMillis = System.currentTimeMillis();
      synchronized (map) {
         while (true) {
            long endNanos = (timeMillis + delay) * Timestamp.MILLION
                  + nanoCounter;
            key = new Timestamp(endNanos);
            Object obj = map.get(key);
            if (obj == null) {
               map.put(key, new Container(this.useWeakReference, listener,
                     userData));
               break;
            } else {
               nanoCounter++; // We loop to avoid two similar keys, this should
                              // happen very seldom
               // System.out.println("Looping nanoCounter=" + nanoCounter);
            }
         }
         this.mapHasNewEntry = true;
      }
      synchronized (this) {
         notify();
      }
      return key;
   }

   /**
    * Refresh a listener before the timeout happened.
    * <p />
    * NOTE: The returned timeout handle is different from the original one.
    * <p />
    *
    * NOTE: If you are not sure if the key has elapsed already try this:
    *
    * <pre>
    * timeout.removeTimeoutListener(timeoutHandle);
    * timeoutHandle = timeout.addTimeoutListener(this, &quot;1000L&quot;, &quot;UserData&quot;);
    * </pre>
    *
    * @param key
    *           The timeout handle you received by a previous
    *           addTimeoutListener() call.<br />
    *           It is invalid after this call.
    * @param delay
    *           The timeout in milliseconds measured from now.
    * @return A new handle which you can use to unregister with
    *         removeTimeoutListener()
    * @exception XmlBlasterException
    *               if key is null or unknown or invalid because timer elapsed
    *               already
    */
   public final Timestamp refreshTimeoutListener(Timestamp key, long delay)
         throws XmlBlasterException {
      if (key == null) {
         Thread.dumpStack();
         throw new XmlBlasterException(Global.instance(),
               ErrorCode.INTERNAL_NULLPOINTER, ME + "addTimeoutListener",
               "The timeout handle is null, no timeout refresh done");
      }
      Object obj;
      synchronized (map) {
         obj = map.remove(key);
      }

      if (obj == null) {
         String pos = Global.getStackTraceAsString(null);
         throw new XmlBlasterException(Global.instance(),
               ErrorCode.RESOURCE_UNAVAILABLE, ME, "The timeout handle '" + key
                     + "' is unknown, no timeout refresh done: " + pos);
      }
      Container container = (Container) obj;
      I_Timeout callback = container.getCallback();
      if (callback == null) {
         if (this.useWeakReference)
            throw new XmlBlasterException(Global.instance(),
                  ErrorCode.INTERNAL_UNKNOWN, ME,
                  "The weak callback reference for timeout handle '" + key
                        + "' is garbage collected, no timeout refresh done");
         else
            throw new XmlBlasterException(Global.instance(),
                  ErrorCode.INTERNAL_UNKNOWN, ME,
                  "Internal error for timeout handle '" + key
                        + "', no timeout refresh done");
      }
      return addTimeoutListener(callback, delay, container.getUserData());
   }

   /**
    * Checks if key is null -> addTimeoutListener else refreshTimeoutListener()
    * in a thread save way. <br />
    * Note however that your passed key is different from the returned key and
    * you need to synchronize this call to avoid having a stale key (two threads
    * enter this method the same time, the key gets invalid by the first thread
    * and the second passed a stale key as the first thread has not yet returned
    * to update 'key')
    */
   public final Timestamp addOrRefreshTimeoutListener(I_Timeout listener,
         long delay, Object userData, Timestamp key) throws XmlBlasterException {
      if (key == null) {
         return addTimeoutListener(listener, delay, userData);
      } else {
         try {
            return refreshTimeoutListener(key, delay);
         } catch (XmlBlasterException e) {
            if (ErrorCode.RESOURCE_UNAVAILABLE == e.getErrorCode()) {
               return addTimeoutListener(listener, delay, userData);
            }
            throw e;
         }
      }
   }

   /**
    * Remove a listener before the timeout happened.
    * <p />
    *
    * @param key
    *           The timeout handle you received by a previous
    *           addTimeoutListener() call.
    */
   public final void removeTimeoutListener(Timestamp key) {
      if (key == null)
         return;
      synchronized (map) {
         Container container = (Container) map.remove(key);
         if (container != null) {
            container.reset();
            container = null;
         }
      }
   }

   /**
    * Is this handle expired?
    * <p />
    *
    * @param key
    *           The timeout handle you received by a previous
    *           addTimeoutListener() call<br />
    * @return true/false
    */
   public final boolean isExpired(Timestamp key) {
      synchronized (map) {
         return map.get(key) == null;
      }
   }

   /**
    * How long to my timeout.
    * <p />
    *
    * @param key
    *           The timeout handle you received by a previous
    *           addTimeoutListener() call.
    * @return Milliseconds to timeout, or -1 if not known.
    */
   public final long spanToTimeout(Timestamp key) {
      if (key == null) {
         return -1;
      }
      synchronized (map) {
         Container container = (Container) map.get(key);
         if (container == null) {
            return -1;
         }
         // We know that the key contains the timeout date
         return getTimeout(key) - System.currentTimeMillis();
      }
   }

   /**
    * How long am i running.
    * <p />
    *
    * @param key
    *           The timeout handle you received by a previous
    *           addTimeoutListener() call.
    * @return Milliseconds since creation, or -1 if not known.
    */
   public final long elapsed(Timestamp key) {
      synchronized (map) {
         Container container = (Container) map.get(key);
         if (container == null) {
            return -1;
         }
         return System.currentTimeMillis() - container.creation;
      }
   }

   /**
    * Access the end of life span.
    * <p />
    *
    * @param key
    *           The timeout handle you received by a previous
    *           addTimeoutListener() call.
    * @return Time in milliseconds since midnight, January 1, 1970 UTC or -1 if
    *         not known.
    */
   public final long getTimeout(Timestamp key) {
      if (key == null)
         return -1;
      return key.getMillis();
   }

   /**
    * Reset all pending timeouts.
    */
   public final void removeAll() {
      synchronized (map) {
         map.clear();
      }
   }

   /**
    * Reset and stop the Timeout manager thread.
    */
   public void shutdown() {
      removeAll();
      running = false;
      synchronized (this) {
         notify();
      }
      // destroy();
   }

   /**
    * Method for testing only.
    * <p />
    * Invoke: java -Djava.compiler= org.xmlBlaster.util.Timeout
    */
   public static void main(String args[]) throws Exception {
      Timeout t = new Timeout();
      System.out.println("Timeout constructor done, sleeping 10 sec "
            + t.toString());
      try {
         Thread.sleep(10000);
      } catch (InterruptedException e) {
      }
   }

   /** Eample for the standard case */
   static void testStrongReference() {
      Timeout timeout = new Timeout("TestTimer");
      System.out.println("Timer created and ready.");
      Timestamp timeoutHandle = timeout.addTimeoutListener(new I_Timeout() {
         public void timeout(Object userData) {
            System.out.println("Timeout happened");
            System.exit(0);
         }
      }, 2000L, null);
      try {
         Thread.sleep(4000L);
      } catch (InterruptedException e) {
      }
      System.err.println("ERROR: Timeout not occurred "
            + timeoutHandle.toString());
      System.exit(1);
   }

   /** Test a weak reference */
   static void testWeakReference() {
      Timeout timeout = new Timeout("TestTimer", true);
      System.out.println("Timer created and ready.");

      {
         WeakObj weakObj = new WeakObj();
         /* Timestamp timeoutHandle = */timeout.addTimeoutListener(weakObj,
               4000L, weakObj);
         weakObj = null;
      }

      System.gc();
      System.out.println("Called gc()"); // NOTE: Without "weakObj = null" and
                                          // "System.gc()" the test fails
      try {
         Thread.sleep(8000L);
      } catch (InterruptedException e) {
      }
      System.out.println("SUCCESS: No timeout happened");
      System.exit(0);
   }
}

/** Test a weak reference */
final class WeakObj implements I_Timeout {
   public void timeout(Object userData) {
      System.err
            .println("ERROR: Timeout invoked, weak object was not garbage collected.");
      System.exit(1);
   }

   //public void finalize() {
   //   super.finalize();
   //   System.out.println("WeakObj is garbage collected");
   //}
}
TOP

Related Classes of org.xmlBlaster.util.WeakObj

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.