in src/server/pegasus_server_impl.cpp [186:312]
void pegasus_server_impl::gc_checkpoints(bool force_reserve_one)
{
int min_count = force_reserve_one ? 1 : _checkpoint_reserve_min_count;
uint64_t reserve_time = force_reserve_one ? 0 : _checkpoint_reserve_time_seconds;
std::deque<int64_t> temp_list;
{
::dsn::utils::auto_lock<::dsn::utils::ex_lock_nr> l(_checkpoints_lock);
if (_checkpoints.size() <= min_count)
return;
temp_list = _checkpoints;
}
// find the max checkpoint which can be deleted
int64_t max_del_d = -1;
uint64_t current_time = dsn_now_ms() / 1000;
for (int i = 0; i < temp_list.size(); ++i) {
if (i + min_count >= temp_list.size())
break;
int64_t d = temp_list[i];
if (reserve_time > 0) {
// we check last write time of "CURRENT" instead of directory, because the directory's
// last write time may be updated by previous incompleted garbage collection.
auto cpt_dir =
::dsn::utils::filesystem::path_combine(data_dir(), chkpt_get_dir_name(d));
auto current_file = ::dsn::utils::filesystem::path_combine(cpt_dir, "CURRENT");
if (!::dsn::utils::filesystem::file_exists(current_file)) {
max_del_d = d;
continue;
}
time_t tm;
if (!dsn::utils::filesystem::last_write_time(current_file, tm)) {
LOG_WARNING("get last write time of file {} failed", current_file);
break;
}
auto last_write_time = (uint64_t)tm;
if (last_write_time + reserve_time >= current_time) {
// not expired
break;
}
}
max_del_d = d;
}
if (max_del_d == -1) {
// no checkpoint to delete
LOG_INFO_PREFIX("no checkpoint to garbage collection, checkpoints_count = {}",
temp_list.size());
return;
}
std::list<int64_t> to_delete_list;
int64_t min_d = 0;
int64_t max_d = 0;
int checkpoints_count = 0;
{
::dsn::utils::auto_lock<::dsn::utils::ex_lock_nr> l(_checkpoints_lock);
int delete_max_index = -1;
for (int i = 0; i < _checkpoints.size(); ++i) {
int64_t del_d = _checkpoints[i];
if (i + min_count >= _checkpoints.size() || del_d > max_del_d)
break;
to_delete_list.push_back(del_d);
delete_max_index = i;
}
if (delete_max_index >= 0) {
_checkpoints.erase(_checkpoints.begin(), _checkpoints.begin() + delete_max_index + 1);
}
if (!_checkpoints.empty()) {
min_d = _checkpoints.front();
max_d = _checkpoints.back();
checkpoints_count = _checkpoints.size();
} else {
min_d = 0;
max_d = 0;
checkpoints_count = 0;
}
}
// do delete
std::list<int64_t> put_back_list;
for (auto &del_d : to_delete_list) {
auto cpt_dir =
::dsn::utils::filesystem::path_combine(data_dir(), chkpt_get_dir_name(del_d));
if (::dsn::utils::filesystem::directory_exists(cpt_dir)) {
if (::dsn::utils::filesystem::remove_path(cpt_dir)) {
LOG_INFO_PREFIX("checkpoint directory {} removed by garbage collection", cpt_dir);
} else {
LOG_ERROR_PREFIX("checkpoint directory {} remove failed by garbage collection",
cpt_dir);
put_back_list.push_back(del_d);
}
} else {
LOG_INFO_PREFIX("checkpoint directory {} does not exist, ignored by garbage collection",
cpt_dir);
}
}
// put back checkpoints which is not deleted, to make it delete again in the next gc time.
// ATTENTION: the put back checkpoint may be incomplete, which will cause failure on load. But
// it would not cause data lost, because incomplete checkpoint can not be loaded successfully.
if (!put_back_list.empty()) {
::dsn::utils::auto_lock<::dsn::utils::ex_lock_nr> l(_checkpoints_lock);
if (_checkpoints.empty() || put_back_list.back() < _checkpoints.front()) {
// just insert to front will hold the order
_checkpoints.insert(_checkpoints.begin(), put_back_list.begin(), put_back_list.end());
} else {
// need to re-sort
_checkpoints.insert(_checkpoints.begin(), put_back_list.begin(), put_back_list.end());
std::sort(_checkpoints.begin(), _checkpoints.end());
}
if (!_checkpoints.empty()) {
min_d = _checkpoints.front();
max_d = _checkpoints.back();
checkpoints_count = _checkpoints.size();
} else {
min_d = 0;
max_d = 0;
checkpoints_count = 0;
}
}
LOG_INFO_PREFIX("after checkpoint garbage collection, checkpoints_count = {}, min_checkpoint = "
"{}, max_checkpoint = {}",
checkpoints_count,
min_d,
max_d);
}