diff --git a/contrib/mysql_fdw/Makefile b/contrib/mysql_fdw/Makefile index e43521352..5e6584dad 100644 --- a/contrib/mysql_fdw/Makefile +++ b/contrib/mysql_fdw/Makefile @@ -1,61 +1,61 @@ -# -# 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. -# --------------------------------------------------------------------------------------- -# -# Makefile -# Makefile for the mysql_fdw -# -# IDENTIFICATION -# contrib/mysql_fdw/Makefile -# -# --------------------------------------------------------------------------------------- - -all:mysql_fdw_target -install:install-data - -top_builddir ?= ../../ -MYSQL_FDW_DIR=$(top_builddir)/third_party/dependency/mysql_fdw -MYSQL_FDW_PACKAGE=mysql_fdw-REL-2_5_3 -MYSQL_FDW_PATCH=huawei_mysql_fdw-2.5.3_patch -MYSQL_FDW_MEGRED_SOURCES_DIR=$(MYSQL_FDW_DIR)/code - -.PHONY: mysql_fdw_target -mysql_fdw_target: - @$(call create_mysql_fdw_sources) - @make -C $(MYSQL_FDW_MEGRED_SOURCES_DIR)/$(MYSQL_FDW_PACKAGE) - -.PHONY: install-data -install-data: mysql_fdw_target - @make -C $(MYSQL_FDW_MEGRED_SOURCES_DIR)/$(MYSQL_FDW_PACKAGE) install - -uninstall distclean clean: - @rm -rf $(MYSQL_FDW_MEGRED_SOURCES_DIR) - -MYSQL_FDW_RELEVANT_SOURCES = connection.c deparse.c mysql_fdw.c mysql_query.c option.c - -define create_mysql_fdw_sources - rm -rf $(MYSQL_FDW_MEGRED_SOURCES_DIR); \ - mkdir $(MYSQL_FDW_MEGRED_SOURCES_DIR); \ - tar xfzv $(MYSQL_FDW_DIR)/$(MYSQL_FDW_PACKAGE).tar.gz -C $(MYSQL_FDW_MEGRED_SOURCES_DIR) &> /dev/null; \ - for ((i=1;i<=99;i++)); \ - do \ - file_name="$(MYSQL_FDW_DIR)/$$i-mysql_fdw-2.5.3_patch.patch"; \ - if [ ! -f "$$file_name" ]; then \ - exit 0; \ - fi; \ - patch -p0 -d $(MYSQL_FDW_MEGRED_SOURCES_DIR)/$(MYSQL_FDW_PACKAGE) < $$file_name &> /dev/null; \ - done - rename ".c" ".cpp" $(MYSQL_FDW_MEGRED_SOURCES_DIR)/$(MYSQL_FDW_PACKAGE)/*.c; \ - patch -p0 -d $(MYSQL_FDW_MEGRED_SOURCES_DIR)/$(MYSQL_FDW_PACKAGE) < $(MYSQL_FDW_DIR)/$(MYSQL_FDW_PATCH).patch &> /dev/null; -endef +# +# 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. +# --------------------------------------------------------------------------------------- +# +# Makefile +# Makefile for the mysql_fdw +# +# IDENTIFICATION +# contrib/mysql_fdw/Makefile +# +# --------------------------------------------------------------------------------------- + +all:mysql_fdw_target +install:install-data + +top_builddir ?= ../../ +MYSQL_FDW_DIR=$(top_builddir)/third_party/dependency/mysql_fdw +MYSQL_FDW_PACKAGE=mysql_fdw-REL-2_5_3 +MYSQL_FDW_PATCH=openGauss_mysql_fdw-2.5.3_patch +MYSQL_FDW_MEGRED_SOURCES_DIR=$(MYSQL_FDW_DIR)/code + +.PHONY: mysql_fdw_target +mysql_fdw_target: + @$(call create_mysql_fdw_sources) + @make -C $(MYSQL_FDW_MEGRED_SOURCES_DIR)/$(MYSQL_FDW_PACKAGE) + +.PHONY: install-data +install-data: mysql_fdw_target + @make -C $(MYSQL_FDW_MEGRED_SOURCES_DIR)/$(MYSQL_FDW_PACKAGE) install + +uninstall distclean clean: + @rm -rf $(MYSQL_FDW_MEGRED_SOURCES_DIR) + +MYSQL_FDW_RELEVANT_SOURCES = connection.c deparse.c mysql_fdw.c mysql_query.c option.c + +define create_mysql_fdw_sources + rm -rf $(MYSQL_FDW_MEGRED_SOURCES_DIR); \ + mkdir $(MYSQL_FDW_MEGRED_SOURCES_DIR); \ + tar xfzv $(MYSQL_FDW_DIR)/$(MYSQL_FDW_PACKAGE).tar.gz -C $(MYSQL_FDW_MEGRED_SOURCES_DIR) &> /dev/null; \ + for ((i=1;i<=99;i++)); \ + do \ + file_name="$(MYSQL_FDW_DIR)/$$i-mysql_fdw-2.5.3_patch.patch"; \ + if [ ! -f "$$file_name" ]; then \ + exit 0; \ + fi; \ + patch -p0 -d $(MYSQL_FDW_MEGRED_SOURCES_DIR)/$(MYSQL_FDW_PACKAGE) < $$file_name &> /dev/null; \ + done + rename ".c" ".cpp" $(MYSQL_FDW_MEGRED_SOURCES_DIR)/$(MYSQL_FDW_PACKAGE)/*.c; \ + patch -p0 -d $(MYSQL_FDW_MEGRED_SOURCES_DIR)/$(MYSQL_FDW_PACKAGE) < $(MYSQL_FDW_DIR)/$(MYSQL_FDW_PATCH).patch &> /dev/null; +endef diff --git a/contrib/oracle_fdw/Makefile b/contrib/oracle_fdw/Makefile index 2dad602fd..31f4565c1 100644 --- a/contrib/oracle_fdw/Makefile +++ b/contrib/oracle_fdw/Makefile @@ -1,59 +1,59 @@ -# -# 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. -# --------------------------------------------------------------------------------------- -# -# Makefile -# Makefile for the oracle_fdw -# -# IDENTIFICATION -# contrib/oracle_fdw/Makefile -# -# --------------------------------------------------------------------------------------- - -all:oracle_fdw_target -install:install-data - -top_builddir ?= ../../ -ORACLE_FDW_DIR=$(top_builddir)/third_party/dependency/oracle_fdw -ORACLE_FDW_PACKAGE=oracle_fdw-ORACLE_FDW_2_2_0 -ORACLE_FDW_PATCH=huawei_oracle_fdw-2.2.0_patch -ORACLE_FDW_MEGRED_SOURCES_DIR=$(ORACLE_FDW_DIR)/code - -.PHONY: oracle_fdw_target -oracle_fdw_target: - @$(call create_oracle_fdw_sources) - @make -C $(ORACLE_FDW_MEGRED_SOURCES_DIR)/$(ORACLE_FDW_PACKAGE) NO_PGXS=1 - -.PHONY: install-data -install-data: oracle_fdw_target - @make -C $(ORACLE_FDW_MEGRED_SOURCES_DIR)/$(ORACLE_FDW_PACKAGE) NO_PGXS=1 install - -uninstall distclean clean: - @rm -rf $(ORACLE_FDW_MEGRED_SOURCES_DIR) - -define create_oracle_fdw_sources - rm -rf $(ORACLE_FDW_MEGRED_SOURCES_DIR); \ - mkdir $(ORACLE_FDW_MEGRED_SOURCES_DIR); \ - tar xfzv $(ORACLE_FDW_DIR)/$(ORACLE_FDW_PACKAGE).tar.gz -C $(ORACLE_FDW_MEGRED_SOURCES_DIR) &> /dev/null; \ - for ((i=1;i<=99;i++)); \ - do \ - file_name="$(ORACLE_FDW_DIR)/$$i-oracle_fdw-2.2.0_patch.patch"; \ - if [ ! -f "$$file_name" ]; then \ - exit 0; \ - fi; \ - patch -p0 -d $(ORACLE_FDW_MEGRED_SOURCES_DIR)/$(ORACLE_FDW_PACKAGE) < $$file_name &> /dev/null; \ - done - rename ".c" ".cpp" $(ORACLE_FDW_MEGRED_SOURCES_DIR)/$(ORACLE_FDW_PACKAGE)/*.c; \ - patch -p0 -d $(ORACLE_FDW_MEGRED_SOURCES_DIR)/$(ORACLE_FDW_PACKAGE) < $(ORACLE_FDW_DIR)/$(ORACLE_FDW_PATCH).patch &> /dev/null; -endef +# +# 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. +# --------------------------------------------------------------------------------------- +# +# Makefile +# Makefile for the oracle_fdw +# +# IDENTIFICATION +# contrib/oracle_fdw/Makefile +# +# --------------------------------------------------------------------------------------- + +all:oracle_fdw_target +install:install-data + +top_builddir ?= ../../ +ORACLE_FDW_DIR=$(top_builddir)/third_party/dependency/oracle_fdw +ORACLE_FDW_PACKAGE=oracle_fdw-ORACLE_FDW_2_2_0 +ORACLE_FDW_PATCH=openGauss_oracle_fdw-2.2.0_patch +ORACLE_FDW_MEGRED_SOURCES_DIR=$(ORACLE_FDW_DIR)/code + +.PHONY: oracle_fdw_target +oracle_fdw_target: + @$(call create_oracle_fdw_sources) + @make -C $(ORACLE_FDW_MEGRED_SOURCES_DIR)/$(ORACLE_FDW_PACKAGE) NO_PGXS=1 + +.PHONY: install-data +install-data: oracle_fdw_target + @make -C $(ORACLE_FDW_MEGRED_SOURCES_DIR)/$(ORACLE_FDW_PACKAGE) NO_PGXS=1 install + +uninstall distclean clean: + @rm -rf $(ORACLE_FDW_MEGRED_SOURCES_DIR) + +define create_oracle_fdw_sources + rm -rf $(ORACLE_FDW_MEGRED_SOURCES_DIR); \ + mkdir $(ORACLE_FDW_MEGRED_SOURCES_DIR); \ + tar xfzv $(ORACLE_FDW_DIR)/$(ORACLE_FDW_PACKAGE).tar.gz -C $(ORACLE_FDW_MEGRED_SOURCES_DIR) &> /dev/null; \ + for ((i=1;i<=99;i++)); \ + do \ + file_name="$(ORACLE_FDW_DIR)/$$i-oracle_fdw-2.2.0_patch.patch"; \ + if [ ! -f "$$file_name" ]; then \ + exit 0; \ + fi; \ + patch -p0 -d $(ORACLE_FDW_MEGRED_SOURCES_DIR)/$(ORACLE_FDW_PACKAGE) < $$file_name &> /dev/null; \ + done + rename ".c" ".cpp" $(ORACLE_FDW_MEGRED_SOURCES_DIR)/$(ORACLE_FDW_PACKAGE)/*.c; \ + patch -p0 -d $(ORACLE_FDW_MEGRED_SOURCES_DIR)/$(ORACLE_FDW_PACKAGE) < $(ORACLE_FDW_DIR)/$(ORACLE_FDW_PATCH).patch &> /dev/null; +endef diff --git a/src/bin/gs_guc/cluster_guc.conf b/src/bin/gs_guc/cluster_guc.conf index 4b4d8bc4c..4c8ce0845 100644 --- a/src/bin/gs_guc/cluster_guc.conf +++ b/src/bin/gs_guc/cluster_guc.conf @@ -528,7 +528,6 @@ max_inner_tool_connections|int|1,8388607|NULL|NULL| max_keep_log_seg|int|0,2147483647|NULL|NULL| max_background_workers|int|0,262143|NULL|NULL| min_parallel_table_scan_size|int|0,715827882|kB|NULL| -max_parallel_workers|int|0,1024|NULL|NULL| max_parallel_workers_per_gather|int|0,1024|NULL|NULL| parallel_tuple_cost|real|0,1.79769e+308|NULL|NULL| parallel_setup_cost|real|0,1.79769e+308|NULL|NULL| diff --git a/src/common/backend/utils/misc/guc.cpp b/src/common/backend/utils/misc/guc.cpp index 704dcfe5f..2f243d6fe 100644 --- a/src/common/backend/utils/misc/guc.cpp +++ b/src/common/backend/utils/misc/guc.cpp @@ -9229,22 +9229,6 @@ static void init_configure_names_int() NULL, NULL }, - { - { - "max_parallel_workers", - PGC_USERSET, - RESOURCES_ASYNCHRONOUS, - gettext_noop("Sets the maximum number of parallel workers that can be active at one time."), - NULL - }, - &g_instance.attr.attr_common.max_parallel_workers, - 8, - 0, - MAX_PARALLEL_WORKER_LIMIT, - NULL, - NULL, - NULL - }, { { "max_parallel_workers_per_gather", @@ -9253,7 +9237,7 @@ static void init_configure_names_int() gettext_noop("Sets the maximum number of parallel processes per executor node."), NULL }, - &g_instance.attr.attr_common.max_parallel_workers_per_gather, + &u_sess->attr.attr_sql.max_parallel_workers_per_gather, 2, 0, MAX_PARALLEL_WORKER_LIMIT, diff --git a/src/common/backend/utils/time/tqual.cpp b/src/common/backend/utils/time/tqual.cpp index 0457263eb..cff0ee255 100755 --- a/src/common/backend/utils/time/tqual.cpp +++ b/src/common/backend/utils/time/tqual.cpp @@ -898,17 +898,19 @@ bool HeapTupleSatisfiesMVCC(HeapTuple htup, Snapshot snapshot, Buffer buffer) TransactionIdStatus hintstatus; Page page = BufferGetPage(buffer); - ereport(DEBUG1, - (errmsg("HeapTupleSatisfiesMVCC self(%u,%u) ctid(%u,%u) cur_xid " XID_FMT " xmin " XID_FMT - " xmax " XID_FMT " csn " CSN_FMT, - ItemPointerGetBlockNumber(&htup->t_self), - ItemPointerGetOffsetNumber(&htup->t_self), - ItemPointerGetBlockNumber(&tuple->t_ctid), - ItemPointerGetOffsetNumber(&tuple->t_ctid), - GetCurrentTransactionIdIfAny(), - HeapTupleHeaderGetXmin(page, tuple), - HeapTupleHeaderGetXmax(page, tuple), - snapshot->snapshotcsn))); + if (SHOW_DEBUG_MESSAGE()) { + ereport(DEBUG1, + (errmsg("HeapTupleSatisfiesMVCC self(%u,%u) ctid(%u,%u) cur_xid " XID_FMT " xmin " XID_FMT + " xmax " XID_FMT " csn " CSN_FMT, + ItemPointerGetBlockNumber(&htup->t_self), + ItemPointerGetOffsetNumber(&htup->t_self), + ItemPointerGetBlockNumber(&tuple->t_ctid), + ItemPointerGetOffsetNumber(&tuple->t_ctid), + GetCurrentTransactionIdIfAny(), + HeapTupleHeaderGetXmin(page, tuple), + HeapTupleHeaderGetXmax(page, tuple), + snapshot->snapshotcsn))); + } /* * Just valid for read-only transaction when u_sess->attr.attr_common.XactReadOnly is true. diff --git a/src/gausskernel/optimizer/path/allpaths.cpp b/src/gausskernel/optimizer/path/allpaths.cpp index de14a87c0..5195fbc4f 100755 --- a/src/gausskernel/optimizer/path/allpaths.cpp +++ b/src/gausskernel/optimizer/path/allpaths.cpp @@ -899,7 +899,7 @@ static void set_plain_rel_pathlist(PlannerInfo* root, RelOptInfo* rel, RangeTblE * sophisticated, but we need something here for now. */ while (rel->pages > parallel_threshold * 3 && - parallel_degree < g_instance.attr.attr_common.max_parallel_workers_per_gather) { + parallel_degree < u_sess->attr.attr_sql.max_parallel_workers_per_gather) { parallel_degree++; parallel_threshold *= 3; if (parallel_threshold >= PG_INT32_MAX / 3) diff --git a/src/gausskernel/optimizer/plan/planner.cpp b/src/gausskernel/optimizer/plan/planner.cpp index 7e7718d96..510325fd2 100644 --- a/src/gausskernel/optimizer/plan/planner.cpp +++ b/src/gausskernel/optimizer/plan/planner.cpp @@ -462,7 +462,7 @@ PlannedStmt* standard_planner(Query* parse, int cursorOptions, ParamListInfo bou */ glob->parallelModeOK = (cursorOptions & CURSOR_OPT_PARALLEL_OK) != 0 && IsUnderPostmaster && parse->commandType == CMD_SELECT && !parse->hasModifyingCTE && parse->utilityStmt == NULL && - g_instance.attr.attr_common.max_parallel_workers_per_gather > 0 && !IsParallelWorker() && + u_sess->attr.attr_sql.max_parallel_workers_per_gather > 0 && !IsParallelWorker() && !IsolationIsSerializable() && !has_parallel_hazard((Node *)parse, true); /* diff --git a/src/gausskernel/process/postmaster/bgworker.cpp b/src/gausskernel/process/postmaster/bgworker.cpp index 4f3d64eb7..830112144 100644 --- a/src/gausskernel/process/postmaster/bgworker.cpp +++ b/src/gausskernel/process/postmaster/bgworker.cpp @@ -683,7 +683,7 @@ void StartBackgroundWorker(void* bgWorkerSlotShmAddr) BackgroundWorker *worker = t_thrd.bgworker_cxt.my_bgworker_entry; bgworker_main_type entrypt; - t_thrd.proc_cxt.MyProgName = "BackgroundWorker"; + knl_thread_set_name("BgWorker"); /* * Create memory context and buffer used for RowDescription messages. As * SendRowDescriptionMessage(), via exec_describe_statement_message(), is diff --git a/src/gausskernel/runtime/executor/execParallel.cpp b/src/gausskernel/runtime/executor/execParallel.cpp index 4e0289a26..65956a57d 100644 --- a/src/gausskernel/runtime/executor/execParallel.cpp +++ b/src/gausskernel/runtime/executor/execParallel.cpp @@ -1,589 +1,582 @@ -/* ------------------------------------------------------------------------- - * - * execParallel.c - * Support routines for parallel execution. - * - * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group - * Portions Copyright (c) 1994, Regents of the University of California - * - * This file contains routines that are intended to support setting up, - * using, and tearing down a ParallelContext from within the PostgreSQL - * executor. The ParallelContext machinery will handle starting the - * workers and ensuring that their state generally matches that of the - * leader; see src/backend/access/transam/README.parallel for details. - * However, we must save and restore relevant executor state, such as - * any ParamListInfo associated with the query, buffer usage info, and - * the actual plan to be passed down to the worker. - * - * IDENTIFICATION - * src/backend/executor/execParallel.c - * - * ------------------------------------------------------------------------- - */ - -#include "postgres.h" - -#include "executor/execParallel.h" -#include "executor/executor.h" -#include "executor/nodeSeqscan.h" -#include "executor/tqueue.h" -#include "nodes/nodeFuncs.h" -#include "optimizer/planmain.h" -#include "optimizer/planner.h" -#include "storage/spin.h" -#include "tcop/tcopprot.h" -#include "utils/memutils.h" -#include "utils/snapmgr.h" - -#define PARALLEL_TUPLE_QUEUE_SIZE 65536 - -/* DSM structure for accumulating per-PlanState instrumentation. */ -struct SharedExecutorInstrumentation { - int instrument_options; - uint32 instrument_offset; /* offset of first Instrumentation struct */ - int num_workers; /* # of workers */ - int num_plan_nodes; /* # of plan nodes */ - int plan_node_id[FLEXIBLE_ARRAY_MEMBER]; /* array of plan node IDs */ - /* array of num_plan_nodes * num_workers Instrumentation objects follows */ -}; -#define GetInstrumentationArray(sei) (AssertVariableIsOfTypeMacro(sei, SharedExecutorInstrumentation *), \ - (Instrumentation *)(((char *)sei) + sei->instrument_offset)) - - -/* Context object for ExecParallelEstimate. */ -typedef struct ExecParallelEstimateContext { - ParallelContext *pcxt; - int nnodes; -} ExecParallelEstimateContext; - -/* Context object for ExecParallelEstimate. */ -typedef struct ExecParallelInitializeDSMContext { - ParallelContext *pcxt; - SharedExecutorInstrumentation *instrumentation; - int nnodes; -} ExecParallelInitializeDSMContext; - -/* Helper functions that run in the parallel leader. */ -static char *ExecSerializePlan(Plan *plan, EState *estate); -static bool ExecParallelEstimate(PlanState *node, ExecParallelEstimateContext *e); -static bool ExecParallelInitializeDSM(PlanState *node, ExecParallelInitializeDSMContext *d); -static shm_mq_handle **ExecParallelSetupTupleQueues(ParallelContext *pcxt, bool reinitialize); -static bool ExecParallelRetrieveInstrumentation(PlanState *planstate, SharedExecutorInstrumentation *instrumentation); - -/* Helper functions that run in the parallel worker. */ -static DestReceiver *ExecParallelGetReceiver(void *seg); - -/* - * Create a serialized representation of the plan to be sent to each worker. - */ -static char *ExecSerializePlan(Plan *plan, EState *estate) -{ - ListCell *tlist = NULL; - - /* We can't scribble on the original plan, so make a copy. */ - plan = (Plan *)copyObject(plan); - - /* - * The worker will start its own copy of the executor, and that copy will - * insert a junk filter if the toplevel node has any resjunk entries. We - * don't want that to happen, because while resjunk columns shouldn't be - * sent back to the user, here the tuples are coming back to another - * backend which may very well need them. So mutate the target list - * accordingly. This is sort of a hack; there might be better ways to do - * this... - */ - foreach (tlist, plan->targetlist) { - TargetEntry *tle = (TargetEntry *)lfirst(tlist); - - tle->resjunk = false; - } - - /* - * Create a dummy PlannedStmt. Most of the fields don't need to be valid - * for our purposes, but the worker will need at least a minimal - * PlannedStmt to start the executor. - */ - PlannedStmt *pstmt = makeNode(PlannedStmt); - pstmt->commandType = CMD_SELECT; - pstmt->queryId = 0; - pstmt->hasReturning = 0; - pstmt->hasModifyingCTE = 0; - pstmt->canSetTag = 1; - pstmt->transientPlan = 0; - pstmt->planTree = plan; - pstmt->rtable = estate->es_range_table; - pstmt->resultRelations = NIL; - pstmt->utilityStmt = NULL; - pstmt->subplans = NIL; - pstmt->rewindPlanIDs = NULL; - pstmt->rowMarks = NIL; - pstmt->nParamExec = estate->es_plannedstmt->nParamExec; - pstmt->relationOids = NIL; - pstmt->invalItems = NIL; /* workers can't replan anyway... */ - pstmt->num_plannodes = estate->es_plannedstmt->num_plannodes; - - /* Return serialized copy of our dummy PlannedStmt. */ - return nodeToString(pstmt); -} - -/* - * Ordinary plan nodes won't do anything here, but parallel-aware plan nodes - * may need some state which is shared across all parallel workers. Before - * we size the DSM, give them a chance to call shm_toc_estimate_chunk or - * shm_toc_estimate_keys on &pcxt->estimator. - * - * While we're at it, count the number of PlanState nodes in the tree, so - * we know how many SharedPlanStateInstrumentation structures we need. - */ -static bool ExecParallelEstimate(PlanState *planstate, ExecParallelEstimateContext *e) -{ - if (planstate == NULL) - return false; - - /* Count this node. */ - e->nnodes++; - - /* Call estimators for parallel-aware nodes. */ - switch (nodeTag(planstate)) { - case T_SeqScanState: - ExecSeqScanEstimate((SeqScanState *)planstate, e->pcxt); - break; - default: - break; - } - - return planstate_tree_walker(planstate, (bool (*)())ExecParallelEstimate, e); -} - -/* - * Ordinary plan nodes won't do anything here, but parallel-aware plan nodes - * may need to initialize shared state in the DSM before parallel workers - * are available. They can allocate the space they previous estimated using - * shm_toc_allocate, and add the keys they previously estimated using - * shm_toc_insert, in each case targeting pcxt->toc. - */ -static bool ExecParallelInitializeDSM(PlanState *planstate, ExecParallelInitializeDSMContext *d) -{ - if (planstate == NULL) - return false; - - /* If instrumentation is enabled, initialize slot for this node. */ - if (d->instrumentation != NULL) { - d->instrumentation->plan_node_id[d->nnodes] = planstate->plan->plan_node_id; - } - - /* Count this node. */ - d->nnodes++; - knl_u_parallel_context *cxt = (knl_u_parallel_context *)d->pcxt->seg; - - /* Call initializers for parallel-aware plan nodes. */ - switch (nodeTag(planstate)) { - case T_SeqScanState: - ExecSeqScanInitializeDSM((SeqScanState *)planstate, d->pcxt, cxt->pwCtx->pscan_num); - cxt->pwCtx->pscan_num++; - break; - default: - break; - } - - return planstate_tree_walker(planstate, (bool (*)())ExecParallelInitializeDSM, d); -} - -/* - * It sets up the response queues for backend workers to return tuples - * to the main backend and start the workers. - */ -static shm_mq_handle **ExecParallelSetupTupleQueues(ParallelContext *pcxt, bool reinitialize) -{ - /* Skip this if no workers. */ - if (pcxt->nworkers <= 0) - return NULL; - - /* Allocate memory for shared memory queue handles. */ - shm_mq_handle **responseq = (shm_mq_handle **)palloc(pcxt->nworkers * sizeof(shm_mq_handle *)); - knl_u_parallel_context *cxt = (knl_u_parallel_context *)pcxt->seg; - /* - * If not reinitializing, allocate space from the DSM for the queues; - * otherwise, find the already allocated space. - */ - if (!reinitialize) { - cxt->pwCtx->tupleQueue = (char *)palloc0(PARALLEL_TUPLE_QUEUE_SIZE * (Size)pcxt->nworkers); - } - Assert(cxt->pwCtx->tupleQueue != NULL); - char *tqueuespace = cxt->pwCtx->tupleQueue; - - /* Create the queues, and become the receiver for each. */ - for (int i = 0; i < pcxt->nworkers; ++i) { - shm_mq *mq = shm_mq_create(tqueuespace + i * PARALLEL_TUPLE_QUEUE_SIZE, (Size)PARALLEL_TUPLE_QUEUE_SIZE); - shm_mq_set_receiver(mq, t_thrd.proc); - responseq[i] = shm_mq_attach(mq, pcxt->seg, NULL); - } - - /* Return array of handles. */ - return responseq; -} - -/* - * Re-initialize the parallel executor info such that it can be reused by - * workers. - */ -void ExecParallelReinitialize(ParallelExecutorInfo *pei) -{ - ReinitializeParallelDSM(pei->pcxt); - pei->tqueue = ExecParallelSetupTupleQueues(pei->pcxt, true); - pei->finished = false; -} - -/* - * Sets up the required infrastructure for backend workers to perform - * execution and return results to the main backend. - */ -ParallelExecutorInfo *ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers) -{ - ExecParallelEstimateContext e; - ExecParallelInitializeDSMContext d; - uint32 instrumentation_len = 0; - uint32 instrument_offset = 0; - - /* Allocate object for return value. */ - ParallelExecutorInfo *pei = (ParallelExecutorInfo *)palloc0(sizeof(ParallelExecutorInfo)); - pei->finished = false; - pei->planstate = planstate; - - /* Fix up and serialize plan to be sent to workers. */ - char *pstmt_data = ExecSerializePlan(planstate->plan, estate); - - /* Create a parallel context. */ - ParallelContext *pcxt = CreateParallelContext("postgres", "ParallelQueryMain", nworkers); - pei->pcxt = pcxt; - - /* Estimate space for serialized PlannedStmt. */ - Size pstmt_len = strlen(pstmt_data) + 1; - /* Estimate space for serialized ParamListInfo. */ - Size param_len = EstimateParamListSpace(estate->es_param_list_info); - - /* - * Give parallel-aware nodes a chance to add to the estimates, and get - * a count of how many PlanState nodes there are. - */ - e.pcxt = pcxt; - e.nnodes = 0; - (void)ExecParallelEstimate(planstate, &e); - - /* Estimate space for instrumentation, if required. */ - if (estate->es_instrument) { - instrumentation_len = offsetof(SharedExecutorInstrumentation, plan_node_id) + sizeof(int) * e.nnodes; - instrumentation_len = MAXALIGN(instrumentation_len); - instrument_offset = instrumentation_len; - instrumentation_len += sizeof(Instrumentation) * e.nnodes * nworkers; - } - - /* Everyone's had a chance to ask for space, so now create the DSM. */ - InitializeParallelDSM(pcxt); - knl_u_parallel_context *cxt = (knl_u_parallel_context *)pcxt->seg; - - /* - * OK, now we have a dynamic shared memory segment, and it should be big - * enough to store all of the data we estimated we would want to put into - * it, plus whatever general stuff (not specifically executor-related) the - * ParallelContext itself needs to store there. None of the space we - * asked for has been allocated or initialized yet, though, so do that. - */ - MemoryContext oldcontext = MemoryContextSwitchTo(cxt->memCtx); - - /* Store serialized PlannedStmt. */ - cxt->pwCtx->pstmt_space = (char *)palloc0(pstmt_len); - int rc = memcpy_s(cxt->pwCtx->pstmt_space, pstmt_len, pstmt_data, pstmt_len); - securec_check(rc, "", ""); - - /* Store serialized ParamListInfo. */ - cxt->pwCtx->param_space = (char *)palloc0(param_len); - cxt->pwCtx->param_len = param_len; - SerializeParamList(estate->es_param_list_info, cxt->pwCtx->param_space, param_len); - - /* Allocate space for each worker's BufferUsage; no need to initialize. */ - cxt->pwCtx->bufUsage = (BufferUsage *)palloc0(sizeof(BufferUsage) * pcxt->nworkers); - pei->buffer_usage = cxt->pwCtx->bufUsage; - - /* Set up tuple queues. */ - pei->tqueue = ExecParallelSetupTupleQueues(pcxt, false); - - /* - * If instrumentation options were supplied, allocate space for the - * data. It only gets partially initialized here; the rest happens - * during ExecParallelInitializeDSM. - */ - if (estate->es_instrument) { - cxt->pwCtx->instrumentation = (SharedExecutorInstrumentation *)palloc0(instrumentation_len); - cxt->pwCtx->instrumentation->instrument_options = estate->es_instrument; - cxt->pwCtx->instrumentation->instrument_offset = instrument_offset; - cxt->pwCtx->instrumentation->num_workers = nworkers; - cxt->pwCtx->instrumentation->num_plan_nodes = e.nnodes; - Instrumentation *instrument = GetInstrumentationArray(cxt->pwCtx->instrumentation); - for (int i = 0; i < nworkers * e.nnodes; ++i) { - InstrInit(&instrument[i], estate->es_instrument); - } - pei->instrumentation = cxt->pwCtx->instrumentation; - } - - cxt->pwCtx->pscan = (ParallelHeapScanDesc *)palloc0(sizeof(ParallelHeapScanDesc) * e.nnodes); - - /* - * Give parallel-aware nodes a chance to initialize their shared data. - * This also initializes the elements of instrumentation->ps_instrument, - * if it exists. - */ - d.pcxt = pcxt; - d.instrumentation = cxt->pwCtx->instrumentation; - d.nnodes = 0; - - /* Here we switch to old context, cause heap_beginscan_parallel need malloc memory */ - (void)MemoryContextSwitchTo(oldcontext); - (void)ExecParallelInitializeDSM(planstate, &d); - - /* - * Make sure that the world hasn't shifted under our feat. This could - * probably just be an Assert(), but let's be conservative for now. - */ - if (e.nnodes != d.nnodes) { - ereport(ERROR, (errmsg("inconsistent count of PlanState nodes"))); - } - - /* OK, we're ready to rock and roll. */ - return pei; -} - -/* - * Copy instrumentation information about this node and its descendents from - * dynamic shared memory. - */ -static bool ExecParallelRetrieveInstrumentation(PlanState *planstate, SharedExecutorInstrumentation *instrumentation) -{ - int i; - int plan_node_id = planstate->plan->plan_node_id; - - /* Find the instumentation for this node. */ - for (i = 0; i < instrumentation->num_plan_nodes; ++i) { - if (instrumentation->plan_node_id[i] == plan_node_id) { - break; - } - } - if (i >= instrumentation->num_plan_nodes) { - ereport(ERROR, (errmsg("plan node %d not found", plan_node_id))); - } - - /* Accumulate the statistics from all workers. */ - Instrumentation *instrument = GetInstrumentationArray(instrumentation); - instrument += i * instrumentation->num_workers; - for (i = 0; i < instrumentation->num_workers; ++i) { - InstrAggNode(planstate->instrument, &instrument[i]); - } - - /* Also store the per-worker detail. */ - Size ibytes = instrumentation->num_workers * sizeof(Instrumentation); - planstate->worker_instrument = - (WorkerInstrumentation *)palloc(offsetof(WorkerInstrumentation, instrument) + ibytes); - planstate->worker_instrument->num_workers = instrumentation->num_workers; - int rc = memcpy_s(&planstate->worker_instrument->instrument, ibytes, instrument, ibytes); - securec_check(rc, "", ""); - - return planstate_tree_walker(planstate, (bool (*)())ExecParallelRetrieveInstrumentation, instrumentation); -} - - -/* - * Finish parallel execution. We wait for parallel workers to finish, and - * accumulate their buffer usage and instrumentation. - */ -void ExecParallelFinish(ParallelExecutorInfo *pei) -{ - if (pei->finished) - return; - - /* First, wait for the workers to finish. */ - WaitForParallelWorkersToFinish(pei->pcxt); - - /* Next, accumulate buffer usage. */ - for (int i = 0; i < pei->pcxt->nworkers; ++i) - InstrAccumParallelQuery(&pei->buffer_usage[i]); - - /* Finally, accumulate instrumentation, if any. */ - if (pei->instrumentation) { - (void)ExecParallelRetrieveInstrumentation(pei->planstate, pei->instrumentation); - } - - pei->finished = true; -} - -/* - * Clean up whatever ParallelExecutreInfo resources still exist after - * ExecParallelFinish. We separate these routines because someone might - * want to examine the contents of the DSM after ExecParallelFinish and - * before calling this routine. - */ -void ExecParallelCleanup(ParallelExecutorInfo *pei) -{ - if (pei->pcxt != NULL) { - DestroyParallelContext(pei->pcxt); - pei->pcxt = NULL; - } - pfree(pei); -} - -/* - * Create a DestReceiver to write tuples we produce to the shm_mq designated - * for that purpose. - */ -static DestReceiver *ExecParallelGetReceiver(void *seg) -{ - Assert(seg != NULL); - knl_u_parallel_context *cxt = (knl_u_parallel_context *)seg; - - char *mqspace = cxt->pwCtx->tupleQueue; - mqspace += t_thrd.bgworker_cxt.ParallelWorkerNumber * PARALLEL_TUPLE_QUEUE_SIZE; - shm_mq *mq = (shm_mq *)mqspace; - shm_mq_set_sender(mq, t_thrd.proc); - return CreateTupleQueueDestReceiver(shm_mq_attach(mq, seg, NULL)); -} - -/* - * Create a QueryDesc for the PlannedStmt we are to execute, and return it. - */ -static QueryDesc *ExecParallelGetQueryDesc(void *seg, DestReceiver *receiver, int instrument_options) -{ - knl_u_parallel_context *cxt = (knl_u_parallel_context *)seg; - - /* Reconstruct leader-supplied PlannedStmt. */ - PlannedStmt *pstmt = (PlannedStmt *)stringToNode(cxt->pwCtx->pstmt_space); - - /* Reconstruct ParamListInfo. */ - ParamListInfo paramLI = RestoreParamList(cxt->pwCtx->param_space, cxt->pwCtx->param_len); - - /* - * Create a QueryDesc for the query. - * - * It's not obvious how to obtain the query string from here; and even if - * we could copying it would take more cycles than not copying it. But - * it's a bit unsatisfying to just use a dummy string here, so consider - * revising this someday. - */ - return CreateQueryDesc(pstmt, "", GetActiveSnapshot(), InvalidSnapshot, receiver, paramLI, - instrument_options); -} - -/* - * Copy instrumentation information from this node and its descendents into - * dynamic shared memory, so that the parallel leader can retrieve it. - */ -static bool ExecParallelReportInstrumentation(PlanState *planstate, SharedExecutorInstrumentation *instrumentation) -{ - int i; - int plan_node_id = planstate->plan->plan_node_id; - - InstrEndLoop(planstate->instrument); - - /* - * If we shuffled the plan_node_id values in ps_instrument into sorted - * order, we could use binary search here. This might matter someday - * if we're pushing down sufficiently large plan trees. For now, do it - * the slow, dumb way. - */ - for (i = 0; i < instrumentation->num_plan_nodes; ++i) { - if (instrumentation->plan_node_id[i] == plan_node_id) { - break; - } - } - if (i >= instrumentation->num_plan_nodes) { - ereport(ERROR, (errmsg("plan node %d not found", plan_node_id))); - } - - /* - * Add our statistics to the per-node, per-worker totals. It's possible - * that this could happen more than once if we relaunched workers. - */ - Instrumentation *instrument = GetInstrumentationArray(instrumentation); - instrument += i * instrumentation->num_workers; - Assert(IsParallelWorker()); - Assert(t_thrd.bgworker_cxt.ParallelWorkerNumber < instrumentation->num_workers); - InstrAggNode(&instrument[t_thrd.bgworker_cxt.ParallelWorkerNumber], planstate->instrument); - - return planstate_tree_walker(planstate, (bool (*)())ExecParallelReportInstrumentation, instrumentation); -} - -/* - * Initialize the PlanState and its descendents with the information - * retrieved from shared memory. This has to be done once the PlanState - * is allocated and initialized by executor; that is, after ExecutorStart(). - */ -static bool ExecParallelInitializeWorker(PlanState *planstate, void *context) -{ - if (planstate == NULL) - return false; - - /* Call initializers for parallel-aware plan nodes. */ - if (planstate->plan->parallel_aware) { - switch (nodeTag(planstate)) { - case T_SeqScanState: - ExecSeqScanInitializeWorker((SeqScanState *)planstate, context); - break; - default: - break; - } - } - - return planstate_tree_walker(planstate, (bool (*)())ExecParallelInitializeWorker, context); -} - -/* - * Main entrypoint for parallel query worker processes. - * - * We reach this function from ParallelMain, so the setup necessary to create - * a sensible parallel environment has already been done; ParallelMain worries - * about stuff like the transaction state, combo CID mappings, and GUC values, - * so we don't need to deal with any of that here. - * - * Our job is to deal with concerns specific to the executor. The parallel - * group leader will have stored a serialized PlannedStmt, and it's our job - * to execute that plan and write the resulting tuples to the appropriate - * tuple queue. Various bits of supporting information that we need in order - * to do this are also stored in the dsm_segment and can be accessed through - * the shm_toc. - */ -void ParallelQueryMain(void *seg) -{ - int instrument_options = 0; - - /* Set up DestReceiver, SharedExecutorInstrumentation, and QueryDesc. */ - knl_u_parallel_context *cxt = (knl_u_parallel_context *)seg; - DestReceiver *receiver = ExecParallelGetReceiver(seg); - SharedExecutorInstrumentation *instrumentation = cxt->pwCtx->instrumentation; - if (instrumentation != NULL) - instrument_options = instrumentation->instrument_options; - QueryDesc *queryDesc = ExecParallelGetQueryDesc(seg, receiver, instrument_options); - - /* Prepare to track buffer usage during query execution. */ - InstrStartParallelQuery(); - - /* Start up the executor, have it run the plan, and then shut it down. */ - (void)ExecutorStart(queryDesc, 0); - ExecParallelInitializeWorker(queryDesc->planstate, seg); - ExecutorRun(queryDesc, ForwardScanDirection, 0L); - ExecutorFinish(queryDesc); - - /* Report buffer usage during parallel execution. */ - BufferUsage *buffer_usage = cxt->pwCtx->bufUsage; - InstrEndParallelQuery(&buffer_usage[t_thrd.bgworker_cxt.ParallelWorkerNumber]); - - /* Report instrumentation data if any instrumentation options are set. */ - if (instrumentation != NULL) { - (void)ExecParallelReportInstrumentation(queryDesc->planstate, instrumentation); - } - - /* Must do this after capturing instrumentation. */ - ExecutorEnd(queryDesc); - - /* Cleanup. */ - FreeQueryDesc(queryDesc); - (*receiver->rDestroy)(receiver); -} - +/* ------------------------------------------------------------------------- + * + * execParallel.c + * Support routines for parallel execution. + * + * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * This file contains routines that are intended to support setting up, + * using, and tearing down a ParallelContext from within the PostgreSQL + * executor. The ParallelContext machinery will handle starting the + * workers and ensuring that their state generally matches that of the + * leader; see src/backend/access/transam/README.parallel for details. + * However, we must save and restore relevant executor state, such as + * any ParamListInfo associated with the query, buffer usage info, and + * the actual plan to be passed down to the worker. + * + * IDENTIFICATION + * src/backend/executor/execParallel.c + * + * ------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "executor/execParallel.h" +#include "executor/executor.h" +#include "executor/nodeSeqscan.h" +#include "executor/tqueue.h" +#include "nodes/nodeFuncs.h" +#include "optimizer/planmain.h" +#include "optimizer/planner.h" +#include "storage/spin.h" +#include "tcop/tcopprot.h" +#include "utils/memutils.h" +#include "utils/snapmgr.h" + +#define PARALLEL_TUPLE_QUEUE_SIZE 65536 + +/* DSM structure for accumulating per-PlanState instrumentation. */ +struct SharedExecutorInstrumentation { + int instrument_options; + uint32 instrument_offset; /* offset of first Instrumentation struct */ + int num_workers; /* # of workers */ + int num_plan_nodes; /* # of plan nodes */ + int plan_node_id[FLEXIBLE_ARRAY_MEMBER]; /* array of plan node IDs */ + /* array of num_plan_nodes * num_workers Instrumentation objects follows */ +}; +#define GetInstrumentationArray(sei) (AssertVariableIsOfTypeMacro(sei, SharedExecutorInstrumentation *), \ + (Instrumentation *)(((char *)sei) + sei->instrument_offset)) + + +/* Context object for ExecParallelEstimate. */ +typedef struct ExecParallelEstimateContext { + ParallelContext *pcxt; + int nnodes; +} ExecParallelEstimateContext; + +/* Context object for ExecParallelEstimate. */ +typedef struct ExecParallelInitializeDSMContext { + ParallelContext *pcxt; + SharedExecutorInstrumentation *instrumentation; + int nnodes; +} ExecParallelInitializeDSMContext; + +/* Helper functions that run in the parallel leader. */ +static char *ExecSerializePlan(Plan *plan, EState *estate); +static bool ExecParallelEstimate(PlanState *node, ExecParallelEstimateContext *e); +static bool ExecParallelInitializeDSM(PlanState *node, ExecParallelInitializeDSMContext *d); +static shm_mq_handle **ExecParallelSetupTupleQueues(ParallelContext *pcxt, bool reinitialize); +static bool ExecParallelRetrieveInstrumentation(PlanState *planstate, SharedExecutorInstrumentation *instrumentation); + +/* Helper functions that run in the parallel worker. */ +static DestReceiver *ExecParallelGetReceiver(void *seg); + +/* + * Create a serialized representation of the plan to be sent to each worker. + */ +static char *ExecSerializePlan(Plan *plan, EState *estate) +{ + ListCell *tlist = NULL; + + /* We can't scribble on the original plan, so make a copy. */ + plan = (Plan *)copyObject(plan); + + /* + * The worker will start its own copy of the executor, and that copy will + * insert a junk filter if the toplevel node has any resjunk entries. We + * don't want that to happen, because while resjunk columns shouldn't be + * sent back to the user, here the tuples are coming back to another + * backend which may very well need them. So mutate the target list + * accordingly. This is sort of a hack; there might be better ways to do + * this... + */ + foreach (tlist, plan->targetlist) { + TargetEntry *tle = (TargetEntry *)lfirst(tlist); + + tle->resjunk = false; + } + + /* + * Create a dummy PlannedStmt. Most of the fields don't need to be valid + * for our purposes, but the worker will need at least a minimal + * PlannedStmt to start the executor. + */ + PlannedStmt *pstmt = makeNode(PlannedStmt); + pstmt->commandType = CMD_SELECT; + pstmt->queryId = 0; + pstmt->hasReturning = 0; + pstmt->hasModifyingCTE = 0; + pstmt->canSetTag = 1; + pstmt->transientPlan = 0; + pstmt->planTree = plan; + pstmt->rtable = estate->es_range_table; + pstmt->resultRelations = NIL; + pstmt->utilityStmt = NULL; + pstmt->subplans = NIL; + pstmt->rewindPlanIDs = NULL; + pstmt->rowMarks = NIL; + pstmt->nParamExec = estate->es_plannedstmt->nParamExec; + pstmt->relationOids = NIL; + pstmt->invalItems = NIL; /* workers can't replan anyway... */ + pstmt->num_plannodes = estate->es_plannedstmt->num_plannodes; + + /* Return serialized copy of our dummy PlannedStmt. */ + return nodeToString(pstmt); +} + +/* + * Ordinary plan nodes won't do anything here, but parallel-aware plan nodes + * may need some state which is shared across all parallel workers. Before + * we size the DSM, give them a chance to call shm_toc_estimate_chunk or + * shm_toc_estimate_keys on &pcxt->estimator. + * + * While we're at it, count the number of PlanState nodes in the tree, so + * we know how many SharedPlanStateInstrumentation structures we need. + */ +static bool ExecParallelEstimate(PlanState *planstate, ExecParallelEstimateContext *e) +{ + if (planstate == NULL) + return false; + + /* Count this node. */ + e->nnodes++; + + /* Call estimators for parallel-aware nodes. */ + switch (nodeTag(planstate)) { + case T_SeqScanState: + ExecSeqScanEstimate((SeqScanState *)planstate, e->pcxt); + break; + default: + break; + } + + return planstate_tree_walker(planstate, (bool (*)())ExecParallelEstimate, e); +} + +/* + * Ordinary plan nodes won't do anything here, but parallel-aware plan nodes + * may need to initialize shared state in the DSM before parallel workers + * are available. They can allocate the space they previous estimated using + * shm_toc_allocate, and add the keys they previously estimated using + * shm_toc_insert, in each case targeting pcxt->toc. + */ +static bool ExecParallelInitializeDSM(PlanState *planstate, ExecParallelInitializeDSMContext *d) +{ + if (planstate == NULL) + return false; + + /* If instrumentation is enabled, initialize slot for this node. */ + if (d->instrumentation != NULL) { + d->instrumentation->plan_node_id[d->nnodes] = planstate->plan->plan_node_id; + } + + /* Count this node. */ + d->nnodes++; + knl_u_parallel_context *cxt = (knl_u_parallel_context *)d->pcxt->seg; + + /* Call initializers for parallel-aware plan nodes. */ + switch (nodeTag(planstate)) { + case T_SeqScanState: + ExecSeqScanInitializeDSM((SeqScanState *)planstate, d->pcxt, cxt->pwCtx->pscan_num); + cxt->pwCtx->pscan_num++; + break; + default: + break; + } + + return planstate_tree_walker(planstate, (bool (*)())ExecParallelInitializeDSM, d); +} + +/* + * It sets up the response queues for backend workers to return tuples + * to the main backend and start the workers. + */ +static shm_mq_handle **ExecParallelSetupTupleQueues(ParallelContext *pcxt, bool reinitialize) +{ + /* Skip this if no workers. */ + if (pcxt->nworkers <= 0) + return NULL; + + /* Allocate memory for shared memory queue handles. */ + shm_mq_handle **responseq = (shm_mq_handle **)palloc(pcxt->nworkers * sizeof(shm_mq_handle *)); + knl_u_parallel_context *cxt = (knl_u_parallel_context *)pcxt->seg; + /* + * If not reinitializing, allocate space from the DSM for the queues; + * otherwise, find the already allocated space. + */ + if (!reinitialize) { + cxt->pwCtx->tupleQueue = (char *)palloc0(PARALLEL_TUPLE_QUEUE_SIZE * (Size)pcxt->nworkers); + } + Assert(cxt->pwCtx->tupleQueue != NULL); + char *tqueuespace = cxt->pwCtx->tupleQueue; + + /* Create the queues, and become the receiver for each. */ + for (int i = 0; i < pcxt->nworkers; ++i) { + shm_mq *mq = shm_mq_create(tqueuespace + i * PARALLEL_TUPLE_QUEUE_SIZE, (Size)PARALLEL_TUPLE_QUEUE_SIZE); + shm_mq_set_receiver(mq, t_thrd.proc); + responseq[i] = shm_mq_attach(mq, pcxt->seg, NULL); + } + + /* Return array of handles. */ + return responseq; +} + +/* + * Re-initialize the parallel executor info such that it can be reused by + * workers. + */ +void ExecParallelReinitialize(ParallelExecutorInfo *pei) +{ + ReinitializeParallelDSM(pei->pcxt); + pei->tqueue = ExecParallelSetupTupleQueues(pei->pcxt, true); + pei->finished = false; +} + +/* + * Sets up the required infrastructure for backend workers to perform + * execution and return results to the main backend. + */ +ParallelExecutorInfo *ExecInitParallelPlan(PlanState *planstate, EState *estate, int nworkers) +{ + ExecParallelEstimateContext e; + ExecParallelInitializeDSMContext d; + uint32 instrumentation_len = 0; + uint32 instrument_offset = 0; + + /* Allocate object for return value. */ + ParallelExecutorInfo *pei = (ParallelExecutorInfo *)palloc0(sizeof(ParallelExecutorInfo)); + pei->finished = false; + pei->planstate = planstate; + + /* Create a parallel context. */ + ParallelContext *pcxt = CreateParallelContext("postgres", "ParallelQueryMain", nworkers); + pei->pcxt = pcxt; + + /* Estimate space for serialized ParamListInfo. */ + Size param_len = EstimateParamListSpace(estate->es_param_list_info); + + /* + * Give parallel-aware nodes a chance to add to the estimates, and get + * a count of how many PlanState nodes there are. + */ + e.pcxt = pcxt; + e.nnodes = 0; + (void)ExecParallelEstimate(planstate, &e); + + /* Estimate space for instrumentation, if required. */ + if (estate->es_instrument) { + instrumentation_len = offsetof(SharedExecutorInstrumentation, plan_node_id) + sizeof(int) * e.nnodes; + instrumentation_len = MAXALIGN(instrumentation_len); + instrument_offset = instrumentation_len; + instrumentation_len += sizeof(Instrumentation) * e.nnodes * nworkers; + } + + /* Everyone's had a chance to ask for space, so now create the DSM. */ + InitializeParallelDSM(pcxt); + knl_u_parallel_context *cxt = (knl_u_parallel_context *)pcxt->seg; + + /* + * OK, now we have a dynamic shared memory segment, and it should be big + * enough to store all of the data we estimated we would want to put into + * it, plus whatever general stuff (not specifically executor-related) the + * ParallelContext itself needs to store there. None of the space we + * asked for has been allocated or initialized yet, though, so do that. + */ + MemoryContext oldcontext = MemoryContextSwitchTo(cxt->memCtx); + + /* Store serialized PlannedStmt. */ + cxt->pwCtx->pstmt_space = ExecSerializePlan(planstate->plan, estate); + + /* Store serialized ParamListInfo. */ + cxt->pwCtx->param_space = (char *)palloc0(param_len); + cxt->pwCtx->param_len = param_len; + SerializeParamList(estate->es_param_list_info, cxt->pwCtx->param_space, param_len); + + /* Allocate space for each worker's BufferUsage; no need to initialize. */ + cxt->pwCtx->bufUsage = (BufferUsage *)palloc0(sizeof(BufferUsage) * pcxt->nworkers); + pei->buffer_usage = cxt->pwCtx->bufUsage; + + /* Set up tuple queues. */ + pei->tqueue = ExecParallelSetupTupleQueues(pcxt, false); + + /* + * If instrumentation options were supplied, allocate space for the + * data. It only gets partially initialized here; the rest happens + * during ExecParallelInitializeDSM. + */ + if (estate->es_instrument) { + cxt->pwCtx->instrumentation = (SharedExecutorInstrumentation *)palloc0(instrumentation_len); + cxt->pwCtx->instrumentation->instrument_options = estate->es_instrument; + cxt->pwCtx->instrumentation->instrument_offset = instrument_offset; + cxt->pwCtx->instrumentation->num_workers = nworkers; + cxt->pwCtx->instrumentation->num_plan_nodes = e.nnodes; + Instrumentation *instrument = GetInstrumentationArray(cxt->pwCtx->instrumentation); + for (int i = 0; i < nworkers * e.nnodes; ++i) { + InstrInit(&instrument[i], estate->es_instrument); + } + pei->instrumentation = cxt->pwCtx->instrumentation; + } + + cxt->pwCtx->pscan = (ParallelHeapScanDesc *)palloc0(sizeof(ParallelHeapScanDesc) * e.nnodes); + + /* + * Give parallel-aware nodes a chance to initialize their shared data. + * This also initializes the elements of instrumentation->ps_instrument, + * if it exists. + */ + d.pcxt = pcxt; + d.instrumentation = cxt->pwCtx->instrumentation; + d.nnodes = 0; + + /* Here we switch to old context, cause heap_beginscan_parallel need malloc memory */ + (void)MemoryContextSwitchTo(oldcontext); + (void)ExecParallelInitializeDSM(planstate, &d); + + /* + * Make sure that the world hasn't shifted under our feat. This could + * probably just be an Assert(), but let's be conservative for now. + */ + if (e.nnodes != d.nnodes) { + ereport(ERROR, (errmsg("inconsistent count of PlanState nodes"))); + } + + /* OK, we're ready to rock and roll. */ + return pei; +} + +/* + * Copy instrumentation information about this node and its descendents from + * dynamic shared memory. + */ +static bool ExecParallelRetrieveInstrumentation(PlanState *planstate, SharedExecutorInstrumentation *instrumentation) +{ + int i; + int plan_node_id = planstate->plan->plan_node_id; + + /* Find the instumentation for this node. */ + for (i = 0; i < instrumentation->num_plan_nodes; ++i) { + if (instrumentation->plan_node_id[i] == plan_node_id) { + break; + } + } + if (i >= instrumentation->num_plan_nodes) { + ereport(ERROR, (errmsg("plan node %d not found", plan_node_id))); + } + + /* Accumulate the statistics from all workers. */ + Instrumentation *instrument = GetInstrumentationArray(instrumentation); + instrument += i * instrumentation->num_workers; + for (i = 0; i < instrumentation->num_workers; ++i) { + InstrAggNode(planstate->instrument, &instrument[i]); + } + + /* Also store the per-worker detail. */ + Size ibytes = instrumentation->num_workers * sizeof(Instrumentation); + planstate->worker_instrument = + (WorkerInstrumentation *)palloc(offsetof(WorkerInstrumentation, instrument) + ibytes); + planstate->worker_instrument->num_workers = instrumentation->num_workers; + int rc = memcpy_s(&planstate->worker_instrument->instrument, ibytes, instrument, ibytes); + securec_check(rc, "", ""); + + return planstate_tree_walker(planstate, (bool (*)())ExecParallelRetrieveInstrumentation, instrumentation); +} + + +/* + * Finish parallel execution. We wait for parallel workers to finish, and + * accumulate their buffer usage and instrumentation. + */ +void ExecParallelFinish(ParallelExecutorInfo *pei) +{ + if (pei->finished) + return; + + /* First, wait for the workers to finish. */ + WaitForParallelWorkersToFinish(pei->pcxt); + + /* Next, accumulate buffer usage. */ + for (int i = 0; i < pei->pcxt->nworkers; ++i) + InstrAccumParallelQuery(&pei->buffer_usage[i]); + + /* Finally, accumulate instrumentation, if any. */ + if (pei->instrumentation) { + (void)ExecParallelRetrieveInstrumentation(pei->planstate, pei->instrumentation); + } + + pei->finished = true; +} + +/* + * Clean up whatever ParallelExecutreInfo resources still exist after + * ExecParallelFinish. We separate these routines because someone might + * want to examine the contents of the DSM after ExecParallelFinish and + * before calling this routine. + */ +void ExecParallelCleanup(ParallelExecutorInfo *pei) +{ + if (pei->pcxt != NULL) { + DestroyParallelContext(pei->pcxt); + pei->pcxt = NULL; + } + pfree(pei); +} + +/* + * Create a DestReceiver to write tuples we produce to the shm_mq designated + * for that purpose. + */ +static DestReceiver *ExecParallelGetReceiver(void *seg) +{ + Assert(seg != NULL); + knl_u_parallel_context *cxt = (knl_u_parallel_context *)seg; + + char *mqspace = cxt->pwCtx->tupleQueue; + mqspace += t_thrd.bgworker_cxt.ParallelWorkerNumber * PARALLEL_TUPLE_QUEUE_SIZE; + shm_mq *mq = (shm_mq *)mqspace; + shm_mq_set_sender(mq, t_thrd.proc); + return CreateTupleQueueDestReceiver(shm_mq_attach(mq, seg, NULL)); +} + +/* + * Create a QueryDesc for the PlannedStmt we are to execute, and return it. + */ +static QueryDesc *ExecParallelGetQueryDesc(void *seg, DestReceiver *receiver, int instrument_options) +{ + knl_u_parallel_context *cxt = (knl_u_parallel_context *)seg; + + /* Reconstruct leader-supplied PlannedStmt. */ + PlannedStmt *pstmt = (PlannedStmt *)stringToNode(cxt->pwCtx->pstmt_space); + + /* Reconstruct ParamListInfo. */ + ParamListInfo paramLI = RestoreParamList(cxt->pwCtx->param_space, cxt->pwCtx->param_len); + + /* + * Create a QueryDesc for the query. + * + * It's not obvious how to obtain the query string from here; and even if + * we could copying it would take more cycles than not copying it. But + * it's a bit unsatisfying to just use a dummy string here, so consider + * revising this someday. + */ + return CreateQueryDesc(pstmt, "", GetActiveSnapshot(), InvalidSnapshot, receiver, paramLI, + instrument_options); +} + +/* + * Copy instrumentation information from this node and its descendents into + * dynamic shared memory, so that the parallel leader can retrieve it. + */ +static bool ExecParallelReportInstrumentation(PlanState *planstate, SharedExecutorInstrumentation *instrumentation) +{ + int i; + int plan_node_id = planstate->plan->plan_node_id; + + InstrEndLoop(planstate->instrument); + + /* + * If we shuffled the plan_node_id values in ps_instrument into sorted + * order, we could use binary search here. This might matter someday + * if we're pushing down sufficiently large plan trees. For now, do it + * the slow, dumb way. + */ + for (i = 0; i < instrumentation->num_plan_nodes; ++i) { + if (instrumentation->plan_node_id[i] == plan_node_id) { + break; + } + } + if (i >= instrumentation->num_plan_nodes) { + ereport(ERROR, (errmsg("plan node %d not found", plan_node_id))); + } + + /* + * Add our statistics to the per-node, per-worker totals. It's possible + * that this could happen more than once if we relaunched workers. + */ + Instrumentation *instrument = GetInstrumentationArray(instrumentation); + instrument += i * instrumentation->num_workers; + Assert(IsParallelWorker()); + Assert(t_thrd.bgworker_cxt.ParallelWorkerNumber < instrumentation->num_workers); + InstrAggNode(&instrument[t_thrd.bgworker_cxt.ParallelWorkerNumber], planstate->instrument); + + return planstate_tree_walker(planstate, (bool (*)())ExecParallelReportInstrumentation, instrumentation); +} + +/* + * Initialize the PlanState and its descendents with the information + * retrieved from shared memory. This has to be done once the PlanState + * is allocated and initialized by executor; that is, after ExecutorStart(). + */ +static bool ExecParallelInitializeWorker(PlanState *planstate, void *context) +{ + if (planstate == NULL) + return false; + + /* Call initializers for parallel-aware plan nodes. */ + if (planstate->plan->parallel_aware) { + switch (nodeTag(planstate)) { + case T_SeqScanState: + ExecSeqScanInitializeWorker((SeqScanState *)planstate, context); + break; + default: + break; + } + } + + return planstate_tree_walker(planstate, (bool (*)())ExecParallelInitializeWorker, context); +} + +/* + * Main entrypoint for parallel query worker processes. + * + * We reach this function from ParallelMain, so the setup necessary to create + * a sensible parallel environment has already been done; ParallelMain worries + * about stuff like the transaction state, combo CID mappings, and GUC values, + * so we don't need to deal with any of that here. + * + * Our job is to deal with concerns specific to the executor. The parallel + * group leader will have stored a serialized PlannedStmt, and it's our job + * to execute that plan and write the resulting tuples to the appropriate + * tuple queue. Various bits of supporting information that we need in order + * to do this are also stored in the dsm_segment and can be accessed through + * the shm_toc. + */ +void ParallelQueryMain(void *seg) +{ + int instrument_options = 0; + + /* Set up DestReceiver, SharedExecutorInstrumentation, and QueryDesc. */ + knl_u_parallel_context *cxt = (knl_u_parallel_context *)seg; + DestReceiver *receiver = ExecParallelGetReceiver(seg); + SharedExecutorInstrumentation *instrumentation = cxt->pwCtx->instrumentation; + if (instrumentation != NULL) + instrument_options = instrumentation->instrument_options; + QueryDesc *queryDesc = ExecParallelGetQueryDesc(seg, receiver, instrument_options); + + /* Prepare to track buffer usage during query execution. */ + InstrStartParallelQuery(); + + /* Start up the executor, have it run the plan, and then shut it down. */ + (void)ExecutorStart(queryDesc, 0); + ExecParallelInitializeWorker(queryDesc->planstate, seg); + ExecutorRun(queryDesc, ForwardScanDirection, 0L); + ExecutorFinish(queryDesc); + + /* Report buffer usage during parallel execution. */ + BufferUsage *buffer_usage = cxt->pwCtx->bufUsage; + InstrEndParallelQuery(&buffer_usage[t_thrd.bgworker_cxt.ParallelWorkerNumber]); + + /* Report instrumentation data if any instrumentation options are set. */ + if (instrumentation != NULL) { + (void)ExecParallelReportInstrumentation(queryDesc->planstate, instrumentation); + } + + /* Must do this after capturing instrumentation. */ + ExecutorEnd(queryDesc); + + /* Cleanup. */ + FreeQueryDesc(queryDesc); + (*receiver->rDestroy)(receiver); +} + diff --git a/src/gausskernel/runtime/executor/nodeGather.cpp b/src/gausskernel/runtime/executor/nodeGather.cpp index e9f6f6b8e..6b7eda565 100644 --- a/src/gausskernel/runtime/executor/nodeGather.cpp +++ b/src/gausskernel/runtime/executor/nodeGather.cpp @@ -1,434 +1,436 @@ -/* ------------------------------------------------------------------------- - * - * nodeGather.c - * Support routines for scanning a plan via multiple workers. - * - * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group - * Portions Copyright (c) 1994, Regents of the University of California - * - * A Gather executor launches parallel workers to run multiple copies of a - * plan. It can also run the plan itself, if the workers are not available - * or have not started up yet. It then merges all of the results it produces - * and the results from the workers into a single output stream. Therefore, - * it will normally be used with a plan where running multiple copies of the - * same plan does not produce duplicate output, such as parallel-aware - * SeqScan. - * - * Alternatively, a Gather node can be configured to use just one worker - * and the single-copy flag can be set. In this case, the Gather node will - * run the plan in one worker and will not execute the plan itself. In - * this case, it simply returns whatever tuples were returned by the worker. - * If a worker cannot be obtained, then it will run the plan itself and - * return the results. Therefore, a plan used with a single-copy Gather - * node need not be parallel-aware. - * - * IDENTIFICATION - * src/backend/executor/nodeGather.c - * - * ------------------------------------------------------------------------- - */ - -#include "postgres.h" - -#include "access/relscan.h" -#include "access/xact.h" -#include "executor/execdebug.h" -#include "executor/execParallel.h" -#include "executor/nodeGather.h" -#include "executor/nodeSubplan.h" -#include "executor/tqueue.h" -#include "miscadmin.h" -#include "utils/memutils.h" -#include "utils/rel.h" - - -static TupleTableSlot *gather_getnext(GatherState *gatherstate); -static HeapTuple gather_readnext(GatherState *gatherstate); -static void ExecShutdownGatherWorkers(GatherState *node); - - -/* ---------------------------------------------------------------- - * ExecInitGather - * ---------------------------------------------------------------- - */ -GatherState *ExecInitGather(Gather *node, EState *estate, int eflags) -{ - bool hasoid = false; - - /* Gather node doesn't have innerPlan node. */ - Assert(innerPlan(node) == NULL); - - /* - * create state structure - */ - GatherState *gatherstate = makeNode(GatherState); - gatherstate->ps.plan = (Plan *)node; - gatherstate->ps.state = estate; - gatherstate->need_to_scan_locally = !node->single_copy && - u_sess->attr.attr_sql.parallel_leader_participation; - - /* - * Miscellaneous initialization - * - * create expression context for node - */ - ExecAssignExprContext(estate, &gatherstate->ps); - - /* - * initialize child expressions - */ - gatherstate->ps.targetlist = (List *)ExecInitExpr((Expr *)node->plan.targetlist, (PlanState *)gatherstate); - gatherstate->ps.qual = (List *)ExecInitExpr((Expr *)node->plan.qual, (PlanState *)gatherstate); - - /* - * tuple table initialization - */ - gatherstate->funnel_slot = ExecInitExtraTupleSlot(estate); - ExecInitResultTupleSlot(estate, &gatherstate->ps); - - /* - * now initialize outer plan - */ - Plan *outerNode = outerPlan(node); - outerPlanState(gatherstate) = ExecInitNode(outerNode, estate, eflags); - - gatherstate->ps.ps_TupFromTlist = false; - - /* - * Initialize result tuple type and projection info. - */ - ExecAssignResultTypeFromTL(&gatherstate->ps); - ExecAssignProjectionInfo(&gatherstate->ps, NULL); - - /* - * Initialize funnel slot to same tuple descriptor as outer plan. - */ - if (!ExecContextForcesOids(&gatherstate->ps, &hasoid)) - hasoid = false; - TupleDesc tupDesc = ExecTypeFromTL(outerNode->targetlist, hasoid); - ExecSetSlotDescriptor(gatherstate->funnel_slot, tupDesc); - - return gatherstate; -} - -/* ---------------------------------------------------------------- - * ExecGather(node) - * - * Scans the relation via multiple workers and returns - * the next qualifying tuple. - * ---------------------------------------------------------------- - */ -TupleTableSlot *ExecGather(GatherState *node) -{ - TupleTableSlot *fslot = node->funnel_slot; - int i; - TupleTableSlot *slot = NULL; - TupleTableSlot *resultSlot = NULL; - ExprDoneCond isDone; - - CHECK_FOR_INTERRUPTS(); - - /* - * Initialize the parallel context and workers on first execution. We do - * this on first execution rather than during node initialization, as it - * needs to allocate large dynamic segement, so it is better to do if it - * is really needed. - */ - if (!node->initialized) { - EState *estate = node->ps.state; - Gather *gather = (Gather *)node->ps.plan; - t_thrd.subrole = BACKGROUND_LEADER; - - /* - * Sometimes we might have to run without parallelism; but if - * parallel mode is active then we can try to fire up some workers. - */ - if (gather->num_workers > 0 && IsInParallelMode()) { - bool got_any_worker = false; - - /* Initialize the workers required to execute Gather node. */ - if (!node->pei) - node->pei = ExecInitParallelPlan(node->ps.lefttree, estate, gather->num_workers); - - /* - * Register backend workers. We might not get as many as we - * requested, or indeed any at all. - */ - ParallelContext *pcxt = node->pei->pcxt; - LaunchParallelWorkers(pcxt); - - /* Set up tuple queue readers to read the results. */ - if (pcxt->nworkers > 0) { - node->nreaders = 0; - node->reader = (TupleQueueReader **)palloc(pcxt->nworkers * sizeof(TupleQueueReader *)); - - for (i = 0; i < pcxt->nworkers; ++i) { - if (pcxt->worker[i].bgwhandle == NULL) - continue; - - shm_mq_set_handle(node->pei->tqueue[i], pcxt->worker[i].bgwhandle); - node->reader[node->nreaders++] = - CreateTupleQueueReader(node->pei->tqueue[i], fslot->tts_tupleDescriptor); - got_any_worker = true; - } - } - - /* No workers? Then never mind. */ - if (!got_any_worker) - ExecShutdownGatherWorkers(node); - } - - /* Run plan locally if no workers or not single-copy. */ - node->need_to_scan_locally = (node->reader == NULL) || - (!gather->single_copy && u_sess->attr.attr_sql.parallel_leader_participation); - node->initialized = true; - } - - /* - * Check to see if we're still projecting out tuples from a previous scan - * tuple (because there is a function-returning-set in the projection - * expressions). If so, try to project another one. - */ - if (node->ps.ps_TupFromTlist) { - resultSlot = ExecProject(node->ps.ps_ProjInfo, &isDone); - if (isDone == ExprMultipleResult) - return resultSlot; - /* Done with that source tuple... */ - node->ps.ps_TupFromTlist = false; - } - - /* - * Reset per-tuple memory context to free any expression evaluation - * storage allocated in the previous tuple cycle. Note we can't do this - * until we're done projecting. This will also clear any previous tuple - * returned by a TupleQueueReader; to make sure we don't leave a dangling - * pointer around, clear the working slot first. - */ - (void)ExecClearTuple(node->funnel_slot); - ExprContext *econtext = node->ps.ps_ExprContext; - ResetExprContext(econtext); - - /* Get and return the next tuple, projecting if necessary. */ - for (;;) { - /* - * Get next tuple, either from one of our workers, or by running the - * plan ourselves. - */ - slot = gather_getnext(node); - if (TupIsNull(slot)) - return NULL; - - /* - * form the result tuple using ExecProject(), and return it --- unless - * the projection produces an empty set, in which case we must loop - * back around for another tuple - */ - econtext->ecxt_outertuple = slot; - resultSlot = ExecProject(node->ps.ps_ProjInfo, &isDone); - - if (isDone != ExprEndResult) { - node->ps.ps_TupFromTlist = (isDone == ExprMultipleResult); - return resultSlot; - } - } - - return slot; -} - -/* ---------------------------------------------------------------- - * ExecEndGather - * - * frees any storage allocated through C routines. - * ---------------------------------------------------------------- - */ -void ExecEndGather(GatherState *node) -{ - ExecShutdownGather(node); - ExecFreeExprContext(&node->ps); - (void)ExecClearTuple(node->ps.ps_ResultTupleSlot); - ExecEndNode(outerPlanState(node)); -} - -/* - * Read the next tuple. We might fetch a tuple from one of the tuple queues - * using gather_readnext, or if no tuple queue contains a tuple and the - * single_copy flag is not set, we might generate one locally instead. - */ -static TupleTableSlot *gather_getnext(GatherState *gatherstate) -{ - PlanState *outerPlan = outerPlanState(gatherstate); - TupleTableSlot *fslot = gatherstate->funnel_slot; - - while (gatherstate->reader != NULL || gatherstate->need_to_scan_locally) { - CHECK_FOR_INTERRUPTS(); - - if (gatherstate->reader != NULL) { - HeapTuple tup = gather_readnext(gatherstate); - if (HeapTupleIsValid(tup)) { - (void)ExecStoreTuple(tup, /* tuple to store */ - fslot, /* slot in which to store the tuple */ - InvalidBuffer, /* buffer associated with this tuple */ - true); /* pfree this pointer if not from heap */ - return fslot; - } - } - - if (gatherstate->need_to_scan_locally) { - TupleTableSlot *outerTupleSlot = ExecProcNode(outerPlan); - - if (!TupIsNull(outerTupleSlot)) - return outerTupleSlot; - - gatherstate->need_to_scan_locally = false; - } - } - - return ExecClearTuple(fslot); -} - -/* - * Attempt to read a tuple from one of our parallel workers. - */ -static HeapTuple gather_readnext(GatherState *gatherstate) -{ - int nvisited = 0; - - for (;;) { - bool readerdone = false; - - /* Check for async events, particularly messages from workers. */ - CHECK_FOR_INTERRUPTS(); - - /* Attempt to read a tuple, but don't block if none is available. */ - TupleQueueReader *reader = gatherstate->reader[gatherstate->nextreader]; - HeapTuple tup = TupleQueueReaderNext(reader, true, &readerdone); - - /* - * If this reader is done, remove it. If all readers are done, - * clean up remaining worker state. - */ - if (readerdone) { - Assert(!tup); - DestroyTupleQueueReader(reader); - --gatherstate->nreaders; - if (gatherstate->nreaders == 0) { - ExecShutdownGatherWorkers(gatherstate); - return NULL; - } - Size remainSize = sizeof(TupleQueueReader *) * (gatherstate->nreaders - gatherstate->nextreader); - if (remainSize != 0) { - int rc = memmove_s(&gatherstate->reader[gatherstate->nextreader], remainSize, - &gatherstate->reader[gatherstate->nextreader + 1], remainSize); - securec_check(rc, "", ""); - } - if (gatherstate->nextreader >= gatherstate->nreaders) { - gatherstate->nextreader = 0; - } - continue; - } - - /* If we got a tuple, return it. */ - if (tup) - return tup; - - /* - * Advance nextreader pointer in round-robin fashion. Note that we - * only reach this code if we weren't able to get a tuple from the - * current worker. We used to advance the nextreader pointer after - * every tuple, but it turns out to be much more efficient to keep - * reading from the same queue until that would require blocking. - */ - gatherstate->nextreader++; - if (gatherstate->nextreader >= gatherstate->nreaders) - gatherstate->nextreader = 0; - - /* Have we visited every (surviving) TupleQueueReader? */ - nvisited++; - if (nvisited >= gatherstate->nreaders) { - /* - * If (still) running plan locally, return NULL so caller can - * generate another tuple from the local copy of the plan. - */ - if (gatherstate->need_to_scan_locally) - return NULL; - - /* Nothing to do except wait for developments. */ - (void)WaitLatch(&t_thrd.proc->procLatch, WL_LATCH_SET, 0); - CHECK_FOR_INTERRUPTS(); - ResetLatch(&t_thrd.proc->procLatch); - nvisited = 0; - } - } -} - -/* ---------------------------------------------------------------- - * ExecShutdownGatherWorkers - * - * Destroy the parallel workers. Collect all the stats after - * workers are stopped, else some work done by workers won't be - * accounted. - * ---------------------------------------------------------------- - */ -static void ExecShutdownGatherWorkers(GatherState *node) -{ - /* Shut down tuple queue readers before shutting down workers. */ - if (node->reader != NULL) { - for (int i = 0; i < node->nreaders; ++i) - DestroyTupleQueueReader(node->reader[i]); - - pfree(node->reader); - node->reader = NULL; - } - - /* Now shut down the workers. */ - if (node->pei != NULL) - ExecParallelFinish(node->pei); -} - -/* ---------------------------------------------------------------- - * ExecShutdownGather - * - * Destroy the setup for parallel workers including parallel context. - * Collect all the stats after workers are stopped, else some work - * done by workers won't be accounted. - * ---------------------------------------------------------------- - */ -void ExecShutdownGather(GatherState *node) -{ - ExecShutdownGatherWorkers(node); - - /* Now destroy the parallel context. */ - if (node->pei != NULL) { - ExecParallelCleanup(node->pei); - node->pei = NULL; - } -} - -/* ---------------------------------------------------------------- - * Join Support - * ---------------------------------------------------------------- - */ -/* ---------------------------------------------------------------- - * ExecReScanGather - * - * Re-initialize the workers and rescans a relation via them. - * ---------------------------------------------------------------- - */ -void ExecReScanGather(GatherState *node) -{ - /* - * Re-initialize the parallel workers to perform rescan of relation. - * We want to gracefully shutdown all the workers so that they - * should be able to propagate any error or other information to master - * backend before dying. Parallel context will be reused for rescan. - */ - ExecShutdownGatherWorkers(node); - - node->initialized = false; - - if (node->pei) - ExecParallelReinitialize(node->pei); - - ExecReScan(node->ps.lefttree); -} - +/* ------------------------------------------------------------------------- + * + * nodeGather.c + * Support routines for scanning a plan via multiple workers. + * + * Portions Copyright (c) 1996-2015, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * A Gather executor launches parallel workers to run multiple copies of a + * plan. It can also run the plan itself, if the workers are not available + * or have not started up yet. It then merges all of the results it produces + * and the results from the workers into a single output stream. Therefore, + * it will normally be used with a plan where running multiple copies of the + * same plan does not produce duplicate output, such as parallel-aware + * SeqScan. + * + * Alternatively, a Gather node can be configured to use just one worker + * and the single-copy flag can be set. In this case, the Gather node will + * run the plan in one worker and will not execute the plan itself. In + * this case, it simply returns whatever tuples were returned by the worker. + * If a worker cannot be obtained, then it will run the plan itself and + * return the results. Therefore, a plan used with a single-copy Gather + * node need not be parallel-aware. + * + * IDENTIFICATION + * src/backend/executor/nodeGather.c + * + * ------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/relscan.h" +#include "access/xact.h" +#include "executor/execdebug.h" +#include "executor/execParallel.h" +#include "executor/nodeGather.h" +#include "executor/nodeSubplan.h" +#include "executor/tqueue.h" +#include "miscadmin.h" +#include "utils/memutils.h" +#include "utils/rel.h" + + +static TupleTableSlot *gather_getnext(GatherState *gatherstate); +static HeapTuple gather_readnext(GatherState *gatherstate); +static void ExecShutdownGatherWorkers(GatherState *node); + + +/* ---------------------------------------------------------------- + * ExecInitGather + * ---------------------------------------------------------------- + */ +GatherState *ExecInitGather(Gather *node, EState *estate, int eflags) +{ + bool hasoid = false; + + /* Gather node doesn't have innerPlan node. */ + Assert(innerPlan(node) == NULL); + + /* + * create state structure + */ + GatherState *gatherstate = makeNode(GatherState); + gatherstate->ps.plan = (Plan *)node; + gatherstate->ps.state = estate; + gatherstate->need_to_scan_locally = !node->single_copy && + u_sess->attr.attr_sql.parallel_leader_participation; + + /* + * Miscellaneous initialization + * + * create expression context for node + */ + ExecAssignExprContext(estate, &gatherstate->ps); + + /* + * initialize child expressions + */ + gatherstate->ps.targetlist = (List *)ExecInitExpr((Expr *)node->plan.targetlist, (PlanState *)gatherstate); + gatherstate->ps.qual = (List *)ExecInitExpr((Expr *)node->plan.qual, (PlanState *)gatherstate); + + /* + * tuple table initialization + */ + gatherstate->funnel_slot = ExecInitExtraTupleSlot(estate); + ExecInitResultTupleSlot(estate, &gatherstate->ps); + + /* + * now initialize outer plan + */ + Plan *outerNode = outerPlan(node); + outerPlanState(gatherstate) = ExecInitNode(outerNode, estate, eflags); + + gatherstate->ps.ps_TupFromTlist = false; + + /* + * Initialize result tuple type and projection info. + */ + ExecAssignResultTypeFromTL(&gatherstate->ps); + ExecAssignProjectionInfo(&gatherstate->ps, NULL); + + /* + * Initialize funnel slot to same tuple descriptor as outer plan. + */ + if (!ExecContextForcesOids(&gatherstate->ps, &hasoid)) + hasoid = false; + TupleDesc tupDesc = ExecTypeFromTL(outerNode->targetlist, hasoid); + ExecSetSlotDescriptor(gatherstate->funnel_slot, tupDesc); + + return gatherstate; +} + +/* ---------------------------------------------------------------- + * ExecGather(node) + * + * Scans the relation via multiple workers and returns + * the next qualifying tuple. + * ---------------------------------------------------------------- + */ +TupleTableSlot *ExecGather(GatherState *node) +{ + TupleTableSlot *fslot = node->funnel_slot; + int i; + TupleTableSlot *slot = NULL; + TupleTableSlot *resultSlot = NULL; + ExprDoneCond isDone; + + CHECK_FOR_INTERRUPTS(); + + /* + * Initialize the parallel context and workers on first execution. We do + * this on first execution rather than during node initialization, as it + * needs to allocate large dynamic segement, so it is better to do if it + * is really needed. + */ + if (!node->initialized) { + EState *estate = node->ps.state; + Gather *gather = (Gather *)node->ps.plan; + + /* + * Sometimes we might have to run without parallelism; but if + * parallel mode is active then we can try to fire up some workers. + */ + if (gather->num_workers > 0 && IsInParallelMode()) { + bool got_any_worker = false; + + /* Initialize the workers required to execute Gather node. */ + if (!node->pei) + node->pei = ExecInitParallelPlan(node->ps.lefttree, estate, gather->num_workers); + + /* + * Register backend workers. We might not get as many as we + * requested, or indeed any at all. + */ + ParallelContext *pcxt = node->pei->pcxt; + LaunchParallelWorkers(pcxt); + + /* Set up tuple queue readers to read the results. */ + if (pcxt->nworkers > 0) { + node->nreaders = 0; + node->reader = (TupleQueueReader **)palloc(pcxt->nworkers * sizeof(TupleQueueReader *)); + + for (i = 0; i < pcxt->nworkers; ++i) { + if (pcxt->worker[i].bgwhandle == NULL) + continue; + + shm_mq_set_handle(node->pei->tqueue[i], pcxt->worker[i].bgwhandle); + node->reader[node->nreaders++] = + CreateTupleQueueReader(node->pei->tqueue[i], fslot->tts_tupleDescriptor); + got_any_worker = true; + } + } + + /* No workers? Then never mind. */ + if (!got_any_worker) { + ExecShutdownGatherWorkers(node); + } else { + t_thrd.subrole = BACKGROUND_LEADER; + } + } + + /* Run plan locally if no workers or not single-copy. */ + node->need_to_scan_locally = (node->reader == NULL) || + (!gather->single_copy && u_sess->attr.attr_sql.parallel_leader_participation); + node->initialized = true; + } + + /* + * Check to see if we're still projecting out tuples from a previous scan + * tuple (because there is a function-returning-set in the projection + * expressions). If so, try to project another one. + */ + if (node->ps.ps_TupFromTlist) { + resultSlot = ExecProject(node->ps.ps_ProjInfo, &isDone); + if (isDone == ExprMultipleResult) + return resultSlot; + /* Done with that source tuple... */ + node->ps.ps_TupFromTlist = false; + } + + /* + * Reset per-tuple memory context to free any expression evaluation + * storage allocated in the previous tuple cycle. Note we can't do this + * until we're done projecting. This will also clear any previous tuple + * returned by a TupleQueueReader; to make sure we don't leave a dangling + * pointer around, clear the working slot first. + */ + (void)ExecClearTuple(node->funnel_slot); + ExprContext *econtext = node->ps.ps_ExprContext; + ResetExprContext(econtext); + + /* Get and return the next tuple, projecting if necessary. */ + for (;;) { + /* + * Get next tuple, either from one of our workers, or by running the + * plan ourselves. + */ + slot = gather_getnext(node); + if (TupIsNull(slot)) + return NULL; + + /* + * form the result tuple using ExecProject(), and return it --- unless + * the projection produces an empty set, in which case we must loop + * back around for another tuple + */ + econtext->ecxt_outertuple = slot; + resultSlot = ExecProject(node->ps.ps_ProjInfo, &isDone); + + if (isDone != ExprEndResult) { + node->ps.ps_TupFromTlist = (isDone == ExprMultipleResult); + return resultSlot; + } + } + + return slot; +} + +/* ---------------------------------------------------------------- + * ExecEndGather + * + * frees any storage allocated through C routines. + * ---------------------------------------------------------------- + */ +void ExecEndGather(GatherState *node) +{ + ExecShutdownGather(node); + ExecFreeExprContext(&node->ps); + (void)ExecClearTuple(node->ps.ps_ResultTupleSlot); + ExecEndNode(outerPlanState(node)); +} + +/* + * Read the next tuple. We might fetch a tuple from one of the tuple queues + * using gather_readnext, or if no tuple queue contains a tuple and the + * single_copy flag is not set, we might generate one locally instead. + */ +static TupleTableSlot *gather_getnext(GatherState *gatherstate) +{ + PlanState *outerPlan = outerPlanState(gatherstate); + TupleTableSlot *fslot = gatherstate->funnel_slot; + + while (gatherstate->reader != NULL || gatherstate->need_to_scan_locally) { + CHECK_FOR_INTERRUPTS(); + + if (gatherstate->reader != NULL) { + HeapTuple tup = gather_readnext(gatherstate); + if (HeapTupleIsValid(tup)) { + (void)ExecStoreTuple(tup, /* tuple to store */ + fslot, /* slot in which to store the tuple */ + InvalidBuffer, /* buffer associated with this tuple */ + true); /* pfree this pointer if not from heap */ + return fslot; + } + } + + if (gatherstate->need_to_scan_locally) { + TupleTableSlot *outerTupleSlot = ExecProcNode(outerPlan); + + if (!TupIsNull(outerTupleSlot)) + return outerTupleSlot; + + gatherstate->need_to_scan_locally = false; + } + } + + return ExecClearTuple(fslot); +} + +/* + * Attempt to read a tuple from one of our parallel workers. + */ +static HeapTuple gather_readnext(GatherState *gatherstate) +{ + int nvisited = 0; + + for (;;) { + bool readerdone = false; + + /* Check for async events, particularly messages from workers. */ + CHECK_FOR_INTERRUPTS(); + + /* Attempt to read a tuple, but don't block if none is available. */ + TupleQueueReader *reader = gatherstate->reader[gatherstate->nextreader]; + HeapTuple tup = TupleQueueReaderNext(reader, true, &readerdone); + + /* + * If this reader is done, remove it. If all readers are done, + * clean up remaining worker state. + */ + if (readerdone) { + Assert(!tup); + DestroyTupleQueueReader(reader); + --gatherstate->nreaders; + if (gatherstate->nreaders == 0) { + ExecShutdownGatherWorkers(gatherstate); + return NULL; + } + Size remainSize = sizeof(TupleQueueReader *) * (gatherstate->nreaders - gatherstate->nextreader); + if (remainSize != 0) { + int rc = memmove_s(&gatherstate->reader[gatherstate->nextreader], remainSize, + &gatherstate->reader[gatherstate->nextreader + 1], remainSize); + securec_check(rc, "", ""); + } + if (gatherstate->nextreader >= gatherstate->nreaders) { + gatherstate->nextreader = 0; + } + continue; + } + + /* If we got a tuple, return it. */ + if (tup) + return tup; + + /* + * Advance nextreader pointer in round-robin fashion. Note that we + * only reach this code if we weren't able to get a tuple from the + * current worker. We used to advance the nextreader pointer after + * every tuple, but it turns out to be much more efficient to keep + * reading from the same queue until that would require blocking. + */ + gatherstate->nextreader++; + if (gatherstate->nextreader >= gatherstate->nreaders) + gatherstate->nextreader = 0; + + /* Have we visited every (surviving) TupleQueueReader? */ + nvisited++; + if (nvisited >= gatherstate->nreaders) { + /* + * If (still) running plan locally, return NULL so caller can + * generate another tuple from the local copy of the plan. + */ + if (gatherstate->need_to_scan_locally) + return NULL; + + /* Nothing to do except wait for developments. */ + (void)WaitLatch(&t_thrd.proc->procLatch, WL_LATCH_SET, 0); + CHECK_FOR_INTERRUPTS(); + ResetLatch(&t_thrd.proc->procLatch); + nvisited = 0; + } + } +} + +/* ---------------------------------------------------------------- + * ExecShutdownGatherWorkers + * + * Destroy the parallel workers. Collect all the stats after + * workers are stopped, else some work done by workers won't be + * accounted. + * ---------------------------------------------------------------- + */ +static void ExecShutdownGatherWorkers(GatherState *node) +{ + /* Shut down tuple queue readers before shutting down workers. */ + if (node->reader != NULL) { + for (int i = 0; i < node->nreaders; ++i) + DestroyTupleQueueReader(node->reader[i]); + + pfree(node->reader); + node->reader = NULL; + } + + /* Now shut down the workers. */ + if (node->pei != NULL) + ExecParallelFinish(node->pei); +} + +/* ---------------------------------------------------------------- + * ExecShutdownGather + * + * Destroy the setup for parallel workers including parallel context. + * Collect all the stats after workers are stopped, else some work + * done by workers won't be accounted. + * ---------------------------------------------------------------- + */ +void ExecShutdownGather(GatherState *node) +{ + ExecShutdownGatherWorkers(node); + + /* Now destroy the parallel context. */ + if (node->pei != NULL) { + ExecParallelCleanup(node->pei); + node->pei = NULL; + } +} + +/* ---------------------------------------------------------------- + * Join Support + * ---------------------------------------------------------------- + */ +/* ---------------------------------------------------------------- + * ExecReScanGather + * + * Re-initialize the workers and rescans a relation via them. + * ---------------------------------------------------------------- + */ +void ExecReScanGather(GatherState *node) +{ + /* + * Re-initialize the parallel workers to perform rescan of relation. + * We want to gracefully shutdown all the workers so that they + * should be able to propagate any error or other information to master + * backend before dying. Parallel context will be reused for rescan. + */ + ExecShutdownGatherWorkers(node); + + node->initialized = false; + + if (node->pei) + ExecParallelReinitialize(node->pei); + + ExecReScan(node->ps.lefttree); +} + diff --git a/src/gausskernel/runtime/executor/tqueue.cpp b/src/gausskernel/runtime/executor/tqueue.cpp index 96545a9c5..896d2cb44 100644 --- a/src/gausskernel/runtime/executor/tqueue.cpp +++ b/src/gausskernel/runtime/executor/tqueue.cpp @@ -1,905 +1,898 @@ -/* ------------------------------------------------------------------------- - * - * tqueue.c - * Use shm_mq to send & receive tuples between parallel backends - * - * A DestReceiver of type DestTupleQueue, which is a TQueueDestReceiver - * under the hood, writes tuples from the executor to a shm_mq. - * - * A TupleQueueReader reads tuples from a shm_mq and returns the tuples. - * - * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group - * Portions Copyright (c) 1994, Regents of the University of California - * - * IDENTIFICATION - * src/backend/executor/tqueue.c - * - * ------------------------------------------------------------------------- - */ - -#include "postgres.h" - -#include "access/htup.h" -#include "catalog/pg_type.h" -#include "executor/tqueue.h" -#include "funcapi.h" -#include "lib/stringinfo.h" -#include "miscadmin.h" -#include "utils/array.h" -#include "utils/lsyscache.h" -#include "utils/memutils.h" -#include "utils/rangetypes.h" -#include "utils/syscache.h" -#include "utils/typcache.h" - -typedef enum { - TQUEUE_REMAP_NONE, /* no special processing required */ - TQUEUE_REMAP_ARRAY, /* array */ - TQUEUE_REMAP_RANGE, /* range */ - TQUEUE_REMAP_RECORD /* composite type, named or anonymous */ -} RemapClass; - -typedef struct { - int natts; - RemapClass mapping[FLEXIBLE_ARRAY_MEMBER]; -} RemapInfo; - -typedef struct { - DestReceiver pub; - shm_mq_handle *handle; - MemoryContext tmpcontext; - HTAB *recordhtab; - char mode; - TupleDesc tupledesc; - RemapInfo *remapinfo; -} TQueueDestReceiver; - -typedef struct RecordTypemodMap { - int remotetypmod; - int localtypmod; -} RecordTypemodMap; - -struct TupleQueueReader { - shm_mq_handle *queue; - char mode; - TupleDesc tupledesc; - RemapInfo *remapinfo; - HTAB *typmodmap; -}; - -#define TUPLE_QUEUE_MODE_CONTROL 'c' -#define TUPLE_QUEUE_MODE_DATA 'd' - -static void tqueueWalk(TQueueDestReceiver *tqueue, RemapClass walktype, Datum value); -static void tqueueWalkRecord(TQueueDestReceiver *tqueue, Datum value); -static void tqueueWalkArray(TQueueDestReceiver *tqueue, Datum value); -static void tqueueWalkRange(TQueueDestReceiver *tqueue, Datum value); -static void tqueueSendTypmodInfo(TQueueDestReceiver *tqueue, int typmod, TupleDesc tupledesc); -static void TupleQueueHandleControlMessage(TupleQueueReader *reader, Size nbytes, char *data); -static HeapTuple TupleQueueHandleDataMessage(TupleQueueReader *reader, Size nbytes, HeapTupleHeader data); -static HeapTuple TupleQueueRemapTuple(TupleQueueReader *reader, TupleDesc tupledesc, RemapInfo *remapinfo, - HeapTuple tuple); -static Datum TupleQueueRemap(TupleQueueReader *reader, RemapClass remapclass, Datum value); -static Datum TupleQueueRemapArray(TupleQueueReader *reader, Datum value); -static Datum TupleQueueRemapRange(TupleQueueReader *reader, Datum value); -static Datum TupleQueueRemapRecord(TupleQueueReader *reader, Datum value); -static RemapClass GetRemapClass(Oid type_id); -static RemapInfo *BuildRemapInfo(TupleDesc tupledesc); - - -/* - * Receive a tuple from a query, and send it to the designated shm_mq. - * - * Returns true if successful, false if shm_mq has been detached. - */ -static void tqueueReceiveSlot(TupleTableSlot *slot, DestReceiver *self) -{ - TQueueDestReceiver *tqueue = (TQueueDestReceiver *)self; - TupleDesc tupledesc = slot->tts_tupleDescriptor; - - /* - * Test to see whether the tupledesc has changed; if so, set up for the - * new tupledesc. This is a strange test both because the executor really - * shouldn't change the tupledesc, and also because it would be unsafe if - * the old tupledesc could be freed and a new one allocated at the same - * address. But since some very old code in printtup.c uses a similar - * test, we adopt it here as well. - */ - if (tqueue->tupledesc != tupledesc) { - if (tqueue->remapinfo != NULL) - pfree(tqueue->remapinfo); - tqueue->remapinfo = BuildRemapInfo(tupledesc); - tqueue->tupledesc = tupledesc; - } - - HeapTuple tuple = ExecMaterializeSlot(slot); - - /* - * When, because of the types being transmitted, no record typemod mapping - * can be needed, we can skip a good deal of work. - */ - if (tqueue->remapinfo != NULL) { - RemapInfo *remapinfo = tqueue->remapinfo; - MemoryContext oldcontext = NULL; - - /* Deform the tuple so we can examine it, if not done already. */ - slot_getallattrs(slot); - - /* Iterate over each attribute and search it for transient typemods. */ - Assert(slot->tts_tupleDescriptor->natts == remapinfo->natts); - for (AttrNumber i = 0; i < remapinfo->natts; ++i) { - /* Ignore nulls and types that don't need special handling. */ - if (slot->tts_isnull[i] || remapinfo->mapping[i] == TQUEUE_REMAP_NONE) - continue; - - /* Switch to temporary memory context to avoid leaking. */ - if (oldcontext == NULL) { - if (tqueue->tmpcontext == NULL) - tqueue->tmpcontext = AllocSetContextCreate(TopMemoryContext, "tqueue temporary context", - ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); - oldcontext = MemoryContextSwitchTo(tqueue->tmpcontext); - } - - /* Invoke the appropriate walker function. */ - tqueueWalk(tqueue, remapinfo->mapping[i], slot->tts_values[i]); - } - - /* If we used the temp context, reset it and restore prior context. */ - if (oldcontext != NULL) { - (void)MemoryContextSwitchTo(oldcontext); - MemoryContextReset(tqueue->tmpcontext); - } - - /* If we entered control mode, switch back to data mode. */ - if (tqueue->mode != TUPLE_QUEUE_MODE_DATA) { - tqueue->mode = TUPLE_QUEUE_MODE_DATA; - (void)shm_mq_send(tqueue->handle, sizeof(char), &tqueue->mode, false); - } - } - - /* Send the tuple itself. */ - (void)shm_mq_send(tqueue->handle, tuple->t_len, tuple->t_data, false); -} - -/* - * Invoke the appropriate walker function based on the given RemapClass. - */ -static void tqueueWalk(TQueueDestReceiver *tqueue, RemapClass walktype, Datum value) -{ - check_stack_depth(); - - switch (walktype) { - case TQUEUE_REMAP_NONE: - break; - case TQUEUE_REMAP_ARRAY: - tqueueWalkArray(tqueue, value); - break; - case TQUEUE_REMAP_RANGE: - tqueueWalkRange(tqueue, value); - break; - case TQUEUE_REMAP_RECORD: - tqueueWalkRecord(tqueue, value); - break; - } -} - -/* - * Walk a record and send control messages for transient record types - * contained therein. - */ -static void tqueueWalkRecord(TQueueDestReceiver *tqueue, Datum value) -{ - /* Extract typmod from tuple. */ - HeapTupleHeader tup = DatumGetHeapTupleHeader(value); - Oid type_id = HeapTupleHeaderGetTypeId(tup); - int32 typmod = HeapTupleHeaderGetTypMod(tup); - - /* Look up tuple descriptor in typecache. */ - TupleDesc tupledesc = lookup_rowtype_tupdesc(type_id, typmod); - - /* - * If this is a transient record time, send its TupleDesc as a control - * message. (tqueueSendTypemodInfo is smart enough to do this only once - * per typmod.) - */ - if (type_id == RECORDOID) - tqueueSendTypmodInfo(tqueue, typmod, tupledesc); - - /* - * Build the remap information for this tupledesc. We might want to think - * about keeping a cache of this information keyed by typeid and typemod, - * but let's keep it simple for now. - */ - RemapInfo *remapinfo = BuildRemapInfo(tupledesc); - - /* - * If remapping is required, deform the tuple and process each field. When - * BuildRemapInfo is null, the data types are such that there can be no - * transient record types here, so we can skip all this work. - */ - if (remapinfo != NULL) { - HeapTupleData tdata; - - /* Deform the tuple so we can check each column within. */ - Datum *values = (Datum *)palloc(tupledesc->natts * sizeof(Datum)); - bool *isnull = (bool *)palloc(tupledesc->natts * sizeof(bool)); - tdata.t_len = HeapTupleHeaderGetDatumLength(tup); - ItemPointerSetInvalid(&(tdata.t_self)); - tdata.t_tableOid = InvalidOid; - tdata.t_data = tup; - heap_deform_tuple(&tdata, tupledesc, values, isnull); - - /* Recursively check each non-NULL attribute. */ - for (AttrNumber i = 0; i < tupledesc->natts; ++i) { - if (!isnull[i]) { - tqueueWalk(tqueue, remapinfo->mapping[i], values[i]); - } - } - } - - /* Release reference count acquired by lookup_rowtype_tupdesc. */ - DecrTupleDescRefCount(tupledesc); -} - -/* - * Walk a record and send control messages for transient record types - * contained therein. - */ -static void tqueueWalkArray(TQueueDestReceiver *tqueue, Datum value) -{ - ArrayType *arr = DatumGetArrayTypeP(value); - Oid type_id = ARR_ELEMTYPE(arr); - int16 typlen; - bool typbyval = false; - char typalign; - Datum *elem_values = NULL; - bool *elem_nulls = NULL; - int num_elems; - RemapClass remapclass = GetRemapClass(type_id); - - /* - * If the elements of the array don't need to be walked, we shouldn't have - * been called in the first place: GetRemapClass should have returned NULL - * when asked about this array type. - */ - Assert(remapclass != TQUEUE_REMAP_NONE); - - /* Deconstruct the array. */ - get_typlenbyvalalign(type_id, &typlen, &typbyval, &typalign); - deconstruct_array(arr, type_id, typlen, typbyval, typalign, &elem_values, &elem_nulls, &num_elems); - - /* Walk each element. */ - for (int i = 0; i < num_elems; ++i) { - if (!elem_nulls[i]) { - tqueueWalk(tqueue, remapclass, elem_values[i]); - } - } -} - -/* - * Walk a range type and send control messages for transient record types - * contained therein. - */ -static void tqueueWalkRange(TQueueDestReceiver *tqueue, Datum value) -{ - RangeType *range = DatumGetRangeType(value); - Oid type_id = RangeTypeGetOid(range); - RangeBound lower; - RangeBound upper; - bool empty = false; - - /* - * Extract the lower and upper bounds. It might be worth implementing - * some caching scheme here so that we don't look up the same typeids in - * the type cache repeatedly, but for now let's keep it simple. - */ - TypeCacheEntry *typcache = lookup_type_cache(type_id, TYPECACHE_RANGE_INFO); - if (typcache->rngelemtype == NULL) - ereport(ERROR, (errmsg("type %u is not a range type", type_id))); - range_deserialize(typcache, range, &lower, &upper, &empty); - - /* Nothing to do for an empty range. */ - if (empty) { - return; - } - - /* - * If the range bounds don't need to be walked, we shouldn't have been - * called in the first place: GetRemapClass should have returned NULL when - * asked about this range type. - */ - RemapClass remapclass = GetRemapClass(type_id); - Assert(remapclass != TQUEUE_REMAP_NONE); - - /* Walk each bound, if present. */ - if (!upper.infinite) - tqueueWalk(tqueue, remapclass, upper.val); - if (!lower.infinite) - tqueueWalk(tqueue, remapclass, lower.val); -} - -/* - * Send tuple descriptor information for a transient typemod, unless we've - * already done so previously. - */ -static void tqueueSendTypmodInfo(TQueueDestReceiver *tqueue, int typmod, TupleDesc tupledesc) -{ - StringInfoData buf; - bool found = false; - AttrNumber i; - - /* Initialize hash table if not done yet. */ - if (tqueue->recordhtab == NULL) { - HASHCTL ctl; - - ctl.keysize = sizeof(int); - ctl.entrysize = sizeof(int); - ctl.hcxt = TopMemoryContext; - tqueue->recordhtab = hash_create("tqueue record hashtable", 100, &ctl, HASH_ELEM | HASH_CONTEXT); - } - - /* Have we already seen this record type? If not, must report it. */ - (void)hash_search(tqueue->recordhtab, &typmod, HASH_ENTER, &found); - if (found) { - return; - } - - /* If message queue is in data mode, switch to control mode. */ - if (tqueue->mode != TUPLE_QUEUE_MODE_CONTROL) { - tqueue->mode = TUPLE_QUEUE_MODE_CONTROL; - (void)shm_mq_send(tqueue->handle, sizeof(char), &tqueue->mode, false); - } - - /* Assemble a control message. */ - initStringInfo(&buf); - appendBinaryStringInfo(&buf, (char *)&typmod, sizeof(int)); - appendBinaryStringInfo(&buf, (char *)&tupledesc->natts, sizeof(int)); - appendBinaryStringInfo(&buf, (char *)&tupledesc->tdhasoid, sizeof(bool)); - for (i = 0; i < tupledesc->natts; ++i) - appendBinaryStringInfo(&buf, (char *)tupledesc->attrs[i], sizeof(FormData_pg_attribute)); - - /* Send control message. */ - (void)shm_mq_send(tqueue->handle, buf.len, buf.data, false); -} - - -/* - * Prepare to receive tuples from executor. - */ -static void tqueueStartupReceiver(DestReceiver *self, int operation, TupleDesc typeinfo) -{ - /* do nothing */ -} - -/* - * Clean up at end of an executor run - */ -static void tqueueShutdownReceiver(DestReceiver *self) -{ - TQueueDestReceiver *tqueue = (TQueueDestReceiver *)self; - - if (tqueue->handle != NULL) { - shm_mq_detach(tqueue->handle); - tqueue->handle = NULL; - } -} - -/* - * Destroy receiver when done with it - */ -static void tqueueDestroyReceiver(DestReceiver *self) -{ - TQueueDestReceiver *tqueue = (TQueueDestReceiver *)self; - - if (tqueue->tmpcontext != NULL) - MemoryContextDelete(tqueue->tmpcontext); - if (tqueue->recordhtab != NULL) - hash_destroy(tqueue->recordhtab); - if (tqueue->remapinfo != NULL) - pfree(tqueue->remapinfo); - pfree(self); -} - -/* - * Create a DestReceiver that writes tuples to a tuple queue. - */ -DestReceiver *CreateTupleQueueDestReceiver(shm_mq_handle *handle) -{ - TQueueDestReceiver *self = (TQueueDestReceiver *)palloc0(sizeof(TQueueDestReceiver)); - - self->pub.receiveSlot = tqueueReceiveSlot; - self->pub.rStartup = tqueueStartupReceiver; - self->pub.rShutdown = tqueueShutdownReceiver; - self->pub.rDestroy = tqueueDestroyReceiver; - self->pub.mydest = DestTupleQueue; - self->handle = handle; - self->tmpcontext = NULL; - self->recordhtab = NULL; - self->mode = TUPLE_QUEUE_MODE_DATA; - self->remapinfo = NULL; - - return (DestReceiver *)self; -} - -/* - * Create a tuple queue reader. - */ -TupleQueueReader *CreateTupleQueueReader(shm_mq_handle *handle, TupleDesc tupledesc) -{ - TupleQueueReader *reader = (TupleQueueReader *)palloc0(sizeof(TupleQueueReader)); - - reader->queue = handle; - reader->mode = TUPLE_QUEUE_MODE_DATA; - reader->tupledesc = tupledesc; - reader->remapinfo = BuildRemapInfo(tupledesc); - - return reader; -} - -/* - * Destroy a tuple queue reader. - * - * Note: cleaning up the underlying shm_mq is the caller's responsibility. - * We won't access it here, as it may be detached already. - */ -void DestroyTupleQueueReader(TupleQueueReader *reader) -{ - if (reader->queue != NULL) { - shm_mq_detach(reader->queue); - reader->queue = NULL; - } - if (reader->remapinfo != NULL) - pfree(reader->remapinfo); - pfree(reader); -} - -/* - * Fetch a tuple from a tuple queue reader. - * - * The return value is NULL if there are no remaining tuples or if - * nowait = true and no tuple is ready to return. *done, if not NULL, - * is set to true when there are no remaining tuples and otherwise to false. - * - * The returned tuple, if any, is allocated in CurrentMemoryContext. - * Note that this routine must not leak memory! (We used to allow that, - * but not any more.) - * - * Even when shm_mq_receive() returns SHM_MQ_WOULD_BLOCK, this can still - * accumulate bytes from a partially-read message, so it's useful to call - * this with nowait = true even if nothing is returned. - */ -HeapTuple TupleQueueReaderNext(TupleQueueReader *reader, bool nowait, bool *done) -{ - if (done != NULL) - *done = false; - - for (;;) { - Size nbytes; - void *data = NULL; - /* Attempt to read a message. */ - shm_mq_result result = shm_mq_receive(reader->queue, &nbytes, &data, nowait); - /* If queue is detached, set *done and return NULL. */ - if (result == SHM_MQ_DETACHED) { - if (done != NULL) - *done = true; - return NULL; - } - - /* In non-blocking mode, bail out if no message ready yet. */ - if (result == SHM_MQ_WOULD_BLOCK) - return NULL; - Assert(result == SHM_MQ_SUCCESS); - - /* - * OK, we got a message. Process it. - * - * One-byte messages are mode switch messages, so that we can switch - * between "control" and "data" mode. When in "data" mode, each - * message (unless exactly one byte) is a tuple. When in "control" - * mode, each message provides a transient-typmod-to-tupledesc mapping - * so we can interpret future tuples. - */ - if (nbytes == 1) { - /* Mode switch message. */ - reader->mode = ((char *)data)[0]; - } else if (reader->mode == TUPLE_QUEUE_MODE_DATA) { - /* Tuple data. */ - return TupleQueueHandleDataMessage(reader, nbytes, (HeapTupleHeader)data); - } else if (reader->mode == TUPLE_QUEUE_MODE_CONTROL) { - /* Control message, describing a transient record type. */ - TupleQueueHandleControlMessage(reader, nbytes, (char *)data); - } else { - ereport(ERROR, (errmsg("invalid mode: %d", (int)reader->mode))); - } - } -} - -/* - * Handle a data message - that is, a tuple - from the remote side. - */ -static HeapTuple TupleQueueHandleDataMessage(TupleQueueReader *reader, Size nbytes, HeapTupleHeader data) -{ - HeapTupleData htup; - - ItemPointerSetInvalid(&htup.t_self); - htup.t_tableOid = InvalidOid; - htup.t_len = (uint32)nbytes; - htup.t_data = data; - - return TupleQueueRemapTuple(reader, reader->tupledesc, reader->remapinfo, &htup); -} - -/* - * Remap tuple typmods per control information received from remote side. - */ -static HeapTuple TupleQueueRemapTuple(TupleQueueReader *reader, TupleDesc tupledesc, RemapInfo *remapinfo, - HeapTuple tuple) -{ - /* - * If no remapping is necessary, just copy the tuple into a single - * palloc'd chunk, as caller will expect. - */ - if (remapinfo == NULL) - return heap_copytuple(tuple); - - /* Deform tuple so we can remap record typmods for individual attrs. */ - Datum *values = (Datum *)palloc(tupledesc->natts * sizeof(Datum)); - bool *isnull = (bool *)palloc(tupledesc->natts * sizeof(bool)); - heap_deform_tuple(tuple, tupledesc, values, isnull); - Assert(tupledesc->natts == remapinfo->natts); - - /* Recursively check each non-NULL attribute. */ - for (int i = 0; i < tupledesc->natts; ++i) { - if (isnull[i] || remapinfo->mapping[i] == TQUEUE_REMAP_NONE) - continue; - values[i] = TupleQueueRemap(reader, remapinfo->mapping[i], values[i]); - } - - /* Reform the modified tuple. */ - return heap_form_tuple(tupledesc, values, isnull); -} - -/* - * Remap a value based on the specified remap class. - */ -static Datum TupleQueueRemap(TupleQueueReader *reader, RemapClass remapclass, Datum value) -{ - check_stack_depth(); - - switch (remapclass) { - case TQUEUE_REMAP_NONE: - /* caller probably shouldn't have called us at all, but... */ - return value; - - case TQUEUE_REMAP_ARRAY: - return TupleQueueRemapArray(reader, value); - - case TQUEUE_REMAP_RANGE: - return TupleQueueRemapRange(reader, value); - - case TQUEUE_REMAP_RECORD: - return TupleQueueRemapRecord(reader, value); - } - - ereport(ERROR, (errmsg("unknown remap class: %d", (int)remapclass))); - return (Datum)0; -} - -/* - * Remap an array. - */ -static Datum TupleQueueRemapArray(TupleQueueReader *reader, Datum value) -{ - ArrayType *arr = DatumGetArrayTypeP(value); - Oid type_id = ARR_ELEMTYPE(arr); - int16 typlen; - bool typbyval; - char typalign; - Datum *elem_values = NULL; - bool *elem_nulls = NULL; - int num_elems; - RemapClass remapclass = GetRemapClass(type_id); - - /* - * If the elements of the array don't need to be walked, we shouldn't have - * been called in the first place: GetRemapClass should have returned NULL - * when asked about this array type. - */ - Assert(remapclass != TQUEUE_REMAP_NONE); - - /* Deconstruct the array. */ - get_typlenbyvalalign(type_id, &typlen, &typbyval, &typalign); - deconstruct_array(arr, type_id, typlen, typbyval, typalign, &elem_values, &elem_nulls, &num_elems); - - /* Remap each element. */ - for (int i = 0; i < num_elems; ++i) { - if (!elem_nulls[i]) { - elem_values[i] = TupleQueueRemap(reader, remapclass, elem_values[i]); - } - } - - /* Reconstruct and return the array. */ - arr = construct_md_array(elem_values, elem_nulls, ARR_NDIM(arr), ARR_DIMS(arr), ARR_LBOUND(arr), type_id, typlen, - typbyval, typalign); - return PointerGetDatum(arr); -} - -/* - * Remap a range type. - */ -static Datum TupleQueueRemapRange(TupleQueueReader *reader, Datum value) -{ - RangeType *range = DatumGetRangeType(value); - Oid type_id = RangeTypeGetOid(range); - RangeBound lower; - RangeBound upper; - bool empty = false; - - /* - * Extract the lower and upper bounds. As in tqueueWalkRange, some - * caching might be a good idea here. - */ - TypeCacheEntry *typcache = lookup_type_cache(type_id, TYPECACHE_RANGE_INFO); - if (typcache->rngelemtype == NULL) - ereport(ERROR, (errmsg("type %u is not a range type", type_id))); - range_deserialize(typcache, range, &lower, &upper, &empty); - - /* Nothing to do for an empty range. */ - if (empty) - return value; - - /* - * If the range bounds don't need to be walked, we shouldn't have been - * called in the first place: GetRemapClass should have returned NULL when - * asked about this range type. - */ - RemapClass remapclass = GetRemapClass(type_id); - Assert(remapclass != TQUEUE_REMAP_NONE); - - /* Remap each bound, if present. */ - if (!upper.infinite) - upper.val = TupleQueueRemap(reader, remapclass, upper.val); - if (!lower.infinite) - lower.val = TupleQueueRemap(reader, remapclass, lower.val); - - /* And reserialize. */ - range = range_serialize(typcache, &lower, &upper, empty); - return RangeTypeGetDatum(range); -} - -/* - * Remap a record. - */ -static Datum TupleQueueRemapRecord(TupleQueueReader *reader, Datum value) -{ - HeapTupleData htup; - - /* Fetch type OID and typemod. */ - HeapTupleHeader tup = DatumGetHeapTupleHeader(value); - Oid type_id = HeapTupleHeaderGetTypeId(tup); - int typmod = HeapTupleHeaderGetTypMod(tup); - - /* If transient record, replace remote typmod with local typmod. */ - if (type_id == RECORDOID) { - Assert(reader->typmodmap != NULL); - RecordTypemodMap *mapent = (RecordTypemodMap *)hash_search(reader->typmodmap, &typmod, HASH_FIND, NULL); - if (mapent == NULL) - ereport(ERROR, (errmsg("found unrecognized remote typmod %d", typmod))); - typmod = mapent->localtypmod; - } - - /* - * Fetch tupledesc and compute remap info. We should probably cache this - * so that we don't have to keep recomputing it. - */ - TupleDesc tupledesc = lookup_rowtype_tupdesc(type_id, typmod); - RemapInfo *remapinfo = BuildRemapInfo(tupledesc); - DecrTupleDescRefCount(tupledesc); - - /* Remap tuple. */ - ItemPointerSetInvalid(&htup.t_self); - htup.t_tableOid = InvalidOid; - htup.t_len = HeapTupleHeaderGetDatumLength(tup); - htup.t_data = tup; - HeapTuple atup = TupleQueueRemapTuple(reader, tupledesc, remapinfo, &htup); - HeapTupleHeaderSetTypeId(atup->t_data, type_id); - HeapTupleHeaderSetTypMod(atup->t_data, typmod); - HeapTupleHeaderSetDatumLength(atup->t_data, htup.t_len); - - /* And return the results. */ - return HeapTupleGetDatum(atup); -} - -/* - * Handle a control message from the tuple queue reader. - * - * Control messages are sent when the remote side is sending tuples that - * contain transient record types. We need to arrange to bless those - * record types locally and translate between remote and local typmods. - */ -static void TupleQueueHandleControlMessage(TupleQueueReader *reader, Size nbytes, char *data) -{ - int natts; - int remotetypmod; - bool hasoid = false; - char *buf = data; - Size rc = 0; - int i; - Form_pg_attribute *attrs; - MemoryContext oldcontext; - TupleDesc tupledesc; - RecordTypemodMap *mapent; - bool found; - - /* Extract remote typmod. */ - int errorno = memcpy_s(&remotetypmod, nbytes, &buf[rc], sizeof(int)); - securec_check_c(errorno, "", ""); - nbytes -= sizeof(int); - rc += sizeof(int); - - /* Extract attribute count. */ - errorno = memcpy_s(&natts, nbytes, &buf[rc], sizeof(int)); - securec_check_c(errorno, "", ""); - nbytes -= sizeof(int); - rc += sizeof(int); - - /* Extract hasoid flag. */ - errorno = memcpy_s(&hasoid, nbytes, &buf[rc], sizeof(bool)); - securec_check_c(errorno, "", ""); - nbytes -= sizeof(bool); - rc += sizeof(bool); - - /* Extract attribute details. */ - oldcontext = MemoryContextSwitchTo(t_thrd.mem_cxt.cur_transaction_mem_cxt); - attrs = (Form_pg_attribute *)palloc(natts * sizeof(Form_pg_attribute)); - for (i = 0; i < natts; ++i) { - attrs[i] = (Form_pg_attribute)palloc(sizeof(FormData_pg_attribute)); - errorno = memcpy_s(attrs[i], nbytes, &buf[rc], sizeof(FormData_pg_attribute)); - securec_check_c(errorno, "", ""); - nbytes -= sizeof(FormData_pg_attribute); - rc += sizeof(FormData_pg_attribute); - } - (void)MemoryContextSwitchTo(oldcontext); - - /* We should have read the whole message. */ - Assert(rc == nbytes); - - /* Construct TupleDesc. */ - tupledesc = CreateTupleDesc(natts, hasoid, attrs); - tupledesc = BlessTupleDesc(tupledesc); - - /* Create map if it doesn't exist already. */ - if (reader->typmodmap == NULL) { - HASHCTL ctl; - - ctl.keysize = sizeof(int); - ctl.entrysize = sizeof(RecordTypemodMap); - ctl.hcxt = t_thrd.mem_cxt.cur_transaction_mem_cxt; - reader->typmodmap = hash_create("typmodmap hashtable", 100, &ctl, HASH_ELEM | HASH_CONTEXT); - } - - /* Create map entry. */ - mapent = (RecordTypemodMap *)hash_search(reader->typmodmap, &remotetypmod, HASH_ENTER, &found); - if (found) - ereport(ERROR, (errmsg("duplicate message for typmod %d", remotetypmod))); - mapent->localtypmod = tupledesc->tdtypmod; - ereport(DEBUG3, (errmsg("mapping remote typmod %d to local typmod %d", remotetypmod, tupledesc->tdtypmod))); -} - -/* - * Build a mapping indicating what remapping class applies to each attribute - * described by a tupledesc. - */ -static RemapInfo *BuildRemapInfo(TupleDesc tupledesc) -{ - Size size; - AttrNumber i; - bool noop = true; - - size = offsetof(RemapInfo, mapping) + sizeof(RemapClass) * tupledesc->natts; - RemapInfo *remapinfo = (RemapInfo *)MemoryContextAllocZero(TopMemoryContext, size); - remapinfo->natts = tupledesc->natts; - for (i = 0; i < tupledesc->natts; ++i) { - Form_pg_attribute attr = tupledesc->attrs[i]; - - if (attr->attisdropped) { - remapinfo->mapping[i] = TQUEUE_REMAP_NONE; - continue; - } - - remapinfo->mapping[i] = GetRemapClass(attr->atttypid); - if (remapinfo->mapping[i] != TQUEUE_REMAP_NONE) - noop = false; - } - - if (noop) { - pfree(remapinfo); - remapinfo = NULL; - } - - return remapinfo; -} - -/* - * Determine the remap class assocociated with a particular data type. - * - * Transient record types need to have the typmod applied on the sending side - * replaced with a value on the receiving side that has the same meaning. - * - * Arrays, range types, and all record types (including named composite types) - * need to searched for transient record values buried within them. - * Surprisingly, a walker is required even when the indicated type is a - * composite type, because the actual value may be a compatible transient - * record type. - */ -static RemapClass GetRemapClass(Oid type_id) -{ - RemapClass forceResult = TQUEUE_REMAP_NONE; - RemapClass innerResult = TQUEUE_REMAP_NONE; - - for (;;) { - /* Simple cases. */ - if (type_id == RECORDOID) { - innerResult = TQUEUE_REMAP_RECORD; - break; - } - if (type_id == RECORDARRAYOID) { - innerResult = TQUEUE_REMAP_ARRAY; - break; - } - - /* Otherwise, we need a syscache lookup to figure it out. */ - HeapTuple tup = SearchSysCache1((int)TYPEOID, ObjectIdGetDatum(type_id)); - if (!HeapTupleIsValid(tup)) - ereport(ERROR, (errmsg("cache lookup failed for type %u", type_id))); - Form_pg_type typ = (Form_pg_type)GETSTRUCT(tup); - /* Look through domains to underlying base type. */ - if (typ->typtype == TYPTYPE_DOMAIN) { - type_id = typ->typbasetype; - ReleaseSysCache(tup); - continue; - } - - /* - * Look through arrays to underlying base type, but the final return - * value must be either TQUEUE_REMAP_ARRAY or TQUEUE_REMAP_NONE. (If - * this is an array of integers, for example, we don't need to walk - * it.) - */ - if (OidIsValid(typ->typelem) && typ->typlen == -1) { - type_id = typ->typelem; - ReleaseSysCache(tup); - if (forceResult == TQUEUE_REMAP_NONE) { - forceResult = TQUEUE_REMAP_ARRAY; - } - continue; - } - - /* - * Similarly, look through ranges to the underlying base type, but the - * final return value must be either TQUEUE_REMAP_RANGE or - * TQUEUE_REMAP_NONE. - */ - if (typ->typtype == TYPTYPE_RANGE) { - ReleaseSysCache(tup); - if (forceResult == TQUEUE_REMAP_NONE) { - forceResult = TQUEUE_REMAP_RANGE; - } - type_id = get_range_subtype(type_id); - continue; - } - - /* Walk composite types. Nothing else needs special handling. */ - if (typ->typtype == TYPTYPE_COMPOSITE) { - innerResult = TQUEUE_REMAP_RECORD; - } - ReleaseSysCache(tup); - break; - } - - if (innerResult != TQUEUE_REMAP_NONE && forceResult != TQUEUE_REMAP_NONE) { - return forceResult; - } - return innerResult; -} - +/* ------------------------------------------------------------------------- + * + * tqueue.c + * Use shm_mq to send & receive tuples between parallel backends + * + * A DestReceiver of type DestTupleQueue, which is a TQueueDestReceiver + * under the hood, writes tuples from the executor to a shm_mq. + * + * A TupleQueueReader reads tuples from a shm_mq and returns the tuples. + * + * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/executor/tqueue.c + * + * ------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/htup.h" +#include "catalog/pg_type.h" +#include "executor/tqueue.h" +#include "funcapi.h" +#include "lib/stringinfo.h" +#include "miscadmin.h" +#include "utils/array.h" +#include "utils/lsyscache.h" +#include "utils/memutils.h" +#include "utils/rangetypes.h" +#include "utils/syscache.h" +#include "utils/typcache.h" + +typedef enum { + TQUEUE_REMAP_NONE, /* no special processing required */ + TQUEUE_REMAP_ARRAY, /* array */ + TQUEUE_REMAP_RANGE, /* range */ + TQUEUE_REMAP_RECORD /* composite type, named or anonymous */ +} RemapClass; + +typedef struct { + int natts; + RemapClass mapping[FLEXIBLE_ARRAY_MEMBER]; +} RemapInfo; + +typedef struct { + DestReceiver pub; + shm_mq_handle *handle; + MemoryContext tmpcontext; + HTAB *recordhtab; + char mode; + TupleDesc tupledesc; + RemapInfo *remapinfo; +} TQueueDestReceiver; + +typedef struct RecordTypemodMap { + int remotetypmod; + int localtypmod; +} RecordTypemodMap; + +struct TupleQueueReader { + shm_mq_handle *queue; + char mode; + TupleDesc tupledesc; + RemapInfo *remapinfo; + HTAB *typmodmap; +}; + +#define TUPLE_QUEUE_MODE_CONTROL 'c' +#define TUPLE_QUEUE_MODE_DATA 'd' +#define CHECK_MESSAGE_LEN(remainLen, pos, readSize) \ + do {\ + if (unlikely((remainLen) < (readSize))) {\ + ereport(ERROR, (errmsg("Invalid message length")));\ + }\ + (remainLen) -= (readSize);\ + (pos) += (readSize);\ + } while (0) + +static void tqueueWalk(TQueueDestReceiver *tqueue, RemapClass walktype, Datum value); +static void tqueueWalkRecord(TQueueDestReceiver *tqueue, Datum value); +static void tqueueWalkArray(TQueueDestReceiver *tqueue, Datum value); +static void tqueueWalkRange(TQueueDestReceiver *tqueue, Datum value); +static void tqueueSendTypmodInfo(TQueueDestReceiver *tqueue, int typmod, TupleDesc tupledesc); +static void TupleQueueHandleControlMessage(TupleQueueReader *reader, Size nbytes, char *data); +static HeapTuple TupleQueueHandleDataMessage(TupleQueueReader *reader, Size nbytes, HeapTupleHeader data); +static HeapTuple TupleQueueRemapTuple(TupleQueueReader *reader, TupleDesc tupledesc, RemapInfo *remapinfo, + HeapTuple tuple); +static Datum TupleQueueRemap(TupleQueueReader *reader, RemapClass remapclass, Datum value); +static Datum TupleQueueRemapArray(TupleQueueReader *reader, Datum value); +static Datum TupleQueueRemapRange(TupleQueueReader *reader, Datum value); +static Datum TupleQueueRemapRecord(TupleQueueReader *reader, Datum value); +static RemapClass GetRemapClass(Oid type_id); +static RemapInfo *BuildRemapInfo(TupleDesc tupledesc); + + +/* + * Receive a tuple from a query, and send it to the designated shm_mq. + * + * Returns true if successful, false if shm_mq has been detached. + */ +static void tqueueReceiveSlot(TupleTableSlot *slot, DestReceiver *self) +{ + TQueueDestReceiver *tqueue = (TQueueDestReceiver *)self; + TupleDesc tupledesc = slot->tts_tupleDescriptor; + + /* + * Test to see whether the tupledesc has changed; if so, set up for the + * new tupledesc. This is a strange test both because the executor really + * shouldn't change the tupledesc, and also because it would be unsafe if + * the old tupledesc could be freed and a new one allocated at the same + * address. But since some very old code in printtup.c uses a similar + * test, we adopt it here as well. + */ + if (tqueue->tupledesc != tupledesc) { + if (tqueue->remapinfo != NULL) + pfree(tqueue->remapinfo); + tqueue->remapinfo = BuildRemapInfo(tupledesc); + tqueue->tupledesc = tupledesc; + } + + HeapTuple tuple = ExecMaterializeSlot(slot); + + /* + * When, because of the types being transmitted, no record typemod mapping + * can be needed, we can skip a good deal of work. + */ + if (tqueue->remapinfo != NULL) { + RemapInfo *remapinfo = tqueue->remapinfo; + MemoryContext oldcontext = NULL; + + /* Deform the tuple so we can examine it, if not done already. */ + slot_getallattrs(slot); + + /* Iterate over each attribute and search it for transient typemods. */ + Assert(slot->tts_tupleDescriptor->natts == remapinfo->natts); + for (AttrNumber i = 0; i < remapinfo->natts; ++i) { + /* Ignore nulls and types that don't need special handling. */ + if (slot->tts_isnull[i] || remapinfo->mapping[i] == TQUEUE_REMAP_NONE) + continue; + + /* Switch to temporary memory context to avoid leaking. */ + if (oldcontext == NULL) { + if (tqueue->tmpcontext == NULL) + tqueue->tmpcontext = AllocSetContextCreate(TopMemoryContext, "tqueue temporary context", + ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); + oldcontext = MemoryContextSwitchTo(tqueue->tmpcontext); + } + + /* Invoke the appropriate walker function. */ + tqueueWalk(tqueue, remapinfo->mapping[i], slot->tts_values[i]); + } + + /* If we used the temp context, reset it and restore prior context. */ + if (oldcontext != NULL) { + (void)MemoryContextSwitchTo(oldcontext); + MemoryContextReset(tqueue->tmpcontext); + } + + /* If we entered control mode, switch back to data mode. */ + if (tqueue->mode != TUPLE_QUEUE_MODE_DATA) { + tqueue->mode = TUPLE_QUEUE_MODE_DATA; + (void)shm_mq_send(tqueue->handle, sizeof(char), &tqueue->mode, false); + } + } + + /* Send the tuple itself. */ + (void)shm_mq_send(tqueue->handle, tuple->t_len, tuple->t_data, false); +} + +/* + * Invoke the appropriate walker function based on the given RemapClass. + */ +static void tqueueWalk(TQueueDestReceiver *tqueue, RemapClass walktype, Datum value) +{ + check_stack_depth(); + + switch (walktype) { + case TQUEUE_REMAP_NONE: + break; + case TQUEUE_REMAP_ARRAY: + tqueueWalkArray(tqueue, value); + break; + case TQUEUE_REMAP_RANGE: + tqueueWalkRange(tqueue, value); + break; + case TQUEUE_REMAP_RECORD: + tqueueWalkRecord(tqueue, value); + break; + } +} + +/* + * Walk a record and send control messages for transient record types + * contained therein. + */ +static void tqueueWalkRecord(TQueueDestReceiver *tqueue, Datum value) +{ + /* Extract typmod from tuple. */ + HeapTupleHeader tup = DatumGetHeapTupleHeader(value); + Oid type_id = HeapTupleHeaderGetTypeId(tup); + int32 typmod = HeapTupleHeaderGetTypMod(tup); + + /* Look up tuple descriptor in typecache. */ + TupleDesc tupledesc = lookup_rowtype_tupdesc(type_id, typmod); + + /* + * If this is a transient record time, send its TupleDesc as a control + * message. (tqueueSendTypemodInfo is smart enough to do this only once + * per typmod.) + */ + if (type_id == RECORDOID) + tqueueSendTypmodInfo(tqueue, typmod, tupledesc); + + /* + * Build the remap information for this tupledesc. We might want to think + * about keeping a cache of this information keyed by typeid and typemod, + * but let's keep it simple for now. + */ + RemapInfo *remapinfo = BuildRemapInfo(tupledesc); + + /* + * If remapping is required, deform the tuple and process each field. When + * BuildRemapInfo is null, the data types are such that there can be no + * transient record types here, so we can skip all this work. + */ + if (remapinfo != NULL) { + HeapTupleData tdata; + + /* Deform the tuple so we can check each column within. */ + Datum *values = (Datum *)palloc(tupledesc->natts * sizeof(Datum)); + bool *isnull = (bool *)palloc(tupledesc->natts * sizeof(bool)); + tdata.t_len = HeapTupleHeaderGetDatumLength(tup); + ItemPointerSetInvalid(&(tdata.t_self)); + tdata.t_tableOid = InvalidOid; + tdata.t_data = tup; + heap_deform_tuple(&tdata, tupledesc, values, isnull); + + /* Recursively check each non-NULL attribute. */ + for (AttrNumber i = 0; i < tupledesc->natts; ++i) { + if (!isnull[i]) { + tqueueWalk(tqueue, remapinfo->mapping[i], values[i]); + } + } + } + + /* Release reference count acquired by lookup_rowtype_tupdesc. */ + DecrTupleDescRefCount(tupledesc); +} + +/* + * Walk a record and send control messages for transient record types + * contained therein. + */ +static void tqueueWalkArray(TQueueDestReceiver *tqueue, Datum value) +{ + ArrayType *arr = DatumGetArrayTypeP(value); + Oid type_id = ARR_ELEMTYPE(arr); + int16 typlen; + bool typbyval = false; + char typalign; + Datum *elem_values = NULL; + bool *elem_nulls = NULL; + int num_elems; + RemapClass remapclass = GetRemapClass(type_id); + + /* + * If the elements of the array don't need to be walked, we shouldn't have + * been called in the first place: GetRemapClass should have returned NULL + * when asked about this array type. + */ + Assert(remapclass != TQUEUE_REMAP_NONE); + + /* Deconstruct the array. */ + get_typlenbyvalalign(type_id, &typlen, &typbyval, &typalign); + deconstruct_array(arr, type_id, typlen, typbyval, typalign, &elem_values, &elem_nulls, &num_elems); + + /* Walk each element. */ + for (int i = 0; i < num_elems; ++i) { + if (!elem_nulls[i]) { + tqueueWalk(tqueue, remapclass, elem_values[i]); + } + } +} + +/* + * Walk a range type and send control messages for transient record types + * contained therein. + */ +static void tqueueWalkRange(TQueueDestReceiver *tqueue, Datum value) +{ + RangeType *range = DatumGetRangeType(value); + Oid type_id = RangeTypeGetOid(range); + RangeBound lower; + RangeBound upper; + bool empty = false; + + /* + * Extract the lower and upper bounds. It might be worth implementing + * some caching scheme here so that we don't look up the same typeids in + * the type cache repeatedly, but for now let's keep it simple. + */ + TypeCacheEntry *typcache = lookup_type_cache(type_id, TYPECACHE_RANGE_INFO); + if (typcache->rngelemtype == NULL) + ereport(ERROR, (errmsg("type %u is not a range type", type_id))); + range_deserialize(typcache, range, &lower, &upper, &empty); + + /* Nothing to do for an empty range. */ + if (empty) { + return; + } + + /* + * If the range bounds don't need to be walked, we shouldn't have been + * called in the first place: GetRemapClass should have returned NULL when + * asked about this range type. + */ + RemapClass remapclass = GetRemapClass(type_id); + Assert(remapclass != TQUEUE_REMAP_NONE); + + /* Walk each bound, if present. */ + if (!upper.infinite) + tqueueWalk(tqueue, remapclass, upper.val); + if (!lower.infinite) + tqueueWalk(tqueue, remapclass, lower.val); +} + +/* + * Send tuple descriptor information for a transient typemod, unless we've + * already done so previously. + */ +static void tqueueSendTypmodInfo(TQueueDestReceiver *tqueue, int typmod, TupleDesc tupledesc) +{ + StringInfoData buf; + bool found = false; + AttrNumber i; + + /* Initialize hash table if not done yet. */ + if (tqueue->recordhtab == NULL) { + HASHCTL ctl; + + ctl.keysize = sizeof(int); + ctl.entrysize = sizeof(int); + ctl.hcxt = TopMemoryContext; + tqueue->recordhtab = hash_create("tqueue record hashtable", 100, &ctl, HASH_ELEM | HASH_CONTEXT); + } + + /* Have we already seen this record type? If not, must report it. */ + (void)hash_search(tqueue->recordhtab, &typmod, HASH_ENTER, &found); + if (found) { + return; + } + + /* If message queue is in data mode, switch to control mode. */ + if (tqueue->mode != TUPLE_QUEUE_MODE_CONTROL) { + tqueue->mode = TUPLE_QUEUE_MODE_CONTROL; + (void)shm_mq_send(tqueue->handle, sizeof(char), &tqueue->mode, false); + } + + /* Assemble a control message. */ + initStringInfo(&buf); + appendBinaryStringInfo(&buf, (char *)&typmod, sizeof(int)); + appendBinaryStringInfo(&buf, (char *)&tupledesc->natts, sizeof(int)); + appendBinaryStringInfo(&buf, (char *)&tupledesc->tdhasoid, sizeof(bool)); + for (i = 0; i < tupledesc->natts; ++i) + appendBinaryStringInfo(&buf, (char *)tupledesc->attrs[i], sizeof(FormData_pg_attribute)); + + /* Send control message. */ + (void)shm_mq_send(tqueue->handle, buf.len, buf.data, false); +} + + +/* + * Prepare to receive tuples from executor. + */ +static void tqueueStartupReceiver(DestReceiver *self, int operation, TupleDesc typeinfo) +{ + /* do nothing */ +} + +/* + * Clean up at end of an executor run + */ +static void tqueueShutdownReceiver(DestReceiver *self) +{ + TQueueDestReceiver *tqueue = (TQueueDestReceiver *)self; + + if (tqueue->handle != NULL) { + shm_mq_detach(tqueue->handle); + tqueue->handle = NULL; + } +} + +/* + * Destroy receiver when done with it + */ +static void tqueueDestroyReceiver(DestReceiver *self) +{ + TQueueDestReceiver *tqueue = (TQueueDestReceiver *)self; + + if (tqueue->tmpcontext != NULL) + MemoryContextDelete(tqueue->tmpcontext); + if (tqueue->recordhtab != NULL) + hash_destroy(tqueue->recordhtab); + if (tqueue->remapinfo != NULL) + pfree(tqueue->remapinfo); + pfree(self); +} + +/* + * Create a DestReceiver that writes tuples to a tuple queue. + */ +DestReceiver *CreateTupleQueueDestReceiver(shm_mq_handle *handle) +{ + TQueueDestReceiver *self = (TQueueDestReceiver *)palloc0(sizeof(TQueueDestReceiver)); + + self->pub.receiveSlot = tqueueReceiveSlot; + self->pub.rStartup = tqueueStartupReceiver; + self->pub.rShutdown = tqueueShutdownReceiver; + self->pub.rDestroy = tqueueDestroyReceiver; + self->pub.mydest = DestTupleQueue; + self->handle = handle; + self->tmpcontext = NULL; + self->recordhtab = NULL; + self->mode = TUPLE_QUEUE_MODE_DATA; + self->remapinfo = NULL; + + return (DestReceiver *)self; +} + +/* + * Create a tuple queue reader. + */ +TupleQueueReader *CreateTupleQueueReader(shm_mq_handle *handle, TupleDesc tupledesc) +{ + TupleQueueReader *reader = (TupleQueueReader *)palloc0(sizeof(TupleQueueReader)); + + reader->queue = handle; + reader->mode = TUPLE_QUEUE_MODE_DATA; + reader->tupledesc = tupledesc; + reader->remapinfo = BuildRemapInfo(tupledesc); + + return reader; +} + +/* + * Destroy a tuple queue reader. + * + * Note: cleaning up the underlying shm_mq is the caller's responsibility. + * We won't access it here, as it may be detached already. + */ +void DestroyTupleQueueReader(TupleQueueReader *reader) +{ + if (reader->queue != NULL) { + shm_mq_detach(reader->queue); + reader->queue = NULL; + } + if (reader->remapinfo != NULL) + pfree(reader->remapinfo); + pfree(reader); +} + +/* + * Fetch a tuple from a tuple queue reader. + * + * The return value is NULL if there are no remaining tuples or if + * nowait = true and no tuple is ready to return. *done, if not NULL, + * is set to true when there are no remaining tuples and otherwise to false. + * + * The returned tuple, if any, is allocated in CurrentMemoryContext. + * Note that this routine must not leak memory! (We used to allow that, + * but not any more.) + * + * Even when shm_mq_receive() returns SHM_MQ_WOULD_BLOCK, this can still + * accumulate bytes from a partially-read message, so it's useful to call + * this with nowait = true even if nothing is returned. + */ +HeapTuple TupleQueueReaderNext(TupleQueueReader *reader, bool nowait, bool *done) +{ + if (done != NULL) + *done = false; + + for (;;) { + Size nbytes; + void *data = NULL; + /* Attempt to read a message. */ + shm_mq_result result = shm_mq_receive(reader->queue, &nbytes, &data, nowait); + /* If queue is detached, set *done and return NULL. */ + if (result == SHM_MQ_DETACHED) { + if (done != NULL) + *done = true; + return NULL; + } + + /* In non-blocking mode, bail out if no message ready yet. */ + if (result == SHM_MQ_WOULD_BLOCK) + return NULL; + Assert(result == SHM_MQ_SUCCESS); + + /* + * OK, we got a message. Process it. + * + * One-byte messages are mode switch messages, so that we can switch + * between "control" and "data" mode. When in "data" mode, each + * message (unless exactly one byte) is a tuple. When in "control" + * mode, each message provides a transient-typmod-to-tupledesc mapping + * so we can interpret future tuples. + */ + if (nbytes == 1) { + /* Mode switch message. */ + reader->mode = ((char *)data)[0]; + } else if (reader->mode == TUPLE_QUEUE_MODE_DATA) { + /* Tuple data. */ + return TupleQueueHandleDataMessage(reader, nbytes, (HeapTupleHeader)data); + } else if (reader->mode == TUPLE_QUEUE_MODE_CONTROL) { + /* Control message, describing a transient record type. */ + TupleQueueHandleControlMessage(reader, nbytes, (char *)data); + } else { + ereport(ERROR, (errmsg("invalid mode: %d", (int)reader->mode))); + } + } +} + +/* + * Handle a data message - that is, a tuple - from the remote side. + */ +static HeapTuple TupleQueueHandleDataMessage(TupleQueueReader *reader, Size nbytes, HeapTupleHeader data) +{ + HeapTupleData htup; + + ItemPointerSetInvalid(&htup.t_self); + htup.t_tableOid = InvalidOid; + htup.t_len = (uint32)nbytes; + htup.t_data = data; + + return TupleQueueRemapTuple(reader, reader->tupledesc, reader->remapinfo, &htup); +} + +/* + * Remap tuple typmods per control information received from remote side. + */ +static HeapTuple TupleQueueRemapTuple(TupleQueueReader *reader, TupleDesc tupledesc, RemapInfo *remapinfo, + HeapTuple tuple) +{ + /* + * If no remapping is necessary, just copy the tuple into a single + * palloc'd chunk, as caller will expect. + */ + if (remapinfo == NULL) + return heap_copytuple(tuple); + + /* Deform tuple so we can remap record typmods for individual attrs. */ + Datum *values = (Datum *)palloc(tupledesc->natts * sizeof(Datum)); + bool *isnull = (bool *)palloc(tupledesc->natts * sizeof(bool)); + heap_deform_tuple(tuple, tupledesc, values, isnull); + Assert(tupledesc->natts == remapinfo->natts); + + /* Recursively check each non-NULL attribute. */ + for (int i = 0; i < tupledesc->natts; ++i) { + if (isnull[i] || remapinfo->mapping[i] == TQUEUE_REMAP_NONE) + continue; + values[i] = TupleQueueRemap(reader, remapinfo->mapping[i], values[i]); + } + + /* Reform the modified tuple. */ + return heap_form_tuple(tupledesc, values, isnull); +} + +/* + * Remap a value based on the specified remap class. + */ +static Datum TupleQueueRemap(TupleQueueReader *reader, RemapClass remapclass, Datum value) +{ + check_stack_depth(); + + switch (remapclass) { + case TQUEUE_REMAP_NONE: + /* caller probably shouldn't have called us at all, but... */ + return value; + + case TQUEUE_REMAP_ARRAY: + return TupleQueueRemapArray(reader, value); + + case TQUEUE_REMAP_RANGE: + return TupleQueueRemapRange(reader, value); + + case TQUEUE_REMAP_RECORD: + return TupleQueueRemapRecord(reader, value); + } + + ereport(ERROR, (errmsg("unknown remap class: %d", (int)remapclass))); + return (Datum)0; +} + +/* + * Remap an array. + */ +static Datum TupleQueueRemapArray(TupleQueueReader *reader, Datum value) +{ + ArrayType *arr = DatumGetArrayTypeP(value); + Oid type_id = ARR_ELEMTYPE(arr); + int16 typlen; + bool typbyval; + char typalign; + Datum *elem_values = NULL; + bool *elem_nulls = NULL; + int num_elems; + RemapClass remapclass = GetRemapClass(type_id); + + /* + * If the elements of the array don't need to be walked, we shouldn't have + * been called in the first place: GetRemapClass should have returned NULL + * when asked about this array type. + */ + Assert(remapclass != TQUEUE_REMAP_NONE); + + /* Deconstruct the array. */ + get_typlenbyvalalign(type_id, &typlen, &typbyval, &typalign); + deconstruct_array(arr, type_id, typlen, typbyval, typalign, &elem_values, &elem_nulls, &num_elems); + + /* Remap each element. */ + for (int i = 0; i < num_elems; ++i) { + if (!elem_nulls[i]) { + elem_values[i] = TupleQueueRemap(reader, remapclass, elem_values[i]); + } + } + + /* Reconstruct and return the array. */ + arr = construct_md_array(elem_values, elem_nulls, ARR_NDIM(arr), ARR_DIMS(arr), ARR_LBOUND(arr), type_id, typlen, + typbyval, typalign); + return PointerGetDatum(arr); +} + +/* + * Remap a range type. + */ +static Datum TupleQueueRemapRange(TupleQueueReader *reader, Datum value) +{ + RangeType *range = DatumGetRangeType(value); + Oid type_id = RangeTypeGetOid(range); + RangeBound lower; + RangeBound upper; + bool empty = false; + + /* + * Extract the lower and upper bounds. As in tqueueWalkRange, some + * caching might be a good idea here. + */ + TypeCacheEntry *typcache = lookup_type_cache(type_id, TYPECACHE_RANGE_INFO); + if (typcache->rngelemtype == NULL) + ereport(ERROR, (errmsg("type %u is not a range type", type_id))); + range_deserialize(typcache, range, &lower, &upper, &empty); + + /* Nothing to do for an empty range. */ + if (empty) + return value; + + /* + * If the range bounds don't need to be walked, we shouldn't have been + * called in the first place: GetRemapClass should have returned NULL when + * asked about this range type. + */ + RemapClass remapclass = GetRemapClass(type_id); + Assert(remapclass != TQUEUE_REMAP_NONE); + + /* Remap each bound, if present. */ + if (!upper.infinite) + upper.val = TupleQueueRemap(reader, remapclass, upper.val); + if (!lower.infinite) + lower.val = TupleQueueRemap(reader, remapclass, lower.val); + + /* And reserialize. */ + range = range_serialize(typcache, &lower, &upper, empty); + return RangeTypeGetDatum(range); +} + +/* + * Remap a record. + */ +static Datum TupleQueueRemapRecord(TupleQueueReader *reader, Datum value) +{ + HeapTupleData htup; + + /* Fetch type OID and typemod. */ + HeapTupleHeader tup = DatumGetHeapTupleHeader(value); + Oid type_id = HeapTupleHeaderGetTypeId(tup); + int typmod = HeapTupleHeaderGetTypMod(tup); + + /* If transient record, replace remote typmod with local typmod. */ + if (type_id == RECORDOID) { + Assert(reader->typmodmap != NULL); + RecordTypemodMap *mapent = (RecordTypemodMap *)hash_search(reader->typmodmap, &typmod, HASH_FIND, NULL); + if (mapent == NULL) + ereport(ERROR, (errmsg("found unrecognized remote typmod %d", typmod))); + typmod = mapent->localtypmod; + } + + /* + * Fetch tupledesc and compute remap info. We should probably cache this + * so that we don't have to keep recomputing it. + */ + TupleDesc tupledesc = lookup_rowtype_tupdesc(type_id, typmod); + RemapInfo *remapinfo = BuildRemapInfo(tupledesc); + DecrTupleDescRefCount(tupledesc); + + /* Remap tuple. */ + ItemPointerSetInvalid(&htup.t_self); + htup.t_tableOid = InvalidOid; + htup.t_len = HeapTupleHeaderGetDatumLength(tup); + htup.t_data = tup; + HeapTuple atup = TupleQueueRemapTuple(reader, tupledesc, remapinfo, &htup); + HeapTupleHeaderSetTypeId(atup->t_data, type_id); + HeapTupleHeaderSetTypMod(atup->t_data, typmod); + HeapTupleHeaderSetDatumLength(atup->t_data, htup.t_len); + + /* And return the results. */ + return HeapTupleGetDatum(atup); +} + +/* + * Handle a control message from the tuple queue reader. + * + * Control messages are sent when the remote side is sending tuples that + * contain transient record types. We need to arrange to bless those + * record types locally and translate between remote and local typmods. + */ +static void TupleQueueHandleControlMessage(TupleQueueReader *reader, Size nbytes, char *data) +{ + Size rc = 0; + bool found = false; + + /* Extract remote typmod. */ + int remotetypmod = (int)data[rc]; + CHECK_MESSAGE_LEN(nbytes, rc, sizeof(int)); + + /* Extract attribute count. */ + int natts = (int)data[rc]; + CHECK_MESSAGE_LEN(nbytes, rc, sizeof(int)); + + /* Extract hasoid flag. */ + bool hasoid = (bool)data[rc]; + CHECK_MESSAGE_LEN(nbytes, rc, sizeof(bool)); + + /* Extract attribute details. */ + MemoryContext oldcontext = MemoryContextSwitchTo(t_thrd.mem_cxt.cur_transaction_mem_cxt); + Form_pg_attribute *attrs = (Form_pg_attribute *)palloc(natts * sizeof(Form_pg_attribute)); + for (int i = 0; i < natts; ++i) { + attrs[i] = (Form_pg_attribute)palloc(sizeof(FormData_pg_attribute)); + int errorno = memcpy_s(attrs[i], sizeof(FormData_pg_attribute), &data[rc], sizeof(FormData_pg_attribute)); + securec_check_c(errorno, "", ""); + CHECK_MESSAGE_LEN(nbytes, rc, sizeof(FormData_pg_attribute)); + } + (void)MemoryContextSwitchTo(oldcontext); + + /* We should have read the whole message. */ + Assert(rc == nbytes); + + /* Construct TupleDesc. */ + TupleDesc tupledesc = CreateTupleDesc(natts, hasoid, attrs); + tupledesc = BlessTupleDesc(tupledesc); + + /* Create map if it doesn't exist already. */ + if (reader->typmodmap == NULL) { + HASHCTL ctl; + + ctl.keysize = sizeof(int); + ctl.entrysize = sizeof(RecordTypemodMap); + ctl.hcxt = t_thrd.mem_cxt.cur_transaction_mem_cxt; + reader->typmodmap = hash_create("typmodmap hashtable", 100, &ctl, HASH_ELEM | HASH_CONTEXT); + } + + /* Create map entry. */ + RecordTypemodMap *mapent = (RecordTypemodMap *)hash_search(reader->typmodmap, &remotetypmod, HASH_ENTER, &found); + if (found) { + ereport(ERROR, (errmsg("duplicate message for typmod %d", remotetypmod))); + } + mapent->localtypmod = tupledesc->tdtypmod; + ereport(DEBUG3, (errmsg("mapping remote typmod %d to local typmod %d", remotetypmod, tupledesc->tdtypmod))); +} + +/* + * Build a mapping indicating what remapping class applies to each attribute + * described by a tupledesc. + */ +static RemapInfo *BuildRemapInfo(TupleDesc tupledesc) +{ + Size size; + AttrNumber i; + bool noop = true; + + size = offsetof(RemapInfo, mapping) + sizeof(RemapClass) * tupledesc->natts; + RemapInfo *remapinfo = (RemapInfo *)MemoryContextAllocZero(TopMemoryContext, size); + remapinfo->natts = tupledesc->natts; + for (i = 0; i < tupledesc->natts; ++i) { + Form_pg_attribute attr = tupledesc->attrs[i]; + + if (attr->attisdropped) { + remapinfo->mapping[i] = TQUEUE_REMAP_NONE; + continue; + } + + remapinfo->mapping[i] = GetRemapClass(attr->atttypid); + if (remapinfo->mapping[i] != TQUEUE_REMAP_NONE) + noop = false; + } + + if (noop) { + pfree(remapinfo); + remapinfo = NULL; + } + + return remapinfo; +} + +/* + * Determine the remap class assocociated with a particular data type. + * + * Transient record types need to have the typmod applied on the sending side + * replaced with a value on the receiving side that has the same meaning. + * + * Arrays, range types, and all record types (including named composite types) + * need to searched for transient record values buried within them. + * Surprisingly, a walker is required even when the indicated type is a + * composite type, because the actual value may be a compatible transient + * record type. + */ +static RemapClass GetRemapClass(Oid type_id) +{ + RemapClass forceResult = TQUEUE_REMAP_NONE; + RemapClass innerResult = TQUEUE_REMAP_NONE; + + for (;;) { + /* Simple cases. */ + if (type_id == RECORDOID) { + innerResult = TQUEUE_REMAP_RECORD; + break; + } + if (type_id == RECORDARRAYOID) { + innerResult = TQUEUE_REMAP_ARRAY; + break; + } + + /* Otherwise, we need a syscache lookup to figure it out. */ + HeapTuple tup = SearchSysCache1((int)TYPEOID, ObjectIdGetDatum(type_id)); + if (!HeapTupleIsValid(tup)) + ereport(ERROR, (errmsg("cache lookup failed for type %u", type_id))); + Form_pg_type typ = (Form_pg_type)GETSTRUCT(tup); + /* Look through domains to underlying base type. */ + if (typ->typtype == TYPTYPE_DOMAIN) { + type_id = typ->typbasetype; + ReleaseSysCache(tup); + continue; + } + + /* + * Look through arrays to underlying base type, but the final return + * value must be either TQUEUE_REMAP_ARRAY or TQUEUE_REMAP_NONE. (If + * this is an array of integers, for example, we don't need to walk + * it.) + */ + if (OidIsValid(typ->typelem) && typ->typlen == -1) { + type_id = typ->typelem; + ReleaseSysCache(tup); + if (forceResult == TQUEUE_REMAP_NONE) { + forceResult = TQUEUE_REMAP_ARRAY; + } + continue; + } + + /* + * Similarly, look through ranges to the underlying base type, but the + * final return value must be either TQUEUE_REMAP_RANGE or + * TQUEUE_REMAP_NONE. + */ + if (typ->typtype == TYPTYPE_RANGE) { + ReleaseSysCache(tup); + if (forceResult == TQUEUE_REMAP_NONE) { + forceResult = TQUEUE_REMAP_RANGE; + } + type_id = get_range_subtype(type_id); + continue; + } + + /* Walk composite types. Nothing else needs special handling. */ + if (typ->typtype == TYPTYPE_COMPOSITE) { + innerResult = TQUEUE_REMAP_RECORD; + } + ReleaseSysCache(tup); + break; + } + + if (innerResult != TQUEUE_REMAP_NONE && forceResult != TQUEUE_REMAP_NONE) { + return forceResult; + } + return innerResult; +} + diff --git a/src/gausskernel/storage/access/common/indextuple.cpp b/src/gausskernel/storage/access/common/indextuple.cpp index 671dfaba7..0b07e3fcc 100644 --- a/src/gausskernel/storage/access/common/indextuple.cpp +++ b/src/gausskernel/storage/access/common/indextuple.cpp @@ -398,18 +398,16 @@ IndexTuple index_truncate_tuple(TupleDesc tupleDescriptor, IndexTuple olditup, i TupleDesc itupdesc = CreateTupleDescCopyConstr(tupleDescriptor); Datum values[INDEX_MAX_KEYS]; bool isnull[INDEX_MAX_KEYS]; - IndexTuple newitup; - int indnatts = tupleDescriptor->natts; - Assert(indnatts <= INDEX_MAX_KEYS); + Assert(tupleDescriptor->natts <= INDEX_MAX_KEYS); Assert(new_indnatts > 0); - Assert(new_indnatts < indnatts); + Assert(new_indnatts < tupleDescriptor->natts); index_deform_tuple(olditup, tupleDescriptor, values, isnull); /* form new tuple that will contain only key attributes */ itupdesc->natts = new_indnatts; - newitup = index_form_tuple(itupdesc, values, isnull); + IndexTuple newitup = index_form_tuple(itupdesc, values, isnull); newitup->t_tid = olditup->t_tid; FreeTupleDesc(itupdesc); diff --git a/src/gausskernel/storage/access/heap/heapam.cpp b/src/gausskernel/storage/access/heap/heapam.cpp index c0f81499c..db38af3b5 100644 --- a/src/gausskernel/storage/access/heap/heapam.cpp +++ b/src/gausskernel/storage/access/heap/heapam.cpp @@ -366,9 +366,11 @@ void heapgetpage(HeapScanDesc scan, BlockNumber page) scan->rs_vistuples[ntup++] = line_off; } - ereport(DEBUG1, - (errmsg( - "heapgetpage xid %lu ctid(%u,%d) valid %d", GetCurrentTransactionIdIfAny(), page, line_off, valid))); + if (SHOW_DEBUG_MESSAGE()) { + ereport(DEBUG1, + (errmsg( + "heapgetpage xid %lu ctid(%u,%d) valid %d", GetCurrentTransactionIdIfAny(), page, line_off, valid))); + } } } diff --git a/src/gausskernel/storage/access/transam/parallel.cpp b/src/gausskernel/storage/access/transam/parallel.cpp index a6e95823a..b40e234b7 100644 --- a/src/gausskernel/storage/access/transam/parallel.cpp +++ b/src/gausskernel/storage/access/transam/parallel.cpp @@ -1,1093 +1,1166 @@ -/* ------------------------------------------------------------------------- - * - * parallel.c - * Infrastructure for launching parallel workers - * - * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group - * Portions Copyright (c) 1994, Regents of the University of California - * - * IDENTIFICATION - * src/backend/access/transam/parallel.c - * - * ------------------------------------------------------------------------- - */ - -#include "postgres.h" - -#include "access/nbtree.h" -#include "access/parallel.h" -#include "access/xact.h" -#include "access/xlog.h" -#include "catalog/pg_enum.h" -#include "catalog/index.h" -#include "catalog/namespace.h" -#include "commands/async.h" -#include "executor/execParallel.h" -#include "libpq/libpq.h" -#include "libpq/pqsignal.h" -#include "libpq/pqformat.h" -#include "libpq/pqmq.h" -#include "miscadmin.h" -#include "optimizer/planner.h" -#include "pgstat.h" -#include "storage/ipc.h" -#include "storage/predicate.h" -#include "storage/sinval.h" -#include "storage/spin.h" -#include "tcop/tcopprot.h" -#include "utils/combocid.h" -#include "utils/guc.h" -#include "utils/inval.h" -#include "utils/memutils.h" -#include "utils/relmapper.h" -#include "utils/snapmgr.h" -#include "utils/typcache.h" - - -/* - * We don't want to waste a lot of memory on an error queue which, most of - * the time, will process only a handful of small messages. However, it is - * desirable to make it large enough that a typical ErrorResponse can be sent - * without blocking. That way, a worker that errors out can write the whole - * message into the queue and terminate without waiting for the user backend. - */ -#define PARALLEL_ERROR_QUEUE_SIZE 16384 - -/* - * List of internal parallel worker entry points. We need this for - * reasons explained in LookupParallelWorkerFunction(), below. - */ -static const struct { - const char *fn_name; - parallel_worker_main_type fn_addr; -} InternalParallelWorkers[] = { - { - "ParallelQueryMain", ParallelQueryMain - } -}; - -/* Private functions. */ -static void HandleParallelMessage(ParallelContext *pcxt, int i, StringInfo msg); -static void WaitForParallelWorkersToExit(ParallelContext *pcxt); -static parallel_worker_main_type LookupParallelWorkerFunction(const char *libraryname, const char *funcname); -static void ParallelWorkerShutdown(int code, Datum arg); - -/* - * Establish a new parallel context. This should be done after entering - * parallel mode, and (unless there is an error) the context should be - * destroyed before exiting the current subtransaction. - */ -ParallelContext *CreateParallelContext(const char *library_name, const char *function_name, int nworkers) -{ - /* It is unsafe to create a parallel context if not in parallel mode. */ - Assert(IsInParallelMode()); - - /* Number of workers should be non-negative. */ - Assert(nworkers >= 0); - - /* We might be running in a short-lived memory context. */ - MemoryContext oldcontext = MemoryContextSwitchTo(u_sess->top_transaction_mem_cxt); - - /* Initialize a new ParallelContext. */ - ParallelContext *pcxt = (ParallelContext *)palloc0(sizeof(ParallelContext)); - pcxt->subid = GetCurrentSubTransactionId(); - pcxt->nworkers = nworkers; - pcxt->library_name = pstrdup(library_name); - pcxt->function_name = pstrdup(function_name); - pcxt->error_context_stack = t_thrd.log_cxt.error_context_stack; - dlist_push_head(&t_thrd.bgworker_cxt.pcxt_list, &pcxt->node); - - /* Restore previous memory context. */ - (void)MemoryContextSwitchTo(oldcontext); - - return pcxt; -} - -/* - * Establish the dynamic shared memory segment for a parallel context and - * copy state and other bookkeeping information that will be needed by - * parallel workers into it. - */ -void InitializeParallelDSM(ParallelContext *pcxt) -{ - int i; - Snapshot transaction_snapshot = GetTransactionSnapshot(); - Snapshot active_snapshot = GetActiveSnapshot(); - - /* - * Create DSM and initialize with new table of contents. But if the user - * didn't request any workers, then don't bother creating a dynamic shared - * memory segment; instead, just use backend-private memory. - * - * Also, if we can't create a dynamic shared memory segment because the - * maximum number of segments have already been created, then fall back to - * backend-private memory, and plan not to use any workers. We hope this - * won't happen very often, but it's better to abandon the use of - * parallelism than to fail outright. - */ - pcxt->seg = dsm_create(); - - knl_u_parallel_context *cxt = (knl_u_parallel_context *)pcxt->seg; - MemoryContext oldcontext = MemoryContextSwitchTo(cxt->memCtx); - - /* Initialize fixed-size state in shared memory. */ - cxt->pwCtx->database_id = u_sess->proc_cxt.MyDatabaseId; - cxt->pwCtx->authenticated_user_id = GetAuthenticatedUserId(); - cxt->pwCtx->outer_user_id = GetCurrentRoleId(); - cxt->pwCtx->is_superuser = u_sess->attr.attr_common.session_auth_is_superuser; - GetUserIdAndSecContext(&cxt->pwCtx->current_user_id, &cxt->pwCtx->sec_context); - GetTempNamespaceState(&cxt->pwCtx->temp_namespace_id, &cxt->pwCtx->temp_toast_namespace_id); - cxt->pwCtx->parallel_master_pgproc = t_thrd.proc; - cxt->pwCtx->parallel_master_pid = t_thrd.proc_cxt.MyProcPid; - cxt->pwCtx->parallel_master_backend_id = t_thrd.proc_cxt.MyBackendId; - cxt->pwCtx->xact_ts = GetCurrentTransactionStartTimestamp(); - cxt->pwCtx->stmt_ts = GetCurrentStatementStartTimestamp(); - SpinLockInit(&cxt->pwCtx->mutex); - cxt->pwCtx->last_xlog_end = 0; - - /* We can skip the rest of this if we're not budgeting for any workers. */ - if (pcxt->nworkers > 0) { - /* Serialize combo CID state. */ - cxt->pwCtx->usedComboCids = u_sess->utils_cxt.usedComboCids; - cxt->pwCtx->comboCids = u_sess->utils_cxt.comboCids; - cxt->pwCtx->sizeComboCids = u_sess->utils_cxt.sizeComboCids; - cxt->pwCtx->comboHash = u_sess->utils_cxt.comboHash; - - /* Serialize transaction snapshot and active snapshot. */ - Size tsnaplen = EstimateSnapshotSpace(transaction_snapshot); - Size asnaplen = EstimateSnapshotSpace(active_snapshot); - - cxt->pwCtx->tsnapspace = (char *)palloc0(tsnaplen); - cxt->pwCtx->tsnapspace_len = tsnaplen; - SerializeSnapshot(transaction_snapshot, cxt->pwCtx->tsnapspace, tsnaplen); - cxt->pwCtx->asnapspace = (char *)palloc0(asnaplen); - cxt->pwCtx->asnapspace_len = asnaplen; - SerializeSnapshot(active_snapshot, cxt->pwCtx->asnapspace, asnaplen); - - Size searchPathLen = strlen(u_sess->attr.attr_common.namespace_search_path); - cxt->pwCtx->namespace_search_path = (char *)palloc(searchPathLen + 1); - int rc = strcpy_s(cxt->pwCtx->namespace_search_path, searchPathLen + 1, - u_sess->attr.attr_common.namespace_search_path); - securec_check_c(rc, "", ""); - - /* Serialize transaction state. */ - cxt->pwCtx->xactIsoLevel = u_sess->utils_cxt.XactIsoLevel; - cxt->pwCtx->xactDeferrable = u_sess->attr.attr_storage.XactDeferrable; - cxt->pwCtx->topTransactionId = GetTopTransactionIdIfAny(); - cxt->pwCtx->currentTransactionId = GetCurrentTransactionIdIfAny(); - cxt->pwCtx->currentCommandId = t_thrd.xact_cxt.currentCommandId; - cxt->pwCtx->nParallelCurrentXids = t_thrd.xact_cxt.nParallelCurrentXids; - cxt->pwCtx->ParallelCurrentXids = t_thrd.xact_cxt.ParallelCurrentXids; - - /* Serialize relmapper state. */ - cxt->pwCtx->active_shared_updates = u_sess->relmap_cxt.active_shared_updates; - cxt->pwCtx->active_local_updates = u_sess->relmap_cxt.active_local_updates; - - /* Allocate space for worker information. */ - pcxt->worker = (ParallelWorkerInfo *)palloc0(sizeof(ParallelWorkerInfo) * pcxt->nworkers); - - /* - * Establish error queues in dynamic shared memory. - * - * These queues should be used only for transmitting ErrorResponse, - * NoticeResponse, and NotifyResponse protocol messages. Tuple data - * should be transmitted via separate (possibly larger?) queues. - */ - cxt->pwCtx->errorQueue = (char *)palloc0(mul_size(pcxt->nworkers, PARALLEL_ERROR_QUEUE_SIZE)); - for (i = 0; i < pcxt->nworkers; ++i) { - shm_mq *mq = - shm_mq_create(cxt->pwCtx->errorQueue + i * PARALLEL_ERROR_QUEUE_SIZE, PARALLEL_ERROR_QUEUE_SIZE); - shm_mq_set_receiver(mq, t_thrd.proc); - pcxt->worker[i].error_mqh = shm_mq_attach(mq, pcxt->seg, NULL); - } - - /* - * Serialize entrypoint information. It's unsafe to pass function - * pointers across processes, as the function pointer may be different - * in each process in EXEC_BACKEND builds, so we always pass library - * and function name. (We use library name "postgres" for functions - * in the core backend.) - */ - Size lnamelen = strlen(pcxt->library_name); - cxt->pwCtx->library_name = (char *)palloc(lnamelen + 1); - rc = strcpy_s(cxt->pwCtx->library_name, lnamelen + 1, pcxt->library_name); - securec_check_c(rc, "", ""); - - Size fnamelen = strlen(pcxt->function_name); - cxt->pwCtx->function_name = (char *)palloc(fnamelen + 1); - rc = strcpy_s(cxt->pwCtx->function_name, fnamelen + 1, pcxt->function_name); - securec_check_c(rc, "", ""); - } - - /* Restore previous memory context. */ - (void)MemoryContextSwitchTo(oldcontext); -} - -/* - * Reinitialize the dynamic shared memory segment for a parallel context such - * that we could launch workers for it again. - */ -void ReinitializeParallelDSM(ParallelContext *pcxt) -{ - /* Wait for any old workers to exit. */ - if (pcxt->nworkers_launched > 0) { - WaitForParallelWorkersToFinish(pcxt); - WaitForParallelWorkersToExit(pcxt); - pcxt->nworkers_launched = 0; - if (pcxt->known_attached_workers) { - pfree(pcxt->known_attached_workers); - pcxt->known_attached_workers = NULL; - pcxt->nknown_attached_workers = 0; - } - } - - knl_u_parallel_context *cxt = (knl_u_parallel_context *)pcxt->seg; - - /* Reset a few bits of fixed parallel state to a clean state. */ - cxt->pwCtx->last_xlog_end = 0; - - /* Recreate error queues (if they exist). */ - if (pcxt->nworkers > 0) { - for (int i = 0; i < pcxt->nworkers; ++i) { - char *start = cxt->pwCtx->errorQueue + i * PARALLEL_ERROR_QUEUE_SIZE; - shm_mq *mq = shm_mq_create(start, PARALLEL_ERROR_QUEUE_SIZE); - shm_mq_set_receiver(mq, t_thrd.proc); - pcxt->worker[i].error_mqh = shm_mq_attach(mq, pcxt->seg, NULL); - } - } -} - -/* - * Launch parallel workers. - */ -void LaunchParallelWorkers(ParallelContext *pcxt) -{ - BackgroundWorker worker; - int i; - bool any_registrations_failed = false; - - /* Skip this if we have no workers. */ - if (pcxt->nworkers == 0) - return; - - /* If we do have workers, we'd better have a DSM segment. */ - Assert(pcxt->seg != NULL); - - /* We might be running in a short-lived memory context. */ - MemoryContext oldcontext = MemoryContextSwitchTo(u_sess->top_transaction_mem_cxt); - - /* Configure a worker. */ - int rc = memset_s(&worker, sizeof(worker), 0, sizeof(worker)); - securec_check(rc, "", ""); - rc = sprintf_s(worker.bgw_name, BGW_MAXLEN, "parallel worker for PID %lu", t_thrd.proc_cxt.MyProcPid); - securec_check_ss(rc, "", ""); - rc = sprintf_s(worker.bgw_type, BGW_MAXLEN, "parallel worker"); - securec_check_ss(rc, "", ""); - worker.bgw_flags = BGWORKER_SHMEM_ACCESS | BGWORKER_BACKEND_DATABASE_CONNECTION | BGWORKER_CLASS_PARALLEL; - worker.bgw_start_time = BgWorkerStart_ConsistentState; - worker.bgw_restart_time = BGW_NEVER_RESTART; - rc = strcpy_s(worker.bgw_library_name, BGW_MAXLEN, "postgres"); - securec_check(rc, "", ""); - rc = strcpy_s(worker.bgw_function_name, BGW_MAXLEN, "ParallelWorkerMain"); - securec_check(rc, "", ""); - worker.bgw_main_arg = PointerGetDatum(pcxt->seg); - worker.bgw_notify_pid = t_thrd.proc_cxt.MyProcPid; - worker.bgw_parallel_context = pcxt->seg; - - /* - * Start workers. - * - * The caller must be able to tolerate ending up with fewer workers than - * expected, so there is no need to throw an error here if registration - * fails. It wouldn't help much anyway, because registering the worker in - * no way guarantees that it will start up and initialize successfully. - */ - for (i = 0; i < pcxt->nworkers; ++i) { - rc = memcpy_s(worker.bgw_extra, BGW_EXTRALEN, &i, sizeof(int)); - securec_check(rc, "", ""); - if (!any_registrations_failed && RegisterDynamicBackgroundWorker(&worker, &pcxt->worker[i].bgwhandle)) { - shm_mq_set_handle(pcxt->worker[i].error_mqh, pcxt->worker[i].bgwhandle); - pcxt->nworkers_launched++; - } else { - /* - * If we weren't able to register the worker, then we've bumped up - * against the max_worker_processes limit, and future - * registrations will probably fail too, so arrange to skip them. - * But we still have to execute this code for the remaining slots - * to make sure that we forget about the error queues we budgeted - * for those workers. Otherwise, we'll wait for them to start, - * but they never will. - */ - any_registrations_failed = true; - pcxt->worker[i].bgwhandle = NULL; - shm_mq_detach(pcxt->worker[i].error_mqh); - pcxt->worker[i].error_mqh = NULL; - } - } - - /* - * Now that nworkers_launched has taken its final value, we can initialize - * known_attached_workers. - */ - if (pcxt->nworkers_launched > 0) { - pcxt->known_attached_workers = (bool *)palloc0(sizeof(bool) * pcxt->nworkers_launched); - pcxt->nknown_attached_workers = 0; - } - - /* Restore previous memory context. */ - (void)MemoryContextSwitchTo(oldcontext); -} - -/* - * Wait for all workers to attach to their error queues, and throw an error if - * any worker fails to do this. - * - * Callers can assume that if this function returns successfully, then the - * number of workers given by pcxt->nworkers_launched have initialized and - * attached to their error queues. Whether or not these workers are guaranteed - * to still be running depends on what code the caller asked them to run; - * this function does not guarantee that they have not exited. However, it - * does guarantee that any workers which exited must have done so cleanly and - * after successfully performing the work with which they were tasked. - * - * If this function is not called, then some of the workers that were launched - * may not have been started due to a fork() failure, or may have exited during - * early startup prior to attaching to the error queue, so nworkers_launched - * cannot be viewed as completely reliable. It will never be less than the - * number of workers which actually started, but it might be more. Any workers - * that failed to start will still be discovered by - * WaitForParallelWorkersToFinish and an error will be thrown at that time, - * provided that function is eventually reached. - * - * In general, the leader process should do as much work as possible before - * calling this function. fork() failures and other early-startup failures - * are very uncommon, and having the leader sit idle when it could be doing - * useful work is undesirable. However, if the leader needs to wait for - * all of its workers or for a specific worker, it may want to call this - * function before doing so. If not, it must make some other provision for - * the failure-to-start case, lest it wait forever. On the other hand, a - * leader which never waits for a worker that might not be started yet, or - * at least never does so prior to WaitForParallelWorkersToFinish(), need not - * call this function at all. - */ -void WaitForParallelWorkersToAttach(ParallelContext *pcxt) -{ - int i; - - /* Skip this if we have no launched workers. */ - if (pcxt->nworkers_launched == 0) - return; - - for (;;) { - /* - * This will process any parallel messages that are pending and it may - * also throw an error propagated from a worker. - */ - CHECK_FOR_INTERRUPTS(); - - for (i = 0; i < pcxt->nworkers_launched; ++i) { - shm_mq *mq = NULL; - int rc; - ThreadId pid; - - if (pcxt->known_attached_workers[i]) - continue; - - /* - * If error_mqh is NULL, then the worker has already exited - * cleanly. - */ - if (pcxt->worker[i].error_mqh == NULL) { - pcxt->known_attached_workers[i] = true; - ++pcxt->nknown_attached_workers; - continue; - } - - BgwHandleStatus status = GetBackgroundWorkerPid(pcxt->worker[i].bgwhandle, &pid); - if (status == BGWH_STARTED) { - /* Has the worker attached to the error queue? */ - mq = shm_mq_get_queue(pcxt->worker[i].error_mqh); - if (shm_mq_get_sender(mq) != NULL) { - /* Yes, so it is known to be attached. */ - pcxt->known_attached_workers[i] = true; - ++pcxt->nknown_attached_workers; - } - } else if (status == BGWH_STOPPED) { - /* - * If the worker stopped without attaching to the error queue, - * throw an error. - */ - mq = shm_mq_get_queue(pcxt->worker[i].error_mqh); - if (shm_mq_get_sender(mq) == NULL) - ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), - errmsg("parallel worker failed to initialize"), - errhint("More details may be available in the server log."))); - - pcxt->known_attached_workers[i] = true; - ++pcxt->nknown_attached_workers; - } else { - /* - * Worker not yet started, so we must wait. The postmaster - * will notify us if the worker's state changes. Our latch - * might also get set for some other reason, but if so we'll - * just end up waiting for the same worker again. - */ - rc = WaitLatch(&t_thrd.proc->procLatch, WL_LATCH_SET, -1); - if (rc & WL_LATCH_SET) { - ResetLatch(&t_thrd.proc->procLatch); - } - } - } - - /* If all workers are known to have started, we're done. */ - if (pcxt->nknown_attached_workers >= pcxt->nworkers_launched) { - Assert(pcxt->nknown_attached_workers == pcxt->nworkers_launched); - break; - } - } -} - -/* - * Wait for all workers to finish computing. - * - * Even if the parallel operation seems to have completed successfully, it's - * important to call this function afterwards. We must not miss any errors - * the workers may have thrown during the parallel operation, or any that they - * may yet throw while shutting down. - * - * Also, we want to update our notion of XactLastRecEnd based on worker - * feedback. - */ -void WaitForParallelWorkersToFinish(ParallelContext *pcxt) -{ - for (;;) { - bool anyone_alive = false; - int nfinished = 0; - int i; - - /* - * This will process any parallel messages that are pending, which may - * change the outcome of the loop that follows. It may also throw an - * error propagated from a worker. - */ - CHECK_FOR_INTERRUPTS(); - - for (i = 0; i < pcxt->nworkers_launched; ++i) { - /* - * If error_mqh is NULL, then the worker has already exited - * cleanly. If we have received a message through error_mqh from - * the worker, we know it started up cleanly, and therefore we're - * certain to be notified when it exits. - */ - if (pcxt->worker[i].error_mqh == NULL) - ++nfinished; - else if (pcxt->known_attached_workers[i]) { - anyone_alive = true; - break; - } - } - - if (!anyone_alive) { - /* If all workers are known to have finished, we're done. */ - if (nfinished >= pcxt->nworkers_launched) { - Assert(nfinished == pcxt->nworkers_launched); - break; - } - - /* - * We didn't detect any living workers, but not all workers are - * known to have exited cleanly. Either not all workers have - * launched yet, or maybe some of them failed to start or - * terminated abnormally. - */ - for (i = 0; i < pcxt->nworkers_launched; ++i) { - ThreadId pid; - - /* - * If the worker is BGWH_NOT_YET_STARTED or BGWH_STARTED, we - * should just keep waiting. If it is BGWH_STOPPED, then - * further investigation is needed. - */ - if (pcxt->worker[i].error_mqh == NULL || pcxt->worker[i].bgwhandle == NULL || - GetBackgroundWorkerPid(pcxt->worker[i].bgwhandle, &pid) != BGWH_STOPPED) - continue; - - /* - * Check whether the worker ended up stopped without ever - * attaching to the error queue. If so, the postmaster was - * unable to fork the worker or it exited without initializing - * properly. We must throw an error, since the caller may - * have been expecting the worker to do some work before - * exiting. - */ - shm_mq *mq = shm_mq_get_queue(pcxt->worker[i].error_mqh); - if (shm_mq_get_sender(mq) == NULL) - ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), - errmsg("parallel worker failed to initialize"), - errhint("More details may be available in the server log."))); - - /* - * The worker is stopped, but is attached to the error queue. - * Unless there's a bug somewhere, this will only happen when - * the worker writes messages and terminates after the - * CHECK_FOR_INTERRUPTS() near the top of this function and - * before the call to GetBackgroundWorkerPid(). In that case, - * or latch should have been set as well and the right things - * will happen on the next pass through the loop. - */ - } - } - - (void)WaitLatch(&t_thrd.proc->procLatch, WL_LATCH_SET, -1); - ResetLatch(&t_thrd.proc->procLatch); - } - - knl_u_parallel_context *cxt = (knl_u_parallel_context *)pcxt->seg; - if (cxt->pwCtx->last_xlog_end > t_thrd.xlog_cxt.XactLastRecEnd) - t_thrd.xlog_cxt.XactLastRecEnd = cxt->pwCtx->last_xlog_end; -} - -/* - * Wait for all workers to exit. - * - * This function ensures that workers have been completely shutdown. The - * difference between WaitForParallelWorkersToFinish and this function is - * that former just ensures that last message sent by worker backend is - * received by master backend whereas this ensures the complete shutdown. - */ -static void WaitForParallelWorkersToExit(ParallelContext *pcxt) -{ - /* Wait until the workers actually die. */ - for (int i = 0; i < pcxt->nworkers_launched; ++i) { - if (pcxt->worker == NULL || pcxt->worker[i].bgwhandle == NULL) { - continue; - } - - BgwHandleStatus status = WaitForBackgroundWorkerShutdown(pcxt->worker[i].bgwhandle); - /* - * If the postmaster kicked the bucket, we have no chance of cleaning - * up safely -- we won't be able to tell when our workers are actually - * dead. This doesn't necessitate a PANIC since they will all abort - * eventually, but we can't safely continue this session. - */ - if (status == BGWH_POSTMASTER_DIED) - ereport(FATAL, - (errcode(ERRCODE_ADMIN_SHUTDOWN), errmsg("postmaster exited during a parallel transaction"))); - - /* Release memory. */ - pfree(pcxt->worker[i].bgwhandle); - pcxt->worker[i].bgwhandle = NULL; - } -} - -/* - * Destroy a parallel context. - * - * If expecting a clean exit, you should use WaitForParallelWorkersToFinish() - * first, before calling this function. When this function is invoked, any - * remaining workers are forcibly killed; the dynamic shared memory segment - * is unmapped; and we then wait (uninterruptibly) for the workers to exit. - */ -void DestroyParallelContext(ParallelContext *pcxt) -{ - int i; - - /* - * Be careful about order of operations here! We remove the parallel - * context from the list before we do anything else; otherwise, if an - * error occurs during a subsequent step, we might try to nuke it again - * from AtEOXact_Parallel or AtEOSubXact_Parallel. - */ - dlist_delete(&pcxt->node); - - /* Kill each worker in turn, and forget their error queues. */ - if (pcxt->worker != NULL) { - for (i = 0; i < pcxt->nworkers_launched; ++i) { - if (pcxt->worker[i].error_mqh != NULL) { - TerminateBackgroundWorker(pcxt->worker[i].bgwhandle); - - shm_mq_detach(pcxt->worker[i].error_mqh); - pcxt->worker[i].error_mqh = NULL; - } - } - } - - /* - * If this parallel context is actually in backend-private memory rather - * than shared memory, free that memory instead. - */ - if (pcxt->private_memory != NULL) { - pfree(pcxt->private_memory); - pcxt->private_memory = NULL; - } - - /* - * We can't finish transaction commit or abort until all of the workers - * have exited. This means, in particular, that we can't respond to - * interrupts at this stage. - */ - HOLD_INTERRUPTS(); - WaitForParallelWorkersToExit(pcxt); - RESUME_INTERRUPTS(); - - /* Free the worker array itself. */ - if (pcxt->worker != NULL) { - pfree(pcxt->worker); - pcxt->worker = NULL; - } - - /* - * If we have allocated a shared memory segment, detach it. This will - * implicitly detach the error queues, and any other shared memory queues, - * stored there. - */ - if (pcxt->seg != NULL) { - dsm_detach(&(pcxt->seg)); - pcxt->seg = NULL; - } - - /* Free memory. */ - pfree(pcxt->library_name); - pfree(pcxt->function_name); - pfree(pcxt); -} - -/* - * Are there any parallel contexts currently active? - */ -bool ParallelContextActive(void) -{ - return !dlist_is_empty(&t_thrd.bgworker_cxt.pcxt_list); -} - -/* - * Handle receipt of an interrupt indicating a parallel worker message. - * - * Note: this is called within a signal handler! All we can do is set a flag - * that will cause the next CHECK_FOR_INTERRUPTS() to invoke HandleParallelMessages(). - */ -void HandleParallelMessageInterrupt(void) -{ - InterruptPending = true; - t_thrd.bgworker_cxt.ParallelMessagePending = true; - SetLatch(&t_thrd.proc->procLatch); -} - -/* - * Handle any queued protocol messages received from parallel workers. - */ -void HandleParallelMessages(void) -{ - dlist_iter iter; - - /* - * This is invoked from ProcessInterrupts(), and since some of the - * functions it calls contain CHECK_FOR_INTERRUPTS(), there is a potential - * for recursive calls if more signals are received while this runs. It's - * unclear that recursive entry would be safe, and it doesn't seem useful - * even if it is safe, so let's block interrupts until done. - */ - HOLD_INTERRUPTS(); - - /* - * Moreover, CurrentMemoryContext might be pointing almost anywhere. We - * don't want to risk leaking data into long-lived contexts, so let's do - * our work here in a private context that we can reset on each use. - */ - if (t_thrd.bgworker_cxt.hpm_context == NULL) /* first time through? */ - t_thrd.bgworker_cxt.hpm_context = - AllocSetContextCreate(TopMemoryContext, "HandleParallelMessages", ALLOCSET_DEFAULT_SIZES); - else - MemoryContextReset(t_thrd.bgworker_cxt.hpm_context); - - MemoryContext oldcontext = MemoryContextSwitchTo(t_thrd.bgworker_cxt.hpm_context); - - /* OK to process messages. Reset the flag saying there are more to do. */ - t_thrd.bgworker_cxt.ParallelMessagePending = false; - - dlist_foreach(iter, &t_thrd.bgworker_cxt.pcxt_list) - { - ParallelContext *pcxt = dlist_container(ParallelContext, node, iter.cur); - if (pcxt->worker == NULL) - continue; - - for (int i = 0; i < pcxt->nworkers_launched; ++i) { - /* - * Read as many messages as we can from each worker, but stop when - * either (1) the worker's error queue goes away, which can happen - * if we receive a Terminate message from the worker; or (2) no - * more messages can be read from the worker without blocking. - */ - while (pcxt->worker[i].error_mqh != NULL) { - Size nbytes; - void *data = NULL; - - shm_mq_result res = shm_mq_receive(pcxt->worker[i].error_mqh, &nbytes, &data, true); - if (res == SHM_MQ_WOULD_BLOCK) { - break; - } else if (res == SHM_MQ_SUCCESS) { - StringInfoData msg; - - initStringInfo(&msg); - appendBinaryStringInfo(&msg, (const char *)data, nbytes); - HandleParallelMessage(pcxt, i, &msg); - pfree(msg.data); - } else { - ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), - errmsg("lost connection to parallel worker"))); - } - } - } - } - - (void)MemoryContextSwitchTo(oldcontext); - - /* Might as well clear the context on our way out */ - MemoryContextReset(t_thrd.bgworker_cxt.hpm_context); - - RESUME_INTERRUPTS(); -} - -/* - * Handle a single protocol message received from a single parallel worker. - */ -static void HandleParallelMessage(ParallelContext *pcxt, int i, StringInfo msg) -{ - if (pcxt->known_attached_workers != NULL && !pcxt->known_attached_workers[i]) { - pcxt->known_attached_workers[i] = true; - pcxt->nknown_attached_workers++; - } - - char msgtype = (char)pq_getmsgbyte(msg); - - switch (msgtype) { - case 'K': /* BackendKeyData */ - { - ThreadId pid = pq_getmsgint64(msg); - - (void)pq_getmsgint64(msg); /* discard cancel key */ - pq_getmsgend(msg); - pcxt->worker[i].pid = pid; - break; - } - - case 'E': /* ErrorResponse */ - case 'N': /* NoticeResponse */ - { - ErrorData edata; - - /* Parse ErrorResponse or NoticeResponse. */ - pq_parse_errornotice(msg, &edata); - - /* Death of a worker isn't enough justification for suicide. */ - edata.elevel = Min(edata.elevel, ERROR); - - /* - * If desired, add a context line to show that this is a - * message propagated from a parallel worker. Otherwise, it - * can sometimes be confusing to understand what actually - * happened. (We don't do this in FORCE_PARALLEL_REGRESS mode - * because it causes test-result instability depending on - * whether a parallel worker is actually used or not.) - */ - if (u_sess->attr.attr_sql.force_parallel_mode != FORCE_PARALLEL_REGRESS) { - if (edata.context) { - /* 1 for '\0', 1 for '\n' */ - Size len = strlen(edata.context) + strlen("parallel worker") + 2; - edata.context = (char *)palloc(len); - int rc = sprintf_s(edata.context, len, "%s\n%s", edata.context, "parallel worker"); - securec_check_ss(rc, "", ""); - } else { - edata.context = pstrdup(_("parallel worker")); - } - } - - /* - * Context beyond that should use the error context callbacks - * that were in effect when the ParallelContext was created, - * not the current ones. - */ - ErrorContextCallback *save_error_context_stack = t_thrd.log_cxt.error_context_stack; - t_thrd.log_cxt.error_context_stack = pcxt->error_context_stack; - - /* Rethrow error or print notice. */ - ThrowErrorData(&edata); - - /* Not an error, so restore previous context stack. */ - t_thrd.log_cxt.error_context_stack = save_error_context_stack; - - break; - } - - case 'A': /* NotifyResponse */ - { - /* Propagate NotifyResponse. */ - uint32 pid = pq_getmsgint(msg, 4); - const char *channel = pq_getmsgrawstring(msg); - const char *payload = pq_getmsgrawstring(msg); - pq_endmessage(msg); - - NotifyMyFrontEnd(channel, payload, pid); - - break; - } - - case 'X': /* Terminate, indicating clean exit */ - { - shm_mq_detach(pcxt->worker[i].error_mqh); - pcxt->worker[i].error_mqh = NULL; - break; - } - - default: { - ereport(ERROR, - (errmsg("unrecognized message type received from parallel worker: %c (message length %d bytes)", - msgtype, msg->len))); - } - } -} - -/* - * End-of-subtransaction cleanup for parallel contexts. - * - * Currently, it's forbidden to enter or leave a subtransaction while - * parallel mode is in effect, so we could just blow away everything. But - * we may want to relax that restriction in the future, so this code - * contemplates that there may be multiple subtransaction IDs in pcxt_list. - */ -void AtEOSubXact_Parallel(bool isCommit, SubTransactionId mySubId) -{ - while (!dlist_is_empty(&t_thrd.bgworker_cxt.pcxt_list)) { - ParallelContext *pcxt = dlist_head_element(ParallelContext, node, &t_thrd.bgworker_cxt.pcxt_list); - if (pcxt->subid != mySubId) - break; - if (isCommit) - ereport(WARNING, (errmsg("leaked parallel context"))); - DestroyParallelContext(pcxt); - } -} - -/* - * End-of-transaction cleanup for parallel contexts. - */ -void AtEOXact_Parallel(bool isCommit) -{ - while (!dlist_is_empty(&t_thrd.bgworker_cxt.pcxt_list)) { - ParallelContext *pcxt = dlist_head_element(ParallelContext, node, &t_thrd.bgworker_cxt.pcxt_list); - if (isCommit) - ereport(WARNING, (errmsg("leaked parallel context"))); - DestroyParallelContext(pcxt); - } -} - -/* - * Main entrypoint for parallel workers. - */ -void ParallelWorkerMain(Datum main_arg) -{ - StringInfoData msgbuf; - - knl_u_parallel_context *ctx = (knl_u_parallel_context *)DatumGetPointer(main_arg); - - /* Set flag to indicate that we're initializing a parallel worker. */ - t_thrd.bgworker_cxt.InitializingParallelWorker = true; - - /* Establish signal handlers. */ - gspqsignal(SIGTERM, die); - BackgroundWorkerUnblockSignals(); - - /* Determine and set our parallel worker number. */ - Assert(t_thrd.bgworker_cxt.ParallelWorkerNumber == -1); - int rc = memcpy_s(&t_thrd.bgworker_cxt.ParallelWorkerNumber, sizeof(int), - t_thrd.bgworker_cxt.my_bgworker_entry->bgw_extra, sizeof(int)); - securec_check(rc, "", ""); - - /* Set up a memory context to work in, just for cleanliness. */ - CurrentMemoryContext = AllocSetContextCreate(TopMemoryContext, "Parallel worker", ALLOCSET_DEFAULT_SIZES); - - /* Arrange to signal the leader if we exit. */ - on_shmem_exit(ParallelWorkerShutdown, (Datum)0); - - /* - * Now we can find and attach to the error queue provided for us. That's - * good, because until we do that, any errors that happen here will not be - * reported back to the process that requested that this worker be - * launched. - */ - char *error_queue_space = ctx->pwCtx->errorQueue; - shm_mq *mq = (shm_mq *)(error_queue_space + t_thrd.bgworker_cxt.ParallelWorkerNumber * PARALLEL_ERROR_QUEUE_SIZE); - shm_mq_set_sender(mq, t_thrd.proc); - shm_mq_handle *mqh = shm_mq_attach(mq, ctx, NULL); - pq_redirect_to_shm_mq(mqh); - pq_set_parallel_master(ctx->pwCtx->parallel_master_pid, ctx->pwCtx->parallel_master_backend_id); - - /* - * Send a BackendKeyData message to the process that initiated parallelism - * so that it has access to our PID before it receives any other messages - * from us. Our cancel key is sent, too, since that's the way the - * protocol message is defined, but it won't actually be used for anything - * in this case. - */ - pq_beginmessage(&msgbuf, 'K'); - pq_sendint64(&msgbuf, t_thrd.proc_cxt.MyProcPid); - pq_sendint64(&msgbuf, t_thrd.proc_cxt.MyCancelKey); - pq_endmessage(&msgbuf); - - /* - * Hooray! Primary initialization is complete. Now, we need to set up our - * backend-local state to match the original backend. - */ - /* - * Restore transaction and statement start-time timestamps. This must - * happen before anything that would start a transaction, else asserts in - * xact.c will fire. - */ - SetParallelStartTimestamps(ctx->pwCtx->xact_ts, ctx->pwCtx->stmt_ts); - - /* - * Identify the entry point to be called. In theory this could result in - * loading an additional library, though most likely the entry point is in - * the core backend or in a library we just loaded. - */ - parallel_worker_main_type entrypt = - LookupParallelWorkerFunction(ctx->pwCtx->library_name, ctx->pwCtx->function_name); - - /* Restore database connection. */ - BackgroundWorkerInitializeConnectionByOid(ctx->pwCtx->database_id, ctx->pwCtx->authenticated_user_id, 0); - - /* - * Set the client encoding to the database encoding, since that is what - * the leader will expect. - */ - (void)SetClientEncoding(GetDatabaseEncoding()); - - /* Crank up a transaction state appropriate to a parallel worker. */ - StartParallelWorkerTransaction(ctx->pwCtx); - - /* Restore combo CID state. */ - u_sess->utils_cxt.usedComboCids = ctx->pwCtx->usedComboCids; - u_sess->utils_cxt.comboCids = ctx->pwCtx->comboCids; - u_sess->utils_cxt.sizeComboCids = ctx->pwCtx->sizeComboCids; - u_sess->utils_cxt.comboHash = ctx->pwCtx->comboHash; - - /* Restore namespace search path */ - u_sess->attr.attr_common.namespace_search_path = ctx->pwCtx->namespace_search_path; - - /* Restore transaction snapshot. */ - RestoreTransactionSnapshot(RestoreSnapshot(ctx->pwCtx->tsnapspace, ctx->pwCtx->tsnapspace_len), - ctx->pwCtx->parallel_master_pgproc); - /* Restore active snapshot. */ - PushActiveSnapshot(RestoreSnapshot(ctx->pwCtx->asnapspace, ctx->pwCtx->asnapspace_len)); - - /* - * We've changed which tuples we can see, and must therefore invalidate - * system caches. - */ - InvalidateSystemCaches(); - - /* - * Restore current role id. Skip verifying whether session user is - * allowed to become this role and blindly restore the leader's state for - * current role. - */ - SetCurrentRoleId(ctx->pwCtx->outer_user_id, ctx->pwCtx->is_superuser); - - /* Restore user ID and security context. */ - SetUserIdAndSecContext(ctx->pwCtx->current_user_id, ctx->pwCtx->sec_context); - - /* Restore temp-namespace state to ensure search path matches leader's. */ - SetTempNamespaceState(ctx->pwCtx->temp_namespace_id, ctx->pwCtx->temp_toast_namespace_id); - - /* Restore relmapper state. */ - u_sess->relmap_cxt.active_shared_updates = ctx->pwCtx->active_shared_updates; - u_sess->relmap_cxt.active_local_updates = ctx->pwCtx->active_local_updates; - - /* - * We've initialized all of our state now; nothing should change - * hereafter. - */ - t_thrd.bgworker_cxt.InitializingParallelWorker = false; - EnterParallelMode(); - - /* - * Time to do the real work: invoke the caller-supplied code. - */ - entrypt(ctx); - - /* Must exit parallel mode to pop active snapshot. */ - ExitParallelMode(); - - /* Must pop active snapshot so snapmgr.c doesn't complain. */ - PopActiveSnapshot(); - - /* Shut down the parallel-worker transaction. */ - EndParallelWorkerTransaction(); - - /* Report success. */ - pq_putmessage('X', NULL, 0); -} - -/* - * Update shared memory with the ending location of the last WAL record we - * wrote, if it's greater than the value already stored there. - */ -void ParallelWorkerReportLastRecEnd(XLogRecPtr last_xlog_end) -{ - knl_u_parallel_context *ctx = (knl_u_parallel_context *)t_thrd.bgworker_cxt.my_bgworker_entry->bgw_parallel_context; - Assert(ctx->pwCtx != NULL); - SpinLockAcquire(&ctx->pwCtx->mutex); - if (ctx->pwCtx->last_xlog_end < last_xlog_end) { - ctx->pwCtx->last_xlog_end = last_xlog_end; - } - SpinLockRelease(&ctx->pwCtx->mutex); -} - -/* - * Make sure the leader tries to read from our error queue one more time. - * This guards against the case where we exit uncleanly without sending an - * ErrorResponse to the leader, for example because some code calls proc_exit - * directly. - */ -static void ParallelWorkerShutdown(int code, Datum arg) -{ - (void)SendProcSignal(t_thrd.msqueue_cxt.pq_mq_parallel_master_pid, PROCSIG_PARALLEL_MESSAGE, - t_thrd.msqueue_cxt.pq_mq_parallel_master_backend_id); -} - -/* - * Look up (and possibly load) a parallel worker entry point function. - * - * For functions contained in the core code, we use library name "postgres" - * and consult the InternalParallelWorkers array. External functions are - * looked up, and loaded if necessary, using load_external_function(). - * - * The point of this is to pass function names as strings across process - * boundaries. We can't pass actual function addresses because of the - * possibility that the function has been loaded at a different address - * in a different process. This is obviously a hazard for functions in - * loadable libraries, but it can happen even for functions in the core code - * on platforms using EXEC_BACKEND (e.g., Windows). - * - * At some point it might be worthwhile to get rid of InternalParallelWorkers[] - * in favor of applying load_external_function() for core functions too; - * but that raises portability issues that are not worth addressing now. - */ -static parallel_worker_main_type LookupParallelWorkerFunction(const char *libraryname, const char *funcname) -{ - /* - * If the function is to be loaded from postgres itself, search the - * InternalParallelWorkers array. - */ - if (strcmp(libraryname, "postgres") == 0) { - for (size_t i = 0; i < lengthof(InternalParallelWorkers); i++) { - if (strcmp(InternalParallelWorkers[i].fn_name, funcname) == 0) - return InternalParallelWorkers[i].fn_addr; - } - - ereport(ERROR, (errmsg("internal function \"%s\" not found", funcname))); - } - - ereport(ERROR, (errmsg("library\"%s\" function \"%s\" not supported", libraryname, funcname))); - return NULL; -} - +/* ------------------------------------------------------------------------- + * + * parallel.c + * Infrastructure for launching parallel workers + * + * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * IDENTIFICATION + * src/backend/access/transam/parallel.c + * + * ------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "access/nbtree.h" +#include "access/parallel.h" +#include "access/xact.h" +#include "access/xlog.h" +#include "catalog/pg_enum.h" +#include "catalog/index.h" +#include "catalog/namespace.h" +#include "commands/async.h" +#include "executor/execParallel.h" +#include "libpq/libpq.h" +#include "libpq/pqsignal.h" +#include "libpq/pqformat.h" +#include "libpq/pqmq.h" +#include "miscadmin.h" +#include "optimizer/planner.h" +#include "pgstat.h" +#include "storage/ipc.h" +#include "storage/predicate.h" +#include "storage/sinval.h" +#include "storage/spin.h" +#include "tcop/tcopprot.h" +#include "utils/combocid.h" +#include "utils/guc.h" +#include "utils/inval.h" +#include "utils/memutils.h" +#include "utils/relmapper.h" +#include "utils/snapmgr.h" +#include "utils/typcache.h" +#ifdef __USE_NUMA +#include +#endif + +/* + * We don't want to waste a lot of memory on an error queue which, most of + * the time, will process only a handful of small messages. However, it is + * desirable to make it large enough that a typical ErrorResponse can be sent + * without blocking. That way, a worker that errors out can write the whole + * message into the queue and terminate without waiting for the user backend. + */ +#define PARALLEL_ERROR_QUEUE_SIZE 16384 + +/* + * List of internal parallel worker entry points. We need this for + * reasons explained in LookupParallelWorkerFunction(), below. + */ +static const struct { + const char *fn_name; + parallel_worker_main_type fn_addr; +} InternalParallelWorkers[] = { + { + "ParallelQueryMain", ParallelQueryMain + } +}; + +/* Private functions. */ +static void HandleParallelMessage(ParallelContext *pcxt, int i, StringInfo msg); +static void WaitForParallelWorkersToExit(ParallelContext *pcxt); +static parallel_worker_main_type LookupParallelWorkerFunction(const char *libraryname, const char *funcname); +static void ParallelWorkerShutdown(int code, Datum arg); +#ifdef __USE_NUMA +static bool SaveCpuAffinity(cpu_set_t **cpuset); +static void GetCurrentNumaNode(ParallelInfoContext *pcxt); +#endif + +/* + * Establish a new parallel context. This should be done after entering + * parallel mode, and (unless there is an error) the context should be + * destroyed before exiting the current subtransaction. + */ +ParallelContext *CreateParallelContext(const char *library_name, const char *function_name, int nworkers) +{ + /* It is unsafe to create a parallel context if not in parallel mode. */ + Assert(IsInParallelMode()); + + /* Number of workers should be non-negative. */ + Assert(nworkers >= 0); + + /* We might be running in a short-lived memory context. */ + MemoryContext oldcontext = MemoryContextSwitchTo(u_sess->top_transaction_mem_cxt); + + /* Initialize a new ParallelContext. */ + ParallelContext *pcxt = (ParallelContext *)palloc0(sizeof(ParallelContext)); + pcxt->subid = GetCurrentSubTransactionId(); + pcxt->nworkers = nworkers; + pcxt->library_name = pstrdup(library_name); + pcxt->function_name = pstrdup(function_name); + pcxt->error_context_stack = t_thrd.log_cxt.error_context_stack; + dlist_push_head(&t_thrd.bgworker_cxt.pcxt_list, &pcxt->node); + + /* Restore previous memory context. */ + (void)MemoryContextSwitchTo(oldcontext); + + return pcxt; +} + +/* + * Establish the dynamic shared memory segment for a parallel context and + * copy state and other bookkeeping information that will be needed by + * parallel workers into it. + */ +void InitializeParallelDSM(ParallelContext *pcxt) +{ + int i; + Snapshot transaction_snapshot = GetTransactionSnapshot(); + Snapshot active_snapshot = GetActiveSnapshot(); + + /* + * Create DSM and initialize with new table of contents. But if the user + * didn't request any workers, then don't bother creating a dynamic shared + * memory segment; instead, just use backend-private memory. + * + * Also, if we can't create a dynamic shared memory segment because the + * maximum number of segments have already been created, then fall back to + * backend-private memory, and plan not to use any workers. We hope this + * won't happen very often, but it's better to abandon the use of + * parallelism than to fail outright. + */ + pcxt->seg = dsm_create(); + + knl_u_parallel_context *cxt = (knl_u_parallel_context *)pcxt->seg; + MemoryContext oldcontext = MemoryContextSwitchTo(cxt->memCtx); + + /* Initialize fixed-size state in shared memory. */ + cxt->pwCtx->database_id = u_sess->proc_cxt.MyDatabaseId; + cxt->pwCtx->authenticated_user_id = GetAuthenticatedUserId(); + cxt->pwCtx->outer_user_id = GetCurrentRoleId(); + cxt->pwCtx->is_superuser = u_sess->attr.attr_common.session_auth_is_superuser; + GetUserIdAndSecContext(&cxt->pwCtx->current_user_id, &cxt->pwCtx->sec_context); + GetTempNamespaceState(&cxt->pwCtx->temp_namespace_id, &cxt->pwCtx->temp_toast_namespace_id); + cxt->pwCtx->parallel_master_pgproc = t_thrd.proc; + cxt->pwCtx->parallel_master_pid = t_thrd.proc_cxt.MyProcPid; + cxt->pwCtx->parallel_master_backend_id = t_thrd.proc_cxt.MyBackendId; + cxt->pwCtx->xact_ts = GetCurrentTransactionStartTimestamp(); + cxt->pwCtx->stmt_ts = GetCurrentStatementStartTimestamp(); + SpinLockInit(&cxt->pwCtx->mutex); + cxt->pwCtx->last_xlog_end = 0; + + /* We can skip the rest of this if we're not budgeting for any workers. */ + if (pcxt->nworkers > 0) { + /* Serialize combo CID state. */ + cxt->pwCtx->usedComboCids = u_sess->utils_cxt.usedComboCids; + cxt->pwCtx->comboCids = u_sess->utils_cxt.comboCids; + cxt->pwCtx->sizeComboCids = u_sess->utils_cxt.sizeComboCids; + cxt->pwCtx->comboHash = u_sess->utils_cxt.comboHash; + + /* Serialize transaction snapshot and active snapshot. */ + Size tsnaplen = EstimateSnapshotSpace(transaction_snapshot); + Size asnaplen = EstimateSnapshotSpace(active_snapshot); + + cxt->pwCtx->tsnapspace = (char *)palloc0(tsnaplen); + cxt->pwCtx->tsnapspace_len = tsnaplen; + SerializeSnapshot(transaction_snapshot, cxt->pwCtx->tsnapspace, tsnaplen); + cxt->pwCtx->asnapspace = (char *)palloc0(asnaplen); + cxt->pwCtx->asnapspace_len = asnaplen; + SerializeSnapshot(active_snapshot, cxt->pwCtx->asnapspace, asnaplen); + + Size searchPathLen = strlen(u_sess->attr.attr_common.namespace_search_path); + cxt->pwCtx->namespace_search_path = (char *)palloc(searchPathLen + 1); + int rc = strcpy_s(cxt->pwCtx->namespace_search_path, searchPathLen + 1, + u_sess->attr.attr_common.namespace_search_path); + securec_check_c(rc, "", ""); + + /* Serialize transaction state. */ + cxt->pwCtx->xactIsoLevel = u_sess->utils_cxt.XactIsoLevel; + cxt->pwCtx->xactDeferrable = u_sess->attr.attr_storage.XactDeferrable; + cxt->pwCtx->topTransactionId = GetTopTransactionIdIfAny(); + cxt->pwCtx->currentTransactionId = GetCurrentTransactionIdIfAny(); + cxt->pwCtx->currentCommandId = t_thrd.xact_cxt.currentCommandId; + cxt->pwCtx->nParallelCurrentXids = t_thrd.xact_cxt.nParallelCurrentXids; + cxt->pwCtx->ParallelCurrentXids = t_thrd.xact_cxt.ParallelCurrentXids; + + /* Serialize relmapper state. */ + cxt->pwCtx->active_shared_updates = u_sess->relmap_cxt.active_shared_updates; + cxt->pwCtx->active_local_updates = u_sess->relmap_cxt.active_local_updates; + + /* Allocate space for worker information. */ + pcxt->worker = (ParallelWorkerInfo *)palloc0(sizeof(ParallelWorkerInfo) * pcxt->nworkers); + +#ifdef __USE_NUMA + GetCurrentNumaNode(cxt->pwCtx); +#endif + /* + * Establish error queues in dynamic shared memory. + * + * These queues should be used only for transmitting ErrorResponse, + * NoticeResponse, and NotifyResponse protocol messages. Tuple data + * should be transmitted via separate (possibly larger?) queues. + */ + cxt->pwCtx->errorQueue = (char *)palloc0(mul_size(pcxt->nworkers, PARALLEL_ERROR_QUEUE_SIZE)); + for (i = 0; i < pcxt->nworkers; ++i) { + shm_mq *mq = + shm_mq_create(cxt->pwCtx->errorQueue + i * PARALLEL_ERROR_QUEUE_SIZE, PARALLEL_ERROR_QUEUE_SIZE); + shm_mq_set_receiver(mq, t_thrd.proc); + pcxt->worker[i].error_mqh = shm_mq_attach(mq, pcxt->seg, NULL); + } + + /* + * Serialize entrypoint information. It's unsafe to pass function + * pointers across processes, as the function pointer may be different + * in each process in EXEC_BACKEND builds, so we always pass library + * and function name. (We use library name "postgres" for functions + * in the core backend.) + */ + Size lnamelen = strlen(pcxt->library_name); + cxt->pwCtx->library_name = (char *)palloc(lnamelen + 1); + rc = strcpy_s(cxt->pwCtx->library_name, lnamelen + 1, pcxt->library_name); + securec_check_c(rc, "", ""); + + Size fnamelen = strlen(pcxt->function_name); + cxt->pwCtx->function_name = (char *)palloc(fnamelen + 1); + rc = strcpy_s(cxt->pwCtx->function_name, fnamelen + 1, pcxt->function_name); + securec_check_c(rc, "", ""); + } + + /* Restore previous memory context. */ + (void)MemoryContextSwitchTo(oldcontext); +} + +/* + * Reinitialize the dynamic shared memory segment for a parallel context such + * that we could launch workers for it again. + */ +void ReinitializeParallelDSM(ParallelContext *pcxt) +{ + /* Wait for any old workers to exit. */ + if (pcxt->nworkers_launched > 0) { + WaitForParallelWorkersToFinish(pcxt); + WaitForParallelWorkersToExit(pcxt); + pcxt->nworkers_launched = 0; + if (pcxt->known_attached_workers) { + pfree(pcxt->known_attached_workers); + pcxt->known_attached_workers = NULL; + pcxt->nknown_attached_workers = 0; + } + } + + knl_u_parallel_context *cxt = (knl_u_parallel_context *)pcxt->seg; + + /* Reset a few bits of fixed parallel state to a clean state. */ + cxt->pwCtx->last_xlog_end = 0; + + /* Recreate error queues (if they exist). */ + if (pcxt->nworkers > 0) { + for (int i = 0; i < pcxt->nworkers; ++i) { + char *start = cxt->pwCtx->errorQueue + i * PARALLEL_ERROR_QUEUE_SIZE; + shm_mq *mq = shm_mq_create(start, PARALLEL_ERROR_QUEUE_SIZE); + shm_mq_set_receiver(mq, t_thrd.proc); + pcxt->worker[i].error_mqh = shm_mq_attach(mq, pcxt->seg, NULL); + } + } +} + +/* + * Launch parallel workers. + */ +void LaunchParallelWorkers(ParallelContext *pcxt) +{ + BackgroundWorker worker; + int i; + bool any_registrations_failed = false; + + /* Skip this if we have no workers. */ + if (pcxt->nworkers == 0) + return; + + /* If we do have workers, we'd better have a DSM segment. */ + Assert(pcxt->seg != NULL); + + /* We might be running in a short-lived memory context. */ + MemoryContext oldcontext = MemoryContextSwitchTo(u_sess->top_transaction_mem_cxt); + + /* Configure a worker. */ + int rc = memset_s(&worker, sizeof(worker), 0, sizeof(worker)); + securec_check(rc, "", ""); + rc = sprintf_s(worker.bgw_name, BGW_MAXLEN, "parallel worker for PID %lu", t_thrd.proc_cxt.MyProcPid); + securec_check_ss(rc, "", ""); + rc = sprintf_s(worker.bgw_type, BGW_MAXLEN, "parallel worker"); + securec_check_ss(rc, "", ""); + worker.bgw_flags = BGWORKER_SHMEM_ACCESS | BGWORKER_BACKEND_DATABASE_CONNECTION | BGWORKER_CLASS_PARALLEL; + worker.bgw_start_time = BgWorkerStart_ConsistentState; + worker.bgw_restart_time = BGW_NEVER_RESTART; + rc = strcpy_s(worker.bgw_library_name, BGW_MAXLEN, "postgres"); + securec_check(rc, "", ""); + rc = strcpy_s(worker.bgw_function_name, BGW_MAXLEN, "ParallelWorkerMain"); + securec_check(rc, "", ""); + worker.bgw_main_arg = PointerGetDatum(pcxt->seg); + worker.bgw_notify_pid = t_thrd.proc_cxt.MyProcPid; + worker.bgw_parallel_context = pcxt->seg; + + /* + * Start workers. + * + * The caller must be able to tolerate ending up with fewer workers than + * expected, so there is no need to throw an error here if registration + * fails. It wouldn't help much anyway, because registering the worker in + * no way guarantees that it will start up and initialize successfully. + */ + for (i = 0; i < pcxt->nworkers; ++i) { + rc = memcpy_s(worker.bgw_extra, BGW_EXTRALEN, &i, sizeof(int)); + securec_check(rc, "", ""); + if (!any_registrations_failed && RegisterDynamicBackgroundWorker(&worker, &pcxt->worker[i].bgwhandle)) { + shm_mq_set_handle(pcxt->worker[i].error_mqh, pcxt->worker[i].bgwhandle); + pcxt->nworkers_launched++; + } else { + /* + * If we weren't able to register the worker, then we've bumped up + * against the max_worker_processes limit, and future + * registrations will probably fail too, so arrange to skip them. + * But we still have to execute this code for the remaining slots + * to make sure that we forget about the error queues we budgeted + * for those workers. Otherwise, we'll wait for them to start, + * but they never will. + */ + any_registrations_failed = true; + pcxt->worker[i].bgwhandle = NULL; + shm_mq_detach(pcxt->worker[i].error_mqh); + pcxt->worker[i].error_mqh = NULL; + } + } + + /* + * Now that nworkers_launched has taken its final value, we can initialize + * known_attached_workers. + */ + if (pcxt->nworkers_launched > 0) { + pcxt->known_attached_workers = (bool *)palloc0(sizeof(bool) * pcxt->nworkers_launched); + pcxt->nknown_attached_workers = 0; + } + + /* Restore previous memory context. */ + (void)MemoryContextSwitchTo(oldcontext); +} + +/* + * Wait for all workers to attach to their error queues, and throw an error if + * any worker fails to do this. + * + * Callers can assume that if this function returns successfully, then the + * number of workers given by pcxt->nworkers_launched have initialized and + * attached to their error queues. Whether or not these workers are guaranteed + * to still be running depends on what code the caller asked them to run; + * this function does not guarantee that they have not exited. However, it + * does guarantee that any workers which exited must have done so cleanly and + * after successfully performing the work with which they were tasked. + * + * If this function is not called, then some of the workers that were launched + * may not have been started due to a fork() failure, or may have exited during + * early startup prior to attaching to the error queue, so nworkers_launched + * cannot be viewed as completely reliable. It will never be less than the + * number of workers which actually started, but it might be more. Any workers + * that failed to start will still be discovered by + * WaitForParallelWorkersToFinish and an error will be thrown at that time, + * provided that function is eventually reached. + * + * In general, the leader process should do as much work as possible before + * calling this function. fork() failures and other early-startup failures + * are very uncommon, and having the leader sit idle when it could be doing + * useful work is undesirable. However, if the leader needs to wait for + * all of its workers or for a specific worker, it may want to call this + * function before doing so. If not, it must make some other provision for + * the failure-to-start case, lest it wait forever. On the other hand, a + * leader which never waits for a worker that might not be started yet, or + * at least never does so prior to WaitForParallelWorkersToFinish(), need not + * call this function at all. + */ +void WaitForParallelWorkersToAttach(ParallelContext *pcxt) +{ + int i; + + /* Skip this if we have no launched workers. */ + if (pcxt->nworkers_launched == 0) + return; + + for (;;) { + /* + * This will process any parallel messages that are pending and it may + * also throw an error propagated from a worker. + */ + CHECK_FOR_INTERRUPTS(); + + for (i = 0; i < pcxt->nworkers_launched; ++i) { + shm_mq *mq = NULL; + int rc; + ThreadId pid; + + if (pcxt->known_attached_workers[i]) + continue; + + /* + * If error_mqh is NULL, then the worker has already exited + * cleanly. + */ + if (pcxt->worker[i].error_mqh == NULL) { + pcxt->known_attached_workers[i] = true; + ++pcxt->nknown_attached_workers; + continue; + } + + BgwHandleStatus status = GetBackgroundWorkerPid(pcxt->worker[i].bgwhandle, &pid); + if (status == BGWH_STARTED) { + /* Has the worker attached to the error queue? */ + mq = shm_mq_get_queue(pcxt->worker[i].error_mqh); + if (shm_mq_get_sender(mq) != NULL) { + /* Yes, so it is known to be attached. */ + pcxt->known_attached_workers[i] = true; + ++pcxt->nknown_attached_workers; + } + } else if (status == BGWH_STOPPED) { + /* + * If the worker stopped without attaching to the error queue, + * throw an error. + */ + mq = shm_mq_get_queue(pcxt->worker[i].error_mqh); + if (shm_mq_get_sender(mq) == NULL) + ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("parallel worker failed to initialize"), + errhint("More details may be available in the server log."))); + + pcxt->known_attached_workers[i] = true; + ++pcxt->nknown_attached_workers; + } else { + /* + * Worker not yet started, so we must wait. The postmaster + * will notify us if the worker's state changes. Our latch + * might also get set for some other reason, but if so we'll + * just end up waiting for the same worker again. + */ + rc = WaitLatch(&t_thrd.proc->procLatch, WL_LATCH_SET, -1); + if (rc & WL_LATCH_SET) { + ResetLatch(&t_thrd.proc->procLatch); + } + } + } + + /* If all workers are known to have started, we're done. */ + if (pcxt->nknown_attached_workers >= pcxt->nworkers_launched) { + Assert(pcxt->nknown_attached_workers == pcxt->nworkers_launched); + break; + } + } +} + +/* + * Wait for all workers to finish computing. + * + * Even if the parallel operation seems to have completed successfully, it's + * important to call this function afterwards. We must not miss any errors + * the workers may have thrown during the parallel operation, or any that they + * may yet throw while shutting down. + * + * Also, we want to update our notion of XactLastRecEnd based on worker + * feedback. + */ +void WaitForParallelWorkersToFinish(ParallelContext *pcxt) +{ + for (;;) { + bool anyone_alive = false; + int nfinished = 0; + int i; + + /* + * This will process any parallel messages that are pending, which may + * change the outcome of the loop that follows. It may also throw an + * error propagated from a worker. + */ + CHECK_FOR_INTERRUPTS(); + + for (i = 0; i < pcxt->nworkers_launched; ++i) { + /* + * If error_mqh is NULL, then the worker has already exited + * cleanly. If we have received a message through error_mqh from + * the worker, we know it started up cleanly, and therefore we're + * certain to be notified when it exits. + */ + if (pcxt->worker[i].error_mqh == NULL) + ++nfinished; + else if (pcxt->known_attached_workers[i]) { + anyone_alive = true; + break; + } + } + + if (!anyone_alive) { + /* If all workers are known to have finished, we're done. */ + if (nfinished >= pcxt->nworkers_launched) { + Assert(nfinished == pcxt->nworkers_launched); + break; + } + + /* + * We didn't detect any living workers, but not all workers are + * known to have exited cleanly. Either not all workers have + * launched yet, or maybe some of them failed to start or + * terminated abnormally. + */ + for (i = 0; i < pcxt->nworkers_launched; ++i) { + ThreadId pid; + + /* + * If the worker is BGWH_NOT_YET_STARTED or BGWH_STARTED, we + * should just keep waiting. If it is BGWH_STOPPED, then + * further investigation is needed. + */ + if (pcxt->worker[i].error_mqh == NULL || pcxt->worker[i].bgwhandle == NULL || + GetBackgroundWorkerPid(pcxt->worker[i].bgwhandle, &pid) != BGWH_STOPPED) + continue; + + /* + * Check whether the worker ended up stopped without ever + * attaching to the error queue. If so, the postmaster was + * unable to fork the worker or it exited without initializing + * properly. We must throw an error, since the caller may + * have been expecting the worker to do some work before + * exiting. + */ + shm_mq *mq = shm_mq_get_queue(pcxt->worker[i].error_mqh); + if (shm_mq_get_sender(mq) == NULL) + ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("parallel worker failed to initialize"), + errhint("More details may be available in the server log."))); + + /* + * The worker is stopped, but is attached to the error queue. + * Unless there's a bug somewhere, this will only happen when + * the worker writes messages and terminates after the + * CHECK_FOR_INTERRUPTS() near the top of this function and + * before the call to GetBackgroundWorkerPid(). In that case, + * or latch should have been set as well and the right things + * will happen on the next pass through the loop. + */ + } + } + + (void)WaitLatch(&t_thrd.proc->procLatch, WL_LATCH_SET, -1); + ResetLatch(&t_thrd.proc->procLatch); + } + + knl_u_parallel_context *cxt = (knl_u_parallel_context *)pcxt->seg; + if (cxt->pwCtx->last_xlog_end > t_thrd.xlog_cxt.XactLastRecEnd) + t_thrd.xlog_cxt.XactLastRecEnd = cxt->pwCtx->last_xlog_end; +} + +/* + * Wait for all workers to exit. + * + * This function ensures that workers have been completely shutdown. The + * difference between WaitForParallelWorkersToFinish and this function is + * that former just ensures that last message sent by worker backend is + * received by master backend whereas this ensures the complete shutdown. + */ +static void WaitForParallelWorkersToExit(ParallelContext *pcxt) +{ + /* Wait until the workers actually die. */ + for (int i = 0; i < pcxt->nworkers_launched; ++i) { + if (pcxt->worker == NULL || pcxt->worker[i].bgwhandle == NULL) { + continue; + } + + BgwHandleStatus status = WaitForBackgroundWorkerShutdown(pcxt->worker[i].bgwhandle); + /* + * If the postmaster kicked the bucket, we have no chance of cleaning + * up safely -- we won't be able to tell when our workers are actually + * dead. This doesn't necessitate a PANIC since they will all abort + * eventually, but we can't safely continue this session. + */ + if (status == BGWH_POSTMASTER_DIED) + ereport(FATAL, + (errcode(ERRCODE_ADMIN_SHUTDOWN), errmsg("postmaster exited during a parallel transaction"))); + + /* Release memory. */ + pfree(pcxt->worker[i].bgwhandle); + pcxt->worker[i].bgwhandle = NULL; + } +} + +/* + * Destroy a parallel context. + * + * If expecting a clean exit, you should use WaitForParallelWorkersToFinish() + * first, before calling this function. When this function is invoked, any + * remaining workers are forcibly killed; the dynamic shared memory segment + * is unmapped; and we then wait (uninterruptibly) for the workers to exit. + */ +void DestroyParallelContext(ParallelContext *pcxt) +{ + int i; + + /* + * Be careful about order of operations here! We remove the parallel + * context from the list before we do anything else; otherwise, if an + * error occurs during a subsequent step, we might try to nuke it again + * from AtEOXact_Parallel or AtEOSubXact_Parallel. + */ + dlist_delete(&pcxt->node); + + /* Kill each worker in turn, and forget their error queues. */ + if (pcxt->worker != NULL) { + for (i = 0; i < pcxt->nworkers_launched; ++i) { + if (pcxt->worker[i].error_mqh != NULL) { + TerminateBackgroundWorker(pcxt->worker[i].bgwhandle); + + shm_mq_detach(pcxt->worker[i].error_mqh); + pcxt->worker[i].error_mqh = NULL; + } + } + } + + /* + * If this parallel context is actually in backend-private memory rather + * than shared memory, free that memory instead. + */ + if (pcxt->private_memory != NULL) { + pfree(pcxt->private_memory); + pcxt->private_memory = NULL; + } + + /* + * We can't finish transaction commit or abort until all of the workers + * have exited. This means, in particular, that we can't respond to + * interrupts at this stage. + */ + HOLD_INTERRUPTS(); + WaitForParallelWorkersToExit(pcxt); + RESUME_INTERRUPTS(); + + /* Free the worker array itself. */ + if (pcxt->worker != NULL) { + pfree(pcxt->worker); + pcxt->worker = NULL; + } + + /* + * If we have allocated a shared memory segment, detach it. This will + * implicitly detach the error queues, and any other shared memory queues, + * stored there. + */ + if (pcxt->seg != NULL) { + dsm_detach(&(pcxt->seg)); + pcxt->seg = NULL; + } + + /* Free memory. */ + pfree(pcxt->library_name); + pfree(pcxt->function_name); + pfree(pcxt); +} + +/* + * Are there any parallel contexts currently active? + */ +bool ParallelContextActive(void) +{ + return !dlist_is_empty(&t_thrd.bgworker_cxt.pcxt_list); +} + +/* + * Handle receipt of an interrupt indicating a parallel worker message. + * + * Note: this is called within a signal handler! All we can do is set a flag + * that will cause the next CHECK_FOR_INTERRUPTS() to invoke HandleParallelMessages(). + */ +void HandleParallelMessageInterrupt(void) +{ + InterruptPending = true; + t_thrd.bgworker_cxt.ParallelMessagePending = true; + SetLatch(&t_thrd.proc->procLatch); +} + +/* + * Handle any queued protocol messages received from parallel workers. + */ +void HandleParallelMessages(void) +{ + dlist_iter iter; + + /* + * This is invoked from ProcessInterrupts(), and since some of the + * functions it calls contain CHECK_FOR_INTERRUPTS(), there is a potential + * for recursive calls if more signals are received while this runs. It's + * unclear that recursive entry would be safe, and it doesn't seem useful + * even if it is safe, so let's block interrupts until done. + */ + HOLD_INTERRUPTS(); + + /* + * Moreover, CurrentMemoryContext might be pointing almost anywhere. We + * don't want to risk leaking data into long-lived contexts, so let's do + * our work here in a private context that we can reset on each use. + */ + if (t_thrd.bgworker_cxt.hpm_context == NULL) /* first time through? */ + t_thrd.bgworker_cxt.hpm_context = + AllocSetContextCreate(TopMemoryContext, "HandleParallelMessages", ALLOCSET_DEFAULT_SIZES); + else + MemoryContextReset(t_thrd.bgworker_cxt.hpm_context); + + MemoryContext oldcontext = MemoryContextSwitchTo(t_thrd.bgworker_cxt.hpm_context); + + /* OK to process messages. Reset the flag saying there are more to do. */ + t_thrd.bgworker_cxt.ParallelMessagePending = false; + + dlist_foreach(iter, &t_thrd.bgworker_cxt.pcxt_list) + { + ParallelContext *pcxt = dlist_container(ParallelContext, node, iter.cur); + if (pcxt->worker == NULL) + continue; + + for (int i = 0; i < pcxt->nworkers_launched; ++i) { + /* + * Read as many messages as we can from each worker, but stop when + * either (1) the worker's error queue goes away, which can happen + * if we receive a Terminate message from the worker; or (2) no + * more messages can be read from the worker without blocking. + */ + while (pcxt->worker[i].error_mqh != NULL) { + Size nbytes; + void *data = NULL; + + shm_mq_result res = shm_mq_receive(pcxt->worker[i].error_mqh, &nbytes, &data, true); + if (res == SHM_MQ_WOULD_BLOCK) { + break; + } else if (res == SHM_MQ_SUCCESS) { + StringInfoData msg; + + initStringInfo(&msg); + appendBinaryStringInfo(&msg, (const char *)data, nbytes); + HandleParallelMessage(pcxt, i, &msg); + pfree(msg.data); + } else { + ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), + errmsg("lost connection to parallel worker"))); + } + } + } + } + + (void)MemoryContextSwitchTo(oldcontext); + + /* Might as well clear the context on our way out */ + MemoryContextReset(t_thrd.bgworker_cxt.hpm_context); + + RESUME_INTERRUPTS(); +} + +/* + * Handle a single protocol message received from a single parallel worker. + */ +static void HandleParallelMessage(ParallelContext *pcxt, int i, StringInfo msg) +{ + if (pcxt->known_attached_workers != NULL && !pcxt->known_attached_workers[i]) { + pcxt->known_attached_workers[i] = true; + pcxt->nknown_attached_workers++; + } + + char msgtype = (char)pq_getmsgbyte(msg); + + switch (msgtype) { + case 'K': /* BackendKeyData */ + { + ThreadId pid = pq_getmsgint64(msg); + + (void)pq_getmsgint64(msg); /* discard cancel key */ + pq_getmsgend(msg); + pcxt->worker[i].pid = pid; + break; + } + + case 'E': /* ErrorResponse */ + case 'N': /* NoticeResponse */ + { + ErrorData edata; + + /* Parse ErrorResponse or NoticeResponse. */ + pq_parse_errornotice(msg, &edata); + + /* Death of a worker isn't enough justification for suicide. */ + edata.elevel = Min(edata.elevel, ERROR); + + /* + * If desired, add a context line to show that this is a + * message propagated from a parallel worker. Otherwise, it + * can sometimes be confusing to understand what actually + * happened. (We don't do this in FORCE_PARALLEL_REGRESS mode + * because it causes test-result instability depending on + * whether a parallel worker is actually used or not.) + */ + if (u_sess->attr.attr_sql.force_parallel_mode != FORCE_PARALLEL_REGRESS) { + if (edata.context) { + /* 1 for '\0', 1 for '\n' */ + Size len = strlen(edata.context) + strlen("parallel worker") + 2; + edata.context = (char *)palloc(len); + int rc = sprintf_s(edata.context, len, "%s\n%s", edata.context, "parallel worker"); + securec_check_ss(rc, "", ""); + } else { + edata.context = pstrdup(_("parallel worker")); + } + } + + /* + * Context beyond that should use the error context callbacks + * that were in effect when the ParallelContext was created, + * not the current ones. + */ + ErrorContextCallback *save_error_context_stack = t_thrd.log_cxt.error_context_stack; + t_thrd.log_cxt.error_context_stack = pcxt->error_context_stack; + + /* Rethrow error or print notice. */ + ThrowErrorData(&edata); + + /* Not an error, so restore previous context stack. */ + t_thrd.log_cxt.error_context_stack = save_error_context_stack; + + break; + } + + case 'A': /* NotifyResponse */ + { + /* Propagate NotifyResponse. */ + uint32 pid = pq_getmsgint(msg, 4); + const char *channel = pq_getmsgrawstring(msg); + const char *payload = pq_getmsgrawstring(msg); + pq_endmessage(msg); + + NotifyMyFrontEnd(channel, payload, pid); + + break; + } + + case 'X': /* Terminate, indicating clean exit */ + { + shm_mq_detach(pcxt->worker[i].error_mqh); + pcxt->worker[i].error_mqh = NULL; + break; + } + + default: { + ereport(ERROR, + (errmsg("unrecognized message type received from parallel worker: %c (message length %d bytes)", + msgtype, msg->len))); + } + } +} + +/* + * End-of-subtransaction cleanup for parallel contexts. + * + * Currently, it's forbidden to enter or leave a subtransaction while + * parallel mode is in effect, so we could just blow away everything. But + * we may want to relax that restriction in the future, so this code + * contemplates that there may be multiple subtransaction IDs in pcxt_list. + */ +void AtEOSubXact_Parallel(bool isCommit, SubTransactionId mySubId) +{ + while (!dlist_is_empty(&t_thrd.bgworker_cxt.pcxt_list)) { + ParallelContext *pcxt = dlist_head_element(ParallelContext, node, &t_thrd.bgworker_cxt.pcxt_list); + if (pcxt->subid != mySubId) + break; + if (isCommit) + ereport(WARNING, (errmsg("leaked parallel context"))); + DestroyParallelContext(pcxt); + } +} + +/* + * End-of-transaction cleanup for parallel contexts. + */ +void AtEOXact_Parallel(bool isCommit) +{ + while (!dlist_is_empty(&t_thrd.bgworker_cxt.pcxt_list)) { + ParallelContext *pcxt = dlist_head_element(ParallelContext, node, &t_thrd.bgworker_cxt.pcxt_list); + if (isCommit) + ereport(WARNING, (errmsg("leaked parallel context"))); + DestroyParallelContext(pcxt); + } +} + +/* + * Main entrypoint for parallel workers. + */ +void ParallelWorkerMain(Datum main_arg) +{ + StringInfoData msgbuf; + + knl_u_parallel_context *ctx = (knl_u_parallel_context *)DatumGetPointer(main_arg); + + /* Set flag to indicate that we're initializing a parallel worker. */ + t_thrd.bgworker_cxt.InitializingParallelWorker = true; + + /* Establish signal handlers. */ + gspqsignal(SIGTERM, die); + BackgroundWorkerUnblockSignals(); + + /* Determine and set our parallel worker number. */ + Assert(t_thrd.bgworker_cxt.ParallelWorkerNumber == -1); + int rc = memcpy_s(&t_thrd.bgworker_cxt.ParallelWorkerNumber, sizeof(int), + t_thrd.bgworker_cxt.my_bgworker_entry->bgw_extra, sizeof(int)); + securec_check(rc, "", ""); + + char bgWorkerName[MAX_THREAD_NAME_LENGTH]; + rc = sprintf_s(bgWorkerName, MAX_THREAD_NAME_LENGTH, "BgWorker%d", t_thrd.bgworker_cxt.ParallelWorkerNumber); + securec_check_ss(rc, "", ""); + knl_thread_set_name(bgWorkerName); + +#ifdef __USE_NUMA + if (ctx->pwCtx->numaNode != -1) { + rc = numa_run_on_node(ctx->pwCtx->numaNode); + if (rc != 0) { + ereport(WARNING, (errmsg("numa_run_on_node failed, %m"))); + } + } +#endif + + /* Set up a memory context to work in, just for cleanliness. */ + CurrentMemoryContext = AllocSetContextCreate(TopMemoryContext, "Parallel worker", ALLOCSET_DEFAULT_SIZES); + + /* Arrange to signal the leader if we exit. */ + on_shmem_exit(ParallelWorkerShutdown, (Datum)0); + + /* + * Now we can find and attach to the error queue provided for us. That's + * good, because until we do that, any errors that happen here will not be + * reported back to the process that requested that this worker be + * launched. + */ + char *error_queue_space = ctx->pwCtx->errorQueue; + shm_mq *mq = (shm_mq *)(error_queue_space + t_thrd.bgworker_cxt.ParallelWorkerNumber * PARALLEL_ERROR_QUEUE_SIZE); + shm_mq_set_sender(mq, t_thrd.proc); + shm_mq_handle *mqh = shm_mq_attach(mq, ctx, NULL); + pq_redirect_to_shm_mq(mqh); + pq_set_parallel_master(ctx->pwCtx->parallel_master_pid, ctx->pwCtx->parallel_master_backend_id); + + /* + * Send a BackendKeyData message to the process that initiated parallelism + * so that it has access to our PID before it receives any other messages + * from us. Our cancel key is sent, too, since that's the way the + * protocol message is defined, but it won't actually be used for anything + * in this case. + */ + pq_beginmessage(&msgbuf, 'K'); + pq_sendint64(&msgbuf, t_thrd.proc_cxt.MyProcPid); + pq_sendint64(&msgbuf, t_thrd.proc_cxt.MyCancelKey); + pq_endmessage(&msgbuf); + + /* + * Hooray! Primary initialization is complete. Now, we need to set up our + * backend-local state to match the original backend. + */ + /* + * Restore transaction and statement start-time timestamps. This must + * happen before anything that would start a transaction, else asserts in + * xact.c will fire. + */ + SetParallelStartTimestamps(ctx->pwCtx->xact_ts, ctx->pwCtx->stmt_ts); + + /* + * Identify the entry point to be called. In theory this could result in + * loading an additional library, though most likely the entry point is in + * the core backend or in a library we just loaded. + */ + parallel_worker_main_type entrypt = + LookupParallelWorkerFunction(ctx->pwCtx->library_name, ctx->pwCtx->function_name); + + /* Restore database connection. */ + BackgroundWorkerInitializeConnectionByOid(ctx->pwCtx->database_id, ctx->pwCtx->authenticated_user_id, 0); + + /* + * Set the client encoding to the database encoding, since that is what + * the leader will expect. + */ + (void)SetClientEncoding(GetDatabaseEncoding()); + + /* Crank up a transaction state appropriate to a parallel worker. */ + StartParallelWorkerTransaction(ctx->pwCtx); + + /* Restore combo CID state. */ + u_sess->utils_cxt.usedComboCids = ctx->pwCtx->usedComboCids; + u_sess->utils_cxt.comboCids = ctx->pwCtx->comboCids; + u_sess->utils_cxt.sizeComboCids = ctx->pwCtx->sizeComboCids; + u_sess->utils_cxt.comboHash = ctx->pwCtx->comboHash; + + /* Restore namespace search path */ + u_sess->attr.attr_common.namespace_search_path = ctx->pwCtx->namespace_search_path; + + /* Restore transaction snapshot. */ + RestoreTransactionSnapshot(RestoreSnapshot(ctx->pwCtx->tsnapspace, ctx->pwCtx->tsnapspace_len), + ctx->pwCtx->parallel_master_pgproc); + /* Restore active snapshot. */ + PushActiveSnapshot(RestoreSnapshot(ctx->pwCtx->asnapspace, ctx->pwCtx->asnapspace_len)); + + /* + * We've changed which tuples we can see, and must therefore invalidate + * system caches. + */ + InvalidateSystemCaches(); + + /* + * Restore current role id. Skip verifying whether session user is + * allowed to become this role and blindly restore the leader's state for + * current role. + */ + SetCurrentRoleId(ctx->pwCtx->outer_user_id, ctx->pwCtx->is_superuser); + + /* Restore user ID and security context. */ + SetUserIdAndSecContext(ctx->pwCtx->current_user_id, ctx->pwCtx->sec_context); + + /* Restore temp-namespace state to ensure search path matches leader's. */ + SetTempNamespaceState(ctx->pwCtx->temp_namespace_id, ctx->pwCtx->temp_toast_namespace_id); + + /* Restore relmapper state. */ + u_sess->relmap_cxt.active_shared_updates = ctx->pwCtx->active_shared_updates; + u_sess->relmap_cxt.active_local_updates = ctx->pwCtx->active_local_updates; + + /* + * We've initialized all of our state now; nothing should change + * hereafter. + */ + t_thrd.bgworker_cxt.InitializingParallelWorker = false; + EnterParallelMode(); + + /* + * Time to do the real work: invoke the caller-supplied code. + */ + entrypt(ctx); + + /* Must exit parallel mode to pop active snapshot. */ + ExitParallelMode(); + + /* Must pop active snapshot so snapmgr.c doesn't complain. */ + PopActiveSnapshot(); + + /* Shut down the parallel-worker transaction. */ + EndParallelWorkerTransaction(); + + /* Report success. */ + pq_putmessage('X', NULL, 0); +} + +/* + * Update shared memory with the ending location of the last WAL record we + * wrote, if it's greater than the value already stored there. + */ +void ParallelWorkerReportLastRecEnd(XLogRecPtr last_xlog_end) +{ + knl_u_parallel_context *ctx = (knl_u_parallel_context *)t_thrd.bgworker_cxt.my_bgworker_entry->bgw_parallel_context; + Assert(ctx->pwCtx != NULL); + SpinLockAcquire(&ctx->pwCtx->mutex); + if (ctx->pwCtx->last_xlog_end < last_xlog_end) { + ctx->pwCtx->last_xlog_end = last_xlog_end; + } + SpinLockRelease(&ctx->pwCtx->mutex); +} + +/* + * Make sure the leader tries to read from our error queue one more time. + * This guards against the case where we exit uncleanly without sending an + * ErrorResponse to the leader, for example because some code calls proc_exit + * directly. + */ +static void ParallelWorkerShutdown(int code, Datum arg) +{ + (void)SendProcSignal(t_thrd.msqueue_cxt.pq_mq_parallel_master_pid, PROCSIG_PARALLEL_MESSAGE, + t_thrd.msqueue_cxt.pq_mq_parallel_master_backend_id); +} + +#ifdef __USE_NUMA +static bool SaveCpuAffinity(cpu_set_t **cpuset) +{ + *cpuset = (cpu_set_t*)palloc(sizeof(cpu_set_t)); + int rc = pthread_getaffinity_np(t_thrd.proc->pid, sizeof(cpu_set_t), *cpuset); + if (rc != 0) { + pfree_ext(*cpuset); + ereport(WARNING, (errmsg("pthread_getaffinity_np failed:%d", rc))); + return false; + } + return true; +} + +static void GetCurrentNumaNode(ParallelInfoContext *pcxt) +{ + pcxt->numaNode = -1; + pcxt->cpuset = NULL; + int cpu = sched_getcpu(); + if (cpu < 0) { + ereport(WARNING, (errmsg("sched_getcpu failed, %m"))); + return; + } + + int numaNode = numa_node_of_cpu(cpu); + if (numaNode < 0) { + ereport(WARNING, (errmsg("numa_node_of_cpu failed, %m"))); + return; + } + + /* Save current CPU affinity, then we can restore it's value after the query is done */ + if (!SaveCpuAffinity(&pcxt->cpuset)) { + return; + } + + /* + * Set thread to current numa node, then it won't schedule to other numa node + * during query(in most cases). We should reset the affinity after the query is done. + */ + int rc = numa_run_on_node(numaNode); + if (rc < 0) { + pfree_ext(pcxt->cpuset); + ereport(WARNING, (errmsg("numa_run_on_node failed, %m"))); + return; + } + + pcxt->numaNode = numaNode; +} +#endif + + +/* + * Look up (and possibly load) a parallel worker entry point function. + * + * For functions contained in the core code, we use library name "postgres" + * and consult the InternalParallelWorkers array. External functions are + * looked up, and loaded if necessary, using load_external_function(). + * + * The point of this is to pass function names as strings across process + * boundaries. We can't pass actual function addresses because of the + * possibility that the function has been loaded at a different address + * in a different process. This is obviously a hazard for functions in + * loadable libraries, but it can happen even for functions in the core code + * on platforms using EXEC_BACKEND (e.g., Windows). + * + * At some point it might be worthwhile to get rid of InternalParallelWorkers[] + * in favor of applying load_external_function() for core functions too; + * but that raises portability issues that are not worth addressing now. + */ +static parallel_worker_main_type LookupParallelWorkerFunction(const char *libraryname, const char *funcname) +{ + /* + * If the function is to be loaded from postgres itself, search the + * InternalParallelWorkers array. + */ + if (strcmp(libraryname, "postgres") == 0) { + for (size_t i = 0; i < lengthof(InternalParallelWorkers); i++) { + if (strcmp(InternalParallelWorkers[i].fn_name, funcname) == 0) + return InternalParallelWorkers[i].fn_addr; + } + + ereport(ERROR, (errmsg("internal function \"%s\" not found", funcname))); + } + + ereport(ERROR, (errmsg("library\"%s\" function \"%s\" not supported", libraryname, funcname))); + return NULL; +} + diff --git a/src/gausskernel/storage/ipc/dsm.cpp b/src/gausskernel/storage/ipc/dsm.cpp index 9571d82bd..58df80ac9 100644 --- a/src/gausskernel/storage/ipc/dsm.cpp +++ b/src/gausskernel/storage/ipc/dsm.cpp @@ -1,63 +1,79 @@ -/* ------------------------------------------------------------------------- - * - * dsm.c - * manage dynamic shared memory segments - * - * This file provides a set of services to make programming with dynamic - * shared memory segments more convenient. Unlike the low-level - * facilities provided by dsm_impl.h and dsm_impl.c, mappings and segments - * created using this module will be cleaned up automatically. Mappings - * will be removed when the resource owner under which they were created - * is cleaned up, unless dsm_pin_mapping() is used, in which case they - * have session lifespan. Segments will be removed when there are no - * remaining mappings, or at postmaster shutdown in any case. After a - * hard postmaster crash, remaining segments will be removed, if they - * still exist, at the next postmaster startup. - * - * Portions Copyright (c) 2020 Huawei Technologies Co.,Ltd - * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group - * Portions Copyright (c) 1994, Regents of the University of California - * - * - * IDENTIFICATION - * src/gausskernel/storage/ipc/dsm.c - * - * ------------------------------------------------------------------------- - */ - -#include "postgres.h" -#include "storage/dsm.h" -#include "knl/knl_session.h" -#include "utils/memutils.h" -#include "postmaster/bgworker_internals.h" - -void dsm_detach(void **seg) -{ - Assert(*seg != NULL); - knl_u_parallel_context *ctx = (knl_u_parallel_context *)*seg; - MemoryContextDelete(ctx->memCtx); - ctx->memCtx = NULL; - ctx->pwCtx = NULL; - ctx->used = false; -} - -void *dsm_create(void) -{ - for (int i = 0; i < DSM_MAX_ITEM_PER_QUERY; i++) { - if (u_sess->parallel_ctx[i].used == false) { - u_sess->parallel_ctx[i].memCtx = AllocSetContextCreate(u_sess->top_mem_cxt, "parallel query", - ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE, SHARED_CONTEXT); - - MemoryContext oldContext = MemoryContextSwitchTo(u_sess->parallel_ctx[i].memCtx); - u_sess->parallel_ctx[i].pwCtx = (ParallelInfoContext *)palloc0(sizeof(ParallelInfoContext)); - (void)MemoryContextSwitchTo(oldContext); - - u_sess->parallel_ctx[i].used = true; - return &(u_sess->parallel_ctx[i]); - } - } - - ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_RESOURCES), errmsg("too many dynamic shared memory segments"))); - return NULL; -} - +/* ------------------------------------------------------------------------- + * + * dsm.c + * manage dynamic shared memory segments + * + * This file provides a set of services to make programming with dynamic + * shared memory segments more convenient. Unlike the low-level + * facilities provided by dsm_impl.h and dsm_impl.c, mappings and segments + * created using this module will be cleaned up automatically. Mappings + * will be removed when the resource owner under which they were created + * is cleaned up, unless dsm_pin_mapping() is used, in which case they + * have session lifespan. Segments will be removed when there are no + * remaining mappings, or at postmaster shutdown in any case. After a + * hard postmaster crash, remaining segments will be removed, if they + * still exist, at the next postmaster startup. + * + * Portions Copyright (c) 2020 Huawei Technologies Co.,Ltd + * Portions Copyright (c) 1996-2019, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * + * IDENTIFICATION + * src/gausskernel/storage/ipc/dsm.c + * + * ------------------------------------------------------------------------- + */ + +#include "postgres.h" +#include "storage/dsm.h" +#include "knl/knl_session.h" +#include "utils/memutils.h" +#include "postmaster/bgworker_internals.h" + +#ifdef __USE_NUMA +static void RestoreCpuAffinity(cpu_set_t *cpuset) +{ + /* Resotre CPU affinity after parallel query is done. */ + if (cpuset != NULL) { + int rc = pthread_setaffinity_np(t_thrd.proc->pid, sizeof(cpu_set_t), cpuset); + if (rc != 0) { + ereport(WARNING, (errmsg("pthread_setaffinity_np failed:%d", rc))); + } + } +} +#endif + +void dsm_detach(void **seg) +{ + Assert(*seg != NULL); + knl_u_parallel_context *ctx = (knl_u_parallel_context *)*seg; +#ifdef __USE_NUMA + RestoreCpuAffinity(ctx->pwCtx->cpuset); +#endif + MemoryContextDelete(ctx->memCtx); + ctx->memCtx = NULL; + ctx->pwCtx = NULL; + ctx->used = false; +} + +void *dsm_create(void) +{ + for (int i = 0; i < DSM_MAX_ITEM_PER_QUERY; i++) { + if (u_sess->parallel_ctx[i].used == false) { + u_sess->parallel_ctx[i].memCtx = AllocSetContextCreate(u_sess->top_mem_cxt, "parallel query", + ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE, SHARED_CONTEXT); + + MemoryContext oldContext = MemoryContextSwitchTo(u_sess->parallel_ctx[i].memCtx); + u_sess->parallel_ctx[i].pwCtx = (ParallelInfoContext *)palloc0(sizeof(ParallelInfoContext)); + (void)MemoryContextSwitchTo(oldContext); + + u_sess->parallel_ctx[i].used = true; + return &(u_sess->parallel_ctx[i]); + } + } + + ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_RESOURCES), errmsg("too many dynamic shared memory segments"))); + return NULL; +} + diff --git a/src/gausskernel/storage/ipc/shm_mq.cpp b/src/gausskernel/storage/ipc/shm_mq.cpp index ff156e29b..4c11b221b 100644 --- a/src/gausskernel/storage/ipc/shm_mq.cpp +++ b/src/gausskernel/storage/ipc/shm_mq.cpp @@ -608,8 +608,8 @@ shm_mq_result shm_mq_receive(shm_mq_handle *mqh, Size *nbytesp, void **datap, bo lengthbytes = sizeof(Size) - mqh->mqh_partial_bytes; else lengthbytes = rb; - errno_t rc = memcpy_s(&mqh->mqh_buffer[mqh->mqh_partial_bytes], lengthbytes, - rawdata, lengthbytes); + errno_t rc = memcpy_s(&mqh->mqh_buffer[mqh->mqh_partial_bytes], + mqh->mqh_buflen - mqh->mqh_partial_bytes, rawdata, lengthbytes); securec_check(rc, "\0", "\0"); mqh->mqh_partial_bytes += lengthbytes; mqh->mqh_consume_pending += MAXALIGN(lengthbytes); @@ -671,7 +671,8 @@ shm_mq_result shm_mq_receive(shm_mq_handle *mqh, Size *nbytesp, void **datap, bo /* Copy as much as we can. */ Assert(mqh->mqh_partial_bytes + rb <= nbytes); if (rb != 0) { - errno_t rc = memcpy_s(&mqh->mqh_buffer[mqh->mqh_partial_bytes], rb, rawdata, rb); + errno_t rc = memcpy_s(&mqh->mqh_buffer[mqh->mqh_partial_bytes], + mqh->mqh_buflen - mqh->mqh_partial_bytes, rawdata, rb); securec_check(rc, "\0", "\0"); mqh->mqh_partial_bytes += rb; @@ -897,8 +898,8 @@ static shm_mq_result shm_mq_send_bytes(shm_mq_handle *mqh, Size nbytes, const vo * subsequent write to mq_ring, we need a full barrier here.) */ pg_memory_barrier(); - errno_t rc = memcpy_s(&mq->mq_ring[mq->mq_ring_offset + offset], sendnow, - (char*)data + sent, sendnow); + errno_t rc = memcpy_s(&mq->mq_ring[mq->mq_ring_offset + offset], + ringsize - offset, (char*)data + sent, sendnow); securec_check(rc, "\0", "\0"); sent += sendnow; diff --git a/src/include/knl/knl_guc/knl_instance_attr_common.h b/src/include/knl/knl_guc/knl_instance_attr_common.h index 914de7f28..a4b64a2aa 100755 --- a/src/include/knl/knl_guc/knl_instance_attr_common.h +++ b/src/include/knl/knl_guc/knl_instance_attr_common.h @@ -76,10 +76,6 @@ typedef struct knl_instance_attr_common { bool enable_alarm; char* Alarm_component; char* MOTConfigFileName; - - int max_worker_processes; - int max_parallel_workers; - int max_parallel_workers_per_gather; } knl_instance_attr_common; #endif /* SRC_INCLUDE_KNL_KNL_INSTANCE_ATTR_COMMON_H_ */ diff --git a/src/include/knl/knl_guc/knl_session_attr_sql.h b/src/include/knl/knl_guc/knl_session_attr_sql.h index 212f853e5..e3d1ea5d8 100644 --- a/src/include/knl/knl_guc/knl_session_attr_sql.h +++ b/src/include/knl/knl_guc/knl_session_attr_sql.h @@ -205,6 +205,7 @@ typedef struct knl_session_attr_sql { int opfusion_debug_mode; int single_shard_stmt; int force_parallel_mode; + int max_parallel_workers_per_gather; } knl_session_attr_sql; #endif /* SRC_INCLUDE_KNL_KNL_SESSION_ATTR_SQL */ diff --git a/src/include/knl/knl_session.h b/src/include/knl/knl_session.h index 9dcd96351..7b1714e75 100644 --- a/src/include/knl/knl_session.h +++ b/src/include/knl/knl_session.h @@ -2089,7 +2089,10 @@ typedef struct ParallelInfoContext { char *tupleQueue; struct SharedExecutorInstrumentation *instrumentation; char *namespace_search_path; - +#ifdef __USE_NUMA + int numaNode; + cpu_set_t *cpuset; +#endif /* Mutex protects remaining fields. */ slock_t mutex; /* Maximum XactLastRecEnd of any worker. */ diff --git a/src/test/regress/expected/parallel_query.out b/src/test/regress/expected/parallel_query.out new file mode 100644 index 000000000..c3cf35b5b --- /dev/null +++ b/src/test/regress/expected/parallel_query.out @@ -0,0 +1,167 @@ +create table parallel_t1(a int); +insert into parallel_t1 values(generate_series(1,100000)); +--normal plan for seq scan +explain (costs off) select count(*) from parallel_t1; + QUERY PLAN +------------------------------- + Aggregate + -> Seq Scan on parallel_t1 +(2 rows) + +explain (costs off) select count(*) from parallel_t1 where a = 5000; + QUERY PLAN +------------------------------- + Aggregate + -> Seq Scan on parallel_t1 + Filter: (a = 5000) +(3 rows) + +explain (costs off) select count(*) from parallel_t1 where a > 5000; + QUERY PLAN +------------------------------- + Aggregate + -> Seq Scan on parallel_t1 + Filter: (a > 5000) +(3 rows) + +explain (costs off) select count(*) from parallel_t1 where a < 5000; + QUERY PLAN +------------------------------- + Aggregate + -> Seq Scan on parallel_t1 + Filter: (a < 5000) +(3 rows) + +explain (costs off) select count(*) from parallel_t1 where a <> 5000; + QUERY PLAN +------------------------------- + Aggregate + -> Seq Scan on parallel_t1 + Filter: (a <> 5000) +(3 rows) + +select count(*) from parallel_t1; + count +-------- + 100000 +(1 row) + +select count(*) from parallel_t1 where a = 5000; + count +------- + 1 +(1 row) + +select count(*) from parallel_t1 where a > 5000; + count +------- + 95000 +(1 row) + +select count(*) from parallel_t1 where a < 5000; + count +------- + 4999 +(1 row) + +select count(*) from parallel_t1 where a <> 5000; + count +------- + 99999 +(1 row) + +--set parallel parameter +set force_parallel_mode=on; +set parallel_setup_cost=0; +set parallel_tuple_cost=0.000005; +set max_parallel_workers_per_gather=2; +set min_parallel_table_scan_size=0; +set parallel_leader_participation=on; +--parallel plan for seq scan +explain (costs off) select count(*) from parallel_t1; + QUERY PLAN +---------------------------------------------- + Aggregate + -> Gather + Number of Workers: 2 + -> Parallel Seq Scan on parallel_t1 +(4 rows) + +explain (costs off) select count(*) from parallel_t1 where a = 5000; + QUERY PLAN +---------------------------------------------- + Aggregate + -> Gather + Number of Workers: 2 + -> Parallel Seq Scan on parallel_t1 + Filter: (a = 5000) +(5 rows) + +explain (costs off) select count(*) from parallel_t1 where a > 5000; + QUERY PLAN +---------------------------------------------- + Aggregate + -> Gather + Number of Workers: 2 + -> Parallel Seq Scan on parallel_t1 + Filter: (a > 5000) +(5 rows) + +explain (costs off) select count(*) from parallel_t1 where a < 5000; + QUERY PLAN +---------------------------------------------- + Aggregate + -> Gather + Number of Workers: 2 + -> Parallel Seq Scan on parallel_t1 + Filter: (a < 5000) +(5 rows) + +explain (costs off) select count(*) from parallel_t1 where a <> 5000; + QUERY PLAN +---------------------------------------------- + Aggregate + -> Gather + Number of Workers: 2 + -> Parallel Seq Scan on parallel_t1 + Filter: (a <> 5000) +(5 rows) + +select count(*) from parallel_t1; + count +-------- + 100000 +(1 row) + +select count(*) from parallel_t1 where a = 5000; + count +------- + 1 +(1 row) + +select count(*) from parallel_t1 where a > 5000; + count +------- + 95000 +(1 row) + +select count(*) from parallel_t1 where a < 5000; + count +------- + 4999 +(1 row) + +select count(*) from parallel_t1 where a <> 5000; + count +------- + 99999 +(1 row) + +--clean up +drop table parallel_t1; +reset force_parallel_mode; +reset parallel_setup_cost; +reset parallel_tuple_cost; +reset max_parallel_workers_per_gather; +reset min_parallel_table_scan_size; +reset parallel_leader_participation; diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index f92c21121..eeeed506f 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -595,5 +595,8 @@ test: create_procedure create_function pg_compatibility postgres_fdw # autonomous transaction Test test: autonomous_transaction +# parallel query +test: parallel_query + # gs_basebackup test: gs_basebackup diff --git a/src/test/regress/parallel_schedule3 b/src/test/regress/parallel_schedule3 index 644da549f..356f25979 100644 --- a/src/test/regress/parallel_schedule3 +++ b/src/test/regress/parallel_schedule3 @@ -25,3 +25,6 @@ test: upsert_grammer_test_01 upsert_unlog_test upsert_tmp_test test: upsert_grammer_test_02 upsert_restriction upsert_composite test: upsert_trigger_test upsert_explain test: upsert_clean + +# test parallel query +test: parallel_query diff --git a/src/test/regress/sql/parallel_query.sql b/src/test/regress/sql/parallel_query.sql new file mode 100644 index 000000000..d26f9f8d5 --- /dev/null +++ b/src/test/regress/sql/parallel_query.sql @@ -0,0 +1,42 @@ +create table parallel_t1(a int); +insert into parallel_t1 values(generate_series(1,100000)); +--normal plan for seq scan +explain (costs off) select count(*) from parallel_t1; +explain (costs off) select count(*) from parallel_t1 where a = 5000; +explain (costs off) select count(*) from parallel_t1 where a > 5000; +explain (costs off) select count(*) from parallel_t1 where a < 5000; +explain (costs off) select count(*) from parallel_t1 where a <> 5000; +select count(*) from parallel_t1; +select count(*) from parallel_t1 where a = 5000; +select count(*) from parallel_t1 where a > 5000; +select count(*) from parallel_t1 where a < 5000; +select count(*) from parallel_t1 where a <> 5000; + +--set parallel parameter +set force_parallel_mode=on; +set parallel_setup_cost=0; +set parallel_tuple_cost=0.000005; +set max_parallel_workers_per_gather=2; +set min_parallel_table_scan_size=0; +set parallel_leader_participation=on; + +--parallel plan for seq scan +explain (costs off) select count(*) from parallel_t1; +explain (costs off) select count(*) from parallel_t1 where a = 5000; +explain (costs off) select count(*) from parallel_t1 where a > 5000; +explain (costs off) select count(*) from parallel_t1 where a < 5000; +explain (costs off) select count(*) from parallel_t1 where a <> 5000; +select count(*) from parallel_t1; +select count(*) from parallel_t1 where a = 5000; +select count(*) from parallel_t1 where a > 5000; +select count(*) from parallel_t1 where a < 5000; +select count(*) from parallel_t1 where a <> 5000; + +--clean up +drop table parallel_t1; +reset force_parallel_mode; +reset parallel_setup_cost; +reset parallel_tuple_cost; +reset max_parallel_workers_per_gather; +reset min_parallel_table_scan_size; +reset parallel_leader_participation; \ No newline at end of file diff --git a/third_party/dependency/mysql_fdw/huawei_mysql_fdw-2.5.3_patch.patch b/third_party/dependency/mysql_fdw/openGauss_mysql_fdw-2.5.3_patch.patch similarity index 100% rename from third_party/dependency/mysql_fdw/huawei_mysql_fdw-2.5.3_patch.patch rename to third_party/dependency/mysql_fdw/openGauss_mysql_fdw-2.5.3_patch.patch diff --git a/third_party/dependency/oracle_fdw/huawei_oracle_fdw-2.2.0_patch.patch b/third_party/dependency/oracle_fdw/openGauss_oracle_fdw-2.2.0_patch.patch similarity index 100% rename from third_party/dependency/oracle_fdw/huawei_oracle_fdw-2.2.0_patch.patch rename to third_party/dependency/oracle_fdw/openGauss_oracle_fdw-2.2.0_patch.patch