mirror of
https://github.com/jart/cosmopolitan.git
synced 2025-01-31 03:27:39 +00:00
Add tokenbucket module to python.com
This commit is contained in:
parent
e6b7c16a53
commit
2a1c588826
5 changed files with 248 additions and 2 deletions
242
third_party/python/Modules/tokenbucket.c
vendored
Normal file
242
third_party/python/Modules/tokenbucket.c
vendored
Normal file
|
@ -0,0 +1,242 @@
|
|||
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:4;tab-width:8;coding:utf-8 -*-│
|
||||
│vi: set net ft=c ts=4 sts=4 sw=4 fenc=utf-8 :vi│
|
||||
╞══════════════════════════════════════════════════════════════════════════════╡
|
||||
│ Copyright 2021 Justine Alexandra Roberts Tunney │
|
||||
│ │
|
||||
│ Permission to use, copy, modify, and/or distribute this software for │
|
||||
│ any purpose with or without fee is hereby granted, provided that the │
|
||||
│ above copyright notice and this permission notice appear in all copies. │
|
||||
│ │
|
||||
│ THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL │
|
||||
│ WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED │
|
||||
│ WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE │
|
||||
│ AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL │
|
||||
│ DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR │
|
||||
│ PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER │
|
||||
│ TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR │
|
||||
│ PERFORMANCE OF THIS SOFTWARE. │
|
||||
╚─────────────────────────────────────────────────────────────────────────────*/
|
||||
#define PY_SSIZE_T_CLEAN
|
||||
#include "net/http/tokenbucket.h"
|
||||
#include "libc/atomic.h"
|
||||
#include "libc/calls/calls.h"
|
||||
#include "libc/calls/struct/sigaction.h"
|
||||
#include "libc/calls/struct/timespec.h"
|
||||
#include "libc/errno.h"
|
||||
#include "libc/limits.h"
|
||||
#include "libc/macros.internal.h"
|
||||
#include "libc/runtime/runtime.h"
|
||||
#include "libc/sock/sock.h"
|
||||
#include "libc/str/str.h"
|
||||
#include "libc/sysv/consts/clock.h"
|
||||
#include "libc/sysv/consts/sig.h"
|
||||
#include "libc/sysv/consts/timer.h"
|
||||
#include "net/http/tokenbucket.h"
|
||||
#include "third_party/libcxx/math.h"
|
||||
#include "third_party/python/Include/bytesobject.h"
|
||||
#include "third_party/python/Include/import.h"
|
||||
#include "third_party/python/Include/intrcheck.h"
|
||||
#include "third_party/python/Include/longobject.h"
|
||||
#include "third_party/python/Include/modsupport.h"
|
||||
#include "third_party/python/Include/object.h"
|
||||
#include "third_party/python/Include/pyerrors.h"
|
||||
#include "third_party/python/Include/pymacro.h"
|
||||
#include "third_party/python/Include/yoink.h"
|
||||
// clang-format off
|
||||
|
||||
PYTHON_PROVIDE("tokenbucket");
|
||||
PYTHON_PROVIDE("tokenbucket.program");
|
||||
PYTHON_PROVIDE("tokenbucket.acquire");
|
||||
PYTHON_PROVIDE("tokenbucket.count");
|
||||
|
||||
struct TokenBucket {
|
||||
int pid;
|
||||
signed char cidr;
|
||||
struct timespec replenish;
|
||||
union {
|
||||
atomic_schar *b;
|
||||
atomic_uint_fast64_t *w;
|
||||
};
|
||||
} g_tokenbucket;
|
||||
|
||||
PyDoc_STRVAR(tokenbucket_doc,
|
||||
"Token Bucket Module\n\
|
||||
\n\
|
||||
This module implements the token bucket algorithm, which can help\n\
|
||||
keep you safe from denial of service attacks.");
|
||||
|
||||
wontreturn static void
|
||||
tokenbucket_onsignal(int sig)
|
||||
{
|
||||
_Exit(0);
|
||||
}
|
||||
|
||||
static void
|
||||
tokenbucket_atexit(void)
|
||||
{
|
||||
kill(g_tokenbucket.pid, SIGTERM);
|
||||
}
|
||||
|
||||
wontreturn static void
|
||||
tokenbucket_replenisher(void)
|
||||
{
|
||||
struct timespec ts;
|
||||
signal(SIGINT, tokenbucket_onsignal);
|
||||
signal(SIGHUP, tokenbucket_onsignal);
|
||||
signal(SIGTERM, tokenbucket_onsignal);
|
||||
signal(SIGUSR1, SIG_IGN);
|
||||
signal(SIGUSR2, SIG_IGN);
|
||||
ts = timespec_real();
|
||||
for (;;) {
|
||||
if (!clock_nanosleep(CLOCK_REALTIME, TIMER_ABSTIME, &ts, 0)) {
|
||||
ReplenishTokens(g_tokenbucket.w, (1ul << g_tokenbucket.cidr) / 8);
|
||||
ts = timespec_add(ts, g_tokenbucket.replenish);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
PyDoc_STRVAR(config_doc,
|
||||
"config($module, replenish=1.0, cidr=24)\n\
|
||||
--\n\n\
|
||||
Configures global token bucket.\n\
|
||||
\n\
|
||||
This function launches a background process. This function can only be\n\
|
||||
called once. The replenish interval is in seconds and specifies how\n\
|
||||
often the background process will add a token to each bucket. The\n\
|
||||
cidr specifies the network bit granularity.");
|
||||
|
||||
static PyObject *
|
||||
tokenbucket_config(PyObject *self, PyObject *args)
|
||||
{
|
||||
double replenish = 1.0;
|
||||
unsigned cidr = 24;
|
||||
if (g_tokenbucket.pid > 0) {
|
||||
PyErr_SetString(PyExc_ValueError,
|
||||
"tokenbucket.config() can only be called once");
|
||||
return 0;
|
||||
}
|
||||
if (!PyArg_ParseTuple(args, "|dI:config", &replenish, &cidr)) {
|
||||
return 0;
|
||||
}
|
||||
if (!(8 <= cidr && cidr <= 32)) {
|
||||
PyErr_SetString(PyExc_ValueError, "require 8 <= cidr <= 32");
|
||||
return 0;
|
||||
}
|
||||
if (!(0 < replenish && replenish <= LONG_MAX)) {
|
||||
PyErr_SetString(PyExc_ValueError, "need 0 < replenish <= LONG_MAX");
|
||||
return 0;
|
||||
}
|
||||
if (!(g_tokenbucket.b = _mapshared(1ul << cidr))) {
|
||||
PyErr_NoMemory();
|
||||
return 0;
|
||||
}
|
||||
memset(g_tokenbucket.b, 127, 1ul << cidr);
|
||||
g_tokenbucket.cidr = cidr;
|
||||
g_tokenbucket.replenish = timespec_frommicros(replenish * 1e6);
|
||||
_PyImport_AcquireLock();
|
||||
g_tokenbucket.pid = fork();
|
||||
if (!g_tokenbucket.pid) {
|
||||
PyOS_AfterFork();
|
||||
} else {
|
||||
_PyImport_ReleaseLock();
|
||||
}
|
||||
if (g_tokenbucket.pid == -1) {
|
||||
munmap(g_tokenbucket.b, 1ul << cidr);
|
||||
return PyErr_SetFromErrno(PyExc_OSError);
|
||||
}
|
||||
atexit(tokenbucket_atexit);
|
||||
if (!g_tokenbucket.pid) {
|
||||
tokenbucket_replenisher();
|
||||
__builtin_unreachable();
|
||||
}
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
PyDoc_STRVAR(acquire_doc,
|
||||
"acquire($module, ip)\n\
|
||||
--\n\n\
|
||||
Acquires token for IP address.\n\
|
||||
\n\
|
||||
This removes a token from the bucket associated with `ip` and then\n\
|
||||
returns the number of tokens that were in the bucket beforehand.\n\
|
||||
\n\
|
||||
Return values greater than zero mean a token was atomically acquired.\n\
|
||||
Values less than or equal zero means the bucket is empty.");
|
||||
|
||||
static PyObject *
|
||||
tokenbucket_acquire(PyObject *self, PyObject *args)
|
||||
{
|
||||
uint32_t ip;
|
||||
const char *ipstr;
|
||||
if (g_tokenbucket.pid <= 0) {
|
||||
PyErr_SetString(PyExc_ValueError,
|
||||
"tokenbucket.config() needs to be called first");
|
||||
return 0;
|
||||
}
|
||||
if (!PyArg_ParseTuple(args, "s:acquire", &ipstr)) {
|
||||
return 0;
|
||||
}
|
||||
if ((ip = ntohl(inet_addr(ipstr))) == -1u) {
|
||||
PyErr_SetString(PyExc_ValueError, "bad ipv4 address");
|
||||
return 0;
|
||||
}
|
||||
return PyLong_FromLong(AcquireToken(g_tokenbucket.b, ip,
|
||||
g_tokenbucket.cidr));
|
||||
}
|
||||
|
||||
PyDoc_STRVAR(count_doc,
|
||||
"count($module, ip)\n\
|
||||
--\n\n\
|
||||
Counts token for IP address.\n\
|
||||
\n\
|
||||
Return values greater than zero mean a token was atomically countd.\n\
|
||||
Values less than or equal zero means the bucket is empty.");
|
||||
|
||||
static PyObject *
|
||||
tokenbucket_count(PyObject *self, PyObject *args)
|
||||
{
|
||||
uint32_t ip;
|
||||
const char *ipstr;
|
||||
if (g_tokenbucket.pid <= 0) {
|
||||
PyErr_SetString(PyExc_ValueError,
|
||||
"tokenbucket.config() needs to be called first");
|
||||
return 0;
|
||||
}
|
||||
if (!PyArg_ParseTuple(args, "s:count", &ipstr)) {
|
||||
return 0;
|
||||
}
|
||||
if ((ip = ntohl(inet_addr(ipstr))) == -1u) {
|
||||
PyErr_SetString(PyExc_ValueError, "bad ipv4 address");
|
||||
return 0;
|
||||
}
|
||||
return PyLong_FromLong(CountTokens(g_tokenbucket.b, ip,
|
||||
g_tokenbucket.cidr));
|
||||
}
|
||||
|
||||
static PyMethodDef tokenbucket_methods[] = {
|
||||
{"config", tokenbucket_config, METH_VARARGS, config_doc},
|
||||
{"acquire", tokenbucket_acquire, METH_VARARGS, acquire_doc},
|
||||
{"count", tokenbucket_count, METH_VARARGS, count_doc},
|
||||
{0},
|
||||
};
|
||||
|
||||
static struct PyModuleDef tokenbucketmodule = {
|
||||
PyModuleDef_HEAD_INIT,
|
||||
"tokenbucket",
|
||||
tokenbucket_doc,
|
||||
-1,
|
||||
tokenbucket_methods,
|
||||
};
|
||||
|
||||
PyMODINIT_FUNC
|
||||
PyInit_tokenbucket(void)
|
||||
{
|
||||
PyObject *m;
|
||||
if (!(m = PyModule_Create(&tokenbucketmodule))) return 0;
|
||||
return !PyErr_Occurred() ? m : 0;
|
||||
}
|
||||
|
||||
_Section(".rodata.pytab.1") const struct _inittab _PyImport_Inittab_tokenbucket = {
|
||||
"tokenbucket",
|
||||
PyInit_tokenbucket,
|
||||
};
|
2
third_party/python/Python/cosmomodule.c
vendored
2
third_party/python/Python/cosmomodule.c
vendored
|
@ -356,7 +356,7 @@ static PyMethodDef cosmo_methods[] = {
|
|||
{"syscount", cosmo_syscount, METH_NOARGS, syscount_doc},
|
||||
{"popcount", cosmo_popcount, METH_VARARGS, popcount_doc},
|
||||
{"decimate", cosmo_decimate, METH_VARARGS, decimate_doc},
|
||||
{"verynice", cosmo_verynice, METH_VARARGS, verynice_doc},
|
||||
{"verynice", cosmo_verynice, METH_NOARGS, verynice_doc},
|
||||
#ifdef __x86_64__
|
||||
{"getcpucore", cosmo_getcpucore, METH_NOARGS, getcpucore_doc},
|
||||
{"getcpunode", cosmo_getcpunode, METH_NOARGS, getcpunode_doc},
|
||||
|
|
3
third_party/python/python.c
vendored
3
third_party/python/python.c
vendored
|
@ -226,7 +226,6 @@ PYTHON_YOINK("decimal");
|
|||
PYTHON_YOINK("difflib");
|
||||
PYTHON_YOINK("doctest");
|
||||
PYTHON_YOINK("dummy_threading");
|
||||
PYTHON_YOINK("threading");
|
||||
PYTHON_YOINK("enum");
|
||||
PYTHON_YOINK("filecmp");
|
||||
PYTHON_YOINK("fileinput");
|
||||
|
@ -306,7 +305,9 @@ PYTHON_YOINK("tabnanny");
|
|||
PYTHON_YOINK("tempfile");
|
||||
PYTHON_YOINK("textwrap");
|
||||
PYTHON_YOINK("this");
|
||||
PYTHON_YOINK("threading");
|
||||
PYTHON_YOINK("token");
|
||||
PYTHON_YOINK("tokenbucket");
|
||||
PYTHON_YOINK("tokenize");
|
||||
PYTHON_YOINK("trace");
|
||||
PYTHON_YOINK("traceback");
|
||||
|
|
2
third_party/python/python.mk
vendored
2
third_party/python/python.mk
vendored
|
@ -685,6 +685,7 @@ THIRD_PARTY_PYTHON_STAGE2_A_SRCS = \
|
|||
third_party/python/Modules/mmapmodule.c \
|
||||
third_party/python/Modules/parsermodule.c \
|
||||
third_party/python/Modules/posixmodule.c \
|
||||
third_party/python/Modules/tokenbucket.c \
|
||||
third_party/python/Modules/pwdmodule.c \
|
||||
third_party/python/Modules/pyexpat.c \
|
||||
third_party/python/Modules/resource.c \
|
||||
|
@ -1177,6 +1178,7 @@ THIRD_PARTY_PYTHON_STAGE2_A_DIRECTDEPS = \
|
|||
LIBC_TINYMATH \
|
||||
LIBC_X \
|
||||
LIBC_ZIPOS \
|
||||
NET_HTTP \
|
||||
NET_HTTPS \
|
||||
THIRD_PARTY_BZIP2 \
|
||||
THIRD_PARTY_GDTOA \
|
||||
|
|
|
@ -101,6 +101,7 @@
|
|||
"__builtin_ia32_movntdq"
|
||||
"__has_attribute"
|
||||
"__has_builtin"
|
||||
"__has_include"
|
||||
"__has_cpp_attribute"
|
||||
"__has_feature"
|
||||
"__ATOMIC_RELAXED"
|
||||
|
|
Loading…
Reference in a new issue