/**
   * Method cleans up all events that either outnumber queue size or exceeds time-to-live value. It
   * does none if someone else cleans up queue (lock is locked) or if there are queue readers
   * (readersNum > 0).
   */
  private void cleanupQueue() {
    long now = U.currentTimeMillis();

    long queueOversize = evts.sizex() - expireCnt;

    for (int i = 0; i < queueOversize && evts.sizex() > expireCnt; i++) {
      GridEvent expired = evts.poll();

      if (log.isDebugEnabled()) log.debug("Event expired by count: " + expired);
    }

    while (true) {
      ConcurrentLinkedDeque8.Node<GridEvent> node = evts.peekx();

      if (node == null) // Queue is empty.
      break;

      GridEvent evt = node.item();

      if (evt == null) // Competing with another thread.
      continue;

      if (now - evt.timestamp() < expireAgeMs) break;

      if (evts.unlinkx(node) && log.isDebugEnabled())
        log.debug("Event expired by age: " + node.item());
    }
  }
  /** {@inheritDoc} */
  @Override
  public void spiStop() throws GridSpiException {
    unregisterMBean();

    // Reset events.
    evts.clear();

    // Ack ok stop.
    if (log.isDebugEnabled()) log.debug(stopInfo());
  }
  /** {@inheritDoc} */
  @Override
  public void record(GridEvent evt) throws GridSpiException {
    assert evt != null;

    // Filter out events.
    if (filter == null || filter.apply(evt)) {
      cleanupQueue();

      evts.add(evt);

      // Make sure to filter out metrics updates to prevent log from flooding.
      if (evt.type() != EVT_NODE_METRICS_UPDATED && log.isDebugEnabled())
        log.debug("Event recorded: " + evt);
    }
  }
  /** {@inheritDoc} */
  @Override
  public void spiStart(String gridName) throws GridSpiException {
    // Start SPI start stopwatch.
    startStopwatch();

    assertParameter(expireCnt > 0, "expireCnt > 0");
    assertParameter(expireAgeMs > 0, "expireAgeMs > 0");

    // Ack parameters.
    if (log.isDebugEnabled()) {
      log.debug(configInfo("expireAgeMs", expireAgeMs));
      log.debug(configInfo("expireCnt", expireCnt));
    }

    registerMBean(gridName, this, GridMemoryEventStorageSpiMBean.class);

    // Ack ok start.
    if (log.isDebugEnabled()) log.debug(startInfo());
  }