CernVM-FS  2.12.0
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Pages
fuse_evict.cc
Go to the documentation of this file.
1 
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 "mountpoint.h"
19 #include "shortstring.h"
20 #include "util/logging.h"
21 #include "util/platform.h"
22 #include "util/posix.h"
23 #include "util/smalloc.h"
24 
25 using namespace std; // NOLINT
26 
28  : timeout_s_((timeout_s == 0) ? 0 : (timeout_s + kTimeoutSafetyMarginSec))
29 {
30  status_ = reinterpret_cast<atomic_int32 *>(smalloc(sizeof(atomic_int32)));
31  atomic_init32(status_);
32 }
33 
34 
36  free(status_);
37 }
38 
39 
42 }
43 
44 
45 //------------------------------------------------------------------------------
46 
47 
49 const unsigned FuseInvalidator::kCheckTimeoutFreqMs = 100;
50 const unsigned FuseInvalidator::kCheckTimeoutFreqOps = 256;
51 
53 
65  return FuseInvalidator::g_fuse_notify_invalidation_ && (FUSE_VERSION >= 29);
66 }
67 
68 
70  MountPoint *mount_point,
71  void **fuse_channel_or_session,
72  bool fuse_notify_invalidation)
73  : mount_point_(mount_point)
74  , inode_tracker_(mount_point->inode_tracker())
75  , dentry_tracker_(mount_point->dentry_tracker())
76  , fuse_channel_or_session_(fuse_channel_or_session)
77  , spawned_(false)
78 {
79  g_fuse_notify_invalidation_ = fuse_notify_invalidation;
81  memset(&thread_invalidator_, 0, sizeof(thread_invalidator_));
82  atomic_init32(&terminated_);
83 }
84 
86  glue::InodeTracker *inode_tracker,
87  glue::DentryTracker *dentry_tracker,
88  void **fuse_channel_or_session,
89  bool fuse_notify_invalidation)
90  : mount_point_(NULL)
91  , inode_tracker_(inode_tracker)
92  , dentry_tracker_(dentry_tracker)
93  , fuse_channel_or_session_(fuse_channel_or_session)
94  , spawned_(false)
95 {
96  g_fuse_notify_invalidation_ = fuse_notify_invalidation;
98  memset(&thread_invalidator_, 0, sizeof(thread_invalidator_));
99  atomic_init32(&terminated_);
100 }
101 
102 
104  atomic_cas32(&terminated_, 0, 1);
105  if (spawned_) {
106  char c = 'Q';
107  WritePipe(pipe_ctrl_[1], &c, 1);
108  pthread_join(thread_invalidator_, NULL);
109  }
111 }
112 
113 
115  assert(handle != NULL);
116  char c = 'I';
117  WritePipe(pipe_ctrl_[1], &c, 1);
118  WritePipe(pipe_ctrl_[1], &handle, sizeof(handle));
119 }
120 
122  uint64_t parent_ino, const NameString &name)
123 {
124  char c = 'D';
125  WritePipe(pipe_ctrl_[1], &c, 1);
126  WritePipe(pipe_ctrl_[1], &parent_ino, sizeof(parent_ino));
127  unsigned len = name.GetLength();
128  WritePipe(pipe_ctrl_[1], &len, sizeof(len));
129  WritePipe(pipe_ctrl_[1], name.GetChars(), len);
130 }
131 
133  FuseInvalidator *invalidator = reinterpret_cast<FuseInvalidator *>(data);
134  LogCvmfs(kLogCvmfs, kLogDebug, "starting dentry invalidator thread");
135 
136  bool reported_missing_inval_support = false;
137  char c;
138  Handle *handle;
139  while (true) {
140  ReadPipe(invalidator->pipe_ctrl_[0], &c, 1);
141  if (c == 'Q')
142  break;
143 
144  if (c == 'D') {
145  uint64_t parent_ino;
146  unsigned len;
147  ReadPipe(invalidator->pipe_ctrl_[0], &parent_ino, sizeof(parent_ino));
148  ReadPipe(invalidator->pipe_ctrl_[0], &len, sizeof(len));
149  char *name = static_cast<char *>(smalloc(len + 1));
150  ReadPipe(invalidator->pipe_ctrl_[0], name, len);
151  name[len] = '\0';
152  if (invalidator->fuse_channel_or_session_ == NULL) {
153  if (!reported_missing_inval_support) {
155  "missing fuse support for dentry invalidation (%lu/%s)",
156  parent_ino, name);
157  reported_missing_inval_support = true;
158  }
159  free(name);
160  continue;
161  }
162  LogCvmfs(kLogCvmfs, kLogDebug, "evicting single dentry %" PRIu64 "/%s",
163  parent_ino, name);
164 #if CVMFS_USE_LIBFUSE == 2
165  fuse_lowlevel_notify_inval_entry(*reinterpret_cast<struct fuse_chan**>(
166  invalidator->fuse_channel_or_session_), parent_ino, name, len);
167 #else
168  fuse_lowlevel_notify_inval_entry(*reinterpret_cast<struct fuse_session**>(
169  invalidator->fuse_channel_or_session_), parent_ino, name, len);
170 #endif
171  free(name);
172  continue;
173  }
174 
175  assert(c == 'I');
176  ReadPipe(invalidator->pipe_ctrl_[0], &handle, sizeof(handle));
177  LogCvmfs(kLogCvmfs, kLogDebug, "invalidating kernel caches, timeout %u",
178  handle->timeout_s_);
179 
180  uint64_t deadline = platform_monotonic_time() + handle->timeout_s_;
181 
182  // Fallback: drainout by timeout
183  if ((invalidator->fuse_channel_or_session_ == NULL) ||
185  {
186  while (platform_monotonic_time() < deadline) {
188  if (atomic_read32(&invalidator->terminated_) == 1) {
190  "cancel cache eviction due to termination");
191  break;
192  }
193  }
194  handle->SetDone();
195  continue;
196  }
197 
198  // We must not hold a lock when calling fuse_lowlevel_notify_inval_entry.
199  // Therefore, we first copy all the inodes into a temporary data structure.
200  glue::InodeTracker::Cursor inode_cursor(
201  invalidator->inode_tracker_->BeginEnumerate());
202  uint64_t inode;
203  while (invalidator->inode_tracker_->NextInode(&inode_cursor, &inode))
204  {
205  invalidator->evict_list_.PushBack(inode);
206  }
207  invalidator->inode_tracker_->EndEnumerate(&inode_cursor);
208 
209  unsigned i = 0;
210  unsigned N = invalidator->evict_list_.size();
211  while (i < N) {
212  uint64_t inode = invalidator->evict_list_.At(i);
213  if (inode == 0)
214  inode = FUSE_ROOT_ID;
215  // Can fail, e.g. the inode might be already evicted
216 
217  int dbg_retval;
218 
219 #if CVMFS_USE_LIBFUSE == 2
220  dbg_retval = fuse_lowlevel_notify_inval_inode(
221  *reinterpret_cast<struct fuse_chan**>(
222  invalidator->fuse_channel_or_session_), inode, 0, 0);
223 #else
224  dbg_retval = fuse_lowlevel_notify_inval_inode(
225  *reinterpret_cast<struct fuse_session**>(
226  invalidator->fuse_channel_or_session_), inode, 0, 0);
227 #endif
229  "evicting inode %" PRIu64 " with retval: %d",
230  inode, dbg_retval);
231 
232  (void) dbg_retval; // prevent compiler complaining
233 
234  if ((++i % kCheckTimeoutFreqOps) == 0) {
235  if (platform_monotonic_time() >= deadline) {
237  "cancel cache eviction after %u entries due to timeout", i);
238  break;
239  }
240  if (atomic_read32(&invalidator->terminated_) == 1) {
242  "cancel cache eviction due to termination");
243  break;
244  }
245  }
246  }
247 
248  // Do the dentry tracker last to increase the effectiveness of pruning
249  invalidator->dentry_tracker_->Prune();
250  // Copy and empty the dentry tracker in a single atomic operation
251  glue::DentryTracker *dentries_copy = invalidator->dentry_tracker_->Move();
252  glue::DentryTracker::Cursor dentry_cursor = dentries_copy->BeginEnumerate();
253  uint64_t entry_parent;
254  NameString entry_name;
255  i = 0;
256 
257 #if CVMFS_USE_LIBFUSE == 2
258  int (*notify_func)(struct fuse_chan*, fuse_ino_t, const char*, size_t);
259  notify_func = &fuse_lowlevel_notify_inval_entry;
260 #else
261  int (*notify_func)(struct fuse_session*, fuse_ino_t, const char*, size_t);
262  notify_func = &fuse_lowlevel_notify_inval_entry;
263 #if FUSE_VERSION >= FUSE_MAKE_VERSION(3, 16)
264  // must be libfuse >= 3.16, otherwise the signature is wrong and it
265  // will fail building
266  // mount_point can only be NULL for unittests
267  if (invalidator->mount_point_ != NULL &&
268  invalidator->mount_point_->fuse_expire_entry()) {
269  notify_func = &fuse_lowlevel_notify_expire_entry;
270  }
271 #endif
272 #endif
273 
274  while (dentries_copy->NextEntry(&dentry_cursor, &entry_parent, &entry_name))
275  {
276  LogCvmfs(kLogCvmfs, kLogDebug, "evicting dentry %lu --> %s",
277  entry_parent, entry_name.c_str());
278  // Can fail, e.g. the entry might be already evicted
279 #if CVMFS_USE_LIBFUSE == 2
280  struct fuse_chan* channel_or_session =
281  *reinterpret_cast<struct fuse_chan**>(
282  invalidator->fuse_channel_or_session_);
283 #else
284  struct fuse_session* channel_or_session =
285  *reinterpret_cast<struct fuse_session**>(
286  invalidator->fuse_channel_or_session_);
287 #endif
288 
289  notify_func(channel_or_session, entry_parent, entry_name.GetChars(),
290  entry_name.GetLength());
291 
292  if ((++i % kCheckTimeoutFreqOps) == 0) {
293  if (atomic_read32(&invalidator->terminated_) == 1) {
295  "cancel cache eviction due to termination");
296  break;
297  }
298  }
299  }
300  dentries_copy->EndEnumerate(&dentry_cursor);
301  delete dentries_copy;
302 
303  handle->SetDone();
304  invalidator->evict_list_.Clear();
305  }
306 
307  LogCvmfs(kLogCvmfs, kLogDebug, "stopping dentry invalidator thread");
308  return NULL;
309 }
310 
311 
313  int retval;
314  retval = pthread_create(&thread_invalidator_, NULL, MainInvalidator, this);
315  assert(retval == 0);
316  spawned_ = true;
317 }
atomic_int32 terminated_
Definition: fuse_evict.h:116
Item At(const size_t index) const
Definition: bigvector.h:50
bool fuse_expire_entry()
Definition: mountpoint.h:520
Cursor BeginEnumerate()
Definition: glue_buffer.h:763
glue::DentryTracker * dentry_tracker_
Definition: fuse_evict.h:104
assert((mem||(size==0))&&"Out Of Memory")
MountPoint * mount_point_
Definition: cvmfs.cc:128
static const unsigned kCheckTimeoutFreqMs
Definition: fuse_evict.h:92
bool NextEntry(Cursor *cursor, uint64_t *inode_parent, NameString *name)
Definition: glue_buffer.cc:246
void EndEnumerate(Cursor *cursor)
Definition: glue_buffer.h:788
void MakePipe(int pipe_fd[2])
Definition: posix.cc:492
static bool HasFuseNotifyInval()
Definition: fuse_evict.cc:54
static const unsigned kTimeoutSafetyMarginSec
Definition: fuse_evict.h:88
int32_t atomic_int32
Definition: atomic.h:17
void EndEnumerate(Cursor *cursor)
Definition: glue_buffer.cc:261
static const unsigned kCheckTimeoutFreqOps
Definition: fuse_evict.h:97
BigVector< uint64_t > evict_list_
Definition: fuse_evict.h:117
void ** fuse_channel_or_session_
Definition: fuse_evict.h:108
bool NextInode(Cursor *cursor, uint64_t *inode)
Definition: glue_buffer.h:784
atomic_int32 * status_
Definition: fuse_evict.h:64
void Clear()
Definition: bigvector.h:76
Handle(unsigned timeout_s)
Definition: fuse_evict.cc:27
DentryTracker * Move()
Definition: glue_buffer.cc:160
uint64_t platform_monotonic_time()
MountPoint * mount_point_
Definition: fuse_evict.h:101
void InvalidateDentry(uint64_t parent_ino, const NameString &name)
Definition: fuse_evict.cc:121
void PushBack(const Item &item)
Definition: bigvector.h:60
glue::InodeTracker * inode_tracker_
Definition: fuse_evict.h:103
static bool g_fuse_notify_invalidation_
Definition: fuse_evict.h:119
int pipe_ctrl_[2]
Definition: fuse_evict.h:110
void InvalidateInodes(Handle *handle)
Definition: fuse_evict.cc:114
pthread_t thread_invalidator_
Definition: fuse_evict.h:111
void SafeSleepMs(const unsigned ms)
Definition: posix.cc:1975
unsigned GetLength() const
Definition: shortstring.h:131
const char * c_str() const
Definition: shortstring.h:145
const char * GetChars() const
Definition: shortstring.h:123
void WritePipe(int fd, const void *buf, size_t nbyte)
Definition: posix.cc:501
void ReadPipe(int fd, void *buf, size_t nbyte)
Definition: posix.cc:513
void ClosePipe(int pipe_fd[2])
Definition: posix.cc:551
static void * MainInvalidator(void *data)
Definition: fuse_evict.cc:132
FuseInvalidator(MountPoint *mountpoint, void **fuse_channel_or_session, bool fuse_notify_invalidation)
Definition: fuse_evict.cc:69
size_t size() const
Definition: bigvector.h:121
CVMFS_EXPORT void LogCvmfs(const LogSource source, const int mask, const char *format,...)
Definition: logging.cc:528