From 7ef32c05b73e44b6ba913e4a7098bbf842d80a2c Mon Sep 17 00:00:00 2001 From: Roman Janota Date: Mon, 13 Apr 2026 15:33:17 +0200 Subject: [PATCH 1/4] session server UPDATE CH thread lifecycle management Store thread IDs in ch_thread_arg to allow proper pthread_join when stopping threads. Add nc_session_server_ch_client_dispatch_stop_if_dispatched() to centralize thread stopping logic, fixing several locking issues: - Only unlock ch_lock if it was successfully acquired in nc_session_free() - Use config_update_lock to prevent data race in nc_server_destroy() - Properly handle config lock unlocking during thread join to avoid deadlock --- src/server_config.c | 172 +++++++++++++++++++------------------------ src/session.c | 28 +++++-- src/session_p.h | 12 +++ src/session_server.c | 137 ++++++++++++++++++++++++---------- 4 files changed, 207 insertions(+), 142 deletions(-) diff --git a/src/server_config.c b/src/server_config.c index ccf309d9..e79883e0 100644 --- a/src/server_config.c +++ b/src/server_config.c @@ -3941,7 +3941,7 @@ config_netconf_client(const struct lyd_node *node, enum nc_operation parent_op, struct lyd_node *n; enum nc_operation op; const char *name; - LY_ARRAY_COUNT_TYPE i = 0, j = 0; + LY_ARRAY_COUNT_TYPE i = 0; struct nc_ch_client *ch_client = NULL; NC_NODE_GET_OP(node, parent_op, &op); @@ -3988,30 +3988,6 @@ config_netconf_client(const struct lyd_node *node, enum nc_operation parent_op, /* all children processed, we can now delete the client */ if (op == NC_OP_DELETE) { - /* CH THREADS DATA RD LOCK */ - if (nc_rwlock_lock(&server_opts.ch_threads_lock, NC_RWLOCK_READ, NC_CH_THREADS_LOCK_TIMEOUT, __func__) != 1) { - return 1; - } - - /* find the thread data for this CH client, not found <==> CH thread not running */ - LY_ARRAY_FOR(server_opts.ch_threads, j) { - if (!strcmp(server_opts.ch_threads[j]->client_name, name)) { - break; - } - } - - if (j < LY_ARRAY_COUNT(server_opts.ch_threads)) { - /* the CH thread is running, notify it to stop */ - if (nc_mutex_lock(&server_opts.ch_threads[j]->cond_lock, NC_CH_COND_LOCK_TIMEOUT, __func__) != 1) { - server_opts.ch_threads[j]->thread_running = 0; - pthread_cond_signal(&server_opts.ch_threads[j]->cond); - nc_mutex_unlock(&server_opts.ch_threads[j]->cond_lock, __func__); - } - } - - /* CH THREADS DATA UNLOCK */ - nc_rwlock_unlock(&server_opts.ch_threads_lock, __func__); - /* we can use 'i' from above */ if (i < LY_ARRAY_COUNT(config->ch_clients) - 1) { config->ch_clients[i] = config->ch_clients[LY_ARRAY_COUNT(config->ch_clients) - 1]; @@ -5330,6 +5306,37 @@ nc_server_config_reconcile_sockets_listen(struct nc_server_config *old_cfg, #ifdef NC_ENABLED_SSH_TLS +/** + * @brief Check if there are any new Call Home clients created in the new configuration. + * + * @param[in] old_cfg Old, currently active server configuration. + * @param[in] new_cfg New server configuration currently being applied. + * @return 1 if there are new CH clients, 0 otherwise. + */ +static int +nc_server_config_new_ch_clients_created(struct nc_server_config *old_cfg, struct nc_server_config *new_cfg) +{ + struct nc_ch_client *old_ch_client, *new_ch_client; + int found; + + /* check if there are any new clients */ + LY_ARRAY_FOR(new_cfg->ch_clients, struct nc_ch_client, new_ch_client) { + found = 0; + LY_ARRAY_FOR(old_cfg->ch_clients, struct nc_ch_client, old_ch_client) { + if (!strcmp(new_ch_client->name, old_ch_client->name)) { + found = 1; + break; + } + } + if (!found) { + return 1; + } + } + + /* no differences found */ + return 0; +} + /** * @brief Atomically dispatch new Call Home clients and reuse existing ones. * @@ -5347,11 +5354,16 @@ nc_server_config_reconcile_chclients_dispatch(struct nc_server_config *old_cfg, int found; LY_ARRAY_COUNT_TYPE i = 0; char **started_clients = NULL, **client_name = NULL; + int dispatch_new_clients = 1; if (!server_opts.ch_dispatch_data.acquire_ctx_cb || !server_opts.ch_dispatch_data.release_ctx_cb || !server_opts.ch_dispatch_data.new_session_cb) { - /* Call Home dispatch callbacks not set, nothing to do */ - return 0; + /* Call Home dispatch callbacks not set, we can't dispatch new clients, but we can still stop deleted ones */ + if (nc_server_config_new_ch_clients_created(old_cfg, new_cfg)) { + WRN(NULL, "New Call Home clients were created but Call Home dispatch callbacks are not set - " + "new clients will not be dispatched automatically."); + } + dispatch_new_clients = 0; } /* @@ -5359,43 +5371,46 @@ nc_server_config_reconcile_chclients_dispatch(struct nc_server_config *old_cfg, * Start clients present in new_cfg that are not already running. * Track successfully started clients for potential rollback. */ - LY_ARRAY_FOR(new_cfg->ch_clients, struct nc_ch_client, new_ch_client) { - found = 0; + if (dispatch_new_clients) { + /* only dispatch if all required CBs are set */ + LY_ARRAY_FOR(new_cfg->ch_clients, struct nc_ch_client, new_ch_client) { + found = 0; - /* CH THREADS LOCK (reading server_opts.ch_threads) */ - if (nc_rwlock_lock(&server_opts.ch_threads_lock, NC_RWLOCK_READ, NC_CH_THREADS_LOCK_TIMEOUT, __func__) != 1) { - rc = 1; - goto rollback; - } + /* CH THREADS LOCK (reading server_opts.ch_threads) */ + if (nc_rwlock_lock(&server_opts.ch_threads_lock, NC_RWLOCK_READ, NC_CH_THREADS_LOCK_TIMEOUT, __func__) != 1) { + rc = 1; + goto rollback; + } - LY_ARRAY_FOR(server_opts.ch_threads, struct nc_server_ch_thread_arg *, ch_thread_arg) { - if (!strcmp(new_ch_client->name, (*ch_thread_arg)->client_name)) { - /* already running, do not start again */ - found = 1; - break; + LY_ARRAY_FOR(server_opts.ch_threads, struct nc_server_ch_thread_arg *, ch_thread_arg) { + if (!strcmp(new_ch_client->name, (*ch_thread_arg)->client_name)) { + /* already running, do not start again */ + found = 1; + break; + } } - } - /* CH THREADS UNLOCK */ - nc_rwlock_unlock(&server_opts.ch_threads_lock, __func__); + /* CH THREADS UNLOCK */ + nc_rwlock_unlock(&server_opts.ch_threads_lock, __func__); - if (!found) { - /* this is a new Call Home client, dispatch it */ - rc = _nc_connect_ch_client_dispatch(new_ch_client, server_opts.ch_dispatch_data.acquire_ctx_cb, - server_opts.ch_dispatch_data.release_ctx_cb, server_opts.ch_dispatch_data.ctx_cb_data, - server_opts.ch_dispatch_data.new_session_cb, server_opts.ch_dispatch_data.new_session_cb_data); - if (rc) { - /* FAILURE! trigger rollback */ - goto rollback; - } + if (!found) { + /* this is a new Call Home client, dispatch it */ + rc = _nc_connect_ch_client_dispatch(new_ch_client, server_opts.ch_dispatch_data.acquire_ctx_cb, + server_opts.ch_dispatch_data.release_ctx_cb, server_opts.ch_dispatch_data.ctx_cb_data, + server_opts.ch_dispatch_data.new_session_cb, server_opts.ch_dispatch_data.new_session_cb_data); + if (rc) { + /* FAILURE! trigger rollback */ + goto rollback; + } - /* successfully started, track it for potential rollback */ - LY_ARRAY_NEW_GOTO(NULL, started_clients, client_name, rc, rollback); - *client_name = strdup(new_ch_client->name); - NC_CHECK_ERRMEM_GOTO(!*client_name, rc = 1, rollback); + /* successfully started, track it for potential rollback */ + LY_ARRAY_NEW_GOTO(NULL, started_clients, client_name, rc, rollback); + *client_name = strdup(new_ch_client->name); + NC_CHECK_ERRMEM_GOTO(!*client_name, rc = 1, rollback); - /* ownership transferred to array */ - client_name = NULL; + /* ownership transferred to array */ + client_name = NULL; + } } } @@ -5415,27 +5430,10 @@ nc_server_config_reconcile_chclients_dispatch(struct nc_server_config *old_cfg, if (!found) { /* this Call Home client was deleted, notify it to stop */ - ch_thread_arg = NULL; - - /* CH THREADS LOCK (reading server_opts.ch_threads) */ - if (nc_rwlock_lock(&server_opts.ch_threads_lock, NC_RWLOCK_READ, NC_CH_THREADS_LOCK_TIMEOUT, __func__) != 1) { - /* Continue even if lock fails - best effort cleanup */ - continue; - } - LY_ARRAY_FOR(server_opts.ch_threads, struct nc_server_ch_thread_arg *, ch_thread_arg) { - if (!strcmp(old_ch_client->name, (*ch_thread_arg)->client_name)) { - /* notify the thread to stop */ - if (nc_mutex_lock(&(*ch_thread_arg)->cond_lock, NC_CH_COND_LOCK_TIMEOUT, __func__) != 1) { - (*ch_thread_arg)->thread_running = 0; - pthread_cond_signal(&(*ch_thread_arg)->cond); - nc_mutex_unlock(&(*ch_thread_arg)->cond_lock, __func__); - } - break; - } + if ((rc = nc_session_server_ch_client_dispatch_stop_if_dispatched(old_ch_client->name, NC_RWLOCK_WRITE))) { + ERR(NULL, "Failed to dispatch stop for Call Home client \"%s\".", old_ch_client->name); + goto rollback; } - /* CH THREADS UNLOCK */ - nc_rwlock_unlock(&server_opts.ch_threads_lock, __func__); - /* Note: if ch_thread_arg is NULL here, the thread wasn't running. That's fine. */ } } @@ -5450,25 +5448,7 @@ nc_server_config_reconcile_chclients_dispatch(struct nc_server_config *old_cfg, * to return to the pre-call state. */ LY_ARRAY_FOR(started_clients, i) { - ch_thread_arg = NULL; - - /* CH THREADS LOCK (reading server_opts.ch_threads) */ - if (nc_rwlock_lock(&server_opts.ch_threads_lock, NC_RWLOCK_READ, NC_CH_THREADS_LOCK_TIMEOUT, __func__) != 1) { - /* Continue even if lock fails - best effort rollback */ - continue; - } - LY_ARRAY_FOR(server_opts.ch_threads, struct nc_server_ch_thread_arg *, ch_thread_arg) { - if (!strcmp(started_clients[i], (*ch_thread_arg)->client_name)) { - /* notify the newly started thread to stop */ - nc_mutex_lock(&(*ch_thread_arg)->cond_lock, NC_CH_COND_LOCK_TIMEOUT, __func__); - (*ch_thread_arg)->thread_running = 0; - pthread_cond_signal(&(*ch_thread_arg)->cond); - nc_mutex_unlock(&(*ch_thread_arg)->cond_lock, __func__); - break; - } - } - /* CH THREADS UNLOCK */ - nc_rwlock_unlock(&server_opts.ch_threads_lock, __func__); + nc_session_server_ch_client_dispatch_stop_if_dispatched(started_clients[i], NC_RWLOCK_WRITE); } /* rc is already set to non-zero from the failure point */ diff --git a/src/session.c b/src/session.c index b3b2c694..ddbf2d06 100644 --- a/src/session.c +++ b/src/session.c @@ -910,7 +910,7 @@ nc_session_free_transport(struct nc_session *session, int *multisession) * NC_CLIENT: we are waiting for the server to acknowledge our SSH channel EOF * by sending us its own SSH channel EOF. */ if (ssh_channel_poll_timeout(session->ti.libssh.channel, NC_SESSION_FREE_SSH_POLL_EOF_TIMEOUT, 0) != SSH_EOF) { - WRN(session, "Timeout for receiving SSH channel EOF from the peer elapsed."); + WRN(session, "Timeout for receiving SSH channel EOF from the %s elapsed.", session->side == NC_CLIENT ? "server" : "client"); } } ssh_channel_free(session->ti.libssh.channel); @@ -1001,7 +1001,7 @@ nc_session_free_transport(struct nc_session *session, int *multisession) API void nc_session_free(struct nc_session *session, void (*data_free)(void *)) { - int r, i, rpc_locked = 0; + int r, i, rpc_locked = 0, ch_locked = 0; int multisession = 0; /* flag for more NETCONF sessions on a single SSH session */ struct timespec ts; NC_STATUS status; @@ -1012,7 +1012,9 @@ nc_session_free(struct nc_session *session, void (*data_free)(void *)) if ((session->side == NC_SERVER) && (session->flags & NC_SESSION_CALLHOME)) { /* CH LOCK, continue on error */ - r = nc_mutex_lock(&session->opts.server.ch_lock, NC_SESSION_CH_LOCK_TIMEOUT, __func__); + if (nc_mutex_lock(&session->opts.server.ch_lock, NC_SESSION_CH_LOCK_TIMEOUT, __func__) == 1) { + ch_locked = 1; + } } /* store status, so we can check if this session is already closing */ @@ -1020,7 +1022,7 @@ nc_session_free(struct nc_session *session, void (*data_free)(void *)) if ((session->side == NC_SERVER) && (session->flags & NC_SESSION_CALLHOME)) { /* CH UNLOCK */ - if (r == 1) { + if (ch_locked) { /* only if we locked it */ nc_mutex_unlock(&session->opts.server.ch_lock, __func__); } @@ -1060,8 +1062,12 @@ nc_session_free(struct nc_session *session, void (*data_free)(void *)) nc_client_msgs_free(session); } - if (session->status == NC_STATUS_RUNNING) { - /* notify the peer that we're closing the session */ + /* notify the peer that we're closing the session, either if: + * - session running - normal disconnect from client + * - session invalid - client disconnected from a Call Home session */ + if ((session->status == NC_STATUS_RUNNING) || + ((session->side == NC_SERVER) && (session->flags & NC_SESSION_CALLHOME) && + (session->status == NC_STATUS_INVALID) && (session->term_reason == NC_SESSION_TERM_CLOSED))) { if (session->side == NC_CLIENT) { /* graceful close: + transport shutdown indication */ nc_session_free_client_close_graceful(session); @@ -1073,7 +1079,10 @@ nc_session_free(struct nc_session *session, void (*data_free)(void *)) if ((session->side == NC_SERVER) && (session->flags & NC_SESSION_CALLHOME)) { /* CH LOCK */ - nc_mutex_lock(&session->opts.server.ch_lock, NC_SESSION_CH_LOCK_TIMEOUT, __func__); + ch_locked = 0; + if (nc_mutex_lock(&session->opts.server.ch_lock, NC_SESSION_CH_LOCK_TIMEOUT, __func__) == 1) { + ch_locked = 1; + } } /* mark session for closing */ @@ -1096,7 +1105,10 @@ nc_session_free(struct nc_session *session, void (*data_free)(void *)) if ((session->side == NC_SERVER) && (session->flags & NC_SESSION_CALLHOME)) { /* CH UNLOCK */ - nc_mutex_unlock(&session->opts.server.ch_lock, __func__); + if (ch_locked) { + /* only if we locked it */ + nc_mutex_unlock(&session->opts.server.ch_lock, __func__); + } } /* transport implementation cleanup */ diff --git a/src/session_p.h b/src/session_p.h index 1cebecf1..60c34ef6 100644 --- a/src/session_p.h +++ b/src/session_p.h @@ -692,6 +692,7 @@ struct nc_server_ch_thread_arg { uint8_t cur_attempt, void *user_data); /**< failed to create a new session cb */ void *new_session_fail_cb_data; /**< new session fail cb data */ + pthread_t tid; /**< Thread ID of the Call Home client thread. */ int thread_running; /**< A boolean value that is truthy while the underlying Call Home thread is running */ pthread_mutex_t cond_lock; /**< Condition's lock used for signalling the thread to terminate */ pthread_cond_t cond; /**< Condition used for signalling the thread to terminate */ @@ -1387,6 +1388,17 @@ NC_MSG_TYPE nc_connect_callhome(const char *host, uint16_t port, NC_TRANSPORT_IM #ifdef NC_ENABLED_SSH_TLS +/** + * @brief Stop a dispatched Call Home client thread, if such thread was dispatched for the given client name. + * + * @param[in] client_name Name of the Call Home client to stop the thread for. + * @param[in] config_lock_mode Lock mode of the configuration lock, if it is held by the caller. + * If NC_RWLOCK_WRITE is passed, config lock will be briefly unlocked and then locked again. + * The caller MUST ensure that it holds the CONFIG APPLY mutex, so that nobody steals the wrlock from him. + * @return 0 if the thread was successfully stopped or not found, 1 on error. + */ +int nc_session_server_ch_client_dispatch_stop_if_dispatched(const char *client_name, enum nc_rwlock_mode config_lock_mode); + /** * @brief Dispatch a thread connecting to a listening NETCONF client and creating Call Home sessions. * diff --git a/src/session_server.c b/src/session_server.c index 3a7ef140..aea96383 100644 --- a/src/session_server.c +++ b/src/session_server.c @@ -1299,6 +1299,8 @@ nc_server_init(void) API void nc_server_destroy(void) { + int config_update_locked = 0; + enum nc_rwlock_mode config_lock_mode = NC_RWLOCK_NONE; uint32_t i; for (i = 0; i < server_opts.capabilities_count; i++) { @@ -1316,27 +1318,28 @@ nc_server_destroy(void) nc_server_notif_cert_expiration_thread_stop(1); #endif /* NC_ENABLED_SSH_TLS */ - /* CONFIG WR LOCK */ - nc_rwlock_lock(&server_opts.config_lock, NC_RWLOCK_WRITE, NC_CONFIG_LOCK_TIMEOUT, __func__); - - /* destroy the server configuration */ - nc_server_config_free(&server_opts.config); + /* CONFIG UPDATE LOCK, continue on error */ + if (nc_mutex_lock(&server_opts.config_update_lock, NC_CONFIG_LOCK_TIMEOUT, __func__) == 1) { + config_update_locked = 1; + } - /* CH THREADS LOCK */ - nc_rwlock_lock(&server_opts.ch_threads_lock, NC_RWLOCK_READ, NC_CH_THREADS_LOCK_TIMEOUT, __func__); + /* CONFIG WR LOCK, continue on error */ + if (nc_rwlock_lock(&server_opts.config_lock, NC_RWLOCK_WRITE, NC_CONFIG_LOCK_TIMEOUT, __func__) == 1) { + config_lock_mode = NC_RWLOCK_WRITE; + } - /* notify the CH threads to exit */ - LY_ARRAY_FOR(server_opts.ch_threads, i) { - if (nc_mutex_lock(&server_opts.ch_threads[i]->cond_lock, NC_CH_COND_LOCK_TIMEOUT, __func__) != 1) { - continue; +#ifdef NC_ENABLED_SSH_TLS + if (!config_update_locked || (config_lock_mode != NC_RWLOCK_WRITE)) { + WRN(NULL, "Config locks not acquired, skipping Call Home threads cleanup."); + } else { + /* stop all dispatched CH threads */ + LY_ARRAY_FOR(server_opts.config.ch_clients, i) { + nc_session_server_ch_client_dispatch_stop_if_dispatched(server_opts.config.ch_clients[i].name, config_lock_mode); } - server_opts.ch_threads[i]->thread_running = 0; - pthread_cond_signal(&server_opts.ch_threads[i]->cond); - nc_mutex_unlock(&server_opts.ch_threads[i]->cond_lock, __func__); } - /* CH THREADS UNLOCK */ - nc_rwlock_unlock(&server_opts.ch_threads_lock, __func__); + /* destroy the server configuration */ + nc_server_config_free(&server_opts.config); #ifdef NC_ENABLED_SSH_TLS free(server_opts.authkey_path_fmt); @@ -1359,7 +1362,14 @@ nc_server_destroy(void) server_opts.unix_paths = NULL; /* CONFIG WR UNLOCK */ - nc_rwlock_unlock(&server_opts.config_lock, __func__); + if (config_lock_mode != NC_RWLOCK_NONE) { + nc_rwlock_unlock(&server_opts.config_lock, __func__); + } + + /* CONFIG UPDATE UNLOCK */ + if (config_update_locked) { + nc_mutex_unlock(&server_opts.config_update_lock, __func__); + } #ifdef NC_ENABLED_SSH_TLS curl_global_cleanup(); @@ -3753,29 +3763,83 @@ nc_ch_client_thread(void *arg) } int -_nc_connect_ch_client_dispatch(const struct nc_ch_client *ch_client, nc_server_ch_session_acquire_ctx_cb acquire_ctx_cb, - nc_server_ch_session_release_ctx_cb release_ctx_cb, void *ctx_cb_data, nc_server_ch_new_session_cb new_session_cb, - void *new_session_cb_data) +nc_session_server_ch_client_dispatch_stop_if_dispatched(const char *client_name, enum nc_rwlock_mode config_lock_mode) { int rc = 0, r; - enum nc_rwlock_mode ch_threads_lock = NC_RWLOCK_NONE; + struct nc_server_ch_thread_arg **ch_thread_arg; pthread_t tid; - struct nc_server_ch_thread_arg *arg = NULL, **new_item; - pthread_attr_t attr; + enum nc_rwlock_mode ch_threads_lock = NC_RWLOCK_NONE; - /* init pthread attribute */ - if ((r = pthread_attr_init(&attr))) { - ERR(NULL, "Initializing pthread attributes failed (%s).", strerror(r)); - return -1; + /* CH THREADS LOCK */ + if (nc_rwlock_lock(&server_opts.ch_threads_lock, NC_RWLOCK_READ, NC_CH_THREADS_LOCK_TIMEOUT, __func__) != 1) { + return 1; } + ch_threads_lock = NC_RWLOCK_READ; - /* set the thread to be detached */ - if ((r = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED))) { - ERR(NULL, "Setting pthread attributes to detached failed (%s).", strerror(r)); - rc = -1; - goto cleanup; + if (config_lock_mode == NC_RWLOCK_WRITE) { + /* CONFIG UNLOCK - if the caller holds the config lock in write mode, we need to unlock it + * to prevent deadlock with the CH thread, it tries to acquire the config lock in read mode when it + * checks if the client still exists. + * It is the caller's responsibility to hold config apply mutex, so noone steals the write lock from him */ + nc_rwlock_unlock(&server_opts.config_lock, __func__); + } + + LY_ARRAY_FOR(server_opts.ch_threads, struct nc_server_ch_thread_arg *, ch_thread_arg) { + if (strcmp(client_name, (*ch_thread_arg)->client_name)) { + continue; + } + + /* CH COND LOCK */ + if (nc_mutex_lock(&(*ch_thread_arg)->cond_lock, NC_CH_COND_LOCK_TIMEOUT, __func__) != 1) { + rc = 1; + goto cleanup; + } + + /* notify the thread to stop */ + (*ch_thread_arg)->thread_running = 0; + tid = (*ch_thread_arg)->tid; + pthread_cond_signal(&(*ch_thread_arg)->cond); + + /* CH COND UNLOCK */ + nc_mutex_unlock(&(*ch_thread_arg)->cond_lock, __func__); + + /* CH THREADS UNLOCK - let the thread free itself from the global array, it needs WR lock for that */ + nc_rwlock_unlock(&server_opts.ch_threads_lock, __func__); + ch_threads_lock = NC_RWLOCK_NONE; + + /* wait for the thread to end */ + r = pthread_join(tid, NULL); + if (r) { + ERR(NULL, "Joining Call Home client \"%s\" thread failed (%s).", client_name, strerror(r)); + rc = 1; + goto cleanup; + } + break; + } + +cleanup: + if (config_lock_mode == NC_RWLOCK_WRITE) { + /* CONFIG LOCK - lock it back if we unlocked it. It MUST succeed, if the caller holds the config apply mutex */ + assert(nc_rwlock_lock(&server_opts.config_lock, NC_RWLOCK_WRITE, -1, __func__) == 1); + } + + if (ch_threads_lock) { + /* CH THREADS UNLOCK */ + nc_rwlock_unlock(&server_opts.ch_threads_lock, __func__); } + return rc; +} + +int +_nc_connect_ch_client_dispatch(const struct nc_ch_client *ch_client, nc_server_ch_session_acquire_ctx_cb acquire_ctx_cb, + nc_server_ch_session_release_ctx_cb release_ctx_cb, void *ctx_cb_data, nc_server_ch_new_session_cb new_session_cb, + void *new_session_cb_data) +{ + int rc = 0, r; + enum nc_rwlock_mode ch_threads_lock = NC_RWLOCK_NONE; + struct nc_server_ch_thread_arg *arg = NULL, **new_item; + /* create the thread argument */ arg = calloc(1, sizeof *arg); NC_CHECK_ERRMEM_GOTO(!arg, rc = -1, cleanup); @@ -3804,13 +3868,11 @@ _nc_connect_ch_client_dispatch(const struct nc_ch_client *ch_client, nc_server_c LY_ARRAY_NEW_GOTO(NULL, server_opts.ch_threads, new_item, rc, cleanup); *new_item = arg; - /* CH THREADS UNLOCK */ - nc_rwlock_unlock(&server_opts.ch_threads_lock, __func__); - ch_threads_lock = NC_RWLOCK_NONE; - /* create the CH thread */ - if ((r = pthread_create(&tid, &attr, nc_ch_client_thread, arg))) { + if ((r = pthread_create(&arg->tid, NULL, nc_ch_client_thread, arg))) { ERR(NULL, "Creating a new thread failed (%s).", strerror(r)); + /* remove from the array */ + LY_ARRAY_DECREMENT_FREE(server_opts.ch_threads); rc = -1; goto cleanup; } @@ -3823,7 +3885,6 @@ _nc_connect_ch_client_dispatch(const struct nc_ch_client *ch_client, nc_server_c /* CH THREADS UNLOCK */ nc_rwlock_unlock(&server_opts.ch_threads_lock, __func__); } - pthread_attr_destroy(&attr); if (arg) { free(arg->client_name); free(arg); From dc8687bcd55fd33757336d6ed0c5aa3c1fee19cf Mon Sep 17 00:00:00 2001 From: Roman Janota Date: Mon, 13 Apr 2026 16:12:34 +0200 Subject: [PATCH 2/4] session server BUGFIX compile without ssh_tls --- src/session_server.c | 1 + src/session_server.h | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/src/session_server.c b/src/session_server.c index aea96383..a2b3e45c 100644 --- a/src/session_server.c +++ b/src/session_server.c @@ -1337,6 +1337,7 @@ nc_server_destroy(void) nc_session_server_ch_client_dispatch_stop_if_dispatched(server_opts.config.ch_clients[i].name, config_lock_mode); } } +#endif /* NC_ENABLED_SSH_TLS */ /* destroy the server configuration */ nc_server_config_free(&server_opts.config); diff --git a/src/session_server.h b/src/session_server.h index 59b2483d..be477046 100644 --- a/src/session_server.h +++ b/src/session_server.h @@ -448,6 +448,8 @@ NC_MSG_TYPE nc_session_accept_ssh_channel(struct nc_session *orig_session, struc */ NC_MSG_TYPE nc_ps_accept_ssh_channel(struct nc_pollsession *ps, struct nc_session **session); +#endif /* NC_ENABLED_SSH_TLS */ + /** * @brief Set the UNIX socket path for a given endpoint name. * @@ -491,6 +493,8 @@ int nc_server_get_unix_socket_dir(char **dir); /** @} Server Session */ +#ifdef NC_ENABLED_SSH_TLS + /** * @defgroup server_ssh Server SSH * @ingroup server From 3a647b8bab11e2ddf3aea92986558d705b32c935 Mon Sep 17 00:00:00 2001 From: Roman Janota Date: Mon, 13 Apr 2026 15:44:52 +0200 Subject: [PATCH 3/4] SOVERSION bump to version 5.3.7 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d73a9c46..8fe3d41b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -66,7 +66,7 @@ set(LIBNETCONF2_VERSION ${LIBNETCONF2_MAJOR_VERSION}.${LIBNETCONF2_MINOR_VERSION # with backward compatible change and micro version is connected with any internal change of the library. set(LIBNETCONF2_MAJOR_SOVERSION 5) set(LIBNETCONF2_MINOR_SOVERSION 3) -set(LIBNETCONF2_MICRO_SOVERSION 6) +set(LIBNETCONF2_MICRO_SOVERSION 7) set(LIBNETCONF2_SOVERSION_FULL ${LIBNETCONF2_MAJOR_SOVERSION}.${LIBNETCONF2_MINOR_SOVERSION}.${LIBNETCONF2_MICRO_SOVERSION}) set(LIBNETCONF2_SOVERSION ${LIBNETCONF2_MAJOR_SOVERSION}) From b28885f5e9ce14bb80ecf830b7c6b2bab3bfd885 Mon Sep 17 00:00:00 2001 From: Roman Janota Date: Mon, 13 Apr 2026 15:44:58 +0200 Subject: [PATCH 4/4] VERSION bump to version 4.3.1 --- CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 8fe3d41b..14781983 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -58,7 +58,7 @@ set(CMAKE_MACOSX_RPATH TRUE) # micro version is changed with a set of small changes or bugfixes anywhere in the project. set(LIBNETCONF2_MAJOR_VERSION 4) set(LIBNETCONF2_MINOR_VERSION 3) -set(LIBNETCONF2_MICRO_VERSION 0) +set(LIBNETCONF2_MICRO_VERSION 1) set(LIBNETCONF2_VERSION ${LIBNETCONF2_MAJOR_VERSION}.${LIBNETCONF2_MINOR_VERSION}.${LIBNETCONF2_MICRO_VERSION}) # Version of the library