/**
 * Copyright (c) 2021 OceanBase
 * OceanBase CE is licensed under Mulan PubL v2.
 * You can use this software according to the terms and conditions of the Mulan PubL v2.
 * You may obtain a copy of Mulan PubL v2 at:
 *          http://license.coscl.org.cn/MulanPubL-2.0
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PubL v2 for more details.
 */

#define USING_LOG_PREFIX CLOG

#include "ob_log_line_cache.h"

#include "lib/allocator/ob_malloc.h"  // ob_malloc
#include "lib/ob_running_mode.h"      // mini_mode
#include "lib/oblog/ob_log_module.h"  // LOG_*
#include "lib/container/ob_array.h"   // ObArray
#include "clog/ob_log_utils.h"        // top_k

namespace oceanbase {
using namespace common;
namespace clog {

ObLogLineCache::ObLogLineCache()
    : inited_(false),
      blocks_(nullptr),
      block_array_size_(0),
      block_token_(),
      block_alloc_(),
      line_futex_(),
      block_wash_cond_(),
      cached_line_count_(0),
      get_line_count_(0),
      load_line_count_(0),
      block_hit_count_(0),
      line_hit_count_(0),
      block_conflict_count_(0),
      exceed_mem_limit_count_(0),
      washed_line_count_(0),
      lru_washed_line_count_(0),
      washed_line_read_size_(0),
      repeated_block_load_count_(0),
      repeated_block_load_interval_(0)
{}

int ObLogLineCache::init(
    const int64_t max_cache_file_count, const int64_t fixed_cached_size_in_file_count, const char* label)
{
  int ret = OB_SUCCESS;
  int64_t max_cache_block_count = (max_cache_file_count * BLOCK_COUNT_PER_FILE);
  int64_t prealloc_block_count = (fixed_cached_size_in_file_count * BLOCK_COUNT_PER_FILE);

  Block* blocks = nullptr;
  int64_t block_array_size = !lib::is_mini_mode() ? DEFAULT_BLOCK_ARRAY_SIZE : MINI_MODE_BLOCK_ARRAY_SIZE;
  if (OB_UNLIKELY(inited_)) {
    LOG_WARN("init twice", K(inited_));
    ret = OB_INIT_TWICE;
  } else if (OB_UNLIKELY(max_cache_file_count <= 0) || OB_UNLIKELY(fixed_cached_size_in_file_count <= 0) ||
             OB_UNLIKELY(fixed_cached_size_in_file_count > max_cache_file_count)) {
    LOG_WARN("invalid argument", K(max_cache_file_count), K(fixed_cached_size_in_file_count));
    ret = OB_INVALID_ARGUMENT;
  } else if (OB_UNLIKELY((CLOG_FILE_SIZE % BLOCK_SIZE) != 0) || OB_UNLIKELY((BLOCK_SIZE % LINE_SIZE) != 0)) {
    LOG_WARN("invalid block_size or line_size", LITERAL_K(BLOCK_SIZE), LITERAL_K(LINE_SIZE), LITERAL_K(CLOG_FILE_SIZE));
    ret = OB_INVALID_ARGUMENT;
  } else if (OB_FAIL(block_token_.init(max_cache_block_count, label))) {
    LOG_WARN("init block token fail", K(ret), K(max_cache_block_count), K(max_cache_file_count));
  } else if (OB_FAIL(block_alloc_.init(prealloc_block_count, label))) {
    LOG_WARN("init block alloc fail", K(ret), K(prealloc_block_count));
  } else if (OB_ISNULL(blocks = (Block*)ob_malloc(sizeof(Block) * block_array_size, "LogLineBlocks"))) {
    ret = OB_ALLOCATE_MEMORY_FAILED;
    LOG_WARN("alloc blocks failed", K(ret));
  } else {
    blocks_ = blocks;
    block_array_size_ = block_array_size;
    for (int64_t index = 0; index < block_array_size_; index++) {
      new (&blocks_[index]) Block();
      blocks_[index].reset();
    }
    cached_line_count_ = 0;
    get_line_count_ = 0;
    load_line_count_ = 0;
    block_hit_count_ = 0;
    line_hit_count_ = 0;
    block_conflict_count_ = 0;
    exceed_mem_limit_count_ = 0;
    washed_line_count_ = 0;
    lru_washed_line_count_ = 0;
    washed_line_read_size_ = 0;
    repeated_block_load_count_ = 0;
    repeated_block_load_interval_ = 0;
    inited_ = true;
  }
  return ret;
}

void ObLogLineCache::destroy()
{
  inited_ = false;

  // reclaim all allocated blocks
  if (block_token_.get_used_count() > 0) {
    int64_t total_washed_block_count = 0;
    int64_t total_washed_line_count = 0;
    int64_t cached_block_count_before_wash = block_token_.get_used_count();

    // Compulsory elimination, the expiration time is set to 0
    (void)do_wash_(0, total_washed_block_count, total_washed_line_count);

    // after the elimination, there are still blocks in use
    if (block_token_.get_used_count() > 0) {
      LOG_WARN("Line Cache still have blocks used after force-wash",
          K(total_washed_block_count),
          K(total_washed_line_count),
          K(cached_block_count_before_wash),
          "cached_block_count",
          block_token_.get_used_count());
    }
  }

  block_token_.destroy();
  block_alloc_.destroy();
  cached_line_count_ = 0;
  get_line_count_ = 0;
  load_line_count_ = 0;
  block_hit_count_ = 0;
  line_hit_count_ = 0;
  block_conflict_count_ = 0;
  exceed_mem_limit_count_ = 0;
  washed_line_count_ = 0;
  lru_washed_line_count_ = 0;
  washed_line_read_size_ = 0;
  repeated_block_load_count_ = 0;
  repeated_block_load_interval_ = 0;
  if (blocks_ != nullptr) {
    ob_free(blocks_);
    blocks_ = nullptr;
  }
  block_array_size_ = 0;
}

// block conflict case
// Keep trying to eliminate the target block until the elimination is successful or timeout
int ObLogLineCache::handle_when_block_conflict_(
    Block& block, const int64_t block_index, const file_id_t file_id, const int64_t end_tstamp)
{
  int ret = OB_SUCCESS;
  bool done = false;
  bool wash_succeed = false;
  int64_t block_washed_line_count = 0;
  int64_t block_washed_line_read_size = 0;
  int64_t expire_time = 0;  // expires immediately, compulsory elimination
  TokenValueType block_id = static_cast<TokenValueType>(block_index);

  while (OB_SUCCESS == ret && !done) {
    if (block.is_empty() || file_id == block.get_file_id()) {
      // current block is empty, or the current block is loaded with the data of this file, mark as complete
      done = true;
    } else {
      int64_t cur_tstamp = ObTimeUtility::current_time();

      // execute forced elimination of block
      if (OB_FAIL(do_wash_block_(
              block_id, expire_time, cur_tstamp, wash_succeed, block_washed_line_count, block_washed_line_read_size))) {
        LOG_WARN("do_wash_block_ fail", K(ret), K(block_id), K(expire_time), K(cur_tstamp));
      } else {
        if (wash_succeed) {
          // eliminated successfully
          done = true;
        } else {
          // eliminated failed, wait
          int64_t left_time = end_tstamp - cur_tstamp;
          if (left_time <= 0) {
            ret = OB_TIMEOUT;
          } else {
            // wait for the next block to be eliminated
            // FIXME:
            // 1. Essentially, should wait for the current block to be eliminated or free,
            //    here waiting for any block to be eliminated is a simplified approach.
            // 2. In current cond implementation, ObCond can only wake up one waiting in each signal,
            //    can not broadcast, most threads will wait for timeout mostly.
            //
            // Considering that conflict case are relatively rare, and once a conflict is encountered,
            // it indicates that the concurrency is relatively large, system availability should be prioritized
            // and concurrency should be reduced at this time.Therefore, the above implementation defects are
            // acceptable.
            int64_t wait_time_on_block_conflict = WAIT_TIME_ON_BLOCK_CONFLICT;
            int64_t wait_time = std::min(left_time, wait_time_on_block_conflict);
            block_wash_cond_.timedwait(wait_time);
          }
        }
      }
    }
  }

  return ret;
}

int ObLogLineCache::handle_when_block_not_hit_(
    const int64_t block_index, const file_id_t file_id, const int64_t end_tstamp, bool& exceed_mem_limit_occured)
{
  int ret = OB_SUCCESS;
  bool done = false;

  // have exceeds the memory limit case encountered or not
  exceed_mem_limit_occured = false;

  while (OB_SUCCESS == ret && !done) {
    int64_t wait_time = 0;

    if (OB_SUCC(prepare_block_(block_index, file_id))) {
      // success
      done = true;
    } else if (OB_EXCEED_MEM_LIMIT == ret) {
      // Exceed total memory limit, eliminate the oldest block
      ret = OB_SUCCESS;
      exceed_mem_limit_occured = true;
      if (OB_FAIL(do_lru_wash_())) {
        LOG_WARN("do_lru_wash_ fail", K(ret));
      } else {
        if (block_token_.get_free_count() > 0) {
          // check if there is a block token available currently
          // if yes, continue to prepare
        } else {
          wait_time = WAIT_TIME_ON_EXCEED_MEM_LIMIT;
        }
      }
    } else if (OB_ALLOCATE_MEMORY_FAILED == ret) {
      // failed to allocate memory, just wait for a while
      ret = OB_SUCCESS;
      wait_time = WAIT_TIME_ON_ALLOCATE_MEM_FAIL;
      // failure to allocate memory is considered as exceeding the memory limit
      exceed_mem_limit_occured = true;
    } else if (OB_ITEM_NOT_MATCH == ret) {
      // does not match, content conflicts, return directly, let the caller function handle
    } else {
      // failed
      LOG_WARN("prepare_block_ fail", K(ret), K(block_index), K(file_id));
    }

    // perform wait operation
    if (OB_SUCCESS == ret && wait_time > 0) {
      int64_t left_time = end_tstamp - ObTimeUtility::current_time();
      wait_time = std::min(left_time, wait_time);

      if (wait_time <= 0) {
        ret = OB_TIMEOUT;
      } else {
        block_wash_cond_.timedwait(wait_time);
      }
    }
  }

  return ret;
}

int ObLogLineCache::get_line(
    const file_id_t file_id, const offset_t offset, const int64_t timeout_us, char*& line, bool& need_load_data)
{
  int ret = OB_SUCCESS;

  if (OB_UNLIKELY(!inited_)) {
    LOG_WARN("not inited", K(inited_));
    ret = OB_NOT_INIT;
  } else if (OB_UNLIKELY(is_entry_invalid(file_id, offset))) {
    LOG_WARN("invalid argument", K(file_id), K(offset), K(timeout_us));
    ret = OB_INVALID_ARGUMENT;
  } else {
    int64_t block_index = calc_block_index(file_id, offset);
    int64_t retry_times = 0;
    int64_t end_tstamp = ObTimeUtility::current_time() + timeout_us;
    Block& block = blocks_[block_index];
    bool exceed_mem_limit_occured = false;
    bool block_conflicted = false;

    ATOMIC_INC(&get_line_count_);
    GetLineTimeoutReason::REASON reason = GetLineTimeoutReason::GET_LINE_INIT;
    while (OB_SUCCESS == ret && NULL == line) {
      if (OB_SUCC(block.get_line(file_id, offset, line_futex_, timeout_us, line, need_load_data))) {
        // success
        if (0 == retry_times) {
          // only the first successful hit is fortuned
          ATOMIC_INC(&block_hit_count_);
        }

        if (!need_load_data) {
          ATOMIC_INC(&line_hit_count_);
        }
      } else if (OB_ITEM_NOT_MATCH == ret) {
        // block conflict
        block_conflicted = true;
        ret = OB_SUCCESS;
        if (OB_FAIL(handle_when_block_conflict_(block, block_index, file_id, end_tstamp))) {
          if (OB_TIMEOUT == ret) {
            // timeout
            reason = GetLineTimeoutReason::BLOCK_CONFLICT_TIMEOUT;
          } else {
            LOG_WARN("handle_when_block_conflict_ fail", K(ret), K(block_index), K(file_id), K(block), K(end_tstamp));
          }
        }
      } else if (OB_ITEM_NOT_SETTED == ret) {
        bool local_exceed_mem_limit_occured = false;
        // block missed
        if (OB_FAIL(handle_when_block_not_hit_(block_index, file_id, end_tstamp, local_exceed_mem_limit_occured))) {
          if (OB_TIMEOUT == ret) {
            // timeout
            reason = GetLineTimeoutReason::BLOCK_NOT_HIT_TIMEOUT;
          } else if (OB_ITEM_NOT_MATCH == ret) {
            // block conflict, retry directly
            ret = OB_SUCCESS;
          } else {
            LOG_WARN("handle_when_block_not_hit_ fail", K(ret), K(block_index), K(file_id), K(end_tstamp), K(block));
          }
        } else {
          if (local_exceed_mem_limit_occured) {
            exceed_mem_limit_occured = true;
          }
        }
      } else if (OB_TIMEOUT == ret) {
        // timeout
        reason = GetLineTimeoutReason::WAIT_LOAD_LINE_TIMEOUT;
      } else {
        LOG_WARN("block get_line fail", K(ret), K(file_id), K(offset), K(timeout_us));
      }

      // finally increase the number of retries
      retry_times++;
    }

    if (OB_TIMEOUT == ret) {
      // timeout waiting for file loading
      LOG_WARN("[LINE_CACHE] get_line timeout",
          K(ret),
          K(file_id),
          K(offset),
          K(block),
          "timeout_reson",
          GetLineTimeoutReason::get_str(reason));
    }

    // modify statistics below
    if (block_conflicted) {
      ATOMIC_INC(&block_conflict_count_);
    }
    if (exceed_mem_limit_occured) {
      ATOMIC_INC(&exceed_mem_limit_count_);
    }
  }
  return ret;
}

int ObLogLineCache::do_wash_block_(const TokenValueType block_index, const int64_t expire_time,
    const int64_t cur_tstamp, bool& wash_succeed, int64_t& block_washed_line_count,
    int64_t& block_washed_line_read_size)
{
  int ret = OB_SUCCESS;
  char* washed_lines = NULL;
  TokenIDType token_id = INVALID_TOKEN_ID;
  wash_succeed = false;
  block_washed_line_count = 0;

  if (OB_UNLIKELY(block_index < 0 || nullptr == blocks_ || block_index >= block_array_size_)) {
    LOG_WARN("invalid block index", K(ret), K(block_index));
    ret = OB_INVALID_ARGUMENT;
  } else {
    Block& block = blocks_[block_index];

    // perform a wash operation
    block.wash(expire_time,
        cur_tstamp,
        wash_succeed,
        washed_lines,
        block_washed_line_count,
        block_washed_line_read_size,
        token_id);

    if (wash_succeed) {
      // If the elimination is successful, the corresponding resources will be recycled
      if (OB_ISNULL(washed_lines)) {
        LOG_WARN("washed_lines is NULL, unexpected error", KP(washed_lines), K(block));
        ret = OB_ERR_UNEXPECTED;
      } else if (OB_FAIL(block_token_.free(token_id, block_index))) {
        LOG_WARN("free token value fail", K(ret), K(token_id), K(block_index));
      } else {
        block_alloc_.free(washed_lines);
        washed_lines = NULL;
        token_id = INVALID_TOKEN_ID;

        (void)ATOMIC_FAA(&cached_line_count_, -block_washed_line_count);

        (void)ATOMIC_FAA(&washed_line_count_, block_washed_line_count);
        (void)ATOMIC_FAA(&washed_line_read_size_, block_washed_line_read_size);
      }

      // eventually notify others
      block_wash_cond_.signal();
    }
  }
  return ret;
}

struct LRUBlock {
  typedef ObLogLineCache::TokenValueType BlockIDType;

  BlockIDType block_id_;
  int64_t last_access_timestamp_;

  LRUBlock() : block_id_(ObLogLineCache::INVALID_TOKEN_VALUE), last_access_timestamp_(0)
  {}

  LRUBlock(const BlockIDType block_id, const int64_t last_access_tstamp)
      : block_id_(block_id), last_access_timestamp_(last_access_tstamp)
  {}

  TO_STRING_KV(K_(block_id), K_(last_access_timestamp));
};

struct LRUBlockComp {
  bool operator()(const LRUBlock& a, const LRUBlock& b)
  {
    return a.last_access_timestamp_ < b.last_access_timestamp_;
  }
};

typedef ObArray<LRUBlock> LRUBlockArray;

// eliminate the oldest visited block
// FIXME: This algorithm takes the slowest K blocks for elimination every time.
//        If the slowest K blocks have a reference count, the elimination will fail.
int ObLogLineCache::do_lru_wash_()
{
  int ret = OB_SUCCESS;
  LRUBlockArray total_array;
  LRUBlockArray top_k_array;
  int64_t total_read_size = 0;
  int64_t total_washed_block_count = 0;
  int64_t total_washed_line_count = 0;
  int64_t valid_block_count = block_token_.get_used_count();
  int64_t lru_wash_count = valid_block_count * LRU_WASH_PERCENT / 100;
  int64_t cur_tstamp = ObTimeUtility::current_time();
  TokenValueType* tokens = block_token_.tokens_;
  int64_t total_token_count = block_token_.get_total_count();

  if (OB_UNLIKELY(!inited_)) {
    ret = OB_NOT_INIT;
    LOG_WARN("not inited", K(ret));
  } else if (lru_wash_count <= 0) {
    // no need to eliminate
  } else if (NULL != tokens) {
    // first traverse all tokens and record all the block information being accessed
    for (int64_t index = 0; OB_SUCCESS == ret && index < total_token_count; index++) {
      TokenValueType block_id = ATOMIC_LOAD(tokens + index);
      if (INVALID_TOKEN_VALUE != block_id) {
        Block& block = blocks_[block_id];
        LRUBlock lru_block(block_id, block.get_last_access_timestamp());
        if (OB_FAIL(total_array.push_back(lru_block))) {
          LOG_WARN("push_back lru_block fail", K(ret), K(total_array), K(lru_block));
        }
      }
    }

    if (OB_SUCCESS == ret) {
      LRUBlockComp cmp;
      // calculate the oldest K blocks
      if (OB_FAIL(top_k(total_array, lru_wash_count, top_k_array, cmp))) {
        LOG_WARN("compute top_k fail", K(ret), K(total_array), K(lru_wash_count), K(top_k_array));
      } else {
        // traverse all the blocks to be eliminated and perform the elimination operation
        for (int64_t index = 0; OB_SUCCESS == ret && index < top_k_array.count(); index++) {
          const LRUBlock& lru_block = top_k_array.at(index);
          TokenValueType block_id = lru_block.block_id_;

          if (INVALID_TOKEN_VALUE != block_id) {
            bool wash_succeed = false;
            int64_t block_washed_line_count = 0;
            int64_t block_washed_line_read_size = 0;
            int64_t expire_time = 0;
            Block& block = blocks_[block_id];

            // first check whether the original access timestamp is still maintained,
            // if not, it will not be eliminated to avoid accidental injury
            if (lru_block.last_access_timestamp_ != block.get_last_access_timestamp()) {
              // not eliminated
            }
            // forced out
            else if (OB_FAIL(do_wash_block_(block_id,
                         expire_time,
                         cur_tstamp,
                         wash_succeed,
                         block_washed_line_count,
                         block_washed_line_read_size))) {
              LOG_WARN("wash block fail", K(ret), K(block_id), K(expire_time), K(cur_tstamp), K(wash_succeed));
            } else if (wash_succeed) {
              total_washed_block_count++;
              total_washed_line_count += block_washed_line_count;
              total_read_size += block_washed_line_read_size;
            }
          }
        }

        if (top_k_array.count() > 0) {
          if (REACH_TIME_INTERVAL(PRINT_WASH_STAT_INFO_INTERVAL)) {
            int64_t read_size_per_line = (0 == total_washed_line_count ? 0 : total_read_size / total_washed_line_count);

            LOG_INFO("[LINE_CACHE] lru_wash",
                K(valid_block_count),
                K(lru_wash_count),
                "top_k_count",
                top_k_array.count(),
                K(total_washed_block_count),
                K(total_washed_line_count),
                K(read_size_per_line),
                "cached_block_count",
                block_token_.get_used_count(),
                K_(cached_line_count));
          }

          (void)ATOMIC_FAA(&lru_washed_line_count_, total_washed_line_count);
        }
      }
    }
  }
  return ret;
}

int ObLogLineCache::do_wash_(
    const int64_t expire_time, int64_t& total_washed_block_count, int64_t& total_washed_line_count)
{
  int ret = OB_SUCCESS;
  int64_t total_read_size = 0;
  total_washed_block_count = 0;
  total_washed_line_count = 0;

  if (block_token_.get_used_count() > 0) {
    TokenValueType* tokens = block_token_.tokens_;
    int64_t total_token_count = block_token_.get_total_count();
    int64_t cur_tstamp = ObTimeUtility::current_time();

    // Traverse all tokens, find all the blocks that use tokens, and judge whether they can be recycled
    if (NULL != tokens) {
      for (int64_t index = 0; OB_SUCCESS == ret && index < total_token_count; index++) {
        TokenValueType block_id = ATOMIC_LOAD(tokens + index);

        if (INVALID_TOKEN_VALUE != block_id) {
          bool wash_succeed = false;
          int64_t block_washed_line_count = 0;
          int64_t block_washed_line_read_size = 0;

          // perform the actual block elimination operation
          if (OB_FAIL(do_wash_block_(block_id,
                  expire_time,
                  cur_tstamp,
                  wash_succeed,
                  block_washed_line_count,
                  block_washed_line_read_size))) {
            LOG_WARN("wash block fail", K(ret), K(block_id), K(expire_time), K(index));
          } else if (wash_succeed) {
            total_washed_block_count++;
            total_washed_line_count += block_washed_line_count;
            total_read_size += block_washed_line_read_size;
          }
        }
      }

      if (total_washed_block_count > 0) {
        if (REACH_TIME_INTERVAL(PRINT_WASH_STAT_INFO_INTERVAL)) {
          int64_t read_size_per_line = (0 == total_washed_line_count ? 0 : total_read_size / total_washed_line_count);

          LOG_INFO("[LINE_CACHE] wash",
              K(expire_time),
              K(total_washed_block_count),
              K(total_washed_line_count),
              K(read_size_per_line),
              "cached_block_count",
              block_token_.get_used_count(),
              K_(cached_line_count));
        }
      }
    }
  }

  return ret;
}

void ObLogLineCache::wash()
{
  int ret = OB_SUCCESS;
  int64_t expire_time = DATA_EXPIRE_TIME_US;  // use normal expiration time
  int64_t total_washed_block_count = 0;
  int64_t total_washed_line_count = 0;

  if (OB_UNLIKELY(!inited_)) {
    LOG_WARN("not inited", K(inited_));
    ret = OB_NOT_INIT;
  } else if (OB_FAIL(do_wash_(expire_time, total_washed_block_count, total_washed_line_count))) {
    LOG_WARN("do_wash_ fail", K(ret), K(expire_time));
  } else {
    // success
  }
}

void ObLogLineCache::stat()
{
  static int64_t last_get_line_count = 0;
  static int64_t last_load_line_count = 0;
  static int64_t last_block_hit_count = 0;
  static int64_t last_line_hit_count = 0;
  static int64_t last_block_conflict_count = 0;
  static int64_t last_exceed_mem_limit_count = 0;
  static int64_t last_washed_line_count = 0;
  static int64_t last_lru_washed_line_count = 0;
  static int64_t last_washed_line_read_size = 0;
  static int64_t last_repeated_block_load_count = 0;
  static int64_t last_repeated_block_load_interval = 0;
  static int64_t last_stat_tstamp = ObTimeUtility::current_time();

  int64_t cur_tstamp = ObTimeUtility::current_time();

  int64_t cur_get_line_count = ATOMIC_LOAD(&get_line_count_);
  int64_t cur_load_line_count = ATOMIC_LOAD(&load_line_count_);
  int64_t cur_block_hit_count = ATOMIC_LOAD(&block_hit_count_);
  int64_t cur_line_hit_count = ATOMIC_LOAD(&line_hit_count_);
  int64_t cur_block_conflict_count = ATOMIC_LOAD(&block_conflict_count_);
  int64_t cur_exceed_mem_limit_count = ATOMIC_LOAD(&exceed_mem_limit_count_);
  int64_t cur_washed_line_count = ATOMIC_LOAD(&washed_line_count_);
  int64_t cur_lru_washed_line_count = ATOMIC_LOAD(&lru_washed_line_count_);
  int64_t cur_washed_line_read_size = ATOMIC_LOAD(&washed_line_read_size_);
  int64_t cur_repeated_block_load_count = ATOMIC_LOAD(&repeated_block_load_count_);
  int64_t cur_repeated_block_load_interval = ATOMIC_LOAD(&repeated_block_load_interval_);

  double delta_time_sec = static_cast<double>((cur_tstamp - last_stat_tstamp) / 1000000);
  double delta_get_line_count = static_cast<double>(cur_get_line_count - last_get_line_count);
  double delta_load_line_count = static_cast<double>(cur_load_line_count - last_load_line_count);
  double delta_block_hit_count = static_cast<double>(cur_block_hit_count - last_block_hit_count);
  double delta_line_hit_count = static_cast<double>(cur_line_hit_count - last_line_hit_count);
  double delta_block_conflict_count = static_cast<double>(cur_block_conflict_count - last_block_conflict_count);
  double delta_exceed_mem_limit_count = static_cast<double>(cur_exceed_mem_limit_count - last_exceed_mem_limit_count);
  double delta_washed_line_count = static_cast<double>(cur_washed_line_count - last_washed_line_count);
  double delta_lru_washed_line_count = static_cast<double>(cur_lru_washed_line_count - last_lru_washed_line_count);
  double delta_washed_line_read_size = static_cast<double>(cur_washed_line_read_size - last_washed_line_read_size);
  double delta_repeated_block_load_count =
      static_cast<double>(cur_repeated_block_load_count - last_repeated_block_load_count);
  double delta_repeated_block_load_interval_ms =
      static_cast<double>((cur_repeated_block_load_interval - last_repeated_block_load_interval) / 1000);

  // read the disk ratio
  double M_size = static_cast<double>(1024 * 1024);
  double read_disk_ratio = (0 == delta_get_line_count ? 0 : delta_load_line_count / delta_get_line_count);
  double read_disk_size_M = delta_load_line_count * static_cast<double>(LINE_SIZE) / M_size;
  double read_disk_size_per_sec_M = (0 == delta_time_sec ? 0 : read_disk_size_M / delta_time_sec);

  // block hit rate
  double block_hit_ratio = (0 == delta_get_line_count ? 0 : delta_block_hit_count / delta_get_line_count);
  // line hit rate
  double line_hit_ratio = (0 == delta_get_line_count ? 0 : delta_line_hit_count / delta_get_line_count);
  // block conflict rate
  double block_conflict_ratio = (0 == delta_get_line_count ? 0 : delta_block_conflict_count / delta_get_line_count);
  // memory rate exceeded
  double exceed_mem_limit_ratio = (0 == delta_get_line_count ? 0 : delta_exceed_mem_limit_count / delta_get_line_count);

  // calculate data utilization based on eliminated lines
  double total_washed_size = delta_washed_line_count * LINE_SIZE;
  double data_use_ratio = (0 == delta_washed_line_count ? 0 : delta_washed_line_read_size / total_washed_size);

  // count of wash lines per second
  double wash_line_count_per_sec = (0 == delta_time_sec ? 0 : delta_washed_line_count / delta_time_sec);
  double lru_wash_line_count_per_sec = (0 == delta_time_sec ? 0 : delta_lru_washed_line_count / delta_time_sec);

  // count of repeated loading blocks per second
  double repeated_block_load_count_per_sec =
      (0 == delta_time_sec ? 0 : delta_repeated_block_load_count / delta_time_sec);

  // calculate the average time interval between repeated loading of blocks
  double repeated_block_load_interval_ms =
      (0 == delta_repeated_block_load_count ? 0
                                            : delta_repeated_block_load_interval_ms / delta_repeated_block_load_count);

  int64_t cached_block_count = get_cached_block_count();
  int64_t cached_line_count = get_cached_line_count();
  int64_t valid_line_per_block = ((0 == cached_block_count) ? 0 : cached_line_count / cached_block_count);

  LOG_INFO("[LINE_CACHE] STAT",
      K(cached_block_count),
      K(cached_line_count),
      K(valid_line_per_block),
      "cached_mem_size(M)",
      get_cached_mem_size() / (1024 * 1024),
      "get_line_count",
      static_cast<int64_t>(delta_get_line_count),
      "read_disk_ratio(%)",
      read_disk_ratio * 100,
      K(read_disk_size_per_sec_M),
      "block_hit_ratio(%)",
      block_hit_ratio * 100,
      "line_hit_ratio(%)",
      line_hit_ratio * 100,
      "data_use_ratio(%)",
      data_use_ratio * 100,
      K(repeated_block_load_count_per_sec),
      K(repeated_block_load_interval_ms),
      "block_conflict_ratio(%)",
      block_conflict_ratio * 100,
      "exceed_mem_limit_ratio(%)",
      exceed_mem_limit_ratio * 100,
      K(wash_line_count_per_sec),
      K(lru_wash_line_count_per_sec));

  last_get_line_count = cur_get_line_count;
  last_load_line_count = cur_load_line_count;
  last_block_hit_count = cur_block_hit_count;
  last_line_hit_count = cur_line_hit_count;
  last_block_conflict_count = cur_block_conflict_count;
  last_exceed_mem_limit_count = cur_exceed_mem_limit_count;
  last_washed_line_count = cur_washed_line_count;
  last_lru_washed_line_count = cur_lru_washed_line_count;
  last_washed_line_read_size = cur_washed_line_read_size;
  last_repeated_block_load_count = cur_repeated_block_load_count;
  last_repeated_block_load_interval = cur_repeated_block_load_interval;
  last_stat_tstamp = cur_tstamp;
}

int ObLogLineCache::prepare_block_(const int64_t block_index, const file_id_t file_id)
{
  int ret = OB_SUCCESS;

  if (OB_UNLIKELY(!inited_)) {
    ret = OB_NOT_INIT;
    LOG_WARN("not inited", K(ret));
  } else {
    TokenIDType token_id = INVALID_TOKEN_ID;
    char* block_ptr = NULL;
    int64_t last_access_tstamp = 0;
    // convert block id to token value
    TokenValueType token_value = static_cast<TokenValueType>(block_index);
    Block& block = blocks_[block_index];

    // assign token id first
    if (OB_FAIL(block_token_.alloc(token_id, token_value))) {
      if (OB_EXCEED_MEM_LIMIT == ret) {
        // memory limit exceeded
      } else {
        LOG_WARN("alloc block token fail", K(ret), K(token_id), K(token_value));
      }
    }
    // assign block
    else if (OB_ISNULL(block_ptr = block_alloc_.alloc())) {
      LOG_WARN("line cache allocate memory for block fail",
          "cached_block_count",
          block_token_.get_used_count(),
          K(cached_line_count_));
      // the actual physical memory is not enough
      ret = OB_ALLOCATE_MEMORY_FAILED;
    }
    // prepare block
    else if (OB_FAIL(block.prepare(file_id, token_id, block_ptr, last_access_tstamp))) {
      if (OB_ENTRY_EXIST == ret || OB_ITEM_NOT_MATCH == ret) {
        // exist or not match
        LOG_WARN("[LINE_CACHE] prepare block conflict with others",
            K(ret),
            K(file_id),
            K(token_id),
            KP(block_ptr),
            K(block));
      } else {
        LOG_WARN("line cache prepare block fail", K(ret), K(file_id), K(token_id), KP(block_ptr), K(block));
      }
    } else {
      // prepare success
      // Check whether the load has been repeated recently
      if (last_access_tstamp > 0) {
        int64_t cur_ts = ObTimeUtility::current_time();
        int64_t delta_time = cur_ts - last_access_tstamp;

        // only count the repeated loading in a short period of time
        if (delta_time <= MIN_INTERVAL_CONSIDER_AS_REPEATED_LAOADING) {
          ATOMIC_INC(&repeated_block_load_count_);
          (void)ATOMIC_FAA(&repeated_block_load_interval_, delta_time);
        }
      }
    }

    // In all cases where the prepare is not successful, the corresponding
    // resources are recovered, including OB_ENTRY_EXIST
    if (OB_SUCCESS != ret) {
      if (INVALID_TOKEN_ID != token_id) {
        int tmp_ret = block_token_.free(token_id, token_value);
        if (OB_SUCCESS != tmp_ret) {
          LOG_WARN("free block token fail", K(tmp_ret), K(ret), K(token_id), K(token_value));
        } else {
          token_id = INVALID_TOKEN_ID;
        }
      }

      if (NULL != block_ptr) {
        block_alloc_.free(block_ptr);
        block_ptr = NULL;
      }
    }

    // If the prepare block returns entry exists, reset ret
    if (OB_ENTRY_EXIST == ret) {
      ret = OB_SUCCESS;
    }
  }
  return ret;
}

int ObLogLineCache::revert_line(const file_id_t file_id, const offset_t offset, const int64_t read_size)
{
  int ret = OB_SUCCESS;
  if (OB_UNLIKELY(!inited_)) {
    ret = OB_NOT_INIT;
  } else if (OB_UNLIKELY(is_entry_invalid(file_id, offset))) {
    LOG_WARN("invalid argument", K(file_id), K(offset));
    ret = OB_INVALID_ARGUMENT;
  } else {
    int64_t block_index = calc_block_index(file_id, offset);
    Block& block = blocks_[block_index];

    if (OB_FAIL(block.revert_line(file_id, offset, read_size))) {
      LOG_WARN("block revert_line fail", K(ret), K(file_id), K(offset), K(read_size), K(block), K(block_index));
    }
  }
  return ret;
}

int ObLogLineCache::mark_line_status(const file_id_t file_id, const offset_t offset, const bool is_load_ready)
{
  int ret = OB_SUCCESS;
  if (OB_UNLIKELY(!inited_)) {
    ret = OB_NOT_INIT;
  } else if (OB_UNLIKELY(is_entry_invalid(file_id, offset))) {
    LOG_WARN("invalid argument", K(file_id), K(offset));
    ret = OB_INVALID_ARGUMENT;
  } else {
    int64_t block_index = calc_block_index(file_id, offset);
    Block& block = blocks_[block_index];

    if (OB_FAIL(block.mark_line_status(file_id, offset, is_load_ready, line_futex_))) {
      LOG_WARN("block mark_line_status fail", K(ret), K(file_id), K(offset), K(is_load_ready), K(block));
    } else {
      if (is_load_ready) {
        ATOMIC_INC(&cached_line_count_);
        ATOMIC_INC(&load_line_count_);
      }
    }
  }
  return ret;
}

/////////////////////////////////// BlockAlloc //////////////////////////////////

ObLogLineCache::BlockAlloc::BlockAlloc() : buf_(NULL), label_(nullptr), free_list_()
{}

int ObLogLineCache::BlockAlloc::init(const int64_t prealloc_block_cnt, const char* label)
{
  int ret = OB_SUCCESS;
  int64_t prealloc_size = prealloc_block_cnt * BLOCK_SIZE;
  int64_t align_size = CLOG_DIO_ALIGN_SIZE;

  if (OB_UNLIKELY(NULL != buf_)) {
    ret = OB_INIT_TWICE;
  } else if (OB_UNLIKELY(prealloc_block_cnt <= 0)) {
    LOG_WARN("invalid argument", K(prealloc_block_cnt));
    ret = OB_INVALID_ARGUMENT;
  } else if (OB_FAIL(free_list_.init(prealloc_block_cnt, global_default_allocator, label))) {
    LOG_WARN("init free list fail", K(ret), K(prealloc_block_cnt));
  } else if (OB_ISNULL(buf_ = (char*)ob_malloc_align(align_size, prealloc_size, label))) {
    LOG_WARN("allocate memory fail", K(buf_), K(prealloc_size), K(prealloc_block_cnt));
    ret = OB_ALLOCATE_MEMORY_FAILED;
  } else {
    for (int64_t index = 0; OB_SUCCESS == ret && index < prealloc_block_cnt; index++) {
      char* block = buf_ + index * BLOCK_SIZE;
      if (OB_FAIL(free_list_.push(block))) {
        LOG_WARN("push block into free list fail", K(ret), KP(block), K(index), K(prealloc_block_cnt));
      }
    }

    if (OB_SUCCESS == ret) {
      label_ = label;
    }
  }

  return ret;
}

void ObLogLineCache::BlockAlloc::destroy()
{
  if (NULL != buf_) {
    ob_free_align(buf_);
    buf_ = NULL;
  }

  free_list_.destroy();
  label_ = nullptr;
}

char* ObLogLineCache::BlockAlloc::alloc()
{
  int ret = OB_SUCCESS;
  char* ptr = NULL;
  if (OB_SUCC(free_list_.pop(ptr))) {
    if (OB_ISNULL(ptr)) {
      LOG_WARN("free list pop NULL ptr",
          KP(ptr),
          "free_list_total",
          free_list_.get_total(),
          "free_list_capacity",
          free_list_.capacity());
    }
  } else {
    ptr = static_cast<char*>(ob_malloc_align(CLOG_DIO_ALIGN_SIZE, BLOCK_SIZE, label_));
  }
  return ptr;
}

void ObLogLineCache::BlockAlloc::free(char* ptr)
{
  int ret = OB_SUCCESS;
  if (OB_NOT_NULL(ptr)) {
    char* end_of_buf = buf_ + free_list_.capacity() * BLOCK_SIZE;

    // determine whether is prealloc out
    if (ptr >= buf_ && ptr < end_of_buf) {
      if (OB_FAIL(free_list_.push(ptr))) {
        LOG_WARN("Line Cache free block fail, push into free list error",
            K(ret),
            KP(ptr),
            KP(buf_),
            K(free_list_.capacity()),
            K(free_list_.get_total()),
            K(free_list_.get_free()));
      } else {
        ptr = NULL;
      }
    } else {
      ob_free_align(ptr);
      ptr = NULL;
    }
  }
}

////////////////////////// BlockToken ///////////////////////////

ObLogLineCache::BlockToken::BlockToken() : tokens_(NULL), free_list_()
{}

ObLogLineCache::BlockToken::~BlockToken()
{
  destroy();
}

int ObLogLineCache::BlockToken::push_free_list_(const TokenIDType token_id)
{
  // The free list does not support scenarios with a value of 0,
  // therefore, 1 is manually added here.
  void* value = reinterpret_cast<void*>(token_id + 1);
  return free_list_.push(value);
}

int ObLogLineCache::BlockToken::pop_free_list_(TokenIDType& token_id)
{
  int ret = OB_SUCCESS;
  void* pop_val = NULL;
  if (OB_SUCC(free_list_.pop(pop_val))) {
    token_id = static_cast<TokenIDType>(reinterpret_cast<int64_t>(pop_val) - 1);
  }
  return ret;
}

int ObLogLineCache::BlockToken::init(const int64_t total_count, const char* label)
{
  int ret = OB_SUCCESS;
  if (OB_UNLIKELY(NULL != tokens_)) {
    LOG_WARN("init twice", KP(tokens_));
    ret = OB_INIT_TWICE;
  } else if (OB_UNLIKELY(total_count <= 0) || OB_UNLIKELY(total_count > MAX_TOKEN_ID)) {
    LOG_WARN("invalid argument", K(total_count));
    ret = OB_INVALID_ARGUMENT;
  } else if (OB_FAIL(free_list_.init(total_count, global_default_allocator, label))) {
    LOG_WARN("init free list fail", K(ret), K(total_count));
  } else {
    int64_t alloc_size = total_count * sizeof(TokenValueType);
    tokens_ = static_cast<TokenValueType*>(ob_malloc(alloc_size, label));

    if (OB_ISNULL(tokens_)) {
      LOG_WARN("allocate memory for token array fail", KP(tokens_));
      ret = OB_ALLOCATE_MEMORY_FAILED;
    } else {
      for (TokenIDType index = 0; OB_SUCCESS == ret && index < total_count; index++) {
        if (OB_FAIL(push_free_list_(index))) {
          LOG_WARN("push free list fail", K(ret), K(index));
        } else {
          tokens_[index] = INVALID_TOKEN_VALUE;
        }
      }
    }
  }
  return ret;
}

void ObLogLineCache::BlockToken::destroy()
{
  if (NULL != tokens_) {
    ob_free(tokens_);
    tokens_ = NULL;
  }

  free_list_.destroy();
}

int ObLogLineCache::BlockToken::alloc(TokenIDType& token_id, const TokenValueType token_value)
{
  int ret = OB_SUCCESS;
  if (OB_ISNULL(tokens_)) {
    ret = OB_NOT_INIT;
  } else if (OB_FAIL(pop_free_list_(token_id))) {
    if (OB_ENTRY_NOT_EXIST == ret) {
      // free list not exists, return exceed the memory limit directly.
      ret = OB_EXCEED_MEM_LIMIT;
    } else {
      LOG_WARN("pop item from free list fail", K(ret));
    }
  } else {
    // check the validity of the token
    if (OB_UNLIKELY(token_id < 0 || token_id >= free_list_.capacity())) {
      LOG_WARN("token_id from free_list is invalid", K(token_id));
      ret = OB_ERR_UNEXPECTED;
    } else {
      // set the corresponding token value
      ATOMIC_STORE(&tokens_[token_id], token_value);
    }
  }
  return ret;
}

int ObLogLineCache::BlockToken::free(const TokenIDType token_id, const TokenValueType token_value)
{
  int ret = OB_SUCCESS;
  if (OB_ISNULL(tokens_)) {
    ret = OB_NOT_INIT;
  } else if (OB_UNLIKELY(token_id < 0 || token_id >= free_list_.capacity())) {
    LOG_WARN("invalid argument", K(token_id));
    ret = OB_INVALID_ARGUMENT;
  } else {
    // mark token value as invalid
    TokenValueType old_value = ATOMIC_CAS(tokens_ + token_id, token_value, INVALID_TOKEN_VALUE);

    if (OB_UNLIKELY(old_value != token_value)) {
      LOG_WARN("token_value does not match target value", K(old_value), K(token_value), K(token_id));
      ret = OB_STATE_NOT_MATCH;
    } else if (OB_FAIL(push_free_list_(token_id))) {
      LOG_WARN("push into free list fail", K(ret), K(token_id));
    }
  }
  return ret;
}

////////////////////////// BlockKey ///////////////////////////

int ObLogLineCache::BlockKey::prepare_begin(const file_id_t file_id)
{
  int ret = OB_SUCCESS;
  bool done = false;
  Value cur_v;

  if (OB_UNLIKELY(0 == file_id)) {
    ret = OB_INVALID_ARGUMENT;
  } else {
    cur_v.key_ = ATOMIC_LOAD(&v_.key_);

    while (!done && OB_SUCCESS == ret) {
      if (file_id == cur_v.file_id_) {
        ret = OB_ENTRY_EXIST;
      } else if (0 != cur_v.file_id_) {
        ret = OB_ITEM_NOT_MATCH;
      } else { /* 0 == cur_v.file_id_ */
        // assume ref_cnt must be 0
        if (OB_UNLIKELY(0 != cur_v.ref_cnt_)) {
          LOG_WARN("ref_cnt is not 0, unexpected error", K(cur_v.key_), K(cur_v.file_id_), K(cur_v.ref_cnt_));
          ret = OB_ERR_UNEXPECTED;
        } else {
          Value new_v, old_v;

          old_v.key_ = cur_v.key_;
          new_v.file_id_ = file_id;
          new_v.ref_cnt_ = -1;  // lock on

          cur_v.key_ = ATOMIC_CAS(&v_.key_, cur_v.key_, new_v.key_);

          // If CAS is successful, the conversion is successful
          if (cur_v.key_ == old_v.key_) {
            done = true;
          }
        }
      }
    }
  }

  return ret;
}

void ObLogLineCache::BlockKey::prepare_end(const file_id_t file_id)
{
  // for performance considerations, no verification here
  Value cur_v;
  cur_v.file_id_ = file_id;
  cur_v.ref_cnt_ = 0;  // unlock
  ATOMIC_SET(&v_.key_, cur_v.key_);
}

int ObLogLineCache::BlockKey::get(const file_id_t file_id)
{
  int ret = OB_SUCCESS;
  bool done = false;
  Value cur_v;

  if (OB_UNLIKELY(0 == file_id)) {
    ret = OB_INVALID_ARGUMENT;
  } else {
    cur_v.key_ = ATOMIC_LOAD(&v_.key_);

    while (!done && OB_SUCCESS == ret) {
      if (0 == cur_v.file_id_) {
        ret = OB_ITEM_NOT_SETTED;
      } else if (file_id != cur_v.file_id_) {
        ret = OB_ITEM_NOT_MATCH;
      } else {  // file_id == cur_v.file_id_
        // If ref_cnt is less than 0, it means locked, busy waiting at this time
        if (cur_v.ref_cnt_ < 0) {
          PAUSE();

          // get the current value
          cur_v.key_ = ATOMIC_LOAD(&v_.key_);
        } else {
          Value new_v, old_v;

          old_v.key_ = cur_v.key_;
          new_v.key_ = cur_v.key_;
          new_v.ref_cnt_++;

          cur_v.key_ = ATOMIC_CAS(&v_.key_, cur_v.key_, new_v.key_);

          // if CAS succeeds, it succeeds
          if (cur_v.key_ == old_v.key_) {
            done = true;
          }
        }
      }
    }
  }
  return ret;
}

int ObLogLineCache::BlockKey::revert(const file_id_t file_id)
{
  int ret = OB_SUCCESS;
  bool done = false;
  Value cur_v;
  cur_v.key_ = ATOMIC_LOAD(&v_.key_);

  if (OB_UNLIKELY(file_id != cur_v.file_id_)) {
    LOG_WARN("file_id does not match", K(cur_v.file_id_), K(file_id), K(cur_v.ref_cnt_));
    ret = OB_ITEM_NOT_MATCH;
  } else {
    while (!done && OB_SUCCESS == ret) {
      if (cur_v.ref_cnt_ <= 0) {
        LOG_WARN("reference count is not positive", K(cur_v.file_id_), K(cur_v.ref_cnt_), K(file_id));
        ret = OB_ERR_UNEXPECTED;
      } else {
        Value new_v, old_v;

        old_v.key_ = cur_v.key_;
        new_v.key_ = cur_v.key_;
        new_v.ref_cnt_--;  // decrease the reference count by 1

        cur_v.key_ = ATOMIC_CAS(&v_.key_, cur_v.key_, new_v.key_);

        if (cur_v.key_ == old_v.key_) {
          done = true;
        }
      }
    }
  }
  return ret;
}

bool ObLogLineCache::BlockKey::wash_begin()
{
  bool done = false;
  bool can_wash = false;
  Value cur_v;
  cur_v.key_ = ATOMIC_LOAD(&v_.key_);

  while (!done) {
    // If the file_id is invalid or the reference count is not 0, it cannot be eliminated,
    // indicating that either the Block is invalid or someone is quoting
    if (0 == cur_v.file_id_ || 0 != cur_v.ref_cnt_) {
      can_wash = false;
      done = true;
    } else {  // 0 != cur_v.file_id_ && 0 == cur_v.ref_cnt_
      Value new_v, old_v;
      old_v.key_ = cur_v.key_;
      new_v.key_ = cur_v.key_;
      new_v.ref_cnt_ = -1;  // lock

      cur_v.key_ = ATOMIC_CAS(&v_.key_, cur_v.key_, new_v.key_);

      if (cur_v.key_ == old_v.key_) {
        can_wash = true;
        done = true;
      }
    }
  }

  return can_wash;
}

void ObLogLineCache::BlockKey::wash_end(const bool wash_succeed)
{
  // for performance considerations, there is no longer a status check
  Value cur_v;
  cur_v.key_ = ATOMIC_LOAD(&v_.key_);

  // if the elimination is successful, set to the initial state
  if (wash_succeed) {
    cur_v.file_id_ = 0;
    cur_v.ref_cnt_ = 0;
  } else {
    // elimination failed, file_id remains as it is, and then unlock
    cur_v.ref_cnt_ = 0;
  }

  ATOMIC_SET(&v_.key_, cur_v.key_);
}

////////////////////////// Block ///////////////////////////

ObLogLineCache::Block::Block()
    : key_(), lines_(NULL), token_id_(INVALID_TOKEN_ID), read_size_(0), last_access_timestamp_(0)
{
  for (int64_t index = 0; index < LINE_COUNT_PER_BLOCK; index++) {
    line_status_[index] = EMPTY;
  }
}

void ObLogLineCache::Block::reset()
{
  // key_ not joinreset

  lines_ = NULL;
  token_id_ = INVALID_TOKEN_ID;
  for (int64_t index = 0; index < LINE_COUNT_PER_BLOCK; index++) {
    line_status_[index] = EMPTY;
  }
  read_size_ = 0;

  //  last_access_timestamp_ do not participate in reset
}

void ObLogLineCache::Block::update_access_timestamp()
{
  ATOMIC_STORE(&last_access_timestamp_, ObTimeUtility::current_time());
}

int ObLogLineCache::Block::prepare(
    const file_id_t file_id, const TokenIDType token_id, char* lines, int64_t& last_access_tstamp)
{
  int ret = OB_SUCCESS;
  if (OB_UNLIKELY(file_id <= 0) || OB_UNLIKELY(token_id < 0) || OB_ISNULL(lines) ||
      OB_UNLIKELY(!ObLogLineCache::is_aligned(lines))) {
    LOG_WARN("invalid argument", K(file_id), K(token_id), KP(lines));
    ret = OB_INVALID_ARGUMENT;
  }
  // seize Block and get the sole prepare right
  else if (OB_FAIL(key_.prepare_begin(file_id))) {
    if (OB_ENTRY_EXIST == ret) {
      // someone prepared
    } else if (OB_ITEM_NOT_MATCH == ret) {
      // check whether conflicts
      // this kind of case is rare, print WARN
      LOG_WARN("Line Cache prepare block conflict",
          K(ret),
          "target_file_id",
          file_id,
          "cur_file_id",
          key_.get_file_id(),
          K(*this));
    } else {
      LOG_WARN("Line Cache BlockKey prepare fail", K(ret), K(key_), K(file_id));
    }
  } else {
    // preemption is successful, initialize each variable
    token_id_ = token_id;
    lines_ = lines;
    read_size_ = 0;

    // get the timestamp of the last update
    last_access_tstamp = ATOMIC_LOAD(&last_access_timestamp_);

    // update access timestamp
    update_access_timestamp();

    // finish prepare
    key_.prepare_end(file_id);
  }
  return ret;
}

int ObLogLineCache::Block::do_get_line_(
    const int64_t line_index, LineFutex& futex, const int64_t timeout_us, char*& line, bool& need_load)
{
  int ret = OB_SUCCESS;

  if (OB_ISNULL(lines_)) {
    LOG_WARN("lines_ is invalid", KP(lines_));
    ret = OB_NOT_INIT;
  } else {
    int64_t wait_time = timeout_us;
    int64_t end_tstamp = ObTimeUtility::current_time() + timeout_us;
    LineStatusType* line_status = line_status_ + line_index;
    LineStatusType cur_val = ATOMIC_LOAD(line_status);

    line = NULL;
    need_load = false;

    // cycle waiting state changing to READY
    // The reason why we need to wait in a loop here is because the state transition
    // of LINE will loop between EMPTY <-> LOADING
    while (OB_SUCCESS == ret && NULL == line) {
      if (EMPTY == cur_val) {
        // line is empty, try to get the right to load
        cur_val = ATOMIC_CAS(line_status, EMPTY, LOADING);
        // EMPTY -> LOADING is successful, get the right to load
        if (EMPTY == cur_val) {
          line = lines_ + (line_index * LINE_SIZE);
          need_load = true;
        }
      }

      if (NULL == line) {
        // if the status is always equal to LOADING, wait in a loop
        if (LOADING == cur_val) {
          if (wait_time <= 0) {
            ret = OB_TIMEOUT;
          } else if (OB_FAIL(futex.wait(line_status, LOADING, wait_time))) {
            if (OB_TIMEOUT != ret) {
              LOG_WARN("wait futex fail", K(ret), K(*line_status), K(timeout_us));
            }
          } else {
            cur_val = ATOMIC_LOAD(line_status);
            wait_time = end_tstamp - ObTimeUtility::current_time();
          }
        }

        if (OB_SUCCESS == ret && READY == cur_val) {
          // READY state directly obtains LINE, and the mark as no need to be loaded
          line = lines_ + (line_index * LINE_SIZE);
          need_load = false;
        }
      }
    }
  }
  return ret;
}

int ObLogLineCache::Block::get_line(const file_id_t file_id, const offset_t offset, LineFutex& futex,
    const int64_t timeout_us, char*& line, bool& need_load)
{
  int ret = OB_SUCCESS;
  int64_t line_index = line_index_in_block(offset);

  if (OB_UNLIKELY(ObLogLineCache::is_entry_invalid(file_id, offset))) {
    LOG_WARN("invalid argument", K(file_id), K(offset));
    ret = OB_INVALID_ARGUMENT;
  }
  // get the right to read, increase the reference count
  else if (OB_FAIL(key_.get(file_id))) {
    if (OB_ITEM_NOT_MATCH == ret) {
      // conflict
    } else if (OB_ITEM_NOT_SETTED == ret) {
      // the current block is invalid
    }
  } else {
    // In the process of acquiring the line, may wait, and this period of time may be considered can be eliminated.
    // Therefore, first update the access timestamp to avoid being eliminated by execution.
    update_access_timestamp();

    // perform specific get operations
    if (OB_FAIL(do_get_line_(line_index, futex, timeout_us, line, need_load))) {
      if (OB_TIMEOUT == ret) {
        // timeout
      } else {
        LOG_WARN("Line Cache block do_get_line_ fail",
            K(ret),
            K(line_index),
            K(timeout_us),
            K(file_id),
            K(offset),
            K(*this));
      }
    }

    // if getting the line fails, revert the read operation and decrease the reference count
    if (OB_SUCCESS != ret) {
      int revert_ret = key_.revert(file_id);
      if (OB_SUCCESS != revert_ret) {
        LOG_WARN("BlockKey revert fail", K(revert_ret), K(file_id), K(key_));
      }
    }
  }
  return ret;
}

int ObLogLineCache::Block::revert_line(const file_id_t file_id, const offset_t offset, const int64_t read_size)
{
  int ret = OB_SUCCESS;
  if (OB_UNLIKELY(ObLogLineCache::is_entry_invalid(file_id, offset))) {
    LOG_WARN("invalid argument", K(file_id), K(offset));
    ret = OB_INVALID_ARGUMENT;
  } else {
    // FIXME: Essentially, only the read operation should update the access timestamp.
    // If the revert operation is updated, the block cannot be released quickly.
    // However, in the future, we still need to evaluate whether revert should be
    // updated if the line is locally cached after being got.
    // update_access_timestamp();

    // increase the amount of data read
    (void)ATOMIC_FAA(&read_size_, read_size);

    // perform revert, subtract the reference count
    if (OB_FAIL(key_.revert(file_id))) {
      LOG_WARN("BlockKey revert fail", K(ret), K(file_id), K(key_));
    }
  }
  return ret;
}

// Note: This operation should be called after the get() operation and between
// the revert() operations, otherwise the correctness is not guaranteed.
int ObLogLineCache::Block::mark_line_status(
    const file_id_t file_id, const offset_t offset, const bool is_load_ready, LineFutex& futex)
{
  int ret = OB_SUCCESS;
  if (OB_UNLIKELY(ObLogLineCache::is_entry_invalid(file_id, offset))) {
    LOG_WARN("invalid argument", K(file_id), K(offset));
    ret = OB_INVALID_ARGUMENT;
  }
  // check file_id
  else if (OB_UNLIKELY(file_id != key_.get_file_id())) {
    LOG_WARN("file_id does not match", K(key_), K(file_id), K(offset));
    ret = OB_ITEM_NOT_MATCH;
  }
  // reference count must be greater than 0, otherwise it cannot be executed
  else if (OB_UNLIKELY(key_.get_ref_cnt() <= 0)) {
    LOG_WARN("Block reference count is not positive, unexpected error", K(key_), K(file_id));
    ret = OB_ERR_UNEXPECTED;
  } else {
    int64_t line_index = line_index_in_block(offset);
    LineStatusType* line_status = line_status_ + line_index;
    // if the external load fails, the state rolls back to EMPTY
    LineStatusType target_status = is_load_ready ? READY : EMPTY;

    int old_val = ATOMIC_CAS(line_status, LOADING, target_status);

    if (OB_UNLIKELY(old_val != LOADING)) {
      LOG_WARN("invalid line status which is not LOADING when revert line",
          K(file_id),
          K(offset),
          K(line_index),
          K(*line_status),
          K(old_val),
          K(is_load_ready));
      ret = OB_ERR_UNEXPECTED;
    } else {
      // wake up all waiting threads
      futex.wake(line_status, INT64_MAX);

      // update access timestamp
      update_access_timestamp();
    }
  }
  return ret;
}

void ObLogLineCache::Block::wash(const int64_t expire_time, const int64_t cur_tstamp, bool& wash_succeed,
    char*& washed_lines, int64_t& washed_line_count, int64_t& washed_line_read_size, TokenIDType& token_id)
{
  int64_t last_access_tstamp = ATOMIC_LOAD(&last_access_timestamp_);
  int64_t delta_time = cur_tstamp - last_access_tstamp;
  bool can_expire_wash = (last_access_tstamp > 0 && delta_time >= expire_time);

  wash_succeed = false;
  washed_lines = NULL;
  washed_line_count = 0;
  token_id = INVALID_TOKEN_ID;
  washed_line_read_size = 0;

  if (can_expire_wash) {
    // When the timeout is reached, check whether it is really washable
    // if can, acquire the mutex at the same time to perform the wash operation
    if (!key_.wash_begin()) {
      LOG_TRACE(
          "[LINE_CACHE] expired block can not wash", K(delta_time), K(expire_time), K(last_access_tstamp), K(*this));
    } else {
      // reload the data and judge based on the latest data
      last_access_tstamp = ATOMIC_LOAD(&last_access_timestamp_);
      delta_time = cur_tstamp - last_access_tstamp;
      can_expire_wash = (last_access_tstamp > 0 && delta_time >= expire_time);

      if (can_expire_wash) {
        // traverse all the lines to see which lines have loaded data
        for (int64_t index = 0; index < LINE_COUNT_PER_BLOCK; index++) {
          if (READY == line_status_[index]) {
            washed_line_count++;
          }
        }

        token_id = token_id_;
        washed_lines = lines_;
        wash_succeed = true;
        washed_line_read_size = ATOMIC_LOAD(&read_size_);

        LOG_TRACE("[LINE_CACHE] wash expired block", K(expire_time), K(delta_time), K(*this));

        // reset all domains
        reset();
      }

      // after the operation is completed, notify key that wash is complete
      key_.wash_end(wash_succeed);
    }
  }
}

const char* ObLogLineCache::GetLineTimeoutReason::get_str(REASON reason)
{
  const char* result = "GET_LINE_INIT";
  switch (reason) {
    case GET_LINE_INIT:
      result = "GET_LINE_INIT";
      break;
    case WAIT_LOAD_LINE_TIMEOUT:
      result = "WAIT_LOAD_LINE_TIMEOUT";
      break;
    case BLOCK_CONFLICT_TIMEOUT:
      result = "BLOCK_CONFLICT_TIMEOUT";
      break;
    case BLOCK_NOT_HIT_TIMEOUT:
      result = "BLOCK_NOT_HIT_TIMEOUT";
      break;
    default:
      result = "UNEXPECTED_REASON";
      break;
  }
  return result;
}

}  // namespace clog
}  // namespace oceanbase
