blob: b32c94f41539fb5ca84902c4eae21255e75a0d4c [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 "runtime/exec-env.h"
#include "runtime/io/disk-io-mgr-internal.h"
#include "runtime/io/disk-io-mgr.h"
#include "runtime/io/hdfs-file-reader.h"
#include "runtime/io/local-file-reader.h"
#include "util/error-util.h"
#include "util/hdfs-util.h"
#include "common/names.h"
using namespace impala;
using namespace impala::io;
using std::mutex;
using std::unique_lock;
DECLARE_bool(cache_remote_file_handles);
DECLARE_bool(cache_s3_file_handles);
DECLARE_bool(cache_abfs_file_handles);
DECLARE_bool(cache_ozone_file_handles);
// Implementation of the ScanRange functionality. Each ScanRange contains a queue
// of ready buffers. For each ScanRange, there is only a single producer and
// consumer thread, i.e. only one disk thread will push to a scan range at
// any time and only one thread will remove from the queue. This is to guarantee
// that buffers are queued and read in file order.
bool ScanRange::EnqueueReadyBuffer(unique_ptr<BufferDescriptor> buffer) {
DCHECK(buffer->buffer_ != nullptr) << "Cannot enqueue freed buffer";
{
unique_lock<mutex> scan_range_lock(lock_);
DCHECK(Validate(scan_range_lock)) << DebugString();
DCHECK(!eosr_queued_);
if (!buffer->is_cached()) {
// All non-cached buffers are enqueued by disk threads. Indicate that the read
// finished.
DCHECK(read_in_flight_);
read_in_flight_ = false;
}
bool buffer_eosr_queued = buffer->eosr();
// Don't access buffer after this as ownership is given to 'buffer_manager_'.
buffer_manager_->EnqueueReadyBuffer(scan_range_lock, move(buffer));
if (!cancel_status_.ok()) {
// This range is cancelled and one or more threads may be blocked in
// WaitForInFlightRead() waiting for the read to complete. Wake up all of them.
buffer_ready_cv_.NotifyAll();
return false;
}
eosr_queued_ = buffer_eosr_queued;
}
buffer_ready_cv_.NotifyOne();
return true;
}
Status ScanRange::GetNext(unique_ptr<BufferDescriptor>* buffer) {
DCHECK(*buffer == nullptr);
bool eosr;
{
unique_lock<mutex> scan_range_lock(lock_);
DCHECK(Validate(scan_range_lock)) << DebugString();
while (!all_buffers_returned(scan_range_lock) &&
buffer_manager_->is_readybuffer_empty()) {
buffer_ready_cv_.Wait(scan_range_lock);
}
// No more buffers to return - return the cancel status or OK if not cancelled.
if (all_buffers_returned(scan_range_lock)) {
// Wait until read finishes to ensure buffers are freed.
while (read_in_flight_) buffer_ready_cv_.Wait(scan_range_lock);
DCHECK(buffer_manager_->is_readybuffer_empty());
return cancel_status_;
}
// Remove the first ready buffer from the queue and return it
buffer_manager_->PopFirstReadyBuffer(scan_range_lock, buffer);
eosr = (*buffer)->eosr();
}
if (eosr) reader_->RemoveActiveScanRange(this);
// Update tracking counters. The buffer has now moved from the IoMgr to the caller.
buffer_manager_->add_buffers_in_reader(1);
return Status::OK();
}
void ScanRange::ReturnBuffer(unique_ptr<BufferDescriptor> buffer_desc) {
DCHECK(buffer_desc != nullptr);
DCHECK_EQ(buffer_desc->scan_range(), this);
vector<unique_ptr<BufferDescriptor>> buffers;
buffers.emplace_back(move(buffer_desc));
AddUnusedBuffers(move(buffers), true);
}
void ScanRange::AddUnusedBuffers(vector<unique_ptr<BufferDescriptor>>&& buffers,
bool returned) {
bool unblocked = false;
{
unique_lock<mutex> scan_range_lock(lock_);
bool buffer_added = buffer_manager_->AddUnusedBuffers(scan_range_lock,
move(buffers), returned);
// Unblock scan range if new buffer has been added to unused buffer queue.
if (buffer_added && blocked_on_buffer_) {
blocked_on_buffer_ = false;
unblocked = true;
}
}
// Need to schedule scan range, if it has been blocked above.
// Must drop the ScanRange lock before invoking ScheduleScanRange.
if (unblocked) ScheduleScanRange();
}
bool ScanRange::FileHandleCacheEnabled() const {
// Global flag for all file handle caching
if (!is_file_handle_caching_enabled()) return false;
if (expected_local_ && IsHdfsPath(file())) return true;
if (FLAGS_cache_remote_file_handles) {
if (disk_id_ == io_mgr_->RemoteDfsDiskId()) return true;
}
if (FLAGS_cache_ozone_file_handles) {
if (expected_local_ && IsOzonePath(file())) return true;
if (disk_id_ == io_mgr_->RemoteOzoneDiskId()) return true;
}
if (FLAGS_cache_s3_file_handles) {
if (disk_id_ == io_mgr_->RemoteS3DiskId()) return true;
}
if (FLAGS_cache_abfs_file_handles) {
if (disk_id_ == io_mgr_->RemoteAbfsDiskId()) return true;
}
return false;
}
ReadOutcome ScanRange::DoReadInternal(DiskQueue* queue, int disk_id, bool use_local_buff,
bool use_mem_buffer, shared_lock<shared_mutex>* local_file_lock) {
int64_t bytes_remaining = bytes_to_read_ - bytes_read_;
DCHECK_GT(bytes_remaining, 0);
// Can't be set to true together.
DCHECK(!(use_local_buff && use_mem_buffer));
unique_ptr<BufferDescriptor> buffer_desc;
FileReader* file_reader = nullptr;
{
unique_lock<mutex> lock(lock_);
DCHECK(!read_in_flight_);
if (!cancel_status_.ok()) return ReadOutcome::CANCELLED;
if (buffer_manager_->is_client_buffer()) {
buffer_desc = unique_ptr<BufferDescriptor>(new BufferDescriptor(
this, client_buffer_.data, client_buffer_.len));
} else {
DCHECK(buffer_manager_->is_internal_buffer())
<< "This code path does not handle other buffer types, i.e. HDFS cache. "
<< "Buffer tag = "
<< static_cast<int>(buffer_manager_->buffer_tag());
buffer_desc = buffer_manager_->GetUnusedBuffer(lock);
if (buffer_desc == nullptr) {
// No buffer available - the range will be rescheduled when a buffer is added.
blocked_on_buffer_ = true;
return ReadOutcome::BLOCKED_ON_BUFFER;
}
buffer_manager_->add_iomgr_buffer_cumulative_bytes_used(buffer_desc->buffer_len());
}
read_in_flight_ = true;
// Set the correct reader to read the range if the memory buffer is not available.
if (!use_mem_buffer) {
if (use_local_buff) {
file_reader = local_buffer_reader_.get();
file_ = disk_buffer_file_->path();
} else {
file_reader = file_reader_.get();
}
use_local_buffer_ = use_local_buff;
}
}
bool eof = false;
Status read_status = Status::OK();
if (use_mem_buffer) {
// The only scenario to use the memory buffer is for the temporary files, the range
// is supposed to be read in one round.
// For the efficiency consideration, don't have the lock of the memory block, the
// safety is implicitly guaranteed by the physical lock of the disk file, which is
// required while removing the disk file and the memory blocks. The other case of
// removing the memory block is when all of the pages have been read, and that could
// only happen after this read.
DCHECK(local_file_lock != nullptr);
read_status = disk_buffer_file_->ReadFromMemBuffer(
offset_, bytes_to_read_, buffer_desc->buffer_, *local_file_lock);
if (read_status.ok()) {
buffer_desc->len_ = bytes_to_read_;
eof = true;
COUNTER_ADD_IF_NOT_NULL(reader_->read_use_mem_counter_, 1L);
COUNTER_ADD_IF_NOT_NULL(reader_->bytes_read_use_mem_counter_, buffer_desc->len_);
COUNTER_ADD_IF_NOT_NULL(reader_->bytes_read_counter_, buffer_desc->len_);
}
} else {
DCHECK(file_reader != nullptr);
// No locks in this section. Only working on local vars. We don't want to hold a
// lock across the read call.
// To use the file handle cache:
// 1. It must be enabled at the daemon level.
// 2. It must be enabled for the particular filesystem.
bool use_file_handle_cache = FileHandleCacheEnabled();
VLOG_FILE << (use_file_handle_cache ? "Using" : "Skipping")
<< " file handle cache for " << (expected_local_ ? "local" : "remote")
<< " file " << file();
// Delay open if configured to use a file handle cache or data cache as cache hits
// don't require an explicit Open.
if (!file_reader->SupportsDelayedOpen()
|| !(use_file_handle_cache || UseDataCache())) {
read_status = file_reader->Open();
}
if (read_status.ok()) {
COUNTER_ADD_IF_NOT_NULL(reader_->active_read_thread_counter_, 1L);
COUNTER_BITOR_IF_NOT_NULL(reader_->disks_accessed_bitmap_, 1LL << disk_id);
if (sub_ranges_.empty()) {
DCHECK(cache_.data == nullptr);
read_status =
file_reader->ReadFromPos(queue, offset_ + bytes_read_, buffer_desc->buffer_,
min(bytes_to_read() - bytes_read_, buffer_desc->buffer_len_),
&buffer_desc->len_, &eof);
} else {
read_status = ReadSubRanges(queue, buffer_desc.get(), &eof, file_reader);
}
COUNTER_ADD_IF_NOT_NULL(reader_->bytes_read_counter_, buffer_desc->len_);
COUNTER_ADD_IF_NOT_NULL(reader_->active_read_thread_counter_, -1L);
if (use_local_buffer_) {
COUNTER_ADD_IF_NOT_NULL(reader_->read_use_local_disk_counter_, 1L);
COUNTER_ADD_IF_NOT_NULL(
reader_->bytes_read_use_local_disk_counter_, buffer_desc->len_);
}
}
}
DCHECK(buffer_desc->buffer_ != nullptr);
DCHECK(!buffer_desc->is_cached())
<< "Pure HDFS cache reads don't go through this code path.";
if (!read_status.ok()) {
// Free buffer to release resources before we cancel the range so that all buffers
// are freed at cancellation.
buffer_desc->Free();
buffer_desc.reset();
// Propagate 'read_status' to the scan range. This will also wake up any waiting
// threads.
CancelInternal(read_status, true);
// At this point we cannot touch the state of this range because the client
// may notice cancellation, then reuse the scan range.
return ReadOutcome::CANCELLED;
}
{
unique_lock<mutex> lock(lock_);
bytes_read_ += buffer_desc->len();
DCHECK_LE(bytes_read_, bytes_to_read_);
// It is end of stream if it is end of file, or read all the bytes.
buffer_desc->eosr_ = eof || bytes_read_ == bytes_to_read_;
}
// After calling EnqueueReadyBuffer(), it is no longer valid to touch 'buffer_desc'.
// Store the state we need before calling EnqueueReadyBuffer().
bool eosr = buffer_desc->eosr();
// No more reads for this scan range - we can close it.
if (eosr && file_reader != nullptr) file_reader->Close();
// Read successful - enqueue the buffer and return the appropriate outcome.
if (!EnqueueReadyBuffer(move(buffer_desc))) return ReadOutcome::CANCELLED;
// At this point, if eosr=true, then we cannot touch the state of this scan range
// because the client may notice eos, then reuse the scan range.
return eosr ? ReadOutcome::SUCCESS_EOSR : ReadOutcome::SUCCESS_NO_EOSR;
}
ReadOutcome ScanRange::DoRead(DiskQueue* queue, int disk_id) {
bool use_local_buffer = false;
bool use_mem_buffer = false;
if (disk_file_ != nullptr && disk_file_->disk_type() != DiskFileType::LOCAL
&& disk_buffer_file_ != nullptr) {
// The sequence for acquiring the locks should always be from the local to
// the remote to avoid deadlocks.
shared_lock<shared_mutex> local_file_lock(*(disk_buffer_file_->GetFileLock()));
shared_lock<shared_mutex> remote_file_lock(*(disk_file_->GetFileLock()));
{
unique_lock<SpinLock> buffer_file_lock(*(disk_buffer_file_->GetStatusLock()));
unique_lock<SpinLock> file_lock(*(disk_file_->GetStatusLock()));
if (disk_buffer_file_->is_deleted(buffer_file_lock)
&& disk_file_->is_deleted(file_lock)) {
// If both of the local buffer file and the remote file have been deleted,
// the only case could be the query is cancelled, so that both files are deleted.
return ReadOutcome::CANCELLED;
}
// The range can be read from local for two cases.
// 1. If the local buffer file is not deleted(evicted) yet.
// 2. A block of the file, which contains the range, has been read and stored in
// the memory.
// If we don't meet any of the cases, the range needs to be read from the remote.
if (!disk_buffer_file_->is_deleted(buffer_file_lock)) {
use_local_buffer = true;
} else if (disk_buffer_file_->CanReadFromReadBuffer(local_file_lock, offset_)) {
use_mem_buffer = true;
} else {
// Read from the remote file. The remote file must be in persisted status.
DCHECK(disk_file_->is_persisted(file_lock));
}
}
return DoReadInternal(
queue, disk_id, use_local_buffer, use_mem_buffer, &local_file_lock);
}
return DoReadInternal(queue, disk_id, use_local_buffer, use_mem_buffer);
}
Status ScanRange::ReadSubRanges(
DiskQueue* queue, BufferDescriptor* buffer_desc, bool* eof, FileReader* file_reader) {
buffer_desc->len_ = 0;
while (buffer_desc->len() < buffer_desc->buffer_len()
&& sub_range_pos_.index < sub_ranges_.size()) {
SubRange& sub_range = sub_ranges_[sub_range_pos_.index];
int64_t offset = sub_range.offset + sub_range_pos_.bytes_read;
int64_t bytes_to_read = min(sub_range.length - sub_range_pos_.bytes_read,
buffer_desc->buffer_len() - buffer_desc->len());
if (cache_.data != nullptr) {
// The cache_.data buffer starts at offset_, so adjust the starting
// offset for the copies.
int64_t buffer_offset = offset - offset_;
DCHECK_LE(buffer_offset + bytes_to_read, cache_.len);
// DCHECKs are only effective with test coverage, so also return an error
// if this would read past the edge of the cache_.data buffer. We wanted
// bytes_to_read, but only cache_.len - buffer_offset bytes were available.
if (buffer_offset + bytes_to_read > cache_.len) {
return Status(TErrorCode::SCANNER_INCOMPLETE_READ, bytes_to_read,
cache_.len - buffer_offset, file(), offset);
}
memcpy(buffer_desc->buffer_ + buffer_desc->len(),
cache_.data + buffer_offset, bytes_to_read);
} else {
int64_t current_bytes_read;
Status read_status = file_reader->ReadFromPos(queue, offset,
buffer_desc->buffer_ + buffer_desc->len(), bytes_to_read, &current_bytes_read,
eof);
if (!read_status.ok()) return read_status;
if (current_bytes_read != bytes_to_read) {
DCHECK(*eof);
DCHECK_LT(current_bytes_read, bytes_to_read);
return Status(TErrorCode::SCANNER_INCOMPLETE_READ, bytes_to_read,
current_bytes_read, file(), offset);
}
}
buffer_desc->len_ += bytes_to_read;
sub_range_pos_.bytes_read += bytes_to_read;
if (sub_range_pos_.bytes_read == sub_range.length) {
sub_range_pos_.index += 1;
sub_range_pos_.bytes_read = 0;
}
}
return Status::OK();
}
void ScanRange::SetBlockedOnBuffer() {
unique_lock<mutex> lock(lock_);
blocked_on_buffer_ = true;
}
void ScanRange::Cancel(const Status& status) {
// Cancelling a range that was never started, ignore.
if (io_mgr_ == nullptr) return;
CancelInternal(status, false);
// Wait until an in-flight read is finished. The read thread will clean up the
// buffer it used. Once the range is cancelled, no more reads should be started.
WaitForInFlightRead();
reader_->RemoveActiveScanRange(this);
}
void ScanRange::CancelInternal(const Status& status, bool read_error) {
DCHECK(io_mgr_ != nullptr);
DCHECK(!status.ok());
FileReader* file_reader = nullptr;
{
// Grab both locks to make sure that we don't change 'cancel_status_' while other
// threads are in critical sections.
unique_lock<mutex> scan_range_lock(lock_);
{
file_reader = use_local_buffer_ ? local_buffer_reader_.get() : file_reader_.get();
unique_lock<SpinLock> fs_lock(file_reader->lock());
DCHECK(Validate(scan_range_lock)) << DebugString();
// If already cancelled, preserve the original reason for cancellation. Most of the
// cleanup is not required if already cancelled, but we need to set
// 'read_in_flight_' to false.
if (cancel_status_.ok()) cancel_status_ = status;
}
/// Clean up ready buffers (i.e., 'buffer_manager_->ready_buffers_') while still
/// holding 'lock_' to prevent other threads from seeing inconsistent state.
buffer_manager_->CleanUpReadyBuffers(scan_range_lock);
/// Clean up buffers that we don't need any more because we won't read any more data.
buffer_manager_->CleanUpUnusedBuffers(scan_range_lock);
if (read_error) {
DCHECK(read_in_flight_);
read_in_flight_ = false;
}
}
buffer_ready_cv_.NotifyAll();
// For cached buffers, we can't close the range until the cached buffer is returned.
// Close() is called from ScanRange::CleanUpBufferLocked().
// TODO: IMPALA-4249 - this Close() call makes it unsafe to reuse a cancelled scan
// range, because there is no synchronisation between this Close() call and the
// client adding the ScanRange back into the IoMgr.
if (!buffer_manager_->is_cached()) {
DCHECK(file_reader != nullptr);
file_reader->Close();
}
}
void ScanRange::WaitForInFlightRead() {
unique_lock<mutex> scan_range_lock(lock_);
while (read_in_flight_) {
buffer_ready_cv_.Wait(scan_range_lock);
}
}
string ScanRange::DebugString() const {
stringstream ss;
ss << "file=" << file_ << " disk_id=" << disk_id_ << " offset=" << offset_;
if (file_reader_) ss << " " << file_reader_->DebugString();
ss << " cancel_status=" << cancel_status_.GetDetail()
<< buffer_manager_->DebugString()
<< " blocked_on_buffer=" << blocked_on_buffer_;
return ss.str();
}
bool ScanRange::Validate(const std::unique_lock<std::mutex>& scan_range_lock) {
if (bytes_read_ > bytes_to_read_) {
LOG(ERROR) << "Bytes read tracking is wrong. Too many bytes have been read."
<< " bytes_read_=" << bytes_read_
<< " bytes_to_read_=" << bytes_to_read_;
return false;
}
// Perform validation for buffer state
if (!buffer_manager_->Validate(scan_range_lock)) {
LOG(ERROR) << "Buffer state validation failed for this scan range: "
<< DebugString();
return false;
}
return true;
}
ScanRange::ScanRange()
: RequestRange(RequestType::READ) {
buffer_manager_ = make_unique<ScanBufferManager>(this);
}
ScanRange::~ScanRange() {
DCHECK(!read_in_flight_);
}
void ScanRange::Reset(const FileInfo &fi, int64_t len, int64_t offset, int disk_id,
bool expected_local, const BufferOpts& buffer_opts, void* meta_data,
DiskFile* disk_file, DiskFile* disk_buffer_file) {
Reset(fi, len, offset, disk_id, expected_local, buffer_opts, {},
meta_data, disk_file, disk_buffer_file);
}
ScanRange* ScanRange::AllocateScanRange(ObjectPool* obj_pool, const FileInfo &fi,
int64_t len, int64_t offset, std::vector<SubRange>&& sub_ranges, void* metadata,
int disk_id, bool expected_local, const BufferOpts& buffer_opts) {
DCHECK_GE(disk_id, -1);
DCHECK_GE(offset, 0);
DCHECK_GE(len, 0);
disk_id = ExecEnv::GetInstance()->disk_io_mgr()->AssignQueue(
fi.filename, disk_id, expected_local, /* check_default_fs */ true);
ScanRange* range = obj_pool->Add(new ScanRange);
range->Reset(fi, len, offset, disk_id, expected_local, buffer_opts,
move(sub_ranges), metadata);
return range;
}
void ScanRange::Reset(const FileInfo &fi, int64_t len, int64_t offset, int disk_id,
bool expected_local, const BufferOpts& buffer_opts, vector<SubRange>&& sub_ranges,
void* meta_data, DiskFile* disk_file, DiskFile* disk_buffer_file) {
DCHECK(buffer_manager_->is_readybuffer_empty());
DCHECK(!read_in_flight_);
DCHECK(fi.filename != nullptr);
DCHECK_GE(len, 0);
DCHECK_GE(offset, 0);
DCHECK(buffer_opts.client_buffer_ == nullptr ||
buffer_opts.client_buffer_len_ >= len_);
fs_ = fi.fs;
if (fs_ != nullptr) {
file_reader_ = make_unique<HdfsFileReader>(this, fs_, false);
local_buffer_reader_ = make_unique<LocalFileReader>(this);
} else {
file_reader_ = make_unique<LocalFileReader>(this);
}
file_ = fi.filename;
len_ = len;
bytes_to_read_ = len;
offset_ = offset;
disk_id_ = disk_id;
is_encrypted_ = fi.is_encrypted;
is_erasure_coded_ = fi.is_erasure_coded;
open_options_ = fi.open_options;
cache_options_ = buffer_opts.cache_options_;
disk_file_ = disk_file;
disk_buffer_file_ = disk_buffer_file;
// HDFS ranges must have an mtime > 0. Local ranges do not use mtime.
mtime_ = fi.mtime;
if (fs_) DCHECK_GT(mtime_, 0);
meta_data_ = meta_data;
if (buffer_opts.client_buffer_ != nullptr) {
buffer_manager_->set_client_buffer();
client_buffer_.data = buffer_opts.client_buffer_;
client_buffer_.len = buffer_opts.client_buffer_len_;
} else {
buffer_manager_->set_internal_buffer();
}
expected_local_ = expected_local;
io_mgr_ = nullptr;
reader_ = nullptr;
sub_ranges_.clear();
sub_range_pos_ = {};
InitSubRanges(move(sub_ranges));
}
void ScanRange::InitSubRanges(vector<SubRange>&& sub_ranges) {
sub_ranges_ = std::move(sub_ranges);
DCHECK(ValidateSubRanges());
MergeSubRanges();
DCHECK(ValidateSubRanges());
sub_range_pos_ = {};
if (sub_ranges_.empty()) return;
int length_sum = 0;
for (auto& sub_range : sub_ranges_) {
length_sum += sub_range.length;
}
bytes_to_read_ = length_sum;
}
bool ScanRange::ValidateSubRanges() {
for (int i = 0; i < sub_ranges_.size(); ++i) {
SubRange& sub_range = sub_ranges_[i];
if (sub_range.length <= 0) return false;
if (sub_range.offset < offset_) return false;
if (sub_range.offset + sub_range.length > offset_ + len_) return false;
if (i == sub_ranges_.size() - 1) break;
SubRange& next_sub_range = sub_ranges_[i+1];
if (sub_range.offset + sub_range.length > next_sub_range.offset) return false;
}
return true;
}
void ScanRange::MergeSubRanges() {
if (sub_ranges_.empty()) return;
for (int i = 0; i < sub_ranges_.size() - 1; ++i) {
SubRange& current = sub_ranges_[i];
int j = i + 1;
for (; j < sub_ranges_.size(); ++j) {
SubRange& sr_j = sub_ranges_[j];
if (sr_j.offset == current.offset + current.length) {
current.length += sr_j.length;
} else {
break;
}
}
if (j > i + 1) {
sub_ranges_.erase(sub_ranges_.begin() + i + 1, sub_ranges_.begin() + j);
}
}
}
void ScanRange::InitInternal(DiskIoMgr* io_mgr, RequestContext* reader) {
DCHECK(!read_in_flight_);
io_mgr_ = io_mgr;
reader_ = reader;
buffer_manager_->Init();
cancel_status_ = Status::OK();
eosr_queued_ = false;
blocked_on_buffer_ = false;
bytes_read_ = 0;
sub_range_pos_ = {};
file_reader_->ResetState();
if (local_buffer_reader_ != nullptr) local_buffer_reader_->ResetState();
unique_lock<mutex> scan_range_lock(lock_);
DCHECK(Validate(scan_range_lock)) << DebugString();
}
void ScanRange::SetFileReader(unique_ptr<FileReader> file_reader) {
file_reader_ = move(file_reader);
}
Status ScanRange::ReadFromCache(
const unique_lock<mutex>& reader_lock, bool* read_succeeded) {
DCHECK(reader_lock.mutex() == &reader_->lock_ && reader_lock.owns_lock());
DCHECK(UseHdfsCache());
DCHECK_EQ(bytes_read_, 0);
*read_succeeded = false;
Status status = file_reader_->Open();
if (!status.ok()) return status;
// Check cancel status.
{
unique_lock<mutex> lock(lock_);
RETURN_IF_ERROR(cancel_status_);
}
file_reader_->CachedFile(&cache_.data, &cache_.len);
// Data was not cached, caller will fall back to normal read path.
if (cache_.data == nullptr) {
VLOG_QUERY << "Cache read failed for scan range: " << DebugString()
<< ". Switching to disk read path.";
// Clean up the scan range state before re-issuing it.
file_reader_->Close();
return Status::OK();
}
// A partial read can happen when files are truncated.
// TODO: If HDFS ever supports partially cached blocks, we'll have to distinguish
// between errors and partially cached blocks here.
if (cache_.len < len()) {
VLOG_QUERY << "Error reading file from HDFS cache: " << file_ << ". Expected "
<< len() << " bytes, but read " << cache_.len << ". Switching to disk read path.";
// Null out the cache buffer to avoid any interactions when this falls
// back to the regular read path.
cache_.len = 0;
cache_.data = nullptr;
// Close the scan range. 'read_succeeded' is still false, so the caller will fall back
// to non-cached read of this scan range.
file_reader_->Close();
return Status::OK();
}
*read_succeeded = true;
// If there are sub-ranges, then we need to memcpy() them from the cached buffer.
if (HasSubRanges()) return Status::OK();
DCHECK(!buffer_manager_->is_client_buffer());
buffer_manager_->set_cached_buffer();
bytes_read_ = cache_.len;
// Create a single buffer desc for the entire scan range and enqueue that.
// The memory is owned by the HDFS java client, not the Impala backend.
unique_ptr<BufferDescriptor> desc = unique_ptr<BufferDescriptor>(new BufferDescriptor(
this, cache_.data, 0));
desc->len_ = cache_.len;
desc->eosr_ = true;
EnqueueReadyBuffer(move(desc));
COUNTER_ADD_IF_NOT_NULL(reader_->bytes_read_counter_, cache_.len);
return Status::OK();
}
Status ScanRange::AllocateBuffersForRange(
BufferPool::ClientHandle* bp_client, int64_t max_bytes,
int64_t min_buffer_size, int64_t max_buffer_size) {
vector<unique_ptr<BufferDescriptor>> buffers;
Status status = buffer_manager_->AllocateBuffersForRange(bp_client, max_bytes, buffers,
min_buffer_size, max_buffer_size);
if (!status.ok() && !buffers.empty()) {
unique_lock<mutex> lock(lock_);
buffer_manager_->CleanUpBuffers(lock, move(buffers));
} else {
AddUnusedBuffers(move(buffers), false);
}
return status;
}
void ScanRange::ScheduleScanRange() {
unique_lock<mutex> reader_lock(reader_->lock_);
// Reader may have been cancelled after we dropped 'scan_range_lock' above.
if (reader_->state_ == RequestContext::Cancelled) {
DCHECK(!cancel_status_.ok());
} else {
reader_->ScheduleScanRange(reader_lock, this);
}
}
void ScanRange::CloseReader(const unique_lock<mutex>& scan_range_lock) {
if (all_buffers_returned(scan_range_lock) &&
buffer_manager_->num_buffers_in_reader() == 0) {
// Close the readers if there are no more buffers in the reader and no more buffers
// will be returned to readers in future. Close() is idempotent so it is ok to call
// multiple times during cleanup so long as the range is actually finished.
if (!use_local_buffer_) {
file_reader_->Close();
} else {
local_buffer_reader_->Close();
}
}
}
BufferDescriptor::BufferDescriptor(ScanRange* scan_range,
uint8_t* buffer, int64_t buffer_len)
: scan_range_(scan_range),
buffer_(buffer),
buffer_len_(buffer_len) {
DCHECK(scan_range != nullptr);
DCHECK(buffer != nullptr);
DCHECK_GE(buffer_len, 0);
}
BufferDescriptor::BufferDescriptor(ScanRange* scan_range,
BufferPool::ClientHandle* bp_client, BufferPool::BufferHandle handle) :
scan_range_(scan_range),
buffer_(handle.data()),
buffer_len_(handle.len()),
bp_client_(bp_client),
handle_(move(handle)) {
DCHECK(scan_range != nullptr);
DCHECK(bp_client_->is_registered());
DCHECK(handle_.is_open());
}
void BufferDescriptor::Free() {
DCHECK(buffer_ != nullptr);
if (!is_cached() && !is_client_buffer()) {
// Only buffers that were allocated by DiskIoMgr need to be freed.
ExecEnv::GetInstance()->buffer_pool()->FreeBuffer(
bp_client_, &handle_);
}
buffer_ = nullptr;
}