Commit 29a40b10c2 for frr
commit 29a40b10c2c0955cb731a977a7a1807eb877dc8c
Author: Enke Chen <enchen@paloaltonetworks.com>
Date: Tue Sep 8 17:44:15 2026 -0700
lib: fix gRPC server initialization with proper error checking
Previously, if BuildAndStart() failed (returning nullptr), the code
would proceed to register RPC handlers with an invalid server pointer,
causing a crash.
Fix this by adding proper error checking after BuildAndStart(). Use a
condition variable to synchronize with the main thread instead of a
polling loop, which was just a heuristic that could fail under load.
Changes:
- Check if BuildAndStart() returns nullptr and exit gracefully
- Add pthread_cond_t for signaling initialization completion
- Signal condition when state changes to RUNNING or SHUTDOWN
- Wait on condition variable instead of polling with usleep()
- Simplify error handling - let frr_grpc_finish() handle cleanup via
the frr_fini hook instead of duplicating cleanup code
- Call raise(SIGTERM) on initialization failure to terminate the FRR
daemon and allow watchfrr to restart
Signed-off-by: Enke Chen <enchen@paloaltonetworks.com>
diff --git a/lib/northbound_grpc.cpp b/lib/northbound_grpc.cpp
index 3a93b5c024..33adf97f9a 100644
--- a/lib/northbound_grpc.cpp
+++ b/lib/northbound_grpc.cpp
@@ -1130,6 +1130,7 @@ struct grpc_pthread_attr {
// Capture these objects so we can try to shut down cleanly
static pthread_mutex_t s_server_lock = PTHREAD_MUTEX_INITIALIZER;
+static pthread_cond_t s_server_cond = PTHREAD_COND_INITIALIZER;
static grpc::Server *s_server;
static grpc::ServerCompletionQueue *s_cq;
@@ -1157,15 +1158,11 @@ static void *grpc_pthread_start(void *arg)
void *tag;
bool ok;
- pthread_mutex_lock(&s_server_lock); // Make coverity happy
- if (grpc_state == GRPC_STATE_SHUTDOWN) {
+ if (!server) {
unsigned int n = 0;
- pthread_mutex_unlock(&s_server_lock);
- if (server)
- server->Shutdown();
- if (cq)
- cq->Shutdown();
+ flog_err(EC_LIB_GRPC_INIT, "%s: gRPC server failed to start on %s", __func__,
+ server_address.str().c_str());
/*
* BuildAndStart() may already have posted library
* events. Drain them before unique_ptr dtors run;
@@ -1173,17 +1170,25 @@ static void *grpc_pthread_start(void *arg)
* is empty. REQUEST_NEWRPC*() has not run, so these
* tags are not RpcStateBase.
*/
+ if (cq)
+ cq->Shutdown();
while (cq && cq->Next(&tag, &ok))
n++;
+ if (n > 0)
+ grpc_debug("%s: early shutdown, drained %u CQ events", __func__, n);
- grpc_debug("%s: early shutdown, drained %u CQ events",
- __func__, n);
+ pthread_mutex_lock(&s_server_lock);
+ grpc_state = GRPC_STATE_SHUTDOWN;
+ pthread_cond_signal(&s_server_cond);
+ pthread_mutex_unlock(&s_server_lock);
return NULL;
}
+ pthread_mutex_lock(&s_server_lock);
s_server = server.get();
s_cq = cq.get();
grpc_state = GRPC_STATE_RUNNING;
+ pthread_cond_signal(&s_server_cond);
/* Schedule unary RPC handlers */
REQUEST_NEWRPC(GetCapabilities, NULL);
@@ -1275,6 +1280,22 @@ static int frr_grpc_init(uint port)
return -1;
}
+ /*
+ * Wait for the gRPC server to initialize. The thread sets
+ * grpc_state to GRPC_STATE_RUNNING on success, or to
+ * GRPC_STATE_SHUTDOWN on failure, and signals the condition.
+ */
+ pthread_mutex_lock(&s_server_lock);
+ while (grpc_state == GRPC_STATE_INIT)
+ pthread_cond_wait(&s_server_cond, &s_server_lock);
+
+ if (grpc_state != GRPC_STATE_RUNNING) {
+ flog_err(EC_LIB_GRPC_INIT, "%s: gRPC server failed to start", __func__);
+ pthread_mutex_unlock(&s_server_lock);
+ return -1;
+ }
+ pthread_mutex_unlock(&s_server_lock);
+
return 0;
}
@@ -1349,6 +1370,7 @@ static void frr_grpc_module_very_late_init(struct event *event)
error:
flog_err(EC_LIB_GRPC_INIT, "failed to initialize the gRPC module");
+ raise(SIGTERM);
}
static int frr_grpc_module_late_init(struct event_loop *tm)