From aacb73aade6063b4965e2c89c35fdabd579a498a Mon Sep 17 00:00:00 2001 From: mbays Date: Sat, 8 May 2021 00:00:00 +0000 Subject: [PATCH] multi-thread --- Makefile | 4 +-- gemscgi.c | 3 -- main.c | 99 +++++++++++++++++++++++++++++++++++++++++++++++++++++---------- 3 files changed, 86 insertions(+), 20 deletions(-) diff --git a/Makefile b/Makefile index ab65494..505d0c2 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ CC=cc -CFLAGS=-Wall +CFLAGS=-Wall -g gemrepl: gemscgi.o main.o - $(CC) $(CFLAGS) -o $@ $^ + $(CC) $(CFLAGS) -lpthread -o $@ $^ %.o: %.c *.h $(CC) $(CFLAGS) -c $< diff --git a/gemscgi.c b/gemscgi.c index c46f239..a8de79e 100644 --- a/gemscgi.c +++ b/gemscgi.c @@ -164,9 +164,6 @@ void runSCGI(const char *socket_path, respond_cb respond, void *respond_object) Request_Info request_info = { }; if (parse_SCGI(s2, &request_info)) { - // TODO: catch SIGPIPE (which indicates that the client closed the - // connection), and do something sensible with it. - // Or have some better way for the server to indicate this. respond(respond_object, &request_info, s2); } } diff --git a/main.c b/main.c index 3699d2d..7967295 100644 --- a/main.c +++ b/main.c @@ -3,6 +3,7 @@ #include #include #include +#include #include #include #include @@ -24,6 +25,7 @@ typedef struct Child { bool exists; bool newborn; + pthread_mutex_t *mutex; // initialised if child->exists, maybe also if not char sess_id[SESSION_ID_LEN]; char owner[64]; uint64_t last_active; @@ -235,20 +237,25 @@ static Child *get_session(State *state, const Request_Info *request_info, int so if (request_info->path_info == NULL || strlen(request_info->path_info) <= 1) { Child *slot = NULL; + uint64_t last_active = UINT64_MAX; for (int i = 0; i < state->num_children; ++i) { Child *const c = &state->children[i]; + if (c->mutex != NULL && pthread_mutex_trylock(c->mutex) != 0) continue; if (c->exists) { - if (slot == NULL || (slot->exists - && slot->last_active > c->last_active)) { + if (last_active > c->last_active) { slot = c; + last_active = c->last_active; } - } else if (slot == NULL || slot->exists) slot = c; + } else if (slot == NULL || last_active < UINT64_MAX) slot = c; + if (c->mutex != NULL) pthread_mutex_unlock(c->mutex); } - if (slot == NULL || (slot->exists && state->num_children < state->max_children)) { + if (slot == NULL || (last_active < UINT64_MAX && state->num_children < state->max_children)) { slot = &state->children[state->num_children++]; } + Child *const child = slot; + if (child->mutex != NULL) pthread_mutex_lock(child->mutex); if (child->exists) { // TODO: would be nice to queue a regretful message for the owner @@ -269,9 +276,30 @@ static Child *get_session(State *state, const Request_Info *request_info, int so if (!spawn(state->command, state->args, request_info->query_string_decoded, child, socket)) { put("40 Spawn failure.\r\n"); + if (child->mutex != NULL) pthread_mutex_unlock(child->mutex); return NULL; } + if (child->mutex == NULL) { + child->mutex = malloc(sizeof(pthread_mutex_t)); + if (child->mutex == NULL) { + put("40 Spawn failure (malloc).\r\n"); + return NULL; + } + + if (pthread_mutex_init(child->mutex, NULL) != 0) { + put("40 Spawn failure (mutex_init).\r\n"); + free(child->mutex); + child->mutex = NULL; + return NULL; + } + + // Note: we never destroy the mutex, because we never know that it + // would be safe to do so. + + pthread_mutex_lock(child->mutex); + } + child->exists = true; child->newborn = true; set_child_last_active(child); @@ -281,6 +309,7 @@ static Child *get_session(State *state, const Request_Info *request_info, int so put("/"); putn(child->sess_id, SESSION_ID_LEN); put("\r\n"); + pthread_mutex_unlock(child->mutex); return NULL; } @@ -289,6 +318,7 @@ static Child *get_session(State *state, const Request_Info *request_info, int so bool found = false; for (int i = 0; i < state->num_children; ++i) { Child *const c = &state->children[i]; + if (c->mutex != NULL && pthread_mutex_trylock(c->mutex) != 0) continue; if (c->exists && 0 == strncmp(c->owner, request_info->tls_client_hash, 64)) { if (!found) { @@ -301,6 +331,7 @@ static Child *get_session(State *state, const Request_Info *request_info, int so putn(c->sess_id, SESSION_ID_LEN); put(" Resume session\r\n"); } + if (c->mutex != NULL) pthread_mutex_unlock(c->mutex); } if (!found) put("No sessions found.\r\n"); return NULL; @@ -315,16 +346,18 @@ static Child *get_session(State *state, const Request_Info *request_info, int so const char *sess_id = request_info->path_info + 1; /* Find child with this sess_id. - * For simplicity, we use a static array of children rather than - * allocating dynamically, and don't sort. This could be optimised. */ + * For simplicity, in particular for the mutex handling, we use a static + * array of children rather than allocating dynamically, and don't sort. + * This could be optimised. */ Child *child = NULL; - for (int i = 0; i < state->num_children; ++i) { + for (int i = 0; child == NULL && i < state->num_children; ++i) { Child *const c = &state->children[i]; + if (c->mutex != NULL && pthread_mutex_trylock(c->mutex) != 0) continue; if (c->exists && 0 == strncmp(c->sess_id, sess_id, SESSION_ID_LEN)) { child = c; - break; } + if (c->mutex != NULL) pthread_mutex_unlock(c->mutex); } if (child == NULL) { @@ -334,7 +367,11 @@ static Child *get_session(State *state, const Request_Info *request_info, int so return NULL; } - if (0 != strncmp(child->owner, request_info->tls_client_hash, 64)) { + pthread_mutex_lock(child->mutex); + const char* owner = child->owner; + pthread_mutex_unlock(child->mutex); + + if (0 != strncmp(owner, request_info->tls_client_hash, 64)) { put("61 Wrong certificate for session.\r\n"); return NULL; } @@ -342,7 +379,7 @@ static Child *get_session(State *state, const Request_Info *request_info, int so return child; } -void do_command(const State* state, Child *child, const char* q, int socket) { +static void do_command(const State* state, Child *child, const char* q, int socket) { if (*q == '!') { ++q; if (*q == '?') { @@ -424,10 +461,9 @@ void do_command(const State* state, Child *child, const char* q, int socket) { --qlen; newline = false; } - signal(SIGPIPE, SIG_IGN); + bool succ = (write(child->in, q, qlen) == qlen && (!newline || write(child->in, "\n", 1) == 1)); - signal(SIGPIPE, SIG_DFL); if (!succ) { put("[gemrepl: error when writing to child]\r\n"); } @@ -463,6 +499,28 @@ void do_command(const State* state, Child *child, const char* q, int socket) { } } +// Wrap arguments of do_command into a single struct, for use with +// pthread_create. +typedef struct Do_Command_Arg { + const State* state; + Child *child; + const char* q; + int socket; +} Do_Command_Arg; + +static void *do_command_thread(void *object) +{ + Do_Command_Arg *arg = (Do_Command_Arg *)object; + + pthread_mutex_lock(arg->child->mutex); + do_command(arg->state, arg->child, arg->q, arg->socket); + pthread_mutex_unlock(arg->child->mutex); + + close(arg->socket); + free(arg); + return NULL; +} + void respond(void *object, const Request_Info *request_info, int socket) { State *state = (State *)object; @@ -474,9 +532,18 @@ void respond(void *object, const Request_Info *request_info, int socket) return; } - // TODO: new thread for this - do_command(state, child, request_info->query_string_decoded, socket); - close(socket); + Do_Command_Arg *arg = malloc(sizeof(Do_Command_Arg)); + if (arg == NULL) { + close(socket); + return; + } + arg->state = state; + arg->child = child; + arg->q = request_info->query_string_decoded; + arg->socket = socket; + + pthread_t tid; + pthread_create(&tid, NULL, do_command_thread, arg); } /* How long in ms to wait for child to output something */ @@ -613,6 +680,8 @@ int main(int argc, char **argv) act.sa_handler = cleanup; sigaction(SIGTERM, &act, NULL); sigaction(SIGINT, &act, NULL); + act.sa_handler = SIG_IGN; + sigaction(SIGPIPE, &act, NULL); runSCGI(socketname, respond, &state); } -- 2.11.4.GIT