# Copyright 2016 by Rackspace Hosting, Inc. # # 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. from .storage_base import StorageBase class Limiter(object): """Limits demand for a finite resource via keyed token buckets. A limiter manages a set of token buckets that have an identical rate, capacity, and storage backend. Each bucket is referenced by a key, allowing for the independent tracking and limiting of multiple consumers of a resource. Args: rate (float): Number of tokens per second to add to the bucket. Over time, the number of tokens that can be consumed is limited by this rate. Each token represents some percentage of a finite resource that may be utilized by a consumer. capacity (int): Maximum number of tokens that the bucket can hold. Once the bucket is full, additional tokens are discarded. The bucket capacity has a direct impact on burst duration. Let M be the maximum possible token request rate, r the token generation rate (tokens/sec), and b the bucket capacity. If r < M the maximum burst duration, in seconds, is: T = b / (M - r) Otherwise, if r >= M, it is not possible to exceed the replenishment rate, and therefore a consumer can burst at full speed indefinitely. The maximum number of tokens that any one burst may consume is: T * M See also: https://en.wikipedia.org/wiki/Token_bucket#Burst_size storage (token_bucket.StorageBase): A storage engine to use for persisting the token bucket data. The following engines are available out of the box: token_bucket.MemoryStorage """ __slots__ = ( '_rate', '_capacity', '_storage', ) def __init__(self, rate, capacity, storage): if not isinstance(rate, (float, int)): raise TypeError('rate must be an int or float') if rate <= 0: raise ValueError('rate must be > 0') if not isinstance(capacity, int): raise TypeError('capacity must be an int') if capacity < 1: raise ValueError('capacity must be >= 1') if not isinstance(storage, StorageBase): raise TypeError('storage must be a subclass of StorageBase') self._rate = rate self._capacity = capacity self._storage = storage def consume(self, key, num_tokens=1): """Attempt to take one or more tokens from a bucket. If the specified token bucket does not yet exist, it will be created and initialized to full capacity before proceeding. Args: key (bytes): A string or bytes object that specifies the token bucket to consume from. If a global limit is desired for all consumers, the same key may be used for every call to consume(). Otherwise, a key based on consumer identity may be used to segregate limits. Keyword Args: num_tokens (int): The number of tokens to attempt to consume, defaulting to 1 if not specified. It may be appropriate to ask for more than one token according to the proportion of the resource that a given request will use, relative to other requests for the same resource. Returns: bool: True if the requested number of tokens were removed from the bucket (conforming), otherwise False (non- conforming). The entire number of tokens requested must be available in the bucket to be conforming. Otherwise, no tokens will be removed (it's all or nothing). """ if not key: if key is None: raise TypeError('key may not be None') raise ValueError('key must not be a non-empty string or bytestring') if num_tokens is None: raise TypeError('num_tokens may not be None') if num_tokens < 1: raise ValueError('num_tokens must be >= 1') self._storage.replenish(key, self._rate, self._capacity) return self._storage.consume(key, num_tokens)