Skip to content

Commit 432383c

Browse files
Merge pull request #307 from asterfusion/fixbug/ASROUTER-1382-v1.4
Fixbug ASROUTER-1382
2 parents ebb7c3c + 2934818 commit 432383c

13 files changed

Lines changed: 404 additions & 69 deletions

File tree

ET2500/vpp-24.02/src/vcl/ldp.c

Lines changed: 63 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
#include <sys/time.h>
3434

3535
#include <vcl/vcl_locked.h>
36+
#include <vcl/vcl_private.h>
3637
#include <vppinfra/time.h>
3738
#include <vppinfra/bitmap.h>
3839
#include <vppinfra/lock.h>
@@ -157,6 +158,57 @@ ldp_worker_get_current (void)
157158
return (ldp->workers + vppcom_worker_index ());
158159
}
159160

161+
/*
162+
* Get the mq epoll fd. The ldp worker ctx is inherited across fork() and
163+
* may hold a stale epfd from the parent or a previous worker generation, so
164+
* prefer the current vcl worker's mqs_epfd which is authoritative.
165+
*/
166+
static inline int
167+
ldp_get_mq_epfd (ldp_worker_ctx_t * ldpw)
168+
{
169+
vcl_worker_t *wrk;
170+
171+
if (vppcom_worker_index () != ~0 &&
172+
(wrk = vcl_worker_get_if_valid (vppcom_worker_index ())) &&
173+
wrk->mqs_epfd > 0)
174+
return wrk->mqs_epfd;
175+
return ldpw->vcl_mq_epfd;
176+
}
177+
178+
/*
179+
* Nest the vcl mq epoll fd into a vep's libc epoll fd, so that mq events
180+
* wake up the app's epoll_wait. The mq_epfd_added flag that used to gate
181+
* this is per-process and inherited across fork(), while libc epfds are
182+
* per-vep and get recreated, so the flag can be stale - just add and
183+
* tolerate EEXIST.
184+
*/
185+
static inline int
186+
ldp_nest_mq_epfd (ldp_worker_ctx_t * ldpw, int epfd, int libc_epfd)
187+
{
188+
struct epoll_event e = { 0 };
189+
int mq_epfd;
190+
191+
if (libc_epfd <= 0 || vppcom_worker_index () == ~0)
192+
return 0;
193+
194+
mq_epfd = ldp_get_mq_epfd (ldpw);
195+
if (mq_epfd <= 0)
196+
return 0;
197+
198+
e.events = EPOLLIN;
199+
e.data.fd = mq_epfd;
200+
if (libc_epoll_ctl (libc_epfd, EPOLL_CTL_ADD, mq_epfd, &e) < 0)
201+
{
202+
if (errno == EEXIST)
203+
return 0;
204+
LDBG (0, "epfd %d, add mq epoll fd %d to libc epoll fd %d failed: "
205+
"errno %d", epfd, mq_epfd, libc_epfd, errno);
206+
return -1;
207+
}
208+
209+
return 1;
210+
}
211+
160212
/*
161213
* RETURN: 0 on success or -1 on error.
162214
* */
@@ -2493,6 +2545,9 @@ epoll_ctl (int epfd, int op, int fd, struct epoll_event *event)
24932545
rv = -1;
24942546
goto done;
24952547
}
2548+
2549+
/* New libc epfd: nest the mq epoll fd so mq events wake the app */
2550+
ldp_nest_mq_epfd (ldp_worker_get_current (), epfd, libc_epfd);
24962551
}
24972552
else if (PREDICT_FALSE (libc_epfd < 0))
24982553
{
@@ -2532,7 +2587,7 @@ ldp_epoll_pwait (int epfd, struct epoll_event *events, int maxevents,
25322587
vls_register_vcl_worker ();
25332588

25342589
ldpw = ldp_worker_get_current ();
2535-
if (epfd == ldpw->vcl_mq_epfd)
2590+
if (epfd == ldp_get_mq_epfd (ldpw))
25362591
return libc_epoll_pwait (epfd, events, maxevents, timeout, sigmask);
25372592

25382593
ep_vlsh = ldp_fd_to_vlsh (epfd);
@@ -2615,7 +2670,7 @@ ldp_epoll_pwait_eventfd (int epfd, struct epoll_event *events,
26152670
vls_register_vcl_worker ();
26162671

26172672
ldpw = ldp_worker_get_current ();
2618-
if (epfd == ldpw->vcl_mq_epfd)
2673+
if (epfd == ldp_get_mq_epfd (ldpw))
26192674
return libc_epoll_pwait (epfd, events, maxevents, timeout, sigmask);
26202675

26212676
ep_vlsh = ldp_fd_to_vlsh (epfd);
@@ -2647,6 +2702,9 @@ ldp_epoll_pwait_eventfd (int epfd, struct epoll_event *events,
26472702
rv = -1;
26482703
goto done;
26492704
}
2705+
2706+
/* New libc epfd: nest the mq epoll fd so mq events wake the app */
2707+
ldp_nest_mq_epfd (ldpw, epfd, libc_epfd);
26502708
}
26512709
if (PREDICT_FALSE (libc_epfd <= 0))
26522710
{
@@ -2657,14 +2715,9 @@ ldp_epoll_pwait_eventfd (int epfd, struct epoll_event *events,
26572715

26582716
if (PREDICT_FALSE (!ldpw->mq_epfd_added))
26592717
{
2660-
struct epoll_event e = { 0 };
2661-
e.events = EPOLLIN;
2662-
e.data.fd = ldpw->vcl_mq_epfd;
2663-
if (libc_epoll_ctl (libc_epfd, EPOLL_CTL_ADD, ldpw->vcl_mq_epfd, &e) <
2664-
0)
2718+
/* Idempotent: fine if the create path above already nested it */
2719+
if (ldp_nest_mq_epfd (ldpw, epfd, libc_epfd) < 0)
26652720
{
2666-
LDBG (0, "epfd %d, add libc mq epoll fd %d to libc epoll fd %d",
2667-
epfd, ldpw->vcl_mq_epfd, libc_epfd);
26682721
rv = -1;
26692722
goto done;
26702723
}
@@ -2700,7 +2753,7 @@ ldp_epoll_pwait_eventfd (int epfd, struct epoll_event *events,
27002753

27012754
for (int i = 0; i < libc_num_ev; i++)
27022755
{
2703-
if (libc_evts[i].data.fd == ldpw->vcl_mq_epfd)
2756+
if (libc_evts[i].data.fd == ldp_get_mq_epfd (ldpw))
27042757
{
27052758
/* We should remove mq epoll fd from events. */
27062759
libc_num_ev--;

ET2500/vpp-24.02/src/vcl/vcl_bapi.c

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -709,15 +709,22 @@ vcl_bapi_app_worker_del (vcl_worker_t * wrk)
709709
{
710710
/* Notify vpp that the worker is going away */
711711
if (wrk->wrk_index == vcl_get_worker_index ())
712-
vcl_bapi_send_app_worker_add_del (0 /* is_add */ );
713-
else
714-
vcl_bapi_send_child_worker_del (wrk);
712+
{
713+
vcl_bapi_send_app_worker_add_del (0 /* is_add */ );
715714

716-
/* Disconnect the binary api */
717-
if (vec_len (vcm->workers) == 1)
718-
vcl_bapi_disconnect_from_vpp ();
719-
else
720-
vl_client_send_disconnect (1 /* vpp should cleanup */ );
715+
/* Disconnect the binary api */
716+
if (vec_len (vcm->workers) == 1)
717+
vcl_bapi_disconnect_from_vpp ();
718+
else
719+
vl_client_send_disconnect (1 /* vpp should cleanup */ );
720+
return;
721+
}
722+
723+
/* Only ask vpp to remove the child worker. The child's api connection
724+
* is reaped by vpp when the child process dies; disconnecting here
725+
* would drop this process' own client registration along with the app
726+
* workers attached to it. */
727+
vcl_bapi_send_child_worker_del (wrk);
721728
}
722729

723730
int

ET2500/vpp-24.02/src/vcl/vcl_locked.c

Lines changed: 102 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
108114
typedef 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
671678
vls_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

688715
static 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)
16741714
static void
16751715
vls_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
16981747
vls_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

17341786
done:
17351787
if (old_sa.sa_flags & SA_SIGINFO)
@@ -1748,12 +1800,17 @@ vls_intercept_sigchld_handler (int signum, siginfo_t * si, void *uc)
17481800
static void
17491801
vls_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

17741837
static 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

Comments
 (0)