|
28 | 28 | #include <functional> |
29 | 29 | #include <memory> |
30 | 30 | #include <mutex> |
| 31 | +#include <shared_mutex> |
31 | 32 | #include <string> |
32 | 33 |
|
33 | 34 | // TASK-036: render_* virtuals now return http_response by value; the |
@@ -234,9 +235,12 @@ class http_resource { |
234 | 235 | * disallow_all, and allow_all all invalidate the cache implicitly, |
235 | 236 | * without any explicit dependency on the mutation API. |
236 | 237 | * |
237 | | - * Thread-safe: a per-resource std::mutex serialises the cache |
238 | | - * fill / read. Once the cache is warm, the lock is uncontended |
239 | | - * on every subsequent call. |
| 238 | + * Thread-safe: a per-resource std::shared_mutex allows concurrent |
| 239 | + * warm-path reads (std::shared_lock) while the cache-fill path |
| 240 | + * takes an exclusive std::unique_lock. Under multi-threaded 405 |
| 241 | + * floods the warm path is fully concurrent; only a stale-mask |
| 242 | + * fill serialises. The methods_allowed_ snapshot is taken inside |
| 243 | + * the lock to eliminate the TOCTOU data race. |
240 | 244 | * |
241 | 245 | * The returned reference is valid until the next mask mutation; |
242 | 246 | * callers must not store it beyond a single dispatch invocation. |
@@ -308,14 +312,15 @@ class http_resource { |
308 | 312 | * are needed, register hooks on the copy separately after construction. |
309 | 313 | * |
310 | 314 | * TASK-058 step 3: cached_allow_mutex_ is non-copyable / non-movable |
311 | | - * (std::mutex has no copy or move). The copy / move special members |
312 | | - * are therefore written by hand and skip the mutex member entirely -- |
313 | | - * the copy / move target gets a freshly default-constructed mutex |
314 | | - * and an invalidated cache (cached_allow_valid_ = false), so the |
315 | | - * next get_allow_header() call on the target rebuilds the cache |
316 | | - * under its own (fresh) lock. This is the correct semantic: copy / |
317 | | - * move is a structural operation; cache state is local to each |
318 | | - * instance and must not be shared by reference. |
| 315 | + * (std::shared_mutex has no copy or move). The copy / move special |
| 316 | + * members are therefore written by hand and skip the mutex member |
| 317 | + * entirely -- the copy / move target gets a freshly |
| 318 | + * default-constructed mutex and an invalidated cache |
| 319 | + * (cached_allow_valid_ = false), so the next get_allow_header() |
| 320 | + * call on the target rebuilds the cache under its own (fresh) lock. |
| 321 | + * This is the correct semantic: copy / move is a structural |
| 322 | + * operation; cache state is local to each instance and must not be |
| 323 | + * shared by reference. |
319 | 324 | **/ |
320 | 325 | http_resource(const http_resource& b) noexcept; |
321 | 326 | http_resource(http_resource&& b) noexcept; |
@@ -361,14 +366,17 @@ class http_resource { |
361 | 366 | // |
362 | 367 | // Thread-safety: the dispatch path can call get_allow_header() |
363 | 368 | // concurrently from multiple MHD worker threads against the same |
364 | | - // resource. The mutex serialises the cache fill / read; once the |
365 | | - // cache is warm, the lock is uncontended on every subsequent 405. |
| 369 | + // resource. std::shared_mutex allows concurrent warm-path reads |
| 370 | + // (std::shared_lock) while the cache-fill (miss/invalidate) path |
| 371 | + // takes an exclusive std::unique_lock. The methods_allowed_ |
| 372 | + // snapshot is taken INSIDE the lock (not before it) to eliminate |
| 373 | + // the TOCTOU data race noted in security-reviewer-iter1-2. |
366 | 374 | // |
367 | 375 | // `mutable` for the same reason as hook_table_: the dispatch path |
368 | 376 | // calls get_allow_header() on a `const http_resource&` (the cache |
369 | 377 | // is logically const-observable -- the visible result is a stable |
370 | 378 | // function of methods_allowed_). |
371 | | - mutable std::mutex cached_allow_mutex_; |
| 379 | + mutable std::shared_mutex cached_allow_mutex_; |
372 | 380 | mutable std::string cached_allow_header_; |
373 | 381 | mutable method_set cached_allow_mask_{}; |
374 | 382 | mutable bool cached_allow_valid_ = false; |
@@ -399,21 +407,21 @@ class http_resource { |
399 | 407 | // table PIMPL (shared_ptr<resource_hook_table>) was added later, growing the |
400 | 408 | // cap to vptr + shared_ptr + method_set + padding. |
401 | 409 | // |
402 | | -// TASK-058 step 3 added a lazy Allow-header cache: std::mutex + |
403 | | -// std::string + method_set + bool. std::mutex is a sizeof~64 storage on |
404 | | -// pthread platforms (libc++/macOS) and ~40 on libstdc++/Linux; std::string |
405 | | -// SBO adds ~24-32; the method_set / bool fields slot into existing padding. |
406 | | -// The new ceiling is the old (3*void* + 2*method_set) plus the new cache |
407 | | -// payload, padded for alignment. See test/bench_sizeof_http_resource.cpp |
408 | | -// for the v1-anchored algebra; the value here documents the |
409 | | -// post-TASK-058 layout shape. |
| 410 | +// TASK-058 step 3 added a lazy Allow-header cache: std::shared_mutex + |
| 411 | +// std::string + method_set + bool. std::shared_mutex is a sizeof~56 |
| 412 | +// storage on pthread platforms (libc++/macOS) and ~56 on libstdc++/Linux |
| 413 | +// (similar to std::mutex); std::string SBO adds ~24-32; the method_set / |
| 414 | +// bool fields slot into existing padding. The new ceiling is the old |
| 415 | +// (3*void* + 2*method_set) plus the new cache payload, padded for |
| 416 | +// alignment. See test/bench_sizeof_http_resource.cpp for the v1-anchored |
| 417 | +// algebra; the value here documents the post-TASK-058 layout shape. |
410 | 418 | static_assert(sizeof(http_resource) <= |
411 | 419 | sizeof(void*) * 3 + sizeof(method_set) * 2 |
412 | | - + sizeof(std::mutex) + sizeof(std::string) |
| 420 | + + sizeof(std::shared_mutex) + sizeof(std::string) |
413 | 421 | + sizeof(method_set) + sizeof(bool) * 8, |
414 | 422 | "http_resource should be approximately vptr + shared_ptr + " |
415 | 423 | "method_set (TASK-051) + Allow-header cache " |
416 | | - "(mutex + string + method_set + bool) after TASK-058 step 3"); |
| 424 | + "(shared_mutex + string + method_set + bool) after TASK-058 step 3"); |
417 | 425 |
|
418 | 426 | } // namespace httpserver |
419 | 427 | #endif // SRC_HTTPSERVER_HTTP_RESOURCE_HPP_ |
0 commit comments