in watchman/query/eval.cpp [241:453]
QueryResult w_query_execute(
const Query* query,
const std::shared_ptr<Root>& root,
QueryGenerator generator,
SavedStateFactory savedStateFactory) {
QueryResult res;
ClockSpec resultClock(ClockPosition{});
bool disableFreshInstance{false};
auto requestId = query->request_id;
PerfSample sample("query_execute");
if (requestId && !requestId.empty()) {
log(DBG, "request_id = ", requestId, "\n");
sample.add_meta("request_id", w_string_to_json(requestId));
}
// We want to check this before we sync, as the SCM may generate changes
// in the filesystem when running the underlying commands to query it.
if (query->since_spec && query->since_spec->hasScmParams()) {
auto scm = root->view()->getSCM();
if (!scm) {
throw QueryExecError("This root does not support SCM-aware queries.");
}
// Populate transition counter at start of query. This allows us to
// determine if SCM operations ocurred concurrent with query execution.
res.stateTransCountAtStartOfQuery = root->stateTransCount.load();
resultClock.scmMergeBaseWith = query->since_spec->scmMergeBaseWith;
resultClock.scmMergeBase =
scm->mergeBaseWith(resultClock.scmMergeBaseWith, requestId);
// Always update the saved state storage type and key, but conditionally
// update the saved state commit id below based on wether the mergebase has
// changed.
if (query->since_spec->hasSavedStateParams()) {
resultClock.savedStateStorageType =
query->since_spec->savedStateStorageType;
resultClock.savedStateConfig = query->since_spec->savedStateConfig;
}
if (resultClock.scmMergeBase != query->since_spec->scmMergeBase) {
// The merge base is different, so on the assumption that a lot of
// things have changed between the prior and current state of
// the world, we're just going to ask the SCM to tell us about
// the changes, then we're going to feed that change list through
// a simpler watchman query.
auto modifiedMergebase = resultClock.scmMergeBase;
if (query->since_spec->hasSavedStateParams()) {
// Find the most recent saved state to the new mergebase and return
// changed files since that saved state, if available.
auto savedStateInterface = savedStateFactory(
query->since_spec->savedStateStorageType,
query->since_spec->savedStateConfig,
scm,
root->config,
[root](PerfSample& sample) {
root->addPerfSampleMetadata(sample);
});
auto savedStateResult = savedStateInterface->getMostRecentSavedState(
resultClock.scmMergeBase);
res.savedStateInfo = savedStateResult.savedStateInfo;
if (savedStateResult.commitId) {
resultClock.savedStateCommitId = savedStateResult.commitId;
// Modify the mergebase to be the saved state mergebase so we can
// return changed files since the saved state.
modifiedMergebase = savedStateResult.commitId;
} else {
// Setting the saved state commit id to the empty string alerts the
// client that the mergebase changed, yet no saved state was
// available. The changed files list will be relative to the prior
// clock as if scm-aware queries were not being used at all, to ensure
// clients have all changed files they need.
resultClock.savedStateCommitId = w_string();
modifiedMergebase = nullptr;
}
}
// If the modified mergebase is null then we had no saved state available
// so we need to fall back to the normal behavior of returning all changes
// since the prior clock, so we should not update the generator in that
// case.
if (modifiedMergebase) {
disableFreshInstance = true;
generator = [root, modifiedMergebase, requestId](
const Query* q,
const std::shared_ptr<Root>& r,
QueryContext* c) {
auto changedFiles =
root->view()->getSCM()->getFilesChangedSinceMergeBaseWith(
modifiedMergebase, requestId);
auto pathList = json_array_of_size(changedFiles.size());
for (auto& f : changedFiles) {
json_array_append_new(pathList, w_string_to_json(f));
}
auto spec = r->view()->getMostRecentRootNumberAndTickValue();
ClockStamp clock{spec.ticks, ::time(nullptr)};
for (auto& pathEntry : pathList.array()) {
auto path = json_to_w_string(pathEntry);
auto fullPath = w_string::pathCat({r->root_path, path});
if (!c->fileMatchesRelativeRoot(fullPath)) {
continue;
}
// Note well! At the time of writing the LocalFileResult class
// assumes that removed entries must have been regular files.
// We don't have enough information returned from
// getFilesChangedSinceMergeBaseWith() to distinguish between
// deleted files and deleted symlinks. Also, it is not possible
// to see a directory returned from that call; we're only going
// to enumerate !dirs for this case.
w_query_process_file(
q,
c,
std::make_unique<LocalFileResult>(
fullPath, clock, r->case_sensitive));
}
};
} else if (query->fail_if_no_saved_state) {
throw QueryExecError(
"The merge base changed but no corresponding saved state was "
"found for the new merge base. fail_if_no_saved_state was set "
"in the query so treating this as an error");
}
} else {
if (query->since_spec->hasSavedStateParams()) {
// If the mergebase has not changed, then preserve the input value for
// the saved state commit id so it will be accurate in subscriptions.
resultClock.savedStateCommitId = query->since_spec->savedStateCommitId;
}
}
}
// We should skip asking SCM for the changed files if the query
// indicated to omit those. To do so, lets just make an empty
// generator.
if (query->omit_changed_files) {
generator = [](const Query*, const std::shared_ptr<Root>&, QueryContext*) {
};
}
QueryContext ctx{query, root, disableFreshInstance};
// Track the query against the root.
// This is to enable the `watchman debug-status` diagnostic command.
// It promises only to read the read-only fields in ctx and ctx.query.
root->queries.wlock()->insert(&ctx);
SCOPE_EXIT {
root->queries.wlock()->erase(&ctx);
};
if (query->settle_timeouts) {
auto future = root->waitForSettle(query->settle_timeouts->settle_period);
try {
std::move(future).get(query->settle_timeouts->settle_timeout);
} catch (const folly::FutureTimeout&) {
throw QueryExecError(fmt::format(
"waitForSettle: timed out waiting for settle {} in {}",
query->settle_timeouts->settle_period,
query->settle_timeouts->settle_timeout));
}
}
if (query->sync_timeout.count()) {
ctx.state = QueryContextState::WaitingForCookieSync;
ctx.stopWatch.reset();
try {
auto result = root->syncToNow(query->sync_timeout);
res.debugInfo.cookieFileNames = std::move(result.cookieFileNames);
} catch (const std::exception& exc) {
throw QueryExecError("synchronization failed: ", exc.what());
}
ctx.cookieSyncDuration = ctx.stopWatch.lap();
}
/* The first stage of execution is generation.
* We generate a series of file inputs to pass to
* the query executor.
*
* We evaluate each of the generators one after the
* other. If multiple generators are used, it is
* possible and expected that the same file name
* will be evaluated multiple times if those generators
* both emit the same file.
*/
ctx.clockAtStartOfQuery =
ClockSpec(root->view()->getMostRecentRootNumberAndTickValue());
ctx.lastAgeOutTickValueAtStartOfQuery =
root->view()->getLastAgeOutTickValue();
// Copy in any scm parameters
res.clockAtStartOfQuery = resultClock;
// then update the clock position portion
std::get<ClockSpec::Clock>(res.clockAtStartOfQuery.spec) =
std::get<ClockSpec::Clock>(ctx.clockAtStartOfQuery.spec);
// Evaluate the cursor for this root
ctx.since = query->since_spec ? query->since_spec->evaluate(
ctx.clockAtStartOfQuery.position(),
ctx.lastAgeOutTickValueAtStartOfQuery,
&root->inner.cursors)
: QuerySince{};
if (query->bench_iterations > 0) {
for (uint32_t i = 0; i < query->bench_iterations; ++i) {
QueryContext c{query, root, ctx.disableFreshInstance};
QueryResult r;
c.clockAtStartOfQuery = ctx.clockAtStartOfQuery;
c.since = ctx.since;
execute_common(&c, nullptr, &r, generator);
}
}
execute_common(&ctx, &sample, &res, generator);
return res;
}