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 critical execution path.
018 *
019 * @author Ceki Gülcü
020 */
021public class DefaultInvocationGate implements InvocationGate {
022
023    static final int MASK_DECREASE_RIGHT_SHIFT_COUNT = 2;
024
025    // experiments indicate that even for the most CPU intensive applications with 200 or more threads MASK
026    // values in the order of 0xFFFF is appropriate
027    private static final int MAX_MASK = 0xFFFF;
028    static final int DEFAULT_MASK = 0xF;
029
030    private volatile long mask = DEFAULT_MASK;
031    //private volatile long lastMaskCheck = System.currentTimeMillis();
032
033    // IMPORTANT: This field can be updated by multiple threads. It follows that
034    // its values may *not* be incremented sequentially. However, we don't care
035    // about the actual value of the field except that from time to time the
036    // expression (invocationCounter++ & mask) == mask) should be true.
037    private long invocationCounter = 0;
038
039    // if less than thresholdForMaskIncrease milliseconds elapse between invocations of updateMaskIfNecessary()
040    // method, then the mask should be increased
041    private static final long MASK_INCREASE_THRESHOLD = 100;
042
043    // if more than thresholdForMaskDecrease milliseconds elapse between invocations of updateMaskIfNecessary() method,
044    // then the mask should be decreased
045    private static final long MASK_DECREASE_THRESHOLD = MASK_INCREASE_THRESHOLD * 8;
046
047    
048    public DefaultInvocationGate() {
049        this(MASK_INCREASE_THRESHOLD, MASK_DECREASE_THRESHOLD, System.currentTimeMillis());
050    }
051    
052    public  DefaultInvocationGate(long minDelayThreshold, long maxDelayThreshold, long currentTime) {
053        this.minDelayThreshold = minDelayThreshold;
054        this.maxDelayThreshold = maxDelayThreshold; 
055        this.lowerLimitForMaskMatch = currentTime + minDelayThreshold;
056        this.upperLimitForNoMaskMatch = currentTime + maxDelayThreshold;
057    }
058    
059    private long minDelayThreshold;
060    private long maxDelayThreshold;
061
062    long lowerLimitForMaskMatch;
063    long upperLimitForNoMaskMatch;
064
065    /*
066     * (non-Javadoc)
067     * 
068     * @see ch.qos.logback.core.util.InvocationGate#skipFurtherWork()
069     */
070    @Override
071    final public boolean isTooSoon(long currentTime) {
072        boolean maskMatch = ((invocationCounter++) & mask) == mask;
073
074        if (maskMatch) {
075            if (currentTime < this.lowerLimitForMaskMatch) {
076                increaseMask();
077            }
078            updateLimits(currentTime);
079        } else {
080            if (currentTime > this.upperLimitForNoMaskMatch) {
081                decreaseMask();
082                updateLimits(currentTime);
083                return false;
084            }
085        }
086        return !maskMatch;
087    }
088
089    private void updateLimits(long currentTime) {
090        this.lowerLimitForMaskMatch = currentTime + minDelayThreshold;
091        this.upperLimitForNoMaskMatch = currentTime + maxDelayThreshold;
092    }
093
094
095    // package private, for testing purposes only
096    long getMask() {
097        return mask;
098    }
099    
100    private void increaseMask() {
101        if (mask >= MAX_MASK)
102            return;
103        mask = (mask << 1) | 1;
104    }
105
106    private void decreaseMask() {
107        mask = mask >>> MASK_DECREASE_RIGHT_SHIFT_COUNT;
108    }
109
110    public long getInvocationCounter() {
111        return invocationCounter;
112    }
113}