mirror of
https://github.com/3proxy/3proxy.git
synced 2026-10-28 22:05:48 +08:00
src/loop.c, not used yet: one thread waiting for many sockets and timers, calling back what waits for them, for clients to be served by a thread each serves many of. Sockets are waited for with epoll on Linux, kqueue on the BSDs and macOS, poll() (WSAPoll() on Windows) elsewhere or with LOOP_POLL, level-triggered; one with nothing to wait for is taken from the system, or an error or a hang up would be reported all the same. One deleted while the sockets ready are being called is not called. Timers wait in a wheel of 1024 one-second slots. One due within a turn waits at the head of the slot of its second, all there due in the same second; one due later behind them, in the order of their seconds, or, if due before the last there, at the head of the last slot of the turn, looked at again then. Moving a timer later costs nothing while it waits in the wheel: it is looked at when its old deadline comes and moved then. When its slot comes a timer due within the second waits in a heap, so it fires to the millisecond, as does one set within the second. The clock is monotonic. Another thread posts to the loop, waking it with an eventfd, a pipe, or on Windows a UDP socket sending to itself. tests/looptest.c tests it on its own, timers on a clock of its own through several turns of the wheel: built by CMake, run by the loop case. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
391 lines
9.9 KiB
C
391 lines
9.9 KiB
C
/*
|
|
3APA3A simplest proxy server
|
|
(c) 2002-2026 by Vladimir Dubrovin <vlad@3proxy.org>
|
|
|
|
please read License Agreement
|
|
*/
|
|
|
|
/* The event loop on its own: tests/cases/loop.py runs it. Each check is a
|
|
line, "ok LABEL" or "FAIL LABEL: what", and the exit code is the number
|
|
of failures. */
|
|
|
|
#include "../src/proxy.h"
|
|
#include "../src/loop.h"
|
|
|
|
static int failures;
|
|
|
|
static void check(int cond, const char *label, const char *what){
|
|
if(cond) printf("ok %s\n", label);
|
|
else {
|
|
printf("FAIL %s: %s\n", label, what);
|
|
failures++;
|
|
}
|
|
fflush(stdout);
|
|
}
|
|
|
|
static unsigned long long rnd = 88172645463325252ULL;
|
|
|
|
static unsigned long long rand64(void){
|
|
rnd ^= rnd << 13;
|
|
rnd ^= rnd >> 7;
|
|
rnd ^= rnd << 17;
|
|
return rnd;
|
|
}
|
|
|
|
static uint64_t rand_upto(uint64_t n){
|
|
return n? rand64() % n : 0;
|
|
}
|
|
|
|
|
|
/* ---- timers on a clock of the test's ------------------------------------ */
|
|
|
|
#define NTIMERS 20000
|
|
|
|
static uint64_t fakenow;
|
|
|
|
static uint64_t fakeclock(void){
|
|
return fakenow;
|
|
}
|
|
|
|
struct ttimer {
|
|
struct looptimer t;
|
|
uint64_t expect; /* the deadline it has now */
|
|
int fired, deleted, pending, rearmed;
|
|
};
|
|
|
|
static struct ttimer timers[NTIMERS];
|
|
static uint64_t prevnow, latest;
|
|
static int early, late, twice, wrongdead;
|
|
|
|
static void tfire(struct loop *l, struct looptimer *lt){
|
|
struct ttimer *t = (struct ttimer *)lt;
|
|
|
|
if(t->deleted) wrongdead++;
|
|
if(!t->pending) twice++;
|
|
if(l->now < t->expect) early++;
|
|
/* fired on the first turn its time had come */
|
|
if(prevnow >= t->expect) late++;
|
|
t->pending = 0;
|
|
t->fired++;
|
|
/* some set themselves again from their own call */
|
|
if(!t->rearmed && rand_upto(20) == 0){
|
|
t->rearmed = 1;
|
|
t->expect = l->now + 1 + rand_upto(3000000);
|
|
if(t->expect > latest) latest = t->expect;
|
|
t->pending = 1;
|
|
looptimerset(l, lt, t->expect);
|
|
}
|
|
}
|
|
|
|
static void timertest(void){
|
|
struct loop l;
|
|
uint64_t start;
|
|
int i, pending, lost = 0, deletedfired = 0, steps = 0;
|
|
char what[256];
|
|
|
|
if(loopinit(&l)){
|
|
check(0, "timers: a loop is made", "loopinit() failed");
|
|
return;
|
|
}
|
|
fakenow = start = 1000000ULL * 1000 + 123;
|
|
l.clock = fakeclock;
|
|
l.now = fakenow;
|
|
l.second = fakenow / 1000 + 1;
|
|
prevnow = fakenow;
|
|
|
|
for(i = 0; i < NTIMERS; i++){
|
|
struct ttimer *t = timers + i;
|
|
uint64_t r = rand_upto(10);
|
|
|
|
/* within the second, within a turn of the wheel, past it */
|
|
if(r < 4) t->expect = start + 1 + rand_upto(2000);
|
|
else if(r < 8) t->expect = start + 1 + rand_upto(1200000);
|
|
else t->expect = start + 1024000 + rand_upto(3000000);
|
|
t->t.func = tfire;
|
|
t->pending = 1;
|
|
looptimerset(&l, &t->t, t->expect);
|
|
if(t->expect > latest) latest = t->expect;
|
|
}
|
|
|
|
/* until the last deadline ever set is past */
|
|
while(fakenow <= latest + 2000){
|
|
/* moved later, set earlier, deleted: some pending timer */
|
|
for(i = 0; i < 20; i++){
|
|
struct ttimer *t = timers + rand_upto(NTIMERS);
|
|
uint64_t r = rand_upto(10);
|
|
|
|
if(!t->pending) continue;
|
|
if(r < 6){
|
|
t->expect += rand_upto(700000);
|
|
if(t->expect > latest) latest = t->expect;
|
|
looptimerlater(&l, &t->t, t->expect);
|
|
}
|
|
else if(r < 8 && t->expect > fakenow + 1){
|
|
t->expect = fakenow + 1 + rand_upto(t->expect - fakenow - 1);
|
|
looptimerset(&l, &t->t, t->expect);
|
|
}
|
|
else if(r < 9){
|
|
looptimerdel(&l, &t->t);
|
|
t->pending = 0;
|
|
t->deleted = 1;
|
|
}
|
|
}
|
|
prevnow = fakenow;
|
|
/* steps of a few ms to a second, and now and then a long stall */
|
|
if(rand_upto(500) == 0) fakenow += 1 + rand_upto(300000);
|
|
else fakenow += 1 + rand_upto(rand_upto(4) ? 1500 : 20);
|
|
looponce(&l, 0);
|
|
steps++;
|
|
}
|
|
pending = 0;
|
|
for(i = 0; i < NTIMERS; i++){
|
|
if(timers[i].pending) pending++;
|
|
if(!timers[i].deleted && !timers[i].fired) lost++;
|
|
if(timers[i].deleted && timers[i].fired && !timers[i].rearmed) deletedfired++;
|
|
}
|
|
sprintf(what, "%d left pending, %d never fired", pending, lost);
|
|
check(!pending && !lost, "timers: every timer fires", what);
|
|
sprintf(what, "%d early", early);
|
|
check(!early, "timers: none fires before its deadline", what);
|
|
sprintf(what, "%d fired on a later turn than the first past their deadline", late);
|
|
check(!late, "timers: each fires on the first turn its deadline is past", what);
|
|
sprintf(what, "%d twice, %d deleted", twice, wrongdead + deletedfired);
|
|
check(!twice && !wrongdead && !deletedfired, "timers: once, and never once deleted", what);
|
|
sprintf(what, "%u in the wheel, %u in the heap", l.nwheel, l.nheap);
|
|
check(!l.nwheel && !l.nheap, "timers: nothing left behind", what);
|
|
printf("# %d steps of the clock\n", steps);
|
|
loopfree(&l);
|
|
}
|
|
|
|
|
|
/* ---- sockets -------------------------------------------------------------- */
|
|
|
|
static int pairsock(SOCKET *a, SOCKET *b){
|
|
struct sockaddr_in sa;
|
|
SASIZETYPE len = sizeof(sa);
|
|
SOCKET ls = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
|
|
|
|
*a = *b = INVALID_SOCKET;
|
|
if(ls == INVALID_SOCKET) return 1;
|
|
memset(&sa, 0, sizeof(sa));
|
|
sa.sin_family = AF_INET;
|
|
sa.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
|
if(bind(ls, (struct sockaddr *)&sa, sizeof(sa)) || listen(ls, 1) ||
|
|
getsockname(ls, (struct sockaddr *)&sa, &len) ||
|
|
(*a = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET ||
|
|
connect(*a, (struct sockaddr *)&sa, sizeof(sa)) ||
|
|
(*b = accept(ls, NULL, NULL)) == INVALID_SOCKET){
|
|
#ifdef _WIN32
|
|
closesocket(ls);
|
|
#else
|
|
close(ls);
|
|
#endif
|
|
return 1;
|
|
}
|
|
#ifdef _WIN32
|
|
closesocket(ls);
|
|
#else
|
|
close(ls);
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
static void closesock(SOCKET s){
|
|
#ifdef _WIN32
|
|
closesocket(s);
|
|
#else
|
|
close(s);
|
|
#endif
|
|
}
|
|
|
|
struct tio {
|
|
struct loopio io;
|
|
int calls;
|
|
short revents;
|
|
struct tio *other; /* deleted by this one's call */
|
|
};
|
|
|
|
static void iocall(struct loop *l, struct loopio *io, short revents){
|
|
struct tio *t = (struct tio *)io;
|
|
|
|
t->calls++;
|
|
t->revents |= revents;
|
|
if(t->other) loopdel(l, &t->other->io);
|
|
}
|
|
|
|
static void iotest(void){
|
|
struct loop l;
|
|
struct tio a, c;
|
|
SOCKET s1, s2, s3, s4;
|
|
char buf[16];
|
|
int i;
|
|
|
|
memset(&a, 0, sizeof(a));
|
|
memset(&c, 0, sizeof(c));
|
|
if(loopinit(&l) || pairsock(&s1, &s2) || pairsock(&s3, &s4)){
|
|
check(0, "sockets: a loop and sockets are made", "failed");
|
|
return;
|
|
}
|
|
a.io.fd = s1;
|
|
a.io.events = POLLIN;
|
|
a.io.func = iocall;
|
|
check(!loopadd(&l, &a.io), "sockets: a socket is added", "loopadd() failed");
|
|
|
|
looponce(&l, 100);
|
|
check(a.calls == 0, "sockets: nothing to read, no call", "called");
|
|
|
|
send(s2, "x", 1, 0);
|
|
for(i = 0; i < 10 && !a.calls; i++) looponce(&l, 200);
|
|
check(a.calls >= 1 && (a.revents & POLLIN), "sockets: what comes is readable", "no POLLIN");
|
|
recv(s1, buf, sizeof(buf), 0);
|
|
|
|
a.calls = 0;
|
|
a.revents = 0;
|
|
loopmod(&l, &a.io, POLLOUT);
|
|
for(i = 0; i < 10 && !a.calls; i++) looponce(&l, 200);
|
|
check(a.calls >= 1 && (a.revents & POLLOUT) && !(a.revents & POLLIN),
|
|
"sockets: waited for writing, writable", "no POLLOUT");
|
|
|
|
loopmod(&l, &a.io, 0);
|
|
a.calls = 0;
|
|
send(s2, "y", 1, 0);
|
|
looponce(&l, 200);
|
|
check(a.calls == 0, "sockets: waited for nothing, not called", "called");
|
|
|
|
/* both ready: whichever is called first deletes the other */
|
|
loopmod(&l, &a.io, POLLIN);
|
|
c.io.fd = s3;
|
|
c.io.events = POLLIN;
|
|
c.io.func = iocall;
|
|
loopadd(&l, &c.io);
|
|
a.other = &c;
|
|
c.other = &a;
|
|
send(s4, "z", 1, 0);
|
|
a.calls = c.calls = 0;
|
|
for(i = 0; i < 10 && a.calls + c.calls == 0; i++) looponce(&l, 200);
|
|
check(a.calls + c.calls == 1, "sockets: one deleted in this turn is not called", "both called");
|
|
|
|
/* the one deleted can be added again; the far side closing it is
|
|
readable */
|
|
a.other = c.other = NULL;
|
|
if(!a.calls) loopadd(&l, &a.io);
|
|
if(!c.calls) loopadd(&l, &c.io);
|
|
loopdel(&l, &c.io);
|
|
recv(s1, buf, sizeof(buf), 0);
|
|
a.calls = 0;
|
|
a.revents = 0;
|
|
closesock(s2);
|
|
for(i = 0; i < 10 && !a.calls; i++) looponce(&l, 200);
|
|
check(a.calls >= 1 && (a.revents & (POLLIN | POLLHUP)) && recv(s1, buf, sizeof(buf), 0) == 0,
|
|
"sockets: the far side closing is readable", "not reported");
|
|
|
|
loopdel(&l, &a.io);
|
|
closesock(s1);
|
|
closesock(s3);
|
|
closesock(s4);
|
|
loopfree(&l);
|
|
}
|
|
|
|
|
|
/* ---- real time, and other threads --------------------------------------- */
|
|
|
|
static int timerfired;
|
|
|
|
static void realfire(struct loop *l, struct looptimer *t){
|
|
timerfired = 1;
|
|
}
|
|
|
|
static int posted;
|
|
|
|
static void postcall(struct loop *l, struct loopmsg *m){
|
|
posted = 1;
|
|
}
|
|
|
|
static struct loop tl;
|
|
static struct loopmsg msg;
|
|
static int dostop;
|
|
|
|
#ifdef _WIN32
|
|
static unsigned __stdcall poster(void *arg){
|
|
Sleep(100);
|
|
#else
|
|
static void *poster(void *arg){
|
|
usleep(100000);
|
|
#endif
|
|
if(dostop) loopstop(&tl);
|
|
else {
|
|
msg.func = postcall;
|
|
looppost(&tl, &msg);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static int startposter(void){
|
|
#ifdef _WIN32
|
|
HANDLE h = (HANDLE)_beginthreadex(NULL, 0, poster, NULL, 0, NULL);
|
|
|
|
if(!h) return 1;
|
|
CloseHandle(h);
|
|
return 0;
|
|
#else
|
|
pthread_t t;
|
|
|
|
if(pthread_create(&t, NULL, poster, NULL)) return 1;
|
|
pthread_detach(t);
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
static void realtest(void){
|
|
struct looptimer t;
|
|
uint64_t start, took;
|
|
char what[64];
|
|
|
|
if(loopinit(&tl)){
|
|
check(0, "real time: a loop is made", "loopinit() failed");
|
|
return;
|
|
}
|
|
memset(&t, 0, sizeof(t));
|
|
t.func = realfire;
|
|
start = looptime();
|
|
looptimerset(&tl, &t, start + 150);
|
|
while(!timerfired) looponce(&tl, -1);
|
|
took = looptime() - start;
|
|
sprintf(what, "fired after %u ms", (unsigned)took);
|
|
check(took >= 150 && took < 1100, "real time: a timer fires when it is due", what);
|
|
|
|
start = looptime();
|
|
if(startposter()) check(0, "real time: a thread is started", "failed");
|
|
else {
|
|
looponce(&tl, 5000);
|
|
took = looptime() - start;
|
|
sprintf(what, "after %u ms", (unsigned)took);
|
|
check(posted && took < 2000, "real time: a message from another thread wakes the loop", what);
|
|
}
|
|
|
|
dostop = 1;
|
|
start = looptime();
|
|
if(startposter()) check(0, "real time: a thread is started", "failed");
|
|
else {
|
|
looprun(&tl);
|
|
took = looptime() - start;
|
|
sprintf(what, "after %u ms", (unsigned)took);
|
|
check(took < 2000, "real time: loopstop() from another thread ends looprun()", what);
|
|
}
|
|
loopfree(&tl);
|
|
}
|
|
|
|
/* looptest [SEED]: another seed, other timers */
|
|
int main(int argc, char **argv){
|
|
#ifdef _WIN32
|
|
WSADATA wd;
|
|
|
|
WSAStartup(MAKEWORD(2, 2), &wd);
|
|
#endif
|
|
if(argc > 1 && strtoull(argv[1], NULL, 10)) rnd = strtoull(argv[1], NULL, 10);
|
|
timertest();
|
|
iotest();
|
|
realtest();
|
|
return failures;
|
|
}
|