@@ -105,12 +105,19 @@ typedef struct vcl_locked_session_
105105 uword * vcl_wrk_index_to_session_index ; /**< map vcl wrk to session */
106106} vcl_locked_session_t ;
107107
108+ typedef struct vls_pending_wrk_cleanup_
109+ {
110+ u32 wrk_index ; /**< vcl wrk index of exited child */
111+ pid_t pid ; /**< pid the child had when queued */
112+ } vls_pending_wrk_cleanup_t ;
113+
108114typedef struct vls_worker_
109115{
110116 clib_rwlock_t sh_to_vlsh_table_lock ; /**< ht rwlock with mt workers */
111117 vcl_locked_session_t * vls_pool ; /**< pool of vls session */
112118 uword * sh_to_vlsh_table ; /**< map from vcl sh to vls sh */
113- u32 * pending_vcl_wrk_cleanup ; /**< child vcl wrks to cleanup */
119+ vls_pending_wrk_cleanup_t * pending_vcl_wrk_cleanup ; /**< child vcl wrks
120+ to cleanup */
114121 u32 vcl_wrk_index ; /**< if 1:1 map vls to vcl wrk */
115122} vls_worker_t ;
116123
@@ -667,22 +674,42 @@ vls_listener_wrk_is_active (vcl_locked_session_t * vls, u32 wrk_index)
667674 return (is_set == 1 );
668675}
669676
670- static void
677+ static int
671678vls_listener_wrk_start_listen (vcl_locked_session_t * vls , u32 wrk_index )
672679{
673680 vcl_worker_t * wrk ;
674681 vcl_session_t * ls ;
682+ int rv ;
675683
676684 wrk = vcl_worker_get (wrk_index );
677685 ls = vcl_session_get (wrk , vls -> session_index );
678686
679687 /* Listen request already sent */
680688 if (ls -> flags & VCL_SESSION_F_PENDING_LISTEN )
681- return ;
689+ return 0 ;
682690
683691 vcl_send_session_listen (wrk , ls );
684692
693+ /* Wait synchronously for the bound notification. If listen fails and
694+ * we fail to notice, the worker is never added to the listener's
695+ * workers bitmap in vpp and accepts are silently routed to other
696+ * (possibly dead) workers. */
697+ rv = vppcom_wait_for_session_state_change (ls -> session_index ,
698+ VCL_STATE_LISTEN ,
699+ 5 /* timeout (s) */ );
700+ if (rv )
701+ {
702+ /* Clear pending flag set by vcl_send_session_listen or a retry
703+ * would be short-circuited forever */
704+ ls -> flags &= ~VCL_SESSION_F_PENDING_LISTEN ;
705+ VERR ("worker %u listen failed for session %u state %s: %d" ,
706+ wrk_index , ls -> session_index ,
707+ vcl_session_state_str (ls -> session_state ), rv );
708+ return -1 ;
709+ }
710+
685711 vls_listener_wrk_set (vls , wrk_index , 1 /* is_active */ );
712+ return 0 ;
686713}
687714
688715static void
@@ -1326,18 +1353,31 @@ vls_mp_checks (vcl_locked_session_t * vls, int is_add)
13261353 break ;
13271354
13281355 /* Register worker as listener */
1329- vls_listener_wrk_start_listen (vls , vls -> vcl_wrk_index );
1356+ if (vls_listener_wrk_start_listen (vls , vls -> vcl_wrk_index ))
1357+ break ;
13301358
13311359 /* If owner worker did not attempt to accept/xpoll on the session,
13321360 * force a listen stop for it, since it may not be interested in
13331361 * accepting new sessions.
13341362 * This is pretty much a hack done to give app workers the illusion
13351363 * that it is fine to listen and not accept new sessions for a
13361364 * given listener. Without it, we would accumulate unhandled
1337- * accepts on the passive worker message queue. */
1365+ * accepts on the passive worker message queue.
1366+ * The active bitmap lives in shared memory and is indexed by vcl
1367+ * worker slot, so bits set by previous process generations survive
1368+ * worker slot reuse. Trust it only if the worker currently holding
1369+ * the owner slot is still alive. */
13381370 owner_wrk = vls_shared_get_owner (vls );
1339- if (!vls_listener_wrk_is_active (vls , owner_wrk ))
1340- vls_listener_wrk_stop_listen (vls , owner_wrk );
1371+ if (owner_wrk != vls -> vcl_wrk_index )
1372+ {
1373+ vcl_worker_t * owner = vcl_worker_get_if_valid (owner_wrk );
1374+
1375+ if (owner && kill (owner -> current_pid , 0 ) >= 0
1376+ && vls_listener_wrk_is_active (vls , owner_wrk ))
1377+ break ;
1378+ if (owner )
1379+ vls_listener_wrk_stop_listen (vls , owner_wrk );
1380+ }
13411381 break ;
13421382 default :
13431383 break ;
@@ -1674,19 +1714,28 @@ vls_cleanup_forked_child (vcl_worker_t * wrk, vcl_worker_t * child_wrk)
16741714static void
16751715vls_handle_pending_wrk_cleanup (void )
16761716{
1677- u32 * wip ;
1717+ vls_pending_wrk_cleanup_t * p ;
16781718 vcl_worker_t * child_wrk , * wrk ;
16791719 vls_worker_t * vls_wrk = vls_worker_get_current ();
16801720
16811721 if (PREDICT_TRUE (vec_len (vls_wrk -> pending_vcl_wrk_cleanup ) == 0 ))
16821722 return ;
16831723
16841724 wrk = vcl_worker_get_current ();
1685- vec_foreach (wip , vls_wrk -> pending_vcl_wrk_cleanup )
1725+ vec_foreach (p , vls_wrk -> pending_vcl_wrk_cleanup )
16861726 {
1687- child_wrk = vcl_worker_get_if_valid (* wip );
1727+ child_wrk = vcl_worker_get_if_valid (p -> wrk_index );
16881728 if (!child_wrk )
16891729 continue ;
1730+ /* The worker slot may have been reused by a newer process since the
1731+ * entry was queued. Clean up only if it still belongs to the child
1732+ * that actually exited. */
1733+ if (child_wrk -> current_pid != p -> pid )
1734+ {
1735+ VWRN ("skip cleanup of wrk %u: slot reused by pid %u" ,
1736+ p -> wrk_index , child_wrk -> current_pid );
1737+ continue ;
1738+ }
16901739 vls_cleanup_forked_child (wrk , child_wrk );
16911740 }
16921741 vec_reset_length (vls_wrk -> pending_vcl_wrk_cleanup );
@@ -1698,6 +1747,7 @@ static void
16981747vls_intercept_sigchld_handler (int signum , siginfo_t * si , void * uc )
16991748{
17001749 vcl_worker_t * wrk , * child_wrk ;
1750+ vls_pending_wrk_cleanup_t * pending ;
17011751 vls_worker_t * vls_wrk ;
17021752
17031753 if (vcl_get_worker_index () == ~0 )
@@ -1729,7 +1779,9 @@ vls_intercept_sigchld_handler (int signum, siginfo_t * si, void *uc)
17291779 * So move child wrk cleanup from sighandler to vls_epoll_wait/vls_select.
17301780 */
17311781 vls_wrk = vls_worker_get_current ();
1732- vec_add1 (vls_wrk -> pending_vcl_wrk_cleanup , child_wrk -> wrk_index );
1782+ vec_add2 (vls_wrk -> pending_vcl_wrk_cleanup , pending , 1 );
1783+ pending -> wrk_index = child_wrk -> wrk_index ;
1784+ pending -> pid = child_wrk -> current_pid ;
17331785
17341786done :
17351787 if (old_sa .sa_flags & SA_SIGINFO )
@@ -1748,12 +1800,17 @@ vls_intercept_sigchld_handler (int signum, siginfo_t * si, void *uc)
17481800static void
17491801vls_incercept_sigchld ()
17501802{
1751- struct sigaction sa ;
1752- if (old_sa .sa_sigaction )
1753- {
1754- VDBG (0 , "have intercepted sigchld" );
1755- return ;
1756- }
1803+ struct sigaction sa , cur ;
1804+
1805+ /* The handler restores old_sa after the first SIGCHLD, so re-install on
1806+ * every fork or exits of later children would not queue a cleanup. But
1807+ * if our handler is still installed (no SIGCHLD since the last fork),
1808+ * keep the current old_sa: overwriting it with our own handler would
1809+ * make the handler recurse into itself when the next SIGCHLD fires. */
1810+ if (sigaction (SIGCHLD , 0 , & cur ) == 0 &&
1811+ cur .sa_sigaction == vls_intercept_sigchld_handler )
1812+ return ;
1813+
17571814 clib_memset (& sa , 0 , sizeof (sa ));
17581815 sa .sa_sigaction = vls_intercept_sigchld_handler ;
17591816 sa .sa_flags = SA_SIGINFO ;
@@ -1769,6 +1826,12 @@ vls_app_pre_fork (void)
17691826{
17701827 vls_incercept_sigchld ();
17711828 vcl_flush_mq_events ();
1829+ /* Clean up any pending forked child workers before forking again.
1830+ * Without this, a previous child that exited but wasn't cleaned up
1831+ * yet (pending in the SIGCHLD handler queue) will be lost because
1832+ * the new fork overwrites wrk->forked_child, causing the SIGCHLD
1833+ * handler to skip the old child's cleanup. */
1834+ vls_handle_pending_wrk_cleanup ();
17721835}
17731836
17741837static void
@@ -1789,11 +1852,23 @@ vls_app_fork_child_handler (void)
17891852 /*
17901853 * Allocate and register vcl worker with vpp
17911854 */
1792- if (vppcom_worker_register ())
1793- {
1794- VERR ("couldn't register new worker!" );
1795- return ;
1796- }
1855+ {
1856+ int n_tries = 0 ;
1857+
1858+ while (vppcom_worker_register ())
1859+ {
1860+ VERR ("couldn't register new worker (attempt %u)!" , n_tries + 1 );
1861+ if (++ n_tries >= 5 )
1862+ {
1863+ VERR ("worker registration failed, giving up" );
1864+ /* Unblock the parent spinning on the forking flag, then exit
1865+ * and let the app (e.g., nginx master) respawn the worker */
1866+ vcm -> forking = 0 ;
1867+ _exit (1 );
1868+ }
1869+ usleep (200e3 );
1870+ }
1871+ }
17971872
17981873 /*
17991874 * Allocate/initialize vls worker and share sessions
@@ -1831,7 +1906,11 @@ vls_app_exit (void)
18311906 /* Handle pending wrk cleanup */
18321907 vls_handle_pending_wrk_cleanup ();
18331908
1834- /* Unshare the sessions. VCL will clean up the worker */
1909+ /* Unshare the sessions. VCL will clean up the worker: vppcom_app_exit,
1910+ * registered earlier via atexit, runs right after this handler and does
1911+ * vcl_worker_cleanup (current, notify_vpp=1). Cleaning the vcl worker
1912+ * here as well would leave vppcom_app_exit with worker index ~0 and an
1913+ * out-of-bounds pool access. */
18351914 vls_unshare_vcl_worker_sessions (vcl_worker_get_current ());
18361915 vls_worker_free (wrk );
18371916}
0 commit comments