@@ -2431,7 +2431,9 @@ struct mg_context {
24312431
24322432 /* Thread related */
24332433 stop_flag_t stop_flag; /* Should we stop event loop */
2434+ stop_flag_t stop_accepting; /* Should newly accepted sockets be rejected */
24342435 pthread_mutex_t thread_mutex; /* Protects client_socks or queue */
2436+ pthread_mutex_t socket_mutex; /* Synchronizes worker socket scan and close */
24352437
24362438 pthread_t masterthreadid; /* The master thread ID */
24372439 unsigned int cfg_max_worker_threads; /* How many worker-threads we are
@@ -18405,6 +18407,9 @@ close_connection(struct mg_connection *conn)
1840518407 conn->ssl = NULL;
1840618408 }
1840718409#endif
18410+ if (conn->phys_ctx->context_type == CONTEXT_SERVER) {
18411+ (void)pthread_mutex_lock(&conn->phys_ctx->socket_mutex);
18412+ }
1840818413 if (conn->client.sock != INVALID_SOCKET) {
1840918414#if defined(__ZEPHYR__)
1841018415 closesocket(conn->client.sock);
@@ -18413,6 +18418,9 @@ close_connection(struct mg_connection *conn)
1841318418#endif
1841418419 conn->client.sock = INVALID_SOCKET;
1841518420 }
18421+ if (conn->phys_ctx->context_type == CONTEXT_SERVER) {
18422+ (void)pthread_mutex_unlock(&conn->phys_ctx->socket_mutex);
18423+ }
1841618424
1841718425 /* call the connection_closed callback if assigned */
1841818426 if (conn->phys_ctx->callbacks.connection_closed != NULL) {
@@ -18493,6 +18501,59 @@ mg_close_connection(struct mg_connection *conn)
1849318501}
1849418502
1849518503
18504+ /* WebUI-private extension: quiesce a server without releasing connection
18505+ * storage retained by WebUI callback tasks. Not part of the CivetWeb API. */
18506+ void
18507+ webui_civetweb_shutdown_context_connections(struct mg_context *ctx)
18508+ {
18509+ if ((ctx == NULL) || (ctx->worker_connections == NULL)) {
18510+ return;
18511+ }
18512+
18513+ /* Quiesce socket dispatch before taking the worker snapshot. A socket that
18514+ * was accepted just before this flag changed is rejected by produce_socket,
18515+ * and dynamic worker publication uses the same mutex. Workers themselves
18516+ * remain alive so external users may safely retire their conn pointers. */
18517+ (void)pthread_mutex_lock(&ctx->thread_mutex);
18518+ STOP_FLAG_ASSIGN(&ctx->stop_accepting, 1);
18519+ (void)pthread_mutex_lock(&ctx->socket_mutex);
18520+
18521+ #if defined(ALTERNATIVE_QUEUE)
18522+ for (unsigned int i = 0; i < ctx->spawned_worker_threads; i++) {
18523+ if ((ctx->client_socks != NULL)
18524+ && (ctx->client_socks[i].in_use == 1)
18525+ && (ctx->client_socks[i].sock != INVALID_SOCKET)) {
18526+ shutdown(ctx->client_socks[i].sock, SHUTDOWN_BOTH);
18527+ }
18528+ }
18529+ #else
18530+ /* No worker may consume the queue while thread_mutex is held. Close queued
18531+ * sockets so every connection that can still be used is represented in
18532+ * worker_connections below. */
18533+ while (ctx->sq_tail < ctx->sq_head) {
18534+ struct socket *queued = &ctx->squeue[ctx->sq_tail % ctx->sq_size];
18535+ if (queued->sock != INVALID_SOCKET) {
18536+ set_blocking_mode(queued->sock);
18537+ closesocket(queued->sock);
18538+ queued->sock = INVALID_SOCKET;
18539+ }
18540+ ctx->sq_tail++;
18541+ }
18542+ (void)pthread_cond_broadcast(&ctx->sq_empty);
18543+ #endif
18544+
18545+ unsigned int count = ctx->spawned_worker_threads;
18546+ for (unsigned int i = 0; i < count; i++) {
18547+ struct mg_connection *conn = &ctx->worker_connections[i];
18548+ if (conn->client.sock != INVALID_SOCKET) {
18549+ shutdown(conn->client.sock, SHUTDOWN_BOTH);
18550+ }
18551+ }
18552+ (void)pthread_mutex_unlock(&ctx->socket_mutex);
18553+ (void)pthread_mutex_unlock(&ctx->thread_mutex);
18554+ }
18555+
18556+
1849618557static struct mg_connection *
1849718558mg_connect_client_impl(const struct mg_client_options *client_options,
1849818559 int use_ssl,
@@ -20099,17 +20160,29 @@ static void
2009920160produce_socket(struct mg_context *ctx, const struct socket *sp)
2010020161{
2010120162 unsigned int i;
20163+ (void)pthread_mutex_lock(&ctx->thread_mutex);
20164+ int accepting = STOP_FLAG_IS_ZERO(&ctx->stop_flag)
20165+ && STOP_FLAG_IS_ZERO(&ctx->stop_accepting);
20166+ (void)pthread_mutex_unlock(&ctx->thread_mutex);
20167+ if (!accepting) {
20168+ set_blocking_mode(sp->sock);
20169+ closesocket(sp->sock);
20170+ return;
20171+ }
2010220172
2010320173 (void)mg_start_worker_thread(
2010420174 ctx, 1); /* will start a worker-thread only if there aren't currently
2010520175 any idle worker-threads */
2010620176
20107- while (!ctx->stop_flag) {
20177+ while (STOP_FLAG_IS_ZERO(&ctx->stop_flag)
20178+ && STOP_FLAG_IS_ZERO(&ctx->stop_accepting)) {
2010820179 for (i = 0; i < ctx->spawned_worker_threads; i++) {
2010920180 /* find a free worker slot and signal it */
2011020181 if (ctx->client_socks[i].in_use == 2) {
2011120182 (void)pthread_mutex_lock(&ctx->thread_mutex);
20112- if ((ctx->client_socks[i].in_use == 2) && !ctx->stop_flag) {
20183+ if ((ctx->client_socks[i].in_use == 2)
20184+ && STOP_FLAG_IS_ZERO(&ctx->stop_flag)
20185+ && STOP_FLAG_IS_ZERO(&ctx->stop_accepting)) {
2011320186 ctx->client_socks[i] = *sp;
2011420187 ctx->client_socks[i].in_use = 1;
2011520188 /* socket has been moved to the consumer */
@@ -20149,6 +20222,11 @@ consume_socket(struct mg_context *ctx,
2014920222
2015020223 (void)pthread_mutex_lock(&ctx->thread_mutex);
2015120224 *sp = ctx->client_socks[thread_index];
20225+ /* Ownership moves to the worker connection. Do not leave a stale socket
20226+ * number in the handoff slot where a later shutdown scan could mistake it
20227+ * for an unconsumed connection after the descriptor has been reused. */
20228+ ctx->client_socks[thread_index].sock = INVALID_SOCKET;
20229+ ctx->client_socks[thread_index].in_use = 0;
2015220230 if (ctx->stop_flag) {
2015320231 (void)pthread_mutex_unlock(&ctx->thread_mutex);
2015420232 if (sp->in_use == 1) {
@@ -20223,13 +20301,22 @@ static void
2022320301produce_socket(struct mg_context *ctx, const struct socket *sp)
2022420302{
2022520303 int queue_filled;
20304+ int queued = 0;
2022620305
2022720306 (void)pthread_mutex_lock(&ctx->thread_mutex);
20307+ if (!STOP_FLAG_IS_ZERO(&ctx->stop_flag)
20308+ || !STOP_FLAG_IS_ZERO(&ctx->stop_accepting)) {
20309+ (void)pthread_mutex_unlock(&ctx->thread_mutex);
20310+ set_blocking_mode(sp->sock);
20311+ closesocket(sp->sock);
20312+ return;
20313+ }
2022820314
2022920315 queue_filled = ctx->sq_head - ctx->sq_tail;
2023020316
2023120317 /* If the queue is full, wait */
2023220318 while (STOP_FLAG_IS_ZERO(&ctx->stop_flag)
20319+ && STOP_FLAG_IS_ZERO(&ctx->stop_accepting)
2023320320 && (queue_filled >= ctx->sq_size)) {
2023420321 ctx->sq_blocked = 1; /* Status information: All threads busy */
2023520322#if defined(USE_SERVER_STATS)
@@ -20242,10 +20329,13 @@ produce_socket(struct mg_context *ctx, const struct socket *sp)
2024220329 queue_filled = ctx->sq_head - ctx->sq_tail;
2024320330 }
2024420331
20245- if (queue_filled < ctx->sq_size) {
20332+ if (STOP_FLAG_IS_ZERO(&ctx->stop_flag)
20333+ && STOP_FLAG_IS_ZERO(&ctx->stop_accepting)
20334+ && (queue_filled < ctx->sq_size)) {
2024620335 /* Copy socket to the queue and increment head */
2024720336 ctx->squeue[ctx->sq_head % ctx->sq_size] = *sp;
2024820337 ctx->sq_head++;
20338+ queued = 1;
2024920339 DEBUG_TRACE("queued socket %d", sp ? sp->sock : -1);
2025020340 }
2025120341
@@ -20258,6 +20348,11 @@ produce_socket(struct mg_context *ctx, const struct socket *sp)
2025820348
2025920349 (void)pthread_cond_signal(&ctx->sq_full);
2026020350 (void)pthread_mutex_unlock(&ctx->thread_mutex);
20351+ if (!queued) {
20352+ set_blocking_mode(sp->sock);
20353+ closesocket(sp->sock);
20354+ return;
20355+ }
2026120356
2026220357 (void)mg_start_worker_thread(
2026320358 ctx, 1); /* will start a worker-thread only if there aren't currently
@@ -20740,6 +20835,7 @@ master_thread_run(struct mg_context *ctx)
2074020835 * Therefore, we're checking pfd[i].revents & POLLIN, not
2074120836 * pfd[i].revents == POLLIN. */
2074220837 if (STOP_FLAG_IS_ZERO(&ctx->stop_flag)
20838+ && STOP_FLAG_IS_ZERO(&ctx->stop_accepting)
2074320839 && (pfd[i].revents & POLLIN)) {
2074420840 accept_new_connection(&ctx->listening_sockets[i], ctx);
2074520841 }
@@ -20865,6 +20961,7 @@ free_context(struct mg_context *ctx)
2086520961 * condvars
2086620962 */
2086720963 (void)pthread_mutex_destroy(&ctx->thread_mutex);
20964+ (void)pthread_mutex_destroy(&ctx->socket_mutex);
2086820965
2086920966#if defined(ALTERNATIVE_QUEUE)
2087020967 mg_free(ctx->client_socks);
@@ -20979,7 +21076,7 @@ mg_stop(struct mg_context *ctx)
2097921076 STOP_FLAG_ASSIGN(&ctx->stop_flag, 1);
2098021077
2098121078 /* Closing this socket will cause mg_poll() in all the I/O threads to return
20982- * immediately */
21079+ * immediately. */
2098321080 closesocket(ctx->user_shutdown_notification_socket);
2098421081 ctx->user_shutdown_notification_socket =
2098521082 -1; /* to avoid calling closesocket() again in free_context() */
@@ -21154,13 +21251,24 @@ mg_socketpair(int *sockA, int *sockB)
2115421251static int
2115521252mg_start_worker_thread(struct mg_context *ctx, int only_if_no_idle_threads)
2115621253{
21254+ /* Keep worker reservation, creation and publication atomic with respect to
21255+ * an external stop/connection-interrupt request. The new worker may start
21256+ * running immediately, but it cannot consume a socket until this mutex is
21257+ * released. */
21258+ (void)pthread_mutex_lock(&ctx->thread_mutex);
2115721259 const unsigned int i = ctx->spawned_worker_threads;
2115821260 if (i >= ctx->cfg_max_worker_threads) {
21261+ (void)pthread_mutex_unlock(&ctx->thread_mutex);
2115921262 return -1; /* Oops, we hit our worker-thread limit! No more worker
2116021263 threads, ever! */
2116121264 }
2116221265
21163- (void)pthread_mutex_lock(&ctx->thread_mutex);
21266+ if (!STOP_FLAG_IS_ZERO(&ctx->stop_flag)
21267+ || !STOP_FLAG_IS_ZERO(&ctx->stop_accepting)) {
21268+ (void)pthread_mutex_unlock(&ctx->thread_mutex);
21269+ return -1;
21270+ }
21271+
2116421272#if defined(ALTERNATIVE_QUEUE)
2116521273 if ((only_if_no_idle_threads) && (ctx->idle_worker_thread_count > 0)) {
2116621274#else
@@ -21175,7 +21283,6 @@ mg_start_worker_thread(struct mg_context *ctx, int only_if_no_idle_threads)
2117521283 ctx->idle_worker_thread_count++; /* we do this here to avoid a race
2117621284 condition while the thread is starting
2117721285 up */
21178- (void)pthread_mutex_unlock(&ctx->thread_mutex);
2117921286
2118021287 ctx->worker_connections[i].phys_ctx = ctx;
2118121288 int ret = mg_start_thread_with_id(worker_thread,
@@ -21186,10 +21293,9 @@ mg_start_worker_thread(struct mg_context *ctx, int only_if_no_idle_threads)
2118621293 the table */
2118721294 DEBUG_TRACE("Started worker_thread #%i", ctx->spawned_worker_threads);
2118821295 } else {
21189- (void)pthread_mutex_lock(&ctx->thread_mutex);
2119021296 ctx->idle_worker_thread_count--; /* whoops, roll-back on error */
21191- (void)pthread_mutex_unlock(&ctx->thread_mutex);
2119221297 }
21298+ (void)pthread_mutex_unlock(&ctx->thread_mutex);
2119321299 return ret;
2119421300}
2119521301
@@ -21266,6 +21372,7 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error)
2126621372 pthread_setspecific(sTlsKey, &tls);
2126721373
2126821374 ok = (0 == pthread_mutex_init(&ctx->thread_mutex, &pthread_mutex_attr));
21375+ ok &= (0 == pthread_mutex_init(&ctx->socket_mutex, &pthread_mutex_attr));
2126921376#if !defined(ALTERNATIVE_QUEUE)
2127021377 ok &= (0 == pthread_cond_init(&ctx->sq_empty, NULL));
2127121378 ok &= (0 == pthread_cond_init(&ctx->sq_full, NULL));
@@ -21773,6 +21880,9 @@ mg_start2(struct mg_init_data *init, struct mg_error_data *error)
2177321880 pthread_setspecific(sTlsKey, NULL);
2177421881 return NULL;
2177521882 }
21883+ for (i = 0; (unsigned)i < ctx->cfg_max_worker_threads; i++) {
21884+ ctx->worker_connections[i].client.sock = INVALID_SOCKET;
21885+ }
2177621886
2177721887#if defined(ALTERNATIVE_QUEUE)
2177821888 ctx->client_wait_events =
0 commit comments