CernVM-FS  2.11.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 "shortstring.h"
19 #include "util/logging.h"
20 #include "util/platform.h"
21 #include "util/posix.h"
22 #include "util/smalloc.h"
23 
24 using namespace std; // NOLINT
25 
27  : timeout_s_((timeout_s == 0) ? 0 : (timeout_s + kTimeoutSafetyMarginSec))
28 {
29  status_ = reinterpret_cast<atomic_int32 *>(smalloc(sizeof(atomic_int32)));
30  atomic_init32(status_);
31 }
32 
33 
35  free(status_);
36 }
37 
38 
41 }
42 
43 
44 //------------------------------------------------------------------------------
45 
46 
48 const unsigned FuseInvalidator::kCheckTimeoutFreqMs = 100;
49 const unsigned FuseInvalidator::kCheckTimeoutFreqOps = 256;
50 
52 
64  return FuseInvalidator::g_fuse_notify_invalidation_ && (FUSE_VERSION >= 29);
65 }
66 
67 
69  glue::InodeTracker *inode_tracker,
70  glue::DentryTracker *dentry_tracker,
71  void **fuse_channel_or_session,
72  bool fuse_notify_invalidation)
73  : inode_tracker_(inode_tracker)
74  , dentry_tracker_(dentry_tracker)
75  , fuse_channel_or_session_(fuse_channel_or_session)
76  , spawned_(false)
77 {
78  g_fuse_notify_invalidation_ = fuse_notify_invalidation;
80  memset(&thread_invalidator_, 0, sizeof(thread_invalidator_));
81  atomic_init32(&terminated_);
82 }
83 
84 
86  atomic_cas32(&terminated_, 0, 1);
87  if (spawned_) {
88  char c = 'Q';
89  WritePipe(pipe_ctrl_[1], &c, 1);
90  pthread_join(thread_invalidator_, NULL);
91  }
93 }
94 
95 
97  assert(handle != NULL);
98  char c = 'I';
99  WritePipe(pipe_ctrl_[1], &c, 1);
100  WritePipe(pipe_ctrl_[1], &handle, sizeof(handle));
101 }
102 
104  uint64_t parent_ino, const NameString &name)
105 {
106  char c = 'D';
107  WritePipe(pipe_ctrl_[1], &c, 1);
108  WritePipe(pipe_ctrl_[1], &parent_ino, sizeof(parent_ino));
109  unsigned len = name.GetLength();
110  WritePipe(pipe_ctrl_[1], &len, sizeof(len));
111  WritePipe(pipe_ctrl_[1], name.GetChars(), len);
112 }
113 
114 
116  FuseInvalidator *invalidator = reinterpret_cast<FuseInvalidator *>(data);
117  LogCvmfs(kLogCvmfs, kLogDebug, "starting dentry invalidator thread");
118 
119  bool reported_missing_inval_support = false;
120  char c;
121  Handle *handle;
122  while (true) {
123  ReadPipe(invalidator->pipe_ctrl_[0], &c, 1);
124  if (c == 'Q')
125  break;
126 
127  if (c == 'D') {
128  uint64_t parent_ino;
129  unsigned len;
130  ReadPipe(invalidator->pipe_ctrl_[0], &parent_ino, sizeof(parent_ino));
131  ReadPipe(invalidator->pipe_ctrl_[0], &len, sizeof(len));
132  char *name = static_cast<char *>(smalloc(len + 1));
133  ReadPipe(invalidator->pipe_ctrl_[0], name, len);
134  name[len] = '\0';
135  if (invalidator->fuse_channel_or_session_ == NULL) {
136  if (!reported_missing_inval_support) {
138  "missing fuse support for dentry invalidation (%d/%s)",
139  parent_ino, name);
140  reported_missing_inval_support = true;
141  }
142  free(name);
143  continue;
144  }
145  LogCvmfs(kLogCvmfs, kLogDebug, "evicting single dentry %" PRIu64 "/%s",
146  parent_ino, name);
147 #if CVMFS_USE_LIBFUSE == 2
148  fuse_lowlevel_notify_inval_entry(*reinterpret_cast<struct fuse_chan**>(
149  invalidator->fuse_channel_or_session_), parent_ino, name, len);
150 #else
151  fuse_lowlevel_notify_inval_entry(*reinterpret_cast<struct fuse_session**>(
152  invalidator->fuse_channel_or_session_), parent_ino, name, len);
153 #endif
154  free(name);
155  continue;
156  }
157 
158  assert(c == 'I');
159  ReadPipe(invalidator->pipe_ctrl_[0], &handle, sizeof(handle));
160  LogCvmfs(kLogCvmfs, kLogDebug, "invalidating kernel caches, timeout %u",
161  handle->timeout_s_);
162 
163  uint64_t deadline = platform_monotonic_time() + handle->timeout_s_;
164 
165  // Fallback: drainout by timeout
166  if ((invalidator->fuse_channel_or_session_ == NULL) ||
168  {
169  while (platform_monotonic_time() < deadline) {
171  if (atomic_read32(&invalidator->terminated_) == 1) {
173  "cancel cache eviction due to termination");
174  break;
175  }
176  }
177  handle->SetDone();
178  continue;
179  }
180 
181  // We must not hold a lock when calling fuse_lowlevel_notify_inval_entry.
182  // Therefore, we first copy all the inodes into a temporary data structure.
183  glue::InodeTracker::Cursor inode_cursor(
184  invalidator->inode_tracker_->BeginEnumerate());
185  uint64_t inode;
186  while (invalidator->inode_tracker_->NextInode(&inode_cursor, &inode))
187  {
188  invalidator->evict_list_.PushBack(inode);
189  }
190  invalidator->inode_tracker_->EndEnumerate(&inode_cursor);
191 
192  unsigned i = 0;
193  unsigned N = invalidator->evict_list_.size();
194  while (i < N) {
195  uint64_t inode = invalidator->evict_list_.At(i);
196  if (inode == 0)
197  inode = FUSE_ROOT_ID;
198  // Can fail, e.g. the inode might be already evicted
199 #if CVMFS_USE_LIBFUSE == 2
200  fuse_lowlevel_notify_inval_inode(*reinterpret_cast<struct fuse_chan**>(
201  invalidator->fuse_channel_or_session_), inode, 0, 0);
202 #else
203  fuse_lowlevel_notify_inval_inode(*reinterpret_cast<struct fuse_session**>(
204  invalidator->fuse_channel_or_session_), inode, 0, 0);
205 #endif
206  LogCvmfs(kLogCvmfs, kLogDebug, "evicting inode %" PRIu64, inode);
207 
208  if ((++i % kCheckTimeoutFreqOps) == 0) {
209  if (platform_monotonic_time() >= deadline) {
211  "cancel cache eviction after %u entries due to timeout", i);
212  break;
213  }
214  if (atomic_read32(&invalidator->terminated_) == 1) {
216  "cancel cache eviction due to termination");
217  break;
218  }
219  }
220  }
221 
222  // Do the dentry tracker last to increase the effectiveness of pruning
223  invalidator->dentry_tracker_->Prune();
224  // Copy and empty the dentry tracker in a single atomic operation
225  glue::DentryTracker *dentries_copy = invalidator->dentry_tracker_->Move();
226  glue::DentryTracker::Cursor dentry_cursor = dentries_copy->BeginEnumerate();
227  uint64_t entry_parent;
228  NameString entry_name;
229  i = 0;
230  while (dentries_copy->NextEntry(&dentry_cursor, &entry_parent, &entry_name))
231  {
232  LogCvmfs(kLogCvmfs, kLogDebug, "evicting dentry %d --> %s",
233  entry_parent, entry_name.c_str());
234  // Can fail, e.g. the entry might be already evicted
235 #if CVMFS_USE_LIBFUSE == 2
236  fuse_lowlevel_notify_inval_entry(*reinterpret_cast<struct fuse_chan**>(
237  invalidator->fuse_channel_or_session_),
238  entry_parent, entry_name.GetChars(), entry_name.GetLength());
239 #else
240  fuse_lowlevel_notify_inval_entry(*reinterpret_cast<struct fuse_session**>(
241  invalidator->fuse_channel_or_session_),
242  entry_parent, entry_name.GetChars(), entry_name.GetLength());
243 #endif
244  if ((++i % kCheckTimeoutFreqOps) == 0) {
245  if (atomic_read32(&invalidator->terminated_) == 1) {
247  "cancel cache eviction due to termination");
248  break;
249  }
250  }
251  }
252  dentries_copy->EndEnumerate(&dentry_cursor);
253  delete dentries_copy;
254 
255  handle->SetDone();
256  invalidator->evict_list_.Clear();
257  }
258 
259  LogCvmfs(kLogCvmfs, kLogDebug, "stopping dentry invalidator thread");
260  return NULL;
261 }
262 
263 
265  int retval;
266  retval = pthread_create(&thread_invalidator_, NULL, MainInvalidator, this);
267  assert(retval == 0);
268  spawned_ = true;
269 }
#define LogCvmfs(source, mask,...)
Definition: logging.h:22
atomic_int32 terminated_
Definition: fuse_evict.h:104
Item At(const size_t index) const
Definition: bigvector.h:50
Cursor BeginEnumerate()
Definition: glue_buffer.h:753
glue::DentryTracker * dentry_tracker_
Definition: fuse_evict.h:92
FuseInvalidator(glue::InodeTracker *inode_tracker, glue::DentryTracker *dentry_tracker, void **fuse_channel_or_session, bool fuse_notify_invalidation)
Definition: fuse_evict.cc:68
assert((mem||(size==0))&&"Out Of Memory")
static const unsigned kCheckTimeoutFreqMs
Definition: fuse_evict.h:82
bool NextEntry(Cursor *cursor, uint64_t *inode_parent, NameString *name)
Definition: glue_buffer.cc:245
void EndEnumerate(Cursor *cursor)
Definition: glue_buffer.h:778
void MakePipe(int pipe_fd[2])
Definition: posix.cc:487
static bool HasFuseNotifyInval()
Definition: fuse_evict.cc:53
static const unsigned kTimeoutSafetyMarginSec
Definition: fuse_evict.h:78
int32_t atomic_int32
Definition: atomic.h:17
void EndEnumerate(Cursor *cursor)
Definition: glue_buffer.cc:260
static const unsigned kCheckTimeoutFreqOps
Definition: fuse_evict.h:87
BigVector< uint64_t > evict_list_
Definition: fuse_evict.h:105
void ** fuse_channel_or_session_
Definition: fuse_evict.h:96
bool NextInode(Cursor *cursor, uint64_t *inode)
Definition: glue_buffer.h:774
atomic_int32 * status_
Definition: fuse_evict.h:61
void Clear()
Definition: bigvector.h:76
Handle(unsigned timeout_s)
Definition: fuse_evict.cc:26
DentryTracker * Move()
Definition: glue_buffer.cc:159
uint64_t platform_monotonic_time()
void InvalidateDentry(uint64_t parent_ino, const NameString &name)
Definition: fuse_evict.cc:103
void PushBack(const Item &item)
Definition: bigvector.h:60
glue::InodeTracker * inode_tracker_
Definition: fuse_evict.h:91
static bool g_fuse_notify_invalidation_
Definition: fuse_evict.h:107
int pipe_ctrl_[2]
Definition: fuse_evict.h:98
void InvalidateInodes(Handle *handle)
Definition: fuse_evict.cc:96
pthread_t thread_invalidator_
Definition: fuse_evict.h:99
void SafeSleepMs(const unsigned ms)
Definition: posix.cc:1879
unsigned GetLength() const
Definition: shortstring.h:116
const char * c_str() const
Definition: shortstring.h:130
const char * GetChars() const
Definition: shortstring.h:108
void WritePipe(int fd, const void *buf, size_t nbyte)
Definition: posix.cc:496
void ReadPipe(int fd, void *buf, size_t nbyte)
Definition: posix.cc:508
void ClosePipe(int pipe_fd[2])
Definition: posix.cc:546
static void * MainInvalidator(void *data)
Definition: fuse_evict.cc:115
size_t size() const
Definition: bigvector.h:121