GCC Code Coverage Report
Directory: cvmfs/ Exec Total Coverage
File: cvmfs/fuse_evict.cc Lines: 79 80 98.8 %
Date: 2019-02-03 02:48:13 Branches: 32 40 80.0 %

Line Branch Exec Source
1
/**
2
 * This file is part of the CernVM File System.
3
 */
4
5
#define __STDC_FORMAT_MACROS
6
7
#include "cvmfs_config.h"
8
#include "fuse_evict.h"
9
10
#include <inttypes.h>
11
#include <stdint.h>
12
13
#include <cassert>
14
#include <cstdlib>
15
#include <cstring>
16
17
#include "glue_buffer.h"
18
#include "logging.h"
19
#include "platform.h"
20
#include "shortstring.h"
21
#include "smalloc.h"
22
#include "util/posix.h"
23
24
using namespace std;  // NOLINT
25
26
4
FuseInvalidator::Handle::Handle(unsigned timeout_s)
27
4
  : timeout_s_((timeout_s == 0) ? 0 : (timeout_s + kTimeoutSafetyMarginSec))
28
{
29
4
  status_ = reinterpret_cast<atomic_int32 *>(smalloc(sizeof(atomic_int32)));
30
4
  atomic_init32(status_);
31
4
}
32
33
34
4
FuseInvalidator::Handle::~Handle() {
35
4
  free(status_);
36
4
}
37
38
39
5
void FuseInvalidator::Handle::WaitFor() {
40
5
  while (!IsDone()) SafeSleepMs(FuseInvalidator::kCheckTimeoutFreqMs);
41
5
}
42
43
44
//------------------------------------------------------------------------------
45
46
47
const unsigned FuseInvalidator::kTimeoutSafetyMarginSec = 1;
48
const unsigned FuseInvalidator::kCheckTimeoutFreqMs = 100;
49
const unsigned FuseInvalidator::kCheckTimeoutFreqOps = 256;
50
51
bool FuseInvalidator::g_fuse_notify_invalidation_ = true;
52
53
3
bool FuseInvalidator::HasFuseNotifyInval() {
54
  /**
55
   * Technically, also libfuse 2.8 has support.  Libfuse 2.8 comes with EL6,
56
   * which had bugs reported related to the fuse_notify_inval_...() functions.
57
   * Since just waiting for the timeout works perfectly fine, there is no reason
58
   * to optimize for forced cache eviction too aggressively.
59
   *
60
   * TODO(jblomer): could we have libfuse 2.9 or higher with a very old kernel
61
   * that doesn't support active invalidation?  How old does the kernel need
62
   * to be?  Probably that situation is never triggered in practice.
63
   */
64
3
  return FuseInvalidator::g_fuse_notify_invalidation_ && (FUSE_VERSION >= 29);
65
}
66
67
68
5
FuseInvalidator::FuseInvalidator(
69
  glue::InodeTracker *inode_tracker,
70
  struct fuse_chan **fuse_channel,
71
  bool fuse_notify_invalidation)
72
  : inode_tracker_(inode_tracker)
73
  , fuse_channel_(fuse_channel)
74
5
  , spawned_(false)
75
{
76
5
  g_fuse_notify_invalidation_ = fuse_notify_invalidation;
77
5
  MakePipe(pipe_ctrl_);
78
5
  memset(&thread_invalidator_, 0, sizeof(thread_invalidator_));
79
5
  atomic_init32(&terminated_);
80
5
}
81
82
83
5
FuseInvalidator::~FuseInvalidator() {
84
5
  atomic_cas32(&terminated_, 0, 1);
85
5
  if (spawned_) {
86
4
    char c = 'Q';
87
4
    WritePipe(pipe_ctrl_[1], &c, 1);
88
4
    pthread_join(thread_invalidator_, NULL);
89
  }
90
5
  ClosePipe(pipe_ctrl_);
91
}
92
93
94
5
void FuseInvalidator::InvalidateInodes(Handle *handle) {
95
5
  assert(handle != NULL);
96
5
  char c = 'I';
97
5
  WritePipe(pipe_ctrl_[1], &c, 1);
98
5
  WritePipe(pipe_ctrl_[1], &handle, sizeof(handle));
99
5
}
100
101
102
4
void *FuseInvalidator::MainInvalidator(void *data) {
103
4
  FuseInvalidator *invalidator = reinterpret_cast<FuseInvalidator *>(data);
104
4
  LogCvmfs(kLogCvmfs, kLogDebug, "starting dentry invalidator thread");
105
106
  char c;
107
  Handle *handle;
108
5
  while (true) {
109
9
    ReadPipe(invalidator->pipe_ctrl_[0], &c, 1);
110
9
    if (c == 'Q')
111
4
      break;
112
113
5
    assert(c == 'I');
114
5
    ReadPipe(invalidator->pipe_ctrl_[0], &handle, sizeof(handle));
115
    LogCvmfs(kLogCvmfs, kLogDebug, "invalidating kernel caches, timeout %u",
116
5
             handle->timeout_s_);
117
118
5
    uint64_t deadline = platform_monotonic_time() + handle->timeout_s_;
119
120
    // Fallback: drainout by timeout
121

5
    if ((invalidator->fuse_channel_ == NULL) || !HasFuseNotifyInval()) {
122
4
      while (platform_monotonic_time() < deadline) {
123
1
        SafeSleepMs(kCheckTimeoutFreqMs);
124
1
        if (atomic_read32(&invalidator->terminated_) == 1) {
125
          LogCvmfs(kLogCvmfs, kLogDebug,
126
1
                   "cancel cache eviction due to termination");
127
1
          break;
128
        }
129
      }
130
2
      handle->SetDone();
131
2
      continue;
132
    }
133
134
    // We must not hold a lock when calling fuse_lowlevel_notify_inval_entry.
135
    // Therefore, we first copy all the inodes into a temporary data structure.
136
    glue::InodeTracker::Cursor cursor(
137
3
      invalidator->inode_tracker_->BeginEnumerate());
138
    uint64_t inode;
139
3078
    while (invalidator->inode_tracker_->NextInode(&cursor, &inode))
140
    {
141
3072
      invalidator->evict_list_.PushBack(inode);
142
    }
143
3
    invalidator->inode_tracker_->EndEnumerate(&cursor);
144
145
3
    unsigned i = 0;
146
3
    unsigned N = invalidator->evict_list_.size();
147
1540
    while (i < N) {
148
1536
      uint64_t inode = invalidator->evict_list_.At(i);
149
1536
      if (inode == 0)
150
        inode = FUSE_ROOT_ID;
151
      // Can fail, e.g. the inode might be already evicted
152
      fuse_lowlevel_notify_inval_inode(
153
1536
        *invalidator->fuse_channel_, inode, 0, 0);
154
1536
      LogCvmfs(kLogCvmfs, kLogDebug, "evicting inode %" PRIu64, inode);
155
156
1536
      if ((++i % kCheckTimeoutFreqOps) == 0) {
157
6
        if (platform_monotonic_time() >= deadline) {
158
          LogCvmfs(kLogCvmfs, kLogDebug,
159
1
                   "cancel cache eviction after %u entries due to timeout", i);
160
1
          break;
161
        }
162
5
        if (atomic_read32(&invalidator->terminated_) == 1) {
163
          LogCvmfs(kLogCvmfs, kLogDebug,
164
1
                   "cancel cache eviction due to termination");
165
1
          break;
166
        }
167
      }
168
    }
169
3
    handle->SetDone();
170
3
    invalidator->evict_list_.Clear();
171
  }
172
173
4
  LogCvmfs(kLogCvmfs, kLogDebug, "stopping dentry invalidator thread");
174
4
  return NULL;
175
}
176
177
178
4
void FuseInvalidator::Spawn() {
179
  int retval;
180
4
  retval = pthread_create(&thread_invalidator_, NULL, MainInvalidator, this);
181
4
  assert(retval == 0);
182
4
  spawned_ = true;
183

49
}