mirror of
https://github.com/3proxy/3proxy.git
synced 2026-10-28 22:05:48 +08:00
Fibers: stacks a thread switches to and back
src/fiber.c, not used yet: a fiber runs a function on a stack of its own, yielding back to the thread which resumed it, for a thread to serve many clients with code written as if each had a thread. The switch is our own on x86-64 (System V), aarch64, i386, arm (ARM or Thumb code, with or without a floating point unit) and mips o32 (with soft, 32 or 64 bit floating point registers, position independent or not), Windows' fibers on Windows. It saves the registers a function has to keep and swaps the stack. Elsewhere there are no fibers (WITH_FIBERS is not defined), nor where x86-64 Linux checks returns against a shadow stack, which fiberusable() tells: each client has a thread there. Stacks are mapped with a guard page below them. A fiber whose function returned is started again with another, its stack kept. Switches are annotated for AddressSanitizer. tests/fibertest.c tests them: yielding and resuming, the integer and floating point registers a function keeps across switches both ways, a thousand fibers resumed at random each keeping its stack, recursion deep in a stack, fibers sleeping on the event loop's timers. The units case, which was the loop case, runs it with looptest. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
cc98c498fe
commit
4e1455b419
@ -391,6 +391,7 @@ endif()
|
|||||||
set(3PROXY_CORE_SOURCES
|
set(3PROXY_CORE_SOURCES
|
||||||
src/3proxy.c
|
src/3proxy.c
|
||||||
src/loop.c
|
src/loop.c
|
||||||
|
src/fiber.c
|
||||||
src/auth.c
|
src/auth.c
|
||||||
src/acl.c
|
src/acl.c
|
||||||
src/limiter.c
|
src/limiter.c
|
||||||
@ -628,13 +629,17 @@ elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
|
|||||||
target_link_libraries(3proxy PRIVATE dl)
|
target_link_libraries(3proxy PRIVATE dl)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# The event loop on its own, for tests/cases/loop.py: not installed
|
# The event loop and fibers on their own, for tests/cases/units.py: not
|
||||||
|
# installed
|
||||||
add_executable(looptest tests/looptest.c src/loop.c)
|
add_executable(looptest tests/looptest.c src/loop.c)
|
||||||
target_include_directories(looptest PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
add_executable(fibertest tests/fibertest.c src/fiber.c src/loop.c)
|
||||||
target_link_libraries(looptest PRIVATE Threads::Threads)
|
foreach(unit looptest fibertest)
|
||||||
|
target_include_directories(${unit} PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/src)
|
||||||
|
target_link_libraries(${unit} PRIVATE Threads::Threads)
|
||||||
if(WIN32)
|
if(WIN32)
|
||||||
target_link_libraries(looptest PRIVATE ${WINDOWS_LIBS})
|
target_link_libraries(${unit} PRIVATE ${WINDOWS_LIBS})
|
||||||
endif()
|
endif()
|
||||||
|
endforeach()
|
||||||
|
|
||||||
# Build 3proxy_crypt utility
|
# Build 3proxy_crypt utility
|
||||||
add_executable(3proxy_crypt
|
add_executable(3proxy_crypt
|
||||||
|
|||||||
@ -34,6 +34,9 @@ sockgetchar$(OBJSUFFICS): sockgetchar.c proxy.h structures.h
|
|||||||
loop$(OBJSUFFICS): loop.c loop.h proxy.h structures.h
|
loop$(OBJSUFFICS): loop.c loop.h proxy.h structures.h
|
||||||
$(CC) $(CFLAGS) loop.c
|
$(CC) $(CFLAGS) loop.c
|
||||||
|
|
||||||
|
fiber$(OBJSUFFICS): fiber.c fiber.h proxy.h structures.h
|
||||||
|
$(CC) $(CFLAGS) fiber.c
|
||||||
|
|
||||||
proxy$(OBJSUFFICS): proxy.c proxy.h structures.h $(PROXYMAIN)
|
proxy$(OBJSUFFICS): proxy.c proxy.h structures.h $(PROXYMAIN)
|
||||||
$(CC) $(CFLAGS) $(DEFINEOPTION)WITHMAIN $(DEFINEOPTION)NOPORTMAP $(DEFINEOPTION)ANONYMOUS $(DEFINEOPTION)NOUDPMAIN proxy.c
|
$(CC) $(CFLAGS) $(DEFINEOPTION)WITHMAIN $(DEFINEOPTION)NOPORTMAP $(DEFINEOPTION)ANONYMOUS $(DEFINEOPTION)NOUDPMAIN proxy.c
|
||||||
|
|
||||||
@ -203,6 +206,6 @@ ssl$(OBJSUFFICS): ssl.c structures.h proxy.h ssl.h
|
|||||||
pcre$(OBJSUFFICS): pcre.c structures.h
|
pcre$(OBJSUFFICS): pcre.c structures.h
|
||||||
$(CC) $(COUT)pcre$(OBJSUFFICS) $(CFLAGS) $(DEFINEOPTION)WITH_PCRE pcre.c
|
$(CC) $(COUT)pcre$(OBJSUFFICS) $(CFLAGS) $(DEFINEOPTION)WITH_PCRE pcre.c
|
||||||
|
|
||||||
$(BUILDDIR)3proxy$(EXESUFFICS): 3proxy$(OBJSUFFICS) loop$(OBJSUFFICS) mainfunc$(OBJSUFFICS) srvproxy$(OBJSUFFICS) srvpop3p$(OBJSUFFICS) srvimapp$(OBJSUFFICS) srvsmtpp$(OBJSUFFICS) srvftppr$(OBJSUFFICS) srvsocks$(OBJSUFFICS) srvtcppm$(OBJSUFFICS) srvtlspr$(OBJSUFFICS) srvauto$(OBJSUFFICS) srvudppm$(OBJSUFFICS) sockmap$(OBJSUFFICS) udpsockmap$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) common$(OBJSUFFICS) sem$(OBJSUFFICS) auth$(OBJSUFFICS) acl$(OBJSUFFICS) limiter$(OBJSUFFICS) redirect$(OBJSUFFICS) authradius$(OBJSUFFICS) hash$(OBJSUFFICS) hashtables$(OBJSUFFICS) resolve$(OBJSUFFICS) sql$(OBJSUFFICS) conf$(OBJSUFFICS) log$(OBJSUFFICS) datatypes$(OBJSUFFICS) md4$(OBJSUFFICS) md5$(OBJSUFFICS) blake2$(OBJSUFFICS) 3proxy_crypt$(OBJSUFFICS) base64$(OBJSUFFICS) ftp$(OBJSUFFICS) stringtable$(OBJSUFFICS) $(HTTPSRV_OBJS) srvdnspr$(OBJSUFFICS) plugins$(OBJSUFFICS) mdhash$(OBJSUFFICS) $(SSL_OBJS) $(PCRE_OBJS) $(TRANSPARENT_OBJS) $(COMPATLIBS) $(VERSIONDEP)
|
$(BUILDDIR)3proxy$(EXESUFFICS): 3proxy$(OBJSUFFICS) loop$(OBJSUFFICS) fiber$(OBJSUFFICS) mainfunc$(OBJSUFFICS) srvproxy$(OBJSUFFICS) srvpop3p$(OBJSUFFICS) srvimapp$(OBJSUFFICS) srvsmtpp$(OBJSUFFICS) srvftppr$(OBJSUFFICS) srvsocks$(OBJSUFFICS) srvtcppm$(OBJSUFFICS) srvtlspr$(OBJSUFFICS) srvauto$(OBJSUFFICS) srvudppm$(OBJSUFFICS) sockmap$(OBJSUFFICS) udpsockmap$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) common$(OBJSUFFICS) sem$(OBJSUFFICS) auth$(OBJSUFFICS) acl$(OBJSUFFICS) limiter$(OBJSUFFICS) redirect$(OBJSUFFICS) authradius$(OBJSUFFICS) hash$(OBJSUFFICS) hashtables$(OBJSUFFICS) resolve$(OBJSUFFICS) sql$(OBJSUFFICS) conf$(OBJSUFFICS) log$(OBJSUFFICS) datatypes$(OBJSUFFICS) md4$(OBJSUFFICS) md5$(OBJSUFFICS) blake2$(OBJSUFFICS) 3proxy_crypt$(OBJSUFFICS) base64$(OBJSUFFICS) ftp$(OBJSUFFICS) stringtable$(OBJSUFFICS) $(HTTPSRV_OBJS) srvdnspr$(OBJSUFFICS) plugins$(OBJSUFFICS) mdhash$(OBJSUFFICS) $(SSL_OBJS) $(PCRE_OBJS) $(TRANSPARENT_OBJS) $(COMPATLIBS) $(VERSIONDEP)
|
||||||
$(LN) $(LNOUT)$(BUILDDIR)3proxy$(EXESUFFICS) $(LDFLAGS) $(VERFILE) 3proxy$(OBJSUFFICS) loop$(OBJSUFFICS) mainfunc$(OBJSUFFICS) auth$(OBJSUFFICS) acl$(OBJSUFFICS) limiter$(OBJSUFFICS) redirect$(OBJSUFFICS) authradius$(OBJSUFFICS) hash$(OBJSUFFICS) hashtables$(OBJSUFFICS) resolve$(OBJSUFFICS) sql$(OBJSUFFICS) conf$(OBJSUFFICS) datatypes$(OBJSUFFICS) srvauto$(OBJSUFFICS) srvproxy$(OBJSUFFICS) srvpop3p$(OBJSUFFICS) srvimapp$(OBJSUFFICS) srvsmtpp$(OBJSUFFICS) srvftppr$(OBJSUFFICS) srvsocks$(OBJSUFFICS) srvtcppm$(OBJSUFFICS) srvtlspr$(OBJSUFFICS) srvudppm$(OBJSUFFICS) sockmap$(OBJSUFFICS) udpsockmap$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) common$(OBJSUFFICS) sem$(OBJSUFFICS) log$(OBJSUFFICS) 3proxy_crypt$(OBJSUFFICS) md4$(OBJSUFFICS) md5$(OBJSUFFICS) blake2$(OBJSUFFICS) base64$(OBJSUFFICS) ftp$(OBJSUFFICS) stringtable$(OBJSUFFICS) $(HTTPSRV_OBJS) srvdnspr$(OBJSUFFICS) plugins$(OBJSUFFICS) mdhash$(OBJSUFFICS) $(SSL_OBJS) $(PCRE_OBJS) $(TRANSPARENT_OBJS) $(COMPATLIBS) $(LIBS) $(PCRE_LIBS)
|
$(LN) $(LNOUT)$(BUILDDIR)3proxy$(EXESUFFICS) $(LDFLAGS) $(VERFILE) 3proxy$(OBJSUFFICS) loop$(OBJSUFFICS) fiber$(OBJSUFFICS) mainfunc$(OBJSUFFICS) auth$(OBJSUFFICS) acl$(OBJSUFFICS) limiter$(OBJSUFFICS) redirect$(OBJSUFFICS) authradius$(OBJSUFFICS) hash$(OBJSUFFICS) hashtables$(OBJSUFFICS) resolve$(OBJSUFFICS) sql$(OBJSUFFICS) conf$(OBJSUFFICS) datatypes$(OBJSUFFICS) srvauto$(OBJSUFFICS) srvproxy$(OBJSUFFICS) srvpop3p$(OBJSUFFICS) srvimapp$(OBJSUFFICS) srvsmtpp$(OBJSUFFICS) srvftppr$(OBJSUFFICS) srvsocks$(OBJSUFFICS) srvtcppm$(OBJSUFFICS) srvtlspr$(OBJSUFFICS) srvudppm$(OBJSUFFICS) sockmap$(OBJSUFFICS) udpsockmap$(OBJSUFFICS) sockgetchar$(OBJSUFFICS) common$(OBJSUFFICS) sem$(OBJSUFFICS) log$(OBJSUFFICS) 3proxy_crypt$(OBJSUFFICS) md4$(OBJSUFFICS) md5$(OBJSUFFICS) blake2$(OBJSUFFICS) base64$(OBJSUFFICS) ftp$(OBJSUFFICS) stringtable$(OBJSUFFICS) $(HTTPSRV_OBJS) srvdnspr$(OBJSUFFICS) plugins$(OBJSUFFICS) mdhash$(OBJSUFFICS) $(SSL_OBJS) $(PCRE_OBJS) $(TRANSPARENT_OBJS) $(COMPATLIBS) $(LIBS) $(PCRE_LIBS)
|
||||||
|
|
||||||
|
|||||||
474
src/fiber.c
Normal file
474
src/fiber.c
Normal file
@ -0,0 +1,474 @@
|
|||||||
|
/*
|
||||||
|
3APA3A simplest proxy server
|
||||||
|
(c) 2002-2026 by Vladimir Dubrovin <vlad@3proxy.org>
|
||||||
|
|
||||||
|
please read License Agreement
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include "proxy.h"
|
||||||
|
#include "fiber.h"
|
||||||
|
|
||||||
|
#ifdef WITH_FIBERS
|
||||||
|
|
||||||
|
#ifndef _WIN32
|
||||||
|
#include <sys/mman.h>
|
||||||
|
#endif
|
||||||
|
#if defined FIBER_X86_64 && defined __linux__
|
||||||
|
#include <sys/syscall.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined __has_feature
|
||||||
|
#if __has_feature(address_sanitizer)
|
||||||
|
#define FIBER_ASAN
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
|
#if defined __SANITIZE_ADDRESS__ && !defined FIBER_ASAN
|
||||||
|
#define FIBER_ASAN
|
||||||
|
#endif
|
||||||
|
#if defined FIBER_ASAN && defined FIBER_WINDOWS
|
||||||
|
#undef FIBER_ASAN
|
||||||
|
#endif
|
||||||
|
#ifdef FIBER_ASAN
|
||||||
|
#include <sanitizer/common_interface_defs.h>
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#define FTLS __declspec(thread)
|
||||||
|
#else
|
||||||
|
#define FTLS __thread
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* the fiber running, NULL while the thread runs on its own stack */
|
||||||
|
static FTLS struct fiber *current;
|
||||||
|
#ifdef FIBER_ASAN
|
||||||
|
/* the thread's own stack, for AddressSanitizer */
|
||||||
|
static FTLS const void *threadbottom;
|
||||||
|
static FTLS size_t threadsize;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static void fibermain(struct fiber *f);
|
||||||
|
|
||||||
|
|
||||||
|
/* ---- the switch ----------------------------------------------------------
|
||||||
|
|
||||||
|
fiberswap(from, to) saves the registers a function has to keep on the
|
||||||
|
stack, the stack pointer then to *from, and goes on from the stack
|
||||||
|
pointer to, where fiberswap() saved them, or fiberstart() put them for
|
||||||
|
a fiber not started: returning goes on with fiberentry(). */
|
||||||
|
|
||||||
|
#ifndef FIBER_WINDOWS
|
||||||
|
|
||||||
|
#ifdef __APPLE__
|
||||||
|
#define FSYM(x) "_" #x
|
||||||
|
#else
|
||||||
|
#define FSYM(x) #x
|
||||||
|
#endif
|
||||||
|
#if defined __ELF__
|
||||||
|
#define FTYPE(x) ".type " FSYM(x) ",%function\n"
|
||||||
|
#define FSIZE(x) ".size " FSYM(x) ",.-" FSYM(x) "\n"
|
||||||
|
#else
|
||||||
|
#define FTYPE(x)
|
||||||
|
#define FSIZE(x)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
void fiberswap(void **from, void *to);
|
||||||
|
|
||||||
|
#if defined FIBER_X86_64
|
||||||
|
/* rbx, rbp, r12-r15 */
|
||||||
|
__asm__(
|
||||||
|
".text\n"
|
||||||
|
".globl " FSYM(fiberswap) "\n"
|
||||||
|
FTYPE(fiberswap)
|
||||||
|
FSYM(fiberswap) ":\n"
|
||||||
|
" pushq %rbp\n"
|
||||||
|
" pushq %rbx\n"
|
||||||
|
" pushq %r12\n"
|
||||||
|
" pushq %r13\n"
|
||||||
|
" pushq %r14\n"
|
||||||
|
" pushq %r15\n"
|
||||||
|
" movq %rsp, (%rdi)\n"
|
||||||
|
" movq %rsi, %rsp\n"
|
||||||
|
" popq %r15\n"
|
||||||
|
" popq %r14\n"
|
||||||
|
" popq %r13\n"
|
||||||
|
" popq %r12\n"
|
||||||
|
" popq %rbx\n"
|
||||||
|
" popq %rbp\n"
|
||||||
|
" ret\n"
|
||||||
|
FSIZE(fiberswap)
|
||||||
|
);
|
||||||
|
#elif defined FIBER_AARCH64
|
||||||
|
/* x19-x28, x29 (frame), x30 (return), d8-d15 */
|
||||||
|
__asm__(
|
||||||
|
".text\n"
|
||||||
|
".globl " FSYM(fiberswap) "\n"
|
||||||
|
".p2align 2\n"
|
||||||
|
FTYPE(fiberswap)
|
||||||
|
FSYM(fiberswap) ":\n"
|
||||||
|
" sub sp, sp, #160\n"
|
||||||
|
" stp x19, x20, [sp, #0]\n"
|
||||||
|
" stp x21, x22, [sp, #16]\n"
|
||||||
|
" stp x23, x24, [sp, #32]\n"
|
||||||
|
" stp x25, x26, [sp, #48]\n"
|
||||||
|
" stp x27, x28, [sp, #64]\n"
|
||||||
|
" stp x29, x30, [sp, #80]\n"
|
||||||
|
" stp d8, d9, [sp, #96]\n"
|
||||||
|
" stp d10, d11, [sp, #112]\n"
|
||||||
|
" stp d12, d13, [sp, #128]\n"
|
||||||
|
" stp d14, d15, [sp, #144]\n"
|
||||||
|
" mov x2, sp\n"
|
||||||
|
" str x2, [x0]\n"
|
||||||
|
" mov sp, x1\n"
|
||||||
|
" ldp x19, x20, [sp, #0]\n"
|
||||||
|
" ldp x21, x22, [sp, #16]\n"
|
||||||
|
" ldp x23, x24, [sp, #32]\n"
|
||||||
|
" ldp x25, x26, [sp, #48]\n"
|
||||||
|
" ldp x27, x28, [sp, #64]\n"
|
||||||
|
" ldp x29, x30, [sp, #80]\n"
|
||||||
|
" ldp d8, d9, [sp, #96]\n"
|
||||||
|
" ldp d10, d11, [sp, #112]\n"
|
||||||
|
" ldp d12, d13, [sp, #128]\n"
|
||||||
|
" ldp d14, d15, [sp, #144]\n"
|
||||||
|
" add sp, sp, #160\n"
|
||||||
|
" ret\n"
|
||||||
|
FSIZE(fiberswap)
|
||||||
|
);
|
||||||
|
#elif defined FIBER_I386
|
||||||
|
/* ebx, esi, edi, ebp; the arguments on the stack */
|
||||||
|
__asm__(
|
||||||
|
".text\n"
|
||||||
|
".globl " FSYM(fiberswap) "\n"
|
||||||
|
FTYPE(fiberswap)
|
||||||
|
FSYM(fiberswap) ":\n"
|
||||||
|
" movl 4(%esp), %eax\n"
|
||||||
|
" movl 8(%esp), %edx\n"
|
||||||
|
" pushl %ebp\n"
|
||||||
|
" pushl %ebx\n"
|
||||||
|
" pushl %esi\n"
|
||||||
|
" pushl %edi\n"
|
||||||
|
" movl %esp, (%eax)\n"
|
||||||
|
" movl %edx, %esp\n"
|
||||||
|
" popl %edi\n"
|
||||||
|
" popl %esi\n"
|
||||||
|
" popl %ebx\n"
|
||||||
|
" popl %ebp\n"
|
||||||
|
" ret\n"
|
||||||
|
FSIZE(fiberswap)
|
||||||
|
);
|
||||||
|
#elif defined FIBER_ARM
|
||||||
|
/* r4-r11, lr, and d8-d15 where there is a floating point unit; in ARM
|
||||||
|
state, bx returning to Thumb code as well */
|
||||||
|
#ifdef __ARM_FP
|
||||||
|
#define FARMVFP 1
|
||||||
|
#define FARMVPUSH " vpush {d8-d15}\n"
|
||||||
|
#define FARMVPOP " vpop {d8-d15}\n"
|
||||||
|
#else
|
||||||
|
#define FARMVFP 0
|
||||||
|
#define FARMVPUSH
|
||||||
|
#define FARMVPOP
|
||||||
|
#endif
|
||||||
|
__asm__(
|
||||||
|
".syntax unified\n"
|
||||||
|
".text\n"
|
||||||
|
".arm\n"
|
||||||
|
".globl " FSYM(fiberswap) "\n"
|
||||||
|
".p2align 2\n"
|
||||||
|
FTYPE(fiberswap)
|
||||||
|
FSYM(fiberswap) ":\n"
|
||||||
|
" push {r4-r11, lr}\n"
|
||||||
|
FARMVPUSH
|
||||||
|
" mov r2, sp\n"
|
||||||
|
" str r2, [r0]\n"
|
||||||
|
" mov sp, r1\n"
|
||||||
|
FARMVPOP
|
||||||
|
" pop {r4-r11, lr}\n"
|
||||||
|
" bx lr\n"
|
||||||
|
FSIZE(fiberswap)
|
||||||
|
#ifdef __thumb__
|
||||||
|
".thumb\n"
|
||||||
|
#endif
|
||||||
|
);
|
||||||
|
#elif defined FIBER_MIPS
|
||||||
|
/* s0-s7, fp, gp, ra, and f20-f30 (f20-f31 with 64 bit registers) unless
|
||||||
|
floating point is soft. A fiber starts at fibertramp, which calls
|
||||||
|
fiberentry() through t9, as position independent code wants. */
|
||||||
|
#if defined __mips_soft_float
|
||||||
|
#define FMIPSFP ""
|
||||||
|
#elif defined __mips_fpr && __mips_fpr == 64
|
||||||
|
#define FMIPSFP(op) \
|
||||||
|
" " op " $f20, 48($sp)\n " op " $f21, 56($sp)\n " op " $f22, 64($sp)\n" \
|
||||||
|
" " op " $f23, 72($sp)\n " op " $f24, 80($sp)\n " op " $f25, 88($sp)\n" \
|
||||||
|
" " op " $f26, 96($sp)\n " op " $f27, 104($sp)\n " op " $f28, 112($sp)\n" \
|
||||||
|
" " op " $f29, 120($sp)\n " op " $f30, 128($sp)\n " op " $f31, 136($sp)\n"
|
||||||
|
#else
|
||||||
|
#define FMIPSFP(op) \
|
||||||
|
" " op " $f20, 48($sp)\n " op " $f22, 56($sp)\n " op " $f24, 64($sp)\n" \
|
||||||
|
" " op " $f26, 72($sp)\n " op " $f28, 80($sp)\n " op " $f30, 88($sp)\n"
|
||||||
|
#endif
|
||||||
|
#ifdef __mips_soft_float
|
||||||
|
#define FMIPSSAVE ""
|
||||||
|
#define FMIPSLOAD ""
|
||||||
|
#else
|
||||||
|
#define FMIPSSAVE FMIPSFP("sdc1")
|
||||||
|
#define FMIPSLOAD FMIPSFP("ldc1")
|
||||||
|
#endif
|
||||||
|
#define FMIPSFRAME 144
|
||||||
|
__asm__(
|
||||||
|
".text\n"
|
||||||
|
".set push\n"
|
||||||
|
".set noreorder\n"
|
||||||
|
".set nomacro\n"
|
||||||
|
".globl fiberswap\n"
|
||||||
|
".ent fiberswap\n"
|
||||||
|
".type fiberswap,%function\n"
|
||||||
|
"fiberswap:\n"
|
||||||
|
" addiu $sp, $sp, -144\n"
|
||||||
|
" sw $16, 0($sp)\n"
|
||||||
|
" sw $17, 4($sp)\n"
|
||||||
|
" sw $18, 8($sp)\n"
|
||||||
|
" sw $19, 12($sp)\n"
|
||||||
|
" sw $20, 16($sp)\n"
|
||||||
|
" sw $21, 20($sp)\n"
|
||||||
|
" sw $22, 24($sp)\n"
|
||||||
|
" sw $23, 28($sp)\n"
|
||||||
|
" sw $30, 32($sp)\n"
|
||||||
|
" sw $28, 36($sp)\n"
|
||||||
|
" sw $31, 40($sp)\n"
|
||||||
|
FMIPSSAVE
|
||||||
|
" sw $sp, 0($4)\n"
|
||||||
|
" move $sp, $5\n"
|
||||||
|
" lw $16, 0($sp)\n"
|
||||||
|
" lw $17, 4($sp)\n"
|
||||||
|
" lw $18, 8($sp)\n"
|
||||||
|
" lw $19, 12($sp)\n"
|
||||||
|
" lw $20, 16($sp)\n"
|
||||||
|
" lw $21, 20($sp)\n"
|
||||||
|
" lw $22, 24($sp)\n"
|
||||||
|
" lw $23, 28($sp)\n"
|
||||||
|
" lw $30, 32($sp)\n"
|
||||||
|
" lw $28, 36($sp)\n"
|
||||||
|
" lw $31, 40($sp)\n"
|
||||||
|
FMIPSLOAD
|
||||||
|
" jr $31\n"
|
||||||
|
" addiu $sp, $sp, 144\n"
|
||||||
|
".end fiberswap\n"
|
||||||
|
".size fiberswap,.-fiberswap\n"
|
||||||
|
".globl fibertramp\n"
|
||||||
|
".ent fibertramp\n"
|
||||||
|
".type fibertramp,%function\n"
|
||||||
|
"fibertramp:\n"
|
||||||
|
" move $25, $16\n"
|
||||||
|
" jalr $25\n"
|
||||||
|
" nop\n"
|
||||||
|
".end fibertramp\n"
|
||||||
|
".size fibertramp,.-fibertramp\n"
|
||||||
|
".set pop\n"
|
||||||
|
);
|
||||||
|
void fibertramp(void);
|
||||||
|
#endif
|
||||||
|
|
||||||
|
/* where a fiber starts: on its own stack, with nothing to return to */
|
||||||
|
static void fiberentry(void){
|
||||||
|
fibermain(current);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* what the thread was doing while a fiber runs */
|
||||||
|
static FTLS void *threadsp;
|
||||||
|
|
||||||
|
#endif /* !FIBER_WINDOWS */
|
||||||
|
|
||||||
|
#ifdef FIBER_WINDOWS
|
||||||
|
#ifndef FIBER_FLAG_FLOAT_SWITCH
|
||||||
|
#define FIBER_FLAG_FLOAT_SWITCH 1
|
||||||
|
#endif
|
||||||
|
/* the thread as a fiber, to switch back to */
|
||||||
|
static FTLS void *threadfiber;
|
||||||
|
|
||||||
|
static VOID WINAPI fiberproc(LPVOID arg){
|
||||||
|
fibermain((struct fiber *)arg);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
|
|
||||||
|
/* ---- fibers ----------------------------------------------------------------- */
|
||||||
|
|
||||||
|
/* 0 where fibers cannot be switched to: a shadow stack, checked by the
|
||||||
|
processor against each return, which ours would not match. */
|
||||||
|
int fiberusable(void){
|
||||||
|
#if defined FIBER_X86_64 && defined __linux__ && defined SYS_arch_prctl
|
||||||
|
unsigned long features = 0;
|
||||||
|
|
||||||
|
/* ARCH_SHSTK_STATUS, ARCH_SHSTK_SHSTK */
|
||||||
|
if(!syscall(SYS_arch_prctl, 0x5005, &features) && (features & 1)) return 0;
|
||||||
|
#endif
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int fiberinit(struct fiber *f, size_t stacksize){
|
||||||
|
memset(f, 0, sizeof(*f));
|
||||||
|
#ifdef FIBER_WINDOWS
|
||||||
|
f->handle = CreateFiberEx(0, stacksize, FIBER_FLAG_FLOAT_SWITCH, fiberproc, f);
|
||||||
|
return !f->handle;
|
||||||
|
#else
|
||||||
|
{
|
||||||
|
size_t page = (size_t)sysconf(_SC_PAGESIZE);
|
||||||
|
int flags = MAP_PRIVATE;
|
||||||
|
void *p;
|
||||||
|
|
||||||
|
#ifdef MAP_ANONYMOUS
|
||||||
|
flags |= MAP_ANONYMOUS;
|
||||||
|
#else
|
||||||
|
flags |= MAP_ANON;
|
||||||
|
#endif
|
||||||
|
#ifdef MAP_STACK
|
||||||
|
flags |= MAP_STACK;
|
||||||
|
#endif
|
||||||
|
stacksize = (stacksize + page - 1) & ~(page - 1);
|
||||||
|
f->mapped = stacksize + page;
|
||||||
|
p = mmap(NULL, f->mapped, PROT_READ | PROT_WRITE, flags, -1, 0);
|
||||||
|
if(p == MAP_FAILED) return 1;
|
||||||
|
f->map = p;
|
||||||
|
/* running over the bottom faults rather than overwriting */
|
||||||
|
if(mprotect(f->map, page, PROT_NONE)){
|
||||||
|
munmap(f->map, f->mapped);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/* A fiber neither running nor in the middle of func. */
|
||||||
|
void fiberfree(struct fiber *f){
|
||||||
|
#ifdef FIBER_WINDOWS
|
||||||
|
if(f->handle) DeleteFiber(f->handle);
|
||||||
|
#else
|
||||||
|
if(f->map) munmap(f->map, f->mapped);
|
||||||
|
#endif
|
||||||
|
memset(f, 0, sizeof(*f));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* func(arg) is what f runs once resumed: f is new, or func of its last
|
||||||
|
start returned. */
|
||||||
|
void fiberstart(struct fiber *f, void (*func)(void *arg), void *arg){
|
||||||
|
f->func = func;
|
||||||
|
f->arg = arg;
|
||||||
|
if(f->started) return;
|
||||||
|
f->started = 1;
|
||||||
|
#ifndef FIBER_WINDOWS
|
||||||
|
{
|
||||||
|
uintptr_t top = (uintptr_t)(f->map + f->mapped) & ~(uintptr_t)15;
|
||||||
|
void **sp;
|
||||||
|
|
||||||
|
#if defined FIBER_X86_64
|
||||||
|
sp = (void **)top;
|
||||||
|
*--sp = NULL; /* fiberentry() returns nowhere */
|
||||||
|
*--sp = (void *)fiberentry; /* where fiberswap() returns */
|
||||||
|
sp -= 6;
|
||||||
|
memset(sp, 0, 6 * sizeof(*sp));
|
||||||
|
#elif defined FIBER_AARCH64
|
||||||
|
sp = (void **)top - 20;
|
||||||
|
memset(sp, 0, 20 * sizeof(*sp));
|
||||||
|
sp[11] = (void *)fiberentry; /* x30 */
|
||||||
|
#elif defined FIBER_I386
|
||||||
|
sp = (void **)top;
|
||||||
|
*--sp = NULL;
|
||||||
|
*--sp = (void *)fiberentry;
|
||||||
|
sp -= 4;
|
||||||
|
memset(sp, 0, 4 * sizeof(*sp));
|
||||||
|
#elif defined FIBER_ARM
|
||||||
|
{
|
||||||
|
int n = (FARMVFP? 16 : 0) + 9;
|
||||||
|
|
||||||
|
sp = (void **)top - n;
|
||||||
|
memset(sp, 0, n * sizeof(*sp));
|
||||||
|
sp[n - 1] = (void *)fiberentry; /* lr */
|
||||||
|
}
|
||||||
|
#elif defined FIBER_MIPS
|
||||||
|
{
|
||||||
|
void *gp;
|
||||||
|
|
||||||
|
/* room for the arguments fiberentry() may store above it */
|
||||||
|
sp = (void **)(top - 32) - FMIPSFRAME / sizeof(void *);
|
||||||
|
memset(sp, 0, FMIPSFRAME);
|
||||||
|
__asm__ volatile ("move %0, $28" : "=r"(gp));
|
||||||
|
sp[0] = (void *)fiberentry; /* s0, called by fibertramp */
|
||||||
|
sp[9] = gp;
|
||||||
|
sp[10] = (void *)fibertramp; /* ra */
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
f->sp = sp;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Runs f until it yields or func returns: from the thread's own stack.
|
||||||
|
1 while func has not returned. */
|
||||||
|
int fiberresume(struct fiber *f){
|
||||||
|
#ifdef FIBER_ASAN
|
||||||
|
void *fake = NULL;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
current = f;
|
||||||
|
#ifdef FIBER_ASAN
|
||||||
|
{
|
||||||
|
size_t page = (size_t)sysconf(_SC_PAGESIZE);
|
||||||
|
|
||||||
|
__sanitizer_start_switch_fiber(&fake, f->map + page, f->mapped - page);
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
#ifdef FIBER_WINDOWS
|
||||||
|
if(!threadfiber){
|
||||||
|
if(!(threadfiber = ConvertThreadToFiber(NULL)) && GetLastError() == ERROR_ALREADY_FIBER)
|
||||||
|
threadfiber = GetCurrentFiber();
|
||||||
|
if(!threadfiber){
|
||||||
|
current = NULL;
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SwitchToFiber(f->handle);
|
||||||
|
#else
|
||||||
|
fiberswap(&threadsp, f->sp);
|
||||||
|
#endif
|
||||||
|
#ifdef FIBER_ASAN
|
||||||
|
__sanitizer_finish_switch_fiber(fake, NULL, NULL);
|
||||||
|
#endif
|
||||||
|
current = NULL;
|
||||||
|
return f->func != NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* From a fiber: back to the thread, until it is resumed. */
|
||||||
|
void fiberyield(void){
|
||||||
|
struct fiber *f = current;
|
||||||
|
|
||||||
|
#ifdef FIBER_ASAN
|
||||||
|
__sanitizer_start_switch_fiber(&f->fake, threadbottom, threadsize);
|
||||||
|
#endif
|
||||||
|
#ifdef FIBER_WINDOWS
|
||||||
|
SwitchToFiber(threadfiber);
|
||||||
|
#else
|
||||||
|
fiberswap(&f->sp, threadsp);
|
||||||
|
#endif
|
||||||
|
#ifdef FIBER_ASAN
|
||||||
|
__sanitizer_finish_switch_fiber(f->fake, &threadbottom, &threadsize);
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
struct fiber *fibercurrent(void){
|
||||||
|
return current;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* What a fiber runs: func each time it is started, yielding once it
|
||||||
|
returns. */
|
||||||
|
static void fibermain(struct fiber *f){
|
||||||
|
#ifdef FIBER_ASAN
|
||||||
|
__sanitizer_finish_switch_fiber(NULL, &threadbottom, &threadsize);
|
||||||
|
#endif
|
||||||
|
for(;;){
|
||||||
|
f->func(f->arg);
|
||||||
|
f->func = NULL;
|
||||||
|
fiberyield();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
60
src/fiber.h
Normal file
60
src/fiber.h
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
/*
|
||||||
|
3APA3A simplest proxy server
|
||||||
|
(c) 2002-2026 by Vladimir Dubrovin <vlad@3proxy.org>
|
||||||
|
|
||||||
|
please read License Agreement
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Fibers: stacks of their own a thread switches to and back, for a thread
|
||||||
|
to serve many clients each with code written as if it had a thread of
|
||||||
|
its own. The switch is our own where the processor is one of ours,
|
||||||
|
Windows' fibers on Windows; elsewhere there are none (WITH_FIBERS is not
|
||||||
|
defined), and each client has a thread. A fiber is resumed by the thread
|
||||||
|
it belongs to and yields back to it: fibers do not switch to each other,
|
||||||
|
nor move between threads. */
|
||||||
|
|
||||||
|
#ifndef _FIBER_H
|
||||||
|
#define _FIBER_H
|
||||||
|
|
||||||
|
#if defined _WIN32
|
||||||
|
#if !defined __WATCOMC__ && !defined _WINCE
|
||||||
|
#define FIBER_WINDOWS
|
||||||
|
#endif
|
||||||
|
#elif defined __x86_64__ && defined __LP64__
|
||||||
|
#define FIBER_X86_64
|
||||||
|
#elif defined __aarch64__ && defined __LP64__
|
||||||
|
#define FIBER_AARCH64
|
||||||
|
#elif defined __i386__
|
||||||
|
#define FIBER_I386
|
||||||
|
#elif defined __arm__
|
||||||
|
#define FIBER_ARM
|
||||||
|
#elif defined __mips__ && defined _MIPS_SIM && defined _ABIO32 && _MIPS_SIM == _ABIO32
|
||||||
|
#define FIBER_MIPS
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if defined FIBER_WINDOWS || defined FIBER_X86_64 || defined FIBER_AARCH64 || defined FIBER_I386 || defined FIBER_ARM || defined FIBER_MIPS
|
||||||
|
#define WITH_FIBERS
|
||||||
|
|
||||||
|
struct fiber {
|
||||||
|
void *sp; /* where its registers are while it does not run */
|
||||||
|
unsigned char *map; /* its stack, the guard page at the bottom */
|
||||||
|
size_t mapped;
|
||||||
|
void (*func)(void *arg); /* what it runs, NULL once returned */
|
||||||
|
void *arg;
|
||||||
|
int started;
|
||||||
|
#ifdef FIBER_WINDOWS
|
||||||
|
void *handle;
|
||||||
|
#endif
|
||||||
|
void *fake; /* AddressSanitizer's, while it does not run */
|
||||||
|
};
|
||||||
|
|
||||||
|
int fiberusable(void);
|
||||||
|
int fiberinit(struct fiber *f, size_t stacksize);
|
||||||
|
void fiberfree(struct fiber *f);
|
||||||
|
void fiberstart(struct fiber *f, void (*func)(void *arg), void *arg);
|
||||||
|
int fiberresume(struct fiber *f);
|
||||||
|
void fiberyield(void);
|
||||||
|
struct fiber *fibercurrent(void);
|
||||||
|
|
||||||
|
#endif
|
||||||
|
#endif
|
||||||
@ -68,10 +68,13 @@ Note that access rules accumulate until `flush`, so a service section that
|
|||||||
means to stand on its own should start with one - otherwise an earlier
|
means to stand on its own should start with one - otherwise an earlier
|
||||||
`allow *` matches first and the rule under test is never reached.
|
`allow *` matches first and the rule under test is never reached.
|
||||||
|
|
||||||
The event loop (`src/loop.c`) is tested on its own by `tests/looptest.c`,
|
The event loop (`src/loop.c`) and fibers (`src/fiber.c`) are tested on their
|
||||||
which CMake builds next to the 3proxy binary and the `loop` case runs: timers
|
own by `tests/looptest.c` and `tests/fibertest.c`, which CMake builds next to
|
||||||
on a clock of its own, through several turns of the wheel, sockets, and
|
the 3proxy binary and the `units` case runs: timers on a clock of their own,
|
||||||
other threads. Where it was not built the case is skipped.
|
through several turns of the wheel, sockets, other threads; fibers yielding
|
||||||
|
and resumed, the registers a function keeps, a thousand stacks, fibers
|
||||||
|
waiting on the loop's timers. Where they were not built the case is
|
||||||
|
skipped.
|
||||||
|
|
||||||
## What is not covered yet
|
## What is not covered yet
|
||||||
|
|
||||||
|
|||||||
@ -1,30 +0,0 @@
|
|||||||
"""The event loop on its own: tests/looptest.c, built by CMake next to the
|
|
||||||
3proxy binary, run with a few seeds for its timers. Skipped where it was not
|
|
||||||
built (the Makefiles do not build it)."""
|
|
||||||
|
|
||||||
import os
|
|
||||||
import subprocess
|
|
||||||
|
|
||||||
|
|
||||||
def run(t):
|
|
||||||
name = "looptest.exe" if os.name == "nt" else "looptest"
|
|
||||||
path = os.path.join(os.path.dirname(os.path.abspath(t.binary)), name)
|
|
||||||
if not os.path.exists(path):
|
|
||||||
t.skip("looptest is built by CMake only")
|
|
||||||
return
|
|
||||||
for seed in ("1", "2", "3"):
|
|
||||||
try:
|
|
||||||
done = subprocess.run([path, seed], stdout=subprocess.PIPE,
|
|
||||||
stderr=subprocess.STDOUT, timeout=120)
|
|
||||||
except subprocess.TimeoutExpired:
|
|
||||||
t.fail(f"looptest {seed} finishes")
|
|
||||||
continue
|
|
||||||
out = done.stdout.decode(errors="replace")
|
|
||||||
for line in out.splitlines():
|
|
||||||
if line.startswith("ok "):
|
|
||||||
t.ok(f"seed {seed}: {line[3:]}")
|
|
||||||
elif line.startswith("FAIL "):
|
|
||||||
label, _, what = line[5:].partition(": ")
|
|
||||||
t.fail(f"seed {seed}: {label}", actual=what)
|
|
||||||
if done.returncode and "FAIL " not in out:
|
|
||||||
t.fail(f"looptest {seed} exits cleanly", 0, done.returncode)
|
|
||||||
32
tests/cases/units.py
Normal file
32
tests/cases/units.py
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
"""The event loop and fibers on their own: tests/looptest.c and
|
||||||
|
tests/fibertest.c, built by CMake next to the 3proxy binary, each run with
|
||||||
|
a few seeds. Skipped where they were not built (the Makefiles do not build
|
||||||
|
them)."""
|
||||||
|
|
||||||
|
import os
|
||||||
|
import subprocess
|
||||||
|
|
||||||
|
|
||||||
|
def run(t):
|
||||||
|
here = os.path.dirname(os.path.abspath(t.binary))
|
||||||
|
for unit in ("looptest", "fibertest"):
|
||||||
|
path = os.path.join(here, unit + (".exe" if os.name == "nt" else ""))
|
||||||
|
if not os.path.exists(path):
|
||||||
|
t.skip(f"{unit} is built by CMake only")
|
||||||
|
continue
|
||||||
|
for seed in ("1", "2", "3"):
|
||||||
|
try:
|
||||||
|
done = subprocess.run([path, seed], stdout=subprocess.PIPE,
|
||||||
|
stderr=subprocess.STDOUT, timeout=120)
|
||||||
|
except subprocess.TimeoutExpired:
|
||||||
|
t.fail(f"{unit} {seed} finishes")
|
||||||
|
continue
|
||||||
|
out = done.stdout.decode(errors="replace")
|
||||||
|
for line in out.splitlines():
|
||||||
|
if line.startswith("ok "):
|
||||||
|
t.ok(f"{unit} {seed}: {line[3:]}")
|
||||||
|
elif line.startswith("FAIL "):
|
||||||
|
label, _, what = line[5:].partition(": ")
|
||||||
|
t.fail(f"{unit} {seed}: {label}", actual=what)
|
||||||
|
if done.returncode and "FAIL " not in out:
|
||||||
|
t.fail(f"{unit} {seed} exits cleanly", 0, done.returncode)
|
||||||
321
tests/fibertest.c
Normal file
321
tests/fibertest.c
Normal file
@ -0,0 +1,321 @@
|
|||||||
|
/*
|
||||||
|
3APA3A simplest proxy server
|
||||||
|
(c) 2002-2026 by Vladimir Dubrovin <vlad@3proxy.org>
|
||||||
|
|
||||||
|
please read License Agreement
|
||||||
|
*/
|
||||||
|
|
||||||
|
/* Fibers on their own, and waiting on the event loop: tests/cases/units.py
|
||||||
|
runs it. Each check is a line, "ok LABEL" or "FAIL LABEL: what", the
|
||||||
|
exit code the number of failures. */
|
||||||
|
|
||||||
|
#include "../src/proxy.h"
|
||||||
|
#include "../src/loop.h"
|
||||||
|
#include "../src/fiber.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);
|
||||||
|
}
|
||||||
|
|
||||||
|
#ifndef WITH_FIBERS
|
||||||
|
|
||||||
|
int main(void){
|
||||||
|
printf("ok no fibers on this processor: each client has a thread\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
#define STACK 65536
|
||||||
|
|
||||||
|
#ifdef _MSC_VER
|
||||||
|
#define NOINLINE __declspec(noinline)
|
||||||
|
#else
|
||||||
|
#define NOINLINE __attribute__((noinline))
|
||||||
|
#endif
|
||||||
|
|
||||||
|
static unsigned long long rnd = 88172645463325252ULL;
|
||||||
|
|
||||||
|
static unsigned long long rand64(void){
|
||||||
|
rnd ^= rnd << 13;
|
||||||
|
rnd ^= rnd >> 7;
|
||||||
|
rnd ^= rnd << 17;
|
||||||
|
return rnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ---- one fiber, yielding ------------------------------------------------ */
|
||||||
|
|
||||||
|
static int steps[8], nsteps;
|
||||||
|
static struct fiber *seen;
|
||||||
|
|
||||||
|
static void stepper(void *arg){
|
||||||
|
int i;
|
||||||
|
|
||||||
|
seen = fibercurrent();
|
||||||
|
for(i = 0; i < 5; i++){
|
||||||
|
steps[nsteps++] = i + *(int *)arg;
|
||||||
|
fiberyield();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void basic(void){
|
||||||
|
struct fiber f;
|
||||||
|
int base = 100, i, more = 0, ok = 1;
|
||||||
|
char what[128];
|
||||||
|
|
||||||
|
check(fibercurrent() == NULL, "the thread runs on its own stack", "a fiber is current");
|
||||||
|
if(fiberinit(&f, STACK)){
|
||||||
|
check(0, "a fiber is made", "fiberinit() failed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fiberstart(&f, stepper, &base);
|
||||||
|
for(i = 0; i < 6; i++){
|
||||||
|
more = fiberresume(&f);
|
||||||
|
if(i < 5 && !more) ok = 0;
|
||||||
|
}
|
||||||
|
check(seen == &f, "a fiber is current while it runs", "it is not");
|
||||||
|
check(fibercurrent() == NULL, "the thread is back on its own stack", "a fiber is current");
|
||||||
|
for(i = 0; i < 5; i++) if(steps[i] != 100 + i) ok = 0;
|
||||||
|
sprintf(what, "%d steps, more %d", nsteps, more);
|
||||||
|
check(ok && nsteps == 5 && !more, "it runs to each yield in turn, then returns", what);
|
||||||
|
|
||||||
|
/* started again once it returned */
|
||||||
|
nsteps = 0;
|
||||||
|
base = 200;
|
||||||
|
fiberstart(&f, stepper, &base);
|
||||||
|
while(fiberresume(&f));
|
||||||
|
check(nsteps == 5 && steps[4] == 204, "a fiber is started again", "it is not");
|
||||||
|
fiberfree(&f);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ---- what a function has to keep, kept ---------------------------------- */
|
||||||
|
|
||||||
|
/* Many values live across the switch: the compiler keeps them in the
|
||||||
|
registers a call has to preserve. */
|
||||||
|
static NOINLINE unsigned long churn(unsigned long seed, double *dout,
|
||||||
|
int (*sw)(void *), void *swarg){
|
||||||
|
unsigned long a = seed, b = seed * 3, c = seed * 5, d = seed * 7, e = seed * 11,
|
||||||
|
g = seed * 13, h = seed * 17, k = seed * 19;
|
||||||
|
double x = (double)seed / 3, y = (double)seed / 7, z = (double)seed * 1.5,
|
||||||
|
w = (double)seed + 0.25, u = (double)seed * 0.125, v = (double)seed - 0.5,
|
||||||
|
q = (double)seed * 2.75, r = (double)seed / 9;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
for(i = 0; i < 4; i++){
|
||||||
|
a += b ^ c; b += c ^ d; c += d ^ e; d += e ^ g; e += g ^ h; g += h ^ k; h += k ^ a; k += a ^ b;
|
||||||
|
x = x * 1.0001 + y; y = y * 0.9999 + z; z = z * 1.0002 - w; w = w * 0.9998 + u;
|
||||||
|
u = u * 1.0003 + v; v = v * 0.9997 - q; q = q * 1.0004 + r; r = r * 0.9996 + x;
|
||||||
|
sw(swarg);
|
||||||
|
}
|
||||||
|
*dout = x + y + z + w + u + v + q + r;
|
||||||
|
return a ^ b ^ c ^ d ^ e ^ g ^ h ^ k;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int noswitch(void *arg){
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int doyield(void *arg){
|
||||||
|
fiberyield();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int doresume(void *arg){
|
||||||
|
return fiberresume((struct fiber *)arg);
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned long fibresult;
|
||||||
|
static double fibdouble;
|
||||||
|
|
||||||
|
static void churner(void *arg){
|
||||||
|
fibresult = churn(12345, &fibdouble, doyield, NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void kept(void){
|
||||||
|
struct fiber f, g;
|
||||||
|
unsigned long expect, got;
|
||||||
|
double dexpect, dgot;
|
||||||
|
|
||||||
|
if(fiberinit(&f, STACK) || fiberinit(&g, STACK)){
|
||||||
|
check(0, "fibers are made", "fiberinit() failed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
expect = churn(12345, &dexpect, noswitch, NULL);
|
||||||
|
|
||||||
|
/* the fiber's values across its yields */
|
||||||
|
fiberstart(&f, churner, NULL);
|
||||||
|
while(fiberresume(&f));
|
||||||
|
check(fibresult == expect && fibdouble == dexpect,
|
||||||
|
"a fiber's integer and floating point values survive its yields", "they changed");
|
||||||
|
|
||||||
|
/* the thread's values across the fiber running */
|
||||||
|
fiberstart(&g, churner, NULL);
|
||||||
|
got = churn(12345, &dgot, doresume, &g);
|
||||||
|
while(fiberresume(&g));
|
||||||
|
check(got == expect && dgot == dexpect,
|
||||||
|
"the thread's integer and floating point values survive a fiber running", "they changed");
|
||||||
|
fiberfree(&f);
|
||||||
|
fiberfree(&g);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ---- many fibers, their stacks their own -------------------------------- */
|
||||||
|
|
||||||
|
#define NFIB 1000
|
||||||
|
#define ROUNDS 20
|
||||||
|
|
||||||
|
static int corrupted;
|
||||||
|
|
||||||
|
static void filler(void *arg){
|
||||||
|
unsigned id = (unsigned)(uintptr_t)arg;
|
||||||
|
unsigned char buf[4096];
|
||||||
|
int r;
|
||||||
|
size_t i;
|
||||||
|
|
||||||
|
for(r = 0; r < ROUNDS; r++){
|
||||||
|
for(i = 0; i < sizeof(buf); i++) buf[i] = (unsigned char)(id * 31 + r * 7 + i);
|
||||||
|
fiberyield();
|
||||||
|
for(i = 0; i < sizeof(buf); i++) if(buf[i] != (unsigned char)(id * 31 + r * 7 + i)){
|
||||||
|
corrupted++;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void many(void){
|
||||||
|
static struct fiber f[NFIB];
|
||||||
|
static int done[NFIB];
|
||||||
|
int i, left = NFIB;
|
||||||
|
char what[64];
|
||||||
|
|
||||||
|
for(i = 0; i < NFIB; i++){
|
||||||
|
if(fiberinit(f + i, STACK)){
|
||||||
|
check(0, "a thousand fibers are made", "fiberinit() failed");
|
||||||
|
while(i--) fiberfree(f + i);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
fiberstart(f + i, filler, (void *)(uintptr_t)i);
|
||||||
|
}
|
||||||
|
while(left){
|
||||||
|
i = (int)(rand64() % NFIB);
|
||||||
|
if(done[i]) continue;
|
||||||
|
if(!fiberresume(f + i)){
|
||||||
|
done[i] = 1;
|
||||||
|
left--;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
sprintf(what, "%d stacks changed under their fiber", corrupted);
|
||||||
|
check(!corrupted, "a thousand fibers resumed at random keep their stacks", what);
|
||||||
|
for(i = 0; i < NFIB; i++) fiberfree(f + i);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ---- deep enough ------------------------------------------------------------ */
|
||||||
|
|
||||||
|
static NOINLINE unsigned deep(unsigned n){
|
||||||
|
volatile unsigned char pad[256];
|
||||||
|
|
||||||
|
pad[0] = (unsigned char)n;
|
||||||
|
pad[255] = (unsigned char)(n >> 1);
|
||||||
|
if(!n) return 0;
|
||||||
|
return pad[0] + pad[255] + deep(n - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static unsigned deepresult;
|
||||||
|
|
||||||
|
static void deeper(void *arg){
|
||||||
|
fiberyield();
|
||||||
|
deepresult = deep(150);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void depth(void){
|
||||||
|
struct fiber f;
|
||||||
|
|
||||||
|
if(fiberinit(&f, STACK)) return;
|
||||||
|
fiberstart(&f, deeper, NULL);
|
||||||
|
while(fiberresume(&f));
|
||||||
|
check(deepresult == deep(150), "a fiber uses most of its stack", "wrong result");
|
||||||
|
fiberfree(&f);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/* ---- fibers waiting on the event loop ------------------------------------ */
|
||||||
|
|
||||||
|
#define NWAIT 100
|
||||||
|
|
||||||
|
struct waiter {
|
||||||
|
struct fiber f;
|
||||||
|
struct looptimer t;
|
||||||
|
struct loop *l;
|
||||||
|
unsigned ms;
|
||||||
|
uint64_t slept;
|
||||||
|
int done;
|
||||||
|
};
|
||||||
|
|
||||||
|
static void wake(struct loop *l, struct looptimer *t){
|
||||||
|
struct waiter *w = t->data;
|
||||||
|
|
||||||
|
if(!fiberresume(&w->f)) w->done = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* what a client's code will do: wait as if it had a thread of its own */
|
||||||
|
static void sleeper(void *arg){
|
||||||
|
struct waiter *w = arg;
|
||||||
|
uint64_t start = looptime();
|
||||||
|
|
||||||
|
looptimerset(w->l, &w->t, start + w->ms);
|
||||||
|
fiberyield();
|
||||||
|
w->slept = looptime() - start;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void onloop(void){
|
||||||
|
static struct waiter w[NWAIT];
|
||||||
|
struct loop l;
|
||||||
|
int i, left, early = 0;
|
||||||
|
char what[64];
|
||||||
|
|
||||||
|
if(loopinit(&l)) return;
|
||||||
|
for(i = 0; i < NWAIT; i++){
|
||||||
|
memset(w + i, 0, sizeof(w[i]));
|
||||||
|
if(fiberinit(&w[i].f, STACK)) return;
|
||||||
|
w[i].l = &l;
|
||||||
|
w[i].ms = 10 + (unsigned)(rand64() % 300);
|
||||||
|
w[i].t.func = wake;
|
||||||
|
w[i].t.data = w + i;
|
||||||
|
fiberstart(&w[i].f, sleeper, w + i);
|
||||||
|
fiberresume(&w[i].f);
|
||||||
|
}
|
||||||
|
for(left = NWAIT; left; ){
|
||||||
|
looponce(&l, -1);
|
||||||
|
for(left = 0, i = 0; i < NWAIT; i++) if(!w[i].done) left++;
|
||||||
|
}
|
||||||
|
for(i = 0; i < NWAIT; i++) if(w[i].slept < w[i].ms) early++;
|
||||||
|
sprintf(what, "%d woke early", early);
|
||||||
|
check(!early, "a hundred fibers sleep on the loop's timers, each as long as it asked", what);
|
||||||
|
for(i = 0; i < NWAIT; i++) fiberfree(&w[i].f);
|
||||||
|
loopfree(&l);
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char **argv){
|
||||||
|
if(!fiberusable()){
|
||||||
|
printf("ok fibers cannot be used here (a shadow stack): each client has a thread\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
basic();
|
||||||
|
kept();
|
||||||
|
many();
|
||||||
|
depth();
|
||||||
|
onloop();
|
||||||
|
return failures;
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
Loading…
Reference in New Issue
Block a user