001/**
002 * Logback: the reliable, generic, fast and flexible logging framework.
003 * Copyright (C) 1999-2015, QOS.ch. All rights reserved.
004 *
005 * This program and the accompanying materials are dual-licensed under
006 * either the terms of the Eclipse Public License v1.0 as published by
007 * the Eclipse Foundation
008 *
009 *   or (per the licensee's choosing)
010 *
011 * under the terms of the GNU Lesser General Public License version 2.1
012 * as published by the Free Software Foundation.
013 */
014package ch.qos.logback.core.util;
015
016/**
017 * This class serves as a gateway for invocations of a "costly" operation on a
018 * critical execution path.
019 *
020 * @author Ceki Gülcü
021 */
022public class DefaultInvocationGate implements InvocationGate {
023
024    static final int MASK_DECREASE_RIGHT_SHIFT_COUNT = 2;
025
026    // experiments indicate that even for the most CPU intensive applications with
027    // 200 or more threads MASK
028    // values in the order of 0xFFFF is appropriate
029    private static final int MAX_MASK = 0xFFFF;
030    static final int DEFAULT_MASK = 0xF;
031
032    private volatile long mask = DEFAULT_MASK;
033    // private volatile long lastMaskCheck = System.currentTimeMillis();
034
035    // IMPORTANT: This field can be updated by multiple threads. It follows that
036    // its values may *not* be incremented sequentially. However, we don't care
037    // about the actual value of the field except that from time to time the
038    // expression (invocationCounter++ & mask) == mask) should be true.
039    private long invocationCounter = 0;
040
041    // if less than thresholdForMaskIncrease milliseconds elapse between invocations
042    // of updateMaskIfNecessary()
043    // method, then the mask should be increased
044    private static final long MASK_INCREASE_THRESHOLD = 100;
045
046    // if more than thresholdForMaskDecrease milliseconds elapse between invocations
047    // of updateMaskIfNecessary() method,
048    // then the mask should be decreased
049    private static final long MASK_DECREASE_THRESHOLD = MASK_INCREASE_THRESHOLD * 8;
050
051    public DefaultInvocationGate() {
052        this(MASK_INCREASE_THRESHOLD, MASK_DECREASE_THRESHOLD, System.currentTimeMillis());
053    }
054
055    public DefaultInvocationGate(long minDelayThreshold, long maxDelayThreshold, long currentTime) {
056        this.minDelayThreshold = minDelayThreshold;
057        this.maxDelayThreshold = maxDelayThreshold;
058        this.lowerLimitForMaskMatch = currentTime + minDelayThreshold;
059        this.upperLimitForNoMaskMatch = currentTime + maxDelayThreshold;
060    }
061
062    private long minDelayThreshold;
063    private long maxDelayThreshold;
064
065    long lowerLimitForMaskMatch;
066    long upperLimitForNoMaskMatch;
067
068    /*
069     * (non-Javadoc)
070     * 
071     * @see ch.qos.logback.core.util.InvocationGate#skipFurtherWork()
072     */
073    @Override
074    final public boolean isTooSoon(long currentTime) {
075        boolean maskMatch = ((invocationCounter++) & mask) == mask;
076
077        if (maskMatch) {
078            if (currentTime < this.lowerLimitForMaskMatch) {
079                increaseMask();
080            }
081            updateLimits(currentTime);
082        } else {
083            if (currentTime > this.upperLimitForNoMaskMatch) {
084                decreaseMask();
085                updateLimits(currentTime);
086                return false;
087            }
088        }
089        return !maskMatch;
090    }
091
092    private void updateLimits(long currentTime) {
093        this.lowerLimitForMaskMatch = currentTime + minDelayThreshold;
094        this.upperLimitForNoMaskMatch = currentTime + maxDelayThreshold;
095    }
096
097    // package private, for testing purposes only
098    long getMask() {
099        return mask;
100    }
101
102    private void increaseMask() {
103        if (mask >= MAX_MASK)
104            return;
105        mask = (mask << 1) | 1;
106    }
107
108    private void decreaseMask() {
109        mask = mask >>> MASK_DECREASE_RIGHT_SHIFT_COUNT;
110    }
111
112    public long getInvocationCounter() {
113        return invocationCounter;
114    }
115}