Example #1
0
  /**
   * Best-effort attempt to remove an argument from a batch. This may get invoked when a
   * cancellation occurs somewhere downstream. This method finds the first occurrence of an argument
   * in the batch, and removes that occurrence.
   *
   * <p>This is currently O(n). If an O(1) approach is needed, then we need to refactor internals to
   * use a Map instead of Queue. My first pass at this is fairly naive, on the suspicion that
   * unsubscription will be rare enough to not cause a perf problem.
   *
   * @param arg argument to remove from batch
   */
  /* package-private */ void remove(RequestArgumentType arg) {
    if (batchStarted.get()) {
      // nothing we can do
      return;
    }

    if (batchLock.readLock().tryLock()) {
      try {
        /* double-check now that we have the lock - if the batch is started, deleting is useless */
        if (batchStarted.get()) {
          return;
        }

        for (CollapsedRequest<ResponseType, RequestArgumentType> collapsedRequest :
            batchArgumentQueue) {
          if (arg.equals(collapsedRequest.getArgument())) {
            batchArgumentQueue.remove(collapsedRequest);
            count.decrementAndGet();
            return; // just remove a single instance
          }
        }
      } finally {
        batchLock.readLock().unlock();
      }
    }
  }
Example #2
0
 public void shutdown() {
   // take the 'batchStarted' state so offers and execution will not be triggered elsewhere
   if (batchStarted.compareAndSet(false, true)) {
     // get the write lock so offers are synced with this (we don't really need to unlock as this
     // is a one-shot deal to shutdown)
     batchLock.writeLock().lock();
     try {
       // if we win the 'start' and once we have the lock we can now shut it down otherwise another
       // thread will finish executing this batch
       if (count.get() > 0) {
         logger.warn(
             "Requests still exist in queue but will not be executed due to RequestCollapser shutdown: "
                 + count.get(),
             new IllegalStateException());
         /*
          * In the event that there is a concurrency bug or thread scheduling prevents the timer from ticking we need to handle this so the Future.get() calls do not block.
          *
          * I haven't been able to reproduce this use case on-demand but when stressing a machine saw this occur briefly right after the JVM paused (logs stopped scrolling).
          *
          * This safety-net just prevents the CollapsedRequestFutureImpl.get() from waiting on the CountDownLatch until its max timeout.
          */
         for (CollapsedRequest<ResponseType, RequestArgumentType> request : batchArgumentQueue) {
           try {
             ((CollapsedRequestSubject<ResponseType, RequestArgumentType>) request)
                 .setExceptionIfResponseNotReceived(
                     new IllegalStateException("Requests not executed before shutdown."));
           } catch (Exception e) {
             logger.debug("Failed to setException on CollapsedRequestFutureImpl instances.", e);
           }
           /**
            * https://github.com/Netflix/Hystrix/issues/78 Include more info when collapsed
            * requests remain in queue
            */
           logger.warn(
               "Request still in queue but not be executed due to RequestCollapser shutdown. Argument => "
                   + request.getArgument()
                   + "   Request Object => "
                   + request,
               new IllegalStateException());
         }
       }
     } finally {
       batchLock.writeLock().unlock();
     }
   }
 }