Skip to content

Commit

Permalink
detect when cookie dir has gone awol
Browse files Browse the repository at this point in the history
Summary:
I got here because `btrfs` doesn't send `IN_DELETE_SELF` notifications
when a watched subvolume is unmounted.  Until that is addresses in the kernel
we tackle this from a different angle.  We periodically perform a synchronized
`clock` call against each of the watches.  When that happens and a btrfs volume
has been deleted then the cookie sync will error out because the directory
structure no longer exists.

Let's catch that case and generate a recrawl; the recrawl will discover the
removal and cancel the watch.

And while we're in here, let's also deal with just the vcs subdir going away.

Refs: facebook#25
Refs: facebook#501

Reviewed By: simpkins

Differential Revision: D7014364

fbshipit-source-id: 9acd20efa843563626b73c6f6e34c3787dd28a39
  • Loading branch information
wez authored and ip4368 committed Dec 10, 2018
1 parent 2108f80 commit 7c1fdc6
Show file tree
Hide file tree
Showing 2 changed files with 79 additions and 4 deletions.
45 changes: 41 additions & 4 deletions root/sync.cpp
Original file line number Diff line number Diff line change
@@ -1,13 +1,28 @@
/* Copyright 2012-present Facebook, Inc.
* Licensed under the Apache License, Version 2.0 */

#include "watchman.h"
#include "InMemoryView.h"
#include "watchman.h"
#include "watchman_error_category.h"

using namespace watchman;

namespace {
class NeedRecrawl : public std::runtime_error {
public:
using std::runtime_error::runtime_error;
};
} // namespace

bool watchman_root::syncToNow(std::chrono::milliseconds timeout) {
w_perf_t sample("sync_to_now");

auto res = view()->syncToNow(timeout);
bool res;
try {
res = view()->syncToNow(timeout);
} catch (const NeedRecrawl& exc) {
scheduleRecrawl(exc.what());
return false;
}

// We want to know about all timeouts
if (!res) {
Expand Down Expand Up @@ -35,7 +50,29 @@ bool watchman_root::syncToNow(std::chrono::milliseconds timeout) {
* time, false otherwise.
*/
bool watchman::InMemoryView::syncToNow(std::chrono::milliseconds timeout) {
return cookies_.syncToNow(timeout);
try {
return cookies_.syncToNow(timeout);
} catch (const std::system_error& exc) {
// Note that timeouts in syncToNow are reported as a `false` return
// value, so if we get any exception here then something must be
// really wrong. In practice the most likely cause is that
// the cookie dir no longer exists. The sanest sounding thing
// to do in this situation is schedule a recrawl.

if (exc.code() == watchman::error_code::no_such_file_or_directory ||
exc.code() == watchman::error_code::not_a_directory) {
if (cookies_.cookieDir() == root_path) {
throw NeedRecrawl("root dir was removed and we didn't get notified");
} else {
// The cookie dir was a VCS subdir and it got deleted. Let's
// focus instead on the parent dir and recursively retry.
cookies_.setCookieDir(root_path);
return cookies_.syncToNow(timeout);
}
}

throw;
}
}

/* vim:ts=2:sw=2:et:
Expand Down
38 changes: 38 additions & 0 deletions tests/integration/test_cookie.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
# vim:ts=4:sw=4:et:
# Copyright 2018-present Facebook, Inc.
# Licensed under the Apache License, Version 2.0

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
# no unicode literals

import WatchmanTestCase
import os
import pywatchman


@WatchmanTestCase.expand_matrix
class TestCookie(WatchmanTestCase.WatchmanTestCase):

def test_delete_cookie_dir(self):
root = self.mkdtemp()
cookie_dir = os.path.join(root, '.hg')
os.mkdir(cookie_dir)
self.touchRelative(root, 'foo')

self.watchmanCommand('watch-project', root)
self.assertFileList(root, files=['foo', '.hg'])

os.rmdir(cookie_dir)
self.assertFileList(root, files=['foo'])
os.unlink(os.path.join(root, 'foo'))
self.assertFileList(root, files=[])
os.rmdir(root)
with self.assertRaises(pywatchman.WatchmanError) as ctx:
self.assertFileList(root, files=[])
reason = str(ctx.exception)
self.assertTrue(
('No such file' in reason) or
('unable to resolve root' in reason),
msg=reason)

0 comments on commit 7c1fdc6

Please sign in to comment.