1 //  Copyright (c) 2011-present, Facebook, Inc.  All rights reserved.
2 //  This source code is licensed under both the GPLv2 (found in the
3 //  COPYING file in the root directory) and Apache 2.0 License
4 //  (found in the LICENSE.Apache file in the root directory).
5 
6 #ifndef ROCKSDB_LITE
7 
8 #include "file/delete_scheduler.h"
9 
10 #include <thread>
11 #include <vector>
12 
13 #include "file/sst_file_manager_impl.h"
14 #include "logging/logging.h"
15 #include "port/port.h"
16 #include "rocksdb/env.h"
17 #include "test_util/sync_point.h"
18 #include "util/mutexlock.h"
19 
20 namespace ROCKSDB_NAMESPACE {
21 
DeleteScheduler(Env * env,FileSystem * fs,int64_t rate_bytes_per_sec,Logger * info_log,SstFileManagerImpl * sst_file_manager,double max_trash_db_ratio,uint64_t bytes_max_delete_chunk)22 DeleteScheduler::DeleteScheduler(Env* env, FileSystem* fs,
23                                  int64_t rate_bytes_per_sec, Logger* info_log,
24                                  SstFileManagerImpl* sst_file_manager,
25                                  double max_trash_db_ratio,
26                                  uint64_t bytes_max_delete_chunk)
27     : env_(env),
28       fs_(fs),
29       total_trash_size_(0),
30       rate_bytes_per_sec_(rate_bytes_per_sec),
31       pending_files_(0),
32       bytes_max_delete_chunk_(bytes_max_delete_chunk),
33       closing_(false),
34       cv_(&mu_),
35       bg_thread_(nullptr),
36       info_log_(info_log),
37       sst_file_manager_(sst_file_manager),
38       max_trash_db_ratio_(max_trash_db_ratio) {
39   assert(sst_file_manager != nullptr);
40   assert(max_trash_db_ratio >= 0);
41   MaybeCreateBackgroundThread();
42 }
43 
~DeleteScheduler()44 DeleteScheduler::~DeleteScheduler() {
45   {
46     InstrumentedMutexLock l(&mu_);
47     closing_ = true;
48     cv_.SignalAll();
49   }
50   if (bg_thread_) {
51     bg_thread_->join();
52   }
53 }
54 
DeleteFile(const std::string & file_path,const std::string & dir_to_sync,const bool force_bg)55 Status DeleteScheduler::DeleteFile(const std::string& file_path,
56                                    const std::string& dir_to_sync,
57                                    const bool force_bg) {
58   Status s;
59   if (rate_bytes_per_sec_.load() <= 0 || (!force_bg &&
60       total_trash_size_.load() >
61           sst_file_manager_->GetTotalSize() * max_trash_db_ratio_.load())) {
62     // Rate limiting is disabled or trash size makes up more than
63     // max_trash_db_ratio_ (default 25%) of the total DB size
64     TEST_SYNC_POINT("DeleteScheduler::DeleteFile");
65     s = fs_->DeleteFile(file_path, IOOptions(), nullptr);
66     if (s.ok()) {
67       sst_file_manager_->OnDeleteFile(file_path);
68     }
69     return s;
70   }
71 
72   // Move file to trash
73   std::string trash_file;
74   s = MarkAsTrash(file_path, &trash_file);
75 
76   if (!s.ok()) {
77     ROCKS_LOG_ERROR(info_log_, "Failed to mark %s as trash -- %s",
78                     file_path.c_str(), s.ToString().c_str());
79     s = fs_->DeleteFile(file_path, IOOptions(), nullptr);
80     if (s.ok()) {
81       sst_file_manager_->OnDeleteFile(file_path);
82     }
83     return s;
84   }
85 
86   // Update the total trash size
87   uint64_t trash_file_size = 0;
88   fs_->GetFileSize(trash_file, IOOptions(), &trash_file_size, nullptr);
89   total_trash_size_.fetch_add(trash_file_size);
90 
91   // Add file to delete queue
92   {
93     InstrumentedMutexLock l(&mu_);
94     queue_.emplace(trash_file, dir_to_sync);
95     pending_files_++;
96     if (pending_files_ == 1) {
97       cv_.SignalAll();
98     }
99   }
100   return s;
101 }
102 
GetBackgroundErrors()103 std::map<std::string, Status> DeleteScheduler::GetBackgroundErrors() {
104   InstrumentedMutexLock l(&mu_);
105   return bg_errors_;
106 }
107 
108 const std::string DeleteScheduler::kTrashExtension = ".trash";
IsTrashFile(const std::string & file_path)109 bool DeleteScheduler::IsTrashFile(const std::string& file_path) {
110   return (file_path.size() >= kTrashExtension.size() &&
111           file_path.rfind(kTrashExtension) ==
112               file_path.size() - kTrashExtension.size());
113 }
114 
CleanupDirectory(Env * env,SstFileManagerImpl * sfm,const std::string & path)115 Status DeleteScheduler::CleanupDirectory(Env* env, SstFileManagerImpl* sfm,
116                                          const std::string& path) {
117   Status s;
118   // Check if there are any files marked as trash in this path
119   std::vector<std::string> files_in_path;
120   s = env->GetChildren(path, &files_in_path);
121   if (!s.ok()) {
122     return s;
123   }
124   for (const std::string& current_file : files_in_path) {
125     if (!DeleteScheduler::IsTrashFile(current_file)) {
126       // not a trash file, skip
127       continue;
128     }
129 
130     Status file_delete;
131     std::string trash_file = path + "/" + current_file;
132     if (sfm) {
133       // We have an SstFileManager that will schedule the file delete
134       sfm->OnAddFile(trash_file);
135       file_delete = sfm->ScheduleFileDeletion(trash_file, path);
136     } else {
137       // Delete the file immediately
138       file_delete = env->DeleteFile(trash_file);
139     }
140 
141     if (s.ok() && !file_delete.ok()) {
142       s = file_delete;
143     }
144   }
145 
146   return s;
147 }
148 
MarkAsTrash(const std::string & file_path,std::string * trash_file)149 Status DeleteScheduler::MarkAsTrash(const std::string& file_path,
150                                     std::string* trash_file) {
151   // Sanity check of the path
152   size_t idx = file_path.rfind("/");
153   if (idx == std::string::npos || idx == file_path.size() - 1) {
154     return Status::InvalidArgument("file_path is corrupted");
155   }
156 
157   Status s;
158   if (DeleteScheduler::IsTrashFile(file_path)) {
159     // This is already a trash file
160     *trash_file = file_path;
161     return s;
162   }
163 
164   *trash_file = file_path + kTrashExtension;
165   // TODO(tec) : Implement Env::RenameFileIfNotExist and remove
166   //             file_move_mu mutex.
167   int cnt = 0;
168   InstrumentedMutexLock l(&file_move_mu_);
169   while (true) {
170     s = fs_->FileExists(*trash_file, IOOptions(), nullptr);
171     if (s.IsNotFound()) {
172       // We found a path for our file in trash
173       s = fs_->RenameFile(file_path, *trash_file, IOOptions(), nullptr);
174       break;
175     } else if (s.ok()) {
176       // Name conflict, generate new random suffix
177       *trash_file = file_path + std::to_string(cnt) + kTrashExtension;
178     } else {
179       // Error during FileExists call, we cannot continue
180       break;
181     }
182     cnt++;
183   }
184   if (s.ok()) {
185     sst_file_manager_->OnMoveFile(file_path, *trash_file);
186   }
187   return s;
188 }
189 
BackgroundEmptyTrash()190 void DeleteScheduler::BackgroundEmptyTrash() {
191   TEST_SYNC_POINT("DeleteScheduler::BackgroundEmptyTrash");
192 
193   while (true) {
194     InstrumentedMutexLock l(&mu_);
195     while (queue_.empty() && !closing_) {
196       cv_.Wait();
197     }
198 
199     if (closing_) {
200       return;
201     }
202 
203     // Delete all files in queue_
204     uint64_t start_time = env_->NowMicros();
205     uint64_t total_deleted_bytes = 0;
206     int64_t current_delete_rate = rate_bytes_per_sec_.load();
207     while (!queue_.empty() && !closing_) {
208       if (current_delete_rate != rate_bytes_per_sec_.load()) {
209         // User changed the delete rate
210         current_delete_rate = rate_bytes_per_sec_.load();
211         start_time = env_->NowMicros();
212         total_deleted_bytes = 0;
213       }
214 
215       // Get new file to delete
216       const FileAndDir& fad = queue_.front();
217       std::string path_in_trash = fad.fname;
218 
219       // We don't need to hold the lock while deleting the file
220       mu_.Unlock();
221       uint64_t deleted_bytes = 0;
222       bool is_complete = true;
223       // Delete file from trash and update total_penlty value
224       Status s =
225           DeleteTrashFile(path_in_trash, fad.dir, &deleted_bytes, &is_complete);
226       total_deleted_bytes += deleted_bytes;
227       mu_.Lock();
228       if (is_complete) {
229         queue_.pop();
230       }
231 
232       if (!s.ok()) {
233         bg_errors_[path_in_trash] = s;
234       }
235 
236       // Apply penlty if necessary
237       uint64_t total_penlty;
238       if (current_delete_rate > 0) {
239         // rate limiting is enabled
240         total_penlty =
241             ((total_deleted_bytes * kMicrosInSecond) / current_delete_rate);
242         while (!closing_ && !cv_.TimedWait(start_time + total_penlty)) {}
243       } else {
244         // rate limiting is disabled
245         total_penlty = 0;
246       }
247       TEST_SYNC_POINT_CALLBACK("DeleteScheduler::BackgroundEmptyTrash:Wait",
248                                &total_penlty);
249 
250       if (is_complete) {
251         pending_files_--;
252       }
253       if (pending_files_ == 0) {
254         // Unblock WaitForEmptyTrash since there are no more files waiting
255         // to be deleted
256         cv_.SignalAll();
257       }
258     }
259   }
260 }
261 
DeleteTrashFile(const std::string & path_in_trash,const std::string & dir_to_sync,uint64_t * deleted_bytes,bool * is_complete)262 Status DeleteScheduler::DeleteTrashFile(const std::string& path_in_trash,
263                                         const std::string& dir_to_sync,
264                                         uint64_t* deleted_bytes,
265                                         bool* is_complete) {
266   uint64_t file_size;
267   Status s = fs_->GetFileSize(path_in_trash, IOOptions(), &file_size, nullptr);
268   *is_complete = true;
269   TEST_SYNC_POINT("DeleteScheduler::DeleteTrashFile:DeleteFile");
270   if (s.ok()) {
271     bool need_full_delete = true;
272     if (bytes_max_delete_chunk_ != 0 && file_size > bytes_max_delete_chunk_) {
273       uint64_t num_hard_links = 2;
274       // We don't have to worry aobut data race between linking a new
275       // file after the number of file link check and ftruncte because
276       // the file is now in trash and no hardlink is supposed to create
277       // to trash files by RocksDB.
278       Status my_status = fs_->NumFileLinks(path_in_trash, IOOptions(),
279                                            &num_hard_links, nullptr);
280       if (my_status.ok()) {
281         if (num_hard_links == 1) {
282           std::unique_ptr<FSWritableFile> wf;
283           my_status = fs_->ReopenWritableFile(path_in_trash, FileOptions(),
284                                               &wf, nullptr);
285           if (my_status.ok()) {
286             my_status = wf->Truncate(file_size - bytes_max_delete_chunk_,
287                                      IOOptions(), nullptr);
288             if (my_status.ok()) {
289               TEST_SYNC_POINT("DeleteScheduler::DeleteTrashFile:Fsync");
290               my_status = wf->Fsync(IOOptions(), nullptr);
291             }
292           }
293           if (my_status.ok()) {
294             *deleted_bytes = bytes_max_delete_chunk_;
295             need_full_delete = false;
296             *is_complete = false;
297           } else {
298             ROCKS_LOG_WARN(info_log_,
299                            "Failed to partially delete %s from trash -- %s",
300                            path_in_trash.c_str(), my_status.ToString().c_str());
301           }
302         } else {
303           ROCKS_LOG_INFO(info_log_,
304                          "Cannot delete %s slowly through ftruncate from trash "
305                          "as it has other links",
306                          path_in_trash.c_str());
307         }
308       } else if (!num_link_error_printed_) {
309         ROCKS_LOG_INFO(
310             info_log_,
311             "Cannot delete files slowly through ftruncate from trash "
312             "as Env::NumFileLinks() returns error: %s",
313             my_status.ToString().c_str());
314         num_link_error_printed_ = true;
315       }
316     }
317 
318     if (need_full_delete) {
319       s = fs_->DeleteFile(path_in_trash, IOOptions(), nullptr);
320       if (!dir_to_sync.empty()) {
321         std::unique_ptr<FSDirectory> dir_obj;
322         if (s.ok()) {
323           s = fs_->NewDirectory(dir_to_sync, IOOptions(), &dir_obj, nullptr);
324         }
325         if (s.ok()) {
326           s = dir_obj->Fsync(IOOptions(), nullptr);
327           TEST_SYNC_POINT_CALLBACK(
328               "DeleteScheduler::DeleteTrashFile::AfterSyncDir",
329               reinterpret_cast<void*>(const_cast<std::string*>(&dir_to_sync)));
330         }
331       }
332       *deleted_bytes = file_size;
333       sst_file_manager_->OnDeleteFile(path_in_trash);
334     }
335   }
336   if (!s.ok()) {
337     // Error while getting file size or while deleting
338     ROCKS_LOG_ERROR(info_log_, "Failed to delete %s from trash -- %s",
339                     path_in_trash.c_str(), s.ToString().c_str());
340     *deleted_bytes = 0;
341   } else {
342     total_trash_size_.fetch_sub(*deleted_bytes);
343   }
344 
345   return s;
346 }
347 
WaitForEmptyTrash()348 void DeleteScheduler::WaitForEmptyTrash() {
349   InstrumentedMutexLock l(&mu_);
350   while (pending_files_ > 0 && !closing_) {
351     cv_.Wait();
352   }
353 }
354 
MaybeCreateBackgroundThread()355 void DeleteScheduler::MaybeCreateBackgroundThread() {
356   if(bg_thread_ == nullptr && rate_bytes_per_sec_.load() > 0) {
357     bg_thread_.reset(
358         new port::Thread(&DeleteScheduler::BackgroundEmptyTrash, this));
359   }
360 }
361 
362 }  // namespace ROCKSDB_NAMESPACE
363 
364 #endif  // ROCKSDB_LITE
365