blob: 9659faf624dcfdc38bc610d5582a2c9a0b3686c6 [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 "exec/tuple-file-reader.h"
#include "gutil/strings/substitute.h"
#include "kudu/util/array_view.h"
#include "kudu/util/env.h"
#include "kudu/util/slice.h"
#include "runtime/mem-tracker.h"
#include "runtime/row-batch.h"
#include "runtime/runtime-state.h"
#include "util/debug-util.h"
#include "util/kudu-status-util.h"
#include "common/names.h"
namespace impala {
TupleFileReader::TupleFileReader(
const std::string& path, MemTracker* parent, RuntimeProfile* profile)
: path_(path),
tracker_(new MemTracker(-1, "TupleFileReader", parent)),
read_timer_(profile ? ADD_TIMER(profile, "TupleCacheReadTime") : nullptr),
deserialize_timer_(
profile ? ADD_TIMER(profile, "TupleCacheDeserializeTime") : nullptr),
bytes_read_(profile ?
ADD_COUNTER(profile, "TupleCacheBytesRead", TUnit::BYTES) : nullptr) {}
TupleFileReader::~TupleFileReader() {
// MemTracker expects an explicit close. Since the TupleFileReader can be freed
// before query execution ends, this must unregister the MemTracker from its parent
// before it is freed.
tracker_->CloseAndUnregisterFromParent();
}
Status TupleFileReader::Open(RuntimeState *state) {
kudu::RWFileOptions opts;
opts.mode = kudu::Env::OpenMode::MUST_EXIST;
KUDU_RETURN_IF_ERROR(
kudu::Env::Default()->NewRWFile(opts, path_, &reader_),
"Failed to open tuple cache file");
// Get the file size
KUDU_RETURN_IF_ERROR(reader_->Size(&file_size_),
"Failed to get size for the tuple cache file");
RETURN_IF_ERROR(DebugAction(state->query_options(), "TUPLE_FILE_READER_OPEN"));
return Status::OK();
}
Status TupleFileReader::GetNext(RuntimeState *state,
BufferPool::ClientHandle* bpclient, RowBatch* output_row_batch, bool* eos) {
if (offset_ == file_size_) {
*eos = true;
return Status::OK();
}
// Only the first rowbatch starts at the zero offset of the file.
// We use this for injecting errors for testing purposes.
bool first_rowbatch = offset_ == 0;
*eos = false;
SCOPED_TIMER(read_timer_);
// Each block starts with the sizes of the variable chunks of data:
// 1. The header size
// 2. The tuple data size
// 3. The tuple offsets size
size_t header_len;
size_t tuple_data_len;
size_t tuple_offsets_len;
vector<kudu::Slice> chunk_lens_slices = {
kudu::Slice(reinterpret_cast<const char*>(&header_len), sizeof(header_len)),
kudu::Slice(reinterpret_cast<const char*>(&tuple_data_len),
sizeof(tuple_data_len)),
kudu::Slice(reinterpret_cast<const char*>(&tuple_offsets_len),
sizeof(tuple_offsets_len))};
KUDU_RETURN_IF_ERROR(reader_->ReadV(offset_,
kudu::ArrayView<kudu::Slice>(chunk_lens_slices)),
"Failed to read cache file");
size_t chunk_lens_slices_size = sizeof(header_len) + sizeof(tuple_data_len) +
sizeof(tuple_offsets_len);
COUNTER_ADD(bytes_read_, chunk_lens_slices_size);
// tuple_data_len can be zero, see IMPALA-13411.
if (header_len == 0 || tuple_offsets_len == 0) {
string err_msg = Substitute("Invalid data lengths at offset $0 in $1: "
"header_len=$2, tuple_data_len=$3, tuple_offsets_len=$4", offset_, path_,
header_len, tuple_data_len, tuple_offsets_len);
DCHECK(false) << err_msg;
return Status(Substitute("Invalid tuple cache file: $0", err_msg));
}
offset_ += chunk_lens_slices_size;
// Now, we know the total size of the variable-length data, and we can read
// it in a single chunk.
size_t varlen_size = header_len + tuple_data_len + tuple_offsets_len;
// Sanity check: The varlen_size shouldn't be larger than the rest of the file.
// This protects us from doing very large memory allocations if any of the lengths
// are bogus.
if (offset_ + varlen_size > file_size_) {
string err_msg = Substitute("Invalid data lengths at offset $0 in $1 exceed "
"file size $2: header_len=$3, tuple_data_len=$4, tuple_offsets_len=$5",
offset_, path_, file_size_, header_len, tuple_data_len, tuple_offsets_len);
DCHECK(false) << err_msg;
return Status(Substitute("Invalid tuple cache file: $0", err_msg));
}
std::unique_ptr<char []> varlen_data(new char[varlen_size]);
kudu::Slice header_slice = kudu::Slice(varlen_data.get(), header_len);
kudu::Slice tuple_data_slice =
kudu::Slice(varlen_data.get() + header_len, tuple_data_len);
kudu::Slice tuple_offsets_slice =
kudu::Slice(varlen_data.get() + header_len + tuple_data_len, tuple_offsets_len);
std::vector<kudu::Slice> varlen_data_slices =
{ header_slice, tuple_data_slice, tuple_offsets_slice };
KUDU_RETURN_IF_ERROR(reader_->ReadV(offset_,
kudu::ArrayView<kudu::Slice>(varlen_data_slices)),
"Failed to read tuple cache file");
COUNTER_ADD(bytes_read_, varlen_size);
offset_ += varlen_size;
RowBatchHeaderPB header;
// The header is at the start of the varlen data
if (!header.ParseFromArray(header_slice.data(), header_slice.size())) {
return Status(TErrorCode::INTERNAL_ERROR,
"Could not deserialize RowBatchHeaderPB from disk cache");
}
std::unique_ptr<RowBatch> row_batch;
{
SCOPED_TIMER(deserialize_timer_);
RETURN_IF_ERROR(RowBatch::FromProtobuf(output_row_batch->row_desc(), header,
tuple_offsets_slice, tuple_data_slice, tracker_.get(), bpclient, &row_batch));
}
DCHECK_EQ(output_row_batch->num_rows(), 0);
if (output_row_batch->capacity() < row_batch->num_rows()) {
string err_msg =
Substitute("Too many rows ($0) for the output row batch (capacity $1)",
row_batch->num_rows(), output_row_batch->capacity());
DCHECK(false) << err_msg;
return Status(Substitute("Invalid tuple cache file: $0", err_msg));
}
// For testing, we inject an error as late as possible in this function. Nothing
// after this point returns not-OK status. If we can recover from this point,
// we can recover from previous points. There are two different cases: either
// we want an error on the first row batch produced or we want an error on
// a subsequent row batch.
if (first_rowbatch) {
RETURN_IF_ERROR(
DebugAction(state->query_options(), "TUPLE_FILE_READER_FIRST_GETNEXT"));
} else {
RETURN_IF_ERROR(
DebugAction(state->query_options(), "TUPLE_FILE_READER_SECOND_GETNEXT"));
}
// Set eos after any possibility of a not-OK status
if (offset_ == file_size_) {
*eos = true;
}
output_row_batch->AddRows(row_batch->num_rows());
for (int row = 0; row < row_batch->num_rows(); row++) {
TupleRow* src = row_batch->GetRow(row);
TupleRow* dest = output_row_batch->GetRow(row);
// if the input row is shorter than the output row, make sure not to leave
// uninitialized Tuple* around
output_row_batch->ClearRow(dest);
// this works as expected if rows from input_batch form a prefix of
// rows in output_batch
row_batch->CopyRow(src, dest);
}
output_row_batch->CommitRows(row_batch->num_rows());
row_batch->TransferResourceOwnership(output_row_batch);
return Status::OK();
}
} // namespace impala