CachedUnit lookupUnitNonRepoAuth(StringData* requestedPath, const struct stat& statInfo) { if (strstr(requestedPath->data(), "://") != nullptr) { // URL-based units are not currently cached in memory, but the Repo still // caches them on disk. return createUnitFromUrl(requestedPath); } // The string we're using as a key must be static, because we're using it as // a key in the cache (across requests). auto const path = makeStaticString( // XXX: it seems weird we have to do this even though we already ran // resolveVmInclude. (requestedPath->data()[0] == '/' ? requestedPath : String(SourceRootInfo::GetCurrentSourceRoot()) + StrNR(requestedPath) ).get() ); NonRepoUnitCache::accessor acc; if (!s_nonRepoUnitCache.insert(acc, path)) { if (!isChanged(acc->second, statInfo)) { return acc->second.cachedUnit; } } /* * NB: the new-unit creation path is here, and is done while holding the tbb * lock on s_nonRepoUnitCache. This was originally done deliberately to * avoid wasting time in the compiler (during server startup, many requests * hit the same code initial paths that are shared, and would all be * compiling the same files). It's not 100% clear if this is the best way to * handle that idea, though (tbb locks spin aggressively and are expected to * be low contention). */ /* * Don't cache if createNewUnit returns an empty CachedUnit---we'll need to * try again anyway if someone tries to load this path, since it might exist * later. * * If there was a unit for this path already, we need to put it on the * Treadmill for eventual reclaimation. We can't delete it immediately * because other requests may still be using it. */ auto const cu = createUnitFromFile(path); if (auto const oldUnit = acc->second.cachedUnit.unit) { Treadmill::enqueue([oldUnit] { reclaimUnit(oldUnit); }); } acc->second.cachedUnit = cu; acc->second.mtime = statInfo.st_mtim; acc->second.ino = statInfo.st_ino; acc->second.devId = statInfo.st_dev; return cu; }
CachedUnit loadUnitNonRepoAuth(StringData* requestedPath, const struct stat* statInfo, OptLog& ent, const Native::FuncTable& nativeFuncs, const RepoOptions& options, FileLoadFlags& flags) { LogTimer loadTime("load_ms", ent); if (strstr(requestedPath->data(), "://") != nullptr) { // URL-based units are not currently cached in memory, but the Repo still // caches them on disk. return createUnitFromUrl(requestedPath, nativeFuncs, flags); } rqtrace::EventGuard trace{"WRITE_UNIT"}; // The string we're using as a key must be static, because we're using it as // a key in the cache (across requests). auto const path = makeStaticString( // XXX: it seems weird we have to do this even though we already ran // resolveVmInclude. (FileUtil::isAbsolutePath(requestedPath->toCppString()) ? String{requestedPath} : String(SourceRootInfo::GetCurrentSourceRoot()) + StrNR(requestedPath) ).get() ); auto const rpath = [&] () -> const StringData* { if (RuntimeOption::CheckSymLink) { std::string rp = StatCache::realpath(path->data()); if (rp.size() != 0) { if (rp.size() != path->size() || memcmp(rp.data(), path->data(), rp.size())) { return makeStaticString(rp); } } } return path; }(); Stream::Wrapper* w = nullptr; auto& cache = getNonRepoCache(rpath, w); assertx( !w || &cache != &s_nonRepoUnitCache || !RuntimeOption::EvalUnixServerQuarantineUnits ); // Freeing a unit while holding the tbb lock would cause a rank violation when // recycle-tc is enabled as reclaiming dead functions requires that the code // and metadata locks be acquired. Unit* releaseUnit = nullptr; SCOPE_EXIT { if (releaseUnit) delete releaseUnit; }; auto const updateAndUnlock = [] (auto& cachedUnit, auto p) { auto old = cachedUnit.update_and_unlock(std::move(p)); if (old) { // We don't need to do anything explicitly; the copy_ptr // destructor will take care of it. Treadmill::enqueue([unit_to_delete = std::move(old)] () {}); } }; auto cuptr = [&] { NonRepoUnitCache::const_accessor rpathAcc; cache.insert(rpathAcc, rpath); auto& cachedUnit = rpathAcc->second.cachedUnit; if (auto const tmp = cachedUnit.copy()) { if (!isChanged(tmp, statInfo, options)) { flags = FileLoadFlags::kHitMem; if (ent) ent->setStr("type", "cache_hit_readlock"); return tmp; } } cachedUnit.lock_for_update(); try { if (auto const tmp = cachedUnit.copy()) { if (!isChanged(tmp, statInfo, options)) { cachedUnit.unlock(); flags = FileLoadFlags::kWaited; if (ent) ent->setStr("type", "cache_hit_writelock"); return tmp; } if (ent) ent->setStr("type", "cache_stale"); } else { if (ent) ent->setStr("type", "cache_miss"); } trace.finish(); auto const cu = createUnitFromFile(rpath, &releaseUnit, w, ent, nativeFuncs, options, flags); auto const isICE = cu.unit && cu.unit->isICE(); auto p = copy_ptr<CachedUnitWithFree>(cu, statInfo, isICE, options); // Don't cache the unit if it was created in response to an internal error // in ExternCompiler. Such units represent transient events. if (UNLIKELY(isICE)) { cachedUnit.unlock(); return p; } updateAndUnlock(cachedUnit, p); return p; } catch (...) { cachedUnit.unlock(); throw; } }(); auto const ret = cuptr->cu; if (!ret.unit || !ret.unit->isICE()) { if (path != rpath) { NonRepoUnitCache::const_accessor pathAcc; cache.insert(pathAcc, path); if (pathAcc->second.cachedUnit.get().get() != cuptr) { auto& cachedUnit = pathAcc->second.cachedUnit; cachedUnit.lock_for_update(); updateAndUnlock(cachedUnit, std::move(cuptr)); } } } return ret; }