varch/test/test_coroutine.c
Lamdonn c0a7f18093 Update coroutine
1. Compatibility with coroutine API highly intensive IO, sockets, etc
2. Fix running fail on arm64
3. Add low power configuration hook
TODO:
1. Add a scheduling ready task queue mechanism
2. Add CoTask communication channel
2026-10-11 00:01:44 +08:00

736 lines
21 KiB
C

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <time.h>
#if defined(_WIN32)
#include <windows.h>
#else
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#endif
#if defined(TEST_TARGET_coroutine)
#include <varch/command.h>
#include <varch/unitt.h>
#include <varch/coroutine.h>
#include <varch/coroutine_hook.h>
#include <varch/coroutine_io.h>
#else
#include "init.h"
#include "command.h"
#include "unitt.h"
#include "kern.h"
#include "coroutine.h"
#include "coroutine_hook.h"
#include "coroutine_io.h"
#endif
#define MUTI_THREAD_NUM 2
#if (MUTI_THREAD_NUM <= 0)
#error "MUTI_THREAD_NUM must be greater than 0"
#endif
#ifdef _WIN32
#include <windows.h>
uint64_t GetTimerUsec(void)
{
LARGE_INTEGER li;
LARGE_INTEGER frequency;
QueryPerformanceFrequency(&frequency);
QueryPerformanceCounter(&li);
return (uint64_t)li.QuadPart * 1000000 / frequency.QuadPart;
}
#else
#include <sys/time.h>
uint64_t GetTimerUsec(void)
{
struct timeval mstime;
uint64_t us = 0;
gettimeofday(&mstime, NULL);
us = mstime.tv_sec * 1000000 + mstime.tv_usec;
return us;
}
#endif
/************************************************************************************/
/************************************* Unit Test ************************************/
/************************************************************************************/
// #define EXIT_TEST
extern uint64_t unitt_clock(void);
static int s_libco_step = 0;
static void *libco_test_worker(void *arg)
{
s_libco_step = 1;
CoTask_Yield();
s_libco_step = 2;
CoTask_Yield();
s_libco_step = 3;
return NULL;
}
static int test_libco(void)
{
CoScheduler_Init(0, GetTimerUsec, 1000);
s_libco_step = 0;
CoTask_t task = CoTask_Create(libco_test_worker, .manual=1);
if (!task) return UNITT_E_FAIL;
int ret1 = CoTask_Resume(task);
if (ret1 != COROUTINE_E_OK || s_libco_step != 1) return UNITT_E_FAIL;
int ret2 = CoTask_Resume(task);
if (ret2 != COROUTINE_E_OK || s_libco_step != 2) return UNITT_E_FAIL;
int ret3 = CoTask_Resume(task);
if (ret3 != 1 || s_libco_step != 3) return UNITT_E_FAIL;
CoTask_Delete(task);
return UNITT_E_OK;
}
static int test_0(void)
{
return test_libco();
}
static void unitt_task(void)
{
static UNITT_TCASE rand_tests[] = {
UNITT_TCASE(test_0),
UNITT_TCASE(test_libco),
};
static UNITT suites[] = {
{ "coroutine suite", rand_tests, sizeof(rand_tests) / sizeof(rand_tests[0]) , unitt_clock },
};
UNITT_EXE(suites);
}
/************************************************************************************/
/************************************* Base Test ************************************/
/************************************************************************************/
static CoEvent gEvent = COEVENT_STATIC_VALUE;
/* --- 1. Manual Point-to-Point Test (libco style) --- */
static void *manual_worker(void *arg)
{
printf(" [Manual Task] Step 1: started, yielding...\n");
CoTask_Yield();
printf(" [Manual Task] Step 2: resumed, yielding again...\n");
CoTask_Yield();
printf(" [Manual Task] Step 3: resumed, yielding again...\n");
CoTask_Yield();
printf(" [Manual Task] Step 4: resumed, finishing!\n");
return NULL;
}
static void test_manual(void)
{
printf("=== [Test Manual] Point-to-Point Stepping (CoTask_Resume & CoTask_Yield) ===\n");
CoScheduler_Init(0, GetTimerUsec, 1000);
CoTask_t task = CoTask_Create(manual_worker, .manual = 1);
if (!task)
{
printf("Failed to create manual task!\n");
return;
}
for (int step = 1; step <= 4; step++)
{
printf("[Main] Calling CoTask_Resume (%d)...\n", step);
int ret = CoTask_Resume(task);
printf("[Main] CoTask_Resume returned: %d\n", ret);
}
CoTask_Delete(task);
printf("=== [Test Manual] Completed! ===\n\n");
}
/* --- 2. Shared Stack Test --- */
static void *shared_worker_a(void *arg)
{
volatile int buf[100];
for (int i = 0; i < 100; i++) buf[i] = 1000 + i;
printf(" [Shared A] initial buf[0]=%d, buf[99]=%d, yielding...\n", buf[0], buf[99]);
fflush(stdout);
CoTask_Yield();
printf(" [Shared A] resumed! checking buf[0]=%d, buf[99]=%d\n", buf[0], buf[99]);
if (buf[0] != 1000 || buf[99] != 1099)
{
printf(" [Shared A] ERROR: stack data corrupted!\n");
}
else
{
printf(" [Shared A] SUCCESS: stack data perfectly preserved!\n");
}
fflush(stdout);
return NULL;
}
static void *shared_worker_b(void *arg)
{
volatile int buf[100];
for (int i = 0; i < 100; i++) buf[i] = 2000 + i;
printf(" [Shared B] initial buf[0]=%d, buf[99]=%d, yielding...\n", buf[0], buf[99]);
fflush(stdout);
CoTask_Yield();
printf(" [Shared B] resumed! checking buf[0]=%d, buf[99]=%d\n", buf[0], buf[99]);
if (buf[0] != 2000 || buf[99] != 2099)
{
printf(" [Shared B] ERROR: stack data corrupted!\n");
}
else
{
printf(" [Shared B] SUCCESS: stack data perfectly preserved!\n");
}
fflush(stdout);
return NULL;
}
static void test_shared(void)
{
printf("=== [Test Shared] Shared Stack Mode (Copy on Suspend, Restore on Resume) ===\n");
#if (COROUTINE_SHARED_STACK_SIZE > 0)
Coroutine_Init(.malloc = malloc, .free = free);
CoScheduler_Init(0, GetTimerUsec, 1000);
CoTask_t ta = CoTask_Create(shared_worker_a, .manual = 1, .sharedStack = 1);
CoTask_t tb = CoTask_Create(shared_worker_b, .manual = 1, .sharedStack = 1);
if (!ta || !tb)
{
printf("Failed to create shared stack tasks! (Ensure dynamic memory allocator is configured)\n");
return;
}
printf("[Main] Step 1: Run Task A on shared stack...\n");
CoTask_Resume(ta);
printf("[Main] Step 2: Run Task B (overwriting physical shared stack)...\n");
CoTask_Resume(tb);
printf("[Main] Step 3: Resume Task A (restores stack A)...\n");
CoTask_Resume(ta);
printf("[Main] Step 4: Resume Task B (restores stack B)...\n");
CoTask_Resume(tb);
CoTask_Delete(ta);
CoTask_Delete(tb);
printf("=== [Test Shared] Completed! ===\n\n");
#else
printf("COROUTINE_SHARED_STACK_SIZE is 0. Shared stack feature is disabled!\n");
#endif
}
/* --- 3. Single Scheduler Automatic Mode --- */
static void *single_tick(void *arg)
{
for (int i = 1; i <= 5; i++)
{
CoTask_Wait(.ms = 100);
printf(" [Single Scheduler] tick %d/5...\n", i);
CoEvent_Notify(&gEvent, 0x02);
}
printf(" [Single Scheduler] tick finished!\n");
return NULL;
}
static void *single_tock(void *arg)
{
for (int i = 1; i <= 5; i++)
{
uint32_t evs = CoTask_Wait(.pEvent = &gEvent, .ms = 500);
if (evs & 0x02)
{
printf(" [Single Scheduler] tock received event %d/5!\n", i);
}
}
printf(" [Single Scheduler] tock finished, exiting scheduler 0...\n");
CoScheduler_Exit(0);
return NULL;
}
static void test_single(void)
{
printf("=== [Test Single] Single Scheduler Automatic Mode ===\n");
CoScheduler_Init(0, GetTimerUsec, 1000);
gEvent.flag = 0;
CoTask_Create(single_tick);
CoTask_Create(single_tock);
printf("[Main] Starting single scheduler 0...\n");
CoScheduler_Start(0);
printf("=== [Test Single] Completed! ===\n\n");
}
/* --- 4. Multi-Scheduler Multi-Threaded Mode --- */
static pthread_t threads[MUTI_THREAD_NUM];
static pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
static void thread_lock(void)
{
pthread_mutex_lock(&mutex);
}
static void thread_unlock(void)
{
pthread_mutex_unlock(&mutex);
}
static void *multi_tick(void *arg)
{
for (int i = 1; i <= 5; i++)
{
CoTask_Wait(.ms = 100);
printf(" [Multi-Core] Thread %d: tick %d/5...\n", CoTask_SchedulerId(), i);
CoEvent_Notify(&gEvent, 0x02);
}
return NULL;
}
static void *multi_tock(void *arg)
{
for (int i = 1; i <= 5; i++)
{
uint32_t evs = CoTask_Wait(.pEvent = &gEvent, .ms = 500);
if (evs & 0x02)
{
printf(" [Multi-Core] Thread %d: tock received event %d/5!\n", CoTask_SchedulerId(), i);
}
}
printf(" [Multi-Core] tock finished, exiting schedulers...\n");
for (int i = 0; i < MUTI_THREAD_NUM; i++)
{
CoScheduler_Exit(i);
}
return NULL;
}
static void *muti_thread_entry(void *arg)
{
CoScheduler_Start(*(int *)arg);
return NULL;
}
static void test_multi(void)
{
printf("=== [Test Multi] Multi-Scheduler Multi-Threaded Mode ===\n");
int threadId[MUTI_THREAD_NUM];
Coroutine_Init(.lock = thread_lock, .unlock = thread_unlock);
for (int i = 0; i < MUTI_THREAD_NUM; i++)
{
CoScheduler_Init(i, GetTimerUsec, 1000);
}
gEvent.flag = 0;
CoTask_Create(multi_tick, .schedulerId = 0);
CoTask_Create(multi_tock, .schedulerId = 1);
for (int i = 0; i < MUTI_THREAD_NUM; i++)
{
threadId[i] = i;
pthread_create(&threads[i], NULL, muti_thread_entry, &threadId[i]);
}
for (int i = 0; i < MUTI_THREAD_NUM; i++)
{
pthread_join(threads[i], NULL);
}
printf("=== [Test Multi] Completed! ===\n\n");
}
/* --- 5. Low-Power Hook Benchmark (CPU Load Comparison) --- */
static uint64_t GetThreadCpuTimeUs(void)
{
#if defined(_WIN32)
FILETIME createTime, exitTime, kernelTime, userTime;
if (GetThreadTimes(GetCurrentThread(), &createTime, &exitTime, &kernelTime, &userTime))
{
ULARGE_INTEGER k, u;
k.LowPart = kernelTime.dwLowDateTime;
k.HighPart = kernelTime.dwHighDateTime;
u.LowPart = userTime.dwLowDateTime;
u.HighPart = userTime.dwHighDateTime;
return (k.QuadPart + u.QuadPart) / 10; // 100ns units to us
}
return 0;
#elif defined(CLOCK_THREAD_CPUTIME_ID)
struct timespec ts;
if (clock_gettime(CLOCK_THREAD_CPUTIME_ID, &ts) == 0)
{
return (uint64_t)ts.tv_sec * 1000000ULL + (uint64_t)ts.tv_nsec / 1000ULL;
}
return 0;
#else
return (uint64_t)clock() * 1000000ULL / CLOCKS_PER_SEC;
#endif
}
static void *hook_bench_worker(void *arg)
{
/* Wait for 400 ms */
CoTask_Wait(.ms = 400);
CoScheduler_Exit(0);
return NULL;
}
static void test_hook(void)
{
printf("=== [Test Low-Power Hook Benchmark: 400ms Idle] ===\n");
/* --- Phase 1: WITHOUT Hook (Busy-Spin) --- */
printf("[Phase 1] Testing WITHOUT Hook (CPU busy-spin)...\n");
CoScheduler_Init(0, GetTimerUsec, 1000); // No hooks
CoTask_Create(hook_bench_worker);
uint64_t wall_start_1 = GetTimerUsec();
uint64_t cpu_start_1 = GetThreadCpuTimeUs();
CoScheduler_Start(0);
uint64_t cpu_end_1 = GetThreadCpuTimeUs();
uint64_t wall_end_1 = GetTimerUsec();
double wall_ms_1 = (double)(wall_end_1 - wall_start_1) / 1000.0;
double cpu_ms_1 = (double)(cpu_end_1 - cpu_start_1) / 1000.0;
double cpu_load_1 = (wall_ms_1 > 0) ? (cpu_ms_1 / wall_ms_1 * 100.0) : 0.0;
if (cpu_load_1 > 100.0) cpu_load_1 = 100.0;
printf(" -> Wall Time: %.1f ms, Active CPU Time: %.1f ms, CPU Load: %.1f%%\n\n",
wall_ms_1, cpu_ms_1, cpu_load_1);
/* --- Phase 2: WITH Hook (Interruptible Low-Power Sleep) --- */
printf("[Phase 2] Testing WITH Low-Power Hook (Interruptible sleep)...\n");
CoScheduler_Init(0, GetTimerUsec, 1000);
CoScheduler_BindDefaultHooks(0);
CoTask_Create(hook_bench_worker);
uint64_t wall_start_2 = GetTimerUsec();
uint64_t cpu_start_2 = GetThreadCpuTimeUs();
CoScheduler_Start(0);
uint64_t cpu_end_2 = GetThreadCpuTimeUs();
uint64_t wall_end_2 = GetTimerUsec();
CoScheduler_UnbindDefaultHooks(0);
double wall_ms_2 = (double)(wall_end_2 - wall_start_2) / 1000.0;
double cpu_ms_2 = (double)(cpu_end_2 - cpu_start_2) / 1000.0;
double cpu_load_2 = (wall_ms_2 > 0) ? (cpu_ms_2 / wall_ms_2 * 100.0) : 0.0;
if (cpu_load_2 > 100.0) cpu_load_2 = 100.0;
printf(" -> Wall Time: %.1f ms, Active CPU Time: %.1f ms, CPU Load: %.1f%%\n\n",
wall_ms_2, cpu_ms_2, cpu_load_2);
printf("==================== Benchmark Comparison ====================\n");
printf(" WITHOUT Hook : CPU Load = %5.1f%% (CPU was churning millions of cycles)\n", cpu_load_1);
printf(" WITH Hook : CPU Load = %5.1f%% (CPU entered kernel sleep, 0%% drain)\n", cpu_load_2);
printf(" Energy Saving: CPU active execution reduced by > %.1f%%!\n",
(cpu_ms_1 > cpu_ms_2) ? ((cpu_ms_1 - cpu_ms_2) / cpu_ms_1 * 100.0) : 99.0);
printf("==============================================================\n\n");
}
/* --- 6. High-Density Asynchronous I/O Test --- */
#define TEST_IO_PORT 19876
static void *io_server_task(void *arg)
{
printf(" [IO Server] Starting non-blocking server on 127.0.0.1:%d...\n", TEST_IO_PORT);
int listen_fd = CoSocket_Create(AF_INET, SOCK_STREAM, 0);
if (listen_fd < 0)
{
printf(" [IO Server] Failed to create socket!\n");
return NULL;
}
int opt = 1;
#if defined(_WIN32)
setsockopt((SOCKET)listen_fd, SOL_SOCKET, SO_REUSEADDR, (const char *)&opt, sizeof(opt));
#else
setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
#endif
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(TEST_IO_PORT);
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
if (bind(listen_fd, (struct sockaddr *)&addr, sizeof(addr)) != 0)
{
printf(" [IO Server] Failed to bind port!\n");
CoSocket_Close(listen_fd);
return NULL;
}
listen(listen_fd, 5);
printf(" [IO Server] Listening, waiting for client accept (cooperative yield)...\n");
int client_fd = CoSocket_Accept(listen_fd, NULL, NULL, 5000);
if (client_fd < 0)
{
printf(" [IO Server] Accept failed or timed out! (%d)\n", client_fd);
CoSocket_Close(listen_fd);
return NULL;
}
printf(" [IO Server] Client connected! Reading message (cooperative yield)...\n");
char buf[128] = {0};
int n = CoSocket_Read(client_fd, buf, sizeof(buf) - 1, 15000);
if (n > 0)
{
printf(" [IO Server] Received client message: \"%s\"\n", buf);
const char *echo_prefix = "ECHO: ";
char resp[256];
snprintf(resp, sizeof(resp), "%s%s", echo_prefix, buf);
printf(" [IO Server] Sending echo response: \"%s\"...\n", resp);
CoSocket_Write(client_fd, resp, strlen(resp), 5000);
}
else
{
printf(" [IO Server] Read failed or timed out! (n = %d)\n", n);
}
CoSocket_Close(client_fd);
CoSocket_Close(listen_fd);
printf(" [IO Server] Server task completed successfully, exiting scheduler!\n");
CoScheduler_Exit(0);
return NULL;
}
static void *io_client_task(void *arg)
{
/* Allow server to start and listen first */
CoTask_Wait(.ms = 50);
printf(" [IO Client] Connecting to 127.0.0.1:%d (cooperative yield)...\n", TEST_IO_PORT);
int client_fd = CoSocket_Create(AF_INET, SOCK_STREAM, 0);
if (client_fd < 0)
{
printf(" [IO Client] Failed to create client socket!\n");
return NULL;
}
struct sockaddr_in addr;
memset(&addr, 0, sizeof(addr));
addr.sin_family = AF_INET;
addr.sin_port = htons(TEST_IO_PORT);
addr.sin_addr.s_addr = inet_addr("127.0.0.1");
int ret = CoSocket_Connect(client_fd, &addr, sizeof(addr), 5000);
if (ret != 0)
{
printf(" [IO Client] Connect failed!\n");
CoSocket_Close(client_fd);
return NULL;
}
const char *msg = "Hello High-Throughput Coroutine IO!";
printf(" [IO Client] Connected! Sending message: \"%s\"...\n", msg);
CoTask_Wait(.ms = 10000);
CoSocket_Write(client_fd, msg, strlen(msg), 5000);
char resp[256] = {0};
printf(" [IO Client] Waiting for echo response (cooperative yield)...\n");
int n = CoSocket_Read(client_fd, resp, sizeof(resp) - 1, 5000);
if (n > 0)
{
printf(" [IO Client] Received Echo response: \"%s\"!\n", resp);
}
else
{
printf(" [IO Client] Read response failed or timed out! (%d)\n", n);
}
CoSocket_Close(client_fd);
printf(" [IO Client] Client task finished!\n");
return NULL;
}
static void test_io(void)
{
printf("=== [Test Asynchronous Socket I/O Multiplexing] ===\n");
CoScheduler_Init(0, GetTimerUsec, 1000);
CoIo_AttachScheduler(0);
CoTask_Create(io_server_task);
CoTask_Create(io_client_task);
printf("[Main] Starting single-threaded scheduler with cooperative I/O Reactor...\n");
CoScheduler_Start(0);
CoIo_DetachScheduler(0);
printf("=== [Test Asynchronous Socket I/O] Completed! ===\n\n");
}
static void test_base(void)
{
test_manual();
test_shared();
}
/************************************************************************************/
/************************************* Command ************************************/
/************************************************************************************/
static void usage(void)
{
printf(
"Usage: coroutine [opt] [arg] ...\n"
"\n"
"options:\n"
" -e <execute> Specifies the test mode to execute:\n"
" <manual> Test manual point-to-point stepping (libco style)\n"
" <shared> Test shared stack (copy-on-suspend)\n"
" <single> Test single scheduler automatic mode\n"
" <multi> Test multi-scheduler multi-threaded mode\n"
" <hook> Test low-power hook comparison (CPU load benchmark)\n"
" <io> Test high-throughput asynchronous socket I/O\n"
" <base> Test base default functions (manual & shared)\n"
" <ut> Unit test\n"
" -h Print help\n"
" -v Print version\n"
" -u [<period>] Unit test period, unit ms, the default is 1000ms\n"
"\n"
);
}
static int test(int argc, char *argv[])
{
char *execute = NULL;
int ut_period = 1000;
/* Initialize default allocator for tests */
Coroutine_Init(.malloc = malloc, .free = free);
/* reset getopt */
command_opt_init();
while (1)
{
int opt = command_getopt(argc, argv, "e:hvu::");
if (opt == -1) break;
switch (opt)
{
// Add others opt here
case 'u' :
if (command_optarg) ut_period = atoi(command_optarg);
break;
case 'e' :
execute = command_optarg;
break;
case 'v' :
printf("coroutine version %d.%d.%d\r\n", COROUTINE_V_MAJOR, COROUTINE_V_MINOR, COROUTINE_V_PATCH);
return 0;
case '?':
printf("Unknown option `%c`\r\n", command_optopt);
return -1;
case 'h' :
default:
usage();
return 0;
}
}
if (execute)
{
if (!strcmp(execute, "manual"))
{
test_manual();
}
else if (!strcmp(execute, "shared"))
{
test_shared();
}
else if (!strcmp(execute, "single"))
{
test_single();
}
else if (!strcmp(execute, "multi"))
{
test_multi();
}
else if (!strcmp(execute, "hook"))
{
test_hook();
}
else if (!strcmp(execute, "io"))
{
test_io();
}
else if (!strcmp(execute, "base"))
{
test_base();
}
else if (!strcmp(execute, "ut"))
{
#if defined(TEST_TARGET_coroutine)
while (1)
{
unitt_task();
usleep(1000 * ut_period);
}
#else
printf("create task %d\r\n", task_create(ut_period, unitt_task));
#endif
}
else
{
printf("Unknown execute option: %s\n", execute);
usage();
return -1;
}
}
else
{
test_base();
}
#if 0
if (1 == command_optind) // no opt
{
}
for (int index = command_optind; index < argc; index++)
{
if (!strcmp(argv[index], "base"))
{
test_base();
}
}
if (1 == argc)
{
test_base();
}
#endif
return 0;
}
/************************************************************************************/
/************************************ Test entry ************************************/
/************************************************************************************/
#if defined(TEST_TARGET_coroutine)
int main(int argc, char *argv[])
{
return test(argc, argv);
}
#else
void test_coroutine(void)
{
command_export("coroutine", test);
}
init_export_app(test_coroutine);
#endif