/* * Copyright 2014-2017 Real Logic Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package io.aeron.driver; import io.aeron.logbuffer.TermGapScanner; import org.agrona.concurrent.UnsafeBuffer; import static io.aeron.logbuffer.TermGapScanner.scanForGap; /** * Detecting and handling of gaps in a message stream. * * Each detector only notifies a single run of a gap in a message stream. */ public class LossDetector implements TermGapScanner.GapHandler { private static final long TIMER_INACTIVE = -1; private final FeedbackDelayGenerator delayGenerator; private final LossHandler lossHandler; private final Gap scannedGap = new Gap(); private final Gap activeGap = new Gap(); private long expiry = TIMER_INACTIVE; /** * Create a loss detector for a channel. * * @param delayGenerator to use for delay determination * @param lossHandler to call when signalling a gap */ public LossDetector(final FeedbackDelayGenerator delayGenerator, final LossHandler lossHandler) { this.delayGenerator = delayGenerator; this.lossHandler = lossHandler; } /** * Scan for gaps and handle received data. * * The handler keeps track from scan to scan what is a gap and what must have been repaired. * * @param termBuffer to scan * @param rebuildPosition to start scanning from * @param hwmPosition to scan up to * @param now time in nanoseconds * @param termLengthMask used for offset calculation * @param positionBitsToShift used for position calculation * @param initialTermId used by the scanner * @return packed outcome of the scan. */ public long scan( final UnsafeBuffer termBuffer, final long rebuildPosition, final long hwmPosition, final long now, final int termLengthMask, final int positionBitsToShift, final int initialTermId) { boolean lossFound = false; int rebuildOffset = (int)rebuildPosition & termLengthMask; if (rebuildPosition < hwmPosition) { final int rebuildTermCount = (int)(rebuildPosition >>> positionBitsToShift); final int hwmTermCount = (int)(hwmPosition >>> positionBitsToShift); final int rebuildTermId = initialTermId + rebuildTermCount; final int hwmTermOffset = (int)hwmPosition & termLengthMask; final int limitOffset = rebuildTermCount == hwmTermCount ? hwmTermOffset : termBuffer.capacity(); rebuildOffset = scanForGap(termBuffer, rebuildTermId, rebuildOffset, limitOffset, this); if (rebuildOffset < limitOffset) { if (!scannedGap.matches(activeGap)) { activateGap(now, scannedGap); lossFound = true; } checkTimerExpiry(now); } } return pack(rebuildOffset, lossFound); } public void onGap(final int termId, final int offset, final int length) { scannedGap.set(termId, offset, length); } /** * Pack the values for workCount and rebuildOffset into a long for returning on the stack. * * @param rebuildOffset value to be packed. * @param lossFound value to be packed. * @return a long with both ints packed into it. */ public static long pack(final int rebuildOffset, final boolean lossFound) { return ((long)rebuildOffset << 32) | (lossFound ? 1 : 0); } /** * Has loss been found in the scan? * * @param scanOutcome into which the fragments read value has been packed. * @return if loss has been found or not. */ public static boolean lossFound(final long scanOutcome) { return ((int)scanOutcome) != 0; } /** * The offset up to which the log has been rebuilt. * * @param scanOutcome into which the offset value has been packed. * @return the offset up to which the log has been rebuilt. */ public static int rebuildOffset(final long scanOutcome) { return (int)(scanOutcome >>> 32); } private void activateGap(final long now, final Gap gap) { activeGap.set(gap.termId, gap.termOffset, gap.length); if (delayGenerator.shouldFeedbackImmediately()) { expiry = now; } else { expiry = now + delayGenerator.generateDelay(); } } private void checkTimerExpiry(final long now) { if (now >= expiry) { lossHandler.onGapDetected(activeGap.termId, activeGap.termOffset, activeGap.length); expiry = now + delayGenerator.generateDelay(); } } static final class Gap { int termId; int termOffset = -1; int length; public void set(final int termId, final int termOffset, final int length) { this.termId = termId; this.termOffset = termOffset; this.length = length; } public boolean matches(final Gap other) { return other.termId == this.termId && other.termOffset == this.termOffset; } } }