├── golang_runtime ├── .DS_Store ├── Dockerfile ├── Makefile ├── coroutine.c ├── coroutine.h ├── coroutines_queue.c ├── coroutines_queue.h ├── main.c ├── runtime.c ├── runtime.h ├── scheduler.c ├── scheduler.h ├── thread_pool.c ├── thread_pool.h └── utils.h └── synchronous_tcp_server ├── Dockerfile ├── Makefile └── main.c /golang_runtime/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/VladimirBalun/system_programming/54368c2d7517a772e82f144750c8dd92272ed6c8/golang_runtime/.DS_Store -------------------------------------------------------------------------------- /golang_runtime/Dockerfile: -------------------------------------------------------------------------------- 1 | #FROM gcc:latest 2 | # 3 | #COPY . /build 4 | #WORKDIR /build/ 5 | # 6 | #RUN make all 7 | #CMD ["./golang_runtime"] 8 | 9 | FROM ubuntu:latest 10 | 11 | RUN apt-get update && apt-get install -y valgrind && apt-get install -y software-properties-common && apt-get install -y gcc && apt-get install -y gdb && apt-get install -y vim && apt-get install -y build-essential 12 | 13 | COPY . /build 14 | WORKDIR /build/ 15 | 16 | RUN make all 17 | CMD ["./golang_runtime"] 18 | #CMD ["valgrind", "--leak-check=yes", "./golang_runtime"] 19 | #CMD ["valgrind", "--tool=memcheck", "./golang_runtime"] 20 | 21 | #CMD ["valgrind", "--tool=helgrind", "--max-stackframe=10540336", "./golang_runtime"] 22 | 23 | #CMD ["valgrind", "--tool=drd", "--read-var-info=yes", "./golang_runtime"] // data_race 24 | #CMD ["valgrind", "--tool=drd", "--exclusive-threshold=10", "./golang_runtime"] 25 | -------------------------------------------------------------------------------- /golang_runtime/Makefile: -------------------------------------------------------------------------------- 1 | PROGRAM_NAME = golang_runtime 2 | LD_FLAGS = -lpthread 3 | GCC_FLAGS = -std=c99 -Wextra -Werror -Wall -Wno-gnu-folding-constant 4 | 5 | all: scheduler.c coroutine.c coroutines_queue.c thread_pool.c runtime.c main.c 6 | gcc $(GCC_FLAGS) $(LD_FLAGS) scheduler.c coroutine.c coroutines_queue.c thread_pool.c runtime.c main.c -o $(PROGRAM_NAME) 7 | 8 | clean: 9 | rm $(PROGRAM_NAME) -------------------------------------------------------------------------------- /golang_runtime/coroutine.c: -------------------------------------------------------------------------------- 1 | #include "coroutine.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "utils.h" 9 | #include "scheduler.h" 10 | 11 | #define STACK_SIZE (1 << 20) * 2 12 | 13 | typedef struct coroutine_t { 14 | scheduler_t* scheduler; 15 | ucontext_t* context; 16 | coroutine_status_t status; 17 | } coroutine_t; 18 | 19 | static ucontext_t* __create_context(); 20 | static void __coroutine_trampline(coroutine_t*, coroutine_fn_t); 21 | 22 | coroutine_t* coroutine_init(coroutine_fn_t coroutine_fn) 23 | { 24 | ucontext_t* context = __create_context(); 25 | if (!context) { 26 | fprintf(stderr, "[%s:%d] failed to create coroutine context\n", __FILE__, __LINE__); 27 | return NULL; 28 | } 29 | 30 | coroutine_t* coroutine = malloc(sizeof(coroutine_t)); 31 | if (!coroutine) { 32 | fprintf(stderr, "[%s:%d] failed to allocate memory for coroutine\n", __FILE__, __LINE__); 33 | return NULL; 34 | } 35 | 36 | coroutine->scheduler = NULL; 37 | coroutine->context = context; 38 | coroutine->status = STATUS_RUNNABLE; 39 | makecontext(coroutine->context, (void (*)())__coroutine_trampline, 2, coroutine, coroutine_fn); 40 | 41 | return coroutine; 42 | } 43 | 44 | void coroutine_set_scheduler(coroutine_t* coroutine, scheduler_t* scheduler) 45 | { 46 | if (!coroutine) { 47 | fprintf(stderr, "[%s:%d] incorrect coroutine\n", __FILE__, __LINE__); 48 | return; 49 | } 50 | 51 | coroutine->scheduler = scheduler; 52 | } 53 | 54 | scheduler_t* coroutine_get_scheduler(coroutine_t* coroutine) 55 | { 56 | if (!coroutine) { 57 | fprintf(stderr, "[%s:%d] incorrect coroutine\n", __FILE__, __LINE__); 58 | return NULL; 59 | } 60 | 61 | return coroutine->scheduler; 62 | } 63 | 64 | coroutine_t* coroutine_make_current_context() 65 | { 66 | ucontext_t* context = __create_context(); 67 | if (!context) { 68 | fprintf(stderr, "[%s:%d] failed to create coroutine context\n", __FILE__, __LINE__); 69 | return NULL; 70 | } 71 | 72 | if (getcontext(context) < 0) { 73 | fprintf(stderr, "[%s:%d] failed to initialize context\n", __FILE__, __LINE__); 74 | return NULL; 75 | } 76 | 77 | coroutine_t* coroutine = malloc(sizeof(coroutine_t)); 78 | if (!coroutine) { 79 | fprintf(stderr, "[%s:%d] failed to allocate memory for coroutine\n", __FILE__, __LINE__); 80 | return NULL; 81 | } 82 | 83 | coroutine->scheduler = NULL; 84 | coroutine->context = context; 85 | coroutine->status = STATUS_RUNNABLE; 86 | return coroutine; 87 | } 88 | 89 | coroutine_status_t coroutine_status(coroutine_t* coroutine) 90 | { 91 | if (!coroutine) 92 | return STATUS_STOPPED; 93 | 94 | return coroutine->status; 95 | } 96 | 97 | void coroutine_switch(coroutine_t* from_coroutine, coroutine_t* to_coroutine) 98 | { 99 | if (!from_coroutine || !to_coroutine) { 100 | fprintf(stderr, "[%s:%d] incorrect coroutines\n", __FILE__, __LINE__); 101 | return; 102 | } 103 | 104 | if (from_coroutine->status != STATUS_STOPPED) 105 | from_coroutine->status = STATUS_RUNNABLE; 106 | 107 | to_coroutine->status = STATUS_RUNNING; 108 | if (swapcontext(from_coroutine->context, to_coroutine->context) < 0) 109 | fprintf(stderr, "[%s:%d] failed to swap context\n", __FILE__, __LINE__); 110 | } 111 | 112 | void coroutine_stop(coroutine_t* coroutine) 113 | { 114 | if (!coroutine) { 115 | fprintf(stderr, "[%s:%d] incorrect coroutine\n", __FILE__, __LINE__); 116 | return; 117 | } 118 | 119 | coroutine->status = STATUS_STOPPED; 120 | } 121 | 122 | void coroutine_destroy(coroutine_t** coroutine) 123 | { 124 | if (!coroutine || !(*coroutine)) { 125 | fprintf(stderr, "[%s:%d] incorrect coroutine\n", __FILE__, __LINE__); 126 | return; 127 | } 128 | 129 | SAFE_DELETE((*coroutine)->context->uc_stack.ss_sp); 130 | SAFE_DELETE((*coroutine)->context); 131 | SAFE_DELETE(*coroutine); 132 | } 133 | 134 | static ucontext_t* __create_context() 135 | { 136 | void* stack = malloc(STACK_SIZE); 137 | if (!stack) { 138 | fprintf(stderr, "[%s:%d] failed to allocate memory for stack\n", __FILE__, __LINE__); 139 | return NULL; 140 | } 141 | 142 | ucontext_t* context = malloc(sizeof(ucontext_t)); 143 | if (!context) { 144 | fprintf(stderr, "[%s:%d] failed to allocate memory for context\n", __FILE__, __LINE__); 145 | free(stack); 146 | return NULL; 147 | } 148 | 149 | if (getcontext(context) < 0) { 150 | fprintf(stderr, "[%s:%d] failed to initialize context\n", __FILE__, __LINE__); 151 | free(stack); 152 | free(context); 153 | return NULL; 154 | } 155 | 156 | context->uc_stack.ss_sp = stack; 157 | context->uc_stack.ss_size = STACK_SIZE; 158 | context->uc_stack.ss_flags = 0; 159 | 160 | return context; 161 | } 162 | 163 | static void __coroutine_trampline(coroutine_t* coroutine, coroutine_fn_t function) 164 | { 165 | if (!coroutine) { 166 | fprintf(stderr, "[%s:%d] incorrect coroutine\n", __FILE__, __LINE__); 167 | return; 168 | } 169 | 170 | if (!function) { 171 | fprintf(stderr, "[%s:%d] incorrect function\n", __FILE__, __LINE__); 172 | return; 173 | } 174 | 175 | function(coroutine); 176 | 177 | coroutine_stop(coroutine); 178 | coroutine_switch(coroutine, scheduler_get_scheduling_coroutine(coroutine->scheduler)); 179 | } -------------------------------------------------------------------------------- /golang_runtime/coroutine.h: -------------------------------------------------------------------------------- 1 | #ifndef __COROUTINE_H__ 2 | #define __COROUTINE_H__ 3 | 4 | typedef struct scheduler_t scheduler_t; 5 | typedef struct coroutine_t coroutine_t; 6 | 7 | typedef void (*coroutine_fn_t)(coroutine_t*); 8 | 9 | typedef enum coroutine_status_t { 10 | STATUS_RUNNING, 11 | STATUS_RUNNABLE, 12 | STATUS_STOPPED, 13 | } coroutine_status_t; 14 | 15 | coroutine_t* coroutine_make_current_context(); 16 | coroutine_t* coroutine_init(coroutine_fn_t); 17 | coroutine_status_t coroutine_status(coroutine_t*); 18 | void coroutine_switch(coroutine_t*, coroutine_t*); 19 | void coroutine_stop(coroutine_t*); 20 | void coroutine_destroy(coroutine_t**); 21 | 22 | void coroutine_set_scheduler(coroutine_t*, scheduler_t*); 23 | scheduler_t* coroutine_get_scheduler(coroutine_t*); 24 | 25 | #endif // __COROUTINE_H__ -------------------------------------------------------------------------------- /golang_runtime/coroutines_queue.c: -------------------------------------------------------------------------------- 1 | #include "coroutines_queue.h" 2 | 3 | #include "utils.h" 4 | #include "coroutine.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | typedef struct node_t node_t; 11 | typedef struct node_t { 12 | coroutine_t* coroutine; 13 | node_t* next; 14 | } node_t; 15 | 16 | typedef struct coroutines_queue_t { 17 | node_t* head; 18 | node_t* tail; 19 | 20 | pthread_mutex_t mutex; 21 | pthread_cond_t not_empty; 22 | 23 | int waiting_number; 24 | int waiting_max_number; 25 | int need_to_wait; 26 | } coroutines_queue_t; 27 | 28 | coroutines_queue_t* coroutines_queue_init(int waiting_max_number) 29 | { 30 | coroutines_queue_t* queue = malloc(sizeof(coroutines_queue_t)); 31 | if (!queue) { 32 | fprintf(stderr, "[%s:%d] failed to allocate memory for queue\n", __FILE__, __LINE__); 33 | return NULL; 34 | } 35 | 36 | if (pthread_mutex_init(&queue->mutex, NULL) != 0) { 37 | free(queue); 38 | fprintf(stderr, "[%s:%d] failed to init mutex for queue\n", __FILE__, __LINE__); 39 | return NULL; 40 | } 41 | 42 | if (pthread_cond_init(&queue->not_empty, NULL) != 0) { 43 | pthread_mutex_destroy(&queue->mutex); 44 | free(queue); 45 | fprintf(stderr, "[%s:%d] failed to init cond for queue\n", __FILE__, __LINE__); 46 | return NULL; 47 | } 48 | 49 | queue->head = NULL; 50 | queue->tail = NULL; 51 | queue->waiting_number = 0; 52 | queue->waiting_max_number = waiting_max_number; 53 | queue->need_to_wait = TRUE; 54 | return queue; 55 | } 56 | 57 | void coroutines_queue_push(coroutines_queue_t* queue, coroutine_t* coroutine) 58 | { 59 | if (!queue) { 60 | fprintf(stderr, "[%s:%d] incorrect queue\n", __FILE__, __LINE__); 61 | return; 62 | } 63 | 64 | if (!coroutine) { 65 | fprintf(stderr, "[%s:%d] incorrect coroutine\n", __FILE__, __LINE__); 66 | return; 67 | } 68 | 69 | node_t* node = malloc(sizeof(node_t)); 70 | if (!node) { 71 | fprintf(stderr, "[%s:%d] failed to allocate memory for node\n", __FILE__, __LINE__); 72 | return; 73 | } 74 | 75 | node->coroutine = coroutine; 76 | node->next = NULL; 77 | 78 | pthread_mutex_lock(&queue->mutex); 79 | 80 | if (!queue->head) { 81 | queue->head = node; 82 | queue->tail = node; 83 | } else { 84 | queue->tail->next = node; 85 | queue->tail = node; 86 | } 87 | 88 | pthread_mutex_unlock(&queue->mutex); 89 | pthread_cond_signal(&queue->not_empty); 90 | } 91 | 92 | coroutine_t* coroutines_queue_pop(coroutines_queue_t* queue) 93 | { 94 | if (!queue) { 95 | fprintf(stderr, "[%s:%d] incorrect queue\n", __FILE__, __LINE__); 96 | return NULL; 97 | } 98 | 99 | pthread_mutex_lock(&queue->mutex); 100 | 101 | while (!queue->head && queue->need_to_wait == TRUE) { 102 | ++queue->waiting_number; 103 | if (queue->waiting_number == queue->waiting_max_number) { 104 | queue->need_to_wait = FALSE; 105 | pthread_cond_broadcast(&queue->not_empty); 106 | break; 107 | } 108 | 109 | pthread_cond_wait(&queue->not_empty, &queue->mutex); 110 | --queue->waiting_number; 111 | } 112 | 113 | if (!queue->head) { 114 | pthread_mutex_unlock(&queue->mutex); 115 | return NULL; 116 | } 117 | 118 | node_t* temp_node = queue->head; 119 | coroutine_t* coroutine = temp_node->coroutine; 120 | queue->head = queue->head->next; 121 | if (!queue->head) 122 | queue->tail = NULL; 123 | 124 | pthread_mutex_unlock(&queue->mutex); 125 | 126 | SAFE_DELETE(temp_node); 127 | return coroutine; 128 | } 129 | 130 | void coroutines_queue_destroy(coroutines_queue_t** queue) 131 | { 132 | if (!queue || !(*queue)) { 133 | fprintf(stderr, "[%s:%d] incorrect queue\n", __FILE__, __LINE__); 134 | return; 135 | } 136 | 137 | node_t* iterator = (*queue)->head; 138 | while (iterator) { 139 | node_t* temp = iterator; 140 | iterator = iterator->next; 141 | coroutine_destroy(&temp->coroutine); 142 | SAFE_DELETE(temp); 143 | } 144 | 145 | pthread_cond_destroy(&(*queue)->not_empty); 146 | pthread_mutex_destroy(&(*queue)->mutex); 147 | SAFE_DELETE(*queue); 148 | } -------------------------------------------------------------------------------- /golang_runtime/coroutines_queue.h: -------------------------------------------------------------------------------- 1 | #ifndef __COROUTINES_QUEUE_H__ 2 | #define __COROUTINES_QUEUE_H__ 3 | 4 | typedef struct coroutine_t coroutine_t; 5 | typedef struct coroutines_queue_t coroutines_queue_t; 6 | 7 | coroutines_queue_t* coroutines_queue_init(int); 8 | void coroutines_queue_push(coroutines_queue_t*, coroutine_t*); 9 | coroutine_t* coroutines_queue_pop(coroutines_queue_t*); 10 | void coroutines_queue_destroy(coroutines_queue_t**); 11 | 12 | #endif // __COROUTINES_QUEUE_H__ -------------------------------------------------------------------------------- /golang_runtime/main.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "runtime.h" 10 | #include "coroutine.h" 11 | #include "scheduler.h" 12 | #include "coroutines_queue.h" 13 | #include "thread_pool.h" 14 | 15 | #define GO_RUNTIME(function) \ 16 | printf("[%ld] - runtime started\n", syscall(__NR_gettid)); \ 17 | runtime_t* runtime = runtime_init(4); \ 18 | runtime_run(runtime, (function)); \ 19 | runtime_destroy(&runtime); \ 20 | printf("[%ld] - runtime finished\n", syscall(__NR_gettid)); 21 | 22 | #define GO(function) \ 23 | scheduler_spawn_coroutine((coroutine_get_scheduler(coroutine)), (function)) 24 | 25 | #define PRINT(text) \ 26 | printf("[%ld] - %s", syscall(__NR_gettid), (text)); \ 27 | scheduler_park_coroutine((coroutine_get_scheduler(coroutine))) 28 | 29 | void coroutine5(coroutine_t* coroutine) 30 | { 31 | if (!coroutine) { 32 | fprintf(stderr, "[%s:%d] incorrect coroutine\n", __FILE__, __LINE__); 33 | return; 34 | } 35 | 36 | PRINT("coroutine5 started\n"); 37 | PRINT("coroutine5 middleware\n"); 38 | PRINT("coroutine5 finished\n"); 39 | } 40 | 41 | void coroutine4(coroutine_t* coroutine) 42 | { 43 | if (!coroutine) { 44 | fprintf(stderr, "[%s:%d] incorrect coroutine\n", __FILE__, __LINE__); 45 | return; 46 | } 47 | 48 | PRINT("coroutine4 started\n"); 49 | PRINT("coroutine4 middleware\n"); 50 | PRINT("coroutine4 finished\n"); 51 | } 52 | 53 | void coroutine3(coroutine_t* coroutine) 54 | { 55 | if (!coroutine) { 56 | fprintf(stderr, "[%s:%d] incorrect coroutine\n", __FILE__, __LINE__); 57 | return; 58 | } 59 | 60 | PRINT("coroutine3 started\n"); 61 | PRINT("coroutine3 middleware\n"); 62 | PRINT("coroutine3 finished\n"); 63 | } 64 | 65 | void coroutine2(coroutine_t* coroutine) 66 | { 67 | if (!coroutine) { 68 | fprintf(stderr, "[%s:%d] incorrect coroutine\n", __FILE__, __LINE__); 69 | return; 70 | } 71 | 72 | GO(coroutine3); 73 | GO(coroutine4); 74 | GO(coroutine5); 75 | 76 | PRINT("coroutine2 started\n"); 77 | PRINT("coroutine2 finished\n"); 78 | } 79 | 80 | void coroutine1(coroutine_t* coroutine) 81 | { 82 | if (!coroutine) { 83 | fprintf(stderr, "[%s:%d] incorrect coroutine\n", __FILE__, __LINE__); 84 | return; 85 | } 86 | 87 | PRINT("coroutine1 started\n"); 88 | PRINT("coroutine1 finished\n"); 89 | } 90 | 91 | void main_coroutine(coroutine_t* coroutine) 92 | { 93 | if (!coroutine) { 94 | fprintf(stderr, "[%s:%d] incorrect coroutine\n", __FILE__, __LINE__); 95 | return; 96 | } 97 | 98 | PRINT("main coroutine started\n"); 99 | GO(coroutine1); 100 | PRINT("main middleware\n"); 101 | GO(coroutine2); 102 | PRINT("main coroutine finished\n"); 103 | } 104 | 105 | int main() 106 | { 107 | GO_RUNTIME(main_coroutine); 108 | return EXIT_SUCCESS; 109 | } -------------------------------------------------------------------------------- /golang_runtime/runtime.c: -------------------------------------------------------------------------------- 1 | #include "runtime.h" 2 | 3 | #include "utils.h" 4 | #include "scheduler.h" 5 | #include "thread_pool.h" 6 | #include "coroutines_queue.h" 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #define MIN_THREADS_NUMBER 1 13 | #define MAX_THREADS_NUMBER 64 14 | #define DEFAULT_THREADS_NUMBER 4 15 | 16 | typedef struct runtime_t { 17 | thread_pool_t* thread_pool; 18 | coroutines_queue_t* global_queue; 19 | } runtime_t; 20 | 21 | static void* __thread_local_schedule(void* thread_parameter) 22 | { 23 | if (!thread_parameter) { 24 | fprintf(stderr, "[%s:%d] incorrect thread_parameter\n", __FILE__, __LINE__); 25 | pthread_exit(0); 26 | } 27 | 28 | scheduler_t* scheduler = (scheduler_t*)(thread_parameter); 29 | scheduler_run(scheduler); 30 | scheduler_destroy(scheduler); 31 | pthread_exit(0); 32 | } 33 | 34 | runtime_t* runtime_init(int threads_number) 35 | { 36 | if (threads_number < MIN_THREADS_NUMBER || threads_number > MAX_THREADS_NUMBER) 37 | threads_number = DEFAULT_THREADS_NUMBER; 38 | 39 | coroutines_queue_t* queue = coroutines_queue_init(threads_number); 40 | if (!queue) { 41 | fprintf(stderr, "[%s:%d] failed to create coroutines queue\n", __FILE__, __LINE__); 42 | return NULL; 43 | } 44 | 45 | thread_pool_t* thread_pool = thread_pool_init(threads_number); 46 | if (!thread_pool) { 47 | coroutines_queue_destroy(&queue); 48 | fprintf(stderr, "[%s:%d] failed to create thread_pool\n", __FILE__, __LINE__); 49 | return NULL; 50 | } 51 | 52 | runtime_t* runtime = malloc(sizeof(runtime_t)); 53 | if (!runtime) { 54 | coroutines_queue_destroy(&queue); 55 | thread_pool_destroy(&thread_pool); 56 | fprintf(stderr, "[%s:%d] failed to allocate memory for runtime\n", __FILE__, __LINE__); 57 | return NULL; 58 | } 59 | 60 | runtime->thread_pool = thread_pool; 61 | runtime->global_queue = queue; 62 | return runtime; 63 | } 64 | 65 | void runtime_run(runtime_t* runtime, coroutine_fn_t main_coroutine_fn) 66 | { 67 | if (!runtime) { 68 | fprintf(stderr, "[%s:%d] incorrect runtime\n", __FILE__, __LINE__); 69 | return; 70 | } 71 | 72 | if (!main_coroutine_fn) { 73 | fprintf(stderr, "[%s:%d] incorrect main_coroutine_fn\n", __FILE__, __LINE__); 74 | return; 75 | } 76 | 77 | int threads_number = thread_pool_threads_number(runtime->thread_pool); 78 | for (int thread_idx = 0; thread_idx < threads_number; ++thread_idx) { 79 | scheduler_t* scheduler = scheduler_init(runtime->global_queue); 80 | if (!scheduler) { 81 | fprintf(stderr, "[%s:%d] failed to create scheduler\n", __FILE__, __LINE__); 82 | continue; 83 | } 84 | 85 | if (thread_idx == threads_number - 1) 86 | scheduler_spawn_coroutine(scheduler, main_coroutine_fn); 87 | thread_pool_run_thread(runtime->thread_pool, __thread_local_schedule, scheduler); 88 | } 89 | 90 | thread_pool_wait_threads(runtime->thread_pool); 91 | } 92 | 93 | void runtime_destroy(runtime_t** runtime) 94 | { 95 | if (!runtime || !(*runtime)) { 96 | fprintf(stderr, "[%s:%d] incorrect runtime\n", __FILE__, __LINE__); 97 | return; 98 | } 99 | 100 | thread_pool_destroy(&(*runtime)->thread_pool); 101 | coroutines_queue_destroy(&(*runtime)->global_queue); 102 | SAFE_DELETE(*runtime); 103 | } -------------------------------------------------------------------------------- /golang_runtime/runtime.h: -------------------------------------------------------------------------------- 1 | #ifndef __RUNTIME_H__ 2 | #define __RUNTIME_H__ 3 | 4 | typedef struct runtime_t runtime_t; 5 | typedef struct coroutine_t coroutine_t; 6 | 7 | typedef void (*coroutine_fn_t)(coroutine_t*); 8 | 9 | runtime_t* runtime_init(int); 10 | void runtime_run(runtime_t*, coroutine_fn_t); 11 | void runtime_destroy(runtime_t**); 12 | 13 | #endif // __RUNTIME_H__ -------------------------------------------------------------------------------- /golang_runtime/scheduler.c: -------------------------------------------------------------------------------- 1 | #include "scheduler.h" 2 | 3 | #include "utils.h" 4 | #include "coroutine.h" 5 | #include "coroutines_queue.h" 6 | 7 | #include 8 | #include 9 | 10 | typedef struct scheduler_t { 11 | coroutines_queue_t* global_queue; 12 | coroutine_t* running_coroutine; 13 | coroutine_t* scheduling_coroutine; 14 | } scheduler_t; 15 | 16 | static void __schedule_coroutines(scheduler_t* scheduler) 17 | { 18 | if (!scheduler) { 19 | fprintf(stderr, "[%s:%d] incorrect scheduler\n", __FILE__, __LINE__); 20 | return; 21 | } 22 | 23 | coroutine_t* coroutine = coroutines_queue_pop(scheduler->global_queue); 24 | while (coroutine) { 25 | scheduler->running_coroutine = coroutine; 26 | coroutine_set_scheduler(coroutine, scheduler); 27 | coroutine_switch(scheduler->scheduling_coroutine, coroutine); 28 | 29 | if (coroutine_status(coroutine) == STATUS_STOPPED) 30 | coroutine_destroy(&coroutine); 31 | 32 | coroutine = coroutines_queue_pop(scheduler->global_queue); 33 | } 34 | } 35 | 36 | scheduler_t* scheduler_init(coroutines_queue_t* queue) 37 | { 38 | if (!queue) { 39 | fprintf(stderr, "[%s:%d] incorrect queue\n", __FILE__, __LINE__); 40 | return NULL; 41 | } 42 | 43 | scheduler_t* scheduler = malloc(sizeof(scheduler_t)); 44 | if (!scheduler) { 45 | fprintf(stderr, "[%s:%d] failed to allocate memory for scheduler\n", __FILE__, __LINE__); 46 | return NULL; 47 | } 48 | 49 | scheduler->global_queue = queue; 50 | scheduler->running_coroutine = NULL; 51 | scheduler->scheduling_coroutine = NULL; 52 | return scheduler; 53 | } 54 | 55 | coroutine_t* scheduler_get_scheduling_coroutine(scheduler_t* scheduler) 56 | { 57 | if (!scheduler) { 58 | fprintf(stderr, "[%s:%d] incorrect scheduler\n", __FILE__, __LINE__); 59 | return NULL; 60 | } 61 | 62 | return scheduler->scheduling_coroutine; 63 | } 64 | 65 | void scheduler_run(scheduler_t* scheduler) 66 | { 67 | if (!scheduler) { 68 | fprintf(stderr, "[%s:%d] incorrect scheduler\n", __FILE__, __LINE__); 69 | return; 70 | } 71 | 72 | coroutine_t* current_coroutine = coroutine_make_current_context(); 73 | if (!current_coroutine) { 74 | fprintf(stderr, "[%s:%d] failed to make current coroutine\n", __FILE__, __LINE__); 75 | return; 76 | } 77 | 78 | scheduler->scheduling_coroutine = current_coroutine; 79 | __schedule_coroutines(scheduler); 80 | } 81 | 82 | void scheduler_spawn_coroutine(scheduler_t* scheduler, coroutine_fn_t function) 83 | { 84 | if (!scheduler) { 85 | fprintf(stderr, "[%s:%d] incorrect scheduler\n", __FILE__, __LINE__); 86 | return; 87 | } 88 | 89 | if (!function) { 90 | fprintf(stderr, "[%s:%d] incorrect function\n", __FILE__, __LINE__); 91 | return; 92 | } 93 | 94 | coroutine_t* coroutine = coroutine_init(function); 95 | if (!coroutine) { 96 | fprintf(stderr, "[%s:%d] failed to create coroutine\n", __FILE__, __LINE__); 97 | return; 98 | } 99 | 100 | coroutines_queue_push(scheduler->global_queue, coroutine); 101 | } 102 | 103 | void scheduler_park_coroutine(scheduler_t* scheduler) 104 | { 105 | if (!scheduler) { 106 | fprintf(stderr, "[%s:%d] incorrect scheduler\n", __FILE__, __LINE__); 107 | return; 108 | } 109 | 110 | coroutine_t* running_coroutine = scheduler->running_coroutine; 111 | scheduler->running_coroutine = NULL; 112 | 113 | coroutine_set_scheduler(running_coroutine, NULL); 114 | coroutines_queue_push(scheduler->global_queue, running_coroutine); 115 | coroutine_switch(running_coroutine, scheduler->scheduling_coroutine); 116 | } 117 | 118 | void scheduler_destroy(scheduler_t* scheduler) 119 | { 120 | if (!scheduler) { 121 | fprintf(stderr, "[%s:%d] incorrect scheduler\n", __FILE__, __LINE__); 122 | return; 123 | } 124 | 125 | coroutine_destroy(&scheduler->scheduling_coroutine); 126 | SAFE_DELETE(scheduler); 127 | } -------------------------------------------------------------------------------- /golang_runtime/scheduler.h: -------------------------------------------------------------------------------- 1 | #ifndef __SCHEDULER_H__ 2 | #define __SCHEDULER_H__ 3 | 4 | typedef struct coroutine_t coroutine_t; 5 | typedef struct scheduler_t scheduler_t; 6 | typedef struct coroutines_queue_t coroutines_queue_t; 7 | 8 | typedef void (*coroutine_fn_t)(coroutine_t*); 9 | 10 | scheduler_t* scheduler_init(coroutines_queue_t*); 11 | coroutine_t* scheduler_get_scheduling_coroutine(scheduler_t*); 12 | 13 | void scheduler_run(scheduler_t*); 14 | void scheduler_spawn_coroutine(scheduler_t*, coroutine_fn_t); 15 | void scheduler_park_coroutine(scheduler_t*); 16 | void scheduler_destroy(scheduler_t*); 17 | 18 | #endif // __SCHEDULER_H__ -------------------------------------------------------------------------------- /golang_runtime/thread_pool.c: -------------------------------------------------------------------------------- 1 | #include "thread_pool.h" 2 | 3 | #include "utils.h" 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | typedef struct thread_pool_t { 10 | pthread_t* threads; 11 | int last_thread_idx; 12 | int threads_number; 13 | } thread_pool_t; 14 | 15 | thread_pool_t* thread_pool_init(int threads_number) 16 | { 17 | thread_pool_t* thread_pool = malloc(sizeof(thread_pool_t)); 18 | if (!thread_pool) { 19 | fprintf(stderr, "[%s:%d] failed to allocate memory for thread_pool\n", __FILE__, __LINE__); 20 | return NULL; 21 | } 22 | 23 | pthread_t* threads = malloc(sizeof(pthread_t) * threads_number); 24 | if (!threads) { 25 | free(thread_pool); 26 | fprintf(stderr, "[%s:%d] failed to allocate memory for threads\n", __FILE__, __LINE__); 27 | return NULL; 28 | } 29 | 30 | thread_pool->threads = threads; 31 | thread_pool->last_thread_idx = 0; 32 | thread_pool->threads_number = threads_number; 33 | return thread_pool; 34 | } 35 | 36 | int thread_pool_threads_number(thread_pool_t* thread_pool) 37 | { 38 | if (!thread_pool) { 39 | fprintf(stderr, "[%s:%d] incorrect thread_pool\n", __FILE__, __LINE__); 40 | return 0; 41 | } 42 | 43 | return thread_pool->threads_number; 44 | } 45 | 46 | void thread_pool_run_thread(thread_pool_t* thread_pool, thread_fn_t thread_fn, void* parameter) 47 | { 48 | if (!thread_pool) { 49 | fprintf(stderr, "[%s:%d] incorrect thread_pool\n", __FILE__, __LINE__); 50 | return; 51 | } 52 | 53 | if (!thread_fn) { 54 | fprintf(stderr, "[%s:%d] incorrect thread_fn\n", __FILE__, __LINE__); 55 | return; 56 | } 57 | 58 | if (thread_pool->last_thread_idx == thread_pool->threads_number) { 59 | fprintf(stderr, "[%s:%d] threads number overflow\n", __FILE__, __LINE__); 60 | return; 61 | } 62 | 63 | int thread_idx = thread_pool->last_thread_idx++; 64 | if (pthread_create(&thread_pool->threads[thread_idx], NULL, thread_fn, parameter) != 0) 65 | fprintf(stderr, "[%s:%d] failed to create thread\n", __FILE__, __LINE__); 66 | } 67 | 68 | void thread_pool_wait_threads(thread_pool_t* thread_pool) 69 | { 70 | if (!thread_pool) { 71 | fprintf(stderr, "[%s:%d] incorrect thread_pool\n", __FILE__, __LINE__); 72 | return; 73 | } 74 | 75 | for (int idx = 0; idx < thread_pool->last_thread_idx; ++idx) { 76 | if (pthread_join(thread_pool->threads[idx], NULL) != 0) 77 | fprintf(stderr, "[%s:%d] failed to join thread\n", __FILE__, __LINE__); 78 | } 79 | } 80 | 81 | void thread_pool_destroy(thread_pool_t** thread_pool) 82 | { 83 | if (!thread_pool || !*thread_pool) { 84 | fprintf(stderr, "[%s:%d] incorrect thread_pool\n", __FILE__, __LINE__); 85 | return; 86 | } 87 | 88 | SAFE_DELETE((*thread_pool)->threads); 89 | SAFE_DELETE((*thread_pool)); 90 | } -------------------------------------------------------------------------------- /golang_runtime/thread_pool.h: -------------------------------------------------------------------------------- 1 | #ifndef __THREAD_POOL_H__ 2 | #define __THREAD_POOL_H__ 3 | 4 | typedef struct thread_pool_t thread_pool_t; 5 | 6 | typedef void* (*thread_fn_t)(void*); 7 | 8 | thread_pool_t* thread_pool_init(int); 9 | int thread_pool_threads_number(thread_pool_t*); 10 | void thread_pool_run_thread(thread_pool_t*, thread_fn_t, void*); 11 | void thread_pool_wait_threads(thread_pool_t*); 12 | void thread_pool_destroy(thread_pool_t**); 13 | 14 | #endif // __THREAD_POOL_H__ -------------------------------------------------------------------------------- /golang_runtime/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef __UTILS_H__ 2 | #define __UTILS_H__ 3 | 4 | #define TRUE 1 5 | #define FALSE 0 6 | 7 | #define SAFE_DELETE(pointer) \ 8 | free((pointer)); \ 9 | (pointer) = NULL; 10 | 11 | #endif // __UTILS_H__ -------------------------------------------------------------------------------- /synchronous_tcp_server/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM gcc:latest 2 | 3 | COPY . /build 4 | WORKDIR /build/ 5 | 6 | RUN make all 7 | CMD ["./tcp_server"] -------------------------------------------------------------------------------- /synchronous_tcp_server/Makefile: -------------------------------------------------------------------------------- 1 | PROGRAM_NAME = tcp_server 2 | GCC_FLAGS = -Wextra -Werror -Wall -Wno-gnu-folding-constant 3 | 4 | all: main.c 5 | gcc $(GCC_FLAGS) main.c -o $(PROGRAM_NAME) 6 | 7 | clean: 8 | rm $(PROGRAM_NAME) -------------------------------------------------------------------------------- /synchronous_tcp_server/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #define SERVER_PORT 32223 11 | #define CLIENT_QUEUE_SIZE 5 12 | #define CONNECTION_BUFFER_SIZE 4 << 10 13 | 14 | int main() 15 | { 16 | struct sockaddr_in server_address; 17 | server_address.sin_family = AF_INET; 18 | server_address.sin_addr.s_addr = htonl(INADDR_ANY); 19 | server_address.sin_port = htons(SERVER_PORT); 20 | 21 | int server_socket = socket(AF_INET, SOCK_STREAM, 0); 22 | if (server_socket < 0) { 23 | fprintf(stderr, "failed to create server socket\n"); 24 | return EXIT_FAILURE; 25 | } 26 | 27 | if (bind(server_socket, (struct sockaddr*)&server_address, sizeof(server_address)) < 0) { 28 | fprintf(stderr, "failed to bind server socket\n"); 29 | return EXIT_FAILURE; 30 | } 31 | 32 | if (listen(server_socket, CLIENT_QUEUE_SIZE) < 0) { 33 | fprintf(stderr, "failed to listen server socket\n"); 34 | return EXIT_FAILURE; 35 | } 36 | 37 | while (1) { 38 | struct sockaddr_in client_address; 39 | int address_size = sizeof(client_address); 40 | int client_socket = accept(server_socket, (struct sockaddr*)&client_address, (socklen_t*)&address_size); 41 | if (client_socket < 0) { 42 | fprintf(stderr, "failed to accept client socket\n"); 43 | continue; 44 | } 45 | 46 | char buffer[CONNECTION_BUFFER_SIZE]; 47 | 48 | while (1) { 49 | int read_bytes = read(client_socket, buffer, sizeof(buffer)); 50 | if (read_bytes < 0) { 51 | fprintf(stderr, "failed to read from client socket\n"); 52 | break; 53 | } 54 | 55 | const char* response = "Hello from TCP server\n"; 56 | int written_bytes = write(client_socket, response, strlen(response)); 57 | if (written_bytes < 0) { 58 | fprintf(stderr, "failed to read from client socket\n"); 59 | break; 60 | } 61 | } 62 | } 63 | 64 | return EXIT_SUCCESS; 65 | } --------------------------------------------------------------------------------