1 /* Copyright 2012-present Facebook, Inc.
2  * Licensed under the Apache License, Version 2.0 */
3 #include "InMemoryView.h"
4 #include <algorithm>
5 #include <thread>
6 #include "ThreadPool.h"
7 #include "make_unique.h"
8 #include "watchman.h"
9 #include "watchman_scopeguard.h"
10 
11 // Each root gets a number that uniquely identifies it within the process. This
12 // helps avoid confusion if a root is removed and then added again.
13 static std::atomic<long> next_root_number{1};
14 
15 namespace watchman {
16 
InMemoryFileResult(const watchman_file * file,ContentHashCache & contentHashCache)17 InMemoryFileResult::InMemoryFileResult(
18     const watchman_file* file,
19     ContentHashCache& contentHashCache)
20     : file_(file), contentHashCache_(contentHashCache) {}
21 
stat() const22 const FileInformation& InMemoryFileResult::stat() const {
23   return file_->stat;
24 }
25 
baseName() const26 w_string_piece InMemoryFileResult::baseName() const {
27   return file_->getName();
28 }
29 
dirName()30 w_string_piece InMemoryFileResult::dirName() {
31   if (!dirName_) {
32     dirName_ = file_->parent->getFullPath();
33   }
34   return dirName_;
35 }
36 
exists() const37 bool InMemoryFileResult::exists() const {
38   return file_->exists;
39 }
40 
ctime() const41 const w_clock_t& InMemoryFileResult::ctime() const {
42   return file_->ctime;
43 }
44 
otime() const45 const w_clock_t& InMemoryFileResult::otime() const {
46   return file_->otime;
47 }
48 
readLink() const49 Future<w_string> InMemoryFileResult::readLink() const {
50   return makeFuture(file_->symlink_target);
51 }
52 
getContentSha1()53 Future<FileResult::ContentHash> InMemoryFileResult::getContentSha1() {
54   auto dir = dirName();
55   dir.advance(contentHashCache_.rootPath().size());
56 
57   // If dirName is the root, dir.size() will now be zero
58   if (dir.size() > 0) {
59     // if not at the root, skip the slash character at the
60     // front of dir
61     dir.advance(1);
62   }
63 
64   ContentHashCacheKey key{w_string::pathCat({dir, baseName()}),
65                           size_t(file_->stat.size),
66                           file_->stat.mtime};
67 
68   return contentHashCache_.get(key).then(
69       [](Result<std::shared_ptr<const ContentHashCache::Node>>&& result)
70           -> FileResult::ContentHash { return result.value()->value(); });
71 }
72 
view(const w_string & root_path)73 InMemoryView::view::view(const w_string& root_path)
74     : root_dir(watchman::make_unique<watchman_dir>(root_path, nullptr)),
75       rootNumber(next_root_number++) {}
76 
InMemoryView(w_root_t * root,std::shared_ptr<Watcher> watcher)77 InMemoryView::InMemoryView(w_root_t* root, std::shared_ptr<Watcher> watcher)
78     : cookies_(root->cookies),
79       config_(root->config),
80       view_(view(root->root_path)),
81       root_path(root->root_path),
82       watcher_(watcher),
83       contentHashCache_(
84           root->root_path,
85           config_.getInt("content_hash_max_items", 128 * 1024),
86           std::chrono::milliseconds(
87               config_.getInt("content_hash_negative_cache_ttl_ms", 2000))),
88       enableContentCacheWarming_(
89           config_.getBool("content_hash_warming", false)),
90       maxFilesToWarmInContentCache_(
91           size_t(config_.getInt("content_hash_max_warm_per_settle", 1024))),
92       syncContentCacheWarming_(
93           config_.getBool("content_hash_warm_wait_before_settle", false)),
94       scm_(SCM::scmForPath(root->root_path)) {}
95 
insertAtHeadOfFileList(struct watchman_file * file)96 void InMemoryView::view::insertAtHeadOfFileList(struct watchman_file* file) {
97   file->next = latest_file;
98   if (file->next) {
99     file->next->prev = &file->next;
100   }
101   latest_file = file;
102   file->prev = &latest_file;
103 }
104 
markFileChanged(SyncView::LockedPtr & view,watchman_file * file,const struct timeval & now)105 void InMemoryView::markFileChanged(
106     SyncView::LockedPtr& view,
107     watchman_file* file,
108     const struct timeval& now) {
109   if (file->exists) {
110     watcher_->startWatchFile(file);
111   }
112 
113   file->otime.timestamp = now.tv_sec;
114   file->otime.ticks = view->mostRecentTick;
115 
116   if (view->latest_file != file) {
117     // unlink from list
118     file->removeFromFileList();
119 
120     // and move to the head
121     view->insertAtHeadOfFileList(file);
122   }
123 }
124 
resolveDir(SyncView::ConstLockedPtr & view,const w_string & dir_name) const125 const watchman_dir* InMemoryView::resolveDir(
126     SyncView::ConstLockedPtr& view,
127     const w_string& dir_name) const {
128   watchman_dir* dir;
129   const char* dir_component;
130   const char* dir_end;
131 
132   if (dir_name == root_path) {
133     return view->root_dir.get();
134   }
135 
136   dir_component = dir_name.data();
137   dir_end = dir_component + dir_name.size();
138 
139   dir = view->root_dir.get();
140   dir_component += root_path.size() + 1; // Skip root path prefix
141 
142   w_assert(dir_component <= dir_end, "impossible file name");
143 
144   while (true) {
145     w_string_t component;
146     auto sep = (const char*)memchr(dir_component, '/', dir_end - dir_component);
147     // Note: if sep is NULL it means that we're looking at the basename
148     // component of the input directory name, which is the terminal
149     // iteration of this search.
150 
151     w_string_new_len_typed_stack(
152         &component,
153         dir_component,
154         sep ? (uint32_t)(sep - dir_component)
155             : (uint32_t)(dir_end - dir_component),
156         W_STRING_BYTE);
157 
158     auto child = dir->getChildDir(&component);
159     if (!child) {
160       return nullptr;
161     }
162 
163     dir = child;
164 
165     if (!sep) {
166       // We reached the end of the string
167       if (dir) {
168         // We found the dir
169         return dir;
170       }
171       // Does not exist
172       return nullptr;
173     }
174 
175     // Skip to the next component for the next iteration
176     dir_component = sep + 1;
177   }
178 
179   return nullptr;
180 }
181 
resolveDir(SyncView::LockedPtr & view,const w_string & dir_name,bool create)182 watchman_dir* InMemoryView::resolveDir(
183     SyncView::LockedPtr& view,
184     const w_string& dir_name,
185     bool create) {
186   watchman_dir *dir, *parent;
187   const char* dir_component;
188   const char* dir_end;
189 
190   if (dir_name == root_path) {
191     return view->root_dir.get();
192   }
193 
194   dir_component = dir_name.data();
195   dir_end = dir_component + dir_name.size();
196 
197   dir = view->root_dir.get();
198   dir_component += root_path.size() + 1; // Skip root path prefix
199 
200   w_assert(dir_component <= dir_end, "impossible file name");
201 
202   while (true) {
203     w_string_t component;
204     auto sep = (const char*)memchr(dir_component, '/', dir_end - dir_component);
205     // Note: if sep is NULL it means that we're looking at the basename
206     // component of the input directory name, which is the terminal
207     // iteration of this search.
208 
209     w_string_new_len_typed_stack(
210         &component,
211         dir_component,
212         sep ? (uint32_t)(sep - dir_component)
213             : (uint32_t)(dir_end - dir_component),
214         W_STRING_BYTE);
215 
216     auto child = dir->getChildDir(&component);
217 
218     if (!child && !create) {
219       return nullptr;
220     }
221     if (!child && sep && create) {
222       // A component in the middle wasn't present.  Since we're in create
223       // mode, we know that the leaf must exist.  The assumption is that
224       // we have another pending item for the parent.  We'll create the
225       // parent dir now and our other machinery will populate its contents
226       // later.
227       w_string child_name(dir_component, (uint32_t)(sep - dir_component));
228 
229       auto& new_child = dir->dirs[child_name];
230       new_child.reset(new watchman_dir(child_name, dir));
231 
232       child = new_child.get();
233     }
234 
235     parent = dir;
236     dir = child;
237 
238     if (!sep) {
239       // We reached the end of the string
240       if (dir) {
241         // We found the dir
242         return dir;
243       }
244       // We need to create the dir
245       break;
246     }
247 
248     // Skip to the next component for the next iteration
249     dir_component = sep + 1;
250   }
251 
252   w_string child_name(dir_component, (uint32_t)(dir_end - dir_component));
253   auto& new_child = parent->dirs[child_name];
254   new_child.reset(new watchman_dir(child_name, parent));
255 
256   dir = new_child.get();
257 
258   return dir;
259 }
260 
markDirDeleted(SyncView::LockedPtr & view,struct watchman_dir * dir,const struct timeval & now,bool recursive)261 void InMemoryView::markDirDeleted(
262     SyncView::LockedPtr& view,
263     struct watchman_dir* dir,
264     const struct timeval& now,
265     bool recursive) {
266   if (!dir->last_check_existed) {
267     // If we know that it doesn't exist, return early
268     return;
269   }
270   dir->last_check_existed = false;
271 
272   for (auto& it : dir->files) {
273     auto file = it.second.get();
274 
275     if (file->exists) {
276       auto full_name = w_dir_path_cat_str(dir, file->getName());
277       w_log(W_LOG_DBG, "mark_deleted: %s\n", full_name.c_str());
278       file->exists = false;
279       markFileChanged(view, file, now);
280     }
281   }
282 
283   if (recursive) {
284     for (auto& it : dir->dirs) {
285       auto child = it.second.get();
286 
287       markDirDeleted(view, child, now, true);
288     }
289   }
290 }
291 
getOrCreateChildFile(SyncView::LockedPtr & view,watchman_dir * dir,const w_string & file_name,const struct timeval & now)292 watchman_file* InMemoryView::getOrCreateChildFile(
293     SyncView::LockedPtr& view,
294     watchman_dir* dir,
295     const w_string& file_name,
296     const struct timeval& now) {
297   // file_name is typically a baseName slice; let's use it as-is
298   // to look up a child...
299   auto it = dir->files.find(file_name);
300   if (it != dir->files.end()) {
301     return it->second.get();
302   }
303 
304   // ... but take the shorter string from inside the file that
305   // we create as the key.
306   auto file = watchman_file::make(file_name, dir);
307   auto& file_ptr = dir->files[file->getName()];
308   file_ptr = std::move(file);
309 
310   file_ptr->ctime.ticks = view->mostRecentTick;
311   file_ptr->ctime.timestamp = now.tv_sec;
312 
313   auto suffix = file_name.suffix();
314   if (suffix) {
315     auto& sufhead = view->suffixes[suffix];
316     if (!sufhead) {
317       // Create the list head if we don't already have one for this suffix.
318       sufhead.reset(new watchman::InMemoryView::file_list_head);
319     }
320 
321     file_ptr->suffix_next = sufhead->head;
322     if (file_ptr->suffix_next) {
323       sufhead->head->suffix_prev = &file_ptr->suffix_next;
324     }
325     sufhead->head = file_ptr.get();
326     file_ptr->suffix_prev = &sufhead->head;
327   }
328 
329   watcher_->startWatchFile(file_ptr.get());
330 
331   return file_ptr.get();
332 }
333 
ageOutFile(std::unordered_set<w_string> & dirs_to_erase,watchman_file * file)334 void InMemoryView::ageOutFile(
335     std::unordered_set<w_string>& dirs_to_erase,
336     watchman_file* file) {
337   auto parent = file->parent;
338 
339   auto full_name = w_dir_path_cat_str(parent, file->getName());
340   w_log(W_LOG_DBG, "age_out file=%s\n", full_name.c_str());
341 
342   // Revise tick for fresh instance reporting
343   last_age_out_tick = std::max(last_age_out_tick, file->otime.ticks);
344 
345   // If we have a corresponding dir, we want to arrange to remove it, but only
346   // after we have unlinked all of the associated file nodes.
347   dirs_to_erase.insert(full_name);
348 
349   // Remove the entry from the containing file hash; this will free it.
350   // We don't need to stop watching it, because we already stopped watching it
351   // when we marked it as !exists.
352   parent->files.erase(file->getName());
353 }
354 
ageOut(w_perf_t & sample,std::chrono::seconds minAge)355 void InMemoryView::ageOut(w_perf_t& sample, std::chrono::seconds minAge) {
356   struct watchman_file *file, *prior;
357   time_t now;
358   uint32_t num_aged_files = 0;
359   uint32_t num_walked = 0;
360   std::unordered_set<w_string> dirs_to_erase;
361 
362   time(&now);
363   last_age_out_timestamp = now;
364   auto view = view_.wlock();
365 
366   file = view->latest_file;
367   prior = nullptr;
368   while (file) {
369     ++num_walked;
370     if (file->exists || file->otime.timestamp + minAge.count() > now) {
371       prior = file;
372       file = file->next;
373       continue;
374     }
375 
376     ageOutFile(dirs_to_erase, file);
377     num_aged_files++;
378 
379     // Go back to last good file node; we can't trust that the
380     // value of file->next saved before age_out_file is a valid
381     // file node as anything past that point may have also been
382     // aged out along with it.
383     file = prior;
384   }
385 
386   for (auto& name : dirs_to_erase) {
387     auto parent = resolveDir(view, name.dirName(), false);
388     if (parent) {
389       parent->dirs.erase(name.baseName());
390     }
391   }
392 
393   if (num_aged_files + dirs_to_erase.size()) {
394     w_log(
395         W_LOG_ERR,
396         "aged %" PRIu32 " files, %" PRIu32 " dirs\n",
397         num_aged_files,
398         uint32_t(dirs_to_erase.size()));
399   }
400   sample.add_meta(
401       "age_out",
402       json_object({{"walked", json_integer(num_walked)},
403                    {"files", json_integer(num_aged_files)},
404                    {"dirs", json_integer(dirs_to_erase.size())}}));
405 }
406 
timeGenerator(w_query * query,struct w_query_ctx * ctx) const407 void InMemoryView::timeGenerator(w_query* query, struct w_query_ctx* ctx)
408     const {
409   struct watchman_file* f;
410 
411   // Walk back in time until we hit the boundary
412   auto view = view_.rlock();
413   for (f = view->latest_file; f; f = f->next) {
414     ctx->bumpNumWalked();
415     // Note that we use <= for the time comparisons in here so that we
416     // report the things that changed inclusive of the boundary presented.
417     // This is especially important for clients using the coarse unix
418     // timestamp as the since basis, as they would be much more
419     // likely to miss out on changes if we didn't.
420     if (ctx->since.is_timestamp && f->otime.timestamp <= ctx->since.timestamp) {
421       break;
422     }
423     if (!ctx->since.is_timestamp && f->otime.ticks <= ctx->since.clock.ticks) {
424       break;
425     }
426 
427     if (!w_query_file_matches_relative_root(ctx, f)) {
428       continue;
429     }
430 
431     w_query_process_file(
432         query,
433         ctx,
434         watchman::make_unique<InMemoryFileResult>(f, contentHashCache_));
435   }
436 }
437 
suffixGenerator(w_query * query,struct w_query_ctx * ctx) const438 void InMemoryView::suffixGenerator(w_query* query, struct w_query_ctx* ctx)
439     const {
440   struct watchman_file* f;
441 
442   auto view = view_.rlock();
443   for (const auto& suff : query->suffixes) {
444     // Head of suffix index for this suffix
445     auto it = view->suffixes.find(suff);
446     if (it == view->suffixes.end()) {
447       continue;
448     }
449 
450     // Walk and process
451     for (f = it->second->head; f; f = f->suffix_next) {
452       ctx->bumpNumWalked();
453       if (!w_query_file_matches_relative_root(ctx, f)) {
454         continue;
455       }
456 
457       w_query_process_file(
458           query,
459           ctx,
460           watchman::make_unique<InMemoryFileResult>(f, contentHashCache_));
461     }
462   }
463 }
464 
pathGenerator(w_query * query,struct w_query_ctx * ctx) const465 void InMemoryView::pathGenerator(w_query* query, struct w_query_ctx* ctx)
466     const {
467   w_string_t* relative_root;
468   struct watchman_file* f;
469 
470   if (query->relative_root) {
471     relative_root = query->relative_root;
472   } else {
473     relative_root = root_path;
474   }
475 
476   auto view = view_.rlock();
477 
478   for (const auto& path : query->paths) {
479     const watchman_dir* dir;
480     w_string_t* file_name;
481     w_string dir_name;
482 
483     // Compose path with root
484     auto full_name = w_string::pathCat({relative_root, path.name});
485 
486     // special case of root dir itself
487     if (w_string_equal(root_path, full_name)) {
488       // dirname on the root is outside the root, which is useless
489       dir = resolveDir(view, full_name);
490       goto is_dir;
491     }
492 
493     // Ideally, we'd just resolve it directly as a dir and be done.
494     // It's not quite so simple though, because we may resolve a dir
495     // that had been deleted and replaced by a file.
496     // We prefer to resolve the parent and walk down.
497     dir_name = full_name.dirName();
498     if (!dir_name) {
499       continue;
500     }
501 
502     dir = resolveDir(view, dir_name);
503 
504     if (!dir) {
505       // Doesn't exist, and never has
506       continue;
507     }
508 
509     if (!dir->files.empty()) {
510       file_name = w_string_basename(path.name);
511       f = dir->getChildFile(file_name);
512       w_string_delref(file_name);
513 
514       // If it's a file (but not an existent dir)
515       if (f && (!f->exists || !f->stat.isDir())) {
516         ctx->bumpNumWalked();
517         w_query_process_file(
518             query,
519             ctx,
520             watchman::make_unique<InMemoryFileResult>(f, contentHashCache_));
521         continue;
522       }
523     }
524 
525     // Is it a dir?
526     if (dir->dirs.empty()) {
527       continue;
528     }
529 
530     dir = dir->getChildDir(full_name.baseName());
531   is_dir:
532     // We got a dir; process recursively to specified depth
533     if (dir) {
534       dirGenerator(query, ctx, dir, path.depth);
535     }
536   }
537 }
538 
dirGenerator(w_query * query,struct w_query_ctx * ctx,const watchman_dir * dir,uint32_t depth) const539 void InMemoryView::dirGenerator(
540     w_query* query,
541     struct w_query_ctx* ctx,
542     const watchman_dir* dir,
543     uint32_t depth) const {
544   for (auto& it : dir->files) {
545     auto file = it.second.get();
546     ctx->bumpNumWalked();
547 
548     w_query_process_file(
549         query,
550         ctx,
551         watchman::make_unique<InMemoryFileResult>(file, contentHashCache_));
552   }
553 
554   if (depth > 0) {
555     for (auto& it : dir->dirs) {
556       const auto child = it.second.get();
557 
558       dirGenerator(query, ctx, child, depth - 1);
559     }
560   }
561 }
562 
allFilesGenerator(w_query * query,struct w_query_ctx * ctx) const563 void InMemoryView::allFilesGenerator(w_query* query, struct w_query_ctx* ctx)
564     const {
565   struct watchman_file* f;
566   auto view = view_.rlock();
567 
568   for (f = view->latest_file; f; f = f->next) {
569     ctx->bumpNumWalked();
570     if (!w_query_file_matches_relative_root(ctx, f)) {
571       continue;
572     }
573 
574     w_query_process_file(
575         query,
576         ctx,
577         watchman::make_unique<InMemoryFileResult>(f, contentHashCache_));
578   }
579 }
580 
getMostRecentRootNumberAndTickValue() const581 ClockPosition InMemoryView::getMostRecentRootNumberAndTickValue() const {
582   auto view = view_.rlock();
583   return ClockPosition(view->rootNumber, view->mostRecentTick);
584 }
585 
getCurrentClockString() const586 w_string InMemoryView::getCurrentClockString() const {
587   auto view = view_.rlock();
588   char clockbuf[128];
589   if (!clock_id_string(
590           view->rootNumber, view->mostRecentTick, clockbuf, sizeof(clockbuf))) {
591     throw std::runtime_error("clock string exceeded clockbuf size");
592   }
593   return w_string(clockbuf, W_STRING_UNICODE);
594 }
595 
getLastAgeOutTickValue() const596 uint32_t InMemoryView::getLastAgeOutTickValue() const {
597   return last_age_out_tick;
598 }
599 
getLastAgeOutTimeStamp() const600 time_t InMemoryView::getLastAgeOutTimeStamp() const {
601   return last_age_out_timestamp;
602 }
603 
startThreads(const std::shared_ptr<w_root_t> & root)604 void InMemoryView::startThreads(const std::shared_ptr<w_root_t>& root) {
605   // Start a thread to call into the watcher API for filesystem notifications
606   auto self = std::static_pointer_cast<InMemoryView>(shared_from_this());
607   w_log(W_LOG_DBG, "starting threads for %p %s\n", this, root_path.c_str());
608   std::thread notifyThreadInstance([self, root]() {
609     w_set_thread_name("notify %p %s", self.get(), self->root_path.c_str());
610     try {
611       self->notifyThread(root);
612     } catch (const std::exception& e) {
613       watchman::log(watchman::ERR, "Exception: ", e.what(), " cancel root\n");
614       root->cancel();
615     }
616     watchman::log(watchman::DBG, "out of loop\n");
617   });
618   notifyThreadInstance.detach();
619 
620   // Wait for it to signal that the watcher has been initialized
621   bool pinged = false;
622   pending_.lockAndWait(std::chrono::milliseconds(-1) /* infinite */, pinged);
623 
624   // And now start the IO thread
625   std::thread ioThreadInstance([self, root]() {
626     w_set_thread_name("io %p %s", self.get(), self->root_path.c_str());
627     try {
628       self->ioThread(root);
629     } catch (const std::exception& e) {
630       watchman::log(watchman::ERR, "Exception: ", e.what(), " cancel root\n");
631       root->cancel();
632     }
633     watchman::log(watchman::DBG, "out of loop\n");
634   });
635   ioThreadInstance.detach();
636 }
637 
signalThreads()638 void InMemoryView::signalThreads() {
639   w_log(W_LOG_DBG, "signalThreads! %p %s\n", this, root_path.c_str());
640   stopThreads_ = true;
641   watcher_->signalThreads();
642   pending_.ping();
643 }
644 
wakeThreads()645 void InMemoryView::wakeThreads() {
646   pending_.ping();
647 }
648 
doAnyOfTheseFilesExist(const std::vector<w_string> & fileNames) const649 bool InMemoryView::doAnyOfTheseFilesExist(
650     const std::vector<w_string>& fileNames) const {
651   auto view = view_.rlock();
652   for (auto& name : fileNames) {
653     auto fullName = w_string::pathCat({root_path, name});
654     const auto dir = resolveDir(view, fullName.dirName());
655     if (!dir) {
656       continue;
657     }
658 
659     auto file = dir->getChildFile(fullName.baseName());
660     if (!file) {
661       continue;
662     }
663     if (file->exists) {
664       return true;
665     }
666   }
667   return false;
668 }
669 
getWatcher() const670 const std::shared_ptr<Watcher>& InMemoryView::getWatcher() const {
671   return watcher_;
672 }
673 
getName() const674 const w_string& InMemoryView::getName() const {
675   return watcher_->name;
676 }
677 
getSCM() const678 SCM* InMemoryView::getSCM() const {
679   return scm_.get();
680 }
681 
warmContentCache()682 void InMemoryView::warmContentCache() {
683   if (!enableContentCacheWarming_) {
684     return;
685   }
686 
687   watchman::log(
688       watchman::DBG, "considering files for content hash cache warming\n");
689 
690   size_t n = 0;
691   std::deque<Future<std::shared_ptr<const ContentHashCache::Node>>> futures;
692 
693   {
694     // Walk back in time until we hit the boundary, or hit the limit
695     // on the number of files we should warm up.
696     auto view = view_.rlock();
697     struct watchman_file* f;
698     for (f = view->latest_file; f && n < maxFilesToWarmInContentCache_;
699          f = f->next) {
700       if (f->otime.ticks <= lastWarmedTick_) {
701         watchman::log(
702             watchman::DBG,
703             "warmContentCache: stop because file ticks ",
704             f->otime.ticks,
705             " is <= lastWarmedTick_ ",
706             lastWarmedTick_,
707             "\n");
708         break;
709       }
710 
711       if (f->exists && f->stat.isFile()) {
712         // Note: we could also add an expression to further constrain
713         // the things we warm up here.  Let's see if we need it before
714         // going ahead and adding.
715 
716         auto dirStr = f->parent->getFullPath();
717         w_string_piece dir(dirStr);
718         dir.advance(contentHashCache_.rootPath().size());
719 
720         // If dirName is the root, dir.size() will now be zero
721         if (dir.size() > 0) {
722           // if not at the root, skip the slash character at the
723           // front of dir
724           dir.advance(1);
725         }
726         ContentHashCacheKey key{w_string::pathCat({dir, f->getName()}),
727                                 size_t(f->stat.size),
728                                 f->stat.mtime};
729 
730         watchman::log(
731             watchman::DBG, "warmContentCache: lookup ", key.relativePath, "\n");
732         auto f = contentHashCache_.get(key);
733         if (syncContentCacheWarming_) {
734           futures.emplace_back(std::move(f));
735         }
736         ++n;
737       }
738     }
739 
740     lastWarmedTick_ = view->mostRecentTick;
741   }
742 
743   watchman::log(
744       watchman::DBG,
745       "warmContentCache, lastWarmedTick_ now ",
746       lastWarmedTick_,
747       " scheduled ",
748       n,
749       " files for hashing, will wait for ",
750       futures.size(),
751       " lookups to finish\n");
752 
753   if (syncContentCacheWarming_) {
754     // Wait for them to finish, but don't use get() because we don't
755     // care about any errors that may have occurred.
756     collectAll(futures.begin(), futures.end()).wait();
757     watchman::log(watchman::DBG, "warmContentCache: hashing complete\n");
758   }
759 }
760 
debugContentHashCache(struct watchman_client * client,const json_ref & args)761 void InMemoryView::debugContentHashCache(
762     struct watchman_client* client,
763     const json_ref& args) {
764   /* resolve the root */
765   if (json_array_size(args) != 2) {
766     send_error_response(
767         client, "wrong number of arguments for 'debug-contenthash'");
768     return;
769   }
770 
771   auto root = resolve_root_or_err(client, args, 1, false);
772   if (!root) {
773     return;
774   }
775   auto view = std::dynamic_pointer_cast<watchman::InMemoryView>(root->view());
776   if (!view) {
777     send_error_response(client, "root is not an InMemoryView watcher");
778     return;
779   }
780 
781   auto stats = view->contentHashCache_.stats();
782   auto resp = make_response();
783   resp.set({{"cacheHit", json_integer(stats.cacheHit)},
784             {"cacheShare", json_integer(stats.cacheShare)},
785             {"cacheMiss", json_integer(stats.cacheMiss)},
786             {"cacheEvict", json_integer(stats.cacheEvict)},
787             {"cacheStore", json_integer(stats.cacheStore)},
788             {"cacheLoad", json_integer(stats.cacheLoad)},
789             {"cacheErase", json_integer(stats.cacheErase)},
790             {"clearCount", json_integer(stats.clearCount)},
791             {"size", json_integer(stats.size)}});
792   send_and_dispose_response(client, std::move(resp));
793 }
794 W_CMD_REG(
795     "debug-contenthash",
796     InMemoryView::debugContentHashCache,
797     CMD_DAEMON,
798     w_cmd_realpath_root);
799 }
800