2 +----------------------------------------------------------------------+
4 +----------------------------------------------------------------------+
5 | Copyright (c) 2010-present Facebook, Inc. (http://www.facebook.com) |
6 +----------------------------------------------------------------------+
7 | This source file is subject to version 3.01 of the PHP license, |
8 | that is bundled with this package in the file LICENSE, and is |
9 | available through the world-wide-web at the following url: |
10 | http://www.php.net/license/3_01.txt |
11 | If you did not receive a copy of the PHP license and are unable to |
12 | obtain it through the world-wide-web, please send a note to |
13 | license@php.net so we can mail you a copy immediately. |
14 +----------------------------------------------------------------------+
16 #include "hphp/runtime/base/unit-cache.h"
18 #include <sys/types.h>
25 #include <folly/ScopeGuard.h>
26 #include <folly/portability/Fcntl.h>
28 #include "hphp/util/assertions.h"
29 #include "hphp/util/rank.h"
30 #include "hphp/util/mutex.h"
31 #include "hphp/util/process.h"
32 #include "hphp/util/struct-log.h"
33 #include "hphp/util/timer.h"
34 #include "hphp/runtime/base/system-profiler.h"
35 #include "hphp/runtime/base/runtime-option.h"
36 #include "hphp/runtime/base/zend-string.h"
37 #include "hphp/runtime/base/string-util.h"
38 #include "hphp/runtime/base/file-util.h"
39 #include "hphp/runtime/base/plain-file.h"
40 #include "hphp/runtime/base/stat-cache.h"
41 #include "hphp/runtime/base/stream-wrapper-registry.h"
42 #include "hphp/runtime/base/file-stream-wrapper.h"
43 #include "hphp/runtime/base/program-functions.h"
44 #include "hphp/runtime/base/rds.h"
45 #include "hphp/runtime/server/cli-server.h"
46 #include "hphp/runtime/server/source-root-info.h"
47 #include "hphp/runtime/vm/debugger-hook.h"
48 #include "hphp/runtime/vm/extern-compiler.h"
49 #include "hphp/runtime/vm/repo.h"
50 #include "hphp/runtime/vm/runtime.h"
51 #include "hphp/runtime/vm/treadmill.h"
54 #define st_mtim st_mtimespec
55 #define st_ctim st_ctimespec
60 //////////////////////////////////////////////////////////////////////
64 //////////////////////////////////////////////////////////////////////
66 using OptLog
= folly::Optional
<StructuredLogEntry
>;
69 LogTimer(const char* name
, OptLog
& ent
)
72 , m_start(m_ent
? Timer::GetThreadCPUTimeNanos() : -1)
74 ~LogTimer() { stop(); }
77 if (m_start
== -1) return;
79 auto const elapsed
= Timer::GetThreadCPUTimeNanos() - m_start
;
80 m_ent
->setInt(m_name
, elapsed
/ 1000);
91 CachedUnit() = default;
92 explicit CachedUnit(Unit
* unit
, size_t rdsBitId
)
97 Unit
* unit
{nullptr}; // null if there is no Unit for this path
98 size_t rdsBitId
{-1u}; // id of the RDS bit for whether the Unit is included
101 //////////////////////////////////////////////////////////////////////
102 // RepoAuthoritative mode unit caching
105 * In RepoAuthoritative mode, loaded units are never unloaded, we
106 * don't support symlink chasing, you can't include urls, and files
107 * are never changed, which makes the code here significantly simpler.
108 * Because of this it pays to keep it separate from the other cases so
109 * they don't need to be littered with RepoAuthoritative checks.
112 using RepoUnitCache
= RankedCHM
<
113 const StringData
*, // must be static
115 StringDataHashCompare
,
118 RepoUnitCache s_repoUnitCache
;
120 CachedUnit
lookupUnitRepoAuth(const StringData
* path
) {
121 path
= makeStaticString(path
);
123 RepoUnitCache::accessor acc
;
124 if (!s_repoUnitCache
.insert(acc
, path
)) {
130 * Insert path. Find the Md5 for this path, and then the unit for
131 * this Md5. If either aren't found we return the
132 * default-constructed cache entry.
134 * NB: we're holding the CHM lock on this bucket while we're doing
138 if (Repo::get().findFile(path
->data(),
139 RuntimeOption::SourceRoot
,
140 md5
) == RepoStatus::error
) {
144 acc
->second
.unit
= Repo::get().loadUnit(path
->data(), md5
).release();
145 if (acc
->second
.unit
) {
146 acc
->second
.rdsBitId
= rds::allocBit();
149 s_repoUnitCache
.erase(acc
);
155 //////////////////////////////////////////////////////////////////////
156 // Non-repo mode unit caching
158 struct CachedUnitWithFree
{
159 CachedUnitWithFree() = delete;
160 explicit CachedUnitWithFree(const CachedUnitWithFree
&) = delete;
161 CachedUnitWithFree
& operator=(const CachedUnitWithFree
&) = delete;
163 explicit CachedUnitWithFree(const CachedUnit
& src
) : cu(src
) {}
164 ~CachedUnitWithFree() {
165 if (auto oldUnit
= cu
.unit
) {
166 Treadmill::enqueue([oldUnit
] { delete oldUnit
; });
172 struct CachedUnitNonRepo
{
173 std::shared_ptr
<CachedUnitWithFree
> cachedUnit
;
177 struct timespec mtime
;
178 struct timespec ctime
;
184 using NonRepoUnitCache
= RankedCHM
<
185 const StringData
*, // must be static
187 StringDataHashCompare
,
190 NonRepoUnitCache s_nonRepoUnitCache
;
192 // When running in remote unix server mode with UnixServerQuarantineUnits set,
193 // we need to cache any unit generated from a file descriptor passed via the
194 // client process in a special quarantined cached. Units in this cache may only
195 // be loaded in unix server requests from the same user and are never used when
196 // handling web requests.
197 using PerUserCache
= folly::AtomicHashMap
<uid_t
, NonRepoUnitCache
*>;
198 PerUserCache
s_perUserUnitCaches(10);
201 int64_t timespecCompare(const struct timespec
& l
,
202 const struct timespec
& r
) {
203 if (l
.tv_sec
!= r
.tv_sec
) return l
.tv_sec
- r
.tv_sec
;
204 return l
.tv_nsec
- r
.tv_nsec
;
208 uint64_t g_units_seen_count
= 0;
210 bool stressUnitCache() {
211 if (RuntimeOption::EvalStressUnitCacheFreq
<= 0) return false;
212 if (RuntimeOption::EvalStressUnitCacheFreq
== 1) return true;
213 return ++g_units_seen_count
% RuntimeOption::EvalStressUnitCacheFreq
== 0;
216 bool isChanged(const CachedUnitNonRepo
& cu
, const struct stat
& s
) {
217 // If the cached unit is null, we always need to consider it out of date (in
218 // case someone created the file). This case should only happen if something
219 // successfully stat'd the file, but then it was gone by the time we tried to
221 return !cu
.cachedUnit
||
222 cu
.cachedUnit
->cu
.unit
== nullptr ||
224 cu
.mtime
- s
.st_mtime
< 0 ||
226 timespecCompare(cu
.mtime
, s
.st_mtim
) < 0 ||
227 timespecCompare(cu
.ctime
, s
.st_ctim
) < 0 ||
229 cu
.ino
!= s
.st_ino
||
230 cu
.devId
!= s
.st_dev
||
234 folly::Optional
<String
> readFileAsString(Stream::Wrapper
* w
,
235 const StringData
* path
) {
237 if (const auto f
= w
->open(StrNR(path
), "r", 0, nullptr)) {
242 auto const fd
= open(path
->data(), O_RDONLY
);
243 if (fd
< 0) return folly::none
;
244 auto file
= req::make
<PlainFile
>(fd
);
248 CachedUnit
createUnitFromString(const char* path
,
249 const String
& contents
,
252 auto const md5
= MD5
{mangleUnitMd5(string_md5(contents
.slice()))};
253 // Try the repo; if it's not already there, invoke the compiler.
254 if (auto unit
= Repo::get().loadUnit(path
, md5
)) {
255 return CachedUnit
{ unit
.release(), rds::allocBit() };
257 LogTimer
compileTimer("compile_ms", ent
);
258 auto const unit
= compile_file(contents
.data(), contents
.size(), md5
, path
,
260 return CachedUnit
{ unit
, rds::allocBit() };
263 CachedUnit
createUnitFromUrl(const StringData
* const requestedPath
) {
264 auto const w
= Stream::getWrapperFromURI(StrNR(requestedPath
));
265 if (!w
) return CachedUnit
{};
266 auto const f
= w
->open(StrNR(requestedPath
), "r", 0, nullptr);
267 if (!f
) return CachedUnit
{};
271 return createUnitFromString(requestedPath
->data(), sb
.detach(), nullptr, ent
);
274 CachedUnit
createUnitFromFile(const StringData
* const path
,
275 Unit
** releaseUnit
, Stream::Wrapper
* w
,
277 auto const contents
= readFileAsString(w
, path
);
279 ? createUnitFromString(path
->data(), *contents
, releaseUnit
, ent
)
283 // When running via the CLI server special access checks may need to be
284 // performed, and in the event that the server is unable to load the file an
285 // alternative per client cache may be used.
286 NonRepoUnitCache
& getNonRepoCache(const StringData
* rpath
,
287 Stream::Wrapper
*& w
) {
288 if (auto uc
= get_cli_ucred()) {
289 if (!(w
= Stream::getWrapperFromURI(StrNR(rpath
)))) {
290 return s_nonRepoUnitCache
;
293 auto unit_check_quarantine
= [&] () -> NonRepoUnitCache
& {
294 if (!RuntimeOption::EvalUnixServerQuarantineUnits
) {
295 return s_nonRepoUnitCache
;
297 auto iter
= s_perUserUnitCaches
.find(uc
->uid
);
298 if (iter
!= s_perUserUnitCaches
.end()) return *iter
->second
;
299 auto cache
= new NonRepoUnitCache
;
300 auto res
= s_perUserUnitCaches
.insert(uc
->uid
, cache
);
301 if (!res
.second
) delete cache
;
302 return *res
.first
->second
;
305 // If the server cannot access rpath attempt to open the unit on the
306 // client. When UnixServerQuarantineUnits is set store units opened by
307 // clients in per UID caches which are never accessible by server web
309 if (access(rpath
->data(), R_OK
) == -1) {
310 return unit_check_quarantine();
313 // When UnixServerVerifyExeAccess is set clients may not execute units if
314 // they cannot read them, even when the server has access. To verify that
315 // clients have access they are asked to open the file for read access,
316 // and using fstat the server verifies that the file it sees is identical
317 // to the unit opened by the client.
318 if (RuntimeOption::EvalUnixServerVerifyExeAccess
) {
319 struct stat local
, remote
;
320 auto remoteFile
= w
->open(StrNR(rpath
), "r", 0, nullptr);
322 fcntl(remoteFile
->fd(), F_GETFL
) != O_RDONLY
||
323 fstat(remoteFile
->fd(), &remote
) != 0 ||
324 stat(rpath
->data(), &local
) != 0 ||
325 remote
.st_dev
!= local
.st_dev
||
326 remote
.st_ino
!= local
.st_ino
) {
327 return unit_check_quarantine();
331 // When the server is able to read the file prefer to access it that way,
332 // in all modes units loaded by the server are cached for all clients.
335 return s_nonRepoUnitCache
;
338 CachedUnit
loadUnitNonRepoAuth(StringData
* requestedPath
,
339 const struct stat
& statInfo
,
341 LogTimer
loadTime("load_ms", ent
);
342 if (strstr(requestedPath
->data(), "://") != nullptr) {
343 // URL-based units are not currently cached in memory, but the Repo still
344 // caches them on disk.
345 return createUnitFromUrl(requestedPath
);
348 // The string we're using as a key must be static, because we're using it as
349 // a key in the cache (across requests).
352 // XXX: it seems weird we have to do this even though we already ran
354 (FileUtil::isAbsolutePath(requestedPath
->toCppString())
355 ? String
{requestedPath
}
356 : String(SourceRootInfo::GetCurrentSourceRoot()) + StrNR(requestedPath
)
360 auto const rpath
= [&] () -> const StringData
* {
361 if (RuntimeOption::CheckSymLink
) {
362 std::string rp
= StatCache::realpath(path
->data());
363 if (rp
.size() != 0) {
364 if (rp
.size() != path
->size() ||
365 memcmp(rp
.data(), path
->data(), rp
.size())) {
366 return makeStaticString(rp
);
373 Stream::Wrapper
* w
= nullptr;
374 auto& cache
= getNonRepoCache(rpath
, w
);
377 !w
|| &cache
!= &s_nonRepoUnitCache
||
378 !RuntimeOption::EvalUnixServerQuarantineUnits
381 // Freeing a unit while holding the tbb lock would cause a rank violation when
382 // recycle-tc is enabled as reclaiming dead functions requires that the code
383 // and metadata locks be acquired.
384 Unit
* releaseUnit
= nullptr;
385 SCOPE_EXIT
{ if (releaseUnit
) delete releaseUnit
; };
387 auto const cuptr
= [&] () -> std::shared_ptr
<CachedUnitWithFree
> {
388 NonRepoUnitCache::accessor rpathAcc
;
390 if (!cache
.insert(rpathAcc
, rpath
)) {
391 if (!isChanged(rpathAcc
->second
, statInfo
)) {
392 if (ent
) ent
->setStr("type", "cache_hit_writelock");
393 return rpathAcc
->second
.cachedUnit
;
395 if (ent
) ent
->setStr("type", "cache_stale");
397 if (ent
) ent
->setStr("type", "cache_miss");
401 * NB: the new-unit creation path is here, and is done while holding the tbb
402 * lock on s_nonRepoUnitCache. This was originally done deliberately to
403 * avoid wasting time in the compiler (during server startup, many requests
404 * hit the same code initial paths that are shared, and would all be
405 * compiling the same files). It's not 100% clear if this is the best way
406 * to handle that idea, though (tbb locks spin aggressively and are
407 * expected to be low contention).
410 auto const cu
= createUnitFromFile(rpath
, &releaseUnit
, w
, ent
);
411 rpathAcc
->second
.cachedUnit
= std::make_shared
<CachedUnitWithFree
>(cu
);
413 rpathAcc
->second
.mtime
= statInfo
.st_mtime
;
415 rpathAcc
->second
.mtime
= statInfo
.st_mtim
;
416 rpathAcc
->second
.ctime
= statInfo
.st_ctim
;
418 rpathAcc
->second
.ino
= statInfo
.st_ino
;
419 rpathAcc
->second
.devId
= statInfo
.st_dev
;
421 return rpathAcc
->second
.cachedUnit
;
425 NonRepoUnitCache::accessor pathAcc
;
426 cache
.insert(pathAcc
, path
);
427 pathAcc
->second
.cachedUnit
= cuptr
;
429 pathAcc
->second
.mtime
= statInfo
.st_mtime
;
431 pathAcc
->second
.mtime
= statInfo
.st_mtim
;
432 pathAcc
->second
.ctime
= statInfo
.st_ctim
;
434 pathAcc
->second
.ino
= statInfo
.st_ino
;
435 pathAcc
->second
.devId
= statInfo
.st_dev
;
441 CachedUnit
lookupUnitNonRepoAuth(StringData
* requestedPath
,
442 const struct stat
& statInfo
,
444 // Steady state, its probably already in the cache. Try that first
446 NonRepoUnitCache::const_accessor acc
;
447 if (s_nonRepoUnitCache
.find(acc
, requestedPath
)) {
448 if (!isChanged(acc
->second
, statInfo
)) {
449 if (ent
) ent
->setStr("type", "cache_hit_readlock");
450 return acc
->second
.cachedUnit
->cu
;
454 return loadUnitNonRepoAuth(requestedPath
, statInfo
, ent
);
457 //////////////////////////////////////////////////////////////////////
458 // resolveVmInclude callbacks
460 struct ResolveIncludeContext
{
461 String path
; // translated path of the file
462 struct stat
* s
; // stat for the file
463 bool allow_dir
; // return true for dirs?
466 bool findFile(const StringData
* path
, struct stat
* s
, bool allow_dir
,
467 Stream::Wrapper
* w
) {
468 // We rely on this side-effect in RepoAuthoritative mode right now, since the
469 // stat information is an output-param of resolveVmInclude, but we aren't
470 // really going to call stat.
473 if (RuntimeOption::RepoAuthoritative
) {
474 return lookupUnitRepoAuth(path
).unit
!= nullptr;
477 if (StatCache::stat(path
->data(), s
) == 0) {
478 // The call explicitly populates the struct for dirs, but returns false for
479 // them because it is geared toward file includes.
480 return allow_dir
|| !S_ISDIR(s
->st_mode
);
483 if (w
&& w
->stat(StrNR(path
), s
) == 0) {
484 return allow_dir
|| !S_ISDIR(s
->st_mode
);
489 bool findFileWrapper(const String
& file
, void* ctx
) {
490 auto const context
= static_cast<ResolveIncludeContext
*>(ctx
);
491 assert(context
->path
.isNull());
493 Stream::Wrapper
* w
= Stream::getWrapperFromURI(file
);
494 if (w
&& !w
->isNormalFileStream()) {
495 if (w
->stat(file
, context
->s
) == 0) {
496 context
->path
= file
;
502 if (StringUtil::IsFileUrl(file
)) {
503 return findFileWrapper(file
.substr(7), ctx
);
506 if (!w
) return false;
509 RuntimeOption::RepoAuthoritative
||
510 dynamic_cast<FileStreamWrapper
*>(w
) ||
511 !w
->isNormalFileStream()
515 // TranslatePath() will canonicalize the path and also check
516 // whether the file is in an allowed directory.
517 String translatedPath
= File::TranslatePathKeepRelative(file
);
518 if (!FileUtil::isAbsolutePath(file
.toCppString())) {
519 if (findFile(translatedPath
.get(), context
->s
, context
->allow_dir
,
521 context
->path
= translatedPath
;
526 if (RuntimeOption::SandboxMode
|| !RuntimeOption::AlwaysUseRelativePath
) {
527 if (findFile(translatedPath
.get(), context
->s
, context
->allow_dir
, passW
)) {
528 context
->path
= translatedPath
;
532 std::string
server_root(SourceRootInfo::GetCurrentSourceRoot());
533 if (server_root
.empty()) {
534 server_root
= std::string(g_context
->getCwd().data());
535 if (server_root
.empty() ||
536 FileUtil::isDirSeparator(server_root
[server_root
.size() - 1])) {
537 server_root
+= FileUtil::getDirSeparator();
540 String
rel_path(FileUtil::relativePath(server_root
, translatedPath
.data()));
541 if (findFile(rel_path
.get(), context
->s
, context
->allow_dir
, passW
)) {
542 context
->path
= rel_path
;
549 StructuredLogEntry
& ent
,
555 ent
.setStr("include_path", path
->data());
556 ent
.setStr("current_dir", cwd
);
557 ent
.setStr("resolved_path", rpath
.data());
558 if (auto const u
= cu
.unit
) {
559 const StringData
* err
;
561 if (u
->compileTimeFatal(err
, line
)) {
562 ent
.setStr("result", "compile_fatal");
563 ent
.setStr("error", err
->data());
564 } else if (u
->parseFatal(err
, line
)) {
565 ent
.setStr("result", "parse_fatal");
566 ent
.setStr("error", err
->data());
568 ent
.setStr("result", "success");
571 ent
.setStr("md5", u
->md5().toString());
572 ent
.setStr("repo_sn", folly::to
<std::string
>(u
->sn()));
573 ent
.setStr("repo_id", folly::to
<std::string
>(u
->repoID()));
575 ent
.setInt("bc_len", u
->bclen());
576 ent
.setInt("num_litstrs", u
->numLitstrs());
577 ent
.setInt("num_funcs", u
->funcs().size());
578 ent
.setInt("num_classes", u
->preclasses().size());
579 ent
.setInt("num_type_aliases", u
->typeAliases().size());
581 ent
.setStr("result", "file_not_found");
584 switch (requestKind
) {
585 case RequestKind::Warmup
: ent
.setStr("request_kind", "warmup"); break;
586 case RequestKind::Profile
: ent
.setStr("request_kind", "profile"); break;
587 case RequestKind::Standard
: ent
.setStr("request_kind", "standard"); break;
588 case RequestKind::NonVM
: ent
.setStr("request_kind", "nonVM"); break;
590 ent
.setInt("request_count", requestCount());
592 StructuredLog::log("hhvm_unit_cache", ent
);
595 //////////////////////////////////////////////////////////////////////
597 CachedUnit
checkoutFile(
599 const struct stat
& statInfo
,
602 return RuntimeOption::RepoAuthoritative
603 ? lookupUnitRepoAuth(path
)
604 : lookupUnitNonRepoAuth(path
, statInfo
, ent
);
607 const std::string
mangleUnitPHP7Options() {
608 // As the list of options increases, we may want to do something smarter here?
610 s
+= (RuntimeOption::PHP7_IntSemantics
? '1' : '0') +
611 (RuntimeOption::PHP7_LTR_assign
? '1' : '0') +
612 (RuntimeOption::PHP7_NoHexNumerics
? '1' : '0') +
613 (RuntimeOption::PHP7_Builtins
? '1' : '0') +
614 (RuntimeOption::PHP7_ScalarTypes
? '1' : '0') +
615 (RuntimeOption::PHP7_Substr
? '1' : '0') +
616 (RuntimeOption::PHP7_UVS
? '1' : '0');
620 const std::string
mangleAliasedNamespaces() {
622 s
+= folly::to
<std::string
>(RuntimeOption::AliasedNamespaces
.size());
624 for (auto& par
: RuntimeOption::AliasedNamespaces
) {
625 s
+= par
.first
+ '\0' + par
.second
+ '\0';
630 const std::string
mangleExternCompilerVersions() {
632 if (HackcMode::kNever
!= hackc_mode()) {
633 s
+= hackc_version();
638 //////////////////////////////////////////////////////////////////////
640 } // end empty namespace
642 //////////////////////////////////////////////////////////////////////
644 std::string
mangleUnitMd5(const std::string
& fileMd5
) {
645 std::string t
= fileMd5
+ '\0'
646 + (RuntimeOption::AssertEmitted
? '1' : '0')
647 + (RuntimeOption::AutoprimeGenerators
? '1' : '0')
648 + (RuntimeOption::EnableHipHopExperimentalSyntax
? '1' : '0')
649 + (RuntimeOption::EnableHipHopSyntax
? '1' : '0')
650 + (RuntimeOption::EnableXHP
? '1' : '0')
651 + (RuntimeOption::EvalAllowHhas
? '1' : '0')
652 + (RuntimeOption::EvalEmitSwitch
? '1' : '0')
653 + (RuntimeOption::EvalEnableCallBuiltin
? '1' : '0')
654 + (RuntimeOption::EvalHackArrCompatNotices
? '1' : '0')
655 + (RuntimeOption::EvalHackArrCompatIsArrayNotices
? '1' : '0')
656 + (RuntimeOption::EvalHackArrCompatPromoteNotices
? '1' : '0')
657 + (RuntimeOption::EvalHackArrCompatTypeHintNotices
? '1' : '0')
658 + (RuntimeOption::EvalHackArrCompatDVCmpNotices
? '1' : '0')
659 + (RuntimeOption::EvalHackCompilerFallback
? '1' : '0')
660 + (RuntimeOption::EvalJitEnableRenameFunction
? '1' : '0')
661 + (RuntimeOption::EvalLoadFilepathFromUnitCache
? '1' : '0')
662 + (RuntimeOption::IntsOverflowToInts
? '1' : '0')
663 + (RuntimeOption::EvalReffinessInvariance
? '1' : '0')
664 + (RuntimeOption::EvalCreateInOutWrapperFunctions
? '1' : '0')
665 + std::to_string(RuntimeOption::EvalForbidDynamicCalls
)
666 + (RuntimeOption::EvalNoticeOnBuiltinDynamicCalls
? '1' : '0')
667 + (RuntimeOption::EvalHackArrDVArrs
? '1' : '0')
668 + (RuntimeOption::EvalDisableHphpcOpts
? '1' : '0')
669 + (RuntimeOption::EvalUseMSRVForInOut
? '1' : '0')
670 + (RuntimeOption::EvalAllowObjectDestructors
? '1' : '0')
671 + (RuntimeOption::EvalUseExternCompilerForSystemLib
? '1' : '0')
672 + RuntimeOption::EvalHackCompilerCommand
+ '\0'
673 + mangleUnitPHP7Options()
674 + mangleAliasedNamespaces()
675 + mangleExternCompilerVersions();
676 return string_md5(t
);
679 size_t numLoadedUnits() {
680 if (RuntimeOption::RepoAuthoritative
) {
681 return s_repoUnitCache
.size();
683 return s_nonRepoUnitCache
.size();
686 String
resolveVmInclude(StringData
* path
,
687 const char* currentDir
,
689 bool allow_dir
/* = false */) {
690 ResolveIncludeContext ctx
;
692 ctx
.allow_dir
= allow_dir
;
694 resolve_include(String
{path
}, currentDir
, findFileWrapper
, vpCtx
);
695 // If resolve_include() could not find the file, return NULL
699 Unit
* lookupUnit(StringData
* path
, const char* currentDir
, bool* initial_opt
) {
701 bool& initial
= initial_opt
? *initial_opt
: init
;
705 if (!RuntimeOption::RepoAuthoritative
&&
706 StructuredLog::coinflip(RuntimeOption::EvalLogUnitLoadRate
)) {
710 LogTimer
lookupTimer("lookup_ms", ent
);
713 * NB: the m_evaledFiles map is only for the debugger, and could be omitted
714 * in RepoAuthoritative mode, but currently isn't.
718 auto const spath
= resolveVmInclude(path
, currentDir
, &s
);
719 if (spath
.isNull()) return nullptr;
721 auto const eContext
= g_context
.getNoCheck();
723 // Check if this file has already been included.
724 auto it
= eContext
->m_evaledFiles
.find(spath
.get());
725 if (it
!= end(eContext
->m_evaledFiles
)) {
726 // In RepoAuthoritative mode we assume that the files are unchanged.
728 if (RuntimeOption::RepoAuthoritative
||
729 (it
->second
.ts_sec
> s
.st_mtime
) ||
730 ((it
->second
.ts_sec
== s
.st_mtime
) &&
731 (it
->second
.ts_nsec
>= s
.st_mtim
.tv_nsec
))) {
732 return it
->second
.unit
;
736 // This file hasn't been included yet, so we need to parse the file
737 auto const cunit
= checkoutFile(spath
.get(), s
, ent
);
738 if (cunit
.unit
&& initial_opt
) {
739 // if initial_opt is not set, this shouldn't be recorded as a
740 // per request fetch of the file.
741 if (rds::testAndSetBit(cunit
.rdsBitId
)) {
744 // if parsing was successful, update the mappings for spath and
745 // rpath (if it exists).
746 eContext
->m_evaledFilesOrder
.push_back(cunit
.unit
->filepath());
747 eContext
->m_evaledFiles
[spath
.get()] =
748 {cunit
.unit
, s
.st_mtime
, static_cast<unsigned long>(s
.st_mtim
.tv_nsec
)};
749 spath
.get()->incRefCount();
750 if (!cunit
.unit
->filepath()->same(spath
.get())) {
751 eContext
->m_evaledFiles
[cunit
.unit
->filepath()] =
752 {cunit
.unit
, s
.st_mtime
, static_cast<unsigned long>(s
.st_mtim
.tv_nsec
)};
754 if (g_system_profiler
) {
755 g_system_profiler
->fileLoadCallBack(path
->toCppString());
757 DEBUGGER_ATTACHED_ONLY(phpDebuggerFileLoadHook(cunit
.unit
));
761 if (ent
) logLoad(*ent
, path
, currentDir
, spath
, cunit
);
765 //////////////////////////////////////////////////////////////////////
768 auto& repo
= Repo::get();
769 auto units
= repo
.enumerateUnits(RepoIdLocal
, true, false);
770 if (units
.size() == 0) {
771 units
= repo
.enumerateUnits(RepoIdCentral
, true, false);
773 if (!units
.size()) return;
775 std::vector
<std::thread
> workers
;
776 auto numWorkers
= Process::GetCPUCount();
777 // Compute a batch size that causes each thread to process approximately 16
778 // batches. Even if the batches are somewhat imbalanced in what they contain,
779 // the straggler workers are very unlikey to take more than 10% longer than
780 // the first worker to finish.
781 size_t batchSize
{std::max(units
.size() / numWorkers
/ 16, size_t(1))};
782 std::atomic
<size_t> index
{0};
783 for (auto worker
= 0; worker
< numWorkers
; ++worker
) {
784 workers
.push_back(std::thread([&] {
789 auto begin
= index
.fetch_add(batchSize
);
790 auto end
= std::min(begin
+ batchSize
, units
.size());
791 if (begin
>= end
) break;
792 auto unitCount
= end
- begin
;
793 for (auto i
= size_t{0}; i
< unitCount
; ++i
) {
794 auto& kv
= units
[begin
+ i
];
796 lookupUnit(String(RuntimeOption::SourceRoot
+ kv
.first
).get(),
799 // swallow errors silently
809 for (auto& worker
: workers
) {
814 //////////////////////////////////////////////////////////////////////