Github user rmaucher commented on a diff in the pull request: https://github.com/apache/tomcat/pull/126#discussion_r224803205 --- Diff: java/org/apache/tomcat/util/net/RateLimiter.java --- @@ -0,0 +1,163 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You 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 org.apache.tomcat.util.net; + +import java.io.IOException; + +/** Abstract base class to rate limit IO. Typically implementations are + * shared across multiple IndexInputs or IndexOutputs (for example + * those involved all merging). Those IndexInputs and + * IndexOutputs would call {@link #pause} whenever the have read + * or written more than {@link #getMinPauseCheckBytes} bytes. */ +public abstract class RateLimiter { + + /** + * Sets an updated MB per second rate limit. + */ + public abstract void setMBPerSec(double mbPerSec); + + /** + * The current MB per second rate limit. + */ + public abstract double getMBPerSec(); + + /** Pauses, if necessary, to keep the instantaneous IO + * rate at or below the target. + * <p> + * Note: the implementation is thread-safe + * </p> + * @return the pause time in nano seconds + * */ + public abstract long pause(long bytes); + + /** How many bytes caller should add up itself before invoking {@link #pause}. */ + public abstract long getMinPauseCheckBytes(); + + /** + * Simple class to rate limit IO. + */ + public static class SimpleRateLimiter extends RateLimiter { + + private final static int MIN_PAUSE_CHECK_MSEC = 5; + + private volatile double mbPerSec; + private volatile long minPauseCheckBytes; + private long lastNS; + + // TODO: we could also allow eg a sub class to dynamically + // determine the allowed rate, eg if an app wants to + // change the allowed rate over time or something + + /** mbPerSec is the MB/sec max IO rate */ + public SimpleRateLimiter(double mbPerSec) { + setMBPerSec(mbPerSec); + lastNS = System.nanoTime(); + } + + /** + * Sets an updated mb per second rate limit. + */ + @Override + public void setMBPerSec(double mbPerSec) { + this.mbPerSec = mbPerSec; + minPauseCheckBytes = (long) ((MIN_PAUSE_CHECK_MSEC / 1000.0) * mbPerSec * 1024 * 1024); + } + + @Override + public long getMinPauseCheckBytes() { + return minPauseCheckBytes; + } + + /** + * The current mb per second rate limit. + */ + @Override + public double getMBPerSec() { + return this.mbPerSec; + } + + /** Pauses, if necessary, to keep the instantaneous IO + * rate at or below the target. Be sure to only call + * this method when bytes > {@link #getMinPauseCheckBytes}, + * otherwise it will pause way too long! + * + * @return the pause time in nano seconds */ + @Override + public long pause(long bytes) { + + long startNS = System.nanoTime(); + + double secondsToPause = (bytes/1024./1024.) / mbPerSec; + + long targetNS; + + // Sync'd to read + write lastNS: + synchronized (this) { + + // Time we should sleep until; this is purely instantaneous + // rate (just adds seconds onto the last time we had paused to); + // maybe we should also offer decayed recent history one? + targetNS = lastNS + (long) (1000000000 * secondsToPause); + + if (startNS >= targetNS) { + // OK, current time is already beyond the target sleep time, + // no pausing to do. + + // Set to startNS, not targetNS, to enforce the instant rate, not + // the "averaaged over all history" rate: + lastNS = startNS; + return 0; + } + + lastNS = targetNS; + } + + long curNS = startNS; + + // While loop because Thread.sleep doesn't always sleep + // enough: + while (true) { + final long pauseNS = targetNS - curNS; + if (pauseNS > 0) { + try { + // NOTE: except maybe on real-time JVMs, minimum realistic sleep time + // is 1 msec; if you pass just 1 nsec the default impl rounds + // this up to 1 msec: + int sleepNS; + int sleepMS; + if (pauseNS > 100000L * Integer.MAX_VALUE) { + // Not really practical (sleeping for 25 days) but we shouldn't overflow int: + sleepMS = Integer.MAX_VALUE; + sleepNS = 0; + } else { + sleepMS = (int) (pauseNS/1000000); + sleepNS = (int) (pauseNS % 1000000); + } + Thread.sleep(sleepMS, sleepNS); --- End diff -- If you are going to use such a mechanism, I would recommend using a regular servlet instead. Sendfile is faster in theory, but there are many options out there that could be considered first unless you are switching to a more advanced design for the throttling.
--- --------------------------------------------------------------------- To unsubscribe, e-mail: dev-unsubscr...@tomcat.apache.org For additional commands, e-mail: dev-h...@tomcat.apache.org