protected void runInThreadPool() {
    try {
      if (isCacheClosing() || getLocalRegion().isClosed() || getLocalRegion().isDestroyed()) {
        return;
      }
      waitOnExpirationSuspension();
      if (logger.isTraceEnabled()) {
        logger.trace("{} is fired", this);
      }

      // do our work...
      performTimeout();
    } catch (RegionDestroyedException re) {
      // Ignore - our job is done
    } catch (EntryNotFoundException ex) {
      // Ignore
    } catch (CancelException ex) {
      // ignore
    } catch (VirtualMachineError err) {
      SystemFailure.initiateFailure(err);
      // If this ever returns, rethrow the error. We're poisoned
      // now, so don't let this thread continue.
      throw err;
    } catch (Throwable ex) {
      // Whenever you catch Error or Throwable, you must also
      // catch VirtualMachineError (see above). However, there is
      // _still_ a possibility that you are dealing with a cascading
      // error condition, so you also need to check to see if the JVM
      // is still usable:
      SystemFailure.checkFailure();
      logger.fatal("Exception in expiration task", ex);
    } finally {
      if (expiryTaskListener != null) {
        expiryTaskListener.afterTaskRan(this);
      }
    }
  }
An ExpiryTask is sent run() to perform its task. Note that this run() method should never throw
an exception - otherwise, it takes out the java.util.Timer thread, causing an exception
whenever we try to schedule more expiration tasks.
geode-core/src/main/java/org/apache/geode/internal/cache/ExpiryTask.java