blob: 7faa022611135a692b45ca68e3de761975be9215 [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/exchange-node.h"
#include <boost/scoped_ptr.hpp>
#include "exec/exec-node-util.h"
#include "exprs/scalar-expr.h"
#include "runtime/exec-env.h"
#include "runtime/fragment-state.h"
#include "runtime/krpc-data-stream-mgr.h"
#include "runtime/krpc-data-stream-recvr.h"
#include "runtime/row-batch.h"
#include "runtime/runtime-state.h"
#include "util/debug-util.h"
#include "util/runtime-profile-counters.h"
#include "util/time.h"
#include "util/tuple-row-compare.h"
#include "gen-cpp/PlanNodes_types.h"
#include "common/names.h"
DECLARE_int32(stress_datastream_recvr_delay_ms);
using namespace impala;
DEFINE_int64(exchg_node_buffer_size_bytes, 1024 * 1024 * 10,
"(Advanced) Maximum size of per-query receive-side buffer");
Status ExchangePlanNode::Init(const TPlanNode& tnode, FragmentState* state) {
RETURN_IF_ERROR(PlanNode::Init(tnode, state));
bool is_merging = tnode_->exchange_node.__isset.sort_info;
if (!is_merging) return Status::OK();
const TSortInfo& sort_info = tnode.exchange_node.sort_info;
RETURN_IF_ERROR(ScalarExpr::Create(
sort_info.ordering_exprs, *row_descriptor_, state, &ordering_exprs_));
row_comparator_config_ =
state->obj_pool()->Add(new TupleRowComparatorConfig(sort_info, ordering_exprs_));
state->CheckAndAddCodegenDisabledMessage(codegen_status_msgs_);
return Status::OK();
}
void ExchangePlanNode::Close() {
ScalarExpr::Close(ordering_exprs_);
PlanNode::Close();
}
Status ExchangePlanNode::CreateExecNode(RuntimeState* state, ExecNode** node) const {
ObjectPool* pool = state->obj_pool();
*node = pool->Add(new ExchangeNode(pool, *this, state->desc_tbl()));
return Status::OK();
}
ExchangeNode::ExchangeNode(
ObjectPool* pool, const ExchangePlanNode& pnode, const DescriptorTbl& descs)
: ExecNode(pool, pnode, descs),
num_senders_(0),
stream_recvr_(),
input_row_desc_(descs, pnode.tnode_->exchange_node.input_row_tuples,
vector<bool>(pnode.tnode_->nullable_tuples.begin(),
pnode.tnode_->nullable_tuples.begin()
+ pnode.tnode_->exchange_node.input_row_tuples.size())),
next_row_idx_(0),
is_merging_(pnode.tnode_->exchange_node.__isset.sort_info),
offset_(pnode.tnode_->exchange_node.__isset.offset ?
pnode.tnode_->exchange_node.offset :
0),
num_rows_skipped_(0) {
DCHECK_GE(offset_, 0);
DCHECK(is_merging_ || (offset_ == 0));
if (!is_merging_) return;
less_than_.reset(new TupleRowLexicalComparator(*pnode.row_comparator_config_));
}
Status ExchangeNode::Prepare(RuntimeState* state) {
RETURN_IF_ERROR(ExecNode::Prepare(state));
convert_row_batch_timer_ = ADD_TIMER(runtime_profile(), "ConvertRowBatchTime");
#ifndef NDEBUG
if (FLAGS_stress_datastream_recvr_delay_ms > 0) {
SleepForMs(FLAGS_stress_datastream_recvr_delay_ms);
}
#endif
RETURN_IF_ERROR(ExecEnv::GetInstance()->buffer_pool()->RegisterClient(
Substitute("Exchg Recvr (id=$0)", id_), nullptr,
ExecEnv::GetInstance()->buffer_reservation(), mem_tracker(),
numeric_limits<int64_t>::max(), runtime_profile(), &recvr_buffer_pool_client_,
MemLimit::HARD));
// TODO: figure out appropriate buffer size
DCHECK_GT(num_senders_, 0);
stream_recvr_ = ExecEnv::GetInstance()->stream_mgr()->CreateRecvr(
&input_row_desc_, *state, state->fragment_instance_id(), id_, num_senders_,
FLAGS_exchg_node_buffer_size_bytes, is_merging_, runtime_profile(), mem_tracker(),
&recvr_buffer_pool_client_);
return Status::OK();
}
void ExchangePlanNode::Codegen(FragmentState* state) {
DCHECK(state->ShouldCodegen());
PlanNode::Codegen(state);
if (IsNodeCodegenDisabled()) return;
if (row_comparator_config_ != nullptr) {
Status codegen_status;
llvm::Function* compare_fn = nullptr;
codegen_status = row_comparator_config_->Codegen(state, &compare_fn);
if (codegen_status.ok()) {
codegen_status =
SortedRunMerger::Codegen(state, compare_fn, &codegend_heapify_helper_fn_);
}
AddCodegenStatus(codegen_status);
}
}
Status ExchangeNode::Open(RuntimeState* state) {
SCOPED_TIMER(runtime_profile_->total_time_counter());
ScopedOpenEventAdder ea(this);
RETURN_IF_ERROR(ExecNode::Open(state));
RETURN_IF_CANCELLED(state);
if (is_merging_) {
const ExchangePlanNode& pnode = static_cast<const ExchangePlanNode&>(plan_node_);
// CreateMerger() will populate its merging heap with batches from the stream_recvr_,
// so it is not necessary to call FillInputRowBatch().
RETURN_IF_ERROR(
less_than_->Open(pool_, state, expr_perm_pool(), expr_results_pool()));
RETURN_IF_ERROR(stream_recvr_->CreateMerger(*less_than_.get(),
pnode.codegend_heapify_helper_fn_));
} else {
RETURN_IF_ERROR(FillInputRowBatch(state));
}
return Status::OK();
}
Status ExchangeNode::Reset(RuntimeState* state, RowBatch* row_batch) {
DCHECK(false) << "NYI";
return Status("NYI");
}
void ExchangeNode::Close(RuntimeState* state) {
if (is_closed()) return;
if (less_than_.get() != nullptr) less_than_->Close(state);
if (stream_recvr_ != nullptr) stream_recvr_->Close();
ExecEnv::GetInstance()->buffer_pool()->DeregisterClient(&recvr_buffer_pool_client_);
ExecNode::Close(state);
}
Status ExchangeNode::FillInputRowBatch(RuntimeState* state) {
DCHECK(!is_merging_);
Status ret_status;
{
SCOPED_TIMER(state->total_network_receive_timer());
ret_status = stream_recvr_->GetBatch(&input_batch_);
}
VLOG_FILE << "exch: has batch=" << (input_batch_ == NULL ? "false" : "true")
<< " #rows=" << (input_batch_ != NULL ? input_batch_->num_rows() : 0)
<< " is_cancelled=" << (ret_status.IsCancelled() ? "true" : "false")
<< " instance_id=" << PrintId(state->fragment_instance_id());
return ret_status;
}
void ExchangeNode::ReleaseRecvrResources(RowBatch* output_batch) {
stream_recvr_->TransferAllResources(output_batch);
stream_recvr_->CancelStream();
}
Status ExchangeNode::GetNext(RuntimeState* state, RowBatch* output_batch, bool* eos) {
SCOPED_TIMER(runtime_profile_->total_time_counter());
ScopedGetNextEventAdder ea(this, eos);
RETURN_IF_ERROR(ExecDebugAction(TExecNodePhase::GETNEXT, state));
if (ReachedLimit()) {
ReleaseRecvrResources(output_batch);
*eos = true;
return Status::OK();
} else {
*eos = false;
}
if (is_merging_) return GetNextMerging(state, output_batch, eos);
while (true) {
{
SCOPED_TIMER(convert_row_batch_timer_);
RETURN_IF_CANCELLED(state);
RETURN_IF_ERROR(QueryMaintenance(state));
if (input_batch_ != nullptr) {
// copy rows until we hit the limit/capacity or until we exhaust input_batch_
int available_in_input = input_batch_->num_rows() - next_row_idx_;
int free_in_output = output_batch->capacity() - output_batch->num_rows();
int rows_to_copy = std::min(available_in_input, free_in_output);
if (limit_ != -1) {
rows_to_copy =
static_cast<int>(std::min<int64_t>(rows_to_copy, limit_ - rows_returned()));
}
if (rows_to_copy > 0) {
int dst_offset = output_batch->AddRows(rows_to_copy);
output_batch->CopyRows(input_batch_, rows_to_copy, next_row_idx_, dst_offset);
next_row_idx_ += rows_to_copy;
output_batch->CommitRows(rows_to_copy);
IncrementNumRowsReturned(rows_to_copy);
COUNTER_SET(rows_returned_counter_, rows_returned());
}
}
if (ReachedLimit()) {
ReleaseRecvrResources(output_batch);
*eos = true;
return Status::OK();
}
if (output_batch->AtCapacity()) return Status::OK();
}
// we need more rows
stream_recvr_->TransferAllResources(output_batch);
RETURN_IF_ERROR(FillInputRowBatch(state));
*eos = (input_batch_ == nullptr);
// No need to call CancelStream() on the receiver here as all incoming row batches
// have been consumed so we should have replied to all senders already.
if (*eos) return Status::OK();
next_row_idx_ = 0;
DCHECK(input_batch_->row_desc()->LayoutIsPrefixOf(*output_batch->row_desc()));
}
}
Status ExchangeNode::GetNextMerging(RuntimeState* state, RowBatch* output_batch,
bool* eos) {
DCHECK_EQ(output_batch->num_rows(), 0);
RETURN_IF_CANCELLED(state);
RETURN_IF_ERROR(QueryMaintenance(state));
// Clear any expr result allocations made by the merger.
expr_results_pool_->Clear();
RETURN_IF_ERROR(stream_recvr_->GetNext(output_batch, eos));
while (num_rows_skipped_ < offset_) {
num_rows_skipped_ += output_batch->num_rows();
// Throw away rows in the output batch until the offset is skipped.
int64_t rows_to_keep = num_rows_skipped_ - offset_;
if (rows_to_keep > 0) {
output_batch->CopyRows(0, output_batch->num_rows() - rows_to_keep, rows_to_keep);
output_batch->set_num_rows(rows_to_keep);
} else {
output_batch->set_num_rows(0);
}
if (rows_to_keep > 0 || *eos || output_batch->AtCapacity()) break;
RETURN_IF_ERROR(stream_recvr_->GetNext(output_batch, eos));
}
CheckLimitAndTruncateRowBatchIfNeeded(output_batch, eos);
// On eos, transfer all remaining resources from the input batches maintained
// by the merger to the output batch. Also cancel the underlying receiver so
// the senders' fragments can exit early.
if (*eos) ReleaseRecvrResources(output_batch);
COUNTER_SET(rows_returned_counter_, rows_returned());
return Status::OK();
}
void ExchangeNode::DebugString(int indentation_level, stringstream* out) const {
*out << string(indentation_level * 2, ' ');
*out << "ExchangeNode(#senders=" << num_senders_;
ExecNode::DebugString(indentation_level, out);
*out << ")";
}