* Copyright (c) 2020 Huawei Technologies Co.,Ltd.
*
* openGauss is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
*
* http://license.coscl.org.cn/MulanPSL2
*
* 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 PSL v2 for more details.
* -------------------------------------------------------------------------
*
* streamConsumer.cpp
* Support methods for class StreamConsumer.
*
* IDENTIFICATION
* src/gausskernel/process/stream/streamConsumer.cpp
*
* -------------------------------------------------------------------------
*/
#include "postgres.h"
#include "knl/knl_variable.h"
#include "access/printtup.h"
#include "distributelayer/streamConsumer.h"
#include "distributelayer/streamTransportComm.h"
#include "executor/executor.h"
#include "executor/exec/execStream.h"
#include "libpq/libpq.h"
#include "libcomm/libcomm.h"
#include "miscadmin.h"
#include "pgxc/pgxc.h"
extern GlobalNodeDefinition* global_node_definition;
StreamConsumer::StreamConsumer(MemoryContext context) : StreamObj(context, STREAM_CONSUMER)
{
m_sharedContext = NULL;
m_originProducerNodeList = NULL;
m_ready = false;
m_expectProducer = NULL;
m_currentProducerNum = 0;
}
StreamConsumer::~StreamConsumer()
{
m_originProducerNodeList = NULL;
m_expectProducer = NULL;
m_sharedContext = NULL;
}
* @Description: Init the consumer object
*
* @param[IN] key: stream key
* @param[IN] execProducerNodes: producer node list
* @param[IN] desc: parallel description
* @param[IN] transType: transport type
* @return: void
*/
void StreamConsumer::init(StreamKey key, List* execProducerNodes, ParallelDesc desc, StreamTransType transType,
StreamSharedContext* sharedContext)
{
int i = 0;
bool found = false;
int producerNum = 0;
AutoMutexLock streamLock(&m_streamInfoLock);
AutoMutexLock copyLock(&nodeDefCopyLock);
errno_t rc = EOK;
bool localNodeOnly = STREAM_IS_LOCAL_NODE(desc.distriType);
if (localNodeOnly)
producerNum = desc.producerDop;
else {
producerNum = desc.producerDop * list_length(execProducerNodes);
#ifdef USE_SPQ
if (IS_SPQ_EXECUTOR && m_plan) {
producerNum = desc.producerDop * m_plan->num_nodes;
}
#endif
}
Assert(producerNum > 0);
m_parallel_desc = desc;
m_currentProducerNum = 0;
m_connNum = producerNum;
m_key = key;
m_ready = false;
m_transtype = transType;
m_sharedContext = sharedContext;
m_transport = (StreamTransport**)MemoryContextAllocZero(m_memoryCxt, producerNum * sizeof(StreamTransport*));
m_expectProducer = (StreamConnInfo*)MemoryContextAllocZero(m_memoryCxt, producerNum * sizeof(StreamConnInfo));
m_originProducerNodeList = NIL;
#ifdef ENABLE_MULTIPLE_NODES
copyLock.lock();
ListCell* nodelistCell = NULL;
int nodeIdx;
for (int j = 0; j < desc.producerDop; j++) {
foreach (nodelistCell, execProducerNodes) {
nodeIdx = lfirst_int(nodelistCell);
if (localNodeOnly && nodeIdx != u_sess->pgxc_cxt.PGXCNodeId)
continue;
rc = strncpy_s(&m_expectProducer[i].nodeName[0],
NAMEDATALEN,
global_node_definition->nodesDefinition[nodeIdx].nodename.data,
strlen(global_node_definition->nodesDefinition[nodeIdx].nodename.data) + 1);
securec_check(rc, "\0", "\0");
m_expectProducer[i].nodeIdx = nodeIdx;
i++;
}
}
copyLock.unLock();
#endif
#ifdef USE_SPQ
if (IS_SPQ_EXECUTOR && m_plan != nullptr) {
for (int j = 0; j < desc.producerDop; j++) {
for(;i < m_plan->num_nodes; i++) {
if (localNodeOnly) {
continue;
}
rc = strncpy_s(&m_expectProducer[i].nodeName[0],
NAMEDATALEN,
m_plan->nodesDefinition[i].nodename.data,
strlen(m_plan->nodesDefinition[i].nodename.data) + 1);
securec_check(rc, "\0", "\0");
m_expectProducer[i].nodeIdx = i;
}
}
}
#endif
for (i = 0; i < producerNum; i++) {
int nodeNameLen = 0;
libcommaddrinfo* libcommaddr = NULL;
libcommaddr = (libcommaddrinfo*)palloc0(sizeof(libcommaddrinfo));
libcommaddr->ctrl_port = -1;
libcommaddr->listen_port = g_instance.attr.attr_network.comm_sctp_port + i;
nodeNameLen = strlen(&m_expectProducer[i].nodeName[0]);
rc = strncpy_s(libcommaddr->nodename, NAMEDATALEN, &m_expectProducer[i].nodeName[0], nodeNameLen + 1);
securec_check(rc, "\0", "\0");
m_transport[i] = New(m_memoryCxt) StreamCOMM(libcommaddr, false);
StreamCOMM* scomm = (StreamCOMM*)m_transport[i];
scomm->m_addr->streamKey.queryId = m_key.queryId;
scomm->m_addr->streamKey.planNodeId = m_key.planNodeId;
scomm->m_addr->streamKey.producerSmpId = 0;
scomm->m_addr->streamKey.consumerSmpId = m_key.smpIdentifier;
}
HOLD_INTERRUPTS();
streamLock.lock();
AutoContextSwitch streamInfoCxtGuard(StreamInfoContext);
StreamElement* element = (StreamElement*)hash_search(m_streamInfoTbl, &m_key, HASH_ENTER, &found);
if (element == NULL) {
streamLock.unLock();
ereport(
ERROR, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("Failed to create stream element due to out of memory")));
}
if (found == false) {
STREAM_LOG(DEBUG2,
"init consumer(%lu, %u, %u), key not found, build it",
m_key.queryId,
m_key.planNodeId,
m_key.smpIdentifier);
element->value = (StreamValue*)palloc0_noexcept(sizeof(StreamValue));
if (NULL == element->value) {
hash_search(m_streamInfoTbl, &key, HASH_REMOVE, NULL);
streamLock.unLock();
ereport(ERROR,
(errcode(ERRCODE_OUT_OF_MEMORY), errmsg("Failed to generate stream element due to out of memory")));
}
element->value->connNum = 0;
element->value->connInfoSize = 0;
element->value->connInfo = NULL;
} else {
if (element->value == NULL || element->value->consumer) {
streamLock.unLock();
ereport(ERROR,
(errmodule(MOD_STREAM),
errcode(ERRCODE_STREAM_DUPLICATE_QUERY_ID),
errmsg("Distribute query fail due to duplicate plan id")));
}
}
if (found == true) {
Assert(element->value->connInfo != NULL);
updateTransportInfo(element->value);
if (element->value->connInfo) {
pfree_ext(element->value->connInfo);
element->value->connInfo = NULL;
element->value->connInfoSize = 0;
}
}
if (m_threadSyncObjInit == false) {
pthread_mutex_init(&m_mutex, NULL);
pthread_cond_init(&m_cond, NULL);
m_threadSyncObjInit = true;
}
element->value->consumer = this;
m_init = true;
STREAM_LOG(DEBUG2,
"consumer inited, StreamKey(%lu, %u, %u), "
"local: %s, producer nodelist length: %d",
key.queryId,
key.planNodeId,
key.smpIdentifier,
PRINTTRUE(localNodeOnly),
list_length(execProducerNodes));
streamLock.unLock();
RESUME_INTERRUPTS();
}
* @Description: Deinit the consumer object
*
* @return: void
*/
void StreamConsumer::deInit()
{
AutoMutexLock streamHashLock(&m_streamInfoLock);
StreamElement* delinfo = NULL;
HOLD_INTERRUPTS();
streamHashLock.lock();
if (m_init == false) {
if (m_threadSyncObjInit == true) {
pthread_mutex_destroy(&m_mutex);
pthread_cond_destroy(&m_cond);
m_threadSyncObjInit = false;
}
streamHashLock.unLock();
RESUME_INTERRUPTS();
return;
}
releaseCommStream();
if (NULL != m_sharedContext) {
gs_memory_close_conn(m_sharedContext, m_connNum, u_sess->stream_cxt.smp_id);
}
if (m_ready || u_sess->stream_cxt.dummy_thread == true) {
delinfo = (StreamElement*)hash_search(m_streamInfoTbl, &m_key, HASH_REMOVE, NULL);
if (delinfo != NULL) {
if (delinfo->value->connInfo != NULL) {
pfree_ext(delinfo->value->connInfo);
delinfo->value->connInfo = NULL;
}
pfree_ext(delinfo->value);
delinfo->value = NULL;
}
} else {
* set flag to release net port in case some producer not ready and element
* in stream info hash table can not get removed in wakeUpConsumer,
* or consumer get canceled when waiting producer ready.
*/
u_sess->stream_cxt.global_obj->setNeedClean(true);
}
if (m_threadSyncObjInit == true) {
pthread_mutex_destroy(&m_mutex);
pthread_cond_destroy(&m_cond);
m_threadSyncObjInit = false;
}
m_init = false;
streamHashLock.unLock();
m_originProducerNodeList = NIL;
RESUME_INTERRUPTS();
}
* @Description: Release sctp mode stream
*
* @return: void
*/
void StreamConsumer::releaseCommStream()
{
if (m_transport != NULL) {
for (int i = 0; i < m_connNum; i++) {
Assert(t_thrd.int_cxt.ImmediateInterruptOK == false);
StreamCOMM* scomm = (StreamCOMM*)m_transport[i];
if (scomm != NULL)
scomm->release();
}
}
}
* @Description: Get nodeIdx of producer by nodename
*
* @param[IN] nodename: node name
* @return: node index
*/
int StreamConsumer::getNodeIdx(const char* nodename)
{
for (int i = 0; i < m_connNum; i++) {
if (pg_strncasecmp(m_expectProducer[i].nodeName, nodename, strlen(nodename)) == 0)
return m_expectProducer[i].nodeIdx;
}
return -1;
}
#ifdef USE_SPQ
* @Description: Get expectProducer nodeName
*
* @return: nodeName
*/
char* StreamConsumer::getExpectProducerNodeName()
{
return m_expectProducer->nodeName;
}
void StreamConsumer::setPstmt(PlannedStmt* p_stmt)
{
m_plan = p_stmt;
}
#endif
* @Description: Find un connect producer
*
* @param[OUT] str: string to store unconnected producer(s)
* @return: void
*/
void StreamConsumer::findUnconnectProducer(StringInfo str)
{
bool found = false;
for (int j = 0; j < m_connNum; j++) {
found = false;
for (int i = 0; i < m_currentProducerNum; i++) {
if (strcmp(m_expectProducer[j].nodeName, m_transport[i]->m_nodeName) == 0) {
found = true;
break;
}
}
if (!found)
appendStringInfo(str, " %s", m_expectProducer[j].nodeName);
}
}
* @Description: Get nodeIdx of the first un-connected producer.
*
* @return: int: nodeid
*/
int StreamConsumer::getFirstUnconnectedProducerNodeIdx()
{
bool found = false;
int nodeIdx = -1;
for (int j = 0; j < m_connNum; j++) {
found = false;
for (int i = 0; i < m_currentProducerNum; i++) {
if (strcmp(m_expectProducer[j].nodeName, m_transport[i]->m_nodeName) == 0) {
found = true;
break;
}
}
if (!found) {
nodeIdx = m_expectProducer[j].nodeIdx;
break;
}
}
return nodeIdx;
}
* @Description: Waiting for the producer is ready
*
* @return: void
*/
void StreamConsumer::waitProducerReady()
{
if (STREAM_IS_LOCAL_NODE(m_parallel_desc.distriType)) {
m_ready = true;
return;
}
AutoMutexLock streamLock(&m_mutex);
if (m_ready == false) {
STREAM_LOG(DEBUG2,
"consumer(%lu, %u, %u) begin sleep until producer ready",
m_key.queryId,
m_key.planNodeId,
m_key.smpIdentifier);
WaitState oldStatus = pgstat_report_waitstatus(STATE_WAIT_UNDEFINED, true);
Assert(t_thrd.int_cxt.ImmediateInterruptOK == false);
streamLock.lock();
struct timespec timer;
int ret;
int ntimes = 1;
StringInfoData str;
initStringInfo(&str);
while (m_ready == false) {
clock_gettime(CLOCK_REALTIME, &timer);
timer.tv_sec += 3;
timer.tv_nsec = 0;
ret = pthread_cond_timedwait(&m_cond, &m_mutex, &timer);
if (ret == ETIMEDOUT || m_ready == false) {
streamLock.unLock();
ntimes++;
pgstat_report_waitstatus_comm(STATE_STREAM_WAIT_PRODUCER_READY,
getFirstUnconnectedProducerNodeIdx(),
m_connNum - m_currentProducerNum,
m_key.planNodeId,
#ifdef USE_SPQ
m_plan ? m_plan->num_nodes : -1);
#else
global_node_definition ? global_node_definition->num_nodes : -1);
#endif
if (ntimes == 300) {
if (t_thrd.int_cxt.QueryCancelPending) {
ereport(WARNING, (errmodule(MOD_STREAM),
errcode(ERRCODE_CONNECTION_TIMED_OUT),
errmsg("Did not handle interrupt signal, with InterruptHoldoffCount(%u),",
t_thrd.int_cxt.InterruptHoldoffCount)));
}
resetStringInfo(&str);
findUnconnectProducer(&str);
ereport(ERROR,
(errmodule(MOD_STREAM),
errcode(ERRCODE_CONNECTION_TIMED_OUT),
errmsg("Distribute query initializing network connection timeout. un-connected nodes: %s",
str.data)));
}
CHECK_FOR_INTERRUPTS();
streamLock.lock();
}
}
streamLock.unLock();
pgstat_report_waitstatus(oldStatus);
STREAM_LOG(DEBUG2,
"consumer(%lu, %u, %u) wake up as producer ready",
m_key.queryId,
m_key.planNodeId,
m_key.smpIdentifier);
}
return;
}
* @Description: Wake up consumer and let it work
*
* @param[IN] commKey: stream key
* @param[IN] connInfo: connection info
* @return: true if wake up consumer successfully
*/
bool StreamConsumer::wakeUpConsumerCallBack(CommStreamKey commKey, StreamConnInfo connInfo)
{
bool found = false;
bool res = true;
AutoMutexLock streamLock(&m_streamInfoLock);
int nodeNameLen = strlen(connInfo.nodeName);
errno_t rc = EOK;
StreamKey key = {0};
key.queryId = commKey.queryId;
key.planNodeId = commKey.planNodeId;
key.smpIdentifier = commKey.consumerSmpId;
connInfo.producerSmpId = commKey.producerSmpId;
streamLock.lock();
if (StreamNodeGroup::checkStreamConnectPermission(key.queryId) == false) {
streamLock.unLock();
return false;
}
AutoContextSwitch streamInfoCxtGuard(StreamInfoContext);
* Register me in the global consumer table if the consumer thread has not been started.
* Libcomm r_flow_ctrl thread call this, must not use palloc and elog.
* so use isLibcommThread flag, return false where malloc failed.
*/
StreamElement* element = (StreamElement*)hash_search(m_streamInfoTbl, &key, HASH_ENTER, &found);
if (element == NULL) {
errno = ENOMEM;
streamLock.unLock();
return false;
}
if (found == false) {
element->value = (StreamValue*)palloc0_noexcept(sizeof(StreamValue));
if (NULL == element->value) {
hash_search(m_streamInfoTbl, &key, HASH_REMOVE, NULL);
errno = ENOMEM;
streamLock.unLock();
return false;
}
element->value->connInfo = (StreamConnInfo*)palloc0_noexcept(sizeof(StreamConnInfo) * INIT_STREAMINFO_SIZE);
if (NULL == element->value->connInfo) {
pfree_ext(element->value);
element->value = NULL;
hash_search(m_streamInfoTbl, &key, HASH_REMOVE, NULL);
errno = ENOMEM;
streamLock.unLock();
return false;
}
element->value->connInfoSize = INIT_STREAMINFO_SIZE;
element->value->connInfo[0].port.libcomm_layer.gsock = connInfo.port.libcomm_layer.gsock;
rc = strncpy_s(element->value->connInfo[0].nodeName, NAMEDATALEN, connInfo.nodeName, nodeNameLen + 1);
securec_check(rc, "\0", "\0");
element->value->connInfo[0].producerSmpId = connInfo.producerSmpId;
element->value->connNum = 1;
element->value->consumer = NULL;
} else {
if (element->value) {
if (element->value->consumer == NULL) {
element->value->connInfo[element->value->connNum].port.libcomm_layer.gsock =
connInfo.port.libcomm_layer.gsock;
rc = strncpy_s(element->value->connInfo[element->value->connNum].nodeName,
NAMEDATALEN,
connInfo.nodeName,
nodeNameLen + 1);
securec_check(rc, "\0", "\0");
element->value->connInfo[element->value->connNum].producerSmpId = connInfo.producerSmpId;
element->value->connNum++;
if (element->value->connNum == element->value->connInfoSize) {
StreamConnInfo* new_connInfo = NULL;
int new_connInfoSize = 2 * element->value->connInfoSize;
new_connInfo = (StreamConnInfo*)palloc0_noexcept(new_connInfoSize * sizeof(StreamConnInfo));
if (NULL == new_connInfo) {
pfree_ext(element->value->connInfo);
element->value->connInfo = NULL;
pfree_ext(element->value);
element->value = NULL;
hash_search(m_streamInfoTbl, &key, HASH_REMOVE, NULL);
errno = ENOMEM;
streamLock.unLock();
return false;
}
rc = memcpy_s(new_connInfo,
new_connInfoSize * sizeof(StreamConnInfo),
element->value->connInfo,
element->value->connInfoSize * sizeof(StreamConnInfo));
securec_check(rc, "\0", "\0");
pfree_ext(element->value->connInfo);
element->value->connInfo = new_connInfo;
element->value->connInfoSize = new_connInfoSize;
}
} else {
res = element->value->consumer->updateStreamCommInfo(&connInfo);
if (element->value->connInfo != NULL) {
pfree_ext(element->value->connInfo);
element->value->connInfo = NULL;
}
}
} else {
streamLock.unLock();
return false;
}
}
streamLock.unLock();
return res;
}
* @Description: Update the stream info
*
* @param[IN] connInfo: connection info
* @return: true if update successfully
*/
bool StreamConsumer::updateStreamCommInfo(StreamConnInfo* connInfo)
{
AutoMutexLock streamLock(&m_mutex);
streamLock.lock();
if (m_currentProducerNum >= m_connNum) {
streamLock.unLock();
return false;
}
StreamCOMM* scomm = (StreamCOMM*)m_transport[m_currentProducerNum];
scomm->updateInfo(connInfo);
m_currentProducerNum++;
if (m_currentProducerNum == m_connNum) {
m_ready = true;
(void)pthread_cond_signal(&m_cond);
}
streamLock.unLock();
return true;
}
* @Description: Update the transport info
*
* @param[IN] val: stream value
* @return: void
*/
void StreamConsumer::updateTransportInfo(StreamValue* val)
{
if (val->connNum > m_connNum)
ereport(ERROR,
(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
errmsg("Distribute query fail due to duplicate plan id when register consumer socket, "
"current_connNum: %d, expected_connNum: %d",
val->connNum,
m_connNum)));
for (int i = 0; i < val->connNum; i++) {
m_transport[i]->updateInfo(&val->connInfo[i]);
val->connInfo[i].port.tcpLayer.fd = NO_SOCKET;
m_currentProducerNum++;
if (m_currentProducerNum == m_connNum)
m_ready = true;
}
}