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)