| 276 | } |
| 277 | |
| 278 | void |
| 279 | prof_recent_alloc(tsd_t *tsd, edata_t *edata, size_t size, size_t usize) { |
| 280 | cassert(config_prof); |
| 281 | assert(edata != NULL); |
| 282 | prof_tctx_t *tctx = edata_prof_tctx_get(edata); |
| 283 | |
| 284 | malloc_mutex_assert_not_owner(tsd_tsdn(tsd), tctx->tdata->lock); |
| 285 | malloc_mutex_lock(tsd_tsdn(tsd), &prof_recent_alloc_mtx); |
| 286 | prof_recent_alloc_assert_count(tsd); |
| 287 | |
| 288 | /* |
| 289 | * Reserve a new prof_recent_t node if needed. If needed, we release |
| 290 | * the prof_recent_alloc_mtx lock and allocate. Then, rather than |
| 291 | * immediately checking for OOM, we regain the lock and try to make use |
| 292 | * of the reserve node if needed. There are six scenarios: |
| 293 | * |
| 294 | * \ now | no need | need but OOMed | need and allocated |
| 295 | * later \ | | | |
| 296 | * ------------------------------------------------------------ |
| 297 | * no need | (1) | (2) | (3) |
| 298 | * ------------------------------------------------------------ |
| 299 | * need | (4) | (5) | (6) |
| 300 | * |
| 301 | * First, "(4)" never happens, because we don't release the lock in the |
| 302 | * middle if there's no need for a new node; in such cases "(1)" always |
| 303 | * takes place, which is trivial. |
| 304 | * |
| 305 | * Out of the remaining four scenarios, "(6)" is the common case and is |
| 306 | * trivial. "(5)" is also trivial, in which case we'll rollback the |
| 307 | * effect of prof_recent_alloc_prepare() as expected. |
| 308 | * |
| 309 | * "(2)" / "(3)" occurs when the need for a new node is gone after we |
| 310 | * regain the lock. If the new node is successfully allocated, i.e. in |
| 311 | * the case of "(3)", we'll release it in the end; otherwise, i.e. in |
| 312 | * the case of "(2)", we do nothing - we're lucky that the OOM ends up |
| 313 | * doing no harm at all. |
| 314 | * |
| 315 | * Therefore, the only performance cost of the "release lock" -> |
| 316 | * "allocate" -> "regain lock" design is the "(3)" case, but it happens |
| 317 | * very rarely, so the cost is relatively small compared to the gain of |
| 318 | * not having to have the lock order of prof_recent_alloc_mtx above all |
| 319 | * the allocation locks. |
| 320 | */ |
| 321 | prof_recent_t *reserve = NULL; |
| 322 | if (prof_recent_alloc_max_get(tsd) == -1 || |
| 323 | prof_recent_alloc_count < prof_recent_alloc_max_get(tsd)) { |
| 324 | assert(prof_recent_alloc_max_get(tsd) != 0); |
| 325 | malloc_mutex_unlock(tsd_tsdn(tsd), &prof_recent_alloc_mtx); |
| 326 | reserve = prof_recent_allocate_node(tsd_tsdn(tsd)); |
| 327 | malloc_mutex_lock(tsd_tsdn(tsd), &prof_recent_alloc_mtx); |
| 328 | prof_recent_alloc_assert_count(tsd); |
| 329 | } |
| 330 | |
| 331 | if (prof_recent_alloc_max_get(tsd) == 0) { |
| 332 | assert(prof_recent_alloc_is_empty(tsd)); |
| 333 | goto label_rollback; |
| 334 | } |
| 335 |
no test coverage detected