moonchen commented on issue #13358:
URL: 
https://github.com/apache/trafficserver/issues/13358#issuecomment-5029807164

   I think it's worth restating how ET_NET threading is supposed to work here, 
because I believe these crashes are a symptom of that model being violated 
rather than something to paper over at the queue level:
   
   1. Each client connection and its transactions stay on one ET_NET thread.
   2. The origin connections for those transactions run on that same ET_NET 
thread.
   3. This is maintained by scheduling continuations back onto that thread — 
`adjust_thread` for the cache-open handoff, `migrateToCurrentThread` for origin 
connections reused from the pool.
   4. `nh->mutex` is a per-ET_NET-thread lock, and a stack can come to hold it 
two ways:
      - **Stacks originating in the NetHandler** hold it because the NetHandler 
locks it directly — the net poll (`waitForActivity`) and the `InactivityCop` 
(whose continuation mutex *is* `nh->mutex`) take it and hold it reentrantly 
across a whole batch.
      - **Stacks originating in the event loop** hold whatever mutex the 
continuation carries — `process_event` locks `e->mutex == continuation->mutex` 
before `handleEvent`. For the session/SM continuations in these stacks, that 
carried mutex is the **session/vc** mutex, *not* `nh->mutex`, so they must 
acquire `nh->mutex` **fresh** when they reach `add_to_*_queue`. (`HttpSM` never 
locks it directly — it reaches it through `release → add_to_keep_alive_queue`.)
   5. AIO completions are steered back to the ET_NET thread that spawned them — 
reads via the AIO op's stored thread (`io.thread`, dispatched by 
`op->thread->schedule_imm`), aggregated writes via the CacheVC's 
`thread_affinity`, set in `new_CacheVC` to `cont->mutex->thread_holding`.
   
   **What the crash stacks tell us — and what they don't.** All three abort in 
a *fresh* `MUTEX_TRY_LOCK(nh->mutex)` inside `add_to_*_queue`, reached from a 
continuation dispatched via `EThread::process_event` (the event loop, not the 
poll). `Mutex_trylock` succeeds reentrantly when the current thread already 
holds the lock, so a failure there tells us exactly one thing: **at that 
instant, some thread other than the one running the crashing stack holds 
`nh->mutex`.**
   
   It does *not* tell us whether the crashing continuation is even on the right 
thread. It can be either of these:
   
   - **(a) the crashing stack is on the wrong thread** — the continuation for 
an `nh->thread`-owned vc got scheduled elsewhere, tries `nh->mutex`, and fails 
because the *owner* is legitimately holding its own lock in its poll. The lock 
holder is behaving correctly; the bug is that the continuation isn't on its 
thread.
   - **(b) the crashing stack is on the owner, but a foreign thread holds the 
lock** — something grabbed `nh->mutex` off-thread.
   
   The follow-up report — the crash persisting after adding `if (this->thread 
!= this_ethread()) return true;` to `add_to_active_queue` — is evidence that 
(b) occurs at least for the H2 `create_stream` path (the guard short-circuits 
(a), yet it still aborted). I wouldn't assume that for the HTTP/1.1 and 
H2-teardown stacks; those could well be (a). Either way the root cause is a 
continuation on the wrong thread, and the `add_to_*_queue` asserts are a 
downstream tripwire for it — I'd rather find and fix the mis-scheduling than 
weaken the assert.
   
   **Finding the culprit.** Rather than reason further from the stacks, I think 
the productive next step is to instrument this and let it tell us which path is 
at fault. A few options, roughly in order of effort:
   
   - **Rule out (a) from the existing cores.** At the aborting frame, compare 
the vc's owner thread — `this->thread` (== `nh->thread`) — to the `EThread` 
actually executing (the `this` of the `EThread::execute_regular` frame). If 
they differ, it's (a), and the continuation was mis-scheduled.
   - **Identify the holder for (b).** `nh->mutex->thread_holding` gives the 
holding thread (present in release builds); a debug/lock-debug build also 
records the acquiring file/line in `nh->mutex->srcloc`. Caveat: 
`thread_holding` is cleared on release, so a *transient* holder can read back 
as null in a post-mortem core (this is what @bryancall observed in #5950). If 
the holder is the owner in its poll, it's a long hold and will still be 
populated (pointing at `waitForActivity`).
   - **Trace it live.** Finding *where* the wrong thread takes the lock means 
catching acquisitions across all the lock sites, not just the queue functions — 
the holder in (b) could be any path that touches `nh->mutex`. Two ways to do 
that without a lot of ceremony:
     - **`bpftrace`, no rebuild** (attractive since this reproduces in 
production). The ProxyMutex lock helpers are inlined, so trace the underlying 
`pthread_mutex_lock`/`pthread_mutex_trylock` — they see every acquisition 
regardless of which variable named the mutex. Keep the "home thread" in a BPF 
map keyed by the mutex address (seed it from a NetHandler symbol, e.g. 
`waitForActivity`, so only NetHandler mutexes are tracked), and flag + capture 
a stack whenever a non-home tid acquires one. It's heavy (those pthread calls 
are hot process-wide), so use a short capture window and filter to tracked 
addresses in-kernel.
     - **A cheaper, probabilistic sample:** attach to the hot actor methods 
(`UnixNetVConnection` / session / SM) and check `this->thread` vs 
`this->nh->mutex->thread_holding` (or just the running tid vs `this->thread`). 
That samples the "actor running off its owner thread" condition directly, at 
much lower cost, without touching the lock primitives.
   
   @wuxcer — if you can try one of these (the `bpftrace` route needs no 
rebuild), the thread ids at the point of failure — the executing thread, 
`vc->thread`, and `thread_holding` — plus a stack for the off-home acquirer 
should tell us whether we're chasing (a) or (b), and which path put the 
continuation on the wrong thread.
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to