ocsp: use Bravo lock for cached responses - #13490
Conversation
There was a problem hiding this comment.
Pull request overview
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Improve OCSP stapling cache concurrency by replacing per-certificate mutex serialization with a Bravo reader-writer lock, reducing TLS handshake hot-path contention while keeping cache updates exclusive.
Changes:
- Replace
ink_mutexincertinfowithts::bravo::shared_mutexand adopt shared/exclusive locking patterns. - Copy cached OCSP DER under a shared lock, then perform OpenSSL/BoringSSL handoff outside the critical section.
- Update refresh scanner (
ocsp_update) to use shared reads for staleness checks.
|
Thank you! |
4ee0aee to
7388915
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.
Suppressed comments (1)
src/iocore/net/OCSPStapling.cc:1451
- In the OpenSSL (non-BoringSSL) path,
resp_derlen,required_capacity, andis_response_availableare declared without initialization and only conditionally assigned inside the loop. Even though the control flow ensures they’re set before use, this pattern can trigger-Wmaybe-uninitialized/static-analysis warnings and makes the loop harder to reason about. Initialize them defensively at declaration to keep the code warning-free and clearer.
unsigned char *p = nullptr;
unsigned int resp_capacity = 0;
unsigned int resp_derlen;
while (true) {
4942f92 to
8494cc5
Compare
|
The retry loop is correct, though it took some staring to convince myself. Main suggestion: use That is the codebase's own idiom for exactly this shape — read-mostly state with hot readers. Two reasons beyond consistency:
Possible crossed wire: your reply to Copilot above says the writer path "now explicitly uses Minor: the description says certinfo is allocated with |
OCSP stapling serializes TLS handshake readers with refresh scans on a per-certificate mutex. Busy certificates therefore pay unnecessary lock contention on the handshake hot path. This patch uses the annotated Bravo reader-writer lock so handshake and refresh readers can proceed concurrently while cache updates remain exclusive. It keeps prefetched state immutable and allocates the OpenSSL destination outside the shared lock. The cached response is copied once after its size is revalidated.
8494cc5 to
327715d
Compare
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 1 out of 1 changed files in this pull request and generated no new comments.
Suppressed comments (1)
src/iocore/net/OCSPStapling.cc:1438
- In the BoringSSL path,
SiteThrottledError(...)is invoked while holdingcinf->resp_mutex(shared lock). This does log formatting + throttling and can take additional internal locks; doing that while holding the OCSP cache lock increases contention on the TLS handshake hot path and can amplify lock-ordering risk. Consider computing the availability predicate under the shared lock, then releasing the lock before logging/returning.
ts::bravo::shared_lock<ts::bravo::shared_mutex> lock(cinf->resp_mutex);
time_t current_time = time(nullptr);
if (cinf->resp_derlen == 0 || cinf->is_expire || (cinf->expire_time < current_time && !cinf->is_prefetched)) {
SiteThrottledError("ssl_callback_ocsp_stapling: failed to get certificate status for %s", cinf->certname);
return SSL_TLSEXT_ERR_NOACK;
}
cmcfarlen
left a comment
There was a problem hiding this comment.
Lets make sure the squash commit message says we used bravo lock since it says std::shared_mutex atm. Thanks again for getting this across the finish line!
OCSP stapling serializes TLS handshake readers with refresh scans on a per-certificate mutex. Busy certificates therefore pay unnecessary lock contention on the handshake hot path. This patch uses the annotated Bravo reader-writer lock so handshake and refresh readers can proceed concurrently while cache updates remain exclusive. It keeps prefetched state immutable and allocates the OpenSSL destination outside the shared lock. The cached response is copied once after its size is revalidated. Co-authored-by: Craig Taylor <cmtaylor@apple.com> (cherry picked from commit 14ce04d)
|
Cherry-picked to the 10.2.x branch as 25a5858 for the 10.2.0 release. |
Originally authored by Craig Taylor (@c-taylor) in #13226. This carries his work forward on current master and preserves his commit authorship.
Replace the per-certinfo ink_mutex with a ts::bravo::shared_mutex so readers on the TLS handshake hot path (ssl_callback_ocsp_stapling) and the refresh scan (ocsp_update) can run concurrently; only the cache update takes an exclusive lock. The OpenSSL handshake reader now allocates outside the shared lock, then revalidates the response size and copies the DER staple directly from the cache while locked, avoiding an extra full copy. BoringSSL copies the cached response directly while holding the shared lock.
The original work also fixed three pre-existing bugs. Those companion fixes have since landed on master and are preserved by this rebase:
This is a rework of #13097 to separate into 2x independent PRs.