/**
   * Called from the {@link DriverConductor}.
   *
   * @param now in nanoseconds
   * @param statusMessageTimeout for sending of Status Messages.
   */
  void trackRebuild(final long now, final long statusMessageTimeout) {
    long minSubscriberPosition = Long.MAX_VALUE;
    long maxSubscriberPosition = Long.MIN_VALUE;

    for (final ReadablePosition subscriberPosition : subscriberPositions) {
      final long position = subscriberPosition.getVolatile();
      minSubscriberPosition = Math.min(minSubscriberPosition, position);
      maxSubscriberPosition = Math.max(maxSubscriberPosition, position);
    }

    final long rebuildPosition = Math.max(this.rebuildPosition.get(), maxSubscriberPosition);
    final long hwmPosition = this.hwmPosition.getVolatile();

    final long scanOutcome =
        lossDetector.scan(
            termBuffers[indexByPosition(rebuildPosition, positionBitsToShift)],
            rebuildPosition,
            hwmPosition,
            now,
            termLengthMask,
            positionBitsToShift,
            initialTermId);

    final int rebuildTermOffset = (int) rebuildPosition & termLengthMask;
    final long newRebuildPosition =
        (rebuildPosition - rebuildTermOffset) + rebuildOffset(scanOutcome);
    this.rebuildPosition.proposeMaxOrdered(newRebuildPosition);

    final long ccOutcome =
        congestionControl.onTrackRebuild(
            now,
            minSubscriberPosition,
            nextSmPosition,
            hwmPosition,
            rebuildPosition,
            newRebuildPosition,
            lossFound(scanOutcome));

    final int window = CongestionControlUtil.receiverWindowLength(ccOutcome);
    final long threshold = CongestionControlUtil.positionThreshold(window);

    if (CongestionControlUtil.shouldForceStatusMessage(ccOutcome)
        || (now > (lastStatusMessageTimestamp + statusMessageTimeout))
        || (minSubscriberPosition > (nextSmPosition + threshold))) {
      scheduleStatusMessage(now, minSubscriberPosition, window);
      cleanBufferTo(minSubscriberPosition - (termLengthMask + 1));
    }
  }
  private boolean isDrained() {
    long minSubscriberPosition = Long.MAX_VALUE;

    for (final ReadablePosition subscriberPosition : subscriberPositions) {
      minSubscriberPosition = Math.min(minSubscriberPosition, subscriberPosition.getVolatile());
    }

    return minSubscriberPosition >= rebuildPosition.get();
  }
  private void cleanBufferTo(final long newCleanPosition) {
    final long cleanPosition = this.cleanPosition;
    final int bytesForCleaning = (int) (newCleanPosition - cleanPosition);
    final UnsafeBuffer dirtyTerm = termBuffers[indexByPosition(cleanPosition, positionBitsToShift)];
    final int termOffset = (int) cleanPosition & termLengthMask;
    final int length = Math.min(bytesForCleaning, dirtyTerm.capacity() - termOffset);

    if (length > 0) {
      dirtyTerm.setMemory(termOffset, length, (byte) 0);
      this.cleanPosition = cleanPosition + length;
    }
  }