blob: 0d0ec31cdf6fa0f4410d0e2fc629c07980ded84f [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
#include <tuple>
#include "runtime/io/handle-cache.h"
#include "runtime/io/hdfs-monitored-ops.h"
#include "util/hash-util.h"
#include "util/impalad-metrics.h"
#include "util/lru-multi-cache.inline.h"
#include "util/metrics.h"
#include "util/time.h"
#ifndef IMPALA_RUNTIME_DISK_IO_MGR_HANDLE_CACHE_INLINE_H
#define IMPALA_RUNTIME_DISK_IO_MGR_HANDLE_CACHE_INLINE_H
namespace impala {
namespace io {
HdfsFileHandle::~HdfsFileHandle() {
if (hdfs_file_ != nullptr && fs_ != nullptr) {
VLOG_FILE << "hdfsCloseFile() fid=" << hdfs_file_;
hdfsCloseFile(fs_, hdfs_file_); // TODO: check return code
}
fs_ = nullptr;
fname_ = nullptr;
hdfs_file_ = nullptr;
}
Status HdfsFileHandle::Init(HdfsMonitor* monitor) {
Status status = monitor->OpenHdfsFileWithTimeout(fs_, fname_, O_RDONLY, 0,
&hdfs_file_);
// fname_ is no longer needed, null it out
fname_ = nullptr;
return status;
}
CachedHdfsFileHandle::CachedHdfsFileHandle(const hdfsFS& fs, const string* fname,
int64_t mtime)
: HdfsFileHandle(fs, fname, mtime) {
ImpaladMetrics::IO_MGR_NUM_CACHED_FILE_HANDLES->Increment(1L);
}
CachedHdfsFileHandle::~CachedHdfsFileHandle() {
ImpaladMetrics::IO_MGR_NUM_CACHED_FILE_HANDLES->Increment(-1L);
}
FileHandleCache::Accessor::Accessor() : cache_accessor_() {}
FileHandleCache::Accessor::Accessor(
FileHandleCachePartition::CacheType::Accessor&& cache_accessor)
: cache_accessor_(std::move(cache_accessor)) {
ImpaladMetrics::IO_MGR_NUM_FILE_HANDLES_OUTSTANDING->Increment(1L);
}
void FileHandleCache::Accessor::Set(
FileHandleCachePartition::CacheType::Accessor&& cache_accessor) {
// No change if it was empty before
if (cache_accessor_.Get()) {
ImpaladMetrics::IO_MGR_NUM_FILE_HANDLES_OUTSTANDING->Increment(-1L);
}
cache_accessor_ = std::move(cache_accessor);
// No change if it has received an empty accessor
if (cache_accessor_.Get()) {
ImpaladMetrics::IO_MGR_NUM_FILE_HANDLES_OUTSTANDING->Increment(1L);
}
}
CachedHdfsFileHandle* FileHandleCache::Accessor::Get() {
return cache_accessor_.Get();
}
void FileHandleCache::Accessor::Release() {
if (cache_accessor_.Get()) {
ImpaladMetrics::IO_MGR_NUM_FILE_HANDLES_OUTSTANDING->Increment(-1L);
cache_accessor_.Release();
}
}
void FileHandleCache::Accessor::Destroy() {
if (cache_accessor_.Get()) {
ImpaladMetrics::IO_MGR_NUM_FILE_HANDLES_OUTSTANDING->Increment(-1L);
cache_accessor_.Destroy();
}
}
FileHandleCache::Accessor::~Accessor() {
if (cache_accessor_.Get()) {
VLOG_FILE << "hdfsUnbufferFile() fid=" << Get()->file();
if (hdfsUnbufferFile(Get()->file()) != 0) {
VLOG_FILE << "FS does not support file handle unbuffering, closing file="
<< cache_accessor_.GetKey()->first;
Destroy();
} else {
// Calling explicit release to handle metrics
Release();
}
}
}
FileHandleCache::FileHandleCache(size_t capacity, size_t num_partitions,
uint64_t unused_handle_timeout_secs, HdfsMonitor* hdfs_monitor)
: cache_partitions_(num_partitions),
unused_handle_timeout_secs_(unused_handle_timeout_secs),
hdfs_monitor_(hdfs_monitor) {
DCHECK_GT(num_partitions, 0);
size_t remainder = capacity % num_partitions;
size_t base_capacity = capacity / num_partitions;
size_t partition_capacity = (remainder > 0 ? base_capacity + 1 : base_capacity);
for (FileHandleCachePartition& p : cache_partitions_) {
p.cache.SetCapacity(partition_capacity);
}
}
FileHandleCache::~FileHandleCache() {
shut_down_promise_.Set(true);
if (eviction_thread_ != nullptr) eviction_thread_->Join();
}
Status FileHandleCache::Init() {
return Thread::Create("disk-io-mgr-handle-cache", "File Handle Timeout",
&FileHandleCache::EvictHandlesLoop, this, &eviction_thread_);
}
Status FileHandleCache::GetFileHandle(const hdfsFS& fs, std::string* fname, int64_t mtime,
bool require_new_handle, FileHandleCache::Accessor* accessor, bool* cache_hit) {
DCHECK_GT(mtime, 0);
// Hash the key and get appropriate partition
int index = HashUtil::Hash(fname->data(), fname->size(), 0) % cache_partitions_.size();
FileHandleCachePartition& p = cache_partitions_[index];
auto cache_key = std::make_pair(*fname, mtime);
// If this requires a new handle, skip to the creation codepath. Otherwise,
// find an unused entry with the same mtime
if (!require_new_handle) {
auto cache_accessor = p.cache.Get(cache_key);
if (cache_accessor.Get()) {
// Found a handler in cache and reserved it
*cache_hit = true;
accessor->Set(std::move(cache_accessor));
return Status::OK();
}
}
// There was no entry that was free or caller asked for a new handle
*cache_hit = false;
// Emplace a new file handle and get access
auto accessor_tmp = p.cache.EmplaceAndGet(cache_key, fs, fname, mtime);
// Opening a file handle requires talking to the NameNode so it can take some time.
Status status = accessor_tmp.Get()->Init(hdfs_monitor_);
if (UNLIKELY(!status.ok())) {
// Removing the handler from the cache after failed initialization.
accessor_tmp.Destroy();
return status;
}
// Moving the cache accessor to the in/out parameter
accessor->Set(std::move(accessor_tmp));
return Status::OK();
}
void FileHandleCache::EvictHandlesLoop() {
while (true) {
if (unused_handle_timeout_secs_) {
for (FileHandleCachePartition& p : cache_partitions_) {
uint64_t now = MonotonicSeconds();
uint64_t oldest_allowed_timestamp =
now > unused_handle_timeout_secs_ ? now - unused_handle_timeout_secs_ : 0;
p.cache.EvictOlderThan(oldest_allowed_timestamp);
}
}
// This Get() will time out until shutdown, when the promise is set.
bool timed_out;
shut_down_promise_.Get(EVICT_HANDLES_PERIOD_MS, &timed_out);
if (!timed_out) break;
}
// The promise must be set to true.
DCHECK(shut_down_promise_.IsSet());
DCHECK(shut_down_promise_.Get());
}
}
}
#endif