1 /**
2 * Licensed to the Apache Software Foundation (ASF) under one or more contributor license
3 * agreements. See the NOTICE file distributed with this work for additional information regarding
4 * copyright ownership. The ASF licenses this file to you under the Apache License, Version 2.0 (the
5 * "License"); you may not use this file except in compliance with the License. You may obtain a
6 * copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable
7 * law or agreed to in writing, software distributed under the License is distributed on an "AS IS"
8 * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License
9 * for the specific language governing permissions and limitations under the License.
10 */
11
12 package org.apache.hadoop.hbase.quotas;
13
14 import java.util.concurrent.TimeUnit;
15
16 import org.apache.hadoop.hbase.classification.InterfaceAudience;
17 import org.apache.hadoop.hbase.classification.InterfaceStability;
18
19 /**
20 * Simple rate limiter.
21 *
22 * Usage Example:
23 * // At this point you have a unlimited resource limiter
24 * RateLimiter limiter = new AverageIntervalRateLimiter();
25 * or new FixedIntervalRateLimiter();
26 * limiter.set(10, TimeUnit.SECONDS); // set 10 resources/sec
27 *
28 * while (true) {
29 * // call canExecute before performing resource consuming operation
30 * bool canExecute = limiter.canExecute();
31 * // If there are no available resources, wait until one is available
32 * if (!canExecute) Thread.sleep(limiter.waitInterval());
33 * // ...execute the work and consume the resource...
34 * limiter.consume();
35 * }
36 */
37 @InterfaceAudience.Private
38 @InterfaceStability.Evolving
39 @edu.umd.cs.findbugs.annotations.SuppressWarnings(value="IS2_INCONSISTENT_SYNC",
40 justification="FindBugs seems confused; says limit and tlimit " +
41 "are mostly synchronized...but to me it looks like they are totally synchronized")
42 public abstract class RateLimiter {
43 public static final String QUOTA_RATE_LIMITER_CONF_KEY = "hbase.quota.rate.limiter";
44 private long tunit = 1000; // Timeunit factor for translating to ms.
45 private long limit = Long.MAX_VALUE; // The max value available resource units can be refilled to.
46 private long avail = Long.MAX_VALUE; // Currently available resource units
47
48 /**
49 * Refill the available units w.r.t the elapsed time.
50 * @param limit Maximum available resource units that can be refilled to.
51 * @return how many resource units may be refilled ?
52 */
53 abstract long refill(long limit);
54
55 /**
56 * Time in milliseconds to wait for before requesting to consume 'amount' resource.
57 * @param limit Maximum available resource units that can be refilled to.
58 * @param available Currently available resource units
59 * @param amount Resources for which time interval to calculate for
60 * @return estimate of the ms required to wait before being able to provide 'amount' resources.
61 */
62 abstract long getWaitInterval(long limit, long available, long amount);
63
64
65 /**
66 * Set the RateLimiter max available resources and refill period.
67 * @param limit The max value available resource units can be refilled to.
68 * @param timeUnit Timeunit factor for translating to ms.
69 */
70 public synchronized void set(final long limit, final TimeUnit timeUnit) {
71 switch (timeUnit) {
72 case MILLISECONDS:
73 tunit = 1;
74 break;
75 case SECONDS:
76 tunit = 1000;
77 break;
78 case MINUTES:
79 tunit = 60 * 1000;
80 break;
81 case HOURS:
82 tunit = 60 * 60 * 1000;
83 break;
84 case DAYS:
85 tunit = 24 * 60 * 60 * 1000;
86 break;
87 default:
88 throw new RuntimeException("Unsupported " + timeUnit.name() + " TimeUnit.");
89 }
90 this.limit = limit;
91 this.avail = limit;
92 }
93
94 public String toString() {
95 String rateLimiter = this.getClass().getSimpleName();
96 if (getLimit() == Long.MAX_VALUE) {
97 return rateLimiter + "(Bypass)";
98 }
99 return rateLimiter + "(avail=" + getAvailable() + " limit=" + getLimit() +
100 " tunit=" + getTimeUnitInMillis() + ")";
101 }
102
103 /**
104 * Sets the current instance of RateLimiter to a new values.
105 *
106 * if current limit is smaller than the new limit, bump up the available resources.
107 * Otherwise allow clients to use up the previously available resources.
108 */
109 public synchronized void update(final RateLimiter other) {
110 this.tunit = other.tunit;
111 if (this.limit < other.limit) {
112 // If avail is capped to this.limit, it will never overflow,
113 // otherwise, avail may overflow, just be careful here.
114 long diff = other.limit - this.limit;
115 if (this.avail <= Long.MAX_VALUE - diff) {
116 this.avail += diff;
117 this.avail = Math.min(this.avail, other.limit);
118 } else {
119 this.avail = other.limit;
120 }
121 }
122 this.limit = other.limit;
123 }
124
125 public synchronized boolean isBypass() {
126 return getLimit() == Long.MAX_VALUE;
127 }
128
129 public synchronized long getLimit() {
130 return limit;
131 }
132
133 public synchronized long getAvailable() {
134 return avail;
135 }
136
137 protected synchronized long getTimeUnitInMillis() {
138 return tunit;
139 }
140
141 /**
142 * Is there at least one resource available to allow execution?
143 * @return true if there is at least one resource available, otherwise false
144 */
145 public boolean canExecute() {
146 return canExecute(1);
147 }
148
149 /**
150 * Are there enough available resources to allow execution?
151 * @param amount the number of required resources, a non-negative number
152 * @return true if there are enough available resources, otherwise false
153 */
154 public synchronized boolean canExecute(final long amount) {
155 if (isBypass()) {
156 return true;
157 }
158
159 long refillAmount = refill(limit);
160 if (refillAmount == 0 && avail < amount) {
161 return false;
162 }
163 // check for positive overflow
164 if (avail <= Long.MAX_VALUE - refillAmount) {
165 avail = Math.max(0, Math.min(avail + refillAmount, limit));
166 } else {
167 avail = Math.max(0, limit);
168 }
169 if (avail >= amount) {
170 return true;
171 }
172 return false;
173 }
174
175 /**
176 * consume one available unit.
177 */
178 public void consume() {
179 consume(1);
180 }
181
182 /**
183 * consume amount available units, amount could be a negative number
184 * @param amount the number of units to consume
185 */
186 public synchronized void consume(final long amount) {
187
188 if (isBypass()) {
189 return;
190 }
191
192 if (amount >= 0 ) {
193 this.avail -= amount;
194 if (this.avail < 0) {
195 this.avail = 0;
196 }
197 } else {
198 if (this.avail <= Long.MAX_VALUE + amount) {
199 this.avail -= amount;
200 this.avail = Math.min(this.avail, this.limit);
201 } else {
202 this.avail = this.limit;
203 }
204 }
205 }
206
207 /**
208 * @return estimate of the ms required to wait before being able to provide 1 resource.
209 */
210 public long waitInterval() {
211 return waitInterval(1);
212 }
213
214 /**
215 * @return estimate of the ms required to wait before being able to provide "amount" resources.
216 */
217 public synchronized long waitInterval(final long amount) {
218 // TODO Handle over quota?
219 return (amount <= avail) ? 0 : getWaitInterval(getLimit(), avail, amount);
220 }
221
222 // These two method are for strictly testing purpose only
223
224 public abstract void setNextRefillTime(long nextRefillTime);
225
226 public abstract long getNextRefillTime();
227 }