void StaticMetaBase::reserve(EntryID* id) { auto& meta = *this; ThreadEntry* threadEntry = (*threadEntry_)(); size_t prevCapacity = threadEntry->getElementsCapacity(); uint32_t idval = id->getOrAllocate(meta); if (prevCapacity > idval) { return; } size_t newCapacity; ElementWrapper* reallocated = reallocate(threadEntry, idval, newCapacity); // Success, update the entry { std::lock_guard<std::mutex> g(meta.lock_); if (prevCapacity == 0) { meta.push_back(threadEntry); } if (reallocated) { /* * Note: we need to hold the meta lock when copying data out of * the old vector, because some other thread might be * destructing a ThreadLocal and writing to the elements vector * of this thread. */ if (prevCapacity != 0) { memcpy( reallocated, threadEntry->elements, sizeof(*reallocated) * prevCapacity); } std::swap(reallocated, threadEntry->elements); } for (size_t i = prevCapacity; i < newCapacity; i++) { threadEntry->elements[i].node.initZero(threadEntry, i); } threadEntry->setElementsCapacity(newCapacity); } free(reallocated); }
void StaticMetaBase::destroy(EntryID* ent) { try { auto& meta = *this; // Elements in other threads that use this id. std::vector<ElementWrapper> elements; { SharedMutex::WriteHolder wlock(nullptr); if (meta.strict_) { /* * In strict mode, the logic guarantees per-thread instances are * destroyed by the moment ThreadLocal<> dtor returns. * In order to achieve that, we should wait until concurrent * onThreadExit() calls (that might acquire ownership over per-thread * instances in order to destroy them) are finished. */ wlock = SharedMutex::WriteHolder(meta.accessAllThreadsLock_); } { std::lock_guard<std::mutex> g(meta.lock_); uint32_t id = ent->value.exchange(kEntryIDInvalid); if (id == kEntryIDInvalid) { return; } auto& node = meta.head_.elements[id].node; while (!node.empty()) { auto* next = node.getNext(); next->eraseZero(); ThreadEntry* e = next->parent; auto elementsCapacity = e->getElementsCapacity(); if (id < elementsCapacity && e->elements[id].ptr) { elements.push_back(e->elements[id]); /* * Writing another thread's ThreadEntry from here is fine; * the only other potential reader is the owning thread -- * from onThreadExit (which grabs the lock, so is properly * synchronized with us) or from get(), which also grabs * the lock if it needs to resize the elements vector. * * We can't conflict with reads for a get(id), because * it's illegal to call get on a thread local that's * destructing. */ e->elements[id].ptr = nullptr; e->elements[id].deleter1 = nullptr; e->elements[id].ownsDeleter = false; } } meta.freeIds_.push_back(id); } } // Delete elements outside the locks. for (ElementWrapper& elem : elements) { if (elem.dispose(TLPDestructionMode::ALL_THREADS)) { elem.cleanup(); } } } catch (...) { // Just in case we get a lock error or something anyway... LOG(WARNING) << "Destructor discarding an exception that was thrown."; } }
uint32_t StaticMetaBase::elementsCapacity() const { ThreadEntry* threadEntry = (*threadEntry_)(); return FOLLY_LIKELY(!!threadEntry) ? threadEntry->getElementsCapacity() : 0; }