From bcbebe4ccd2dcab513bd65fe336603db3e22ff69 Mon Sep 17 00:00:00 2001 From: nuoya <1204149038@qq.com> Date: Sun, 27 Aug 2023 16:06:13 +0800 Subject: [PATCH 01/50] Update tcap_manager.cpp --- src/gausskernel/storage/tcap/tcap_manager.cpp | 4378 ++++++++++------- 1 file changed, 2482 insertions(+), 1896 deletions(-) diff --git a/src/gausskernel/storage/tcap/tcap_manager.cpp b/src/gausskernel/storage/tcap/tcap_manager.cpp index da0bee6fb..7e46b5c4c 100644 --- a/src/gausskernel/storage/tcap/tcap_manager.cpp +++ b/src/gausskernel/storage/tcap/tcap_manager.cpp @@ -1,1896 +1,2482 @@ -/* - * Copyright (c) Huawei Technologies Co., Ltd. 2020-2020. All rights reserved. - * - * 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. - * --------------------------------------------------------------------------------------- - * - * tcap_manager.cpp - * Routines to support Timecapsule `Recyclebin-based query, restore`. - * We use Tr prefix to indicate it in following coding. - * - * IDENTIFICATION - * src/gausskernel/storage/tcap/tcap_manager.cpp - * - * --------------------------------------------------------------------------------------- - */ - -#include "postgres.h" - -#include "pgstat.h" -#include "access/reloptions.h" -#include "access/sysattr.h" -#include "access/xlog.h" -#include "catalog/pg_database.h" -#include "catalog/dependency.h" -#include "catalog/heap.h" -#include "catalog/index.h" -#include "catalog/indexing.h" -#include "catalog/objectaccess.h" -#include "catalog/pg_collation_fn.h" -#include "catalog/pg_collation.h" -#include "catalog/pg_constraint.h" -#include "catalog/pg_conversion_fn.h" -#include "catalog/pg_conversion.h" -#include "catalog/pg_depend.h" -#include "catalog/pg_extension_data_source.h" -#include "catalog/pg_extension.h" -#include "catalog/pg_foreign_data_wrapper.h" -#include "catalog/pg_foreign_server.h" -#include "catalog/pg_job.h" -#include "catalog/pg_language.h" -#include "catalog/pg_largeobject.h" -#include "catalog/pg_object.h" -#include "catalog/pg_opclass.h" -#include "catalog/pg_operator.h" -#include "catalog/pg_opfamily.h" -#include "catalog/pg_partition_fn.h" -#include "catalog/pg_proc.h" -#include "catalog/pg_recyclebin.h" -#include "catalog/pg_rewrite.h" -#include "catalog/pg_rlspolicy.h" -#include "catalog/pg_synonym.h" -#include "catalog/pg_tablespace.h" -#include "catalog/pg_trigger.h" -#include "catalog/pg_ts_config.h" -#include "catalog/pg_ts_dict.h" -#include "catalog/pg_ts_parser.h" -#include "catalog/pg_ts_template.h" -#include "catalog/pgxc_class.h" -#include "catalog/pg_partition.h" -#include "catalog/storage.h" -#include "commands/comment.h" -#include "commands/dbcommands.h" -#include "commands/directory.h" -#include "commands/extension.h" -#include "commands/proclang.h" -#include "commands/schemacmds.h" -#include "commands/seclabel.h" -#include "commands/sec_rls_cmds.h" -#include "commands/tablecmds.h" -#include "commands/tablespace.h" -#include "commands/trigger.h" -#include "commands/typecmds.h" -#include "executor/node/nodeModifyTable.h" -#include "rewrite/rewriteRemove.h" -#include "storage/lmgr.h" -#include "storage/predicate.h" -#include "storage/smgr/relfilenode.h" -#include "utils/acl.h" -#include "utils/builtins.h" -#include "utils/fmgroids.h" -#include "utils/inval.h" -#include "utils/lsyscache.h" -#include "utils/relcache.h" -#include "utils/snapmgr.h" -#include "utils/syscache.h" - -#include "storage/tcap.h" -#include "storage/tcap_impl.h" - -static bool TrIsRefRbObject(const ObjectAddress *obj, Relation depRel = NULL); -void TrDoPurgeObjectDrop(TrObjDesc *desc); - -char *TrGenObjName(char *rbname, Oid classId, Oid objid) -{ - int rc = EOK; - - rc = snprintf_s(rbname, NAMEDATALEN, NAMEDATALEN - 1, "BIN$%X%X%X$%llX==$0", - u_sess->proc_cxt.MyDatabaseId, classId, objid, (uint64)GetXLogInsertRecPtr()); - securec_check_ss_c(rc, "\0", "\0"); - - return rbname; -} - -static TransactionId TrRbGetRcyfrozenxid64(HeapTuple rbtup, Relation rbRel = NULL) -{ - Datum datum; - bool isNull = false; - TransactionId rcyfrozenxid64; - bool relArgNull = rbRel == NULL; - - if (relArgNull) { - rbRel = heap_open(RecyclebinRelationId, AccessShareLock); - } - - datum = heap_getattr(rbtup, Anum_pg_recyclebin_rcyfrozenxid64, RelationGetDescr(rbRel), &isNull); - Assert(!isNull); - - rcyfrozenxid64 = DatumGetTransactionId(datum); - - if (relArgNull) { - heap_close(rbRel, AccessShareLock); - } - - return rcyfrozenxid64; -} - -void TrDescInit(Relation rel, TrObjDesc *desc, TrObjOperType operType, - TrObjType objType, bool canpurge, bool isBaseObj) -{ - errno_t rc = EOK; - - /* Notice: desc->id, desc->baseid will be assigned by invoker later. */ - desc->dbid = u_sess->proc_cxt.MyDatabaseId; - desc->relid = RelationGetRelid(rel); - - (void)TrGenObjName(desc->name, RelationRelationId, desc->relid); - - rc = strncpy_s(desc->originname, NAMEDATALEN, RelationGetRelationName(rel), - strlen(RelationGetRelationName(rel))); - securec_check(rc, "\0", "\0"); - - desc->operation = operType; - desc->type = objType; - desc->recyclecsn = t_thrd.xact_cxt.ShmemVariableCache->nextCommitSeqNo; - desc->recycletime = GetCurrentTimestamp(); - desc->createcsn = RelationGetCreatecsn(rel); - desc->changecsn = RelationGetChangecsn(rel); - desc->nspace = RelationGetNamespace(rel); - desc->owner = RelationGetOwner(rel); - desc->tablespace = RelationGetTablespace(rel); - desc->relfilenode = RelationGetRelFileNode(rel); - desc->frozenxid = RelationGetRelFrozenxid(rel); - desc->frozenxid64 = RelationGetRelFrozenxid64(rel); - desc->canrestore = objType == RB_OBJ_TABLE; - desc->canpurge = canpurge; -} - -void TrPartDescInit(Relation rel, Partition part, TrObjDesc *desc, TrObjOperType operType, - TrObjType objType, bool canpurge, bool isBaseObj) -{ - errno_t rc = EOK; - - /* Notice: desc->id, desc->baseid will be assigned by invoker later. */ - desc->dbid = u_sess->proc_cxt.MyDatabaseId; - desc->relid = part->pd_id; - - (void)TrGenObjName(desc->name, PartitionRelationId, desc->relid); - - rc = strncpy_s(desc->originname, NAMEDATALEN, RelationGetRelationName(rel), - strlen(RelationGetRelationName(rel))); - securec_check(rc, "\0", "\0"); - - int len = strlen(PartitionGetPartitionName(part)) + strlen(RelationGetRelationName(rel)) + 1; - rc = strcat_s(desc->originname, len, PartitionGetPartitionName(part)); - securec_check(rc, "\0", "\0"); - - desc->operation = operType; - desc->type = objType; - desc->recyclecsn = t_thrd.xact_cxt.ShmemVariableCache->nextCommitSeqNo; - desc->recycletime = GetCurrentTimestamp(); - desc->createcsn = RelationGetCreatecsn(rel); - desc->changecsn = RelationGetChangecsn(rel); - desc->nspace = RelationGetNamespace(rel); - desc->owner = RelationGetOwner(rel); - desc->tablespace = part->pd_part->reltablespace; - desc->relfilenode = part->pd_part->relfilenode; - desc->frozenxid = part->pd_part->relfrozenxid; - desc->frozenxid64 = PartGetRelFrozenxid64(part); - desc->canrestore = false; - desc->canpurge = canpurge; -} - -static void TrDescRead(TrObjDesc *desc, HeapTuple rbtup) -{ - Form_pg_recyclebin rbForm = (Form_pg_recyclebin)GETSTRUCT(rbtup); - - desc->id = HeapTupleGetOid(rbtup); - desc->baseid = rbForm->rcybaseid; - - desc->dbid = rbForm->rcydbid; - desc->relid = rbForm->rcyrelid; - (void)namestrcpy((Name)desc->name, NameStr(rbForm->rcyname)); - (void)namestrcpy((Name)desc->originname, NameStr(rbForm->rcyoriginname)); - desc->operation = (rbForm->rcyoperation == 'd') ? RB_OPER_DROP : RB_OPER_TRUNCATE; - desc->type = (TrObjType)rbForm->rcytype; - desc->recyclecsn = rbForm->rcyrecyclecsn; - desc->recycletime = rbForm->rcyrecycletime; - desc->createcsn = rbForm->rcycreatecsn; - desc->changecsn = rbForm->rcychangecsn; - desc->nspace = rbForm->rcynamespace; - desc->owner = rbForm->rcyowner; - desc->tablespace = rbForm->rcytablespace; - desc->relfilenode = rbForm->rcyrelfilenode; - desc->canrestore = rbForm->rcycanrestore; - desc->canpurge = rbForm->rcycanpurge; - desc->frozenxid = rbForm->rcyfrozenxid; - desc->frozenxid64 = TrRbGetRcyfrozenxid64(rbtup); -} - -Oid TrDescWrite(TrObjDesc *desc) -{ - Relation rel; - HeapTuple tup; - bool nulls[Natts_pg_recyclebin] = {0}; - Datum values[Natts_pg_recyclebin]; - NameData name; - NameData originname; - Oid rbid; - - values[Anum_pg_recyclebin_rcydbid - 1] = ObjectIdGetDatum(desc->dbid); - values[Anum_pg_recyclebin_rcybaseid - 1] = ObjectIdGetDatum(desc->baseid); - values[Anum_pg_recyclebin_rcyrelid - 1] = ObjectIdGetDatum(desc->relid); - (void)namestrcpy(&name, desc->name); - values[Anum_pg_recyclebin_rcyname - 1] = NameGetDatum(&name); - (void)namestrcpy(&originname, desc->originname); - values[Anum_pg_recyclebin_rcyoriginname - 1] = NameGetDatum(&originname); - values[Anum_pg_recyclebin_rcyoperation - 1] = (desc->operation == RB_OPER_DROP) ? 'd' : 't'; - values[Anum_pg_recyclebin_rcytype - 1] = Int32GetDatum(desc->type); - values[Anum_pg_recyclebin_rcyrecyclecsn - 1] = Int64GetDatum(desc->recyclecsn); - values[Anum_pg_recyclebin_rcyrecycletime - 1] = TimestampTzGetDatum(desc->recycletime); - values[Anum_pg_recyclebin_rcycreatecsn - 1] = Int64GetDatum(desc->createcsn); - values[Anum_pg_recyclebin_rcychangecsn - 1] = Int64GetDatum(desc->changecsn); - values[Anum_pg_recyclebin_rcynamespace - 1] = ObjectIdGetDatum(desc->nspace); - values[Anum_pg_recyclebin_rcyowner - 1] = ObjectIdGetDatum(desc->owner); - values[Anum_pg_recyclebin_rcytablespace - 1] = ObjectIdGetDatum(desc->tablespace); - values[Anum_pg_recyclebin_rcyrelfilenode - 1] = ObjectIdGetDatum(desc->relfilenode); - values[Anum_pg_recyclebin_rcycanrestore - 1] = BoolGetDatum(desc->canrestore); - values[Anum_pg_recyclebin_rcycanpurge - 1] = BoolGetDatum(desc->canpurge); - values[Anum_pg_recyclebin_rcyfrozenxid - 1] = ShortTransactionIdGetDatum(desc->frozenxid); - values[Anum_pg_recyclebin_rcyfrozenxid64 - 1] = TransactionIdGetDatum(desc->frozenxid64); - - rel = heap_open(RecyclebinRelationId, RowExclusiveLock); - - tup = heap_form_tuple(RelationGetDescr(rel), values, nulls); - - rbid = simple_heap_insert(rel, tup); - - CatalogUpdateIndexes(rel, tup); - - heap_freetuple_ext(tup); - - heap_close(rel, RowExclusiveLock); - - CommandCounterIncrement(); - - return rbid; -} - -static bool TrFetchOrinameImpl(Oid nspId, const char *oriname, TrObjType type, - TrObjDesc *desc, TrOperMode operMode) -{ - Relation rbRel; - SysScanDesc sd; - ScanKeyData skey[3]; - HeapTuple tup; - bool found = false; - - if (!OidIsValid(nspId)) { - return false; - } - - rbRel = heap_open(RecyclebinRelationId, AccessShareLock); - - ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcynamespace, BTEqualStrategyNumber, - F_OIDEQ, ObjectIdGetDatum(nspId)); - ScanKeyInit(&skey[1], Anum_pg_recyclebin_rcydbid, BTEqualStrategyNumber, - F_OIDEQ, ObjectIdGetDatum(u_sess->proc_cxt.MyDatabaseId)); - ScanKeyInit(&skey[2], Anum_pg_recyclebin_rcyoriginname, BTEqualStrategyNumber, - F_NAMEEQ, CStringGetDatum(oriname)); - - sd = systable_beginscan(rbRel, RecyclebinDbidNspOrinameIndexId, true, NULL, 3, skey); - while ((tup = systable_getnext(sd)) != NULL) { - Form_pg_recyclebin rbForm = (Form_pg_recyclebin)GETSTRUCT(tup); - if ((rbForm->rcytype != type && rbForm->rcytype == RB_OBJ_TABLE) || - (rbForm->rcytype != type && rbForm->rcytype == RB_OBJ_INDEX) || - (operMode == RB_OPER_RESTORE_DROP && rbForm->rcyoperation != 'd') || - (operMode == RB_OPER_RESTORE_TRUNCATE && rbForm->rcyoperation != 't')) { - continue; - } - - found = true; - TrDescRead(desc, tup); - break; - } - - systable_endscan(sd); - heap_close(rbRel, AccessShareLock); - - return found; -} - -bool TrFetchName(const char *rcyname, TrObjType type, TrObjDesc *desc, TrOperMode operMode) -{ - Relation rbRel; - SysScanDesc sd; - ScanKeyData skey[1]; - HeapTuple tup; - bool found = false; - - rbRel = heap_open(RecyclebinRelationId, AccessShareLock); - - ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcyname, BTEqualStrategyNumber, - F_NAMEEQ, CStringGetDatum(rcyname)); - - sd = systable_beginscan(rbRel, RecyclebinNameIndexId, true, NULL, 1, skey); - if ((tup = systable_getnext(sd)) != NULL) { - Form_pg_recyclebin rbForm = (Form_pg_recyclebin)GETSTRUCT(tup); - if ((rbForm->rcytype != type && rbForm->rcytype == RB_OBJ_TABLE) || - (rbForm->rcytype != type && rbForm->rcytype == RB_OBJ_INDEX)) { - ereport(ERROR, - (errmsg("The recycle object \"%s\" type mismatched.", rcyname))); - } - if ((operMode == RB_OPER_RESTORE_DROP && rbForm->rcyoperation != 'd') || - (operMode == RB_OPER_RESTORE_TRUNCATE && rbForm->rcyoperation != 't')) { - ereport(ERROR, - (errmsg("recycle object \"%s\" desired does not exist", rcyname))); - } - - found = true; - TrDescRead(desc, tup); - } - - systable_endscan(sd); - heap_close(rbRel, AccessShareLock); - - return found; -} - -static bool TrFetchOriname(const char *schemaname, const char *relname, TrObjType type, - TrObjDesc *desc, TrOperMode operMode) -{ - bool found = false; - Oid nspId; - - if (schemaname) { - nspId = get_namespace_oid(schemaname, true); - found = TrFetchOrinameImpl(nspId, relname, type, desc, operMode); - } else { - List *activeSearchPath = NIL; - ListCell *l = NULL; - - recomputeNamespacePath(); - activeSearchPath = list_copy(u_sess->catalog_cxt.activeSearchPath); - foreach (l, activeSearchPath) { - nspId = lfirst_oid(l); - if (TrFetchOrinameImpl(nspId, relname, type, desc, operMode)) { - found = true; - break; - } - } - list_free_ext(activeSearchPath); - if (!found) { - nspId = PG_TOAST_NAMESPACE; - found = TrFetchOrinameImpl(nspId, relname, type, desc, operMode); - } - } - - return found; -} - -void TrUpdateBaseid(const TrObjDesc *desc) -{ - Relation rbRel; - SysScanDesc sd; - ScanKeyData skey[1]; - HeapTuple tup; - HeapTuple newtup; - Datum values[Natts_pg_recyclebin] = { 0 }; - bool nulls[Natts_pg_recyclebin] = { false }; - bool replaces[Natts_pg_recyclebin] = { false }; - - rbRel = heap_open(RecyclebinRelationId, RowExclusiveLock); - - ScanKeyInit(&skey[0], ObjectIdAttributeNumber, BTEqualStrategyNumber, - F_OIDEQ, ObjectIdGetDatum(desc->id)); - - sd = systable_beginscan(rbRel, RecyclebinIdIndexId, true, NULL, 1, skey); - if ((tup = systable_getnext(sd)) == NULL) { - ereport(ERROR, (errmsg("recycle object %u does not exist", desc->id))); - } - - replaces[Anum_pg_recyclebin_rcybaseid - 1] = true; - values[Anum_pg_recyclebin_rcybaseid - 1] = ObjectIdGetDatum(desc->baseid); - - newtup = heap_modify_tuple(tup, RelationGetDescr(rbRel), values, nulls, replaces); - - simple_heap_update(rbRel, &newtup->t_self, newtup); - - CatalogUpdateIndexes(rbRel, newtup); - - heap_freetuple_ext(newtup); - - systable_endscan(sd); - heap_close(rbRel, RowExclusiveLock); - - return; -} - -static void TrLockRelationImpl(Oid relid, TrObjType type) -{ - /* - * Lock failed may due to concurrently purge/timecapsule/DQL - * on recycle object, or access on normal relation. - */ - if (!ConditionalLockRelationOid(relid, AccessExclusiveLock)) { - ereport(ERROR, - (errcode(ERRCODE_RBIN_LOCK_NOT_AVAILABLE), - errmsg("could not obtain lock on relation \"%u\"", relid))); - } - - /* - * Now that we have the lock, probe to see if the relation - * really exists or not. - */ - AcceptInvalidationMessages(); - if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid)) && type != RB_OBJ_PARTITION) { - /* Clean already held locks if error return. */ - UnlockRelationOid(relid, AccessExclusiveLock); - ereport(ERROR, - (errcode(ERRCODE_RBIN_UNDEFINED_OBJECT), - errmsg("relation \"%u\" does not exist", relid))); - } else if (!SearchSysCacheExists1(PARTRELID, ObjectIdGetDatum(relid)) && type == RB_OBJ_PARTITION) { - /* Clean already held locks if error return. */ - UnlockRelationOid(relid, AccessExclusiveLock); - ereport(ERROR, - (errcode(ERRCODE_RBIN_UNDEFINED_OBJECT), - errmsg("partition \"%u\" does not exist", relid))); - } -} - -static void TrLockRelation(TrObjDesc *desc) -{ - Oid heapOid = InvalidOid; - - /* Lock heap relation for index first */ - if (desc->type == RB_OBJ_INDEX) { - heapOid = IndexGetRelation(desc->relid, true); - if (!OidIsValid(heapOid)) { - ereport(ERROR, - (errcode(ERRCODE_RBIN_UNDEFINED_OBJECT), - errmsg("relation \"%u\" does not exist", desc->relid))); - } - TrLockRelationImpl(heapOid, desc->type); - } - - /* Use TRY-CATCH block to clean locks already held if error. */ - PG_TRY(); - { - /* Lock relation self */ - TrLockRelationImpl(desc->relid, desc->type); - } - PG_CATCH(); - { - if (desc->type == RB_OBJ_INDEX) { - UnlockRelationOid(heapOid, AccessExclusiveLock); - } - PG_RE_THROW(); - } - PG_END_TRY(); -} - -static void TrUnlockTrItem(TrObjDesc *desc) -{ - UnlockDatabaseObject(RecyclebinRelationId, desc->id, 0, - AccessExclusiveLock); -} - -static void TrLockTrItem(TrObjDesc *desc) -{ - Relation rbRel; - SysScanDesc sd; - ScanKeyData skey[1]; - HeapTuple tup; - - /* 1. Try to lock rb item in AccessExclusiveLock */ - if (!ConditionalLockDatabaseObject(RecyclebinRelationId, desc->id, 0, AccessExclusiveLock)) { - ereport(ERROR, - (errcode(ERRCODE_RBIN_LOCK_NOT_AVAILABLE), - errmsg("could not obtain lock on recycle object '%s'", desc->name))); - } - - /* - * 2. Now that we have the lock, probe to see if the rb item really - * exists or not. - */ - AcceptInvalidationMessages(); - - rbRel = heap_open(RecyclebinRelationId, AccessShareLock); - - ScanKeyInit(&skey[0], ObjectIdAttributeNumber, BTEqualStrategyNumber, - F_OIDEQ, ObjectIdGetDatum(desc->id)); - - sd = systable_beginscan(rbRel, RecyclebinIdIndexId, true, NULL, 1, skey); - if ((tup = systable_getnext(sd)) == NULL) { - UnlockDatabaseObject(RecyclebinRelationId, desc->id, 0, AccessExclusiveLock); - systable_endscan(sd); - heap_close(rbRel, AccessShareLock); - ereport(ERROR, - (errcode(ERRCODE_RBIN_UNDEFINED_OBJECT), - errmsg("recycle object \"%s\" does not exist", desc->name))); - } - - systable_endscan(sd); - heap_close(rbRel, AccessShareLock); - - return; -} - -static void TrOperMatch(const TrObjDesc *desc, TrOperMode operMode) -{ - switch (operMode) { - case RB_OPER_PURGE: - if (!desc->canpurge && desc->type != RB_OBJ_PARTITION) { - ereport(ERROR, - (errcode(ERRCODE_INVALID_OPERATION), - errmsg("recycle object \"%s\" cannot be purged", desc->name))); - } - break; - - case RB_OPER_RESTORE_DROP: - if ((!desc->canrestore && desc->type != RB_OBJ_PARTITION) || desc->operation != RB_OPER_DROP) { - ereport(ERROR, - (errcode(ERRCODE_INVALID_OPERATION), - errmsg("recycle object \"%s\" cannot be restored", desc->name))); - } - break; - - case RB_OPER_RESTORE_TRUNCATE: - if ((!desc->canrestore && desc->type != RB_OBJ_PARTITION) || desc->operation != RB_OPER_TRUNCATE) { - ereport(ERROR, - (errcode(ERRCODE_INVALID_OPERATION), - errmsg("recycle object \"%s\" cannot be restored", desc->name))); - } - break; - - default: - ereport(ERROR, - (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE), - errmsg("unrecognized recyclebin operation: %u", operMode))); - break; - } -} - -/* - * Fetch object from recycle bin for rb operations - purge, restore : - * Prefer to fetch as original name, then recycle name. - */ -void TrOperFetch(const RangeVar *purobj, TrObjType objtype, TrObjDesc *desc, TrOperMode operMode) -{ - bool found = false; - - AcceptInvalidationMessages(); - - /* Prefer to fetch as original name */ - found = TrFetchOriname(purobj->schemaname, purobj->relname, objtype, desc, operMode); - /* if not found, then fetch as recycle name */ - if (!found) { - found = TrFetchName(purobj->relname, objtype, desc, operMode); - } - - /* not found, throw error */ - if (!found) { - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_TABLE), - errmsg("recycle object \"%s\" desired does not exist", purobj->relname))); - } - - TrOperMatch(desc, operMode); - - return; -} - -static void TrPermRestore(TrObjDesc *desc, TrOperMode operMode) -{ - AclResult aclCreateResult; - - /* Check namespace permissions. */ - aclCreateResult = pg_namespace_aclcheck(desc->nspace, desc->authid, ACL_CREATE); - if (aclCreateResult != ACLCHECK_OK) { - aclcheck_error(aclCreateResult, ACL_KIND_NAMESPACE, get_namespace_name(desc->nspace)); - } - - AclResult aclUsageResult = pg_namespace_aclcheck(desc->nspace, desc->authid, ACL_USAGE); - if (aclUsageResult != ACLCHECK_OK) { - aclcheck_error(aclUsageResult, ACL_KIND_NAMESPACE, get_namespace_name(desc->nspace)); - } - - /* Allow restore to either table owner or schema owner */ - if (!pg_class_ownercheck(desc->relid, desc->authid) && !pg_namespace_ownercheck(desc->nspace, desc->authid)) { - aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, desc->name); - return; - } - - if (operMode == RB_OPER_RESTORE_TRUNCATE) { - AclResult aclTruncateResult = pg_class_aclcheck(desc->relid, desc->authid, ACL_TRUNCATE); - if (aclTruncateResult != ACLCHECK_OK) { - aclcheck_error(aclTruncateResult, ACL_KIND_CLASS, desc->name); - } - } -} - -static void TrPermPurge(TrObjDesc *desc, TrOperMode operMode) -{ - AclResult result; - - result = pg_namespace_aclcheck(desc->nspace, desc->authid, ACL_USAGE); - if (result != ACLCHECK_OK) { - aclcheck_error(result, ACL_KIND_NAMESPACE, get_namespace_name(desc->nspace)); - } - if (!pg_class_ownercheck(desc->relid, desc->authid) && !pg_namespace_ownercheck(desc->nspace, desc->authid)) { - aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, desc->name); - } -} - -/* - * Check permission for rb operations - purge, restore - */ -static void TrPerm(TrObjDesc *desc, TrOperMode operMode) -{ - switch (operMode) { - case RB_OPER_RESTORE_DROP: - case RB_OPER_RESTORE_TRUNCATE: - TrPermRestore(desc, operMode); - break; - case RB_OPER_PURGE: - TrPermPurge(desc, operMode); - break; - default: - /* Never reached here. */ - Assert(0); - break; - } -} - -/* - * Prepare for rb operations - purge, restore : - * check permission, lock objects - */ -void TrOperPrep(TrObjDesc *desc, TrOperMode operMode) -{ - bool needLockRelation = false; - - /* - * 1. Check permission. - */ - TrPerm(desc, operMode); - - /* - * 2. Acquire lock on rb item, avoid concurrently purge, restore. - */ - TrLockTrItem(desc); - - /* - * 3. Acquire lock on relation, avoid concurrently DQL. - * Notice: ignore this step when we purge truncated relation - * as base relation may not exists. - */ - needLockRelation = !(operMode == RB_OPER_PURGE && desc->operation == RB_OPER_TRUNCATE); - if (needLockRelation) { - /* Use TRY-CATCH block to clean locks already held if error. */ - PG_TRY(); - { - TrLockRelation(desc); - } - PG_CATCH(); - { - TrUnlockTrItem(desc); - PG_RE_THROW(); - } - PG_END_TRY(); - } -} - -bool NeedTrComm(Oid relid) -{ - Relation rel; - Form_pg_class classForm; - - if (/* - *Disable Recyclebin-based-Drop/Truncate when - */ - /* recyclebin disabled, or */ - !u_sess->attr.attr_storage.enable_recyclebin || - /* target db is template1, or */ - u_sess->proc_cxt.MyDatabaseId == TemplateDbOid || - /* in maintenance mode, or */ - u_sess->attr.attr_common.xc_maintenance_mode || - /* in in-place upgrade mode, or */ - t_thrd.proc->workingVersionNum < 92350 || - /* in non-singlenode mode, or */ - (g_instance.role != VSINGLENODE) || - /* in bootstrap mode. */ - IsInitdb) { - return false; - } - - rel = relation_open(relid, NoLock); - classForm = rel->rd_rel; - if (/* - * Disable Recyclebin-based-Drop/Truncate if - */ - /* table is non ordinary table, or */ - classForm->relkind != RELKIND_RELATION || - /* is non heap table, or */ - rel->rd_tam_type == TAM_HEAP || - /* is non regular table, or */ - classForm->relpersistence != RELPERSISTENCE_PERMANENT || - /* is shared table across databases, or */ - classForm->relisshared || - /* has derived classes, or */ - classForm->relhassubclass || - /* has any PARTIAL CLUSTER KEY, or */ - classForm->relhasclusterkey || - /* is cstore table, or */ - (rel->rd_options && StdRelOptIsColStore(rel->rd_options)) || RelationIsColStore(rel) || - /* is hbkt table, or */ - (RELATION_HAS_BUCKET(rel) || RELATION_OWN_BUCKET(rel)) || - /* is dfs table, or */ - RelationIsPAXFormat(rel) || - /* is resizing, or */ - RelationInClusterResizing(rel) || - /* is in system namespace. */ - (IsSystemNamespace(classForm->relnamespace) || IsToastNamespace(classForm->relnamespace) || - IsCStoreNamespace(classForm->relnamespace))) { - relation_close(rel, NoLock); - return false; - } - - relation_close(rel, NoLock); - - return true; -} - -TrObjType TrGetObjType(Oid nspId, char relKind) -{ - TrObjType type = RB_OBJ_TABLE; - - switch (relKind) { - case RELKIND_INDEX: - type = IsToastNamespace(nspId) ? RB_OBJ_TOAST_INDEX : RB_OBJ_INDEX; - break; - case RELKIND_RELATION: - type = RB_OBJ_TABLE; - break; - case RELKIND_SEQUENCE: - case RELKIND_LARGE_SEQUENCE: - type = RB_OBJ_SEQUENCE; - break; - case RELKIND_TOASTVALUE: - type = RB_OBJ_TOAST; - break; - case PARTTYPE_PARTITIONED_RELATION: - type = RB_OBJ_PARTITION; - break; - case RELKIND_GLOBAL_INDEX: - type = RB_OBJ_GLOBAL_INDEX; - break; - case RELKIND_MATVIEW: - type = RB_OBJ_MATVIEW; - break; - default: - /* Never reached here. */ - Assert(0); - break; - } - - return type; -} - -static bool TrObjAddrExists(Oid classid, Oid objid, ObjectAddresses *objSet) -{ - int i; - - for (i = 0; i < objSet->numrefs; i++) { - if (TrObjIsEqualEx(classid, objid, &objSet->refs[i])) { - return true; - } - } - - return false; -} - -/* - * output: refobjs - */ -void TrFindAllRefObjs(Relation depRel, const ObjectAddress *subobj, - ObjectAddresses *refobjs, bool ignoreObjSubId) -{ - SysScanDesc sd; - HeapTuple tuple; - ScanKeyData key[3]; - int nkeys; - - ScanKeyInit(&key[0], Anum_pg_depend_classid, BTEqualStrategyNumber, F_OIDEQ, - ObjectIdGetDatum(subobj->classId)); - ScanKeyInit(&key[1], Anum_pg_depend_objid, BTEqualStrategyNumber, F_OIDEQ, - ObjectIdGetDatum(subobj->objectId)); - nkeys = 2; - if (!ignoreObjSubId && subobj->objectSubId != 0) { - ScanKeyInit(&key[2], Anum_pg_depend_objsubid, BTEqualStrategyNumber, F_INT4EQ, - Int32GetDatum(subobj->objectSubId)); - nkeys = 3; - } - - sd = systable_beginscan(depRel, DependDependerIndexId, true, NULL, nkeys, key); - while (HeapTupleIsValid(tuple = systable_getnext(sd))) { - Form_pg_depend depForm = (Form_pg_depend)GETSTRUCT(tuple); - /* Cascaded clean rb object in `DROP SCHEMA` command. */ - if (depForm->refclassid == NamespaceRelationId) { - continue; - } - - /* We keep `objSet` unique when `ignoreObjSubId = true` to avoid circle recursive. */ - if (!ignoreObjSubId || !TrObjAddrExists(depForm->refclassid, depForm->refobjid, refobjs)) { - add_object_address_ext(depForm->refclassid, depForm->refobjid, - depForm->refobjsubid, depForm->deptype, refobjs); - } - } - - systable_endscan(sd); - return; -} - -static void TrFindAllInternalObjs(Relation depRel, const ObjectAddress *refobj, - ObjectAddresses *objSet, bool ignoreObjSubId = false) -{ - SysScanDesc sd; - HeapTuple tuple; - ScanKeyData key[3]; - int nkeys; - - ScanKeyInit(&key[0], Anum_pg_depend_refclassid, BTEqualStrategyNumber, F_OIDEQ, - ObjectIdGetDatum(refobj->classId)); - ScanKeyInit(&key[1], Anum_pg_depend_refobjid, BTEqualStrategyNumber, F_OIDEQ, - ObjectIdGetDatum(refobj->objectId)); - nkeys = 2; - if (!ignoreObjSubId && refobj->objectSubId != 0) { - ScanKeyInit(&key[2], Anum_pg_depend_refobjsubid, BTEqualStrategyNumber, F_INT4EQ, - Int32GetDatum(refobj->objectSubId)); - nkeys = 3; - } - - sd = systable_beginscan(depRel, DependReferenceIndexId, true, NULL, nkeys, key); - while (HeapTupleIsValid(tuple = systable_getnext(sd))) { - Form_pg_depend depForm = (Form_pg_depend)GETSTRUCT(tuple); - if (depForm->deptype != 'i') { - continue; - } - - /* We keep `objSet` unique when `ignoreObjSubId = true` to avoid circle recursive. */ - if (!ignoreObjSubId || !TrObjAddrExists(depForm->classid, depForm->objid, objSet)) { - add_object_address_ext(depForm->classid, depForm->objid, - depForm->objsubid, depForm->deptype, objSet); - } - } - - systable_endscan(sd); - return; -} - -static void TrDoPurgeObject(TrObjDesc *desc) -{ - if (desc->operation == RB_OPER_DROP) { - TrDoPurgeObjectDrop(desc); - } else { - TrDoPurgeObjectTruncate(desc); - } -} - -void TrPurgeObject(RangeVar *purobj, TrObjType type) -{ - TrObjDesc desc; - - TrOperFetch(purobj, type, &desc, RB_OPER_PURGE); - - desc.authid = GetUserId(); - TrOperPrep(&desc, RB_OPER_PURGE); - - TrDoPurgeObject(&desc); - - return; -} - -const int PURGE_BATCH = 64; -const int PURGE_SINGL = 64; -typedef void (*TrFetchBeginHook)(SysScanDesc *sd, Oid objId); -typedef bool (*TrFetchMatchHook)(Relation rbRel, HeapTuple rbTup, Oid objId); - -static void TrFetchBegin(TrFetchBeginHook fetchHook, SysScanDesc *sd, Oid objId) -{ - fetchHook(sd, objId); -} - -// @return: true for eof -static bool TrFetchExec(TrFetchMatchHook matchHook, Oid objId, SysScanDesc sd, TrObjDesc *desc) -{ - HeapTuple tup; - while ((tup = systable_getnext(sd)) != NULL) { - Form_pg_recyclebin rbForm = (Form_pg_recyclebin)GETSTRUCT(tup); - if ((rbForm->rcytype == RB_OBJ_TABLE) && matchHook(sd->heap_rel, tup, objId)) { - Assert (rbForm->rcycanpurge); - TrDescRead(desc, tup); - return false; - } else if ((rbForm->rcytype == RB_OBJ_PARTITION) && matchHook(sd->heap_rel, tup, objId)) { - Assert (!rbForm->rcycanpurge); - TrDescRead(desc, tup); - return false; - } - } - return true; -} - -static void TrFetchEnd(SysScanDesc sd) -{ - Relation rbRel = sd->heap_rel; - - systable_endscan(sd); - heap_close(rbRel, AccessShareLock); -} - -static bool TrPurgeBatch(TrFetchBeginHook beginHook, TrFetchMatchHook matchHook, - Oid objId, Oid roleid, uint32 maxBatch, PurgeMsgRes *localRes) -{ - SysScanDesc sd = NULL; - TrObjDesc desc; - uint32 count = 0; - bool eof = false; - - RbMsgResetRes(localRes); - - StartTransactionCommand(); - - TrFetchBegin(beginHook, &sd, objId); - while (!(eof = TrFetchExec(matchHook, objId, sd, &desc))) { - CHECK_FOR_INTERRUPTS(); - - PG_TRY(); - { - desc.authid = roleid; - TrOperPrep(&desc, RB_OPER_PURGE); - - TrDoPurgeObject(&desc); - localRes->purgedNum++; - } - PG_CATCH(); - { - int errcode = geterrcode(); - if (errcode == ERRCODE_RBIN_LOCK_NOT_AVAILABLE) { - errno_t rc; - rc = strncpy_s(localRes->errMsg, RB_MAX_ERRMSG_SIZE, Geterrmsg(), RB_MAX_ERRMSG_SIZE - 1); - securec_check(rc, "\0", "\0"); - localRes->skippedNum++; - } else if (errcode == ERRCODE_RBIN_UNDEFINED_OBJECT) { - localRes->undefinedNum++; - } else { - PG_RE_THROW(); - } - } - PG_END_TRY(); - - if (++count >= maxBatch) { - break; - } - } - - TrFetchEnd(sd); - - CommitTransactionCommand(); - - return eof; -} - -static void TrFetchBeginSpace(SysScanDesc *sd, Oid spcId) -{ - ScanKeyData skey[2]; - Relation rbRel; - - rbRel = heap_open(RecyclebinRelationId, AccessShareLock); - - ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcytablespace, BTEqualStrategyNumber, - F_OIDEQ, ObjectIdGetDatum(spcId)); - ScanKeyInit(&skey[1], Anum_pg_recyclebin_rcydbid, BTEqualStrategyNumber, - F_OIDEQ, ObjectIdGetDatum(u_sess->proc_cxt.MyDatabaseId)); - - *sd = systable_beginscan(rbRel, RecyclebinDbidSpcidRcycsnIndexId, true, NULL, 2, skey); -} - -static bool TrFetchMatchSpace(Relation rbRel, HeapTuple rbTup, Oid objId) -{ - Form_pg_recyclebin rbForm = (Form_pg_recyclebin)GETSTRUCT(rbTup); - return rbForm->rcytablespace == objId; -} - -void TrPurgeTablespace(int64 id) -{ - PurgeMsgReq *req = &RbMsg(id)->req; - PurgeMsgRes localRes; - bool eof = false; - - do { - eof = TrPurgeBatch(TrFetchBeginSpace, TrFetchMatchSpace, req->objId, req->authId, PURGE_BATCH, &localRes); - RbMsgSetStatistics(id, &localRes); - } while (!eof && localRes.skippedNum == 0); -} - -void TrPurgeTablespaceDML(int64 id) -{ - PurgeMsgReq *req = &RbMsg(id)->req; - PurgeMsgRes localRes; - bool eof = false; - - do { - eof = TrPurgeBatch(TrFetchBeginSpace, TrFetchMatchSpace, req->objId, req->authId, PURGE_SINGL, &localRes); - RbMsgSetStatistics(id, &localRes); - } while (!eof && localRes.purgedNum == 0); -} - -static void TrFetchBeginRecyclebin(SysScanDesc *sd, Oid objId) -{ - ScanKeyData skey[2]; - Relation rbRel; - - rbRel = heap_open(RecyclebinRelationId, AccessShareLock); - - ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcydbid, BTEqualStrategyNumber, - F_OIDEQ, ObjectIdGetDatum(u_sess->proc_cxt.MyDatabaseId)); - - *sd = systable_beginscan(rbRel, RecyclebinDbidRelidIndexId, true, NULL, 1, skey); -} - -static bool TrFetchMatchRecyclebin(Relation rbRel, HeapTuple rbTup, Oid objId) -{ - return true; -} - -void TrPurgeRecyclebin(int64 id) -{ - PurgeMsgReq *req = &RbMsg(id)->req; - PurgeMsgRes localRes; - bool eof = false; - - do { - eof = TrPurgeBatch(TrFetchBeginRecyclebin, TrFetchMatchRecyclebin, - InvalidOid, req->authId, PURGE_BATCH, &localRes); - RbMsgSetStatistics(id, &localRes); - } while (!eof && localRes.skippedNum == 0); -} - -static void TrFetchBeginSchema(SysScanDesc *sd, Oid objId) -{ - ScanKeyData skey[2]; - Relation rbRel; - - rbRel = heap_open(RecyclebinRelationId, AccessShareLock); - ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcynamespace, BTEqualStrategyNumber, - F_OIDEQ, ObjectIdGetDatum(objId)); - ScanKeyInit(&skey[1], Anum_pg_recyclebin_rcydbid, BTEqualStrategyNumber, - F_OIDEQ, ObjectIdGetDatum(u_sess->proc_cxt.MyDatabaseId)); - - *sd = systable_beginscan(rbRel, RecyclebinDbidNspOrinameIndexId, true, NULL, 2, skey); -} - -static bool TrFetchMatchSchema(Relation rbRel, HeapTuple rbTup, Oid objId) -{ - Form_pg_recyclebin rbForm = (Form_pg_recyclebin)GETSTRUCT(rbTup); - return rbForm->rcynamespace == objId; -} - -void TrPurgeSchema(int64 id) -{ - PurgeMsgReq *req = &RbMsg(id)->req; - PurgeMsgRes localRes; - bool eof = false; - - do { - eof = TrPurgeBatch(TrFetchBeginSchema, TrFetchMatchSchema, req->objId, req->authId, PURGE_BATCH, &localRes); - RbMsgSetStatistics(id, &localRes); - } while (!eof && localRes.skippedNum == 0); -} - -static void TrFetchBeginUser(SysScanDesc *sd, Oid objId) -{ - ScanKeyData skey[2]; - Relation rbRel; - - rbRel = heap_open(RecyclebinRelationId, AccessShareLock); - - ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcydbid, BTEqualStrategyNumber, - F_OIDEQ, ObjectIdGetDatum(u_sess->proc_cxt.MyDatabaseId)); - - *sd = systable_beginscan(rbRel, RecyclebinDbidRelidIndexId, true, NULL, 1, skey); -} - -static bool TrFetchMatchUser(Relation rbRel, HeapTuple rbTup, Oid objId) -{ - Form_pg_recyclebin rbForm = (Form_pg_recyclebin)GETSTRUCT(rbTup); - return rbForm->rcyowner == objId; -} - -void TrPurgeUser(int64 id) -{ - PurgeMsgReq *req = &RbMsg(id)->req; - PurgeMsgRes localRes; - bool eof = false; - - do { - eof = TrPurgeBatch(TrFetchBeginUser, TrFetchMatchUser, req->objId, req->authId, PURGE_BATCH, &localRes); - RbMsgSetStatistics(id, &localRes); - } while (!eof && localRes.skippedNum == 0); -} - -static void TrFetchBeginAuto(SysScanDesc *sd, Oid objId) -{ - ScanKeyData skey[2]; - Relation rbRel; - - rbRel = heap_open(RecyclebinRelationId, AccessShareLock); - - ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcydbid, BTEqualStrategyNumber, - F_OIDEQ, ObjectIdGetDatum(u_sess->proc_cxt.MyDatabaseId)); - - *sd = systable_beginscan(rbRel, RecyclebinDbidRelidIndexId, true, NULL, 1, skey); -} - -static bool TrFetchMatchAuto(Relation rbRel, HeapTuple rbTup, Oid objId) -{ - bool isNull = false; - Datum datumRcyTime = heap_getattr(rbTup, Anum_pg_recyclebin_rcyrecycletime, - RelationGetDescr(rbRel), &isNull); - - long secs; - int msecs; - TimestampDifference(isNull ? 0 : DatumGetTimestampTz(datumRcyTime), - GetCurrentTimestamp(), &secs, &msecs); - - return secs > u_sess->attr.attr_storage.recyclebin_retention_time || secs < 0; -} - -void TrPurgeAuto(int64 id) -{ - PurgeMsgReq *req = &RbMsg(id)->req; - PurgeMsgRes localRes; - bool eof = false; - do { - eof = TrPurgeBatch(TrFetchBeginAuto, TrFetchMatchAuto, InvalidOid, req->authId, PURGE_BATCH, &localRes); - RbMsgSetStatistics(id, &localRes); - } while (!eof); -} - -void TrSwapRelfilenode(Relation rbRel, HeapTuple rbTup, bool isPart) -{ - Relation relRel; - HeapTuple relTup; - HeapTuple newTup; - TrObjDesc desc; - int maxNattr = 0; - Datum *values = NULL; - bool *nulls = NULL; - bool *replaces = NULL; - NameData name; - errno_t rc = EOK; - bool isNull = false; - int relfilenoIndex = 0; - int frozenxidIndex = 0; - int frozenxid64Index = 0; - bool isPartition = false; - - TrDescRead(&desc, rbTup); - - if (desc.type == RB_OBJ_PARTITION || (desc.type == RB_OBJ_INDEX && isPart)) { - isPartition = true; - } - if (isPartition) { - maxNattr = Max(Natts_pg_partition, Natts_pg_recyclebin); - relRel = heap_open(PartitionRelationId, RowExclusiveLock); - relTup = SearchSysCacheCopy1(PARTRELID, ObjectIdGetDatum(desc.relid)); - relfilenoIndex = Anum_pg_partition_relfilenode; - frozenxidIndex = Anum_pg_partition_relfrozenxid; - frozenxid64Index = Anum_pg_partition_relfrozenxid64; - } else { - maxNattr = Max(Natts_pg_class, Natts_pg_recyclebin); - relRel = heap_open(RelationRelationId, RowExclusiveLock); - relTup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(desc.relid)); - relfilenoIndex = Anum_pg_class_relfilenode; - frozenxidIndex = Anum_pg_class_relfrozenxid; - frozenxid64Index = Anum_pg_class_relfrozenxid64; - } - - /* 1. Update pg_class or pg_partition */ - if (!HeapTupleIsValid(relTup)) { - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_TABLE), - errmsg("cache lookup failed for relation %u", desc.relid))); - } - - values = (Datum *)palloc0(sizeof(Datum) * maxNattr); - nulls = (bool *)palloc0(sizeof(bool) * maxNattr); - replaces = (bool *)palloc0(sizeof(bool) * maxNattr); - - replaces[relfilenoIndex - 1] = true; - values[relfilenoIndex - 1] = ObjectIdGetDatum(desc.relfilenode); - - replaces[frozenxidIndex - 1] = true; - values[frozenxidIndex - 1] = ShortTransactionIdGetDatum(desc.frozenxid); - - replaces[frozenxid64Index - 1] = true; - values[frozenxid64Index - 1] = TransactionIdGetDatum(desc.frozenxid64); - - newTup = heap_modify_tuple(relTup, RelationGetDescr(relRel), values, nulls, replaces); - - simple_heap_update(relRel, &newTup->t_self, newTup); - - CatalogUpdateIndexes(relRel, newTup); - - heap_freetuple_ext(newTup); - - /* 2. Update pg_recyclebin */ - rc = memset_s(values, sizeof(Datum) * maxNattr, 0, sizeof(Datum) * maxNattr); - securec_check(rc, "\0", "\0"); - rc = memset_s(nulls, sizeof(bool) * maxNattr, false, sizeof(bool) * maxNattr); - securec_check(rc, "\0", "\0"); - rc = memset_s(replaces, sizeof(bool) * maxNattr, false, sizeof(bool) * maxNattr); - securec_check(rc, "\0", "\0"); - - (void)TrGenObjName(NameStr(name), RelationRelationId, desc.relid); - replaces[Anum_pg_recyclebin_rcyname - 1] = true; - values[Anum_pg_recyclebin_rcyname - 1] = NameGetDatum(&name); - - replaces[Anum_pg_recyclebin_rcyoriginname - 1] = true; - if (isPartition) { - values[Anum_pg_recyclebin_rcyoriginname - 1] = NameGetDatum(&desc.originname); - } else { - values[Anum_pg_recyclebin_rcyoriginname - 1] = NameGetDatum(&((Form_pg_class)GETSTRUCT(relTup))->relname); - } - - replaces[Anum_pg_recyclebin_rcyrecyclecsn - 1] = true; - values[Anum_pg_recyclebin_rcyrecyclecsn - 1] = Int64GetDatum(t_thrd.xact_cxt.ShmemVariableCache->nextCommitSeqNo); - - replaces[Anum_pg_recyclebin_rcyrecycletime - 1] = true; - values[Anum_pg_recyclebin_rcyrecycletime - 1] = TimestampTzGetDatum(GetCurrentTimestamp()); - - replaces[Anum_pg_recyclebin_rcyrelfilenode - 1] = true; - if (isPartition) { - values[Anum_pg_recyclebin_rcyrelfilenode - 1] = - ObjectIdGetDatum(((Form_pg_partition)GETSTRUCT(relTup))->relfilenode); - } else { - values[Anum_pg_recyclebin_rcyrelfilenode - 1] = - ObjectIdGetDatum(((Form_pg_class)GETSTRUCT(relTup))->relfilenode); - } - - replaces[Anum_pg_recyclebin_rcyfrozenxid - 1] = true; - if (isPartition) { - values[Anum_pg_recyclebin_rcyfrozenxid - 1] = - ShortTransactionIdGetDatum(((Form_pg_partition)GETSTRUCT(relTup))->relfrozenxid); - } else { - values[Anum_pg_recyclebin_rcyfrozenxid - 1] = - ShortTransactionIdGetDatum(((Form_pg_class)GETSTRUCT(relTup))->relfrozenxid); - } - - replaces[Anum_pg_recyclebin_rcyfrozenxid64 - 1] = true; - Datum xid64datum = heap_getattr(relTup, frozenxid64Index, RelationGetDescr(relRel), &isNull); - values[Anum_pg_recyclebin_rcyfrozenxid64 - 1] = DatumGetTransactionId(xid64datum); - - newTup = heap_modify_tuple(rbTup, RelationGetDescr(rbRel), values, nulls, replaces); - - simple_heap_update(rbRel, &newTup->t_self, newTup); - - CatalogUpdateIndexes(rbRel, newTup); - - heap_freetuple_ext(newTup); - - pfree(values); - pfree(nulls); - pfree(replaces); - - heap_freetuple_ext(relTup); - heap_close(relRel, RowExclusiveLock); - return; -} - -void TrBaseRelMatched(TrObjDesc *baseDesc) -{ - ObjectAddress obj = {RelationRelationId, baseDesc->relid}; - if (TrIsRefRbObject(&obj)) { - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_OBJECT), - errmsg("relation \"%s\" does not exist", baseDesc->originname))); - } - - Relation rel = RelationIdGetRelation(baseDesc->relid); - Assert(RelationIsValid(rel)); - if (RelationGetCreatecsn(rel) != (CommitSeqNo)baseDesc->createcsn) { - ereport(ERROR, - (errmsg("The recycle object \"%s\" and relation \"%s\" mismatched.", - baseDesc->name, RelationGetRelationName(rel)))); - } - - if (RelationGetChangecsn(rel) > (CommitSeqNo)baseDesc->changecsn) { - ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), - errmsg("The table definition of \"%s\" has been changed.", - RelationGetRelationName(rel)))); - } - - RelationClose(rel); -} - -void TrAdjustFrozenXid64(Oid dbid, TransactionId *frozenXID) -{ - Relation rbRel; - SysScanDesc sd; - HeapTuple rbtup; - - if (!TcapFeatureAvail()) { - return; - } - - rbRel = heap_open(RecyclebinRelationId, AccessShareLock); - - sd = systable_beginscan(rbRel, InvalidOid, false, NULL, 0, NULL); - while ((rbtup = systable_getnext(sd)) != NULL) { - Form_pg_recyclebin rbForm = (Form_pg_recyclebin)GETSTRUCT(rbtup); - TransactionId rcyfrozenxid64; - - if (rbForm->rcydbid != dbid || (rbForm->rcytype != RB_OBJ_TABLE && rbForm->rcytype != RB_OBJ_TOAST)) { - continue; - } - - rcyfrozenxid64 = TrRbGetRcyfrozenxid64(rbtup, rbRel); - Assert(TransactionIdIsNormal(rcyfrozenxid64)); - - if (TransactionIdPrecedes(rcyfrozenxid64, *frozenXID)) { - *frozenXID = rcyfrozenxid64; - } - } - - systable_endscan(sd); - heap_close(rbRel, AccessShareLock); - - return; -} - -bool TrRbIsEmptyDb(Oid dbid) -{ - Relation rbRel; - SysScanDesc sd; - HeapTuple tup; - ScanKeyData skey[1]; - - ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcydbid, BTEqualStrategyNumber, F_OIDEQ, - ObjectIdGetDatum(dbid)); - - rbRel = heap_open(RecyclebinRelationId, AccessShareLock); - sd = systable_beginscan(rbRel, RecyclebinDbidRelidIndexId, true, NULL, 1, skey); - tup = systable_getnext(sd); - systable_endscan(sd); - heap_close(rbRel, AccessShareLock); - - return tup == NULL; -} - -bool TrRbIsEmptySpc(Oid spcId) -{ - Relation rbRel; - SysScanDesc sd; - ScanKeyData skey[1]; - HeapTuple tup; - - rbRel = heap_open(RecyclebinRelationId, AccessShareLock); - - ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcytablespace, BTEqualStrategyNumber, - F_OIDEQ, ObjectIdGetDatum(spcId)); - - sd = systable_beginscan(rbRel, RecyclebinDbidSpcidRcycsnIndexId, true, NULL, 1, skey); - tup = systable_getnext(sd); - systable_endscan(sd); - heap_close(rbRel, AccessShareLock); - - return tup == NULL; -} - -bool TrRbIsEmptySchema(Oid nspId) -{ - Relation rbRel; - SysScanDesc sd; - ScanKeyData skey[2]; - HeapTuple tup; - - rbRel = heap_open(RecyclebinRelationId, AccessShareLock); - - ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcynamespace, BTEqualStrategyNumber, - F_OIDEQ, ObjectIdGetDatum(nspId)); - ScanKeyInit(&skey[1], Anum_pg_recyclebin_rcydbid, BTEqualStrategyNumber, - F_OIDEQ, ObjectIdGetDatum(u_sess->proc_cxt.MyDatabaseId)); - - sd = systable_beginscan(rbRel, RecyclebinDbidNspOrinameIndexId, true, NULL, 2, skey); - tup = systable_getnext(sd); - - systable_endscan(sd); - heap_close(rbRel, AccessShareLock); - - return tup == NULL; -} - -bool TrRbIsEmptyUser(Oid roleId) -{ - Relation rbRel; - SysScanDesc sd; - HeapTuple tup; - bool found = false; - - rbRel = heap_open(RecyclebinRelationId, AccessShareLock); - - sd = systable_beginscan(rbRel, InvalidOid, false, NULL, 0, NULL); - - while ((tup = systable_getnext(sd)) != NULL) { - Form_pg_recyclebin rbForm = (Form_pg_recyclebin)GETSTRUCT(tup); - if ((TrObjType)rbForm->rcytype != RB_OBJ_TABLE || rbForm->rcyowner != roleId) { - continue; - } - - found = true; - break; - } - - systable_endscan(sd); - heap_close(rbRel, AccessShareLock); - - return !found; -} - -static bool TrOidExists(const List *lOid, Oid oid) -{ - ListCell *cell = NULL; - if (lOid == NULL) { - return false; - } - - foreach (cell, lOid) { - if (oid == (*(Oid *)lfirst(cell))) { - return true; - } - } - return false; -} - -List *TrGetDbListRcy(void) -{ - Relation rbRel; - SysScanDesc sd; - HeapTuple tup; - - List *lName = NIL; - List *lOid = NIL; - char *dbname = NULL; - - rbRel = heap_open(RecyclebinRelationId, AccessShareLock); - - sd = systable_beginscan(rbRel, InvalidOid, false, NULL, 0, NULL); - - while ((tup = systable_getnext(sd)) != NULL) { - Form_pg_recyclebin rbForm = (Form_pg_recyclebin)GETSTRUCT(tup); - if (TrOidExists(lOid, rbForm->rcydbid)) { - continue; - } - Oid *oid = (Oid *)palloc0(sizeof(Oid)); - *oid = rbForm->rcydbid; - lOid = lappend(lOid, oid); - - dbname = get_database_name(rbForm->rcydbid); - if (dbname == NULL) { - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_DATABASE), - errmsg("database \"%u\" does not exist", rbForm->rcydbid))); - } - lName = lappend(lName, dbname); - } - - list_free_deep(lOid); - - systable_endscan(sd); - heap_close(rbRel, AccessShareLock); - - return lName; -} - -List *TrGetDbListSpc(Oid spcId) -{ - List *lName = NIL; - List *lOid = NIL; - char *dbname = NULL; - - Relation rbRel; - SysScanDesc sd; - ScanKeyData skey[1]; - HeapTuple tup; - - rbRel = heap_open(RecyclebinRelationId, AccessShareLock); - - ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcytablespace, BTEqualStrategyNumber, - F_OIDEQ, ObjectIdGetDatum(spcId)); - - sd = systable_beginscan(rbRel, RecyclebinDbidSpcidRcycsnIndexId, true, NULL, 1, skey); - - while ((tup = systable_getnext(sd)) != NULL) { - Form_pg_recyclebin rbForm = (Form_pg_recyclebin)GETSTRUCT(tup); - if (TrOidExists(lOid, rbForm->rcydbid)) { - continue; - } - Oid *oid = (Oid *)palloc0(sizeof(Oid)); - *oid = rbForm->rcydbid; - lOid = lappend(lOid, oid); - - dbname = get_database_name(rbForm->rcydbid); - if (dbname == NULL) { - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_DATABASE), - errmsg("database \"%u\" does not exist", rbForm->rcydbid))); - } - lName = lappend(lName, dbname); - } - - list_free_deep(lOid); - - systable_endscan(sd); - heap_close(rbRel, AccessShareLock); - - return lName; -} - -List *TrGetDbListSchema(Oid nspId) -{ - Relation rbRel; - SysScanDesc sd; - ScanKeyData skey[1]; - HeapTuple tup; - - List *lName = NIL; - List *lOid = NIL; - char *dbname = NULL; - - rbRel = heap_open(RecyclebinRelationId, AccessShareLock); - - ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcynamespace, BTEqualStrategyNumber, - F_OIDEQ, ObjectIdGetDatum(nspId)); - - sd = systable_beginscan(rbRel, RecyclebinDbidNspOrinameIndexId, true, NULL, 1, skey); - - while ((tup = systable_getnext(sd)) != NULL) { - Form_pg_recyclebin rbForm = (Form_pg_recyclebin)GETSTRUCT(tup); - if (TrOidExists(lOid, rbForm->rcydbid)) { - continue; - } - Oid *oid = (Oid *)palloc0(sizeof(Oid)); - *oid = rbForm->rcydbid; - lOid = lappend(lOid, oid); - - dbname = get_database_name(rbForm->rcydbid); - if (dbname == NULL) { - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_DATABASE), - errmsg("database \"%u\" does not exist", rbForm->rcydbid))); - } - lName = lappend(lName, dbname); - } - - list_free_deep(lOid); - - systable_endscan(sd); - heap_close(rbRel, AccessShareLock); - - return lName; -} - -List *TrGetDbListUser(Oid roleId) -{ - Relation rbRel; - SysScanDesc sd; - HeapTuple tup; - - List *lName = NIL; - List *lOid = NIL; - char *dbname = NULL; - - rbRel = heap_open(RecyclebinRelationId, AccessShareLock); - - sd = systable_beginscan(rbRel, InvalidOid, false, NULL, 0, NULL); - - while ((tup = systable_getnext(sd)) != NULL) { - Form_pg_recyclebin rbForm = (Form_pg_recyclebin)GETSTRUCT(tup); - if ((TrObjType)rbForm->rcytype != RB_OBJ_TABLE || rbForm->rcyowner != roleId) { - continue; - } - if (TrOidExists(lOid, rbForm->rcydbid)) { - continue; - } - Oid *oid = (Oid *)palloc0(sizeof(Oid)); - *oid = rbForm->rcydbid; - lOid = lappend(lOid, oid); - - dbname = get_database_name(rbForm->rcydbid); - if (dbname == NULL) { - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_DATABASE), - errmsg("database \"%u\" does not exist", rbForm->rcydbid))); - } - - lName = lappend(lName, dbname); - } - - list_free_deep(lOid); - - systable_endscan(sd); - heap_close(rbRel, AccessShareLock); - - return lName; -} - -/* - * TrGetDatabaseList - * Return a list of all databases found in pg_database. - */ -List *TrGetDbListAuto(void) -{ - List* dblist = NIL; - Relation rel; - SysScanDesc sd; - HeapTuple tup; - - rel = heap_open(DatabaseRelationId, AccessShareLock); - sd = systable_beginscan(rel, InvalidOid, false, NULL, 0, NULL); - - while ((tup = systable_getnext(sd)) != NULL) { - Form_pg_database pgdatabase = (Form_pg_database)GETSTRUCT(tup); - if (strcmp(NameStr(pgdatabase->datname), "template0") == 0 || - strcmp(NameStr(pgdatabase->datname), "template1") == 0) { - continue; - } - dblist = lappend(dblist, pstrdup(NameStr(pgdatabase->datname))); - } - - systable_endscan(sd); - heap_close(rel, AccessShareLock); - - return dblist; -} - -static bool TrObjInRecyclebin(const ObjectAddress *obj) -{ - Relation rbRel; - SysScanDesc sd; - HeapTuple tup; - ScanKeyData skey[2]; - bool found = false; - - if (getObjectClass(obj) != OCLASS_CLASS) { - return false; - } - - ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcydbid, BTEqualStrategyNumber, F_OIDEQ, - ObjectIdGetDatum(u_sess->proc_cxt.MyDatabaseId)); - ScanKeyInit(&skey[1], Anum_pg_recyclebin_rcyrelid, BTEqualStrategyNumber, F_OIDEQ, - ObjectIdGetDatum(obj->objectId)); - - rbRel = heap_open(RecyclebinRelationId, AccessShareLock); - sd = systable_beginscan(rbRel, RecyclebinDbidRelidIndexId, true, NULL, 2, skey); - while ((tup = systable_getnext(sd)) != NULL) { - Form_pg_recyclebin rbForm = (Form_pg_recyclebin)GETSTRUCT(tup); - if ((TrObjType)rbForm->rcyoperation == 'd') { - found = true; - break; - } - } - systable_endscan(sd); - heap_close(rbRel, AccessShareLock); - - return found; -} - -/* - * May this object be a recyclebin object? - * true: with "BIN$" prefix, or not a Relation\Type\Trigger\Constraint\Rule - * false: without "BIN$" prefix, or not exists - */ -static bool TrMaybeRbObject(Oid classid, Oid objid, const char *objname = NULL) -{ - HeapTuple tup; - - /* Note: we preserve rule origin name when RbDrop. */ - if (classid != RewriteRelationId && objname) { - return strncmp(objname, "BIN$", 4) == 0; - } - - switch (classid) { - case RelationRelationId: - tup = SearchSysCache1(RELOID, ObjectIdGetDatum(objid)); - if (tup != NULL) { - objname = NameStr(((Form_pg_class)GETSTRUCT(tup))->relname); - ReleaseSysCache(tup); - } - break; - case TypeRelationId: - tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(objid)); - if (tup != NULL) { - objname = NameStr(((Form_pg_type)GETSTRUCT(tup))->typname); - ReleaseSysCache(tup); - } - break; - case TriggerRelationId: { - Relation relTrig; - ScanKeyData skey[1]; - SysScanDesc sd; - - relTrig = heap_open(TriggerRelationId, AccessShareLock); - ScanKeyInit(&skey[0], ObjectIdAttributeNumber, BTEqualStrategyNumber, - F_OIDEQ, ObjectIdGetDatum(objid)); - sd = systable_beginscan(relTrig, TriggerOidIndexId, true, NULL, 1, skey); - if ((tup = systable_getnext(sd)) != NULL) { - objname = NameStr(((Form_pg_trigger)GETSTRUCT(tup))->tgname); - } - systable_endscan(sd); - heap_close(relTrig, AccessShareLock); - break; - } - case ConstraintRelationId: - tup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(objid)); - if (tup != NULL) { - objname = NameStr(((Form_pg_constraint)GETSTRUCT(tup))->conname); - ReleaseSysCache(tup); - } - break; - case NamespaceRelationId: - /* Treate Namespace as non-recyclebin object. */ - return false; - default: - /* May be a recyclebin object. */ - return true; - } - - if (objname) { - return strncmp(objname, "BIN$", 4) == 0; - } - - return false; -} - -static bool TrIsRefRbObjectImpl(Relation depRel, const ObjectAddress *obj, ObjectAddresses *objSet) -{ - int startIdx; - - if (TrObjInRecyclebin(obj)) { - return true; - } - - startIdx = objSet->numrefs; - - if (!TrMaybeRbObject(obj->classId, obj->objectId)) { - return false; - } - - TrFindAllRefObjs(depRel, obj, objSet, true); - TrFindAllInternalObjs(depRel, obj, objSet, true); - - for (int i = startIdx; i < objSet->numrefs; i++) { - if (TrIsRefRbObjectImpl(depRel, &objSet->refs[i], objSet)) { - return true; - } - } - - return false; -} - -/* object is a rb object, or reference to a rb object. */ -static bool TrIsRefRbObject(const ObjectAddress *obj, Relation depRel) -{ - ObjectAddresses *objSet = new_object_addresses(); - bool relArgNull = depRel == NULL; - bool result = false; - - if (relArgNull) { - depRel = heap_open(DependRelationId, AccessShareLock); - } - - /* Note: we not care obj->deptype here. */ - add_object_address_ext1(obj, objSet); - - result = TrIsRefRbObjectImpl(depRel, obj, objSet); - - free_object_addresses(objSet); - - if (relArgNull) { - heap_close(depRel, AccessShareLock); - } - - return result; -} - -bool TrIsRefRbObjectEx(Oid classid, Oid objid, const char *objname) -{ - if (!TcapFeatureAvail()) { - return false; - } - - /* Note: we preserve rule origin name when RbDrop. */ - if (TrRbIsEmptyDb(u_sess->proc_cxt.MyDatabaseId)) { - return false; - } - - if (classid != RewriteRelationId && objname && strncmp(objname, "BIN$", 4) != 0) { - return false; - } - - ObjectAddress obj = {classid, objid}; - - return TrIsRefRbObject(&obj); -} - -void TrForbidAccessRbDependencies(Relation depRel, const ObjectAddress *depender, - const ObjectAddress *referenced, int nreferenced) -{ - if (!TcapFeatureAvail()) { - return; - } - - if (IsInitdb || TrRbIsEmptyDb(u_sess->proc_cxt.MyDatabaseId)) { - return; - } - - if (TrIsRefRbObject(depender, depRel)) { - elog (ERROR, "can not access recycle object."); - } - - for (int i = 0; i < nreferenced; i++, referenced++) { - if (TrIsRefRbObject(referenced, depRel)) { - elog (ERROR, "can not access recycle object."); - } - } - - return; -} - -void TrForbidAccessRbObject(Oid classid, Oid objid, const char *objname) -{ - if (!TcapFeatureAvail()) { - return; - } - - if (TrRbIsEmptyDb(u_sess->proc_cxt.MyDatabaseId) || !TrMaybeRbObject(classid, objid, objname)) { - return; - } - - ObjectAddress obj = {classid, objid}; - if (TrIsRefRbObject(&obj)) { - elog (ERROR, "can not access recycle object."); - } - - return; -} - -Datum gs_is_recycle_object(PG_FUNCTION_ARGS) -{ - int classid = PG_GETARG_INT32(0); - int objid = PG_GETARG_INT32(1); - Name objname = PG_GETARG_NAME(2); - bool result = false; - result = TrIsRefRbObjectEx(classid, objid, NameStr(*objname)); - PG_RETURN_BOOL(result); -} +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2020-2020. All rights reserved. + * + * 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. + * --------------------------------------------------------------------------------------- + * + * tcap_manager.cpp + * Routines to support Timecapsule `Recyclebin-based query, restore`. + * We use Tr prefix to indicate it in following coding. + * + * IDENTIFICATION + * src/gausskernel/storage/tcap/tcap_manager.cpp + * + * --------------------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "pgstat.h" +#include "access/reloptions.h" +#include "access/sysattr.h" +#include "access/xlog.h" +#include "catalog/pg_database.h" +#include "catalog/dependency.h" +#include "catalog/heap.h" +#include "catalog/index.h" +#include "catalog/indexing.h" +#include "catalog/objectaccess.h" +#include "catalog/pg_collation_fn.h" +#include "catalog/pg_collation.h" +#include "catalog/pg_constraint.h" +#include "catalog/pg_conversion_fn.h" +#include "catalog/pg_conversion.h" +#include "catalog/pg_depend.h" +#include "catalog/pg_extension_data_source.h" +#include "catalog/pg_extension.h" +#include "catalog/pg_foreign_data_wrapper.h" +#include "catalog/pg_foreign_server.h" +#include "catalog/pg_job.h" +#include "catalog/pg_language.h" +#include "catalog/pg_largeobject.h" +#include "catalog/pg_object.h" +#include "catalog/pg_opclass.h" +#include "catalog/pg_operator.h" +#include "catalog/pg_opfamily.h" +#include "catalog/pg_partition_fn.h" +#include "catalog/pg_proc.h" +#include "catalog/pg_recyclebin.h" +#include "catalog/pg_rewrite.h" +#include "catalog/pg_rlspolicy.h" +#include "catalog/pg_synonym.h" +#include "catalog/pg_tablespace.h" +#include "catalog/pg_trigger.h" +#include "catalog/pg_ts_config.h" +#include "catalog/pg_ts_dict.h" +#include "catalog/pg_ts_parser.h" +#include "catalog/pg_ts_template.h" +#include "catalog/pgxc_class.h" +#include "catalog/pg_partition.h" +#include "catalog/storage.h" +#include "commands/comment.h" +#include "commands/dbcommands.h" +#include "commands/directory.h" +#include "commands/extension.h" +#include "commands/proclang.h" +#include "commands/schemacmds.h" +#include "commands/seclabel.h" +#include "commands/sec_rls_cmds.h" +#include "commands/tablecmds.h" +#include "commands/tablespace.h" +#include "commands/trigger.h" +#include "commands/typecmds.h" +#include "executor/node/nodeModifyTable.h" +#include "rewrite/rewriteRemove.h" +#include "storage/lmgr.h" +#include "storage/predicate.h" +#include "storage/smgr/relfilenode.h" +#include "utils/acl.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" +#include "utils/inval.h" +#include "utils/lsyscache.h" +#include "utils/relcache.h" +#include "utils/snapmgr.h" +#include "utils/syscache.h" + +#include "storage/tcap.h" +#include "storage/tcap_impl.h" + +static bool TrIsRefRbObject(const ObjectAddress *obj, Relation depRel = NULL); +void TrDoPurgeObjectDrop(TrObjDesc *desc); +/* + * 功能:生成命名对象的名称,并将结果存储在rbname指向的缓冲区中 + * 参数列表: + * rbname:指向字符缓冲区的指针,用于存储生成的对象名称 + * classId:对象的类别ID + * objid:对象的ID + */ +char *TrGenObjName(char *rbname, Oid classId, Oid objid) +{ + int rc = EOK;// 声明并初始化一个整数变量rc,用于储存执行的结果 + // 使用snprintf_s函数将格式化的字符串写入rbname缓冲区 + rc = snprintf_s(rbname, NAMEDATALEN, NAMEDATALEN - 1, "BIN$%X%X%X$%llX==$0", + u_sess->proc_cxt.MyDatabaseId, classId, objid, (uint64)GetXLogInsertRecPtr()); + securec_check_ss_c(rc, "\0", "\0");// 检查snprintf_s函数的执行结果,确保没有错误发生 + + return rbname;// 返回存储对象名称的缓冲区的地址作为函数结果 +} +/* + * 功能:从HeapTuple中获取回收站中的冻结事务ID(64位),并返回该ID + * 参数列表: + * - rbtup:指向待处理的HeapTuple的指针 + * - rbRel:Recycle Bin关系的Relation(可选参数) + */ +static TransactionId TrRbGetRcyfrozenxid64(HeapTuple rbtup, Relation rbRel = NULL) +{ + Datum datum;// 存储属性值的数据类型 + bool isNull = false;// 标识属性值是否为NULL + TransactionId rcyfrozenxid64;// 存储回收站中的冻结事务ID(64位) + bool relArgNull = rbRel == NULL;// 检查rbRel是否为NULL的标志 + // 如果rbRel为NULL,则打开Recycle Bin关系(Recycle Bin表) + if (relArgNull) { + rbRel = heap_open(RecyclebinRelationId, AccessShareLock); + } + // 从HeapTuple中获取pg_recyclebin表中rcyfrozenxid64属性的值 + datum = heap_getattr(rbtup, Anum_pg_recyclebin_rcyfrozenxid64, RelationGetDescr(rbRel), &isNull); + Assert(!isNull);// 断言属性值不应为NULL + // 将datum转换为TransactionId类型,并赋值给rcyfrozenxid64 + rcyfrozenxid64 = DatumGetTransactionId(datum); + // 如果rbRel为NULL,则关闭Recycle Bin关系 + if (relArgNull) { + heap_close(rbRel, AccessShareLock); + } + + return rcyfrozenxid64;// 返回获取的冻结事务ID(64位) +} +/* + * 功能:初始化TrObjDesc结构体,存储关于对象的描述信息 + * 参数列表: + * rel:指向目标关系的Relation + * desc:指向TrObjDesc结构体的指针,用于存储对象描述信息 + * operType:操作类型,表示对象的操作(创建、删除等) + * objType:对象类型,表示对象的类型(表、索引等) + * canpurge:是否允许清除对象标志 + * isBaseObj:是否基本对象标志 + */ +void TrDescInit(Relation rel, TrObjDesc *desc, TrObjOperType operType, + TrObjType objType, bool canpurge, bool isBaseObj) +{ + errno_t rc = EOK;// 声明并初始化一个错误号变量rc,用于存储函数执行的结果 + + /* Notice: desc->id, desc->baseid will be assigned by invoker later. */ + desc->dbid = u_sess->proc_cxt.MyDatabaseId;// 将当前会话所在的数据库ID赋值给desc->dbid + desc->relid = RelationGetRelid(rel);// 将目标关系的Relid赋值给desc->relid + // 生成对象的名称,存储在desc->name中 + (void)TrGenObjName(desc->name, RelationRelationId, desc->relid); + // 将目标关系的名称拷贝到desc->originname中 + rc = strncpy_s(desc->originname, NAMEDATALEN, RelationGetRelationName(rel), + strlen(RelationGetRelationName(rel))); + securec_check(rc, "\0", "\0"); + // 初始化对象描述的各个字段 + desc->operation = operType; + desc->type = objType; + desc->recyclecsn = t_thrd.xact_cxt.ShmemVariableCache->nextCommitSeqNo; + desc->recycletime = GetCurrentTimestamp(); + desc->createcsn = RelationGetCreatecsn(rel); + desc->changecsn = RelationGetChangecsn(rel); + desc->nspace = RelationGetNamespace(rel); + desc->owner = RelationGetOwner(rel); + desc->tablespace = RelationGetTablespace(rel); + desc->relfilenode = RelationGetRelFileNode(rel); + desc->frozenxid = RelationGetRelFrozenxid(rel); + desc->frozenxid64 = RelationGetRelFrozenxid64(rel); + desc->canrestore = objType == RB_OBJ_TABLE; + desc->canpurge = canpurge; +} +/* + * 功能:初始化TrObjDesc结构体,存储关于分区对象的描述信息 + * 参数列表: + * rel:指向父关系的Relation + * part:指向分区的Partition + * desc:指向TrObjDesc结构体的指针,用于存储对象描述信息 + * operType:操作类型,表示对象的操作(创建、删除等) + * objType:对象类型,表示对象的类型(表、索引等) + * canpurge:是否允许清除对象标志 + * isBaseObj:是否基本对象标志 + */ +void TrPartDescInit(Relation rel, Partition part, TrObjDesc *desc, TrObjOperType operType, + TrObjType objType, bool canpurge, bool isBaseObj) +{ + errno_t rc = EOK;// 声明并初始化一个错误号变量rc,用于存储函数执行的结果 + + /* Notice: desc->id, desc->baseid will be assigned by invoker later. */ + desc->dbid = u_sess->proc_cxt.MyDatabaseId;// 将当前会话所在的数据库ID赋值给desc->dbid + desc->relid = part->pd_id;// 将分区的pd_id赋值给desc->relid + // 生成对象的名称,存储在desc->name中 + (void)TrGenObjName(desc->name, PartitionRelationId, desc->relid); + // 将父关系的名称和分区的名称连接起来,存储在desc->originname中 + rc = strncpy_s(desc->originname, NAMEDATALEN, RelationGetRelationName(rel), + strlen(RelationGetRelationName(rel))); + securec_check(rc, "\0", "\0"); + + int len = strlen(PartitionGetPartitionName(part)) + strlen(RelationGetRelationName(rel)) + 1; + rc = strcat_s(desc->originname, len, PartitionGetPartitionName(part)); + securec_check(rc, "\0", "\0"); + // 初始化对象描述的各个字段 + desc->operation = operType; + desc->type = objType; + desc->recyclecsn = t_thrd.xact_cxt.ShmemVariableCache->nextCommitSeqNo; + desc->recycletime = GetCurrentTimestamp(); + desc->createcsn = RelationGetCreatecsn(rel); + desc->changecsn = RelationGetChangecsn(rel); + desc->nspace = RelationGetNamespace(rel); + desc->owner = RelationGetOwner(rel); + desc->tablespace = part->pd_part->reltablespace; + desc->relfilenode = part->pd_part->relfilenode; + desc->frozenxid = part->pd_part->relfrozenxid; + desc->frozenxid64 = PartGetRelFrozenxid64(part); + desc->canrestore = false; + desc->canpurge = canpurge; +} +/* + * 功能:从HeapTuple中读取并填充对象描述信息到TrObjDesc结构体 + * 参数列表: + * desc:指向TrObjDesc结构体的指针,用于存储对象描述信息 + * rbtup:指向HeapTuple的指针,表示从回收站关系读取的元组 + */ +static void TrDescRead(TrObjDesc *desc, HeapTuple rbtup) +{ + Form_pg_recyclebin rbForm = (Form_pg_recyclebin)GETSTRUCT(rbtup); + + // 从HeapTuple中获取对象的Oid,赋值给desc->id + desc->id = HeapTupleGetOid(rbtup); + // 从HeapTuple中获取对象的基础ID,赋值给desc->baseid + desc->baseid = rbForm->rcybaseid; + + // 从HeapTuple中获取对象的数据库ID,赋值给desc->dbid + desc->dbid = rbForm->rcydbid; + // 从HeapTuple中获取对象的关系ID,赋值给desc->relid + desc->relid = rbForm->rcyrelid; + // 从HeapTuple中获取对象的名称,拷贝到desc->name + (void)namestrcpy((Name)desc->name, NameStr(rbForm->rcyname)); + // 从HeapTuple中获取对象的原始名称,拷贝到desc->originname + (void)namestrcpy((Name)desc->originname, NameStr(rbForm->rcyoriginname)); + // 根据HeapTuple中的操作类型标志,设置desc->operation + desc->operation = (rbForm->rcyoperation == 'd') ? RB_OPER_DROP : RB_OPER_TRUNCATE; + // 将HeapTuple中的对象类型转换为TrObjType,赋值给desc->type + desc->type = (TrObjType)rbForm->rcytype; + // 从HeapTuple中获取对象的回收CSN,赋值给desc->recyclecsn + desc->recyclecsn = rbForm->rcyrecyclecsn; + // 从HeapTuple中获取对象的回收时间,赋值给desc->recycletime + desc->recycletime = rbForm->rcyrecycletime; + // 从HeapTuple中获取对象的创建CSN,赋值给desc->createcsn + desc->createcsn = rbForm->rcycreatecsn; + // 从HeapTuple中获取对象的变更CSN,赋值给desc->changecsn + desc->changecsn = rbForm->rcychangecsn; + // 从HeapTuple中获取对象的命名空间,赋值给desc->nspace + desc->nspace = rbForm->rcynamespace; + // 从HeapTuple中获取对象的命名空间,赋值给desc->nspace + desc->owner = rbForm->rcyowner; + // 从HeapTuple中获取对象的表空间,赋值给desc->tablespace + desc->tablespace = rbForm->rcytablespace; + // 从HeapTuple中获取对象的关系文件节点,赋值给desc->relfilenode + desc->relfilenode = rbForm->rcyrelfilenode; + // 从HeapTuple中获取对象的可恢复标志,赋值给desc->canrestore + desc->canrestore = rbForm->rcycanrestore; + // 从HeapTuple中获取对象的可清除标志,赋值给desc->canpurge + desc->canpurge = rbForm->rcycanpurge; + // 从HeapTuple中获取对象的冻结事务ID,赋值给desc->frozenxid + desc->frozenxid = rbForm->rcyfrozenxid; + // 调用TrRbGetRcyfrozenxid64函数,获取64位冻结事务ID,赋值给desc->frozenxid64 + desc->frozenxid64 = TrRbGetRcyfrozenxid64(rbtup); +} +/* + * 功能:将TrObjDesc结构体中的对象描述信息写入回收站关系 + * 参数列表: + * desc:指向TrObjDesc结构体的指针,包含要写入回收站关系的对象描述信息 + */ +Oid TrDescWrite(TrObjDesc *desc) +{ + Relation rel; + HeapTuple tup; + bool nulls[Natts_pg_recyclebin] = {0}; + Datum values[Natts_pg_recyclebin]; + NameData name; + NameData originname; + Oid rbid; + + // 填充属性数组values,将对象描述信息中的各属性值赋值给对应位置 + values[Anum_pg_recyclebin_rcydbid - 1] = ObjectIdGetDatum(desc->dbid); + values[Anum_pg_recyclebin_rcybaseid - 1] = ObjectIdGetDatum(desc->baseid); + values[Anum_pg_recyclebin_rcyrelid - 1] = ObjectIdGetDatum(desc->relid); + (void)namestrcpy(&name, desc->name); + values[Anum_pg_recyclebin_rcyname - 1] = NameGetDatum(&name); + (void)namestrcpy(&originname, desc->originname); + values[Anum_pg_recyclebin_rcyoriginname - 1] = NameGetDatum(&originname); + values[Anum_pg_recyclebin_rcyoperation - 1] = (desc->operation == RB_OPER_DROP) ? 'd' : 't'; + values[Anum_pg_recyclebin_rcytype - 1] = Int32GetDatum(desc->type); + values[Anum_pg_recyclebin_rcyrecyclecsn - 1] = Int64GetDatum(desc->recyclecsn); + values[Anum_pg_recyclebin_rcyrecycletime - 1] = TimestampTzGetDatum(desc->recycletime); + values[Anum_pg_recyclebin_rcycreatecsn - 1] = Int64GetDatum(desc->createcsn); + values[Anum_pg_recyclebin_rcychangecsn - 1] = Int64GetDatum(desc->changecsn); + values[Anum_pg_recyclebin_rcynamespace - 1] = ObjectIdGetDatum(desc->nspace); + values[Anum_pg_recyclebin_rcyowner - 1] = ObjectIdGetDatum(desc->owner); + values[Anum_pg_recyclebin_rcytablespace - 1] = ObjectIdGetDatum(desc->tablespace); + values[Anum_pg_recyclebin_rcyrelfilenode - 1] = ObjectIdGetDatum(desc->relfilenode); + values[Anum_pg_recyclebin_rcycanrestore - 1] = BoolGetDatum(desc->canrestore); + values[Anum_pg_recyclebin_rcycanpurge - 1] = BoolGetDatum(desc->canpurge); + values[Anum_pg_recyclebin_rcyfrozenxid - 1] = ShortTransactionIdGetDatum(desc->frozenxid); + values[Anum_pg_recyclebin_rcyfrozenxid64 - 1] = TransactionIdGetDatum(desc->frozenxid64); + + rel = heap_open(RecyclebinRelationId, RowExclusiveLock);// 打开回收站关系 + // 创建一个新的HeapTuple,填充属性值和null标志 + tup = heap_form_tuple(RelationGetDescr(rel), values, nulls); + // 在关系中插入新的元组,获取新元组的对象ID(rbid) + rbid = simple_heap_insert(rel, tup); + // 更新关系的索引 + CatalogUpdateIndexes(rel, tup); + // 释放HeapTuple + heap_freetuple_ext(tup); + // 关闭回收站关系 + heap_close(rel, RowExclusiveLock); + // 增加命令计数器,以保证元数据的一致性 + CommandCounterIncrement(); + + return rbid; +} +/* + * 功能:在回收站关系中根据命名空间、原始名称、类型和操作模式搜索并获取对象描述信息 + * 参数列表: + * nspId:命名空间ID,指定要搜索的命名空间 + * oriname:对象的原始名称,要搜索的对象名称 + * type:TrObjType,指定对象的类型 + * desc:指向TrObjDesc结构体的指针,用于存储找到的对象描述信息 + * operMode:TrOperMode,指定操作模式(还原模式) + */ +static bool TrFetchOrinameImpl(Oid nspId, const char *oriname, TrObjType type, + TrObjDesc *desc, TrOperMode operMode) +{ + Relation rbRel; + SysScanDesc sd; + ScanKeyData skey[3]; + HeapTuple tup; + bool found = false; + // 如果命名空间ID无效,直接返回false + if (!OidIsValid(nspId)) { + return false; + } + // 打开回收站关系,获取共享锁 + rbRel = heap_open(RecyclebinRelationId, AccessShareLock); + // 初始化扫描键,按照命名空间、数据库ID和原始名称进行搜索 + ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcynamespace, BTEqualStrategyNumber, + F_OIDEQ, ObjectIdGetDatum(nspId)); + ScanKeyInit(&skey[1], Anum_pg_recyclebin_rcydbid, BTEqualStrategyNumber, + F_OIDEQ, ObjectIdGetDatum(u_sess->proc_cxt.MyDatabaseId)); + ScanKeyInit(&skey[2], Anum_pg_recyclebin_rcyoriginname, BTEqualStrategyNumber, + F_NAMEEQ, CStringGetDatum(oriname)); + // 开始扫描回收站关系,使用预定义索引和扫描键 + sd = systable_beginscan(rbRel, RecyclebinDbidNspOrinameIndexId, true, NULL, 3, skey); + while ((tup = systable_getnext(sd)) != NULL) { + Form_pg_recyclebin rbForm = (Form_pg_recyclebin)GETSTRUCT(tup); + // 检查对象类型和操作模式是否匹配 + if ((rbForm->rcytype != type && rbForm->rcytype == RB_OBJ_TABLE) || + (rbForm->rcytype != type && rbForm->rcytype == RB_OBJ_INDEX) || + (operMode == RB_OPER_RESTORE_DROP && rbForm->rcyoperation != 'd') || + (operMode == RB_OPER_RESTORE_TRUNCATE && rbForm->rcyoperation != 't')) { + continue; + } + // 找到匹配的对象,读取对象描述信息并标记为找到 + found = true; + TrDescRead(desc, tup); + break; + } + // 结束扫描并关闭回收站关系 + systable_endscan(sd); + heap_close(rbRel, AccessShareLock); + + return found; +} +/* + * 功能:在回收站关系中根据对象名称、类型和操作模式搜索并获取对象描述信息 + * 参数列表: + * rcyname:要搜索的回收站对象的名称 + * type:TrObjType,指定对象的类型 + * desc:指向TrObjDesc结构体的指针,用于存储找到的对象描述信息 + * operMode:TrOperMode,指定操作模式(还原模式) + */ +bool TrFetchName(const char *rcyname, TrObjType type, TrObjDesc *desc, TrOperMode operMode) +{ + Relation rbRel; + SysScanDesc sd; + ScanKeyData skey[1]; + HeapTuple tup; + bool found = false; + // 打开回收站关系,获取共享锁 + rbRel = heap_open(RecyclebinRelationId, AccessShareLock); + // 初始化扫描键,按照对象名称进行搜索 + ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcyname, BTEqualStrategyNumber, + F_NAMEEQ, CStringGetDatum(rcyname)); + // 开始扫描回收站关系,使用预定义索引和扫描键 + sd = systable_beginscan(rbRel, RecyclebinNameIndexId, true, NULL, 1, skey); + if ((tup = systable_getnext(sd)) != NULL) { + Form_pg_recyclebin rbForm = (Form_pg_recyclebin)GETSTRUCT(tup); + // 检查对象类型是否匹配 + if ((rbForm->rcytype != type && rbForm->rcytype == RB_OBJ_TABLE) || + (rbForm->rcytype != type && rbForm->rcytype == RB_OBJ_INDEX)) { + ereport(ERROR, + (errmsg("The recycle object \"%s\" type mismatched.", rcyname))); + } + // 检查操作模式是否匹配 + if ((operMode == RB_OPER_RESTORE_DROP && rbForm->rcyoperation != 'd') || + (operMode == RB_OPER_RESTORE_TRUNCATE && rbForm->rcyoperation != 't')) { + ereport(ERROR, + (errmsg("recycle object \"%s\" desired does not exist", rcyname))); + } + // 找到匹配的对象,读取对象描述信息并标记为找到 + found = true; + TrDescRead(desc, tup); + } + // 结束扫描并关闭回收站关系 + systable_endscan(sd); + heap_close(rbRel, AccessShareLock); + + return found; +} +/* + * 功能:在回收站关系中根据模式名称、对象名称、类型和操作模式搜索并获取对象描述信息 + * 参数列表: + * schemaname:模式名称,可选的命名空间名称 + * relname:要搜索的回收站对象的名称 + * type:TrObjType,指定对象的类型 + * desc:指向TrObjDesc结构体的指针,用于存储找到的对象描述信息 + * operMode:TrOperMode,指定操作模式(还原模式) + */ +static bool TrFetchOriname(const char *schemaname, const char *relname, TrObjType type, + TrObjDesc *desc, TrOperMode operMode) +{ + bool found = false; + Oid nspId; + // 如果给定了模式名称 + if (schemaname) { + // 获取模式的命名空间ID + nspId = get_namespace_oid(schemaname, true); + // 调用TrFetchOrinameImpl函数进行搜索 + found = TrFetchOrinameImpl(nspId, relname, type, desc, operMode); + } else { + List *activeSearchPath = NIL; + ListCell *l = NULL; + + // 重新计算命名空间搜索路径 + recomputeNamespacePath(); + // 复制当前的命名空间搜索路径 + activeSearchPath = list_copy(u_sess->catalog_cxt.activeSearchPath); + // 遍历命名空间搜索路径 + foreach (l, activeSearchPath) { + nspId = lfirst_oid(l); + // 调用TrFetchOrinameImpl函数进行搜索,如果找到则标记为找到并退出循环 + if (TrFetchOrinameImpl(nspId, relname, type, desc, operMode)) { + found = true; + break; + } + } + // 释放命名空间搜索路径列表 + list_free_ext(activeSearchPath); + // 如果在当前搜索路径中未找到,则在PG_TOAST命名空间中搜索 + if (!found) { + nspId = PG_TOAST_NAMESPACE; + found = TrFetchOrinameImpl(nspId, relname, type, desc, operMode); + } + } + + return found; +} +/* + * 功能:更新回收站关系中指定对象的baseid字段 + * 参数列表: + * desc:指向TrObjDesc结构体的指针,包含待更新对象的描述信息 + */ +void TrUpdateBaseid(const TrObjDesc *desc) +{ + // 声明回收站关系 + Relation rbRel; + // 声明系统扫描描述符 + SysScanDesc sd; + // 声明扫描键数组,用于构建扫描条件 + ScanKeyData skey[1]; + // 声明原始的堆元组 + HeapTuple tup; + // 声明用于更新的新堆元组 + HeapTuple newtup; + // 声明值数组,用于存储待更新的字段值 + Datum values[Natts_pg_recyclebin] = { 0 }; + // 声明空值数组,标识字段是否为空 + bool nulls[Natts_pg_recyclebin] = { false }; + // 声明替换标志数组,标识是否要替换对应字段 + bool replaces[Natts_pg_recyclebin] = { false }; + // 打开回收站关系,获取行独占锁 + rbRel = heap_open(RecyclebinRelationId, RowExclusiveLock); + // 初始化扫描键,按照对象的id进行搜索 + ScanKeyInit(&skey[0], ObjectIdAttributeNumber, BTEqualStrategyNumber, + F_OIDEQ, ObjectIdGetDatum(desc->id)); + // 开始扫描回收站关系,使用预定义索引和扫描键 + sd = systable_beginscan(rbRel, RecyclebinIdIndexId, true, NULL, 1, skey); + if ((tup = systable_getnext(sd)) == NULL) { + // 如果未找到匹配的对象,报告错误 + ereport(ERROR, (errmsg("recycle object %u does not exist", desc->id))); + } + // 准备更新字段的值和标志 + replaces[Anum_pg_recyclebin_rcybaseid - 1] = true; + values[Anum_pg_recyclebin_rcybaseid - 1] = ObjectIdGetDatum(desc->baseid); + // 根据新值创建新的元组 + newtup = heap_modify_tuple(tup, RelationGetDescr(rbRel), values, nulls, replaces); + // 执行元组更新 + simple_heap_update(rbRel, &newtup->t_self, newtup); + // 更新索引 + CatalogUpdateIndexes(rbRel, newtup); + // 释放内存 + heap_freetuple_ext(newtup); + // 结束扫描并关闭回收站关系 + systable_endscan(sd); + heap_close(rbRel, RowExclusiveLock); + + return; +} +/* + * 功能:在回收站对象中加锁指定的关系,并检查关系是否存在 + * 参数列表: + * relid:要加锁的关系的对象ID + * type:对象的类型(表、索引、分区等) + */ +static void TrLockRelationImpl(Oid relid, TrObjType type) +{ + /* + * Lock failed may due to concurrently purge/timecapsule/DQL + * on recycle object, or access on normal relation. + */ + if (!ConditionalLockRelationOid(relid, AccessExclusiveLock)) { + ereport(ERROR, + (errcode(ERRCODE_RBIN_LOCK_NOT_AVAILABLE), + errmsg("could not obtain lock on relation \"%u\"", relid))); + } + + /* + * Now that we have the lock, probe to see if the relation + * really exists or not. + */ + AcceptInvalidationMessages(); + if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid)) && type != RB_OBJ_PARTITION) { + /* Clean already held locks if error return. */ + UnlockRelationOid(relid, AccessExclusiveLock); + ereport(ERROR, + (errcode(ERRCODE_RBIN_UNDEFINED_OBJECT), + errmsg("relation \"%u\" does not exist", relid))); + } else if (!SearchSysCacheExists1(PARTRELID, ObjectIdGetDatum(relid)) && type == RB_OBJ_PARTITION) { + /* Clean already held locks if error return. */ + UnlockRelationOid(relid, AccessExclusiveLock); + ereport(ERROR, + (errcode(ERRCODE_RBIN_UNDEFINED_OBJECT), + errmsg("partition \"%u\" does not exist", relid))); + } +} +/* + * 功能:对回收站对象中的关系进行加锁,首先锁定索引关系(如果有),然后锁定关系自身 + * 参数列表: + * desc:回收站对象描述 + */ +static void TrLockRelation(TrObjDesc *desc) +{ + Oid heapOid = InvalidOid; + + /* Lock heap relation for index first */ + if (desc->type == RB_OBJ_INDEX) { + heapOid = IndexGetRelation(desc->relid, true); + if (!OidIsValid(heapOid)) { + ereport(ERROR, + (errcode(ERRCODE_RBIN_UNDEFINED_OBJECT), + errmsg("relation \"%u\" does not exist", desc->relid))); + } + TrLockRelationImpl(heapOid, desc->type); + } + + /* Use TRY-CATCH block to clean locks already held if error. */ + PG_TRY(); + { + /* Lock relation self */ + TrLockRelationImpl(desc->relid, desc->type); + } + PG_CATCH(); + { + if (desc->type == RB_OBJ_INDEX) { + UnlockRelationOid(heapOid, AccessExclusiveLock); + } + PG_RE_THROW(); + } + PG_END_TRY(); +} +/* + * 功能:对回收站中的指定对象进行解锁 + * 参数列表: + * desc:回收站对象描述 + */ +static void TrUnlockTrItem(TrObjDesc *desc) +{ + // 对指定的回收站对象进行解锁 + UnlockDatabaseObject(RecyclebinRelationId, desc->id, 0, + AccessExclusiveLock); +} + +static void TrLockTrItem(TrObjDesc *desc) +{ + Relation rbRel; + SysScanDesc sd; + ScanKeyData skey[1]; + HeapTuple tup; + + /* 1. Try to lock rb item in AccessExclusiveLock */ + if (!ConditionalLockDatabaseObject(RecyclebinRelationId, desc->id, 0, AccessExclusiveLock)) { + ereport(ERROR, + (errcode(ERRCODE_RBIN_LOCK_NOT_AVAILABLE), + errmsg("could not obtain lock on recycle object '%s'", desc->name))); + } + + /* + * 2. Now that we have the lock, probe to see if the rb item really + * exists or not. + */ + AcceptInvalidationMessages(); + + rbRel = heap_open(RecyclebinRelationId, AccessShareLock); + + ScanKeyInit(&skey[0], ObjectIdAttributeNumber, BTEqualStrategyNumber, + F_OIDEQ, ObjectIdGetDatum(desc->id)); + + sd = systable_beginscan(rbRel, RecyclebinIdIndexId, true, NULL, 1, skey); + if ((tup = systable_getnext(sd)) == NULL) { + UnlockDatabaseObject(RecyclebinRelationId, desc->id, 0, AccessExclusiveLock); + systable_endscan(sd); + heap_close(rbRel, AccessShareLock); + ereport(ERROR, + (errcode(ERRCODE_RBIN_UNDEFINED_OBJECT), + errmsg("recycle object \"%s\" does not exist", desc->name))); + } + + systable_endscan(sd); + heap_close(rbRel, AccessShareLock); + + return; +} +/* + * 功能:验证回收站操作是否匹配对象的属性 + * + * 参数列表: + * desc:回收站对象描述信息 + * operMode:操作模式 + */ +static void TrOperMatch(const TrObjDesc *desc, TrOperMode operMode) +{ + switch (operMode) { + case RB_OPER_PURGE: + /* 对象不允许被清除且不是分区对象,报告无法清除的错误 */ + if (!desc->canpurge && desc->type != RB_OBJ_PARTITION) { + ereport(ERROR, + (errcode(ERRCODE_INVALID_OPERATION), + errmsg("recycle object \"%s\" cannot be purged", desc->name))); + } + break; + + case RB_OPER_RESTORE_DROP: + /* 对象不允许被恢复且不是分区对象,或者操作类型不是DROP,报告无法恢复的错误 */ + if ((!desc->canrestore && desc->type != RB_OBJ_PARTITION) || desc->operation != RB_OPER_DROP) { + ereport(ERROR, + (errcode(ERRCODE_INVALID_OPERATION), + errmsg("recycle object \"%s\" cannot be restored", desc->name))); + } + break; + + case RB_OPER_RESTORE_TRUNCATE: + /* 对象不允许被恢复且不是分区对象,或者操作类型不是TRUNCATE,报告无法恢复的错误 */ + if ((!desc->canrestore && desc->type != RB_OBJ_PARTITION) || desc->operation != RB_OPER_TRUNCATE) { + ereport(ERROR, + (errcode(ERRCODE_INVALID_OPERATION), + errmsg("recycle object \"%s\" cannot be restored", desc->name))); + } + break; + + default: + /* 未知的回收站操作类型,报告无法识别的错误 */ + ereport(ERROR, + (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE), + errmsg("unrecognized recyclebin operation: %u", operMode))); + break; + } +} + +/* + * Fetch object from recycle bin for rb operations - purge, restore : + * Prefer to fetch as original name, then recycle name. + */ +void TrOperFetch(const RangeVar *purobj, TrObjType objtype, TrObjDesc *desc, TrOperMode operMode) +{ + bool found = false; + + AcceptInvalidationMessages(); + + /* Prefer to fetch as original name */ + found = TrFetchOriname(purobj->schemaname, purobj->relname, objtype, desc, operMode); + /* if not found, then fetch as recycle name */ + if (!found) { + found = TrFetchName(purobj->relname, objtype, desc, operMode); + } + + /* not found, throw error */ + if (!found) { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("recycle object \"%s\" desired does not exist", purobj->relname))); + } + + TrOperMatch(desc, operMode); + + return; +} +/* + * 功能:恢复回收站对象时的权限检查 + * + * 参数列表: + * desc:回收站对象描述信息 + * operMode:操作模式 + */ +static void TrPermRestore(TrObjDesc *desc, TrOperMode operMode) +{ + AclResult aclCreateResult; + + /* Check namespace permissions. */ + aclCreateResult = pg_namespace_aclcheck(desc->nspace, desc->authid, ACL_CREATE); + if (aclCreateResult != ACLCHECK_OK) { + aclcheck_error(aclCreateResult, ACL_KIND_NAMESPACE, get_namespace_name(desc->nspace)); + } + + AclResult aclUsageResult = pg_namespace_aclcheck(desc->nspace, desc->authid, ACL_USAGE); + if (aclUsageResult != ACLCHECK_OK) { + aclcheck_error(aclUsageResult, ACL_KIND_NAMESPACE, get_namespace_name(desc->nspace)); + } + + /* Allow restore to either table owner or schema owner */ + if (!pg_class_ownercheck(desc->relid, desc->authid) && !pg_namespace_ownercheck(desc->nspace, desc->authid)) { + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, desc->name); + return; + } + /* 如果操作模式是恢复为截断,则检查表截断权限 */ + if (operMode == RB_OPER_RESTORE_TRUNCATE) { + AclResult aclTruncateResult = pg_class_aclcheck(desc->relid, desc->authid, ACL_TRUNCATE); + if (aclTruncateResult != ACLCHECK_OK) { + aclcheck_error(aclTruncateResult, ACL_KIND_CLASS, desc->name); + } + } +} +/* + * 功能:验证回收站清除操作的权限 + * 参数列表: + * desc:回收站对象描述信息 + * operMode:操作模式 + */ +static void TrPermPurge(TrObjDesc *desc, TrOperMode operMode) +{ + AclResult result; + /* 检查命名空间的使用权限 */ + result = pg_namespace_aclcheck(desc->nspace, desc->authid, ACL_USAGE); + if (result != ACLCHECK_OK) { + aclcheck_error(result, ACL_KIND_NAMESPACE, get_namespace_name(desc->nspace)); + } + /* 允许清除的条件:是表的所有者或者是命名空间的所有者 */ + if (!pg_class_ownercheck(desc->relid, desc->authid) && !pg_namespace_ownercheck(desc->nspace, desc->authid)) { + aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, desc->name); + } +} + +/* + * Check permission for rb operations - purge, restore + */ + /* + * 功能:验证回收站操作的权限 + * 参数列表: + * desc:回收站对象描述信息 + * operMode:操作模式(操作类型) + */ +static void TrPerm(TrObjDesc *desc, TrOperMode operMode) +{ + switch (operMode) { + case RB_OPER_RESTORE_DROP: + case RB_OPER_RESTORE_TRUNCATE: + TrPermRestore(desc, operMode);// 调用 TrPermRestore 验证恢复操作权限 + break; + case RB_OPER_PURGE: + TrPermPurge(desc, operMode);// 调用 TrPermPurge 验证清除操作权限 + break; + default: + Assert(0); + break; + } +} + +/* + * Prepare for rb operations - purge, restore : + * check permission, lock objects + */ +/* + * TrOperPrep - 准备进行回收操作 + * + * 此函数用于准备进行回收操作。它执行以下步骤: + * 1. 检查权限。 + * 2. 获取回收站条目的锁,以避免并发的清除和恢复操作。 + * 3. 获取关系的锁,以避免并发的数据查询操作。注意:当进行清除截断关系操作时,忽略此步骤, + * 因为基础关系可能不存在。 + * + * 参数列表: + * desc: 回收站对象描述符,用于表示要进行操作的回收站对象 + * operMode: 回收操作的模式,表示执行的具体操作类型 + */ +void TrOperPrep(TrObjDesc *desc, TrOperMode operMode) +{ + bool needLockRelation = false; + + /* + * 1. Check permission. + */ + TrPerm(desc, operMode); + + /* + * 2. Acquire lock on rb item, avoid concurrently purge, restore. + */ + TrLockTrItem(desc); + + /* + * 3. Acquire lock on relation, avoid concurrently DQL. + * Notice: ignore this step when we purge truncated relation + * as base relation may not exists. + */ + needLockRelation = !(operMode == RB_OPER_PURGE && desc->operation == RB_OPER_TRUNCATE); + if (needLockRelation) { + /* Use TRY-CATCH block to clean locks already held if error. */ + PG_TRY(); + { + TrLockRelation(desc); + } + PG_CATCH(); + { + TrUnlockTrItem(desc); + PG_RE_THROW(); + } + PG_END_TRY(); + } +} +/* + * 功能:根据传入的关系ID(relid)判断是否可以对该关系进行回收操作。 + * + * 参数列表: + * relid: 关系ID + * + */ +bool NeedTrComm(Oid relid) +{ + Relation rel; + Form_pg_class classForm; + + if (/* + *Disable Recyclebin-based-Drop/Truncate when + */ + /* recyclebin disabled, or */ + !u_sess->attr.attr_storage.enable_recyclebin || + /* target db is template1, or */ + u_sess->proc_cxt.MyDatabaseId == TemplateDbOid || + /* in maintenance mode, or */ + u_sess->attr.attr_common.xc_maintenance_mode || + /* in in-place upgrade mode, or */ + t_thrd.proc->workingVersionNum < 92350 || + /* in non-singlenode mode, or */ + (g_instance.role != VSINGLENODE) || + /* in bootstrap mode. */ + IsInitdb) { + return false; + } + + rel = relation_open(relid, NoLock); + classForm = rel->rd_rel; + if (/* + * Disable Recyclebin-based-Drop/Truncate if + */ + /* table is non ordinary table, or */ + classForm->relkind != RELKIND_RELATION || + /* is non heap table, or */ + rel->rd_tam_type == TAM_HEAP || + /* is non regular table, or */ + classForm->relpersistence != RELPERSISTENCE_PERMANENT || + /* is shared table across databases, or */ + classForm->relisshared || + /* has derived classes, or */ + classForm->relhassubclass || + /* has any PARTIAL CLUSTER KEY, or */ + classForm->relhasclusterkey || + /* is cstore table, or */ + (rel->rd_options && StdRelOptIsColStore(rel->rd_options)) || RelationIsColStore(rel) || + /* is hbkt table, or */ + (RELATION_HAS_BUCKET(rel) || RELATION_OWN_BUCKET(rel)) || + /* is dfs table, or */ + RelationIsPAXFormat(rel) || + /* is resizing, or */ + RelationInClusterResizing(rel) || + /* is in system namespace. */ + (IsSystemNamespace(classForm->relnamespace) || IsToastNamespace(classForm->relnamespace) || + IsCStoreNamespace(classForm->relnamespace))) { + relation_close(rel, NoLock);// 关闭关系 + return false; + } + + relation_close(rel, NoLock);// 关闭关系 + + return true; +} +/* + * 功能:此函数根据传入的命名空间ID和关系类型标识,确定并返回对应的回收对象类型。 + * + * 参数列表: + * nspId: 命名空间ID,用于判断对象是否在TOAST命名空间 + * relKind: 关系类型标识,表示对象的类型 + */ +TrObjType TrGetObjType(Oid nspId, char relKind) +{ + // 默认回收对象类型为表 RB_OBJ_TABLE + TrObjType type = RB_OBJ_TABLE; + // 根据关系类型标识进行判断并设置对应的回收对象类型 + switch (relKind) { + case RELKIND_INDEX: + // 如果是索引对象,根据是否在TOAST命名空间判断是TOAST索引还是普通索引 + type = IsToastNamespace(nspId) ? RB_OBJ_TOAST_INDEX : RB_OBJ_INDEX; + break; + case RELKIND_RELATION: + // 如果是表对象,设置为表类型 + type = RB_OBJ_TABLE; + break; + case RELKIND_SEQUENCE: + case RELKIND_LARGE_SEQUENCE: + // 如果是序列对象,设置为序列类型 + type = RB_OBJ_SEQUENCE; + break; + case RELKIND_TOASTVALUE: + // 如果是TOAST值对象,设置为TOAST类型 + type = RB_OBJ_TOAST; + break; + case PARTTYPE_PARTITIONED_RELATION: + // 如果是分区表对象,设置为分区表类型 + type = RB_OBJ_PARTITION; + break; + case RELKIND_GLOBAL_INDEX: + // 如果是全局索引对象,设置为全局索引类型 + type = RB_OBJ_GLOBAL_INDEX; + break; + case RELKIND_MATVIEW: + // 如果是物化视图对象,设置为物化视图类型 + type = RB_OBJ_MATVIEW; + break; + default: + /* Never reached here. */ + // 对于其他未知的关系类型,断言报错,因为不应该到达这个分支 + Assert(0); + break; + } + // 返回确定的回收对象类型 + return type; +} +/* + * 功能:用于检查给定的对象地址是否存在于给定的对象地址集合中。 + * + * 参数列表: + * classid: 对象的类别标识 + * objid: 对象的唯一标识 + * objSet: 对象地址集合,包含多个对象的地址信息 + */ +static bool TrObjAddrExists(Oid classid, Oid objid, ObjectAddresses *objSet) +{ + int i; + // 遍历对象地址集合中的每个对象地址 + for (i = 0; i < objSet->numrefs; i++) { + // 如果给定的对象地址和集合中的对象地址匹配,返回 true + if (TrObjIsEqualEx(classid, objid, &objSet->refs[i])) { + return true; + } + } + // 若遍历完所有对象地址仍未找到匹配项,返回 false + return false; +} + +/* + * output: refobjs + */ +/* + * 功能用于查找引用给定对象的所有其他对象,并将这些引用对象的地址添加到指定的对象地址集合中。 + * + * 参数列表: + * depRel: 用于扫描依赖关系的关系对象 + * subobj: 被引用对象的地址信息 + * refobjs: 存储引用对象地址的对象地址集合 + * ignoreObjSubId: 是否忽略被引用对象的子标识,默认为 false + */ +void TrFindAllRefObjs(Relation depRel, const ObjectAddress *subobj, + ObjectAddresses *refobjs, bool ignoreObjSubId) +{ + SysScanDesc sd; + HeapTuple tuple; + ScanKeyData key[3]; + int nkeys; + // 初始化用于扫描的键 + ScanKeyInit(&key[0], Anum_pg_depend_classid, BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(subobj->classId)); + ScanKeyInit(&key[1], Anum_pg_depend_objid, BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(subobj->objectId)); + nkeys = 2; + // 如果不忽略对象的子标识且子标识不为0,添加第三个键 + if (!ignoreObjSubId && subobj->objectSubId != 0) { + ScanKeyInit(&key[2], Anum_pg_depend_objsubid, BTEqualStrategyNumber, F_INT4EQ, + Int32GetDatum(subobj->objectSubId)); + nkeys = 3; + } + // 开始扫描依赖关系 + sd = systable_beginscan(depRel, DependDependerIndexId, true, NULL, nkeys, key); + while (HeapTupleIsValid(tuple = systable_getnext(sd))) { + Form_pg_depend depForm = (Form_pg_depend)GETSTRUCT(tuple); + /* Cascaded clean rb object in `DROP SCHEMA` command. */ + if (depForm->refclassid == NamespaceRelationId) { + continue; + } + + /* We keep `objSet` unique when `ignoreObjSubId = true` to avoid circle recursive. */ + if (!ignoreObjSubId || !TrObjAddrExists(depForm->refclassid, depForm->refobjid, refobjs)) { + add_object_address_ext(depForm->refclassid, depForm->refobjid, + depForm->refobjsubid, depForm->deptype, refobjs); + } + } + + systable_endscan(sd);//结束扫描 + return; +} +/* + * 功能:用于查找引用给定对象的所有内部对象,并将这些内部对象的地址添加到指定的对象地址集合中。 + * + * 参数列表: + * depRel: 用于扫描依赖关系的关系对象 + * refobj: 被引用对象的地址信息 + * objSet: 存储内部对象地址的对象地址集合 + * ignoreObjSubId: 是否忽略被引用对象的子标识,默认为 false + */ +static void TrFindAllInternalObjs(Relation depRel, const ObjectAddress *refobj, + ObjectAddresses *objSet, bool ignoreObjSubId = false) +{ + SysScanDesc sd; + HeapTuple tuple; + ScanKeyData key[3]; + int nkeys; + // 初始化用于扫描的键 + ScanKeyInit(&key[0], Anum_pg_depend_refclassid, BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(refobj->classId)); + ScanKeyInit(&key[1], Anum_pg_depend_refobjid, BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(refobj->objectId)); + nkeys = 2; + // 如果不忽略对象的子标识且子标识不为0,添加第三个键 + if (!ignoreObjSubId && refobj->objectSubId != 0) { + ScanKeyInit(&key[2], Anum_pg_depend_refobjsubid, BTEqualStrategyNumber, F_INT4EQ, + Int32GetDatum(refobj->objectSubId)); + nkeys = 3; + } + // 开始扫描依赖关系 + sd = systable_beginscan(depRel, DependReferenceIndexId, true, NULL, nkeys, key); + while (HeapTupleIsValid(tuple = systable_getnext(sd))) { + Form_pg_depend depForm = (Form_pg_depend)GETSTRUCT(tuple); + // 仅处理 deptype 为 'i' 的对象 + if (depForm->deptype != 'i') { + continue; + } + + /* We keep `objSet` unique when `ignoreObjSubId = true` to avoid circle recursive. */ + if (!ignoreObjSubId || !TrObjAddrExists(depForm->classid, depForm->objid, objSet)) { + // 将内部对象的地址添加到对象地址集合中 + add_object_address_ext(depForm->classid, depForm->objid, + depForm->objsubid, depForm->deptype, objSet); + } + } + + systable_endscan(sd);// 结束扫描 + return; +} +/* + * 功能根据回收站对象的操作类型(操作码)来执行相应的清除操作。可以是 DROP 或 TRUNCATE。 + * + * 参数列表: + * desc: 待清除的回收站对象描述 + */ +static void TrDoPurgeObject(TrObjDesc *desc) +{ + if (desc->operation == RB_OPER_DROP) { + TrDoPurgeObjectDrop(desc); + } else { + TrDoPurgeObjectTruncate(desc); + } +} +/* + * 功能:执行回收站对象的永久删除操作 + * 参数列表: + * purobj:要永久删除的对象的范围变量 + * type:对象的类型(例如表、索引等) + */ +void TrPurgeObject(RangeVar *purobj, TrObjType type) +{ + TrObjDesc desc; + + // 获取回收站对象的描述信息 + TrOperFetch(purobj, type, &desc, RB_OPER_PURGE); + + // 设置操作的授权用户 ID + desc.authid = GetUserId(); + + // 准备执行删除操作,包括权限检查等 + TrOperPrep(&desc, RB_OPER_PURGE); + + // 执行实际的永久删除操作 + TrDoPurgeObject(&desc); + + return; +} +// 一次永久删除操作中要处理的对象数量(批量处理的大小) +const int PURGE_BATCH = 64; +// 单个永久删除操作中要处理的对象数量 +const int PURGE_SINGL = 64; +// TrFetchBeginHook 是函数指针类型,用于指向在获取回收站对象前执行的钩子函数。 +// 该钩子函数接受 SysScanDesc 指针和对象的 ID 作为参数,用于初始化扫描以获取回收站对象。 +typedef void (*TrFetchBeginHook)(SysScanDesc *sd, Oid objId); +// TrFetchMatchHook 是函数指针类型,用于指向在获取回收站对象后执行的钩子函数。 +// 该钩子函数接受回收站关系、堆元组和对象 ID 作为参数,用于检查回收站对象是否匹配。 +typedef bool (*TrFetchMatchHook)(Relation rbRel, HeapTuple rbTup, Oid objId); +/* + * 功能:开始执行回收站对象提取操作的钩子函数 + * 参数列表: + * fetchHook:提取操作的钩子函数指针 + * sd:系统扫描描述符指针 + * objId:对象的标识符 + */ +static void TrFetchBegin(TrFetchBeginHook fetchHook, SysScanDesc *sd, Oid objId) +{ + // 调用提取操作的钩子函数,传入系统扫描描述符和对象标识符 + fetchHook(sd, objId); +} + +// @return: true for eof +/* + * 功能:执行回收站对象提取操作的执行函数 + * 参数列表: + * matchHook:匹配操作的钩子函数指针 + * objId:对象的标识符 + * sd:系统扫描描述符 + * desc:回收站对象描述结构的指针 + */ +static bool TrFetchExec(TrFetchMatchHook matchHook, Oid objId, SysScanDesc sd, TrObjDesc *desc) +{ + HeapTuple tup; + while ((tup = systable_getnext(sd)) != NULL) { + // 获取堆元组的结构体形式 + Form_pg_recyclebin rbForm = (Form_pg_recyclebin)GETSTRUCT(tup); + + // 根据回收站对象类型和匹配操作的结果进行处理 + if ((rbForm->rcytype == RB_OBJ_TABLE) && matchHook(sd->heap_rel, tup, objId)) { + // 断言回收站对象可以永久删除 + Assert(rbForm->rcycanpurge); + + // 读取回收站对象描述信息 + TrDescRead(desc, tup); + + // 返回 false,表示成功匹配并提取对象 + return false; + } else if ((rbForm->rcytype == RB_OBJ_PARTITION) && matchHook(sd->heap_rel, tup, objId)) { + // 断言回收站对象不可永久删除 + Assert(!rbForm->rcycanpurge); + + // 读取回收站对象描述信息 + TrDescRead(desc, tup); + + // 返回 false,表示成功匹配并提取对象 + return false; + } + } + + // 如果没有匹配的对象,返回 true + return true; +} + +/* + * 功能:结束回收站对象提取操作的扫描并释放相关资源 + * 参数列表: + * sd:系统扫描描述符 + */ +static void TrFetchEnd(SysScanDesc sd) +{ + // 获取回收站关系的引用 + Relation rbRel = sd->heap_rel; + + // 结束系统扫描 + systable_endscan(sd); + + // 关闭回收站关系,释放共享锁 + heap_close(rbRel, AccessShareLock); +} + + +/* + * 功能:批量永久删除回收站对象 + * 参数列表: + * beginHook:开始提取的钩子函数 + * matchHook:匹配的钩子函数 + * objId:对象的ID + * roleid:角色的ID + * maxBatch:最大批次数量 + * localRes:用于存储删除结果的结构体指针 + */ +static bool TrPurgeBatch(TrFetchBeginHook beginHook, TrFetchMatchHook matchHook, + Oid objId, Oid roleid, uint32 maxBatch, PurgeMsgRes *localRes) +{ + SysScanDesc sd = NULL; + TrObjDesc desc; + uint32 count = 0; + bool eof = false; + + // 重置本地删除结果信息 + RbMsgResetRes(localRes); + + // 开启事务 + StartTransactionCommand(); + + // 开始提取回收站对象 + TrFetchBegin(beginHook, &sd, objId); + + // 循环执行批量删除操作,直到批次结束 + while (!(eof = TrFetchExec(matchHook, objId, sd, &desc))) { + CHECK_FOR_INTERRUPTS(); + + // 使用 TRY-CATCH 块来处理删除过程中的异常情况 + PG_TRY(); + { + // 设置要执行删除的角色 + desc.authid = roleid; + + // 准备删除操作,检查权限等 + TrOperPrep(&desc, RB_OPER_PURGE); + + // 执行删除操作 + TrDoPurgeObject(&desc); + + // 更新本地删除结果统计 + localRes->purgedNum++; + } + PG_CATCH(); + { + int errcode = geterrcode(); + if (errcode == ERRCODE_RBIN_LOCK_NOT_AVAILABLE) { + errno_t rc; + // 如果锁不可用,记录错误消息,并增加跳过数量 + rc = strncpy_s(localRes->errMsg, RB_MAX_ERRMSG_SIZE, Geterrmsg(), RB_MAX_ERRMSG_SIZE - 1); + securec_check(rc, "\0", "\0"); + localRes->skippedNum++; + } else if (errcode == ERRCODE_RBIN_UNDEFINED_OBJECT) { + // 如果对象未定义,增加未定义数量 + localRes->undefinedNum++; + } else { + // 其他异常情况,重新抛出异常 + PG_RE_THROW(); + } + } + PG_END_TRY(); + + // 如果达到最大批次数量,跳出循环 + if (++count >= maxBatch) { + break; + } + } + + // 结束回收站对象提取操作 + TrFetchEnd(sd); + + // 提交事务 + CommitTransactionCommand(); + + // 返回批次是否结束的标志 + return eof; +} + + +/* + * 功能:开始提取指定表空间下的回收站对象 + * 参数列表: + * sd:用于扫描的 SysScanDesc 指针 + * spcId:表空间的ID + */ +static void TrFetchBeginSpace(SysScanDesc *sd, Oid spcId) +{ + ScanKeyData skey[2]; + Relation rbRel; + + // 打开回收站关系 + rbRel = heap_open(RecyclebinRelationId, AccessShareLock); + + // 初始化扫描键 + ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcytablespace, BTEqualStrategyNumber, + F_OIDEQ, ObjectIdGetDatum(spcId)); + ScanKeyInit(&skey[1], Anum_pg_recyclebin_rcydbid, BTEqualStrategyNumber, + F_OIDEQ, ObjectIdGetDatum(u_sess->proc_cxt.MyDatabaseId)); + + // 开始扫描回收站对象 + *sd = systable_beginscan(rbRel, RecyclebinDbidSpcidRcycsnIndexId, true, NULL, 2, skey); +} + +/* + * 功能:匹配回收站对象是否属于指定的表空间 + * 参数列表: + * rbRel:回收站关系 + * rbTup:当前的回收站元组 + * objId:要匹配的对象ID + */ +static bool TrFetchMatchSpace(Relation rbRel, HeapTuple rbTup, Oid objId) +{ + Form_pg_recyclebin rbForm = (Form_pg_recyclebin)GETSTRUCT(rbTup); + return rbForm->rcytablespace == objId; +} + +/* + * 功能:清除指定表空间下的回收站对象 + * 参数列表: + * id:回收站消息的ID + */ +void TrPurgeTablespace(int64 id) +{ + PurgeMsgReq *req = &RbMsg(id)->req; // 获取回收站消息的请求信息 + PurgeMsgRes localRes; // 本地的清除结果 + bool eof = false; // 表示是否到达回收站对象的末尾 + + do { + // 逐批清除回收站对象 + eof = TrPurgeBatch(TrFetchBeginSpace, TrFetchMatchSpace, req->objId, req->authId, PURGE_BATCH, &localRes); + + // 更新消息的统计信息 + RbMsgSetStatistics(id, &localRes); + } while (!eof && localRes.skippedNum == 0); +} + + +/* + * 功能:以逐个对象方式清除指定表空间下的回收站对象 + * 参数列表: + * id:回收站消息的ID + */ +void TrPurgeTablespaceDML(int64 id) +{ + PurgeMsgReq *req = &RbMsg(id)->req; // 获取回收站消息的请求信息 + PurgeMsgRes localRes; // 本地的清除结果 + bool eof = false; // 表示是否到达回收站对象的末尾 + + do { + // 逐个对象清除回收站对象 + eof = TrPurgeBatch(TrFetchBeginSpace, TrFetchMatchSpace, req->objId, req->authId, PURGE_SINGL, &localRes); + + // 更新消息的统计信息 + RbMsgSetStatistics(id, &localRes); + } while (!eof && localRes.purgedNum == 0); +} +/* + * 功能:初始化回收站中指定对象的扫描 + * 参数列表: + * sd:SysScanDesc 结构的指针,用于接收扫描描述 + * objId:要初始化扫描的对象的ID + */ +static void TrFetchBeginRecyclebin(SysScanDesc *sd, Oid objId) +{ + ScanKeyData skey[2]; // 扫描键数组 + Relation rbRel; // 回收站关系对象 + + rbRel = heap_open(RecyclebinRelationId, AccessShareLock); // 打开回收站关系对象 + + // 初始化扫描键,通过数据库ID来筛选回收站中的对象 + ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcydbid, BTEqualStrategyNumber, + F_OIDEQ, ObjectIdGetDatum(u_sess->proc_cxt.MyDatabaseId)); + + // 开始扫描回收站关系对象 + *sd = systable_beginscan(rbRel, RecyclebinDbidRelidIndexId, true, NULL, 1, skey); +} + + +/* + * 功能:检查回收站对象是否匹配,始终返回 true,即表示匹配任何回收站对象 + * 参数列表: + * rbRel:回收站关系对象 + * rbTup:回收站元组(HeapTuple) + * objId:要匹配的对象的ID(无效的) + */ +static bool TrFetchMatchRecyclebin(Relation rbRel, HeapTuple rbTup, Oid objId) +{ + return true; // 始终返回 true,表示匹配任何回收站对象 +} + +/* + * 功能:永久删除回收站中的对象 + * 参数列表: + * id:消息ID + */ +void TrPurgeRecyclebin(int64 id) +{ + PurgeMsgReq *req = &RbMsg(id)->req; // 获取消息请求 + PurgeMsgRes localRes; // 用于保存本地清除结果 + bool eof = false; // 标识扫描是否结束 + + do { + // 执行回收站对象的批量清除 + eof = TrPurgeBatch(TrFetchBeginRecyclebin, TrFetchMatchRecyclebin, + InvalidOid, req->authId, PURGE_BATCH, &localRes); + + // 更新消息的统计信息 + RbMsgSetStatistics(id, &localRes); + } while (!eof && localRes.skippedNum == 0); // 继续循环直到扫描结束或没有被跳过的对象 +} + + +/* + * 功能:开始在回收站中扫描与指定模式匹配的对象 + * 参数列表: + * sd:扫描描述符指针,用于存储扫描结果 + * objId:要匹配的模式的ID + */ +static void TrFetchBeginSchema(SysScanDesc *sd, Oid objId) +{ + ScanKeyData skey[2]; // 扫描键数组 + Relation rbRel; // 回收站关系对象 + + rbRel = heap_open(RecyclebinRelationId, AccessShareLock); // 打开回收站关系对象 + ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcynamespace, BTEqualStrategyNumber, + F_OIDEQ, ObjectIdGetDatum(objId)); // 初始化扫描键,匹配模式的命名空间 + ScanKeyInit(&skey[1], Anum_pg_recyclebin_rcydbid, BTEqualStrategyNumber, + F_OIDEQ, ObjectIdGetDatum(u_sess->proc_cxt.MyDatabaseId)); // 初始化扫描键,匹配当前数据库ID + + *sd = systable_beginscan(rbRel, RecyclebinDbidNspOrinameIndexId, true, NULL, 2, skey); // 开始扫描 +} + + +/* + * 功能:在回收站中匹配指定模式的对象 + * 参数列表: + * rbRel:回收站关系对象 + * rbTup:回收站中的元组 + * objId:要匹配的模式的ID + */ +static bool TrFetchMatchSchema(Relation rbRel, HeapTuple rbTup, Oid objId) +{ + Form_pg_recyclebin rbForm = (Form_pg_recyclebin)GETSTRUCT(rbTup); // 获取元组的数据结构体 + return rbForm->rcynamespace == objId; // 返回是否匹配模式的命名空间 +} + +/* + * 功能:执行回收站中指定模式的对象的永久删除操作 + * 参数列表: + * id:要删除的模式的ID + */ +void TrPurgeSchema(int64 id) +{ + PurgeMsgReq *req = &RbMsg(id)->req; // 获取请求消息 + PurgeMsgRes localRes; // 用于存储删除操作的结果 + bool eof = false; + + do { + eof = TrPurgeBatch(TrFetchBeginSchema, TrFetchMatchSchema, req->objId, req->authId, PURGE_BATCH, &localRes); // 执行批量永久删除操作 + RbMsgSetStatistics(id, &localRes); // 更新删除统计信息 + } while (!eof && localRes.skippedNum == 0); // 当批量删除未完成且未跳过任何操作时继续执行 +} + +/* + * 功能:在回收站中根据用户ID开始扫描匹配的对象 + * 参数列表: + * sd:扫描描述符指针,用于存储扫描结果 + * objId:要匹配的对象的ID(在这里是用户ID) + */ +static void TrFetchBeginUser(SysScanDesc *sd, Oid objId) +{ + ScanKeyData skey[2]; // 扫描键数组 + Relation rbRel; // 回收站关系对象 + + rbRel = heap_open(RecyclebinRelationId, AccessShareLock); // 打开回收站关系对象,获取访问共享锁 + + // 初始化扫描键 + ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcydbid, BTEqualStrategyNumber, + F_OIDEQ, ObjectIdGetDatum(u_sess->proc_cxt.MyDatabaseId)); + + // 开始扫描回收站关系对象,并将扫描结果存储到扫描描述符中 + *sd = systable_beginscan(rbRel, RecyclebinDbidRelidIndexId, true, NULL, 1, skey); +} + +/* + * 功能:在回收站中匹配指定用户的对象 + * 参数列表: + * rbRel:回收站关系对象 + * rbTup:回收站中的元组 + * objId:要匹配的用户的ID + */ +static bool TrFetchMatchUser(Relation rbRel, HeapTuple rbTup, Oid objId) +{ + Form_pg_recyclebin rbForm = (Form_pg_recyclebin)GETSTRUCT(rbTup); // 获取元组的数据结构体 + return rbForm->rcyowner == objId; // 返回是否匹配指定用户的所有者 +} + +/* + * 功能:执行回收站中指定用户的对象的永久删除操作 + * 参数列表: + * id:要删除的用户的ID + */ +void TrPurgeUser(int64 id) +{ + PurgeMsgReq *req = &RbMsg(id)->req; // 获取请求消息 + PurgeMsgRes localRes; // 用于存储删除操作的结果 + bool eof = false; + + do { + eof = TrPurgeBatch(TrFetchBeginUser, TrFetchMatchUser, req->objId, req->authId, PURGE_BATCH, &localRes); // 执行批量永久删除操作 + RbMsgSetStatistics(id, &localRes); // 更新删除统计信息 + } while (!eof && localRes.skippedNum == 0); // 当批量删除未完成且未跳过任何操作时继续执行 +} + +/* + * 功能:在回收站中根据自动清理策略开始扫描匹配的对象 + * 参数列表: + * sd:扫描描述符指针,用于存储扫描结果 + * objId:要匹配的对象的ID(在这里是无效的,不使用) + */ +static void TrFetchBeginAuto(SysScanDesc *sd, Oid objId) +{ + ScanKeyData skey[2]; // 扫描键数组 + Relation rbRel; // 回收站关系对象 + + rbRel = heap_open(RecyclebinRelationId, AccessShareLock); // 打开回收站关系对象,获取访问共享锁 + + // 初始化扫描键 + ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcydbid, BTEqualStrategyNumber, + F_OIDEQ, ObjectIdGetDatum(u_sess->proc_cxt.MyDatabaseId)); + + // 开始扫描回收站关系对象,并将扫描结果存储到扫描描述符中 + *sd = systable_beginscan(rbRel, RecyclebinDbidRelidIndexId, true, NULL, 1, skey); +} +/* + * 功能:在回收站中匹配自动删除的对象,判断是否满足自动删除条件 + * 参数列表: + * rbRel:回收站关系对象 + * rbTup:回收站中的元组 + * objId:要匹配的对象的ID + */ +static bool TrFetchMatchAuto(Relation rbRel, HeapTuple rbTup, Oid objId) +{ + bool isNull = false; + Datum datumRcyTime = heap_getattr(rbTup, Anum_pg_recyclebin_rcyrecycletime, + RelationGetDescr(rbRel), &isNull); + + long secs; + int msecs; + TimestampDifference(isNull ? 0 : DatumGetTimestampTz(datumRcyTime), + GetCurrentTimestamp(), &secs, &msecs); + + return secs > u_sess->attr.attr_storage.recyclebin_retention_time || secs < 0; // 判断是否满足自动删除条件 +} + +/* + * 功能:执行回收站中自动删除的对象的永久删除操作 + * 参数列表: + * id:要删除的对象的ID + */ +void TrPurgeAuto(int64 id) +{ + PurgeMsgReq *req = &RbMsg(id)->req; // 获取请求消息 + PurgeMsgRes localRes; // 用于存储删除操作的结果 + bool eof = false; + + do { + eof = TrPurgeBatch(TrFetchBeginAuto, TrFetchMatchAuto, InvalidOid, req->authId, PURGE_BATCH, &localRes); // 执行批量永久删除操作 + RbMsgSetStatistics(id, &localRes); // 更新删除统计信息 + } while (!eof); // 当批量删除未完成时继续执行 +} +/* + * 功能:交换回收站对象和关系对象的文件节点信息 + * 参数列表: + * rbRel:回收站关系对象 + * rbTup:回收站中的对象元组 + * isPart:是否是分区对象 + */ +void TrSwapRelfilenode(Relation rbRel, HeapTuple rbTup, bool isPart) +{ + Relation relRel; // 关系对象的关系描述符 + HeapTuple relTup; // 关系对象元组 + HeapTuple newTup; // 新关系对象元组 + TrObjDesc desc; // 回收站对象的描述信息 + int maxNattr = 0; // 最大属性数 + Datum *values = NULL; // 属性值数组 + bool *nulls = NULL; // NULL值数组 + bool *replaces = NULL; // 需要替换的属性数组 + NameData name; // 名称数据 + errno_t rc = EOK; // 错误码 + bool isNull = false; // 是否为NULL + int relfilenoIndex = 0; // relfileno属性的索引 + int frozenxidIndex = 0; // frozenxid属性的索引 + int frozenxid64Index = 0; // frozenxid64属性的索引 + bool isPartition = false; // 是否为分区对象 + // 读取回收站对象描述 + TrDescRead(&desc, rbTup); + // 判断是否为分区对象 + if (desc.type == RB_OBJ_PARTITION || (desc.type == RB_OBJ_INDEX && isPart)) { + isPartition = true; + } + // 根据对象类型确定要操作的系统表 + if (isPartition) { + maxNattr = Max(Natts_pg_partition, Natts_pg_recyclebin); + relRel = heap_open(PartitionRelationId, RowExclusiveLock); + relTup = SearchSysCacheCopy1(PARTRELID, ObjectIdGetDatum(desc.relid)); + relfilenoIndex = Anum_pg_partition_relfilenode; + frozenxidIndex = Anum_pg_partition_relfrozenxid; + frozenxid64Index = Anum_pg_partition_relfrozenxid64; + } else { + maxNattr = Max(Natts_pg_class, Natts_pg_recyclebin); + relRel = heap_open(RelationRelationId, RowExclusiveLock); + relTup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(desc.relid)); + relfilenoIndex = Anum_pg_class_relfilenode; + frozenxidIndex = Anum_pg_class_relfrozenxid; + frozenxid64Index = Anum_pg_class_relfrozenxid64; + } + + /* 1. Update pg_class or pg_partition */ + if (!HeapTupleIsValid(relTup)) { + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("cache lookup failed for relation %u", desc.relid))); + } + // 为更新构造属性值、是否为空和是否更新的数组 + values = (Datum *)palloc0(sizeof(Datum) * maxNattr); + nulls = (bool *)palloc0(sizeof(bool) * maxNattr); + replaces = (bool *)palloc0(sizeof(bool) * maxNattr); + // 设置需要更新的属性和对应的新值 + replaces[relfilenoIndex - 1] = true; + values[relfilenoIndex - 1] = ObjectIdGetDatum(desc.relfilenode); + + replaces[frozenxidIndex - 1] = true; + values[frozenxidIndex - 1] = ShortTransactionIdGetDatum(desc.frozenxid); + + replaces[frozenxid64Index - 1] = true; + values[frozenxid64Index - 1] = TransactionIdGetDatum(desc.frozenxid64); + // 构造新的元组 + newTup = heap_modify_tuple(relTup, RelationGetDescr(relRel), values, nulls, replaces); + // 执行堆表更新操作 + simple_heap_update(relRel, &newTup->t_self, newTup); + // 更新索引 + CatalogUpdateIndexes(relRel, newTup); + + heap_freetuple_ext(newTup); + + /* 2. Update pg_recyclebin */ + rc = memset_s(values, sizeof(Datum) * maxNattr, 0, sizeof(Datum) * maxNattr); + securec_check(rc, "\0", "\0"); + rc = memset_s(nulls, sizeof(bool) * maxNattr, false, sizeof(bool) * maxNattr); + securec_check(rc, "\0", "\0"); + rc = memset_s(replaces, sizeof(bool) * maxNattr, false, sizeof(bool) * maxNattr); + securec_check(rc, "\0", "\0"); + // 生成回收站对象名 + (void)TrGenObjName(NameStr(name), RelationRelationId, desc.relid); + replaces[Anum_pg_recyclebin_rcyname - 1] = true; + values[Anum_pg_recyclebin_rcyname - 1] = NameGetDatum(&name); + + replaces[Anum_pg_recyclebin_rcyoriginname - 1] = true; + if (isPartition) { + values[Anum_pg_recyclebin_rcyoriginname - 1] = NameGetDatum(&desc.originname); + } else { + values[Anum_pg_recyclebin_rcyoriginname - 1] = NameGetDatum(&((Form_pg_class)GETSTRUCT(relTup))->relname); + } + + replaces[Anum_pg_recyclebin_rcyrecyclecsn - 1] = true; + values[Anum_pg_recyclebin_rcyrecyclecsn - 1] = Int64GetDatum(t_thrd.xact_cxt.ShmemVariableCache->nextCommitSeqNo); + + replaces[Anum_pg_recyclebin_rcyrecycletime - 1] = true; + values[Anum_pg_recyclebin_rcyrecycletime - 1] = TimestampTzGetDatum(GetCurrentTimestamp()); + + replaces[Anum_pg_recyclebin_rcyrelfilenode - 1] = true; + if (isPartition) { + values[Anum_pg_recyclebin_rcyrelfilenode - 1] = + ObjectIdGetDatum(((Form_pg_partition)GETSTRUCT(relTup))->relfilenode); + } else { + values[Anum_pg_recyclebin_rcyrelfilenode - 1] = + ObjectIdGetDatum(((Form_pg_class)GETSTRUCT(relTup))->relfilenode); + } + + replaces[Anum_pg_recyclebin_rcyfrozenxid - 1] = true; + if (isPartition) { + values[Anum_pg_recyclebin_rcyfrozenxid - 1] = + ShortTransactionIdGetDatum(((Form_pg_partition)GETSTRUCT(relTup))->relfrozenxid); + } else { + values[Anum_pg_recyclebin_rcyfrozenxid - 1] = + ShortTransactionIdGetDatum(((Form_pg_class)GETSTRUCT(relTup))->relfrozenxid); + } + + replaces[Anum_pg_recyclebin_rcyfrozenxid64 - 1] = true; + if (isPartition) { + // 如果是分区对象,将分区对象的relfrozenxid64设置为回收站元组的属性值 + values[Anum_pg_recyclebin_rcyfrozenxid64 - 1] = + ShortTransactionIdGetDatum(((Form_pg_partition)GETSTRUCT(relTup))->relfrozenxid); + } else { + // 如果是非分区对象,将关系对象的relfrozenxid64设置为回收站元组的属性值 + values[Anum_pg_recyclebin_rcyfrozenxid64 - 1] = + ShortTransactionIdGetDatum(((Form_pg_class)GETSTRUCT(relTup))->relfrozenxid); + } + + // 构造新的回收站元组 + newTup = heap_modify_tuple(rbTup, RelationGetDescr(rbRel), values, nulls, replaces); + + // 执行堆表更新操作 + simple_heap_update(rbRel, &newTup->t_self, newTup); + + // 更新索引 + CatalogUpdateIndexes(rbRel, newTup); + + heap_freetuple_ext(newTup); + + // 释放动态分配的内存 + pfree(values); + pfree(nulls); + pfree(replaces); + + // 释放关系对象元组的内存并关闭关系对象的表 + heap_freetuple_ext(relTup); + heap_close(relRel, RowExclusiveLock); + return; +} + +/* + * 功能:验证基表与回收站对象是否匹配 + * 参数列表: + * baseDesc:基表对象的描述信息 + */ +void TrBaseRelMatched(TrObjDesc *baseDesc) +{ + ObjectAddress obj = {RelationRelationId, baseDesc->relid}; // 创建ObjectAddress结构体,表示基表对象的地址信息 + if (TrIsRefRbObject(&obj)) { // 判断基表是否有关联的回收站对象 + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("relation \"%s\" does not exist", baseDesc->originname))); // 报错,提示基表不存在 + } + + Relation rel = RelationIdGetRelation(baseDesc->relid); // 根据基表的OID获取基表的Relation结构体 + Assert(RelationIsValid(rel)); // 断言,确保获取的Relation结构体是有效的 + if (RelationGetCreatecsn(rel) != (CommitSeqNo)baseDesc->createcsn) { // 比较基表的创建CSN与回收站对象的createcsn是否匹配 + ereport(ERROR, + (errmsg("The recycle object \"%s\" and relation \"%s\" mismatched.", + baseDesc->name, RelationGetRelationName(rel)))); // 报错,提示回收站对象和基表不匹配 + } + + if (RelationGetChangecsn(rel) > (CommitSeqNo)baseDesc->changecsn) { // 比较基表的修改CSN与回收站对象的changecsn是否匹配 + ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), + errmsg("The table definition of \"%s\" has been changed.", + RelationGetRelationName(rel)))); // 报错,提示基表定义已更改 + } + + RelationClose(rel); // 关闭基表的Relation结构体 +} + +/* + * 功能:根据回收站对象的冻结XID64来调整传入的frozenXID + * 参数列表: + * dbid:数据库的OID + * frozenXID:传入的冻结XID指针,将根据回收站对象的XID来进行调整 + */ +void TrAdjustFrozenXid64(Oid dbid, TransactionId *frozenXID) +{ + Relation rbRel; + SysScanDesc sd; + HeapTuple rbtup; + + if (!TcapFeatureAvail()) { // 检查是否支持TCAP功能 + return; + } + + rbRel = heap_open(RecyclebinRelationId, AccessShareLock); // 打开回收站关系 + + sd = systable_beginscan(rbRel, InvalidOid, false, NULL, 0, NULL); // 开始扫描回收站关系表 + while ((rbtup = systable_getnext(sd)) != NULL) { // 循环遍历回收站对象 + Form_pg_recyclebin rbForm = (Form_pg_recyclebin)GETSTRUCT(rbtup); // 获取回收站对象的元组结构 + + TransactionId rcyfrozenxid64; + + if (rbForm->rcydbid != dbid || (rbForm->rcytype != RB_OBJ_TABLE && rbForm->rcytype != RB_OBJ_TOAST)) { + // 检查回收站对象是否与给定的数据库ID匹配,并且类型是表或TOAST + continue; // 若不匹配,继续下一个回收站对象 + } + + rcyfrozenxid64 = TrRbGetRcyfrozenxid64(rbtup, rbRel); // 获取回收站对象的冻结XID64 + Assert(TransactionIdIsNormal(rcyfrozenxid64)); // 断言:确保获取的XID64是正常的事务ID + + if (TransactionIdPrecedes(rcyfrozenxid64, *frozenXID)) { + // 比较回收站对象的冻结XID64和传入的冻结XID + *frozenXID = rcyfrozenxid64; // 更新传入的冻结XID为较小的XID64值 + } + } + + systable_endscan(sd); // 结束扫描 + heap_close(rbRel, AccessShareLock); // 关闭回收站关系 + + return; +} + +/* + * 功能:检查指定数据库是否为空的回收站 + * 参数列表: + * dbid:数据库的OID + */ +bool TrRbIsEmptyDb(Oid dbid) +{ + Relation rbRel; + SysScanDesc sd; + HeapTuple tup; + ScanKeyData skey[1]; + + ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcydbid, BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(dbid)); + + rbRel = heap_open(RecyclebinRelationId, AccessShareLock); // 打开回收站关系 + + sd = systable_beginscan(rbRel, RecyclebinDbidRelidIndexId, true, NULL, 1, skey); // 开始扫描回收站关系表 + tup = systable_getnext(sd); // 获取扫描结果的下一个元组 + systable_endscan(sd); // 结束扫描 + heap_close(rbRel, AccessShareLock); // 关闭回收站关系 + + return tup == NULL; // 若元组为空,返回true,否则返回false +} + + +/* + * 功能:检查指定表空间是否为空的回收站 + * 参数列表: + * spcId:表空间的OID + */ +bool TrRbIsEmptySpc(Oid spcId) +{ + Relation rbRel; + SysScanDesc sd; + ScanKeyData skey[1]; + HeapTuple tup; + + rbRel = heap_open(RecyclebinRelationId, AccessShareLock); // 打开回收站关系 + + ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcytablespace, BTEqualStrategyNumber, + F_OIDEQ, ObjectIdGetDatum(spcId)); // 创建扫描键,匹配指定表空间的回收站记录 + + sd = systable_beginscan(rbRel, RecyclebinDbidSpcidRcycsnIndexId, true, NULL, 1, skey); // 开始扫描回收站关系表 + tup = systable_getnext(sd); // 获取扫描结果的下一个元组 + systable_endscan(sd); // 结束扫描 + heap_close(rbRel, AccessShareLock); // 关闭回收站关系 + + return tup == NULL; // 若元组为空,返回true,否则返回false +} + + +/* + * 功能:检查指定模式是否为空的回收站 + * 参数列表: + * nspId:模式的OID + */ +bool TrRbIsEmptySchema(Oid nspId) +{ + Relation rbRel; + SysScanDesc sd; + ScanKeyData skey[2]; + HeapTuple tup; + + rbRel = heap_open(RecyclebinRelationId, AccessShareLock); // 打开回收站关系 + + ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcynamespace, BTEqualStrategyNumber, + F_OIDEQ, ObjectIdGetDatum(nspId)); // 创建扫描键,匹配指定模式的回收站记录 + ScanKeyInit(&skey[1], Anum_pg_recyclebin_rcydbid, BTEqualStrategyNumber, + F_OIDEQ, ObjectIdGetDatum(u_sess->proc_cxt.MyDatabaseId)); // 创建扫描键,匹配当前数据库ID + + sd = systable_beginscan(rbRel, RecyclebinDbidNspOrinameIndexId, true, NULL, 2, skey); // 开始扫描回收站关系表 + tup = systable_getnext(sd); // 获取扫描结果的下一个元组 + + systable_endscan(sd); // 结束扫描 + heap_close(rbRel, AccessShareLock); // 关闭回收站关系 + + return tup == NULL; // 若元组为空,返回true,否则返回false +} + + +/* + * 功能:检查指定用户是否为空的回收站 + * 参数列表: + * roleId:用户的OID + */ +bool TrRbIsEmptyUser(Oid roleId) +{ + Relation rbRel; + SysScanDesc sd; + HeapTuple tup; + bool found = false; + + rbRel = heap_open(RecyclebinRelationId, AccessShareLock); // 打开回收站关系 + + sd = systable_beginscan(rbRel, InvalidOid, false, NULL, 0, NULL); // 开始扫描回收站关系表 + + while ((tup = systable_getnext(sd)) != NULL) { // 循环获取扫描结果的下一个元组 + Form_pg_recyclebin rbForm = (Form_pg_recyclebin)GETSTRUCT(tup); + if ((TrObjType)rbForm->rcytype != RB_OBJ_TABLE || rbForm->rcyowner != roleId) { + continue; + } + + found = true; + break; + } + + systable_endscan(sd); // 结束扫描 + heap_close(rbRel, AccessShareLock); // 关闭回收站关系 + + return !found; // 若找不到与用户相关的回收站对象,返回true,否则返回false +} + + +/* + * 功能:检查给定OID是否存在于指定的OID列表中 + * 参数列表: + * lOid:OID列表 + * oid:要检查的OID + */ +static bool TrOidExists(const List *lOid, Oid oid) +{ + ListCell *cell = NULL; + if (lOid == NULL) { // 检查OID列表是否为空 + return false; // 如果为空,返回false + } + + foreach (cell, lOid) { // 遍历OID列表中的每个元素 + if (oid == (*(Oid *)lfirst(cell))) { // 检查当前元素是否与指定的OID相等 + return true; // 如果相等,返回true + } + } + return false; // 如果没有找到匹配的OID,返回false +} + + +/* + * 功能:获取包含回收站中存在的数据库名称的列表 + */ +List *TrGetDbListRcy(void) +{ + Relation rbRel; + SysScanDesc sd; + HeapTuple tup; + + List *lName = NIL; // 用于存储数据库名称的List + List *lOid = NIL; // 用于存储数据库OID的List + char *dbname = NULL; // 用于存储数据库名称的临时变量 + + rbRel = heap_open(RecyclebinRelationId, AccessShareLock); // 打开回收站表 + + sd = systable_beginscan(rbRel, InvalidOid, false, NULL, 0, NULL); // 开始系统表扫描 + + while ((tup = systable_getnext(sd)) != NULL) { // 遍历系统表扫描结果 + Form_pg_recyclebin rbForm = (Form_pg_recyclebin)GETSTRUCT(tup); + + if (TrOidExists(lOid, rbForm->rcydbid)) { // 检查当前数据库OID是否已存在于lOid列表中 + continue; // 如果存在,继续下一次循环 + } + + Oid *oid = (Oid *)palloc0(sizeof(Oid)); // 为数据库OID分配内存 + *oid = rbForm->rcydbid; // 将当前数据库OID赋值给oid + lOid = lappend(lOid, oid); // 将oid添加到lOid列表中 + + dbname = get_database_name(rbForm->rcydbid); // 获取当前数据库OID对应的数据库名称 + if (dbname == NULL) { // 检查数据库名称是否为空 + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_DATABASE), + errmsg("database \"%u\" does not exist", rbForm->rcydbid))); + } + lName = lappend(lName, dbname); // 将数据库名称添加到lName列表中 + } + + list_free_deep(lOid); // 释放lOid列表中的内存 + + systable_endscan(sd); // 结束系统表扫描 + heap_close(rbRel, AccessShareLock); // 关闭回收站表 + + return lName; // 返回包含数据库名称的List +} + + +/* + * 功能:获取回收站中指定表空间中存在的数据库名称的列表 + * 参数列表: + * spcId:表空间OID + */ +List *TrGetDbListSpc(Oid spcId) +{ + List *lName = NIL; // 用于存储数据库名称的List + List *lOid = NIL; // 用于存储数据库OID的List + char *dbname = NULL; // 用于存储数据库名称的临时变量 + + Relation rbRel; + SysScanDesc sd; + ScanKeyData skey[1]; + HeapTuple tup; + + rbRel = heap_open(RecyclebinRelationId, AccessShareLock); // 打开回收站表 + + ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcytablespace, BTEqualStrategyNumber, + F_OIDEQ, ObjectIdGetDatum(spcId)); // 初始化扫描键,根据表空间OID筛选 + + sd = systable_beginscan(rbRel, RecyclebinDbidSpcidRcycsnIndexId, true, NULL, 1, skey); // 开始系统表扫描 + + while ((tup = systable_getnext(sd)) != NULL) { // 遍历系统表扫描结果 + Form_pg_recyclebin rbForm = (Form_pg_recyclebin)GETSTRUCT(tup); + + if (TrOidExists(lOid, rbForm->rcydbid)) { // 检查当前数据库OID是否已存在于lOid列表中 + continue; // 如果存在,继续下一次循环 + } + + Oid *oid = (Oid *)palloc0(sizeof(Oid)); // 为数据库OID分配内存 + *oid = rbForm->rcydbid; // 将当前数据库OID赋值给oid + lOid = lappend(lOid, oid); // 将oid添加到lOid列表中 + + dbname = get_database_name(rbForm->rcydbid); // 获取当前数据库OID对应的数据库名称 + if (dbname == NULL) { // 检查数据库名称是否为空 + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_DATABASE), + errmsg("database \"%u\" does not exist", rbForm->rcydbid))); + } + lName = lappend(lName, dbname); // 将数据库名称添加到lName列表中 + } + + list_free_deep(lOid); // 释放lOid列表中的内存 + + systable_endscan(sd); // 结束系统表扫描 + heap_close(rbRel, AccessShareLock); // 关闭回收站表 + + return lName; // 返回包含数据库名称的List +} + + +/* + * 功能:获取回收站中指定模式中存在的数据库名称的列表 + * 参数列表: + * nspId:模式OID + */ +List *TrGetDbListSchema(Oid nspId) +{ + Relation rbRel; + SysScanDesc sd; + ScanKeyData skey[1]; + HeapTuple tup; + + List *lName = NIL; // 用于存储数据库名称的List + List *lOid = NIL; // 用于存储数据库OID的List + char *dbname = NULL; // 用于存储数据库名称的临时变量 + + rbRel = heap_open(RecyclebinRelationId, AccessShareLock); // 打开回收站表 + + ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcynamespace, BTEqualStrategyNumber, + F_OIDEQ, ObjectIdGetDatum(nspId)); // 初始化扫描键,根据模式OID筛选 + + sd = systable_beginscan(rbRel, RecyclebinDbidNspOrinameIndexId, true, NULL, 1, skey); // 开始系统表扫描 + + while ((tup = systable_getnext(sd)) != NULL) { // 遍历系统表扫描结果 + Form_pg_recyclebin rbForm = (Form_pg_recyclebin)GETSTRUCT(tup); + + if (TrOidExists(lOid, rbForm->rcydbid)) { // 检查当前数据库OID是否已存在于lOid列表中 + continue; // 如果存在,继续下一次循环 + } + + Oid *oid = (Oid *)palloc0(sizeof(Oid)); // 为数据库OID分配内存 + *oid = rbForm->rcydbid; // 将当前数据库OID赋值给oid + lOid = lappend(lOid, oid); // 将oid添加到lOid列表中 + + dbname = get_database_name(rbForm->rcydbid); // 获取当前数据库OID对应的数据库名称 + if (dbname == NULL) { // 检查数据库名称是否为空 + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_DATABASE), + errmsg("database \"%u\" does not exist", rbForm->rcydbid))); + } + lName = lappend(lName, dbname); // 将数据库名称添加到lName列表中 + } + + list_free_deep(lOid); // 释放lOid列表中的内存 + + systable_endscan(sd); // 结束系统表扫描 + heap_close(rbRel, AccessShareLock); // 关闭回收站表 + + return lName; // 返回包含数据库名称的List +} + + +/* + * 功能:获取回收站中指定角色拥有的数据库名称的列表 + * 参数列表: + * roleId:角色OID + */ +List *TrGetDbListUser(Oid roleId) +{ + Relation rbRel; + SysScanDesc sd; + HeapTuple tup; + + List *lName = NIL; // 用于存储数据库名称的List + List *lOid = NIL; // 用于存储数据库OID的List + char *dbname = NULL; // 用于存储数据库名称的临时变量 + + rbRel = heap_open(RecyclebinRelationId, AccessShareLock); // 打开回收站表 + + sd = systable_beginscan(rbRel, InvalidOid, false, NULL, 0, NULL); // 开始系统表扫描 + + while ((tup = systable_getnext(sd)) != NULL) { // 遍历系统表扫描结果 + Form_pg_recyclebin rbForm = (Form_pg_recyclebin)GETSTRUCT(tup); + + // 检查当前记录是否为表对象且拥有指定角色 + if ((TrObjType)rbForm->rcytype != RB_OBJ_TABLE || rbForm->rcyowner != roleId) { + continue; // 如果不满足条件,继续下一次循环 + } + + if (TrOidExists(lOid, rbForm->rcydbid)) { // 检查当前数据库OID是否已存在于lOid列表中 + continue; // 如果存在,继续下一次循环 + } + + Oid *oid = (Oid *)palloc0(sizeof(Oid)); // 为数据库OID分配内存 + *oid = rbForm->rcydbid; // 将当前数据库OID赋值给oid + lOid = lappend(lOid, oid); // 将oid添加到lOid列表中 + + dbname = get_database_name(rbForm->rcydbid); // 获取当前数据库OID对应的数据库名称 + if (dbname == NULL) { // 检查数据库名称是否为空 + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_DATABASE), + errmsg("database \"%u\" does not exist", rbForm->rcydbid))); + } + lName = lappend(lName, dbname); // 将数据库名称添加到lName列表中 + } + + list_free_deep(lOid); // 释放lOid列表中的内存 + + systable_endscan(sd); // 结束系统表扫描 + heap_close(rbRel, AccessShareLock); // 关闭回收站表 + + return lName; // 返回包含数据库名称的List +} + + +/* + * TrGetDatabaseList + * Return a list of all databases found in pg_database. + */ +/* + * 功能:获取非模板数据库的名称列表 + */ +List *TrGetDbListAuto(void) +{ + List* dblist = NIL; // 用于存储数据库名称的List + Relation rel; + SysScanDesc sd; + HeapTuple tup; + + rel = heap_open(DatabaseRelationId, AccessShareLock); // 打开数据库关系表 + + sd = systable_beginscan(rel, InvalidOid, false, NULL, 0, NULL); // 开始系统表扫描 + + while ((tup = systable_getnext(sd)) != NULL) { // 遍历系统表扫描结果 + Form_pg_database pgdatabase = (Form_pg_database)GETSTRUCT(tup); + + // 检查数据库名称是否为 "template0" 或 "template1" + if (strcmp(NameStr(pgdatabase->datname), "template0") == 0 || + strcmp(NameStr(pgdatabase->datname), "template1") == 0) { + continue; // 如果是模板数据库,继续下一次循环 + } + + dblist = lappend(dblist, pstrdup(NameStr(pgdatabase->datname))); // 将非模板数据库名称添加到dblist列表中 + } + + systable_endscan(sd); // 结束系统表扫描 + heap_close(rel, AccessShareLock); // 关闭数据库关系表 + + return dblist; // 返回包含非模板数据库名称的List +} + +/* + * 功能:检查给定对象是否在回收站中 + * 参数列表: + * obj:要检查的对象的地址 + */ +static bool TrObjInRecyclebin(const ObjectAddress *obj) +{ + Relation rbRel; + SysScanDesc sd; + HeapTuple tup; + ScanKeyData skey[2]; + bool found = false; + + // 检查对象是否是关系类 + if (getObjectClass(obj) != OCLASS_CLASS) { + return false; + } + + // 设置扫描键值,用于检索与数据库ID和关系ID匹配的回收站条目 + ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcydbid, BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(u_sess->proc_cxt.MyDatabaseId)); + ScanKeyInit(&skey[1], Anum_pg_recyclebin_rcyrelid, BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(obj->objectId)); + + rbRel = heap_open(RecyclebinRelationId, AccessShareLock); // 打开回收站关系表 + sd = systable_beginscan(rbRel, RecyclebinDbidRelidIndexId, true, NULL, 2, skey); // 开始系统表扫描 + + // 遍历扫描结果,检查是否有与操作为删除的回收站条目匹配 + while ((tup = systable_getnext(sd)) != NULL) { + Form_pg_recyclebin rbForm = (Form_pg_recyclebin)GETSTRUCT(tup); + if ((TrObjType)rbForm->rcyoperation == 'd') { + found = true; + break; + } + } + systable_endscan(sd); // 结束系统表扫描 + heap_close(rbRel, AccessShareLock); // 关闭回收站关系表 + + return found; // 返回是否找到与操作为删除的回收站条目匹配的标志 +} +/* + * May this object be a recyclebin object? + * true: with "BIN$" prefix, or not a Relation\Type\Trigger\Constraint\Rule + * false: without "BIN$" prefix, or not exists + */ +/* + * 功能:检查对象是否可能是回收站对象 + * 参数列表: + * classid:对象所属的类别ID + * objid:对象的ID + * objname:对象的名称(可选) + */ +static bool TrMaybeRbObject(Oid classid, Oid objid, const char *objname = NULL) +{ + HeapTuple tup; + + /* 注意:在 RbDrop 时,我们保留规则源名称。*/ + if (classid != RewriteRelationId && objname) { + return strncmp(objname, "BIN$", 4) == 0; // 如果对象名称以 "BIN$" 开头,返回 true + } + + switch (classid) { + case RelationRelationId: + tup = SearchSysCache1(RELOID, ObjectIdGetDatum(objid)); // 在系统缓存中查找关系类对象 + if (tup != NULL) { + objname = NameStr(((Form_pg_class)GETSTRUCT(tup))->relname); // 获取关系类对象的名称 + ReleaseSysCache(tup); + } + break; + case TypeRelationId: + tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(objid)); // 在系统缓存中查找类型对象 + if (tup != NULL) { + objname = NameStr(((Form_pg_type)GETSTRUCT(tup))->typname); // 获取类型对象的名称 + ReleaseSysCache(tup); + } + break; + case TriggerRelationId: { + Relation relTrig; + ScanKeyData skey[1]; + SysScanDesc sd; + + relTrig = heap_open(TriggerRelationId, AccessShareLock); // 打开触发器关系表 + ScanKeyInit(&skey[0], ObjectIdAttributeNumber, BTEqualStrategyNumber, + F_OIDEQ, ObjectIdGetDatum(objid)); + sd = systable_beginscan(relTrig, TriggerOidIndexId, true, NULL, 1, skey); // 开始系统表扫描 + if ((tup = systable_getnext(sd)) != NULL) { + objname = NameStr(((Form_pg_trigger)GETSTRUCT(tup))->tgname); // 获取触发器对象的名称 + } + systable_endscan(sd); // 结束系统表扫描 + heap_close(relTrig, AccessShareLock); // 关闭触发器关系表 + break; + } + case ConstraintRelationId: + tup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(objid)); // 在系统缓存中查找约束对象 + if (tup != NULL) { + objname = NameStr(((Form_pg_constraint)GETSTRUCT(tup))->conname); // 获取约束对象的名称 + ReleaseSysCache(tup); + } + break; + case NamespaceRelationId: + /* 将命名空间视为非回收站对象。 */ + return false; + default: + /* 可能是回收站对象。 */ + return true; + } + + if (objname) { + return strncmp(objname, "BIN$", 4) == 0; // 如果对象名称以 "BIN$" 开头,返回 true + } + + return false; // 默认情况下,返回 false +} + + +/* + * 功能:递归地检查对象及其引用对象是否在回收站中 + * 参数列表: + * depRel:对象所在关系 + * obj:待检查的对象地址 + * objSet:对象地址集合 + */ +static bool TrIsRefRbObjectImpl(Relation depRel, const ObjectAddress *obj, ObjectAddresses *objSet) +{ + int startIdx; + + if (TrObjInRecyclebin(obj)) { // 检查对象是否在回收站中 + return true; + } + + startIdx = objSet->numrefs; // 记录对象地址集合中的引用对象数量 + + if (!TrMaybeRbObject(obj->classId, obj->objectId)) { // 检查对象是否可能是回收站对象 + return false; + } + + TrFindAllRefObjs(depRel, obj, objSet, true); // 查找对象的所有引用对象 + TrFindAllInternalObjs(depRel, obj, objSet, true); // 查找对象的所有内部对象 + + for (int i = startIdx; i < objSet->numrefs; i++) { // 遍历新添加的引用对象 + if (TrIsRefRbObjectImpl(depRel, &objSet->refs[i], objSet)) { + return true; // 如果引用对象或其引用对象在回收站中,返回 true + } + } + + return false; // 默认情况下,返回 false +} + + +/* object is a rb object, or reference to a rb object. */ +/* + * 功能:检查对象及其引用对象是否在回收站中(调用实现函数) + * 参数列表: + * obj:待检查的对象地址 + * depRel:对象所在关系 + */ +static bool TrIsRefRbObject(const ObjectAddress *obj, Relation depRel) +{ + ObjectAddresses *objSet = new_object_addresses(); // 创建对象地址集合 + bool relArgNull = depRel == NULL; // 检查传入的关系对象是否为空 + bool result = false; + + if (relArgNull) { + depRel = heap_open(DependRelationId, AccessShareLock); // 若为空,打开 DependRelation 表 + } + + /* Note: we not care obj->deptype here. */ + add_object_address_ext1(obj, objSet); // 将待检查的对象地址添加到对象地址集合中 + + result = TrIsRefRbObjectImpl(depRel, obj, objSet); // 调用实现函数检查对象及其引用对象是否在回收站中 + + free_object_addresses(objSet); // 释放对象地址集合的内存 + + if (relArgNull) { + heap_close(depRel, AccessShareLock); // 若之前打开了关系表,则关闭它 + } + + return result; // 返回检查结果,对象或其引用对象是否在回收站中 +} + + +/* + * 功能:检查对象及其引用对象是否在回收站中(扩展函数) + * 参数列表: + * classid:对象的类别标识 + * objid:对象的标识 + * objname:对象的名称 + */ +bool TrIsRefRbObjectEx(Oid classid, Oid objid, const char *objname) +{ + if (!TcapFeatureAvail()) { + return false; // 如果 Tcap 功能不可用,则返回 false + } + + /* Note: we preserve rule origin name when RbDrop. */ + if (TrRbIsEmptyDb(u_sess->proc_cxt.MyDatabaseId)) { + return false; // 如果当前数据库为空,则返回 false + } + + if (classid != RewriteRelationId && objname && strncmp(objname, "BIN$", 4) != 0) { + return false; // 如果对象不是 RewriteRelation,且对象名称不以 "BIN$" 开头,则返回 false + } + + ObjectAddress obj = {classid, objid}; // 创建对象地址结构 + + return TrIsRefRbObject(&obj); // 调用 TrIsRefRbObject 函数检查对象及其引用对象是否在回收站中 +} + + +/* + * 功能:禁止访问与回收站对象相关的依赖关系 + * 参数: + * depRel:依赖关系表的关系 + * depender:依赖对象地址 + * referenced:被依赖对象地址的数组 + * nreferenced:被依赖对象地址数组的长度 + */ +void TrForbidAccessRbDependencies(Relation depRel, const ObjectAddress *depender, + const ObjectAddress *referenced, int nreferenced) +{ + if (!TcapFeatureAvail()) { + return; // 如果 Tcap 功能不可用,则直接返回 + } + + if (IsInitdb || TrRbIsEmptyDb(u_sess->proc_cxt.MyDatabaseId)) { + return; // 如果是 Initdb 模式,或者当前数据库为空,则直接返回 + } + + if (TrIsRefRbObject(depender, depRel)) { + elog (ERROR, "can not access recycle object."); // 如果依赖对象或其引用对象在回收站中,则报错,不允许访问 + } + + for (int i = 0; i < nreferenced; i++, referenced++) { + if (TrIsRefRbObject(referenced, depRel)) { + elog (ERROR, "can not access recycle object."); // 如果被依赖对象或其引用对象在回收站中,则报错,不允许访问 + } + } + + return; +} + + +/* + * 功能:禁止访问回收站对象 + * 参数列表: + * classid:对象类别的 OID + * objid:对象的 OID + * objname:对象的名称 + */ +void TrForbidAccessRbObject(Oid classid, Oid objid, const char *objname) +{ + if (!TcapFeatureAvail()) { + return; // 如果 Tcap 功能不可用,则直接返回 + } + + if (TrRbIsEmptyDb(u_sess->proc_cxt.MyDatabaseId) || !TrMaybeRbObject(classid, objid, objname)) { + return; // 如果当前数据库为空,或者对象可能不是回收站对象,则直接返回 + } + + ObjectAddress obj = {classid, objid}; + if (TrIsRefRbObject(&obj)) { + elog (ERROR, "can not access recycle object."); // 如果对象或其引用对象在回收站中,则报错,不允许访问 + } + + return; +} + +/* + * 功能:判断是否为回收站对象的 PostgreSQL 函数 + * 参数列表: + * classid:对象类别的 OID + * objid:对象的 OID + * objname:对象的名称 + */ +Datum gs_is_recycle_object(PG_FUNCTION_ARGS) +{ + int classid = PG_GETARG_INT32(0); // 获取函数参数中的对象类别 OID + int objid = PG_GETARG_INT32(1); // 获取函数参数中的对象 OID + Name objname = PG_GETARG_NAME(2); // 获取函数参数中的对象名称 + bool result = false; // 用于存储判断结果,默认为 false + + // 调用 TrIsRefRbObjectEx 函数判断给定对象是否为回收站对象,并将结果存储在 result 中 + result = TrIsRefRbObjectEx(classid, objid, NameStr(*objname)); + + // 返回布尔值结果 + PG_RETURN_BOOL(result); +} -- 2.34.1 From e80e9b46c19f6d1cc419829b494c8c7aed9e9704 Mon Sep 17 00:00:00 2001 From: nuoya <1204149038@qq.com> Date: Sun, 27 Aug 2023 16:13:15 +0800 Subject: [PATCH 02/50] Update tcap_version.cpp --- src/gausskernel/storage/tcap/tcap_version.cpp | 469 ++++++++++++------ 1 file changed, 316 insertions(+), 153 deletions(-) diff --git a/src/gausskernel/storage/tcap/tcap_version.cpp b/src/gausskernel/storage/tcap/tcap_version.cpp index aad39ed3f..20eb627bc 100644 --- a/src/gausskernel/storage/tcap/tcap_version.cpp +++ b/src/gausskernel/storage/tcap/tcap_version.cpp @@ -53,74 +53,78 @@ #include "storage/tcap.h" #include "catalog/pg_constraint.h" - +//用于判断指定关系表是否包含外键约束 static bool TvIsContainsForeignKey(Oid relid) { - Relation rbRel; - SysScanDesc sd; - ScanKeyData key; - HeapTuple tup; - bool isContainsForeignKey = false; + Relation rbRel; //用于访问系统目录表的关系对象 + SysScanDesc sd; //用于扫描系统目录表的扫描描述对象 + ScanKeyData key;//扫描键对象,用于设置扫描的条件 + HeapTuple tup; //表示系统目录表中的一个元组 + bool isContainsForeignKey = false;// 表示是否包含外键约束,初始值为假 + //打开系统目录表,并将结果赋值给变量 rbRel,以便后续的操作可以使用这个关系对象来访问系统目录表中的信息 rbRel = heap_open(ConstraintRelationId, AccessShareLock); - + ScanKeyInit(&key, Anum_pg_constraint_conrelid, BTEqualStrategyNumber, - F_OIDEQ, ObjectIdGetDatum(relid)); - + F_OIDEQ, ObjectIdGetDatum(relid));//初始化扫描键,用于在系统目录表上进行扫描 + //在系统目录表上启动一个扫描操作,并将扫描描述对象赋值给变量 sd,用于管理扫描过程 sd = systable_beginscan(rbRel, ConstraintRelidIndexId, true, SnapshotNow, 1, &key); while ((tup = systable_getnext(sd)) != NULL) { - Form_pg_constraint con = (Form_pg_constraint)GETSTRUCT(tup); + Form_pg_constraint con = (Form_pg_constraint)GETSTRUCT(tup);//获取元组的数据 /* Contains a foreign key or referenced by foreign key */ if (con->contype == CONSTRAINT_FOREIGN && con->conrelid == relid) { - isContainsForeignKey = true; + isContainsForeignKey = true;//如果找到符合条件的外键约束,设置标志为真 break; } } - systable_endscan(sd); + systable_endscan(sd);//结束扫描 heap_close(rbRel, AccessShareLock); - return isContainsForeignKey; + return isContainsForeignKey;//返回是否包含外键约束的结果 } - +// 用于检查指定关系表是否被外键约束引用 static bool TvIsReferencedByForeignKey(Oid relid) { - Relation rbRel; - SysScanDesc sd; - HeapTuple tup; - bool isReferencedByForeignKey = false; - - rbRel = heap_open(ConstraintRelationId, AccessShareLock); + Relation rbRel;//用于访问系统目录表的关系对象 + SysScanDesc sd;//用于扫描系统目录表的扫描描述对象 + HeapTuple tup; //表示系统目录表中的一个元组 + bool isReferencedByForeignKey = false;//表示是否被外键约束引用,初始值为假 + rbRel = heap_open(ConstraintRelationId, AccessShareLock);//打开系统目录表 + + //在系统目录表上启动一个扫描操作,无需索引,不锁定扫描行,使用快照为当前时刻,不设置扫描条件 sd = systable_beginscan(rbRel, InvalidOid, false, SnapshotNow, 0, NULL); while ((tup = systable_getnext(sd)) != NULL) { - Form_pg_constraint con = (Form_pg_constraint)GETSTRUCT(tup); + Form_pg_constraint con = (Form_pg_constraint)GETSTRUCT(tup);//获取元组的数据 /* Not referenced by foreign key */ if (con->confrelid == relid) { - isReferencedByForeignKey = true; + isReferencedByForeignKey = true;//如果找到符合条件的引用,设置标志为真 break; } } - systable_endscan(sd); - heap_close(rbRel, AccessShareLock); + systable_endscan(sd);//结束扫描 + heap_close(rbRel, AccessShareLock);//关闭系统目录表 - return isReferencedByForeignKey; + return isReferencedByForeignKey;//返回是否被外键约束引用的结果 } - +//用于检查指定关系表是否包含外键约束或被外键约束引用 static bool TvForeignKeyCheck(Oid relid) -{ - return (TvIsContainsForeignKey(relid) || TvIsReferencedByForeignKey(relid)); +{ + //调用TvIsContainsForeignKey函数和TvIsReferencedByForeignKey函数来判断指定关系表是否包含外键约束或被外键约束引用 + //当返回true时说明此关系表包含外键约束或被外键约束引用,反之则不包含 + return (TvIsContainsForeignKey(relid) || TvIsReferencedByForeignKey(relid));/ } - +//此函数用于检查指定关系表是否支持TimeCapsule特性 static bool TvFeatureSupport(Oid relid, char **errstr, bool isTimecapsuleTable) { Relation rel = RelationIdGetRelation(relid); - Form_pg_class classForm; + Form_pg_class classForm;//存储表的元数据信息 if (!RelationIsValid(rel)) { ereport( @@ -128,9 +132,9 @@ static bool TvFeatureSupport(Oid relid, char **errstr, bool isTimecapsuleTable) errmsg("could not open relation with OID %u", relid))); } - classForm = rel->rd_rel; + classForm = rel->rd_rel;// 获取表的元数据信息 if (classForm->relkind != RELKIND_RELATION) { - *errstr = "timecapsule feature does not support non-ordinary table"; + *errstr = "timecapsule feature does not support non-ordinary table";//各种不支持TimeCapsule特性的原因 } else if (is_sys_table(RelationGetRelid(rel))) { *errstr = "timecapsule feature does not support system table"; } else if (classForm->relpersistence != RELPERSISTENCE_PERMANENT) { @@ -159,17 +163,19 @@ static bool TvFeatureSupport(Oid relid, char **errstr, bool isTimecapsuleTable) *errstr = NULL; } - RelationClose(rel); + RelationClose(rel);// 关闭表关系对象 return *errstr == NULL; } - +//用于在指定范围下检测表是否支持TimeCapsule特性 void TvCheckVersionScan(RangeTblEntry *rte) { - char *errstr = NULL; + char *errstr = NULL;// 初始化存储错误信息的字符串指针 + //调用TvFeatureSupport函数检查给定表是否支持 TimeCapsule 特性 if (!TvFeatureSupport(rte->relid, &errstr, false)) { + // 如果不支持,输出错误信息 ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), (errmsg("%s", errstr)))); @@ -178,69 +184,71 @@ void TvCheckVersionScan(RangeTblEntry *rte) return; } +//用于检查给定的扫描状态是否是版本表扫描 bool TvIsVersionScan(const ScanState *ss) { - EState *estate = ss->ps.state; - Scan *scan = (Scan *)ss->ps.plan; + EState *estate = ss->ps.state;//获取扫描状态关联的执行状态对象 + Scan *scan = (Scan *)ss->ps.plan;//获取扫描状态关联的扫描计划节点 TimeCapsuleClause *tcc = rt_fetch(scan->scanrelid, estate->es_range_table)->timecapsule; - - return tcc != NULL; + // 获取查询范围表中与扫描计划节点关联的 TimeCapsule 子句 + return tcc != NULL;//根据返回结果判断是否是版本表扫描 } /* * Whether the plan contains version table scan. */ +//用于检查给定的查询计划是否包含版本表扫描 bool TvIsVersionPlan(const PlannedStmt *stmt) { - ListCell *l = NULL; - foreach (l, stmt->rtable) { + ListCell *l = NULL;//初始化链表遍历用的指针 + foreach (l, stmt->rtable) {//遍历查询计划中的范围表条目 RangeTblEntry *rte = (RangeTblEntry *)lfirst(l); - if (rte->timecapsule != NULL) { - return true; + if (rte->timecapsule != NULL) {//如果范围表条目具有TimeCapsule子句 + return true;//返回真,说明包含版本表扫描 } } return false; } - +//用于将版本表达式规范化 Node *TvTransformVersionExpr(ParseState *pstate, TvVersionType tvtype, Node *tvver) { - Node *verExpr = tvver; + Node *verExpr = tvver;//用于存储版本表达式的变量 - verExpr = transformExpr(pstate, tvver); - if (checkExprHasSubLink(verExpr)) { + verExpr = transformExpr(pstate, tvver);//使用transformExpr函数转换版本表达式 + if (checkExprHasSubLink(verExpr)) {//检查表达式是否包含子链接 ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION), errmsg("timecapsule clause not support sublink."))); } - + //根据版本类型进行强制转换 if (tvtype == TV_VERSION_TIMESTAMP) { verExpr = coerce_to_specific_type(pstate, verExpr, TIMESTAMPTZOID, "TIMESTAMP"); } else { - verExpr = coerce_to_specific_type(pstate, verExpr, INT8OID, "CSN"); + verExpr = coerce_to_specific_type(pstate, verExpr, INT8OID, "CSN");//分配表达式的排序规则 } assign_expr_collations(pstate, verExpr); - return verExpr; + return verExpr;//返回规范化后的版本表达式 } - +//用于计算版本表达式的常量值 static Const *TvEvalVerExpr(TvVersionType tvtype, Node *tvver) { - Const *result = (Const *)tvver; + Const *result = (Const *)tvver;//用于存储版本表达式的常量结果 - if (!IsA(result, Const)) { + if (!IsA(result, Const)) {//如果版本表达式非常量 Node *verExpr; - ParseState *pstate = make_parsestate(NULL); - - verExpr = TvTransformVersionExpr(pstate, tvtype, tvver); - free_parsestate(pstate); + ParseState *pstate = make_parsestate(NULL);//创建解析状态对象 + verExpr = TvTransformVersionExpr(pstate, tvtype, tvver);//将版本表达式规范化 + free_parsestate(pstate);//释放解析状态对象 + // 使用evaluate_expr函数对转换后的表达式进行评估,获取常量值 result = (Const *)evaluate_expr((Expr *)verExpr, exprType(verExpr), exprTypmod(verExpr), exprCollation(verExpr)); } - + //检查结果是否是常量且非空 if (!IsA(result, Const) || result->constisnull) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), @@ -265,12 +273,12 @@ TransactionId TvFetchSnpxminRecycle(TimestampTz tz) Datum value; bool isnull = false; TransactionId snapxmin = FirstNormalTransactionId; - + // 打开快照表以进行访问 rel = heap_open(SnapshotRelationId, AccessShareLock); - + // 初始化扫描键,限制时间小于等于给定时间 ScanKeyInit(&skey[0], Anum_pg_snapshot_snptime, BTLessEqualStrategyNumber, F_TIMESTAMP_LE, TimestampTzGetDatum(tz)); - + // 开始扫描快照表,根据时间范围检索记录 sd = systable_beginscan(rel, SnapshotTimeCsnIndexId, true, NULL, 1, skey); tup = systable_getnext(sd); /* Limit 1 */ @@ -291,6 +299,7 @@ TransactionId TvFetchSnpxminRecycle(TimestampTz tz) * We use the round-down way to obtain snapshots. that is, * select * from gs_txn_snapshot where snptime <= :tz order by snptime desc limit 1; */ +// 获取指定时间的快照信息 static void TvFetchSnapTz(TimestampTz tz, Snapshot snap) { Relation rel; @@ -318,10 +327,10 @@ static void TvFetchSnapTz(TimestampTz tz, Snapshot snap) ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("cannot find the restore point"))); } - + //获取记录中的快照信息值 value = heap_getattr(tup, Anum_pg_snapshot_snpsnapshot, RelationGetDescr(rel), &isnull); snapstr = TextDatumGetCString(value); - + //反序列化快照信息字符串为快照对象 TxnSnapDeserialize(snapstr, snap); systable_endscan(sd); @@ -337,6 +346,7 @@ static void TvFetchSnapTz(TimestampTz tz, Snapshot snap) * We use the round-down way to obtain snapshots. that is, * select * from gs_txn_snapshot where snpcsn <= :csn order by snpcsn desc limit 1; */ +//跟CSN序列号获取快照信息 static void TvFetchSnapCsn(int64 csn, Snapshot snap) { Relation rel; @@ -380,6 +390,7 @@ static void TvFetchSnapCsn(int64 csn, Snapshot snap) * must be set to MaxTransactionId to ensure the correctness. */ if ((CommitSeqNo)csn != snap->snapshotcsn) { + //如果指定的CSN与快照的CSN不相等,则更新快照对象的一些属性 snap->snapshotcsn = (CommitSeqNo)csn; snap->timeline = 0; snap->xmin = snap->xmin; @@ -393,31 +404,38 @@ static void TvFetchSnapCsn(int64 csn, Snapshot snap) return; } - +//根据版本类型和值获取快照信息 static Snapshot TvFetchSnap(TvVersionType type, Const *value) { - Snapshot snap = (Snapshot)palloc0(sizeof(SnapshotData)); - + Snapshot snap = (Snapshot)palloc0(sizeof(SnapshotData));//为快照对象分配内存 + //根据版本类型获取快照信息 if (type == TV_VERSION_TIMESTAMP) { TvFetchSnapTz(DatumGetTimestampTz(value->constvalue), snap); } else { TvFetchSnapCsn(DatumGetInt64(value->constvalue), snap); } - snap->satisfies = SNAPSHOT_VERSION_MVCC; + snap->satisfies = SNAPSHOT_VERSION_MVCC;//将快照对象的隔离级别设置为多版本并发控制 return snap; } - +/* + * 功能:用于获取特定版本的快照对象 + * 参数: + * relation: 要获取快照的关系(表) + * tvtype: 版本类型(时间戳或 CSN) + * tvver: 用于确定快照的版本值 + */ static Snapshot TvGetSnap(Relation relation, TvVersionType tvtype, Node *tvver) { - Const *value; - Snapshot snap; + Const *value;//用于存储版本值的常量对象 + Snapshot snap;//快照对象 + //通过TvEvalVerExpr函数计算版本值 value = TvEvalVerExpr(tvtype, tvver); - + //验证快照是否适用于给定的关系 snap = TvFetchSnap(tvtype, value); - + //验证快照是否适用于给定的关系 if (!tableam_tcap_validate_snap(relation, snap)) { ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("Restore point too old"))); @@ -425,18 +443,23 @@ static Snapshot TvGetSnap(Relation relation, TvVersionType tvtype, Node *tvver) return snap; } - +/* + * 功能:用于验证关系表的定义是否与指定的快照相匹配 + * 参数: + * - relid: 要验证的关系的 OID + * - snapcsn: 快照的提交序列号 + */ static void TvValidateRelDDL(Oid relid, CommitSeqNo snapcsn) { - Relation rel = RelationIdGetRelation(relid); - if (!RelationIsValid(rel)) { + Relation rel = RelationIdGetRelation(relid);//通过id获取要验证的关系 + if (!RelationIsValid(rel)) {//检查关系是否有效 ereport( - ERROR, (errcode(ERRCODE_RELATION_OPEN_ERROR), + ERROR, (errcode(ERRCODE_RELATION_OPEN_ERROR), //无效时报错 errmsg("could not open relation with OID %u", relid))); } - + //检查关系的变更CSN是否大于等于快照的CSN if (RelationGetChangecsn(rel) >= snapcsn) { - ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), + ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), //不匹配时报错 errmsg("The table definition of \"%s\" has been changed.", RelationGetRelationName(rel)))); } @@ -450,37 +473,51 @@ static void TvValidateRelDDL(Oid relid, CommitSeqNo snapcsn) * Choose user-specified snapshot if TimeCapsule clause exists, otherwise * estate->es_snapshot instead. */ +/* + * 功能:用于选择用于扫描的快照 + * 参数: + * relation: 扫描关联的关系对象 + * scan: 扫描计划节点 + * ss: 扫描状态 + */ Snapshot TvChooseScanSnap(Relation relation, Scan *scan, ScanState *ss) { - EState *estate = ss->ps.state; - Snapshot snap = estate->es_snapshot; + EState *estate = ss->ps.state;//获取执行状态对象 + Snapshot snap = estate->es_snapshot;//获取执行状态中的快照 + //获取范围表中与扫描关联的条目 RangeTblEntry *rte = rt_fetch(scan->scanrelid, estate->es_range_table); TimeCapsuleClause *tcc = rte->timecapsule; if (likely(tcc == NULL)) { - return snap; + return snap;//返回默认的快照 } else { bool isnull = false; ExprContext *econtext; Datum val; Const *con; - + //创建表达式的上下文信息 econtext = CreateExprContext(estate); val = ExecEvalExprSwitchContext(ExecInitExpr((Expr *)tcc->tvver, &ss->ps), - econtext, &isnull, NULL); + econtext, &isnull, NULL);//计算版本值 con = makeConst((tcc->tvtype == TV_VERSION_TIMESTAMP) ? TIMESTAMPTZOID : INT8OID, - -1, InvalidOid, 8, val, isnull, true); - + -1, InvalidOid, 8, val, isnull, true);//创建对应类型的常量节点 + //获取适用于指定版本的快照 snap = TvGetSnap(relation, tcc->tvtype, (Node *)con); + //验证快照版本是否允许执行相关操作 TvValidateRelDDL(rte->relid, snap->snapshotcsn); - + //释放表达式上下文和常量节点 FreeExprContext(econtext, true); pfree(con); } return snap; } - +/* + * 功能:用于根据快照删除表中的数据 + * 参数: + * relid: 关系对象的 OID + * snap: 用于删除数据的快照 + */ void TvDeleteDelta(Oid relid, Snapshot snap) { Relation rel; @@ -490,33 +527,42 @@ void TvDeleteDelta(Oid relid, Snapshot snap) /* Notice: invoker already acquired lock */ rel = heap_open(relid, NoLock); - sd = tableam_scan_begin(rel, snap, 0, NULL); + sd = tableam_scan_begin(rel, snap, 0, NULL);//开始扫描 while ((tup = (HeapTuple)tableam_scan_getnexttuple(sd, ForwardScanDirection)) != NULL) { - simple_heap_delete(rel, &tup->t_self); + simple_heap_delete(rel, &tup->t_self);//循环删除数据直至扫描结束 } tableam_scan_end(sd); heap_close(rel, NoLock); return; } - +/* + * 功能:用于根据给定的快照,删除关系或分区关系中不再需要的数据行 + * 参数: + * rel:主关系 + * partRel:分区关系 + * p:分区对象 + * snap:快照对象 + */ void TvUheapDeleteDeltaRel(Relation rel, Relation partRel, Partition p, Snapshot snap) { + //声明变量,用于存储扫描数据、事务标识等信息 TableScanDesc sd; UHeapTuple tup; TupleTableSlot *oldslot = NULL; TransactionId tmfdXmin = InvalidTransactionId; - - Snapshot snapshotNow = (Snapshot)palloc0(sizeof(SnapshotData)); + + Snapshot snapshotNow = (Snapshot)palloc0(sizeof(SnapshotData));//分配并初始化一个Snapshot对象 (void)GetSnapshotData(snapshotNow, false); - snap->user_data = (void *)snapshotNow; + snap->user_data = (void *)snapshotNow;//将快照与snapshotNow关联 - EState *estate = CreateExecutorState(); + EState *estate = CreateExecutorState();//创建执行状态 /* * We need a ResultRelInfo so we can use the regular executor's * index-entry-making machinery. (There used to be a huge amount of code * here that basically duplicated execUtils.c ...) */ + //创建一个ResultRelInfo对象 ResultRelInfo *resultRelInfo = makeNode(ResultRelInfo); resultRelInfo->ri_RangeTableIndex = 1; /* dummy */ resultRelInfo->ri_RelationDesc = rel; @@ -525,12 +571,14 @@ void TvUheapDeleteDeltaRel(Relation rel, Relation partRel, Partition p, Snapshot estate->es_num_result_relations = 1; estate->es_result_relation_info = resultRelInfo; + //根据是否存在分区关系选择要扫描的关系 Relation relRel = (partRel != NULL) ? partRel : rel; - sd = tableam_scan_begin(relRel, snap, 0, NULL); + sd = tableam_scan_begin(relRel, snap, 0, NULL);//根据选择的扫描关系初始化表扫描描述符 + while ((tup = (UHeapTuple)tableam_scan_getnexttuple(sd, ForwardScanDirection)) != NULL) { SimpleUHeapDelete(relRel, &tup->ctid, snapshotNow, &oldslot, &tmfdXmin); ExecDeleteIndexTuples(oldslot, &tup->ctid, estate, relRel, p, NULL, false); - if (relRel != NULL && relRel->rd_mlogoid != InvalidOid) { + if (relRel != NULL && relRel->rd_mlogoid != InvalidOid) {//如果关系存在并且具有有效的mlogoid,则将删除信息插入mlog表 insert_into_mlog_table(relRel, relRel->rd_mlogoid, NULL, &tup->ctid, tmfdXmin, 'D'); } if (oldslot) { @@ -554,11 +602,18 @@ void TvUheapDeleteDeltaRel(Relation rel, Relation partRel, Partition p, Snapshot return; } - +/* + * 功能:根据给定的快照,递归删除主关系及其所有分区关系中不再需要的数据行。 + * + * 参数列表: + * rel:主关系。 + * relid:主关系的OID。 + * snap:快照对象。 + */ void TvUheapDeleteDeltaPart(Relation rel, Oid relid, Snapshot snap) { - List* partTupleList = NIL; - ListCell* partCell = NULL; + List* partTupleList = NIL;//分区元组列表 + ListCell* partCell = NULL;//分区元组遍历指针 /* Open partition table, find all partition names based on the parentId. * partitioned table unspport the unlogged table. @@ -570,59 +625,91 @@ void TvUheapDeleteDeltaPart(Relation rel, Oid relid, Snapshot snap) foreach (partCell, partTupleList) { /* the "tup" just for get partOid, UHeapTup has no HEAP_HASOID flag, so here use HeapTuple */ HeapTuple tup = (HeapTuple)lfirst(partCell); - Oid partOid = HeapTupleGetOid(tup); - Partition p = partitionOpen(rel, partOid, AccessExclusiveLock); - Relation partRel = partitionGetRelation(rel, p); + Oid partOid = HeapTupleGetOid(tup);//获取分区OID + Partition p = partitionOpen(rel, partOid, AccessExclusiveLock);//打开分区 + Relation partRel = partitionGetRelation(rel, p);//获取分区关系 - if (RelationIsSubPartitioned(rel)) { - List* subPartTupleList = searchPgPartitionByParentId(PART_OBJ_TYPE_TABLE_SUB_PARTITION, partOid); - ListCell* subPartCell = NULL; - foreach (subPartCell, subPartTupleList) { + if (RelationIsSubPartitioned(rel)) {//当主关系存在子分区执行以下操作 + List* subPartTupleList = searchPgPartitionByParentId(PART_OBJ_TYPE_TABLE_SUB_PARTITION, partOid);//查找子分区元组列表 + ListCell* subPartCell = NULL;//初始化子分区元组遍历指针 + foreach (subPartCell, subPartTupleList) {//遍历子分区 HeapTuple subTup = (HeapTuple)lfirst(subPartCell); - Oid subPartOid = HeapTupleGetOid(subTup); - Partition subPar = partitionOpen(partRel, subPartOid, AccessExclusiveLock); - Relation subPartRel = partitionGetRelation(partRel, subPar); - TvUheapDeleteDeltaRel(rel, subPartRel, subPar, snap); - releaseDummyRelation(&subPartRel); - partitionClose(partRel, subPar, NoLock); + Oid subPartOid = HeapTupleGetOid(subTup);//获取子分区OID + Partition subPar = partitionOpen(partRel, subPartOid, AccessExclusiveLock);//子分区元组遍历 + Relation subPartRel = partitionGetRelation(partRel, subPar);//获取子分区关系 + TvUheapDeleteDeltaRel(rel, subPartRel, subPar, snap);//删除子分区的不再需要的数据行 + releaseDummyRelation(&subPartRel);//释放子分区关系 + partitionClose(partRel, subPar, NoLock);//关闭子分区 } - freePartList(subPartTupleList); + freePartList(subPartTupleList);//释放子分区元组列表 } else { - TvUheapDeleteDeltaRel(rel, partRel, p, snap); + TvUheapDeleteDeltaRel(rel, partRel, p, snap);//删除分区的不再需要的数据行 } - releaseDummyRelation(&partRel); - partitionClose(rel, p, NoLock); + releaseDummyRelation(&partRel);//释放分区关系 + partitionClose(rel, p, NoLock);//关闭分区 } - freePartList(partTupleList); + freePartList(partTupleList);//释放分区元组列表 return; } - +/* + * 功能:根据给定的快照,删除主关系或其分区关系中不再需要的数据行。 + * + * 参数列表: + * relid:关系的OID。 + * snap:快照对象。 + */ void TvUheapDeleteDelta(Oid relid, Snapshot snap) { - Relation rel = heap_open(relid, NoLock); - if (RELATION_IS_PARTITIONED(rel)) { + Relation rel = heap_open(relid, NoLock);//以NoLock无锁模式访问待操作的关系 + if (RELATION_IS_PARTITIONED(rel)) {//根据关系是否为分区表进行不同的操作 + //如果是分区表,则调用TvUheapDeleteDeltaPart函数来删除每个分区的Delta数据 TvUheapDeleteDeltaPart(rel, relid, snap); } else { + //否则直接调用TvUheapDeleteDeltaRel函数来删除Delta数据 TvUheapDeleteDeltaRel(rel, NULL, NULL, snap); } heap_close(rel, NoLock); } - +/* + * 功能:用于获取数据库中的元组 + * + * 参数列表: + * arg:函数指针 + */ typedef HeapTuple (*TvFetchTupleHook)(void *arg); static HeapTuple TvFetchTuple(void *arg) { + //调用tableam_scan_getnexttuple函数来获取下一个元组 HeapTuple tup = (HeapTuple)tableam_scan_getnexttuple((TableScanDesc)arg, ForwardScanDirection); - + //如果获取到了元组调用tableam_tops_copy_tuple函数对元组进行拷贝,并返回拷贝后的结果 + //否则,返回NULL表示没有获取到元组 return tup ? (HeapTuple)tableam_tops_copy_tuple(tup) : NULL; } - +/* + * 功能:用于获取数据库中的UHeapTuple元组 + * + * 参数列表: + * arg:函数指针 + */ typedef UHeapTuple (*TvUheapFetchTupleHook)(void *arg); static UHeapTuple TvUheapFetchTuple(void *arg) { return (UHeapTuple)tableam_scan_getnexttuple((TableScanDesc)arg, ForwardScanDirection); } - +/* + * 功能:用于向关系中批量插入元组,即批量插入操作 + * 参数: + * rel:要插入元组的关系。 + * estate:执行状态信息。 + * mycid:当前命令的 CommandId。 + * hiOptions:插入选项。 + * resultRelInfo:结果关系信息。 + * myslot:元组槽,用于临时存储元组。 + * bistate:批量插入状态信息。 + * nBufferedTuples:要插入的元组数量。 + * bufferedTuples:要插入的元组数组。 + */ static void TvBatchInsert(Relation rel, EState *estate, CommandId mycid, int hiOptions, ResultRelInfo *resultRelInfo, TupleTableSlot *myslot, BulkInsertState bistate, @@ -647,9 +734,9 @@ static void TvBatchInsert(Relation rel, EState *estate, CommandId mycid, int i; for (i = 0; i < nBufferedTuples; i++) { List *recheckIndexes = NULL; - + //将当前要插入的元组存储到元组槽中 (void)ExecStoreTuple(bufferedTuples[i], myslot, InvalidBuffer, false); - + //执行索引插入操作,返回需要重新检查的索引列表 recheckIndexes = ExecInsertIndexTuples(myslot, &(bufferedTuples[i]->t_self), estate, @@ -658,7 +745,7 @@ static void TvBatchInsert(Relation rel, EState *estate, CommandId mycid, InvalidBktId, NULL, NULL); - + //释放重新检查索引列表的内存 list_free(recheckIndexes); } } @@ -668,6 +755,14 @@ static void TvBatchInsert(Relation rel, EState *estate, CommandId mycid, const int MAX_BUFFERED_TUPLES_TCAP = 1000; const int MAX_BUFFERED_TUPLES_NUM_TCAP = 65535; +/* + * 功能:用于执行插入丢失元组操作的实现函数。 + * 参数: + * rel:要插入元组的关系。 + * snap:当前事务的快照。 + * fetchTupleHook:获取元组的钩子函数。 + * arg:钩子函数的参数。 + */ static void TvInsertLostImpl(Relation rel, Snapshot snap, TvFetchTupleHook fetchTupleHook, void *arg) { HeapTuple tuple; @@ -680,8 +775,8 @@ static void TvInsertLostImpl(Relation rel, Snapshot snap, TvFetchTupleHook fetch BulkInsertState bistate; int nBufferedTuples = 0; - HeapTuple *bufferedTuples = NULL; - Size bufferedTuplesSize = 0; + HeapTuple *bufferedTuples = NULL;//缓冲的元组数组 + Size bufferedTuplesSize = 0;//缓冲的元组总大小 /* * We need a ResultRelInfo so we can use the regular executor's @@ -734,8 +829,8 @@ static void TvInsertLostImpl(Relation rel, Snapshot snap, TvFetchTupleHook fetch ExecConstraints(resultRelInfo, slot, estate); } - bufferedTuples[nBufferedTuples++] = tuple; - bufferedTuplesSize += tuple->t_len; + bufferedTuples[nBufferedTuples++] = tuple;//将元组加入缓冲数组 + bufferedTuplesSize += tuple->t_len;//增加缓冲元组总大小 /* * If the buffer filled up, flush it. Also flush if the total @@ -747,32 +842,43 @@ static void TvInsertLostImpl(Relation rel, Snapshot snap, TvFetchTupleHook fetch TvBatchInsert(rel, estate, mycid, hiOptions, resultRelInfo, myslot, bistate, nBufferedTuples, bufferedTuples); - nBufferedTuples = 0; - bufferedTuplesSize = 0; + nBufferedTuples = 0;//重置缓冲元组数量 + bufferedTuplesSize = 0;//重置缓冲元组数量 } } /* Flush any remaining buffered tuples */ if (nBufferedTuples > 0) { + //如果仍有缓冲的元组,调用TvBatchInsert函数将它们一并插入 TvBatchInsert(rel, estate, mycid, hiOptions, resultRelInfo, myslot, bistate, nBufferedTuples, bufferedTuples); } - + //释放BulkInsertState对象的资源 FreeBulkInsertState(bistate); - + //切换回旧的内存上下文 MemoryContextSwitchTo(oldcontext); - + //重置执行器的元组表,释放其中的资源 ExecResetTupleTable(estate->es_tupleTable, false); - + //关闭所有的索引 ExecCloseIndices(resultRelInfo); - + //释放执行状态对象的资源 FreeExecutorState(estate); - + //释放缓冲元组数组的内存 pfree(bufferedTuples); + //释放ResultRelInfo对象的内存 pfree(resultRelInfo); return; } - +/* + * 功能:在UHeap表中批量插入丢失的元组。 + * 参数列表: + * rel:表示要插入的关系 + * partRel:表示分区关系 + * p:分区对象 + * snap:快照对象 + * fetchTupleHook:元组获取钩子函数 + * arg:元组获取钩子函数的参数 + */ static void TvUheapInsertLostImpl(Relation rel, Relation partRel, Partition p, Snapshot snap, TvUheapFetchTupleHook fetchTupleHook, void *arg) { @@ -780,7 +886,7 @@ static void TvUheapInsertLostImpl(Relation rel, Relation partRel, Partition p, ResultRelInfo *resultRelInfo; EState *estate = CreateExecutorState(); TupleTableSlot *myslot; - CommandId mycid = GetCurrentCommandId(true); + CommandId mycid = GetCurrentCommandId(true);//获取当前命令的ID /* * We need a ResultRelInfo so we can use the regular executor's * index-entry-making machinery. (There used to be a huge amount of code @@ -789,12 +895,12 @@ static void TvUheapInsertLostImpl(Relation rel, Relation partRel, Partition p, resultRelInfo = makeNode(ResultRelInfo); resultRelInfo->ri_RangeTableIndex = 1; /* dummy */ resultRelInfo->ri_RelationDesc = rel; - ExecOpenIndices(resultRelInfo, false); + ExecOpenIndices(resultRelInfo, false);//打开索引 estate->es_result_relations = resultRelInfo; estate->es_num_result_relations = 1; estate->es_result_relation_info = resultRelInfo; - Relation relRel = (partRel != NULL) ? partRel : rel; + Relation relRel = (partRel != NULL) ? partRel : rel;//根据分区关系是否有指定选择要插入的表 /* Set up a tuple slot too */ myslot = ExecInitExtraTupleSlot(estate, TAM_USTORE); ExecSetSlotDescriptor(myslot, RelationGetDescr(relRel)); @@ -819,7 +925,9 @@ static void TvUheapInsertLostImpl(Relation rel, Relation partRel, Partition p, UHeapInsert(relRel, tuple, mycid, NULL); List *recheckIndexes = NULL; + //执行索引插入操作 recheckIndexes = ExecInsertIndexTuples(myslot, &tuple->ctid, estate, partRel, p, InvalidBktId, NULL, NULL); + //如果表有更改记录日志表(mlog),则插入相应的更改日志 if (relRel != NULL && relRel->rd_mlogoid != InvalidOid) { HeapTuple htup = NULL; Assert(relRel->rd_tam_type == TAM_USTORE); @@ -829,24 +937,29 @@ static void TvUheapInsertLostImpl(Relation rel, Relation partRel, Partition p, } list_free(recheckIndexes); } - MemoryContextSwitchTo(oldcontext); + MemoryContextSwitchTo(oldcontext);//切换回之前的内存上下文 - ExecResetTupleTable(estate->es_tupleTable, false); + ExecResetTupleTable(estate->es_tupleTable, false);//重置执行器的元组表 - ExecCloseIndices(resultRelInfo); + ExecCloseIndices(resultRelInfo);//关闭所有的索引 /* free the fakeRelationCache */ if (estate->esfRelations != NULL) { FakeRelationCacheDestroy(estate->esfRelations); } - FreeExecutorState(estate); + FreeExecutorState(estate);//释放执行状态对象的资源 pfree(resultRelInfo); return; } - +/* + * 功能:在给定的关系(表)中插入丢失的元组。 + * 参数列表: + * relid:要插入元组的关系(表)的标识符 + * snap:快照对象 + */ void TvInsertLost(Oid relid, Snapshot snap) { Relation rel; @@ -864,10 +977,18 @@ void TvInsertLost(Oid relid, Snapshot snap) heap_close(rel, NoLock); return; } - +/* + * 功能:用于在给定的关系(表)或其分区中插入丢失的元组。 + * 参数列表: + * rel:关系(表)的描述 + * partRel:分区关系(表)的描述 + * p:分区对象 + * snap:快照对象 + */ void TvUheapInsertLostRel(Relation rel, Relation partRel, Partition p, Snapshot snap) { TableScanDesc sd; + //如果partRel为NULL,则在关系(表)上开始扫描,否则在分区关系(表)上开始扫描 if (partRel == NULL) { sd = tableam_scan_begin(rel, snap, 0, NULL); } else { @@ -878,7 +999,13 @@ void TvUheapInsertLostRel(Relation rel, Relation partRel, Partition p, Snapshot tableam_scan_end(sd); return; } - +/* + * 功能:用于在给定的分区表的所有分区中插入丢失的元组。 + * 参数列表: + * rel:关系(表)的描述 + * relid:元组的关系(表)的标识符 + * snap:快照对象 + */ void TvUheapInsertLostPart(Relation rel, Oid relid, Snapshot snap) { List* partTupleList = NIL; @@ -898,20 +1025,29 @@ void TvUheapInsertLostPart(Relation rel, Oid relid, Snapshot snap) Partition p = partitionOpen(rel, partOid, AccessExclusiveLock); Relation partRel = partitionGetRelation(rel, p); - if (RelationIsSubPartitioned(rel)) { + if (RelationIsSubPartitioned(rel)) {//如果分区表存在子分区 + //获取所有子分区元组列表 List* subPartTupleList = searchPgPartitionByParentId(PART_OBJ_TYPE_TABLE_SUB_PARTITION, partOid); ListCell* subPartCell = NULL; + //遍历每个子分区元组 foreach (subPartCell, subPartTupleList) { + //获取子分区元组和其标识符 HeapTuple subTup = (HeapTuple)lfirst(subPartCell); Oid subPartOid = HeapTupleGetOid(subTup); + //打开子分区并获取子分区的描述 Partition subPar = partitionOpen(partRel, subPartOid, AccessExclusiveLock); Relation subPartRel = partitionGetRelation(partRel, subPar); + //调用TvUheapInsertLostRel函数插入丢失的元组到子分区中 TvUheapInsertLostRel(rel, subPartRel, subPar, snap); + //释放子分区的关系描述 releaseDummyRelation(&subPartRel); + //关闭子分区 partitionClose(partRel, subPar, AccessExclusiveLock); } + //释放子分区元组列表的内存 freePartList(subPartTupleList); } else { + //调用TvUheapInsertLostRel函数插入丢失的元组到当前分区中 TvUheapInsertLostRel(rel, partRel, p, snap); } releaseDummyRelation(&partRel); @@ -922,18 +1058,41 @@ void TvUheapInsertLostPart(Relation rel, Oid relid, Snapshot snap) } +/* + * 功能:用于将历史数据插入 TimeCapsule 版本表 + * + * 参数列表: + * relid:要执行还原的关系(表)OID + * snap:用于读取历史数据的快照 + */ void TvUheapInsertLost(Oid relid, Snapshot snap) { + // 打开给定 OID 对应的关系表,不加任何锁 Relation rel = heap_open(relid, NoLock); + + // 检查关系是否是分区表 if (RELATION_IS_PARTITIONED(rel)) { + // 如果是分区表,则执行 TvUheapInsertLostPart 函数,为每个分区执行历史数据插入操作 TvUheapInsertLostPart(rel, relid, snap); } else { + // 如果不是分区表,则执行 TvUheapInsertLostRel 函数,执行历史数据插入操作 + // 此处的 NULL 参数表示没有分区关系和分区,仅针对整个关系进行操作 TvUheapInsertLostRel(rel, NULL, NULL, snap); } + + // 关闭打开的关系表,不加任何锁 heap_close(rel, NoLock); + + // 函数结束,返回 return; } +/* + * 功能:检查是否允许还原特定版本,并执行还原操作 + * + * 参数列表: + * rel:要还原版本的关系(表)对象 + */ static void TvCheckVersionRestore(Relation rel) { char *errstr = NULL; @@ -956,7 +1115,12 @@ static void TvCheckVersionRestore(Relation rel) return; } - +/* + * 功能:执行版本还原操作 + * + * 参数列表: + * stmt:时间胶囊语句 + */ void TvRestoreVersion(TimeCapsuleStmt *stmt) { Relation rel; @@ -1001,4 +1165,3 @@ void TvRestoreVersion(TimeCapsuleStmt *stmt) return; } - -- 2.34.1 From 8abe360784b9f3411813a451d5a79c9c2df3b269 Mon Sep 17 00:00:00 2001 From: LuckYzaw <3180017453@qq.com> Date: Sun, 27 Aug 2023 16:16:38 +0800 Subject: [PATCH 03/50] Update aiocompleter.cpp --- .../process/postmaster/aiocompleter.cpp | 1134 +++++++++++++++-- 1 file changed, 1033 insertions(+), 101 deletions(-) diff --git a/src/gausskernel/process/postmaster/aiocompleter.cpp b/src/gausskernel/process/postmaster/aiocompleter.cpp index 6d055f45e..69fca92ee 100644 --- a/src/gausskernel/process/postmaster/aiocompleter.cpp +++ b/src/gausskernel/process/postmaster/aiocompleter.cpp @@ -1,3 +1,15 @@ +/* + 下段注释描述了文件的版权信息以及对于AIO完成线程的基本介绍。它们用于处理异步IO请求,以及如何启动和停止这些线程。 + * aiocompleter.cpp文件的整体功能是用于AIO 完成器线程完成预取(Prefetch )和后端写入(BackWrite )I/O操作 + * 使用场景:AIO 完成器线程使用 Linux Native AIO 完成 AIO 请求。 + * 单个 AIO 完成器线程为与特定 AIO 上下文和 I/O 优先级相关联的 AIO 队列提供服务。 + * 1. 停止:只能在所有工作线程或后台数据写(bgwriter)线程都已停止后,AIO完成器线程才能停止。 + * + * 2. 启动:当postmaster启动子进程结束或是在执行归档恢复的开始,AIO完成器线程会立即启动,并且持续保持开启状态,直到postemaster下达终止命令 + * [1.正常终止:SIGTERM指令:指示线程等待任何挂起的 AIO 并做好退出准备。 + * 2.紧急终止:SIGQUIT指令:当AIO完成器线程意外退出,postmaster会将其视为“后端崩溃”状态,共享内存可能遭受损坏,此时通过SIGQUIT指令终止剩余的后端后启动恢复循环] + * + */ /* * Copyright (c) 2020 Huawei Technologies Co.,Ltd. * @@ -39,6 +51,7 @@ * * ------------------------------------------------------------------------- */ +/*引入所需的系统库和模块。*/ #include "postgres.h" #include "knl/knl_variable.h" #include "gssignal/gs_signal.h" @@ -52,6 +65,15 @@ #include "utils/memutils.h" #include +/* +解释了AIO completer线程的结构和数组。每个线程都有一个唯一的上下文,可能处理不同类型的请求。 +aioCompltrThread_t 结构对应于每个AIO completer线程,存储在 compltrArray 数组中,该数组包含 MAX_AIOCOMPLTR_THREADS 个槽位。 +compltrArray 数组在 postmaster 上下文中定义。 + +第一组参数 context、eventsp 和 tid 是在线程启动时设置的,它们对每个线程都是唯一的。 +cmpltrDesc 指针指向 compltrDescArray 中的 AioCompltrDesc_t 结构,其中包含了完成器的参数。同类型的完成器共享相同的描述符。 +compltrDescArray 数组中有 NUM_AIOCOMPLTR_TYPES 个 AioCompltrDesc_t 结构,每个结构代表一个 AioCompltrType。 +*/ /* * Each AIO completer thread has a unique context, and potentially * processes different types of requests. There is an aioCompltrThread_t @@ -69,46 +91,69 @@ * The compltrDescArray array is defined in the postmaster context. * */ -int CompltrReadReq(void* aioDesc, long res); -int CompltrWriteReq(void* aioDesc, long res); -int CompltrReadCUReq(void* aioDesc, long res); -int CompltrWriteCUReq(void* aioDesc, long res); +/* + 这里声明了一些函数原型,用于处理不同类型的AIO请求。 +*/ +int CompltrReadReq(void* aioDesc, long res);// 发起异步读取请求 +int CompltrWriteReq(void* aioDesc, long res);// 发起异步写入请求 +int CompltrReadCUReq(void* aioDesc, long res);// 发起异步读取CU(Control Unit,控制单元)请求 +int CompltrWriteCUReq(void* aioDesc, long res);// 发起异步写入CU请求 -ThreadId Compltrfork_exec(int compltrIdx); +ThreadId Compltrfork_exec(int compltrIdx);// 创建线程并执行异步I/O处理 /* * GUC parameters */ /* Maximum number of Completer threads -compile time define */ -#define MAX_AIOCOMPLTR_THREADS 4 +#define MAX_AIOCOMPLTR_THREADS 4 //在编译时定义的宏,用于设置异步I/O操作完成者线程池的最大线程数量。用于限制了可以同时运行的完成者线程的最大数量。 /* Number of Completer threads and the number of sets of Completers */ -const int AioCompltrThreads = 4; -int AioCompltrSets = 1; -const int AioCompltrShutdownTimeout = 1; +const int AioCompltrThreads = 4;// 整数常量,表示异步I/O操作完成者线程池中的线程数量。这里设置了4个完成者线程。 +int AioCompltrSets = 1; // 整数变量,表示完成者线程池的集数。此处设置只有一个线程池集。 +const int AioCompltrShutdownTimeout = 1;// 整数常量,表示异步I/O完成者线程池关闭的超时时间,此处设置为1秒。 /* Completer callback to handle the AIO event */ -typedef int (*AioCallback_t)(void*, long); +typedef int (*AioCallback_t)(void*, long);// 定义函数指针类型 AioCallback_t,用于表示异步I/O事件的Completer回调函数。 + // 指向一个参数为 (void*, long) 并返回 int 类型的函数 /* * Completer Thread definitions */ +/* +结构体类型 AioCompltrDesc,用于描述AIO完成器的特性、参数以及请求属性。 +它包含以下字段: + reqtype: AioCompltrType,表示完成器的类型,用于标识不同类型的AIO请求。 + callback: AioCallback_t,是一个函数指针,指向用于处理AIO事件的回调函数。 + maxevents: int,表示允许同时处理的最大AIO事件数量。 + min_nr: int,表示等待的最小事件数量,当达到此数量时,完成器可以开始处理这些事件。 + max_nr: int,表示最大可检索的事件数量,即一次从内核获取的最大事件数量。 + timeout: int,表示等待事件的最大时间,超过此时间后,完成器可以处理已获取的事件。 + reqprio: AioPriority,表示AIO请求的优先级。 +*/ typedef struct AioCompltrDesc { /* Completer characteristics */ - AioCompltrType reqtype; /* Completer type */ - AioCallback_t callback; /* Completer function */ - int maxevents; /* AIO Maximum events in progress */ + AioCompltrType reqtype; /* Completer type 表示完成器的类型,用于标识不同类型的AIO请求。*/ + AioCallback_t callback; /* Completer function 函数指针,指向用于处理AIO事件的回调函数*/ + int maxevents; /* AIO Maximum events in progress 表示允许同时处理的最大AIO事件数量*/ /* Completer parameters */ - int min_nr; /* Min number of events to wait for */ - int max_nr; /* Max number of events to retrieve */ - int timeout; /* Max time to wait */ + int min_nr; /* Min number of events to wait for 示等待的最小事件数量,当达到此数量时,完成器可以开始处理这些事件*/ + int max_nr; /* Max number of events to retrieve 表示最大可检索的事件数量,即一次从内核获取的最大事件数量*/ + int timeout; /* Max time to wait 表示等待事件的最大时间,超过此时间后,完成器可以处理已获取的事件*/ /* Request properties */ - AioPriority reqprio; /* Request priority served */ + AioPriority reqprio; /* Request priority served 表示AIO请求的优先级*/ } AioCompltrDesc_t; +/* + AioCompltrThread_t 结构体,用于表示异步I/O Completer线程的信息和状态。 + + context:io_context_t 表示异步I/O上下文,用于管理和跟踪异步I/O操作。 + eventsp:struct io_event* 指向 io_event 结构体的指针,表示异步I/O事件的指针数组。用于存储异步I/O操作的事件,以便后续处理。 + tid:ThreadId 表示异步I/O Completer线程的线程ID。用于标识和管理线程。 + compltrDescp:AioCompltrDesc_t* 指向 AioCompltrDesc_t 类型的指针,AioCompltrDesc_t用于描述AIO完成器的特性、参数以及请求属性 +*/ typedef struct { io_context_t context; /* AIO context */ struct io_event* eventsp; /* AIO events to process */ @@ -116,6 +161,8 @@ typedef struct { AioCompltrDesc_t* compltrDescp; /* Completer descriptor */ } AioCompltrThread_t; + + /* * The compltrDescArray contains the description of the different types * of completer threads. These are used to setup the context for each @@ -158,6 +205,20 @@ typedef struct { * policy employed. CFQ takes into account the priorities, but there is also a * wide gulf between the priority of sync and async i/o that dwarfs these. */ +/* +初始化compltrDescArray数组,用于配置不同类型的异步I/O Completer线程的参数。 + +- AioCompltrDesc_t:AioCompltrDesc结构体类型,用于描述AIO Completer的特性、参数以及请求属性。 +- NUM_AIOCOMPLTR_TYPES:常量,表示异步I/O Completer线程的类型数量。数组 compltrDescArray的大小 +- compltrDescArray[NUM_AIOCOMPLTR_TYPES]:包含了不同类型的异步I/O Completer线程的描述信息的数组。 +- `PageListPrefetchType`:页列表预取请求类型。该类型的异步I/O完成器线程处理后台异步读取数据页到内存的请求,以减少查询或操作时的延迟。 + +- `PageListBackWriteType`:页列表后台写入请求类型。该类型的异步I/O完成器线程处理将数据页从内存写回磁盘的请求,通常用于执行后台的数据页回写操作。 + +- `CUListPrefetchType`:CU(Column Unit)列表预取请求类型。类似于页列表预取,该类型的异步I/O完成器线程处理后台异步读取列存储单元(CU)到内存的请求,以优化查询性能。 + +- `CUListWriteType`:CU列表写入请求类型。与页列表后台写入类似,该类型的异步I/O完成器线程处理将修改后的列存储单元(CU)从内存写回磁盘的请求。 +*/ AioCompltrDesc_t compltrDescArray[NUM_AIOCOMPLTR_TYPES] = { /* reqtype, callback, maxevents, min_nr, max_nr, tsec, reqprio */ {PageListPrefetchType, CompltrReadReq, 65536, 1, 16384, 60, HighPri}, @@ -171,18 +232,25 @@ AioCompltrDesc_t compltrDescArray[NUM_AIOCOMPLTR_TYPES] = { * any AIO Completer theads. The Array contains one element for * each completer thread. */ -AioCompltrThread_t compltrArray[MAX_AIOCOMPLTR_THREADS]; + +AioCompltrThread_t compltrArray[MAX_AIOCOMPLTR_THREADS]; // MAX_AIOCOMPLTR_THREADS 4 /* * AioCompltrReady flag is set/cleared from the postmaster * context. It is used to remember the Completer state. + * 用于标记异步I/O完成者线程是否准备就绪 */ -static bool volatile AioCompltrReady = false; +static bool volatile AioCompltrReady = false;// 初始化false,表示异步I/O完成器线程尚未准备就绪。 -/* Associate a template with a thread index */ +/* Associate a template with a thread index +用于关联线程索引(threadIdx)与Completer描述数组 compltrDescArray 中的模板 +*/ #define AIOCOMPLTR_TEMPLATE(threadIdx) (&compltrDescArray[(threadIdx) % NUM_AIOCOMPLTR_TYPES]) -/* Determine a completer thread index to be used for a given type and h val */ +/* Determine a completer thread index to be used for a given type and h val +用于确定给定类型(typeIdx)和哈希值(h)的异步I/O Completer线程索引。 +通过计算出在 compltrArray 中的索引,以选择适当的Completer线程来处理特定类型的异步I/O请求。 +*/ #define AIOCOMPLTR_THREAD_IDX(typeIdx, h) ((((h) % AioCompltrSets) * NUM_AIOCOMPLTR_TYPES) + (typeIdx)) /* @@ -193,6 +261,9 @@ static bool volatile AioCompltrReady = false; * @Return: true start ok * @See also: */ +/*用于检查异步I/O完成者是否已经启动的函数。 + *返回布尔值:如果异步I/O Completers已准备就绪,则返回 true,否则返回 false +*/ bool AioCompltrIsReady(void) { return AioCompltrReady; @@ -204,6 +275,9 @@ bool AioCompltrIsReady(void) * @Return: function ptr * @See also: */ +/*用于获取特定请求类型的回调函数的函数。 + *接受一个参数 reqType,表示异步I/O completer类型; + *返回与该类型相关联的回调函数指针。*/ AioCallback_t ComptrCallback(AioCompltrType reqType) { return compltrDescArray[reqType].callback; @@ -215,6 +289,10 @@ AioCallback_t ComptrCallback(AioCompltrType reqType) * @Return: Request priority * @See also: */ +/*用于获取特定请求类型的优先级的函数。 + *接受一个参数 reqType,表示异步I/O completer类型 + *返回该类型请求的优先级。 + */ short CompltrPriority(AioCompltrType reqType) { return compltrDescArray[reqType].reqprio; @@ -228,6 +306,10 @@ short CompltrPriority(AioCompltrType reqType) * @Return:io_context * @See also: */ +/*用于获取特定请求类型的完成者上下文的函数。 + *接受两个参数,reqType 表示异步I/O completer类型,h 表示索引 + *根据类型和索引获取相应的completer上下文(io_context_t) + */ io_context_t CompltrContext(AioCompltrType reqType, int h) { return compltrArray[AIOCOMPLTR_THREAD_IDX(reqType, h)].context; @@ -237,9 +319,12 @@ io_context_t CompltrContext(AioCompltrType reqType, int h) /* * Signal handlers */ -static void CompltrConfig(SIGNAL_ARGS); -static void CompltrQuickDie(SIGNAL_ARGS); -static void CompltrShutdown(SIGNAL_ARGS); +/* +用于内部实现和处理特定的信号事件的私有函数原型声明,当信号事件发生时调用。 +*/ +static void CompltrConfig(SIGNAL_ARGS);// 用于配置异步I/O completer的相关参数和设置 +static void CompltrQuickDie(SIGNAL_ARGS);// 用于快速终止异步I/O completer线程。 +static void CompltrShutdown(SIGNAL_ARGS);// 用于在异步I/O completer线程退出时执行清理和关闭操作 /* * @Description: Compltrfork_exec() and AioCompltrStart() are used to start the @@ -260,9 +345,17 @@ static void CompltrShutdown(SIGNAL_ARGS); * @Return: thread id * @See also: */ +/* +用于启动异步I/O Completer线程。在当前的实现中,openGauss并不支持异步I/O,所以该函数只会发出错误消息表示不支持,并返回无效的线程ID。 + +该函数会格式化命令行参数列表(arglist),然后使用 fork 和 exec 函数创建一个新的线程来执行异步I/O Completer线程的处理。 +其中,compltrIdx 被转换为一个3字符的字符串,这样可以避免在代码中需要特殊处理这个参数,因为参数的传递在执行过程中已经被处理好了。 +*/ ThreadId Compltrfork_exec(int compltrIdx) { + // 发出错误消息,表示不支持异步I/O ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("openGauss current do not support AIO"))); + // 返回无效的线程ID return InvalidTid; } @@ -287,87 +380,101 @@ ThreadId Compltrfork_exec(int compltrIdx) */ int AioCompltrStart(void) { - int error = 0; - int try_times = 0; + int error = 0;// 错误码初始化 + int try_times = 0;// 尝试次数初始化 /* * Only allow MAX_AIOCOMPLTR_THREADS */ + //检查当前配置的 AIO Completer 线程数量是否超过了预定义的最大数量 MAX_AIOCOMPLTR_THREADS if (AioCompltrThreads > MAX_AIOCOMPLTR_THREADS) { - error = 1; - return error; + error = 1;// 设置错误码 + return error;// 返回错误码,中断函数执行 } - + // 调用安全内存函数memset_s 初始化 compltrArray,将内存设置为零 errno_t rc = memset_s(&compltrArray, sizeof(AioCompltrThread_t) * MAX_AIOCOMPLTR_THREADS, 0, sizeof(AioCompltrThread_t) * MAX_AIOCOMPLTR_THREADS); + //错误检查,用于确保 memset_s 函数是否执行成功。如果 rc(返回值)不为零,表示内存初始化出现了错误 securec_check(rc, "\0", "\0"); /* * Initialize the compltrArray */ + //初始化 compltrArray 数组 for (int i = 0; i < AioCompltrThreads; i++) { - try_times = 0; + try_times = 0;// 尝试次数归零 /* Assign a template to the thread descriptor */ - compltrArray[i].compltrDescp = AIOCOMPLTR_TEMPLATE(i); + compltrArray[i].compltrDescp = AIOCOMPLTR_TEMPLATE(i);// 为线程描述符分配对应的模板 /* Create the i/o queue and fill in the context */ do { + // 使用 io_setup 函数创建异步 I/O 队列,参数为当前线程的最大事件数和队列上下文。 error = io_setup(compltrArray[i].compltrDescp->maxevents, &compltrArray[i].context); + // 如果成功创建队列或者遇到的错误不是 EAGAIN(表示资源暂时不可用),则跳出循环。 if (error == 0 || error != -EAGAIN) { break; } - try_times++; + try_times++; // 尝试次数增加 + // 打印日志,记录当前尝试的线程ID、尝试次数和错误码。 ereport(LOG, (errmsg("AIO Startup, Completer thread id =%d try times=%d, error=%d", i, try_times, error))); - pg_usleep(100000L); - } while (try_times < 5); + pg_usleep(100000L);// 延迟 100,000 微秒(0.1 秒),然后继续下一次尝试。 + } while (try_times < 5);// 循环继续,直到成功创建队列或者尝试次数达到 5 次。 if (error != 0) { - goto AioCompltrStartError; + goto AioCompltrStartError;// 发生错误时跳转到错误处理标签 } /* Allocate the event array for the thread */ + /* 为线程分配事件数组的内存 用于存储事件数组 + compltrArray[i].compltrDescp->max_nr 表示事件数组的最大大小,sizeof(struct io_event) 表示单个事件的大小。*/ compltrArray[i].eventsp = (io_event*)malloc(compltrArray[i].compltrDescp->max_nr * sizeof(struct io_event)); - + // 检查内存分配是否成功,失败则进入if语句内部 if (compltrArray[i].eventsp == (struct io_event*)NULL) { /* malloc failed for some reason... */ - error = 2; + error = 2;// 设置错误码为 2,表示内存分配失败 + //打印日志,记录内存分配失败的详细信息,包括最大事件数和错误码。 ereport(LOG, (errmsg("AIO Startup malloc io_event failed: max_nr(%d), %d", compltrArray[i].compltrDescp->max_nr, error))); + // 跳转到错误处理标签 AioCompltrStartError,执行错误处理操作 goto AioCompltrStartError; } /* Start AIO Completer thread */ + //使用 Compltrfork_exec(i) 函数启动一个异步 I/O 完成线程,并将线程索引 i 作为参数传递给函数。函数返回线程的 ID。 compltrArray[i].tid = Compltrfork_exec(i); + // 检查线程是否成功启动。如果启动失败,会进入条件内部。 if (compltrArray[i].tid == (ThreadId)-1) { /* starting a thread failed */ - error = 3; + error = 3;// 设置错误码为 3,表示启动线程失败 + // 打印日志,记录启动线程失败的详细信息,包括错误码。 ereport(LOG, (errmsg("Start AIO Completer thread failed: %d", error))); - goto AioCompltrStartError; + goto AioCompltrStartError; // 发生错误时跳转到错误处理标签 } }; /* The AIO Completers are open for business */ - AioCompltrReady = true; + AioCompltrReady = true;// AIO Completers 准备就绪 /* successful return */ return 0; +//错误处理标签 AioCompltrStartError 标识错误处理的起始位置 AioCompltrStartError: /* * If anything went wrong, then stop any threads started * and deallocate the resources. */ - AioCompltrStop(SIGTERM); - ereport(LOG, (errmsg("AIO Startup Failed,error=%d", error))); + AioCompltrStop(SIGTERM);// 停止已启动的异步 I/O 完成线程并释放资源 + ereport(LOG, (errmsg("AIO Startup Failed,error=%d", error)));// 记录启动失败的错误日志 - return error; + return error;// 返回错误码 } /* @@ -380,39 +487,45 @@ AioCompltrStartError: void AioCompltrStop(int signal) { gs_thread_t thread; - AioCompltrReady = false; + AioCompltrReady = false;// 停止AIO Completer线程的标志设置为false,表示线程不再运行 /* * Stop the threads in the compltrArray. */ + // 循环遍历AIO Completer线程数组,停止所有线程 for (int i = 0; i < AioCompltrThreads; i++) { /* * Stop the threads that were started */ + // 检查线程标识符是否有效 if (compltrArray[i].tid != 0) { + // 如果线程已启动,使用gs_signal_send函数向线程发送信号停止线程,将传入的signal作为信号类型。 if (gs_signal_send(compltrArray[i].tid, signal) < 0) { + // 如果发送信号失败,记录错误日志,指明哪个线程的信号发送失败。 ereport(LOG, (errmsg("kill(%ld,%d) failed: %m", (long)(compltrArray[i].tid), signal))); } } } - + // 如果数据库正在崩溃,只需杀死completers并退出。 if (signal == SIGQUIT) { /* * if the database is crashing just kill the completers * and bail-out. */ - return; + return;// 直接返回,杀死completers并退出 } /* * Wait for the stopped threads to exit. */ + // 循环遍历AIO Completer线程数组,等待AIO Completer线程退出并进行清理 for (int i = 0; i < AioCompltrThreads; i++) { /* * Wait for the killed threads to exit */ - if (compltrArray[i].tid != 0) { - thread.thid = compltrArray[i].tid; + if (compltrArray[i].tid != 0) {// 检查线程标识符是否有效 + thread.thid = compltrArray[i].tid;// 保存线程标识符,用于等待线程退出 + // 使用gs_thread_join函数等待线程退出。如果返回值不为0,表示等待线程退出失败。 if (gs_thread_join(thread, NULL) != 0) { /* * If the thread does not exist, treat it as normal exit and we continue to @@ -420,12 +533,14 @@ void AioCompltrStop(int signal) * not know the current status of the thread and it's better to quit directly * which sames more safely. */ - if (ESRCH == pthread_kill(thread.thid, 0)) + if (ESRCH == pthread_kill(thread.thid, 0)) // 使用pthread_kill函数检查线程是否不存在 + // 如果线程不存在,记录错误日志 ereport(LOG, (errmsg("failed to join thread %lu, no such process", thread.thid))); else + // 如果线程存在但无法等待其退出,标记线程崩溃 HandleChildCrash(thread.thid, 1, "AIO process"); } - compltrArray[i].tid = (pid_t)0; + compltrArray[i].tid = (pid_t)0; // 清空线程标识符,表示线程已经退出 } } @@ -433,18 +548,20 @@ void AioCompltrStop(int signal) * Deallocate their context and event arrays, if any */ for (int i = 0; i < AioCompltrThreads; i++) { - compltrArray[i].compltrDescp = (AioCompltrDesc_t*)NULL; + compltrArray[i].compltrDescp = (AioCompltrDesc_t*)NULL;// // 清空线程描述符指针,防止悬空指针 /* destroy the AIO context */ + // 如果AIO上下文存在 if (compltrArray[i].context) { - io_destroy(compltrArray[i].context); - compltrArray[i].context = (io_context_t)NULL; + io_destroy(compltrArray[i].context);// 销毁AIO上下文,释放相关资源 + compltrArray[i].context = (io_context_t)NULL;// 清空AIO上下文指针,防止悬空指针 } /* Deallocate the events array */ + // 如果事件数组指针存在 if (compltrArray[i].eventsp) { - free(compltrArray[i].eventsp); - compltrArray[i].eventsp = (struct io_event*)NULL; + free(compltrArray[i].eventsp);// 释放事件数组的内存,防止内存泄漏 + compltrArray[i].eventsp = (struct io_event*)NULL;// 清空事件数组指针,防止悬空指针 } } @@ -460,29 +577,35 @@ void AioCompltrStop(int signal) */ void AioCompltrMain(int ac, char** av) { + // 检查命令行参数数量是否足够 if (ac < 4) { ereport(WARNING, (errmsg("invalid AIO argument num:%d", ac))); exit(1); } /* compltrIdx identifies this thread. */ + // 解析Completer线程的索引 int compltrIdx = atoi(av[3]); /* * Global thread local shortcuts to the completer descriptor * in the compltrArray, these are assigned on entry. */ - io_context_t context = compltrArray[compltrIdx].context; - io_event* eventsp = compltrArray[compltrIdx].eventsp; - AioCompltrDesc_t* compltrDescp = compltrArray[compltrIdx].compltrDescp; - int min_nr = compltrDescp->min_nr; - int max_nr = compltrDescp->max_nr; - struct timespec timeout; - struct timespec shutdown_timeout; - timeout.tv_sec = compltrDescp->timeout; - timeout.tv_nsec = 0; - shutdown_timeout.tv_sec = AioCompltrShutdownTimeout; - shutdown_timeout.tv_nsec = 0; - AioCallback_t callback = compltrDescp->callback; + /* + 使用本地变量提前获取对完成器描述符中相关信息的引用,以便后续代码中使用。从而减少冗余的操作,提高代码可读性和性能。 + 其中包括了AIO上下文、事件数组指针、最小/最大等待事件数量、超时设置和AIO回调函数等信息的获取。 + */ + io_context_t context = compltrArray[compltrIdx].context; // AIO上下文 + io_event* eventsp = compltrArray[compltrIdx].eventsp;// 事件数组指针 + AioCompltrDesc_t* compltrDescp = compltrArray[compltrIdx].compltrDescp;// 完成器描述符指针 + int min_nr = compltrDescp->min_nr;// 最小等待事件数量 + int max_nr = compltrDescp->max_nr; // 最大可检索的事件数量 + struct timespec timeout;// 事件等待超时 + struct timespec shutdown_timeout; // 关闭等待超时 + timeout.tv_sec = compltrDescp->timeout;// 设置等待事件超时的秒数 + timeout.tv_nsec = 0; // 设置等待事件超时的纳秒数 + shutdown_timeout.tv_sec = AioCompltrShutdownTimeout;// 设置关闭等待超时的秒数 + shutdown_timeout.tv_nsec = 0;// 设置关闭等待超时的纳秒数 + AioCallback_t callback = compltrDescp->callback;// 获取AIO回调函数的指针 /* * Handle signals the postmaster might send us @@ -490,27 +613,28 @@ void AioCompltrMain(int ac, char** av) * SIGQUIT causes immediate exit without cleanup. * SIGUSR1 is presently unused- reserved for future use. */ - (void)gspqsignal(SIGHUP, CompltrConfig); /* retrieve config */ - (void)gspqsignal(SIGINT, SIG_IGN); - (void)gspqsignal(SIGTERM, CompltrShutdown); /* shutdown */ - (void)gspqsignal(SIGQUIT, CompltrQuickDie); /* hard crash time */ - (void)gspqsignal(SIGALRM, SIG_IGN); - (void)gspqsignal(SIGPIPE, SIG_IGN); - (void)gspqsignal(SIGUSR1, SIG_IGN); /* reserved */ - (void)gspqsignal(SIGUSR2, SIG_IGN); + //为异步I/O Completer线程设置信号处理方式,确保线程能够适当地响应不同的信号 + (void)gspqsignal(SIGHUP, CompltrConfig); /* retrieve config */ //SIGHUP 信号将触发 CompltrConfig 函数,用于重新加载配置。 + (void)gspqsignal(SIGINT, SIG_IGN);// SIGINT 信号被忽略,不会中断线程执行。 + (void)gspqsignal(SIGTERM, CompltrShutdown); /* shutdown */ //SIGTERM 信号将触发 CompltrShutdown 函数,用于进行线程关闭和清理。 + (void)gspqsignal(SIGQUIT, CompltrQuickDie); /* hard crash time */ // SIGQUIT 信号将触发 CompltrQuickDie 函数,用于快速终止线程执行。 + (void)gspqsignal(SIGALRM, SIG_IGN);// SIGALRM 信号被忽略。 + (void)gspqsignal(SIGPIPE, SIG_IGN);// SIGPIPE 信号被忽略。 + (void)gspqsignal(SIGUSR1, SIG_IGN); /* reserved */ //SIGUSR1 信号都被忽略。 + (void)gspqsignal(SIGUSR2, SIG_IGN);// SIGUSR2 信号被忽略。 /* * Reset some signals that are accepted by postmaster but we don't * need. */ - (void)gspqsignal(SIGCHLD, SIG_DFL); - (void)gspqsignal(SIGTTIN, SIG_DFL); - (void)gspqsignal(SIGTTOU, SIG_DFL); - (void)gspqsignal(SIGCONT, SIG_DFL); - (void)gspqsignal(SIGWINCH, SIG_DFL); + (void)gspqsignal(SIGCHLD, SIG_DFL);// 恢复默认处理 SIGCHLD 信号 + (void)gspqsignal(SIGTTIN, SIG_DFL);// 恢复默认处理 SIGTTIN 信号 + (void)gspqsignal(SIGTTOU, SIG_DFL); // 恢复默认处理 SIGTTOU 信号 + (void)gspqsignal(SIGCONT, SIG_DFL);// 恢复默认处理 SIGCONT 信号 + (void)gspqsignal(SIGWINCH, SIG_DFL);// 恢复默认处理 SIGWINCH 信号 /* We allow SIGQUIT (quickdie) at all times */ - sigdelset(&t_thrd.libpq_cxt.BlockSig, SIGQUIT); + sigdelset(&t_thrd.libpq_cxt.BlockSig, SIGQUIT);// 允许 SIGQUIT (quickdie) 信号随时被接收 /* Create a resource owner to keep track of our resources (buffer * pins etc... @@ -524,20 +648,22 @@ void AioCompltrMain(int ac, char** av) /* * Unblock signals (blocked when postmaster forked us) */ - gs_signal_setmask(&t_thrd.libpq_cxt.UnBlockSig, NULL); - (void)gs_signal_unblock_sigusr2(); + gs_signal_setmask(&t_thrd.libpq_cxt.UnBlockSig, NULL);// 解除之前在主进程中阻塞的信号 + (void)gs_signal_unblock_sigusr2();// 解除对 SIGUSR2 信号的阻塞 /* Announce that the Completer has been started */ - ereport(LOG, (errmsg("AIO Completer %d STARTED.", compltrIdx))); + ereport(LOG, (errmsg("AIO Completer %d STARTED.", compltrIdx)));// 在日志中记录异步I/O Completer线程已经启动 for (;;) { - int eventsReceived; + int eventsReceived;//存储从 io_getevents 函数中获取的已完成的异步I/O事件数量或错误代码 /* * Reload configuration -if requested. */ + // 如果有重新加载配置的请求,进行相应的处理,但在此代码段中被禁用了 if (t_thrd.aio_cxt.config_requested) { /* disabled config request, not time for this now. ProcessConfigFile PGC_SIGHUP; */ + // 如果配置重载请求被设置,这段代码会禁用配置请求并略过后续的处理,以避免在当前上下文中重新加载配置文件 t_thrd.aio_cxt.config_requested = false; } @@ -548,11 +674,12 @@ void AioCompltrMain(int ac, char** av) * allow the thread to exit quickly when its time comes. * Once shutdown is requested, there is no going back. */ + // 如果收到了关闭请求 if (t_thrd.aio_cxt.shutdown_requested) { - timeout = shutdown_timeout; + timeout = shutdown_timeout;// 设置超时时间为shutdown_timeout,以便快速退出线程 - ereport(LOG, (errmsg("AIO Completer %d EXITED.", compltrIdx))); - proc_exit(0); + ereport(LOG, (errmsg("AIO Completer %d EXITED.", compltrIdx)));// 记录日志,表示AIO Completer正在退出 + proc_exit(0);// 退出线程 } /* @@ -560,12 +687,15 @@ void AioCompltrMain(int ac, char** av) * on the given context. Retry if the syscall is * interrupted. */ + // 等待一些AIO请求完成在给定的上下文中,最小等待事件数量为min_nr,最大为max_nr,最长等待时间由timeout指定 + // 返回值是一个整数,表示成功获取的事件数量。如果返回值为负数,表示发生了错误, eventsReceived = io_getevents(context, min_nr, max_nr, eventsp, &timeout); /* * If io_getevents() got interrupted, * take the opportunity to check for pending requests. * Then restart the io_getevents() call. */ + // 如果 io_getevents() 被中断,重新尝试获取事件 if (eventsReceived == -EINTR) { continue; } @@ -573,11 +703,13 @@ void AioCompltrMain(int ac, char** av) /* * io_getevents() reports errors as negative values. */ + // io_getevents() 报告错误的返回值为负数 if (eventsReceived < 0) { /* Report error */ + // 记录错误日志,发出 PANIC 级别的错误报告 ereport(PANIC, (errmsg("AIO Completer io_getevents() failed: error %d .", eventsReceived))); } - + // 断言,确保 eventsReceived 不超过最大数量 Assert(eventsReceived <= max_nr); /* @@ -585,22 +717,26 @@ void AioCompltrMain(int ac, char** av) * We expect 0 to max_nr requests. The obj here is * the I/O request and the db context. */ + // 逐个遍历、处理异步I/O事件数组中的每个事件,执行特定操作或回调函数。 for (struct io_event* eventp = eventsp; eventsReceived--; eventp++) { - callback((void*)eventp->obj, eventp->res); + callback((void*)eventp->obj, eventp->res);// 调用回调函数,传入 I/O 请求的 obj 和结果 } } + // 所有事件处理完成后,线程即将退出 + // 记录线程退出的日志信息 ereport(LOG, (errmsg("AIO Completer %d EXITED.", compltrIdx))); - exit(0); + exit(0);// 退出线程,返回状态码 0 } /* * @Description: signal handler routines for config,not used now * @See also: */ +// 信号处理程序,用于处理配置请求,目前未使用 static void CompltrConfig(SIGNAL_ARGS) { - t_thrd.aio_cxt.config_requested = true; + t_thrd.aio_cxt.config_requested = true;// 标记配置请求为true,表示配置请求已经发生 } /* @@ -609,31 +745,33 @@ static void CompltrConfig(SIGNAL_ARGS) * so we need to stop what we're doing and exit. * @See also: */ +// 快速退出的信号处理程序,当postmaster通过SIGQUIT信号发出终止请求时触发 static void CompltrQuickDie(SIGNAL_ARGS) { - PG_SETMASK(&t_thrd.libpq_cxt.BlockSig); + PG_SETMASK(&t_thrd.libpq_cxt.BlockSig);// 阻塞所有信号 /* * We DO NOT want to run proc_exit() callbacks -- we're here because * shared memory may be corrupted. Like the other postmaster * children, ...Just nail the windows shut and get out of town.... */ - on_exit_reset(); + on_exit_reset();// 重置进程退出回调 /* * Note we do exit(2) not exit(0)... * ...just like the other postmaster children. */ - exit(2); + exit(2);// 以退出码2退出,类似其他主控进程子进程的做法 } /* * @Description: CompltrShutdown() occurs when signalled SIGTERM by the postmaster. * @See also: */ +// 关闭信号处理程序,当postmaster通过SIGTERM信号发出关闭请求时触发 static void CompltrShutdown(SIGNAL_ARGS) { - t_thrd.aio_cxt.shutdown_requested = true; + t_thrd.aio_cxt.shutdown_requested = true;// 设置异步I/O Completer线程的关闭请求标志 } /** @@ -641,12 +779,806 @@ static void CompltrShutdown(SIGNAL_ARGS) * @in void * @return void */ +// 初始化异步I/O的资源 void AioResourceInitialize(void) { + // 创建一个共享内存上下文用于异步I/O资源,以便进行内存分配和管理 AdioSharedContext = AllocSetContextCreate((MemoryContext)g_instance.instance_context, - "AdioSharedMemory", - ALLOCSET_DEFAULT_MINSIZE, - ALLOCSET_DEFAULT_INITSIZE, - ALLOCSET_DEFAULT_MAXSIZE, - SHARED_CONTEXT); + "AdioSharedMemory",// 设置上下文的名称,用于标识和识别此上下文。 + ALLOCSET_DEFAULT_MINSIZE,//设置上下文的初始最小内存分配大小 + ALLOCSET_DEFAULT_INITSIZE,//设置上下文的初始内存池大小 + ALLOCSET_DEFAULT_MAXSIZE,// 设置上下文的最大内存池大小 + SHARED_CONTEXT);// 设置上下文的标志,指示它是一个共享上下文 +} +/* + 下段注释描述了文件的版权信息以及对于AIO完成线程的基本介绍。它们用于处理异步IO请求,以及如何启动和停止这些线程。 + * aiocompleter.cpp文件的整体功能是用于AIO 完成器线程完成预取(Prefetch )和后端写入(BackWrite )I/O操作 + * 使用场景:AIO 完成器线程使用 Linux Native AIO 完成 AIO 请求。 + * 单个 AIO 完成器线程为与特定 AIO 上下文和 I/O 优先级相关联的 AIO 队列提供服务。 + * 1. 停止:只能在所有工作线程或后台数据写(bgwriter)线程都已停止后,AIO完成器线程才能停止。 + * + * 2. 启动:当postmaster启动子进程结束或是在执行归档恢复的开始,AIO完成器线程会立即启动,并且持续保持开启状态,直到postemaster下达终止命令 + * [1.正常终止:SIGTERM指令:指示线程等待任何挂起的 AIO 并做好退出准备。 + * 2.紧急终止:SIGQUIT指令:当AIO完成器线程意外退出,postmaster会将其视为“后端崩溃”状态,共享内存可能遭受损坏,此时通过SIGQUIT指令终止剩余的后端后启动恢复循环] + * + */ +/* + * 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. + * ------------------------------------------------------------------------- + * + * aiocompleter.cpp + * + * The AIO completer threads complete Prefetch and BackWrite I/O so they + * may only be stopped after all the worker threads or bgwrite threads have + * been stopped. + * + * The aiocompleter threads complete AIO requests using Linux Native AIO. + * A single AIO completer thread serves on AIO queue associated with a + * specific AIO context and I/O priority. + * + * The AIO completer threads are started by the postmaster as soon as the + * startup subprocess finishes, or as soon as recovery begins if we are + * doing archive recovery. They remain alive until the postmaster commands + * them to terminate. Normal termination is by SIGTERM, which instructs the + * threads to wait for any pending AIO and be prepared to exit. + * via exit(0). Emergency termination is by SIGQUIT. + * + * If completer thread exits unexpectedly, the postmaster treats that the same + * as a backend crash: shared memory may be corrupted, so remaining backends + * should be killed by SIGQUIT and then a recovery cycle started. + * + * IDENTIFICATION + * src/gausskernel/process/postmaster/aiocompleter.cpp + * + * ------------------------------------------------------------------------- + */ +/*引入所需的系统库和模块。*/ +#include "postgres.h" +#include "knl/knl_variable.h" +#include "gssignal/gs_signal.h" +#include "libpq/pqsignal.h" +#include "postmaster/aiocompleter.h" +#include "postmaster/postmaster.h" +#include "storage/smgr/fd.h" +#include "storage/ipc.h" +#include "storage/pmsignal.h" +#include "utils/guc.h" +#include "utils/memutils.h" +#include + +/* +解释了AIO completer线程的结构和数组。每个线程都有一个唯一的上下文,可能处理不同类型的请求。 +aioCompltrThread_t 结构对应于每个AIO completer线程,存储在 compltrArray 数组中,该数组包含 MAX_AIOCOMPLTR_THREADS 个槽位。 +compltrArray 数组在 postmaster 上下文中定义。 + +第一组参数 context、eventsp 和 tid 是在线程启动时设置的,它们对每个线程都是唯一的。 +cmpltrDesc 指针指向 compltrDescArray 中的 AioCompltrDesc_t 结构,其中包含了完成器的参数。同类型的完成器共享相同的描述符。 +compltrDescArray 数组中有 NUM_AIOCOMPLTR_TYPES 个 AioCompltrDesc_t 结构,每个结构代表一个 AioCompltrType。 +*/ +/* + * Each AIO completer thread has a unique context, and potentially + * processes different types of requests. There is an aioCompltrThread_t + * structure for each completer thread, located in the compltrArray. + * The compltrArray contains MAX_AIOCOMPLTR_THREADS slots. + * The compltrArray is defined in the postmaster context. + * + * The first three parameters context, eventsp and tid + * are set when the thread is started, they are unique to each thread. + * The cmpltrDesc pointer points to a AioCompltrDesc_t structure in the + * compltrDescArray containing the parameters for the completer. + * Completers of the same type use the same descriptor. + * The compltrDescArray has NUM_AIOCOMPLTR_TYPES AioCompltrDesc_t structures + * one for each AioCompltrType. + * The compltrDescArray array is defined in the postmaster context. + * + */ +/* + 这里声明了一些函数原型,用于处理不同类型的AIO请求。 +*/ +int CompltrReadReq(void* aioDesc, long res);// 发起异步读取请求 +int CompltrWriteReq(void* aioDesc, long res);// 发起异步写入请求 +int CompltrReadCUReq(void* aioDesc, long res);// 发起异步读取CU(Control Unit,控制单元)请求 +int CompltrWriteCUReq(void* aioDesc, long res);// 发起异步写入CU请求 + +ThreadId Compltrfork_exec(int compltrIdx);// 创建线程并执行异步I/O处理 + +/* + * GUC parameters + */ +/* Maximum number of Completer threads -compile time define */ +#define MAX_AIOCOMPLTR_THREADS 4 //在编译时定义的宏,用于设置异步I/O操作完成者线程池的最大线程数量。用于限制了可以同时运行的完成者线程的最大数量。 + +/* Number of Completer threads and the number of sets of Completers */ +const int AioCompltrThreads = 4;// 整数常量,表示异步I/O操作完成者线程池中的线程数量。这里设置了4个完成者线程。 +int AioCompltrSets = 1; // 整数变量,表示完成者线程池的集数。此处设置只有一个线程池集。 +const int AioCompltrShutdownTimeout = 1;// 整数常量,表示异步I/O完成者线程池关闭的超时时间,此处设置为1秒。 + +/* Completer callback to handle the AIO event */ +typedef int (*AioCallback_t)(void*, long);// 定义函数指针类型 AioCallback_t,用于表示异步I/O事件的Completer回调函数。 + // 指向一个参数为 (void*, long) 并返回 int 类型的函数 + +/* + * Completer Thread definitions + */ +/* +结构体类型 AioCompltrDesc,用于描述AIO完成器的特性、参数以及请求属性。 +它包含以下字段: + reqtype: AioCompltrType,表示完成器的类型,用于标识不同类型的AIO请求。 + callback: AioCallback_t,是一个函数指针,指向用于处理AIO事件的回调函数。 + maxevents: int,表示允许同时处理的最大AIO事件数量。 + min_nr: int,表示等待的最小事件数量,当达到此数量时,完成器可以开始处理这些事件。 + max_nr: int,表示最大可检索的事件数量,即一次从内核获取的最大事件数量。 + timeout: int,表示等待事件的最大时间,超过此时间后,完成器可以处理已获取的事件。 + reqprio: AioPriority,表示AIO请求的优先级。 +*/ +typedef struct AioCompltrDesc { + /* Completer characteristics */ + AioCompltrType reqtype; /* Completer type 表示完成器的类型,用于标识不同类型的AIO请求。*/ + AioCallback_t callback; /* Completer function 函数指针,指向用于处理AIO事件的回调函数*/ + int maxevents; /* AIO Maximum events in progress 表示允许同时处理的最大AIO事件数量*/ + + /* Completer parameters */ + int min_nr; /* Min number of events to wait for 示等待的最小事件数量,当达到此数量时,完成器可以开始处理这些事件*/ + int max_nr; /* Max number of events to retrieve 表示最大可检索的事件数量,即一次从内核获取的最大事件数量*/ + int timeout; /* Max time to wait 表示等待事件的最大时间,超过此时间后,完成器可以处理已获取的事件*/ + + /* Request properties */ + AioPriority reqprio; /* Request priority served 表示AIO请求的优先级*/ + +} AioCompltrDesc_t; + +/* + AioCompltrThread_t 结构体,用于表示异步I/O Completer线程的信息和状态。 + + context:io_context_t 表示异步I/O上下文,用于管理和跟踪异步I/O操作。 + eventsp:struct io_event* 指向 io_event 结构体的指针,表示异步I/O事件的指针数组。用于存储异步I/O操作的事件,以便后续处理。 + tid:ThreadId 表示异步I/O Completer线程的线程ID。用于标识和管理线程。 + compltrDescp:AioCompltrDesc_t* 指向 AioCompltrDesc_t 类型的指针,AioCompltrDesc_t用于描述AIO完成器的特性、参数以及请求属性 +*/ +typedef struct { + io_context_t context; /* AIO context */ + struct io_event* eventsp; /* AIO events to process */ + ThreadId tid; /* AIO thread tid */ + AioCompltrDesc_t* compltrDescp; /* Completer descriptor */ +} AioCompltrThread_t; + + + +/* + * The compltrDescArray contains the description of the different types + * of completer threads. These are used to setup the context for each + * completer thread. Each type could potentially has a unique set + * of parameters. + * + * For now, only the completer function and priority varies. + * The rest of the values are the same for all the threads: + * + * reqtype and callback + * The reqtype is the type of async I/O request. The + * callback is the function used to process the request. + * The completers are configured to each handle a different type + * of request. The type of request dictates the callback used. + * At least that is the case now, this could change in the future. + * + * maxevents + * The number of requests in the queue for the context is limited to 64K here. + * So in total, the 4 threads require 64k * 4 io descriptors. + * Typically the system io descriptor maximum is 64k. + * Verify that there are sufficient available by checking aio-max-nr. + * Compare /proc/sys/fs/aio-nr against /proc/sys/fs/aio-max-nr to determine + * whether the limit is too small. It is advisable to set fs.aio-max-nr to + * 1048576 (1m) in /etc/sysctl.conf. + * + * min_nr, max_nr and timeout + * The minimum number of requests to return is set to 1, this + * makes io_getevents a blocking call that sleeps until one i/o is available. + * The maximum number of requests in the queue is arbitrarily set + * to here, but it could be as large as the entire queue. + * The maximum wait is set to 10 minutes, but any nonzero value will allow + * io_getevents() to enter an interruptable sleep (Returning -EINTR + * when a signal arrives). + * + * priority + * The priority values are intended to give specific Page List prefetchs the + * highest priority, while giving Range prefetch and Range write-back a + * medium priority, and the periodic write-back the lowest priority. + * The effectiveness of these relative settings depends upon the i/o scheduling + * policy employed. CFQ takes into account the priorities, but there is also a + * wide gulf between the priority of sync and async i/o that dwarfs these. + */ +/* +初始化compltrDescArray数组,用于配置不同类型的异步I/O Completer线程的参数。 + +- AioCompltrDesc_t:AioCompltrDesc结构体类型,用于描述AIO Completer的特性、参数以及请求属性。 +- NUM_AIOCOMPLTR_TYPES:常量,表示异步I/O Completer线程的类型数量。数组 compltrDescArray的大小 +- compltrDescArray[NUM_AIOCOMPLTR_TYPES]:包含了不同类型的异步I/O Completer线程的描述信息的数组。 +- `PageListPrefetchType`:页列表预取请求类型。该类型的异步I/O完成器线程处理后台异步读取数据页到内存的请求,以减少查询或操作时的延迟。 + +- `PageListBackWriteType`:页列表后台写入请求类型。该类型的异步I/O完成器线程处理将数据页从内存写回磁盘的请求,通常用于执行后台的数据页回写操作。 + +- `CUListPrefetchType`:CU(Column Unit)列表预取请求类型。类似于页列表预取,该类型的异步I/O完成器线程处理后台异步读取列存储单元(CU)到内存的请求,以优化查询性能。 + +- `CUListWriteType`:CU列表写入请求类型。与页列表后台写入类似,该类型的异步I/O完成器线程处理将修改后的列存储单元(CU)从内存写回磁盘的请求。 +*/ +AioCompltrDesc_t compltrDescArray[NUM_AIOCOMPLTR_TYPES] = { + /* reqtype, callback, maxevents, min_nr, max_nr, tsec, reqprio */ + {PageListPrefetchType, CompltrReadReq, 65536, 1, 16384, 60, HighPri}, + {PageListBackWriteType, CompltrWriteReq, 65536, 1, 16384, 60, HighPri}, + {CUListPrefetchType, CompltrReadCUReq, 65536, 1, 16384, 60, HighPri}, + {CUListWriteType, CompltrWriteCUReq, 65536, 1, 16384, 60, HighPri}}; + +/* + * AIO Completer Array defines the AIO completer threads, it is + * initialized by the postmaster using CompltrAioInit prior to starting + * any AIO Completer theads. The Array contains one element for + * each completer thread. + */ + +AioCompltrThread_t compltrArray[MAX_AIOCOMPLTR_THREADS]; // MAX_AIOCOMPLTR_THREADS 4 + +/* + * AioCompltrReady flag is set/cleared from the postmaster + * context. It is used to remember the Completer state. + * 用于标记异步I/O完成者线程是否准备就绪 + */ +static bool volatile AioCompltrReady = false;// 初始化false,表示异步I/O完成器线程尚未准备就绪。 + +/* Associate a template with a thread index +用于关联线程索引(threadIdx)与Completer描述数组 compltrDescArray 中的模板 +*/ +#define AIOCOMPLTR_TEMPLATE(threadIdx) (&compltrDescArray[(threadIdx) % NUM_AIOCOMPLTR_TYPES]) + +/* Determine a completer thread index to be used for a given type and h val +用于确定给定类型(typeIdx)和哈希值(h)的异步I/O Completer线程索引。 +通过计算出在 compltrArray 中的索引,以选择适当的Completer线程来处理特定类型的异步I/O请求。 +*/ +#define AIOCOMPLTR_THREAD_IDX(typeIdx, h) ((((h) % AioCompltrSets) * NUM_AIOCOMPLTR_TYPES) + (typeIdx)) + +/* + * Exported functions + */ +/* + * @Description: Check whether the Completers have been started + * @Return: true start ok + * @See also: + */ +/*用于检查异步I/O完成者是否已经启动的函数。 + *返回布尔值:如果异步I/O Completers已准备就绪,则返回 true,否则返回 false +*/ +bool AioCompltrIsReady(void) +{ + return AioCompltrReady; +} + +/* + * @Description: Obtain callback for request type + * @Param[IN] reqType: aio completer type + * @Return: function ptr + * @See also: + */ +/*用于获取特定请求类型的回调函数的函数。 + *接受一个参数 reqType,表示异步I/O completer类型; + *返回与该类型相关联的回调函数指针。*/ +AioCallback_t ComptrCallback(AioCompltrType reqType) +{ + return compltrDescArray[reqType].callback; +} + +/* + * @Description: Obtain the priority for the request type + * @Param[IN] reqType: aio completer type + * @Return: Request priority + * @See also: + */ +/*用于获取特定请求类型的优先级的函数。 + *接受一个参数 reqType,表示异步I/O completer类型 + *返回该类型请求的优先级。 + */ +short CompltrPriority(AioCompltrType reqType) +{ + return compltrDescArray[reqType].reqprio; +} + +/* + * @Description: Obtain the completer context for the i/o request, Must consult the AioCompltrArray + * to find the specific thread to get its context. + * @Param[IN] h: index + * @Param[IN] reqType: aio completer type + * @Return:io_context + * @See also: + */ +/*用于获取特定请求类型的完成者上下文的函数。 + *接受两个参数,reqType 表示异步I/O completer类型,h 表示索引 + *根据类型和索引获取相应的completer上下文(io_context_t) + */ +io_context_t CompltrContext(AioCompltrType reqType, int h) +{ + return compltrArray[AIOCOMPLTR_THREAD_IDX(reqType, h)].context; +} + +/* Prototypes for private functions */ +/* + * Signal handlers + */ +/* +用于内部实现和处理特定的信号事件的私有函数原型声明,当信号事件发生时调用。 +*/ +static void CompltrConfig(SIGNAL_ARGS);// 用于配置异步I/O completer的相关参数和设置 +static void CompltrQuickDie(SIGNAL_ARGS);// 用于快速终止异步I/O completer线程。 +static void CompltrShutdown(SIGNAL_ARGS);// 用于在异步I/O completer线程退出时执行清理和关闭操作 + +/* + * @Description: Compltrfork_exec() and AioCompltrStart() are used to start the + * completer threads. + * + * PG was designed to start processes and pass parameters on the + * command line and via shared memory. We do not need to do + * that with our threads implementation, but rather than change all that + * now we are going to pass the compltrIdx on the command line + * and allow the running thread to find its descriptor in the compltrArray + * in the global context. + * + * Compltrfork_exec formats the arglist, then fork and exec the AIO + * Completer thread. The compltrIdx is converted to a 3 character string, + * so that the parameter does not have to be handled specially by the + * intervening PG code. + * @Param[IN] compltrIdx: aio thread index + * @Return: thread id + * @See also: + */ +/* +用于启动异步I/O Completer线程。在当前的实现中,openGauss并不支持异步I/O,所以该函数只会发出错误消息表示不支持,并返回无效的线程ID。 + +该函数会格式化命令行参数列表(arglist),然后使用 fork 和 exec 函数创建一个新的线程来执行异步I/O Completer线程的处理。 +其中,compltrIdx 被转换为一个3字符的字符串,这样可以避免在代码中需要特殊处理这个参数,因为参数的传递在执行过程中已经被处理好了。 +*/ +ThreadId Compltrfork_exec(int compltrIdx) +{ + // 发出错误消息,表示不支持异步I/O + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("openGauss current do not support AIO"))); + // 返回无效的线程ID + return InvalidTid; +} + +/* + * @Description: AioCompltrStart + * Set-up the Aio Completer thread descriptors and start the threads. + * This function is invoked in the global context by the postmaster + * to start all the Aio Completer Threads. + * + * Globals + * The compltrArray and AioCompltrThreads + * are globals that control the allocation and configuration of the + * AIO completer threads. + * + * AioCompltrThreads is the number of AIO completer threads. + * Each Completer thread has a unique AioCompltrThread_t context in the + * compltrArray. Each Completer refers to a compltrDescp that + * contains the parameters for the type of request the Completer servers. + * Each Completer context also contains an AIO context and event array. + * @Return: 0 --success; error --failed + * @See also: + */ +int AioCompltrStart(void) +{ + int error = 0;// 错误码初始化 + int try_times = 0;// 尝试次数初始化 + + /* + * Only allow MAX_AIOCOMPLTR_THREADS + */ + //检查当前配置的 AIO Completer 线程数量是否超过了预定义的最大数量 MAX_AIOCOMPLTR_THREADS + if (AioCompltrThreads > MAX_AIOCOMPLTR_THREADS) { + error = 1;// 设置错误码 + return error;// 返回错误码,中断函数执行 + } + // 调用安全内存函数memset_s 初始化 compltrArray,将内存设置为零 + errno_t rc = memset_s(&compltrArray, + sizeof(AioCompltrThread_t) * MAX_AIOCOMPLTR_THREADS, + 0, + sizeof(AioCompltrThread_t) * MAX_AIOCOMPLTR_THREADS); + //错误检查,用于确保 memset_s 函数是否执行成功。如果 rc(返回值)不为零,表示内存初始化出现了错误 + securec_check(rc, "\0", "\0"); + + /* + * Initialize the compltrArray + */ + //初始化 compltrArray 数组 + for (int i = 0; i < AioCompltrThreads; i++) { + try_times = 0;// 尝试次数归零 + /* Assign a template to the thread descriptor */ + compltrArray[i].compltrDescp = AIOCOMPLTR_TEMPLATE(i);// 为线程描述符分配对应的模板 + + /* Create the i/o queue and fill in the context */ + do { + // 使用 io_setup 函数创建异步 I/O 队列,参数为当前线程的最大事件数和队列上下文。 + error = io_setup(compltrArray[i].compltrDescp->maxevents, &compltrArray[i].context); + // 如果成功创建队列或者遇到的错误不是 EAGAIN(表示资源暂时不可用),则跳出循环。 + if (error == 0 || error != -EAGAIN) { + break; + } + + try_times++; // 尝试次数增加 + // 打印日志,记录当前尝试的线程ID、尝试次数和错误码。 + ereport(LOG, (errmsg("AIO Startup, Completer thread id =%d try times=%d, error=%d", i, try_times, error))); + pg_usleep(100000L);// 延迟 100,000 微秒(0.1 秒),然后继续下一次尝试。 + } while (try_times < 5);// 循环继续,直到成功创建队列或者尝试次数达到 5 次。 + + if (error != 0) { + goto AioCompltrStartError;// 发生错误时跳转到错误处理标签 + } + + /* Allocate the event array for the thread */ + /* 为线程分配事件数组的内存 用于存储事件数组 + compltrArray[i].compltrDescp->max_nr 表示事件数组的最大大小,sizeof(struct io_event) 表示单个事件的大小。*/ + compltrArray[i].eventsp = (io_event*)malloc(compltrArray[i].compltrDescp->max_nr * sizeof(struct io_event)); + // 检查内存分配是否成功,失败则进入if语句内部 + if (compltrArray[i].eventsp == (struct io_event*)NULL) { + + /* malloc failed for some reason... */ + error = 2;// 设置错误码为 2,表示内存分配失败 + //打印日志,记录内存分配失败的详细信息,包括最大事件数和错误码。 + ereport(LOG, + (errmsg("AIO Startup malloc io_event failed: max_nr(%d), %d", + compltrArray[i].compltrDescp->max_nr, + error))); + // 跳转到错误处理标签 AioCompltrStartError,执行错误处理操作 + goto AioCompltrStartError; + } + + /* Start AIO Completer thread */ + //使用 Compltrfork_exec(i) 函数启动一个异步 I/O 完成线程,并将线程索引 i 作为参数传递给函数。函数返回线程的 ID。 + compltrArray[i].tid = Compltrfork_exec(i); + + // 检查线程是否成功启动。如果启动失败,会进入条件内部。 + if (compltrArray[i].tid == (ThreadId)-1) { + /* starting a thread failed */ + error = 3;// 设置错误码为 3,表示启动线程失败 + // 打印日志,记录启动线程失败的详细信息,包括错误码。 + ereport(LOG, (errmsg("Start AIO Completer thread failed: %d", error))); + goto AioCompltrStartError; // 发生错误时跳转到错误处理标签 + } + }; + + /* The AIO Completers are open for business */ + AioCompltrReady = true;// AIO Completers 准备就绪 + + /* successful return */ + return 0; + +//错误处理标签 AioCompltrStartError 标识错误处理的起始位置 +AioCompltrStartError: + /* + * If anything went wrong, then stop any threads started + * and deallocate the resources. + */ + AioCompltrStop(SIGTERM);// 停止已启动的异步 I/O 完成线程并释放资源 + ereport(LOG, (errmsg("AIO Startup Failed,error=%d", error)));// 记录启动失败的错误日志 + + return error;// 返回错误码 +} + +/* + * @Description: Stop the Completer threads, cleanup any partially started ones. + * Send SIGQUIT and forget about the threads. + * The caller must ensure that no AIO is in progress prior to using this function. + * @Param[IN] signal:signal + * @See also: + */ +void AioCompltrStop(int signal) +{ + gs_thread_t thread; + AioCompltrReady = false;// 停止AIO Completer线程的标志设置为false,表示线程不再运行 + + /* + * Stop the threads in the compltrArray. + */ + // 循环遍历AIO Completer线程数组,停止所有线程 + for (int i = 0; i < AioCompltrThreads; i++) { + /* + * Stop the threads that were started + */ + // 检查线程标识符是否有效 + if (compltrArray[i].tid != 0) { + // 如果线程已启动,使用gs_signal_send函数向线程发送信号停止线程,将传入的signal作为信号类型。 + if (gs_signal_send(compltrArray[i].tid, signal) < 0) { + // 如果发送信号失败,记录错误日志,指明哪个线程的信号发送失败。 + ereport(LOG, (errmsg("kill(%ld,%d) failed: %m", (long)(compltrArray[i].tid), signal))); + } + } + } + // 如果数据库正在崩溃,只需杀死completers并退出。 + if (signal == SIGQUIT) { + /* + * if the database is crashing just kill the completers + * and bail-out. + */ + return;// 直接返回,杀死completers并退出 + } + + /* + * Wait for the stopped threads to exit. + */ + // 循环遍历AIO Completer线程数组,等待AIO Completer线程退出并进行清理 + for (int i = 0; i < AioCompltrThreads; i++) { + /* + * Wait for the killed threads to exit + */ + if (compltrArray[i].tid != 0) {// 检查线程标识符是否有效 + thread.thid = compltrArray[i].tid;// 保存线程标识符,用于等待线程退出 + // 使用gs_thread_join函数等待线程退出。如果返回值不为0,表示等待线程退出失败。 + if (gs_thread_join(thread, NULL) != 0) { + /* + * If the thread does not exist, treat it as normal exit and we continue to + * do our clean-up work. Otherwise, we treate it as crashed 'cause we do + * not know the current status of the thread and it's better to quit directly + * which sames more safely. + */ + if (ESRCH == pthread_kill(thread.thid, 0)) // 使用pthread_kill函数检查线程是否不存在 + // 如果线程不存在,记录错误日志 + ereport(LOG, (errmsg("failed to join thread %lu, no such process", thread.thid))); + else + // 如果线程存在但无法等待其退出,标记线程崩溃 + HandleChildCrash(thread.thid, 1, "AIO process"); + } + compltrArray[i].tid = (pid_t)0; // 清空线程标识符,表示线程已经退出 + } + } + + /* + * Deallocate their context and event arrays, if any + */ + for (int i = 0; i < AioCompltrThreads; i++) { + compltrArray[i].compltrDescp = (AioCompltrDesc_t*)NULL;// // 清空线程描述符指针,防止悬空指针 + + /* destroy the AIO context */ + // 如果AIO上下文存在 + if (compltrArray[i].context) { + io_destroy(compltrArray[i].context);// 销毁AIO上下文,释放相关资源 + compltrArray[i].context = (io_context_t)NULL;// 清空AIO上下文指针,防止悬空指针 + } + + /* Deallocate the events array */ + // 如果事件数组指针存在 + if (compltrArray[i].eventsp) { + free(compltrArray[i].eventsp);// 释放事件数组的内存,防止内存泄漏 + compltrArray[i].eventsp = (struct io_event*)NULL;// 清空事件数组指针,防止悬空指针 + } + } + + /* successful return */ + return; +} + +/* + * @Description: Main entry point for an AIO Completer thread + * @Param[IN] ac: param count + * @Param[IN] av: param list + * @See also: + */ +void AioCompltrMain(int ac, char** av) +{ + // 检查命令行参数数量是否足够 + if (ac < 4) { + ereport(WARNING, (errmsg("invalid AIO argument num:%d", ac))); + exit(1); + } + /* compltrIdx identifies this thread. */ + // 解析Completer线程的索引 + int compltrIdx = atoi(av[3]); + + /* + * Global thread local shortcuts to the completer descriptor + * in the compltrArray, these are assigned on entry. + */ + /* + 使用本地变量提前获取对完成器描述符中相关信息的引用,以便后续代码中使用。从而减少冗余的操作,提高代码可读性和性能。 + 其中包括了AIO上下文、事件数组指针、最小/最大等待事件数量、超时设置和AIO回调函数等信息的获取。 + */ + io_context_t context = compltrArray[compltrIdx].context; // AIO上下文 + io_event* eventsp = compltrArray[compltrIdx].eventsp;// 事件数组指针 + AioCompltrDesc_t* compltrDescp = compltrArray[compltrIdx].compltrDescp;// 完成器描述符指针 + int min_nr = compltrDescp->min_nr;// 最小等待事件数量 + int max_nr = compltrDescp->max_nr; // 最大可检索的事件数量 + struct timespec timeout;// 事件等待超时 + struct timespec shutdown_timeout; // 关闭等待超时 + timeout.tv_sec = compltrDescp->timeout;// 设置等待事件超时的秒数 + timeout.tv_nsec = 0; // 设置等待事件超时的纳秒数 + shutdown_timeout.tv_sec = AioCompltrShutdownTimeout;// 设置关闭等待超时的秒数 + shutdown_timeout.tv_nsec = 0;// 设置关闭等待超时的纳秒数 + AioCallback_t callback = compltrDescp->callback;// 获取AIO回调函数的指针 + + /* + * Handle signals the postmaster might send us + * SIGTERM causes the thread to prepare to exit + * SIGQUIT causes immediate exit without cleanup. + * SIGUSR1 is presently unused- reserved for future use. + */ + //为异步I/O Completer线程设置信号处理方式,确保线程能够适当地响应不同的信号 + (void)gspqsignal(SIGHUP, CompltrConfig); /* retrieve config */ //SIGHUP 信号将触发 CompltrConfig 函数,用于重新加载配置。 + (void)gspqsignal(SIGINT, SIG_IGN);// SIGINT 信号被忽略,不会中断线程执行。 + (void)gspqsignal(SIGTERM, CompltrShutdown); /* shutdown */ //SIGTERM 信号将触发 CompltrShutdown 函数,用于进行线程关闭和清理。 + (void)gspqsignal(SIGQUIT, CompltrQuickDie); /* hard crash time */ // SIGQUIT 信号将触发 CompltrQuickDie 函数,用于快速终止线程执行。 + (void)gspqsignal(SIGALRM, SIG_IGN);// SIGALRM 信号被忽略。 + (void)gspqsignal(SIGPIPE, SIG_IGN);// SIGPIPE 信号被忽略。 + (void)gspqsignal(SIGUSR1, SIG_IGN); /* reserved */ //SIGUSR1 信号都被忽略。 + (void)gspqsignal(SIGUSR2, SIG_IGN);// SIGUSR2 信号被忽略。 + + /* + * Reset some signals that are accepted by postmaster but we don't + * need. + */ + (void)gspqsignal(SIGCHLD, SIG_DFL);// 恢复默认处理 SIGCHLD 信号 + (void)gspqsignal(SIGTTIN, SIG_DFL);// 恢复默认处理 SIGTTIN 信号 + (void)gspqsignal(SIGTTOU, SIG_DFL); // 恢复默认处理 SIGTTOU 信号 + (void)gspqsignal(SIGCONT, SIG_DFL);// 恢复默认处理 SIGCONT 信号 + (void)gspqsignal(SIGWINCH, SIG_DFL);// 恢复默认处理 SIGWINCH 信号 + + /* We allow SIGQUIT (quickdie) at all times */ + sigdelset(&t_thrd.libpq_cxt.BlockSig, SIGQUIT);// 允许 SIGQUIT (quickdie) 信号随时被接收 + + /* Create a resource owner to keep track of our resources (buffer + * pins etc... + * + * Create a memory context if we ever allocate memory here... + * + * Handle exceptions like from ereport, elog if we do not want + * to just die... +// AioCompltrMain() perhaps need better exception handling? jeh + */ + /* + * Unblock signals (blocked when postmaster forked us) + */ + gs_signal_setmask(&t_thrd.libpq_cxt.UnBlockSig, NULL);// 解除之前在主进程中阻塞的信号 + (void)gs_signal_unblock_sigusr2();// 解除对 SIGUSR2 信号的阻塞 + + /* Announce that the Completer has been started */ + ereport(LOG, (errmsg("AIO Completer %d STARTED.", compltrIdx)));// 在日志中记录异步I/O Completer线程已经启动 + + for (;;) { + int eventsReceived;//存储从 io_getevents 函数中获取的已完成的异步I/O事件数量或错误代码 + + /* + * Reload configuration -if requested. + */ + // 如果有重新加载配置的请求,进行相应的处理,但在此代码段中被禁用了 + if (t_thrd.aio_cxt.config_requested) { + /* disabled config request, not time for this now. ProcessConfigFile PGC_SIGHUP; */ + // 如果配置重载请求被设置,这段代码会禁用配置请求并略过后续的处理,以避免在当前上下文中重新加载配置文件 + t_thrd.aio_cxt.config_requested = false; + } + + /* + * If shutdown is requested, set shutdown_pending and + * restart io_getevents() if it was interrupted. + * The io_getevents() timeout is reduced to shutdown_timeout to + * allow the thread to exit quickly when its time comes. + * Once shutdown is requested, there is no going back. + */ + // 如果收到了关闭请求 + if (t_thrd.aio_cxt.shutdown_requested) { + timeout = shutdown_timeout;// 设置超时时间为shutdown_timeout,以便快速退出线程 + + ereport(LOG, (errmsg("AIO Completer %d EXITED.", compltrIdx)));// 记录日志,表示AIO Completer正在退出 + proc_exit(0);// 退出线程 + } + + /* + * Wait for some AIO request(s) to complete + * on the given context. Retry if the syscall is + * interrupted. + */ + // 等待一些AIO请求完成在给定的上下文中,最小等待事件数量为min_nr,最大为max_nr,最长等待时间由timeout指定 + // 返回值是一个整数,表示成功获取的事件数量。如果返回值为负数,表示发生了错误, + eventsReceived = io_getevents(context, min_nr, max_nr, eventsp, &timeout); + /* + * If io_getevents() got interrupted, + * take the opportunity to check for pending requests. + * Then restart the io_getevents() call. + */ + // 如果 io_getevents() 被中断,重新尝试获取事件 + if (eventsReceived == -EINTR) { + continue; + } + + /* + * io_getevents() reports errors as negative values. + */ + // io_getevents() 报告错误的返回值为负数 + if (eventsReceived < 0) { + /* Report error */ + // 记录错误日志,发出 PANIC 级别的错误报告 + ereport(PANIC, (errmsg("AIO Completer io_getevents() failed: error %d .", eventsReceived))); + } + // 断言,确保 eventsReceived 不超过最大数量 + Assert(eventsReceived <= max_nr); + + /* + * Call the callback for each event returned + * We expect 0 to max_nr requests. The obj here is + * the I/O request and the db context. + */ + // 逐个遍历、处理异步I/O事件数组中的每个事件,执行特定操作或回调函数。 + for (struct io_event* eventp = eventsp; eventsReceived--; eventp++) { + callback((void*)eventp->obj, eventp->res);// 调用回调函数,传入 I/O 请求的 obj 和结果 + } + } + // 所有事件处理完成后,线程即将退出 + + // 记录线程退出的日志信息 + ereport(LOG, (errmsg("AIO Completer %d EXITED.", compltrIdx))); + exit(0);// 退出线程,返回状态码 0 +} + +/* + * @Description: signal handler routines for config,not used now + * @See also: + */ +// 信号处理程序,用于处理配置请求,目前未使用 +static void CompltrConfig(SIGNAL_ARGS) +{ + t_thrd.aio_cxt.config_requested = true;// 标记配置请求为true,表示配置请求已经发生 +} + +/* + * @Description: CompltrQuickDie() occurs when signalled SIGQUIT by the postmaster. + * Some backend has bought the farm, + * so we need to stop what we're doing and exit. + * @See also: + */ +// 快速退出的信号处理程序,当postmaster通过SIGQUIT信号发出终止请求时触发 +static void CompltrQuickDie(SIGNAL_ARGS) +{ + PG_SETMASK(&t_thrd.libpq_cxt.BlockSig);// 阻塞所有信号 + + /* + * We DO NOT want to run proc_exit() callbacks -- we're here because + * shared memory may be corrupted. Like the other postmaster + * children, ...Just nail the windows shut and get out of town.... + */ + on_exit_reset();// 重置进程退出回调 + + /* + * Note we do exit(2) not exit(0)... + * ...just like the other postmaster children. + */ + exit(2);// 以退出码2退出,类似其他主控进程子进程的做法 +} + +/* + * @Description: CompltrShutdown() occurs when signalled SIGTERM by the postmaster. + * @See also: + */ +// 关闭信号处理程序,当postmaster通过SIGTERM信号发出关闭请求时触发 +static void CompltrShutdown(SIGNAL_ARGS) +{ + t_thrd.aio_cxt.shutdown_requested = true;// 设置异步I/O Completer线程的关闭请求标志 +} + +/** + * @Description: Initialize Resource used by adio + * @in void + * @return void + */ +// 初始化异步I/O的资源 +void AioResourceInitialize(void) +{ + // 创建一个共享内存上下文用于异步I/O资源,以便进行内存分配和管理 + AdioSharedContext = AllocSetContextCreate((MemoryContext)g_instance.instance_context, + "AdioSharedMemory",// 设置上下文的名称,用于标识和识别此上下文。 + ALLOCSET_DEFAULT_MINSIZE,//设置上下文的初始最小内存分配大小 + ALLOCSET_DEFAULT_INITSIZE,//设置上下文的初始内存池大小 + ALLOCSET_DEFAULT_MAXSIZE,// 设置上下文的最大内存池大小 + SHARED_CONTEXT);// 设置上下文的标志,指示它是一个共享上下文 } -- 2.34.1 From a3dbaa662a4dff6bd9a262b1059499513fcd0336 Mon Sep 17 00:00:00 2001 From: LuckYzaw <3180017453@qq.com> Date: Sun, 27 Aug 2023 16:18:21 +0800 Subject: [PATCH 04/50] Update alarmchecker.cpp --- .../process/postmaster/alarmchecker.cpp | 167 +++++++++++------- 1 file changed, 106 insertions(+), 61 deletions(-) diff --git a/src/gausskernel/process/postmaster/alarmchecker.cpp b/src/gausskernel/process/postmaster/alarmchecker.cpp index 2ffe93696..4ba9d67ff 100644 --- a/src/gausskernel/process/postmaster/alarmchecker.cpp +++ b/src/gausskernel/process/postmaster/alarmchecker.cpp @@ -22,6 +22,7 @@ * * ------------------------------------------------------------------------- */ +// 该文件实现了openGauss的报警检查线程功能,主要用于检查数据库运行过程中的异常情况并进行相应的报警处理 #include "postgres.h" #include "knl/knl_variable.h" @@ -48,64 +49,77 @@ #include "replication/walsender.h" // declare the global variable of alarm module -int g_alarmReportInterval; -char g_alarmComponentPath[MAXPGPATH]; -int g_alarmReportMaxCount; +// 声明用于控制报警模块行为的全局变量 +int g_alarmReportInterval; // 报警上报的时间间隔(单位:秒) +char g_alarmComponentPath[MAXPGPATH]; // 报警组件路径,存储报警信息的组件的路径(长度为MAXPGPATH) +int g_alarmReportMaxCount; // 最大报警上报次数 /* seconds, interval of alarm check loop. */ -static const int AlarmCheckInterval = 1; +static const int AlarmCheckInterval = 1; // 报警检查循环的时间间隔,初始设置为1秒 -bool enable_alarm = false; +bool enable_alarm = false; // 表示是否启用报警功能。初始值为false,通过设置为true来启用报警功能 -static Alarm* DataInstAlarmList = NULL; +static Alarm* DataInstAlarmList = NULL; // 指向 Alarm 结构体的指针,表示报警项的列表。报警项是用于检测不同类型报警的配置信息和处理函数的集合。 -static int DataInstAlarmListSize = 0; +static int DataInstAlarmListSize = 0; // 报警项列表的大小,即列表中报警项的数量 -AlarmCheckResult DataOrRedoDirNotExistChecker(Alarm* alarm, AlarmAdditionalParam* additionalParam); +// 函数原型声明 +AlarmCheckResult DataOrRedoDirNotExistChecker(Alarm* alarm, AlarmAdditionalParam* additionalParam);//报警检查函数,用于检查数据目录或重做日志目录是否存在 -static void DataInstAlarmItemInitialize(void); -static void acSighupHandler(SIGNAL_ARGS); -static void acSigquitHandler(SIGNAL_ARGS); +static void DataInstAlarmItemInitialize(void); // 报警项初始化函数,用于初始化数据实例的报警项列表。 -extern AlarmCheckResult DataInstArchChecker(Alarm* alarm, AlarmAdditionalParam* additionalParam); -extern AlarmCheckResult ConnAuthMethodChecker(Alarm* alarm, AlarmAdditionalParam* additionalParam); +static void acSighupHandler(SIGNAL_ARGS); // SIGHUP信号处理函数,用于在收到SIGHUP信号时设置相关标志 +static void acSigquitHandler(SIGNAL_ARGS); // SIGQUIT信号处理函数的原型,用于在收到SIGQUIT信号时设置相关标志。 +// 数据实例归档检查函数,用于检查数据实例的归档状态 +extern AlarmCheckResult DataInstArchChecker(Alarm* alarm, AlarmAdditionalParam* additionalParam); +// 连接认证方法检查函数,用于检查连接的认证方法是否异常 +extern AlarmCheckResult ConnAuthMethodChecker(Alarm* alarm, AlarmAdditionalParam* additionalParam); +// 数据实例连接到GTM的检查函数,用于检查数据实例连接到GTM的状态 extern AlarmCheckResult DataInstConnToGTMChecker(Alarm* alarm, AlarmAdditionalParam* additionalParam); +// 初始化数据实例报警列表 void DataInstAlarmItemInitialize(void) { + // 设置数据实例报警项的数量为6 DataInstAlarmListSize = 6; + // 分配内存来存储数据实例报警项 DataInstAlarmList = (Alarm*)AlarmAlloc(sizeof(Alarm) * DataInstAlarmListSize); + // 检查内存分配是否成功,如果失败则记录错误日志并退出程序 if (NULL == DataInstAlarmList) { AlarmLog(ALM_LOG, "Out of memory: DataInstAlarmItemInitialize failed."); exit(1); } - // ALM_AI_MissingDataInstDataOrRedoDir + // 初始化各个数据实例报警项 参数列表中后三个分别为报警项的类型、严重性级别,以及对应的检查函数 + // ALM_AI_MissingDataInstDataOrRedoDir 报警项 AlarmItemInitialize( &(DataInstAlarmList[0]), ALM_AI_MissingDataInstDataOrRedoDir, ALM_AS_Normal, DataOrRedoDirNotExistChecker); - // ALM_AI_MissingDataInstWalSegmt + // ALM_AI_MissingDataInstWalSegmt 报警项 AlarmItemInitialize( &(DataInstAlarmList[1]), ALM_AI_MissingDataInstWalSegmt, ALM_AS_Normal, WalSegmentsRemovedChecker); - // ALM_AI_TooManyDataInstConn + // ALM_AI_TooManyDataInstConn 报警项 AlarmItemInitialize(&(DataInstAlarmList[2]), ALM_AI_TooManyDataInstConn, ALM_AS_Normal, ConnectionOverloadChecker); - // ALM_AI_AbnormalDataInstArch + // ALM_AI_AbnormalDataInstArch 报警项 AlarmItemInitialize(&(DataInstAlarmList[3]), ALM_AI_AbnormalDataInstArch, ALM_AS_Normal, DataInstArchChecker); - // ALM_AI_AbnormalDataInstConnAuthMethod + // ALM_AI_AbnormalDataInstConnAuthMethod 报警项 AlarmItemInitialize( &(DataInstAlarmList[4]), ALM_AI_AbnormalDataInstConnAuthMethod, ALM_AS_Normal, ConnAuthMethodChecker); - // ALM_AI_AbnormalDataInstConnToGTM + // ALM_AI_AbnormalDataInstConnToGTM 报警项 AlarmItemInitialize( &(DataInstAlarmList[5]), ALM_AI_AbnormalDataInstConnToGTM, ALM_AS_Normal, DataInstConnToGTMChecker); } +// 特定条件下启动报警检查线程,以便定期检查系统状态并进行报警处理。 ThreadId startAlarmChecker(void) { + // 如果不是在Postmaster环境下或者报警功能被禁用,则直接返回0,表示未启动报警检查线程 if (!IsPostmasterEnvironment || !enable_alarm) { return 0; } - + // 否则,调用initialize_util_thread函数启动报警检查线程,并返回线程ID return initialize_util_thread(ALARMCHECK); } +// 维护一个周期性的报警检查线程,用于及时发现系统异常情况并进行相应的处理。 NON_EXEC_STATIC void AlarmCheckerMain() { @@ -113,21 +127,23 @@ NON_EXEC_STATIC void AlarmCheckerMain() IsUnderPostmaster = true; /* reset t_thrd.proc_cxt.MyProcPid */ - t_thrd.proc_cxt.MyProcPid = gs_thread_self(); + t_thrd.proc_cxt.MyProcPid = gs_thread_self(); //将当前线程的系统级线程ID分配给MyProcPid,以标识当前线程 /* record Start Time for logging */ - t_thrd.proc_cxt.MyStartTime = time(NULL); + t_thrd.proc_cxt.MyStartTime = time(NULL); //获取当前的系统时间,即记录线程的启动时间。 /* reord my name */ - t_thrd.proc_cxt.MyProgName = "AlarmChecker"; + t_thrd.proc_cxt.MyProgName = "AlarmChecker"; // 设置线程的名称,用于标识当前线程的名称 /* Identify myself via ps */ - init_ps_display("AlarmChecker", "", "", ""); + init_ps_display("AlarmChecker", "", "", ""); // 设置线程在进程状态(ps)显示中的标识为 "AlarmChecker" - AlarmLog(ALM_LOG, "alarm checker started."); + AlarmLog(ALM_LOG, "alarm checker started."); // 记录报警检查线程启动信息 + // 初始化Latch支持,用于等待Latch的触发 InitializeLatchSupport(); /* needed for latch waits */ + // 初始化用于信号处理的私有Latch,以便在信号到达时唤醒线程执行相应的处理 /* Initialize private latch for use by signal handlers */ InitLatch(&t_thrd.alarm_cxt.AlarmCheckerLatch); @@ -139,10 +155,15 @@ NON_EXEC_STATIC void AlarmCheckerMain() * want to wait for the backends to exit, whereupon the postmaster will * tell us it's okay to shut down (via SIGUSR2). */ + // 处理了信号的设置和忽略,确保报警检查线程能够正确响应或忽略不同的信号 + // 将SIGHUP信号的处理函数设置为acSighupHandler。当收到SIGHUP信号时,会触发该信号处理函数,用于读取配置文件的标志位。 (void)gspqsignal(SIGHUP, acSighupHandler); /* set flag to read config file */ + // 将SIGINT和SIGTERM信号的处理设置为忽略状态,当收到这两个信号时,不会触发任何处理。 (void)gspqsignal(SIGINT, SIG_IGN); (void)gspqsignal(SIGTERM, SIG_IGN); + // 将SIGQUIT信号的处理函数设置为acSigquitHandler。当收到SIGQUIT信号时,会触发该信号处理函数,用于执行快速退出操作。 (void)gspqsignal(SIGQUIT, acSigquitHandler); + // 将SIGALRM、SIGPIPE、SIGUSR1、SIGUSR2信号的处理设置为忽略状态,即当收到以上信号时,不会触发任何处理。 (void)gspqsignal(SIGALRM, SIG_IGN); (void)gspqsignal(SIGPIPE, SIG_IGN); (void)gspqsignal(SIGUSR1, SIG_IGN); @@ -151,46 +172,57 @@ NON_EXEC_STATIC void AlarmCheckerMain() /* * Reset some signals that are accepted by postmaster but not here */ - (void)gspqsignal(SIGCHLD, SIG_DFL); - (void)gspqsignal(SIGTTIN, SIG_DFL); - (void)gspqsignal(SIGTTOU, SIG_DFL); - (void)gspqsignal(SIGCONT, SIG_DFL); - (void)gspqsignal(SIGWINCH, SIG_DFL); + // 重置一些信号的默认处理方式,以确保报警检查线程不会干扰其他信号的处理 + // 将信号的处理设置为默认处理方式 SIG_DFL + (void)gspqsignal(SIGCHLD, SIG_DFL); // 子进程状态变化信号 + (void)gspqsignal(SIGTTIN, SIG_DFL); // 后台进程试图从终端读取时发送的信号 + (void)gspqsignal(SIGTTOU, SIG_DFL); // 后台进程试图向终端写入时发送的信号 + (void)gspqsignal(SIGCONT, SIG_DFL); // 用于继续停止的进程的信号 + (void)gspqsignal(SIGWINCH, SIG_DFL); // 终端窗口大小发生变化时发送的信号 + // 处理信号掩码,以允许接收一些特定的信号 gs_signal_setmask(&t_thrd.libpq_cxt.UnBlockSig, NULL); (void)gs_signal_unblock_sigusr2(); /* all is done info top memory context. */ + // 切换当前线程的内存上下文到默认的内存上下文组 (void)MemoryContextSwitchTo(THREAD_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_DEFAULT)); - + // 调用函数,初始化数据实例的报警列表 DataInstAlarmItemInitialize(); + // 报警检查线程的主要工作循环,用于持续进行报警检查和处理 for (;;) { /* Clear any already-pending wakeups */ + //将报警检查线程的私有Latch重置为未触发状态,防止在等待期间可能发生的竞争条件或意外触发。 ResetLatch(&t_thrd.alarm_cxt.AlarmCheckerLatch); /* the normal shutdown case */ + // 如果收到了终止信号,退出循环 if (t_thrd.alarm_cxt.gotSigdie) break; /* * reload the postgresql.conf */ + // 如果收到了重新加载配置文件的信号 if (t_thrd.alarm_cxt.gotSighup) { + // 设置为 false,表示报警检查线程不再需要重新加载配置文件。 + // 用于处理 SIGHUP 信号执行操作,标记已经理重新加载配置文件的请求,以便线程在下次循环迭代时不会再次触发重新加载 t_thrd.alarm_cxt.gotSighup = false; - ProcessConfigFile(PGC_SIGHUP); + ProcessConfigFile(PGC_SIGHUP); // 调用ProcessConfigFile函数重新加载配置文件 } - + // 调用AlarmCheckerLoop函数进行报警检查 AlarmCheckerLoop(DataInstAlarmList, DataInstAlarmListSize); /* * Sleep until there's something to do */ + // 等待一段时间,等待Latch被设置或超时 (void)WaitLatch(&t_thrd.alarm_cxt.AlarmCheckerLatch, WL_LATCH_SET | WL_TIMEOUT, AlarmCheckInterval * 1000); } - + // 记录日志,标识报警检查线程正在关闭 AlarmLog(ALM_LOG, "alarm checker shutting down..."); - + // 调用 proc_exit 函数终止线程执行。参数 0 表示正常退出,线程将在此处终止并释放相关资源 proc_exit(0); } @@ -203,15 +235,16 @@ NON_EXEC_STATIC void AlarmCheckerMain() * Description : * Notes : */ +// 信号处理函数,用于处理 SIGHUP 信号,并设置相应的标志 static void acSighupHandler(SIGNAL_ARGS) { - int save_errno = errno; + int save_errno = errno; // 保存当前 errno 的值,以便后续恢复 - t_thrd.alarm_cxt.gotSighup = true; + t_thrd.alarm_cxt.gotSighup = true; // 将线程上下文中的 gotSighup 标志设置为 true,表示收到了 SIGHUP 信号 - SetLatch(&t_thrd.alarm_cxt.AlarmCheckerLatch); + SetLatch(&t_thrd.alarm_cxt.AlarmCheckerLatch); // 使用 SetLatch 函数触发线程的私有 Latch,以便唤醒线程并处理信号 - errno = save_errno; + errno = save_errno;// 恢复之前保存的 errno 值,确保不影响其他代码对 errno 的操作 } /* @@ -220,84 +253,96 @@ static void acSighupHandler(SIGNAL_ARGS) * Description : * Notes : */ +// 信号处理函数,用于处理 SIGTERM 或 SIGINT 信号,并设置相应的标志 static void acSigquitHandler(SIGNAL_ARGS) { - int save_errno = errno; + int save_errno = errno;// 保存当前 errno 的值,以便后续恢复 - t_thrd.alarm_cxt.gotSigdie = true; + t_thrd.alarm_cxt.gotSigdie = true;// 将线程上下文中的 gotSigdie 标志设置为 true,表示收到了 SIGTERM 或 SIGINT 信号 - SetLatch(&t_thrd.alarm_cxt.AlarmCheckerLatch); + SetLatch(&t_thrd.alarm_cxt.AlarmCheckerLatch);// 使用 SetLatch 函数触发线程的私有 Latch,以便唤醒线程并处理信号 - errno = save_errno; + errno = save_errno;// 恢复之前保存的 errno 值,确保不影响其他代码对 errno 的操作 } +// 用于检查目录是否存在,以及目录是否具有合适的属性和权限,即检查目录的有效性。 bool isDirExist(const char* dir) { + // 创建一个用于存放文件/目录属性信息的结构体 struct stat stat_buf; - + // 使用 stat 函数获取目录的状态信息,如果返回值不等于0,则表示目录不存在 if (stat(dir, &stat_buf) != 0) return false; - + // 使用 S_ISDIR 宏判断目录的文件类型是否为目录,如果不是目录类型,则返回 false if (!S_ISDIR(stat_buf.st_mode)) return false; + // 如果不在 Windows 平台且不在 Cygwin 环境中 #if !defined(WIN32) && !defined(__CYGWIN__) - + // 检查目录的拥有者是否为当前用户,如果不是则返回 false if (stat_buf.st_uid != geteuid()) return false; - + // 检查目录的权限是否为用户可读、写和执行权限,如果不是则返回 false if ((stat_buf.st_mode & S_IRWXU) != S_IRWXU) return false; #endif - + // 如果以上条件都满足,则返回 true,表示目录存在且符合要求 return true; } AlarmCheckResult DataOrRedoDirNotExistChecker(Alarm* alarm, AlarmAdditionalParam* additionalParam) { + // 检查 data 目录和 pg_xlog 目录是否存在 if (isDirExist(t_thrd.proc_cxt.DataDir) && isDirExist("pg_xlog")) { // fill the alarm message - WriteAlarmAdditionalInfo(additionalParam, - g_instance.attr.attr_common.PGXCNodeName, + // 填写报警信息 + WriteAlarmAdditionalInfo(additionalParam, //additionalParam:报警的附加参数,用于存储报警信息的详细内容 + g_instance.attr.attr_common.PGXCNodeName, // 数据库实例的名称,用于标识报警发生的实例 "", "", - alarm, - ALM_AT_Resume, - g_instance.attr.attr_common.PGXCNodeName); - return ALM_ACR_Normal; + alarm, //报警的类型或描述,用于标识具体的报警原因或问题 + ALM_AT_Resume,// 设置报警动作为“恢复”,表示报警条件已经解决 + g_instance.attr.attr_common.PGXCNodeName); //数据库实例的名称,用于填写报警信息 + return ALM_ACR_Normal;// 返回报警检查结果为“正常” } else { // fill the alarm message + // 填写报警信息 WriteAlarmAdditionalInfo(additionalParam, g_instance.attr.attr_common.PGXCNodeName, "", "", alarm, - ALM_AT_Fault, + ALM_AT_Fault, // 设置报警动作为“故障” g_instance.attr.attr_common.PGXCNodeName); - return ALM_ACR_Abnormal; + return ALM_ACR_Abnormal;// 返回报警检查结果为“异常” } } /* implementation of alarm module. */ +// 用于释放动态分配的内存,避免内存泄漏 void AlarmFree(void* pointer) { - if (pointer != NULL) - pfree(pointer); + if (pointer != NULL)// 检查指针是否非空 + pfree(pointer); // 使用 pfree 函数释放内存 } +// 用于分配指定大小的内存块 void* AlarmAlloc(size_t size) { - return palloc(size); + return palloc(size);// 调用 palloc 函数分配指定大小的内存块,并返回分配的内存块指针 } +// 日志输出函数,用于在不同级别输出报警信息 +// 输入参数分别为报警级别、前缀和报警文本,根据需要输出不同级别的报警信息以进行监控和调试。 void AlarmLogImplementation(int level, const char* prefix, const char* logtext) { + // 使用 switch 语句根据不同的级别选择不同的日志输出函数并输出信息 switch (level) { - case ALM_DEBUG: + case ALM_DEBUG:// 在 DEBUG3 级别输出报警信息,使用 errmsg 函数输出带有前缀和文本的日志 ereport(DEBUG3, (errmsg("%s%s", prefix, logtext))); break; - case ALM_LOG: + case ALM_LOG:// 在 LOG 级别输出报警信息,使用 errmsg 函数输出带有前缀和文本的日志 ereport(LOG, (errmsg("%s%s", prefix, logtext))); break; default: -- 2.34.1 From 6a29026dbdfe35c162d18f7bdf118b7c40f7cdd1 Mon Sep 17 00:00:00 2001 From: Nemoo <18210033860@163.com> Date: Sun, 27 Aug 2023 22:09:56 +0800 Subject: [PATCH 05/50] Update aggregatecmds.cpp --- .../optimizer/commands/aggregatecmds.cpp | 170 +++++++++--------- 1 file changed, 81 insertions(+), 89 deletions(-) diff --git a/src/gausskernel/optimizer/commands/aggregatecmds.cpp b/src/gausskernel/optimizer/commands/aggregatecmds.cpp index 68db543f5..3d59f9c9d 100644 --- a/src/gausskernel/optimizer/commands/aggregatecmds.cpp +++ b/src/gausskernel/optimizer/commands/aggregatecmds.cpp @@ -43,47 +43,47 @@ #include "catalog/pg_proc_fn.h" /* - * DefineAggregate - * - * "oldstyle" signals the old (pre-8.2) style where the aggregate input type - * is specified by a BASETYPE element in the parameters. Otherwise, - * "args" defines the input type(s). + DefineAggregate + "oldstyle" 表示旧式聚合函数定义风格, + 其中聚合函数的输入类型由参数中的 BASETYPE 元素指定。 + 否则,"args" 定义了输入类型 */ void DefineAggregate(List* name, List* args, bool oldstyle, List* parameters) { - char* aggName = NULL; - Oid aggNamespace; - AclResult aclresult; - List* transfuncName = NIL; - List* finalfuncName = NIL; - List* sortoperatorName = NIL; - TypeName* baseType = NULL; - TypeName* transType = NULL; - char* initval = NULL; + char* aggName = NULL;//字符型指针,用于存储聚合函数的名称 + Oid aggNamespace;//对象标识符类型,表示聚合函数所属的命名空间 + AclResult aclresult;//aclresult 是一个枚举值,表示访问控制的结果 + List* transfuncName = NIL;//链表类型,用于存储聚合函数的过渡函数的名称 + List* finalfuncName = NIL;//用于存储聚合函数的最终函数的名称 + List* sortoperatorName = NIL;//用于存储排序操作符的名称 + TypeName* baseType = NULL;//一个指向 TypeName 结构的指针,表示聚合函数的基础类型 + TypeName* transType = NULL;//也是TypeName结构的指针,表示聚合函数的过渡类型 + char* initval = NULL;//用于存储聚合函数的初始值 #ifdef PGXC List* collectfuncName = NIL; char* initcollect = NULL; #endif - Oid* aggArgTypes = NULL; - int numArgs; - Oid transTypeId; - ListCell* pl = NULL; +//定义了用于收集数据的函数名称和初始值 + Oid* aggArgTypes = NULL;//表示聚合函数的参数类型 + int numArgs;//表示聚合函数的参数数量 + Oid transTypeId;//表示聚合函数的过渡类型的标识符 + ListCell* pl = NULL;//循环中间变量 - /* attribute for ordered set aggregate */ + //有序集合聚合函数的属性或特征 char aggKind = AGGKIND_NORMAL; - /* Convert list of names to a name and namespace */ + //将一组名称列表转换为名称和命名空间 aggNamespace = QualifiedNameGetCreationNamespace(name, &aggName); - /* Check we have creation rights in target namespace */ + //检查是否具有在目标命名空间中创建的权限 aclresult = pg_namespace_aclcheck(aggNamespace, GetUserId(), ACL_CREATE); if (aclresult != ACLCHECK_OK) aclcheck_error(aclresult, ACL_KIND_NAMESPACE, get_namespace_name(aggNamespace)); if (u_sess->attr.attr_sql.enforce_a_behavior) { Oid proowner = InvalidOid; /* - * isalter is true, change the owner of the objects as the owner of the - * namespace, if the owner of the namespce has the same name as the namescpe + 如果 isalter 为真,则将对象的所有者更改为命名空间的所有者, + 但前提是命名空间的所有者与命名空间本身具有相同的名称 */ bool isalter = false; proowner = GetUserIdFromNspId(aggNamespace); @@ -102,10 +102,8 @@ void DefineAggregate(List* name, List* args, bool oldstyle, List* parameters) foreach (pl, parameters) { DefElem* defel = (DefElem*)lfirst(pl); - /* - * sfunc1, stype1, and initcond1 are accepted as obsolete spellings - * for sfunc, stype, initcond. - */ + //sfunc1、stype1 和 initcond1 被认为 是sfunc、stype 和 initcond 的过时拼写方式 + if (pg_strcasecmp(defel->defname, "sfunc") == 0) transfuncName = defGetQualifiedName(defel); else if (pg_strcasecmp(defel->defname, "sfunc1") == 0) @@ -124,6 +122,10 @@ void DefineAggregate(List* name, List* args, bool oldstyle, List* parameters) initval = defGetString(defel); else if (pg_strcasecmp(defel->defname, "initcond1") == 0) initval = defGetString(defel); + /* + 上述使用一个循环遍历 parameters 列表中的每个元素(DefElem 结构), + 然后根据元素的 defname 字段(参数名称)的值执行不同的操作 + */ #ifdef PGXC else if (pg_strcasecmp(defel->defname, "cfunc") == 0) collectfuncName = defGetQualifiedName(defel); @@ -135,29 +137,23 @@ void DefineAggregate(List* name, List* args, bool oldstyle, List* parameters) (errcode(ERRCODE_SYNTAX_ERROR), errmsg("aggregate attribute \"%s\" not recognized", defel->defname))); } - /* - * make sure we have our required definitions - */ + + //确保我们有所需的定义变量 + if (transType == NULL) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("aggregate stype must be specified"))); if (transfuncName == NIL) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("aggregate sfunc must be specified"))); - /* - * look up the aggregate's input datatype(s). - */ if (oldstyle) { /* - * Old style: use basetype parameter. This supports aggregates of - * zero or one input, with input type ANY meaning zero inputs. - * - * Historically we allowed the command to look like basetype = 'ANY' - * so we must do a case-insensitive comparison for the name ANY. Ugh. + Old style支持零个或一个输入的聚合函数,其中输入类型 ANY 表示零个输入 + 以往我们允许命令看起来像 basetype = 'ANY',因此我们必须对名称 ANY 进行不区分大小写的比较 */ if (baseType == NULL) ereport(ERROR, (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("aggregate input type must be specified"))); - + //baseType参数的值不能为空,聚合函数的输入类型必须指定 if (pg_strcasecmp(TypeNameToString(baseType), "ANY") == 0) { numArgs = 0; aggArgTypes = NULL; @@ -167,9 +163,6 @@ void DefineAggregate(List* name, List* args, bool oldstyle, List* parameters) aggArgTypes[0] = typenameTypeId(NULL, baseType); } } else { - /* - * New style: args is a list of TypeNames (possibly zero of 'em). - */ ListCell* lc = NULL; int i = 0; @@ -178,32 +171,31 @@ void DefineAggregate(List* name, List* args, bool oldstyle, List* parameters) (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("basetype is redundant with aggregate input type specification"))); - /* Given ordered set aggregate with no direct args, aggr_args variable is modified in gram.y. - So the parse of aggr_args should be changed. See gram.y for detail. */ + /* 在使用没有直接参数的有序集合聚合函数时,aggr_args 变量会在 gram.y 文件中进行修改。 + 因此,解析 aggr_args 的过程应该进行相应的更改。 + */ numArgs = list_length((List*)linitial(args)); + // 获取传递给聚合函数的参数数量 aggArgTypes = (Oid*)palloc(sizeof(Oid) * numArgs); - foreach (lc, (List*)linitial(args)) { + // 为聚合函数的参数类型标识符分配内存 + foreach (lc, (List*)linitial(args)) + {//// 遍历传递给聚合函数的参数列表 TypeName* curTypeName = (TypeName*)lfirst(lc); aggArgTypes[i++] = typenameTypeId(NULL, curTypeName); + // 获取当前参数的类型标识符并存储到数组中 } - /* Set aggKind to AGGKIND_ORDERED_SET if second arg of aggr_args is 0. */ if (intVal(lsecond(args)) == 0) { aggKind = AGGKIND_ORDERED_SET; } } /* - * look up the aggregate's transtype. - * - * transtype can't be a pseudo-type, since we need to be able to store - * values of the transtype. However, we can allow polymorphic transtype - * in some cases (AggregateCreate will check). Also, we allow "internal" - * for functions that want to pass pointers to private data structures; - * but allow that only to superusers, since you could crash the system (or - * worse) by connecting up incompatible internal-using functions in an - * aggregate. + 下面查找聚合函数的 transtype + transtype 不能是伪类型,因为我们需要能够存储 transtype 的值。 + 然而,在某些情况下,我们可以允许多态的 transtype(AggregateCreate 函数将会检查这一点)。 + 此外,我们允许使用 "internal",用于那些希望传递指向私有数据结构的指针的函数 */ transTypeId = typenameTypeId(NULL, transType); if (get_typtype(transTypeId) == TYPTYPE_PSEUDO && !IsPolymorphicType(transTypeId) @@ -212,45 +204,44 @@ void DefineAggregate(List* name, List* args, bool oldstyle, List* parameters) (errcode(ERRCODE_INVALID_FUNCTION_DEFINITION), errmsg("aggregate transition data type cannot be %s", format_type_be(transTypeId)))); } - /* - * Most of the argument-checking is done inside of AggregateCreate - */ - AggregateCreate(aggName, /* aggregate name */ - aggNamespace, /* namespace */ - aggKind, /* agg kind */ - aggArgTypes, /* input data type(s) */ - numArgs, - transfuncName, /* step function name */ + + //大部分的参数检查都在 AggregateCreate 函数内部完成 + AggregateCreate(aggName, /* 聚合函数的名称 */ + aggNamespace, /* 命名空间 */ + aggKind, /* 聚合种类 */ + aggArgTypes, /* 输入类型种类 */ + numArgs, /* 输入参数数量 */ + transfuncName, /* 过渡函数名称 */ #ifdef PGXC - collectfuncName, /* collect function name */ + collectfuncName, /* 收集函数名称 */ #endif - finalfuncName, /* final function name */ - sortoperatorName, /* sort operator name */ - transTypeId, /* transition data type */ + finalfuncName, /* 最终函数名称 */ + sortoperatorName, /* 排序操作符名称 */ + transTypeId, /* 过渡数据类型 */ #ifdef PGXC - initval, /* initial condition */ - initcollect); /* initial condition for collection function */ + initval, /* 初始条件 */ + initcollect); /* 收集函数的初始条件 */ #else - initval); /* initial condition */ + initval); /* 初始条件 */ #endif } void RenameAggregate(List* name, List* args, const char* newname) { - Oid procOid; - Oid namespaceOid; - HeapTuple tup; - Form_pg_proc procForm; - Relation rel; - AclResult aclresult; - bool isNull = false; - rel = heap_open(ProcedureRelationId, RowExclusiveLock); + Oid procOid; /* 聚合函数的 Oid */ + Oid namespaceOid; /* 命名空间的 Oid */ + HeapTuple tup; /* HeapTuple 结构,用于存储元组 */ + Form_pg_proc procForm; /* pg_proc 表中的元组结构 */ + Relation rel; /* pg_proc 表的 Relation 对象 */ + AclResult aclresult; /* AclResult 枚举,用于存储访问控制的结果 */ + bool isNull = false; /* 布尔变量,表示是否为 NULL */ + rel = heap_open(ProcedureRelationId, RowExclusiveLock); /* 打开 pg_proc 表 */ - /* Look up function and make sure it's an aggregate */ + /* 查询函数并确保他是聚合的*/ procOid = LookupAggNameTypeNames(name, args, false); tup = SearchSysCacheCopy1(PROCOID, ObjectIdGetDatum(procOid)); - if (!HeapTupleIsValid(tup)) /* should not happen */ + if (!HeapTupleIsValid(tup)) /* 如果运行正常这是不会出现的 */ ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for function %u", procOid))); procForm = (Form_pg_proc)GETSTRUCT(tup); @@ -265,13 +256,14 @@ void RenameAggregate(List* name, List* args, const char* newname) #ifndef ENABLE_MULTIPLE_NODES Datum allargtypes = ProcedureGetAllArgTypes(tup, &isNull); Datum argmodes = SysCacheGetAttr(PROCOID, tup, Anum_pg_proc_proargmodes, &isNull); - /* make sure the new name doesn't exist */ + // 在系统缓存中搜索具有相同参数的函数 if (SearchSysCacheForProcAllArgs( CStringGetDatum(newname), allargtypes, ObjectIdGetDatum(namespaceOid), ObjectIdGetDatum(packageoid), argmodes)) + // 如果找到相同参数的函数,报告错误 ereport(ERROR, (errcode(ERRCODE_DUPLICATE_FUNCTION), errmsg("function %s already exists in schema \"%s\"", @@ -288,16 +280,16 @@ void RenameAggregate(List* name, List* args, const char* newname) funcname_signature_string(newname, procForm->pronargs, NIL, proargs->values), get_namespace_name(namespaceOid)))); #endif - /* must be owner */ + // 检查当前用户是否是聚合函数的所有者,如果不是,则报告错误 if (!pg_proc_ownercheck(procOid, GetUserId())) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_PROC, NameListToString(name)); - /* must have CREATE privilege on namespace */ + // 检查当前用户是否有在命名空间中创建对象的权限 aclresult = pg_namespace_aclcheck(namespaceOid, GetUserId(), ACL_CREATE); if (aclresult != ACLCHECK_OK) aclcheck_error(aclresult, ACL_KIND_NAMESPACE, get_namespace_name(namespaceOid)); - /* rename */ + //重命名 (void)namestrcpy(&(((Form_pg_proc)GETSTRUCT(tup))->proname), newname); simple_heap_update(rel, &tup->t_self, tup); CatalogUpdateIndexes(rel, tup); @@ -307,15 +299,15 @@ void RenameAggregate(List* name, List* args, const char* newname) } /* - * Change aggregate owner + 下面函数用来更改聚合函数的所有者 */ void AlterAggregateOwner(List* name, List* args, Oid newOwnerId) { Oid procOid; - /* Look up function and make sure it's an aggregate */ + /* 查找函数并确保它是一个聚合函数。 */ procOid = LookupAggNameTypeNames(name, args, false); - /* The rest is just like a function */ + /* 其余部分与普通函数类似 */ AlterFunctionOwner_oid(procOid, newOwnerId); -} +} \ No newline at end of file -- 2.34.1 From da2a11dd1c2224166e1a618bfdff7929b1e12ed1 Mon Sep 17 00:00:00 2001 From: nuoya <1204149038@qq.com> Date: Mon, 25 Sep 2023 15:29:55 +0800 Subject: [PATCH 06/50] Update obs_connector.cpp --- .../storage/dfs/obs/obs_connector.cpp | 215 +++++++++++++++--- 1 file changed, 187 insertions(+), 28 deletions(-) diff --git a/src/gausskernel/storage/dfs/obs/obs_connector.cpp b/src/gausskernel/storage/dfs/obs/obs_connector.cpp index 041485b67..99d2b4eee 100644 --- a/src/gausskernel/storage/dfs/obs/obs_connector.cpp +++ b/src/gausskernel/storage/dfs/obs/obs_connector.cpp @@ -30,9 +30,9 @@ #include "utils/plog.h" #if defined(__LP64__) || defined(__64BIT__) - typedef unsigned int GS_UINT32; +typedef unsigned int GS_UINT32; #else - typedef unsigned long GS_UINT32; +typedef unsigned long GS_UINT32; #endif #define OBS_NOT_IMPLEMENT \ @@ -41,25 +41,50 @@ HTAB *OBSConnectorCache = NULL; namespace dfs { +/* + * 功能:创建一个 OBS 连接器对象 + * + * 参数列表: + * ctx:内存上下文,用于内存管理 + * foreignTableId:外部表的对象标识符 + */ OBSConnector::OBSConnector(MemoryContext ctx, Oid foreignTableId) : m_memcontext(ctx), m_handler(NULL), srvType(T_INVALID) { // build obs handler m_handler = searchConnectorCache(foreignTableId); } - +/* + * 功能:创建一个 OBS 连接器对象 + * + * 参数列表: + * ctx:内存上下文,用于内存管理 + * obsOptions:OBS 连接选项,包含连接到 OBS 所需的配置信息 + */ OBSConnector::OBSConnector(MemoryContext ctx, ObsOptions *obsOptions) : m_memcontext(ctx), m_handler(NULL), srvType(T_INVALID) { // build obs handler m_handler = createRWHandler(obsOptions); } - +/* + * 功能:销毁 OBS 连接器对象 + * + * 注意:在对象销毁时,调用了 Destroy() 函数以执行必要的清理操作。 + */ OBSConnector::~OBSConnector() { Destroy(); } - +/* + * 功能:销毁 OBS 连接器对象 + * + * 注意: + * - 这个函数用于销毁 OBS 连接器对象,包括清理与对象相关的资源。 + * - 在销毁前,将 m_handler->m_object_info.key 设置为 NULL,以避免悬挂指针。 + * - 调用 DestroyObsReadWriteHandler() 函数销毁 OBS 读写处理程序,并传递 true 参数以确保释放所有相关资源。 + * - 最后,将 m_handler 指针设置为 NULL,防止重复销毁。 + */ void OBSConnector::Destroy() { // IMPORT: m_handler->m_prefix not alloc on this class 's memeory context @@ -315,47 +340,99 @@ List *OBSConnector::listObjectsStat(char *searchPath, const char *primitivePrefi return objectList; } - +/* + * 功能:获取指定文件的 DFS 块信息 + * + * 参数列表: + * filePath:要查询块信息的文件路径 + * + * 注意: + * - 该函数尚未实现,只是一个占位符。在实现此功能之前,它返回 NULL。 + * - 一旦实现了获取文件的 DFS 块信息的功能,应该更新此函数以提供正确的实现。 + * - DFS 块信息通常包括块的位置、大小等相关信息。 + */ DFSBlockInfo *OBSConnector::getBlockLocations(char *filePath) { + // 使用 OBS_NOT_IMPLEMENT 标记函数未实现 OBS_NOT_IMPLEMENT; + // 返回 NULL,因为函数未实现 return NULL; } - +/* + * 功能:删除指定路径的目录(未实现) + * + * 参数列表: + * path:要删除的目录路径 + * recursive:是否递归删除子目录(1 表示递归删除,0 表示不递归) + */ int OBSConnector::dropDirectory(const char *path, int recursive) { + // 使用 OBS_NOT_IMPLEMENT 标记函数未实现 OBS_NOT_IMPLEMENT; + // 返回 0,表示删除成功(占位返回值,实际删除未实现) return 0; } - +/* + * 功能:创建指定路径的目录(未实现) + * + * 参数列表: + * path:要创建的目录路径 + */ int OBSConnector::createDirectory(const char *path) { + // 使用 OBS_NOT_IMPLEMENT 标记函数未实现 OBS_NOT_IMPLEMENT; + // 返回 0,表示创建成功(占位返回值,实际创建未实现) return 0; } - +/* + * 功能:打开指定路径的文件 + * + * 参数列表: + * path:要打开的文件路径 + * flag:文件打开标志,O_RDONLY 表示只读,其他表示其他操作 + */ int OBSConnector::openFile(const char *path, int flag) { + // 声明 bucket 和 prefix 字符串指针,并初始化为 NULL char *bucket = NULL; char *prefix = NULL; + // 通过 FetchUrlPropertiesForQuery 函数获取路径中的 bucket 和 prefix FetchUrlPropertiesForQuery(path, &bucket, &prefix); + // 设置存储处理器的对象信息的 key 为 prefix m_handler->m_object_info.key = prefix; + + // 如果存储处理器的选项中的桶名称为空,将其设置为 bucket if (m_handler->m_option.bucket_options.bucket_name == NULL) m_handler->m_option.bucket_options.bucket_name = bucket; + // 根据打开标志设置存储处理器的类型为读取或写入 if (flag == O_RDONLY) ObsReadWriteHandlerSetType(m_handler, OBS_READ); else ObsReadWriteHandlerSetType(m_handler, OBS_WRITE); + + // 返回 0,表示打开成功 return 0; } - +/* + * 功能:删除指定路径的文件或目录 + * + * 参数列表: + * path:要删除的文件或目录的路径 + * recursive:递归删除标志,1 表示递归删除,0 表示只删除指定文件或目录 + * + * 返回值: + * 0 表示删除成功,其他值表示删除失败 + */ int OBSConnector::deleteFile(const char *path, int recursive) { int ret = 0; + // 调用 deleteOBSObject 函数删除指定路径的文件或目录,并将结果赋给 ret ret = deleteOBSObject(m_handler); + // 返回删除操作的结果 return ret; } @@ -435,27 +512,57 @@ bool OBSConnector::existsFile(const char *path) return isExists; } - +/* + * 功能:检查是否存在有效文件 + * + * 返回值: + * false 表示不存在有效文件 + */ bool OBSConnector::hasValidFile() const { /* OBS_NOT_IMPLEMENT; */ return false; } - +/* + * 功能:将数据写入当前文件 + * + * 参数列表: + * buffer:要写入的数据的缓冲区 + * length:要写入的数据的长度 + * + * 返回值: + * 0 表示写入成功,其他值表示写入失败 + */ int OBSConnector::writeCurrentFile(const char *buffer, int length) { int ret = 0; + // 调用 writeObsTempFile 函数将数据写入当前文件,并将结果赋给 ret ret = writeObsTempFile(m_handler, buffer, length); + + // 返回写入操作的结果 return ret; } - +/* + * 功能:从当前文件中读取数据到缓冲区,直到读取指定长度的数据或达到文件末尾 + * + * 参数列表: + * buffer:用于存储读取数据的缓冲区 + * length:要读取的数据的长度 + * offset:读取数据的起始偏移量(未使用) + * + * 返回值: + * 0 表示成功读取指定长度的数据,-1 表示读取失败或达到文件末尾 + */ int OBSConnector::readCurrentFileFully(char *buffer, int length, int64 offset) { int ret = 0; + // 调用 read_bucket_object 函数从当前文件中读取数据到缓冲区,返回已读取的数据长度 int readSize = (int)read_bucket_object(m_handler, buffer, length); + // 如果已读取的数据长度不等于指定的长度,将返回值设置为-1,表示读取失败或达到文件末尾 if (readSize != length) { ret = -1; } + // 返回读取操作的结果 return ret; } @@ -533,19 +640,37 @@ OBSReadWriteHandler *OBSConnector::searchConnectorCache(Oid foreignTableId) return handler; } - +/* + * 功能:创建 OBS 读写处理程序 + * + * 参数列表: + * obsOptions:包含 OBS 配置信息的选项结构 + * + * 返回值: + * 返回新创建的 OBS 读写处理程序的指针 + */ OBSReadWriteHandler *OBSConnector::createRWHandler(ObsOptions *obsOptions) { + // 切换内存上下文到当前类的内存上下文 AutoContextSwitch memGuard(m_memcontext); OBSReadWriteHandler *handler = NULL; + // 创建 OBS 读写处理程序,并为查询进行配置 handler = CreateObsReadWriteHandlerForQuery(obsOptions); handler->in_computing = true; return handler; } - +/* + * 从缓存中获取 OBS 连接选项 + * + * 参数列表: + * foreignTableId:外部表的 OID + * + * 返回值: + * 返回 ObsOptions 结构的指针,包含 OBS 连接选项 + */ ObsOptions *OBSConnector::getOptionFromCache(Oid foreignTableId) { bool found = false; @@ -565,6 +690,7 @@ ObsOptions *OBSConnector::getOptionFromCache(Oid foreignTableId) if (found && (entry && entry->access_key != NULL && entry->secret_access_key != NULL && entry->address != NULL)) { GS_UINT32 outPutLen; ObsOptions *retOption = copyObsOptions(entry); + // 解码加密的秘密访问密钥 pfree(retOption->secret_access_key); retOption->secret_access_key = SEC_decodeBase64(entry->secret_access_key, &outPutLen); return retOption; @@ -576,9 +702,11 @@ ObsOptions *OBSConnector::getOptionFromCache(Oid foreignTableId) /* Put the new connector info into the hash table. */ (void)LWLockAcquire(DfsConnectorCacheLock, LW_EXCLUSIVE); entry = (ObsOptions *)hash_search(OBSConnectorCache, (void *)&key, HASH_ENTER, &found); + // 如果未能成功添加到哈希表,报错 if (entry == NULL) ereport(PANIC, (errcode(ERRCODE_UNDEFINED_OBJECT), errmodule(MOD_OBS), errmsg("build global OBS connect cache hash table failed"))); + // 如果在哈希表中未找到连接选项,将外部表的选项复制到哈希表中 if (!found) { Assert(obsOptions != NULL); entry->address = obsOptions->address; @@ -591,6 +719,7 @@ ObsOptions *OBSConnector::getOptionFromCache(Oid foreignTableId) } LWLockRelease(DfsConnectorCacheLock); + // 返回连接选项的副本,并解码加密的秘密访问密钥 ObsOptions *retOption = copyObsOptions(entry); GS_UINT32 outPutLen; pfree(retOption->secret_access_key); @@ -608,52 +737,82 @@ int OBSConnector::getType() { return (int)OBS_CONNECTOR; } - +/* + * 功能:用于数据结构(如哈希表或搜索树)中,以帮助确定元素的位置或查找元素 + * 参数说明: + * key1:指向第一个Oid的指针。 + * key2:指向第二个Oid的指针。 + * keySize:表示Oid的大小,通常是sizeof(Oid)。 + */ static int matchOid(const void *key1, const void *key2, Size keySize) { return (int)(*(Oid *)key1 - *(Oid *)key2); } - +/* + * 功能:初始化OBS连接器缓存锁 + * + * 无参数 + */ void InitOBSConnectorCacheLock() { - HASHCTL ctl; - errno_t rc = 0; + HASHCTL ctl; // 哈希表的控制结构 + errno_t rc = 0; // 错误码 if (OBSConnectorCache == NULL) { + // 清零控制结构 rc = memset_s(&ctl, sizeof(ctl), 0, sizeof(ctl)); securec_check(rc, "\0", "\0"); - ctl.hcxt = g_instance.instance_context; - ctl.keysize = sizeof(Oid); - ctl.entrysize = sizeof(ObsOptions); - ctl.match = (HashCompareFunc)matchOid; - ctl.hash = (HashValueFunc)oid_hash; - OBSConnectorCache = hash_create("OBS connector cache", 50, &ctl, - HASH_ELEM | HASH_FUNCTION | HASH_COMPARE | HASH_SHRCTX); + ctl.hcxt = g_instance.instance_context; // 分配内存的上下文 + ctl.keysize = sizeof(Oid); // 键的大小,通常是Oid的大小 + ctl.entrysize = sizeof(ObsOptions); // 哈希表中每个条目的大小 + ctl.match = (HashCompareFunc)matchOid; // 用于比较键的函数 + ctl.hash = (HashValueFunc)oid_hash; // 计算哈希值的函数 + // 创建哈希表,带有指定的控制选项 + OBSConnectorCache = + hash_create("OBS connector cache", 50, &ctl, HASH_ELEM | HASH_FUNCTION | HASH_COMPARE | HASH_SHRCTX); + // 如果哈希表创建失败,发出 PANIC 级别的错误信息 if (OBSConnectorCache == NULL) ereport(PANIC, (errmodule(MOD_HDFS), errmsg("could not initialize OBS connector hash table"))); } } - +/* + * 功能:使 OBS 连接器缓存无效 + * + * 参数列表: + * serverOid:外部表所在的服务器的对象标识符(OID) + * + * 返回值: + * 如果成功清理缓存,则返回 true;否则返回 false + */ bool InvalidOBSConnectorCache(Oid serverOid) { - bool realClean = false; + bool realClean = false; // 用于标记是否真正清理了缓存 + // 获取 DfsConnectorCacheLock 锁,以独占模式 (void)LWLockAcquire(DfsConnectorCacheLock, LW_EXCLUSIVE); + // 在哈希表 OBSConnectorCache 中查找并移除指定的键(serverOid) ObsOptions *entry = (ObsOptions *)hash_search(OBSConnectorCache, (void *)&serverOid, HASH_REMOVE, NULL); + // 如果找到了指定的键(entry 不为 NULL),则进行清理操作 if (entry != NULL) { + // 如果 access_key 不为 NULL,释放其内存 if (entry->access_key != NULL) { pfree_ext(entry->access_key); } + // 如果 secret_access_key 不为 NULL,使用 OPENSSL_free 释放其内存,并将其置为 NULL if (entry->secret_access_key != NULL) { OPENSSL_free(entry->secret_access_key); entry->secret_access_key = NULL; } + // 如果 address 不为 NULL,释放其内存 if (entry->address != NULL) { pfree_ext(entry->address); } + // 标记已经成功清理缓存 realClean = true; } + // 释放 DfsConnectorCacheLock 锁 LWLockRelease(DfsConnectorCacheLock); + // 返回是否成功清理缓存的标志 return realClean; } } // namespace dfs -- 2.34.1 From 196cd35e8a4bd58c318153bfc6e381587047c7f0 Mon Sep 17 00:00:00 2001 From: nuoya <1204149038@qq.com> Date: Mon, 25 Sep 2023 15:30:20 +0800 Subject: [PATCH 07/50] Update dorado_fd.cpp --- .../storage/dorado_operation/dorado_fd.cpp | 134 ++++++++++++++++-- 1 file changed, 120 insertions(+), 14 deletions(-) diff --git a/src/gausskernel/storage/dorado_operation/dorado_fd.cpp b/src/gausskernel/storage/dorado_operation/dorado_fd.cpp index 9a075d771..fbfd04698 100644 --- a/src/gausskernel/storage/dorado_operation/dorado_fd.cpp +++ b/src/gausskernel/storage/dorado_operation/dorado_fd.cpp @@ -34,58 +34,96 @@ void DoradoReadCtlInfo(ShareStorageXLogCtl *ctlInfo); int DoradoWriteXLog(XLogRecPtr startLsn, char *buf, int writeLen); int DoradoReadXLog(XLogRecPtr startLsn, char *buf, int expectReadLen); void DoradoFsync(); - +/* + * 功能:定义一个静态常量结构体,并初始化其成员函数指针 + */ static const ShareStorageOperateIf doradoOperateIf = { DoradoReadCtlInfo, DoradoWriteCtlInfo, DoradoReadXLog, DoradoWriteXLog, DoradoFsync, }; - +/* + * 功能:计算 Xlog 位置 + * + * 参数列表: + * expect:表示期望的 Xlog 位置 + */ static inline uint64 GetXlogPos(uint64 expect) { + // 计算 Xlog 位置,通过将期望的位置加上常量 DORADO_XLOG_START_POS 得到 return (expect + DORADO_XLOG_START_POS); } - +/* + * 功能:初始化 Dorado 存储 + * + * 参数列表: + * filePath:Xlog 文件的路径 + * fileSize:Xlog 文件的大小 + */ void InitDoradoStorage(char *filePath, uint64 fileSize) { + // 断言确保共享存储操作控制块没有被初始化 Assert(!g_instance.xlog_cxt.shareStorageopCtl.isInit); + // 设置 Xlog 文件路径为传入的 filePath g_instance.xlog_cxt.shareStorageopCtl.xlogFilePath = filePath; - + + // 设置块大小为 MEMORY_ALIGNED_SIZE(一个常量) g_instance.xlog_cxt.shareStorageopCtl.blkSize = MEMORY_ALIGNED_SIZE; + // 设置操作接口为 doradoOperateIf(之前定义的结构体) g_instance.xlog_cxt.shareStorageopCtl.opereateIf = &doradoOperateIf; + // 规范化文件路径,确保它是绝对路径 canonicalize_path(filePath); + // 打开 Xlog 文件,以读写方式打开,二进制模式,使用 O_DIRECT 标志 + // S_IRUSR | S_IWUSR 表示文件权限,S_IRUSR 表示用户有读权限,S_IWUSR 表示用户有写权限 g_instance.xlog_cxt.shareStorageopCtl.fd = open(filePath, O_RDWR | PG_BINARY | O_DIRECT, S_IRUSR | S_IWUSR); + // 如果文件打开失败,报告错误并且终止程序(PANIC) if (g_instance.xlog_cxt.shareStorageopCtl.fd < 0) { ereport(PANIC, (errcode_for_file_access(), errmsg("could not open xlog file \"%s\" : %m", filePath))); } + // 设置共享存储操作控制块的初始化标志为 true g_instance.xlog_cxt.shareStorageopCtl.isInit = true; + // 如果正在进行数据库初始化(IsInitdb 为真),设置 Xlog 文件大小为传入的 fileSize if (IsInitdb) { g_instance.xlog_cxt.shareStorageopCtl.xlogFileSize = fileSize; } else { + // 否则,如果不是初始化数据库,断言确保共享存储 XLog 控制块不为空 Assert(g_instance.xlog_cxt.shareStorageXLogCtl != NULL); + // 调用 DoradoReadCtlInfo 函数读取共享存储 XLog 控制块的信息,并设置 Xlog 文件大小 DoradoReadCtlInfo(g_instance.xlog_cxt.shareStorageXLogCtl); g_instance.xlog_cxt.shareStorageopCtl.xlogFileSize = g_instance.xlog_cxt.shareStorageXLogCtl->xlogFileSize; } } - +/* + * 功能:将共享存储控制信息写入文件 + * + * 参数列表: + * ctlInfo:共享存储 XLog 控制块的指针 + */ void DoradoWriteCtlInfo(const ShareStorageXLogCtl *ctlInfo) { + // 断言确保文件描述符大于 0,即文件已经成功打开 Assert(g_instance.xlog_cxt.shareStorageopCtl.fd > 0); + // 检查共享存储块大小是否与控制块大小对齐,如果不对齐,报告错误并终止程序 if (!IS_TYPE_ALIGINED(g_instance.xlog_cxt.shareStorageopCtl.blkSize, ctlInfo)) { ereport(PANIC, (errcode_for_file_access(), errmsg("dorado write control info ptr(%p) is not match,mask is %X", ctlInfo, g_instance.xlog_cxt.shareStorageopCtl.blkSize))); } + // 检查控制块的魔数和检验码是否匹配,如果不匹配,报告致命错误 if (ctlInfo->magic != SHARE_STORAGE_CTL_MAGIC || ctlInfo->checkNumber != SHARE_STORAGE_CTL_CHCK_NUMBER) { ereport(FATAL, (errmsg("ShareStorageXLogCtl info in memory maybe damaged"))); } + // 计算控制块的 CRC 校验值 pg_crc32c crc = CalShareStorageCtlInfoCrc(ctlInfo); + // 检查计算得到的 CRC 是否与控制块中的 CRC 相匹配,如果不匹配,报告致命错误 if (!EQ_CRC32C(crc, ctlInfo->crc)) { ereport(FATAL, (errmsg("crc check fail for ShareStorageXLogCtl in DoradoWriteCtlInfo"))); } // write 512bytes + // 使用 pwrite 函数将控制块写入文件,写入大小为 DORADO_CTL_WRITE_SIZE 字节,偏移量为 0 ssize_t actualBytes = pwrite(g_instance.xlog_cxt.shareStorageopCtl.fd, ctlInfo, DORADO_CTL_WRITE_SIZE, 0); + // 检查实际写入的字节数是否与期望的字节数相匹配,如果不匹配,报告错误 if (actualBytes != (ssize_t)DORADO_CTL_WRITE_SIZE) { /* if write didn't set errno, assume no disk space */ if (errno == 0) { @@ -97,41 +135,68 @@ void DoradoWriteCtlInfo(const ShareStorageXLogCtl *ctlInfo) (unsigned long)actualBytes, (unsigned long)DORADO_CTL_WRITE_SIZE))); } } - +/* + * 功能:从文件中读取共享存储控制信息 + * + * 参数列表: + * ctlInfo:共享存储 XLog 控制块的指针 + */ void DoradoReadCtlInfo(ShareStorageXLogCtl *ctlInfo) { + // 断言确保文件描述符大于 0,即文件已经成功打开 Assert(g_instance.xlog_cxt.shareStorageopCtl.fd > 0); + // 检查共享存储块大小是否与控制块大小对齐,如果不对齐,报告错误并终止程序 if (!IS_TYPE_ALIGINED(g_instance.xlog_cxt.shareStorageopCtl.blkSize, ctlInfo)) { ereport(PANIC, (errcode_for_file_access(), errmsg("dorado read control info ptr(%p) is not match,mask is %X", ctlInfo, g_instance.xlog_cxt.shareStorageopCtl.blkSize))); } + // 使用 pread 函数从已打开的文件中读取控制块信息,读取大小为 DORADO_CTL_WRITE_SIZE 字节,偏移量为 0 ssize_t actualBytes = pread(g_instance.xlog_cxt.shareStorageopCtl.fd, ctlInfo, DORADO_CTL_WRITE_SIZE, 0); + // 检查实际读取的字节数是否与期望的字节数相匹配,如果不匹配,报告错误并终止程序 if (actualBytes != (ssize_t)DORADO_CTL_WRITE_SIZE) { ereport(PANIC, (errcode_for_file_access(), errmsg("could not read dorado ctl info: %m"))); } + // 检查控制块的魔数和检验码是否匹配,如果不匹配,报告致命错误 if (ctlInfo->magic != SHARE_STORAGE_CTL_MAGIC || ctlInfo->checkNumber != SHARE_STORAGE_CTL_CHCK_NUMBER) { ereport(FATAL, (errmsg("dorado ctl info maybe damaged"))); } + // 计算控制块的 CRC 校验值 pg_crc32c crc = CalShareStorageCtlInfoCrc(ctlInfo); + // 检查计算得到的 CRC 是否与控制块中的 CRC 相匹配,如果不匹配,报告致命错误 if (!EQ_CRC32C(crc, ctlInfo->crc)) { ereport(FATAL, (errmsg("crc check fail for ShareStorageXLogCtl in DoradoReadCtlInfo"))); } } - +/* + * 功能:从文件中读取 XLog 数据 + * + * 参数列表: + * startLsn:开始读取的 XLog 记录位置 + * buf:用于存储读取数据的缓冲区 + * expectReadLen:期望读取的数据长度 + * + * 返回值:成功读取的字节数,或者错误时返回负值 + */ int DoradoReadXLog(XLogRecPtr startLsn, char *buf, int expectReadLen) { + // 断言确保文件描述符大于 0,即文件已经成功打开 Assert(g_instance.xlog_cxt.shareStorageopCtl.fd > 0); + // 检查共享存储块大小是否与缓冲区大小对齐,如果不对齐,报告错误并终止程序 if (!IS_TYPE_ALIGINED(g_instance.xlog_cxt.shareStorageopCtl.blkSize, buf)) { ereport(PANIC, (errcode_for_file_access(), errmsg("dorado read xlog ptr(%p) is not match,mask is %X", buf, g_instance.xlog_cxt.shareStorageopCtl.blkSize))); } + // 计算起始位置,即 startLsn 对 XLog 文件大小取模 uint64 startPos = startLsn % g_instance.xlog_cxt.shareStorageopCtl.xlogFileSize; + // 如果期望读取的数据不会超过文件末尾 if ((startPos + expectReadLen) <= g_instance.xlog_cxt.shareStorageopCtl.xlogFileSize) { + // 使用 pread 函数从已打开的文件中读取数据,读取大小为 expectReadLen 字节,起始位置为 GetXlogPos(startPos) ssize_t actualBytes = pread(g_instance.xlog_cxt.shareStorageopCtl.fd, buf, expectReadLen, GetXlogPos(startPos)); + // 如果读取失败,报告错误并终止程序 if (actualBytes < 0) { uint32 shiftSize = 32; ereport(PANIC, (errcode_for_file_access(), errmsg("read xlog(start:%X/%X, pos:%lu len:%d) failed : %m", @@ -139,11 +204,16 @@ int DoradoReadXLog(XLogRecPtr startLsn, char *buf, int expectReadLen) static_cast(startLsn), startPos, expectReadLen))); } + // 返回成功读取的字节数 return static_cast(actualBytes); - } else { + } else { // 如果期望读取的数据会超过文件末尾,需要分两次读取 + // 第一次读取的大小,从 startPos 到文件末尾 int firstReadSize = g_instance.xlog_cxt.shareStorageopCtl.xlogFileSize - startPos; + // 第二次读取的大小,剩余部分 int secondReadSize = expectReadLen - firstReadSize; + // 第一次读取数据,起始位置为 GetXlogPos(startPos) ssize_t actualBytes = pread(g_instance.xlog_cxt.shareStorageopCtl.fd, buf, firstReadSize, GetXlogPos(startPos)); + // 如果第一次读取失败,报告错误并终止程序 if (actualBytes < 0) { uint32 shiftSize = 32; ereport(PANIC, (errcode_for_file_access(), errmsg("first read xlog(start:%X/%X, pos:%lu len:%d) failed:%m", @@ -151,11 +221,14 @@ int DoradoReadXLog(XLogRecPtr startLsn, char *buf, int expectReadLen) static_cast(startLsn), startPos, firstReadSize))); } + // 如果第一次读取的字节数不足,返回实际读取的字节数 if (actualBytes < firstReadSize) { return static_cast(actualBytes); } + // 第二次读取数据,起始位置为 GetXlogPos(0) actualBytes = pread(t_thrd.xlog_cxt.openLogFile, buf + firstReadSize, secondReadSize, GetXlogPos(0)); + // 如果第二次读取失败,报告错误并终止程序 if (actualBytes < 0) { uint32 shiftSize = 32; XLogRecPtr nextStartLsn = startLsn + firstReadSize; @@ -164,23 +237,40 @@ int DoradoReadXLog(XLogRecPtr startLsn, char *buf, int expectReadLen) static_cast(nextStartLsn), secondReadSize))); } + // 返回成功读取的字节数,包括第一次和第二次读取的部分 return static_cast(actualBytes + firstReadSize); } } - +/* + * 功能:向文件中写入 XLog 数据 + * + * 参数列表: + * startLsn:要写入的 XLog 记录的起始位置 + * buf:包含要写入的数据的缓冲区 + * writeLen:要写入的数据长度 + * + * 返回值:成功写入的数据长度,或者错误时返回负值 + */ int DoradoWriteXLog(XLogRecPtr startLsn, char *buf, int writeLen) { + // 断言确保文件描述符大于 0,即文件已经成功打开 Assert(g_instance.xlog_cxt.shareStorageopCtl.fd > 0); + // 计算起始位置,即 startLsn 对 XLog 文件大小取模 uint64 startPos = startLsn % g_instance.xlog_cxt.shareStorageopCtl.xlogFileSize; + // 检查共享存储块大小是否与缓冲区大小对齐,如果不对齐,报告错误并终止程序 if (!IS_TYPE_ALIGINED(g_instance.xlog_cxt.shareStorageopCtl.blkSize, buf)) { ereport(PANIC, (errcode_for_file_access(), errmsg("dorado write xlog ptr(%p) is not match,mask is %X", buf, g_instance.xlog_cxt.shareStorageopCtl.blkSize))); } + // 如果写入的数据不会超过文件末尾 if ((startPos + writeLen) <= g_instance.xlog_cxt.shareStorageopCtl.xlogFileSize) { + // 使用 pwrite 函数向已打开的文件中写入数据,写入大小为 writeLen 字节,起始位置为 GetXlogPos(startPos) ssize_t actualBytes = pwrite(g_instance.xlog_cxt.shareStorageopCtl.fd, buf, writeLen, GetXlogPos(startPos)); + // 如果写入的字节数不等于期望的字节数,报告错误并终止程序 if (actualBytes != writeLen) { + // 如果写入未设置 errno,假定是磁盘空间不足 if (errno == 0) { errno = ENOSPC; } @@ -190,13 +280,18 @@ int DoradoWriteXLog(XLogRecPtr startLsn, char *buf, int writeLen) static_cast(startLsn >> shiftSize), static_cast(startLsn), startPos, writeLen))); } - } else { + } else { // 如果写入的数据会超过文件末尾,需要分两次写入 + // 第一次写入的大小,从 startPos 到文件末尾 int firstWriteSize = g_instance.xlog_cxt.shareStorageopCtl.xlogFileSize - startPos; + // 第二次写入的大小,剩余部分 int secondWriteSize = writeLen - firstWriteSize; - ssize_t actualBytes = pwrite(g_instance.xlog_cxt.shareStorageopCtl.fd, buf, firstWriteSize, - GetXlogPos(startPos)); + // 第一次写入数据,起始位置为 GetXlogPos(startPos) + ssize_t actualBytes = + pwrite(g_instance.xlog_cxt.shareStorageopCtl.fd, buf, firstWriteSize, GetXlogPos(startPos)); + // 如果第一次写入的字节数不等于第一次写入的大小,报告错误并终止程序 if (actualBytes != firstWriteSize) { + // 如果写入未设置 errno,假定是磁盘空间不足 if (errno == 0) { errno = ENOSPC; } @@ -207,9 +302,12 @@ int DoradoWriteXLog(XLogRecPtr startLsn, char *buf, int writeLen) static_cast(startLsn), startPos, firstWriteSize))); } - actualBytes = pwrite(g_instance.xlog_cxt.shareStorageopCtl.fd, buf + firstWriteSize, secondWriteSize, - GetXlogPos(0)); + // 第二次写入数据,起始位置为 GetXlogPos(0) + actualBytes = + pwrite(g_instance.xlog_cxt.shareStorageopCtl.fd, buf + firstWriteSize, secondWriteSize, GetXlogPos(0)); + // 如果第二次写入的字节数不等于第二次写入的大小,报告错误并终止程序 if (actualBytes != secondWriteSize) { + // 如果写入未设置 errno,假定是磁盘空间不足 if (errno == 0) { errno = ENOSPC; } @@ -222,12 +320,20 @@ int DoradoWriteXLog(XLogRecPtr startLsn, char *buf, int writeLen) } } + // 返回成功写入的数据长度 return writeLen; } +/* + * 功能:将共享存储文件的数据同步到磁盘 + */ + void DoradoFsync() { + // 断言确保文件描述符大于 0,即文件已经成功打开 Assert(g_instance.xlog_cxt.shareStorageopCtl.fd > 0); + + // 使用 fsync 函数将共享存储文件的数据同步到磁盘 if (fsync(g_instance.xlog_cxt.shareStorageopCtl.fd) != 0) { ereport(PANIC, (errcode_for_file_access(), errmsg("could not fsync dorado file %s: %m", g_instance.xlog_cxt.shareStorageopCtl.xlogFilePath))); -- 2.34.1 From fdf406b7d135a1a485a0e11efac9319cbd58afe1 Mon Sep 17 00:00:00 2001 From: nuoya <1204149038@qq.com> Date: Mon, 25 Sep 2023 15:30:46 +0800 Subject: [PATCH 08/50] Update knl_uundofile.cpp --- .../storage/smgr/knl_uundofile.cpp | 538 ++++++++++++++---- 1 file changed, 417 insertions(+), 121 deletions(-) diff --git a/src/gausskernel/storage/smgr/knl_uundofile.cpp b/src/gausskernel/storage/smgr/knl_uundofile.cpp index 5cc6123e2..1785d0349 100644 --- a/src/gausskernel/storage/smgr/knl_uundofile.cpp +++ b/src/gausskernel/storage/smgr/knl_uundofile.cpp @@ -30,16 +30,15 @@ #define UNDOFORMAT(f) UNDODEBUGSTR f UNDODEBUGINFO /* Populate a file tag describing an undofile.cpp segment file. */ -#define INIT_UNDO_FILE_TAG(tag, rNode, segNo) \ - do \ - { \ - errno_t errorno = EOK; \ - errorno = memset_s(&(tag), sizeof(FileTag), (0), sizeof(FileTag)); \ - securec_check(errorno, "\0", "\0"); \ - (tag).handler = SYNC_HANDLER_UNDO; \ - (tag).forknum = MAIN_FORKNUM; \ - (tag).rnode = (rNode); \ - (tag).segno = (segNo); \ +#define INIT_UNDO_FILE_TAG(tag, rNode, segNo) \ + do { \ + errno_t errorno = EOK; \ + errorno = memset_s(&(tag), sizeof(FileTag), (0), sizeof(FileTag)); \ + securec_check(errorno, "\0", "\0"); \ + (tag).handler = SYNC_HANDLER_UNDO; \ + (tag).forknum = MAIN_FORKNUM; \ + (tag).rnode = (rNode); \ + (tag).segno = (segNo); \ } while (false); /* @@ -77,66 +76,106 @@ static const int UNDO_FILE_EXTEND_PAGES = 8; /* local routines */ static UndoFileState *AllocUndoFileState(void); static void GetUndoFilePath(int zoneId, uint32 dbId, int segno, char *path, int len); -static UndoFileState *OpenUndoFile(SMgrRelation reln, ForkNumber forknum, BlockNumber blockNum, ExtensionBehavior behavior); +static UndoFileState *OpenUndoFile(SMgrRelation reln, ForkNumber forknum, BlockNumber blockNum, + ExtensionBehavior behavior); static void RegisterDirtyUndoSegment(SMgrRelation reln, const UndoFileState *state); static void RegisterForgetUndoRequests(RelFileNodeBackend rnode, uint32 segno); static void RegisterUnlinkUndoRequests(RelFileNodeBackend rnode, uint32 segno); static BlockNumber GetUndoFileBlocks(SMgrRelation reln, ForkNumber forknum, const UndoFileState *state); void CheckUndoFileDirectory(UndoPersistence upersistence); - +/* + * 功能:根据数据库ID确定UNDO文件的大小 + * + * 参数列表: + * dbId:数据库ID,用于确定UNDO文件大小的依据 + */ static inline uint32 UNDO_FILE_SIZE(uint32 dbId) { if (dbId == UNDO_DB_OID) { - return UNDO_FILE_MAXSIZE; + return UNDO_FILE_MAXSIZE; // 如果数据库ID等于UNDO_DB_OID,则返回UNDO_FILE_MAXSIZE } - return UNDO_META_MAXSIZE; + return UNDO_META_MAXSIZE; // 否则返回UNDO_META_MAXSIZE } +/* + * 功能:根据数据库ID确定UNDO文件的块数 + * + * 参数列表: + * dbId:数据库ID,用于确定UNDO文件块数的依据 + */ static inline uint32 UNDO_FILE_BLOCK(uint32 dbId) { if (dbId == UNDO_DB_OID) { - return UNDO_FILE_BLOCKS; + return UNDO_FILE_BLOCKS; // 如果数据库ID等于UNDO_DB_OID,则返回UNDO_FILE_BLOCKS } - return UNDO_META_BLOCKS; + return UNDO_META_BLOCKS; // 否则返回UNDO_META_BLOCKS } /* allocate UndoFileState memory. */ +/* + * 功能:分配UndoFileState结构体的内存并返回指针 + * + * 参数列表:无 + */ static UndoFileState *AllocUndoFileState(void) { MemoryContext current; if (EnableLocalSysCache()) { - current = t_thrd.lsc_cxt.lsc->lsc_mydb_memcxt; + current = t_thrd.lsc_cxt.lsc->lsc_mydb_memcxt; // 获取本地系统缓存的内存上下文 } else { - current = u_sess->storage_cxt.UndoFileCxt; + current = u_sess->storage_cxt.UndoFileCxt; // 获取全局UndoFile上下文 } - return (UndoFileState *)MemoryContextAlloc(current, sizeof(UndoFileState)); + return (UndoFileState *)MemoryContextAlloc(current, + sizeof(UndoFileState)); // 分配UndoFileState结构体的内存并返回指针 } - +/* + * 功能:设置UndoFileState结构体的字段值 + * + * 参数列表: + * state:指向UndoFileState结构体的指针 + * segno:文件段号 + * file:文件句柄 + */ static inline void SetUndoFileState(UndoFileState *state, int segno, File file) { - state->segno = segno; - state->file = file; + state->segno = segno; // 设置UndoFileState结构体的segno字段 + state->file = file; // 设置UndoFileState结构体的file字段 } - +/* + * 功能:初始化Undo文件相关的内存上下文 + * + * 参数列表:无 + */ void InitUndoFile(void) { + // 如果启用了本地系统缓存,直接返回,不需要初始化全局Undo文件上下文 if (EnableLocalSysCache()) { return; } + // 确保全局Undo文件上下文尚未初始化 Assert(u_sess->storage_cxt.UndoFileCxt == NULL); - u_sess->storage_cxt.UndoFileCxt = AllocSetContextCreate(u_sess->top_mem_cxt, "UndoFileSmgr", ALLOCSET_DEFAULT_SIZES); + // 创建全局Undo文件上下文,基于当前会话的顶级内存上下文 u_sess->top_mem_cxt + u_sess->storage_cxt.UndoFileCxt = + AllocSetContextCreate(u_sess->top_mem_cxt, "UndoFileSmgr", ALLOCSET_DEFAULT_SIZES); } - +/* + * 功能:获取Undo文件的目录路径 + * + * 参数列表: + * path:用于存储目录路径的缓冲区 + * len:缓冲区长度 + * upersistence:Undo文件的持久性,可以是UNDO_PERMANENT、UNDO_UNLOGGED或UNDO_TEMP + */ void GetUndoFileDirectory(char *path, int len, UndoPersistence upersistence) { Assert(len >= UNDO_FILE_DIR_LEN); errno_t rc = EOK; if (upersistence == UNDO_PERMANENT) { - rc = snprintf_s(path, len, len - 1, UNDO_PERMANENT_DIR); + rc = snprintf_s(path, len, len - 1, UNDO_PERMANENT_DIR); // 设置目录路径为永久Undo文件目录 } else if (upersistence == UNDO_UNLOGGED) { - rc = snprintf_s(path, len, len - 1, UNDO_UNLOGGED_DIR); + rc = snprintf_s(path, len, len - 1, UNDO_UNLOGGED_DIR); // 设置目录路径为非日志Undo文件目录 } else { - rc = snprintf_s(path, len, len - 1, UNDO_TEMP_DIR); + rc = snprintf_s(path, len, len - 1, UNDO_TEMP_DIR); // 设置目录路径为临时Undo文件目录- } securec_check_ss(rc, "\0", "\0"); return; @@ -144,11 +183,13 @@ void GetUndoFileDirectory(char *path, int len, UndoPersistence upersistence) void CheckUndoDirectory(void) { + // 用于测试的条件编译,模拟UNDO_CHECK_DIRECTORY_FAILED错误情况 WHITEBOX_TEST_STUB(UNDO_CHECK_DIRECTORY_FAILED, WhiteboxDefaultErrorEmit); + // 尝试创建Undo文件的根目录,如果创建失败并且错误码不是EEXIST(目录已存在),则报告错误 if (mkdir(UNDO_FILE_DIR_PREFIX, S_IRWXU) < 0 && errno != EEXIST) { - ereport(ERROR, (errcode_for_file_access(), - errmsg("could not create directory \"%s\": %m", UNDO_FILE_DIR_PREFIX))); + ereport(ERROR, + (errcode_for_file_access(), errmsg("could not create directory \"%s\": %m", UNDO_FILE_DIR_PREFIX))); } return; } @@ -156,28 +197,39 @@ void CheckUndoDirectory(void) void CheckUndoFileDirectory(UndoPersistence upersistence) { char dir[UNDO_FILE_DIR_LEN]; - GetUndoFileDirectory(dir, UNDO_FILE_DIR_LEN, upersistence); + GetUndoFileDirectory(dir, UNDO_FILE_DIR_LEN, upersistence); // 获取Undo文件目录路径 + // 用于测试的条件编译,模拟UNDO_CHECK_FILE_DIRECTORY_FAILED错误情况 WHITEBOX_TEST_STUB(UNDO_CHECK_FILE_DIRECTORY_FAILED, WhiteboxDefaultErrorEmit); + // 尝试创建Undo文件目录,如果创建失败并且错误码不是EEXIST(目录已存在),则进行额外处理 if (mkdir(dir, S_IRWXU) != 0 && errno != EEXIST) { + // 如果错误码不是ENOENT(目录不存在)或者不处于XLOG恢复状态,则报告错误 if (errno != ENOENT || !t_thrd.xlog_cxt.InRecovery) { ereport(ERROR, (errcode_for_file_access(), errmsg("could not create directory \"%s\": %m", dir))); } + // 尝试创建Undo文件的根目录,如果创建失败并且错误码不是EEXIST(目录已存在),则报告错误 if (mkdir(UNDO_FILE_DIR_PREFIX, S_IRWXU) < 0 && errno != EEXIST) { - ereport(ERROR, (errcode_for_file_access(), errmsg("could not create directory \"%s\": %m", UNDO_FILE_DIR_PREFIX))); + ereport(ERROR, + (errcode_for_file_access(), errmsg("could not create directory \"%s\": %m", UNDO_FILE_DIR_PREFIX))); } + // 再次尝试创建Undo文件目录,如果创建失败并且错误码不是EEXIST(目录已存在),则报告错误 if (mkdir(dir, S_IRWXU) != 0 && errno != EEXIST) { ereport(ERROR, (errcode_for_file_access(), errmsg("could not create directory \"%s\": %m", dir))); } } return; } - +/* + * 功能:清理Undo文件的目录,如果目录存在的话 + * + * 参数列表: + * upersistence:Undo文件的持久性,可以是UNDO_PERMANENT、UNDO_UNLOGGED或UNDO_TEMP + */ void CleanUndoFileDirectory(UndoPersistence upersistence) { char dir[UNDO_FILE_DIR_LEN]; - GetUndoFileDirectory(dir, UNDO_FILE_DIR_LEN, upersistence); + GetUndoFileDirectory(dir, UNDO_FILE_DIR_LEN, upersistence); // 获取Undo文件目录路径 if (rmdir(dir) < 0 && errno != ENOENT) { /* try again */ if ((rmdir(dir) < 0) && (errno != ENOENT)) { @@ -186,15 +238,29 @@ void CleanUndoFileDirectory(UndoPersistence upersistence) } return; } - +/* + * 功能:获取Undo文件的完整路径 + * + * 参数列表: + * zoneId:Undo文件的区域ID + * dbId:数据库ID,用于区分数据文件和元数据文件 + * segno:文件段号 + * path:用于存储完整路径的缓冲区 + * len:缓冲区长度 + */ void GetUndoFilePath(int zoneId, uint32 dbId, int segno, char *path, int len) { + // 确保缓冲区长度足够大 Assert(len >= UNDO_FILE_PATH_LEN); char dir[UNDO_FILE_DIR_LEN]; errno_t rc = EOK; DECLARE_NODE_COUNT(); - GET_UPERSISTENCE_BY_ZONEID(zoneId, nodeCount); + GET_UPERSISTENCE_BY_ZONEID(zoneId, nodeCount); // 获取持久性类型 + + // 获取Undo文件目录路径 GetUndoFileDirectory(dir, UNDO_FILE_DIR_LEN, upersistence); + + // 根据数据库ID的不同,设置不同的文件名格式 if (dbId == UNDO_DB_OID) { rc = snprintf_s(path, len, len - 1, "%s/%05X.%07zX", dir, zoneId, segno); } else { @@ -203,38 +269,66 @@ void GetUndoFilePath(int zoneId, uint32 dbId, int segno, char *path, int len) securec_check_ss(rc, "\0", "\0"); return; } - +/* + * 功能:获取Undo文件的块数 + * + * 参数列表: + * reln:SMgrRelation对象,用于表示关联的Undo文件 + * forknum:ForkNumber,表示文件的分叉类型 + * + * 返回值:最大块号(MaxBlockNumber),表示文件的块数 + */ BlockNumber GetUndoFileNblocks(SMgrRelation reln, ForkNumber forknum) { - return MaxBlockNumber; + return MaxBlockNumber; // 返回最大块号,表示文件的块数 } /* Get number of blocks present in a single disk undofile. */ +/* + * 功能:获取Undo文件的块数 + * + * 参数列表: + * reln:SMgrRelation对象,用于表示关联的Undo文件 + * forknum:ForkNumber,表示文件的分叉类型 + * state:UndoFileState对象,表示Undo文件的状态信息 + * + * 返回值:BlockNumber,表示文件的块数 + */ static BlockNumber GetUndoFileBlocks(SMgrRelation reln, ForkNumber forknum, const UndoFileState *state) { + // 确保传入的UndoFileState对象不为空 Assert(state != NULL); - + // 获取文件的完整路径 char *fileName = FilePathName(state->file); + // 使用FileSeek函数获取文件的长度 off_t len = FileSeek(state->file, 0L, SEEK_END); + // 获取Undo文件的期望大小 uint32 undoFileSize = UNDO_FILE_SIZE(reln->smgr_rnode.node.dbNode); - + // 用于测试的条件编译,模拟UNDO_GET_FILE_BLOCKS_FAILED错误情况 WHITEBOX_TEST_STUB(UNDO_GET_FILE_BLOCKS_FAILED, WhiteboxDefaultErrorEmit); - + // 检查文件长度是否小于0,如果是则报告错误并关闭文件 if (len < 0) { CloseUndoFile(reln, forknum, InvalidBlockNumber); ereport(ERROR, (errmsg(UNDOFORMAT("could not seek to end of file \"%s\": %m."), fileName))); } - + // 检查文件长度是否符合期望的块大小,如果不符合则报告错误并关闭文件 if (len % BLCKSZ != 0) { CloseUndoFile(reln, forknum, InvalidBlockNumber); ereport(ERROR, (errmsg(UNDOFORMAT("The expected size of file \"%s\" is %d, but the actual size is %ld."), - fileName, undoFileSize, len))); + fileName, undoFileSize, len))); } /* note that this calculation will ignore any partial block at EOF */ - return (BlockNumber)(len / BLCKSZ); + return (BlockNumber)(len / BLCKSZ); // 返回文件的块数 } - +/* + * 功能:创建Undo文件 + * + * 参数列表: + * reln:SMgrRelation对象,用于表示关联的Undo文件 + * forknum:ForkNumber,表示文件的分叉类型 + * isRedo:一个布尔值,指示是否在重做中创建Undo文件 + */ void CreateUndoFile(SMgrRelation reln, ForkNumber forknum, bool isRedo) { /* Undo file creation is managed by ExtendUndoFile. */ @@ -242,13 +336,26 @@ void CreateUndoFile(SMgrRelation reln, ForkNumber forknum, bool isRedo) } /* Create an undo file, expand the file by 8 pages until the file size reaches 1 MB. */ +/* + * 功能:扩展Undo文件 + * + * 参数列表: + * reln:SMgrRelation对象,用于表示关联的Undo文件 + * forknum:ForkNumber,表示文件的分叉类型 + * blockno:要扩展到的块号 + * buffer:用于填充的缓冲区(未使用) + * skipFsync:一个布尔值,指示是否跳过fsync + */ void ExtendUndoFile(SMgrRelation reln, ForkNumber forknum, BlockNumber blockno, char *buffer, bool skipFsync) { + // 确保SMgrRelation对象不为空 Assert(reln != NULL); - + // 获取Undo文件的状态信息 UndoFileState *state = (UndoFileState *)reln->fileState; + // 获取Undo文件的块大小和期望大小 uint32 undoFileBlocks = UNDO_FILE_BLOCK(reln->smgr_rnode.node.dbNode); uint32 undoFileSize = UNDO_FILE_SIZE(reln->smgr_rnode.node.dbNode); + // 初始化变量 int segno = -1; char path[UNDO_FILE_PATH_LEN]; File fd; @@ -258,55 +365,64 @@ void ExtendUndoFile(SMgrRelation reln, ForkNumber forknum, BlockNumber blockno, struct stat statBuffer; BlockNumber blockNum; RelFileNodeForkNum filenode; - + // 如果传入的块号无效,则报告错误 if (blockno == InvalidBlockNumber) { ereport(ERROR, (errmsg(UNDOFORMAT("cannot extend undo file beyond %u blocks."), InvalidBlockNumber))); } - + // 如果Undo文件的状态信息为空,则分配一个新的状态信息对象 if (state == NULL) { state = AllocUndoFileState(); reln->fileState = state; } else if (state->file > 0) { FileClose(state->file); } + // 设置状态信息 SetUndoFileState(state, -1, -1); WHITEBOX_TEST_STUB(UNDO_EXTEND_FILE_FAILED, WhiteboxDefaultErrorEmit); - - ADIO_RUN() { + // 条件编译,根据是否支持ADIO设置标志 + ADIO_RUN() + { flags |= O_DIRECT; } ADIO_END(); - + // 计算扩展块所在的段号 segno = (int)(blockno / undoFileBlocks); - GetUndoFilePath(reln->smgr_rnode.node.relNode, reln->smgr_rnode.node.dbNode, - segno, path, UNDO_FILE_PATH_LEN); + // 获取Undo文件的完整路径 + GetUndoFilePath(reln->smgr_rnode.node.relNode, reln->smgr_rnode.node.dbNode, segno, path, UNDO_FILE_PATH_LEN); + // 构建文件节点对象 filenode = RelFileNodeForkNumFill(reln->smgr_rnode, forknum, segno); + // 打开文件 fd = DataFileIdOpenFile(path, filenode, (int)flags, S_IRUSR | S_IWUSR); + // 如果打开文件失败,则进行错误处理 if (fd < 0) { int saveErrno = errno; DECLARE_NODE_COUNT(); GET_UPERSISTENCE_BY_ZONEID((int)reln->smgr_rnode.node.relNode, nodeCount); CheckUndoFileDirectory(upersistence); fd = DataFileIdOpenFile(path, filenode, (int)flags, S_IRUSR | S_IWUSR); + // 如果再次打开文件失败,则报告错误 if (fd < 0) { /* be sure to report the error reported by create, not open */ + // 如果再次打开文件失败,则报告错误 errno = saveErrno; CloseUndoFile(reln, forknum, InvalidBlockNumber); ereport(ERROR, (errmsg(UNDOFORMAT("could not create file \"%s\": %m."), path))); } } - + // 获取文件状态信息 if (fstat(GetVfdCache()[fd].fd, &statBuffer) < 0) { CloseUndoFile(reln, forknum, InvalidBlockNumber); ereport(ERROR, (errmsg(UNDOFORMAT("could not stat file \"%s\": %m."), path))); } - + // 设置状态信息 SetUndoFileState(state, segno, fd); + // 获取文件的当前位置 seekpos = statBuffer.st_size; char undoBuffer[BLCKSZ] = {'\0'}; /* Extend file to undoFileSize. */ + // 扩展文件到期望大小 while (seekpos < (off_t)undoFileSize) { off_t diffSize = (off_t)undoFileSize - seekpos; if (diffSize < BLCKSZ) { @@ -314,21 +430,25 @@ void ExtendUndoFile(SMgrRelation reln, ForkNumber forknum, BlockNumber blockno, } else { nbytes = FilePWrite(fd, (char *)undoBuffer, BLCKSZ, seekpos, WAIT_EVENT_UNDO_FILE_EXTEND); } + // 扩展文件到期望大小 if (nbytes < 0) { CloseUndoFile(reln, forknum, InvalidBlockNumber); + // 删除创建失败的Undo文件 if (unlink(path) != 0) { - ereport(ERROR, (errmsg(UNDOFORMAT("could not delete undo file during initialization \"%s\": %m."), path))); + ereport(ERROR, + (errmsg(UNDOFORMAT("could not delete undo file during initialization \"%s\": %m."), path))); } ereport(ERROR, (errmsg(UNDOFORMAT("could not initialize undo log segment file \"%s\": %m."), path))); } seekpos += (off_t)nbytes; } - + // 如果不跳过fsync并且不是临时文件,则注册Dirty Undo Segment if (!skipFsync && !SmgrIsTemp(reln)) { RegisterDirtyUndoSegment(reln, state); } - + // 获取文件的块数 blockNum = GetUndoFileBlocks(reln, forknum, state); + // 如果文件的块数不等于期望的块数,则报告PANIC错误 if (blockNum != undoFileBlocks) { ereport(PANIC, (errmsg(UNDOFORMAT("The undo file \"%s\" size is incorrect, blockNum=%u."), path, blockNum))); } @@ -336,26 +456,46 @@ void ExtendUndoFile(SMgrRelation reln, ForkNumber forknum, BlockNumber blockno, } /* Open the undo file and return the UndoFileState. */ -static UndoFileState *OpenUndoFile(SMgrRelation reln, ForkNumber forknum, BlockNumber blockno, ExtensionBehavior behavior) +/* + * 功能:打开Undo文件 + * + * 参数列表: + * reln:SMgrRelation对象,用于表示关联的Undo文件 + * forknum:ForkNumber,表示文件的分叉类型 + * blockno:要打开的块号 + * behavior:扩展行为(用于处理文件不存在的情况) + * + * 返回值:打开的Undo文件的状态信息(UndoFileState对象) + */ +static UndoFileState *OpenUndoFile(SMgrRelation reln, ForkNumber forknum, BlockNumber blockno, + ExtensionBehavior behavior) { + // 确保SMgrRelation对象不为空 Assert(reln != NULL); + // 获取Undo文件的状态信息 UndoFileState *state = (UndoFileState *)reln->fileState; + + // 获取Undo文件的块大小 uint32 undoFileBlocks = UNDO_FILE_BLOCK(reln->smgr_rnode.node.dbNode); + + // 构建Undo文件的路径 char path[UNDO_FILE_PATH_LEN]; File fd; uint32 flags = O_RDWR | PG_BINARY; + // 计算块所在的段号 int segno = (int)(blockno / undoFileBlocks); BlockNumber blockNum; RelFileNodeForkNum filenode; WHITEBOX_TEST_STUB(UNDO_OPEN_FILE_FAILED, WhiteboxDefaultErrorEmit); - + // 如果传入的块号无效,则报告错误 if (blockno == InvalidBlockNumber) { ereport(ERROR, (errmsg(UNDOFORMAT("cannot open undo file beyond %u blocks."), InvalidBlockNumber))); } /* No work if already open */ + // 如果文件已经打开,则直接返回状态信息 if (state != NULL) { if (state->file > 0) { if (state->segno == segno) { @@ -368,35 +508,43 @@ static UndoFileState *OpenUndoFile(SMgrRelation reln, ForkNumber forknum, BlockN state = AllocUndoFileState(); reln->fileState = state; } + // 设置状态信息 SetUndoFileState(state, -1, -1); - - ADIO_RUN() { + // 条件编译,根据是否支持ADIO设置标志 + ADIO_RUN() + { flags |= O_DIRECT; } ADIO_END(); - - GetUndoFilePath(reln->smgr_rnode.node.relNode, reln->smgr_rnode.node.dbNode, - segno, path, UNDO_FILE_PATH_LEN); + // 获取Undo文件的完整路径 + GetUndoFilePath(reln->smgr_rnode.node.relNode, reln->smgr_rnode.node.dbNode, segno, path, UNDO_FILE_PATH_LEN); + // 构建文件节点对象 filenode = RelFileNodeForkNumFill(reln->smgr_rnode, forknum, segno); + // 打开文件 fd = DataFileIdOpenFile(path, filenode, (int)flags, S_IRUSR | S_IWUSR); - + // 如果打开文件失败,则进行错误处理 if (fd < 0) { - if ((behavior == EXTENSION_RETURN_NULL) && FILE_POSSIBLY_DELETED(errno)) { + if ((behavior == EXTENSION_RETURN_NULL) && FILE_POSSIBLY_DELETED(errno)) { // 文件可能已被删除 return NULL; } + // 检查系统是否处于引导处理模式或者正在进行恢复操作 + // 如果是其中之一,表示系统可能处于初始化或者恢复阶段,此时尝试创建文件 if (IsBootstrapProcessingMode() || t_thrd.xlog_cxt.InRecovery) { DECLARE_NODE_COUNT(); GET_UPERSISTENCE_BY_ZONEID((int)reln->smgr_rnode.node.relNode, nodeCount); - CheckUndoFileDirectory(upersistence); - ExtendUndoFile(reln, forknum, blockno, NULL, false); - fd = DataFileIdOpenFile(path, filenode, (int)flags, S_IRUSR | S_IWUSR); + CheckUndoFileDirectory(upersistence); // 用于检查Undo文件目录是否存在,如果不存在则创建它 + ExtendUndoFile(reln, forknum, blockno, NULL, false); // 扩展Undo文件,以适应要读取的块 + fd = DataFileIdOpenFile(path, filenode, (int)flags, + S_IRUSR | S_IWUSR); // 尝试再次打开文件的操作,使用相同的路径和文件节点 } if (fd < 0) { - CloseUndoFile(reln, forknum, InvalidBlockNumber); + CloseUndoFile(reln, forknum, InvalidBlockNumber); // 关闭Undo文件 + // 检查 fetchRecord 是否为 true,表示当前上下文正在尝试获取Undo记录 if (t_thrd.undo_cxt.fetchRecord == true) { - t_thrd.undo_cxt.fetchRecord = false; + t_thrd.undo_cxt.fetchRecord = false; // 表示不再尝试获取Undo记录 if (t_thrd.storage_cxt.InProgressBuf != NULL) { Buffer buffer = BufferDescriptorGetBuffer(t_thrd.storage_cxt.InProgressBuf); + // 终止缓冲区IO操作,但不标记为有效 TerminateBufferIO(t_thrd.storage_cxt.InProgressBuf, false, BM_VALID); ReleaseBuffer(buffer); } @@ -406,16 +554,21 @@ static UndoFileState *OpenUndoFile(SMgrRelation reln, ForkNumber forknum, BlockN } } } - + // 设置状态信息 SetUndoFileState(state, segno, fd); + // 获取文件的块数 blockNum = GetUndoFileBlocks(reln, forknum, state); + // 如果文件的块数小于期望的块数,则报告警告并扩展文件 if (blockNum < undoFileBlocks) { ereport(WARNING, (errmsg(UNDOFORMAT("The undo file \"%s\" size is small than undoFileBlocks, " - "file blockNum=%u, we will extend undo file."), path, blockNum))); + "file blockNum=%u, we will extend undo file."), + path, blockNum))); ExtendUndoFile(reln, forknum, blockno, NULL, false); } else if (blockNum > undoFileBlocks) { + // 如果文件的块数小于期望的块数,则报告警告并扩展文件 ereport(WARNING, (errmsg(UNDOFORMAT("The undo file \"%s\" size is big than undoFileBlocks, " - "file blockNum=%u, we will extend undo file."), path, blockNum))); + "file blockNum=%u, we will extend undo file."), + path, blockNum))); /* close undofile before unlink undo file */ CloseUndoFile(reln, forknum, InvalidBlockNumber); UnlinkUndoFile(reln->smgr_rnode, forknum, true, blockno); @@ -425,43 +578,67 @@ static UndoFileState *OpenUndoFile(SMgrRelation reln, ForkNumber forknum, BlockN } /* Read the specified block from a undo file. */ +/* + * 功能:从Undo文件中读取数据块 + * + * 参数列表: + * reln:SMgrRelation对象,表示关联的Undo文件 + * forknum:ForkNumber,表示文件的分叉类型 + * blockno:要读取的块号 + * buffer:用于存储读取数据的缓冲区 + * + */ SMGR_READ_STATUS ReadUndoFile(SMgrRelation reln, ForkNumber forknum, BlockNumber blockno, char *buffer) { + // 确保缓冲区不为空 Assert(buffer != NULL); - + // 用于存储Undo文件的状态信息 UndoFileState *state = NULL; char *fileName = NULL; off_t seekpos; int nbytes; + // 获取Undo文件的块大小 uint32 undoFileBlocks = UNDO_FILE_BLOCK(reln->smgr_rnode.node.dbNode); - + // 打开Undo文件,如果文件不存在则返回错误 state = OpenUndoFile(reln, forknum, blockno, EXTENSION_FAIL); + // 计算要读取的位置 seekpos = (off_t)BLCKSZ * (blockno % undoFileBlocks); fileName = FilePathName(state->file); - + // 断言要读取的位置在文件有效范围内 Assert(seekpos < (off_t)(undoFileBlocks * BLCKSZ)); - + // 条件编译,根据是否支持ADIO设置标志 WHITEBOX_TEST_STUB(UNDO_READ_FILE_FAILED, WhiteboxDefaultErrorEmit); - + // 从Undo文件中读取数据块 nbytes = FilePRead(state->file, buffer, BLCKSZ, seekpos, WAIT_EVENT_UNDO_FILE_READ); + // 如果读取的字节数不等于期望的块大小,报告错误 if (nbytes != BLCKSZ) { CloseUndoFile(reln, forknum, InvalidBlockNumber); if (nbytes < 0) { ereport(ERROR, (errmsg(UNDOFORMAT("could not read block %u in file \"%s\": %m."), blockno, fileName))); } - ereport(ERROR, (errmsg(UNDOFORMAT("could not read block %u in file \"%s\": read only %d of %d bytes."), - blockno, fileName, nbytes, BLCKSZ))); + ereport(ERROR, (errmsg(UNDOFORMAT("could not read block %u in file \"%s\": read only %d of %d bytes."), blockno, + fileName, nbytes, BLCKSZ))); } - + // 检查读取的数据块是否通过校验,返回相应的状态 if (PageIsVerified((Page)buffer, blockno)) { return SMGR_RD_OK; } else { return SMGR_RD_CRC_ERROR; } } - +/* + * 功能:将数据写入Undo文件指定块 + * + * 参数列表: + * reln:SMgrRelation对象,表示关联的Undo文件 + * forknum:ForkNumber,表示文件的分叉类型 + * blockNum:要写入的块号 + * buffer:包含待写入数据的缓冲区 + * skipFsync:是否跳过Fsync操作的标志 + */ void WriteUndoFile(SMgrRelation reln, ForkNumber forknum, BlockNumber blockNum, const char *buffer, bool skipFsync) { + // 检查输入缓冲区是否为空 Assert(buffer != NULL); UndoFileState *state = NULL; @@ -469,23 +646,31 @@ void WriteUndoFile(SMgrRelation reln, ForkNumber forknum, BlockNumber blockNum, int nbytes; off_t seekpos; uint32 undoFileBlocks = UNDO_FILE_BLOCK(reln->smgr_rnode.node.dbNode); - + // 打开或创建Undo文件,如果文件不存在则返回NULL state = OpenUndoFile(reln, forknum, blockNum, EXTENSION_FAIL); seekpos = (off_t)BLCKSZ * (blockNum % undoFileBlocks); fileName = FilePathName(state->file); - + // 检查写入位置是否合法 Assert(seekpos < (off_t)(undoFileBlocks * BLCKSZ)); - + // 模拟白盒测试:模拟写入文件失败 + /* + * 白盒测试是一种测试手段,用于模拟在测试环境中产生写文件失败的情况, + * 以确保系统在面对异常情况时有适当的处理机制。 + */ WHITEBOX_TEST_STUB(UNDO_WRITE_FILE_FAILED, WhiteboxDefaultErrorEmit); - + // 使用FilePWrite函数将数据写入Undo文件的指定位置 nbytes = FilePWrite(state->file, buffer, BLCKSZ, seekpos, WAIT_EVENT_UNDO_FILE_WRITE); + // 如果写入的字节数不等于块大小,表示写入失败 if (nbytes != BLCKSZ) { + // 关闭Undo文件 CloseUndoFile(reln, forknum, InvalidBlockNumber); + // 如果写入出错,报错并记录错误信息 if (nbytes < 0) { ereport(ERROR, (errmsg(UNDOFORMAT("could not write block %u in file \"%s\": %m."), blockNum, fileName))); } + // 如果写入不完整,报错并记录错误信息 ereport(ERROR, (errmsg(UNDOFORMAT("could not write block %u in file \"%s\": wrote only %d of %d bytes."), - blockNum, fileName, nbytes, BLCKSZ))); + blockNum, fileName, nbytes, BLCKSZ))); } /* Tell checkpointer this file is dirty. */ @@ -494,83 +679,122 @@ void WriteUndoFile(SMgrRelation reln, ForkNumber forknum, BlockNumber blockNum, } return; } - +/* + * 功能:将 Undo 文件的一个或多个数据块写回磁盘 + * + * 参数列表: + * reln:SMgrRelation对象,表示关联的Undo文件 + * forknum:ForkNumber,表示文件的分叉类型 + * blockno:要写回的块的起始块号 + * nblocks:要写回的块的数量 + */ void WritebackUndoFile(SMgrRelation reln, ForkNumber forknum, BlockNumber blockno, BlockNumber nblocks) { + // 获取 Undo 文件块的大小,通常是 8KB uint32 undoFileBlocks = UNDO_FILE_BLOCK(reln->smgr_rnode.node.dbNode); - + // 获取 Undo 文件块的大小,通常是 8KB while (nblocks > 0) { UndoFileState *state = NULL; int segStart; int segEnd; BlockNumber nflush = nblocks; off_t seekpos; - + // 打开或创建 Undo 文件,如果文件不存在,函数返回 NULL state = OpenUndoFile(reln, forknum, blockno, EXTENSION_RETURN_NULL); + // 计算当前写回块的起始段和结束段 segStart = blockno / undoFileBlocks; segEnd = (blockno + nblocks - 1) / undoFileBlocks; /* * We might be flushing buffers of already removed relations, that's * ok, just ignore that case. */ + // 如果当前 Undo 文件不存在(可能已被删除),直接返回 if (state == NULL) { return; } - + // 如果当前写回跨越多个段,计算出要写回的块数量 if (segStart != segEnd) { nflush = undoFileBlocks - (blockno % undoFileBlocks); } - + // 断言要写回的块数量在合理范围内 Assert(nflush >= 1); Assert(nflush <= nblocks); - + // 计算要写回的块的偏移量 seekpos = (off_t)BLCKSZ * (blockno % undoFileBlocks); + // 调用 FileWriteback 函数将块写回 Undo 文件 FileWriteback(state->file, seekpos, (off_t)BLCKSZ * nflush); - + // 更新剩余块数和块号 nblocks -= nflush; blockno += nflush; } } - +/* + * 功能:预取(Prefetch)Undo文件的一个数据块到内存中 + * + * 参数列表: + * reln:SMgrRelation对象,表示关联的Undo文件 + * forknum:ForkNumber,表示文件的分叉类型 + * blockNum:要预取的块号 + */ void PrefetchUndoFile(SMgrRelation reln, ForkNumber forknum, BlockNumber blockNum) { + /* + *检查是否定义了 USE_PREFETCH 宏。如果在编译时定义了这个宏,那么条件成立,后面的代码块会被编译和执行。 + */ #ifdef USE_PREFETCH + // 定义一个指向Undo文件状态结构的指针 UndoFileState *state = NULL; + // 计算要预取的块的偏移量 off_t seekpos; + // 获取Undo文件块的大小,通常是8KB uint32 undoFileBlocks = UNDO_FILE_BLOCK(reln->smgr_rnode.node.dbNode); - + // 打开或创建Undo文件,如果文件不存在,函数返回NULL state = OpenUndoFile(reln, forknum, blockNum, EXTENSION_FAIL); + // 打开或创建Undo文件,如果文件不存在,函数返回NULL seekpos = (off_t)BLCKSZ * (blockNum % undoFileBlocks); - + // 断言块的偏移量在合理范围内 Assert(seekpos < (off_t)(undoFileBlocks * BLCKSZ)); - + // 调用FilePrefetch函数将块预取到内存中 (void)FilePrefetch(state->file, seekpos, BLCKSZ, WAIT_EVENT_UNDO_FILE_PREFETCH); + + // 表示条件编译的结束标记 #endif /* USE_PREFETCH */ } -void UnlinkUndoFile(const RelFileNodeBackend& rnode, ForkNumber forkNum, bool isRedo, BlockNumber blockNum) +void UnlinkUndoFile(const RelFileNodeBackend &rnode, ForkNumber forkNum, bool isRedo, BlockNumber blockNum) { + // 断言块号不为InvalidBlockNumber,即要操作的块号合法 Assert(blockNum != InvalidBlockNumber); + // 定义一个用于存储Undo文件路径的字符数组 char path[UNDO_FILE_PATH_LEN]; + // 获取Undo文件块的大小,通常是8KB uint32 undoFileBlocks = UNDO_FILE_BLOCK(rnode.node.dbNode); + // 用于存储unlink函数的返回值 int ret; + // 从RelFileNodeBackend对象中获取关联Undo文件的Zone ID int zid = rnode.node.relNode; + // 计算块所在的Undo文件段号 int segno = blockNum / undoFileBlocks; + // 获取Undo文件的路径 GetUndoFilePath(zid, rnode.node.dbNode, segno, path, UNDO_FILE_PATH_LEN); - + // 如果正在执行redo操作,或者正在进行原地升级,或者文件分叉类型不是MAIN_FORKNUM if (isRedo || u_sess->attr.attr_common.IsInplaceUpgrade || forkNum != MAIN_FORKNUM || RelFileNodeBackendIsTemp(rnode) || rnode.node.bucketNode != InvalidBktId) { + // 如果文件不是临时文件,注册一个忘记Undo请求 if (!RelFileNodeBackendIsTemp(rnode)) { RegisterForgetUndoRequests(rnode, segno); } + // 报告等待事件,表示正在执行Undo文件删除操作 pgstat_report_waitevent(WAIT_EVENT_UNDO_FILE_UNLINK); + // 尝试删除Undo文件,如果删除失败,尝试再次删除 if (unlink(path) < 0 && errno != ENOENT) { /* try again */ if ((unlink(path) < 0) && (errno != ENOENT) && !isRedo) { ereport(WARNING, (errmsg(UNDOFORMAT("could not remove file \"%s\": %m."), path))); } } + // 报告等待事件,表示Undo文件删除操作结束 pgstat_report_waitevent(WAIT_EVENT_END); } else { /* truncate(2) would be easier here, but Windows hasn't got it */ @@ -578,6 +802,7 @@ void UnlinkUndoFile(const RelFileNodeBackend& rnode, ForkNumber forkNum, bool is fd = BasicOpenFile(path, O_RDWR | PG_BINARY, 0); if (fd >= 0) { int save_errno; + // 截断Undo文件,将其大小截断为0 ret = ftruncate(fd, 0); save_errno = errno; (void)close(fd); @@ -585,16 +810,25 @@ void UnlinkUndoFile(const RelFileNodeBackend& rnode, ForkNumber forkNum, bool is } else { ret = -1; } + // 如果截断操作失败并且不是因为文件不存在,则报错 if (ret < 0 && errno != ENOENT) { ereport(WARNING, (errcode_for_file_access(), errmsg("could not truncate file \"%s\": %m", path))); } /* Register request to unlink first segment later */ + // 注册一个请求,稍后删除第一个Undo文件段 RegisterUnlinkUndoRequests(rnode, segno); } return; } - +/* + * 功能:关闭Undo文件 + * + * 参数列表: + * reln:SMgrRelation对象,表示关联的Undo文件 + * forkNum:ForkNumber,表示文件的分叉类型 + * blockNum:要关闭的块号(可选参数,用于指定关闭哪个块,如果不需要关闭特定块,可以传入InvalidBlockNumber) + */ void CloseUndoFile(SMgrRelation reln, ForkNumber forkNum, BlockNumber blockNum) { Assert(reln != NULL); @@ -605,7 +839,7 @@ void CloseUndoFile(SMgrRelation reln, ForkNumber forkNum, BlockNumber blockNum) if (state == NULL) { return; } - reln->fileState = NULL; /* prevent dangling pointer after error */ + reln->fileState = NULL; /* prevent dangling pointer after error */ /* if not closed already */ if (state->file >= 0) { @@ -615,62 +849,122 @@ void CloseUndoFile(SMgrRelation reln, ForkNumber forkNum, BlockNumber blockNum) } /* Sync undo file. */ +/* + * 功能:同步(刷盘)Undo文件 + * + * 参数列表: + * tag:指向FileTag结构的指针,用于标识Undo文件的位置和属性 + * path:用于存储Undo文件路径的字符数组 + * + */ int SyncUndoFile(const FileTag *tag, char *path) { + // 使用smgropen打开指定Undo文件,reln为SMgrRelation对象 SMgrRelation reln = smgropen(tag->rnode, InvalidBackendId); - uint32 undoFileBlocks = UNDO_FILE_BLOCK(tag->rnode.dbNode); + uint32 undoFileBlocks = UNDO_FILE_BLOCK(tag->rnode.dbNode); // 获取Undo文件块数 - GetUndoFilePath(tag->rnode.relNode, tag->rnode.dbNode, tag->segno, - path, UNDO_FILE_PATH_LEN); - UndoFileState *state = OpenUndoFile(reln, tag->forknum, - tag->segno * undoFileBlocks, EXTENSION_RETURN_NULL); + // 获取指定Undo文件的路径 + GetUndoFilePath(tag->rnode.relNode, tag->rnode.dbNode, tag->segno, path, UNDO_FILE_PATH_LEN); + // 打开指定Undo文件的状态,如果文件不存在则返回NULL + UndoFileState *state = OpenUndoFile(reln, tag->forknum, tag->segno * undoFileBlocks, EXTENSION_RETURN_NULL); + // 如果文件状态为空,表示文件不存在或者未被打开,返回-1表示同步失败 if (state == NULL) { return -1; } - + // 调用FileSync函数对文件进行同步(刷盘)操作,等待同步完成 return FileSync(state->file, WAIT_EVENT_UNDO_FILE_SYNC); } - +/* + * 功能:尝试删除(解链接)Undo文件 + * + * 参数列表: + * tag:指向FileTag结构的指针,用于标识Undo文件的位置和属性 + * path:用于存储Undo文件路径的字符数组 + */ int SyncUnlinkUndoFile(const FileTag *tag, char *path) { - GetUndoFilePath(tag->rnode.relNode, tag->rnode.dbNode, tag->segno, - path, UNDO_FILE_PATH_LEN); + // 获取指定Undo文件的路径 + GetUndoFilePath(tag->rnode.relNode, tag->rnode.dbNode, tag->segno, path, UNDO_FILE_PATH_LEN); /* Try to unlink the file. */ + // 尝试删除(解链接)指定路径的文件,成功返回0,失败返回-1 return unlink(path); } - +/* + * 功能:注册Undo文件的删除请求 + * + * 参数列表: + * rnode:RelFileNodeBackend 结构,表示Undo文件的相关信息 + * segno:uint32,表示Undo文件的段号 + */ static void RegisterUnlinkUndoRequests(RelFileNodeBackend rnode, uint32 segno) { FileTag tag; + // 初始化一个FileTag结构,用于标识Undo文件的位置和属性 INIT_UNDO_FILE_TAG(tag, rnode.node, segno); - (void) RegisterSyncRequest(&tag, SYNC_UNLINK_REQUEST, true /* retryOnError */ ); + // 向同步请求队列中注册删除请求,设置retryOnError参数为true + (void)RegisterSyncRequest(&tag, SYNC_UNLINK_REQUEST, true /* retryOnError */); } - +/* + * 功能:注册忘记Undo文件的请求 + * + * 参数列表: + * rnode:RelFileNodeBackend 结构,表示Undo文件的相关信息 + * segno:uint32,表示Undo文件的段号 + */ static void RegisterForgetUndoRequests(RelFileNodeBackend rnode, uint32 segno) { FileTag tag; + // 初始化一个FileTag结构,用于标识Undo文件的位置和属性 INIT_UNDO_FILE_TAG(tag, rnode.node, segno); - (void) RegisterSyncRequest(&tag, SYNC_FORGET_REQUEST, true /* retryOnError */ ); + // 初始化一个FileTag结构,用于标识Undo文件的位置和属性 + (void)RegisterSyncRequest(&tag, SYNC_FORGET_REQUEST, true /* retryOnError */); } - +/* + * 功能:注册 "脏" 的Undo段的同步请求 + * + * 参数列表: + * reln:SMgrRelation 结构,表示Undo文件的关联关系 + * state:UndoFileState 结构,表示Undo文件的状态信息 + * + * 说明: + * 该函数会将一个 "脏" 的Undo段的同步请求添加到同步请求队列中,以确保Undo段中的数据被写入持久存储。 + * 如果同步请求队列已满,函数将尝试立即执行文件同步操作。 + */ static void RegisterDirtyUndoSegment(SMgrRelation reln, const UndoFileState *state) { /* Temp relations should never be fsync'd */ Assert(!SmgrIsTemp(reln)); FileTag tag; + // 初始化一个FileTag结构,用于标识Undo文件段的位置和属性 INIT_UNDO_FILE_TAG(tag, reln->smgr_rnode.node, state->segno); - - if (!RegisterSyncRequest(&tag, SYNC_REQUEST, false /* retryOnError */ )) { + // 向同步请求队列中注册 "脏" Undo段的同步请求,设置retryOnError参数为false + if (!RegisterSyncRequest(&tag, SYNC_REQUEST, false /* retryOnError */)) { ereport(DEBUG5, (errmsg(UNDOFORMAT("could not forward fsync request because request queue is full.")))); + // 如果同步请求队列已满,尝试立即执行文件同步操作 if (FileSync(state->file, WAIT_EVENT_DATA_FILE_SYNC) < 0) { ereport(ERROR, (errmsg(UNDOFORMAT("could not fsync file \"%s\": %m."), FilePathName(state->file)))); } } } - +/* + * 功能:检查指定的Undo文件是否存在 + * + * 参数列表: + * reln:SMgrRelation 结构,表示Undo文件的关联关系 + * forkNum:ForkNumber,表示文件的分叉类型 + * blockNum:要检查的块号 + * + * 返回值: + * 如果Undo文件存在,返回true;否则,返回false。 + * + * 说明: + * 该函数首先关闭指定的Undo文件,以确保我们注意到如果在打开文件后已被删除。 + * 然后,它尝试打开Undo文件。如果成功打开文件,它将返回true,否则返回false。 + * 最后,它再次关闭Undo文件,以确保文件状态不受影响。 + */ bool CheckUndoFileExists(SMgrRelation reln, ForkNumber forkNum, BlockNumber blockNum) { /* @@ -680,9 +974,11 @@ bool CheckUndoFileExists(SMgrRelation reln, ForkNumber forkNum, BlockNumber bloc CloseUndoFile(reln, forkNum, blockNum); bool isExist = false; + // 尝试打开Undo文件,如果成功打开文件,则将isExist设置为true if (OpenUndoFile(reln, forkNum, blockNum, EXTENSION_RETURN_NULL) != NULL) { isExist = true; } + // 再次关闭文件,以确保文件状态不受影响 CloseUndoFile(reln, forkNum, blockNum); return isExist; } -- 2.34.1 From a4317b822be12a227bf2489436240dcb67c81df9 Mon Sep 17 00:00:00 2001 From: nuoya <1204149038@qq.com> Date: Mon, 25 Sep 2023 15:31:03 +0800 Subject: [PATCH 09/50] Update md.cpp --- src/gausskernel/storage/smgr/md.cpp | 883 ++++++++++++++++++---------- 1 file changed, 579 insertions(+), 304 deletions(-) diff --git a/src/gausskernel/storage/smgr/md.cpp b/src/gausskernel/storage/smgr/md.cpp index 51bd5d1d6..3302a0484 100644 --- a/src/gausskernel/storage/smgr/md.cpp +++ b/src/gausskernel/storage/smgr/md.cpp @@ -44,16 +44,15 @@ #include "pgstat.h" /* Populate a file tag describing an md.cpp segment file. */ -#define INIT_MD_FILETAG(tag, rNode, forkNum, segNo) \ - do \ - { \ - errno_t errorno = EOK; \ - errorno = memset_s(&(tag), sizeof(FileTag), (0), sizeof(FileTag)); \ - securec_check(errorno, "\0", "\0"); \ - (tag).handler = SYNC_HANDLER_MD; \ - (tag).forknum = (forkNum); \ - (tag).rnode = (rNode); \ - (tag).segno = (segNo); \ +#define INIT_MD_FILETAG(tag, rNode, forkNum, segNo) \ + do { \ + errno_t errorno = EOK; \ + errorno = memset_s(&(tag), sizeof(FileTag), (0), sizeof(FileTag)); \ + securec_check(errorno, "\0", "\0"); \ + (tag).handler = SYNC_HANDLER_MD; \ + (tag).forknum = (forkNum); \ + (tag).rnode = (rNode); \ + (tag).segno = (segNo); \ } while (false); constexpr mode_t FILE_RW_PERMISSION = 0600; @@ -105,8 +104,8 @@ inline static uint4 PageCompressChunkSize(SMgrRelation reln) */ typedef struct _MdfdVec { File mdfd_vfd; /* fd number in fd.c's pool */ - File mdfd_vfd_pca; /* page compression address file 's fd number in fd.cpp's pool */ - File mdfd_vfd_pcd; /* page compression data file 's fd number in fd.cpp's pool */ + File mdfd_vfd_pca; /* page compression address file 's fd number in fd.cpp's pool */ + File mdfd_vfd_pcd; /* page compression data file 's fd number in fd.cpp's pool */ BlockNumber mdfd_segno; /* segment number, from 0 */ struct _MdfdVec *mdfd_chain; /* next segment, or NULL */ } MdfdVec; @@ -117,71 +116,156 @@ static MdfdVec *mdopen(SMgrRelation reln, ForkNumber forknum, ExtensionBehavior static MdfdVec *_fdvec_alloc(void); static char *_mdfd_segpath(const SMgrRelation reln, ForkNumber forknum, BlockNumber segno); static MdfdVec *_mdfd_openseg(SMgrRelation reln, ForkNumber forkno, BlockNumber segno, int oflags); -static MdfdVec *_mdfd_getseg(SMgrRelation reln, ForkNumber forkno, BlockNumber blkno, bool skipFsync, ExtensionBehavior behavior); +static MdfdVec *_mdfd_getseg(SMgrRelation reln, ForkNumber forkno, BlockNumber blkno, bool skipFsync, + ExtensionBehavior behavior); static BlockNumber _mdnblocks(SMgrRelation reln, ForkNumber forknum, const MdfdVec *seg); static void register_dirty_segment(SMgrRelation reln, ForkNumber forknum, const MdfdVec *seg); static void register_unlink_segment(RelFileNodeBackend rnode, ForkNumber forknum, BlockNumber segno); /* function of compressed table */ static int sync_pcmap(PageCompressHeader *pcMap, uint32 wait_event_info); - +/* + * 功能:检查指定的Undo文件是否存在 + * + * 参数列表: + * rnode:RelFileNode 结构,表示Undo文件的关联关系 + * forknum:ForkNumber,表示文件的分叉类型 + */ bool check_unlink_rel_hashtbl(RelFileNode rnode, ForkNumber forknum) { - HTAB* relfilenode_hashtbl = g_instance.bgwriter_cxt.unlink_rel_hashtbl; - HTAB* relfilenode_fork_hashtbl = g_instance.bgwriter_cxt.unlink_rel_fork_hashtbl; + // 获取名为unlink_rel_hashtbl的哈希表的指针 + HTAB *relfilenode_hashtbl = g_instance.bgwriter_cxt.unlink_rel_hashtbl; + // 获取名为unlink_rel_fork_hashtbl的哈希表的指针 + HTAB *relfilenode_fork_hashtbl = g_instance.bgwriter_cxt.unlink_rel_fork_hashtbl; + + // 定义一个结构体变量entry_key,用于存储关键字信息 ForkRelFileNode entry_key; + // 布尔变量found用于表示是否找到了关键字,初始值为false bool found = false; + // 获取名为rel_hashtbl_lock的轻量级锁(LWLock),以确保安全访问unlink_rel_hashtbl哈希表 LWLockAcquire(g_instance.bgwriter_cxt.rel_hashtbl_lock, LW_SHARED); + + // 在unlink_rel_hashtbl哈希表中搜索关键字rnode,并将搜索结果存储在found变量中 (void)hash_search(relfilenode_hashtbl, &(rnode), HASH_FIND, &found); + + // 释放rel_hashtbl_lock锁,允许其他线程访问unlink_rel_hashtbl哈希表 LWLockRelease(g_instance.bgwriter_cxt.rel_hashtbl_lock); + // 如果在unlink_rel_hashtbl哈希表中没有找到关键字rnode if (!found) { + // 设置entry_key的rnode和forkNum字段 entry_key.rnode = rnode; entry_key.forkNum = forknum; + + // 获取名为rel_one_fork_hashtbl_lock的轻量级锁(LWLock),以确保安全访问unlink_rel_fork_hashtbl哈希表 LWLockAcquire(g_instance.bgwriter_cxt.rel_one_fork_hashtbl_lock, LW_SHARED); + + // 在unlink_rel_fork_hashtbl哈希表中搜索关键字entry_key,并将搜索结果存储在found变量中 (void)hash_search(relfilenode_fork_hashtbl, &(entry_key), HASH_FIND, &found); + + // 释放rel_one_fork_hashtbl_lock锁,允许其他线程访问unlink_rel_fork_hashtbl哈希表 LWLockRelease(g_instance.bgwriter_cxt.rel_one_fork_hashtbl_lock); } + + // 返回found变量的值,表示是否找到了关键字 return found; } - -static int OpenPcaFile(const char *path, const RelFileNodeBackend &node, const ForkNumber &forkNum, const uint32 &segNo, int oflags = 0) +/* + * 功能:打开指定的PCA文件 + * + * 参数列表: + * path:PCA文件的路径 + * node:RelFileNodeBackend 结构,表示Undo文件的关联关系 + * forkNum:ForkNumber,表示文件的分叉类型 + * segNo:要打开的段号 + * oflags:打开文件的标志位(默认值为0) + */ +static int OpenPcaFile(const char *path, const RelFileNodeBackend &node, const ForkNumber &forkNum, const uint32 &segNo, + int oflags = 0) { + // 断言,确保node的opt字段不为0且forkNum为MAIN_FORKNUM Assert(node.node.opt != 0 && forkNum == MAIN_FORKNUM); + + // 定义一个字符数组dst,用于存储目标文件路径 char dst[MAXPGPATH]; + + // 调用CopyCompressedPath函数,将path复制到dst,并添加COMPRESSED_TABLE_PCA_FILE后缀 CopyCompressedPath(dst, path, COMPRESSED_TABLE_PCA_FILE); + + // 定义一个整数变量flags,用于表示文件打开的标志位 + // O_RDWR表示以可读写方式打开文件 + // PG_BINARY表示以二进制模式打开文件 + // oflags表示其他用户自定义的标志位 uint32 flags = O_RDWR | PG_BINARY | oflags; + + // 调用DataFileIdOpenFile函数,以指定的路径和参数打开文件,并返回文件描述符 return DataFileIdOpenFile(dst, RelFileNodeForkNumFill(node, PCA_FORKNUM, segNo), (int)flags, S_IRUSR | S_IWUSR); } - -static int OpenPcdFile(const char *path, const RelFileNodeBackend &node, const ForkNumber &forkNum, const uint32 &segNo, int oflags = 0) +/* + * 功能:打开指定的PCD文件 + * + * 参数列表: + * path:PCD文件的路径 + * node:RelFileNodeBackend 结构,表示Undo文件的关联关系 + * forkNum:ForkNumber,表示文件的分叉类型 + * segNo:要打开的段号 + * oflags:打开文件的标志位(默认值为0) + */ +static int OpenPcdFile(const char *path, const RelFileNodeBackend &node, const ForkNumber &forkNum, const uint32 &segNo, + int oflags = 0) { + // 断言,确保node的opt字段不为0且forkNum为MAIN_FORKNUM Assert(node.node.opt != 0 && forkNum == MAIN_FORKNUM); + + // 定义一个字符数组dst,用于存储目标文件路径 char dst[MAXPGPATH]; + + // 调用CopyCompressedPath函数,将path复制到dst,并添加COMPRESSED_TABLE_PCD_FILE后缀 CopyCompressedPath(dst, path, COMPRESSED_TABLE_PCD_FILE); + + // 定义一个整数变量flags,用于表示文件打开的标志位 + // O_RDWR表示以可读写方式打开文件 + // PG_BINARY表示以二进制模式打开文件 + // oflags表示其他用户自定义的标志位 uint32 flags = O_RDWR | PG_BINARY | oflags; + + // 调用DataFileIdOpenFile函数,以指定的路径和参数打开文件,并返回文件描述符 return DataFileIdOpenFile(dst, RelFileNodeForkNumFill(node, PCD_FORKNUM, segNo), (int)flags, S_IRUSR | S_IWUSR); } - +/* + * 功能:注册脏段并执行同步操作 + * + * 参数列表: + * reln:SMgrRelation 结构,表示与脏段关联的存储管理器关系 + * forknum:ForkNumber,表示文件的分叉类型 + * seg:MdfdVec 结构,表示脏段的元数据文件描述符向量 + */ static void RegisterCompressDirtySegment(SMgrRelation reln, ForkNumber forknum, const MdfdVec *seg) { + // 获取压缩页的内存映射 PageCompressHeader *pcMap = GetPageCompressMemoryMap(seg->mdfd_vfd_pca, PageCompressChunkSize(reln)); + // 同步压缩页内存映射到磁盘,等待COMPRESS_ADDRESS_FILE_SYNC事件 if (sync_pcmap(pcMap, WAIT_EVENT_COMPRESS_ADDRESS_FILE_SYNC) != 0) { + // 如果同步失败并且需要取消链接文件,输出DEBUG级别的错误消息,然后返回 if (check_unlink_rel_hashtbl(reln->smgr_rnode.node, forknum)) { ereport(DEBUG1, (errmsg("could not fsync file \"%s\": %m", FilePathName(seg->mdfd_vfd)))); return; } - ereport(data_sync_elevel(ERROR), (errcode_for_file_access(), errmsg("could not msync file \"%s\": %m", - FilePathName(seg->mdfd_vfd_pca)))); + // 否则,输出ERROR级别的错误消息,表示同步失败 + ereport(data_sync_elevel(ERROR), (errcode_for_file_access(), + errmsg("could not msync file \"%s\": %m", FilePathName(seg->mdfd_vfd_pca)))); } + // 同步脏段的数据文件到磁盘,等待DATA_FILE_SYNC事件 if (FileSync(seg->mdfd_vfd_pcd, WAIT_EVENT_DATA_FILE_SYNC) < 0) { + // 如果同步失败并且需要取消链接文件,输出DEBUG级别的错误消息,然后返回 if (check_unlink_rel_hashtbl(reln->smgr_rnode.node, forknum)) { ereport(DEBUG1, (errmsg("could not fsync file \"%s\": %m", FilePathName(seg->mdfd_vfd)))); return; } - ereport(data_sync_elevel(ERROR), (errcode_for_file_access(), errmsg("could not fsync file \"%s\": %m", - FilePathName(seg->mdfd_vfd_pcd)))); + // 否则,输出ERROR级别的错误消息,表示同步失败 + ereport(data_sync_elevel(ERROR), (errcode_for_file_access(), + errmsg("could not fsync file \"%s\": %m", FilePathName(seg->mdfd_vfd_pcd)))); } } /* @@ -213,8 +297,8 @@ static void register_dirty_segment(SMgrRelation reln, ForkNumber forknum, const ereport(DEBUG1, (errmsg("could not fsync file \"%s\": %m", FilePathName(seg->mdfd_vfd)))); return; } - ereport(data_sync_elevel(ERROR), (errcode_for_file_access(), - errmsg("could not fsync file \"%s\": %m", FilePathName(seg->mdfd_vfd)))); + ereport(data_sync_elevel(ERROR), (errcode_for_file_access(), errmsg("could not fsync file \"%s\": %m", + FilePathName(seg->mdfd_vfd)))); } } } @@ -223,15 +307,25 @@ static void register_dirty_segment(SMgrRelation reln, ForkNumber forknum, const /* * register_unlink_segment() -- Schedule a file to be deleted after next checkpoint */ +/* + * 功能:注册取消链接段的同步请求 + * + * 参数列表: + * rnode:RelFileNodeBackend 结构,表示与脏段关联的存储管理器关系 + * forknum:ForkNumber,表示文件的分叉类型 + * segno:要取消链接的段号 + */ static void register_unlink_segment(RelFileNodeBackend rnode, ForkNumber forknum, BlockNumber segno) { FileTag tag; - + // 初始化文件标签tag,用于标识要取消链接的文件段 INIT_MD_FILETAG(tag, rnode.node, forknum, segno); /* Should never be used with temp relations */ + // 初始化文件标签tag,用于标识要取消链接的文件段 Assert(!RelFileNodeBackendIsTemp(rnode)); - + // 注册同步请求,要求取消链接指定的文件段 + // SYNC_UNLINK_REQUEST表示取消链接请求,retryOnError表示在错误情况下是否重试 RegisterSyncRequest(&tag, SYNC_UNLINK_REQUEST, true /* retryOnError */); } @@ -245,46 +339,70 @@ void md_register_forget_request(RelFileNode rnode, ForkNumber forknum, BlockNumb RegisterSyncRequest(&tag, SYNC_FORGET_REQUEST, true /* retryOnError */); } - +/* + * 功能:检查分配的块数和块号是否有效 + * + * 参数列表: + * pcAddr:PageCompressAddr 结构,表示压缩页的地址信息 + * chunk_size:uint32,表示块的大小 + * blocknum:BlockNumber,表示块的编号 + * v:MdfdVec 结构,表示脏段的元数据文件描述符向量 + */ static void allocate_chunk_check(PageCompressAddr *pcAddr, uint32 chunk_size, BlockNumber blocknum, MdfdVec *v) { /* check allocated chunk number */ Assert(chunk_size == BLCKSZ / 2 || chunk_size == BLCKSZ / 4 || chunk_size == BLCKSZ / 8 || - chunk_size == BLCKSZ / 16); + chunk_size == BLCKSZ / 16); // 断言,确保chunk_size是有效的块大小,可以是BLCKSZ的2、4、8或16分之一 if (pcAddr->allocated_chunks > BLCKSZ / chunk_size) { - ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("invalid chunks %u of block %u in file \"%s\"", - pcAddr->allocated_chunks, blocknum, - FilePathName(v->mdfd_vfd_pca)))); + // 如果分配的块数大于文件块大小与块大小的比值,报错 + ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("invalid chunks %u of block %u in file \"%s\"", pcAddr->allocated_chunks, blocknum, + FilePathName(v->mdfd_vfd_pca)))); } auto maxChunkNumbers = MAX_CHUNK_NUMBER(chunk_size); for (auto i = 0; i < pcAddr->allocated_chunks; i++) { if (pcAddr->chunknos[i] <= 0 || pcAddr->chunknos[i] > maxChunkNumbers) { - ereport(ERROR, - (errcode(ERRCODE_DATA_CORRUPTED), errmsg("invalid chunk number %u of block %u in file \"%s\"", - pcAddr->chunknos[i], blocknum, - FilePathName(v->mdfd_vfd_pca)))); + // 如果分配的块号小于等于0或大于最大允许块号,报错 + ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("invalid chunk number %u of block %u in file \"%s\"", pcAddr->chunknos[i], blocknum, + FilePathName(v->mdfd_vfd_pca)))); } } } - -int openrepairfile(char* path, RelFileNodeForkNum filenode) +/* + * 功能:打开用于修复的文件 + * + * 参数列表: + * path:要打开的文件路径 + * filenode:RelFileNodeForkNum 结构,表示文件的关联关系和分叉类型 + */ +int openrepairfile(char *path, RelFileNodeForkNum filenode) { + // 初始化文件描述符为-1 int fd = -1; const int TEMPLEN = 8; + // 定义并初始化打开文件的标志位,以支持读写和二进制模式 volatile uint32 repair_flags = O_RDWR | PG_BINARY; + // 分配用于构建临时文件路径的内存 char *temppath = (char *)palloc(strlen(path) + TEMPLEN); + // 使用sprintf_s构建临时文件路径,并检查是否出错 errno_t rc = sprintf_s(temppath, strlen(path) + TEMPLEN, "%s.repair", path); securec_check_ss(rc, "", ""); + // 根据编译器支持的标志位,决定是否使用O_DIRECT ADIO_RUN() { repair_flags |= O_DIRECT; } ADIO_END(); + // 调用DataFileIdOpenFile函数,以临时文件路径和filenode参数打开文件 + // 并返回文件描述符 fd = DataFileIdOpenFile(temppath, filenode, (int)repair_flags, 0600); + // 如果打开文件失败,输出警告消息 if (fd < 0) { ereport(WARNING, (errmsg("[file repair] could not open repair file %s: %m", temppath))); } + // 释放分配的内存 pfree(temppath); return fd; } @@ -317,7 +435,7 @@ bool mdexists(SMgrRelation reln, ForkNumber forkNum, BlockNumber blockNum) return (mdopen(reln, forkNum, EXTENSION_RETURN_NULL) != NULL); } -static int RetryDataFileIdOpenFile(bool isRedo, char* path, const RelFileNodeForkNum &filenode, uint32 flags) +static int RetryDataFileIdOpenFile(bool isRedo, char *path, const RelFileNodeForkNum &filenode, uint32 flags) { int save_errno = errno; int fd = -1; @@ -353,7 +471,6 @@ static int RetryDataFileIdOpenFile(bool isRedo, char* path, const RelFileNodeFor ereport(ERROR, (errcode_for_file_access(), errmsg("could not create file \"%s\": %m", path))); } return fd; - } /* @@ -363,7 +480,7 @@ static int RetryDataFileIdOpenFile(bool isRedo, char* path, const RelFileNodeFor */ void mdcreate(SMgrRelation reln, ForkNumber forkNum, bool isRedo) { - char* path = NULL; + char *path = NULL; File fd; RelFileNodeForkNum filenode; volatile uint32 flags = O_RDWR | O_CREAT | O_EXCL | PG_BINARY; @@ -441,7 +558,7 @@ void mdcreate(SMgrRelation reln, ForkNumber forkNum, bool isRedo) reln->md_fd[forkNum] = _fdvec_alloc(); reln->md_fd[forkNum]->mdfd_vfd_pca = fd_pca; - reln->md_fd[forkNum] ->mdfd_vfd_pcd = fd_pcd; + reln->md_fd[forkNum]->mdfd_vfd_pcd = fd_pcd; reln->md_fd[forkNum]->mdfd_vfd = fd; reln->md_fd[forkNum]->mdfd_segno = 0; reln->md_fd[forkNum]->mdfd_chain = NULL; @@ -494,7 +611,7 @@ void mdcreate(SMgrRelation reln, ForkNumber forkNum, bool isRedo) * Note: any failure should be reported as WARNING not ERROR, because * we are usually not in a transaction anymore when this is called. */ -void mdunlink(const RelFileNodeBackend& rnode, ForkNumber forkNum, bool isRedo, uint32 segno) +void mdunlink(const RelFileNodeBackend &rnode, ForkNumber forkNum, bool isRedo, uint32 segno) { Assert(segno == InvalidBlockNumber); @@ -507,35 +624,59 @@ void mdunlink(const RelFileNodeBackend& rnode, ForkNumber forkNum, bool isRedo, mdunlinkfork(rnode, forkNum, isRedo); } } - +/* + * 功能:设置指定关系和分叉的最大段号 + * + * 参数列表: + * max_segno:要设置的最大段号 + * rnode:RelFileNode 结构,表示关系的关联关系 + * forknum:ForkNumber,表示文件的分叉类型 + */ void set_max_segno_delrel(int max_segno, RelFileNode rnode, ForkNumber forknum) { + // 获取名为unlink_rel_hashtbl的哈希表的指针 HTAB *relfilenode_hashtbl = g_instance.bgwriter_cxt.unlink_rel_hashtbl; - HTAB* relfilenode_fork_hashtbl = g_instance.bgwriter_cxt.unlink_rel_fork_hashtbl; + // 获取名为unlink_rel_fork_hashtbl的哈希表的指针 + HTAB *relfilenode_fork_hashtbl = g_instance.bgwriter_cxt.unlink_rel_fork_hashtbl; + // 定义一个DelFileTag结构体指针entry,用于存储哈希表中的关键字信息 DelFileTag *entry = NULL; + // 定义一个ForkRelFileNode结构体变量entry_key,用于存储关键字信息 ForkRelFileNode entry_key; + // 定义一个DelForkFileTag结构体指针fork_entry,用于存储哈希表中的关键字信息 DelForkFileTag *fork_entry = NULL; + // 布尔变量found用于表示是否找到了关键字,初始值为false bool found = false; - + // 获取名为rel_hashtbl_lock的轻量级锁(LWLock),以确保安全访问unlink_rel_hashtbl哈希表 LWLockAcquire(g_instance.bgwriter_cxt.rel_hashtbl_lock, LW_SHARED); - entry = (DelFileTag*)hash_search(relfilenode_hashtbl, &(rnode), HASH_FIND, &found); + // 在unlink_rel_hashtbl哈希表中搜索关键字rnode,并将搜索结果存储在entry中 + entry = (DelFileTag *)hash_search(relfilenode_hashtbl, &(rnode), HASH_FIND, &found); + // 如果找到了关键字 if (found) { + // 比较max_segno和entry->maxSegNo,如果max_segno更大,更新entry->maxSegNo if (max_segno > entry->maxSegNo) { entry->maxSegNo = max_segno; } } + // 释放rel_hashtbl_lock锁,允许其他线程访问unlink_rel_hashtbl哈希表 LWLockRelease(g_instance.bgwriter_cxt.rel_hashtbl_lock); + // 如果在unlink_rel_hashtbl哈希表中没有找到关键字rnode if (!found) { + // 设置entry_key的rnode和forkNum字段 entry_key.rnode = rnode; entry_key.forkNum = forknum; + // 获取名为rel_one_fork_hashtbl_lock的轻量级锁(LWLock),以确保安全访问unlink_rel_fork_hashtbl哈希表 LWLockAcquire(g_instance.bgwriter_cxt.rel_one_fork_hashtbl_lock, LW_SHARED); - fork_entry = (DelForkFileTag*)hash_search(relfilenode_fork_hashtbl, &(entry_key), HASH_FIND, &found); + // 在unlink_rel_fork_hashtbl哈希表中搜索关键字entry_key,并将搜索结果存储在fork_entry中 + fork_entry = (DelForkFileTag *)hash_search(relfilenode_fork_hashtbl, &(entry_key), HASH_FIND, &found); + // 如果找到了关键字 if (found) { + // 比较max_segno和fork_entry->maxSegNo,如果max_segno更大,更新fork_entry->maxSegNo if (max_segno > fork_entry->maxSegNo) { fork_entry->maxSegNo = max_segno; } } + // 释放rel_one_fork_hashtbl_lock锁,允许其他线程访问unlink_rel_fork_hashtbl哈希表 LWLockRelease(g_instance.bgwriter_cxt.rel_one_fork_hashtbl_lock); } return; @@ -543,7 +684,7 @@ void set_max_segno_delrel(int max_segno, RelFileNode rnode, ForkNumber forknum) /** * set all zero to pca file - * @param fd pca file fd + * @param fd pca file fd * @param chunkSize chunkSize * @param path for errport * @return 0 for success and other for failed @@ -578,7 +719,7 @@ static int ResetPcaFileInner(int fd, int chunkSize, char *path) } /** - * + * * @param fd pca_file fd * @param chunkSize chunkSize destination * @param path for ereport @@ -597,94 +738,143 @@ static bool ReadChunkSizeFromFile(int fd, uint16 *chunkSize, char *path) } return true; } - +/* + * 功能:重置指定路径的压缩页映射文件 + * + * 参数列表: + * path:要重置的文件的路径 + * rnode:RelFileNodeBackend 结构,表示文件的关联关系 + */ static int ResetPcMap(char *path, const RelFileNodeBackend &rnode) { int ret = 0; char pcfile_path[MAXPGPATH]; + // 使用snprintf_s构建压缩页映射文件的路径,并检查是否出错 int rc = snprintf_s(pcfile_path, MAXPGPATH, MAXPGPATH - 1, PCA_SUFFIX, path); securec_check_ss(rc, "\0", "\0"); + // 打开压缩页映射文件,以可读写和二进制模式打开 int fd_pca = BasicOpenFile(pcfile_path, O_RDWR | PG_BINARY, 0); + // 如果打开文件失败,返回-1 if (fd_pca < 0) { return -1; } uint16 chunkSize; + // 从压缩页映射文件中读取块大小 if (ReadChunkSizeFromFile(fd_pca, &chunkSize, pcfile_path)) { + // 从压缩页映射文件中读取块大小 ret = ResetPcaFileInner(fd_pca, chunkSize, pcfile_path); } else { ret = -1; } + // 保存当前的errno值 int save_errno = errno; + // 关闭打开的文件 (void)close(fd_pca); + // 恢复之前保存的errno值 errno = save_errno; - + return ret; } - -static void UnlinkCompressedFile(const RelFileNode& node, ForkNumber forkNum, char* path) +/* + * 功能:删除压缩文件(包括PCA和PCD文件) + * + * 参数列表: + * node:RelFileNode 结构,表示文件的关联关系 + * forkNum:ForkNumber,表示文件的分叉类型 + * path:文件的路径 + */ +static void UnlinkCompressedFile(const RelFileNode &node, ForkNumber forkNum, char *path) { + // 检查文件是否为压缩文件,如果不是,直接返回 if (!IS_COMPRESSED_RNODE(node, forkNum)) { return; } /* remove pca */ char pcfile_path[MAXPGPATH]; + // 检查文件是否为压缩文件,如果不是,直接返回 errno_t rc = snprintf_s(pcfile_path, MAXPGPATH, MAXPGPATH - 1, PCA_SUFFIX, path); securec_check_ss(rc, "\0", "\0"); + // 调用unlink函数,删除PCA文件 int ret = unlink(pcfile_path); + // 如果删除失败,并且错误码不是ENOENT(文件不存在),输出警告消息 if (ret < 0 && errno != ENOENT) { ereport(WARNING, (errcode_for_file_access(), errmsg("could not remove file \"%s\": %m", pcfile_path))); } /* remove pcd */ rc = snprintf_s(pcfile_path, MAXPGPATH, MAXPGPATH - 1, PCD_SUFFIX, path); securec_check_ss(rc, "\0", "\0"); + // 调用unlink函数,删除PCD文件 ret = unlink(pcfile_path); + // 如果删除失败,并且错误码不是ENOENT(文件不存在),输出警告消息 if (ret < 0 && errno != ENOENT) { ereport(WARNING, (errcode_for_file_access(), errmsg("could not remove file \"%s\": %m", pcfile_path))); } } - +/* + * 功能:删除与指定段路径相关的修复文件 + * + * 参数列表: + * segpath:段路径 + */ static void mdcleanrepairfile(char *segpath) { + // 定义常量TEMPELN,表示临时文件名长度 const int TEMPLEN = 8; struct stat statBuf; - + // 分配内存用于构建临时文件路径,其长度为段路径的长度加上TEMPELN char *temppath = (char *)palloc(strlen(segpath) + TEMPLEN); + // 使用sprintf_s构建临时文件路径,并检查是否出错 errno_t rc = sprintf_s(temppath, strlen(segpath) + TEMPLEN, "%s.repair", segpath); securec_check_ss(rc, "", ""); + // 使用stat函数检查临时文件是否存在 if (stat(temppath, &statBuf) >= 0) { + // 如果存在,使用unlink函数删除临时文件 (void)unlink(temppath); - ereport(LOG, (errcode_for_file_access(), - errmsg("remove repair file \"%s\"", temppath))); + // 输出日志消息,表示已删除修复文件 + ereport(LOG, (errcode_for_file_access(), errmsg("remove repair file \"%s\"", temppath))); } + // 释放分配的内存 pfree(temppath); } - -static void mdunlinkfork(const RelFileNodeBackend& rnode, ForkNumber forkNum, bool isRedo) +/* + * 功能:删除指定关联关系和分叉类型的文件 + * + * 参数列表: + * rnode:RelFileNodeBackend 结构,表示文件的关联关系 + * forkNum:ForkNumber,表示文件的分叉类型 + * isRedo:指示是否在重做过程中操作的标志 + */ +static void mdunlinkfork(const RelFileNodeBackend &rnode, ForkNumber forkNum, bool isRedo) { - char* path = NULL; + char *path = NULL; int ret; - + // 获取文件的路径 path = relpath(rnode, forkNum); - + /* * Delete or truncate the first segment. */ Assert(IsHeapFileNode(rnode.node)); + // 如果在重做过程中操作,或者是 inplace 升级,或者不是主分叉,或者文件关联关系是临时的 if (isRedo || u_sess->attr.attr_common.IsInplaceUpgrade || forkNum != MAIN_FORKNUM || RelFileNodeBackendIsTemp(rnode)) { /* First, forget any pending sync requests for the first segment */ + // 如果文件不是临时的,取消对第一个段的同步请求 if (!RelFileNodeBackendIsTemp(rnode)) { md_register_forget_request(rnode.node, forkNum, 0 /* first segment */); } /* Next unlink the file */ - ret = unlink(path); + ret = unlink(path); // 删除文件 + // 如果删除失败,并且错误码不是ENOENT(文件不存在),输出警告消息 if (ret < 0 && errno != ENOENT) { ereport(WARNING, (errcode_for_file_access(), errmsg("could not remove file \"%s\": %m", path))); } + // 如果删除失败,并且错误码不是ENOENT(文件不存在),输出警告消息 if (isRedo) { mdcleanrepairfile(path); } + // 调用UnlinkCompressedFile函数删除压缩文件 UnlinkCompressedFile(rnode.node, forkNum, path); } else { /* truncate(2) would be easier here, but Windows hasn't got it */ @@ -694,16 +884,18 @@ static void mdunlinkfork(const RelFileNodeBackend& rnode, ForkNumber forkNum, bo if (fd >= 0) { int save_errno; - ret = ftruncate(fd, 0); + ret = ftruncate(fd, 0); // 使用ftruncate函数截断文件,用于删除文件内容 save_errno = errno; (void)close(fd); errno = save_errno; } else { ret = -1; } + // 如果截断失败,并且错误码不是ENOENT(文件不存在),输出警告消息 if (ret < 0 && errno != ENOENT) { ereport(WARNING, (errcode_for_file_access(), errmsg("could not truncate file \"%s\": %m", path))); } + // 如果文件是压缩文件,调用ResetPcMap函数重置压缩页映射文件 if (IS_COMPRESSED_RNODE(rnode.node, forkNum)) { // dont truncate pca! pca may be occupied by other threads by mmap ret = ResetPcMap(path, rnode); @@ -722,6 +914,7 @@ static void mdunlinkfork(const RelFileNodeBackend& rnode, ForkNumber forkNum, bo } else { ret = -1; } + // 如果删除失败,并且错误码不是ENOENT,输出警告消息 if (ret < 0 && errno != ENOENT) { ereport(WARNING, (errcode_for_file_access(), errmsg("could not truncate file \"%s\": %m", dataPath))); } @@ -757,7 +950,7 @@ static void mdunlinkfork(const RelFileNodeBackend& rnode, ForkNumber forkNum, bo /* ENOENT is expected after the last segment... */ if (errno != ENOENT) { ereport(WARNING, (errcode_for_file_access(), - errmsg("could not stat file \"%s\" before removing: %m", segpath))); + errmsg("could not stat file \"%s\" before removing: %m", segpath))); } break; } @@ -777,8 +970,7 @@ static void mdunlinkfork(const RelFileNodeBackend& rnode, ForkNumber forkNum, bo rc = sprintf_s(segpath, strlen(path) + 12, "%s.%u", path, segno); securec_check_ss(rc, "", ""); if (unlink(segpath) < 0) { - ereport(WARNING, (errcode_for_file_access(), - errmsg("could not remove file \"%s\": %m", segpath))); + ereport(WARNING, (errcode_for_file_access(), errmsg("could not remove file \"%s\": %m", segpath))); } if (IS_COMPRESSED_RNODE(rnode.node, forkNum)) { char pcfile_segpath[MAXPGPATH]; @@ -806,21 +998,35 @@ static void mdunlinkfork(const RelFileNodeBackend& rnode, ForkNumber forkNum, bo pfree(path); } - -static inline void ExtendChunksOfBlock(PageCompressHeader* pcMap, PageCompressAddr* pcAddr, int needChunks, MdfdVec* v) +/* + * 功能:为指定的块分配额外的压缩块,并将压缩块的状态同步到映射文件 + * + * 参数列表: + * pcMap:PageCompressHeader 结构,表示页压缩映射头部信息 + * pcAddr:PageCompressAddr 结构,表示页的压缩地址信息 + * needChunks:需要分配的压缩块数量 + * v:MdfdVec 结构,表示元数据文件描述符向量 + */ +static inline void ExtendChunksOfBlock(PageCompressHeader *pcMap, PageCompressAddr *pcAddr, int needChunks, MdfdVec *v) { + // 如果已分配的压缩块数量小于所需数量 if (pcAddr->allocated_chunks < needChunks) { auto allocateNumber = needChunks - pcAddr->allocated_chunks; + // 使用原子操作分配压缩块编号 int chunkno = (pc_chunk_number_t)pg_atomic_fetch_add_u32(&pcMap->allocated_chunks, allocateNumber) + 1; + // 分配压缩块编号给地址结构中未分配的位置 for (int i = pcAddr->allocated_chunks; i < needChunks; ++i, ++chunkno) { pcAddr->chunknos[i] = chunkno; } pcAddr->allocated_chunks = needChunks; - + // 如果已分配的压缩块数量与上次同步时的数量之差超过指定阈值 if (pg_atomic_read_u32(&pcMap->allocated_chunks) - pg_atomic_read_u32(&pcMap->last_synced_allocated_chunks) > COMPRESS_ADDRESS_FLUSH_CHUNKS) { + // 标记映射文件需要同步 pcMap->sync = false; + // 标记映射文件需要同步 if (sync_pcmap(pcMap, WAIT_EVENT_COMPRESS_ADDRESS_FILE_FLUSH) != 0) { + // 输出错误消息,表示同步失败 ereport(data_sync_elevel(ERROR), (errcode_for_file_access(), errmsg("could not msync file \"%s\": %m", FilePathName(v->mdfd_vfd_pca)))); } @@ -832,14 +1038,14 @@ static inline void ExtendChunksOfBlock(PageCompressHeader* pcMap, PageCompressAd * mdextend_pc() -- Add a block to the specified page compressed relation. * */ -static void mdextend_pc(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, const char* buffer, bool skipFsync) +static void mdextend_pc(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, const char *buffer, bool skipFsync) { #ifdef CHECK_WRITE_VS_EXTEND Assert(blocknum >= mdnblocks(reln, forknum)); #endif Assert(IS_COMPRESSED_MAINFORK(reln, forknum)); - MdfdVec* v = _mdfd_getseg(reln, MAIN_FORKNUM, blocknum, skipFsync, EXTENSION_CREATE); + MdfdVec *v = _mdfd_getseg(reln, MAIN_FORKNUM, blocknum, skipFsync, EXTENSION_CREATE); RelFileCompressOption option; TransCompressOptions(reln->smgr_rnode.node, &option); uint32 chunk_size = CHUNK_SIZE_LIST[option.compressChunkSize]; @@ -849,23 +1055,22 @@ static void mdextend_pc(SMgrRelation reln, ForkNumber forknum, BlockNumber block Assert(blocknum % RELSEG_SIZE >= pg_atomic_read_u32(&pcMap->nblocks)); uint32 maxAllocChunkNum = (uint32)(BLCKSZ / chunk_size - 1); - PageCompressAddr* pcAddr = GET_PAGE_COMPRESS_ADDR(pcMap, chunk_size, blocknum); + PageCompressAddr *pcAddr = GET_PAGE_COMPRESS_ADDR(pcMap, chunk_size, blocknum); prealloc_chunk = (prealloc_chunk > maxAllocChunkNum) ? maxAllocChunkNum : prealloc_chunk; /* check allocated chunk number */ if (pcAddr->allocated_chunks > BLCKSZ / chunk_size) { - ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("invalid chunks %u of block %u in file \"%s\"", - pcAddr->allocated_chunks, blocknum, - FilePathName(v->mdfd_vfd_pca)))); + ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("invalid chunks %u of block %u in file \"%s\"", pcAddr->allocated_chunks, blocknum, + FilePathName(v->mdfd_vfd_pca)))); } for (int i = 0; i < pcAddr->allocated_chunks; ++i) { if (pcAddr->chunknos[i] <= 0 || pcAddr->chunknos[i] > (BLCKSZ / chunk_size) * RELSEG_SIZE) { - ereport(ERROR, - (errcode(ERRCODE_DATA_CORRUPTED), errmsg("invalid chunk number %u of block %u in file \"%s\"", - pcAddr->chunknos[i], blocknum, - FilePathName(v->mdfd_vfd_pca)))); + ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("invalid chunk number %u of block %u in file \"%s\"", pcAddr->chunknos[i], blocknum, + FilePathName(v->mdfd_vfd_pca)))); } } @@ -875,21 +1080,19 @@ static void mdextend_pc(SMgrRelation reln, ForkNumber forknum, BlockNumber block if (!PageIsNew(buffer)) { int work_buffer_size = CompressPageBufferBound(buffer, algorithm); if (work_buffer_size < 0) { - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("mdextend_pc unrecognized compression algorithm %d", algorithm))); + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("mdextend_pc unrecognized compression algorithm %d", algorithm))); } - work_buffer = (char *) palloc(work_buffer_size); + work_buffer = (char *)palloc(work_buffer_size); int compressed_page_size = CompressPage(buffer, work_buffer, work_buffer_size, option); if (compressed_page_size < 0) { - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("mdextend_pc unrecognized compression algorithm %d", algorithm))); + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("mdextend_pc unrecognized compression algorithm %d", algorithm))); } nchunks = (compressed_page_size - 1) / chunk_size + 1; if (nchunks * chunk_size >= BLCKSZ) { pfree(work_buffer); - work_buffer = (char *) buffer; + work_buffer = (char *)buffer; nchunks = BLCKSZ / chunk_size; } else { /* fill zero in the last chunk */ @@ -909,8 +1112,8 @@ static void mdextend_pc(SMgrRelation reln, ForkNumber forknum, BlockNumber block * worker_buffer = NULL -> nchunks = 0 */ for (int i = 0; i < nchunks; i++) { - char* buffer_pos = work_buffer + chunk_size * i; - off_t seekpos = (off_t) OFFSET_OF_PAGE_COMPRESS_CHUNK(chunk_size, pcAddr->chunknos[i]); + char *buffer_pos = work_buffer + chunk_size * i; + off_t seekpos = (off_t)OFFSET_OF_PAGE_COMPRESS_CHUNK(chunk_size, pcAddr->chunknos[i]); // write continuous chunks int range = 1; while (i < nchunks - 1 && pcAddr->chunknos[i + 1] == pcAddr->chunknos[i] + 1) { @@ -921,14 +1124,15 @@ static void mdextend_pc(SMgrRelation reln, ForkNumber forknum, BlockNumber block int nbytes; if ((nbytes = FileWrite(v->mdfd_vfd_pcd, buffer_pos, write_amount, seekpos)) != write_amount) { if (nbytes < 0) { - ereport(ERROR, (errcode_for_file_access(), errmsg("could not extend file \"%s\": %m", - FilePathName(v->mdfd_vfd_pcd)), errhint( - "Check free disk space."))); + ereport(ERROR, (errcode_for_file_access(), + errmsg("could not extend file \"%s\": %m", FilePathName(v->mdfd_vfd_pcd)), + errhint("Check free disk space."))); } /* short write: complain appropriately */ - ereport(ERROR, (errcode(ERRCODE_DISK_FULL), errmsg( - "could not extend file \"%s\": wrote only %d of %d bytes at block %u", FilePathName(v->mdfd_vfd_pcd), - nbytes, write_amount, blocknum), errhint("Check free disk space."))); + ereport(ERROR, (errcode(ERRCODE_DISK_FULL), + errmsg("could not extend file \"%s\": wrote only %d of %d bytes at block %u", + FilePathName(v->mdfd_vfd_pcd), nbytes, write_amount, blocknum), + errhint("Check free disk space."))); } } @@ -940,7 +1144,6 @@ static void mdextend_pc(SMgrRelation reln, ForkNumber forknum, BlockNumber block /* write checksum */ pcAddr->checksum = AddrChecksum32(blocknum, pcAddr, chunk_size); - if (pg_atomic_read_u32(&pcMap->nblocks) < blocknum % RELSEG_SIZE + 1) { pg_atomic_write_u32(&pcMap->nblocks, blocknum % RELSEG_SIZE + 1); } @@ -954,7 +1157,7 @@ static void mdextend_pc(SMgrRelation reln, ForkNumber forknum, BlockNumber block register_dirty_segment(reln, forknum, v); } - Assert(_mdnblocks(reln, forknum, v) <= ((BlockNumber) RELSEG_SIZE)); + Assert(_mdnblocks(reln, forknum, v) <= ((BlockNumber)RELSEG_SIZE)); } /* @@ -966,8 +1169,7 @@ static void mdextend_pc(SMgrRelation reln, ForkNumber forknum, BlockNumber block * EOF). Note that we assume writing a block beyond current EOF * causes intervening file space to become filled with zeroes. */ -void mdextend(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, - char *buffer, bool skipFsync) +void mdextend(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, char *buffer, bool skipFsync) { off_t seekpos; int nbytes; @@ -1011,20 +1213,19 @@ void mdextend(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, */ if ((nbytes = FilePWrite(v->mdfd_vfd, buffer, BLCKSZ, seekpos, WAIT_EVENT_DATA_FILE_EXTEND)) != BLCKSZ) { if (check_unlink_rel_hashtbl(reln->smgr_rnode.node, forknum)) { - ereport(DEBUG1, - (errmsg("could not extend file \"%s\": %m, this relation has been removed", - FilePathName(v->mdfd_vfd)))); + ereport(DEBUG1, (errmsg("could not extend file \"%s\": %m, this relation has been removed", + FilePathName(v->mdfd_vfd)))); } else { if (nbytes < 0) { - ereport(ERROR, - (errcode_for_file_access(), errmsg("could not extend file \"%s\": %m", FilePathName(v->mdfd_vfd)), - errhint("Check free disk space."))); + ereport(ERROR, (errcode_for_file_access(), + errmsg("could not extend file \"%s\": %m", FilePathName(v->mdfd_vfd)), + errhint("Check free disk space."))); } /* short write: complain appropriately */ ereport(ERROR, (errcode(ERRCODE_DISK_FULL), - errmsg("could not extend file \"%s\": wrote only %d of %d bytes at block %u", - FilePathName(v->mdfd_vfd), nbytes, BLCKSZ, blocknum), - errhint("Check free disk space."))); + errmsg("could not extend file \"%s\": wrote only %d of %d bytes at block %u", + FilePathName(v->mdfd_vfd), nbytes, BLCKSZ, blocknum), + errhint("Check free disk space."))); } } @@ -1082,15 +1283,15 @@ static File mdopenagain(SMgrRelation reln, ForkNumber forknum, ExtensionBehavior return fd; } -static int MdOpenRetryOpenFile(char* path, const RelFileNodeForkNum &filenode, ExtensionBehavior behavior, uint32 flags) +static int MdOpenRetryOpenFile(char *path, const RelFileNodeForkNum &filenode, ExtensionBehavior behavior, uint32 flags) { int fd = -1; /* - * During bootstrap, there are cases where a system relation will be - * accessed (by internal backend processes) before the bootstrap - * script nominally creates it. Therefore, accept mdopen() as a - * substitute for mdcreate() in bootstrap mode only. (See mdcreate) - */ + * During bootstrap, there are cases where a system relation will be + * accessed (by internal backend processes) before the bootstrap + * script nominally creates it. Therefore, accept mdopen() as a + * substitute for mdcreate() in bootstrap mode only. (See mdcreate) + */ if (IsBootstrapProcessingMode()) { flags |= (O_CREAT | O_EXCL); fd = DataFileIdOpenFile(path, filenode, (int)flags, FILE_RW_PERMISSION); @@ -1199,9 +1400,17 @@ static MdfdVec *mdopen(SMgrRelation reln, ForkNumber forknum, ExtensionBehavior /* * mdclose() -- Close the specified relation, if it isn't closed already. */ +/* + * 功能:关闭指定关联关系、分叉类型和块号的文件描述符 + * + * 参数列表: + * reln:SMgrRelation 结构,表示文件的存储管理器关系 + * forknum:ForkNumber,表示文件的分叉类型 + * blockNum:BlockNumber,表示块号 + */ void mdclose(SMgrRelation reln, ForkNumber forknum, BlockNumber blockNum) { - MdfdVec *v = reln->md_fd[forknum]; + MdfdVec *v = reln->md_fd[forknum]; // 获取文件描述符向量 /* No work if already closed */ if (v == NULL) { @@ -1210,20 +1419,20 @@ void mdclose(SMgrRelation reln, ForkNumber forknum, BlockNumber blockNum) reln->md_fd[forknum] = NULL; /* prevent dangling pointer after error */ - while (v != NULL) { + while (v != NULL) { // 获取文件描述符向量 MdfdVec *ov = v; /* if not closed already */ if (IS_COMPRESSED_MAINFORK(reln, forknum)) { - if (v->mdfd_vfd_pca >= 0) { - FileClose(v->mdfd_vfd_pca); + if (v->mdfd_vfd_pca >= 0) { // 如果压缩地址文件描述符有效 + FileClose(v->mdfd_vfd_pca); // 关闭压缩地址文件 } - if (v->mdfd_vfd_pcd >= 0) { - FileClose(v->mdfd_vfd_pcd); + if (v->mdfd_vfd_pcd >= 0) { // 如果压缩地址文件描述符有效 + FileClose(v->mdfd_vfd_pca); // 关闭压缩地址文件 } } else { - if (v->mdfd_vfd >= 0) { - FileClose(v->mdfd_vfd); + if (v->mdfd_vfd >= 0) { // 如果文件描述符有效 + FileClose(v->mdfd_vfd); // 关闭文件 } } @@ -1236,21 +1445,29 @@ void mdclose(SMgrRelation reln, ForkNumber forknum, BlockNumber blockNum) /* * mdprefetch() -- Initiate asynchronous read of the specified block of a relation */ +/* + * 功能:预取指定关联关系、分叉类型和块号的文件块数据 + * + * 参数列表: + * reln:SMgrRelation 结构,表示文件的存储管理器关系 + * forknum:ForkNumber,表示文件的分叉类型 + * blocknum:BlockNumber,表示块号 + */ void mdprefetch(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum) { -#ifdef USE_PREFETCH +#ifdef USE_PREFETCH // 如果启用了预取功能 off_t seekpos; MdfdVec *v = NULL; - v = _mdfd_getseg(reln, forknum, blocknum, false, EXTENSION_FAIL); + v = _mdfd_getseg(reln, forknum, blocknum, false, EXTENSION_FAIL); // 获取文件描述符向量 if (v == NULL) { return; } - if (IS_COMPRESSED_MAINFORK(reln, forknum)) { - int chunk_size = PageCompressChunkSize(reln); - PageCompressHeader *pcMap = GetPageCompressMemoryMap(v->mdfd_vfd_pca, chunk_size); - PageCompressAddr *pcAddr = GET_PAGE_COMPRESS_ADDR(pcMap, chunk_size, blocknum); + if (IS_COMPRESSED_MAINFORK(reln, forknum)) { // 如果是压缩文件 + int chunk_size = PageCompressChunkSize(reln); // 如果是压缩文件 + PageCompressHeader *pcMap = GetPageCompressMemoryMap(v->mdfd_vfd_pca, chunk_size); // 获取压缩映射头部 + PageCompressAddr *pcAddr = GET_PAGE_COMPRESS_ADDR(pcMap, chunk_size, blocknum); // 获取压缩地址信息 /* check chunk number */ if (pcAddr->nchunks < 0 || pcAddr->nchunks > BLCKSZ / chunk_size) { if (u_sess->attr.attr_security.zero_damaged_pages || t_thrd.xlog_cxt.InRecovery) { @@ -1263,6 +1480,7 @@ void mdprefetch(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum) } for (uint8 i = 0; i < pcAddr->nchunks; i++) { + /* 检查压缩块编号是否合法 */ if (pcAddr->chunknos[i] <= 0 || pcAddr->chunknos[i] > (uint32)(BLCKSZ / chunk_size) * RELSEG_SIZE) { if (u_sess->attr.attr_security.zero_damaged_pages || t_thrd.xlog_cxt.InRecovery) { return; @@ -1278,13 +1496,14 @@ void mdprefetch(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum) range++; i++; } + /* 检查压缩块编号是否合法 */ (void)FilePrefetch(v->mdfd_vfd_pcd, seekpos, chunk_size * range, WAIT_EVENT_DATA_FILE_PREFETCH); } - } else { + } else { // 如果不是压缩文件 seekpos = (off_t)BLCKSZ * (blocknum % ((BlockNumber)RELSEG_SIZE)); Assert(seekpos < (off_t)BLCKSZ * RELSEG_SIZE); - + /* 使用FilePrefetch函数预取文件数据 */ (void)FilePrefetch(v->mdfd_vfd, seekpos, BLCKSZ, WAIT_EVENT_DATA_FILE_PREFETCH); } #endif /* USE_PREFETCH */ @@ -1361,8 +1580,8 @@ void mdwriteback(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, Bl FileWriteback(v->mdfd_vfd_pcd, seekpos, (off_t)nchunks * chunk_size); } } else { - seekpos = (off_t) BLCKSZ * (blocknum % ((BlockNumber) RELSEG_SIZE)); - FileWriteback(v->mdfd_vfd, seekpos, (off_t) BLCKSZ * nflush); + seekpos = (off_t)BLCKSZ * (blocknum % ((BlockNumber)RELSEG_SIZE)); + FileWriteback(v->mdfd_vfd, seekpos, (off_t)BLCKSZ * nflush); } nblocks -= nflush; blocknum += nflush; @@ -1614,82 +1833,106 @@ int CompltrWriteReq(void *aioDesc, long res) return 0; } -const int FILE_NAME_LEN = 128; +const int FILE_NAME_LEN = 128; // 定义文件名的最大长度为128 +/* + * 功能:检查指定文件的状态信息并将结果记录到日志中 + * + * 参数列表: + * file_name:char 指针,表示要检查的文件名 + */ static void check_file_stat(char *file_name) { int rc; - struct stat stat_buf; - char file_path[MAX_PATH_LEN] = {0}; - char strfbuf[FILE_NAME_LEN]; + struct stat stat_buf; // 定义文件名的最大长度为128 + char file_path[MAX_PATH_LEN] = {0}; // 定义文件名的最大长度为128 + char strfbuf[FILE_NAME_LEN]; // 存储时间戳的字符数组 + // 如果数据目录或文件名为空,则直接返回 if (t_thrd.proc_cxt.DataDir == NULL || file_name == NULL) { return; } + // 构建文件的完整路径 rc = snprintf_s(file_path, MAX_PATH_LEN, MAX_PATH_LEN - 1, "%s/%s", t_thrd.proc_cxt.DataDir, file_name); securec_check_ss(rc, "", ""); + // 使用stat函数获取文件的状态信息 if (stat(file_path, &stat_buf) == 0) { - pg_time_t stamp_time = (pg_time_t)stat_buf.st_mtime; + pg_time_t stamp_time = (pg_time_t)stat_buf.st_mtime; // 使用stat函数获取文件的状态信息 if (log_timezone != NULL) { - struct pg_tm *tm = pg_localtime(&stamp_time, log_timezone); + struct pg_tm *tm = pg_localtime(&stamp_time, log_timezone); // 转换时间戳为本地时间 if (tm != NULL) { + // 格式化时间戳为字符串,包括日期、时间和时区信息 (void)pg_strftime(strfbuf, sizeof(strfbuf), "%Y-%m-%d %H:%M:%S %Z", tm); ereport(LOG, (errmsg("file \"%s\" size is %ld bytes, last modify time is %s.", file_name, stat_buf.st_size, strfbuf))); } else { + // 如果无法获取本地时间,只记录文件名和大小 ereport(LOG, (errmsg("file \"%s\" size is %ld bytes.", file_name, stat_buf.st_size))); } } else { + // 如果无法获取时区信息,只记录文件名和大小 ereport(LOG, (errmsg("file \"%s\" size is %ld bytes.", file_name, stat_buf.st_size))); } } else { + // 如果无法获取文件状态信息,记录错误信息 ereport(LOG, (errmsg("could not stat the file : \"%s\".", file_name))); } } -#define CONTINUOUS_ASSIGN_2(a, b, value) do { \ - (a) = (value); \ - (b) = (value); \ -} while (0) +#define CONTINUOUS_ASSIGN_2(a, b, value) \ + do { \ + (a) = (value); \ + (b) = (value); \ + } while (0) -#define CONTINUOUS_ASSIGN_3(a, b, c, value) do { \ - (a) = (value); \ - (b) = (value); \ - (c) = (value); \ -} while (0) +#define CONTINUOUS_ASSIGN_3(a, b, c, value) \ + do { \ + (a) = (value); \ + (b) = (value); \ + (c) = (value); \ + } while (0) /* * mdread_pc() -- Read the specified block from a page compressed relation. */ +/* + * 功能: 从压缩存储中读取压缩块并解压到给定缓冲区中 + * + * 功能列表: + * reln: 存储管理器关系对象 + * forknum: 文件句柄编号(例如主要句柄或其他句柄) + * blocknum: 要读取的块号 + * buffer: 存储读取并解压后的数据的缓冲区 + */ bool mdread_pc(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, char *buffer) { - Assert(IS_COMPRESSED_MAINFORK(reln, forknum)); + Assert(IS_COMPRESSED_MAINFORK(reln, forknum)); // 确保文件是压缩文件 - MdfdVec *v = _mdfd_getseg(reln, forknum, blocknum, false, EXTENSION_FAIL); + MdfdVec *v = _mdfd_getseg(reln, forknum, blocknum, false, EXTENSION_FAIL); // 获取文件段描述符 RelFileCompressOption option; - TransCompressOptions(reln->smgr_rnode.node, &option); - uint32 chunk_size = CHUNK_SIZE_LIST[option.compressChunkSize]; - uint8 algorithm = option.compressAlgorithm; - PageCompressHeader *pcMap = GetPageCompressMemoryMap(v->mdfd_vfd_pca, chunk_size); - PageCompressAddr *pcAddr = GET_PAGE_COMPRESS_ADDR(pcMap, chunk_size, blocknum); - uint8 nchunks = pcAddr->nchunks; + TransCompressOptions(reln->smgr_rnode.node, &option); // 转换压缩选项 + uint32 chunk_size = CHUNK_SIZE_LIST[option.compressChunkSize]; // 获取压缩块大小 + uint8 algorithm = option.compressAlgorithm; // 获取压缩算法 + PageCompressHeader *pcMap = GetPageCompressMemoryMap(v->mdfd_vfd_pca, chunk_size); // 获取压缩页内存映射 + PageCompressAddr *pcAddr = GET_PAGE_COMPRESS_ADDR(pcMap, chunk_size, blocknum); // 获取压缩页地址信息 + uint8 nchunks = pcAddr->nchunks; // 压缩页中块的数量 if (nchunks == 0) { MemSet(buffer, 0, BLCKSZ); - return true; + return true; // 如果块的数量为0,直接填充0并返回 } if (nchunks > BLCKSZ / chunk_size) { if (u_sess->attr.attr_security.zero_damaged_pages || t_thrd.xlog_cxt.InRecovery) { MemSet(buffer, 0, BLCKSZ); - return true; + return true; // 如果块的数量超出预期,并且允许零损坏页面或者处于恢复模式,直接填充0并返回 } else { #ifndef ENABLE_MULTIPLE_NODES if (RecoveryInProgress()) { - return false; + return false; // 如果处于恢复模式,返回false } #endif - ereport(ERROR, - (errcode(ERRCODE_DATA_CORRUPTED), errmsg("invalid chunks %u of block %u in file \"%s\"", nchunks, - blocknum, FilePathName(v->mdfd_vfd_pca)))); + // 否则,报告数据损坏错误 + ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("invalid chunks %u of block %u in file \"%s\"", + nchunks, blocknum, FilePathName(v->mdfd_vfd_pca)))); } } @@ -1697,18 +1940,19 @@ bool mdread_pc(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, char if (pcAddr->chunknos[i] <= 0 || pcAddr->chunknos[i] > MAX_CHUNK_NUMBER(chunk_size)) { if (u_sess->attr.attr_security.zero_damaged_pages || t_thrd.xlog_cxt.InRecovery) { MemSet(buffer, 0, BLCKSZ); - return true; + return true; // 如果块的编号无效,并且允许零损坏页面或者处于恢复模式,直接填充0并返回 } else { - check_file_stat(FilePathName(v->mdfd_vfd_pcd)); + check_file_stat(FilePathName(v->mdfd_vfd_pcd)); // 检查文件状态 force_backtrace_messages = true; #ifndef ENABLE_MULTIPLE_NODES if (RecoveryInProgress()) { - return false; + return false; // 检查文件状态 } #endif - ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("invalid chunks %u of block %u in file \"%s\"", - nchunks, blocknum, - FilePathName(v->mdfd_vfd_pca)))); + // 检查文件状态 + ereport(ERROR, + (errcode(ERRCODE_DATA_CORRUPTED), errmsg("invalid chunks %u of block %u in file \"%s\"", + nchunks, blocknum, FilePathName(v->mdfd_vfd_pca)))); } } } @@ -1717,10 +1961,10 @@ bool mdread_pc(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, char char *buffer_pos = NULL; uint8 start; int read_amount; - char *compress_buffer = (char*)palloc(chunk_size * nchunks); + char *compress_buffer = (char *)palloc(chunk_size * nchunks); // 检查文件状态 for (uint8 i = 0; i < nchunks; ++i) { buffer_pos = compress_buffer + chunk_size * i; - off_t seekpos = (off_t) OFFSET_OF_PAGE_COMPRESS_CHUNK(chunk_size, pcAddr->chunknos[i]); + off_t seekpos = (off_t)OFFSET_OF_PAGE_COMPRESS_CHUNK(chunk_size, pcAddr->chunknos[i]); start = i; while (i < nchunks - 1 && pcAddr->chunknos[i + 1] == pcAddr->chunknos[i] + 1) { i++; @@ -1741,9 +1985,8 @@ bool mdread_pc(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, char return false; } #endif - ereport(ERROR, - (errcode_for_file_access(), errmsg("could not read block %u in file \"%s\": %m", blocknum, - FilePathName(v->mdfd_vfd_pcd)))); + ereport(ERROR, (errcode_for_file_access(), errmsg("could not read block %u in file \"%s\": %m", + blocknum, FilePathName(v->mdfd_vfd_pcd)))); } /* * Short read: we are at or past EOF, or we read a partial block at @@ -1765,9 +2008,9 @@ bool mdread_pc(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, char return false; } #endif - ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg( - "could not read block %u in file \"%s\": read only %d of %d bytes", blocknum, - FilePathName(v->mdfd_vfd_pcd), nbytes, read_amount))); + ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("could not read block %u in file \"%s\": read only %d of %d bytes", blocknum, + FilePathName(v->mdfd_vfd_pcd), nbytes, read_amount))); } } } @@ -1781,9 +2024,9 @@ bool mdread_pc(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, char nbytes = DecompressPage(compress_buffer, buffer, algorithm); if (nbytes != BLCKSZ) { if (nbytes == -2) { - ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg( - "could not recognized compression algorithm %d for file \"%s\"", algorithm, - FilePathName(v->mdfd_vfd_pcd)))); + ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("could not recognized compression algorithm %d for file \"%s\"", algorithm, + FilePathName(v->mdfd_vfd_pcd)))); } if (u_sess->attr.attr_security.zero_damaged_pages || t_thrd.xlog_cxt.InRecovery) { pfree(compress_buffer); @@ -1798,9 +2041,9 @@ bool mdread_pc(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, char return false; } #endif - ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg( - "could not decompress block %u in file \"%s\": decompress %d of %d bytes", blocknum, - FilePathName(v->mdfd_vfd_pcd), nbytes, BLCKSZ))); + ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("could not decompress block %u in file \"%s\": decompress %d of %d bytes", + blocknum, FilePathName(v->mdfd_vfd_pcd), nbytes, BLCKSZ))); } } } @@ -1811,60 +2054,73 @@ bool mdread_pc(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, char /* * mdread() -- Read the specified block from a relation. */ +/* + * 功能:从存储管理器中读取指定块的内容到缓冲区,并进行校验。 + * + * 参数列表: + * reln: SMgrRelation 结构体,表示存储管理器关系。 + * forknum: ForkNumber 值,表示要读取的分支号。 + * blocknum: BlockNumber 值,表示要读取的块号。 + * buffer: char 指针,用于接收读取的块数据的缓冲区。 + * + * 返回值: + * SMGR_READ_STATUS 枚举,表示读取操作的结果状态。 + */ SMGR_READ_STATUS mdread(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, char *buffer) { - off_t seekpos; - int nbytes; - MdfdVec *v = NULL; + off_t seekpos; // 声明变量 seekpos,用于文件定位 + int nbytes; // 声明变量 nbytes,用于存储读取的字节数 + MdfdVec *v = NULL; // 声明文件描述符向量指针,并初始化为 NULL - instr_time startTime; - instr_time endTime; - PgStat_Counter timeDiff = 0; - static THR_LOCAL PgStat_Counter msgCount = 0; - static THR_LOCAL PgStat_Counter sumPage = 0; - static THR_LOCAL PgStat_Counter sumTime = 0; - static THR_LOCAL PgStat_Counter lstTime = 0; - static THR_LOCAL PgStat_Counter minTime = 0; - static THR_LOCAL PgStat_Counter maxTime = 0; + instr_time startTime; // 计时开始 + instr_time endTime; // 计时结束 + PgStat_Counter timeDiff = 0; // 存储时间差 + static THR_LOCAL PgStat_Counter msgCount = 0; // 消息计数 + static THR_LOCAL PgStat_Counter sumPage = 0; // 总页数 + static THR_LOCAL PgStat_Counter sumTime = 0; // 总时间 + static THR_LOCAL PgStat_Counter lstTime = 0; // 上次时间 + static THR_LOCAL PgStat_Counter minTime = 0; // 最小时间 + static THR_LOCAL PgStat_Counter maxTime = 0; // 最大时间 static THR_LOCAL Oid lstFile = InvalidOid; static THR_LOCAL Oid lstDb = InvalidOid; static THR_LOCAL Oid lstSpc = InvalidOid; if (IS_COMPRESSED_MAINFORK(reln, forknum)) { + // 如果是压缩的主分支,调用 mdread_pc 函数进行读取和校验 bool success = mdread_pc(reln, forknum, blocknum, buffer); if (success && PageIsVerified((Page)buffer, blocknum)) { - return SMGR_RD_OK; + return SMGR_RD_OK; // 读取成功且通过校验 } else { - return SMGR_RD_CRC_ERROR; + return SMGR_RD_CRC_ERROR; // 读取失败或未通过校验 } } - (void)INSTR_TIME_SET_CURRENT(startTime); + (void)INSTR_TIME_SET_CURRENT(startTime); // 记录开始时间 TRACE_POSTGRESQL_SMGR_MD_READ_START(forknum, blocknum, reln->smgr_rnode.node.spcNode, reln->smgr_rnode.node.dbNode, reln->smgr_rnode.node.relNode, reln->smgr_rnode.backend); - v = _mdfd_getseg(reln, forknum, blocknum, false, EXTENSION_FAIL); + v = _mdfd_getseg(reln, forknum, blocknum, false, EXTENSION_FAIL); // 获取文件描述符向量 if (v == NULL) { - return SMGR_RD_NO_BLOCK; + return SMGR_RD_NO_BLOCK; // 未找到块 } - seekpos = (off_t)BLCKSZ * (blocknum % ((BlockNumber)RELSEG_SIZE)); + seekpos = (off_t)BLCKSZ * (blocknum % ((BlockNumber)RELSEG_SIZE)); // 计算文件定位位置 - nbytes = FilePRead(v->mdfd_vfd, buffer, BLCKSZ, seekpos, WAIT_EVENT_DATA_FILE_READ); + nbytes = FilePRead(v->mdfd_vfd, buffer, BLCKSZ, seekpos, WAIT_EVENT_DATA_FILE_READ); // 从文件中读取数据 TRACE_POSTGRESQL_SMGR_MD_READ_DONE(forknum, blocknum, reln->smgr_rnode.node.spcNode, reln->smgr_rnode.node.dbNode, reln->smgr_rnode.node.relNode, reln->smgr_rnode.backend, nbytes, BLCKSZ); - (void)INSTR_TIME_SET_CURRENT(endTime); - INSTR_TIME_SUBTRACT(endTime, startTime); - timeDiff = INSTR_TIME_GET_MICROSEC(endTime); + (void)INSTR_TIME_SET_CURRENT(endTime); // 记录结束时间 + INSTR_TIME_SUBTRACT(endTime, startTime); // 计算时间差 + timeDiff = INSTR_TIME_GET_MICROSEC(endTime); // 获取微秒级时间差 if (msgCount == 0) { - lstFile = reln->smgr_rnode.node.relNode; - lstDb = reln->smgr_rnode.node.dbNode; - lstSpc = reln->smgr_rnode.node.spcNode; - CONTINUOUS_ASSIGN_2(msgCount, sumPage, 1); - CONTINUOUS_ASSIGN_3(sumTime, minTime, maxTime, timeDiff); + lstFile = reln->smgr_rnode.node.relNode; // 设置上次文件 + lstDb = reln->smgr_rnode.node.dbNode; // 设置上次数据库 + lstSpc = reln->smgr_rnode.node.spcNode; // 设置上次空间 + CONTINUOUS_ASSIGN_2(msgCount, sumPage, 1); // 分配值给计数和总页数 + CONTINUOUS_ASSIGN_3(sumTime, minTime, maxTime, timeDiff); // 分配值给总时间、最小时间和最大时间 } else if (msgCount % STAT_MSG_BATCH == 0 || lstFile != reln->smgr_rnode.node.relNode) { PgStat_MsgFile msg; errno_t rc; @@ -1881,28 +2137,28 @@ SMGR_READ_STATUS mdread(SMgrRelation reln, ForkNumber forknum, BlockNumber block msg.maxtim = maxTime; reportFileStat(&msg); - rc = memset_s(&msg, sizeof(PgStat_MsgFile), 0, sizeof(PgStat_MsgFile)); + rc = memset_s(&msg, sizeof(PgStat_MsgFile), 0, sizeof(PgStat_MsgFile)); // 清零消息结构 securec_check(rc, "", ""); - CONTINUOUS_ASSIGN_2(msgCount, sumPage, 1); - sumTime = timeDiff; + CONTINUOUS_ASSIGN_2(msgCount, sumPage, 1); // 分配值给计数和总页数 + sumTime = timeDiff; // 更新总时间 if (lstFile != reln->smgr_rnode.node.relNode) { - lstFile = reln->smgr_rnode.node.relNode; - lstDb = reln->smgr_rnode.node.dbNode; - lstSpc = reln->smgr_rnode.node.spcNode; + lstFile = reln->smgr_rnode.node.relNode; // 更新上次文件 + lstDb = reln->smgr_rnode.node.dbNode; // 更新上次数据库 + lstSpc = reln->smgr_rnode.node.spcNode; // 更新上次空间 CONTINUOUS_ASSIGN_3(sumTime, minTime, maxTime, timeDiff); } } else { - msgCount++; - sumPage++; - sumTime += timeDiff; + msgCount++; // 增加消息计数 + sumPage++; // 增加总页数 + sumTime += timeDiff; // 增加总时间 } - lstTime = timeDiff; + lstTime = timeDiff; // 更新上次时间 if (minTime > timeDiff) { - minTime = timeDiff; + minTime = timeDiff; // 更新最小时间 } if (maxTime < timeDiff) { - maxTime = timeDiff; + maxTime = timeDiff; // 更新最大时间 } if (nbytes != BLCKSZ) { @@ -1919,7 +2175,7 @@ SMGR_READ_STATUS mdread(SMgrRelation reln, ForkNumber forknum, BlockNumber block * update a block that was later truncated away. */ if (u_sess->attr.attr_security.zero_damaged_pages || t_thrd.xlog_cxt.InRecovery) { - MemSet(buffer, 0, BLCKSZ); + MemSet(buffer, 0, BLCKSZ); // 在特定条件下,填充缓冲区为零 } else { check_file_stat(FilePathName(v->mdfd_vfd)); force_backtrace_messages = true; @@ -1930,7 +2186,7 @@ SMGR_READ_STATUS mdread(SMgrRelation reln, ForkNumber forknum, BlockNumber block } } - if (PageIsVerified((Page) buffer, blocknum)) { + if (PageIsVerified((Page)buffer, blocknum)) { return SMGR_RD_OK; } else { return SMGR_RD_CRC_ERROR; @@ -1954,10 +2210,9 @@ static void mdwrite_pc(SMgrRelation reln, ForkNumber forknum, BlockNumber blockn bool mmapSync = false; MdfdVec *v = _mdfd_getseg(reln, forknum, blocknum, skipFsync, EXTENSION_FAIL); - if (check_unlink_rel_hashtbl(reln->smgr_rnode.node, forknum)) { - ereport(DEBUG1, (errmsg("could not write block %u in file \"%s\": %m, this relation has been removed", - blocknum, FilePathName(v->mdfd_vfd)))); + ereport(DEBUG1, (errmsg("could not write block %u in file \"%s\": %m, this relation has been removed", blocknum, + FilePathName(v->mdfd_vfd)))); /* this file need skip sync */ return; } @@ -1982,16 +2237,18 @@ static void mdwrite_pc(SMgrRelation reln, ForkNumber forknum, BlockNumber blockn /* compress page */ auto work_buffer_size = CompressPageBufferBound(buffer, algorithm); if (work_buffer_size < 0) { - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg( - "mdwrite_pc unrecognized compression algorithm %d,chunk_size:%ud,level:%d,prealloc_chunk:%ud", algorithm, - chunk_size, level, prealloc_chunk))); + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("mdwrite_pc unrecognized compression algorithm %d,chunk_size:%ud,level:%d,prealloc_chunk:%ud", + algorithm, chunk_size, level, prealloc_chunk))); } - char *work_buffer = (char *) palloc(work_buffer_size); + char *work_buffer = (char *)palloc(work_buffer_size); auto compress_buffer_size = CompressPage(buffer, work_buffer, work_buffer_size, option); if (compress_buffer_size < 0) { - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg( - "mdwrite_pc unrecognized compression algorithm %d,chunk_size:%ud,level:%d,prealloc_chunk:%ud", algorithm, - chunk_size, level, prealloc_chunk))); + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("mdwrite_pc unrecognized compression algorithm %d,chunk_size:%ud,level:%d,prealloc_chunk:%ud", + algorithm, chunk_size, level, prealloc_chunk))); } uint8 nchunks = (compress_buffer_size - 1) / chunk_size + 1; @@ -1999,11 +2256,11 @@ static void mdwrite_pc(SMgrRelation reln, ForkNumber forknum, BlockNumber blockn if (bufferSize >= BLCKSZ) { /* store original page if can not save space? */ pfree(work_buffer); - work_buffer = (char *) buffer; + work_buffer = (char *)buffer; nchunks = BLCKSZ / chunk_size; } else { /* fill zero in the last chunk */ - if ((uint32) compress_buffer_size < bufferSize) { + if ((uint32)compress_buffer_size < bufferSize) { auto leftSize = bufferSize - compress_buffer_size; errno_t rc = memset_s(work_buffer + compress_buffer_size, leftSize, 0, leftSize); securec_check(rc, "", ""); @@ -2016,7 +2273,7 @@ static void mdwrite_pc(SMgrRelation reln, ForkNumber forknum, BlockNumber blockn // write chunks of compressed page for (auto i = 0; i < nchunks; ++i) { auto buffer_pos = work_buffer + chunk_size * i; - off_t seekpos = (off_t) OFFSET_OF_PAGE_COMPRESS_CHUNK(chunk_size, pcAddr->chunknos[i]); + off_t seekpos = (off_t)OFFSET_OF_PAGE_COMPRESS_CHUNK(chunk_size, pcAddr->chunknos[i]); auto start = i; while (i < nchunks - 1 && pcAddr->chunknos[i + 1] == pcAddr->chunknos[i] + 1) { i++; @@ -2033,14 +2290,14 @@ static void mdwrite_pc(SMgrRelation reln, ForkNumber forknum, BlockNumber blockn if (nbytes != write_amount) { if (nbytes < 0) { - ereport(ERROR, - (errcode_for_file_access(), errmsg("could not write block %u in file \"%s\": %m", blocknum, - FilePathName(v->mdfd_vfd_pcd)))); + ereport(ERROR, (errcode_for_file_access(), errmsg("could not write block %u in file \"%s\": %m", + blocknum, FilePathName(v->mdfd_vfd_pcd)))); } /* short write: complain appropriately */ - ereport(ERROR, (errcode(ERRCODE_DISK_FULL), errmsg( - "could not write block %u in file \"%s\": wrote only %d of %d bytes", blocknum, - FilePathName(v->mdfd_vfd_pcd), nbytes, BLCKSZ), errhint("Check free disk space."))); + ereport(ERROR, (errcode(ERRCODE_DISK_FULL), + errmsg("could not write block %u in file \"%s\": wrote only %d of %d bytes", blocknum, + FilePathName(v->mdfd_vfd_pcd), nbytes, BLCKSZ), + errhint("Check free disk space."))); } } @@ -2060,13 +2317,11 @@ static void mdwrite_pc(SMgrRelation reln, ForkNumber forknum, BlockNumber blockn pfree(work_buffer); } - if (!skipFsync && !SmgrIsTemp(reln)) { register_dirty_segment(reln, forknum, v); } } - /* * mdwrite() -- Write the supplied block at the appropriate location. * @@ -2176,19 +2431,19 @@ void mdwrite(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, const if (nbytes != BLCKSZ) { if (check_unlink_rel_hashtbl(reln->smgr_rnode.node, forknum)) { ereport(DEBUG1, (errmsg("could not write block %u in file \"%s\": %m, this relation has been removed", - blocknum, FilePathName(v->mdfd_vfd)))); + blocknum, FilePathName(v->mdfd_vfd)))); /* this file need skip sync */ skipFsync = true; } else { if (nbytes < 0) { - ereport(ERROR, (errcode_for_file_access(), errmsg("could not write block %u in file \"%s\": %m", blocknum, - FilePathName(v->mdfd_vfd)))); + ereport(ERROR, (errcode_for_file_access(), errmsg("could not write block %u in file \"%s\": %m", + blocknum, FilePathName(v->mdfd_vfd)))); } /* short write: complain appropriately */ ereport(ERROR, (errcode(ERRCODE_DISK_FULL), - errmsg("could not write block %u in file \"%s\": wrote only %d of %d bytes", blocknum, - FilePathName(v->mdfd_vfd), nbytes, BLCKSZ), - errhint("Check free disk space."))); + errmsg("could not write block %u in file \"%s\": wrote only %d of %d bytes", blocknum, + FilePathName(v->mdfd_vfd), nbytes, BLCKSZ), + errhint("Check free disk space."))); } } @@ -2258,7 +2513,7 @@ BlockNumber mdnblocks(SMgrRelation reln, ForkNumber forknum) if (v->mdfd_chain == NULL) { if (check_unlink_rel_hashtbl(reln->smgr_rnode.node, forknum)) { ereport(DEBUG1, (errmsg("\"%s\": %m, this relation has been removed", - _mdfd_segpath(reln, forknum, segno)))); + _mdfd_segpath(reln, forknum, segno)))); return 0; } ereport(ERROR, (errcode_for_file_access(), @@ -2288,7 +2543,7 @@ void mdtruncate(SMgrRelation reln, ForkNumber forknum, BlockNumber nblocks) */ curnblk = mdnblocks(reln, forknum); if (curnblk == 0) { - return; + return; } if (nblocks > curnblk) { /* Bogus request ... but no complaint if InRecovery */ @@ -2315,7 +2570,7 @@ void mdtruncate(SMgrRelation reln, ForkNumber forknum, BlockNumber nblocks) * from the mdfd_chain). We truncate the file, but do not delete * it, for reasons explained in the header comments. */ - if (IS_COMPRESSED_MAINFORK(reln, forknum)) { + if (IS_COMPRESSED_MAINFORK(reln, forknum)) { chunk_size = PageCompressChunkSize(reln); pcMap = GetPageCompressMemoryMap(v->mdfd_vfd_pca, chunk_size); pg_atomic_write_u32(&pcMap->nblocks, 0); @@ -2340,18 +2595,18 @@ void mdtruncate(SMgrRelation reln, ForkNumber forknum, BlockNumber nblocks) errmsg("could not truncate file \"%s\": %m", FilePathName(v->mdfd_vfd_pcd)))); } } else { - if (FileTruncate(v->mdfd_vfd, 0, WAIT_EVENT_DATA_FILE_TRUNCATE) < 0) { - if (check_unlink_rel_hashtbl(reln->smgr_rnode.node, forknum)) { - ereport(DEBUG1, (errmsg("could not truncate file \"%s\": %m, this relation has been removed", - FilePathName(v->mdfd_vfd)))); - FileClose(ov->mdfd_vfd); - pfree(ov); - break; - } - ereport(ERROR, (errcode_for_file_access(), - errmsg("could not truncate file \"%s\": %m", FilePathName(v->mdfd_vfd)))); - } - } + if (FileTruncate(v->mdfd_vfd, 0, WAIT_EVENT_DATA_FILE_TRUNCATE) < 0) { + if (check_unlink_rel_hashtbl(reln->smgr_rnode.node, forknum)) { + ereport(DEBUG1, (errmsg("could not truncate file \"%s\": %m, this relation has been removed", + FilePathName(v->mdfd_vfd)))); + FileClose(ov->mdfd_vfd); + pfree(ov); + break; + } + ereport(ERROR, (errcode_for_file_access(), + errmsg("could not truncate file \"%s\": %m", FilePathName(v->mdfd_vfd)))); + } + } if (!SmgrIsTemp(reln)) { register_dirty_segment(reln, forknum, v); @@ -2377,7 +2632,7 @@ void mdtruncate(SMgrRelation reln, ForkNumber forknum, BlockNumber nblocks) */ BlockNumber last_seg_blocks = nblocks - prior_blocks; if (IS_COMPRESSED_MAINFORK(reln, forknum)) { - pc_chunk_number_t max_used_chunkno = (pc_chunk_number_t) 0; + pc_chunk_number_t max_used_chunkno = (pc_chunk_number_t)0; uint32 allocated_chunks; chunk_size = PageCompressChunkSize(reln); pcMap = GetPageCompressMemoryMap(v->mdfd_vfd_pca, chunk_size); @@ -2391,27 +2646,26 @@ void mdtruncate(SMgrRelation reln, ForkNumber forknum, BlockNumber nblocks) pcMap->sync = false; if (sync_pcmap(pcMap, WAIT_EVENT_COMPRESS_ADDRESS_FILE_SYNC) != 0) { ereport(data_sync_elevel(ERROR), - (errcode_for_file_access(), errmsg("could not msync file \"%s\": %m", - FilePathName(v->mdfd_vfd_pca)))); + (errcode_for_file_access(), + errmsg("could not msync file \"%s\": %m", FilePathName(v->mdfd_vfd_pca)))); } allocated_chunks = pg_atomic_read_u32(&pcMap->allocated_chunks); /* find the max used chunkno */ - for (BlockNumber blk = (BlockNumber) 0; blk < (BlockNumber) last_seg_blocks; blk++) { + for (BlockNumber blk = (BlockNumber)0; blk < (BlockNumber)last_seg_blocks; blk++) { pcAddr = GET_PAGE_COMPRESS_ADDR(pcMap, chunk_size, blk); /* check allocated_chunks for one page */ if (pcAddr->allocated_chunks > BLCKSZ / chunk_size) { - ereport(ERROR, - (errcode(ERRCODE_DATA_CORRUPTED), errmsg("invalid chunks %u of block %u in file \"%s\"", - pcAddr->allocated_chunks, blk, - FilePathName(v->mdfd_vfd_pca)))); + ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("invalid chunks %u of block %u in file \"%s\"", pcAddr->allocated_chunks, + blk, FilePathName(v->mdfd_vfd_pca)))); } /* check chunknos for one page */ for (i = 0; i < pcAddr->allocated_chunks; i++) { if (pcAddr->chunknos[i] == 0 || pcAddr->chunknos[i] > allocated_chunks) { - ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg( - "invalid chunk number %u of block %u in file \"%s\"", pcAddr->chunknos[i], blk, - FilePathName(v->mdfd_vfd_pca)))); + ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("invalid chunk number %u of block %u in file \"%s\"", + pcAddr->chunknos[i], blk, FilePathName(v->mdfd_vfd_pca)))); } if (pcAddr->chunknos[i] > max_used_chunkno) { @@ -2456,10 +2710,20 @@ void mdtruncate(SMgrRelation reln, ForkNumber forknum, BlockNumber nblocks) prior_blocks += RELSEG_SIZE; } } - -static bool CompressMdImmediateSync(SMgrRelation reln, ForkNumber forknum, MdfdVec* v) +/* + * 功能:执行立即同步操作,确保对压缩文件的修改被同步到磁盘上 + * + * 参数列表: + * reln:SMgrRelation 结构,表示文件所属的 SMgrRelation + * forknum:ForkNumber 枚举,表示文件的分支号 + * v:MdfdVec 结构指针,表示文件的 MdfdVec 结构 + * + */ +static bool CompressMdImmediateSync(SMgrRelation reln, ForkNumber forknum, MdfdVec *v) { - PageCompressHeader* pcMap = GetPageCompressMemoryMap(v->mdfd_vfd_pca, PageCompressChunkSize(reln)); + // 获取压缩文件的页压缩头部的指针 + PageCompressHeader *pcMap = GetPageCompressMemoryMap(v->mdfd_vfd_pca, PageCompressChunkSize(reln)); + // 同步 PageCompressHeader 结构到磁盘 if (sync_pcmap(pcMap, WAIT_EVENT_COMPRESS_ADDRESS_FILE_SYNC) != 0) { if (check_unlink_rel_hashtbl(reln->smgr_rnode.node, forknum)) { ereport(DEBUG1, (errmsg("could not fsync file \"%s\": %m, this relation has been removed", @@ -2469,6 +2733,7 @@ static bool CompressMdImmediateSync(SMgrRelation reln, ForkNumber forknum, MdfdV ereport(data_sync_elevel(ERROR), (errcode_for_file_access(), errmsg("could not msync file \"%s\": %m", FilePathName(v->mdfd_vfd_pca)))); } + // 同步 PageCompressData 到磁盘 if (FileSync(v->mdfd_vfd_pcd, WAIT_EVENT_DATA_FILE_IMMEDIATE_SYNC) < 0) { if (check_unlink_rel_hashtbl(reln->smgr_rnode.node, forknum)) { ereport(DEBUG1, (errmsg("could not fsync file \"%s\": %m, this relation has been removed", @@ -2499,21 +2764,21 @@ void mdimmedsync(SMgrRelation reln, ForkNumber forknum) v = mdopen(reln, forknum, EXTENSION_FAIL); while (v != NULL) { - if (IS_COMPRESSED_MAINFORK(reln, forknum)) { + if (IS_COMPRESSED_MAINFORK(reln, forknum)) { if (!CompressMdImmediateSync(reln, forknum, v)) { break; } } else { - if (FileSync(v->mdfd_vfd, WAIT_EVENT_DATA_FILE_IMMEDIATE_SYNC) < 0) { - if (check_unlink_rel_hashtbl(reln->smgr_rnode.node, forknum)) { - ereport(DEBUG1, (errmsg("could not fsync file \"%s\": %m, this relation has been removed", - FilePathName(v->mdfd_vfd)))); - break; - } - ereport(data_sync_elevel(ERROR), (errcode_for_file_access(), - errmsg("could not fsync file \"%s\": %m", FilePathName(v->mdfd_vfd)))); - } - } + if (FileSync(v->mdfd_vfd, WAIT_EVENT_DATA_FILE_IMMEDIATE_SYNC) < 0) { + if (check_unlink_rel_hashtbl(reln->smgr_rnode.node, forknum)) { + ereport(DEBUG1, (errmsg("could not fsync file \"%s\": %m, this relation has been removed", + FilePathName(v->mdfd_vfd)))); + break; + } + ereport(data_sync_elevel(ERROR), (errcode_for_file_access(), errmsg("could not fsync file \"%s\": %m", + FilePathName(v->mdfd_vfd)))); + } + } v = v->mdfd_chain; } } @@ -2641,7 +2906,7 @@ static MdfdVec *_mdfd_openseg(SMgrRelation reln, ForkNumber forknum, BlockNumber pfree(fullpath); return NULL; } - + int fd_pca = -1; int fd_pcd = -1; if (IS_COMPRESSED_MAINFORK(reln, forknum)) { @@ -2755,7 +3020,7 @@ static MdfdVec *_mdfd_getseg(SMgrRelation reln, ForkNumber forknum, BlockNumber } if (check_unlink_rel_hashtbl(reln->smgr_rnode.node, forknum)) { ereport(DEBUG1, (errmsg("could not open file \"%s\" (target block %u): %m", - _mdfd_segpath(reln, forknum, nextsegno), blkno))); + _mdfd_segpath(reln, forknum, nextsegno), blkno))); return NULL; } @@ -2784,13 +3049,13 @@ static BlockNumber _mdnblocks(SMgrRelation reln, ForkNumber forknum, const MdfdV off_t len; if (IS_COMPRESSED_MAINFORK(reln, forknum)) { PageCompressHeader *pcMap = GetPageCompressMemoryMap(seg->mdfd_vfd_pca, PageCompressChunkSize(reln)); - return (BlockNumber) pg_atomic_read_u32(&pcMap->nblocks); + return (BlockNumber)pg_atomic_read_u32(&pcMap->nblocks); } len = FileSeek(seg->mdfd_vfd, 0L, SEEK_END); if (len < 0) { if (check_unlink_rel_hashtbl(reln->smgr_rnode.node, forknum)) { ereport(DEBUG1, (errmsg("could not seek to end of file \"%s\": %m, this relation has been removed", - FilePathName(seg->mdfd_vfd)))); + FilePathName(seg->mdfd_vfd)))); return 0; } ereport(ERROR, (errcode_for_file_access(), @@ -2810,14 +3075,14 @@ static BlockNumber _mdnblocks(SMgrRelation reln, ForkNumber forknum, const MdfdV int SyncMdFile(const FileTag *ftag, char *path) { SMgrRelation reln = smgropen(ftag->rnode, InvalidBackendId, GetColumnNum(ftag->forknum)); - MdfdVec *v; - char *p; + MdfdVec *v; + char *p; File file = -1; File pcaFd = -1; File pcdFd = -1; int result; int savedErrno; - bool needClose = false; + bool needClose = false; /* Provide the path for informational messages. */ p = _mdfd_segpath(reln, ftag->forknum, ftag->segno); @@ -2825,8 +3090,7 @@ int SyncMdFile(const FileTag *ftag, char *path) pfree(p); /* Try to open the requested segment. */ - v = _mdfd_getseg(reln, ftag->forknum, ftag->segno * (BlockNumber) RELSEG_SIZE, - false, EXTENSION_RETURN_NULL); + v = _mdfd_getseg(reln, ftag->forknum, ftag->segno * (BlockNumber)RELSEG_SIZE, false, EXTENSION_RETURN_NULL); if (IS_COMPRESSED_RNODE(ftag->rnode, ftag->forknum)) { if (v == NULL) { pcaFd = OpenPcaFile(path, reln->smgr_rnode, ftag->forknum, ftag->segno); @@ -2904,7 +3168,7 @@ int SyncMdFile(const FileTag *ftag, char *path) */ int UnlinkMdFile(const FileTag *ftag, char *path) { - char *p; + char *p; /* Compute the path. */ p = relpathperm(ftag->rnode, MAIN_FORKNUM); @@ -2931,19 +3195,29 @@ bool MatchMdFileTag(const FileTag *ftag, const FileTag *candidate) */ return ftag->rnode.dbNode == candidate->rnode.dbNode; } - +/* + * 功能:同步页压缩内存映射的数据到磁盘 + * + * 参数列表: + * pcMap:PageCompressHeader 结构的指针,表示页压缩内存映射的头部信息 + * wait_event_info:一个表示等待事件的标识,用于记录等待事件的类型 + */ static int sync_pcmap(PageCompressHeader *pcMap, uint32 wait_event_info) { + // 如果已经标记为需要同步,则直接返回成功 if (pg_atomic_read_u32(&pcMap->sync) == true) { return 0; } int returnCode; uint32 nblocks, allocated_chunks, last_synced_nblocks, last_synced_allocated_chunks; + // 如果已经标记为需要同步,则直接返回成功 nblocks = pg_atomic_read_u32(&pcMap->nblocks); allocated_chunks = pg_atomic_read_u32(&pcMap->allocated_chunks); last_synced_nblocks = pg_atomic_read_u32(&pcMap->last_synced_nblocks); last_synced_allocated_chunks = pg_atomic_read_u32(&pcMap->last_synced_allocated_chunks); + // 调用 pc_msync 函数执行真正的内存映射同步操作 returnCode = pc_msync(pcMap); + // 如果同步成功,更新已同步的属性值 if (returnCode == 0) { if (last_synced_nblocks != nblocks) { pg_atomic_write_u32(&pcMap->last_synced_nblocks, nblocks); @@ -2953,6 +3227,7 @@ static int sync_pcmap(PageCompressHeader *pcMap, uint32 wait_event_info) pg_atomic_write_u32(&pcMap->last_synced_allocated_chunks, allocated_chunks); } } + // 标记为已同步 pcMap->sync = true; return returnCode; } \ No newline at end of file -- 2.34.1 From fc4b67f9ea2139e9b3071e9d3ed7d04a94139bca Mon Sep 17 00:00:00 2001 From: nuoya <1204149038@qq.com> Date: Mon, 25 Sep 2023 15:31:23 +0800 Subject: [PATCH 10/50] Update mmap_shared.cpp --- src/gausskernel/storage/smgr/mmap_shared.cpp | 70 +++++++++++++++++--- 1 file changed, 61 insertions(+), 9 deletions(-) diff --git a/src/gausskernel/storage/smgr/mmap_shared.cpp b/src/gausskernel/storage/smgr/mmap_shared.cpp index 4d8c85c73..02aee4c38 100644 --- a/src/gausskernel/storage/smgr/mmap_shared.cpp +++ b/src/gausskernel/storage/smgr/mmap_shared.cpp @@ -54,96 +54,148 @@ static inline pthread_mutex_t *MmapPartitionLock(size_t hashCode) { return &mmapLockArray[hashCode % LOCK_ARRAY_SIZE]; } - +/* + * 功能:同步页压缩内存映射的数据到磁盘 + * + * 参数列表: + * pcMap:PageCompressHeader 结构的指针,表示页压缩内存映射的头部信息 + * wait_event_info:一个表示等待事件的标识,用于记录等待事件的类型 + */ static inline PageCompressHeader *MmapSharedMapFile(Vfd *vfdP, uint16 chunkSize, uint2 opt, bool readonly) { + // 调用 pc_mmap_real_size 函数映射共享内存文件,返回一个指向 PageCompressHeader 结构体的指针 map。 auto map = pc_mmap_real_size(vfdP->fd, SIZE_OF_PAGE_COMPRESS_ADDR_FILE(chunkSize), false); + // 检查映射的文件区域是否有效,若无效则进行初始化设置。 if (map->chunk_size == 0 || map->algorithm == 0) { + // 检查映射的文件区域是否有效,若无效则进行初始化设置。 map->chunk_size = chunkSize; map->algorithm = GET_COMPRESS_ALGORITHM(opt); + // 使用 pc_msync 函数将映射区域的数据同步到文件中。 if (pc_msync(map) != 0) { + // 如果同步失败,则生成错误报告。 ereport(data_sync_elevel(ERROR), (errcode_for_file_access(), errmsg("could not msync file \"%s\": %m", vfdP->fileName))); } } + // 如果正在进行恢复操作且映射区域未同步,则调用 CheckAndRepairCompressAddress 函数进行检查和修复。 if (RecoveryInProgress() && !map->sync) { CheckAndRepairCompressAddress(map, chunkSize, map->algorithm, vfdP->fileName); } + // 返回映射区域的指针 map。 return map; } - +/* + * 功能:初始化页压缩内存映射的锁数组 + */ void RealInitialMMapLockArray() { + // 遍历锁数组,为每个元素初始化一个互斥锁 for (size_t i = 0; i < LOCK_ARRAY_SIZE; ++i) { pthread_mutex_init(&mmapLockArray[i], NULL); } - + // 初始化哈希表控制结构体 HASHCTL ctl; /* hash accessed by database file id */ + // 使用 memset_s 函数将 ctl 结构体清零 errno_t rc = memset_s(&ctl, sizeof(ctl), 0, sizeof(ctl)); securec_check(rc, "", ""); - + // 设置哈希键的大小和每个条目的大小 ctl.keysize = sizeof(RelFileNodeForkNum); ctl.entrysize = sizeof(MmapEntry); ctl.hash = tag_hash; ctl.num_partitions = LOCK_ARRAY_SIZE; const size_t initLen = 256; + // 初始化内存哈希表,用于页压缩内存映射的缓存 g_instance.mmapCache = HeapMemInitHash( "mmap hash", initLen, (Max(g_instance.attr.attr_common.max_files_per_process, t_thrd.storage_cxt.max_userdatafiles)) / 2, &ctl, HASH_ELEM | HASH_FUNCTION | HASH_PARTITION); } - +/* + * 功能:获取页压缩内存映射的头部信息 + * + * 参数列表: + * vfd:指向文件描述符信息的指针 + * chunkSize:uint16 类型,表示页面压缩的块大小 + * relFileNodeForkNum:RelFileNodeForkNum 结构,表示文件的标识信息 + */ PageCompressHeader *GetPageCompressHeader(void *vfd, uint16 chunkSize, const RelFileNodeForkNum &relFileNodeForkNum) { + // 将传入的 vfd 转换为 Vfd 结构体指针 Vfd *currentVfd = (Vfd *)vfd; + // 计算哈希码以用于锁分区 uint32 hashCode = MmapTableHashCode(relFileNodeForkNum); + // 使用 AutoMutexLock 自动加锁,锁定相应的分区 AutoMutexLock mmapLock(MmapPartitionLock(hashCode)); - + // 加锁 mmapLock.lock(); + // 初始化查找结果标志 bool find = false; + // 在内存哈希表中搜索文件标识,并返回相应的 MmapEntry 结构指针 MmapEntry *mmapEntry = (MmapEntry *)hash_search_with_hash_value(g_instance.mmapCache, (void *)&relFileNodeForkNum, hashCode, HASH_ENTER, &find); + // 如果未找到对应条目,则进行初始化设置 if (!find) { mmapEntry->pcmap = NULL; mmapEntry->reference = 0; } + // 如果 pcmap 为空,则调用 MmapSharedMapFile 函数映射文件 if (mmapEntry->pcmap == NULL) { mmapEntry->pcmap = MmapSharedMapFile(currentVfd, chunkSize, relFileNodeForkNum.rnode.node.opt, false); } + // 增加对映射区域的引用计数 ++mmapEntry->reference; + // 增加对映射区域的引用计数 mmapLock.unLock(); + // 返回映射区域的指针 return mmapEntry->pcmap; } - +/* + * 功能:解除页压缩内存映射的引用计数,并在不再使用时释放资源 + * + * 参数列表: + * vfd:指向文件描述符信息的指针 + */ void UnReferenceAddrFile(void *vfd) { + // 将传入的 vfd 转换为 Vfd 结构体指针 Vfd *currentVfd = (Vfd *)vfd; + // 获取文件标识信息 RelFileNodeForkNum relFileNodeForkNum = currentVfd->fileNode; + // 计算哈希码以用于锁分区 uint32 hashCode = MmapTableHashCode(relFileNodeForkNum); + // 使用 AutoMutexLock 自动加锁,锁定相应的分区 AutoMutexLock mmapLock(MmapPartitionLock(hashCode)); mmapLock.lock(); + // 在内存哈希表中搜索文件标识,并返回相应的 MmapEntry 结构指针(HASH_FIND 模式) MmapEntry *mmapEntry = (MmapEntry *)hash_search_with_hash_value(g_instance.mmapCache, (void *)&relFileNodeForkNum, hashCode, HASH_FIND, NULL); + // 如果没有找到对应的条目,则生成错误报告 if (mmapEntry == NULL) { ereport(ERROR, (errcode_for_file_access(), errmsg("UnReferenceAddrFile failed! mmap not found, filePath: %s", currentVfd->fileName))); } + // 减少对映射区域的引用计数 --mmapEntry->reference; + // 如果引用计数降为零,释放资源 if (mmapEntry->reference == 0) { + // 调用 pc_munmap 函数释放映射区域的资源 if (pc_munmap(mmapEntry->pcmap) != 0) { ereport(ERROR, (errcode_for_file_access(), errmsg("could not munmap file \"%s\": %m", currentVfd->fileName))); } + // 从内存哈希表中移除对应的哈希键 if (hash_search_with_hash_value(g_instance.mmapCache, (void *)&relFileNodeForkNum, hashCode, HASH_REMOVE, NULL) == NULL) { ereport(ERROR, (errcode_for_file_access(), errmsg("UnReferenceAddrFile failed! remove hash key failed, filePath: %s", currentVfd->fileName))); } - } else if (mmapEntry->reference < 0) { + } + // 如果引用计数小于零,生成致命错误报告 + else if (mmapEntry->reference < 0) { ereport(FATAL, (errcode_for_file_access(), errmsg("could not munmap file \"%s\": %m", currentVfd->fileName))); } - mmapLock.unLock(); + mmapLock.unLock(); // 解锁 } \ No newline at end of file -- 2.34.1 From 902c179c9d8ec01ab4fa3ffeb52ba44b0e84d612 Mon Sep 17 00:00:00 2001 From: nuoya <1204149038@qq.com> Date: Mon, 25 Sep 2023 15:31:40 +0800 Subject: [PATCH 11/50] Update page_compression.cpp --- .../storage/smgr/page_compression.cpp | 193 +++++++++++++----- 1 file changed, 147 insertions(+), 46 deletions(-) diff --git a/src/gausskernel/storage/smgr/page_compression.cpp b/src/gausskernel/storage/smgr/page_compression.cpp index f83fc46fa..513379277 100644 --- a/src/gausskernel/storage/smgr/page_compression.cpp +++ b/src/gausskernel/storage/smgr/page_compression.cpp @@ -35,39 +35,58 @@ #include "storage/checksum.h" #include "storage/page_compression.h" #include "storage/page_compression_impl.h" - -static void CheckHeaderOfCompressAddr(PageCompressHeader* pcMap, uint16 chunk_size, uint8 algorithm, const char* path) +/* + * 功能:检查页压缩内存映射的头部信息是否合法,如果不合法则根据配置进行处理 + * + * 参数列表: + * pcMap:PageCompressHeader 结构的指针,表示页压缩内存映射的头部信息 + * chunk_size:uint16 类型,表示页面压缩的块大小 + * algorithm:uint8 类型,表示压缩算法 + * path:const char* 类型,表示文件路径 + */ +static void CheckHeaderOfCompressAddr(PageCompressHeader *pcMap, uint16 chunk_size, uint8 algorithm, const char *path) { + // 检查页压缩内存映射的块大小和压缩算法是否与预期值相符 if (pcMap->chunk_size != chunk_size || pcMap->algorithm != algorithm) { + // 如果启用了 zero_damaged_pages 配置,生成警告并重新初始化映射头部信息 if (u_sess->attr.attr_security.zero_damaged_pages) { ereport(WARNING, - (errcode(ERRCODE_DATA_CORRUPTED), - errmsg("invalid chunk_size %u or algorithm %u in head of compress relation address file \"%s\", " - "and reinitialized it.", - pcMap->chunk_size, - pcMap->algorithm, - path))); + (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("invalid chunk_size %u or algorithm %u in head of compress relation address file \"%s\", " + "and reinitialized it.", + pcMap->chunk_size, pcMap->algorithm, path))); + // 重新设置压缩算法、nblocks、allocated_chunks 和 last_synced_allocated_chunks pcMap->algorithm = algorithm; pg_atomic_write_u32(&pcMap->nblocks, RELSEG_SIZE); pg_atomic_write_u32(&pcMap->allocated_chunks, 0); pg_atomic_write_u32(&pcMap->last_synced_allocated_chunks, 0); pcMap->chunk_size = chunk_size; } else { + // 如果未启用 zero_damaged_pages 配置,生成错误报告 ereport(ERROR, - (errcode(ERRCODE_DATA_CORRUPTED), - errmsg("invalid chunk_size %u or algorithm %u in head of compress relation address file \"%s\"", - pcMap->chunk_size, - pcMap->algorithm, - path))); + (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("invalid chunk_size %u or algorithm %u in head of compress relation address file \"%s\"", + pcMap->chunk_size, pcMap->algorithm, path))); } } } - +/* + * 功能:检查和修复页压缩内存映射的地址信息,以及更新相关信息 + * + * 参数列表: + * pcMap:PageCompressHeader 结构的指针,表示页压缩内存映射的头部信息 + * chunk_size:uint16 类型,表示页面压缩的块大小 + * algorithm:uint8 类型,表示压缩算法 + * path:const char* 类型,表示文件路径 + */ void CheckAndRepairCompressAddress(PageCompressHeader *pcMap, uint16 chunk_size, uint8 algorithm, const char *path) { + // 获取上次恢复的时间 TimestampTz lastRecoveryTime = pcMap->last_recovery_start_time; + // 获取数据库启动的时间 TimestampTz pgStartTime = t_thrd.time_cxt.pg_start_time; + // 错误码变量 error_t rc; /* if the relation had been checked in this startup, skip */ if (lastRecoveryTime == pgStartTime) { @@ -76,21 +95,25 @@ void CheckAndRepairCompressAddress(PageCompressHeader *pcMap, uint16 chunk_size, /* check head of compress address file */ CheckHeaderOfCompressAddr(pcMap, chunk_size, algorithm, path); - + // 读取头部信息中的块数和已分配块数 uint32 nblocks = pg_atomic_read_u32(&pcMap->nblocks); uint32 allocated_chunks = pg_atomic_read_u32(&pcMap->allocated_chunks); + // 为全局块号数组分配内存 BlockNumber *global_chunknos = (BlockNumber *)palloc0(MAX_CHUNK_NUMBER(chunk_size) * sizeof(BlockNumber)); - + // 初始化块号变量 BlockNumber max_blocknum = (BlockNumber)-1; BlockNumber max_nonzero_blocknum = (BlockNumber)-1; BlockNumber max_allocated_chunkno = (pc_chunk_number_t)0; /* check compress address of every pages */ for (BlockNumber blocknum = 0; blocknum < (BlockNumber)RELSEG_SIZE; ++blocknum) { + // 获取当前页面的压缩地址信息 PageCompressAddr *pcAddr = GET_PAGE_COMPRESS_ADDR(pcMap, chunk_size, blocknum); + // 获取当前页面的压缩地址信息 if (pcAddr->checksum != AddrChecksum32(blocknum, pcAddr, chunk_size)) { ereport(WARNING, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("invalid checkum %u of block %u in file \"%s\"", pcAddr->checksum, blocknum, path))); + // 将该块的压缩地址信息清零 pcAddr->allocated_chunks = pcAddr->nchunks = 0; for (int i = 0; i < BLCKSZ / chunk_size; ++i) { pcAddr->chunknos[i] = 0; @@ -104,8 +127,10 @@ void CheckAndRepairCompressAddress(PageCompressHeader *pcMap, uint16 chunk_size, */ /* check allocated_chunks for one page */ + // 检查每个页面的已分配块数 if (pcAddr->allocated_chunks > BLCKSZ / chunk_size) { if (u_sess->attr.attr_security.zero_damaged_pages) { + // 如果启用了 zero_damaged_pages 配置,将该块的压缩地址信息清零 rc = memset_s((void *)pcAddr, SIZE_OF_PAGE_COMPRESS_ADDR(chunk_size), 0, SIZE_OF_PAGE_COMPRESS_ADDR(chunk_size)); securec_check_c(rc, "\0", "\0"); @@ -114,6 +139,7 @@ void CheckAndRepairCompressAddress(PageCompressHeader *pcMap, uint16 chunk_size, pcAddr->allocated_chunks, blocknum, path))); continue; } else { + // 如果未启用 zero_damaged_pages 配置,释放内存并生成错误报告 pfree(global_chunknos); ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("invalid allocated_chunks %u of block %u in file \"%s\"", @@ -122,10 +148,12 @@ void CheckAndRepairCompressAddress(PageCompressHeader *pcMap, uint16 chunk_size, } /* check chunknos for one page */ + // 检查每个页面的块号是否有效 for (int i = 0; i < pcAddr->allocated_chunks; ++i) { /* check for invalid chunkno */ if (pcAddr->chunknos[i] == 0 || pcAddr->chunknos[i] > MAX_CHUNK_NUMBER(chunk_size)) { if (u_sess->attr.attr_security.zero_damaged_pages) { + // 如果启用了 zero_damaged_pages 配置,将该块的压缩地址信息清零 rc = memset_s((void *)pcAddr, SIZE_OF_PAGE_COMPRESS_ADDR(chunk_size), 0, SIZE_OF_PAGE_COMPRESS_ADDR(chunk_size)); securec_check_c(rc, "\0", "\0"); @@ -134,6 +162,7 @@ void CheckAndRepairCompressAddress(PageCompressHeader *pcMap, uint16 chunk_size, pcAddr->chunknos[i], blocknum, path))); continue; } else { + // 如果未启用 zero_damaged_pages 配置,释放内存并生成错误报告 pfree(global_chunknos); ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("invalid chunk number %u of block %u in file \"%s\"", pcAddr->chunknos[i], @@ -144,6 +173,7 @@ void CheckAndRepairCompressAddress(PageCompressHeader *pcMap, uint16 chunk_size, /* check for duplicate chunkno */ if (global_chunknos[pcAddr->chunknos[i] - 1] != 0) { if (u_sess->attr.attr_security.zero_damaged_pages) { + // 如果启用了 zero_damaged_pages 配置,将该块的压缩地址信息清零 rc = memset_s((void *)pcAddr, SIZE_OF_PAGE_COMPRESS_ADDR(chunk_size), 0, SIZE_OF_PAGE_COMPRESS_ADDR(chunk_size)); securec_check_c(rc, "\0", "\0"); @@ -155,6 +185,7 @@ void CheckAndRepairCompressAddress(PageCompressHeader *pcMap, uint16 chunk_size, pcAddr->chunknos[i], blocknum, global_chunknos[pcAddr->chunknos[i] - 1], path))); continue; } else { + // 如果未启用 zero_damaged_pages 配置,释放内存并生成错误报告 pfree(global_chunknos); ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), @@ -177,6 +208,7 @@ void CheckAndRepairCompressAddress(PageCompressHeader *pcMap, uint16 chunk_size, /* check nchunks for one page */ if (pcAddr->nchunks > pcAddr->allocated_chunks) { if (u_sess->attr.attr_security.zero_damaged_pages) { + // 如果启用了 zero_damaged_pages 配置,将该块的压缩地址信息清零 rc = memset_s((void *)pcAddr, SIZE_OF_PAGE_COMPRESS_ADDR(chunk_size), 0, SIZE_OF_PAGE_COMPRESS_ADDR(chunk_size)); securec_check_c(rc, "\0", "\0"); @@ -187,6 +219,7 @@ void CheckAndRepairCompressAddress(PageCompressHeader *pcMap, uint16 chunk_size, pcAddr->nchunks, pcAddr->allocated_chunks, blocknum, path))); continue; } else { + // 如果未启用 zero_damaged_pages 配置,释放内存并生成错误报告 pfree(global_chunknos); ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("nchunks %u exceeds allocated_chunks %u of block %u in file \"%s\"", @@ -194,11 +227,13 @@ void CheckAndRepairCompressAddress(PageCompressHeader *pcMap, uint16 chunk_size, } } + // 更新块号信息 max_blocknum = blocknum; if (pcAddr->nchunks > 0) { max_nonzero_blocknum = blocknum; } + // 更新全局块号数组 for (int i = 0; i < pcAddr->allocated_chunks; ++i) { global_chunknos[pcAddr->chunknos[i] - 1] = blocknum + 1; if (pcAddr->chunknos[i] > max_allocated_chunkno) { @@ -286,16 +321,24 @@ void CheckAndRepairCompressAddress(PageCompressHeader *pcMap, uint16 chunk_size, pcMap->last_recovery_start_time = pgStartTime; } -int64 CalculateMainForkSize(char* pathName, RelFileNode* rnode, ForkNumber forkNumber) +int64 CalculateMainForkSize(char *pathName, RelFileNode *rnode, ForkNumber forkNumber) { Assert(IS_COMPRESSED_RNODE((*rnode), forkNumber)); Assert(rnode->bucketNode == -1); return CalculateCompressMainForkSize(pathName); } - -void CopyCompressedPath(char dst[MAXPGPATH], const char* pathName, CompressedFileType compressFileType) +/* + * 功能:复制压缩文件的路径 + * + * 参数列表: + * dst:目标路径的字符数组,用于存储复制后的路径 + * pathName:源路径的字符串,表示压缩文件的路径 + * compressFileType:压缩文件的类型,可以是 COMPRESSED_TABLE_PCA_FILE 或 COMPRESSED_TABLE_PCD_FILE + */ +void CopyCompressedPath(char dst[MAXPGPATH], const char *pathName, CompressedFileType compressFileType) { int rc; + // 根据压缩文件类型构造目标路径 if (compressFileType == COMPRESSED_TABLE_PCA_FILE) { rc = snprintf_s(dst, MAXPGPATH, MAXPGPATH - 1, PCA_SUFFIX, pathName); } else { @@ -303,52 +346,91 @@ void CopyCompressedPath(char dst[MAXPGPATH], const char* pathName, CompressedFil } securec_check_ss(rc, "\0", "\0"); } - -int64 CalculateCompressMainForkSize(char* pathName, bool suppressedENOENT) +/* + * 功能:计算压缩主分支文件的大小 + * + * 参数列表: + * pathName:压缩文件的路径字符串 + * suppressedENOENT:一个布尔值,表示是否忽略 ENOENT 错误 + * + */ +int64 CalculateCompressMainForkSize(char *pathName, bool suppressedENOENT) { int64 totalsize = 0; char pcFilePath[MAXPGPATH]; + // 构造压缩主分支文件的路径,并计算其大小并累加到 totalsize CopyCompressedPath(pcFilePath, pathName, COMPRESSED_TABLE_PCA_FILE); totalsize += CalculateFileSize(pcFilePath, MAXPGPATH, suppressedENOENT); - + // 构造压缩主分支文件的路径,并计算其大小并累加到 totalsize CopyCompressedPath(pcFilePath, pathName, COMPRESSED_TABLE_PCD_FILE); totalsize += CalculateFileSize(pcFilePath, MAXPGPATH, suppressedENOENT); return totalsize; } - -uint16 ReadChunkSize(FILE* pcaFile, char* pcaFilePath, size_t len) +/* + * 功能:从压缩主分支文件中读取块大小 + * + * 参数列表: + * pcaFile:已打开的压缩主分支文件的文件指针 + * pcaFilePath:压缩主分支文件的路径字符串 + * len:文件长度 + * + * 返回值: + * 读取到的块大小(uint16类型) + */ +uint16 ReadChunkSize(FILE *pcaFile, char *pcaFilePath, size_t len) { uint16 chunkSize; + // 将文件指针定位到 PageCompressHeader 结构中的 chunk_size 字段 if (fseeko(pcaFile, (off_t)offsetof(PageCompressHeader, chunk_size), SEEK_SET) != 0) { ereport(ERROR, (errcode_for_file_access(), errmsg("could not seek in file \"%s\": \"%lu\": %m", pcaFilePath, len))); } + // 从文件中读取块大小 if (fread(&chunkSize, sizeof(chunkSize), 1, pcaFile) <= 0) { ereport(ERROR, (errcode_for_file_access(), errmsg("could not open file \"%s\": \"%lu\": %m", pcaFilePath, len))); } return chunkSize; } - -int64 CalculateFileSize(char* pathName, size_t size, bool suppressedENOENT) +/* + * 功能:计算文件的大小 + * + * 参数列表: + * pathName:文件的路径字符串 + * size:文件的默认大小 + * suppressedENOENT:一个布尔值,表示是否忽略 ENOENT 错误 + */ +int64 CalculateFileSize(char *pathName, size_t size, bool suppressedENOENT) { struct stat structstat; + // 获取文件的状态信息,并将其存储在 structstat 结构中 if (stat(pathName, &structstat)) { if (errno == ENOENT) { if (suppressedENOENT) { return 0; } + // 如果文件不存在且不忽略 ENOENT 错误,则报告文件未找到错误 ereport(ERROR, (errcode_for_file_access(), errmsg("could not FIND file \"%s\": %m", pathName))); } else { + // 如果发生其他错误,则报告无法获取文件状态信息的错误 ereport(ERROR, (errcode_for_file_access(), errmsg("could not stat file \"%s\": %m", pathName))); } } return structstat.st_size; } - +/* + * 功能:将压缩块大小转换为索引值 + * + * 参数列表: + * compressedChunkSize:压缩块大小 + * success:一个布尔指针,用于指示转换是否成功 + * + * 返回值: + * 压缩块大小对应的索引值(uint1类型) + */ uint1 ConvertChunkSize(uint32 compressedChunkSize, bool *success) { uint1 chunkSize = INDEX_OF_HALF_BLCKSZ; @@ -366,28 +448,38 @@ uint1 ConvertChunkSize(uint32 compressedChunkSize, bool *success) chunkSize = INDEX_OF_SIXTEENTHS_BLCKSZ; break; default: + // 如果压缩块大小不在预定义的范围内,标记转换失败并返回默认索引值 *success = false; return chunkSize; } + // 标记转换成功并返回索引值 *success = true; return chunkSize; } constexpr int MAX_RETRY_LIMIT = 60; constexpr long RETRY_SLEEP_TIME = 1000000L; - -size_t ReadAllChunkOfBlock(char *dst, size_t destLen, BlockNumber blockNumber, ReadBlockChunksStruct& rbStruct) +/* + * 功能:从压缩文件中读取指定块号的所有块数据 + * + * 参数列表: + * dst:用于存储读取数据的目标缓冲区 + * destLen:目标缓冲区的长度 + * blockNumber:要读取的块号 + * rbStruct:包含读取操作所需信息的结构体 + */ +size_t ReadAllChunkOfBlock(char *dst, size_t destLen, BlockNumber blockNumber, ReadBlockChunksStruct &rbStruct) { - PageCompressHeader* header = rbStruct.header; + PageCompressHeader *header = rbStruct.header; + // 检查块号是否超出最大块号,如果是则报错 if (blockNumber >= header->nblocks) { ereport(ERROR, - (ERRCODE_INVALID_PARAMETER_VALUE, - errmsg("blocknum \"%u\" exceeds max block number", blockNumber))); + (ERRCODE_INVALID_PARAMETER_VALUE, errmsg("blocknum \"%u\" exceeds max block number", blockNumber))); } - const char* fileName = rbStruct.fileName; + const char *fileName = rbStruct.fileName; decltype(PageCompressHeader::chunk_size) chunkSize = header->chunk_size; decltype(ReadBlockChunksStruct::segmentNo) segmentNo = rbStruct.segmentNo; - PageCompressAddr* currentAddr = GET_PAGE_COMPRESS_ADDR(header, chunkSize, blockNumber); + PageCompressAddr *currentAddr = GET_PAGE_COMPRESS_ADDR(header, chunkSize, blockNumber); size_t tryCount = 0; /* for empty chunks write */ @@ -399,25 +491,30 @@ size_t ReadAllChunkOfBlock(char *dst, size_t destLen, BlockNumber blockNumber, R for (uint8 i = 0; i < nchunks; ++i) { off_t seekPos = (off_t)OFFSET_OF_PAGE_COMPRESS_CHUNK(chunkSize, currentAddr->chunknos[i]); uint8 start = i; + // 寻找连续的块,合并读取 while (i < nchunks - 1 && currentAddr->chunknos[i + 1] == currentAddr->chunknos[i] + 1) { i++; } + // 将文件指针定位到块的位置 if (fseeko(rbStruct.fp, seekPos, SEEK_SET) != 0) { ReleaseMap(header, fileName); ereport(ERROR, (errcode_for_file_access(), errmsg("could not seek in file \"%s\": %m", fileName))); } size_t readAmount = chunkSize * (i - start + 1); + // 从文件中读取块数据,并将其写入目标缓冲区 if (fread(dst + start * chunkSize, 1, readAmount, rbStruct.fp) != readAmount && ferror(rbStruct.fp)) { ReleaseMap(header, fileName); ereport(ERROR, (errcode_for_file_access(), errmsg("could not read file \"%s\": %m", fileName))); } } + // 如果块没有数据,则跳出循环 if (nchunks == 0) { break; } char *data = NULL; size_t dataLen; uint32 crc32; + // 从目标缓冲区中获取数据、数据长度和校验和 if (PageIs8BXidHeapVersion(dst)) { HeapPageCompressData *heapPageData = (HeapPageCompressData *)dst; data = heapPageData->data; @@ -429,6 +526,7 @@ size_t ReadAllChunkOfBlock(char *dst, size_t destLen, BlockNumber blockNumber, R dataLen = heapPageData->size; crc32 = heapPageData->crc32; } + // 从目标缓冲区中获取数据、数据长度和校验和 if (DataBlockChecksum(data, dataLen, true) == crc32) { break; } @@ -439,28 +537,31 @@ size_t ReadAllChunkOfBlock(char *dst, size_t destLen, BlockNumber blockNumber, R } else { ReleaseMap(header, fileName); ereport(ERROR, - (errcode_for_file_access(), - errmsg("base backup cheksum or Decompressed blockno %u failed in file \"%s\", aborting backup. " - "nchunks: %u, allocatedChunks: %u, segno: %d.", - blockNumber, - fileName, - nchunks, - allocatedChunks, - segmentNo))); + (errcode_for_file_access(), + errmsg("base backup cheksum or Decompressed blockno %u failed in file \"%s\", aborting backup. " + "nchunks: %u, allocatedChunks: %u, segno: %d.", + blockNumber, fileName, nchunks, allocatedChunks, segmentNo))); } } while (true); + // 如果已分配的块数大于实际块数,将多余的块数据清零 if (allocatedChunks > nchunks) { auto currentWriteSize = nchunks * chunkSize; securec_check( memset_s(dst + currentWriteSize, destLen - currentWriteSize, 0, (allocatedChunks - nchunks) * chunkSize), - "", - ""); + "", ""); } return allocatedChunks * chunkSize; } - -void ReleaseMap(PageCompressHeader* map, const char* fileName) +/* + * 功能:释放页压缩内存映射 + * + * 参数列表: + * map:PageCompressHeader 结构的指针,表示页压缩内存映射的头部信息 + * fileName:文件名,表示要释放的映射对应的文件 + */ +void ReleaseMap(PageCompressHeader *map, const char *fileName) { + // 检查映射指针是否有效,并尝试解除映射 if (map != NULL && pc_munmap(map) != 0) { ereport(WARNING, (errcode_for_file_access(), errmsg("could not munmap file \"%s\": %m", fileName))); } -- 2.34.1 From 639fb3990c0b1f79bde38b7eedc2e7813d72be8c Mon Sep 17 00:00:00 2001 From: nuoya <1204149038@qq.com> Date: Mon, 25 Sep 2023 15:31:59 +0800 Subject: [PATCH 12/50] Update segstore.cpp --- src/gausskernel/storage/smgr/segstore.cpp | 741 ++++++++++++++++++---- 1 file changed, 627 insertions(+), 114 deletions(-) diff --git a/src/gausskernel/storage/smgr/segstore.cpp b/src/gausskernel/storage/smgr/segstore.cpp index f2ddbfc34..5c6ec005a 100755 --- a/src/gausskernel/storage/smgr/segstore.cpp +++ b/src/gausskernel/storage/smgr/segstore.cpp @@ -94,14 +94,25 @@ * 1. drop table. * 2. delete a list of buckets during redistributing. */ +/* + * 功能:更新共享内存段的时间轴信息。 + */ static void seg_update_timeline() { - pg_atomic_add_fetch_u32(&g_instance.segment_cxt.segment_drop_timeline, 1); + // 使用 pg_atomic_add_fetch_u32 函数来原子地增加一个32位无符号整数的值 + pg_atomic_add_fetch_u32(&g_instance.segment_cxt.segment_drop_timeline, 1); // 原子操作函数 } - +/* + * 功能:获取共享内存段的时间轴信息 + */ static uint32 seg_get_drop_timeline() { + // 创建一个名为 expected 的本地变量,初始化为 0 uint32 expected = 0; + // 创建一个名为 expected 的本地变量,初始化为 0 + // 这个函数的目的是将 g_instance.segment_cxt.segment_drop_timeline 的值与 expected 进行比较 + // 如果相等,则将 g_instance.segment_cxt.segment_drop_timeline 的值设置为 0 + // 这个操作是原子的,确保在多线程或多进程环境中的一致性 pg_atomic_compare_exchange_u32(&g_instance.segment_cxt.segment_drop_timeline, &expected, 0); return expected; } @@ -111,29 +122,55 @@ struct SegmentHeadLockPartitionTag { Oid dbNode; BlockNumber head; }; - +/* + * 功能:锁定分段头部的特定分区,以进行同步访问 + * + * 参数列表: + * spcNode:表示表空间节点的唯一标识符 + * dbNode:表示数据库节点的唯一标识符 + * head:表示分段头部的块号 + * mode:表示锁的模式,例如共享锁或排他锁 + */ void LockSegmentHeadPartition(Oid spcNode, Oid dbNode, BlockNumber head, LWLockMode mode) { + // 创建一个 SegmentHeadLockPartitionTag 结构体 tag,并初始化其成员变量。 SegmentHeadLockPartitionTag tag = {.spcNode = spcNode, .dbNode = dbNode, head = head}; + + // 使用哈希函数 hashquickany 计算一个哈希码,以便用于锁的选择。 uint32 hashcode = hashquickany(0xFFFFFFFF, (unsigned char *)&tag, sizeof(tag)); + + // 根据哈希码选择一个特定的 LWLock。 LWLock *lock = SegmentHeadPartitionLock(hashcode); + + // 获取指定的 LWLock,使用给定的锁模式 mode。 LWLockAcquire(lock, mode); } - +/* + * 功能:解锁分段头部的特定分区,以允许其他进程或线程访问 + * + * 参数列表: + * spcNode:表示表空间节点的唯一标识符 + * dbNode:表示数据库节点的唯一标识符 + * head:表示分段头部的块号 + */ void UnlockSegmentHeadPartition(Oid spcNode, Oid dbNode, BlockNumber head) { + // 创建一个 SegmentHeadLockPartitionTag 结构体 tag,并初始化其成员变量。 SegmentHeadLockPartitionTag tag = {.spcNode = spcNode, .dbNode = dbNode, head = head}; + // 使用哈希函数 hashquickany 计算一个哈希码,以便用于锁的选择。 uint32 hashcode = hashquickany(0xFFFFFFFF, (unsigned char *)&tag, sizeof(tag)); + // 根据哈希码选择一个特定的 LWLock。 LWLock *lock = SegmentHeadPartitionLock(hashcode); + // 释放指定的 LWLock,允许其他进程或线程访问。 LWLockRelease(lock); } #define IsBucketSMgrRelation(reln) IsBucketFileNode((reln)->smgr_rnode.node) -#define ReadSegmentBuffer(spc, blockno) \ +#define ReadSegmentBuffer(spc, blockno) \ ReadBufferFast((spc), EXTENT_GROUP_RNODE((spc), SEGMENT_HEAD_EXTENT_SIZE), MAIN_FORKNUM, (blockno), RBM_NORMAL) -#define ReadLevel0Buffer(spc, blockno) \ +#define ReadLevel0Buffer(spc, blockno) \ ReadBufferFast((spc), EXTENT_GROUP_RNODE((spc), LEVEL0_PAGE_EXTENT_SIZE), MAIN_FORKNUM, (blockno), RBM_NORMAL) /* @@ -165,20 +202,51 @@ inline static void SegLogicPageIdToExtentId(BlockNumber logic_id, uint32 *extent *offset = logic_id % EXT_SIZE_8192; } } - +/* + * 功能:记录分段中的桶移动操作到XLOG + * + * 参数列表: + * mapentry:指向 xl_seg_bktentry_tag_t 结构的指针数组,表示桶移动的信息 + * nentry:表示 mapentry 数组中的条目数量 + * buffer:表示要记录的缓冲区 + */ void log_move_segment_buckets(xl_seg_bktentry_tag_t *mapentry, uint32 nentry, Buffer buffer) { + // 使用 XLogAtomicOpRegisterBuffer 函数来注册一个缓冲区以记录 XLOG 信息。 + // 参数包括要注册的缓冲区,指定不记录缓冲区图像,指定记录的操作类型,以及指定在提交时解锁并释放缓冲区。 XLogAtomicOpRegisterBuffer(buffer, REGBUF_NO_IMAGE, SPCXLOG_SEG_MOVE_BUCKETS, XLOG_COMMIT_UNLOCK_RELEASE_BUFFER); + + // 使用 XLogAtomicOpRegisterBufData 函数来注册一个或多个数据块以记录在 XLOG 中。 + // 在这里,首先注册 nentry 的值,表示后续记录的 mapentry 数组的条目数量。 XLogAtomicOpRegisterBufData((char *)&nentry, sizeof(uint32)); + + // 接下来,注册 mapentry 数组的数据,数据的长度为 sizeof(xl_seg_bktentry_tag_t) * nentry。 XLogAtomicOpRegisterBufData((char *)mapentry, sizeof(xl_seg_bktentry_tag_t) * nentry); } - +/* + * 功能:记录分段的Redis信息到XLOG + * + * 参数列表: + * dredis:指向 SegRedisInfo 结构的指针,表示目标分段的Redis信息 + * sredis:指向 SegRedisInfo 结构的指针,表示源分段的Redis信息 + * dbuffer:表示目标分段的缓冲区 + * sbuffer:表示源分段的缓冲区 + */ void log_move_segment_redisinfo(SegRedisInfo *dredis, SegRedisInfo *sredis, Buffer dbuffer, Buffer sbuffer) { - XLogAtomicOpRegisterBuffer(dbuffer, REGBUF_NO_IMAGE, SPCXLOG_BUCKET_ADD_REDISINFO, XLOG_COMMIT_UNLOCK_RELEASE_BUFFER); + // 使用 XLogAtomicOpRegisterBuffer 函数来注册目标分段的缓冲区以记录 XLOG 信息。 + // 参数包括要注册的缓冲区,指定不记录缓冲区图像,指定记录的操作类型,以及指定在提交时解锁并释放缓冲区。 + XLogAtomicOpRegisterBuffer(dbuffer, REGBUF_NO_IMAGE, SPCXLOG_BUCKET_ADD_REDISINFO, + XLOG_COMMIT_UNLOCK_RELEASE_BUFFER); + + // 使用 XLogAtomicOpRegisterBufData 函数来注册目标分段的 Redis 信息的数据块。 XLogAtomicOpRegisterBufData((char *)dredis, sizeof(SegRedisInfo)); - XLogAtomicOpRegisterBuffer(sbuffer, REGBUF_NO_IMAGE, SPCXLOG_BUCKET_ADD_REDISINFO, XLOG_COMMIT_UNLOCK_RELEASE_BUFFER); + // 使用 XLogAtomicOpRegisterBuffer 函数来注册源分段的缓冲区以记录 XLOG 信息。 + XLogAtomicOpRegisterBuffer(sbuffer, REGBUF_NO_IMAGE, SPCXLOG_BUCKET_ADD_REDISINFO, + XLOG_COMMIT_UNLOCK_RELEASE_BUFFER); + + // 使用 XLogAtomicOpRegisterBufData 函数来注册源分段的 Redis 信息的数据块。 XLogAtomicOpRegisterBufData((char *)sredis, sizeof(SegRedisInfo)); } @@ -190,22 +258,33 @@ RelFileNode EXTENT_GROUP_RNODE(SegSpace *spc, ExtentSize extentSize) .bucketNode = SegmentBktId, .opt = 0}; } - -void seg_head_update_xlog(Buffer head_buffer, SegmentHead *seg_head, int level0_slot, - int level1_slot) +/* + * 功能:记录更新分段头部信息的XLOG + * + * 参数列表: + * head_buffer:表示分段头部的缓冲区 + * seg_head:指向 SegmentHead 结构的指针,表示分段头部信息 + * level0_slot:表示要更新的 level 0 插槽的索引,如果不需要更新,则为负数 + * level1_slot:表示要更新的 level 1 插槽的索引,如果不需要更新,则为负数 + */ +void seg_head_update_xlog(Buffer head_buffer, SegmentHead *seg_head, int level0_slot, int level1_slot) { + // 创建 XLogDataUpdateSegmentHead 结构体 xlog_data,并将其初始化为零 XLogDataUpdateSegmentHead xlog_data; errno_t rc = memset_s(&xlog_data, sizeof(xlog_data), 0, sizeof(xlog_data)); securec_check(rc, "\0", "\0"); + // 设置要记录到 XLOG 的数据 xlog_data.level0_slot = level0_slot; xlog_data.level1_slot = level1_slot; + // 如果 level0_slot 大于等于0,则记录 seg_head 中对应索引的 level 0 插槽的值,否则设置为 InvalidBlockNumber if (level0_slot >= 0) { xlog_data.level0_value = seg_head->level0_slots[level0_slot]; } else { xlog_data.level0_value = InvalidBlockNumber; } + // 如果 level1_slot 大于等于0,则记录 seg_head 中对应索引的 level 1 插槽的值,否则设置为 InvalidBlockNumber if (level1_slot >= 0) { xlog_data.level1_value = seg_head->level1_slots[level1_slot]; } else { @@ -221,28 +300,43 @@ void seg_head_update_xlog(Buffer head_buffer, SegmentHead *seg_head, int level0_ XLogAtomicOpRegisterBuffer(head_buffer, REGBUF_KEEP_DATA, SPCXLOG_UPDATE_SEGHEAD, XLOG_COMMIT_KEEP_BUFFER_STATE); XLogAtomicOpRegisterBufData((char *)&xlog_data, sizeof(xlog_data)); } - +/* + * 功能:初始化新的 Level 0 页并记录到XLOG + * + * 参数列表: + * spc:指向 SegSpace 结构的指针,表示分段空间信息 + * new_extent_id:新的范围页扩展标识符 + * seg_head_buffer:表示分段头部的缓冲区 + * new_level0_page:新的 Level 0 页号 + * first_extent:第一个范围页号 + */ void seg_init_new_level0_page(SegSpace *spc, uint32_t new_extent_id, Buffer seg_head_buffer, BlockNumber new_level0_page, BlockNumber first_extent) { + // 从 seg_head_buffer 中获取分段头部信息 SegmentHead *seg_head = (SegmentHead *)PageGetContents(BufferGetPage(seg_head_buffer)); + // 读取新的 Level 0 缓冲区并以排他锁方式锁定它 Buffer new_level0_buffer = ReadLevel0Buffer(spc, new_level0_page); LockBuffer(new_level0_buffer, BUFFER_LOCK_EXCLUSIVE); Page level0_page = BufferGetPage(new_level0_buffer); + // 初始化 Level 0 页 SegPageInit(level0_page, BLCKSZ); PageHeader header = (PageHeader)level0_page; header->pd_lower += sizeof(BMTLevel0Page); + // 获取 Level 0 页的内容 BMTLevel0Page *bmt_level0_page = (BMTLevel0Page *)PageGetContents(level0_page); bmt_level0_page->slots[0] = first_extent; bmt_level0_page->magic = BMTLEVEL0_MAGIC; + // 计算新范围页的 Level 1 插槽 uint32 level1_slot = ExtentIdToLevel1Slot(new_extent_id); seg_head->level1_slots[level1_slot] = new_level0_page; // XLog issue + // 处理 XLog 问题 { // new level0 page, we do not use level0 page buffer anymore, release it. XLogAtomicOpRegisterBuffer(new_level0_buffer, REGBUF_WILL_INIT | REGBUF_KEEP_DATA, SPCXLOG_NEW_LEVEL0_PAGE, @@ -253,20 +347,33 @@ void seg_init_new_level0_page(SegSpace *spc, uint32_t new_extent_id, Buffer seg_ seg_head_update_xlog(seg_head_buffer, seg_head, -1, level1_slot); } } - +/* + * 功能:记录新范围页在 Level 0 页上的信息到XLOG + * + * 参数列表: + * spc:指向 SegSpace 结构的指针,表示分段空间信息 + * seg_head_buffer:表示分段头部的缓冲区 + * new_extent_id:新的范围页扩展标识符 + * new_extent_first_pageno:新范围页的第一个页号 + */ void seg_record_new_extent_on_level0_page(SegSpace *spc, Buffer seg_head_buffer, uint32 new_extent_id, BlockNumber new_extent_first_pageno) { + // 从 seg_head_buffer 中获取分段头部信息 SegmentHead *seg_head = (SegmentHead *)PageGetContents(BufferGetPage(seg_head_buffer)); + // 计算新范围页的 Level 1 插槽 uint32 level1_slot = ExtentIdToLevel1Slot(new_extent_id); + // 获取对应的 Level 0 页号和在 Level 0 页中的偏移量 BlockNumber level0_pageno = seg_head->level1_slots[level1_slot]; uint32 level0_offset = ExtentIdToLevel0PageOffset(new_extent_id); + // 读取并锁定 Level 0 缓冲区,并获取其内容 Buffer level0_buffer = ReadLevel0Buffer(spc, level0_pageno); BMTLevel0Page *bmt_level0_page = (BMTLevel0Page *)PageGetContents(BufferGetPage(level0_buffer)); LockBuffer(level0_buffer, BUFFER_LOCK_EXCLUSIVE); + // 更新 Level 0 页上对应偏移量的 slot 为新范围页的第一个页号 bmt_level0_page->slots[level0_offset] = new_extent_first_pageno; // XLogIssue @@ -325,31 +432,52 @@ void seg_extend_segment(SegSpace *spc, ForkNumber forknum, Buffer seg_head_buffe } } SEGMENTTEST(SEG_STORE_EXTEND_EXTENT, (errmsg("SEG_STORE_EXTEND_EXTENT %s: segment extend an extent!\n", - g_instance.attr.attr_common.PGXCNodeName))); + g_instance.attr.attr_common.PGXCNodeName))); XLogAtomicOpCommit(); END_CRIT_SECTION(); } - +/* + * 功能:获取范围页的位置信息 + * + * 参数列表: + * spc:指向 SegSpace 结构的指针,表示分段空间信息 + * seg_head:指向 SegmentHead 结构的指针,表示分段头部信息 + * extent_id:范围页扩展标识符 + */ BlockNumber seg_extent_location(SegSpace *spc, SegmentHead *seg_head, int extent_id) { + // 如果 extent_id 小于 BMT_HEADER_LEVEL0_SLOTS,则直接返回 seg_head 中对应索引的 level 0 插槽的值 if (extent_id < BMT_HEADER_LEVEL0_SLOTS) { return seg_head->level0_slots[extent_id]; } else { + // 如果 extent_id 小于 BMT_HEADER_LEVEL0_SLOTS,则直接返回 seg_head 中对应索引的 level 0 插槽的值 BlockNumber level0_page_id = ExtentIdToLevel0PageNumber(seg_head, extent_id); + // 读取 Level 0 缓冲区并以共享锁方式锁定它 Buffer buffer = ReadLevel0Buffer(spc, level0_page_id); LockBuffer(buffer, BUFFER_LOCK_SHARE); + // 获取 Level 0 页的内容 BMTLevel0Page *level0_page = (BMTLevel0Page *)PageGetContents(BufferGetPage(buffer)); + // 计算在 Level 0 页中的偏移量 extent_id -= BMT_HEADER_LEVEL0_SLOTS; + // 获取范围页的起始页号,并释放 Level 0 缓冲区 BlockNumber start = level0_page->slots[extent_id % BMT_LEVEL0_SLOTS]; SegUnlockReleaseBuffer(buffer); + // 返回范围页的起始页号 return start; } } - +/* + * 功能:将逻辑页号映射到物理页号并返回相关信息 + * + * 参数列表: + * reln:SMgrRelation 结构,表示存储管理器关系信息 + * seg_head:指向 SegmentHead 结构的指针,表示分段头部信息 + * logic_id:逻辑页号 + */ SegPageLocation seg_logic_to_physic_mapping(SMgrRelation reln, SegmentHead *seg_head, BlockNumber logic_id) { uint32 extent_id; @@ -363,11 +491,15 @@ SegPageLocation seg_logic_to_physic_mapping(SMgrRelation reln, SegmentHead *seg_ errhint("cannot do segment address translation during recovery"))); } + // 将逻辑页号转换为范围页扩展标识符、偏移量和范围页大小 SegLogicPageIdToExtentId(logic_id, &extent_id, &offset, &extent_size); + // 获取范围页的起始页号 BlockNumber extent_start = seg_extent_location(reln->seg_space, seg_head, extent_id); + // 计算物理页号 blocknum = extent_start + offset; + // 返回 SegPageLocation 结构,包含物理页号和相关信息 return { .extent_size = extent_size, .extent_id = extent_id, @@ -397,7 +529,7 @@ static bool open_segment(SMgrRelation reln, ForkNumber forknum, bool create, XLo /* Normal Table Segment Head API */ static bool normal_open_segment(SMgrRelation reln, int forknum, bool create); static BlockNumber normal_alloc_segment(Oid tablespace_id, Oid database_id, BlockNumber preassigned_block, - ExtentInversePointer iptr, bool is_heap_seg_head=false); + ExtentInversePointer iptr, bool is_heap_seg_head = false); void eg_init_segment_head_buffer_content(Buffer seg_head_buffer, BlockNumber seg_head_blocknum, XLogRecPtr lsn) { @@ -479,11 +611,12 @@ static bool normal_open_segment(SMgrRelation reln, int forknum, bool create) main_buffer = ReadSegmentBuffer(reln->seg_space, reln->seg_desc[MAIN_FORKNUM]->head_blocknum); main_head = (SegmentHead *)PageGetContents(BufferGetBlock(main_buffer)); if (unlikely(!IsNormalSegmentHead(main_head))) { - ereport(PANIC, (errmodule(MOD_SEGMENT_PAGE), errmsg("Segment head magic value 0x%lx is invalid," - "head lsn 0x%lx(maybe wrong). Rnode [%u, %u, %u, %d], head blocknum %u.", - main_head->magic, main_head->lsn, reln->smgr_rnode.node.spcNode, reln->smgr_rnode.node.dbNode, - reln->smgr_rnode.node.relNode, reln->smgr_rnode.node.bucketNode, - reln->seg_desc[MAIN_FORKNUM]->head_blocknum))); + ereport(PANIC, (errmodule(MOD_SEGMENT_PAGE), + errmsg("Segment head magic value 0x%lx is invalid," + "head lsn 0x%lx(maybe wrong). Rnode [%u, %u, %u, %d], head blocknum %u.", + main_head->magic, main_head->lsn, reln->smgr_rnode.node.spcNode, + reln->smgr_rnode.node.dbNode, reln->smgr_rnode.node.relNode, + reln->smgr_rnode.node.bucketNode, reln->seg_desc[MAIN_FORKNUM]->head_blocknum))); } /* @@ -520,8 +653,9 @@ static bool normal_open_segment(SMgrRelation reln, int forknum, bool create) fork_head_blocknum = new_head_blocknum; XLogAtomicOpCommit(); - SEGMENTTEST(FREE_EXTENT_ADD_FSM_FORK, (errmsg("FREE_EXTENT_ADD_FSM_FORK %s: add fsm fork, free extent success!\n", - g_instance.attr.attr_common.PGXCNodeName))); + SEGMENTTEST(FREE_EXTENT_ADD_FSM_FORK, + (errmsg("FREE_EXTENT_ADD_FSM_FORK %s: add fsm fork, free extent success!\n", + g_instance.attr.attr_common.PGXCNodeName))); ereport(DEBUG5, (errmodule(MOD_SEGMENT_PAGE), errmsg("Add fork %d for segment %u, fork head is %u", forknum, @@ -540,8 +674,7 @@ CREATE_DESC: /* * Initialize the segment descriptor in SMgrRelationData. */ - SegmentDesc *fork_desc = - (SegmentDesc *)MemoryContextAlloc(LocalSmgrStorageMemoryCxt(), sizeof(SegmentDesc)); + SegmentDesc *fork_desc = (SegmentDesc *)MemoryContextAlloc(LocalSmgrStorageMemoryCxt(), sizeof(SegmentDesc)); fork_desc->head_blocknum = fork_head_blocknum; fork_desc->timeline = seg_get_drop_timeline(); SegmentCheck(fork_head_blocknum >= DF_MAP_GROUP_SIZE); @@ -594,10 +727,11 @@ static void free_last_extent(SegSpace *spc, ForkNumber forknum, Buffer head_buff END_CRIT_SECTION(); SEGMENTTEST(FREE_EXTENT_DROP_EXTENTS, (errmsg("FREE_EXTENT_DROP_EXTENTS %s: " - "drop some extents,remain extents can drop !\n", g_instance.attr.attr_common.PGXCNodeName))); + "drop some extents,remain extents can drop !\n", + g_instance.attr.attr_common.PGXCNodeName))); XLogAtomicOpCommit(); - /* If all extents is freed, flush segment header to disk */ + /* If all extents is freed, flush segment header to disk */ if (head->total_blocks == 0) { SegmentCheck(head->nextents == 0); FlushOneSegmentBuffer(head_buffer); @@ -746,103 +880,165 @@ static void bucket_get_mapentry(BktMainHead *main_head, int4 bucketid, int forkn *map_blocknum = main_head->bkt_map[blockid]; SegmentCheck(*map_blocknum != 0); } - +/* + * 功能:初始化桶位图页并设置相应的信息 + * + * 参数列表: + * map_buffer:表示桶位图页的缓冲区 + * lsn:XLog记录位置 + */ void bucket_init_map_page(Buffer map_buffer, XLogRecPtr lsn) { + // 获取桶位图页 Page map_page = BufferGetPage(map_buffer); + // 初始化桶位图页,设置大小为 BLCKSZ(块大小) SegPageInit(map_page, BLCKSZ); + // 初始化桶位图页,设置大小为 BLCKSZ(块大小) BktHeadMapBlock *map_block = (BktHeadMapBlock *)PageGetContents(map_page); + // 设置桶位图页的魔术数字和记录位置(lsn) map_block->magic = BUCKETMAP_MAGIC; map_block->lsn = lsn; + // 初始化桶位图页中的桶位图项为无效块号 for (uint32 j = 0; j < BktMapEntryNumberPerBlock; j++) { map_block->head_block[j] = InvalidBlockNumber; } + // 增加桶位图页的 pd_lower,以表示数据的结束位置 ((PageHeader)map_page)->pd_lower += sizeof(BktHeadMapBlock); } - +/* + * 功能:分配一个新的桶段 + * + * 参数列表: + * tablespace_id:表空间的标识符 + * database_id:数据库的标识符 + * preassigned_block:预分配的块号 + */ static BlockNumber bucket_alloc_segment(Oid tablespace_id, Oid database_id, BlockNumber preassigned_block) { + // 打开指定表空间和数据库的分段空间 SegSpace *spc = spc_open(tablespace_id, database_id, true); + // 创建一个逆向指针,标识为桶段(BUCKET_SEGMENT),并设置所有者为无效块号 ExtentInversePointer iptr = {.flag = SPC_INVRSPTR_ASSEMBLE_FLAG(BUCKET_SEGMENT, 0), .owner = InvalidBlockNumber}; /* Allocate BktMainHead first */ + // 开始一个原子操作 XLogAtomicOpStart(); + // 分配 BktMainHead 所需的块号,大小为 SEGMENT_HEAD_EXTENT_SIZE BlockNumber main_head_blocknum = spc_alloc_extent(spc, SEGMENT_HEAD_EXTENT_SIZE, MAIN_FORKNUM, InvalidBlockNumber, iptr); + // 读取分配的块号对应的缓冲区 Buffer main_head_buffer = ReadSegmentBuffer(spc, main_head_blocknum); + // 锁定分配的块号对应的缓冲区,以排他锁方式 LockBuffer(main_head_buffer, BUFFER_LOCK_EXCLUSIVE); + // 获取 BktMainHead 的页面 Page main_head_page = BufferGetPage(main_head_buffer); + // 获取 BktMainHead 的页面 SegPageInit(main_head_page, BLCKSZ); + // 增加 BktMainHead 页面的 pd_lower,以表示数据的结束位置 ((PageHeader)main_head_page)->pd_lower += sizeof(BktMainHead); + // 获取 BktMainHead 结构的指针 BktMainHead *main_head = (BktMainHead *)PageGetContents(main_head_page); + // 获取当前 XLog 记录位置(lsn) XLogRecPtr lsn = GetXLogInsertRecPtr(); + // 设置 BktMainHead 的魔术数字、lsn、redis_info 信息 main_head->magic = BUCKET_SEGMENT_MAGIC; main_head->lsn = lsn; main_head->redis_info.redis_xid = InvalidTransactionId; main_head->redis_info.nwords = 0; + // 注册要记录的缓冲区,并指定将初始化(WILL_INIT)缓冲区,以及记录初始化的 XLOG 信息 XLogAtomicOpRegisterBuffer(main_head_buffer, REGBUF_WILL_INIT, SPCXLOG_INIT_BUCKET_HEAD, XLOG_COMMIT_KEEP_BUFFER_STATE); + // 注册要记录的数据,即记录当前 XLog 记录位置(lsn) XLogAtomicOpRegisterBufData((char *)&lsn, sizeof(XLogRecPtr)); + // 提交原子操作 XLogAtomicOpCommit(); + // 为 BktMainHead 分配桶位图块(BktMapBlock),并设置相关 XLOG 记录 for (uint32 i = 0; i < BktMapBlockNumber; i++) { bucket_ensure_mapblock(spc, main_head_buffer, i, lsn); } - + // 增加桶位图页的 pd_lower,以表示数据的结束位置 SegUnlockReleaseBuffer(main_head_buffer); return main_head_blocknum; } - +/* + * 功能:加载桶的主头部信息 + * + * 参数列表: + * spc:指向 SegSpace 结构的指针,表示分段空间信息 + * blocknum:桶的块号 + * buffer:用于返回加载的缓冲区 + * head:用于返回加载的 BktMainHead 结构指针 + */ static inline void bucket_load_main_head(SegSpace *spc, BlockNumber blocknum, Buffer *buffer, BktMainHead **head) { *buffer = ReadSegmentBuffer(spc, blocknum); *head = (BktMainHead *)PageGetContents(BufferGetPage(*buffer)); } - +/* + * 功能:打开或创建一个桶段(bucket segment) + * + * 参数列表: + * reln:SMgrRelation 结构,表示存储管理器关系信息 + * forknum:分叉号 + * create:是否创建 + * lsn:XLog 记录位置 + */ static bool bucket_open_segment(SMgrRelation reln, int forknum, bool create, XLogRecPtr lsn) { + // 如果已经存在对应 forknum 的分段描述符,则直接返回 true if (reln->seg_desc[forknum] != NULL) { return true; } + // 用于存储桶的主头部信息的缓冲区和指针 Buffer main_buffer; BktMainHead *main_head; + // 通过块号加载桶的主头部信息 bucket_load_main_head(reln->seg_space, reln->smgr_rnode.node.relNode, &main_buffer, &main_head); + // 检查加载的主头部信息是否符合预期 SegmentCheck(IsBucketMainHead(main_head)); + // 获取桶的编号 int4 bucketid = reln->smgr_rnode.node.bucketNode; /* find the map block */ + // 查找桶位图中对应的桶位图块号和桶位图项 ID BlockNumber map_blocknum; int map_entry_id; bucket_get_mapentry(main_head, bucketid, forknum, &map_blocknum, &map_entry_id); - if (map_blocknum == InvalidBlockNumber) - { + // 如果桶位图块号为无效块号,且提供的 XLog 记录位置有效,则返回 false + if (map_blocknum == InvalidBlockNumber) { SegmentCheck(XLogRecPtrIsValid(lsn)); SegReleaseBuffer(main_buffer); return false; } /* get the entry */ + // 获取桶位图块的缓冲区并以共享锁方式锁定 Buffer map_buffer = ReadSegmentBuffer(reln->seg_space, map_blocknum); LockBuffer(map_buffer, BUFFER_LOCK_SHARE); BktHeadMapBlock *map_block = (BktHeadMapBlock *)PageGetContents(BufferGetPage(map_buffer)); + // 如果提供的 XLog 记录位置有效,并且不满足条件,返回 false if (XLogRecPtrIsValid(lsn) && (XLByteLE(lsn, map_block->lsn) || map_block->magic != BUCKETMAP_MAGIC)) { SegUnlockReleaseBuffer(map_buffer); SegReleaseBuffer(main_buffer); return false; } + // 获取桶的主块号 BlockNumber head_blocknum = map_block->head_block[map_entry_id]; + // 如果主块号为无效块号 if (head_blocknum == InvalidBlockNumber) { /* the entry is not initialized */ + // 如果不允许创建,则返回 false if (create == false) { SegUnlockReleaseBuffer(map_buffer); SegReleaseBuffer(main_buffer); @@ -859,69 +1055,103 @@ static bool bucket_open_segment(SMgrRelation reln, int forknum, bool create, XLo * require/acquire above */ if (head_blocknum == InvalidBlockNumber) { + // 开始一个原子操作 XLogAtomicOpStart(); + // 创建逆向指针,标识为桶头部(BUCKET_HEAD) ExtentInversePointer iptr = { .flag = SPC_INVRSPTR_ASSEMBLE_FLAG(BUCKET_HEAD, forknum * MAX_BUCKETMAPLEN + reln->smgr_rnode.node.bucketNode), .owner = reln->smgr_rnode.node.relNode}; + // 分配主块号 head_blocknum = normal_alloc_segment(reln->smgr_rnode.node.spcNode, reln->smgr_rnode.node.dbNode, InvalidBlockNumber, iptr); + // 设置桶位图块中对应桶位图项的主块号 map_block->head_block[map_entry_id] = head_blocknum; + // 注册要记录的桶位图块的缓冲区,并指定保持数据(KEEP_DATA),以及记录初始化的 XLOG 信息 XLogAtomicOpRegisterBuffer(map_buffer, REGBUF_KEEP_DATA, SPCXLOG_BUCKET_ADD_BKTHEAD, XLOG_COMMIT_KEEP_BUFFER_STATE); + // 注册要记录的数据,包括桶位图项 ID 和主块号 XLogAtomicOpRegisterBufData((char *)&map_entry_id, sizeof(int)); XLogAtomicOpRegisterBufData((char *)&head_blocknum, sizeof(BlockNumber)); XLogAtomicOpCommit(); + // 用于测试的宏,用于记录插入一个桶中的所有值 SEGMENTTEST(FREE_EXTENT_INSERT_ONE_BUCKET, (errmsg("FREE_EXTENT_INSERT_ONE_BUCKET %s: " - "all values insert into one bucket!\n", g_instance.attr.attr_common.PGXCNodeName))); + "all values insert into one bucket!\n", + g_instance.attr.attr_common.PGXCNodeName))); } } - SegmentDesc *seg_desc = - (SegmentDesc *)MemoryContextAlloc(LocalSmgrStorageMemoryCxt(), sizeof(SegmentDesc)); + // 分配并初始化分段描述符 + SegmentDesc *seg_desc = (SegmentDesc *)MemoryContextAlloc(LocalSmgrStorageMemoryCxt(), sizeof(SegmentDesc)); seg_desc->head_blocknum = head_blocknum; seg_desc->timeline = seg_get_drop_timeline(); + // 检查主块号是否大于等于 DF_MAP_GROUP_SIZE,如果是,表示有效 SegmentCheck(head_blocknum >= DF_MAP_GROUP_SIZE); + // 将分段描述符保存到 SMgrRelation 结构中 reln->seg_desc[forknum] = seg_desc; + // 解锁并释放桶位图块的缓冲区 SegUnlockReleaseBuffer(map_buffer); + // 释放桶的主块号的缓冲区 SegReleaseBuffer(main_buffer); return true; } - +/* + * 功能:断开一个桶(bucket)与相关段(segment)的连接 + * + * 参数列表: + * spc:指向 SegSpace 结构的指针,表示分段空间信息 + * rnode:RelFileNode 结构,表示关系文件节点信息 + */ static void bucket_unlink_one_bucket(SegSpace *spc, const RelFileNode &rnode) { + // 用于存储桶的主头部信息的缓冲区和指针 Buffer main_buffer; BktMainHead *main_head; + // 通过块号加载桶的主头部信息 bucket_load_main_head(spc, rnode.relNode, &main_buffer, &main_head); + // 检查加载的主头部信息是否符合预期 SegmentCheck(IsBucketMainHead(main_head)); + // 以共享锁方式锁定主头部信息的缓冲区 LockBuffer(main_buffer, BUFFER_LOCK_SHARE); + // 遍历所有可能的分叉号 for (int i = 0; i <= SEGMENT_MAX_FORKNUM; i++) { /* Locate the segment head for this fork */ BlockNumber map_blocknum; int map_entry_id; + // 查找桶位图块中对应的桶位图块块号和桶位图项 ID bucket_get_mapentry(main_head, rnode.bucketNode, i, &map_blocknum, &map_entry_id); + // 如果桶位图块块号不为 0 if (map_blocknum != 0) { + // 获取桶位图块的缓冲区并以独占锁方式锁定 Buffer map_buffer = ReadSegmentBuffer(spc, map_blocknum); LockBuffer(map_buffer, BUFFER_LOCK_EXCLUSIVE); BktHeadMapBlock *map_block = (BktHeadMapBlock *)PageGetContents(BufferGetPage(map_buffer)); + // 检查加载的桶位图块是否符合预期 SegmentCheck(IsBucketMapBlock(map_block)); BlockNumber head_blocknum = map_block->head_block[map_entry_id]; + // 如果主块号不为无效块号 if (head_blocknum != InvalidBlockNumber) { + // 获取主块的缓冲区并以独占锁方式锁定 Buffer head_buffer = ReadSegmentBuffer(spc, head_blocknum); LockBuffer(head_buffer, BUFFER_LOCK_EXCLUSIVE); + // 计算桶位图块中 head_block 数组的偏移量 off_t offset = offsetof(BktHeadMapBlock, head_block) + sizeof(BlockNumber) * map_entry_id; + // 释放一个段 free_one_segment(spc, i, head_buffer, head_blocknum, map_buffer, offset); + // 解锁并释放主块的缓冲区 SegUnlockReleaseBuffer(head_buffer); } + // 解锁并释放桶位图块的缓冲区 SegUnlockReleaseBuffer(map_buffer); } } + // 解锁并释放主头部信息的缓冲区 SegUnlockReleaseBuffer(main_buffer); } @@ -985,13 +1215,10 @@ static void bucket_unlink_segment(SegSpace *spc, RelFileNode rnode, Buffer main_ } } ereport(LOG, - (errmsg("rnode <%u %u %u %u>, xid [%lu, %lu, %lu], redis nwords %u, redis is [%s]", - rnode.spcNode, rnode.dbNode, rnode.relNode, rnode.bucketNode, - t_thrd.xact_cxt.ShmemVariableCache->oldestXid, - main_head->redis_info.redis_xid, - GetCurrentTransactionIdIfAny(), - main_head->redis_info.nwords, - redis_committed ? "committed" : "abort"))); + (errmsg("rnode <%u %u %u %u>, xid [%lu, %lu, %lu], redis nwords %u, redis is [%s]", rnode.spcNode, + rnode.dbNode, rnode.relNode, rnode.bucketNode, t_thrd.xact_cxt.ShmemVariableCache->oldestXid, + main_head->redis_info.redis_xid, GetCurrentTransactionIdIfAny(), main_head->redis_info.nwords, + redis_committed ? "committed" : "abort"))); } LockBuffer(main_buffer, BUFFER_LOCK_EXCLUSIVE); @@ -1016,7 +1243,8 @@ static void bucket_unlink_segment(SegSpace *spc, RelFileNode rnode, Buffer main_ ForkNumber forknum = (i * BktMapEntryNumberPerBlock + k) / MAX_BUCKETMAPLEN; free_one_segment(spc, forknum, head_buffer, head_blocknum, map_buffer, offset); SEGMENTTEST(FREE_EXTENT_DROP_BUCKETS, (errmsg("FREE_EXTENT_DROP_BUCKETS %s: " - "drop some buckets,remain buckets can drop !\n", g_instance.attr.attr_common.PGXCNodeName))); + "drop some buckets,remain buckets can drop !\n", + g_instance.attr.attr_common.PGXCNodeName))); SegUnlockReleaseBuffer(head_buffer); } } @@ -1031,70 +1259,115 @@ static void bucket_unlink_segment(SegSpace *spc, RelFileNode rnode, Buffer main_ LockBuffer(main_buffer, BUFFER_LOCK_UNLOCK); } - +/* + * 功能:计算指定分支的所有桶段的总块数 + * + * 参数列表: + * reln:SMgrRelation 结构,表示存储管理器关系信息 + * forknum:ForkNumber,表示分支号 + * main_buffer:Buffer,表示桶的主头部信息的缓冲区 + */ BlockNumber bucket_totalblocks(SMgrRelation reln, ForkNumber forknum, Buffer main_buffer) { + // 打开分段空间 SMgrOpenSpace(reln); + // 获取桶的主头部信息 BktMainHead *main_head = (BktMainHead *)PageGetContents(BufferGetPage(main_buffer)); + // 获取分段空间信息 SegSpace *spc = reln->seg_space; + // 以共享锁方式锁定主头部信息的缓冲区 LockBuffer(main_buffer, BUFFER_LOCK_SHARE); + // 初始化结果变量 BlockNumber result = 0; + // 用于存储当前桶位图块的块号和上一个桶位图块的块号 BlockNumber map_blocknum, last_map_blocknum = InvalidBlockNumber; + // 用于存储桶位图项 ID int map_entry_id; + // 初始化桶位图块缓冲区为无效缓冲区 Buffer map_buffer = InvalidBuffer; + // 遍历所有可能的桶位图项 for (int i = 0; i < MAX_BUCKETMAPLEN; i++) { + // 获取桶位图块中的桶位图项信息 bucket_get_mapentry(main_head, i, forknum, &map_blocknum, &map_entry_id); + // 如果当前桶位图块块号与上一个不同 if (map_blocknum != last_map_blocknum) { + // 如果上一个桶位图块缓冲区不是无效缓冲区,则解锁并释放它 if (map_buffer != InvalidBuffer) { SegUnlockReleaseBuffer(map_buffer); } + // 获取当前桶位图块的缓冲区并以共享锁方式锁定 map_buffer = ReadSegmentBuffer(spc, map_blocknum); LockBuffer(map_buffer, BUFFER_LOCK_SHARE); + // 更新上一个桶位图块块号 last_map_blocknum = map_blocknum; } + // 获取桶位图块的内容 BktHeadMapBlock *map_block = (BktHeadMapBlock *)PageGetContents(BufferGetPage(map_buffer)); + // 检查加载的桶位图块是否符合预期 SegmentCheck(IsBucketMapBlock(map_block)); + // 获取桶段的主块号 BlockNumber head_blocknum = map_block->head_block[map_entry_id]; + // 如果主块号不为无效块号 if (head_blocknum != InvalidBlockNumber) { + // 获取主块的缓冲区 Buffer head_buffer = ReadSegmentBuffer(spc, head_blocknum); + // 获取主块的内容 SegmentHead *seg_head = (SegmentHead *)PageGetContents(BufferGetPage(head_buffer)); + // 以共享锁方式锁定主块的缓冲区,并累加桶段的块数到结果变量 LockBuffer(head_buffer, BUFFER_LOCK_SHARE); result += seg_head->nblocks; + // 解锁并释放主块的缓冲区 SegUnlockReleaseBuffer(head_buffer); } } + // 如果最后一个桶位图块的缓冲区不是无效缓冲区,则解锁并释放它 if (map_buffer != InvalidBuffer) { SegUnlockReleaseBuffer(map_buffer); } + // 解锁主头部信息的缓冲区 LockBuffer(main_buffer, BUFFER_LOCK_UNLOCK); return result; } - +/* + * 功能:根据逻辑块号获取分段的物理位置信息 + * + * 参数列表: + * rnode:RelFileNode 结构,表示文件节点信息 + * forknum:ForkNumber,表示分支号 + * blocknum:BlockNumber,表示逻辑块号 + */ SegPageLocation seg_get_physical_location(RelFileNode rnode, ForkNumber forknum, BlockNumber blocknum) { SMgrRelation reln; + // 如果正在进行恢复过程,报错,不允许在恢复过程中获取分段的地址映射 if (RecoveryInProgress()) { ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("recovery is in progress"), errhint("cannot get segment address translation during recovery"))); } + // 打开存储管理器关系 reln = smgropen(rnode, InvalidBackendId); + // 读取分段的主头部信息缓冲区,不进行创建 Buffer buffer = read_head_buffer(reln, forknum, false); + // 检查获取的缓冲区是否有效 SegmentCheck(BufferIsValid(buffer)); + // 获取分段的主头部信息 SegmentHead *head = (SegmentHead *)PageGetContents(BufferGetBlock(buffer)); + // 调用 seg_logic_to_physic_mapping 函数获取逻辑块号对应的物理位置信息 SegPageLocation loc = seg_logic_to_physic_mapping(reln, head, blocknum); + // 释放获取的主头部信息缓冲区 SegReleaseBuffer(buffer); return loc; } @@ -1117,8 +1390,10 @@ BlockNumber seg_alloc_segment(Oid tablespace_id, Oid database_id, bool isbucket, ExtentInversePointer iptr = {.flag = SPC_INVRSPTR_ASSEMBLE_FLAG(SEGMENT_HEAD, 0), .owner = InvalidBlockNumber}; XLogAtomicOpStart(); result = normal_alloc_segment(tablespace_id, database_id, preassigned_block, iptr, true); - SEGMENTTEST(FREE_EXTENT_CREATE_SEGMENT_UNCOMMIT, (errmsg("FREE_EXTENT_CREATE_SEGMENT_UNCOMMIT %s: alloc segment success, " - "transation uncommit can drop segment!\n", g_instance.attr.attr_common.PGXCNodeName))); + SEGMENTTEST(FREE_EXTENT_CREATE_SEGMENT_UNCOMMIT, + (errmsg("FREE_EXTENT_CREATE_SEGMENT_UNCOMMIT %s: alloc segment success, " + "transation uncommit can drop segment!\n", + g_instance.attr.attr_common.PGXCNodeName))); XLogAtomicOpCommit(); } return result; @@ -1187,7 +1462,14 @@ static Buffer read_head_buffer(SMgrRelation reln, ForkNumber forknum, bool creat return buffer; } - +/* + * 功能:在重做期间读取分段的主头部信息缓冲区 + * + * 参数列表: + * reln:SMgrRelation 结构,表示存储管理器关系信息 + * forknum:ForkNumber,表示分支号 + * lsn:XLogRecPtr,表示逻辑复制点的位置信息 + */ static Buffer read_head_buffer_redo(SMgrRelation reln, ForkNumber forknum, XLogRecPtr lsn) { ASSERT_NORMAL_FORK(forknum); @@ -1201,23 +1483,34 @@ static Buffer read_head_buffer_redo(SMgrRelation reln, ForkNumber forknum, XLogR return buffer; } - - +/* + * 功能:删除分段 + * + * 参数列表: + * rnode:RelFileNode 结构,表示文件节点信息 + */ static void seg_unlink_segment(const RelFileNode &rnode) { + // 更新分段删除时间线 seg_update_timeline(); + // 打开分段空间 SegSpace *spc = spc_open(rnode.spcNode, rnode.dbNode, false); + // 检查分段空间是否有效 SegmentCheck(spc != NULL); + // 函数判断分段类型是普通表还是桶表,并分别调用相应的函数来删除分段 if (!IsBucketFileNode(rnode)) { + // 读取分段的缓冲区,并获取分段头部信息 Buffer buffer = ReadSegmentBuffer(spc, rnode.relNode); SegmentHead *head = (SegmentHead *)PageGetContents(BufferGetBlock(buffer)); + // 根据分段头部信息的魔术数判断分段类型 if (head->magic == SEGMENT_HEAD_MAGIC) { // normal table SEGMENTTEST(FREE_SEGMENT_DROP_SEGMENT, (errmsg("FREE_SEGMENT_DROP_SEGMENT %s: " - "drop segment tb success!\n", g_instance.attr.attr_common.PGXCNodeName))); + "drop segment tb success!\n", + g_instance.attr.attr_common.PGXCNodeName))); normal_unlink_segment(spc, rnode, buffer); } else if (head->magic == BUCKET_SEGMENT_MAGIC) { // Delete all buckets in the table @@ -1232,11 +1525,22 @@ static void seg_unlink_segment(const RelFileNode &rnode) bucket_unlink_one_bucket(spc, rnode); } } - +/* + * 功能:记录存储管理器 API 调用的日志信息 + * + * 参数列表: + * smgr_rnode:RelFileNodeBackend 结构,表示带有后端信息的文件节点信息 + * forknum:ForkNumber,表示分支号 + * blocknum:BlockNumber,表示块号 + * func_name:const char*,表示调用的 API 函数名称 + * elevel:int,表示日志级别,默认为 DEBUG2 + */ static inline void LOG_SMGR_API(RelFileNodeBackend smgr_rnode, ForkNumber forknum, BlockNumber blocknum, const char *func_name, int elevel = DEBUG2) { + // 获取文件节点信息 RelFileNode rnode = smgr_rnode.node; + // 记录日志信息 ereport(DEBUG2, (errmodule(MOD_SEGMENT_PAGE), errmsg("Segment-page smgr api: %s is invoked, SMgrRelation: <%u, %u, %u, %u> %d %u", func_name, rnode.spcNode, rnode.dbNode, rnode.relNode, rnode.bucketNode, forknum, blocknum))); @@ -1254,84 +1558,129 @@ void seg_shutdown() { // do nothing } - +/* + * 功能:关闭分段 + * + * 参数列表: + * reln:SMgrRelation 结构,表示存储管理器关系信息 + * forknum:ForkNumber,表示分支号 + * blockNum:BlockNumber,表示块号 + */ void seg_close(SMgrRelation reln, ForkNumber forknum, BlockNumber blockNum) { + // 断言块号为无效块号 SegmentCheck(blockNum == InvalidBlockNumber); + // 记录存储管理器 API 调用的日志信息 LOG_SMGR_API(reln->smgr_rnode, forknum, InvalidBlockNumber, "seg_close"); + // 如果存储管理器关系中存在分段描述信息且分段描述信息不为空 if (reln->seg_desc && reln->seg_desc[forknum] != NULL) { /* release memory */ pfree(reln->seg_desc[forknum]); reln->seg_desc[forknum] = NULL; } } - +/* + * 功能:创建分段 + * + * 参数列表: + * reln:SMgrRelation 结构,表示存储管理器关系信息 + * forknum:ForkNumber,表示分支号 + * isRedo:bool,表示是否在重做中创建 + */ void seg_create(SMgrRelation reln, ForkNumber forknum, bool isRedo) { + // 记录存储管理器 API 调用的日志信息 LOG_SMGR_API(reln->smgr_rnode, forknum, InvalidBlockNumber, "seg_create"); + // 打开分段,如果分段已存在则返回 true bool res = open_segment(reln, forknum, true); - SegmentCheck(res == true); - (void)res; // keep compiler silent + SegmentCheck(res == true); // 确保成功打开或创建了分段 + // 防止编译器警告,保持编译器静默 + (void)res; // keep compiler silent } - +/* + * 功能:检查分段是否存在 + * + * 参数列表: + * reln:SMgrRelation 结构,表示存储管理器关系信息 + * forknum:ForkNumber,表示分支号 + * blockNum:BlockNumber,表示块号 + */ bool seg_exists(SMgrRelation reln, ForkNumber forknum, BlockNumber blockNum) { + // 记录存储管理器 API 调用的日志信息 LOG_SMGR_API(reln->smgr_rnode, forknum, InvalidBlockNumber, "seg_exists"); + // 调用 open_segment 函数检查分段是否存在,并返回结果 return open_segment(reln, forknum, false); } - +/* + * 功能:删除分段 + * + * 参数列表: + * rnode:RelFileNodeBackend 结构,表示关系文件节点的后端信息 + * forknum:ForkNumber,表示分支号 + * isRedo:bool,表示是否在重做中删除 + * blockNum:BlockNumber,表示块号 + */ void seg_unlink(const RelFileNodeBackend &rnode, ForkNumber forknum, bool isRedo, BlockNumber blockNum) { if (isRedo) { return; } + // 记录存储管理器 API 调用的日志信息,使用 LOG 级别 LOG_SMGR_API(rnode, forknum, InvalidBlockNumber, "seg_unlink", LOG); + // 断言分支号为无效分支号,确保只能删除整个分段而不是单个块 SegmentCheck(forknum == InvalidForkNumber); + // 调用 seg_unlink_segment 函数删除整个分段 seg_unlink_segment(rnode.node); } - +// 结构体 ExtendStat struct ExtendStat { - private: - instr_time start_time; - instr_time end_time; - Oid _spc; - Oid _db; - Oid _file; +private: + instr_time start_time; // 记录起始时间的数据结构 + instr_time end_time; // 记录结束时间的数据结构 + Oid _spc; // 存储空间的标识符 + Oid _db; // 数据库的标识符 + Oid _file; // 文件的标识符 - public: +public: + // 设置文件标识符的方法 void set_file(Oid spc, Oid db, Oid file) { _spc = spc; _db = db; _file = file; } - + // 记录起始时间的方法 void start() { (void)INSTR_TIME_SET_CURRENT(start_time); } - + // 记录结束时间的方法 void end() { (void)INSTR_TIME_SET_CURRENT(end_time); INSTR_TIME_SUBTRACT(end_time, start_time); PgStat_Counter time_diff = (PgStat_Counter)INSTR_TIME_GET_MICROSEC(end_time); + // 创建 PgStat_MsgFile 结构体并初始化为0 PgStat_MsgFile msg; errno_t rc = memset_s(&msg, sizeof(msg), 0, sizeof(msg)); securec_check(rc, "", ""); - msg.dbid = _db; - msg.spcid = _spc; - msg.fn = _file; - msg.rw = 'w'; - msg.cnt = 1; - msg.blks = 1; - msg.tim = time_diff; - msg.lsttim = time_diff; - msg.mintim = time_diff; - msg.maxtim = time_diff; + // 设置 PgStat_MsgFile 结构体的成员变量 + msg.dbid = _db; // 数据库标识符 + msg.spcid = _spc; // 存储空间标识符 + msg.fn = _file; // 文件标识符 + msg.rw = 'w'; // 读写操作类型,这里表示写 + msg.cnt = 1; // 计数器,表示操作次数 + msg.blks = 1; // 操作的块数 + msg.tim = time_diff; // 操作的时间差(以微秒为单位) + msg.lsttim = time_diff; // 最后一次操作的时间差 + msg.mintim = time_diff; // 最小操作时间差 + msg.maxtim = time_diff; // 最大操作时间差 + + // 调用 reportFileStat 函数报告文件统计信息 reportFileStat(&msg); } }; @@ -1454,16 +1803,34 @@ void seg_prefetch(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum) LOG_SMGR_API(reln->smgr_rnode, forknum, blocknum, "seg_prefetch"); /* Not support prefetch yet, just return */ } - +/* + * 功能: 从segment file中读取指定块(数据块)的函数。 + * + * 参数: + * reln: SMgrRelation,表示与段文件相关的SMgrRelation对象。 + * forknum: ForkNumber,表示数据文件的分叉号。 + * blocknum: BlockNumber,要读取的块号。 + * buffer: char*,用于存储读取到的块数据的缓冲区。 + * + * 返回值: + * SMGR_READ_STATUS,表示读取操作的状态,可能的取值包括: + * - SMGR_RD_OK: 读取成功。 + * - SMGR_RD_CRC_ERROR: 块的校验和校验失败,读取失败。 + * + * 注意:函数会自动处理并发访问冲突,确保数据的一致性。 + */ SMGR_READ_STATUS seg_read(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, char *buffer) { + // 记录函数调用信息 LOG_SMGR_API(reln->smgr_rnode, forknum, blocknum, "seg_read"); + // 读取段头部的缓冲区 Buffer seg_buffer = read_head_buffer(reln, forknum, false); SegmentCheck(BufferIsValid(seg_buffer)); SegmentHead *seg_head = (SegmentHead *)PageGetContents(BufferGetBlock(seg_buffer)); SegmentCheck(IsNormalSegmentHead(seg_head)); + // 检查要读取的块号是否超出段的大小 if (seg_head->nblocks <= blocknum) { SegReleaseBuffer(seg_buffer); ereport(ERROR, (errmodule(MOD_SEGMENT_PAGE), errcode(ERRCODE_DATA_CORRUPTED), @@ -1473,15 +1840,19 @@ SMGR_READ_STATUS seg_read(SMgrRelation reln, ForkNumber forknum, BlockNumber blo blocknum, seg_head->nblocks))); } + // 获取段头部的锁 LockSegmentHeadPartition(reln->seg_space->spcNode, reln->seg_space->dbNode, reln->seg_desc[forknum]->head_blocknum, LW_SHARED); + // 将逻辑块号映射到物理块号 SegPageLocation loc = seg_logic_to_physic_mapping(reln, seg_head, blocknum); SegmentCheck(loc.blocknum != InvalidBlockNumber); + // 读取块数据 SegSpace *spc = reln->seg_space; spc_read_block(spc, EXTENT_GROUP_RNODE(spc, loc.extent_size), forknum, buffer, loc.blocknum); + // 释放段头部的锁 UnlockSegmentHeadPartition(reln->seg_space->spcNode, reln->seg_space->dbNode, reln->seg_desc[forknum]->head_blocknum); @@ -1489,6 +1860,7 @@ SMGR_READ_STATUS seg_read(SMgrRelation reln, ForkNumber forknum, BlockNumber blo SegmentCheck(loc.extent_size != 1); /* Set physical location in buffer descriptor, which is used for XLog */ + // 设置缓冲区描述符中的物理位置,用于 XLog 记录 Buffer buf = BlockGetBuffer(buffer); if (BufferIsValid(buf)) { BufferDesc *buf_desc = BufferGetBufferDescriptor(buf); @@ -1496,24 +1868,40 @@ SMGR_READ_STATUS seg_read(SMgrRelation reln, ForkNumber forknum, BlockNumber blo buf_desc->seg_blockno = loc.blocknum; } + // 释放段头部的缓冲区 SegReleaseBuffer(seg_buffer); + // 如果通过校验则返回 SMGR_RD_OK,否则返回 SMGR_RD_CRC_ERROR if (PageIsVerified((Page)buffer, loc.blocknum)) { return SMGR_RD_OK; } else { return SMGR_RD_CRC_ERROR; } } - +/* + * 功能:将数据写入分段文件 + * + * 参数列表: + * reln:SMgrRelation 结构,表示分段文件的SMgrRelation对象 + * forknum:ForkNumber,表示分支号 + * blocknum:BlockNumber,表示要写入的块号 + * buffer:const char*,包含要写入的数据的缓冲区 + * skipFsync:bool,如果为true,表示可以跳过fsync同步以提高性能 + * + * 注意:该函数负责将指定块的数据写入分段文件,管理并发访问冲突以确保数据一致性。 + */ void seg_write(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, const char *buffer, bool skipFsync) { + // 记录SMGR API调用日志 LOG_SMGR_API(reln->smgr_rnode, forknum, blocknum, "seg_write"); + // 读取分段头部缓冲区 Buffer seg_buffer = read_head_buffer(reln, forknum, true); SegmentCheck(BufferIsValid(seg_buffer)); SegmentHead *seg_head = (SegmentHead *)PageGetContents(BufferGetBlock(seg_buffer)); SegmentCheck(IsNormalSegmentHead(seg_head)); + // 检查块号是否超过分段大小 if (seg_head->nblocks <= blocknum) { SegReleaseBuffer(seg_buffer); ereport(ERROR, (errmodule(MOD_SEGMENT_PAGE), errcode(ERRCODE_DATA_CORRUPTED), @@ -1523,42 +1911,62 @@ void seg_write(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, cons blocknum, seg_head->nblocks))); } + // 锁定分段头部所在的分区 LockSegmentHeadPartition(reln->seg_space->spcNode, reln->seg_space->dbNode, reln->seg_desc[forknum]->head_blocknum, LW_SHARED); + // 逻辑到物理地址映射 SegPageLocation loc = seg_logic_to_physic_mapping(reln, seg_head, blocknum); SegmentCheck(loc.blocknum != InvalidBlockNumber); + // 解锁分段头部所在的分区 UnlockSegmentHeadPartition(reln->seg_space->spcNode, reln->seg_space->dbNode, reln->seg_desc[forknum]->head_blocknum); // TODO: remove PageSetChecksumInplace invocation outside SMGR. PageSetChecksumInplace((Page)buffer, loc.blocknum); SegSpace *spc = reln->seg_space; + // 写入数据块 spc_write_block(spc, EXTENT_GROUP_RNODE(spc, loc.extent_size), forknum, buffer, loc.blocknum); + // 释放分段头部缓冲区 SegReleaseBuffer(seg_buffer); } - +/* + * 功能:将指定分段的数据写回磁盘 + * + * 参数列表: + * reln:SMgrRelation 结构,表示分段文件的SMgrRelation对象 + * forknum:ForkNumber,表示分支号 + * blocknum:BlockNumber,表示要写回的起始块号 + * nblocks:BlockNumber,表示要写回的块数量 + * + * 注意:该函数用于将指定分段的数据写回磁盘,可用于缓冲区刷新操作。如果传递的 rNode 表示物理分段文件,则使用 + * spc_writeback 函数,如果表示逻辑分段文件,则逐个逻辑区块写回磁盘。 + */ void seg_writeback(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, BlockNumber nblocks) { + // 记录SMGR API调用日志 LOG_SMGR_API(reln->smgr_rnode, forknum, InvalidBlockNumber, "seg_writeback"); RelFileNode rNode = reln->smgr_rnode.node; if (IsSegmentPhysicalRelNode(rNode)) { SMgrOpenSpace(reln); + // 记录SMGR API调用日志 if (reln->seg_space == NULL) { ereport(ERROR, (errmodule(MOD_SEGMENT_PAGE), errcode(ERRCODE_DATA_CORRUPTED), - errmsg("when write back, segment space [%u, %u] doesn't exist.", reln->smgr_rnode.node.spcNode, - reln->smgr_rnode.node.dbNode))); + errmsg("when write back, segment space [%u, %u] doesn't exist.", + reln->smgr_rnode.node.spcNode, reln->smgr_rnode.node.dbNode))); return; } + // 使用 spc_writeback 函数将数据写回磁盘 spc_writeback(reln->seg_space, EXTENT_TYPE_TO_SIZE(rNode.relNode), forknum, blocknum, nblocks); } else { /* * Logical writes are continues in each extent. * XXX: Continues extents can be merged to reduece system call. */ + // 读取分段头部缓冲区 Buffer seg_buffer = read_head_buffer(reln, forknum, true); SegmentCheck(BufferIsValid(seg_buffer)); SegmentHead *seg_head = (SegmentHead *)PageGetContents(BufferGetBlock(seg_buffer)); @@ -1566,6 +1974,7 @@ void seg_writeback(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, SegmentCheck(blocknum + nblocks < seg_head->nblocks); /* Get the start extent id */ + // 获取起始逻辑区块的映射信息 SegPageLocation loc1 = seg_logic_to_physic_mapping(reln, seg_head, blocknum); SegmentCheck(loc1.blocknum != InvalidBlockNumber); uint32 curr_ext_id = loc1.extent_id; @@ -1590,23 +1999,35 @@ void seg_writeback(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, SegReleaseBuffer(seg_buffer); } } - +/* + * 功能:获取分段中的块数量 + * + * 参数列表: + * reln:SMgrRelation 结构,表示分段文件的SMgrRelation对象 + * forknum:ForkNumber,表示分支号 + * + * 注意:该函数用于获取分段文件中的块数量,根据 forknum 参数区分不同分支。如果分段文件不存在或不包含有效数据,返回0。 + */ BlockNumber seg_nblocks(SMgrRelation reln, ForkNumber forknum) { + // 记录SMGR API调用日志 LOG_SMGR_API(reln->smgr_rnode, forknum, InvalidBlockNumber, "seg_nblocks"); ASSERT_NORMAL_FORK(forknum); + // 检查分段文件是否存在 bool seg_exist = open_segment(reln, forknum, false); if (!seg_exist) { return 0; } + // 检查分段文件是否存在 Buffer seg_buffer = read_head_buffer(reln, forknum, false); SegmentCheck(BufferIsValid(seg_buffer)); SegmentHead *seg_head = (SegmentHead *)PageGetContents(BufferGetBlock(seg_buffer)); SegmentCheck(IsNormalSegmentHead(seg_head)); + // 锁定分段头部缓冲区并获取块数量 LockBuffer(seg_buffer, BUFFER_LOCK_SHARE); BlockNumber nblocks = seg_head->nblocks; SegUnlockReleaseBuffer(seg_buffer); @@ -1618,11 +2039,21 @@ void seg_truncate(SMgrRelation reln, ForkNumber forknum, BlockNumber nblocks) { SegmentCheck(0); } - +/* + * 功能:移动分段中的存储桶 + * + * 参数列表: + * dest:RelFileNodeBackend 结构,表示目标分段文件节点的后端信息 + * src:RelFileNodeBackend 结构,表示源分段文件节点的后端信息 + * bucketList:List 结构,包含要移动的存储桶的列表 + * + * 注意:该函数用于在两个分段之间移动存储桶的数据。源分段中的存储桶数据将被复制到目标分段中。 + */ void seg_move_buckets(const RelFileNodeBackend &dest, const RelFileNodeBackend &src, List *bucketList) { + // 记录存储桶Redis信息的XLOG LOG_SMGR_API(dest, InvalidForkNumber, InvalidBlockNumber, "seg_move_buckets"); - uint32 i,j; + uint32 i, j; uint32 nentry; ListCell *cell = NULL; BktMainHead *shead, *dhead; @@ -1634,16 +2065,18 @@ void seg_move_buckets(const RelFileNodeBackend &dest, const RelFileNodeBackend & SegmentCheck(dest.node.spcNode == src.node.spcNode && dest.node.dbNode == src.node.dbNode); SegSpace *spc = spc_open(dest.node.spcNode, dest.node.dbNode, false); + // 读取目标和源分段的主头部 bucket_load_main_head(spc, dest.node.relNode, &dbuffer, &dhead); bucket_load_main_head(spc, src.node.relNode, &sbuffer, &shead); + // 锁定目标和源分段的主头部 LockBuffer(dbuffer, BUFFER_LOCK_EXCLUSIVE); LockBuffer(sbuffer, BUFFER_LOCK_EXCLUSIVE); XLogAtomicOpStart(); /* Add redis info for both source and dest bucket segment header */ - dhead->redis_info.redis_xid = redis_xid; - shead->redis_info.redis_xid = redis_xid; + dhead->redis_info.redis_xid = redis_xid; + shead->redis_info.redis_xid = redis_xid; dhead->redis_info.nwords = BktBitMaxMapCnt; shead->redis_info.nwords = 0; for (i = 0; i < BktBitMaxMapCnt; i++) { @@ -1695,18 +2128,30 @@ void seg_move_buckets(const RelFileNodeBackend &dest, const RelFileNodeBackend & SegUnlockReleaseBuffer(smapbuffer); } + // 记录存储桶Redis信息的XLOG log_move_segment_redisinfo(&dhead->redis_info, &shead->redis_info, dbuffer, sbuffer); XLogAtomicOpCommit(); } - +/* + * 功能:获取分段头部的LSN + * + * 参数列表: + * spc:SegSpace 结构,表示段空间 + * blockNum:BlockNumber,表示块号 + * isbucket:bool,表示是否为存储桶分段 + * + * 注意:该函数用于获取分段头部的LSN,用于检查分段是否已经被写入日志。 + */ XLogRecPtr seg_get_headlsn(SegSpace *spc, BlockNumber blockNum, bool isbucket) { + // 读取分段头部的缓冲区 Buffer buffer = ReadSegmentBuffer(spc, blockNum); LockBuffer(buffer, BUFFER_LOCK_SHARE); SegmentHead *head = (SegmentHead *)PageGetContents(BufferGetPage(buffer)); uint64 magic = isbucket ? BUCKET_SEGMENT_MAGIC : SEGMENT_HEAD_MAGIC; XLogRecPtr lsn = InvalidXLogRecPtr; + // 检查分段头部的魔数和日志序列号是否有效 if (PageIsSegmentVersion(BufferGetPage(buffer)) && head->magic == magic) { lsn = head->lsn; SegmentCheck(XLogRecPtrIsValid(lsn)); @@ -1717,48 +2162,61 @@ XLogRecPtr seg_get_headlsn(SegSpace *spc, BlockNumber blockNum, bool isbucket) } void seg_immedsync(SMgrRelation reln, ForkNumber forknum) -{ -} +{} void seg_pre_ckpt(void) -{ -} +{} void seg_sync(void) -{ -} +{} void seg_post_ckpt(void) -{ -} +{} void seg_async_read(SMgrRelation reln, ForkNumber forknum, AioDispatchDesc_t **dList, int32 dn) -{ -} +{} void seg_async_write(SMgrRelation reln, ForkNumber forknum, AioDispatchDesc_t **dList, int32 dn) -{ -} - +{} +/* + * 功能:获取指定分支的分段总块数 + * + * 参数列表: + * reln:SMgrRelation 结构,表示SMgrRelation对象 + * forknum:ForkNumber,表示分支号 + * + * 注意:该函数用于获取指定分支的分段总块数,如果分段不存在,则返回0。 + */ BlockNumber seg_fork_totalblocks(SMgrRelation reln, ForkNumber forknum) { + // 检查指定分支的有效性 bool seg_exist = open_segment(reln, forknum, false); if (!seg_exist) { return 0; } + // 读取指定分支的分段头部缓冲区 Buffer seg_buffer = read_head_buffer(reln, forknum, false); SegmentCheck(BufferIsValid(seg_buffer)); SegmentHead *seg_head = (SegmentHead *)PageGetContents(BufferGetBlock(seg_buffer)); + // 获取分段总块数并释放缓冲区 LockBuffer(seg_buffer, BUFFER_LOCK_SHARE); BlockNumber nblocks = seg_head->nblocks; SegUnlockReleaseBuffer(seg_buffer); return nblocks; } - +/* + * 功能:获取指定分支的分段总块数 + * + * 参数列表: + * reln:SMgrRelation 结构,表示SMgrRelation对象 + * forknum:ForkNumber,表示分支号 + * + * 注意:该函数用于获取指定分支的分段总块数,如果分段不存在,则返回0。 + */ BlockNumber seg_totalblocks(SMgrRelation reln, ForkNumber forknum) { LOG_SMGR_API(reln->smgr_rnode, forknum, InvalidBlockNumber, "seg_totalblocks"); @@ -1768,21 +2226,34 @@ BlockNumber seg_totalblocks(SMgrRelation reln, ForkNumber forknum) } ASSERT_NORMAL_FORK(forknum); + // 读取主分支的分段头部缓冲区 Buffer seg_buffer = read_head_buffer(reln, MAIN_FORKNUM, false); SegmentCheck(BufferIsValid(seg_buffer)); SegmentHead *seg_head = (SegmentHead *)PageGetContents(BufferGetBlock(seg_buffer)); BlockNumber res = 0; + // 根据分段类型调用不同的获取总块数函数 if (seg_head->magic == BUCKET_SEGMENT_MAGIC) { res = bucket_totalblocks(reln, forknum, seg_buffer); } else { res = seg_fork_totalblocks(reln, forknum); } + // 释放分段头部缓冲区 SegReleaseBuffer(seg_buffer); return res; } - +/* + * 功能:检查指定分支的分段是否存在 + * + * 参数列表: + * spc:SegSpace 结构,表示SegSpace对象 + * reln:SMgrRelation 结构,表示SMgrRelation对象 + * forknum:ForkNumber,表示分支号 + * pblk:XLogPhyBlock 结构,表示物理块号和日志序列号 + * + * 注意:该函数用于检查指定分支的分段是否存在,存在则返回true,否则返回false。 + */ bool seg_fork_exists(SegSpace *spc, SMgrRelation reln, ForkNumber forknum, const XLogPhyBlock *pblk) { ASSERT_NORMAL_FORK(forknum); @@ -1790,6 +2261,7 @@ bool seg_fork_exists(SegSpace *spc, SMgrRelation reln, ForkNumber forknum, const RelFileNode rnode = reln->smgr_rnode.node; BlockNumber lastblock = spc_size(spc, pblk->relNode, forknum); + // 如果块号大于等于最后一个块号,返回false if (pblk->block >= lastblock) { return false; } @@ -1801,7 +2273,7 @@ bool seg_fork_exists(SegSpace *spc, SMgrRelation reln, ForkNumber forknum, const /* check if main relation is still here */ XLogRecPtr seglsn = seg_get_headlsn(spc, rnode.relNode, IsBucketFileNode(rnode)); - + // 如果LSN无效或者pblk的LSN小于等于分段头部的LSN,返回false if (XLogRecPtrIsInvalid(seglsn) || XLByteLE(pblk->lsn, seglsn)) { /* segment header is reused */ return false; @@ -1809,6 +2281,7 @@ bool seg_fork_exists(SegSpace *spc, SMgrRelation reln, ForkNumber forknum, const /* check if relation or bucket's fork is still here */ Buffer seg_buffer = read_head_buffer_redo(reln, forknum, pblk->lsn); + // 如果分支缓冲区无效,返回false if (!BufferIsValid(seg_buffer)) { /* bucket or fork is dropped */ return false; @@ -1816,6 +2289,7 @@ bool seg_fork_exists(SegSpace *spc, SMgrRelation reln, ForkNumber forknum, const SegmentHead *seg_head = (SegmentHead *)PageGetContents(BufferGetBlock(seg_buffer)); LockBuffer(seg_buffer, BUFFER_LOCK_SHARE); + // 如果分支缓冲区不是分段版本或者分段头部的魔数不是SEGMENT_HEAD_MAGIC,返回false if (!PageIsSegmentVersion(BufferGetBlock(seg_buffer)) || seg_head->magic != SEGMENT_HEAD_MAGIC) { /* segment header reused */ SegUnlockReleaseBuffer(seg_buffer); @@ -1834,25 +2308,64 @@ bool seg_fork_exists(SegSpace *spc, SMgrRelation reln, ForkNumber forknum, const } /* APIs for others */ - +/* + * 功能:为指定的分支预分配扩展空间 + * + * 参数列表: + * rNode:RelFileNode 结构,表示关系文件节点 + * forkNum:ForkNumber,表示分支号 + * blkno:BlockNumber,表示块号 + * + * 注意:该函数用于为指定的分支预分配扩展空间。 + */ void seg_preextend(RelFileNode &rNode, ForkNumber forkNum, BlockNumber blkno) { + // 打开关系文件并进行预分配扩展 SMgrRelation reln = smgropen(rNode, InvalidBackendId); seg_extend_internal(reln, forkNum, blkno); } - +/* + * 功能:物理读取指定分支的指定块数据 + * + * 参数列表: + * spc:SegSpace 结构指针,表示分段空间 + * rNode:RelFileNode 结构,表示关系文件节点 + * forknum:ForkNumber,表示分支号 + * blocknum:BlockNumber,表示块号 + * buffer:char 指针,用于存储读取的数据 + * + * 注意:该函数用于物理读取指定分支的指定块数据。 + */ void seg_physical_read(SegSpace *spc, RelFileNode &rNode, ForkNumber forknum, BlockNumber blocknum, char *buffer) { + // 检查关系文件节点是否为物理文件类型 SegmentCheck(IsSegmentPhysicalRelNode(rNode)); + // 检查分段空间是否有效 SegmentCheck(spc != NULL); + // 调用 spc_read_block 函数进行物理读取 spc_read_block(spc, rNode, forknum, buffer, blocknum); } - +/* + * 功能:物理写入指定分支的指定块数据 + * + * 参数列表: + * spc:SegSpace 结构指针,表示分段空间 + * rNode:RelFileNode 结构,表示关系文件节点 + * forknum:ForkNumber,表示分支号 + * blocknum:BlockNumber,表示块号 + * buffer:const char 指针,用于存储要写入的数据 + * skipFsync:bool,表示是否跳过文件同步操作 + * + * 注意:该函数用于物理写入指定分支的指定块数据。 + */ void seg_physical_write(SegSpace *spc, RelFileNode &rNode, ForkNumber forknum, BlockNumber blocknum, const char *buffer, bool skipFsync) { + // 检查关系文件节点是否为物理文件类型 SegmentCheck(IsSegmentPhysicalRelNode(rNode)); + // 检查分段空间是否有效 SegmentCheck(spc != NULL); + // 调用 spc_write_block 函数进行物理写入 spc_write_block(spc, rNode, forknum, buffer, blocknum); } -- 2.34.1 From 36235f8e12f127dd56aff6184a8b4ffe074d6c28 Mon Sep 17 00:00:00 2001 From: nuoya <1204149038@qq.com> Date: Mon, 25 Sep 2023 15:32:44 +0800 Subject: [PATCH 13/50] Update smgr.cpp --- src/gausskernel/storage/smgr/smgr.cpp | 122 ++++++++------------------ 1 file changed, 37 insertions(+), 85 deletions(-) diff --git a/src/gausskernel/storage/smgr/smgr.cpp b/src/gausskernel/storage/smgr/smgr.cpp index b76bbfe3b..5ad1e44e3 100644 --- a/src/gausskernel/storage/smgr/smgr.cpp +++ b/src/gausskernel/storage/smgr/smgr.cpp @@ -63,63 +63,17 @@ typedef struct f_smgr { static const f_smgr smgrsw[] = { /* magnetic disk */ - { mdinit, - NULL, - mdclose, - mdcreate, - mdexists, - mdunlink, - mdextend, - mdprefetch, - mdread, - mdwrite, - mdwriteback, - mdnblocks, - mdtruncate, - mdimmedsync, - mdasyncread, - mdasyncwrite, - NULL - }, + {mdinit, NULL, mdclose, mdcreate, mdexists, mdunlink, mdextend, mdprefetch, mdread, mdwrite, mdwriteback, mdnblocks, + mdtruncate, mdimmedsync, mdasyncread, mdasyncwrite, NULL}, /* undo file */ - { - InitUndoFile, - NULL, - CloseUndoFile, - CreateUndoFile, - CheckUndoFileExists, - UnlinkUndoFile, - ExtendUndoFile, - PrefetchUndoFile, - ReadUndoFile, - WriteUndoFile, - WritebackUndoFile, - GetUndoFileNblocks, - NULL, - NULL - }, + {InitUndoFile, NULL, CloseUndoFile, CreateUndoFile, CheckUndoFileExists, UnlinkUndoFile, ExtendUndoFile, + PrefetchUndoFile, ReadUndoFile, WriteUndoFile, WritebackUndoFile, GetUndoFileNblocks, NULL, NULL}, /* segment-page */ - { - seg_init, - seg_shutdown, - seg_close, - seg_create, - seg_exists, - seg_unlink, - seg_extend, - seg_prefetch, - seg_read, - seg_write, - seg_writeback, - seg_nblocks, - seg_truncate, - seg_immedsync, - seg_async_read, - seg_async_write, - seg_move_buckets - }, + {seg_init, seg_shutdown, seg_close, seg_create, seg_exists, seg_unlink, seg_extend, seg_prefetch, seg_read, + seg_write, seg_writeback, seg_nblocks, seg_truncate, seg_immedsync, seg_async_read, seg_async_write, + seg_move_buckets}, }; static const int NSmgr = lengthof(smgrsw); @@ -137,7 +91,6 @@ static inline int ChooseSmgrManager(RelFileNode rnode) return MD_MANAGER; } - /* * smgrinit(), smgrshutdown() -- Initialize or shut down storage * managers. @@ -183,7 +136,7 @@ void smgrshutdown(int code, Datum arg) * * This does not attempt to actually open the underlying file. */ -SMgrRelation smgropen(const RelFileNode& rnode, BackendId backend, int col /* = 0 */) +SMgrRelation smgropen(const RelFileNode &rnode, BackendId backend, int col /* = 0 */) { RelFileNodeBackend brnode; SMgrRelation reln; @@ -207,16 +160,15 @@ SMgrRelation smgropen(const RelFileNode& rnode, BackendId backend, int col /* = hashCtrl.hash = tag_hash; if (EnableLocalSysCache()) { hashCtrl.hcxt = t_thrd.lsc_cxt.lsc->lsc_mydb_memcxt; - t_thrd.lsc_cxt.lsc->SMgrRelationHash = hash_create("smgr relation table", 400, - &hashCtrl, HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT); + t_thrd.lsc_cxt.lsc->SMgrRelationHash = + hash_create("smgr relation table", 400, &hashCtrl, HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT); dlist_init(&t_thrd.lsc_cxt.lsc->unowned_reln); } else { hashCtrl.hcxt = u_sess->cache_mem_cxt; - u_sess->storage_cxt.SMgrRelationHash = hash_create("smgr relation table", 400, - &hashCtrl, HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT); + u_sess->storage_cxt.SMgrRelationHash = + hash_create("smgr relation table", 400, &hashCtrl, HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT); dlist_init(&u_sess->storage_cxt.unowned_reln); } - } START_CRIT_SECTION(); @@ -224,7 +176,7 @@ SMgrRelation smgropen(const RelFileNode& rnode, BackendId backend, int col /* = /* Look up or create an entry */ brnode.node = rnode; brnode.backend = backend; - reln = (SMgrRelation)hash_search(GetSMgrRelationHash(), (void*)&brnode, HASH_ENTER, &found); + reln = (SMgrRelation)hash_search(GetSMgrRelationHash(), (void *)&brnode, HASH_ENTER, &found); /* Initialize it if not present before */ if (!found) { @@ -238,8 +190,8 @@ SMgrRelation smgropen(const RelFileNode& rnode, BackendId backend, int col /* = reln->smgr_vm_nblocks = InvalidBlockNumber; reln->encrypt = false; temp = col + 1; - reln->smgr_bcm_nblocks = (BlockNumber*)MemoryContextAllocZero( - LocalSmgrStorageMemoryCxt(), temp * sizeof(BlockNumber)); + reln->smgr_bcm_nblocks = + (BlockNumber *)MemoryContextAllocZero(LocalSmgrStorageMemoryCxt(), temp * sizeof(BlockNumber)); reln->smgr_bcmarry_size = temp; for (colnum = 0; colnum < reln->smgr_bcmarry_size; colnum++) { reln->smgr_bcm_nblocks[colnum] = InvalidBlockNumber; @@ -253,13 +205,13 @@ SMgrRelation smgropen(const RelFileNode& rnode, BackendId backend, int col /* = reln->seg_space = NULL; if (reln->smgr_which == SEGMENT_MANAGER) { reln->md_fdarray_size = fdNeeded; - reln->seg_desc = (struct SegmentDesc **)MemoryContextAllocZero( - LocalSmgrStorageMemoryCxt(), fdNeeded * sizeof(struct SegmentDesc *)); + reln->seg_desc = (struct SegmentDesc **)MemoryContextAllocZero(LocalSmgrStorageMemoryCxt(), + fdNeeded * sizeof(struct SegmentDesc *)); reln->md_fd = NULL; } else if (reln->smgr_which == MD_MANAGER) { reln->md_fdarray_size = fdNeeded; - reln->md_fd = (struct _MdfdVec**)MemoryContextAllocZero( - LocalSmgrStorageMemoryCxt(), fdNeeded * sizeof(struct _MdfdVec*)); + reln->md_fd = (struct _MdfdVec **)MemoryContextAllocZero(LocalSmgrStorageMemoryCxt(), + fdNeeded * sizeof(struct _MdfdVec *)); reln->seg_desc = NULL; } else { reln->md_fdarray_size = 1; @@ -284,7 +236,8 @@ SMgrRelation smgropen(const RelFileNode& rnode, BackendId backend, int col /* = int old_bcmarry_size = reln->smgr_bcmarry_size; temp = reln->smgr_bcmarry_size * 2; temp = Max(temp, (col + 1)); - reln->smgr_bcm_nblocks = (BlockNumber *)repalloc((void *)reln->smgr_bcm_nblocks, temp * sizeof(BlockNumber)); + reln->smgr_bcm_nblocks = + (BlockNumber *)repalloc((void *)reln->smgr_bcm_nblocks, temp * sizeof(BlockNumber)); reln->smgr_bcmarry_size = temp; for (int colnum = old_bcmarry_size; colnum < reln->smgr_bcmarry_size; colnum++) @@ -300,7 +253,7 @@ SMgrRelation smgropen(const RelFileNode& rnode, BackendId backend, int col /* = int old_fdarray_size = reln->md_fdarray_size; temp = reln->md_fdarray_size * 2; temp = Max(temp, fdNeeded); - reln->md_fd = (struct _MdfdVec**)repalloc((void*)reln->md_fd, temp * sizeof(struct _MdfdVec*)); + reln->md_fd = (struct _MdfdVec **)repalloc((void *)reln->md_fd, temp * sizeof(struct _MdfdVec *)); reln->md_fdarray_size = temp; for (int forknum = old_fdarray_size; forknum < reln->md_fdarray_size; forknum++) @@ -387,7 +340,7 @@ static bool smgrhaschildern(SMgrRelation reln) void smgrclose(SMgrRelation reln, BlockNumber blockNum) { ereport(DEBUG5, (errmsg("smgr close %p", reln))); - SMgrRelation* owner = NULL; + SMgrRelation *owner = NULL; int forknum; for (forknum = 0; forknum < (int)(reln->md_fdarray_size); forknum++) { @@ -435,7 +388,7 @@ void smgrclose(SMgrRelation reln, BlockNumber blockNum) * smgrcloseall() -- Close all existing SMgrRelation objects. */ void smgrcloseall(void) -{ +{ HASH_SEQ_STATUS status; SMgrRelation reln; @@ -449,7 +402,7 @@ void smgrcloseall(void) while ((reln = (SMgrRelation)hash_seq_search(&status)) != NULL) { if (smgrhaschildern(reln)) { - /* + /* * if the smgr node has children, it may incur close of all its children. * we can not close it in the first loop, otherwise the hashtable iterator is broken. */ @@ -541,9 +494,7 @@ void smgrcreate(SMgrRelation reln, ForkNumber forknum, bool isRedo) * should be here and not in commands/tablespace.c? But that would imply * importing a lot of stuff that smgr.c oughtn't know, either. */ - TablespaceCreateDbspace(reln->smgr_rnode.node.spcNode, - reln->smgr_rnode.node.dbNode, - isRedo); + TablespaceCreateDbspace(reln->smgr_rnode.node.spcNode, reln->smgr_rnode.node.dbNode, isRedo); (*(smgrsw[reln->smgr_which].smgr_create))(reln, forknum, isRedo); } @@ -668,8 +619,7 @@ void smgrdounlinkfork(SMgrRelation reln, ForkNumber forknum, bool isRedo) * EOF). Note that we assume writing a block beyond current EOF * causes intervening file space to become filled with zeroes. */ -void smgrextend(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, - char *buffer, bool skipFsync) +void smgrextend(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, char *buffer, bool skipFsync) { (*(smgrsw[reln->smgr_which].smgr_extend))(reln, forknum, blocknum, buffer, skipFsync); } @@ -764,15 +714,13 @@ BlockNumber smgrnblocks(SMgrRelation reln, ForkNumber forknum) * Returns an InvalidBlockNumber when not in recovery and when the relation * fork size is not cached. Now, we only support cache main fork. */ -BlockNumber -smgrnblocks_cached(SMgrRelation reln, ForkNumber forknum) +BlockNumber smgrnblocks_cached(SMgrRelation reln, ForkNumber forknum) { /* * For now, we only use cached values in recovery due to lack of a shared * invalidation mechanism for changes in file size. */ - if (RecoveryInProgress() && forknum == MAIN_FORKNUM && - reln->smgr_cached_nblocks != InvalidBlockNumber) { + if (RecoveryInProgress() && forknum == MAIN_FORKNUM && reln->smgr_cached_nblocks != InvalidBlockNumber) { return reln->smgr_cached_nblocks; } @@ -860,7 +808,6 @@ void smgrimmedsync(SMgrRelation reln, ForkNumber forknum) (*(smgrsw[reln->smgr_which].smgr_immedsync))(reln, forknum); } - /* * smgrmovebuckets() -- Move buckets between two relation. */ @@ -943,7 +890,13 @@ ScalarToDatum GetTransferFuncByTypeOid(Oid attTypeOid) } } } - +/* + * 功能:将要删除的关系的某个分支信息添加到哈希表中 + * + * 参数列表: + * node:RelFileNode,表示关系文件节点信息 + * forkNum:ForkNumber,表示分支号 + */ static void push_unlink_rel_one_fork_to_hashtbl(RelFileNode node, ForkNumber forkNum) { HTAB *relfilenode_hashtbl = g_instance.bgwriter_cxt.unlink_rel_fork_hashtbl; @@ -956,7 +909,7 @@ static void push_unlink_rel_one_fork_to_hashtbl(RelFileNode node, ForkNumber for key.forkNum = forkNum; LWLockAcquire(g_instance.bgwriter_cxt.rel_one_fork_hashtbl_lock, LW_EXCLUSIVE); - entry = (DelForkFileTag*)hash_search(relfilenode_hashtbl, &(key), HASH_ENTER, &found); + entry = (DelForkFileTag *)hash_search(relfilenode_hashtbl, &(key), HASH_ENTER, &found); if (!found) { entry->forkrnode.rnode.spcNode = key.rnode.spcNode; entry->forkrnode.rnode.dbNode = key.rnode.dbNode; @@ -974,4 +927,3 @@ static void push_unlink_rel_one_fork_to_hashtbl(RelFileNode node, ForkNumber for } return; } - -- 2.34.1 From e2d08668b6af8bee293946a1ede345a23cde832f Mon Sep 17 00:00:00 2001 From: nuoya <1204149038@qq.com> Date: Mon, 25 Sep 2023 15:33:01 +0800 Subject: [PATCH 14/50] Update data_file.cpp --- .../storage/smgr/segment/data_file.cpp | 419 ++++++++++++------ 1 file changed, 278 insertions(+), 141 deletions(-) diff --git a/src/gausskernel/storage/smgr/segment/data_file.cpp b/src/gausskernel/storage/smgr/segment/data_file.cpp index d3268e23d..15db84a42 100644 --- a/src/gausskernel/storage/smgr/segment/data_file.cpp +++ b/src/gausskernel/storage/smgr/segment/data_file.cpp @@ -55,11 +55,16 @@ static int dv_open_file(char *filename, uint32 flags, int mode) errno = err; return fd; } - +/* + * 功能:关闭文件描述符并生成日志信息 + * + * 参数列表: + * fd:int,表示文件描述符 + */ static void dv_close_file(int fd) { - close(fd); - ereport(LOG, (errmsg("dv_close_file fd is %d", fd))); + close(fd); // 关闭文件描述符 + ereport(LOG, (errmsg("dv_close_file fd is %d", fd))); // 生成日志信息,记录关闭的文件描述符的值 } /* Return a palloc string, and callers should free it */ @@ -100,17 +105,13 @@ void df_create_file(SegLogicFile *sf, bool redo) // File not exists uint32 flags = O_RDWR | O_CREAT | O_EXCL | PG_BINARY; if (sf->segfiles != NULL) { - ereport(LOG, - (errmodule(MOD_SEGMENT_PAGE), - errmsg("[segpage] sf->segfiles is not null, last invocation of df_create_file must be failed " - "halfway. spc/db/relnode/fork: %u/%u/%d/%d", - sf->relNode.spcNode, - sf->relNode.dbNode, - sf->relNode.relNode, - sf->forknum))); + ereport(LOG, (errmodule(MOD_SEGMENT_PAGE), + errmsg("[segpage] sf->segfiles is not null, last invocation of df_create_file must be failed " + "halfway. spc/db/relnode/fork: %u/%u/%d/%d", + sf->relNode.spcNode, sf->relNode.dbNode, sf->relNode.relNode, sf->forknum))); } else { MemoryContext oldcnxt = MemoryContextSwitchTo(INSTANCE_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_STORAGE)); - sf->segfiles = (SegPhysicalFile*)palloc(sizeof(SegPhysicalFile) * DF_ARRAY_EXTEND_STEP); + sf->segfiles = (SegPhysicalFile *)palloc(sizeof(SegPhysicalFile) * DF_ARRAY_EXTEND_STEP); MemoryContextSwitchTo(oldcnxt); for (int i = 0; i < DF_ARRAY_EXTEND_STEP; i++) { @@ -136,17 +137,28 @@ void df_create_file(SegLogicFile *sf, bool redo) } pfree(filename); } - +/* + * 功能:获取分段逻辑文件的物理文件 + * + * 参数列表: + * sf:SegLogicFile*,指向分段逻辑文件的指针 + * sliceno:int,分片号 + * target_block:BlockNumber,目标块号 + * + * 返回值:SegPhysicalFile,表示分段物理文件的结构 + */ static SegPhysicalFile df_get_physical_file(SegLogicFile *sf, int sliceno, BlockNumber target_block) { - AutoMutexLock filelock(&sf->filelock); + AutoMutexLock filelock(&sf->filelock); // 使用自动互斥锁,确保线程安全 filelock.lock(); + // 检查目标块号是否为无效块号 if (target_block == InvalidBlockNumber) { SegmentCheck(0); - ereport(ERROR, - (errcode(ERRCODE_DATA_EXCEPTION), errmsg("df_get_physical_file target_block is InvalidBlockNumber!\n"))); + ereport(ERROR, (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("df_get_physical_file target_block is InvalidBlockNumber!\n"))); } + // 如果目标块号大于文件的总块数,尝试扩展文件 if (sf->total_blocks <= target_block) { ereport(LOG, (errmodule(MOD_SEGMENT_PAGE), errmsg("Try to access file %s block %u, exceeds the file total blocks %u", @@ -157,12 +169,21 @@ static SegPhysicalFile df_get_physical_file(SegLogicFile *sf, int sliceno, Block } } + // 检查分片号是否有效 SegmentCheck(sliceno < sf->file_num); + // 获取指定分片的物理文件 SegPhysicalFile spf = sf->segfiles[sliceno]; return spf; } - +/* + * 功能:刷新分段逻辑文件中的数据到物理文件 + * + * 参数列表: + * sf:SegLogicFile*,指向分段逻辑文件的指针 + * blocknum:BlockNumber,起始块号 + * nblocks:BlockNumber,块的数量 + */ void df_flush_data(SegLogicFile *sf, BlockNumber blocknum, BlockNumber nblocks) { int128 remainBlocks = nblocks; @@ -176,12 +197,15 @@ void df_flush_data(SegLogicFile *sf, BlockNumber blocknum, BlockNumber nblocks) nflush = DF_FILE_SLICE_BLOCKS - (blocknum % DF_FILE_SLICE_BLOCKS); } + // 获取分片的物理文件 SegPhysicalFile spf = df_get_physical_file(sf, slice_start, blocknum); if (spf.fd < 0) { return; } + // 计算物理文件中的偏移位置 off_t seekpos = (off_t)BLCKSZ * (blocknum % DF_FILE_SLICE_BLOCKS); + // 调用底层函数刷新数据到物理文件 pg_flush_data(spf.fd, seekpos, (off_t)nflush * BLCKSZ); remainBlocks -= nflush; @@ -209,7 +233,13 @@ void df_extend_file_vector(SegLogicFile *sf) sf->segfiles = newfiles; sf->vector_capacity = new_capacity; } - +/* + * 功能:关闭所有分段逻辑文件中的文件描述符 + * + * 参数列表: + * key:RepairFileKey,表示需要修复的文件的关键信息 + * max_sliceno:int32,最大分片号 + */ void df_close_all_file(RepairFileKey key, int32 max_sliceno) { Oid relNode = key.relfilenode.relNode; @@ -218,10 +248,12 @@ void df_close_all_file(RepairFileKey key, int32 max_sliceno) SegSpace *spc = spc_init_space_node(key.relfilenode.spcNode, key.relfilenode.dbNode); Assert(relNode <= EXTENT_8192 && relNode > 0); + // 获取分段空间并加锁 AutoMutexLock spc_lock(&spc->lock); spc_lock.lock(); SegExtentGroup *eg = &spc->extent_group[relNode - 1][forknum]; + // 获取分段组并加锁 AutoMutexLock lock(&eg->lock); lock.lock(); @@ -234,11 +266,11 @@ void df_close_all_file(RepairFileKey key, int32 max_sliceno) if (stat(tempfilename, &statBuf) < 0) { /* ENOENT is expected after the last segment... */ if (errno != ENOENT) { - ereport(WARNING, (errcode_for_file_access(), - errmsg("could not stat file \"%s\": %m", tempfilename))); + ereport(WARNING, (errcode_for_file_access(), errmsg("could not stat file \"%s\": %m", tempfilename))); } } else { if (eg->segfile->segfiles[i].fd > 0) { + // 关闭文件描述符 dv_close_file(eg->segfile->segfiles[i].fd); eg->segfile->segfiles[i].fd = -1; } @@ -254,7 +286,13 @@ void df_close_all_file(RepairFileKey key, int32 max_sliceno) spc_lock.unLock(); return; } - +/* + * 功能:清除并关闭所有分段逻辑文件中的文件描述符 + * + * 参数列表: + * key:RepairFileKey,表示需要修复的文件的关键信息 + * max_sliceno:int32,最大分片号 + */ void df_clear_and_close_all_file(RepairFileKey key, int32 max_sliceno) { Oid relNode = key.relfilenode.relNode; @@ -262,10 +300,12 @@ void df_clear_and_close_all_file(RepairFileKey key, int32 max_sliceno) SegSpace *spc = spc_init_space_node(key.relfilenode.spcNode, key.relfilenode.dbNode); Assert(relNode <= EXTENT_8192 && relNode > 0); + // 获取分段空间并加锁 AutoMutexLock spc_lock(&spc->lock); spc_lock.lock(); SegExtentGroup *eg = &spc->extent_group[relNode - 1][forknum]; + // 获取分段组并加锁 AutoMutexLock lock(&eg->lock); lock.lock(); @@ -277,13 +317,12 @@ void df_clear_and_close_all_file(RepairFileKey key, int32 max_sliceno) tempfilename = slice_filename(eg->segfile->filename, i); int ret = unlink(tempfilename); if (ret < 0 && errno != ENOENT) { - ereport(WARNING, (errcode_for_file_access(), - errmsg("could not remove file \"%s\": %m", tempfilename))); + ereport(WARNING, (errcode_for_file_access(), errmsg("could not remove file \"%s\": %m", tempfilename))); } if (ret >= 0) { - ereport(LOG, (errcode_for_file_access(), - errmsg("[file repair] clear segment file \"%s\"", tempfilename))); + ereport(LOG, (errcode_for_file_access(), errmsg("[file repair] clear segment file \"%s\"", tempfilename))); if (eg->segfile->segfiles[i].fd > 0) { + // 关闭文件描述符 dv_close_file(eg->segfile->segfiles[i].fd); eg->segfile->segfiles[i].fd = -1; } @@ -299,7 +338,13 @@ void df_clear_and_close_all_file(RepairFileKey key, int32 max_sliceno) spc_lock.unLock(); return; } - +/* + * 功能:打开所有分段逻辑文件中的文件描述符 + * + * 参数列表: + * key:RepairFileKey,表示需要修复的文件的关键信息 + * max_sliceno:int32,最大分片号 + */ void df_open_all_file(RepairFileKey key, int32 max_sliceno) { int fd = -1; @@ -310,10 +355,12 @@ void df_open_all_file(RepairFileKey key, int32 max_sliceno) SegSpace *spc = spc_init_space_node(key.relfilenode.spcNode, key.relfilenode.dbNode); Assert(relNode <= EXTENT_8192 && relNode > 0); + // 获取分段空间并加锁 AutoMutexLock spc_lock(&spc->lock); spc_lock.lock(); SegExtentGroup *eg = &spc->extent_group[relNode - 1][forknum]; + // 获取分段组并加锁 AutoMutexLock eg_lock(&eg->lock); eg_lock.lock(); @@ -349,20 +396,32 @@ void df_open_all_file(RepairFileKey key, int32 max_sliceno) /* * sliceno == 0, means opening all files; otherwises open until the target slice. */ +/* + * 功能:打开目标文件并初始化相应的文件描述符和相关信息 + * + * 参数列表: + * sf:SegLogicFile*,分段逻辑文件结构指针 + * targetno:int,目标文件号 + */ static void df_open_target_files(SegLogicFile *sf, int targetno) { - int sliceno = sf->file_num; + int sliceno = sf->file_num; // 当前文件数即分片号 uint32 flags = O_RDWR | PG_BINARY; + // 循环打开目标文件和后续文件 for (;;) { + // 如果指定了目标文件号并且已经达到了目标文件号,则退出循环 if (targetno != 0 && targetno < sliceno) { break; } + // 构建文件名 char *filename = slice_filename(sf->filename, sliceno); + // 如果分片号超出了向量的容量,扩展向量 if (sliceno >= sf->vector_capacity) { df_extend_file_vector(sf); } + // 尝试打开文件 int fd = dv_open_file(filename, flags, SEGMENT_FILE_MODE); if (fd < 0) { if (errno != ENOENT) { @@ -375,13 +434,16 @@ static void df_open_target_files(SegLogicFile *sf, int targetno) break; } + // 初始化文件描述符和总块数 sf->segfiles[sliceno].fd = fd; sf->segfiles[sliceno].sliceno = sliceno; off_t size = lseek(fd, 0L, SEEK_END); sf->total_blocks += size / BLCKSZ; + // 释放临时文件名内存 pfree(filename); + // 更新分片号和文件数 sliceno++; sf->file_num++; } @@ -404,33 +466,41 @@ void df_open_files(SegLogicFile *sf) /* * Extend logic file once. Each time we extend at most DF_FILE_SLICE_SIZE. */ +/* + * 功能:在逻辑文件的末尾扩展文件大小 + * + * 参数列表: + * sf:SegLogicFile*,分段逻辑文件结构指针 + */ void df_extend_internal(SegLogicFile *sf) { - int fd = sf->segfiles[sf->file_num - 1].fd; + int fd = sf->segfiles[sf->file_num - 1].fd; // 获取最后一个文件的文件描述符 - off_t last_file_size = lseek(fd, 0L, SEEK_END); - SegmentCheck(last_file_size <= DF_FILE_SLICE_SIZE); + off_t last_file_size = lseek(fd, 0L, SEEK_END); // 获取最后一个文件的当前大小 + SegmentCheck(last_file_size <= DF_FILE_SLICE_SIZE); // 检查是否超出分片大小 - if (last_file_size == DF_FILE_SLICE_SIZE) { - int new_sliceno = sf->file_num; - char *filename = slice_filename(sf->filename, new_sliceno); + if (last_file_size == DF_FILE_SLICE_SIZE) { // 如果最后一个文件已满 + int new_sliceno = sf->file_num; // 新分片号 + char *filename = slice_filename(sf->filename, new_sliceno); // 构建新文件名 + // 如果新分片号超出向量容量,扩展向量 if (new_sliceno >= sf->vector_capacity) { df_extend_file_vector(sf); } + // 创建新文件 int new_fd = dv_open_file(filename, O_RDWR | O_CREAT, SEGMENT_FILE_MODE); if (new_fd < 0) { ereport(ERROR, (errcode_for_file_access(), errmsg("[segpage] could not create file \"%s\": %m", filename))); } + // 扩展新文件大小 if (ftruncate(new_fd, DF_FILE_EXTEND_STEP_SIZE) != 0) { dv_close_file(new_fd); - ereport(ERROR, - (errmodule(MOD_SEGMENT_PAGE), - errcode_for_file_access(), - errmsg("ftuncate file %s failed during df_extend due to %s", filename, strerror(errno)), - errdetail("file path: %s", sf->filename))); + ereport(ERROR, (errmodule(MOD_SEGMENT_PAGE), errcode_for_file_access(), + errmsg("ftuncate file %s failed during df_extend due to %s", filename, strerror(errno)), + errdetail("file path: %s", sf->filename))); } + // 更新分片信息 sf->segfiles[new_sliceno] = {.fd = new_fd, .sliceno = new_sliceno}; sf->file_num++; sf->total_blocks += DF_FILE_EXTEND_STEP_BLOCKS; @@ -445,11 +515,13 @@ void df_extend_internal(SegLogicFile *sf) SegmentCheck(new_size <= DF_FILE_SLICE_SIZE); + // 扩展最后一个文件大小 if (ftruncate(fd, new_size) != 0) { char *filename = slice_filename(sf->filename, sf->file_num - 1); ereport(ERROR, (errmsg("ftuncate file %s failed during df_extend due to %s", filename, strerror(errno)))); } + // 更新总块数 sf->total_blocks += (new_size - last_file_size) / BLCKSZ; } } @@ -457,22 +529,29 @@ void df_extend_internal(SegLogicFile *sf) /* * Extend a logic file to target blocks. */ +/* + * 功能:根据目标块数扩展逻辑文件 + * + * 参数列表: + * sf:SegLogicFile*,分段逻辑文件结构指针 + * target_blocks:BlockNumber,目标块数 + */ void df_extend(SegLogicFile *sf, BlockNumber target_blocks) { - if (target_blocks == InvalidBlockNumber) { + if (target_blocks == InvalidBlockNumber) { // 检查目标块数是否合法 SegmentCheck(0); - ereport(ERROR, - (errcode(ERRCODE_DATA_EXCEPTION), errmsg("df_extend target_blocks is InvalidBlockNumber!\n"))); + ereport(ERROR, (errcode(ERRCODE_DATA_EXCEPTION), errmsg("df_extend target_blocks is InvalidBlockNumber!\n"))); } /* align target blocks to DF_FILE_EXTEND_STEP_BLOCKS */ target_blocks = CM_ALIGN_ANY(target_blocks, DF_FILE_EXTEND_STEP_BLOCKS); - AutoMutexLock lock(&sf->filelock); + AutoMutexLock lock(&sf->filelock); // 获取逻辑文件的锁 lock.lock(); if (sf->total_blocks < target_blocks) { if (!RecoveryInProgress()) { + // 检查是否超过最大大小 uint64 requestSize = 1LLU * BLCKSZ * (target_blocks - sf->total_blocks); TableSpaceUsageManager::IsExceedMaxsize(sf->relNode.spcNode, requestSize, true); } @@ -483,20 +562,26 @@ void df_extend(SegLogicFile *sf, BlockNumber target_blocks) SegmentCheck(sf->file_num > 0); while (sf->total_blocks < target_blocks) { - df_extend_internal(sf); + df_extend_internal(sf); // 循环扩展逻辑文件大小 } ereport(LOG, (errmsg("extend data file %s to %u blocks", sf->filename, target_blocks))); } - +/* + * 功能:缩小逻辑文件大小至目标块数 + * + * 参数列表: + * sf:SegLogicFile*,分段逻辑文件结构指针 + * target:BlockNumber,目标块数 + */ void df_shrink(SegLogicFile *sf, BlockNumber target) { - AutoMutexLock lock(&sf->filelock); + AutoMutexLock lock(&sf->filelock); // 获取逻辑文件的锁 lock.lock(); ssize_t last_file_size; ssize_t last_file_blocks; BlockNumber shrink_blocks; - SegmentCheck(target % DF_FILE_EXTEND_STEP_BLOCKS == 0); + SegmentCheck(target % DF_FILE_EXTEND_STEP_BLOCKS == 0); // 检查目标块数是否按规则对齐 if (sf->total_blocks < target) { return; } @@ -527,8 +612,8 @@ void df_shrink(SegLogicFile *sf, BlockNumber target) if (sf->segfiles[i].fd < 0) { ereport(PANIC, (errmsg("Unlink file %s failed and unable to read it again.", filename))); } else { - ereport( - ERROR, (errmsg("unlink file %s failed during df_shrink due to %s", filename, strerror(errno)))); + ereport(ERROR, + (errmsg("unlink file %s failed during df_shrink due to %s", filename, strerror(errno)))); } } sf->file_num--; @@ -565,63 +650,83 @@ void df_pread_block(SegLogicFile *sf, char *buffer, BlockNumber blocknum) int nbytes = pread(spf.fd, buffer, BLCKSZ, roffset); pgstat_report_waitevent(WAIT_EVENT_END); if (nbytes != BLCKSZ) { - ereport(ERROR, - (errcode(MOD_SEGMENT_PAGE), - errcode_for_file_access(), - errmsg("could not read segment block %d in file %s", blocknum, sf->filename), - errdetail("errno: %d", errno))); + ereport(ERROR, (errcode(MOD_SEGMENT_PAGE), errcode_for_file_access(), + errmsg("could not read segment block %d in file %s", blocknum, sf->filename), + errdetail("errno: %d", errno))); } } - +/* + * 功能:向分段逻辑文件中的指定块写入数据 + * + * 参数列表: + * sf:SegLogicFile*,分段逻辑文件结构指针 + * buffer:const char*,要写入的数据缓冲区指针 + * blocknum:BlockNumber,要写入的块号 + */ void df_pwrite_block(SegLogicFile *sf, const char *buffer, BlockNumber blocknum) { - off_t offset = ((off_t)blocknum) * BLCKSZ; - int sliceno = DF_OFFSET_TO_SLICENO(offset); - off_t roffset = DF_OFFSET_TO_SLICE_OFFSET(offset); + off_t offset = ((off_t)blocknum) * BLCKSZ; // 计算块号对应的偏移量 + int sliceno = DF_OFFSET_TO_SLICENO(offset); // 计算偏移量对应的切片号 + off_t roffset = DF_OFFSET_TO_SLICE_OFFSET(offset); // 计算偏移量对应的切片内偏移量 - pgstat_report_waitevent(WAIT_EVENT_DATA_FILE_WRITE); + pgstat_report_waitevent(WAIT_EVENT_DATA_FILE_WRITE); // 报告等待事件,表示正在进行数据文件写入操作 + + // 获取物理文件句柄 SegPhysicalFile spf = df_get_physical_file(sf, sliceno, blocknum); - int nbytes = pwrite(spf.fd, buffer, BLCKSZ, roffset); - pgstat_report_waitevent(WAIT_EVENT_END); + int nbytes = pwrite(spf.fd, buffer, BLCKSZ, roffset); // 执行写入操作 + pgstat_report_waitevent(WAIT_EVENT_END); // 结束等待事件的报告 + // 检查写入的字节数是否与期望的块大小相等,如果不相等,则抛出错误 if (nbytes != BLCKSZ) { - ereport(ERROR, - (errcode(MOD_SEGMENT_PAGE), - errcode_for_file_access(), - errmsg("could not write segment block %d in file %s, ", blocknum, sf->filename), - errdetail("errno: %d", errno))); + ereport(ERROR, (errcode(MOD_SEGMENT_PAGE), errcode_for_file_access(), + errmsg("could not write segment block %d in file %s, ", blocknum, sf->filename), + errdetail("errno: %d", errno))); } - seg_register_dirty_file(sf, sliceno); + seg_register_dirty_file(sf, sliceno); // 标记文件为脏文件,需要刷新到磁盘 } - +/* + * 功能:初始化分段逻辑文件控制结构 + * + * 参数列表: + * sf:SegLogicFile*,分段逻辑文件结构指针 + * relNode:RelFileNode,关系文件节点标识 + * forknum:ForkNumber,分叉号 + */ void df_ctrl_init(SegLogicFile *sf, RelFileNode relNode, ForkNumber forknum) { - sf->file_num = 0; - sf->segfiles = NULL; - sf->vector_capacity = 0; - sf->total_blocks = 0; + sf->file_num = 0; // 初始化文件数量为0 + sf->segfiles = NULL; // 初始化分段文件结构数组为空 + sf->vector_capacity = 0; // 初始化容量为0 + sf->total_blocks = 0; // 初始化总块数为0 - sf->relNode = relNode; - sf->forknum = forknum; + sf->relNode = relNode; // 设置关系文件节点标识 + sf->forknum = forknum; // 设置分叉号 - char *filename = relpathperm(relNode, forknum); - errno_t er = strcpy_s(sf->filename, MAXPGPATH, filename); - securec_check(er, "\0", "\0"); - pfree(filename); + char *filename = relpathperm(relNode, forknum); // 获取文件路径 + errno_t er = strcpy_s(sf->filename, MAXPGPATH, filename); // 复制文件路径到结构中 + securec_check(er, "\0", "\0"); // 检查字符串复制操作的错误 + pfree(filename); // 释放文件路径内存 - pthread_mutex_init(&sf->filelock, NULL); + pthread_mutex_init(&sf->filelock, NULL); // 初始化文件锁 } - +/* + * 功能:同步分段逻辑文件到磁盘 + * + * 参数列表: + * sf:SegLogicFile*,分段逻辑文件结构指针 + */ void df_fsync(SegLogicFile *sf) { - AutoMutexLock filelock(&sf->filelock); - filelock.lock(); + AutoMutexLock filelock(&sf->filelock); // 创建文件锁对象并自动加锁 + filelock.lock(); // 加锁以保证独占文件操作 for (int i = 0; i < sf->file_num; i++) { if (pg_fsync(sf->segfiles[i].fd)) { - ereport(data_sync_elevel(ERROR), (errcode_for_file_access(), errmodule(MOD_SEGMENT_PAGE), - errmsg("recover failed could not fsync segment data file"), - errdetail( "filename \"%s\", slot id %d, error message: %m", sf->filename, i))); + ereport(data_sync_elevel(ERROR), // 报告错误级别 + (errcode_for_file_access(), // 设置文件访问错误码 + errmodule(MOD_SEGMENT_PAGE), // 设置模块标识 + errmsg("recover failed could not fsync segment data file"), // 错误消息 + errdetail("filename \"%s\", slot id %d, error message: %m", sf->filename, i))); // 详细错误信息 } } } @@ -638,12 +743,12 @@ void df_unlink(SegLogicFile *sf) filelock.lock(); /* - * We first record "drop tablespace" xlog, then do the unlink. When the recovery thread + * We first record "drop tablespace" xlog, then do the unlink. When the recovery thread * replay the drop tablespace xlog, it will reopen the space, i.e., finding all data files - * from 0 to n. Thus, we should unlink files from back to front in case of failure happens + * from 0 to n. Thus, we should unlink files from back to front in case of failure happens * on half, so that the recovery thread can still unlink all files. */ - for (int i = sf->file_num-1; i >= 0; i--) { + for (int i = sf->file_num - 1; i >= 0; i--) { dv_close_file(sf->segfiles[i].fd); sf->segfiles[i].fd = -1; @@ -672,87 +777,109 @@ void forget_space_fsync_request(SegSpace *spc) } } } - +/* + * 功能:向同步管理器注册忘记请求,用于处理分段逻辑文件的回收 + * + * 参数列表: + * sf:SegLogicFile*,分段逻辑文件结构指针 + * segno:int,分段编号 + */ void seg_register_forget_request(SegLogicFile *sf, int segno) { - FileTag ftag; - errno_t er = memset_s(&ftag, sizeof(FileTag), 0, sizeof(FileTag)); - securec_check(er, "", ""); + FileTag ftag; // 创建文件标签结构 + errno_t er = memset_s(&ftag, sizeof(FileTag), 0, sizeof(FileTag)); // 初始化文件标签结构 + securec_check(er, "", ""); // 检查memset_s的返回值,确保安全 - ftag.rnode = sf->relNode; - ftag.forknum = sf->forknum; - ftag.handler = SYNC_HANDLER_SEGMENT; - ftag.segno = segno; + ftag.rnode = sf->relNode; // 设置文件标签的关联文件节点 + ftag.forknum = sf->forknum; // 设置文件标签的分支号 + ftag.handler = SYNC_HANDLER_SEGMENT; // 设置文件标签的处理程序为分段处理程序 + ftag.segno = segno; // 设置文件标签的分段编号 RegisterSyncRequest(&ftag, SYNC_FORGET_REQUEST, true /* retryOnError */); } - +/* + * 功能:向同步管理器注册脏文件请求,用于处理分段逻辑文件的脏数据 + * + * 参数列表: + * sf:SegLogicFile*,分段逻辑文件结构指针 + * segno:int,分段编号 + */ void seg_register_dirty_file(SegLogicFile *sf, int segno) { - FileTag ftag; - errno_t er = memset_s(&ftag, sizeof(FileTag), 0, sizeof(FileTag)); - securec_check(er, "", ""); + FileTag ftag; // 创建文件标签结构 + errno_t er = memset_s(&ftag, sizeof(FileTag), 0, sizeof(FileTag)); // 初始化文件标签结构 + securec_check(er, "", ""); // 检查memset_s的返回值,确保安全 /* Initialize ftag */ - ftag.rnode = sf->relNode; - ftag.forknum = sf->forknum; - ftag.rnode.bucketNode = SegmentBktId; - ftag.handler = SYNC_HANDLER_SEGMENT; - ftag.segno = segno; + ftag.rnode = sf->relNode; // 设置文件标签的关联文件节点 + ftag.forknum = sf->forknum; // 设置文件标签的分支号 + ftag.rnode.bucketNode = SegmentBktId; // 设置文件标签的Bucket节点 + ftag.handler = SYNC_HANDLER_SEGMENT; // 设置文件标签的处理程序为分段处理程序 + ftag.segno = segno; // 设置文件标签的分段编号 - if (!RegisterSyncRequest(&ftag, SYNC_REQUEST, false)) { - ereport(DEBUG5, - (errmodule(MOD_SEGMENT_PAGE), errmsg("could not forward fsync request because request queue is full"))); + if (!RegisterSyncRequest(&ftag, SYNC_REQUEST, + false)) { // 向同步管理器注册脏文件请求,用于处理分段逻辑文件的脏数据,设置重试选项为false + ereport(DEBUG5, (errmodule(MOD_SEGMENT_PAGE), errmsg("could not forward fsync request because request queue is " + "full"))); // 如果注册请求失败,输出DEBUG5级别的日志信息 - if (pg_fsync(sf->segfiles[segno].fd) < 0) { - char *filename = slice_filename(sf->filename, segno); + if (pg_fsync(sf->segfiles[segno].fd) < 0) { // 否则,通过pg_fsync函数尝试同步文件数据 + char *filename = slice_filename(sf->filename, segno); // 获取分段逻辑文件的文件名 ereport(data_sync_elevel(ERROR), - (errcode_for_file_access(), errmsg("could not fsync file \"%s\": %m", filename))); - pfree(filename); + (errcode_for_file_access(), + errmsg("could not fsync file \"%s\": %m", filename))); // 输出同步文件数据失败的错误日志信息 + pfree(filename); // 释放分配的内存空间 } } } - +/* + * 功能:通过文件标签同步分段文件 + * + * 参数列表: + * ftag:const FileTag*,文件标签指针,用于标识要同步的文件 + * path:char*,用于存储文件路径的字符数组 + * + */ int seg_sync_filetag(const FileTag *ftag, char *path) { /* Open tablespace */ SegSpace *spc = spc_open(ftag->rnode.spcNode, ftag->rnode.dbNode, false); - if (spc == NULL) { + if (spc == NULL) { // 如果表空间为空 ereport(LOG, (errmodule(MOD_SEGMENT_PAGE), errmsg("[segpage] seg_sync_filetag %s, ftag->segno is %u, but space is empty", relpathperm(ftag->rnode, ftag->forknum), ftag->segno))); - errno = ENOENT; - return -1; + errno = ENOENT; // 设置错误码为ENOENT + return -1; // 返回-1表示同步失败 } - int extent_size = EXTENT_TYPE_TO_SIZE(ftag->rnode.relNode); - int egid = EXTENT_SIZE_TO_GROUPID(extent_size); + int extent_size = EXTENT_TYPE_TO_SIZE(ftag->rnode.relNode); // 获取文件的大小 + int egid = EXTENT_SIZE_TO_GROUPID(extent_size); // 获取文件的组ID - SegLogicFile *lf = spc->extent_group[egid][ftag->forknum].segfile; - strlcpy(path, lf->filename, MAXPGPATH); + SegLogicFile *lf = spc->extent_group[egid][ftag->forknum].segfile; // 获取文件的大小 + int egid = EXTENT_SIZE_TO_GROUPID(extent_size); // 获取文件的组ID - AutoMutexLock lock(&lf->filelock); - lock.lock(); + AutoMutexLock lock(&lf->filelock); // 创建自动互斥锁 + lock.lock(); // 锁定互斥锁 - if (ftag->segno >= (uint32)lf->file_num) { + if (ftag->segno >= (uint32)lf->file_num) { // 如果要同步的分段编号大于等于已有的文件数 /* File may be deleted by spc_shrink or space delete */ - ereport(LOG, + ereport( + LOG, (errmodule(MOD_SEGMENT_PAGE), - errmsg("[segpage] seg_sync_filetag %s, ftag->segno is %u, but existing file number is %u, the file may " - "be deleted by spc_shrink or drop tablespace", - path, ftag->segno, lf->file_num))); - errno = ENOENT; - return -1; + errmsg("[segpage] seg_sync_filetag %s, ftag->segno is %u, but existing file number is %u, the file may " + "be deleted by spc_shrink or drop tablespace", + path, ftag->segno, lf->file_num))); // 输出错误日志信息 + errno = ENOENT; // 设置错误码为ENOENT + return -1; // 返回-1表示同步失败 } /* can not sync the file not exist */ - SegPhysicalFile *sf = &lf->segfiles[ftag->segno]; + SegPhysicalFile *sf = &lf->segfiles[ftag->segno]; // 获取分段文件结构指针 - SegmentCheck((uint32)sf->sliceno == ftag->segno); - int ret = pg_fsync(sf->fd); + SegmentCheck((uint32)sf->sliceno == ftag->segno); // 检查分段文件的分段编号是否匹配 + int ret = pg_fsync(sf->fd); // 调用pg_fsync函数进行文件同步操作 - ereport(DEBUG1, (errmsg("segment fsync %s", path))); + ereport(DEBUG1, (errmsg("segment fsync %s", path))); // 输出DEBUG1级别的日志信息 - return ret; + return ret; // 返回同步操作的结果 } int seg_unlink_filetag(const FileTag *ftag, char *path) @@ -760,20 +887,30 @@ int seg_unlink_filetag(const FileTag *ftag, char *path) SegmentCheck(0); return 0; } - +/* + * 功能:忘记指定数据库的文件同步请求 + * + * 参数列表: + * dbid:Oid,要忘记同步请求的数据库标识 + */ void segForgetDatabaseFsyncRequests(Oid dbid) { - FileTag tag; - RelFileNode rnode = {.spcNode = 0, .dbNode = dbid, .relNode = 0, .bucketNode = InvalidBktId, .opt = 0}; + FileTag tag; // 创建文件标签结构体 + RelFileNode rnode = { + .spcNode = 0, .dbNode = dbid, .relNode = 0, .bucketNode = InvalidBktId, .opt = 0}; // 创建文件节点结构体 - tag.rnode = rnode; - tag.handler = SYNC_HANDLER_SEGMENT; - tag.forknum = InvalidForkNumber; - tag.segno = InvalidBlockNumber; + tag.rnode = rnode; // 设置文件标签的节点信息为指定数据库 + tag.handler = SYNC_HANDLER_SEGMENT; // 设置文件标签的处理程序为SEGMENT + tag.forknum = InvalidForkNumber; // 设置文件标签的分支号为无效分支号 + tag.segno = InvalidBlockNumber; // 设置文件标签的分段号为无效分段号 RegisterSyncRequest(&tag, SYNC_FILTER_REQUEST, true /*retry on error */); } - +/* 功能:进行比较,以确定文件标签是否满足特定的条件。 + * 参数列表: + * ftag:一个指向 FileTag 结构的指针,表示要比较的目标 FileTag。 + * candidate:一个指向 FileTag 结构的指针,表示要与目标 ftag 进行比较的 FileTag。 + */ bool seg_filetag_matches(const FileTag *ftag, const FileTag *candidate) { return ftag->rnode.dbNode == candidate->rnode.dbNode; -- 2.34.1 From cd8b7da3c0168ff645321217204a0e543c943f0a Mon Sep 17 00:00:00 2001 From: nuoya <1204149038@qq.com> Date: Mon, 25 Sep 2023 15:33:17 +0800 Subject: [PATCH 15/50] Update extent_group.cpp --- .../storage/smgr/segment/extent_group.cpp | 354 ++++++++++++++++-- 1 file changed, 317 insertions(+), 37 deletions(-) diff --git a/src/gausskernel/storage/smgr/segment/extent_group.cpp b/src/gausskernel/storage/smgr/segment/extent_group.cpp index d674ff8ff..a7817ab3a 100644 --- a/src/gausskernel/storage/smgr/segment/extent_group.cpp +++ b/src/gausskernel/storage/smgr/segment/extent_group.cpp @@ -92,44 +92,76 @@ void eg_init_df_ctrl(SegExtentGroup *seg) seg->segfile = sf; } - +/* + * 功能:获取 SegExtentGroup 中关联的 SegLogicFile 的总块数 + * + * 参数列表: + * seg:SegExtentGroup 指针,表示一个段扩展组 + * + */ BlockNumber eg_df_size(SegExtentGroup *seg) { + // 使用 AutoMutexLock 来对 seg->lock 进行自动加锁,确保线程安全 AutoMutexLock lock(&seg->lock); lock.lock(); + // 获取 seg->segfile,即关联的 SegLogicFile 结构 SegLogicFile *sf = seg->segfile; SegmentCheck(sf != NULL); + // 返回 SegLogicFile 结构的 total_blocks 成员,表示总块数 return sf->total_blocks; } - +/* + * 功能:检查 SegExtentGroup 中是否存在关联的 SegLogicFile + * + * 参数列表: + * seg:SegExtentGroup 指针,表示一个段扩展组 + * + * 返回值:bool,如果存在关联的 SegLogicFile,返回 true;否则返回 false。 + */ bool eg_df_exists(SegExtentGroup *seg) { + // 使用 AutoMutexLock 来对 seg->lock 进行自动加锁,确保线程安全 AutoMutexLock lock(&seg->lock); lock.lock(); SegLogicFile *sf = seg->segfile; SegmentCheck(sf != NULL); + // 返回判断关联的 SegLogicFile 是否存在的结果 return (sf->file_num > 0); } - +/* + * 功能:创建 SegExtentGroup 关联的 SegLogicFile,如果尚未创建 + * + * 参数列表: + * seg:SegExtentGroup 指针,表示一个段扩展组 + */ void eg_create_df(SegExtentGroup *seg) { + // 使用 AutoMutexLock 来对 seg->lock 进行自动加锁,确保线程安全 AutoMutexLock lock(&seg->lock); lock.lock(); SegLogicFile *sf = seg->segfile; SegmentCheck(sf != NULL); + // 如果 sf 中的文件数目为 0,说明尚未创建 SegLogicFile,需要创建 if (sf->file_num == 0) { df_create_file(sf, false); } } - +/* + * 功能:扩展 SegExtentGroup 关联的 SegLogicFile 到指定的文件大小 + * + * 参数列表: + * seg:SegExtentGroup 指针,表示一个段扩展组 + * target_file_size:BlockNumber,目标文件大小(块数) + */ void eg_extend_df(SegExtentGroup *seg, BlockNumber target_file_size) { + // 使用 AutoMutexLock 来对 seg->lock 进行自动加锁,确保线程安全 AutoMutexLock lock(&seg->lock); lock.lock(); @@ -137,49 +169,86 @@ void eg_extend_df(SegExtentGroup *seg, BlockNumber target_file_size) SegLogicFile *sf = seg->segfile; SegmentCheck(sf != NULL); + // 调用 df_extend 函数来扩展 SegLogicFile 到指定的文件大小 df_extend(sf, target_file_size); } - +/* + * 功能:将 SegExtentGroup 关联的 SegLogicFile 缩小到指定的目标大小 + * + * 参数列表: + * seg:SegExtentGroup 指针,表示一个段扩展组 + * target:BlockNumber,目标文件大小(块数),缩小到这个大小 + */ void eg_shrink_df(SegExtentGroup *seg, BlockNumber target) { AutoMutexLock lock(&seg->lock); lock.lock(); - + SegLogicFile *sf = seg->segfile; SegmentCheck(sf != NULL); + // 调用 df_shrink 函数来将 SegLogicFile 缩小到指定的目标大小 df_shrink(sf, target); } /* * Check current data file status; this function is usually called when the space is first accessed. */ +/* + * 功能:获取 SegExtentGroup 关联的 SegLogicFile 的状态 + * + * 参数列表: + * seg:SegExtentGroup 指针,表示一个段扩展组 + * + */ SpaceDataFileStatus eg_status(SegExtentGroup *seg) { + // 调用 eg_df_exists 函数检查 SegLogicFile 是否存在 if (!eg_df_exists(seg)) { + // 调用 eg_df_exists 函数检查 SegLogicFile 是否存在 return SpaceDataFileStatus::EMPTY; } + // 调用 eg_df_valid 函数检查 SegLogicFile 是否有效 if (eg_df_valid(seg)) { + // 调用 eg_df_valid 函数检查 SegLogicFile 是否有效 return SpaceDataFileStatus::NORMAL; } + // 如果 SegLogicFile 既不为空也不有效,返回 CRASHED 状态 return SpaceDataFileStatus::CRASHED; } /* Initialize information in SegExtentGroup structure */ +/* + * 功能:用于初始化 SegExtentGroup 结构体,表示一组关联的数据文件的信息 + * + * 参数列表: + * spc:SegSpace 结构体,表示这组数据文件所属的 SegSpace。 + * seg:SegExtentGroup 结构体,要进行初始化的目标结构体。 + * extent_size:int,每个数据文件的大小。 + * forknum:ForkNumber,与数据文件关联的 fork 类型。 + */ void eg_ctrl_init(SegSpace *spc, SegExtentGroup *seg, int extent_size, ForkNumber forknum) { + // 确保 seg 正确关联到 spc 的 extent_group 数组中的位置 SegmentCheck(&spc->extent_group[EXTENT_SIZE_TO_GROUPID(extent_size)][forknum] == seg); + // 设置 seg 的 extent_size 字段,表示每个数据文件的大小 seg->extent_size = extent_size; + // 设置 seg 的 space 字段,表示这组数据文件所属的 SegSpace 结构体 seg->space = spc; + // 为 seg 的 rnode 字段分配一个 RelFileNode 结构体,用于表示文件节点的相关信息 seg->rnode = {.spcNode = spc->spcNode, .dbNode = spc->dbNode, .relNode = EXTENT_SIZE_TO_TYPE(extent_size), .bucketNode = SegmentBktId, .opt = 0}; + // 设置 seg 的 forknum 字段,表示与数据文件关联的 fork 类型 seg->forknum = forknum; + // 设置 seg 的 map_head_entry 字段的初始值 seg->map_head_entry = DF_MAP_HEAD_PAGE; + // 初始化 seg 的互斥锁,确保对 seg 的并发访问的线程安全性 pthread_mutex_init(&seg->lock, NULL); + // 可能是一个初始化数据文件控制结构的函数,用于进一步初始化与这组数据文件关联的控制信息 eg_init_df_ctrl(seg); } @@ -190,36 +259,61 @@ void eg_init_datafile_head(SegExtentGroup *seg) * if necessary in the future. */ } - +/* + * 功能:用于初始化映射头页的内容,这个映射头页包含了一些关于映射的元信息 + * + * 参数列表: + * map_head_page:Page,映射头页的指针。 + * extent_size:int,每个数据文件的大小。 + */ void eg_init_map_head_page_content(Page map_head_page, int extent_size) { + // 使用 SegPageInit 函数初始化映射头页,设置页大小为 BLCKSZ SegPageInit(map_head_page, BLCKSZ); + // 获取映射头页的内容指针 char *content = PageGetContents(map_head_page); + // 将内容解释为 df_map_head_t 结构体,这是映射头页的结构 df_map_head_t *map_head = (df_map_head_t *)content; - map_head->bit_unit = extent_size; - map_head->group_count = 0; - map_head->free_group = 0; - map_head->allocated_extents = 0; + // 设置映射头页的字段值 + map_head->bit_unit = extent_size; // 每个位图单元的大小,即数据文件的大小 + map_head->group_count = 0; // 组的数量,初始为 0 + map_head->free_group = 0; // 空闲组的数量,初始为 0 + map_head->allocated_extents = 0; // 已分配的扩展数量,初始为 0 + // 获取映射头页的页头,并将 pd_lower 字段增加 df_map_head_t 结构体的大小 PageHeader page_header = (PageHeader)map_head_page; page_header->pd_lower += sizeof(df_map_head_t); } - +/* + * 功能:用于初始化映射头,包括创建映射头页并写入相关信息 + * + * 参数列表: + * seg:SegExtentGroup*,扩展组的指针,包含了映射头的相关信息。 + * rec_ptr:XLogRecPtr,用于设置映射头页的LSN。 + */ static void eg_init_map_head(SegExtentGroup *seg, XLogRecPtr rec_ptr) { + // 设置映射头所在的页号 BlockNumber pageno = DF_MAP_HEAD_PAGE; + // 分配一个新的页并初始化为0 Page page = (Page)palloc(BLCKSZ); errno_t er = memset_s((void *)page, BLCKSZ, 0, BLCKSZ); securec_check(er, "", ""); + // 初始化映射头页的内容 eg_init_map_head_page_content(page, seg->extent_size); + // 设置映射头页的LSN PageSetLSN(page, rec_ptr); + // 计算并设置映射头页的校验和 PageSetChecksumInplace(page, pageno); + // 将映射头页写入扩展组的数据文件中 df_pwrite_block(seg->segfile, (char *)page, pageno); + // 释放分配的内存 pfree(page); + // 更新扩展组中的映射头页信息 seg->map_head_entry = pageno; seg->map_head = NULL; } @@ -228,16 +322,26 @@ void eg_clean_data_files(SegExtentGroup *seg) { df_unlink(seg->segfile); } - +/* + * 功能:初始化扩展组的数据文件 + * + * 参数列表: + * eg:SegExtentGroup*,扩展组的指针,包含了数据文件和映射头的相关信息。 + * redo:bool,表示是否处于重做阶段。 + * rec_ptr:XLogRecPtr,用于设置映射头页的LSN。 + */ void eg_init_data_files(SegExtentGroup *eg, bool redo, XLogRecPtr rec_ptr) { SEGMENTTEST(EXTENT_GROUP_INIT_DATA, (errmsg("EXTENT_GROUP_INIT_DATA %s: the first time for create segment tb!\n", - g_instance.attr.attr_common.PGXCNodeName))); + g_instance.attr.attr_common.PGXCNodeName))); /* create file will ensure the file has enough space */ df_create_file(eg->segfile, redo); - eg_init_datafile_head(eg); // initialize data file - eg_init_map_head(eg, rec_ptr); // initialize map head + // 初始化数据文件头 + eg_init_datafile_head(eg); // initialize data file + // 初始化映射头 + eg_init_map_head(eg, rec_ptr); // initialize map head + // 文件同步 df_fsync(eg->segfile); } @@ -273,33 +377,56 @@ SpaceMapLocation eg_locate_map_by_pageid(SegExtentGroup *seg, BlockNumber page_i return result; } - +/* + * 功能:用于设置位图页中指定位置的位,并更新位图页的相关信息 + * + * 参数列表: + * map_page:df_map_page_t*,位图页的指针。 + * bit_id:uint16,要设置的位的位置。 + */ void eg_set_bitmap_page(df_map_page_t *map_page, uint16 bit_id) { + // 检查位图页中指定位置的位是否已经被设置 SegmentCheck(DF_MAP_FREE(map_page->bitmap, bit_id)); + // 设置位图页中指定位置的位 DF_MAP_SET(map_page->bitmap, bit_id); + // 更新位图页的空闲位数 map_page->free_bits--; + // 如果空闲位的起始位置是指定位置,更新空闲位的起始位置 if (map_page->free_begin == bit_id) { map_page->free_begin++; } + // 如果脏位的位置小于指定位置,更新脏位的位置 if (map_page->dirty_last < bit_id) { map_page->dirty_last = bit_id; } } - +/* + * 功能:初始化位图页的内容 + * + * 参数列表: + * bitmap_page:Page,位图页的指针。 + * first_page:BlockNumber,第一个数据页的块号。 + */ void eg_init_bitmap_page_content(Page bitmap_page, BlockNumber first_page) { + // 初始化位图页的内容,将其全部设置为0 SegPageInit(bitmap_page, BLCKSZ); + // 获取位图页的内容 df_map_page_t *df_map_page = (df_map_page_t *)PageGetContents(bitmap_page); + // 设置位图页的起始位置、脏位位置、空闲位数和第一个数据页的块号 df_map_page->free_begin = 0; df_map_page->dirty_last = 0; df_map_page->free_bits = DF_MAP_BIT_CNT; df_map_page->first_page = first_page; + // 获取位图页的页头信息 PageHeader bitmap_page_header = (PageHeader)bitmap_page; + // 检查位图页的页头上限是否等于BLCKSZ SegmentCheck(bitmap_page_header->pd_upper = BLCKSZ); + // 设置位图页的页头下限等于页头上限,标记位图页已经被初始化 bitmap_page_header->pd_lower = bitmap_page_header->pd_upper; } @@ -356,15 +483,28 @@ BlockNumber eg_alloc_extent_from_map_internal(SegExtentGroup *seg, Buffer map_bu return pagenum; } - +/* + * 功能:从位图中分配一个数据扩展块 + * + * 参数列表: + * seg:SegExtentGroup*,分段空间组的指针。 + * map_page_id:BlockNumber,位图页的块号。 + * target_file_size:BlockNumber*,用于返回目标文件的大小。 + * map_group:uint32,位图组的标识。 + * free_page:uint32,空闲页的标识。 + * + */ BlockNumber eg_alloc_extent_from_map(SegExtentGroup *seg, BlockNumber map_page_id, BlockNumber *target_file_size, - uint32 map_group, uint32 free_page) + uint32 map_group, uint32 free_page) { + // 读取位图页并加锁,以便进行操作 Buffer map_buffer = ReadBufferFast(seg->space, seg->rnode, seg->forknum, map_page_id, RBM_NORMAL); LockBuffer(map_buffer, BUFFER_LOCK_EXCLUSIVE); + // 获取位图页的内容 df_map_page_t *map_page = (df_map_page_t *)PageGetContents(BufferGetPage(map_buffer)); + // 检查位图是否已满,如果已满则释放缓冲区并返回无效块号 if (map_page->free_bits == 0) { SegUnlockReleaseBuffer(map_buffer); return InvalidBlockNumber; @@ -379,6 +519,7 @@ BlockNumber eg_alloc_extent_from_map(SegExtentGroup *seg, BlockNumber map_page_i curr++; } + // 如果没有可用位,则释放缓冲区并返回无效块号 if (curr >= DF_MAP_BIT_CNT) { /* current map does not contain a free bit */ SegUnlockReleaseBuffer(map_buffer); @@ -388,11 +529,20 @@ BlockNumber eg_alloc_extent_from_map(SegExtentGroup *seg, BlockNumber map_page_i /* internal function will release and unlock the MapPage buffer */ return eg_alloc_extent_from_map_internal(seg, map_buffer, curr, target_file_size, map_group, free_page); } - +/* + * 功能:从预分配块中分配一个数据扩展块 + * + * 参数列表: + * seg:SegExtentGroup*,分段空间组的指针。 + * preassigned_block:BlockNumber,预分配的数据块号。 + * target_file_size:BlockNumber*,用于返回目标文件的大小。 + */ bool eg_alloc_preassigned_block(SegExtentGroup *seg, BlockNumber preassigned_block, BlockNumber *target_file_size) { + // 通过数据块号定位位图位置 SpaceMapLocation location = eg_locate_map_by_pageid(seg, preassigned_block, seg->map_head); + // 检查位图位置是否有效,如果无效则返回false if (MapLocationIsInvalid(location)) { return false; } @@ -400,13 +550,16 @@ bool eg_alloc_preassigned_block(SegExtentGroup *seg, BlockNumber preassigned_blo BlockNumber map_blocknum = location.map_id + location.group.first_map; + // 读取位图页并加锁,以便进行操作 Buffer map_buffer = ReadBufferFast(seg->space, seg->rnode, seg->forknum, map_blocknum, RBM_NORMAL); LockBuffer(map_buffer, BUFFER_LOCK_EXCLUSIVE); df_map_page_t *map_page = (df_map_page_t *)PageGetContents(BufferGetPage(map_buffer)); + // 检查位图中的位是否可用,如果可用则继续分配,否则报错 if (DF_MAP_FREE(map_page->bitmap, bit_id)) { BlockNumber pagenum = map_page->first_page + bit_id * seg->map_head->bit_unit; // check whether the allocated block exceeds the file size + // 检查分配的块是否超过文件大小 if ((pagenum + seg->extent_size) > seg->segfile->total_blocks) { *target_file_size = pagenum + seg->extent_size; SegUnlockReleaseBuffer(map_buffer); @@ -415,52 +568,83 @@ bool eg_alloc_preassigned_block(SegExtentGroup *seg, BlockNumber preassigned_blo } else { ereport(PANIC, (errmsg("The preassigned segment block %u is already in use", preassigned_block))); } + // 获取当前位图组和页号 uint32 free_group = seg->map_head->free_group; uint32 free_page = seg->map_head->groups[free_group].free_page; SegmentCheck(free_group < seg->map_head->group_count && free_page < seg->map_head->groups[free_group].page_count); + // 调用内部函数进行分配,该函数会释放和解锁MapPage缓冲区 (void)eg_alloc_extent_from_map_internal(seg, map_buffer, bit_id, target_file_size, free_group, free_page); return true; } - +/* + * 功能:初始化位图页 + * + * 参数列表: + * seg:SegExtentGroup*,分段空间组的指针。 + * pageno:BlockNumber,位图页号。 + * first_page:BlockNumber,位图对应的数据文件中的起始页号。 + */ void eg_init_bitmap_page(SegExtentGroup *seg, BlockNumber pageno, BlockNumber first_page) { + // 读取位图页并加锁,以便进行操作 Buffer buffer = ReadBufferFast(seg->space, seg->rnode, seg->forknum, pageno, RBM_NORMAL); LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE); + // 开始事务 START_CRIT_SECTION(); + // 标记缓冲区为脏页,表示需要写回磁盘 SegMarkBufferDirty(buffer); + // 调用函数初始化位图页的内容 eg_init_bitmap_page_content(BufferGetPage(buffer), first_page); /* XLog Issue */ + // 插入XLog记录 XLogBeginInsert(); XLogRegisterData((char *)&first_page, sizeof(BlockNumber)); XLogRegisterBuffer(0, buffer, REGBUF_WILL_INIT); XLogRecPtr rec_ptr = XLogInsert(RM_SEGPAGE_ID, XLOG_SEG_INIT_MAPPAGE, SegmentBktId); + // 设置页LSN PageSetLSN(BufferGetPage(buffer), rec_ptr); + // 设置页LSN END_CRIT_SECTION(); + // 解锁并释放缓冲区 SegUnlockReleaseBuffer(buffer); } - +/* + * 功能:初始化逆向指针页 + * + * 参数列表: + * seg:SegExtentGroup*,分段空间组的指针。 + * pageno:BlockNumber,逆向指针页号。 + */ void eg_init_invrsptr_page(SegExtentGroup *seg, BlockNumber pageno) { + // 读取逆向指针页并清零内容,同时加锁 Buffer buffer = ReadBufferFast(seg->space, seg->rnode, seg->forknum, pageno, RBM_ZERO_AND_LOCK); Page page = BufferGetPage(buffer); + // 开始事务 START_CRIT_SECTION(); + // 标记缓冲区为脏页,表示需要写回磁盘 SegMarkBufferDirty(buffer); + // 初始化页内容,将其清零 SegPageInit(page, BLCKSZ); + // 插入XLog记录 XLogBeginInsert(); XLogRegisterBuffer(0, buffer, REGBUF_WILL_INIT); XLogRecPtr rec_ptr = XLogInsert(RM_SEGPAGE_ID, XLOG_SEG_INIT_INVRSPTR_PAGE, SegmentBktId); + // 设置页LSN PageSetLSN(page, rec_ptr); + // 结束事务 END_CRIT_SECTION(); + // 解锁并释放缓冲区 SegUnlockReleaseBuffer(buffer); } @@ -473,24 +657,37 @@ BlockNumber eg_next_group_start(df_map_head_t *map_head) return last_map_group.first_map + last_map_group.page_count + IPBLOCK_GROUP_SIZE + DF_MAP_GROUP_EXTENTS * (uint32)map_head->bit_unit; } - +/* + * 功能:向分段空间组添加新的映射组 + * + * 参数列表: + * seg:SegExtentGroup*,分段空间组的指针。 + * pageno:BlockNumber,新映射组的起始页号。 + * group_size:uint8,映射组的大小。 + * old_group_count:int,之前的映射组数量。 + */ void eg_add_map_group(SegExtentGroup *seg, BlockNumber pageno, uint8 group_size, int old_group_count) { + // 打印日志,记录添加新映射组的信息 ereport(LOG, (errmsg("Extent group (%u, %u, %u) add new group #%d, start pageno: %u ", seg->rnode.spcNode, seg->rnode.dbNode, seg->rnode.relNode, old_group_count, pageno))); + // 获取映射头缓冲区 Buffer map_head_buffer = seg->map_head_buffer; Page page = BufferGetPage(map_head_buffer); df_map_head_t *map_head = (df_map_head_t *)PageGetContents(page); + // 如果旧的映射组数量小于当前的数量,说明其他操作已经扩展了映射组,直接返回 if (old_group_count < map_head->group_count) { /* Some one else extend the extent group for us */ return; } + // 检查映射组数量是否匹配 SegmentCheck(old_group_count == map_head->group_count); SegmentCheck(group_size == DF_MAP_GROUP_SIZE); BlockNumber start_map_no = pageno; BlockNumber data_start = pageno + group_size + IPBLOCK_GROUP_SIZE; + // 初始化位图页和逆向指针页 for (uint32 i = 0; i < group_size; i++) { eg_init_bitmap_page(seg, pageno, data_start); data_start += DF_MAP_BIT_CNT * seg->extent_size; @@ -501,10 +698,12 @@ void eg_add_map_group(SegExtentGroup *seg, BlockNumber pageno, uint8 group_size, eg_init_invrsptr_page(seg, pageno); pageno++; } + // 打印测试信息 SEGMENTTEST(EXTENT_GROUP_ADD_NEW_GROUP, (errmsg("EXTENT_GROUP_ADD_NEW_GROUP %s: add new group success!\n", - g_instance.attr.attr_common.PGXCNodeName))); + g_instance.attr.attr_common.PGXCNodeName))); /* XLog issue */ { + // 开始事务 START_CRIT_SECTION(); /* Update map head */ df_map_group_t *bitmap_group = &map_head->groups[map_head->group_count]; @@ -513,8 +712,10 @@ void eg_add_map_group(SegExtentGroup *seg, BlockNumber pageno, uint8 group_size, bitmap_group->page_count = group_size; bitmap_group->free_page = 0; + // 标记映射头缓冲区为脏页,表示需要写回磁盘 SegMarkBufferDirty(map_head_buffer); + // 创建XLog记录 xl_new_map_group_info_t new_map_group_info = {.first_map_pageno = start_map_no, .extent_size = seg->extent_size, .group_count = map_head->group_count, @@ -525,11 +726,15 @@ void eg_add_map_group(SegExtentGroup *seg, BlockNumber pageno, uint8 group_size, XLogRegisterBuffer(0, map_head_buffer, REGBUF_STANDARD); XLogRecPtr recptr = XLogInsert(RM_SEGPAGE_ID, XLOG_SEG_ADD_NEW_GROUP, SegmentBktId); + // 设置页LSN PageSetLSN(page, recptr); + // 结束事务 END_CRIT_SECTION(); } - SEGMENTTEST(EXTENT_GROUP_ADD_NEW_GROUP_XLOG, (errmsg("EXTENT_GROUP_ADD_NEW_GROUP_XLOG %s: add new group xlog success!\n", - g_instance.attr.attr_common.PGXCNodeName))); + // 打印测试信息 + SEGMENTTEST(EXTENT_GROUP_ADD_NEW_GROUP_XLOG, + (errmsg("EXTENT_GROUP_ADD_NEW_GROUP_XLOG %s: add new group xlog success!\n", + g_instance.attr.attr_common.PGXCNodeName))); } /* @@ -551,8 +756,7 @@ BlockNumber eg_try_alloc_extent(SegExtentGroup *seg, BlockNumber preassigned_blo df_map_head_t *map_head = seg->map_head; seg->map_head = map_head; - SegmentCheck(map_head->group_count > 0 && - map_head->free_group < map_head->group_count); + SegmentCheck(map_head->group_count > 0 && map_head->free_group < map_head->group_count); /* preassigned block */ if (BlockNumberIsValid(preassigned_block)) { @@ -592,31 +796,48 @@ BlockNumber eg_try_alloc_extent(SegExtentGroup *seg, BlockNumber preassigned_blo } return InvalidBlockNumber; } - +/* + * 功能:检查分段空间组是否为空 + * + * 参数列表: + * seg:SegExtentGroup*,分段空间组的指针。 + */ bool eg_empty(SegExtentGroup *seg) { + // 如果分段数据文件不存在,认为分段空间组为空 if (!eg_df_exists(seg)) { return true; } int result; + // 读取映射头缓冲区并共享锁定 Buffer buffer = eg_read_maphead_buffer(seg); LockBuffer(buffer, BUFFER_LOCK_SHARE); + // 获取映射头信息 seg->map_head = (df_map_head_t *)PageGetContents(BufferGetPage(buffer)); result = seg->map_head->allocated_extents; + // 释放映射头缓冲区的锁定 SegUnlockReleaseBuffer(buffer); #ifdef USE_ASSERT_CHECKING + // 使用断言检查,如果有已分配的分段,则打印日志记录 if (result != 0) { ereport(LOG, (errmsg("Extent group %d is not empty, there are %d extents used", seg->extent_size, result))); } #endif + // 返回结果,如果已分配的分段数为0,则认为分段空间组为空 return result == 0; } - +/* + * 功能:获取分段空间组的存储统计信息 + * + * 参数列表: + * seg:SegExtentGroup*,分段空间组的指针。 + */ SegmentSpaceStat eg_storage_stat(SegExtentGroup *seg) { + // 如果分段数据文件不存在,返回默认的存储统计信息 if (!eg_df_exists(seg)) { return {.extent_size = (uint32)seg->extent_size, .forknum = seg->forknum, @@ -626,9 +847,11 @@ SegmentSpaceStat eg_storage_stat(SegExtentGroup *seg) .utilization = 0, .high_water_mark = 0}; } + // 读取映射头缓冲区并共享锁定 Buffer buffer = eg_read_maphead_buffer(seg); LockBuffer(buffer, BUFFER_LOCK_SHARE); + // 获取映射头信息 df_map_head_t *map_head = (df_map_head_t *)PageGetContents(BufferGetPage(buffer)); BlockNumber total_blocks = seg->segfile->total_blocks; @@ -651,9 +874,20 @@ SegmentSpaceStat eg_storage_stat(SegExtentGroup *seg) SegUnlockReleaseBuffer(buffer); return result; } - +/* + * 功能:获取分段空间组中所有的逆指针和数据块号 + * + * 参数列表: + * seg:SegExtentGroup*,分段空间组的指针。 + * cnt:uint32*,返回找到的逆指针和数据块号的数量。 + * iptrs:ExtentInversePointer**,返回逆指针数组的指针。 + * extents:BlockNumber**,返回数据块号数组的指针。 + * + * 注意:调用者需要在不再需要逆指针和数据块号时释放它们的内存。 + */ void GetAllInversePointer(SegExtentGroup *seg, uint32 *cnt, ExtentInversePointer **iptrs, BlockNumber **extents) { + // 如果分段数据文件不存在,返回空结果 if (!eg_df_exists(seg)) { *cnt = 0; *iptrs = NULL; @@ -665,9 +899,11 @@ void GetAllInversePointer(SegExtentGroup *seg, uint32 *cnt, ExtentInversePointer *extents = (BlockNumber *)palloc(sizeof(BlockNumber) * len); *cnt = 0; + // 获取映射头信息 Buffer buffer = eg_read_maphead_buffer(seg); LockBuffer(buffer, BUFFER_LOCK_SHARE); + // 获取映射头信息 df_map_head_t *map_head = (df_map_head_t *)PageGetContents(BufferGetPage(buffer)); /* copy meta-data from map head, to avoid locking it for long time. */ @@ -676,6 +912,7 @@ void GetAllInversePointer(SegExtentGroup *seg, uint32 *cnt, ExtentInversePointer for (int i = 0; i < group_count; i++) { groups[i] = map_head->groups[i]; } + // 释放映射头缓冲区的锁定 SegUnlockReleaseBuffer(buffer); Buffer ipbuf = InvalidBuffer; @@ -715,40 +952,53 @@ void GetAllInversePointer(SegExtentGroup *seg, uint32 *cnt, ExtentInversePointer SegUnlockReleaseBuffer(map_buffer); } } + // 如果逆指针缓冲区有效,则释放它 if (BufferIsValid(ipbuf)) { SegReleaseBuffer(ipbuf); } } - +/* + * 功能:用于检查分段空间组是否存在,如果不存在则创建之。创建分段空间组时,还会进行一些附加的操作 + * + * 参数列表: + * seg:SegExtentGroup*,分段空间组的指针 + */ void eg_create_if_necessary(SegExtentGroup *seg) { + // 如果分段数据文件不存在 if (!eg_df_exists(seg)) { AutoMutexLock lock(&seg->lock); lock.lock(); + // 检查是否已经有其他进程创建了分段数据文件 if (seg->segfile->file_num > 0) { /* Someone else has created it */ return; } - TablespaceCreateDbspace(seg->rnode.spcNode, seg->rnode.dbNode, false); + // 创建表空间和检查表空间限额 + TablespaceCreateDbspace(seg->rnode.spcNode, seg->rnode.dbNode, false); /* Ensure tablespace limits at first. */ uint64 requestSize = DF_FILE_EXTEND_STEP_SIZE; TableSpaceUsageManager::IsExceedMaxsize(seg->rnode.spcNode, requestSize, true); + // 开始临界区 START_CRIT_SECTION(); t_thrd.pgxact->delayChkpt = true; + // 写入XLog记录,标记分段空间组的创建 XLogBeginInsert(); XLogRegisterData((char *)&seg->rnode, sizeof(RelFileNode)); XLogRegisterData((char *)&seg->forknum, sizeof(ForkNumber)); XLogRecPtr xlog = XLogInsert(RM_SEGPAGE_ID, XLOG_SEG_CREATE_EXTENT_GROUP, SegmentBktId); XLogWaitFlush(xlog); + // 初始化数据文件和映射头 eg_init_data_files(seg, false, xlog); SEGMENTTEST(EXTENT_GROUP_CREATE_EXTENT, (errmsg("EXTENT_GROUP_CREATE_EXTENT %s: create segment file success!\n", - g_instance.attr.attr_common.PGXCNodeName))); + g_instance.attr.attr_common.PGXCNodeName))); t_thrd.pgxact->delayChkpt = false; + // 结束临界区 END_CRIT_SECTION(); } } @@ -756,8 +1006,17 @@ void eg_create_if_necessary(SegExtentGroup *seg) /* * Extent Group Layer APIS: allocate/free extents; auto extend if the space is used up. */ +/* + * 功能:分配一个分段空间组的扩展 + * + * 参数列表: + * seg:SegExtentGroup*,分段空间组的指针 + * preassigned_block:BlockNumber,预分配的块号 + * iptr:ExtentInversePointer,扩展块的逆指针 + */ BlockNumber eg_alloc_extent(SegExtentGroup *seg, BlockNumber preassigned_block, ExtentInversePointer iptr) { + // 如果需要的话,创建分段空间组 eg_create_if_necessary(seg); BlockNumber blocknum = InvalidBlockNumber; @@ -766,6 +1025,7 @@ BlockNumber eg_alloc_extent(SegExtentGroup *seg, BlockNumber preassigned_block, seg->map_head_buffer = eg_read_maphead_buffer(seg); LockBuffer(seg->map_head_buffer, BUFFER_LOCK_EXCLUSIVE); seg->map_head = (df_map_head_t *)PageGetContents(BufferGetBlock(seg->map_head_buffer)); + // 如果映射组数量为0,创建第一个映射组 if (seg->map_head->group_count == 0) { // create the first map group eg_add_map_group(seg, DF_MAP_HEAD_PAGE + 1, DF_MAP_GROUP_SIZE, 0); @@ -776,8 +1036,9 @@ BlockNumber eg_alloc_extent(SegExtentGroup *seg, BlockNumber preassigned_block, // Get an extent, we do not have to release the map head buffer. XLogAtomicOpCommit will do it for us. // set inverse point and return SetInversePointer(seg, blocknum, iptr); - SEGMENTTEST(EXTENT_GROUP_CRITICAL_SECTION, (errmsg("EXTENT_GROUP_CRITICAL_SECTION %s: alloc extent end, begin critical section!\n", - g_instance.attr.attr_common.PGXCNodeName))); + SEGMENTTEST(EXTENT_GROUP_CRITICAL_SECTION, + (errmsg("EXTENT_GROUP_CRITICAL_SECTION %s: alloc extent end, begin critical section!\n", + g_instance.attr.attr_common.PGXCNodeName))); return blocknum; } @@ -797,12 +1058,21 @@ BlockNumber eg_alloc_extent(SegExtentGroup *seg, BlockNumber preassigned_block, /* * Free extent should be reentrant */ +/* + * 功能:用于取消位图页中的指定位设置,将其标记为未使用。 + * + * 参数列表: + * map_page:df_map_page_t*,位图页的指针 + * bit_id:uint16,要取消设置的位的ID + */ void eg_unset_bitmap_page(df_map_page_t *map_page, uint16 bit_id) { SegmentCheck(DF_MAP_NOT_FREE(map_page->bitmap, bit_id)); + // 取消设置位 DF_MAP_UNSET(map_page->bitmap, bit_id); map_page->free_bits++; + // 更新空闲位的起始位置 if (map_page->free_begin > bit_id) { map_page->free_begin = bit_id; } @@ -811,12 +1081,21 @@ void eg_unset_bitmap_page(df_map_page_t *map_page, uint16 bit_id) /* * Free an extent in the extent group. 'allocated_extents' will be updated. */ +/* + * 功能:释放分配的扩展块 + * + * 参数列表: + * seg:SegExtentGroup*,扩展组的指针 + * blocknum:BlockNumber,要释放的块的编号 + */ void eg_free_extent(SegExtentGroup *seg, BlockNumber blocknum) { + // 读取映射头缓冲区 seg->map_head_buffer = ReadBufferFast(seg->space, seg->rnode, seg->forknum, seg->map_head_entry, RBM_NORMAL); LockBuffer(seg->map_head_buffer, BUFFER_LOCK_EXCLUSIVE); df_map_head_t *map_head = (df_map_head_t *)PageGetContents(BufferGetPage(seg->map_head_buffer)); + // 定位要释放的块的位置 SpaceMapLocation location = eg_locate_map_by_pageid(seg, blocknum, map_head); if (MapLocationIsInvalid(location)) { ereport(PANIC, (errmsg("Free an already freed extent"))); @@ -828,6 +1107,7 @@ void eg_free_extent(SegExtentGroup *seg, BlockNumber blocknum) LockBuffer(map_buffer, BUFFER_LOCK_EXCLUSIVE); df_map_page_t *map_page = (df_map_page_t *)PageGetContents(BufferGetPage(map_buffer)); + // 取消位图页中的位设置,将其标记为未使用 eg_unset_bitmap_page(map_page, bit_id); map_head->allocated_extents--; -- 2.34.1 From a662088e7f16f0ca283447da616688eaedbfa74f Mon Sep 17 00:00:00 2001 From: nuoya <1204149038@qq.com> Date: Mon, 25 Sep 2023 15:33:32 +0800 Subject: [PATCH 16/50] Update inverse_ptr.cpp --- .../storage/smgr/segment/inverse_ptr.cpp | 74 +++++++++++++++++-- 1 file changed, 66 insertions(+), 8 deletions(-) diff --git a/src/gausskernel/storage/smgr/segment/inverse_ptr.cpp b/src/gausskernel/storage/smgr/segment/inverse_ptr.cpp index 4f68b5806..40dd5b089 100644 --- a/src/gausskernel/storage/smgr/segment/inverse_ptr.cpp +++ b/src/gausskernel/storage/smgr/segment/inverse_ptr.cpp @@ -32,7 +32,13 @@ static const char *ExtentUsageName[] = {"Not used", "Non-bucket table segment he /* must be last one */ "Invalid Usage Type"}; - +/* + * 功能:获取扩展块的InversePointer块位置 + * + * 参数列表: + * extent:BlockNumber,扩展块的编号 + * extent_size:uint32,扩展块的大小 + */ IpBlockLocation GetIpBlock(BlockNumber extent, uint32 extent_size) { /* Exclude MapHead */ @@ -43,7 +49,7 @@ IpBlockLocation GetIpBlock(BlockNumber extent, uint32 extent_size) uint32 group_id = extent / group_total_blocks; /* - * Group inverse pointer start, plus + * Group inverse pointer start, plus * 1. DF_MAP_HEAD_PAGE * 2. previous groups * 3. Map pages in this group @@ -56,16 +62,20 @@ IpBlockLocation GetIpBlock(BlockNumber extent, uint32 extent_size) SegmentCheck(extent_size > 0); uint32 extent_id = group_offset / extent_size; - IpBlockLocation result = { - .ipblock = group_ip_start + extent_id / EXTENTS_PER_IPBLOCK, - .offset = extent_id % EXTENTS_PER_IPBLOCK - }; + IpBlockLocation result = {.ipblock = group_ip_start + extent_id / EXTENTS_PER_IPBLOCK, + .offset = extent_id % EXTENTS_PER_IPBLOCK}; return result; } - +/* + * 功能:获取扩展块使用情况的名称 + * + * 参数列表: + * iptr:ExtentInversePointer,InversePointer块的指针 + */ const char *GetExtentUsageName(ExtentInversePointer iptr) { int usage = SPC_INVRSPTR_GET_USAGE(iptr); + // 如果使用情况大于或等于INVALID_EXTNT_USAGE,将其设置为INVALID_EXTNT_USAGE if (usage >= INVALID_EXTNT_USAGE) { usage = INVALID_EXTNT_USAGE; } @@ -74,32 +84,58 @@ const char *GetExtentUsageName(ExtentInversePointer iptr) } /* Inverse pointer read buffer */ +/* + * 功能:用于读取扩展组的InversePointer块的缓冲区,并可以选择是否在需要时进行扩展。 + * + * 参数列表: + * seg:SegExtentGroup指针,表示扩展组的信息 + * blocknum:BlockNumber,表示要读取的块号 + * extend:bool,表示是否在需要时进行扩展 + */ Buffer ip_readbuf(SegExtentGroup *seg, BlockNumber blocknum, bool extend) { #ifdef USE_ASSERT_CHECKING + // 检查块号是否小于数据文件的大小 BlockNumber df_size = eg_df_size(seg); SegmentCheck(blocknum < df_size); #endif + // 读取块的缓冲区 Buffer buf = ReadBufferFast(seg->space, seg->rnode, seg->forknum, blocknum, RBM_NORMAL); + // 如果页面是新的,则进行初始化 if (PageIsNew(BufferGetPage(buf))) { SegPageInit(BufferGetPage(buf), BLCKSZ); } return buf; } - +/* + * 功能:用于设置指定块号的InversePointer。它会读取InversePointer块的缓冲区, + * 将指定的InversePointer存储在合适的位置,并进行XLog记录以确保持久性。 + * + * 参数列表: + * seg:SegExtentGroup指针,表示扩展组的信息 + * extent:BlockNumber,表示要设置InversePointer的块号 + * iptr:ExtentInversePointer,表示要设置的InversePointer值 + * + * 注意:函数内部会使用GetIpBlock和ip_readbuf函数来定位和读取InversePointer块 + */ void SetInversePointer(SegExtentGroup *seg, BlockNumber extent, ExtentInversePointer iptr) { + // 使用GetIpBlock函数获取InversePointer块的位置 IpBlockLocation loc = GetIpBlock(extent, seg->extent_size); + // 使用ip_readbuf函数读取InversePointer块的缓冲区,并可以选择在需要时进行扩展 Buffer ipBuffer = ip_readbuf(seg, loc.ipblock, true); SegmentCheck(BufferIsValid(ipBuffer)); + // 锁定缓冲区以进行写操作 LockBuffer(ipBuffer, BUFFER_LOCK_EXCLUSIVE); + // 获取InversePointer块的内容并设置指定位置的值 ExtentInversePointer *eips = (ExtentInversePointer *)PageGetContents(BufferGetBlock(ipBuffer)); eips[loc.offset] = iptr; + // 注册XLog记录以确保持久性 XLogAtomicOpRegisterBuffer(ipBuffer, REGBUF_KEEP_DATA, SPCXLOG_SET_INVERSE_POINTER, XLOG_COMMIT_UNLOCK_RELEASE_BUFFER); XLogAtomicOpRegisterBufData((char *)&loc.offset, sizeof(uint32)); @@ -114,9 +150,26 @@ void SetInversePointer(SegExtentGroup *seg, BlockNumber extent, ExtentInversePoi * is a valid buffer containing the inverse pointer of the given extent. The caller is responsible * to release the buf after it has done scanning. */ +/* + * 功能:用于获取指定块号的InversePointer。它会根据传入的缓冲区指针(如果存在) + * 或者创建新的缓冲区来读取InversePointer,并返回所需的InversePointer值。 + * + * 参数列表: + * seg:SegExtentGroup指针,表示扩展组的信息 + * extent:BlockNumber,表示要获取InversePointer的块号 + * buf:Buffer指针的指针,用于传递或接收InversePointer块的缓冲区 + * 如果传入的缓冲区无效,函数会创建一个新的缓冲区 + * + * 注意: + * - 如果传入的缓冲区不为空且缓冲区与块号不匹配,旧缓冲区会被释放并置为无效。 + * - 函数内部会使用GetIpBlock和ip_readbuf函数来定位和读取InversePointer块。 + * - 函数会确保返回的InversePointer值有效。 + */ ExtentInversePointer GetInversePointer(SegExtentGroup *seg, BlockNumber extent, Buffer *buf) { + // 检查传入的缓冲区指针是否为空 SegmentCheck(buf != NULL); + // 使用GetIpBlock函数获取InversePointer块的位置 IpBlockLocation loc = GetIpBlock(extent, seg->extent_size); if (BufferIsValid(*buf)) { @@ -129,16 +182,21 @@ ExtentInversePointer GetInversePointer(SegExtentGroup *seg, BlockNumber extent, } } + // 如果传入的缓冲区无效,创建一个新的缓冲区并读取InversePointer块 if (BufferIsInvalid(*buf)) { *buf = ip_readbuf(seg, loc.ipblock, false); } + // 检查缓冲区是否有效 SegmentCheck(BufferIsValid(*buf)); + // 锁定缓冲区以进行读取操作,并获取InversePointer值 LockBuffer(*buf, BUFFER_LOCK_SHARE); ExtentInversePointer *eips = (ExtentInversePointer *)PageGetContents(BufferGetBlock(*buf)); ExtentInversePointer res = eips[loc.offset]; + // 解锁缓冲区 LockBuffer(*buf, BUFFER_LOCK_UNLOCK); + // 确保返回的InversePointer值有效 SegmentCheck(InversePointerIsValid(res)); return res; -- 2.34.1 From 84314ca86a7a332268beb96c508eb09b6df1f684 Mon Sep 17 00:00:00 2001 From: nuoya <1204149038@qq.com> Date: Mon, 25 Sep 2023 15:33:47 +0800 Subject: [PATCH 17/50] Update segbuffer.cpp --- .../storage/smgr/segment/segbuffer.cpp | 498 +++++++++++++++--- 1 file changed, 417 insertions(+), 81 deletions(-) diff --git a/src/gausskernel/storage/smgr/segment/segbuffer.cpp b/src/gausskernel/storage/smgr/segment/segbuffer.cpp index df84a6ba0..027c1a0f4 100644 --- a/src/gausskernel/storage/smgr/segment/segbuffer.cpp +++ b/src/gausskernel/storage/smgr/segment/segbuffer.cpp @@ -34,7 +34,7 @@ #include "tsan_annotation.h" #include "pgstat.h" -/* +/* * Segment buffer, used for segment meta data, e.g., segment head, space map head. We separate segment * meta data buffer and normal data buffer (in bufmgr.cpp) to avoid potential dead locks. */ @@ -54,89 +54,142 @@ static void SegTerminateBufferIO(BufferDesc *buf, bool clear_dirty, uint32 set_f extern PrivateRefCountEntry *GetPrivateRefCountEntry(Buffer buffer, bool create, bool do_move); extern void ForgetPrivateRefCountEntry(PrivateRefCountEntry *ref); - +/* + * 功能:中止分段缓冲区IO操作 + */ void AbortSegBufferIO(void) { + // 检查是否存在正在进行的IO操作 if (InProgressBuf != NULL) { + // 获取正在进行IO操作的锁 LWLockAcquire(InProgressBuf->io_in_progress_lock, LW_EXCLUSIVE); + // 调用SegTerminateBufferIO函数终止IO操作,将IO错误标志设置为BM_IO_ERROR SegTerminateBufferIO(InProgressBuf, false, BM_IO_ERROR); } } - +/* + * 功能:等待缓冲区的IO操作完成 + * + * 参数列表: + * buf:BufferDesc 结构,表示待等待IO操作完成的缓冲区 + * + * 注意:该函数不会主动终止缓冲区的IO操作,仅用于等待IO操作完成。 + */ static void WaitIO(BufferDesc *buf) { while (true) { uint32 buf_state; + // 获取缓冲区的状态 buf_state = LockBufHdr(buf); + // 释放对缓冲区状态的锁定 UnlockBufHdr(buf, buf_state); + // 检查缓冲区是否仍在进行IO操作 if (!(buf_state & BM_IO_IN_PROGRESS)) { + // 如果IO操作已完成,则退出循环 break; } + // 获取IO操作的锁以等待IO完成 LWLockAcquire(buf->io_in_progress_lock, LW_SHARED); + // 释放IO操作的锁 LWLockRelease(buf->io_in_progress_lock); } } - +/* + * 功能:开始缓冲区的IO操作 + * + * 参数列表: + * buf:BufferDesc 结构,表示待进行IO操作的缓冲区 + * forInput:bool 值,表示是否是输入操作(读取) + * + * 注意:函数调用者应该在适当的时候释放IO操作锁和修改缓冲区状态。 + */ static bool SegStartBufferIO(BufferDesc *buf, bool forInput) { uint32 buf_state; + // 检查全局变量,确保没有其他缓冲区的IO操作正在进行 SegmentCheck(!InProgressBuf); while (true) { + // 获取缓冲区的IO操作锁(独占模式) LWLockAcquire(buf->io_in_progress_lock, LW_EXCLUSIVE); + // 获取缓冲区的状态 buf_state = LockBufHdr(buf); if (!(buf_state & BM_IO_IN_PROGRESS)) { + // 如果缓冲区的IO操作尚未开始,则退出循环 break; } + // 释放对缓冲区状态的锁定 UnlockBufHdr(buf, buf_state); + // 释放IO操作锁 LWLockRelease(buf->io_in_progress_lock); + // 等待其他IO操作完成 WaitIO(buf); } if (forInput ? (buf_state & BM_VALID) : !(buf_state & BM_DIRTY)) { /* IO finished */ UnlockBufHdr(buf, buf_state); + // 释放IO操作锁 LWLockRelease(buf->io_in_progress_lock); return false; } + // 设置缓冲区的状态标志,表示IO操作已开始 buf_state |= BM_IO_IN_PROGRESS; UnlockBufHdr(buf, buf_state); + // 设置全局变量,指向正在进行IO操作的缓冲区 InProgressBuf = buf; isForInput = forInput; return true; } - +/* + * 功能:终止缓冲区的IO操作 + * + * 参数列表: + * buf:BufferDesc 结构,表示待终止IO操作的缓冲区 + * clear_dirty:bool 值,表示是否清除脏标志 + * set_flag_bits:uint32 值,表示要设置的标志位 + * + * 注意:函数调用者应该在适当的时候释放IO操作锁和修改缓冲区状态。 + */ static void SegTerminateBufferIO(BufferDesc *buf, bool clear_dirty, uint32 set_flag_bits) { + // 检查传入的缓冲区是否与全局变量 InProgressBuf 匹配 SegmentCheck(buf == InProgressBuf); + // 获取缓冲区的状态 uint32 buf_state = LockBufHdr(buf); + // 检查缓冲区的IO操作是否已开始 SegmentCheck(buf_state & BM_IO_IN_PROGRESS); + // 清除 BM_IO_IN_PROGRESS 和 BM_IO_ERROR 标志位 buf_state &= ~(BM_IO_IN_PROGRESS | BM_IO_ERROR); if (clear_dirty) { if (ENABLE_INCRE_CKPT) { + // 检查是否需要从脏页队列中移除该缓冲区 if (!XLogRecPtrIsInvalid(pg_atomic_read_u64(&buf->rec_lsn))) { remove_dirty_page_from_queue(buf); } else { + // 如果缓冲区脏标志被设置但不在脏页队列中,抛出 PANIC 错误 ereport(PANIC, (errmodule(MOD_INCRE_CKPT), errcode(ERRCODE_INVALID_BUFFER), (errmsg("buffer is dirty but not in dirty page queue in TerminateBufferIO_common")))); } + // 检查 BM_JUST_DIRTIED 标志位,如果设置了,表示不需要检查点 if ((buf_state & BM_JUST_DIRTIED)) { buf_state &= ~BM_CHECKPOINT_NEEDED; + // 将缓冲区添加到待刷新队列中 if (!push_pending_flush_queue(BufferDescriptorGetBuffer(buf))) { ereport(PANIC, (errmodule(MOD_INCRE_CKPT), errcode(ERRCODE_INVALID_BUFFER), (errmsg("TerminateBufferIO_common, dirty page queue is full when trying to " @@ -145,51 +198,76 @@ static void SegTerminateBufferIO(BufferDesc *buf, bool clear_dirty, uint32 set_f } } + // 如果 BM_JUST_DIRTIED 没有设置,清除 BM_DIRTY 和 BM_CHECKPOINT_NEEDED 标志位 if (!(buf_state & BM_JUST_DIRTIED)) { buf_state &= ~(BM_DIRTY | BM_CHECKPOINT_NEEDED); } } + // 设置指定的标志位 buf_state |= set_flag_bits; + // 释放缓冲区状态锁 UnlockBufHdr(buf, buf_state); + // 清除全局变量 InProgressBuf,表示IO操作已结束 InProgressBuf = NULL; + // 释放IO操作锁 LWLockRelease(buf->io_in_progress_lock); } - +/* + * 功能:用于等待缓冲区头部解锁 + * + * 参数列表: + * buf:BufferDesc 结构,表示待等待的缓冲区头部 + */ static uint32 SegWaitBufHdrUnlocked(BufferDesc *buf) { #ifndef ENABLE_THREAD_CHECK + // 初始化自旋等待状态,用于记录自旋等待的进度 SpinDelayStatus delayStatus = init_spin_delay(buf); #endif uint32 buf_state; + // 读取缓冲区头部的状态 buf_state = pg_atomic_read_u32(&buf->state); + // 循环检查缓冲区头部的状态,直到解锁 while (buf_state & BM_LOCKED) { #ifndef ENABLE_THREAD_CHECK + // 执行自旋等待 perform_spin_delay(&delayStatus); #endif + // 重新读取缓冲区头部的状态 buf_state = pg_atomic_read_u32(&buf->state); } #ifndef ENABLE_THREAD_CHECK + // 结束自旋等待 finish_spin_delay(&delayStatus); #endif /* ENABLE_THREAD_CHECK only, acqurie semantic */ + // 使用 TsAnnotateHappensAfter 注释确保获取语义 TsAnnotateHappensAfter(&buf->state); + // 返回最终的缓冲区头部状态 return buf_state; } - +/* + * 功能:增加缓冲区的引用计数 + * + * 参数列表: + * buf:BufferDesc 结构,表示待增加引用计数的缓冲区 + */ bool SegPinBuffer(BufferDesc *buf) { + // 打印调试信息 ereport(DEBUG5, (errmodule(MOD_SEGMENT_PAGE), errmsg("[SegPinBuffer] (%u %u %u %d) %d %u ", buf->tag.rnode.spcNode, buf->tag.rnode.dbNode, buf->tag.rnode.relNode, buf->tag.rnode.bucketNode, buf->tag.forkNum, buf->tag.blockNum))); bool result; - PrivateRefCountEntry * ref = GetPrivateRefCountEntry(BufferDescriptorGetBuffer(buf), true, true); + // 获取缓冲区的私有引用计数项 + PrivateRefCountEntry *ref = GetPrivateRefCountEntry(BufferDescriptorGetBuffer(buf), true, true); SegmentCheck(ref != NULL); if (ref->refcount == 0) { @@ -198,114 +276,194 @@ bool SegPinBuffer(BufferDesc *buf) for (;;) { if (old_buf_state & BM_LOCKED) { + // 如果缓冲区已被锁定,等待直到锁定被释放 old_buf_state = SegWaitBufHdrUnlocked(buf); } buf_state = old_buf_state; + // 增加引用计数 buf_state += BUF_REFCOUNT_ONE; + // 使用原子操作比较并交换来设置新的缓冲区状态 if (pg_atomic_compare_exchange_u32(&buf->state, &old_buf_state, buf_state)) { + // 设置结果为缓冲区是否有效 result = old_buf_state & BM_VALID; break; } } } else { + // 如果引用计数不为零,返回 true result = true; } + // 增加私有引用计数项的引用计数 ref->refcount++; + // 扩展当前资源所有者的缓冲区列表并记录缓冲区 ResourceOwnerEnlargeBuffers(t_thrd.utils_cxt.CurrentResourceOwner); ResourceOwnerRememberBuffer(t_thrd.utils_cxt.CurrentResourceOwner, BufferDescriptorGetBuffer(buf)); return result; } - +/* + * 功能:锁定并引用缓冲区,这是一个重要的操作,用于 + * 保证在访问缓冲区时的线程安全性。 + * + * 参数列表: + * buf:BufferDesc 结构,表示待锁定和引用的缓冲区 + * tag:BufferTag 结构,表示缓冲区的标签信息 + * + */ static bool SegPinBufferLocked(BufferDesc *buf, const BufferTag *tag) { ereport(DEBUG5, (errmodule(MOD_SEGMENT_PAGE), errmsg("[SegPinBufferLocked] (%u %u %u %d) %d %u ", tag->rnode.spcNode, tag->rnode.dbNode, tag->rnode.relNode, tag->rnode.bucketNode, tag->forkNum, tag->blockNum))); + // 确保缓冲区已经被锁定 SegmentCheck(BufHdrLocked(buf)); - PrivateRefCountEntry * ref = GetPrivateRefCountEntry(BufferDescriptorGetBuffer(buf), true, true); + // 获取缓冲区的引用计数结构 + PrivateRefCountEntry *ref = GetPrivateRefCountEntry(BufferDescriptorGetBuffer(buf), true, true); + // 确保引用计数结构有效 SegmentCheck(ref != NULL); + // 读取缓冲区的状态 uint32 buf_state = pg_atomic_read_u32(&buf->state); + // 如果缓冲区的引用计数为0,增加引用计数 if (ref->refcount == 0) { buf_state += BUF_REFCOUNT_ONE; } + // 解锁缓冲区,并设置新的状态 UnlockBufHdr(buf, buf_state); + // 增加引用计数 ref->refcount++; + // 扩展当前资源所有者的缓冲区列表 ResourceOwnerEnlargeBuffers(t_thrd.utils_cxt.CurrentResourceOwner); + // 记录缓冲区到当前资源所有者 ResourceOwnerRememberBuffer(t_thrd.utils_cxt.CurrentResourceOwner, BufferDescriptorGetBuffer(buf)); + // 返回缓冲区状态是否有效 return buf_state & BM_VALID; } - +/* + * 功能:解除缓冲区引用,用于确保在多线程环境中正确处理缓冲区的引用和状态。 + * + * 参数列表: + * buf:BufferDesc 结构,表示待解除引用的缓冲区 + * + * 注意: + * 如果缓冲区的引用计数归零,该函数会解锁缓冲区,但不会从内存中释放缓冲区。 + */ void SegUnpinBuffer(BufferDesc *buf) { ereport(DEBUG5, (errmodule(MOD_SEGMENT_PAGE), errmsg("[SegUnpinBuffer] (%u %u %u %d) %d %u ", buf->tag.rnode.spcNode, buf->tag.rnode.dbNode, buf->tag.rnode.relNode, buf->tag.rnode.bucketNode, buf->tag.forkNum, buf->tag.blockNum))); - PrivateRefCountEntry * ref = GetPrivateRefCountEntry(BufferDescriptorGetBuffer(buf), true, true); + // 获取缓冲区的引用计数结构 + PrivateRefCountEntry *ref = GetPrivateRefCountEntry(BufferDescriptorGetBuffer(buf), true, true); + // 确保引用计数结构有效 SegmentCheck(ref != NULL); + // 从当前资源所有者的缓冲区列表中移除缓冲区 ResourceOwnerForgetBuffer(t_thrd.utils_cxt.CurrentResourceOwner, BufferDescriptorGetBuffer(buf)); + // 减少引用计数 ref->refcount--; + // 如果引用计数归零 if (ref->refcount == 0) { uint32 buf_state; uint32 old_buf_state; + // 读取缓冲区的状态 old_buf_state = pg_atomic_read_u32(&buf->state); for (;;) { + // 如果缓冲区被锁定,等待其解锁 if (old_buf_state & BM_LOCKED) { old_buf_state = SegWaitBufHdrUnlocked(buf); } buf_state = old_buf_state; + // 确保引用计数大于0 SegmentCheck(BUF_STATE_GET_REFCOUNT(buf_state) > 0); + // 减少引用计数 buf_state -= BUF_REFCOUNT_ONE; + // 使用原子操作更新缓冲区状态 if (pg_atomic_compare_exchange_u32(&buf->state, &old_buf_state, buf_state)) { break; } } - + + // 确保缓冲区没有等待的引用计数 SegmentCheck(!(buf_state & BM_PIN_COUNT_WAITER)); + // 重置引用计数 ref->refcount = 0; + // 释放引用计数结构 ForgetPrivateRefCountEntry(ref); } } - +/* + * 功能:释放缓冲区 + * + * 参数列表: + * buffer:Buffer 数据类型,表示待释放的缓冲区 + * + * 注意: + * 该函数用于释放一个缓冲区,确保不再需要访问该缓冲区的内容,以免内存泄漏。 + */ void SegReleaseBuffer(Buffer buffer) { + // 检查缓冲区是否是有效的段缓冲区 SegmentCheck(IsSegmentBufferID(buffer - 1)); + // 调用 SegUnpinBuffer 函数来解除对缓冲区的引用 SegUnpinBuffer(GetBufferDescriptor(buffer - 1)); } - -void SegUnlockReleaseBuffer(Buffer buffer) +/* + * 功能:解锁并释放缓冲区 + * + * 参数列表: + * buffer:Buffer 数据类型,表示待解锁并释放的缓冲区 + * + * 注意: + * 该函数适用于需要解锁并释放缓冲区的情况,确保不再需要访问该缓冲区的内容, + * 以免内存泄漏。 + */ +void SegUnlockReleaseBuffer(Buffer buffer) { - LockBuffer(buffer, BUFFER_LOCK_UNLOCK); + // 通过 LockBuffer 函数将缓冲区解锁 + LockBuffer(buffer, BUFFER_LOCK_UNLOCK); + // 调用 SegUnpinBuffer 函数来解除对缓冲区的引用 SegUnpinBuffer(GetBufferDescriptor(buffer - 1)); } - +/* + * 功能:将缓冲区标记为脏页脏页会在以后的时刻被同步到磁盘上,以保证数据的持久性。 + * + * 参数列表: + * buf:Buffer 数据类型,表示待标记为脏页的缓冲区 + * + * 注意: + * 1. 该函数假定缓冲区的内容锁(content_lock)已经由调用者获取,因此没有进行锁的获取操作。 + * 2. 函数会检查脏页是否已经在脏页队列中,如果不在,则将其添加到脏页队列中等待同步。 + */ void SegMarkBufferDirty(Buffer buf) { uint32 old_buf_state, buf_state; BufferDesc *bufHdr; + // 获取缓冲区描述符 bufHdr = GetBufferDescriptor(buf - 1); + // 进行一系列断言检查,确保缓冲区的合法性 SegmentCheck(IsSegmentBufferID(bufHdr->buf_id)); /* unfortunately we can't check if the lock is held exclusively */ SegmentCheck(LWLockHeldByMe(bufHdr->content_lock)); + // 获取缓冲区的旧状态 old_buf_state = LockBufHdr(bufHdr); + // 设置缓冲区的新状态,标记为脏页(BM_DIRTY)和刚刚脏化(BM_JUST_DIRTIED) buf_state = old_buf_state | (BM_DIRTY | BM_JUST_DIRTIED); /* @@ -320,19 +478,33 @@ void SegMarkBufferDirty(Buffer buf) break; } + // 如果脏页队列不满,并且成功将脏页推送到队列中,就退出循环 if (!is_dirty_page_queue_full(bufHdr) && push_pending_flush_queue(buf)) { break; } + // 解锁缓冲区描述符,让其他线程有机会获取锁 UnlockBufHdr(bufHdr, old_buf_state); + // 短暂休眠一段时间,然后再次尝试获取锁 pg_usleep(TEN_MICROSECOND); old_buf_state = LockBufHdr(bufHdr); } } + // 解锁缓冲区描述符,并设置新状态 UnlockBufHdr(bufHdr, buf_state); } - +/* + * 功能:将缓冲区的数据刷新到磁盘上,以确保数据的持久性。 + * + * 参数列表: + * buf:BufferDesc 数据类型,表示待刷新到磁盘的缓冲区 + * reln:SMgrRelation 数据类型,表示关联的存储管理器关系,如果为NULL,则从缓冲区的标签中获取 + * + * 注意: + * 1. 如果在刷新缓冲区时出现错误,函数会进行错误上下文回调和错误处理。 + * 2. 函数会检查是否有其他线程已经刷新了该缓冲区,如果是,则不再重复刷新。 + */ void SegFlushBuffer(BufferDesc *buf, SMgrRelation reln) { if (!SegStartBufferIO(buf, false)) { @@ -354,7 +526,7 @@ void SegFlushBuffer(BufferDesc *buf, SMgrRelation reln) char *buf_to_write = NULL; RedoBufferInfo buffer_info; - + SegSpace *spc; if (reln == NULL || reln->seg_space == NULL) { spc = spc_open(buf->tag.rnode.spcNode, buf->tag.rnode.dbNode, false); @@ -368,27 +540,33 @@ void SegFlushBuffer(BufferDesc *buf, SMgrRelation reln) UnlockBufHdr(buf, buf_state); + // 获取缓冲区的刷新信息 GetFlushBufferInfo(buf, &buffer_info, &buf_state, WITH_NORMAL_CACHE); /* Must be segment-page metadata page */ SegmentCheck(PageIsSegmentVersion(buffer_info.pageinfo.page) || PageIsNew(buffer_info.pageinfo.page)); + // 如果启用了FORCE_FINISH_ENABLED,更新最大的页面刷新LSN if (FORCE_FINISH_ENABLED) { update_max_page_flush_lsn(buffer_info.lsn, t_thrd.proc_cxt.MyProcPid, false); } + // 等待XLog刷新,确保数据写入磁盘之前已经提交到WAL日志 XLogWaitFlush(buffer_info.lsn); /* page data encrypt */ buf_to_write = PageDataEncryptForBuffer(buffer_info.pageinfo.page, buf, true); + // 如果数据指针发生变化,需要重新计算校验和 if (unlikely(buf_to_write != (char *)buffer_info.pageinfo.page)) { PageSetChecksumInplace((Page)buf_to_write, buffer_info.blockinfo.blkno); } else { buf_to_write = PageSetChecksumCopy((Page)buf_to_write, buffer_info.blockinfo.blkno, true); } + // 将数据写入磁盘 seg_physical_write(spc, buf->tag.rnode, buf->tag.forkNum, buf->tag.blockNum, (char *)buf_to_write, false); + // 完成缓冲区的IO操作 SegTerminateBufferIO(buf, true, 0); /* Pop the error context stack, if it was set before */ @@ -396,41 +574,69 @@ void SegFlushBuffer(BufferDesc *buf, SMgrRelation reln) t_thrd.log_cxt.error_context_stack = errcontext.previous; } } - +/* + * 功能:报告无效的页面 + * + * 参数列表: + * key:RepairBlockKey 数据类型,表示无效页面的关键信息,包括文件节点、分支号、块号等。 + * + * 注意: + * 1. 修复无效页面的过程中,会记录错误信息,并在修复完成后输出警告信息。 + * 2. 如果不满足页修复的条件,函数将触发一个错误,报告无效页面。 + */ void ReportInvalidPage(RepairBlockKey key) { /* record bad page, wait the pagerepair thread repair the page */ - if (CheckVerionSupportRepair() && (AmStartupProcess() || AmPageRedoWorker()) && - IsPrimaryClusterStandbyDN() && g_instance.repair_cxt.support_repair) { + if (CheckVerionSupportRepair() && (AmStartupProcess() || AmPageRedoWorker()) && IsPrimaryClusterStandbyDN() && + g_instance.repair_cxt.support_repair) { + // 如果环境支持页修复,且当前进程是启动进程或页重做工作者,并且是主集群的备用数据节点,并且页修复功能被启用 XLogPhyBlock pblk_bak = {0}; - RedoPageRepairCallBack(key, pblk_bak); - log_invalid_page(key.relfilenode, key.forknum, key.blocknum, CRC_CHECK_ERROR, NULL); - ereport(WARNING, (errcode(ERRCODE_DATA_CORRUPTED), - errmsg("invalid page in block %u of relation %s", - key.blocknum, relpathperm(key.relfilenode, key.forknum)))); + RedoPageRepairCallBack(key, pblk_bak); // 调用页修复回调函数,进行修复操作 + log_invalid_page(key.relfilenode, key.forknum, key.blocknum, CRC_CHECK_ERROR, NULL); // 记录无效页面的信息 + ereport(WARNING, + (errcode(ERRCODE_DATA_CORRUPTED), errmsg("invalid page in block %u of relation %s", key.blocknum, + relpathperm(key.relfilenode, key.forknum)))); return; } - ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("invalid page in block %u of relation %s", - key.blocknum, relpathperm(key.relfilenode, key.forknum)))); + // 如果不满足页修复条件,报告无效页面的错误 + ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("invalid page in block %u of relation %s", key.blocknum, + relpathperm(key.relfilenode, key.forknum)))); return; } - +/* + * 功能:快速读取缓冲区的内容 + * + * 参数列表: + * spc:SegSpace 数据类型,表示段存储空间。 + * rnode:RelFileNode 数据类型,表示文件节点信息。 + * forkNum:ForkNumber 数据类型,表示分支号。 + * blockNum:BlockNumber 数据类型,表示要读取的块号。 + * mode:ReadBufferMode 数据类型,表示读取缓冲区的模式,包括零填充和锁定等。 + * + * 注意: + * 1. 如果块号不存在于缓冲池中,函数会根据模式进行初始化,包括零填充或读取存储介质中的数据。 + * 2. 如果读取到的块无效,函数会触发一个错误报告。 + */ Buffer ReadBufferFast(SegSpace *spc, RelFileNode rnode, ForkNumber forkNum, BlockNumber blockNum, ReadBufferMode mode) { bool found = false; + // 尝试在缓冲池中分配或获取指定块号的缓冲区描述符 BufferDesc *bufHdr = SegBufferAlloc(spc, rnode, forkNum, blockNum, &found); - if (!found) { + if (!found) { // 如果块号在缓冲池中不存在 SegmentCheck(!(pg_atomic_read_u32(&bufHdr->state) & BM_VALID)); char *bufBlock = (char *)BufHdrGetBlock(bufHdr); if (mode == RBM_ZERO_AND_LOCK || mode == RBM_ZERO_AND_CLEANUP_LOCK) { + // 零填充模式或零填充并锁定模式 errno_t er = memset_s((char *)bufBlock, BLCKSZ, 0, BLCKSZ); securec_check(er, "", ""); } else { + // 从存储介质中读取块的内容 seg_physical_read(spc, rnode, forkNum, blockNum, bufBlock); if (!PageIsVerified(bufBlock, blockNum)) { + // 验证读取的块是否有效,如果无效则报告错误 RepairBlockKey key; key.relfilenode = rnode; key.forknum = forkNum; @@ -439,58 +645,107 @@ Buffer ReadBufferFast(SegSpace *spc, RelFileNode rnode, ForkNumber forkNum, Bloc return InvalidBuffer; } if (!PageIsSegmentVersion(bufBlock) && !PageIsNew(bufBlock)) { + // 检查页面的版本是否与段存储版本兼容,如果不兼容则报告错误 ereport(PANIC, (errmsg("Read segment-page metadata buffer, block %u of relation %s, but page version is %d", blockNum, relpathperm(rnode, forkNum), PageGetPageLayoutVersion(bufBlock)))); } } - bufHdr->lsn_on_disk = PageGetLSN(bufBlock); + bufHdr->lsn_on_disk = PageGetLSN(bufBlock); // 记录块上的LSN #ifdef USE_ASSERT_CHECKING - bufHdr->lsn_dirty = InvalidXLogRecPtr; + bufHdr->lsn_dirty = InvalidXLogRecPtr; // 初始化LSN为无效值 #endif - SegTerminateBufferIO(bufHdr, false, BM_VALID); + SegTerminateBufferIO(bufHdr, false, BM_VALID); // 终止缓冲区的IO操作 } if (mode == RBM_ZERO_AND_LOCK || mode == RBM_ZERO_AND_CLEANUP_LOCK) { + // 如果模式要求锁定缓冲区,则进行锁定操作 LWLockAcquire(BufferDescriptorGetContentLock(bufHdr), LW_EXCLUSIVE); } - SegmentCheck(SegBufferIsPinned(bufHdr)); + SegmentCheck(SegBufferIsPinned(bufHdr)); // 确保缓冲区被锁定 return BufferDescriptorGetBuffer(bufHdr); } - +/* + * 功能:获取两个轻量级锁的互斥锁 + * + * 参数列表: + * new_partition_lock:新的轻量级锁的指针 + * old_partition_lock:旧的轻量级锁的指针 + * + * 注意: + * 1. 这个函数的目的是确保在获取锁时不会出现死锁,按照锁地址的大小顺序获取锁。 + * 2. 如果两个锁的地址相同,说明只有一个分区,只需获取一个锁。 + */ void LockTwoLWLock(LWLock *new_partition_lock, LWLock *old_partition_lock) { if (old_partition_lock < new_partition_lock) { + // 先获取较小的锁,再获取较大的锁 (void)LWLockAcquire(old_partition_lock, LW_EXCLUSIVE); (void)LWLockAcquire(new_partition_lock, LW_EXCLUSIVE); } else if (old_partition_lock > new_partition_lock) { + // 先获取较小的锁,再获取较大的锁 (void)LWLockAcquire(new_partition_lock, LW_EXCLUSIVE); (void)LWLockAcquire(old_partition_lock, LW_EXCLUSIVE); } else { /* only one partition, only one lock */ + // 如果两个锁的地址相同,说明只有一个分区,只需获取一个锁。 (void)LWLockAcquire(new_partition_lock, LW_EXCLUSIVE); } } +/* + * 功能:在哈希表中查找缓冲区描述符 + * + * 参数列表: + * buf_id:要查找的缓冲区的标识符。 + * new_partition_lock:新的轻量级锁的指针。 + * foundPtr:用于指示是否找到缓冲区描述符的指针。 -BufferDesc * FoundBufferInHashTable(int buf_id, LWLock *new_partition_lock, bool *foundPtr) + * 注意: + * 1. 这个函数用于查找和锁定缓冲区描述符,以便进行后续操作。 + * 2. 如果缓冲区描述符有效,则函数将释放一个轻量级锁,并将“foundPtr”设置为“true”。 + * 3. 如果缓冲区描述符无效,则函数会尝试启动缓冲区的IO操作,并将“foundPtr”设置为“false”。 + */ +BufferDesc *FoundBufferInHashTable(int buf_id, LWLock *new_partition_lock, bool *foundPtr) { + // 获取缓冲区描述符 BufferDesc *buf = GetBufferDescriptor(buf_id); + // 尝试锁定(Pin)缓冲区并判断其是否有效 bool valid = SegPinBuffer(buf); + // 释放新的轻量级锁 LWLockRelease(new_partition_lock); + // 设置“foundPtr”为true *foundPtr = true; + // 如果缓冲区无效 if (!valid) { + // 尝试启动缓冲区的IO操作 if (SegStartBufferIO(buf, true)) { + // 如果成功启动IO操作,则设置“foundPtr”为false *foundPtr = false; } } + // 返回缓冲区描述符 return buf; } +/* + * 功能:分配一个新的缓冲区并返回其缓冲区描述符 + * + * 参数列表: + * spc:段空间(SegSpace)指针,用于表示缓冲区所属的段空间。 + * rnode:文件节点(RelFileNode)。 + * forkNum:分叉号(ForkNumber)。 + * blockNum:块号(BlockNumber)。 + * foundPtr:用于指示是否找到缓冲区描述符的指针。 -BufferDesc *SegBufferAlloc(SegSpace *spc, RelFileNode rnode, ForkNumber forkNum, BlockNumber blockNum, - bool *foundPtr) + * 注意: + * 1. 这个函数首先尝试在哈希表中查找缓冲区描述符,如果找到,则返回该描述符。 + * 2. 如果没有找到缓冲区描述符,函数将分配一个新的缓冲区描述符,并标记为新缓冲区。 + * 3. 在尝试查找或分配缓冲区描述符时,函数会根据需要启动缓冲区的IO操作。 + * 4. 函数最终返回缓冲区描述符,并通过“foundPtr”指示是否找到描述符。 + */ +BufferDesc *SegBufferAlloc(SegSpace *spc, RelFileNode rnode, ForkNumber forkNum, BlockNumber blockNum, bool *foundPtr) { BufferDesc *buf; BufferTag new_tag, old_tag; @@ -501,29 +756,37 @@ BufferDesc *SegBufferAlloc(SegSpace *spc, RelFileNode rnode, ForkNumber forkNum, LWLock *old_partition_lock; bool old_flag_valid; + // 初始化新的缓冲区标签 INIT_BUFFERTAG(new_tag, rnode, forkNum, blockNum); + // 计算新缓冲区标签的哈希值并获取相应的分区锁 new_hash = BufTableHashCode(&new_tag); new_partition_lock = BufMappingPartitionLock(new_hash); + // 尝试以共享模式获取分区锁并查找缓冲区描述符 LWLockAcquire(new_partition_lock, LW_SHARED); int buf_id = BufTableLookup(&new_tag, new_hash); + // 如果找到缓冲区描述符,则返回它 if (buf_id >= 0) { return FoundBufferInHashTable(buf_id, new_partition_lock, foundPtr); } + // 标记未找到缓冲区描述符,并释放分区锁 *foundPtr = FALSE; LWLockRelease(new_partition_lock); for (;;) { + // 从策略中获取一个缓冲区描述符 buf = SegStrategyGetBuffer(&buf_state); SegmentCheck(BUF_STATE_GET_REFCOUNT(buf_state) == 0); old_flags = buf_state & BUF_FLAG_MASK; + // 锁定(Pin)新的缓冲区 SegPinBufferLocked(buf, &new_tag); + // 如果缓冲区标记为脏(BM_DIRTY),则执行相应操作 if (old_flags & BM_DIRTY) { /* backend should not flush dirty pages if working version less than DW_SUPPORT_NEW_SINGLE_FLUSH */ if (!backend_can_flush_dirty_page()) { @@ -548,6 +811,7 @@ BufferDesc *SegBufferAlloc(SegSpace *spc, RelFileNode rnode, ForkNumber forkNum, old_hash = BufTableHashCode(&old_tag); old_partition_lock = BufMappingPartitionLock(old_hash); + // 获取两个分区锁,如果标签有效则获取旧分区锁,否则只获取新分区锁 LockTwoLWLock(new_partition_lock, old_partition_lock); } else { /* if it wasn't valid, we need only the new partition */ @@ -557,9 +821,11 @@ BufferDesc *SegBufferAlloc(SegSpace *spc, RelFileNode rnode, ForkNumber forkNum, old_partition_lock = NULL; } + // 在哈希表中插入新标签,如果成功则返回缓冲区描述符 buf_id = BufTableInsert(&new_tag, new_hash, buf->buf_id); if (buf_id >= 0) { + // 释放缓冲区描述符并返回已找到的描述符 SegUnpinBuffer(buf); if (old_flag_valid && old_partition_lock != new_partition_lock) LWLockRelease(old_partition_lock); @@ -567,6 +833,7 @@ BufferDesc *SegBufferAlloc(SegSpace *spc, RelFileNode rnode, ForkNumber forkNum, return FoundBufferInHashTable(buf_id, new_partition_lock, foundPtr); } + // 如果缓冲区被引用,或者标记为脏(BM_DIRTY),则继续循环 buf_state = LockBufHdr(buf); old_flags = buf_state & BUF_FLAG_MASK; @@ -582,6 +849,7 @@ BufferDesc *SegBufferAlloc(SegSpace *spc, RelFileNode rnode, ForkNumber forkNum, SegUnpinBuffer(buf); } + // 更新缓冲区描述符的标签和状态 buf->tag = new_tag; buf_state &= ~(BM_VALID | BM_DIRTY | BM_JUST_DIRTIED | BM_CHECKPOINT_NEEDED | BM_IO_ERROR | BM_PERMANENT | BUF_USAGECOUNT_MASK); @@ -589,19 +857,30 @@ BufferDesc *SegBufferAlloc(SegSpace *spc, RelFileNode rnode, ForkNumber forkNum, UnlockBufHdr(buf, buf_state); if (old_flag_valid) { + // 从哈希表中删除旧标签 BufTableDelete(&old_tag, old_hash); if (old_partition_lock != new_partition_lock) { LWLockRelease(old_partition_lock); } } + // 释放新分区锁 LWLockRelease(new_partition_lock); + // 设置“foundPtr”为缓冲区是否需要启动IO操作的标志 *foundPtr = !SegStartBufferIO(buf, true); + // 返回缓冲区描述符 return buf; } static uint32 next_victim_buffer = 0; - +/* + * 功能:Clock算法的一个步骤,用于选择下一个被淘汰的缓冲区 + * + * 注意: + * 1. Clock算法是一种用于缓冲区淘汰策略的算法,通常用于管理缓冲区池中的缓冲区。 + * 2. 该函数通过原子操作从next_victim_buffer中获取一个递增的整数,表示下一个被淘汰的缓冲区。 + * 3. 如果计算得到的victim超过了SEGMENT_BUFFER_NUM(缓冲区池的大小),则将其重新映射到0以保持在有效范围内。 + */ static inline uint32 ClockSweepTick(void) { uint32 victim = pg_atomic_fetch_add_u32(&next_victim_buffer, 1); @@ -610,73 +889,102 @@ static inline uint32 ClockSweepTick(void) } return victim; } - -static BufferDesc* get_segbuf_from_candidate_list(uint32* buf_state) +/* + * 功能:从候选缓冲区列表中获取一个缓冲区描述符 + * + * 参数列表: + * buf_state:指向用于存储缓冲区状态的指针,通过此参数返回缓冲区的状态 + * + * 注意: + * 1. 该函数用于从候选缓冲区列表中获取一个可用的缓冲区描述符,通常用于高效的缓冲区管理。 + * 2. 如果ENABLE_INCRE_CKPT开启,并且有页写入进程在运行,函数会尝试从候选缓冲区列表中获取缓冲区。 + * 3. 函数会遍历候选缓冲区列表,并找到一个满足条件的可用缓冲区,返回其描述符。 + * 4. 候选缓冲区的条件包括:引用计数为0、不是元数据缓冲区、未标记为脏缓冲区。 + * 5. 如果没有找到满足条件的缓冲区,函数返回NULL。 + */ +static BufferDesc *get_segbuf_from_candidate_list(uint32 *buf_state) { - BufferDesc* buf = NULL; - uint32 local_buf_state; - int buf_id = 0; + BufferDesc *buf = NULL; // 缓冲区描述符 + uint32 local_buf_state; // 本地缓冲区状态 + int buf_id = 0; // 缓冲区标识符 + // 如果启用增量检查点并且有页写入进程正在运行 if (ENABLE_INCRE_CKPT && pg_atomic_read_u32(&g_instance.ckpt_cxt_ctl->current_page_writer_count) > 0) { int list_num = g_instance.ckpt_cxt_ctl->pgwr_procs.sub_num; - volatile PgBackendStatus* beentry = t_thrd.shemem_ptr_cxt.MyBEEntry; + volatile PgBackendStatus *beentry = t_thrd.shemem_ptr_cxt.MyBEEntry; int list_id = beentry->st_tid > 0 ? (beentry->st_tid % list_num) : (beentry->st_sessionid % list_num); + // 遍历页写入进程的子线程 for (int i = 0; i < list_num; i++) { /* the pagewriter sub thread store normal buffer pool, sub thread starts from 1 */ int thread_id = (list_id + i) % list_num + 1; Assert(thread_id > 0 && thread_id <= list_num); + // 从候选缓冲区列表中获取一个缓冲区标识符 while (seg_candidate_buf_pop(&buf_id, thread_id)) { - buf = GetBufferDescriptor(buf_id); - local_buf_state = LockBufHdr(buf); + buf = GetBufferDescriptor(buf_id); // 获取缓冲区描述符 + local_buf_state = LockBufHdr(buf); // 锁定缓冲区描述符的状态 SegmentCheck(buf_id >= SegmentBufferStartID); + // 如果该缓冲区已被标记为候选 if (g_instance.ckpt_cxt_ctl->candidate_free_map[buf_id]) { g_instance.ckpt_cxt_ctl->candidate_free_map[buf_id] = false; - bool available_buffer = BUF_STATE_GET_REFCOUNT(local_buf_state) == 0 - && !(local_buf_state & BM_IS_META) - && !(local_buf_state & BM_DIRTY); + bool available_buffer = BUF_STATE_GET_REFCOUNT(local_buf_state) == 0 && + !(local_buf_state & BM_IS_META) && !(local_buf_state & BM_DIRTY); if (available_buffer) { - *buf_state = local_buf_state; - return buf; + *buf_state = local_buf_state; // 存储缓冲区的状态 + return buf; // 返回满足条件的缓冲区描述符 } } - UnlockBufHdr(buf, local_buf_state); + UnlockBufHdr(buf, local_buf_state); // 解锁缓冲区描述符 } } - wakeup_pagewriter_thread(); + wakeup_pagewriter_thread(); // 唤醒页写入进程 } - return NULL; + return NULL; // 未找到满足条件的缓冲区,返回NULL } /* lock the buffer descriptor before return */ const int RETRY_COUNT = 3; -static BufferDesc *SegStrategyGetBuffer(uint32 *buf_state) +/* + * 功能:根据策略获取一个缓冲区描述符 + * + * 参数列表: + * buf_state:指向用于存储缓冲区状态的指针,通过此参数返回缓冲区的状态 + * + * 注意: + * 1. 该函数用于根据缓冲区策略获取一个可用的缓冲区描述符。 + * 2. 首先尝试从候选缓冲区列表中获取可用的缓冲区,如果获取成功,则返回。 + * 3. 如果候选缓冲区列表中没有可用的缓冲区,函数会循环尝试通过ClockSweepTick策略来获取可用缓冲区。 + * 4. 获取到的缓冲区必须满足引用计数为0的条件,否则会继续尝试直到成功或达到最大尝试次数。 + * 5. 如果获取了一个可用缓冲区,则将其状态存储在buf_state参数中,并返回该缓冲区的描述符。 + * 6. 如果没有可用缓冲区,则抛出错误提示“no unpinned buffers available”。 + */ +static BufferDesc *SegStrategyGetBuffer(uint32 *buf_state) { // todo: add free list - BufferDesc *buf = get_segbuf_from_candidate_list(buf_state); - int try_counter = SEGMENT_BUFFER_NUM * RETRY_COUNT; + BufferDesc *buf = get_segbuf_from_candidate_list(buf_state); // 尝试从候选缓冲区列表中获取缓冲区 + int try_counter = SEGMENT_BUFFER_NUM * RETRY_COUNT; // 重试计数器 if (buf != NULL) { (void)pg_atomic_fetch_add_u64(&g_instance.ckpt_cxt_ctl->seg_get_buf_num_candidate_list, 1); - return buf; + return buf; // 如果成功获取了候选缓冲区,则返回 } for (;;) { - int buf_id = BufferIdOfSegmentBuffer(ClockSweepTick()); - buf = GetBufferDescriptor(buf_id); + int buf_id = BufferIdOfSegmentBuffer(ClockSweepTick()); // 根据ClockSweep策略获取缓冲区ID + buf = GetBufferDescriptor(buf_id); // 获取缓冲区描述符 - uint32 state = LockBufHdr(buf); + uint32 state = LockBufHdr(buf); // 锁定缓冲区描述符的状态 - if (BUF_STATE_GET_REFCOUNT(state) == 0) { - *buf_state = state; + if (BUF_STATE_GET_REFCOUNT(state) == 0) { // 如果缓冲区引用计数为0 + *buf_state = state; // 存储缓冲区状态 (void)pg_atomic_fetch_add_u64(&g_instance.ckpt_cxt_ctl->seg_get_buf_num_clock_sweep, 1); - return buf; + return buf; // 返回可用缓冲区描述符 } else if (--try_counter == 0) { - UnlockBufHdr(buf, state); + UnlockBufHdr(buf, state); // 解锁缓冲区描述符 ereport(ERROR, (errcode(ERRCODE_INVALID_BUFFER), (errmsg("no unpinned buffers available")))); } - UnlockBufHdr(buf, state); + UnlockBufHdr(buf, state); // 解锁缓冲区描述符 ereport(DEBUG5, (errmodule(MOD_SEGMENT_PAGE), (errmsg("SegStrategyGetBuffer get a pinned buffer, %d, buffer tag <%u, %u, %u, %u>.%d.%u, state %u", @@ -684,7 +992,19 @@ static BufferDesc *SegStrategyGetBuffer(uint32 *buf_state) buf->tag.rnode.bucketNode, buf->tag.forkNum, buf->tag.blockNum, state)))); } } - +/* + * 功能:释放指定存储空间的元数据缓冲区 + * + * 参数列表: + * spcNode:要释放的存储空间的Oid + * dbNode:要释放的数据库的Oid + * + * 注意: + * 1. 该函数用于释放指定存储空间中的元数据缓冲区。 + * 2. 首先关闭所有段头文件。 + * 3. 然后遍历所有缓冲区描述符,查找并释放与指定存储空间和数据库匹配的元数据缓冲区。 + * 4. 对于匹配的缓冲区,如果其状态为“BM_DIRTY”和“BM_VALID”,则将其标记为无效,否则解锁缓冲区。 + */ void SegDropSpaceMetaBuffers(Oid spcNode, Oid dbNode) { int i; @@ -692,7 +1012,7 @@ void SegDropSpaceMetaBuffers(Oid spcNode, Oid dbNode) // Release segment head buffer smgrcloseall(); for (i = SegmentBufferStartID; i < TOTAL_BUFFER_NUM; i++) { - BufferDesc *buf_desc = GetBufferDescriptor(i); + BufferDesc *buf_desc = GetBufferDescriptor(i); // 获取缓冲区描述符 uint32 buf_state; /* * As in DropRelFileNodeBuffers, an unlocked precheck should be safe @@ -702,19 +1022,26 @@ void SegDropSpaceMetaBuffers(Oid spcNode, Oid dbNode) continue; } - buf_state = LockBufHdr(buf_desc); - if (buf_desc->tag.rnode.spcNode == spcNode && buf_desc->tag.rnode.dbNode == dbNode && - (buf_state & BM_DIRTY) && (buf_state & BM_VALID)) { + buf_state = LockBufHdr(buf_desc); // 锁定缓冲区描述符的状态 + if (buf_desc->tag.rnode.spcNode == spcNode && buf_desc->tag.rnode.dbNode == dbNode && (buf_state & BM_DIRTY) && + (buf_state & BM_VALID)) { InvalidateBuffer(buf_desc); /* releases spinlock */ } else { - UnlockBufHdr(buf_desc, buf_state); + UnlockBufHdr(buf_desc, buf_state); // 解锁缓冲区描述符 } } } - +/* + * 功能:刷新单个段缓冲区 + * + * 参数列表: + * buffer:要刷新的缓冲区标识符。 + * + */ void FlushOneSegmentBuffer(Buffer buffer) { BufferDesc *buf_desc = GetBufferDescriptor(buffer - 1); + // 检查缓冲区中的页面是否为段版本页面或新页面,否则引发 PANIC 错误 if (!PageIsSegmentVersion(BufferGetBlock(buffer)) && !PageIsNew(BufferGetBlock(buffer))) { ereport(PANIC, (errmsg("Flush segment-page metadata buffer, block %u of relation %s, but page version is %d", buf_desc->tag.blockNum, relpathperm(buf_desc->tag.rnode, buf_desc->tag.forkNum), @@ -724,37 +1051,46 @@ void FlushOneSegmentBuffer(Buffer buffer) if (dw_enabled() && pg_atomic_read_u32(&g_instance.ckpt_cxt_ctl->current_page_writer_count) > 0) { uint32 pos = 0; bool flush_old_file = false; + // 获取 Double Write 位置 pos = seg_dw_single_flush(buf_desc, &flush_old_file); t_thrd.proc->dw_pos = pos; t_thrd.proc->flush_new_dw = !flush_old_file; + // 调用 SegFlushBuffer 函数刷新缓冲区 SegFlushBuffer(buf_desc, NULL); if (flush_old_file) { g_instance.dw_single_cxt.recovery_buf.single_flush_state[pos] = true; } else { g_instance.dw_single_cxt.single_flush_state[pos] = true; } - t_thrd.proc->dw_pos = -1; + t_thrd.proc->dw_pos = -1; // 清除 Double Write 位置 } else { - SegFlushBuffer(buf_desc, NULL); + SegFlushBuffer(buf_desc, NULL); // 直接调用 SegFlushBuffer 函数刷新缓冲区 } } /* Flush a data buffer. If double write is on, it will invoke single-page-dw first. Caller should lock the buffer. */ +/* + * 功能:刷新单个缓冲区,包括 Double Write 操作 + * + * 参数列表: + * buf_desc:要刷新的缓冲区描述符。 + */ void FlushOneBufferIncludeDW(BufferDesc *buf_desc) { if (dw_enabled()) { bool flush_old_file = false; - uint32 pos = seg_dw_single_flush(buf_desc, &flush_old_file); + uint32 pos = seg_dw_single_flush(buf_desc, &flush_old_file); // 获取 Double Write 位置 t_thrd.proc->dw_pos = pos; t_thrd.proc->flush_new_dw = !flush_old_file; + // 调用 FlushBuffer 函数刷新缓冲区 FlushBuffer(buf_desc, NULL); if (flush_old_file) { g_instance.dw_single_cxt.recovery_buf.single_flush_state[pos] = true; } else { g_instance.dw_single_cxt.single_flush_state[pos] = true; } - t_thrd.proc->dw_pos = -1; + t_thrd.proc->dw_pos = -1; // 清除 Double Write 位置 } else { - FlushBuffer(buf_desc, NULL); + FlushBuffer(buf_desc, NULL); // 直接调用 FlushBuffer 函数刷新缓冲区 } } -- 2.34.1 From c8669968c16d865c01ea41d535ffbda2c8fbe8e2 Mon Sep 17 00:00:00 2001 From: nuoya <1204149038@qq.com> Date: Mon, 25 Sep 2023 15:34:03 +0800 Subject: [PATCH 18/50] Update segxlog.cpp --- .../storage/smgr/segment/segxlog.cpp | 574 +++++++++++++++--- 1 file changed, 475 insertions(+), 99 deletions(-) diff --git a/src/gausskernel/storage/smgr/segment/segxlog.cpp b/src/gausskernel/storage/smgr/segment/segxlog.cpp index 05954cd08..bf85b9e9a 100644 --- a/src/gausskernel/storage/smgr/segment/segxlog.cpp +++ b/src/gausskernel/storage/smgr/segment/segxlog.cpp @@ -35,22 +35,45 @@ /* * Truncate segment size */ +/* + * 功能:处理重做操作中的截断记录 + * + * 参数列表: + * record:XLog 重做记录的状态信息。 + * + * 注意: + * 1. 该函数用于处理重做操作中的截断记录。 + * 2. 首先,通过调用 XLogReadBufferForRedo 获取待处理的缓冲区信息及重做操作类型。 + * 3. 如果重做操作类型为 BLK_NEEDS_REDO,表示需要执行重做操作: + * a. 从 XLog 记录中获取截断后的块数 nblocks。 + * b. 获取缓冲区对应的页面,并将该页面的头部信息转换为 SegmentHead 结构。 + * c. 更新 seg_head->nblocks 为 nblocks,并设置页面的 LSN。 + * d. 标记缓冲区为脏。 + * 4. 最后,如果缓冲区有效,则解锁并释放缓冲区。 + */ static void redo_truncate(XLogReaderState *record) { RedoBufferInfo buffer_info; XLogRedoAction redo_action = XLogReadBufferForRedo(record, 0, &buffer_info); if (redo_action == BLK_NEEDS_REDO) { + // 获取被截断的块数 BlockNumber nblocks = *(BlockNumber *)XLogRecGetBlockData(record, 0, NULL); + // 获取与缓冲区相关联的页面 Page page = buffer_info.pageinfo.page; + // 将页面头信息视为SegmentHead结构 SegmentHead *seg_head = (SegmentHead *)PageGetContents(page); + // 使用截断块数更新seg_head->nblocks seg_head->nblocks = nblocks; + // 设置页面的LSN,以记录重做操作的完成 PageSetLSN(page, buffer_info.lsn); + // 标记缓冲区为脏页,表示页面内容已被修改 SegMarkBufferDirty(buffer_info.buf); } if (BufferIsValid(buffer_info.buf)) { + // 如果缓冲区有效,则解锁并释放它,确保内存管理的一致性 SegUnlockReleaseBuffer(buffer_info.buf); } } @@ -58,17 +81,36 @@ static void redo_truncate(XLogReaderState *record) /* * Move a list of buckets. */ +/* + * 功能该函数负责处理重做期间的移动桶记录。 + * + * 参数列表: + * buffer:要处理的缓冲区。 + * data:XLog重做记录中的数据。 + * + * 注意: + * 1. 该函数用于在重做操作期间处理移动桶记录。 + * 2. 函数从XLog记录中获取一组桶项的信息,然后更新相关缓冲区的映射块。 + * 3. 映射块用于跟踪桶的位置和头信息。 + * 4. 函数遍历每个桶项,将其标识(bktentry_id)映射到新的桶头信息(bktentry_header)。 + */ static void redo_move_buckets(Buffer buffer, const char *data) { + // 获取桶项数据的指针 xl_seg_bktentry_tag_t *bktentry; + // 从XLog记录中获取桶项数量 uint32 nentry; nentry = *(uint32 *)data; bktentry = (xl_seg_bktentry_tag_t *)(data + sizeof(uint32)); + // 获取与缓冲区关联的映射块 BktHeadMapBlock *mapblock = (BktHeadMapBlock *)PageGetContents(BufferGetPage(buffer)); + // 遍历每个桶项 for (uint32 i = 0; i < nentry; i++) { + // 获取桶项的标识 uint32 mapentry_id = bktentry[i].bktentry_id; + // 更新映射块中的头信息,将桶项标识映射到新的桶头信息 mapblock->head_block[mapentry_id] = bktentry[i].bktentry_header; } } @@ -76,60 +118,123 @@ static void redo_move_buckets(Buffer buffer, const char *data) /* * Record redis info for bucket relation. */ +/* + * 功能:该函数负责处理重做期间的添加Redis信息记录。 + * + * 参数列表: + * buffer:要处理的缓冲区 + * data:XLog重做记录中的数据,包含Redis信息 + * + * 注意: + * 1. 该函数用于在重做操作期间处理添加Redis信息记录。 + * 2. 函数从XLog记录中获取Redis信息并将其添加到相关缓冲区中。 + * 3. 如果Redis信息中的单词数量为0,函数将刷新与缓冲区关联的表(relation)的所有缓冲区。 + * 4. 最后,函数将Redis信息添加到缓冲区的头部。 + */ static void redo_bucket_add_redisinfo(Buffer buffer, const char *data) { - SegRedisInfo *redis_info = (SegRedisInfo *)data; + // 从XLog记录中获取Redis信息 + SegRedisInfo *redis_info = (SegRedisInfo *)data; - if (redis_info->nwords == 0) { + // 如果Redis信息中的单词数量为0 + if (redis_info->nwords == 0) { + // 获取缓冲区描述符 BufferDesc *buf_desc = GetBufferDescriptor(buffer - 1); + // 创建虚拟的关系缓存条目 Relation reln = CreateFakeRelcacheEntry(buf_desc->tag.rnode); + // 打开关系的存储管理器 RelationOpenSmgr(reln); + // 刷新与关系关联的所有缓冲区 flush_all_buffers(reln, InvalidOid); + // 释放虚拟的关系缓存条目 FreeFakeRelcacheEntry(reln); } + // 获取缓冲区的主头部 BktMainHead *head = (BktMainHead *)PageGetContents(BufferGetPage(buffer)); - head->redis_info = *redis_info; + // 将Redis信息添加到缓冲区的头部 + head->redis_info = *redis_info; } - +/* + * 功能:该函数负责处理重做期间的取消设置位图记录。 + * + * 参数列表: + * buffer:要处理的缓冲区 + * data:XLog重做记录中的数据,包含取消设置位图信息 + * + * 注意: + * 1. 该函数用于在重做操作期间处理取消设置位图记录。 + * 2. 函数从XLog记录中获取取消设置位图的位标识(bitid)以及相关位图信息。 + * 3. 函数检查MapPage的第一个页是否一致,如果不一致,将引发PANIC错误。 + * 4. 如果尝试取消设置一个已经空闲的位图,则函数将引发PANIC错误。 + * 5. 最后,函数更新MapPage的相关信息,并取消设置指定的位图。 + */ static void redo_unset_bitmap(Buffer buffer, const char *data) { + // 从XLog记录中获取位图标识(bitid) uint16 bitid = *(uint16 *)data; data += sizeof(uint16); + // 获取XLog记录中的MapPage信息 df_map_page_t *log_map_page = (df_map_page_t *)data; + // 获取缓冲区中的MapPage df_map_page_t *map_page = (df_map_page_t *)PageGetContents(BufferGetPage(buffer)); + // 检查MapPage的第一个页是否一致,如果不一致,引发PANIC错误 if (map_page->first_page != log_map_page->first_page) { ereport(PANIC, (errmsg("MapPage's first_page is not consistent, it's %u on disk but should be %u according to xlog", map_page->first_page, log_map_page->first_page))); } - + + // 如果尝试取消设置一个已经空闲的位图,引发PANIC错误 if (DF_MAP_FREE(map_page->bitmap, bitid)) { ereport(PANIC, (errmsg("Try to unset bitmap which is free: %u", bitid))); } + // 更新MapPage的相关信息 map_page->dirty_last = log_map_page->dirty_last; map_page->free_begin = log_map_page->free_begin; map_page->free_bits = log_map_page->free_bits; + // 取消设置指定的位图 DF_MAP_UNSET(map_page->bitmap, bitid); } - +/* + * 功能:处理重做操作中的设置位图记录 + * + * 参数列表: + * buftag:缓冲区标记,用于确定操作的缓冲区位置。 + * buffer:待处理的缓冲区。 + * data:从 XLog 记录中提取的数据。 + * + * 注意: + * 1. 该函数用于处理重做操作中的设置位图记录。 + * 2. 首先,从数据中获取位图的位号 bitid。 + * 3. 获取缓冲区对应的页面,将其转换为 df_map_page_t 结构。 + * 4. 检查页面的 first_page 是否与 XLog 记录中的 log_map_page->first_page 一致,如果不一致,报告 PANIC 错误。 + * 5. 检查指定位是否已被设置,如果已被设置,报告 PANIC 错误。 + * 6. 将页面的 dirty_last、free_begin、free_bits 更新为 log_map_page 中的对应值,并设置指定位。 + * 7. 根据位图的 bitid 计算出对应数据文件的块号 blkno。 + * 8. 根据块号计算出目标块号 target,并使用 spc_extend_file 扩展数据文件到目标块号。 + */ static void redo_set_bitmap(RedoBufferTag buftag, Buffer buffer, const char *data) { + // 从 XLog 记录的数据中获取位图的位号 bitid uint16 bitid = *(uint16 *)data; data += sizeof(uint16); + // 获取 log_map_page 和当前缓冲区的 map_page df_map_page_t *log_map_page = (df_map_page_t *)data; df_map_page_t *map_page = (df_map_page_t *)PageGetContents(BufferGetPage(buffer)); + // 检查页面的 first_page 是否一致,不一致报告 PANIC 错误 if (map_page->first_page != log_map_page->first_page) { ereport(PANIC, (errmsg("MapPage's first_page is not consistent, it's %u on disk but should be %u according to xlog", map_page->first_page, log_map_page->first_page), errhint("segment-page may have bug"))); } + // 检查指定位是否已被设置,如果已被设置,报告 PANIC 错误 if (DF_MAP_NOT_FREE(map_page->bitmap, bitid)) { ereport(PANIC, (errmsg("Try to set bitmap which is not free: %u", bitid), errhint("segment-page may have bug"))); @@ -140,39 +245,92 @@ static void redo_set_bitmap(RedoBufferTag buftag, Buffer buffer, const char *dat DF_MAP_SET(map_page->bitmap, bitid); /* Extend data file if necessary. First get the extent start */ + // 计算数据文件的块号 blkno,以及目标块号 target BlockNumber blkno = map_page->first_page + EXTENT_TYPE_TO_SIZE(buftag.rnode.relNode) * bitid; /* Then plus one extent length */ BlockNumber target = blkno + EXTENT_TYPE_TO_SIZE(buftag.rnode.relNode); + // 打开 SegSpace 并调用 spc_extend_file 扩展数据文件到目标块号 SegSpace *spc = spc_open(buftag.rnode.spcNode, buftag.rnode.dbNode, false); SegmentCheck(spc != NULL); spc_extend_file(spc, buftag.rnode.relNode, buftag.forknum, target); } - +/* + * 功能:处理重做操作中的映射头分配扩展记录 + * + * 参数列表: + * buffer:待处理的缓冲区。 + * data:从 XLog 记录中提取的数据。 + * + * 注意: + * 1. 该函数用于处理重做操作中的映射头分配扩展记录。 + * 2. 从 XLog 记录数据中获取 XLogDataSpaceAllocateExtent 结构,包含分配扩展所需的信息。 + * 3. 获取缓冲区对应的页面,并将其转换为 df_map_head_t 结构。 + * 4. 更新映射头的 allocated_extents、free_group、groups[free_group].free_page 和 high_water_mark 字段 + * 以反映 XLog 记录中的数据。 + */ static void redo_maphead_allocated_extents(Buffer buffer, const char *data) { + // 从 XLog 记录数据中获取 XLogDataSpaceAllocateExtent 结构 XLogDataSpaceAllocateExtent *xlog_data = (XLogDataSpaceAllocateExtent *)data; + // 获取缓冲区对应的页面,并将其转换为 df_map_head_t 结构 df_map_head_t *map_head = (df_map_head_t *)PageGetContents(BufferGetPage(buffer)); + // 更新映射头的 allocated_extents、free_group、groups[free_group].free_page 和 high_water_mark 字段 map_head->allocated_extents = xlog_data->allocated_extents; map_head->free_group = xlog_data->free_group; - map_head->groups[xlog_data->free_group].free_page = xlog_data->free_page; + map_head->groups[xlog_data->free_group].free_page = xlog_data->free_page; map_head->high_water_mark = xlog_data->hwm; } - +/* + * 功能:处理重做操作中的初始化段头记录 + * + * 参数列表: + * buffer:待处理的缓冲区。 + * data:从 XLog 记录中提取的数据。 + * + * 注意: + * 1. 该函数用于处理重做操作中的初始化段头记录。 + * 2. 获取缓冲区描述符,对应于参数 buffer 的编号减一。 + * 3. 使用 SegmentCheck 确保缓冲区的关联关系,即 rnode.relNode 必须等于 SEGMENT_HEAD_EXTENT_SIZE。 + * 4. 从 XLog 记录中获取 LSN,并使用 eg_init_segment_head_buffer_content 初始化缓冲区的内容。 + */ static void redo_init_seghead(Buffer buffer, const char *data) { + // 获取缓冲区描述符,对应于参数 buffer 的编号减一 BufferDesc *buf_desc = GetBufferDescriptor(buffer - 1); + // 使用 SegmentCheck 确保缓冲区的关联关系 SegmentCheck(buf_desc->tag.rnode.relNode == EXTENT_SIZE_TO_TYPE(SEGMENT_HEAD_EXTENT_SIZE)); + // 从 XLog 记录中获取 LSN XLogRecPtr lsn = *(XLogRecPtr *)(data); + // 使用 eg_init_segment_head_buffer_content 初始化缓冲区的内容 eg_init_segment_head_buffer_content(buffer, buf_desc->tag.blockNum, lsn); } - +/* + * 功能:处理重做操作中的更新段头记录 + * + * 参数列表: + * buffer:待处理的缓冲区 + * data:从 XLog 记录中提取的数据 + * + * 注意: + * 1. 该函数用于处理重做操作中的更新段头记录。 + * 2. 从 XLog 记录中获取重做数据 XLogDataUpdateSegmentHead。 + * 3. 获取缓冲区对应的段头信息。 + * 4. 检查 xlog_data 中的 nblocks 是否与段头中的 nblocks 一致,如果不一致,报错。 + * 5. 如果 xlog_data 中的 level0_slot 不小于0,将 level0_slot 位置的值更新为 xlog_data 中的 level0_value。 + * 6. 如果 xlog_data 中的 level1_slot 不小于0,将 level1_slot 位置的值更新为 xlog_data 中的 level1_value。 + * 7. 更新段头的 nextents、total_blocks 属性。 + * 8. 如果 total_blocks 为 0,表示所有 extents 都已释放,将段头刷新到磁盘上。 + */ static void redo_update_seghead(Buffer buffer, const char *data) { + // 从 XLog 记录中获取重做数据 XLogDataUpdateSegmentHead XLogDataUpdateSegmentHead *xlog_data = (XLogDataUpdateSegmentHead *)data; + // 获取缓冲区对应的段头信息 SegmentHead *head = (SegmentHead *)PageGetContents(BufferGetPage(buffer)); + // 检查 xlog_data 中的 nblocks 是否与段头中的 nblocks 一致,如果不一致,报错 if (xlog_data->nblocks != head->nblocks) { ereport(PANIC, (errmsg("redo update seghead, but target head's nblocks is %u, but should be %u according to xlog", @@ -180,174 +338,297 @@ static void redo_update_seghead(Buffer buffer, const char *data) errhint("segment-page may have bug"))); } + // 如果 xlog_data 中的 level0_slot 不小于0,将 level0_slot 位置的值更新为 xlog_data 中的 level0_value if (xlog_data->level0_slot >= 0) { head->level0_slots[xlog_data->level0_slot] = xlog_data->level0_value; } + // 如果 xlog_data 中的 level1_slot 不小于0,将 level1_slot 位置的值更新为 xlog_data 中的 level1_value if (xlog_data->level1_slot >= 0) { head->level1_slots[xlog_data->level1_slot] = xlog_data->level1_value; } + // 更新段头的 nextents、total_blocks 属性 head->nextents = xlog_data->nextents; head->total_blocks = xlog_data->total_blocks; /* If all extents is freed, flush segment header to disk */ + // 如果 total_blocks 为 0,表示所有 extents 都已释放,将段头刷新到磁盘上 if (head->total_blocks == 0) { SegmentCheck(head->nextents == 0); FlushOneSegmentBuffer(buffer); } } - +/* + * 功能:处理重做操作中的新建 level0 页记录 + * + * 参数列表: + * buffer:待处理的缓冲区 + * data:从 XLog 记录中提取的数据 + * + */ static void redo_new_level0_page(Buffer buffer, const char *data) { + // 从 XLog 记录中获取重做数据,即第一个块的块号 first_extent BlockNumber first_extent = *(BlockNumber *)data; + // 获取待处理缓冲区对应的 level0 页 Page level0_page = BufferGetPage(buffer); + // 初始化 level0 页的内容,设置页面大小为 BLCKSZ SegPageInit(level0_page, BLCKSZ); + // 更新页面头部信息 PageHeader header = (PageHeader)level0_page; header->pd_lower += sizeof(BMTLevel0Page); + // 获取 level0 页的内容,将第一个槽 slots[0] 设置为 first_extent BMTLevel0Page *bmt_level0_page = (BMTLevel0Page *)PageGetContents(level0_page); bmt_level0_page->slots[0] = first_extent; + // 设置 bmt_level0_page 的 magic 属性为 BMTLEVEL0_MAGIC bmt_level0_page->magic = BMTLEVEL0_MAGIC; } - +/* + * 功能:处理重做操作中的向 level0 页添加 extent 记录 + * + * 参数列表: + * buffer:待处理的缓冲区 + * data:从 XLog 记录中提取的数据 + */ static void redo_level0_page_add_extent(Buffer buffer, const char *data) { + // 从 XLog 记录中获取重做数据,即要添加的 extent 的槽位号和块号 XLogDataSetLevel0Page *xlog_data = (XLogDataSetLevel0Page *)data; + // 获取待处理缓冲区对应的 level0 页 BMTLevel0Page *bmt_level0_page = (BMTLevel0Page *)PageGetContents(BufferGetPage(buffer)); + // 将 level0 页中指定槽位 slots[slot] 设置为新的 extent bmt_level0_page->slots[xlog_data->slot] = xlog_data->extent; } - +/* + * 功能:处理重做操作中添加分支段的记录 + * + * 参数列表: + * buffer:待处理的缓冲区 + * data:XLog 重做记录中的数据 + */ static void redo_seghead_add_fork_segment(Buffer buffer, const char *data) { - int forknum = *(int *)data; + int forknum = *(int *)data; // 从记录数据中获取分支号 data += sizeof(int); - BlockNumber forkhead = *(BlockNumber *)data; + BlockNumber forkhead = *(BlockNumber *)data; // 从记录数据中获取分支头块号 - SegmentHead *seghead = (SegmentHead *)PageGetContents(BufferGetPage(buffer)); - seghead->fork_head[forknum] = forkhead; + SegmentHead *seghead = (SegmentHead *)PageGetContents(BufferGetPage(buffer)); // 获取缓冲区对应的段头信息 + seghead->fork_head[forknum] = forkhead; // 将分支头块号 forkhead 存储到相应分支号 forknum 的位置 } - +/* + * 功能:处理重做操作中取消链接段头指针的记录 + * + * 参数列表: + * buffer:待处理的缓冲区 + * data:XLog 重做记录中的数据 + */ static void redo_unlink_seghead_ptr(Buffer buffer, const char *data) { - off_t offset = *(off_t *)data; - + off_t offset = *(off_t *)data; // 从记录数据中获取偏移量 + // 获取缓冲区对应的数据,并计算指向偏移量的指针 BlockNumber *owner_pointer = (BlockNumber *)((char *)PageGetContents(BufferGetPage(buffer)) + offset); + // 将偏移量 offset 处的 BlockNumber 设置为 InvalidBlockNumber,取消链接段头指针 *owner_pointer = InvalidBlockNumber; } +/* + * 功能:处理重做操作中初始化桶的主头部记录 + * + * 参数列表: + * buffer:待处理的缓冲区 + * data:XLog 重做记录中的数据 + * + * 注意: + * 1. 该函数用于处理重做操作中初始化桶的主头部记录。 + * 2. 在使用此函数时,请确保传入的缓冲区是合适的,并且数据格式正确。 + */ static void redo_init_bucket_main_head(Buffer buffer, const char *data) { Page main_head_page = BufferGetPage(buffer); - SegPageInit(main_head_page, BLCKSZ); - ((PageHeader)main_head_page)->pd_lower += sizeof(BktMainHead); + SegPageInit(main_head_page, BLCKSZ); // 初始化页面 + ((PageHeader)main_head_page)->pd_lower += sizeof(BktMainHead); // 调整页面头部信息 - BktMainHead *main_head = (BktMainHead *)PageGetContents(main_head_page); - main_head->magic = BUCKET_SEGMENT_MAGIC; - main_head->lsn = *(XLogRecPtr *)data; - main_head->redis_info.redis_xid = InvalidTransactionId; - main_head->redis_info.nwords = 0; + BktMainHead *main_head = (BktMainHead *)PageGetContents(main_head_page); // 获取主头部结构指针 + main_head->magic = BUCKET_SEGMENT_MAGIC; // 设置魔术值 + main_head->lsn = *(XLogRecPtr *)data; // 从重做记录中获取LSN + main_head->redis_info.redis_xid = InvalidTransactionId; // 设置事务ID为无效 + main_head->redis_info.nwords = 0; // 初始化单词数为0 } +/* + * 功能:处理重做操作中释放桶的位图块记录 + * + * 参数列表: + * buffer:待处理的缓冲区 + * data:XLog 重做记录中的数据 + * + * 注意: + * 1. 该函数用于处理重做操作中释放桶的位图块记录。 + * 2. 在使用此函数时,请确保传入的缓冲区和数据格式正确。 + */ static void redo_bucket_free_mapblock(Buffer buffer, const char *data) { - uint32 map_id = *(uint32 *)data; - BktMainHead *main_head = (BktMainHead *)PageGetContents(BufferGetPage(buffer)); - main_head->bkt_map[map_id] = InvalidBlockNumber; + uint32 map_id = *(uint32 *)data; // 从重做记录中获取位图块的ID + BktMainHead *main_head = (BktMainHead *)PageGetContents(BufferGetPage(buffer)); // 获取主头部指针 + main_head->bkt_map[map_id] = InvalidBlockNumber; // 将位图块的ID标记为无效 } - +/* + * 功能:处理重做操作中添加桶的位图块记录 + * + * 参数列表: + * buffer:待处理的缓冲区 + * data:XLog 重做记录中的数据 + * + * 注意: + * 1. 该函数用于处理重做操作中添加桶的位图块记录。 + * 2. 在使用此函数时,请确保传入的缓冲区和数据格式正确。 + */ static void redo_bucket_add_mapblock(Buffer buffer, const char *data) { - uint32 blockid = *(uint32 *)data; + uint32 blockid = *(uint32 *)data; // 从重做记录中获取桶的ID data += sizeof(uint32); - BlockNumber mapblock = *(BlockNumber *)data; + BlockNumber mapblock = *(BlockNumber *)data; // 从重做记录中获取位图块的块号 - BktMainHead *main_head = (BktMainHead *)PageGetContents(BufferGetPage(buffer)); - main_head->bkt_map[blockid] = mapblock; + BktMainHead *main_head = (BktMainHead *)PageGetContents(BufferGetPage(buffer)); // 获取主头部指针 + main_head->bkt_map[blockid] = mapblock; // 将桶的位图块关联起来 } +/* + * 功能:处理重做操作中初始化桶的位图块记录 + * + * 参数列表: + * buffer:待处理的缓冲区 + * data:XLog 重做记录中的数据 + */ static void redo_bucket_init_mapblock(Buffer buffer, const char *data) { - XLogRecPtr lsn = *(XLogRecPtr *)data; - bucket_init_map_page(buffer, lsn); + XLogRecPtr lsn = *(XLogRecPtr *)data; // 从重做记录中获取LSN + bucket_init_map_page(buffer, lsn); // 调用 bucket_init_map_page 函数进行位图块初始化 } +/* + * 功能:处理重做操作中添加桶头映射块的记录 + * + * 参数列表: + * buffer:待处理的缓冲区 + * data:XLog 重做记录中的数据 + */ static void redo_bucket_add_bkthead(Buffer buffer, const char *data) { - int map_entry_id = *(int *)data; + int map_entry_id = *(int *)data; // 从记录数据中获取映射条目ID data += sizeof(int); - BlockNumber head_block = *(BlockNumber *)data; + BlockNumber head_block = *(BlockNumber *)data; // 从记录数据中获取头块号 BktHeadMapBlock *map_block = (BktHeadMapBlock *)PageGetContents(BufferGetPage(buffer)); - map_block->head_block[map_entry_id] = head_block; + map_block->head_block[map_entry_id] = head_block; // 将映射条目ID对应的头块号设置到映射块中 } - +/* + * 功能:处理重做操作中更新空间高水位标记的记录 + * + * 参数列表: + * buffer:待处理的缓冲区 + * data:XLog 重做记录中的数据 + */ static void redo_space_update_hwm(Buffer buffer, const char *data) { XLogDataUpdateSpaceHWM *xlog_data = (XLogDataUpdateSpaceHWM *)data; df_map_head_t *map_head = (df_map_head_t *)PageGetContents(BufferGetPage(buffer)); + // 检查旧高水位标记是否与记录中的值一致 if (map_head->high_water_mark != xlog_data->old_hwm) { ereport(PANIC, (errmsg("update space high water mark, old hwm is %u, but should be %u according to xlog", map_head->high_water_mark, xlog_data->old_hwm), errhint("segment-page may have bug"))); } + // 检查旧映射组数是否与记录中的值一致 if (map_head->group_count != xlog_data->old_groupcnt) { ereport(PANIC, (errmsg("update map group count, old count is %u, but should be %u according to xlog", - map_head->group_count, xlog_data->old_groupcnt))); + map_head->group_count, xlog_data->old_groupcnt))); } + // 更新高水位标记和映射组数为记录中的新值 map_head->high_water_mark = xlog_data->new_hwm; map_head->group_count = xlog_data->new_groupcnt; } - +/* + * 功能:处理重做操作中设置反向指针的记录 + * + * 参数列表: + * buffer:待处理的缓冲区 + * data:XLog 重做记录中的数据 + */ static void redo_set_inverse_pointer(Buffer buffer, const char *data) { - uint32 offset = *(uint32 *)data; + uint32 offset = *(uint32 *)data; // 从记录数据中获取偏移量 data += sizeof(uint32); - ExtentInversePointer iptr = *(ExtentInversePointer *)data; + ExtentInversePointer iptr = *(ExtentInversePointer *)data; // 从记录数据中获取反向指针 ExtentInversePointer *eips = (ExtentInversePointer *)PageGetContents(BufferGetBlock(buffer)); - eips[offset] = iptr; + eips[offset] = iptr; // 将反向指针设置到指定偏移量的位置 } - +/* + * 功能:处理重做操作中缩减段头更新记录 + * + * 参数列表: + * buffer:待处理的缓冲区 + * data:XLog 重做记录中的数据 + */ static void redo_shrink_seghead_update(Buffer buffer, const char *data) { - XLogMoveExtent *xlog_data = (XLogMoveExtent *)data; - SegmentHead *seghead = (SegmentHead *)PageGetContents(BufferGetBlock(buffer)); - int extent_id = xlog_data->extent_id; + XLogMoveExtent *xlog_data = (XLogMoveExtent *)data; // 获取XLog记录中的数据 + SegmentHead *seghead = (SegmentHead *)PageGetContents(BufferGetBlock(buffer)); // 获取缓冲区对应的段头 + int extent_id = xlog_data->extent_id; // 获取要更新的Extent的ID if (extent_id < BMT_HEADER_LEVEL0_SLOTS) { /* Level0 extent, needs updating segment head */ - SegmentCheck(seghead->level0_slots[extent_id] == xlog_data->old_extent); - seghead->level0_slots[extent_id] = xlog_data->new_extent; + SegmentCheck(seghead->level0_slots[extent_id] == xlog_data->old_extent); // 检查旧的Extent是否匹配 + seghead->level0_slots[extent_id] = xlog_data->new_extent; // 更新段头中Level0 extent的信息 } + // 在SEGMENT_REDO_UPDATE_SEGHEAD测试模式下记录错误信息 SEGMENTTEST(SEGMENT_REDO_UPDATE_SEGHEAD, (errmsg("error happens when replaying segment head update in shrink"))); } #define REL_NODE_FORMAT(rnode) rnode.spcNode, rnode.dbNode, rnode.relNode, rnode.bucketNode /* create hash table using shared memory when first needed */ -struct HTAB* redo_create_remain_segs_htbl() +struct HTAB *redo_create_remain_segs_htbl() { HASHCTL ctl; - + + // 初始化哈希表控制结构 errno_t errorno = memset_s(&ctl, sizeof(ctl), 0, sizeof(ctl)); securec_check(errorno, "", ""); - - ctl.keysize = sizeof(RemainExtentHashTag); - ctl.entrysize = sizeof(ExtentTag); - ctl.hash = tag_hash; - int flag = HASH_ELEM | HASH_FUNCTION; - + + ctl.keysize = sizeof(RemainExtentHashTag); // 设置键的大小 + ctl.entrysize = sizeof(ExtentTag); // 设置条目的大小 + ctl.hash = tag_hash; // 设置哈希函数 + int flag = HASH_ELEM | HASH_FUNCTION; // 标记为哈希元素和哈希函数 + + // 使用HeapMemInitHash函数创建并初始化哈希表 return HeapMemInitHash("remain_segs", 1000, DF_MAP_GROUP_EXTENTS, &ctl, flag); } - +/* + * 功能:处理重做操作中的分配段日志记录 + * + * 参数列表: + * buffer:待处理的缓冲区 + * xid:分配段的事务 ID + * + * 注意: + * 1. 这个函数用于处理重做操作中的分配段日志记录。 + * 2. 函数首先获取重做操作对应的缓冲区描述符。 + * 3. 然后构建一个 RemainExtentHashTag 结构,用于标识剩余段的哈希表条目。 + * 4. 接下来,函数尝试在哈希表中查找该条目,如果找到表示该段已存在,输出警告信息。 + * 5. 如果未找到,将该段信息添加到哈希表中,包括事务 ID、剩余段类型、fork 号和 LSN。 + * 6. 最后,释放锁。 + */ static void redo_xlog_log_alloc_seg(Buffer buffer, TransactionId xid) { AutoMutexLock remainSegsLock(&g_instance.xlog_cxt.remain_segs_lock); remainSegsLock.lock(); Assert(TransactionIdIsValid(xid)); - + if (t_thrd.xlog_cxt.remain_segs == NULL) { t_thrd.xlog_cxt.remain_segs = redo_create_remain_segs_htbl(); } @@ -358,28 +639,44 @@ static void redo_xlog_log_alloc_seg(Buffer buffer, TransactionId xid) remainExtentHashTag.rnode = bufDesc->tag.rnode; remainExtentHashTag.rnode.relNode = bufDesc->tag.blockNum; remainExtentHashTag.extentType = bufDesc->tag.rnode.relNode; - + bool found = false; - ExtentTag* extentTag = (ExtentTag *)hash_search(t_thrd.xlog_cxt.remain_segs, (void *)&remainExtentHashTag, - HASH_ENTER, &found); + ExtentTag *extentTag = + (ExtentTag *)hash_search(t_thrd.xlog_cxt.remain_segs, (void *)&remainExtentHashTag, HASH_ENTER, &found); if (found) { - ereport(WARNING, (errmsg("Segment [%u, %u, %u, %d] already existed in remain segs, Xid %lu," - "remainExtentType %u.", REL_NODE_FORMAT(remainExtentHashTag.rnode), extentTag->xid, - extentTag->remainExtentType))); + ereport(WARNING, + (errmsg("Segment [%u, %u, %u, %d] already existed in remain segs, Xid %lu," + "remainExtentType %u.", + REL_NODE_FORMAT(remainExtentHashTag.rnode), extentTag->xid, extentTag->remainExtentType))); } else { extentTag->remainExtentType = ALLOC_SEGMENT; extentTag->xid = xid; extentTag->forkNum = InvalidForkNumber; extentTag->lsn = InvalidXLogRecPtr; - + ereport(DEBUG5, (errmodule(MOD_SEGMENT_PAGE), errmsg("Segment [%u, %u, %u, %d] is alloced, cur xid %lu.", - REL_NODE_FORMAT(remainExtentHashTag.rnode), xid))); + REL_NODE_FORMAT(remainExtentHashTag.rnode), xid))); } remainSegsLock.unLock(); } - -static void redo_xlog_forget_alloc_seg(Buffer buffer, const char* data, int data_len) +/* + * 功能:处理重做操作中忘记分配段的记录 + * + * 参数列表: + * buffer:待处理的缓冲区 + * data:XLog 重做记录中的数据 + * data_len:数据长度 + * + * 注意: + * 1. 函数首先检查数据长度是否符合预期,如果不符合则返回。 + * 2. 然后从数据中获取块号,根据块号构建一个 RemainExtentHashTag 结构,用于标识剩余段的哈希表条目。 + * 3. 获取剩余段的哈希表锁,并尝试在哈希表中查找该条目,如果找到表示该段已释放,输出调试信息。 + * 4. 如果未找到,输出调试信息表示未找到该段。 + * 5. 最后,释放锁。 + */ +static void redo_xlog_forget_alloc_seg(Buffer buffer, const char *data, int data_len) { + // 首先检查数据长度是否符合预期,如果不符合则返回。 int first_part_data_len = sizeof(uint16) + sizeof(df_map_page_t); if (data_len <= first_part_data_len) { Assert(data_len == first_part_data_len); @@ -387,14 +684,14 @@ static void redo_xlog_forget_alloc_seg(Buffer buffer, const char* data, int data } Assert(data_len == (sizeof(uint16) + sizeof(df_map_page_t) + sizeof(BlockNumber))); - BlockNumber* blk_num = (BlockNumber *)(data + sizeof(uint16) + sizeof(df_map_page_t)); + BlockNumber *blk_num = (BlockNumber *)(data + sizeof(uint16) + sizeof(df_map_page_t)); BufferDesc *bufDesc = GetBufferDescriptor(buffer - 1); RemainExtentHashTag remainExtentHashTag; remainExtentHashTag.rnode = bufDesc->tag.rnode; remainExtentHashTag.rnode.relNode = *blk_num; remainExtentHashTag.extentType = bufDesc->tag.rnode.relNode; - + AutoMutexLock remain_segs_lock(&g_instance.xlog_cxt.remain_segs_lock); remain_segs_lock.lock(); if (t_thrd.xlog_cxt.remain_segs == NULL) { @@ -402,26 +699,40 @@ static void redo_xlog_forget_alloc_seg(Buffer buffer, const char* data, int data } bool found = false; - ExtentTag* extentTag = (ExtentTag *)hash_search(t_thrd.xlog_cxt.remain_segs, (void *)&(remainExtentHashTag), - HASH_REMOVE, &found); + // 在哈希表中查找该条目 + ExtentTag *extentTag = + (ExtentTag *)hash_search(t_thrd.xlog_cxt.remain_segs, (void *)&(remainExtentHashTag), HASH_REMOVE, &found); + // 如果找到,表示该段已释放,输出调试信息 if (found) { ereport(DEBUG5, (errmodule(MOD_SEGMENT_PAGE), errmsg("Segment [%u, %u, %u, %d] is really freed after" - "drop trxn committed, xid %lu, remainExtentType %u.", REL_NODE_FORMAT(remainExtentHashTag.rnode), - extentTag->xid, extentTag->remainExtentType))); + "drop trxn committed, xid %lu, remainExtentType %u.", + REL_NODE_FORMAT(remainExtentHashTag.rnode), extentTag->xid, + extentTag->remainExtentType))); } else { + // 如果未找到,输出调试信息表示未找到该段 ereport(DEBUG5, (errmodule(MOD_SEGMENT_PAGE), errmsg("Segment [%u, %u, %u, %d] is not found" - "in remain segs htbl.", REL_NODE_FORMAT(remainExtentHashTag.rnode)))); + "in remain segs htbl.", + REL_NODE_FORMAT(remainExtentHashTag.rnode)))); } + // 释放哈希表锁 remain_segs_lock.unLock(); } - -static void redo_xlog_log_shrink_extent(Buffer buffer, const char* data) +/* + * 功能:处理重做操作中收缩段的记录 + * + * 参数列表: + * buffer:待处理的缓冲区 + * data:XLog 重做记录中的数据 + */ +static void redo_xlog_log_shrink_extent(Buffer buffer, const char *data) { XLogMoveExtent *xlog_data = (XLogMoveExtent *)data; SegmentCheck(xlog_data->old_extent != InvalidBlockNumber); + // 获取剩余段哈希表锁,确保线程安全 AutoMutexLock remain_segs_lock(&g_instance.xlog_cxt.remain_segs_lock); remain_segs_lock.lock(); + // 如果剩余段哈希表不存在,则创建一个新的哈希表 if (t_thrd.xlog_cxt.remain_segs == NULL) { t_thrd.xlog_cxt.remain_segs = redo_create_remain_segs_htbl(); } @@ -436,120 +747,186 @@ static void redo_xlog_log_shrink_extent(Buffer buffer, const char* data) remainExtentHashTag.rnode.bucketNode = SegmentBktId; /* extentType is calculated by extent id */ remainExtentHashTag.extentType = EXTENT_SIZE_TO_TYPE(ExtentSizeByCount(xlog_data->extent_id)); - - ExtentTag* extentTag = (ExtentTag *)hash_search(t_thrd.xlog_cxt.remain_segs, (void *)&remainExtentHashTag, - HASH_ENTER, &found); + + // 在哈希表中查找该条目,如果找到,输出警告信息表示已找到该段 + ExtentTag *extentTag = + (ExtentTag *)hash_search(t_thrd.xlog_cxt.remain_segs, (void *)&remainExtentHashTag, HASH_ENTER, &found); if (found) { - ereport(WARNING, (errmsg("Segment [%u, %u, %u, %d] Extent %u should not be repeatedly founded, xid %lu," - "remainExtentType %u", REL_NODE_FORMAT(remainExtentHashTag.rnode), xlog_data->old_extent, extentTag->xid, - extentTag->remainExtentType))); + ereport(WARNING, (errmsg("Segment [%u, %u, %u, %d] Extent %u should not be repeatedly founded, xid %lu," + "remainExtentType %u", + REL_NODE_FORMAT(remainExtentHashTag.rnode), xlog_data->old_extent, extentTag->xid, + extentTag->remainExtentType))); } else { + // 如果未找到,将该段的信息添加到哈希表,并标记为 SHRINK_EXTENT 类型 extentTag->remainExtentType = SHRINK_EXTENT; extentTag->forkNum = xlog_data->forknum; - + extentTag->xid = InvalidTransactionId; extentTag->lsn = InvalidXLogRecPtr; - ereport(DEBUG5, (errmodule(MOD_SEGMENT_PAGE), errmsg("Segment [%u, %u, %u, %d] Extent %u is replaced " - "during shrinking in shrink extents.", REL_NODE_FORMAT(remainExtentHashTag.rnode), xlog_data->old_extent))); + ereport(DEBUG5, (errmodule(MOD_SEGMENT_PAGE), + errmsg("Segment [%u, %u, %u, %d] Extent %u is replaced " + "during shrinking in shrink extents.", + REL_NODE_FORMAT(remainExtentHashTag.rnode), xlog_data->old_extent))); } + // 释放哈希表锁 remain_segs_lock.unLock(); } - -void redo_xlog_deal_alloc_seg(uint8 opCode, Buffer buffer, const char* data, int data_len, TransactionId xid) +/* + * 处理重做操作中的分配段和释放段的记录 + * + * 参数列表: + * opCode:操作码,指示重做记录的类型 + * buffer:待处理的缓冲区 + * data:XLog 重做记录中的数据 + * data_len:XLog 重做记录数据的长度 + * xid:事务 ID + */ +void redo_xlog_deal_alloc_seg(uint8 opCode, Buffer buffer, const char *data, int data_len, TransactionId xid) { if (opCode == SPCXLOG_INIT_SEGHEAD) { + // 检查是否是初始化段头记录 unsigned char is_seg_head = *(unsigned char *)(data + sizeof(XLogRecPtr)); if (is_seg_head == 0) { return; } + // 调用 redo_xlog_log_alloc_seg 处理初始化段头的记录 redo_xlog_log_alloc_seg(buffer, xid); } else if (opCode == SPCXLOG_INIT_BUCKET_HEAD) { + // 处理初始化桶头记录,调用 redo_xlog_log_alloc_seg 处理 redo_xlog_log_alloc_seg(buffer, xid); } else if (opCode == SPCXLOG_FREE_BITMAP) { + // 处理释放段的记录,调用 redo_xlog_forget_alloc_seg 处理 redo_xlog_forget_alloc_seg(buffer, data, data_len); } else if (opCode == SPCXLOG_SHRINK_SEGHEAD_UPDATE) { + // 处理分配段的记录,调用 redo_xlog_log_shrink_extent 处理 redo_xlog_log_shrink_extent(buffer, data); } } - +/* + * 功能:处理原子重做日志分发 + * + * 参数列表: + * opCode:重做操作的代码 + * redo_buf:重做缓冲区信息 + * data:XLog 重做记录中的数据 + * + * 注意: + * 1. 该函数用于根据重做操作的代码分发不同的处理函数。 + * 2. 根据 opCode 的不同,调用相应的处理函数来处理重做操作。 + */ void redo_atomic_xlog_dispatch(uint8 opCode, RedoBufferInfo *redo_buf, const char *data) { Buffer buffer = redo_buf->buf; + /* 输出调试信息,显示 opCode */ ereport(DEBUG5, (errmodule(MOD_SEGMENT_PAGE), errmsg("redo_atomic_xlog_dispatch opCode: %u", opCode))); if (opCode == SPCXLOG_SET_BITMAP) { + /* 处理设置位图的重做操作 */ redo_set_bitmap(redo_buf->blockinfo, buffer, data); } else if (opCode == SPCXLOG_FREE_BITMAP) { + /* 处理设置位图的重做操作 */ redo_unset_bitmap(buffer, data); } else if (opCode == SPCXLOG_MAPHEAD_ALLOCATED_EXTENTS) { + /* 处理映射头分配扩展的重做操作 */ redo_maphead_allocated_extents(buffer, data); } else if (opCode == SPCXLOG_INIT_SEGHEAD) { + /* 处理初始化段头的重做操作 */ redo_init_seghead(buffer, data); } else if (opCode == SPCXLOG_UPDATE_SEGHEAD) { + /* 处理更新段头的重做操作 */ redo_update_seghead(buffer, data); } else if (opCode == SPCXLOG_NEW_LEVEL0_PAGE) { + /* 处理创建新的 Level0 页的重做操作 */ redo_new_level0_page(buffer, data); } else if (opCode == SPCXLOG_LEVEL0_PAGE_ADD_EXTENT) { + /* 处理 Level0 页添加扩展的重做操作 */ redo_level0_page_add_extent(buffer, data); } else if (opCode == SPCXLOG_SEGHEAD_ADD_FORK_SEGMENT) { + /* 处理段头添加分段的重做操作 */ redo_seghead_add_fork_segment(buffer, data); } else if (opCode == SPCXLOG_UNLINK_SEGHEAD_PTR) { + /* 处理取消链接段头指针的重做操作 */ redo_unlink_seghead_ptr(buffer, data); } else if (opCode == SPCXLOG_INIT_BUCKET_HEAD) { + /* 处理初始化桶主头的重做操作 */ redo_init_bucket_main_head(buffer, data); } else if (opCode == SPCXLOG_BUCKET_FREE_MAPBLOCK) { + /* 处理释放桶地图块的重做操作 */ redo_bucket_free_mapblock(buffer, data); } else if (opCode == SPCXLOG_BUCKET_ADD_MAPBLOCK) { + /* 处理添加桶地图块的重做操作 */ redo_bucket_add_mapblock(buffer, data); } else if (opCode == SPCXLOG_BUCKET_INIT_MAPBLOCK) { + /* 处理初始化桶地图块的重做操作 */ redo_bucket_init_mapblock(buffer, data); } else if (opCode == SPCXLOG_BUCKET_ADD_BKTHEAD) { + /* 处理添加桶主头的重做操作 */ redo_bucket_add_bkthead(buffer, data); } else if (opCode == SPCXLOG_SPACE_UPDATE_HWM) { + /* 处理更新空间高水位标记的重做操作 */ redo_space_update_hwm(buffer, data); } else if (opCode == SPCXLOG_SET_INVERSE_POINTER) { + /* 处理设置逆向指针的重做操作 */ redo_set_inverse_pointer(buffer, data); } else if (opCode == SPCXLOG_SEG_MOVE_BUCKETS) { + /* 处理段移动桶的重做操作 */ redo_move_buckets(buffer, data); } else if (opCode == SPCXLOG_BUCKET_ADD_REDISINFO) { + /* 处理添加桶 Redis 信息的重做操作 */ redo_bucket_add_redisinfo(buffer, data); } else { + /* 默认处理收缩段头更新的重做操作 */ SegmentCheck(opCode == SPCXLOG_SHRINK_SEGHEAD_UPDATE); redo_shrink_seghead_update(buffer, data); } } - +/* + * 刷新移动的段到磁盘 + * + * 参数列表: + * xlog_data:重做记录中的移动段数据 + */ void move_extent_flush_buffer(XLogMoveExtent *xlog_data) { BlockNumber logic_start = ExtentIdToLogicBlockNum(xlog_data->extent_id); - for (int i=0; iextent_id); i++) { + for (int i = 0; i < ExtentSizeByCount(xlog_data->extent_id); i++) { BlockNumber blk = logic_start + i; + // 如果块号超过了段的块数,跳出循环 if (blk >= xlog_data->nblocks) { break; } + // 尝试获取移动后的页面缓冲区 Buffer buffer = try_get_moved_pagebuf(&xlog_data->logic_rnode, xlog_data->forknum, blk); if (BufferIsValid(buffer)) { BlockNumber old_seg_blockno = xlog_data->old_extent + i; BlockNumber new_seg_blockno = xlog_data->new_extent + i; BufferDesc *buf_desc = BufferGetBufferDescriptor(buffer); + // 如果缓冲区的段块号等于旧段块号 if (buf_desc->seg_blockno == old_seg_blockno) { uint32 buf_state = LockBufHdr(buf_desc); + // 如果缓冲区标记为脏 if (buf_state & BM_DIRTY) { /* spin-lock should be released before IO */ + // 释放自旋锁以允许 IO 操作 UnlockBufHdr(buf_desc, buf_state); + // 锁定缓冲区以进行独占刷新 LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE); /* Flush data to the old block */ + // 刷新数据到旧块 FlushOneBufferIncludeDW(buf_desc); + // 解锁缓冲区 LockBuffer(buffer, BUFFER_LOCK_UNLOCK); } else { UnlockBufHdr(buf_desc, buf_state); } /* It's dirty, but we must unpin buffer before InvalidateBuffer */ + // 在无效化缓冲区之前,必须解除缓冲区的引用 UnpinBuffer(buf_desc, true); buf_state = LockBufHdr(buf_desc); + // 如果缓冲区的节点信息匹配 if (RelFileNodeEquals(buf_desc->tag.rnode, xlog_data->logic_rnode) && buf_desc->tag.forkNum == xlog_data->forknum && buf_desc->tag.blockNum == blk) { InvalidateBuffer(buf_desc); @@ -558,6 +935,7 @@ void move_extent_flush_buffer(XLogMoveExtent *xlog_data) } } else { /* Get here only because standby read after we modifiy the segment head */ + // 只有在主备模式下读取到我们修改段头后才会执行到这里 SegmentCheck(buf_desc->seg_blockno == new_seg_blockno); UnpinBuffer(buf_desc, true); } @@ -607,7 +985,7 @@ static void redo_atomic_xlog(XLogReaderState *record) PageSetLSN(redo_buf.pageinfo.page, redo_buf.lsn); SegMarkBufferDirty(redo_buf.buf); } - + for (int j = 0; j < decoded_op.operations; j++) { if (decoded_op.op[j] == SPCXLOG_SHRINK_SEGHEAD_UPDATE) { need_flush_buffer_for_shrink = true; @@ -615,8 +993,7 @@ static void redo_atomic_xlog(XLogReaderState *record) } if (is_need_log_remain_segs) { - redo_xlog_deal_alloc_seg(decoded_op.op[j], redo_buf.buf, - decoded_op.data[j], decoded_op.data_len[j], + redo_xlog_deal_alloc_seg(decoded_op.op[j], redo_buf.buf, decoded_op.data[j], decoded_op.data_len[j], XLogRecGetXid(record)); } } @@ -795,7 +1172,6 @@ void seg_redo_new_page_copy_and_flush(BufferTag *tag, char *data, XLogRecPtr lsn t_thrd.proc->dw_pos = -1; } - /* * This xlog only copy data to the new block, without modifying data in buffer. If the logic block being in the * buffer pool, its pblk points to the old block. The buffer descriptor can not have the logic blocknumber and the new -- 2.34.1 From 0f11f2c6f52b2e0199db1ff63561890b71593dc4 Mon Sep 17 00:00:00 2001 From: nuoya <1204149038@qq.com> Date: Mon, 25 Sep 2023 15:34:28 +0800 Subject: [PATCH 19/50] Update knl_usync.cpp --- src/gausskernel/storage/sync/knl_usync.cpp | 142 ++++++++++----------- 1 file changed, 70 insertions(+), 72 deletions(-) diff --git a/src/gausskernel/storage/sync/knl_usync.cpp b/src/gausskernel/storage/sync/knl_usync.cpp index 17f78738e..f2c73f059 100644 --- a/src/gausskernel/storage/sync/knl_usync.cpp +++ b/src/gausskernel/storage/sync/knl_usync.cpp @@ -54,7 +54,7 @@ * (Regular backends do not track pending operations locally, but forward * them to the checkpointer.) */ -typedef uint16 CycleCtr; /* can be any convenient integer size */ +typedef uint16 CycleCtr; /* can be any convenient integer size */ typedef struct { FileTag tag; /* identifies handler and file */ @@ -109,11 +109,16 @@ static const SyncOps SYNCSW[] = { }; static const int NSync = lengthof(SYNCSW); - +/* + * 功能:初始化待处理操作的哈希表,用于跟踪需要执行的文件系统同步操作 + * + * 参数列表:无 + */ void InitPendingOps(void) { + // 如果不处于Postmaster进程下,或者是启动进程、检查点进程、页写进程,则执行以下操作 if (!IsUnderPostmaster || AmStartupProcess() || AmCheckpointerProcess() || AmPageWriterMainProcess()) { - HASHCTL hashCtl; + HASHCTL hashCtl; errno_t rc; /* @@ -125,17 +130,18 @@ void InitPendingOps(void) * Fortunately the hash table is small so that's unlikely to happen in * practice. */ - u_sess->storage_cxt.pendingOpsCxt = AllocSetContextCreate(u_sess->top_mem_cxt, - "Pending ops context", ALLOCSET_DEFAULT_SIZES); - MemoryContextAllowInCriticalSection(u_sess->storage_cxt.pendingOpsCxt, true); - rc = memset_s(&hashCtl, sizeof(hashCtl), 0, sizeof(hashCtl)); - securec_check(rc, "", ""); - hashCtl.keysize = sizeof(FileTag); - hashCtl.entrysize = sizeof(PendingFsyncEntry); - hashCtl.hcxt = u_sess->storage_cxt.pendingOpsCxt; - hashCtl.hash = tag_hash; - u_sess->storage_cxt.pendingOps = hash_create("Pending Ops Table", - 100L, &hashCtl, HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); + u_sess->storage_cxt.pendingOpsCxt = + AllocSetContextCreate(u_sess->top_mem_cxt, "Pending ops context", + ALLOCSET_DEFAULT_SIZES); // 创建内存上下文来管理待处理操作的内存 + MemoryContextAllowInCriticalSection(u_sess->storage_cxt.pendingOpsCxt, true); // 允许在临界区内分配内存 + rc = memset_s(&hashCtl, sizeof(hashCtl), 0, sizeof(hashCtl)); // 初始化hashCtl结构体 + securec_check(rc, "", ""); // 检查memset_s是否失败 + hashCtl.keysize = sizeof(FileTag); // 哈希键的大小为FileTag的大小 + hashCtl.entrysize = sizeof(PendingFsyncEntry); // 哈希表项的大小为PendingFsyncEntry的大小 + hashCtl.hcxt = u_sess->storage_cxt.pendingOpsCxt; // 哈希表使用上面创建的内存上下文 + hashCtl.hash = tag_hash; // 哈希函数使用tag_hash来计算哈希值 + u_sess->storage_cxt.pendingOps = hash_create( + "Pending Ops Table", 100L, &hashCtl, HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); // 创建哈希表来存储待处理操作 } } @@ -189,7 +195,7 @@ void SyncPostCheckpoint(void) absorbCounter = UNLINKS_PER_ABSORB; while (u_sess->storage_cxt.pendingUnlinks != NIL) { - PendingUnlinkEntry *entry = (PendingUnlinkEntry *) linitial(u_sess->storage_cxt.pendingUnlinks); + PendingUnlinkEntry *entry = (PendingUnlinkEntry *)linitial(u_sess->storage_cxt.pendingUnlinks); char path[MAXPGPATH]; /* @@ -215,8 +221,7 @@ void SyncPostCheckpoint(void) * the possibility that we delete the file first. */ if (errno != ENOENT) { - ereport(WARNING, (errcode_for_file_access(), - errmsg("could not remove file \"%s\": %m", path))); + ereport(WARNING, (errcode_for_file_access(), errmsg("could not remove file \"%s\": %m", path))); } } @@ -287,7 +292,7 @@ static void HandleAbnormalSyncExit(bool syncInProgress) if (syncInProgress) { /* prior try failed, so update any stale cycle_ctr values */ hash_seq_init(&hstat, u_sess->storage_cxt.pendingOps); - while ((entry = (PendingFsyncEntry *) hash_seq_search(&hstat)) != NULL) { + while ((entry = (PendingFsyncEntry *)hash_seq_search(&hstat)) != NULL) { entry->cycle_ctr = u_sess->storage_cxt.sync_cycle_ctr; } } @@ -305,13 +310,13 @@ void ProcessSyncRequests(void) int absorbCounter; /* Statistics on sync times */ - int processed = 0; - instr_time syncStart; - instr_time syncEnd; - instr_time syncDiff; - uint64 elapsed; - uint64 longest = 0; - uint64 totalElapsed = 0; + int processed = 0; + instr_time syncStart; + instr_time syncEnd; + instr_time syncDiff; + uint64 elapsed; + uint64 longest = 0; + uint64 totalElapsed = 0; /* * This is only called during checkpoints, and checkpoints should only @@ -342,7 +347,7 @@ void ProcessSyncRequests(void) /* Now scan the hashtable for fsync requests to process */ absorbCounter = FSYNCS_PER_ABSORB; hash_seq_init(&hstat, u_sess->storage_cxt.pendingOps); - while ((entry = (PendingFsyncEntry *) hash_seq_search(&hstat)) != NULL) { + while ((entry = (PendingFsyncEntry *)hash_seq_search(&hstat)) != NULL) { int failures; /* @@ -364,7 +369,7 @@ void ProcessSyncRequests(void) } /* Else assert we haven't missed it */ - Assert((CycleCtr) (entry->cycle_ctr + 1) == u_sess->storage_cxt.sync_cycle_ctr); + Assert((CycleCtr)(entry->cycle_ctr + 1) == u_sess->storage_cxt.sync_cycle_ctr); /* * If in checkpointer, we want to absorb pending requests every so @@ -405,10 +410,10 @@ void ProcessSyncRequests(void) processed++; if (u_sess->attr.attr_common.log_checkpoints) { - ereport(DEBUG1, (errmsg("checkpoint sync: number=%d file=%s time=%.3f msec", - processed, path, (double) elapsed / MSEC_PER_MICROSEC))); + ereport(DEBUG1, (errmsg("checkpoint sync: number=%d file=%s time=%.3f msec", processed, path, + (double)elapsed / MSEC_PER_MICROSEC))); } - break; /* out of retry loop */ + break; /* out of retry loop */ } /* @@ -418,8 +423,7 @@ void ProcessSyncRequests(void) */ if (!FILE_POSSIBLY_DELETED(errno) || failures > 0) { if (check_unlink_rel_hashtbl(entry->tag.rnode, entry->tag.forknum)) { - ereport(DEBUG1, - (errmsg("could not fsync file \"%s\": %m, this relation has been remove", path))); + ereport(DEBUG1, (errmsg("could not fsync file \"%s\": %m, this relation has been remove", path))); break; } @@ -432,13 +436,12 @@ void ProcessSyncRequests(void) break; } /* treat it as truncate */ - ereport(data_sync_elevel(ERROR), (errcode_for_file_access(), - errmsg("could not fsync file \"%s\": %m", path))); + ereport(data_sync_elevel(ERROR), + (errcode_for_file_access(), errmsg("could not fsync file \"%s\": %m", path))); break; } else { ereport(DEBUG1, - (errcode_for_file_access(), - errmsg("could not fsync file \"%s\" but retrying: %m", path))); + (errcode_for_file_access(), errmsg("could not fsync file \"%s\" but retrying: %m", path))); } /* @@ -475,11 +478,10 @@ void ProcessUnlinkList(const FileTag *ftag) if (!AmPageWriterMainProcess()) { prev = NULL; for (cell = list_head(u_sess->storage_cxt.pendingUnlinks); cell; cell = next) { - PendingUnlinkEntry *entry = (PendingUnlinkEntry *) lfirst(cell); + PendingUnlinkEntry *entry = (PendingUnlinkEntry *)lfirst(cell); next = lnext(cell); if (entry->tag.handler == ftag->handler && SYNCSW[ftag->handler].matchfiletag(ftag, &entry->tag)) { - u_sess->storage_cxt.pendingUnlinks = - list_delete_cell(u_sess->storage_cxt.pendingUnlinks, cell, prev); + u_sess->storage_cxt.pendingUnlinks = list_delete_cell(u_sess->storage_cxt.pendingUnlinks, cell, prev); pfree(entry); } else { prev = cell; @@ -505,7 +507,7 @@ void RememberSyncRequest(const FileTag *ftag, SyncRequestType type) PendingFsyncEntry *entry; /* Cancel previously entered request */ - entry = (PendingFsyncEntry *) hash_search(u_sess->storage_cxt.pendingOps, (void *) ftag, HASH_FIND, NULL); + entry = (PendingFsyncEntry *)hash_search(u_sess->storage_cxt.pendingOps, (void *)ftag, HASH_FIND, NULL); if (entry != NULL) { entry->canceled = true; } @@ -515,9 +517,8 @@ void RememberSyncRequest(const FileTag *ftag, SyncRequestType type) /* Cancel matching fsync requests */ hash_seq_init(&hstat, u_sess->storage_cxt.pendingOps); - while ((entry = (PendingFsyncEntry *) hash_seq_search(&hstat)) != NULL) { - if (entry->tag.handler == ftag->handler && - SYNCSW[ftag->handler].matchfiletag(ftag, &entry->tag)) { + while ((entry = (PendingFsyncEntry *)hash_seq_search(&hstat)) != NULL) { + if (entry->tag.handler == ftag->handler && SYNCSW[ftag->handler].matchfiletag(ftag, &entry->tag)) { entry->canceled = true; } } @@ -528,7 +529,7 @@ void RememberSyncRequest(const FileTag *ftag, SyncRequestType type) Assert(!AmPageWriterMainProcess()); /* Unlink request: put it in the linked list */ MemoryContext oldcxt = MemoryContextSwitchTo(u_sess->storage_cxt.pendingOpsCxt); - PendingUnlinkEntry *entry = (PendingUnlinkEntry*)palloc(sizeof(PendingUnlinkEntry)); + PendingUnlinkEntry *entry = (PendingUnlinkEntry *)palloc(sizeof(PendingUnlinkEntry)); entry->tag = *ftag; entry->cycle_ctr = u_sess->storage_cxt.checkpoint_cycle_ctr; u_sess->storage_cxt.pendingUnlinks = lappend(u_sess->storage_cxt.pendingUnlinks, entry); @@ -541,12 +542,12 @@ void RememberSyncRequest(const FileTag *ftag, SyncRequestType type) Assert(type == SYNC_REQUEST); - entry = (PendingFsyncEntry *) hash_search(u_sess->storage_cxt.pendingOps, (void *) ftag, HASH_ENTER, &found); + entry = (PendingFsyncEntry *)hash_search(u_sess->storage_cxt.pendingOps, (void *)ftag, HASH_ENTER, &found); /* if new entry, initialize it */ if (!found) { entry->cycle_ctr = u_sess->storage_cxt.sync_cycle_ctr; entry->canceled = false; - entry->tag = *ftag; // ZheapTodo initialize tag first + entry->tag = *ftag; // ZheapTodo initialize tag first } /* @@ -594,8 +595,7 @@ static bool ForwardSyncRequest(const FileTag *ftag, SyncRequestType type) ret = CkptForwardSyncRequest(ftag, type); break; default: - ereport(ERROR, - (errmsg("Incremental ckpt, Error SyncRequestType, the type is %d", type))); + ereport(ERROR, (errmsg("Incremental ckpt, Error SyncRequestType, the type is %d", type))); break; } } @@ -672,7 +672,7 @@ void ForgetDatabaseSyncRequests(Oid dbid) /* * We need two tags to forget two kinds of fsync requests generated by segment store and heap store respectively */ - for (int i=0; iincre_ckpt_sync_shmem; /* Initialize skip_slot array */ - skip_slot = (bool*)palloc0(sizeof(bool) * incre_ckpt_sync_shmem->num_requests); + skip_slot = (bool *)palloc0(sizeof(bool) * incre_ckpt_sync_shmem->num_requests); /* must hold the request queue in exclusive mode */ Assert(LWLockHeldByMe(incre_ckpt_sync_shmem->sync_queue_lwlock)); @@ -710,29 +710,27 @@ static bool CompactPageWriterRequestQueue(void) incre_ckpt_sync_shmem->requests[preserve_count++] = incre_ckpt_sync_shmem->requests[n]; } - ereport(DEBUG1, - (errmsg("pagewriter compacted fsync request queue from %d entries to %d entries", - incre_ckpt_sync_shmem->num_requests, preserve_count))); + ereport(DEBUG1, (errmsg("pagewriter compacted fsync request queue from %d entries to %d entries", + incre_ckpt_sync_shmem->num_requests, preserve_count))); incre_ckpt_sync_shmem->num_requests = preserve_count; pfree(skip_slot); return true; } - /* PgwrForwardSyncRequest +/* PgwrForwardSyncRequest * Forward a file-fsync request from a backend to the pagewriter. */ bool PgwrForwardSyncRequest(const FileTag *ftag, SyncRequestType type) { - CheckpointerRequest* request = NULL; + CheckpointerRequest *request = NULL; bool too_full = false; IncreCkptSyncShmemStruct *incre_ckpt_sync_shmem = g_instance.ckpt_cxt_ctl->incre_ckpt_sync_shmem; LWLock *sync_queue_lwlock = incre_ckpt_sync_shmem->sync_queue_lwlock; if (AmPageWriterMainProcess()) { - ereport(ERROR, - (errcode(ERRCODE_WITH_CHECK_OPTION_VIOLATION), - errmsg("PgwrForwardSyncRequest must not be called in pagewriter main thread"))); + ereport(ERROR, (errcode(ERRCODE_WITH_CHECK_OPTION_VIOLATION), + errmsg("PgwrForwardSyncRequest must not be called in pagewriter main thread"))); } LWLockAcquire(sync_queue_lwlock, LW_EXCLUSIVE); @@ -742,8 +740,9 @@ bool PgwrForwardSyncRequest(const FileTag *ftag, SyncRequestType type) * backend will have to perform its own fsync request. But before forcing * that to happen, we can try to compact the request queue. */ - if (incre_ckpt_sync_shmem->pagewritermain_pid == 0 || (incre_ckpt_sync_shmem->num_requests >= - incre_ckpt_sync_shmem->max_requests && !CompactPageWriterRequestQueue())) { + if (incre_ckpt_sync_shmem->pagewritermain_pid == 0 || + (incre_ckpt_sync_shmem->num_requests >= incre_ckpt_sync_shmem->max_requests && + !CompactPageWriterRequestQueue())) { LWLockRelease(sync_queue_lwlock); return false; } @@ -771,8 +770,8 @@ bool PgwrForwardSyncRequest(const FileTag *ftag, SyncRequestType type) */ void PgwrAbsorbFsyncRequests(void) { - CheckpointerRequest* requests = NULL; - CheckpointerRequest* request = NULL; + CheckpointerRequest *requests = NULL; + CheckpointerRequest *request = NULL; IncreCkptSyncShmemStruct *incre_ckpt_sync_shmem = g_instance.ckpt_cxt_ctl->incre_ckpt_sync_shmem; LWLock *sync_queue_lwlock = incre_ckpt_sync_shmem->sync_queue_lwlock; int n; @@ -795,9 +794,9 @@ void PgwrAbsorbFsyncRequests(void) if (n > 0) { errno_t rc; - requests = (CheckpointerRequest*)palloc(n * sizeof(CheckpointerRequest)); + requests = (CheckpointerRequest *)palloc(n * sizeof(CheckpointerRequest)); rc = memcpy_s(requests, n * sizeof(CheckpointerRequest), incre_ckpt_sync_shmem->requests, - n * sizeof(CheckpointerRequest)); + n * sizeof(CheckpointerRequest)); securec_check(rc, "\0", "\0"); } @@ -820,7 +819,7 @@ void PgwrAbsorbFsyncRequests(void) */ void PageWriterSyncWithAbsorption(void) { - volatile IncreCkptSyncShmemStruct* cps = g_instance.ckpt_cxt_ctl->incre_ckpt_sync_shmem; + volatile IncreCkptSyncShmemStruct *cps = g_instance.ckpt_cxt_ctl->incre_ckpt_sync_shmem; SpinLockAcquire(&cps->sync_lock); cps->fsync_start++; @@ -833,10 +832,10 @@ void PageWriterSyncWithAbsorption(void) SpinLockRelease(&cps->sync_lock); } -const int WAIT_THREAD_START = 600; /* every time sleep 0.1 sec, the 1min = 600 * 0.1 sec */ +const int WAIT_THREAD_START = 600; /* every time sleep 0.1 sec, the 1min = 600 * 0.1 sec */ void RequestPgwrSync(void) { - volatile IncreCkptSyncShmemStruct* cps = g_instance.ckpt_cxt_ctl->incre_ckpt_sync_shmem; + volatile IncreCkptSyncShmemStruct *cps = g_instance.ckpt_cxt_ctl->incre_ckpt_sync_shmem; /* * Send signal to request sync. It's possible that the pagewriter main thread @@ -855,9 +854,8 @@ void RequestPgwrSync(void) } else if (gs_signal_send(cps->pagewritermain_pid, SIGINT) != 0) { /* max wait 1min */ if (ntries >= WAIT_THREAD_START) { - ereport(LOG, - (errmsg("could not signal for pagewriter main thread: %m, thread pid is %lu", - cps->pagewritermain_pid))); + ereport(LOG, (errmsg("could not signal for pagewriter main thread: %m, thread pid is %lu", + cps->pagewritermain_pid))); break; } } else { @@ -887,7 +885,7 @@ void PageWriterSync(void) int64 old_fsync_start = 0; int64 new_fsync_start = 0; int64 new_fsync_done = 0; - volatile IncreCkptSyncShmemStruct* cps = g_instance.ckpt_cxt_ctl->incre_ckpt_sync_shmem; + volatile IncreCkptSyncShmemStruct *cps = g_instance.ckpt_cxt_ctl->incre_ckpt_sync_shmem; SpinLockAcquire(&cps->sync_lock); old_fsync_start = cps->fsync_start; SpinLockRelease(&cps->sync_lock); -- 2.34.1 From 365721c3c072978accf432152f2b9e123803a02e Mon Sep 17 00:00:00 2001 From: nuoya <1204149038@qq.com> Date: Mon, 25 Sep 2023 15:34:53 +0800 Subject: [PATCH 20/50] Update tcap_drop.cpp --- src/gausskernel/storage/tcap/tcap_drop.cpp | 3058 +++++++++++--------- 1 file changed, 1731 insertions(+), 1327 deletions(-) diff --git a/src/gausskernel/storage/tcap/tcap_drop.cpp b/src/gausskernel/storage/tcap/tcap_drop.cpp index a6cb8515c..2ca5a1e0c 100644 --- a/src/gausskernel/storage/tcap/tcap_drop.cpp +++ b/src/gausskernel/storage/tcap/tcap_drop.cpp @@ -1,1327 +1,1731 @@ -/* - * Copyright (c) Huawei Technologies Co., Ltd. 2020-2020. All rights reserved. - * - * 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. - * --------------------------------------------------------------------------------------- - * - * tcap_drop.cpp - * Routines to support Timecapsule `Recyclebin-based query, restore`. - * We use Tr prefix to indicate it in following coding. - * - * IDENTIFICATION - * src/gausskernel/storage/tcap/tcap_drop.cpp - * - * --------------------------------------------------------------------------------------- - */ - -#include "postgres.h" - -#include "pgstat.h" -#include "access/reloptions.h" -#include "access/sysattr.h" -#include "access/xlog.h" -#include "catalog/dependency.h" -#include "catalog/heap.h" -#include "catalog/index.h" -#include "catalog/indexing.h" -#include "catalog/objectaccess.h" -#include "catalog/pg_collation_fn.h" -#include "catalog/pg_collation.h" -#include "catalog/pg_constraint.h" -#include "catalog/pg_conversion_fn.h" -#include "catalog/pg_conversion.h" -#include "catalog/pg_depend.h" -#include "catalog/pg_extension_data_source.h" -#include "catalog/pg_extension.h" -#include "catalog/pg_foreign_data_wrapper.h" -#include "catalog/pg_foreign_server.h" -#include "catalog/pg_job.h" -#include "catalog/pg_language.h" -#include "catalog/pg_largeobject.h" -#include "catalog/pg_object.h" -#include "catalog/pg_opclass.h" -#include "catalog/pg_operator.h" -#include "catalog/pg_opfamily.h" -#include "catalog/pg_proc.h" -#include "catalog/pg_recyclebin.h" -#include "catalog/pg_rewrite.h" -#include "catalog/pg_rlspolicy.h" -#include "catalog/pg_synonym.h" -#include "catalog/pg_tablespace.h" -#include "catalog/pg_trigger.h" -#include "catalog/pg_ts_config.h" -#include "catalog/pg_ts_dict.h" -#include "catalog/pg_ts_parser.h" -#include "catalog/pg_ts_template.h" -#include "catalog/pgxc_class.h" -#include "catalog/storage.h" -#include "commands/comment.h" -#include "commands/dbcommands.h" -#include "commands/directory.h" -#include "commands/extension.h" -#include "commands/proclang.h" -#include "commands/schemacmds.h" -#include "commands/seclabel.h" -#include "commands/sec_rls_cmds.h" -#include "commands/tablecmds.h" -#include "commands/tablespace.h" -#include "commands/trigger.h" -#include "commands/typecmds.h" -#include "executor/node/nodeModifyTable.h" -#include "rewrite/rewriteRemove.h" -#include "storage/lmgr.h" -#include "storage/predicate.h" -#include "storage/smgr/relfilenode.h" -#include "utils/acl.h" -#include "utils/builtins.h" -#include "utils/fmgroids.h" -#include "utils/inval.h" -#include "utils/lsyscache.h" -#include "utils/relcache.h" -#include "utils/snapmgr.h" -#include "utils/syscache.h" - -#include "storage/tcap.h" -#include "storage/tcap_impl.h" - -static void TrRenameClass(TrObjDesc *baseDesc, ObjectAddress *object, const char *newName) -{ - Relation rel; - HeapTuple tup; - HeapTuple newtup; - char rbname[NAMEDATALEN]; - Datum values[Natts_pg_class] = { 0 }; - bool nulls[Natts_pg_class] = { false }; - bool replaces[Natts_pg_class] = { false }; - Oid relid = object->objectId; - errno_t rc = EOK; - - if (newName) { - rc = strncpy_s(rbname, NAMEDATALEN, newName, strlen(newName)); - securec_check(rc, "\0", "\0"); - } else { - TrGenObjName(rbname, object->classId, relid); - } - - replaces[Anum_pg_class_relname - 1] = true; - values[Anum_pg_class_relname - 1] = CStringGetDatum(rbname); - - rel = heap_open(RelationRelationId, RowExclusiveLock); - - tup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); - if (!HeapTupleIsValid(tup)) { - ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for relation %u", relid))); - } - - newtup = heap_modify_tuple(tup, RelationGetDescr(rel), values, nulls, replaces); - - simple_heap_update(rel, &newtup->t_self, newtup); - - CatalogUpdateIndexes(rel, newtup); - - ReleaseSysCache(tup); - - heap_freetuple_ext(newtup); - - heap_close(rel, RowExclusiveLock); -} - -static void TrRenameCommon(TrObjDesc *baseDesc, ObjectAddress *object, Oid relid, int natts, int oidAttrNum, - Oid oidIndexId, char *objTag) -{ - Relation rel; - HeapTuple tup; - HeapTuple newtup; - char rbname[NAMEDATALEN]; - Datum *values = (Datum *)palloc0(sizeof(Datum) * natts); - bool *nulls = (bool *)palloc0(sizeof(bool) * natts); - bool *replaces = (bool *)palloc0(sizeof(bool) * natts); - ScanKeyData skey[1]; - SysScanDesc sd; - - TrGenObjName(rbname, object->classId, object->objectId); - - replaces[oidAttrNum - 1] = true; - values[oidAttrNum - 1] = CStringGetDatum(rbname); - - rel = heap_open(relid, RowExclusiveLock); - - ScanKeyInit(&skey[0], ObjectIdAttributeNumber, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(object->objectId)); - sd = systable_beginscan(rel, oidIndexId, true, NULL, 1, skey); - - tup = systable_getnext(sd); - if (!HeapTupleIsValid(tup)) { - pfree(values); - pfree(nulls); - pfree(replaces); - ereport(ERROR, - (errcode(ERRCODE_NO_DATA_FOUND), errmsg("could not find tuple for %s %u", objTag, object->objectId))); - } - - newtup = heap_modify_tuple(tup, RelationGetDescr(rel), values, nulls, replaces); - - simple_heap_update(rel, &newtup->t_self, newtup); - - CatalogUpdateIndexes(rel, newtup); - - heap_freetuple_ext(newtup); - - systable_endscan(sd); - - heap_close(rel, RowExclusiveLock); - - pfree(values); - pfree(nulls); - pfree(replaces); -} - -static void TrDeleteBaseid(Oid baseid) -{ - Relation rbRel; - SysScanDesc sd; - ScanKeyData skey[1]; - HeapTuple tup; - - rbRel = heap_open(RecyclebinRelationId, RowExclusiveLock); - - ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcybaseid, BTEqualStrategyNumber, F_INT8EQ, ObjectIdGetDatum(baseid)); - - sd = systable_beginscan(rbRel, RecyclebinBaseidIndexId, true, NULL, 1, skey); - while (HeapTupleIsValid(tup = systable_getnext(sd))) { - simple_heap_delete(rbRel, &tup->t_self); - } - - systable_endscan(sd); - heap_close(rbRel, RowExclusiveLock); - - /* - * CommandCounterIncrement here to ensure that preceding changes are all - * visible to the next deletion step. - */ - CommandCounterIncrement(); -} - -static void TrDeleteId(Oid id) -{ - Relation rbRel; - SysScanDesc sd; - ScanKeyData skey[1]; - HeapTuple tup; - - rbRel = heap_open(RecyclebinRelationId, RowExclusiveLock); - - ScanKeyInit(&skey[0], ObjectIdAttributeNumber, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(id)); - - sd = systable_beginscan(rbRel, RecyclebinIdIndexId, true, NULL, 1, skey); - if (HeapTupleIsValid(tup = systable_getnext(sd))) { - simple_heap_delete(rbRel, &tup->t_self); - } - - systable_endscan(sd); - heap_close(rbRel, RowExclusiveLock); - - /* - * CommandCounterIncrement here to ensure that preceding changes are all - * visible to the next deletion step. - */ - CommandCounterIncrement(); -} - -static inline bool TrNeedLogicDrop(const ObjectAddress *object) -{ - return object->rbDropMode == RB_DROP_MODE_LOGIC; -} - -static bool TrCanPurge(const TrObjDesc *baseDesc, const ObjectAddress *object, char relKind) -{ - Relation depRel; - SysScanDesc sd; - HeapTuple tuple; - ScanKeyData key[3]; - int nkeys; - bool found = false; - - if (relKind != RELKIND_INDEX && relKind != RELKIND_GLOBAL_INDEX && relKind != RELKIND_RELATION) { - return false; - } - - depRel = heap_open(DependRelationId, AccessShareLock); - - ScanKeyInit(&key[0], Anum_pg_depend_classid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(object->classId)); - ScanKeyInit(&key[1], Anum_pg_depend_objid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(object->objectId)); - nkeys = 2; - if (object->objectSubId != 0) { - ScanKeyInit(&key[2], Anum_pg_depend_objsubid, BTEqualStrategyNumber, F_INT4EQ, - Int32GetDatum(object->objectSubId)); - nkeys = 3; - } - - sd = systable_beginscan(depRel, DependDependerIndexId, true, NULL, nkeys, key); - while (HeapTupleIsValid(tuple = systable_getnext(sd))) { - Form_pg_depend depForm = (Form_pg_depend)GETSTRUCT(tuple); - if (depForm->refclassid == RelationRelationId && depForm->refobjid == baseDesc->relid) { - if (depForm->deptype != DEPENDENCY_AUTO) { - found = false; - break; - } - found = true; - } - } - - systable_endscan(sd); - heap_close(depRel, AccessShareLock); - return found; -} - -static void TrDoDropIndex(TrObjDesc *baseDesc, ObjectAddress *object) -{ - Assert(object->objectSubId == 0); - - if (TrNeedLogicDrop(object)) { - TrObjDesc desc = *baseDesc; - - if (!TR_IS_BASE_OBJ(baseDesc, object)) { - /* Deletion lock already accquired before single object drop. */ - Relation rel = relation_open(object->objectId, NoLock); - - TrDescInit(rel, &desc, RB_OPER_DROP, TrGetObjType(RelationGetNamespace(rel), RELKIND_INDEX), - TrCanPurge(baseDesc, object, RelationGetRelkind(rel))); - relation_close(rel, NoLock); - - TrDescWrite(&desc); - } - - TrRenameClass(baseDesc, object, desc.name); - } else { - index_drop(object->objectId, false); - } - - return; -} - -static void TrDoDropTable(TrObjDesc *baseDesc, ObjectAddress *object, char relKind) -{ - if (TrNeedLogicDrop(object)) { - TrObjDesc desc; - - if (object->objectSubId != 0 || relKind == RELKIND_VIEW || relKind == RELKIND_COMPOSITE_TYPE || - relKind == RELKIND_FOREIGN_TABLE) { - TrRenameClass(baseDesc, object, NULL); - return; - } - - desc = *baseDesc; - if (!TR_IS_BASE_OBJ(baseDesc, object)) { - /* Deletion lock already accquired before single object drop. */ - Relation rel = relation_open(object->objectId, NoLock); - - TrDescInit(rel, &desc, RB_OPER_DROP, TrGetObjType(InvalidOid, relKind), - TrCanPurge(baseDesc, object, relKind)); - relation_close(rel, NoLock); - - TrDescWrite(&desc); - } - - TrRenameClass(baseDesc, object, desc.name); - - return; - } - - /* - * relation_open() must be before the heap_drop_with_catalog(). If you reload - * relation after drop, it may cause other exceptions during the drop process. - */ - if (object->objectSubId != 0) - RemoveAttributeById(object->objectId, object->objectSubId); - else - heap_drop_with_catalog(object->objectId); - - /* - * IMPORANT: The relation must not be reloaded after heap_drop_with_catalog() - * is executed to drop this relation.If you reload relation after drop, it may - * cause other exceptions during the drop process - */ - - return; -} - -static void TrDoDropType(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - TrRenameCommon(baseDesc, object, TypeRelationId, Natts_pg_type, Anum_pg_type_typname, TypeOidIndexId, "type"); - } else { - RemoveTypeById(object->objectId); - } - - return; -} - -static void TrDoDropConstraint(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - TrRenameCommon(baseDesc, object, ConstraintRelationId, Natts_pg_constraint, Anum_pg_constraint_conname, - ConstraintOidIndexId, "constraint"); - } else { - RemoveConstraintById(object->objectId); - } - - return; -} - -static void TrDoDropTrigger(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - TrRenameCommon(baseDesc, object, TriggerRelationId, Natts_pg_trigger, Anum_pg_trigger_tgname, TriggerOidIndexId, - "trigger"); - } else { - RemoveTriggerById(object->objectId); - } - - return; -} - -static void TrDoDropRewrite(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - /* nothing to do as rule-based view requires that origin rule name preserved. */ - } else { - RemoveRewriteRuleById(object->objectId); - } - - return; -} - -static void TrDoDropAttrdef(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - /* nothing to do as no name attribute in system catalog */ - } else { - RemoveAttrDefaultById(object->objectId); - } - - return; -} - -static void TrDoDropProc(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - TrRenameCommon(baseDesc, object, ProcedureRelationId, Natts_pg_proc, Anum_pg_proc_proname, ProcedureOidIndexId, - "procedure"); - } else { - RemoveFunctionById(object->objectId); - } - - return; -} - -static void TrDoDropCast(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - /* nothing to do as no name attribute in system catalog */ - } else { - DropCastById(object->objectId); - } - - return; -} - -static void TrDoDropCollation(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - TrRenameCommon(baseDesc, object, CollationRelationId, Natts_pg_collation, Anum_pg_collation_collname, - CollationOidIndexId, "collation"); - } else { - RemoveCollationById(object->objectId); - } - - return; -} - -static void TrDoDropConversion(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - TrRenameCommon(baseDesc, object, ConversionRelationId, Natts_pg_conversion, Anum_pg_conversion_conname, - ConversionOidIndexId, "conversion"); - } else { - RemoveConversionById(object->objectId); - } - - return; -} - - -static void TrDoDropProceduralLanguage(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - TrRenameCommon(baseDesc, object, LanguageRelationId, Natts_pg_language, Anum_pg_language_lanname, - LanguageOidIndexId, "language"); - } else { - DropProceduralLanguageById(object->objectId); - } - - return; -} - -static void TrDoDropLargeObject(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - /* nothing to do as no name attribute in system catalog */ - } else { - LargeObjectDrop(object->objectId); - } - - return; -} - -static void TrDoDropOperator(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - TrRenameCommon(baseDesc, object, OperatorRelationId, Natts_pg_operator, Anum_pg_operator_oprname, - OperatorOidIndexId, "operator"); - } else { - RemoveOperatorById(object->objectId); - } - - return; -} - -static void TrDoDropOpClass(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - TrRenameCommon(baseDesc, object, OperatorClassRelationId, Natts_pg_opclass, Anum_pg_opclass_opcname, - OpclassOidIndexId, "opclass"); - } else { - RemoveOpClassById(object->objectId); - } - - return; -} - -static void TrDoDropOpFamily(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - TrRenameCommon(baseDesc, object, OperatorFamilyRelationId, Natts_pg_opfamily, Anum_pg_opfamily_opfname, - OpfamilyOidIndexId, "opfamily"); - } else { - RemoveOpFamilyById(object->objectId); - } - - return; -} - - -static void TrDoDropAmOp(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - /* nothing to do as no name attribute in system catalog */ - } else { - RemoveAmOpEntryById(object->objectId); - } - - return; -} - -static void TrDoDropAmProc(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - /* nothing to do as no name attribute in system catalog */ - } else { - RemoveAmProcEntryById(object->objectId); - } - - return; -} - -static void TrDoDropSchema(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - TrRenameCommon(baseDesc, object, NamespaceRelationId, Natts_pg_namespace, Anum_pg_namespace_nspname, - NamespaceOidIndexId, "namespace"); - } else { - RemoveSchemaById(object->objectId); - } - - return; -} - -static void TrDoDropTSParser(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - TrRenameCommon(baseDesc, object, TSParserRelationId, Natts_pg_ts_parser, Anum_pg_ts_parser_prsname, - TSParserOidIndexId, "ts parser"); - } else { - RemoveTSParserById(object->objectId); - } - - return; -} - -static void TrDoDropTSDictionary(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - TrRenameCommon(baseDesc, object, TSDictionaryRelationId, Natts_pg_ts_dict, Anum_pg_ts_dict_dictname, - TSDictionaryOidIndexId, "ts dictionary"); - } else { - RemoveTSDictionaryById(object->objectId); - } - - return; -} - -static void TrDoDropTSTemplate(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - TrRenameCommon(baseDesc, object, TSTemplateRelationId, Natts_pg_ts_template, Anum_pg_ts_template_tmplname, - TSTemplateOidIndexId, "ts template"); - } else { - RemoveTSTemplateById(object->objectId); - } - - return; -} - -static void TrDoDropTSConfiguration(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - TrRenameCommon(baseDesc, object, TSConfigRelationId, Natts_pg_ts_config, Anum_pg_ts_config_cfgname, - TSConfigOidIndexId, "ts configuration"); - } else { - RemoveTSConfigurationById(object->objectId); - } - - return; -} - -static void TrDoDropForeignDataWrapper(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - TrRenameCommon(baseDesc, object, ForeignDataWrapperRelationId, Natts_pg_foreign_data_wrapper, - Anum_pg_foreign_data_wrapper_fdwname, ForeignDataWrapperOidIndexId, "foreign data wrapper"); - } else { - RemoveForeignDataWrapperById(object->objectId); - } - - return; -} - -static void TrDoDropForeignServer(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - TrRenameCommon(baseDesc, object, ForeignServerRelationId, Natts_pg_foreign_server, - Anum_pg_foreign_server_srvname, ForeignServerOidIndexId, "foreign server"); - } else { - RemoveForeignServerById(object->objectId); - } - - return; -} - -static void TrDoDropUserMapping(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - /* nothing to do as no name attribute in system catalog */ - } else { - RemoveUserMappingById(object->objectId); - } - - return; -} - - -static void TrDoDropDefaultACL(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - /* nothing to do as no name attribute in system catalog */ - } else { - RemoveDefaultACLById(object->objectId); - } - - return; -} - -static void TrDoDropPgxcClass(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - /* nothing to do as no name attribute in system catalog */ - } else { - RemovePgxcClass(object->objectId); - } - - return; -} - -static void TrDoDropExtension(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - TrRenameCommon(baseDesc, object, ExtensionRelationId, Natts_pg_extension, Anum_pg_extension_extname, - ExtensionOidIndexId, "extension"); - } else { - RemoveExtensionById(object->objectId); - } - - return; -} - -static void TrDoDropDataSource(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - TrRenameCommon(baseDesc, object, DataSourceRelationId, Natts_pg_extension_data_source, - Anum_pg_extension_data_source_srcname, DataSourceOidIndexId, "extension data source"); - } else { - RemoveDataSourceById(object->objectId); - } - - return; -} - -static void TrDoDropDirectory(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - TrRenameCommon(baseDesc, object, PgDirectoryRelationId, Natts_pg_directory, Anum_pg_directory_directory_name, - PgDirectoryOidIndexId, "directory"); - } else { - RemoveDirectoryById(object->objectId); - } - - return; -} - -static void TrDoDropRlsPolicy(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - TrRenameCommon(baseDesc, object, RlsPolicyRelationId, Natts_pg_rlspolicy, Anum_pg_rlspolicy_polname, - PgRlspolicyOidIndex, "rlspolicy"); - } else { - RemoveRlsPolicyById(object->objectId); - } - - return; -} - -static void TrDoDropJob(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - /* nothing to do as no name attribute in system catalog */ - } else { - RemoveJobById(object->objectId); - } - - return; -} - -static void TrDoDropSynonym(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - TrRenameCommon(baseDesc, object, PgSynonymRelationId, Natts_pg_synonym, Anum_pg_synonym_synname, - SynonymOidIndexId, "synonym"); - } else { - RemoveSynonymById(object->objectId); - } - - return; -} - -/* - * doDeletion: delete a single object - * return false if logic deleted, - * return true if physical deleted, - */ -static void TrDoDrop(TrObjDesc *baseDesc, ObjectAddress *object) -{ - switch (getObjectClass(object)) { - case OCLASS_CLASS: { - char relKind = get_rel_relkind(object->objectId); - if (relKind == RELKIND_INDEX) { - TrDoDropIndex(baseDesc, object); - } else { - /* - * We use a unified entry for others: - * RELKIND_RELATION, RELKIND_SEQUENCE, - * RELKIND_TOASTVALUE, RELKIND_VIEW, - * RELKIND_COMPOSITE_TYPE, RELKIND_FOREIGN_TABLE - */ - TrDoDropTable(baseDesc, object, relKind); - } - break; - } - - case OCLASS_TYPE: - TrDoDropType(baseDesc, object); - break; - - case OCLASS_CONSTRAINT: - TrDoDropConstraint(baseDesc, object); - break; - - case OCLASS_TRIGGER: - TrDoDropTrigger(baseDesc, object); - break; - - case OCLASS_REWRITE: - TrDoDropRewrite(baseDesc, object); - break; - - case OCLASS_DEFAULT: - TrDoDropAttrdef(baseDesc, object); - break; - - case OCLASS_PROC: - TrDoDropProc(baseDesc, object); - break; - - case OCLASS_CAST: - TrDoDropCast(baseDesc, object); - break; - - case OCLASS_COLLATION: - TrDoDropCollation(baseDesc, object); - break; - - case OCLASS_CONVERSION: - TrDoDropConversion(baseDesc, object); - break; - - case OCLASS_LANGUAGE: - TrDoDropProceduralLanguage(baseDesc, object); - break; - - case OCLASS_LARGEOBJECT: - TrDoDropLargeObject(baseDesc, object); - break; - - case OCLASS_OPERATOR: - TrDoDropOperator(baseDesc, object); - break; - - case OCLASS_OPCLASS: - TrDoDropOpClass(baseDesc, object); - break; - - case OCLASS_OPFAMILY: - TrDoDropOpFamily(baseDesc, object); - break; - - case OCLASS_AMOP: - TrDoDropAmOp(baseDesc, object); - break; - - case OCLASS_AMPROC: - TrDoDropAmProc(baseDesc, object); - break; - - case OCLASS_SCHEMA: - TrDoDropSchema(baseDesc, object); - break; - - case OCLASS_TSPARSER: - TrDoDropTSParser(baseDesc, object); - break; - - case OCLASS_TSDICT: - TrDoDropTSDictionary(baseDesc, object); - break; - - case OCLASS_TSTEMPLATE: - TrDoDropTSTemplate(baseDesc, object); - break; - - case OCLASS_TSCONFIG: - TrDoDropTSConfiguration(baseDesc, object); - break; - - /* - * OCLASS_ROLE, OCLASS_DATABASE, OCLASS_TBLSPACE intentionally not - * handled here - */ - - case OCLASS_FDW: - TrDoDropForeignDataWrapper(baseDesc, object); - break; - - case OCLASS_FOREIGN_SERVER: - TrDoDropForeignServer(baseDesc, object); - break; - - case OCLASS_USER_MAPPING: - TrDoDropUserMapping(baseDesc, object); - break; - - case OCLASS_DEFACL: - TrDoDropDefaultACL(baseDesc, object); - break; - - case OCLASS_PGXC_CLASS: - TrDoDropPgxcClass(baseDesc, object); - break; - - case OCLASS_EXTENSION: - TrDoDropExtension(baseDesc, object); - break; - - case OCLASS_DATA_SOURCE: - TrDoDropDataSource(baseDesc, object); - break; - - case OCLASS_DIRECTORY: - TrDoDropDirectory(baseDesc, object); - break; - - case OCLASS_RLSPOLICY: - TrDoDropRlsPolicy(baseDesc, object); - break; - - case OCLASS_PG_JOB: - if ((IS_PGXC_COORDINATOR && !IsConnFromCoord()) || (g_instance.role == VSINGLENODE)) - TrDoDropJob(baseDesc, object); - break; - - case OCLASS_SYNONYM: - TrDoDropSynonym(baseDesc, object); - break; - - default: - ereport(ERROR, - (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE), errmsg("unrecognized object class: %u", object->classId))); - break; - } - - return; -} - -/* - * deleteOneObject: delete a single object for TrDrop. - * - * *depRel is the already-open pg_depend relation. - */ -static void TrDropOneObject(TrObjDesc *baseDesc, ObjectAddress *object, Relation *depRel) -{ - ScanKeyData key[3]; - int nkeys; - SysScanDesc scan; - HeapTuple tup; - - /* DROP hook of the objects being removed */ - if (object_access_hook) { - ObjectAccessDrop dropArg; - - dropArg.dropflags = PERFORM_DELETION_INVALID; - InvokeObjectAccessHook(OAT_DROP, object->classId, object->objectId, object->objectSubId, &dropArg); - } - - /* - * Delete the object itself, in an object-type-dependent way. - * - * We used to do this after removing the outgoing dependency links, but it - * seems just as reasonable to do it beforehand. In the concurrent case - * we *must *do it in this order, because we can't make any transactional - * updates before calling doDeletion() --- they'd get committed right - * away, which is not cool if the deletion then fails. - */ - TrDoDrop(baseDesc, object); - - /* - * In logical drop mode, we will keep all related system entries, including - * linked entries such as pg_depend records. It is done! - */ - if (TrNeedLogicDrop(object)) { - /* - * CommandCounterIncrement here to ensure that preceding changes are all - * visible to the next deletion step. - */ - CommandCounterIncrement(); - - /* - * Logic Drop done! - */ - return; - } - - /* - * In physical drop mode, we continue to remove all related system entries. - */ - - /* - * Now remove any pg_depend records that link from this object to others. - * (Any records linking to this object should be gone already.) - * - * When dropping a whole object (subId = 0), remove all pg_depend records - * for its sub-objects too. - */ - ScanKeyInit(&key[0], Anum_pg_depend_classid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(object->classId)); - ScanKeyInit(&key[1], Anum_pg_depend_objid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(object->objectId)); - if (object->objectSubId != 0) { - ScanKeyInit(&key[2], Anum_pg_depend_objsubid, BTEqualStrategyNumber, F_INT4EQ, - Int32GetDatum(object->objectSubId)); - nkeys = 3; - } else - nkeys = 2; - - scan = systable_beginscan(*depRel, DependDependerIndexId, true, NULL, nkeys, key); - - while (HeapTupleIsValid(tup = systable_getnext(scan))) { - simple_heap_delete(*depRel, &tup->t_self); - } - - systable_endscan(scan); - - /* - * Delete shared dependency references related to this object. Again, if - * subId = 0, remove records for sub-objects too. - */ - deleteSharedDependencyRecordsFor(object->classId, object->objectId, object->objectSubId); - - /* - * Delete any comments or security labels associated with this object. - * (This is a convenient place to do these things, rather than having - * every object type know to do it.) - */ - DeleteComments(object->objectId, object->classId, object->objectSubId); - DeleteSecurityLabel(object); - - /* - * CommandCounterIncrement here to ensure that preceding changes are all - * visible to the next deletion step. - */ - CommandCounterIncrement(); - - /* - * Physical Drop done! - */ -} - -static bool TrObjIsInList(const ObjectAddresses *targetObjects, const ObjectAddress *thisobj) -{ - ObjectAddress *item = NULL; - - for (int i = 0; i < targetObjects->numrefs; i++) { - item = targetObjects->refs + i; - if (TrObjIsEqual(thisobj, item)) { - return true; - } - } - return false; -} - -static ObjectAddress *TrFindIdxInTarget(ObjectAddresses *targetObjects, ObjectAddress *item) -{ - ObjectAddress *thisobj = NULL; - - for (int i = 0; i < targetObjects->numrefs; i++) { - thisobj = targetObjects->refs + i; - if (TrObjIsEqual(item, thisobj)) { - return thisobj; - } - } - - return NULL; -} - -/* - * output: refthisobjs - */ -static void TrFindAllSubObjs(Relation depRel, const ObjectAddress *refobj, ObjectAddresses *refthisobjs) -{ - SysScanDesc sd; - HeapTuple tuple; - ScanKeyData key[3]; - int nkeys; - - ScanKeyInit(&key[0], Anum_pg_depend_refclassid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(refobj->classId)); - ScanKeyInit(&key[1], Anum_pg_depend_refobjid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(refobj->objectId)); - nkeys = 2; - if (refobj->objectSubId != 0) { - ScanKeyInit(&key[2], Anum_pg_depend_refobjsubid, BTEqualStrategyNumber, F_INT4EQ, - Int32GetDatum(refobj->objectSubId)); - nkeys = 3; - } - - sd = systable_beginscan(depRel, DependReferenceIndexId, true, NULL, nkeys, key); - while (HeapTupleIsValid(tuple = systable_getnext(sd))) { - Form_pg_depend depForm = (Form_pg_depend)GETSTRUCT(tuple); - - /* add the refs to list */ - add_object_address_ext(depForm->classid, depForm->objid, depForm->objsubid, depForm->deptype, refthisobjs); - } - - systable_endscan(sd); - return; -} - -static void TrTagPhyDeleteSubObjs(Relation depRel, ObjectAddresses *targetObjects, ObjectAddress *thisobj) -{ - ObjectAddress *item = NULL; - - ObjectAddresses *refthisobjs = new_object_addresses(); - - /* Tag this obj RB_DROP_MODE_PHYSICAL */ - thisobj->rbDropMode = RB_DROP_MODE_PHYSICAL; - - /* Find all sub objs refered to this obj */ - TrFindAllSubObjs(depRel, thisobj, refthisobjs); - - for (int i = 0; i < refthisobjs->numrefs; i++) { - item = refthisobjs->refs + i; - - /* the item must exists in targetObjects. */ - item = TrFindIdxInTarget(targetObjects, item); - if (item == NULL || item->rbDropMode == RB_DROP_MODE_PHYSICAL) { - continue; - } - TrTagPhyDeleteSubObjs(depRel, targetObjects, item); - } - - free_object_addresses(refthisobjs); - return; -} - -static bool TrNeedPhyDelete(Relation depRel, ObjectAddresses *targetObjects, ObjectAddress *thisobj) -{ - ObjectAddress *item = NULL; - ObjectAddresses *refobjs = new_object_addresses(); - bool result = false; - - /* Find all objs this obj refered */ - TrFindAllRefObjs(depRel, thisobj, refobjs); - - /* Step 1: tag refobjs of thisobj, return directly if ALL refobjs not need physical drop. */ - for (int i = 0; i < refobjs->numrefs; i++) { - item = refobjs->refs + i; - if (!TrObjIsInList(targetObjects, item)) { - result = true; - break; - } - } - if (!result) { - free_object_addresses(refobjs); - return result; - } - - /* Step 2: tag refobjs with 'i' deptype to physical drop. */ - for (int i = 0; i < refobjs->numrefs; i++) { - item = refobjs->refs + i; - if (item->deptype == 'i') { - item = TrFindIdxInTarget(targetObjects, item); - Assert(item != NULL); - if (item->rbDropMode == RB_DROP_MODE_PHYSICAL) { - continue; - } - TrTagPhyDeleteSubObjs(depRel, targetObjects, item); - } - } - - free_object_addresses(refobjs); - return result; -} - -static void TrResetDropMode(const ObjectAddresses *targetObjects, const ObjectAddress *baseObj) -{ - ObjectAddress *thisobj = NULL; - - for (int i = 0; i < targetObjects->numrefs; i++) { - thisobj = targetObjects->refs + i; - if (TrObjIsEqual(thisobj, baseObj)) { - thisobj->rbDropMode = RB_DROP_MODE_LOGIC; - continue; - } - thisobj->rbDropMode = RB_DROP_MODE_INVALID; - } - return; -} - -static void TrTagDependentObjects(Relation depRel, ObjectAddresses *targetObjects, const ObjectAddress *baseObj) -{ - ObjectAddress *thisobj = NULL; - - TrResetDropMode(targetObjects, baseObj); - for (int i = 0; i < targetObjects->numrefs; i++) { - thisobj = targetObjects->refs + i; - if (TrDropModeIsAlreadySet(thisobj)) { - continue; - } - - if (TrNeedPhyDelete(depRel, targetObjects, thisobj)) { - TrTagPhyDeleteSubObjs(depRel, targetObjects, thisobj); - } else { - thisobj->rbDropMode = RB_DROP_MODE_LOGIC; - } - } - - return; -} - -bool TrCheckRecyclebinDrop(const DropStmt *stmt, ObjectAddresses *objects) -{ - Relation depRel; - bool rbDrop = false; - - /* No work if no objects... */ - if (objects->numrefs <= 0) - return false; - - if (/* - * Disable Recyclebin-based-Drop when target object is not OBJECT_TABLE, or - */ - stmt->removeType != OBJECT_TABLE || - /* in concurrent drop mode, or */ - stmt->concurrent || - /* with purge option, or */ - stmt->purge || - /* multi objects drop. */ - list_length(stmt->objects) != 1) { - return false; - } - - if (!NeedTrComm(objects->refs->objectId)) { - return false; - } - - depRel = heap_open(DependRelationId, AccessShareLock); - rbDrop = !TrNeedPhyDelete(depRel, objects, &objects->refs[0]); - heap_close(depRel, AccessShareLock); - - return rbDrop; -} - -void TrDrop(const DropStmt* drop, const ObjectAddresses *objects, DropBehavior behavior) -{ - Relation depRel; - Relation baseRel; - TrObjDesc baseDesc; - ObjectAddresses *targetObjects = NULL; - ObjectAddress *baseObj = objects->refs; - - /* - * We save some cycles by opening pg_depend just once and passing the - * Relation pointer down to all the recursive deletion steps. - */ - depRel = heap_open(DependRelationId, RowExclusiveLock); - - /* - * Construct a list of objects to delete (ie, the given objects plus - * everything directly or indirectly dependent on them). Note that - * because we pass the whole objects list as pendingObjects context, we - * won't get a failure from trying to delete an object that is internally - * dependent on another one in the list; we'll just skip that object and - * delete it when we reach its owner. - */ - targetObjects = new_object_addresses(); - - /* - * Acquire deletion lock on each target object. (Ideally the caller - * has done this already, but many places are sloppy about it.) - */ - AcquireDeletionLock(baseObj, PERFORM_DELETION_INVALID); - - /* - * Finds all subobjects that reference the base table recursively. - */ - findDependentObjects(baseObj, DEPFLAG_ORIGINAL, NULL, /* empty stack */ - targetObjects, objects, &depRel); - ereport(LOG, (errmsg("Delete object %u/%u/%d", baseObj->classId, baseObj->objectId, baseObj->objectSubId))); - - /* - * Check if deletion is allowed, and report about cascaded deletes. - * - * If there's exactly one object being deleted, report it the same way as - * in performDeletion(), else we have to be vaguer. - */ - reportDependentObjects(targetObjects, behavior, NOTICE, baseObj); - - /* - * Tag all subobjects' drop mode: LOGIC_DROP, PYHSICAL_DROP. - */ - TrTagDependentObjects(depRel, targetObjects, baseObj); - - /* - * Initialize the baseDesc structure so that the logic dropped subobjects - * can be correctly processed when renamed or placed in recycle bin. Notice - * that base object already locked. - */ - baseRel = relation_open(baseObj->objectId, NoLock); - TrDescInit(baseRel, &baseDesc, RB_OPER_DROP, RB_OBJ_TABLE, true, true); - baseDesc.id = baseDesc.baseid = TrDescWrite(&baseDesc); - TrUpdateBaseid(&baseDesc); - relation_close(baseRel, NoLock); - - Oid relid = RelationGetRelid(baseRel); - UpdatePgObjectChangecsn(relid, baseRel->rd_rel->relkind); - - /* - * Drop all the objects in the proper order. - */ - for (int i = 0; i < targetObjects->numrefs; i++) { - ObjectAddress *thisobj = targetObjects->refs + i; - TrDropOneObject(&baseDesc, thisobj, &depRel); - } - - /* And clean up */ - free_object_addresses(targetObjects); - heap_close(depRel, RowExclusiveLock); -} - -void TrDoPurgeObjectDrop(TrObjDesc *desc) -{ - ObjectAddresses *objects; - ObjectAddress obj; - - objects = new_object_addresses(); - - obj.classId = RelationRelationId; - obj.objectId = desc->relid; - obj.objectSubId = 0; - add_exact_object_address(&obj, objects); - - performMultipleDeletions(objects, DROP_CASCADE, PERFORM_DELETION_INVALID); - - if (desc->type == RB_OBJ_TABLE) { - TrDeleteBaseid(desc->baseid); - } else { /* RB_OBJ_INDEX */ - TrDeleteId(desc->id); - } - - free_object_addresses(objects); - return; -} - -/* TIMECAPSULE TABLE { table_name } TO BEFORE DROP [RENAME TO new_tablename] */ -void TrRestoreDrop(const TimeCapsuleStmt *stmt) -{ - TrObjDesc desc; - ObjectAddress obj; - Relation rel; - - desc.relid = 0; - TrOperFetch(stmt->relation, RB_OBJ_TABLE, &desc, RB_OPER_RESTORE_DROP); - if (desc.relid != 0 && (desc.type == RB_OBJ_TABLE)) { - stmt->relation->relname = desc.name; - rel = heap_openrv(stmt->relation, AccessExclusiveLock); - if (rel->rd_tam_type == TAM_HEAP) { - heap_close(rel, NoLock); - elog(ERROR, "timecapsule does not support astore yet"); - return; - } - heap_close(rel, NoLock); - } - - desc.authid = GetUserId(); - TrOperPrep(&desc, RB_OPER_RESTORE_DROP); - - obj.classId = RelationRelationId; - obj.objectId = desc.relid; - obj.objectSubId = 0; - - TrRenameClass(&desc, &obj, stmt->new_relname ? stmt->new_relname : desc.originname); - - TrDeleteBaseid(desc.baseid); - - return; -} +/* + * Copyright (c) Huawei Technologies Co., Ltd. 2020-2020. All rights reserved. + * + * 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. + * --------------------------------------------------------------------------------------- + * + * tcap_drop.cpp + * Routines to support Timecapsule `Recyclebin-based query, restore`. + * We use Tr prefix to indicate it in following coding. + * + * IDENTIFICATION + * src/gausskernel/storage/tcap/tcap_drop.cpp + * + * --------------------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "pgstat.h" +#include "access/reloptions.h" +#include "access/sysattr.h" +#include "access/xlog.h" +#include "catalog/dependency.h" +#include "catalog/heap.h" +#include "catalog/index.h" +#include "catalog/indexing.h" +#include "catalog/objectaccess.h" +#include "catalog/pg_collation_fn.h" +#include "catalog/pg_collation.h" +#include "catalog/pg_constraint.h" +#include "catalog/pg_conversion_fn.h" +#include "catalog/pg_conversion.h" +#include "catalog/pg_depend.h" +#include "catalog/pg_extension_data_source.h" +#include "catalog/pg_extension.h" +#include "catalog/pg_foreign_data_wrapper.h" +#include "catalog/pg_foreign_server.h" +#include "catalog/pg_job.h" +#include "catalog/pg_language.h" +#include "catalog/pg_largeobject.h" +#include "catalog/pg_object.h" +#include "catalog/pg_opclass.h" +#include "catalog/pg_operator.h" +#include "catalog/pg_opfamily.h" +#include "catalog/pg_proc.h" +#include "catalog/pg_recyclebin.h" +#include "catalog/pg_rewrite.h" +#include "catalog/pg_rlspolicy.h" +#include "catalog/pg_synonym.h" +#include "catalog/pg_tablespace.h" +#include "catalog/pg_trigger.h" +#include "catalog/pg_ts_config.h" +#include "catalog/pg_ts_dict.h" +#include "catalog/pg_ts_parser.h" +#include "catalog/pg_ts_template.h" +#include "catalog/pgxc_class.h" +#include "catalog/storage.h" +#include "commands/comment.h" +#include "commands/dbcommands.h" +#include "commands/directory.h" +#include "commands/extension.h" +#include "commands/proclang.h" +#include "commands/schemacmds.h" +#include "commands/seclabel.h" +#include "commands/sec_rls_cmds.h" +#include "commands/tablecmds.h" +#include "commands/tablespace.h" +#include "commands/trigger.h" +#include "commands/typecmds.h" +#include "executor/node/nodeModifyTable.h" +#include "rewrite/rewriteRemove.h" +#include "storage/lmgr.h" +#include "storage/predicate.h" +#include "storage/smgr/relfilenode.h" +#include "utils/acl.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" +#include "utils/inval.h" +#include "utils/lsyscache.h" +#include "utils/relcache.h" +#include "utils/snapmgr.h" +#include "utils/syscache.h" + +#include "storage/tcap.h" +#include "storage/tcap_impl.h" +/* + * 功能该函数用于重命名数据库中的表。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示要重命名的对象的地址信息。 + * newName:const char指针,表示新的对象名称。 + */ +static void TrRenameClass(TrObjDesc *baseDesc, ObjectAddress *object, const char *newName) +{ + Relation rel; // 用于表示关系(表)的关系对象 + HeapTuple tup; // 用于存储从系统缓存中检索的堆元组 + HeapTuple newtup; // 用于存储修改后的堆元组 + char rbname[NAMEDATALEN]; // 存储新的对象名称的字符数组 + Datum values[Natts_pg_class] = { 0 }; // 存储待更新的列值 + bool nulls[Natts_pg_class] = { false }; // 标志位数组,指示每列是否为NULL + bool replaces[Natts_pg_class] = { false }; // 标志位数组,指示哪些列将被替换 + Oid relid = object->objectId; // 要重命名的表的OID(对象标识符) + errno_t rc = EOK; // 用于处理字符串复制的错误码 + + if (newName) { + rc = strncpy_s(rbname, NAMEDATALEN, newName, strlen(newName)); // 将新名称复制到rbname中 + securec_check(rc, "\0", "\0"); // 检查字符串复制是否出错 + } else { + TrGenObjName(rbname, object->classId, relid); // 根据对象的类和OID生成新名称 + } + + replaces[Anum_pg_class_relname - 1] = true; // 标记表的名称列将被替换 + values[Anum_pg_class_relname - 1] = CStringGetDatum(rbname); // 设置新的表名值 + + rel = heap_open(RelationRelationId, RowExclusiveLock); // 打开pg_class关系,获取用于表的操作句柄 + + tup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); // 从系统缓存中获取表的元组 + if (!HeapTupleIsValid(tup)) { + ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for relation %u", relid))); // 如果找不到表的元组,报错 + } + + newtup = heap_modify_tuple(tup, RelationGetDescr(rel), values, nulls, replaces); // 修改元组的值 + + simple_heap_update(rel, &newtup->t_self, newtup); // 在堆中更新修改后的元组 + + CatalogUpdateIndexes(rel, newtup); // 更新索引 + + ReleaseSysCache(tup); // 释放系统缓存中的元组 + + heap_freetuple_ext(newtup); // 释放修改后的元组的内存 + + heap_close(rel, RowExclusiveLock); // 关闭pg_class关系 +} + +/** + * 功能:通用重命名函数,用于重命名数据库中的某些对象。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示要重命名的对象的地址信息。 + * relid:要操作的关系(表)的OID。 + * natts:待操作的属性数量。 + * oidAttrNum:OID属性的序号。 + * oidIndexId:OID属性对应的索引的OID。 + * objTag:对象的标签,用于报错信息。 + */ +static void TrRenameCommon(TrObjDesc *baseDesc, ObjectAddress *object, Oid relid, int natts, int oidAttrNum, + Oid oidIndexId, char *objTag) +{ + Relation rel; // 用于表示关系(表)的关系对象 + HeapTuple tup; // 用于存储从系统缓存中检索的堆元组 + HeapTuple newtup; // 用于存储修改后的堆元组 + char rbname[NAMEDATALEN]; // 存储新的对象名称的字符数组 + Datum *values = (Datum *)palloc0(sizeof(Datum) * natts); // 存储待更新的列值,初始化为0 + bool *nulls = (bool *)palloc0(sizeof(bool) * natts); // 标志位数组,指示每列是否为NULL,初始化为false + bool *replaces = (bool *)palloc0(sizeof(bool) * natts); // 标志位数组,指示哪些列将被替换,初始化为false + ScanKeyData skey[1]; // 扫描键数据数组 + SysScanDesc sd; // 系统扫描描述符 + + TrGenObjName(rbname, object->classId, object->objectId); // 根据对象的类和OID生成新名称 + + replaces[oidAttrNum - 1] = true; // 标记要被替换的属性 + values[oidAttrNum - 1] = CStringGetDatum(rbname); // 设置新的属性值 + + rel = heap_open(relid, RowExclusiveLock); // 打开要操作的关系(表) + + ScanKeyInit(&skey[0], ObjectIdAttributeNumber, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(object->objectId)); // 初始化扫描键 + sd = systable_beginscan(rel, oidIndexId, true, NULL, 1, skey); // 开始系统表扫描 + + tup = systable_getnext(sd); // 获取扫描结果 + if (!HeapTupleIsValid(tup)) { + pfree(values); + pfree(nulls); + pfree(replaces); + ereport(ERROR, + (errcode(ERRCODE_NO_DATA_FOUND), errmsg("could not find tuple for %s %u", objTag, object->objectId))); // 如果找不到对应元组,报错 + } + + newtup = heap_modify_tuple(tup, RelationGetDescr(rel), values, nulls, replaces); // 修改元组的值 + + simple_heap_update(rel, &newtup->t_self, newtup); // 在堆中更新修改后的元组 + + CatalogUpdateIndexes(rel, newtup); // 更新索引 + + heap_freetuple_ext(newtup); // 释放修改后的元组的内存 + + systable_endscan(sd); // 结束系统表扫描 + + heap_close(rel, RowExclusiveLock); // 关闭关系(表) + + pfree(values); + pfree(nulls); + pfree(replaces); +} + + +/* + * 功能:用于删除指定 baseid 对应的记录。 + * + * 参数列表: + * baseid:要删除的记录的 baseid。 + */ +static void TrDeleteBaseid(Oid baseid) +{ + Relation rbRel; // 用于表示回收站关系(表)的关系对象 + SysScanDesc sd; // 系统扫描描述符 + ScanKeyData skey[1]; // 扫描键数据数组 + HeapTuple tup; // 用于存储从系统表中检索的堆元组 + + rbRel = heap_open(RecyclebinRelationId, RowExclusiveLock); // 打开回收站 + + ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcybaseid, BTEqualStrategyNumber, F_INT8EQ, ObjectIdGetDatum(baseid)); // 初始化扫描键 + + sd = systable_beginscan(rbRel, RecyclebinBaseidIndexId, true, NULL, 1, skey); // 开始系统表扫描 + while (HeapTupleIsValid(tup = systable_getnext(sd))) { + simple_heap_delete(rbRel, &tup->t_self); // 删除扫描到的元组 + } + + systable_endscan(sd); // 结束系统表扫描 + heap_close(rbRel, RowExclusiveLock); // 关闭回收站 + CommandCounterIncrement(); +} + + +/* + * 功能:用于删除指定 id 对应的记录。 + * + * 参数列表: + * id:要删除的记录的 id。 + */ +static void TrDeleteId(Oid id) +{ + Relation rbRel; // 用于表示回收站关系(表)的关系对象 + SysScanDesc sd; // 系统扫描描述符 + ScanKeyData skey[1]; // 扫描键数据数组 + HeapTuple tup; // 用于存储从系统表中检索的堆元组 + + rbRel = heap_open(RecyclebinRelationId, RowExclusiveLock); // 打开回收站关系(表) + + ScanKeyInit(&skey[0], ObjectIdAttributeNumber, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(id)); // 初始化扫描键 + + sd = systable_beginscan(rbRel, RecyclebinIdIndexId, true, NULL, 1, skey); // 开始系统表扫描 + if (HeapTupleIsValid(tup = systable_getnext(sd))) { + simple_heap_delete(rbRel, &tup->t_self); // 删除扫描到的元组 + } + + systable_endscan(sd); // 结束系统表扫描 + heap_close(rbRel, RowExclusiveLock); // 关闭回收站关系(表) + + /* + * CommandCounterIncrement here to ensure that preceding changes are all + * visible to the next deletion step. + */ + CommandCounterIncrement(); +} +/* + * 功能:判断是否需要进行逻辑删除。 + * + * 参数列表: + * object:ObjectAddress结构,表示对象的地址信息。 + * + */ +static inline bool TrNeedLogicDrop(const ObjectAddress *object) +{ + return object->rbDropMode == RB_DROP_MODE_LOGIC; +} + +/** + * 功能:判断是否可以进行清除操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + * relKind:表示对象的关系类型。 + */ +static bool TrCanPurge(const TrObjDesc *baseDesc, const ObjectAddress *object, char relKind) +{ + Relation depRel; // 用于表示依赖关系(表)的关系对象 + SysScanDesc sd; // 系统扫描描述符 + HeapTuple tuple; // 用于存储从系统表中检索的堆元组 + ScanKeyData key[3]; // 扫描键数据数组 + int nkeys; // 扫描键的数量 + bool found = false; // 表示是否找到匹配依赖关系 + + if (relKind != RELKIND_INDEX && relKind != RELKIND_GLOBAL_INDEX && relKind != RELKIND_RELATION) { + return false; // 如果关系类型不是合法类型,直接返回false + } + + depRel = heap_open(DependRelationId, AccessShareLock); // 打开依赖关系(表) + + ScanKeyInit(&key[0], Anum_pg_depend_classid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(object->classId)); // 初始化扫描键 + ScanKeyInit(&key[1], Anum_pg_depend_objid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(object->objectId)); + nkeys = 2; + if (object->objectSubId != 0) { + ScanKeyInit(&key[2], Anum_pg_depend_objsubid, BTEqualStrategyNumber, F_INT4EQ, + Int32GetDatum(object->objectSubId)); + nkeys = 3; + } + + sd = systable_beginscan(depRel, DependDependerIndexId, true, NULL, nkeys, key); // 开始系统表扫描 + while (HeapTupleIsValid(tuple = systable_getnext(sd))) { + Form_pg_depend depForm = (Form_pg_depend)GETSTRUCT(tuple); + if (depForm->refclassid == RelationRelationId && depForm->refobjid == baseDesc->relid) { // 判断是否为依赖于当前表的依赖关系 + if (depForm->deptype != DEPENDENCY_AUTO) { // 如果依赖类型不是自动依赖,不可清除 + found = false; + break; + } + found = true; // 找到匹配的依赖关系 + } + } + + systable_endscan(sd); // 结束系统表扫描 + heap_close(depRel, AccessShareLock); // 关闭依赖关系(表) + return found; +} + + +/* + * 功能:执行删除索引的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropIndex(TrObjDesc *baseDesc, ObjectAddress *object) +{ + Assert(object->objectSubId == 0); // 断言,确保对象的子标识符为0(索引没有子标识符) + + if (TrNeedLogicDrop(object)) { // 如果需要逻辑删除 + TrObjDesc desc = *baseDesc; // 复制基本描述信息 + + if (!TR_IS_BASE_OBJ(baseDesc, object)) { + /* 单个对象删除之前已获取删除锁。*/ + Relation rel = relation_open(object->objectId, NoLock); // 打开要删除的关系(表) + + TrDescInit(rel, &desc, RB_OPER_DROP, TrGetObjType(RelationGetNamespace(rel), RELKIND_INDEX), // 初始化描述信息 + TrCanPurge(baseDesc, object, RelationGetRelkind(rel))); + relation_close(rel, NoLock); // 关闭关系(表) + + TrDescWrite(&desc); // 写入描述信息 + } + + TrRenameClass(baseDesc, object, desc.name); // 重命名对象 + } else { + index_drop(object->objectId, false); // 直接删除索引 + } + + return; +} + +/* + * 功能:执行删除表的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + * relKind:表示对象的关系类型,可以是RELKIND_RELATION、RELKIND_VIEW、RELKIND_COMPOSITE_TYPE或RELKIND_FOREIGN_TABLE。 + */ +static void TrDoDropTable(TrObjDesc *baseDesc, ObjectAddress *object, char relKind) +{ + if (TrNeedLogicDrop(object)) { // 如果需要逻辑删除 + TrObjDesc desc; + + if (object->objectSubId != 0 || relKind == RELKIND_VIEW || relKind == RELKIND_COMPOSITE_TYPE || + relKind == RELKIND_FOREIGN_TABLE) { // 对于子对象或特定类型的对象,直接重命名并返回 + TrRenameClass(baseDesc, object, NULL); + return; + } + + desc = *baseDesc; // 复制基本描述信息 + if (!TR_IS_BASE_OBJ(baseDesc, object)) { + /* 单个对象删除之前已获取删除锁。*/ + Relation rel = relation_open(object->objectId, NoLock); // 打开要删除的关系(表) + + TrDescInit(rel, &desc, RB_OPER_DROP, TrGetObjType(InvalidOid, relKind), // 初始化描述信息 + TrCanPurge(baseDesc, object, relKind)); + relation_close(rel, NoLock); // 关闭关系(表) + + TrDescWrite(&desc); // 写入描述信息 + } + + TrRenameClass(baseDesc, object, desc.name); // 重命名对象 + + return; + } + /* + * relation_open() must be before the heap_drop_with_catalog(). If you reload + * relation after drop, it may cause other exceptions during the drop process. + */ + if (object->objectSubId != 0) + RemoveAttributeById(object->objectId, object->objectSubId); // 删除子属性 + else + heap_drop_with_catalog(object->objectId); // 删除表 + + /* + * IMPORANT: The relation must not be reloaded after heap_drop_with_catalog() + * is executed to drop this relation.If you reload relation after drop, it may + * cause other exceptions during the drop process + */ + + return; +} + +/* + * 功能:执行删除类型的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropType(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { // 如果需要逻辑删除 + TrRenameCommon(baseDesc, object, TypeRelationId, Natts_pg_type, Anum_pg_type_typname, TypeOidIndexId, "type"); // 调用通用重命名函数 + } else { + RemoveTypeById(object->objectId); // 直接删除类型 + } + + return; +} + +/* + * T功能执行删除约束的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropConstraint(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { // 如果需要逻辑删除 + TrRenameCommon(baseDesc, object, ConstraintRelationId, Natts_pg_constraint, Anum_pg_constraint_conname, + ConstraintOidIndexId, "constraint"); // 调用通用重命名函数 + } else { + RemoveConstraintById(object->objectId); // 直接删除约束 + } + + return; +} + + +/* + * 功能:执行删除触发器的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropTrigger(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { // 如果需要逻辑删除 + TrRenameCommon(baseDesc, object, TriggerRelationId, Natts_pg_trigger, Anum_pg_trigger_tgname, TriggerOidIndexId, + "trigger"); // 调用通用重命名函数 + } else { + RemoveTriggerById(object->objectId); // 直接删除触发器 + } + + return; +} + +/* + * 功能:执行删除重写规则的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropRewrite(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { + /* 无需操作,因为基于规则的视图要求保留原始规则名称。*/ + } else { + RemoveRewriteRuleById(object->objectId); // 直接删除重写规则 + } + + return; +} + + +/* + * 功能:执行删除属性默认值的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropAttrdef(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { + /* 无需操作,因为系统目录中没有名称属性。*/ + } else { + RemoveAttrDefaultById(object->objectId); // 直接删除属性默认值 + } + + return; +} + +/* + * 功能:执行删除过程的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropProc(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { // 如果需要逻辑删除 + TrRenameCommon(baseDesc, object, ProcedureRelationId, Natts_pg_proc, Anum_pg_proc_proname, ProcedureOidIndexId, + "procedure"); // 重命名 + } else { + RemoveFunctionById(object->objectId); // 直接删除过程 + } + + return; +} + + +/* + * 功能:执行删除类型转换的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropCast(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { + /* 无需操作,因为系统目录中没有名称属性。*/ + } else { + DropCastById(object->objectId); // 直接删除类型转换 + } + + return; +} + +/* + * 功能:执行删除排序规则的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropCollation(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { // 如果需要逻辑删除 + TrRenameCommon(baseDesc, object, CollationRelationId, Natts_pg_collation, Anum_pg_collation_collname, + CollationOidIndexId, "collation"); // 重命名 + } else { + RemoveCollationById(object->objectId); // 直接删除排序规则 + } + + return; +} + + +/* + * 功能:执行删除类型转换的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropConversion(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { // 如果需要逻辑删除 + TrRenameCommon(baseDesc, object, ConversionRelationId, Natts_pg_conversion, Anum_pg_conversion_conname, + ConversionOidIndexId, "conversion"); // 重命名 + } else { + RemoveConversionById(object->objectId); // 直接删除类型转换 + } + + return; +} + +/* + * 功能:执行删除过程语言的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropProceduralLanguage(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { // 如果需要逻辑删除 + TrRenameCommon(baseDesc, object, LanguageRelationId, Natts_pg_language, Anum_pg_language_lanname, + LanguageOidIndexId, "language"); // 重命名 + } else { + DropProceduralLanguageById(object->objectId); // 直接删除过程语言 + } + + return; +} + + +/* + * 功能:执行删除大对象的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropLargeObject(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { + /* 无需操作,因为系统目录中没有名称属性。*/ + } else { + LargeObjectDrop(object->objectId); // 直接删除对象 + } + + return; +} + +/* + * 功能:执行删除操作符的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropOperator(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { // 如果需要逻辑删除 + TrRenameCommon(baseDesc, object, OperatorRelationId, Natts_pg_operator, Anum_pg_operator_oprname, + OperatorOidIndexId, "operator"); // 重命名 + } else { + RemoveOperatorById(object->objectId); // 直接删除操作符 + } + + return; +} + + +/* + * 功能:执行删除操作符类的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropOpClass(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { // 如果需要逻辑删除 + TrRenameCommon(baseDesc, object, OperatorClassRelationId, Natts_pg_opclass, Anum_pg_opclass_opcname, + OpclassOidIndexId, "opclass"); // 重命名 + } else { + RemoveOpClassById(object->objectId); // 直接删除操作符类 + } + + return; +} + +/* + * 功能:执行删除操作符族的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropOpFamily(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { // 如果需要逻辑删除 + TrRenameCommon(baseDesc, object, OperatorFamilyRelationId, Natts_pg_opfamily, Anum_pg_opfamily_opfname, + OpfamilyOidIndexId, "opfamily"); // 重命名 + } else { + RemoveOpFamilyById(object->objectId); // 直接删除操作符族 + } + + return; +} + + + +/* + * 功能:执行删除操作符访问方法项的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropAmOp(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { + /* 无需操作,因为系统目录中没有名称属性。*/ + } else { + RemoveAmOpEntryById(object->objectId); // 直接删除操作符访问方法项 + } + + return; +} + +/* + * 功能:执行删除过程访问方法项的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropAmProc(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { + /* 无需操作,因为系统目录中没有名称属性。*/ + } else { + RemoveAmProcEntryById(object->objectId); // 直接删除过程访问方法项 + } + + return; +} + + +/* + * 功能:执行删除模式的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropSchema(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { // 如果需要逻辑删除 + TrRenameCommon(baseDesc, object, NamespaceRelationId, Natts_pg_namespace, Anum_pg_namespace_nspname, + NamespaceOidIndexId, "namespace"); // 重命名 + } else { + RemoveSchemaById(object->objectId); // 直接删除模式 + } + + return; +} + +/* + * 功能:执行删除文本搜索解析器的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropTSParser(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { // 如果需要逻辑删除 + TrRenameCommon(baseDesc, object, TSParserRelationId, Natts_pg_ts_parser, Anum_pg_ts_parser_prsname, + TSParserOidIndexId, "ts parser"); // 重命名 + } else { + RemoveTSParserById(object->objectId); // 直接删除文本搜索解析器 + } + + return; +} + +/* + * 功能:执行删除文本搜索字典的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropTSDictionary(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { // 如果需要逻辑删除 + TrRenameCommon(baseDesc, object, TSDictionaryRelationId, Natts_pg_ts_dict, Anum_pg_ts_dict_dictname, + TSDictionaryOidIndexId, "ts dictionary"); // 重命名 + } else { + RemoveTSDictionaryById(object->objectId); // 直接删除文本搜索字典 + } + + return; +} + +/* + * 功能:执行删除文本搜索模板的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropTSTemplate(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { // 如果需要逻辑删除 + TrRenameCommon(baseDesc, object, TSTemplateRelationId, Natts_pg_ts_template, Anum_pg_ts_template_tmplname, + TSTemplateOidIndexId, "ts template"); // 重命名 + } else { + RemoveTSTemplateById(object->objectId); // 直接删除文本搜索模板 + } + + return; +} + + +/* + * 功能:执行删除文本搜索配置的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropTSConfiguration(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { // 如果需要逻辑删除 + TrRenameCommon(baseDesc, object, TSConfigRelationId, Natts_pg_ts_config, Anum_pg_ts_config_cfgname, + TSConfigOidIndexId, "ts configuration"); // 重命名 + } else { + RemoveTSConfigurationById(object->objectId); // 直接删除文本搜索配置 + } + + return; +} + +/* + * 功能:执行删除外部数据封装器的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropForeignDataWrapper(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { // 如果需要逻辑删除 + TrRenameCommon(baseDesc, object, ForeignDataWrapperRelationId, Natts_pg_foreign_data_wrapper, + Anum_pg_foreign_data_wrapper_fdwname, ForeignDataWrapperOidIndexId, "foreign data wrapper"); // 重命名 + } else { + RemoveForeignDataWrapperById(object->objectId); // 直接删除外部数据封装器 + } + + return; +} + +/* + * 功能:执行删除外部服务器的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropForeignServer(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { // 如果需要逻辑删除 + TrRenameCommon(baseDesc, object, ForeignServerRelationId, Natts_pg_foreign_server, + Anum_pg_foreign_server_srvname, ForeignServerOidIndexId, "foreign server"); // 重命名 + } else { + RemoveForeignServerById(object->objectId); // 直接删除外部服务器 + } + + return; +} + +/* + * 功能:执行删除用户映射的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropUserMapping(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { + /* 无需操作,因为系统目录中没有名称属性。*/ + } else { + RemoveUserMappingById(object->objectId); // 直接删除用户映射 + } + + return; +} + + + +/* + * 功能:执行删除默认访问控制列表的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropDefaultACL(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { + /* 无需操作,因为系统目录中没有名称属性。*/ + } else { + RemoveDefaultACLById(object->objectId); // 直接删除默认访问控制列表 + } + + return; +} + +/* + * 功能:执行删除分布式表信息的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropPgxcClass(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { + /* 无需操作,因为系统目录中没有名称属性。*/ + } else { + RemovePgxcClass(object->objectId); // 直接删除分布式表信息 + } + + return; +} + +/* + * 功能:执行删除扩展的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropExtension(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { // 如果需要逻辑删除 + TrRenameCommon(baseDesc, object, ExtensionRelationId, Natts_pg_extension, Anum_pg_extension_extname, + ExtensionOidIndexId, "extension"); // 重命名 + } else { + RemoveExtensionById(object->objectId); // 直接删除扩展 + } + + return; +} + +/* + * 功能:执行删除扩展数据源的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropDataSource(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { // 如果需要逻辑删除 + TrRenameCommon(baseDesc, object, DataSourceRelationId, Natts_pg_extension_data_source, + Anum_pg_extension_data_source_srcname, DataSourceOidIndexId, "extension data source"); // 重命名 + } else { + RemoveDataSourceById(object->objectId); // 直接删除扩展数据源 + } + + return; +} + + +/* + * 功能:执行删除目录的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropDirectory(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { // 如果需要逻辑删除 + TrRenameCommon(baseDesc, object, PgDirectoryRelationId, Natts_pg_directory, Anum_pg_directory_directory_name, + PgDirectoryOidIndexId, "directory"); // 重命名 + } else { + RemoveDirectoryById(object->objectId); // 直接删除目录 + } + + return; +} + +/* + * 功能:执行删除行级安全策略的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropRlsPolicy(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { // 如果需要逻辑删除 + TrRenameCommon(baseDesc, object, RlsPolicyRelationId, Natts_pg_rlspolicy, Anum_pg_rlspolicy_polname, + PgRlspolicyOidIndex, "rlspolicy"); // 重命名 + } else { + RemoveRlsPolicyById(object->objectId); // 直接删除行级安全策略 + } + + return; +} + +/* + * 功能:执行删除作业的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropJob(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { + /* 无需操作,因为系统目录中没有名称属性。*/ + } else { + RemoveJobById(object->objectId); // 直接删除作业 + } + + return; +} + +/* + * 功能:执行删除同义词的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropSynonym(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { // 如果需要逻辑删除 + TrRenameCommon(baseDesc, object, PgSynonymRelationId, Natts_pg_synonym, Anum_pg_synonym_synname, + SynonymOidIndexId, "synonym"); // 重命名 + } else { + RemoveSynonymById(object->objectId); // 直接删除同义词 + } + + return; +} + + +/* + * doDeletion: delete a single object + * return false if logic deleted, + * return true if physical deleted, + */ +/* + * 功能:执行删除对象的操作,根据对象的类别选择相应的删除函数。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDrop(TrObjDesc *baseDesc, ObjectAddress *object) +{ + switch (getObjectClass(object)) { + case OCLASS_CLASS: { + char relKind = get_rel_relkind(object->objectId); + if (relKind == RELKIND_INDEX) { + TrDoDropIndex(baseDesc, object);// 删除索引 + } else { + /* + * We use a unified entry for others: + * RELKIND_RELATION, RELKIND_SEQUENCE, + * RELKIND_TOASTVALUE, RELKIND_VIEW, + * RELKIND_COMPOSITE_TYPE, RELKIND_FOREIGN_TABLE + */ + TrDoDropTable(baseDesc, object, relKind);// 删除表 + } + break; + } + + case OCLASS_TYPE: + TrDoDropType(baseDesc, object); // 删除类型 + break; + + case OCLASS_CONSTRAINT: + TrDoDropConstraint(baseDesc, object); // 删除约束 + break; + + case OCLASS_TRIGGER: + TrDoDropTrigger(baseDesc, object); // 删除触发器 + break; + + case OCLASS_REWRITE: + TrDoDropRewrite(baseDesc, object); // 删除规则 + break; + + case OCLASS_DEFAULT: + TrDoDropAttrdef(baseDesc, object); // 删除默认值 + break; + + case OCLASS_PROC: + TrDoDropProc(baseDesc, object); // 删除函数 + break; + + case OCLASS_CAST: + TrDoDropCast(baseDesc, object); // 删除转换 + break; + + case OCLASS_COLLATION: + TrDoDropCollation(baseDesc, object); // 删除排序规则 + break; + + case OCLASS_CONVERSION: + TrDoDropConversion(baseDesc, object); // 删除转换规则 + break; + + case OCLASS_LANGUAGE: + TrDoDropProceduralLanguage(baseDesc, object); // 删除过程化语言 + break; + + case OCLASS_LARGEOBJECT: + TrDoDropLargeObject(baseDesc, object); // 删除大对象 + break; + + case OCLASS_OPERATOR: + TrDoDropOperator(baseDesc, object); // 删除操作符 + break; + + case OCLASS_OPCLASS: + TrDoDropOpClass(baseDesc, object); // 删除操作符类 + break; + + case OCLASS_OPFAMILY: + TrDoDropOpFamily(baseDesc, object); // 删除操作符族 + break; + + case OCLASS_AMOP: + TrDoDropAmOp(baseDesc, object); // 删除操作符映射 + break; + + case OCLASS_AMPROC: + TrDoDropAmProc(baseDesc, object); // 删除操作符处理函数 + break; + + case OCLASS_SCHEMA: + TrDoDropSchema(baseDesc, object); // 删除模式 + break; + + case OCLASS_TSPARSER: + TrDoDropTSParser(baseDesc, object); // 删除文本搜索解析器 + break; + + case OCLASS_TSDICT: + TrDoDropTSDictionary(baseDesc, object); // 删除文本搜索字典 + break; + + case OCLASS_TSTEMPLATE: + TrDoDropTSTemplate(baseDesc, object); // 删除文本搜索模板 + break; + + case OCLASS_TSCONFIG: + TrDoDropTSConfiguration(baseDesc, object); // 删除文本搜索配置 + break; + + /* + * OCLASS_ROLE, OCLASS_DATABASE, OCLASS_TBLSPACE intentionally not + * handled here + */ + + case OCLASS_FDW: + TrDoDropForeignDataWrapper(baseDesc, object); // 删除外部数据封装器 + break; + + case OCLASS_FOREIGN_SERVER: + TrDoDropForeignServer(baseDesc, object); // 删除外部服务器 + break; + + case OCLASS_USER_MAPPING: + TrDoDropUserMapping(baseDesc, object); // 删除用户映射 + break; + + case OCLASS_DEFACL: + TrDoDropDefaultACL(baseDesc, object); // 删除默认访问控制 + break; + + case OCLASS_PGXC_CLASS: + TrDoDropPgxcClass(baseDesc, object); // 删除分布式类 + break; + + case OCLASS_EXTENSION: + TrDoDropExtension(baseDesc, object); // 删除扩展 + break; + + case OCLASS_DATA_SOURCE: + TrDoDropDataSource(baseDesc, object); // 删除数据源 + break; + + case OCLASS_DIRECTORY: + TrDoDropDirectory(baseDesc, object); // 删除目录 + break; + + case OCLASS_RLSPOLICY: + TrDoDropRlsPolicy(baseDesc, object); // 删除行级安全策略 + break; + + case OCLASS_PG_JOB: + if ((IS_PGXC_COORDINATOR && !IsConnFromCoord()) || (g_instance.role == VSINGLENODE)) + TrDoDropJob(baseDesc, object); // 删除作业 + break; + + case OCLASS_SYNONYM: + TrDoDropSynonym(baseDesc, object); // 删除同义词 + break; + + default: + ereport(ERROR, + (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE), errmsg("unrecognized object class: %u", object->classId))); + break; + } + + return; +} + +/* + * deleteOneObject: delete a single object for TrDrop. + * + * *depRel is the already-open pg_depend relation. + */ +/* + * 功能:删除一个对象,包括其依赖关系等操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + * depRel:Relation指针,表示依赖关系的系统表。 + */ +static void TrDropOneObject(TrObjDesc *baseDesc, ObjectAddress *object, Relation *depRel) +{ + ScanKeyData key[3]; + int nkeys; + SysScanDesc scan; + HeapTuple tup; + + /* DROP hook of the objects being removed */ + if (object_access_hook) { + ObjectAccessDrop dropArg; + + dropArg.dropflags = PERFORM_DELETION_INVALID; + InvokeObjectAccessHook(OAT_DROP, object->classId, object->objectId, object->objectSubId, &dropArg); + } + + /* + * Delete the object itself, in an object-type-dependent way. + * + * We used to do this after removing the outgoing dependency links, but it + * seems just as reasonable to do it beforehand. In the concurrent case + * we *must *do it in this order, because we can't make any transactional + * updates before calling doDeletion() --- they'd get committed right + * away, which is not cool if the deletion then fails. + */ + TrDoDrop(baseDesc, object); + + /* + * In logical drop mode, we will keep all related system entries, including + * linked entries such as pg_depend records. It is done! + */ + if (TrNeedLogicDrop(object)) { + /* + * CommandCounterIncrement here to ensure that preceding changes are all + * visible to the next deletion step. + */ + CommandCounterIncrement(); + + /* + * Logic Drop done! + */ + return; + } + + /* + * In physical drop mode, we continue to remove all related system entries. + */ + + /* + * Now remove any pg_depend records that link from this object to others. + * (Any records linking to this object should be gone already.) + * + * When dropping a whole object (subId = 0), remove all pg_depend records + * for its sub-objects too. + */ + ScanKeyInit(&key[0], Anum_pg_depend_classid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(object->classId)); + ScanKeyInit(&key[1], Anum_pg_depend_objid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(object->objectId)); + if (object->objectSubId != 0) { + ScanKeyInit(&key[2], Anum_pg_depend_objsubid, BTEqualStrategyNumber, F_INT4EQ, + Int32GetDatum(object->objectSubId)); + nkeys = 3; + } else + nkeys = 2; + + scan = systable_beginscan(*depRel, DependDependerIndexId, true, NULL, nkeys, key); + + while (HeapTupleIsValid(tup = systable_getnext(scan))) { + simple_heap_delete(*depRel, &tup->t_self); + } + + systable_endscan(scan); + + /* + * Delete shared dependency references related to this object. Again, if + * subId = 0, remove records for sub-objects too. + */ + deleteSharedDependencyRecordsFor(object->classId, object->objectId, object->objectSubId); + + /* + * Delete any comments or security labels associated with this object. + * (This is a convenient place to do these things, rather than having + * every object type know to do it.) + */ + DeleteComments(object->objectId, object->classId, object->objectSubId); + DeleteSecurityLabel(object); + + /* + * CommandCounterIncrement here to ensure that preceding changes are all + * visible to the next deletion step. + */ + CommandCounterIncrement(); + + /* + * Physical Drop done! + */ +} + +/* + * 功能:判断给定的对象地址是否在对象地址列表中。 + * + * 参数列表: + * targetObjects:ObjectAddresses结构指针,表示目标对象地址列表。 + * thisobj:ObjectAddress结构,表示当前对象的地址。 + */ +static bool TrObjIsInList(const ObjectAddresses *targetObjects, const ObjectAddress *thisobj) +{ + ObjectAddress *item = NULL; + + for (int i = 0; i < targetObjects->numrefs; i++) { + item = targetObjects->refs + i; + if (TrObjIsEqual(thisobj, item)) { + return true; + } + } + return false; +} + +/* + * 功能:在目标对象地址列表中查找给定对象的索引。 + * + * 参数列表: + * targetObjects:ObjectAddresses结构指针,表示目标对象地址列表。 + * item:ObjectAddress结构,表示待查找的对象地址。 + */ +static ObjectAddress *TrFindIdxInTarget(ObjectAddresses *targetObjects, ObjectAddress *item) +{ + ObjectAddress *thisobj = NULL; + + for (int i = 0; i < targetObjects->numrefs; i++) { + thisobj = targetObjects->refs + i; + if (TrObjIsEqual(item, thisobj)) { + return thisobj; + } + } + + return NULL; +} + + +/* + * output: refthisobjs + */ +/* + * 功能:查找所有引用给定对象的依赖对象 + * + * 参数列表: + * depRel:依赖关系的系统目录表的关系描述符 + * refobj:给定对象的地址 + * refthisobjs:存储依赖对象地址的对象列表 + */ +static void TrFindAllSubObjs(Relation depRel, const ObjectAddress *refobj, ObjectAddresses *refthisobjs) +{ + SysScanDesc sd; + HeapTuple tuple; + ScanKeyData key[3]; + int nkeys; + // 初始化扫描键数组,设置条件用于匹配引用对象的类和ID + ScanKeyInit(&key[0], Anum_pg_depend_refclassid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(refobj->classId)); + ScanKeyInit(&key[1], Anum_pg_depend_refobjid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(refobj->objectId)); + nkeys = 2; + // 如果引用对象有子ID,则设置第三个条件 + if (refobj->objectSubId != 0) { + ScanKeyInit(&key[2], Anum_pg_depend_refobjsubid, BTEqualStrategyNumber, F_INT4EQ, + Int32GetDatum(refobj->objectSubId)); + nkeys = 3; + } + + // 使用条件初始化扫描器 + sd = systable_beginscan(depRel, DependReferenceIndexId, true, NULL, nkeys, key); + // 循环遍历满足条件的元组 + while (HeapTupleIsValid(tuple = systable_getnext(sd))) { + // 获取元组中的Form_pg_depend数据 + Form_pg_depend depForm = (Form_pg_depend)GETSTRUCT(tuple); + + /* add the refs to list */ + add_object_address_ext(depForm->classid, depForm->objid, depForm->objsubid, depForm->deptype, refthisobjs); + } + + systable_endscan(sd);// 结束扫描 + return; +} +/* + * 功能:标记物理删除模式下的所有子对象,将其添加到待删除列表中 + * + * 参数列表: + * depRel:依赖关系的系统目录表的关系描述符 + * targetObjects:待删除对象列表 + * thisobj:当前对象的地址 + */ +static void TrTagPhyDeleteSubObjs(Relation depRel, ObjectAddresses *targetObjects, ObjectAddress *thisobj) +{ + ObjectAddress *item = NULL; + // 创建一个用于存储依赖对象地址的对象列表 + ObjectAddresses *refthisobjs = new_object_addresses(); + + /* Tag this obj RB_DROP_MODE_PHYSICAL */ + thisobj->rbDropMode = RB_DROP_MODE_PHYSICAL; + + /* Find all sub objs refered to this obj */ + TrFindAllSubObjs(depRel, thisobj, refthisobjs); + // 遍历引用对象列表,将满足条件的对象标记为物理删除模式 + for (int i = 0; i < refthisobjs->numrefs; i++) { + item = refthisobjs->refs + i; + + /* the item must exists in targetObjects. */ + item = TrFindIdxInTarget(targetObjects, item); + if (item == NULL || item->rbDropMode == RB_DROP_MODE_PHYSICAL) { + continue; + } + // 递归标记满足条件的子对象 + TrTagPhyDeleteSubObjs(depRel, targetObjects, item); + } + // 释放用于存储依赖对象地址的对象列表 + free_object_addresses(refthisobjs); + return; +} +/* + * 功能:检查是否需要进行物理删除操作 + * + * 参数列表: + * depRel:依赖关系的系统目录表的关系描述符 + * targetObjects:待删除对象列表 + * thisobj:当前对象的地址 + */ +static bool TrNeedPhyDelete(Relation depRel, ObjectAddresses *targetObjects, ObjectAddress *thisobj) +{ + ObjectAddress *item = NULL; + // 创建一个用于存储引用对象地址的对象列表 + ObjectAddresses *refobjs = new_object_addresses(); + bool result = false; + + /* Find all objs this obj refered */ + TrFindAllRefObjs(depRel, thisobj, refobjs); + + /* Step 1: tag refobjs of thisobj, return directly if ALL refobjs not need physical drop. */ + for (int i = 0; i < refobjs->numrefs; i++) { + item = refobjs->refs + i; + if (!TrObjIsInList(targetObjects, item)) { + result = true; + break; + } + } + if (!result) { + free_object_addresses(refobjs); + return result; + } + + /* Step 2: tag refobjs with 'i' deptype to physical drop. */ + for (int i = 0; i < refobjs->numrefs; i++) { + item = refobjs->refs + i; + if (item->deptype == 'i') { + item = TrFindIdxInTarget(targetObjects, item); + Assert(item != NULL); + if (item->rbDropMode == RB_DROP_MODE_PHYSICAL) { + continue; + } + // 标记被引用对象及其子对象为物理删除模式 + TrTagPhyDeleteSubObjs(depRel, targetObjects, item); + } + } + // 释放用于存储引用对象地址的对象列表 + free_object_addresses(refobjs); + return result; +} +/* + * 功能重置待删除对象的删除模式 + * + * 参数列表: + * targetObjects:待删除对象列表 + * baseObj:基础对象的地址 + */ +static void TrResetDropMode(const ObjectAddresses *targetObjects, const ObjectAddress *baseObj) +{ + ObjectAddress *thisobj = NULL; + + for (int i = 0; i < targetObjects->numrefs; i++) { + thisobj = targetObjects->refs + i; + if (TrObjIsEqual(thisobj, baseObj)) { + // 将基础对象的删除模式重置为逻辑删除 + thisobj->rbDropMode = RB_DROP_MODE_LOGIC; + continue; + } + // 将其他对象的删除模式重置为无效 + thisobj->rbDropMode = RB_DROP_MODE_INVALID; + } + return; +} + + +/* + * 功能:标记依赖对象的删除模式 + * + * 参数列表: + * depRel:依赖关系关系 + * targetObjects:待删除对象列表 + * baseObj:基础对象的地址 + */ +static void TrTagDependentObjects(Relation depRel, ObjectAddresses *targetObjects, const ObjectAddress *baseObj) +{ + ObjectAddress *thisobj = NULL; + + // 重置待删除对象的删除模式 + TrResetDropMode(targetObjects, baseObj); + + for (int i = 0; i < targetObjects->numrefs; i++) { + thisobj = targetObjects->refs + i; + + // 如果对象的删除模式已经设置,则跳过 + if (TrDropModeIsAlreadySet(thisobj)) { + continue; + } + + // 如果需要进行物理删除 + if (TrNeedPhyDelete(depRel, targetObjects, thisobj)) { + // 标记所有依赖于此对象的子对象为物理删除 + TrTagPhyDeleteSubObjs(depRel, targetObjects, thisobj); + } else { + // 否则将对象的删除模式设置为逻辑删除 + thisobj->rbDropMode = RB_DROP_MODE_LOGIC; + } + } + + return; +} + + +/* + * 功能:检查是否可以进行Recyclebin-based-Drop操作 + * + * 参数列表: + * stmt:删除语句 + * objects:待删除对象列表 + */ +bool TrCheckRecyclebinDrop(const DropStmt *stmt, ObjectAddresses *objects) +{ + Relation depRel; + bool rbDrop = false; + + /* 如果没有待删除对象,则不需要操作 */ + if (objects->numrefs <= 0) + return false; + + /* 检查是否满足Recyclebin-based-Drop条件 */ + if (/* + * 当目标对象不是OBJECT_TABLE时,或者 + */ + stmt->removeType != OBJECT_TABLE || + /* 在并发删除模式下,或者 */ + stmt->concurrent || + /* 使用了purge选项,或者 */ + stmt->purge || + /* 删除多个对象时,不支持Recyclebin-based-Drop。 */ + list_length(stmt->objects) != 1) { + return false; + } + + /* 如果不需要Recyclebin功能,则不支持Recyclebin-based-Drop */ + if (!NeedTrComm(objects->refs->objectId)) { + return false; + } + + /* 打开依赖关系关系表,进行检查 */ + depRel = heap_open(DependRelationId, AccessShareLock); + rbDrop = !TrNeedPhyDelete(depRel, objects, &objects->refs[0]); + heap_close(depRel, AccessShareLock); + + return rbDrop; +} +/* + * 功能:执行删除操作,支持逻辑删除和物理删除 + * + * 参数列表: + * drop:删除语句 + * objects:待删除对象列表 + * behavior:删除行为 + */ +void TrDrop(const DropStmt* drop, const ObjectAddresses *objects, DropBehavior behavior) +{ + Relation depRel; + Relation baseRel; + TrObjDesc baseDesc; + ObjectAddresses *targetObjects = NULL; + ObjectAddress *baseObj = objects->refs; + + /* + * We save some cycles by opening pg_depend just once and passing the + * Relation pointer down to all the recursive deletion steps. + */ + depRel = heap_open(DependRelationId, RowExclusiveLock); + + /* + * Construct a list of objects to delete (ie, the given objects plus + * everything directly or indirectly dependent on them). Note that + * because we pass the whole objects list as pendingObjects context, we + * won't get a failure from trying to delete an object that is internally + * dependent on another one in the list; we'll just skip that object and + * delete it when we reach its owner. + */ + targetObjects = new_object_addresses(); + + /* + * Acquire deletion lock on each target object. (Ideally the caller + * has done this already, but many places are sloppy about it.) + */ + AcquireDeletionLock(baseObj, PERFORM_DELETION_INVALID); + + /* + * Finds all subobjects that reference the base table recursively. + */ + findDependentObjects(baseObj, DEPFLAG_ORIGINAL, NULL, /* empty stack */ + targetObjects, objects, &depRel); + ereport(LOG, (errmsg("Delete object %u/%u/%d", baseObj->classId, baseObj->objectId, baseObj->objectSubId))); + + /* + * Check if deletion is allowed, and report about cascaded deletes. + * + * If there's exactly one object being deleted, report it the same way as + * in performDeletion(), else we have to be vaguer. + */ + reportDependentObjects(targetObjects, behavior, NOTICE, baseObj); + + /* + * Tag all subobjects' drop mode: LOGIC_DROP, PYHSICAL_DROP. + */ + TrTagDependentObjects(depRel, targetObjects, baseObj); + + /* + * Initialize the baseDesc structure so that the logic dropped subobjects + * can be correctly processed when renamed or placed in recycle bin. Notice + * that base object already locked. + */ + baseRel = relation_open(baseObj->objectId, NoLock); + TrDescInit(baseRel, &baseDesc, RB_OPER_DROP, RB_OBJ_TABLE, true, true); + baseDesc.id = baseDesc.baseid = TrDescWrite(&baseDesc); + TrUpdateBaseid(&baseDesc); + relation_close(baseRel, NoLock); + + Oid relid = RelationGetRelid(baseRel); + UpdatePgObjectChangecsn(relid, baseRel->rd_rel->relkind); + + /* + * Drop all the objects in the proper order. + */ + for (int i = 0; i < targetObjects->numrefs; i++) { + ObjectAddress *thisobj = targetObjects->refs + i; + TrDropOneObject(&baseDesc, thisobj, &depRel); + } + + /* And clean up */ + free_object_addresses(targetObjects); + heap_close(depRel, RowExclusiveLock); +} +/* + * 功能:执行回收站中的对象清除操作 + * + * 参数列表: + * desc:对象描述信息 + */ +void TrDoPurgeObjectDrop(TrObjDesc *desc) +{ + ObjectAddresses *objects; // 存储对象地址的结构体,用于批量删除 + ObjectAddress obj; // 对象地址结构体,用于表示对象的地址 + + objects = new_object_addresses(); // 创建对象地址结构体的实例 + + obj.classId = RelationRelationId; // 设置对象地址的类别ID为RelationRelationId,表示关系对象 + obj.objectId = desc->relid; // 设置对象地址的对象ID为被删除对象的关系ID + obj.objectSubId = 0; // 设置对象地址的子对象ID为0,表示主对象 + add_exact_object_address(&obj, objects); // 向对象地址结构体添加对象地址 + + performMultipleDeletions(objects, DROP_CASCADE, PERFORM_DELETION_INVALID); // 执行多个对象的级联删除操作 + + if (desc->type == RB_OBJ_TABLE) { // 如果被删除对象是表对象 + TrDeleteBaseid(desc->baseid); // 删除基础ID + } else { /* RB_OBJ_INDEX */ + TrDeleteId(desc->id); // 删除ID + } + + free_object_addresses(objects); // 释放对象地址结构体的内存 + return; +} + +/* TIMECAPSULE TABLE { table_name } TO BEFORE DROP [RENAME TO new_tablename] */ +/* + * 功能:还原回收站中被删除的对象 + * + * 参数列表: + * stmt:时间胶囊语句 + */ +void TrRestoreDrop(const TimeCapsuleStmt *stmt) +{ + TrObjDesc desc; // 对象描述结构体,用于保存被还原对象的信息 + ObjectAddress obj; // 对象地址结构体,用于表示对象的地址 + Relation rel; // 数据库关系对象,用于操作数据库关系 + + desc.relid = 0; // 初始化被还原对象的关系ID为0 + TrOperFetch(stmt->relation, RB_OBJ_TABLE, &desc, RB_OPER_RESTORE_DROP); // 获取被还原对象的描述信息 + if (desc.relid != 0 && (desc.type == RB_OBJ_TABLE)) { + stmt->relation->relname = desc.name; // 将被还原对象的名称赋值给时间胶囊语句的关系名称 + rel = heap_openrv(stmt->relation, AccessExclusiveLock); // 打开关系对象以进行操作 + if (rel->rd_tam_type == TAM_HEAP) { + heap_close(rel, NoLock); // 关闭关系对象 + elog(ERROR, "timecapsule does not support astore yet"); // 报错,暂不支持还原存储区对象 + return; + } + heap_close(rel, NoLock); // 关闭关系对象 + } + + desc.authid = GetUserId(); // 获取当前用户的权限ID + TrOperPrep(&desc, RB_OPER_RESTORE_DROP); // 准备还原操作,更新描述信息 + + obj.classId = RelationRelationId; // 设置对象地址的类别ID为RelationRelationId,表示关系对象 + obj.objectId = desc.relid; // 设置对象地址的对象ID为被还原对象的关系ID + obj.objectSubId = 0; // 设置对象地址的子对象ID为0,表示主对象 + + TrRenameClass(&desc, &obj, stmt->new_relname ? stmt->new_relname : desc.originname); // 重命名对象 + + TrDeleteBaseid(desc.baseid); // 删除基础ID,即将对象从回收站移出 + + return; +} -- 2.34.1 From 0c739ef4bacee42740815010388d490b347af9bc Mon Sep 17 00:00:00 2001 From: nuoya <1204149038@qq.com> Date: Mon, 25 Sep 2023 15:35:09 +0800 Subject: [PATCH 21/50] Update tcap_truncate.cpp --- .../storage/tcap/tcap_truncate.cpp | 1022 +++++++++-------- 1 file changed, 528 insertions(+), 494 deletions(-) diff --git a/src/gausskernel/storage/tcap/tcap_truncate.cpp b/src/gausskernel/storage/tcap/tcap_truncate.cpp index 0303a2640..f8d2d5ef8 100644 --- a/src/gausskernel/storage/tcap/tcap_truncate.cpp +++ b/src/gausskernel/storage/tcap/tcap_truncate.cpp @@ -1,494 +1,528 @@ -/* - * 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. - * --------------------------------------------------------------------------------------- - * - * tcap_truncate.cpp - * Routines to support Timecapsule `Recyclebin-based query, restore`. - * We use Tr prefix to indicate it in following coding. - * - * IDENTIFICATION - * src/gausskernel/storage/tcap/tcap_truncate.cpp - * - * --------------------------------------------------------------------------------------- - */ - -#include "postgres.h" - -#include "pgstat.h" -#include "access/reloptions.h" -#include "access/sysattr.h" -#include "access/xlog.h" -#include "catalog/dependency.h" -#include "catalog/heap.h" -#include "catalog/index.h" -#include "catalog/indexing.h" -#include "catalog/objectaccess.h" -#include "catalog/pg_collation_fn.h" -#include "catalog/pg_collation.h" -#include "catalog/pg_constraint.h" -#include "catalog/pg_conversion_fn.h" -#include "catalog/pg_conversion.h" -#include "catalog/pg_depend.h" -#include "catalog/pg_extension_data_source.h" -#include "catalog/pg_extension.h" -#include "catalog/pg_foreign_data_wrapper.h" -#include "catalog/pg_foreign_server.h" -#include "catalog/pg_job.h" -#include "catalog/pg_language.h" -#include "catalog/pg_largeobject.h" -#include "catalog/pg_object.h" -#include "catalog/pg_opclass.h" -#include "catalog/pg_operator.h" -#include "catalog/pg_opfamily.h" -#include "catalog/pg_proc.h" -#include "catalog/pg_recyclebin.h" -#include "catalog/pg_rewrite.h" -#include "catalog/pg_rlspolicy.h" -#include "catalog/pg_synonym.h" -#include "catalog/pg_tablespace.h" -#include "catalog/pg_trigger.h" -#include "catalog/pg_ts_config.h" -#include "catalog/pg_ts_dict.h" -#include "catalog/pg_ts_parser.h" -#include "catalog/pg_ts_template.h" -#include "catalog/pgxc_class.h" -#include "catalog/storage.h" -#include "catalog/pg_partition_fn.h" -#include "commands/comment.h" -#include "commands/dbcommands.h" -#include "commands/directory.h" -#include "commands/extension.h" -#include "commands/matview.h" -#include "commands/proclang.h" -#include "commands/schemacmds.h" -#include "commands/seclabel.h" -#include "commands/sec_rls_cmds.h" -#include "commands/tablecmds.h" -#include "commands/tablespace.h" -#include "commands/trigger.h" -#include "commands/typecmds.h" -#include "executor/node/nodeModifyTable.h" -#include "rewrite/rewriteRemove.h" -#include "storage/lmgr.h" -#include "storage/predicate.h" -#include "storage/smgr/relfilenode.h" -#include "utils/acl.h" -#include "utils/builtins.h" -#include "utils/fmgroids.h" -#include "utils/inval.h" -#include "utils/knl_partcache.h" -#include "utils/knl_relcache.h" -#include "utils/lsyscache.h" -#include "utils/relcache.h" -#include "utils/snapmgr.h" -#include "utils/syscache.h" - -#include "storage/tcap.h" -#include "storage/tcap_impl.h" - -void TrRelationSetNewRelfilenode(Relation relation, TransactionId freezeXid, void *baseDesc) -{ - TrObjDesc desc; - TrObjDesc *trBaseDesc = (TrObjDesc *)baseDesc; - RelFileNodeBackend newrnode; - - /* Indexes, sequences must have Invalid frozenxid; other rels must not. */ - Assert((((relation->rd_rel->relkind == RELKIND_INDEX) || (relation->rd_rel->relkind == RELKIND_GLOBAL_INDEX) || - (RELKIND_IS_SEQUENCE(relation->rd_rel->relkind))) ? - (freezeXid == InvalidTransactionId) : - TransactionIdIsNormal(freezeXid)) || - relation->rd_rel->relkind == RELKIND_RELATION); - - /* Record the old relfilenode to recyclebin. */ - desc = *trBaseDesc; - if (!TR_IS_BASE_OBJ_EX(trBaseDesc, RelationGetRelid(relation))) { - TrDescInit(relation, &desc, RB_OPER_TRUNCATE, - TrGetObjType(RelationGetNamespace(relation), RelationGetRelkind(relation)), false); - TrDescWrite(&desc); - } - - /* Allocate a new relfilenode, create storage for the main fork. */ - newrnode = CreateNewRelfilenode(relation, freezeXid); - - /* - * NOTE: if the relation was created in this transaction, it will now be - * present in the pending-delete list twice, once with atCommit true and - * once with atCommit false. Hence, it will be physically deleted at end - * of xact in either case (and the other entry will be ignored by - * smgrDoPendingDeletes, so no error will occur). We could instead remove - * the existing list entry and delete the physical file immediately, but - * for now I'll keep the logic simple. - */ - RelationCloseSmgr(relation); - - /* - * Update pg_class entry for new relfilenode. - */ - UpdatePgclass(relation, freezeXid, &newrnode); - - /* - * Make the pg_class row change visible, as well as the relation map - * change if any. This will cause the relcache entry to get updated, too. - */ - CommandCounterIncrement(); - - /* - * Mark the rel as having been given a new relfilenode in the current - * (sub) transaction. This is a hint that can be used to optimize later - * operations on the rel in the same transaction. - */ - relation->rd_newRelfilenodeSubid = GetCurrentSubTransactionId(); - - /* ... and now we have eoxact cleanup work to do */ - SetRelCacheNeedEOXActWork(true); -} - -void TrPartitionSetNewRelfilenode(Relation parent, Partition part, TransactionId freezeXid, void *baseDesc) -{ - RelFileNodeBackend newrnode; - TrObjDesc desc; - TrObjDesc *trBaseDesc = (TrObjDesc *)baseDesc; - - Assert((parent->rd_rel->relkind == RELKIND_INDEX || RELKIND_IS_SEQUENCE(parent->rd_rel->relkind)) - ? freezeXid == InvalidTransactionId - : TransactionIdIsNormal(freezeXid)); - - /* Record the old relfilenode to recyclebin. */ - desc = *trBaseDesc; - if (!TR_IS_BASE_OBJ_EX(trBaseDesc, part->pd_id)) { - TrPartDescInit(parent, part, &desc, RB_OPER_TRUNCATE, - TrGetObjType(RelationGetNamespace(parent), RelationGetRelkind(parent)), false); - TrDescWrite(&desc); - } - - /* Allocate a new relfilenode */ - newrnode = CreateNewRelfilenodePart(parent, part); - - UpdatePartition(parent, part, freezeXid, &newrnode); - - CommandCounterIncrement(); - - /* - * Mark the part as having been given a new relfilenode in the current - * (sub) transaction. This is a hint that can be used to optimize later - * operations on the rel in the same transaction. - */ - part->pd_newRelfilenodeSubid = GetCurrentSubTransactionId(); - - /* ... and now we have eoxact cleanup work to do */ - SetPartCacheNeedEOXActWork(true); -} - -bool TrCheckRecyclebinTruncate(const TruncateStmt *stmt) -{ - RangeVar *rel = NULL; - Oid relid; - - if (/* - * Disable Recyclebin-based-Truncate when with purge option, or - */ - /* recyblebin disabled, or */ - !u_sess->attr.attr_storage.enable_recyclebin || - /* with purge option, or */ - stmt->purge || - /* with restart_seqs option, or */ - stmt->restart_seqs || - /* multi objects truncate. */ - list_length(stmt->relations) != 1) { - return false; - } - - rel = (RangeVar *)linitial(stmt->relations); - relid = RangeVarGetRelid(rel, NoLock, false); - - return NeedTrComm(relid); -} - -void TrTruncateOnePart(Relation rel, HeapTuple tup, Oid insertBaseid) -{ - Oid toastOid = ((Form_pg_partition)GETSTRUCT(tup))->reltoastrelid; - Relation toastRel = NULL; - Oid partOid = HeapTupleGetOid(tup); - Partition p = partitionOpen(rel, partOid, AccessExclusiveLock); - TrObjDesc baseDesc; - TrPartDescInit(rel, p, &baseDesc, RB_OPER_TRUNCATE, RB_OBJ_PARTITION, false); - baseDesc.id = baseDesc.baseid = insertBaseid; - (void)TrDescWrite(&baseDesc); - TrUpdateBaseid(&baseDesc); - - TrPartitionSetNewRelfilenode(rel, p, u_sess->utils_cxt.RecentXmin, &baseDesc); - - /* process the toast table */ - if (OidIsValid(toastOid)) { - Assert(rel->rd_rel->relpersistence != RELPERSISTENCE_UNLOGGED); - toastRel = heap_open(toastOid, AccessExclusiveLock); - TrRelationSetNewRelfilenode(toastRel, u_sess->utils_cxt.RecentXmin, &baseDesc); - heap_close(toastRel, AccessExclusiveLock); - } - partitionClose(rel, p, AccessExclusiveLock); - - /* report truncate partition to PgStatCollector */ - pgstat_report_truncate(partOid, rel->rd_id, rel->rd_rel->relisshared); -} - -void TrPartitionTableProcess(Relation rel, Oid insertBaseid) -{ - /* truncate partitioned table */ - List* partTupleList = NIL; - ListCell* partCell = NULL; - Oid heap_relid; - bool is_shared = rel->rd_rel->relisshared; - - heap_relid = RelationGetRelid(rel); - /* partitioned table unspport the unlogged table */ - Assert(rel->rd_rel->relpersistence != RELPERSISTENCE_UNLOGGED); - - /* process all partition */ - partTupleList = searchPgPartitionByParentId(PART_OBJ_TYPE_TABLE_PARTITION, rel->rd_id); - foreach (partCell, partTupleList) { - if (RelationIsSubPartitioned(rel)) { - /* the "tup" just for get partOid, UHeapTup has no HEAP_HASOID flag, so here use HeapTuple */ - HeapTuple tup = (HeapTuple)lfirst(partCell); - Oid partOid = HeapTupleGetOid(tup); - Partition p = partitionOpen(rel, partOid, AccessExclusiveLock); - Relation partRel = partitionGetRelation(rel, p); - List* subPartTupleList = searchPgPartitionByParentId(PART_OBJ_TYPE_TABLE_SUB_PARTITION, partOid); - ListCell* subPartCell = NULL; - foreach (subPartCell, subPartTupleList) { - HeapTuple tup = (HeapTuple)lfirst(subPartCell); - TrTruncateOnePart(partRel, tup, insertBaseid); - } - freePartList(subPartTupleList); - - releaseDummyRelation(&partRel); - partitionClose(rel, p, AccessExclusiveLock); - } else { - HeapTuple tup = (HeapTuple)lfirst(partCell); - TrTruncateOnePart(rel, tup, insertBaseid); - } - } - - freePartList(partTupleList); - /* report truncate partitioned table to PgStatCollector */ - pgstat_report_truncate(heap_relid, InvalidOid, is_shared); -} - -void TrTruncate(const TruncateStmt *stmt) -{ - RangeVar *rv = (RangeVar*)linitial(stmt->relations); - Relation rel; - Oid relid; - Oid toastRelid; - TrObjDesc baseDesc; - - /* - * 1. Open relation in AccessExclusiveLock, and check permission, etc. - */ - - rel = heap_openrv(rv, AccessExclusiveLock); - relid = RelationGetRelid(rel); - - /* find matview exists or not. */ - Oid mlogid = find_matview_mlog_table(relid); - if (OidIsValid(mlogid)) { - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("Not support truncate table under materialized view."))); - } - - TrForbidAccessRbObject(RelationRelationId, relid, rv->relname); - - truncate_check_rel(rel); - - /* - * This effectively deletes all rows in the table, and may be done - * in a serializable transaction. In that case we must record a - * rw-conflict in to this transaction from each transaction - * holding a predicate lock on the table. - */ - CheckTableForSerializableConflictIn(rel); - - /* - * 2. Create a new empty storage file for the relation, and assign it - * as the relfilenode value, and record the old relfilenode to recyclebin. - */ - - TrDescInit(rel, &baseDesc, RB_OPER_TRUNCATE, RB_OBJ_TABLE, true, true); - baseDesc.id = baseDesc.baseid = TrDescWrite(&baseDesc); - TrUpdateBaseid(&baseDesc); - - /* - * step 2.1. If rel is partition table, find all partitions, and Create some new empty - * storage file for the all partitions of the relation, and assign them as the - * relfilenodes value, and record the old relfilenodes to recyclebin. - */ - if (RELATION_IS_PARTITIONED(rel)) { - TrPartitionTableProcess(rel, baseDesc.baseid); - } - - TrRelationSetNewRelfilenode(rel, u_sess->utils_cxt.RecentXmin, &baseDesc); - - /* - * 3. The same for the toast table, if any. - */ - - toastRelid = rel->rd_rel->reltoastrelid; - if (OidIsValid(toastRelid) && !RELATION_IS_PARTITIONED(rel)) { - Relation relToast = relation_open(toastRelid, AccessExclusiveLock); - TrRelationSetNewRelfilenode(relToast, u_sess->utils_cxt.RecentXmin, &baseDesc); - heap_close(relToast, NoLock); - } - - /* - * 4. Reconstruct the indexes to match, and we're done. - */ - - (void)ReindexRelation(relid, REINDEX_REL_PROCESS_TOAST, REINDEX_ALL_INDEX, &baseDesc); - - /* - * 5. Report stat, and clean. - */ - - /* report truncate to PgStatCollector */ - pgstat_report_truncate(relid, InvalidOid, false); - - /* Record time of truancate relation. */ - recordRelationMTime(relid, rel->rd_rel->relkind); - - heap_close(rel, NoLock); -} - -/* - * RelationDropStorage - * Schedule unlinking of physical storage at transaction commit. - */ -void TrDoPurgeObjectTruncate(TrObjDesc *desc) -{ - Relation rbRel; - SysScanDesc sd; - ScanKeyData skey[1]; - HeapTuple tup; - - Assert (((desc->type == RB_OBJ_TABLE) && desc->canpurge) || ((desc->type == RB_OBJ_PARTITION) && !desc->canpurge)); - - rbRel = heap_open(RecyclebinRelationId, RowExclusiveLock); - - ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcybaseid, BTEqualStrategyNumber, - F_INT8EQ, Int64GetDatum(desc->baseid)); - - sd = systable_beginscan(rbRel, RecyclebinBaseidIndexId, true, NULL, 1, skey); - while (HeapTupleIsValid(tup = systable_getnext(sd))) { - Form_pg_recyclebin rbForm = (Form_pg_recyclebin)GETSTRUCT(tup); - RelFileNode rnode; - - rnode.spcNode = ConvertToRelfilenodeTblspcOid(rbForm->rcytablespace); - rnode.dbNode = (rnode.spcNode == GLOBALTABLESPACE_OID) ? InvalidOid : - u_sess->proc_cxt.MyDatabaseId; - rnode.relNode = rbForm->rcyrelfilenode; - rnode.opt = 0; - rnode.bucketNode = InvalidBktId; - - /* - * Schedule unlinking of the old storage at transaction commit. - */ - InsertStorageIntoPendingList( - &rnode, InvalidAttrNumber, InvalidBackendId, rbForm->rcyowner, true, false); - - simple_heap_delete(rbRel, &tup->t_self); - - ereport(LOG, (errmsg("Delete truncated object %u/%u/%u", rnode.spcNode, - rnode.dbNode, rnode.relNode))); - } - - systable_endscan(sd); - heap_close(rbRel, RowExclusiveLock); - - /* ... and now we have eoxact cleanup work to do */ - SetRelCacheNeedEOXActWork(true); - - /* - * CommandCounterIncrement here to ensure that preceding changes are all - * visible to the next deletion step. - */ - CommandCounterIncrement(); -} - -/* flashback table to before truncate */ -void TrRestoreTruncate(const TimeCapsuleStmt *stmt) -{ - TrObjDesc baseDesc; - Relation rbRel; - SysScanDesc sd; - ScanKeyData skey[1]; - HeapTuple tup; - Relation rel; - Oid relid; - bool found = false; - TrObjDesc desc; - - /* process restore truncate fail: TIMECAPSULE TABLE "BIN$3C534EBE021$4930808==$0" TO BEFORE TRUNCATE; */ - found = TrFetchName(stmt->relation->relname, RB_OBJ_TABLE, &desc, RB_OPER_RESTORE_TRUNCATE); - if (found) { - stmt->relation->relname = desc.originname; - } - - rel = heap_openrv(stmt->relation, AccessExclusiveLock); - relid = RelationGetRelid(rel); - if (rel->rd_tam_type == TAM_HEAP) { - heap_close(rel, NoLock); - elog(ERROR, "timecapsule does not support astore yet"); - return; - } - - if (found) { - stmt->relation->relname = desc.name; - } - - /* 1. Fetch the latest available recycle object. */ - TrOperFetch(stmt->relation, RB_OBJ_TABLE, &baseDesc, RB_OPER_RESTORE_TRUNCATE); - - /* 2. Lock recycle object and base relation. */ - baseDesc.authid = GetUserId(); - TrOperPrep(&baseDesc, RB_OPER_RESTORE_TRUNCATE); - - /* 3. Check base relation whether normal and matched. */ - TrBaseRelMatched(&baseDesc); - - /* 4. Do restore. */ - rbRel = heap_open(RecyclebinRelationId, RowExclusiveLock); - - ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcybaseid, BTEqualStrategyNumber, - F_INT8EQ, Int64GetDatum(baseDesc.id)); - - sd = systable_beginscan(rbRel, RecyclebinBaseidIndexId, true, NULL, 1, skey); - while (HeapTupleIsValid(tup = systable_getnext(sd))) { - if (!RELATION_IS_PARTITIONED(rel)) { - TrSwapRelfilenode(rbRel, tup, false); - } else { - TrSwapRelfilenode(rbRel, tup, true); - } - } - - systable_endscan(sd); - heap_close(rbRel, RowExclusiveLock); - heap_close(rel, NoLock); - - /* - * CommandCounterIncrement here to ensure that preceding changes are all - * visible to the next deletion step. - */ - CommandCounterIncrement(); - - return; -} +/* + * 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. + * --------------------------------------------------------------------------------------- + * + * tcap_truncate.cpp + * Routines to support Timecapsule `Recyclebin-based query, restore`. + * We use Tr prefix to indicate it in following coding. + * + * IDENTIFICATION + * src/gausskernel/storage/tcap/tcap_truncate.cpp + * + * --------------------------------------------------------------------------------------- + */ + +#include "postgres.h" + +#include "pgstat.h" +#include "access/reloptions.h" +#include "access/sysattr.h" +#include "access/xlog.h" +#include "catalog/dependency.h" +#include "catalog/heap.h" +#include "catalog/index.h" +#include "catalog/indexing.h" +#include "catalog/objectaccess.h" +#include "catalog/pg_collation_fn.h" +#include "catalog/pg_collation.h" +#include "catalog/pg_constraint.h" +#include "catalog/pg_conversion_fn.h" +#include "catalog/pg_conversion.h" +#include "catalog/pg_depend.h" +#include "catalog/pg_extension_data_source.h" +#include "catalog/pg_extension.h" +#include "catalog/pg_foreign_data_wrapper.h" +#include "catalog/pg_foreign_server.h" +#include "catalog/pg_job.h" +#include "catalog/pg_language.h" +#include "catalog/pg_largeobject.h" +#include "catalog/pg_object.h" +#include "catalog/pg_opclass.h" +#include "catalog/pg_operator.h" +#include "catalog/pg_opfamily.h" +#include "catalog/pg_proc.h" +#include "catalog/pg_recyclebin.h" +#include "catalog/pg_rewrite.h" +#include "catalog/pg_rlspolicy.h" +#include "catalog/pg_synonym.h" +#include "catalog/pg_tablespace.h" +#include "catalog/pg_trigger.h" +#include "catalog/pg_ts_config.h" +#include "catalog/pg_ts_dict.h" +#include "catalog/pg_ts_parser.h" +#include "catalog/pg_ts_template.h" +#include "catalog/pgxc_class.h" +#include "catalog/storage.h" +#include "catalog/pg_partition_fn.h" +#include "commands/comment.h" +#include "commands/dbcommands.h" +#include "commands/directory.h" +#include "commands/extension.h" +#include "commands/matview.h" +#include "commands/proclang.h" +#include "commands/schemacmds.h" +#include "commands/seclabel.h" +#include "commands/sec_rls_cmds.h" +#include "commands/tablecmds.h" +#include "commands/tablespace.h" +#include "commands/trigger.h" +#include "commands/typecmds.h" +#include "executor/node/nodeModifyTable.h" +#include "rewrite/rewriteRemove.h" +#include "storage/lmgr.h" +#include "storage/predicate.h" +#include "storage/smgr/relfilenode.h" +#include "utils/acl.h" +#include "utils/builtins.h" +#include "utils/fmgroids.h" +#include "utils/inval.h" +#include "utils/knl_partcache.h" +#include "utils/knl_relcache.h" +#include "utils/lsyscache.h" +#include "utils/relcache.h" +#include "utils/snapmgr.h" +#include "utils/syscache.h" + +#include "storage/tcap.h" +#include "storage/tcap_impl.h" +/* + * 功能:为给定的关系设置新的关系文件节点 + * 参数列表: + * relation:关系对象 + * freezeXid:事务ID + * baseDesc:基础描述对象指针 + */ +void TrRelationSetNewRelfilenode(Relation relation, TransactionId freezeXid, void *baseDesc) +{ + TrObjDesc desc; // 定义一个TrObjDesc类型的变量desc,用于记录关系的描述信息 + TrObjDesc *trBaseDesc = (TrObjDesc *)baseDesc; // 将传入的baseDesc指针转换为TrObjDesc类型的指针 + RelFileNodeBackend newrnode; // 定义一个RelFileNodeBackend类型的变量newrnode,表示新的关系文件节点 + + /* Indexes, sequences must have Invalid frozenxid; other rels must not. */ + Assert((((relation->rd_rel->relkind == RELKIND_INDEX) || (relation->rd_rel->relkind == RELKIND_GLOBAL_INDEX) || + (RELKIND_IS_SEQUENCE(relation->rd_rel->relkind))) + ? (freezeXid == InvalidTransactionId) + : TransactionIdIsNormal(freezeXid)) || + relation->rd_rel->relkind == RELKIND_RELATION); + + /* Record the old relfilenode to recyclebin. */ + desc = *trBaseDesc; // 将baseDesc指针指向的对象复制到desc变量中 + if (!TR_IS_BASE_OBJ_EX(trBaseDesc, RelationGetRelid(relation))) { + TrDescInit(relation, &desc, RB_OPER_TRUNCATE, + TrGetObjType(RelationGetNamespace(relation), RelationGetRelkind(relation)), false); + TrDescWrite(&desc); + } + + /* Allocate a new relfilenode, create storage for the main fork. */ + newrnode = CreateNewRelfilenode(relation, freezeXid); + + /* + * NOTE: if the relation was created in this transaction, it will now be + * present in the pending-delete list twice, once with atCommit true and + * once with atCommit false. Hence, it will be physically deleted at end + * of xact in either case (and the other entry will be ignored by + * smgrDoPendingDeletes, so no error will occur). We could instead remove + * the existing list entry and delete the physical file immediately, but + * for now I'll keep the logic simple. + */ + RelationCloseSmgr(relation); + + /* + * Update pg_class entry for new relfilenode. + */ + UpdatePgclass(relation, freezeXid, &newrnode); + + /* + * Make the pg_class row change visible, as well as the relation map + * change if any. This will cause the relcache entry to get updated, too. + */ + CommandCounterIncrement(); + + /* + * Mark the rel as having been given a new relfilenode in the current + * (sub) transaction. This is a hint that can be used to optimize later + * operations on the rel in the same transaction. + */ + relation->rd_newRelfilenodeSubid = GetCurrentSubTransactionId(); + + /* ... and now we have eoxact cleanup work to do */ + SetRelCacheNeedEOXActWork(true); +} +/* + * 功能:为给定的分区设置新的关系文件节点 + * 参数列表: + * parent:父关系对象 + * part:分区对象 + * freezeXid:事务ID,用于冻结分区 + * baseDesc:基础描述对象指针 + */ +void TrPartitionSetNewRelfilenode(Relation parent, Partition part, TransactionId freezeXid, void *baseDesc) +{ + RelFileNodeBackend newrnode; // 定义一个RelFileNodeBackend类型的变量newrnode,表示新的关系文件节点 + TrObjDesc desc; // 定义一个TrObjDesc类型的变量desc,用于记录关系的描述信息 + TrObjDesc *trBaseDesc = (TrObjDesc *)baseDesc; // 将传入的baseDesc指针转换为TrObjDesc类型的指针 + + Assert((parent->rd_rel->relkind == RELKIND_INDEX || RELKIND_IS_SEQUENCE(parent->rd_rel->relkind)) + ? freezeXid == InvalidTransactionId + : TransactionIdIsNormal(freezeXid)); + + /* Record the old relfilenode to recyclebin. */ + desc = *trBaseDesc; // 将baseDesc指针指向的对象复制到desc变量中 + if (!TR_IS_BASE_OBJ_EX(trBaseDesc, part->pd_id)) { + TrPartDescInit(parent, part, &desc, RB_OPER_TRUNCATE, + TrGetObjType(RelationGetNamespace(parent), RelationGetRelkind(parent)), false); + TrDescWrite(&desc); + } + + /* Allocate a new relfilenode */ + newrnode = CreateNewRelfilenodePart(parent, part); + + UpdatePartition(parent, part, freezeXid, &newrnode); + + CommandCounterIncrement(); // 增加命令计数器,使最近的更改可见 + + /* + * Mark the part as having been given a new relfilenode in the current + * (sub) transaction. This is a hint that can be used to optimize later + * operations on the rel in the same transaction. + */ + part->pd_newRelfilenodeSubid = GetCurrentSubTransactionId(); + + /* ... and now we have eoxact cleanup work to do */ + SetPartCacheNeedEOXActWork(true); // 设置标志,表示需要在事务结束时执行清理工作 +} +/* + * 功能:检查是否可以使用Recyclebin进行截断操作 + * 参数列表: + * stmt:截断语句对象 + */ +bool TrCheckRecyclebinTruncate(const TruncateStmt *stmt) +{ + RangeVar *rel = NULL; // 定义一个RangeVar类型的指针rel,初始化为NULL + Oid relid; // 定义一个Oid类型的变量relid,用于存储关系的对象标识符 + + if (/* + * Disable Recyclebin-based-Truncate when with purge option, or + */ + /* recyblebin disabled, or */ + !u_sess->attr.attr_storage.enable_recyclebin || + /* with purge option, or */ + stmt->purge || + /* with restart_seqs option, or */ + stmt->restart_seqs || + /* multi objects truncate. */ + list_length(stmt->relations) != 1) { + return false; + } + + rel = (RangeVar *)linitial(stmt->relations); // 获取截断语句中的关系 + relid = RangeVarGetRelid(rel, NoLock, false); // 获取关系的对象标识符 + + return NeedTrComm(relid); // 判断是否需要Recyclebin处理该关系的截断 +} +/* + * 功能:截断一个分区 + * 参数列表: + * rel:关系对象 + * tup:分区元组 + * insertBaseid:插入的基础ID + */ +void TrTruncateOnePart(Relation rel, HeapTuple tup, Oid insertBaseid) +{ + Oid toastOid = ((Form_pg_partition)GETSTRUCT(tup))->reltoastrelid; // 获取分区元组中的toast关系对象标识符 + Relation toastRel = NULL; // 定义一个Relation类型的指针toastRel,初始化为NULL + Oid partOid = HeapTupleGetOid(tup); // 获取分区元组的对象标识符 + Partition p = partitionOpen(rel, partOid, AccessExclusiveLock); // 打开分区,获取Partition对象 + TrObjDesc baseDesc; // 定义一个TrObjDesc类型的变量baseDesc,用于记录基础描述信息 + + // 初始化baseDesc描述对象,设置为分区截断操作 + TrPartDescInit(rel, p, &baseDesc, RB_OPER_TRUNCATE, RB_OBJ_PARTITION, false); + baseDesc.id = baseDesc.baseid = insertBaseid; // 设置baseDesc的ID和基础ID + (void)TrDescWrite(&baseDesc); // 将baseDesc描述信息写入存储 + TrUpdateBaseid(&baseDesc); // 更新基础ID + + // 为分区设置新的关系文件节点 + TrPartitionSetNewRelfilenode(rel, p, u_sess->utils_cxt.RecentXmin, &baseDesc); + + /* process the toast table */ + if (OidIsValid(toastOid)) { + Assert(rel->rd_rel->relpersistence != RELPERSISTENCE_UNLOGGED); // 断言验证关系的持久性 + toastRel = heap_open(toastOid, AccessExclusiveLock); // 打开toast关系 + TrRelationSetNewRelfilenode(toastRel, u_sess->utils_cxt.RecentXmin, + &baseDesc); // 为toast关系设置新的关系文件节点 + heap_close(toastRel, AccessExclusiveLock); // 关闭toast关系 + } + partitionClose(rel, p, AccessExclusiveLock); // 关闭分区 + + /* report truncate partition to PgStatCollector */ + pgstat_report_truncate(partOid, rel->rd_id, rel->rd_rel->relisshared); +} + +void TrPartitionTableProcess(Relation rel, Oid insertBaseid) +{ + /* truncate partitioned table */ + List *partTupleList = NIL; + ListCell *partCell = NULL; + Oid heap_relid; + bool is_shared = rel->rd_rel->relisshared; + + heap_relid = RelationGetRelid(rel); + /* partitioned table unspport the unlogged table */ + Assert(rel->rd_rel->relpersistence != RELPERSISTENCE_UNLOGGED); + + /* process all partition */ + partTupleList = searchPgPartitionByParentId(PART_OBJ_TYPE_TABLE_PARTITION, rel->rd_id); + foreach (partCell, partTupleList) { + if (RelationIsSubPartitioned(rel)) { + /* the "tup" just for get partOid, UHeapTup has no HEAP_HASOID flag, so here use HeapTuple */ + HeapTuple tup = (HeapTuple)lfirst(partCell); + Oid partOid = HeapTupleGetOid(tup); + Partition p = partitionOpen(rel, partOid, AccessExclusiveLock); + Relation partRel = partitionGetRelation(rel, p); + List *subPartTupleList = searchPgPartitionByParentId(PART_OBJ_TYPE_TABLE_SUB_PARTITION, partOid); + ListCell *subPartCell = NULL; + foreach (subPartCell, subPartTupleList) { + HeapTuple tup = (HeapTuple)lfirst(subPartCell); + TrTruncateOnePart(partRel, tup, insertBaseid); + } + freePartList(subPartTupleList); + + releaseDummyRelation(&partRel); + partitionClose(rel, p, AccessExclusiveLock); + } else { + HeapTuple tup = (HeapTuple)lfirst(partCell); + TrTruncateOnePart(rel, tup, insertBaseid); + } + } + + freePartList(partTupleList); + /* report truncate partitioned table to PgStatCollector */ + pgstat_report_truncate(heap_relid, InvalidOid, is_shared); +} + +void TrTruncate(const TruncateStmt *stmt) +{ + RangeVar *rv = (RangeVar *)linitial(stmt->relations); + Relation rel; + Oid relid; + Oid toastRelid; + TrObjDesc baseDesc; + + /* + * 1. Open relation in AccessExclusiveLock, and check permission, etc. + */ + + rel = heap_openrv(rv, AccessExclusiveLock); + relid = RelationGetRelid(rel); + + /* find matview exists or not. */ + Oid mlogid = find_matview_mlog_table(relid); + if (OidIsValid(mlogid)) { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("Not support truncate table under materialized view."))); + } + + TrForbidAccessRbObject(RelationRelationId, relid, rv->relname); + + truncate_check_rel(rel); + + /* + * This effectively deletes all rows in the table, and may be done + * in a serializable transaction. In that case we must record a + * rw-conflict in to this transaction from each transaction + * holding a predicate lock on the table. + */ + CheckTableForSerializableConflictIn(rel); + + /* + * 2. Create a new empty storage file for the relation, and assign it + * as the relfilenode value, and record the old relfilenode to recyclebin. + */ + + TrDescInit(rel, &baseDesc, RB_OPER_TRUNCATE, RB_OBJ_TABLE, true, true); + baseDesc.id = baseDesc.baseid = TrDescWrite(&baseDesc); + TrUpdateBaseid(&baseDesc); + + /* + * step 2.1. If rel is partition table, find all partitions, and Create some new empty + * storage file for the all partitions of the relation, and assign them as the + * relfilenodes value, and record the old relfilenodes to recyclebin. + */ + if (RELATION_IS_PARTITIONED(rel)) { + TrPartitionTableProcess(rel, baseDesc.baseid); + } + + TrRelationSetNewRelfilenode(rel, u_sess->utils_cxt.RecentXmin, &baseDesc); + + /* + * 3. The same for the toast table, if any. + */ + + toastRelid = rel->rd_rel->reltoastrelid; + if (OidIsValid(toastRelid) && !RELATION_IS_PARTITIONED(rel)) { + Relation relToast = relation_open(toastRelid, AccessExclusiveLock); + TrRelationSetNewRelfilenode(relToast, u_sess->utils_cxt.RecentXmin, &baseDesc); + heap_close(relToast, NoLock); + } + + /* + * 4. Reconstruct the indexes to match, and we're done. + */ + + (void)ReindexRelation(relid, REINDEX_REL_PROCESS_TOAST, REINDEX_ALL_INDEX, &baseDesc); + + /* + * 5. Report stat, and clean. + */ + + /* report truncate to PgStatCollector */ + pgstat_report_truncate(relid, InvalidOid, false); + + /* Record time of truancate relation. */ + recordRelationMTime(relid, rel->rd_rel->relkind); + + heap_close(rel, NoLock); +} + +/* + * RelationDropStorage + * Schedule unlinking of physical storage at transaction commit. + */ +/* + *功能: 这个函数用于清理被截断(truncate)的对象的信息,从回收站中删除相关记录,以及在事务提交时安排取消链接旧存储。 + *参数列表: + *desc:对象描述,其中包含对象的相关信息。 + */ +void TrDoPurgeObjectTruncate(TrObjDesc *desc) +{ + Relation rbRel; + SysScanDesc sd; + ScanKeyData skey[1]; + HeapTuple tup; + + // 断言:如果是表类型的对象并且可以进行清理,或者是分区对象但不能进行清理,则满足条件 + Assert(((desc->type == RB_OBJ_TABLE) && desc->canpurge) || ((desc->type == RB_OBJ_PARTITION) && !desc->canpurge)); + + // 打开回收站关系,获取排他锁 + rbRel = heap_open(RecyclebinRelationId, RowExclusiveLock); + + // 初始化扫描键 + ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcybaseid, BTEqualStrategyNumber, F_INT8EQ, Int64GetDatum(desc->baseid)); + + // 开始对回收站关系进行扫描 + sd = systable_beginscan(rbRel, RecyclebinBaseidIndexId, true, NULL, 1, skey); + while (HeapTupleIsValid(tup = systable_getnext(sd))) { + Form_pg_recyclebin rbForm = (Form_pg_recyclebin)GETSTRUCT(tup); + RelFileNode rnode; + + // 构造表空间、数据库和关系节点信息 + rnode.spcNode = ConvertToRelfilenodeTblspcOid(rbForm->rcytablespace); + rnode.dbNode = (rnode.spcNode == GLOBALTABLESPACE_OID) ? InvalidOid : u_sess->proc_cxt.MyDatabaseId; + rnode.relNode = rbForm->rcyrelfilenode; + rnode.opt = 0; + rnode.bucketNode = InvalidBktId; + + /* + * 将旧存储的取消链接安排在事务提交时进行 + */ + InsertStorageIntoPendingList(&rnode, InvalidAttrNumber, InvalidBackendId, rbForm->rcyowner, true, false); + + // 删除回收站中的记录 + simple_heap_delete(rbRel, &tup->t_self); + + ereport(LOG, (errmsg("Delete truncated object %u/%u/%u", rnode.spcNode, rnode.dbNode, rnode.relNode))); + } + + // 结束扫描 + systable_endscan(sd); + + // 关闭回收站关系 + heap_close(rbRel, RowExclusiveLock); + + // 设置关系缓存需要在事务结束时进行清理 + SetRelCacheNeedEOXActWork(true); + + /* + * 在这里执行 CommandCounterIncrement,以确保之前的更改对下一个删除步骤都可见。 + */ + CommandCounterIncrement(); +} + +/* flashback table to before truncate */ +void TrRestoreTruncate(const TimeCapsuleStmt *stmt) +{ + TrObjDesc baseDesc; + Relation rbRel; + SysScanDesc sd; + ScanKeyData skey[1]; + HeapTuple tup; + Relation rel; + Oid relid; + bool found = false; + TrObjDesc desc; + + /* process restore truncate fail: TIMECAPSULE TABLE "BIN$3C534EBE021$4930808==$0" TO BEFORE TRUNCATE; */ + found = TrFetchName(stmt->relation->relname, RB_OBJ_TABLE, &desc, RB_OPER_RESTORE_TRUNCATE); + if (found) { + stmt->relation->relname = desc.originname; + } + + rel = heap_openrv(stmt->relation, AccessExclusiveLock); + relid = RelationGetRelid(rel); + if (rel->rd_tam_type == TAM_HEAP) { + heap_close(rel, NoLock); + elog(ERROR, "timecapsule does not support astore yet"); + return; + } + + if (found) { + stmt->relation->relname = desc.name; + } + + /* 1. Fetch the latest available recycle object. */ + TrOperFetch(stmt->relation, RB_OBJ_TABLE, &baseDesc, RB_OPER_RESTORE_TRUNCATE); + + /* 2. Lock recycle object and base relation. */ + baseDesc.authid = GetUserId(); + TrOperPrep(&baseDesc, RB_OPER_RESTORE_TRUNCATE); + + /* 3. Check base relation whether normal and matched. */ + TrBaseRelMatched(&baseDesc); + + /* 4. Do restore. */ + rbRel = heap_open(RecyclebinRelationId, RowExclusiveLock); + + ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcybaseid, BTEqualStrategyNumber, F_INT8EQ, Int64GetDatum(baseDesc.id)); + + sd = systable_beginscan(rbRel, RecyclebinBaseidIndexId, true, NULL, 1, skey); + while (HeapTupleIsValid(tup = systable_getnext(sd))) { + if (!RELATION_IS_PARTITIONED(rel)) { + TrSwapRelfilenode(rbRel, tup, false); + } else { + TrSwapRelfilenode(rbRel, tup, true); + } + } + + systable_endscan(sd); + heap_close(rbRel, RowExclusiveLock); + heap_close(rel, NoLock); + + /* + * CommandCounterIncrement here to ensure that preceding changes are all + * visible to the next deletion step. + */ + CommandCounterIncrement(); + + return; +} -- 2.34.1 From 1e0967d668a0fb8e0949e055e8c01f6a386c354c Mon Sep 17 00:00:00 2001 From: nuoya <1204149038@qq.com> Date: Mon, 25 Sep 2023 15:35:36 +0800 Subject: [PATCH 22/50] Update xlog_share_storage.cpp --- .../xlog_share_storage/xlog_share_storage.cpp | 315 ++++++++++++------ 1 file changed, 212 insertions(+), 103 deletions(-) diff --git a/src/gausskernel/storage/xlog_share_storage/xlog_share_storage.cpp b/src/gausskernel/storage/xlog_share_storage/xlog_share_storage.cpp index 0eb3b6429..df83a4ed2 100644 --- a/src/gausskernel/storage/xlog_share_storage/xlog_share_storage.cpp +++ b/src/gausskernel/storage/xlog_share_storage/xlog_share_storage.cpp @@ -37,7 +37,7 @@ #include "miscadmin.h" #include "pgstat.h" #include "replication/syncrep_gramparse.h" -#include "replication/walsender_private.h" +#include "replication/ walsender_private.h" #include "storage/ipc.h" #include "storage/dorado_operation/dorado_fd.h" #include "storage/xlog_share_storage/xlog_share_storage.h" @@ -53,33 +53,39 @@ const static uint32 CHECK_LOCK_INTERVAL = 0xFF; const static uint32 MAX_SIZE_CAN_COPY_TO_SHARE = 128 * 1024 * 1024; const static uint32 PTR_PRINT_SHIFT_SIZE = 32; - +// 这个函数是用来处理 SIGHUP 信号的。 +// 功能是在接收到 SIGHUP 信号时,设置一个标志来指示已经收到信号,并尝试唤醒进程。 +// 以便进程可以在需要时执行相应的操作。 static void SharedStorageXlogCopyBackendSigHupHandler(SIGNAL_ARGS) { - int save_errno = errno; + int save_errno = errno; // 保存当前的错误信号到 save_errno 变量。 + // 改变标志表示已经收到了 SIGHUP 信号。 t_thrd.sharestoragexlogcopyer_cxt.got_SIGHUP = true; + /* + *检查全局变量 t_thrd.proc: + *如果不为空,就调用 SetLatch 函数。 + *该函数会触发该进程的等待标志(Latch),以便在需要的时候唤醒进程执行一些操作。 + */ if (t_thrd.proc) { SetLatch(&t_thrd.proc->procLatch); } - errno = save_errno; + errno = save_errno; // 将错误信号恢复为之前保存的值。 } - +// 用来处理关闭信号,和 SharedStorageXlogCopyBackendSigHupHandler()函数类似 static void SharedStorageXlogCopyBackendShutdownHandler(SIGNAL_ARGS) { int save_errno = errno; - t_thrd.sharestoragexlogcopyer_cxt.shutdown_requested = true; - if (t_thrd.proc) SetLatch(&t_thrd.proc->procLatch); errno = save_errno; } - +// 用于快速终止进程的信号处理函数。它的主要作用是退出进程,并在退出之前执行一些清理操作。 static void SharedStorageXlogCopyBackendQuickDie(SIGNAL_ARGS) { - gs_signal_setmask(&t_thrd.libpq_cxt.BlockSig, NULL); + gs_signal_setmask(&t_thrd.libpq_cxt.BlockSig, NULL); // 用于设置信号掩码,为了防止在退出过程中受到其他信号的干扰。 /* * We DO NOT want to run proc_exit() callbacks -- we're here because @@ -89,7 +95,7 @@ static void SharedStorageXlogCopyBackendQuickDie(SIGNAL_ARGS) * things by calling exit() directly, we have to reset the callbacks * explicitly to make this work as intended. */ - on_exit_reset(); + on_exit_reset(); // 为了重置在进程退出时应该调用的回调函数,以确保不执行任何已注册的退出回调函数。 /* * Note we do exit(2) not exit(0). This is to force the postmaster into a @@ -99,14 +105,16 @@ static void SharedStorageXlogCopyBackendQuickDie(SIGNAL_ARGS) * should ensure the postmaster sees this as a crash, too, but no harm in * being doubly sure.) */ - exit(2); + exit(2); // 调用 exit,使进程以状态码 2 终止。 } - +// 用于处理 SigUsr1信号的信号处理函数。 static void SharedStorageXlogCopyBackendSigUsr1Handler(SIGNAL_ARGS) { + // 保存当前的错误信号到 save_errno 变量。 + // 目的是为了在函数执行期间不影响其他部分对错误号的使用。 int saveErrno = errno; - latch_sigusr1_handler(); + latch_sigusr1_handler(); // SetLatch使用SigUsr1唤醒在latch上等待的进程。如果我们在等待,唤醒 WaitLatch。 errno = saveErrno; } @@ -117,29 +125,32 @@ void LocalXLogRead(char *buf, XLogRecPtr startptr, Size count) XLogRecPtr recptr; Size nbytes; - p = buf; - recptr = startptr; - nbytes = count; + p = buf; // 指向缓冲区的指针 + recptr = startptr; // 当前的WAL日志指针 + nbytes = count; // 要读取的字节数 while (nbytes > 0) { uint32 startoff; int segbytes; int readbytes; - startoff = recptr % XLogSegSize; + startoff = recptr % XLogSegSize; // 计算当前WAL日志指针在XLOG段内的偏移量 /* Do we need to switch to a different xlog segment? */ - if (t_thrd.sharestoragexlogcopyer_cxt.readFile < 0 || - !XLByteInSeg(recptr, t_thrd.sharestoragexlogcopyer_cxt.readSegNo)) { + // 检查是否需要切换到不同的XLOG段 + if (t_thrd.sharestoragexlogcopyer_cxt.readFile < + 0 || // 检查名为 readFile 的文件描述符是否为负值,从而判断文件是否已经关闭或无效。 + !XLByteInSeg(recptr, t_thrd.sharestoragexlogcopyer_cxt + .readSegNo)) { // 条件判断表达式,用于检查给定的WAL日志位置是否在当前的XLOG段内。 char path[MAXPGPATH]; - + // 如果之前打开的文件描述符有效,则关闭它 if (t_thrd.sharestoragexlogcopyer_cxt.readFile >= 0) { (void)close(t_thrd.sharestoragexlogcopyer_cxt.readFile); } - + // 计算新的XLOG段文件路径 XLByteToSeg(recptr, t_thrd.sharestoragexlogcopyer_cxt.readSegNo); XLogFilePath(path, MAXPGPATH, t_thrd.xlog_cxt.ThisTimeLineID, t_thrd.sharestoragexlogcopyer_cxt.readSegNo); - + // 打开新的XLOG段文件进行读取 t_thrd.sharestoragexlogcopyer_cxt.readFile = BasicOpenFile(path, O_RDONLY | PG_BINARY, 0); if (t_thrd.sharestoragexlogcopyer_cxt.readFile < 0) { /* @@ -147,24 +158,29 @@ void LocalXLogRead(char *buf, XLogRecPtr startptr, Size count) * asked for a too old WAL segment that has already been * removed or recycled. */ + // 如果文件不存在,可能是因为要求的WAL段已被删除或回收 if (errno == ENOENT) { ereport(ERROR, (errcode_for_file_access(), errmsg("requested WAL segment %s has already been removed", XLogFileNameP(t_thrd.xlog_cxt.ThisTimeLineID, t_thrd.sharestoragexlogcopyer_cxt.readSegNo)))); } else { + // 打开文件失败,报告错误 ereport(ERROR, (errcode_for_file_access(), errmsg("could not open file \"%s\" (log segment %s): %m", path, XLogFileNameP(t_thrd.xlog_cxt.ThisTimeLineID, t_thrd.sharestoragexlogcopyer_cxt.readSegNo)))); } } - t_thrd.sharestoragexlogcopyer_cxt.readOff = 0; + t_thrd.sharestoragexlogcopyer_cxt.readOff = 0; // 重置读取偏移量 } /* Need to seek in the file? */ + // 重置读取偏移量 if (t_thrd.sharestoragexlogcopyer_cxt.readOff != startoff) { + // 定位到指定的偏移量 if (lseek(t_thrd.sharestoragexlogcopyer_cxt.readFile, (off_t)startoff, SEEK_SET) < 0) { + // 定位失败,关闭文件并报告错误 (void)close(t_thrd.sharestoragexlogcopyer_cxt.readFile); t_thrd.sharestoragexlogcopyer_cxt.readFile = -1; ereport(ERROR, @@ -173,22 +189,27 @@ void LocalXLogRead(char *buf, XLogRecPtr startptr, Size count) t_thrd.sharestoragexlogcopyer_cxt.readSegNo), startoff))); } - t_thrd.sharestoragexlogcopyer_cxt.readOff = startoff; + t_thrd.sharestoragexlogcopyer_cxt.readOff = startoff; // 更新读取偏移量 } /* How many bytes are within this segment? */ + // 计算在当前XLOG段中要读取的字节数 if (nbytes > (XLogSegSize - startoff)) { segbytes = XLogSegSize - startoff; } else { segbytes = nbytes; } - + // 报告WAL读取等待事件 pgstat_report_waitevent(WAIT_EVENT_WAL_READ); + // 从文件中读取数据 readbytes = read(t_thrd.sharestoragexlogcopyer_cxt.readFile, p, segbytes); + // 报告WAL读取完成事件 pgstat_report_waitevent(WAIT_EVENT_END); + // 检查读取是否失败 if (readbytes <= 0) { + // 读取失败,关闭文件并报告错误 (void)close(t_thrd.sharestoragexlogcopyer_cxt.readFile); - t_thrd.sharestoragexlogcopyer_cxt.readFile = -1; + t_thrd.sharestoragexlogcopyer_cxt.readFile = -1; // 标志读取失败 ereport(ERROR, (errcode_for_file_access(), errmsg("could not read from log segment %s, offset %u, length %lu: %m", @@ -201,226 +222,284 @@ void LocalXLogRead(char *buf, XLogRecPtr startptr, Size count) t_thrd.sharestoragexlogcopyer_cxt.readOff += readbytes; nbytes -= readbytes; - p += readbytes; + p += readbytes; // 更新缓冲区指针 } } - +// 用于通知等待同步的后端(如流复制的从节点)有新的日志位置需要同步。 void NotifySyncWaiters(XLogRecPtr newPos) { - volatile WalSndCtlData *walsndctl = t_thrd.walsender_cxt.WalSndCtl; + volatile WalSndCtlData *walsndctl = t_thrd.walsender_cxt.WalSndCtl; + // 获取同步复制锁以确保原子性地更新同步等待状态 (void)LWLockAcquire(SyncRepLock, LW_EXCLUSIVE); /* * Set the lsn first so that when we wake backends they will release up to * this location. + * 检查当前位置是否大于等待接收、等待写入和等待刷新的LSN, + * 如果是,则更新对应的LSN并唤醒相应的等待队列。 */ + // 如果新位置大于等待接收的LSN,则更新并唤醒等待接收的队列 if (XLByteLT(walsndctl->lsn[SYNC_REP_WAIT_RECEIVE], newPos)) { walsndctl->lsn[SYNC_REP_WAIT_RECEIVE] = newPos; (void)SyncRepWakeQueue(false, SYNC_REP_WAIT_RECEIVE); } + // 如果新位置大于等待写入的LSN,则更新并唤醒等待写入的队列 if (XLByteLT(walsndctl->lsn[SYNC_REP_WAIT_WRITE], newPos)) { walsndctl->lsn[SYNC_REP_WAIT_WRITE] = newPos; (void)SyncRepWakeQueue(false, SYNC_REP_WAIT_WRITE); } + // 如果新位置大于等待刷新的LSN,则更新并唤醒等待刷新的队列 if (XLByteLT(walsndctl->lsn[SYNC_REP_WAIT_FLUSH], newPos)) { walsndctl->lsn[SYNC_REP_WAIT_FLUSH] = newPos; (void)SyncRepWakeQueue(false, SYNC_REP_WAIT_FLUSH); } - + // 释放同步复制锁 LWLockRelease(SyncRepLock); } - +/* + * 该函数用于将新的LSN信息写入共享存储,确保相关控制信息和数据持久化, + * 并通过日志和通知机制,确保同步等待的后端能够得知新的LSN可用,从而进行相应的同步操作。 + */ void PushCtlLsn(XLogRecPtr flushPtr) { ShareStorageXLogCtl *sharestorageCtl = g_instance.xlog_cxt.shareStorageXLogCtl; + // 将flushPtr设置为共享存储XLog控制结构中的insertHead字段。 sharestorageCtl->insertHead = flushPtr; + // 如果insertHead和insertTail之间的距离大于xlogFileSize,调整insertTail的值 if ((sharestorageCtl->insertHead - sharestorageCtl->insertTail) > sharestorageCtl->xlogFileSize) { sharestorageCtl->insertTail = sharestorageCtl->insertHead - sharestorageCtl->xlogFileSize; } + // 计算共享存储XLog控制结构的CRC校验码 sharestorageCtl->crc = CalShareStorageCtlInfoCrc(sharestorageCtl); + // 更新共享存储XLog控制信息 UpdateShareStorageCtlInfo(sharestorageCtl); + // 将XLOG数据刷写到共享存储 FsyncXlogToShareStorage(); + // 根据HaModuleDebug设置日志级别 int mode = u_sess->attr.attr_storage.HaModuleDebug ? LOG : DEBUG2; + // 以指定的日志级别输出消息,显示insertHead的LSN const uint32 shftSz = 32; ereport(mode, (errmsg("[PushCtlLsn]insertHead lsn %08X/%08X\n", (uint32)(flushPtr >> shftSz), (uint32)flushPtr))); + // 通知等待同步的后端,有新的LSN可用 NotifySyncWaiters(flushPtr); } - +// 该函数用于在给定的约束下计算要写入共享存储的实际数据长度, +// 确保数据写入的位置和边界符合块大小和共享存储缓冲区大小的要求。 static inline int CalcWriteLen(XLogRecPtr startWrite, XLogRecPtr endPtr) { + // 确保startWrite的偏移量是XLOG_BLCKSZ的倍数,即起始位置为块的开始,如果断言失败,就会终止程序 Assert((startWrite % XLOG_BLCKSZ) == 0); + // 计算对齐的写入结束位置,使其与ShareStorageBufSize对齐 XLogRecPtr alignWriteEnd = startWrite - startWrite % ShareStorageBufSize + ShareStorageBufSize; + // 如果对齐后的写入结束位置大于endPtr,执行以下操作 if (alignWriteEnd > endPtr) { + // 使用TYPEALIGN计算对齐的实际写入结束位置 XLogRecPtr ActualCopyEnd = TYPEALIGN(XLOG_BLCKSZ, endPtr); + // 计算要写入的数据长度,并将其转换为int类型返回 return static_cast(ActualCopyEnd - startWrite); } else { + // 如果对齐后的写入结束位置不大于endPtr,计算要写入的数据长度,并将其转换为 int类型返回 return static_cast(alignWriteEnd - startWrite); } } - +// 该函数用于获取可以进行覆写的最大XLog位置。 XLogRecPtr GetMaxPosCanOverWrite() { + // 如果不在Postmaster进程中,直接返回最大的XLog位置 if (!IsUnderPostmaster) { return MAX_XLOG_REC_PTR; } - + // 初始化最大刷新位置为无效位置 XLogRecPtr maxFlush = InvalidXLogRecPtr; + // 遍历所有的WAL发送者,找到最大的刷新位置 for (int i = 0; i < g_instance.attr.attr_storage.max_wal_senders; i++) { /* use volatile pointer to prevent code rearrangement */ volatile WalSnd *walsnd = &t_thrd.walsender_cxt.WalSndCtl->walsnds[i]; SpinLockAcquire(&walsnd->mutex); + // 如果WAL发送者的进程ID不为0且peer_role为STANDBY_CLUSTER_MODE if (walsnd->pid != 0 && walsnd->peer_role == STANDBY_CLUSTER_MODE) { if (XLByteLT(maxFlush, walsnd->flush)) { - maxFlush = walsnd->flush; + maxFlush = walsnd->flush; // 更新最大刷新位置 } } SpinLockRelease(&walsnd->mutex); } - + // 如果没有找到有效的刷新位置,返回最大的XLog位置 if (maxFlush == InvalidXLogRecPtr) { return MAX_XLOG_REC_PTR; } - + // 返回可进行覆写的最大位置,即xlog_file_size加上最大刷新位置 return g_instance.attr.attr_storage.xlog_file_size + maxFlush; } - -void AddXLogPageHeader(char* buf, XLogRecPtr startWrite, int writeLen, XLogRecPtr endPtr) +// 该函数用于确保写入的每个数据块都有正确的XLog页面头部,以便在后续的操作中能够正确识别和处理写入的WAL数据。 +void AddXLogPageHeader(char *buf, XLogRecPtr startWrite, int writeLen, XLogRecPtr endPtr) { int offset = 0; - while (offset < writeLen) { + // 使用循环向每个写入的块添加XLog页面头部 + while (offset < writeLen) { + // 计算当前偏移处的XLog页面头部 XLogPageHeader xlogPageHeader = (XLogPageHeader)(buf + offset); + // 设置XLog页面头部的总长度为XLOG_BLCKSZ(即块的大小) xlogPageHeader->xlp_total_len = XLOG_BLCKSZ; + // 移动偏移,准备处理下一个块 offset += XLOG_BLCKSZ; } - + // 如果endPtr小于(不包括等于)startWrite + writeLen,表示写入的数据跨越了一个块的边界 if (XLByteLT(endPtr, startWrite + writeLen)) { + // 计算跨越的部分在最后一个块中的长度 XLogPageHeader xlogPageHeader = (XLogPageHeader)(buf + writeLen - XLOG_BLCKSZ); + // 设置最后一个块中XLog页面头部的总长度,以确保不超过写入的数据的边界 xlogPageHeader->xlp_total_len = endPtr % XLOG_BLCKSZ; } } - +// 该函数用于执行XLog数据的拷贝操作 void DoXlogCopy(XLogRecPtr targetPtr) { uint64 writeLength = 0; ShareStorageXLogCtl *sharestorageCtl = g_instance.xlog_cxt.shareStorageXLogCtl; + // 断言目标位置有效 Assert(targetPtr != InvalidXLogRecPtr); + // 如果目标位置小于等于当前插入头位置,无需进行XLog拷贝 if (XLByteLE(targetPtr, sharestorageCtl->insertHead)) { return; } - + // 计算起始写入位置(整块对齐) XLogRecPtr startWrite = sharestorageCtl->insertHead - (sharestorageCtl->insertHead % XLOG_BLCKSZ); + // 循环进行XLog拷贝 while (XLByteLT(startWrite, targetPtr)) { + // 计算本次写入的长度 int writeLen = CalcWriteLen(startWrite, targetPtr); + // 从本地XLog中读取数据 LocalXLogRead(t_thrd.sharestoragexlogcopyer_cxt.buf, startWrite, static_cast(writeLen)); + // 为写入数据块添加XLog页面头部 AddXLogPageHeader(t_thrd.sharestoragexlogcopyer_cxt.buf, startWrite, writeLen, targetPtr); + // 将数据块写入共享存储 (void)WriteXlogToShareStorage(startWrite, t_thrd.sharestoragexlogcopyer_cxt.buf, writeLen); + // 更新起始写入位置,并累计写入长度 startWrite += writeLen; writeLength += writeLen; + // 如果累计写入长度达到了一个XLog段的大小,或者目标位置小于起始写入位置,推进控制LSN并重置累计写入长度 if ((writeLength >= XLogSegSize) || (XLByteLT(targetPtr, startWrite))) { PushCtlLsn(XLByteLE(startWrite, targetPtr) ? startWrite : targetPtr); writeLength = 0; } } - + // 最后如果还有未推进的LSN,进行推进 if (writeLength > 0) { PushCtlLsn(targetPtr); } } - +// 获取锁 static bool GetLock() { + // 如果XLog锁文件路径为NULL,表示无需获取锁,直接返回成功 if (g_instance.attr.attr_storage.xlog_lock_file_path == NULL) { return true; } - + // 尝试获取NAS写锁,如果成功获取锁则返回成功 if (LockNasWriteFile(g_instance.xlog_cxt.shareStorageLockFd)) { return true; } + // 如果获取锁失败,报告致命错误,指明无法锁定XLog锁文件 ereport(FATAL, (errmsg("could not lock lock file(%d) %s", g_instance.xlog_cxt.shareStorageLockFd, - g_instance.attr.attr_storage.xlog_lock_file_path))); + g_instance.attr.attr_storage.xlog_lock_file_path))); return false; } - +// 释放锁 static bool ReleaseLock() { + // 如果XLog锁文件路径为NULL,表示无需释放锁,直接返回成功 if (g_instance.attr.attr_storage.xlog_lock_file_path == NULL) { return true; } - + // 尝试释放NAS写锁,如果成功释放锁则返回成功 if (UnlockNasWriteFile(g_instance.xlog_cxt.shareStorageLockFd)) { return true; } - + // 如果释放锁失败,报告致命错误,指明无法解锁XLog锁文件 ereport(FATAL, (errmsg("could not unlock lock file(%d) %s", g_instance.xlog_cxt.shareStorageLockFd, - g_instance.attr.attr_storage.xlog_lock_file_path))); + g_instance.attr.attr_storage.xlog_lock_file_path))); return false; } - +// 检查并拷贝XLog数据 bool CheckAndCopyXLog(bool forceCopy) { ShareStorageXLogCtl *sharestorageCtl = g_instance.xlog_cxt.shareStorageXLogCtl; do { XLogRecPtr localFlush = InvalidXLogRecPtr; + // 如果当前为主备模式,获取主备同步位置,否则获取本地刷写位置 if (IS_SHARED_STORAGE_MAIN_STANDBY_MODE) { localFlush = GetFlushMainStandby(); } else { localFlush = GetFlushRecPtr(); } + // 如果本地刷写位置小于等于共享存储中插入位置,表示无需拷贝 if (XLByteLE(localFlush, sharestorageCtl->insertHead)) { return true; } - + // 获取可以覆写的最大位置 XLogRecPtr maxPosCanWrite = GetMaxPosCanOverWrite(); + // 如果共享存储插入位置小于最大可覆写位置,进行XLog拷贝 if (XLByteLT(sharestorageCtl->insertHead, maxPosCanWrite)) { + // 计算期望的拷贝位置,为本地刷写位置与最大可覆写位置中较小的值 XLogRecPtr expectPos = XLByteLT(localFlush, maxPosCanWrite) ? localFlush : maxPosCanWrite; + // 计算期望的拷贝位置,为本地刷写位置与最大可覆写位置中较小的值 if ((expectPos - sharestorageCtl->insertHead) > MAX_SIZE_CAN_COPY_TO_SHARE) { expectPos = sharestorageCtl->insertHead + MAX_SIZE_CAN_COPY_TO_SHARE; } + // 执行XLog数据拷贝 DoXlogCopy(expectPos); } } while (forceCopy && GetLock()); - return false; + return false; // 返回false表示未成功执行拷贝 } - +// 该函数用于关闭共享存储的XLog拷贝操作 void ShutdownShareStorageXLogCopy() { + // 如果未配置XLog文件路径,直接返回 if (g_instance.attr.attr_storage.xlog_file_path == NULL) { return; } - + // 分配和初始化拷贝缓冲区 if (t_thrd.sharestoragexlogcopyer_cxt.originBuf == NULL) { t_thrd.sharestoragexlogcopyer_cxt.originBuf = (char *)palloc(ShareStorageBufSize + g_instance.xlog_cxt.shareStorageopCtl.blkSize); t_thrd.sharestoragexlogcopyer_cxt.buf = (char *)TYPEALIGN(g_instance.xlog_cxt.shareStorageopCtl.blkSize, t_thrd.sharestoragexlogcopyer_cxt.originBuf); } - + // 读取共享存储控制信息并执行XLog拷贝 ReadShareStorageCtlInfo(g_instance.xlog_cxt.shareStorageXLogCtl); CheckAndCopyXLog(true); + // 如果读取文件描述符有效,关闭文件 if (t_thrd.sharestoragexlogcopyer_cxt.readFile >= 0) { (void)close(t_thrd.sharestoragexlogcopyer_cxt.readFile); } - + // 释放拷贝缓冲区的内存 if (t_thrd.sharestoragexlogcopyer_cxt.originBuf != NULL) { pfree(t_thrd.sharestoragexlogcopyer_cxt.originBuf); t_thrd.sharestoragexlogcopyer_cxt.originBuf = NULL; t_thrd.sharestoragexlogcopyer_cxt.buf = NULL; } - + // 清除相关的Latch和标志 g_instance.proc_base->ShareStoragexlogCopyerLatch = NULL; + // 打印日志,表示XLog拷贝已停止 ereport(LOG, (errmsg("stopped xlog copy at %X/%X", - (uint32)(g_instance.xlog_cxt.shareStorageXLogCtl->insertHead >> PTR_PRINT_SHIFT_SIZE), - (uint32)g_instance.xlog_cxt.shareStorageXLogCtl->insertHead))); + (uint32)(g_instance.xlog_cxt.shareStorageXLogCtl->insertHead >> PTR_PRINT_SHIFT_SIZE), + (uint32)g_instance.xlog_cxt.shareStorageXLogCtl->insertHead))); } - +// 该函数用于检查文件大小是否可以更新 bool FileSizeCanUpdate() { + // 获取结束检查点的位置 XLogRecPtr endCheckpointPtr = sizeof(CheckPointPlus) + t_thrd.shemem_ptr_cxt.ControlFile->checkPoint; + // 获取共享存储控制信息 ShareStorageXLogCtl *ctlInfo = g_instance.xlog_cxt.shareStorageXLogCtl; + // 计算检查点位置和插入位置的偏移量 uint64 checkpointPos = endCheckpointPtr % ctlInfo->xlogFileSize; uint64 headPos = ctlInfo->insertHead % ctlInfo->xlogFileSize; + // 如果满足一定条件,返回true表示文件大小可以更新,否则返回false if ((XLByteLE(endCheckpointPtr, ctlInfo->insertHead) && checkpointPos <= headPos) || XLByteLT(ctlInfo->insertHead, endCheckpointPtr)) { return true; @@ -428,32 +507,37 @@ bool FileSizeCanUpdate() return false; } - +// 该函数用于检查共享存储控制信息 void CheckShareStorageCtlInfo(XLogRecPtr localEnd) { + // 如果不处于共享存储模式,直接返回 if (!IS_SHARED_STORAGE_MODE) { return; } - + // 获取共享存储的控制信息 ShareStorageXLogCtl *ctlInfo = g_instance.xlog_cxt.shareStorageXLogCtl; ReadShareStorageCtlInfo(ctlInfo); - + // 检查系统标识是否一致,不一致时报错 if (ctlInfo->systemIdentifier != GetSystemIdentifier()) { ereport(FATAL, (errmsg("database system version is different between shared storage %lu and local %lu", ctlInfo->systemIdentifier, GetSystemIdentifier()))); } - + // 初始化 uint32 shiftSize = 32; - XLogRecPtr shareStorageLatestRecordStart = InvalidXLogRecPtr; + XLogRecPtr shareStorageLatestRecordStart = InvalidXLogRecPtr; // 初始化为无效的位置 int shareStorageLatestRecordLen; pg_crc32 shareStorageLatestRecordCrc; + // 在共享存储中查找最后一个记录的信息 FindLastRecordCheckInfoOnShareStorage(&shareStorageLatestRecordStart, &shareStorageLatestRecordCrc, &shareStorageLatestRecordLen); - + // 断言共享存储最新记录的起始位置加长度小于等于插入位置,确保共享存储数据正确性 Assert(XLByteLE(shareStorageLatestRecordStart + shareStorageLatestRecordLen, ctlInfo->insertHead)); + // 如果本地的结束位置小于共享存储的插入位置 if (XLByteLT(localEnd, ctlInfo->insertHead)) { + // 如果共享存储最新记录的起始位置是无效的,或者本地的结束位置和最新记录的起始位置加对齐长度不一致 if (XLByteEQ(shareStorageLatestRecordStart, InvalidXLogRecPtr) || !XLByteEQ(localEnd, shareStorageLatestRecordStart + MAXALIGN(shareStorageLatestRecordLen))) { + // 报错,表示本地头部小于共享存储头部 ereport(FATAL, (errmsg("the local's head is smaller than The shared storage's head"), errdetail("The shared storage's head %X/%X, the local's head is %X/%X. " "lastrecord on share storage: startlsn:%X/%X crc %u, len %d", @@ -465,10 +549,12 @@ void CheckShareStorageCtlInfo(XLogRecPtr localEnd) shareStorageLatestRecordLen))); } + // 打印日志,表示修改共享存储头部 ereport(LOG, (errmsg("modify share storage head from %X/%X to %X/%X", static_cast(ctlInfo->insertHead >> shiftSize), static_cast(ctlInfo->insertHead), static_cast(localEnd >> shiftSize), static_cast(localEnd)))); + // 修改共享存储控制信息的插入位置和CRC,然后刷新到共享存储 ctlInfo->insertHead = localEnd; ctlInfo->crc = CalShareStorageCtlInfoCrc(ctlInfo); UpdateShareStorageCtlInfo(ctlInfo); @@ -476,10 +562,12 @@ void CheckShareStorageCtlInfo(XLogRecPtr localEnd) } else { char path[MAXPGPATH]; XLogSegNo sendSegNo; + // 将插入位置转换为XLog段号,并获取路径 XLByteToSeg(ctlInfo->insertHead, sendSegNo); XLogFilePath(path, MAXPGPATH, t_thrd.xlog_cxt.ThisTimeLineID, sendSegNo); struct stat stat_buf; + // 如果路径对应的文件不存在,报错,表示本地尾部大于共享存储头部 if (stat(path, &stat_buf) != 0) { ereport(FATAL, (errmsg("the local's tail is bigger than The shared storage's head %X/%X", static_cast(ctlInfo->insertHead >> shiftSize), @@ -488,7 +576,9 @@ void CheckShareStorageCtlInfo(XLogRecPtr localEnd) bool crcValid = false; + // 获取共享存储最新记录的CRC校验值,用于比较本地记录的校验值 pg_crc32 localCheckCrc = GetXlogRecordCrc(shareStorageLatestRecordStart, crcValid, XLogPageRead, 0); + // 如果校验值不匹配,报错,表示共享存储请求的LSN的 if (shareStorageLatestRecordCrc != localCheckCrc) { ereport(FATAL, (errmsg("shared storage request lsn[%X/%X]'s crc mismatched (share, local):[%u,%u].", static_cast(shareStorageLatestRecordStart >> shiftSize), @@ -497,8 +587,8 @@ void CheckShareStorageCtlInfo(XLogRecPtr localEnd) } } - uint32 localTerm = Max(g_instance.comm_cxt.localinfo_cxt.term_from_file, - g_instance.comm_cxt.localinfo_cxt.term_from_xlog); + uint32 localTerm = + Max(g_instance.comm_cxt.localinfo_cxt.term_from_file, g_instance.comm_cxt.localinfo_cxt.term_from_xlog); if (localTerm > ctlInfo->term) { ctlInfo->term = localTerm; ctlInfo->crc = CalShareStorageCtlInfoCrc(ctlInfo); @@ -506,48 +596,55 @@ void CheckShareStorageCtlInfo(XLogRecPtr localEnd) FsyncXlogToShareStorage(); } } - +// 更新共享存储控制信息 void UpdateShareStorageCtlInfo() { bool changed = false; ShareStorageXLogCtl *ctlInfo = g_instance.xlog_cxt.shareStorageXLogCtl; ReadShareStorageCtlInfo(ctlInfo); + // 检查版本号是否一致,如果不一致则更新版本号,并设置changed标志为true if (ctlInfo->version != CURRENT_SHARE_STORAGE_CTL_VERSION) { ctlInfo->version = CURRENT_SHARE_STORAGE_CTL_VERSION; changed = true; } + // 检查长度是否一致,如果不一致则更新长度,并设置changed标志为true if (ctlInfo->length != SizeOfShareStorageXLogCtl) { ctlInfo->length = SizeOfShareStorageXLogCtl; changed = true; } + // 检查xlog文件大小是否一致,如果不一致则更新xlog文件大小,并设置changed标志为true if (ctlInfo->xlogFileSize != (uint64)g_instance.attr.attr_storage.xlog_file_size) { Assert(g_instance.xlog_cxt.shareStorageopCtl.xlogFileSize == ctlInfo->xlogFileSize); + // 如果文件大小无法更新,报错 if (!FileSizeCanUpdate()) { ereport(FATAL, (errmsg("could not update share storage size."), errdetail("current size:%lu, new size:%lu", ctlInfo->xlogFileSize, g_instance.attr.attr_storage.xlog_file_size))); } + // 更新共享存储操作控制中的xlog文件大小,并将控制信息中的xlog文件大小设置为新值 g_instance.xlog_cxt.shareStorageopCtl.xlogFileSize = g_instance.attr.attr_storage.xlog_file_size; ctlInfo->xlogFileSize = g_instance.xlog_cxt.shareStorageopCtl.xlogFileSize; changed = true; } + // 如果有更新,则重新计算CRC校验值,然后更新控制信息 if (changed) { ctlInfo->crc = CalShareStorageCtlInfoCrc(ctlInfo); UpdateShareStorageCtlInfo(ctlInfo); } } - +// 共享存储XLog复制后台退出及清理函数 static void SharedStorageXlogCopyBackendQuitAndClean(int code, Datum arg) { + // 打印日志,表示解锁文件 ereport(LOG, (errmsg("SharedStorageXlogCopyBackendMain unlock file(%d) %s", g_instance.xlog_cxt.shareStorageLockFd, - g_instance.attr.attr_storage.xlog_lock_file_path))); - ReleaseLock(); + g_instance.attr.attr_storage.xlog_lock_file_path))); + ReleaseLock(); // 释放锁 } - +// 这段代码的目的是确保线程能够正确地响应各种系统和自定义的信号,以保证程序的稳定运行和正确退出。 static void InitThreadSignal() { (void)gspqsignal(SIGHUP, SharedStorageXlogCopyBackendSigHupHandler); /* reload config file */ @@ -570,31 +667,37 @@ static void InitThreadSignal() gs_signal_setmask(&t_thrd.libpq_cxt.UnBlockSig, NULL); (void)gs_signal_unblock_sigusr2(); } - +// 该函数主要用于在后台进程中执行共享存储的 XLog 复制操作 void SharedStorageXlogCopyBackendMain(void) { + // 初始化线程信号处理 InitThreadSignal(); + if (IS_SHARED_STORAGE_MAIN_STANDBY_MODE) { + // 检查共享存储的写锁是否已经获取,如果没有获取到写锁,则会尝试获取写锁。 CheckShareStorageWriteLock(); } + // 如果处于恢复状态且不是主-备模式,记录一条日志并退出 if (RecoveryInProgress() && !IS_SHARED_STORAGE_MAIN_STANDBY_MODE) { ereport(LOG, (errmsg("stopped xlog copy in recovery"))); proc_exit(0); /* done */ } - + // 注册进程退出时的清理函数 on_proc_exit(SharedStorageXlogCopyBackendQuitAndClean, 0); - + // 设置进程 latch,用于标识进程活动 g_instance.proc_base->ShareStoragexlogCopyerLatch = &t_thrd.proc->procLatch; - + // 分配用于复制 XLog 数据的缓冲区 t_thrd.sharestoragexlogcopyer_cxt.originBuf = (char *)palloc(ShareStorageBufSize + g_instance.xlog_cxt.shareStorageopCtl.blkSize); - t_thrd.sharestoragexlogcopyer_cxt.buf = (char *)TYPEALIGN(g_instance.xlog_cxt.shareStorageopCtl.blkSize, - t_thrd.sharestoragexlogcopyer_cxt.originBuf); + t_thrd.sharestoragexlogcopyer_cxt.buf = + (char *)TYPEALIGN(g_instance.xlog_cxt.shareStorageopCtl.blkSize, t_thrd.sharestoragexlogcopyer_cxt.originBuf); + // 更新共享存储控制信息 UpdateShareStorageCtlInfo(); + // 记录 XLog 复制操作的开始 ereport(LOG, (errmsg("start xlog copy at %X/%X", - (uint32)(g_instance.xlog_cxt.shareStorageXLogCtl->insertHead >> PTR_PRINT_SHIFT_SIZE), - (uint32)g_instance.xlog_cxt.shareStorageXLogCtl->insertHead))); - + (uint32)(g_instance.xlog_cxt.shareStorageXLogCtl->insertHead >> PTR_PRINT_SHIFT_SIZE), + (uint32)g_instance.xlog_cxt.shareStorageXLogCtl->insertHead))); + // 根据模式确定当前时间线 ID if (IS_SHARED_STORAGE_MAIN_STANDBY_MODE) { t_thrd.xlog_cxt.ThisTimeLineID = GetRecoveryTargetTLI(); } else { @@ -604,47 +707,54 @@ void SharedStorageXlogCopyBackendMain(void) uint64 checkLockCount = 0; pgstat_report_activity(STATE_IDLE, NULL); + // 主循环,处理 XLog 复制和后台活动 for (;;) { ResetLatch(&t_thrd.proc->procLatch); pgstat_report_activity(STATE_RUNNING, NULL); - + // 每次循环定期检查并获取锁 ++checkLockCount; if (checkLockCount & CHECK_LOCK_INTERVAL) { GetLock(); } + // 检查是否有关闭请求,如果有,执行相关清理操作并退出 if (t_thrd.sharestoragexlogcopyer_cxt.shutdown_requested) { t_thrd.sharestoragexlogcopyer_cxt.shutdown_requested = false; g_instance.proc_base->ShareStoragexlogCopyerLatch = NULL; ShutdownShareStorageXLogCopy(); - proc_exit(0); /* done */ + proc_exit(0); /*终止当前进程或线程*/ } + // 检查是否收到 SIGHUP 信号,如果是,重新加载配置文件 if (t_thrd.sharestoragexlogcopyer_cxt.got_SIGHUP) { t_thrd.sharestoragexlogcopyer_cxt.got_SIGHUP = false; ProcessConfigFile(PGC_SIGHUP); } + // 检查并复制 XLog 数据,如果需要等待则继续下一次循环 bool waitCopy = CheckAndCopyXLog(false); if (waitCopy) { continue; } + // 报告进程状态为空闲并等待活动 pgstat_report_activity(STATE_IDLE, NULL); const long sleepTime = 1000L; (void)WaitLatch(&t_thrd.proc->procLatch, WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH, sleepTime); } + // 清理操作并退出进程 g_instance.proc_base->ShareStoragexlogCopyerLatch = NULL; ereport(LOG, (errmsg("stopped xlog copy at %X/%X", - (uint32)(g_instance.xlog_cxt.shareStorageXLogCtl->insertHead >> PTR_PRINT_SHIFT_SIZE), - (uint32)g_instance.xlog_cxt.shareStorageXLogCtl->insertHead))); + (uint32)(g_instance.xlog_cxt.shareStorageXLogCtl->insertHead >> PTR_PRINT_SHIFT_SIZE), + (uint32)g_instance.xlog_cxt.shareStorageXLogCtl->insertHead))); proc_exit(0); } - +// 此函数的作用是唤醒名为 XLogCopyerBackend 的后台线程。 void WakeUpXLogCopyerBackend() { if (g_instance.proc_base->ShareStoragexlogCopyerLatch != NULL) { + // 用于将闩锁设置为激活状态,从而唤醒等待中的 XLogCopyerBackend 线程继续执行。 SetLatch(g_instance.proc_base->ShareStoragexlogCopyerLatch); } } @@ -714,8 +824,8 @@ bool XLogOverwriteFromLocal(bool force) ReadShareStorageCtlInfo(ctlInfo); if (ctlInfo->systemIdentifier != GetSystemIdentifier()) { ereport(WARNING, (errmsg("database system version is different between shared storage and local"), - errdetail("The shared storage's system version is %lu, the local's system version is %lu.", - ctlInfo->systemIdentifier, GetSystemIdentifier()))); + errdetail("The shared storage's system version is %lu, the local's system version is %lu.", + ctlInfo->systemIdentifier, GetSystemIdentifier()))); if (!force) { return false; } @@ -826,8 +936,8 @@ static bool XLogCopyWrite(char *buf, int nbytes, XLogRecPtr recptr) if (copyFile >= 0) { if (close(copyFile) != 0) { - ereport(WARNING, (errcode_for_file_access(), - errmsg("could not close log file %s: %m", XLogFileNameP(copyFileTLI, copySegNo)))); + ereport(WARNING, (errcode_for_file_access(), errmsg("could not close log file %s: %m", + XLogFileNameP(copyFileTLI, copySegNo)))); return false; } } @@ -852,7 +962,7 @@ static bool XLogCopyWrite(char *buf, int nbytes, XLogRecPtr recptr) if (copyOff != (uint32)startoff) { if (lseek(copyFile, (off_t)startoff, SEEK_SET) < 0) { ereport(WARNING, (errcode_for_file_access(), errmsg("could not seek in log file %s to offset %lu: %m", - XLogFileNameP(copyFileTLI, copySegNo), startoff))); + XLogFileNameP(copyFileTLI, copySegNo), startoff))); return false; } copyOff = startoff; @@ -868,16 +978,16 @@ static bool XLogCopyWrite(char *buf, int nbytes, XLogRecPtr recptr) errno = ENOSPC; } ereport(WARNING, (errcode_for_file_access(), - errmsg("could not write to log file %s at offset %u, length %lu: %m", - XLogFileNameP(copyFileTLI, copySegNo), copyOff, INT2ULONG(segbytes)))); + errmsg("could not write to log file %s at offset %u, length %lu: %m", + XLogFileNameP(copyFileTLI, copySegNo), copyOff, INT2ULONG(segbytes)))); return false; } if (copyOff == (uint32)0 && segbytes >= (int)sizeof(XLogPageHeaderData)) { if (((XLogPageHeader)buf)->xlp_magic == XLOG_PAGE_MAGIC && (copySegNo * XLogSegSize) != ((XLogPageHeader)buf)->xlp_pageaddr) { ereport(WARNING, (errcode_for_file_access(), - errmsg("unexpected page addr %lu of log file %s", ((XLogPageHeader)buf)->xlp_pageaddr, - XLogFileNameP(copyFileTLI, copySegNo)))); + errmsg("unexpected page addr %lu of log file %s", ((XLogPageHeader)buf)->xlp_pageaddr, + XLogFileNameP(copyFileTLI, copySegNo)))); return false; } } @@ -912,7 +1022,7 @@ static bool DoXLogCopyFromShare(XLogRecPtr copyStart) xlogreader = XLogReaderAllocate(SharedStorageXLogPageRead, 0, g_instance.xlog_cxt.shareStorageopCtl.blkSize); if (xlogreader == NULL) { ereport(WARNING, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("out of memory"), - errdetail("Failed while allocating an XLog reading processor"))); + errdetail("Failed while allocating an XLog reading processor"))); return false; } xlogreader->system_identifier = GetSystemIdentifier(); @@ -937,7 +1047,7 @@ static bool DoXLogCopyFromShare(XLogRecPtr copyStart) if (copyFile >= 0) { if (close(copyFile) != 0) { ereport(WARNING, (errcode_for_file_access(), - errmsg("could not close log file %s: %m", XLogFileNameP(copyFileTLI, copySegNo)))); + errmsg("could not close log file %s: %m", XLogFileNameP(copyFileTLI, copySegNo)))); return false; } } @@ -967,8 +1077,8 @@ bool XLogOverwriteFromShare() if (ctlInfo->systemIdentifier != GetSystemIdentifier()) { ereport(WARNING, (errmsg("database system version is different between shared storage and local"), - errdetail("The shared storage's system version is %lu, the local's system version is %lu.", - ctlInfo->systemIdentifier, GetSystemIdentifier()))); + errdetail("The shared storage's system version is %lu, the local's system version is %lu.", + ctlInfo->systemIdentifier, GetSystemIdentifier()))); return false; } /* The local log is consistent with the local data and cannot be simply overwritten from the shared storage. @@ -1062,4 +1172,3 @@ bool UnlockNasWriteFile(int fd) ereport(WARNING, (errcode_for_file_access(), errmsg("could not unlock lock file : %m"))); return false; } - -- 2.34.1 From 303eebba66bba77a4c3753efb50cc2dac0989233 Mon Sep 17 00:00:00 2001 From: nuoya <1204149038@qq.com> Date: Mon, 25 Sep 2023 16:19:40 +0800 Subject: [PATCH 23/50] Update globalplancache_util.cpp --- .../globalplancache/globalplancache_util.cpp | 670 +++++++++++++----- 1 file changed, 485 insertions(+), 185 deletions(-) diff --git a/src/gausskernel/process/globalplancache/globalplancache_util.cpp b/src/gausskernel/process/globalplancache/globalplancache_util.cpp index b3c0927cf..f8e8c9462 100644 --- a/src/gausskernel/process/globalplancache/globalplancache_util.cpp +++ b/src/gausskernel/process/globalplancache/globalplancache_util.cpp @@ -1,26 +1,26 @@ /* -* 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. -* ------------------------------------------------------------------------- -* -* globalplancache_util.cpp -* global plan cache -* -* IDENTIFICATION -* src/gausskernel/process/globalplancache/globalplancache_util.cpp -* -* ------------------------------------------------------------------------- -*/ + * 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. + * ------------------------------------------------------------------------- + * + * globalplancache_util.cpp + * global plan cache + * + * IDENTIFICATION + * src/gausskernel/process/globalplancache/globalplancache_util.cpp + * + * ------------------------------------------------------------------------- + */ #include "postgres.h" #include "knl/knl_variable.h" @@ -38,10 +38,17 @@ #include "utils/memutils.h" #include "utils/plancache.h" #include "utils/syscache.h" -void -GlobalPlanCache::FillClassicEnvSignatures(GPCEnv *env) +/* + * 功能:填充经典环境签名 + * + * 参数列表: + * env:指向 GPCEnv 结构的指针,表示环境配置信息 + */ +void GlobalPlanCache::FillClassicEnvSignatures(GPCEnv *env) { + // 初始化环境签名为0 env->plainenv.env_signature = 0; + // 逐个设置环境签名的各位,每一位表示一个环境配置的开关状态 env->plainenv.env_signature |= u_sess->attr.attr_sql.enable_fast_numeric; env->plainenv.env_signature |= u_sess->attr.attr_sql.enable_global_stats << 1; env->plainenv.env_signature |= u_sess->attr.attr_sql.enable_hdfs_predicate_pushdown << 2; @@ -74,17 +81,14 @@ GlobalPlanCache::FillClassicEnvSignatures(GPCEnv *env) env->plainenv.env_signature |= g_instance.attr.attr_sql.enable_orc_cache << 29; env->plainenv.env_signature |= u_sess->attr.attr_sql.acceleration_with_compute_pool << 30; env->plainenv.env_signature |= u_sess->attr.attr_sql.enable_extrapolation_stats << 31; - } - /* * @Description:Fill in the environment signatures which are bitmaps for the boolean type GUC parameters * @in num: GPCEnv * @return - void -*/ -void -GlobalPlanCache::FillEnvSignatures(GPCEnv *env) + */ +void GlobalPlanCache::FillEnvSignatures(GPCEnv *env) { /* We should only call this function if env is not NULL and it's not filled */ Assert(env && !env->filled); @@ -126,19 +130,27 @@ GlobalPlanCache::FillEnvSignatures(GPCEnv *env) env->plainenv.env_signature2 |= u_sess->attr.attr_sql.enable_opfusion << 17; env->plainenv.env_signature2 |= u_sess->attr.attr_sql.enable_partition_opfusion << 18; } - -void -GlobalPlanCache::EnvFill(GPCEnv *env, bool depends_on_role) +/* + * 功能:填充环境信息 + * + * 参数列表: + * env:指向 GPCEnv 结构的指针,表示环境配置信息 + * depends_on_role:布尔值,表示是否依赖于角色 + */ +void GlobalPlanCache::EnvFill(GPCEnv *env, bool depends_on_role) { /* We should only call this function if env is not NULL and it's not filled */ + // 断言:确保 env 不为 NULL,且未被填充过 Assert(env && !env->filled); - + // 初始化 env 为0 errno_t rc = memset_s(env, sizeof(GPCEnv), 0, sizeof(GPCEnv)); securec_check(rc, "\0", "\0"); + // 填充环境签名 FillEnvSignatures(env); + // 逐个设置环境配置参数 env->plainenv.best_agg_plan = u_sess->attr.attr_sql.best_agg_plan; env->plainenv.query_dop_tmp = u_sess->attr.attr_sql.query_dop_tmp; env->plainenv.rewrite_rule = u_sess->attr.attr_sql.rewrite_rule; @@ -174,46 +186,47 @@ GlobalPlanCache::EnvFill(GPCEnv *env, bool depends_on_role) env->plainenv.sql_beta_feature = u_sess->attr.attr_sql.sql_beta_feature; /* new GUC parameters which affect the plan */ + // 填充新的 GUC 参数 env->plainenv.qrw_inlist2join_optmode = u_sess->opt_cxt.qrw_inlist2join_optmode; env->plainenv.skew_strategy_store = u_sess->attr.attr_sql.skew_strategy_store; env->plainenv.database_id = u_sess->proc_cxt.MyDatabaseId; env->plainenv.plancachemode = u_sess->attr.attr_sql.g_planCacheMode; GlobalPlanCache::GetSchemaName(env); - + // 填充预期的计算节点组和默认的存储节点组 if (u_sess->attr.attr_sql.expected_computing_nodegroup) { - int rc = memcpy_s(env->expected_computing_nodegroup, - NAMEDATALEN, - u_sess->attr.attr_sql.expected_computing_nodegroup, - strlen(u_sess->attr.attr_sql.expected_computing_nodegroup)); + int rc = + memcpy_s(env->expected_computing_nodegroup, NAMEDATALEN, u_sess->attr.attr_sql.expected_computing_nodegroup, + strlen(u_sess->attr.attr_sql.expected_computing_nodegroup)); securec_check(rc, "", ""); } else { - env->expected_computing_nodegroup[0] = '\0'; + env->expected_computing_nodegroup[0] = '\0'; } if (u_sess->attr.attr_sql.default_storage_nodegroup) { - int rc = memcpy_s(env->default_storage_nodegroup, - NAMEDATALEN, - u_sess->attr.attr_sql.default_storage_nodegroup, - strlen(u_sess->attr.attr_sql.default_storage_nodegroup)); + int rc = memcpy_s(env->default_storage_nodegroup, NAMEDATALEN, u_sess->attr.attr_sql.default_storage_nodegroup, + strlen(u_sess->attr.attr_sql.default_storage_nodegroup)); securec_check(rc, "", ""); } else { env->default_storage_nodegroup[0] = '\0'; } + // 设置依赖角色信息和用户 ID env->depends_on_role = depends_on_role; env->user_oid = GetUserId(); } - -void -GlobalPlanCache::GetSchemaName(GPCEnv *env) +/* + * 功能:获取当前模式的模式名称 + * + * 参数列表: + * env:指向 GPCEnv 结构的指针,表示环境配置信息 + */ +void GlobalPlanCache::GetSchemaName(GPCEnv *env) { /* get schema name */ if (u_sess->attr.attr_common.namespace_current_schema) { - int rc = memcpy_s(env->schema_name, - NAMEDATALEN, - u_sess->attr.attr_common.namespace_current_schema, + int rc = memcpy_s(env->schema_name, NAMEDATALEN, u_sess->attr.attr_common.namespace_current_schema, strlen(u_sess->attr.attr_common.namespace_current_schema)); securec_check(rc, "", ""); } else { @@ -221,46 +234,77 @@ GlobalPlanCache::GetSchemaName(GPCEnv *env) } } - +/* + * 功能:全局计划缓存重置函数 + * + * 说明:此函数用于重置全局计划缓存,并清除相关内存上下文中的数据。 + */ void GPCResetAll() { pthread_mutex_lock(&g_instance.gpc_reset_lock); for (int i = 0; i < MAX_GLOBAL_CACHEMEM_NUM; ++i) { + // 解除内存上下文中的子上下文封印,以便重置 MemoryContextUnSealChildren(g_instance.cache_cxt.global_plancache_mem[i]); + // 重置内存上下文,清除其中的数据 MemoryContextReset(g_instance.cache_cxt.global_plancache_mem[i]); } + // 初始化全局计划缓存 g_instance.plan_cache->Init(); pthread_mutex_unlock(&g_instance.gpc_reset_lock); + // 记录日志,表示全局计划缓存已重置 GPC_LOG("gpc reset all", 0, 0); } - -void GPCCleanDatanodeStatement(int dn_stmt_num, const char* stmt_name) +/* + * 功能:清理数据节点上的语句 + * + * 参数列表: + * dn_stmt_num:要清理的语句数量 + * stmt_name:待清理语句的名称 + * + * 说明:此函数用于清理数据节点上的指定语句。仅在协调器节点且提供了有效的语句名称时执行。 + */ +void GPCCleanDatanodeStatement(int dn_stmt_num, const char *stmt_name) { + // 如果语句名称为空、为空字符串或不在协调器节点上,直接返回 if (stmt_name == NULL || stmt_name[0] == '\0' || !IS_PGXC_COORDINATOR) return; int n = 0; char *tmp_name = NULL; + // 循环清理指定数量的语句 for (n = 0; n < dn_stmt_num; n++) { + // 获取当前要清理的语句名称 tmp_name = get_datanode_statement_name(stmt_name, n); + // 调用 DropDatanodeStatement 函数来清理语句 DropDatanodeStatement(tmp_name); + // 释放临时语句名称的内存 pfree_ext(tmp_name); } } - -void GPCReGplan(CachedPlanSource* plansource) +/* + * 功能:将 CachedPlanSource 切换为全局计划(gplan) + * + * 参数列表: + * plansource:要切换的 CachedPlanSource 对象 + * + * 说明:此函数用于将 CachedPlanSource 切换为全局计划(gplan)。如果该计划支持 gplan 且当前不是共享状态,则进行切换。 + * 切换后,将该计划移动到首个已保存的计划中,以便进行全局共享。 + */ +void GPCReGplan(CachedPlanSource *plansource) { /* if is unshared for has cplan, and create gplan this time, * reset to shared and move to first_saved_plan */ if (!plansource->is_support_gplan || !plansource->gpc.status.IsUnShareCplan()) return; Assert(plansource->is_saved); + // 设置计划为共享状态 plansource->gpc.status.SetKind(GPC_SHARED); /* move into first_saved_plan */ if (u_sess->pcache_cxt.ungpc_saved_plan == plansource) { u_sess->pcache_cxt.ungpc_saved_plan = plansource->next_saved; } else { - CachedPlanSource* psrc = NULL; + CachedPlanSource *psrc = NULL; + // 寻找并更新未共享计划列表 for (psrc = u_sess->pcache_cxt.ungpc_saved_plan; psrc; psrc = psrc->next_saved) { if (psrc->next_saved == plansource) { psrc->next_saved = plansource->next_saved; @@ -268,72 +312,104 @@ void GPCReGplan(CachedPlanSource* plansource) } } } + // 设置计划的位置为在本地已保存计划列表中 plansource->gpc.status.SetLoc(GPC_SHARE_IN_LOCAL_SAVE_PLAN_LIST); + // 将计划移动到首个已保存计划中 plansource->next_saved = u_sess->pcache_cxt.first_saved_plan; u_sess->pcache_cxt.first_saved_plan = plansource; } - +/* + * 功能:清理与会话相关的全局计划缓存 + * + * 参数列表:无 + * + * 说明:此函数用于清理与会话相关的全局计划缓存。它首先检查是否启用了 CN-GPC(全局计划缓存), + * 如果未启用则直接返回。然后,它会删除所有的已经准备好的语句(prepared statements)。 + * 如果计划处于共享内存中,则删除对应的内存上下文。 + */ void CNGPCCleanUpSession() { + // 如果未启用 CN-GPC,则直接返回 if (!ENABLE_CN_GPC) { return; } + // 删除所有已经准备好的语句 DropAllPreparedStatements(); /* if in shared memory, delete context. */ - CachedPlanSource* psrc = u_sess->pcache_cxt.ungpc_saved_plan; - CachedPlanSource* next = NULL; + CachedPlanSource *psrc = u_sess->pcache_cxt.ungpc_saved_plan; + CachedPlanSource *next = NULL; while (psrc != NULL) { next = psrc->next_saved; - Assert (!psrc->gpc.status.InShareTable()); + // 断言计划不在共享表中 + Assert(!psrc->gpc.status.InShareTable()); + // 如果计划不是私有计划,则删除 if (!psrc->gpc.status.IsPrivatePlan()) DropCachedPlan(psrc); psrc = next; } + // 清理已保存计划列表中的计划 psrc = u_sess->pcache_cxt.first_saved_plan; while (psrc != NULL) { next = psrc->next_saved; - Assert (!psrc->gpc.status.InShareTable()); + // 断言计划不在共享表中 + Assert(!psrc->gpc.status.InShareTable()); + // 如果计划不是私有计划,则删除 if (!psrc->gpc.status.IsPrivatePlan()) DropCachedPlan(psrc); psrc = next; } } - +/* + * 功能:清理与会话相关的已保存计划 + * + * 参数列表:无 + * + * 说明:此函数用于清理与会话相关的已保存计划。它首先检查是否启用了 GPC(全局计划缓存), + * 如果未启用或者没有需要清理的计划,则直接返回。然后,它减少未命名语句(unnamed statement) + * 的引用计数,如果该语句在共享表中。最后,它清理已保存计划列表和未共享计划列表中的计划, + * 如果这些计划不是私有计划的话。 + */ void GPCCleanUpSessionSavedPlan() { + // 如果未启用 GPC 或没有需要清理的计划,则直接返回 if (!ENABLE_GPC) { return; } - if (u_sess->pcache_cxt.first_saved_plan == NULL && - u_sess->pcache_cxt.unnamed_stmt_psrc == NULL && + if (u_sess->pcache_cxt.first_saved_plan == NULL && u_sess->pcache_cxt.unnamed_stmt_psrc == NULL && u_sess->pcache_cxt.ungpc_saved_plan == NULL) { return; } /* unnamed_stmt_psrc only save shared gpc plan or private plan, * so we only need to sub refcount for shared plan. */ + // 如果未命名语句存在并且在共享表中,则减少引用计数并置为NULL if (u_sess->pcache_cxt.unnamed_stmt_psrc && u_sess->pcache_cxt.unnamed_stmt_psrc->gpc.status.InShareTable()) { u_sess->pcache_cxt.unnamed_stmt_psrc->gpc.status.SubRefCount(); u_sess->pcache_cxt.unnamed_stmt_psrc = NULL; } /* if in shared memory, delete context. */ /* For DN and CN */ - CachedPlanSource* psrc = u_sess->pcache_cxt.first_saved_plan; - CachedPlanSource* next = NULL; + CachedPlanSource *psrc = u_sess->pcache_cxt.first_saved_plan; + CachedPlanSource *next = NULL; u_sess->pcache_cxt.first_saved_plan = NULL; while (psrc != NULL) { next = psrc->next_saved; - Assert (!psrc->gpc.status.InShareTable()); + // 断言计划不在共享表中 + Assert(!psrc->gpc.status.InShareTable()); + // 如果计划不是私有计划,则删除 if (!psrc->gpc.status.IsPrivatePlan()) DropCachedPlan(psrc); psrc = next; } /* For CN */ + // 清理未共享计划列表(For CN) psrc = u_sess->pcache_cxt.ungpc_saved_plan; next = NULL; while (psrc != NULL) { next = psrc->next_saved; - Assert (!psrc->gpc.status.InShareTable()); + // 断言计划不在共享表中 + Assert(!psrc->gpc.status.InShareTable()); + // 如果计划不是私有计划,则删除 if (!psrc->gpc.status.IsPrivatePlan()) DropCachedPlan(psrc); psrc = next; @@ -341,75 +417,128 @@ void GPCCleanUpSessionSavedPlan() } /* incase change shared plan in execute stage, copy stmt into sess */ -List* CopyLocalStmt(const List* stmt_list, const MemoryContext parent_cxt, MemoryContext* plan_context) +/* + * 功能:复制本地语句列表 + * + * 参数列表: + * stmt_list:待复制的本地语句列表 + * parent_cxt:父内存上下文,新创建的内存上下文将以其为父上下文 + * plan_context:用于存储新创建的内存上下文 + * + * 返回值:复制后的本地语句列表 + * + * 说明:此函数用于复制给定的本地语句列表,并将复制后的语句列表存储在新的内存上下文中。 + * 新内存上下文以 parent_cxt 为父上下文创建。复制完成后,函数会将新的内存上下文存储在 + * plan_context 指针所指向的位置。 + */ +List *CopyLocalStmt(const List *stmt_list, const MemoryContext parent_cxt, MemoryContext *plan_context) { - *plan_context = AllocSetContextCreate(parent_cxt, - "CopyedStmt", - ALLOCSET_DEFAULT_MINSIZE, - ALLOCSET_DEFAULT_INITSIZE, + // 创建新的内存上下文,以 parent_cxt 为父上下文 + *plan_context = AllocSetContextCreate(parent_cxt, "CopyedStmt", ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); /* * Copy plan into the new context. */ MemoryContext oldcxt = MemoryContextSwitchTo(*plan_context); - List* stmts = (List*)copyObject(stmt_list); + List *stmts = (List *)copyObject(stmt_list); (void)MemoryContextSwitchTo(oldcxt); return stmts; } /* function for modify SPICacheTable, global procedure plancache */ - +/* + * 功能:计算 SPI 缓存哈希值 + * + * 参数列表: + * key:用于计算哈希值的键值 + * keysize:键值的大小 + * + * 返回值:计算得到的哈希值 + * + * 说明:此函数用于计算 SPI 缓存中的哈希值。它接受一个键值(key)和键值的大小(keysize)作为输入, + * 并使用哈希算法计算出一个哈希值作为返回结果。如果键值为 NULL,函数将返回一个特殊值 INVALID_SPI_KEY。 + */ uint32 SPICacheHashFunc(const void *key, Size keysize) { if (unlikely(key == NULL)) { + // 如果键值为 NULL,返回特殊值 INVALID_SPI_KEY return INVALID_SPI_KEY; } + // 使用哈希算法计算哈希值 uint32 val1 = DatumGetUInt32(hash_any((const unsigned char *)key, (int)keysize)); return val1; } /* add new func */ +/* + * 功能:向 SPI 缓存表中插入条目 + * + * 参数列表: + * key:要插入的条目的键 + * func_oid:要插入的条目关联的函数 OID + * + * 说明:此函数用于向 SPI 缓存表中插入新的条目。它接受一个键(key)和一个函数 OID(func_oid)作为输入, + * 并将它们关联在一起插入到 SPI 缓存表中。如果 SPI 缓存表为空或键为特殊值 INVALID_SPI_KEY,则函数不执行任何操作。 + * 如果已经存在相同键的条目,函数会删除该条目并发出警告信息。 + */ void SPICacheTableInsert(uint32 key, Oid func_oid) { if (unlikely(u_sess->SPI_cxt.SPICacheTable == NULL || key == INVALID_SPI_KEY)) return; + // 检查是否已经存在相同的键 bool found = false; - SPIPlanCacheEnt* hentry = (SPIPlanCacheEnt*)hash_search(u_sess->SPI_cxt.SPICacheTable, - (void*)(&key), HASH_ENTER, &found); + SPIPlanCacheEnt *hentry = + (SPIPlanCacheEnt *)hash_search(u_sess->SPI_cxt.SPICacheTable, (void *)(&key), HASH_ENTER, &found); SPI_GPC_LOG("insert spiplan entry", NULL, func_oid); if (found) { + // 如果已经存在相同键的条目,释放相关的资源并发出警告信息 list_free_ext(hentry->SPIplan_list); hentry->SPIplan_list = NULL; elog(WARNING, "should not has same old function entry in SPICacheTable"); } else { hentry->SPIplan_list = NULL; } + // 关联函数 OID 到条目 hentry->func_oid = func_oid; } /* add plan for spi keep plan */ +/* + * 功能:向 SPI 缓存表中的条目插入 SPI 计划 + * + * 参数列表: + * key:要插入的条目的键 + * spi_plan:要插入的 SPI 计划 + * + * 说明:此函数用于向 SPI 缓存表中的指定条目插入新的 SPI 计划。它接受一个键(key)和一个 SPI 计划(spi_plan) + * 作为输入,将 SPI 计划与相应的条目关联起来。如果 SPI 缓存表为空,函数不执行任何操作。 + * 如果已经存在相同键的条目,函数会将 SPI 计划追加到该条目的计划列表中。 + */ void SPICacheTableInsertPlan(uint32 key, SPIPlanPtr spi_plan) { if (unlikely(u_sess->SPI_cxt.SPICacheTable == NULL)) return; + // 检查是否已经存在相同的键 bool found = false; - Assert (spi_plan->saved); - SPIPlanCacheEnt* hentry = (SPIPlanCacheEnt*)hash_search(u_sess->SPI_cxt.SPICacheTable, - (void*)(&key), HASH_ENTER, &found); + Assert(spi_plan->saved); + SPIPlanCacheEnt *hentry = + (SPIPlanCacheEnt *)hash_search(u_sess->SPI_cxt.SPICacheTable, (void *)(&key), HASH_ENTER, &found); if (found) { #ifdef USE_ASSERT_CHECKING /* cannot has same spiplan in list */ - ListCell* cell = NULL; + // 不能在列表中包含相同的 SPI 计划 + ListCell *cell = NULL; if (hentry->SPIplan_list != NULL) { - foreach(cell, hentry->SPIplan_list) { + foreach (cell, hentry->SPIplan_list) { SPIPlanPtr cur = (SPIPlanPtr)lfirst(cell); - Assert (cur->id != spi_plan->id); - Assert (cur != spi_plan); + Assert(cur->id != spi_plan->id); + Assert(cur != spi_plan); } } #endif SPI_GPC_LOG("insert spiplan into entry", spi_plan, hentry->func_oid); + // 切换内存上下文并将 SPI 计划追加到列表中 MemoryContext old_cxt = MemoryContextSwitchTo(u_sess->SPI_cxt.SPICacheTable->hcxt); hentry->SPIplan_list = lappend(hentry->SPIplan_list, spi_plan); (void)MemoryContextSwitchTo(old_cxt); @@ -419,60 +548,100 @@ void SPICacheTableInsertPlan(uint32 key, SPIPlanPtr spi_plan) } /* only use when delete function, plancache will be freed in SPI_freeplan */ +/* + * 功能:从 SPI 缓存表中删除指定键的条目 + * + * 参数列表: + * key:要删除的条目的键 + * + * 说明:此函数用于从 SPI 缓存表中删除指定键的条目。它接受一个键(key)作为输入, + * 并在 SPI 缓存表中查找匹配的条目,如果找到则删除它。如果 SPI 缓存表为空或未找到匹配的条目, + * 函数不执行任何操作。 + */ void SPIPlanCacheTableDelete(uint32 key) { if (unlikely(u_sess->SPI_cxt.SPICacheTable == NULL)) { return; } + // 查找并删除指定键的条目 bool found = false; - SPIPlanCacheEnt* hentry = (SPIPlanCacheEnt*)hash_search(u_sess->SPI_cxt.SPICacheTable, - (void*)(&key), HASH_REMOVE, &found); + SPIPlanCacheEnt *hentry = + (SPIPlanCacheEnt *)hash_search(u_sess->SPI_cxt.SPICacheTable, (void *)(&key), HASH_REMOVE, &found); if (hentry) { SPI_GPC_LOG("delete spiplan entry", NULL, hentry->func_oid); + // 释放计划列表中的内存 list_free_ext(hentry->SPIplan_list); } } - +/* + * 功能:从 SPI 缓存表中的指定键的条目中删除特定 SPI 计划 + * + * 参数列表: + * key:要查找的条目的键 + * plan:要删除的 SPI 计划 + * + * 说明:此函数用于从 SPI 缓存表中的指定键的条目中删除特定的 SPI 计划。 + * 它接受一个键(key)和一个 SPI 计划(plan)作为输入,并在指定的条目中查找匹配的 SPI 计划, + * 如果找到则删除它。如果 SPI 缓存表为空、未找到匹配的条目或未找到匹配的 SPI 计划, + * 函数不执行任何操作。 + */ void SPIPlanCacheTableDeletePlan(uint32 key, SPIPlanPtr plan) { if (unlikely(u_sess->SPI_cxt.SPICacheTable == NULL)) return; - SPIPlanCacheEnt* entry = SPIPlanCacheTableLookup(key); + // 查找指定键的条目 + SPIPlanCacheEnt *entry = SPIPlanCacheTableLookup(key); if (entry != NULL) { #ifdef USE_ASSERT_CHECKING - ListCell* cell = NULL; - foreach(cell, entry->SPIplan_list) { + ListCell *cell = NULL; + foreach (cell, entry->SPIplan_list) { SPIPlanPtr spiplan = (SPIPlanPtr)lfirst(cell); + // 断言确保要删除的计划与查找到的计划匹配 if (spiplan->id == plan->id) Assert(plan == spiplan); } #endif SPI_GPC_LOG("delete spiplan from entry", plan, entry->func_oid); - entry->SPIplan_list = list_delete_ptr(entry->SPIplan_list, (void*)plan); + // 从列表中删除指定的 SPI 计划 + entry->SPIplan_list = list_delete_ptr(entry->SPIplan_list, (void *)plan); } } - +/* + * 功能:使 SPI 缓存表中指定键的计划无效 + * + * 参数列表: + * key:要查找的条目的键 + * + * 说明:此函数用于使 SPI 缓存表中指定键的计划无效。它接受一个键(key)作为输入, + * 查找匹配的 SPI 缓存表条目,并将所有相关的计划标记为无效。 + * 如果 SPI 缓存表为空或未找到匹配的条目,函数不执行任何操作。 + */ void SPIPlanCacheTableInvalidPlan(uint32 key) { if (unlikely(u_sess->SPI_cxt.SPICacheTable == NULL)) { return; } bool found = false; - SPIPlanCacheEnt* hentry = (SPIPlanCacheEnt*)hash_search(u_sess->SPI_cxt.SPICacheTable, - (void*)(&key), HASH_REMOVE, &found); + // 查找指定键的条目 + SPIPlanCacheEnt *hentry = + (SPIPlanCacheEnt *)hash_search(u_sess->SPI_cxt.SPICacheTable, (void *)(&key), HASH_REMOVE, &found); if (hentry) { SPI_GPC_LOG("invalid each spiplan entry", NULL, hentry->func_oid); - ListCell* cell = NULL; + ListCell *cell = NULL; foreach (cell, hentry->SPIplan_list) { SPIPlanPtr spiplan = (SPIPlanPtr)lfirst(cell); + // 检查计划是否具有 plancache_list if (list_length(spiplan->plancache_list) == 0) continue; - ListCell* cl = NULL; + ListCell *cl = NULL; + // 遍历计划中的每个 CachedPlanSource foreach (cl, spiplan->plancache_list) { - CachedPlanSource* plansource = (CachedPlanSource*)lfirst(cl); + CachedPlanSource *plansource = (CachedPlanSource *)lfirst(cl); + // 如果计划在共享表中,将其状态标记为无效 if (plansource->gpc.status.InShareTable()) { plansource->gpc.status.SetStatus(GPC_INVALID); } else { + // 否则,将计划标记为无效 plansource->is_valid = false; if (plansource->gplan) plansource->gplan->is_valid = false; @@ -481,7 +650,13 @@ void SPIPlanCacheTableInvalidPlan(uint32 key) } } } - +/* + * 功能:初始化 SPI 计划缓存表 + * + * 说明:此函数用于初始化 SPI 计划缓存表。SPI 计划缓存表用于存储与 SQL 函数相关的计划, + * 其中每个计划都与一个唯一的键关联。在初始化过程中,函数会设置合适的哈希控制结构(ctl), + * 并创建哈希表来存储计划缓存条目。 + */ void SPICacheTableInit() { HASHCTL ctl; @@ -496,236 +671,355 @@ void SPICacheTableInit() ctl.entrysize = sizeof(SPIPlanCacheEnt); ctl.hash = uint32_hash; ctl.hcxt = u_sess->cache_mem_cxt; + // 创建 SPI 计划缓存表 u_sess->SPI_cxt.SPICacheTable = hash_create("SPIPlanCacheTable", func_per_user, &ctl, HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT); SPI_GPC_LOG("init spiplan cache table", NULL, 0); } - -SPIPlanCacheEnt* SPIPlanCacheTableLookup(uint32 key) +/* + * 功能:查找 SPI 计划缓存表中的条目 + * + * 说明:此函数用于在 SPI 计划缓存表中查找与给定键相关的 SPIPlanCacheEnt 条目。 + * 如果找到匹配的条目,则返回该条目的指针;如果未找到匹配的条目,则返回 NULL。 + */ +SPIPlanCacheEnt *SPIPlanCacheTableLookup(uint32 key) { if (unlikely(u_sess->SPI_cxt.SPICacheTable == NULL)) return NULL; bool found = false; - SPIPlanCacheEnt* hentry = (SPIPlanCacheEnt*)hash_search( - u_sess->SPI_cxt.SPICacheTable, (void*)(&key), HASH_FIND, &found); + // 使用哈希查找在 SPI 计划缓存表中查找匹配的条目 + SPIPlanCacheEnt *hentry = + (SPIPlanCacheEnt *)hash_search(u_sess->SPI_cxt.SPICacheTable, (void *)(&key), HASH_FIND, &found); if (found) - return hentry; + return hentry; // 如果找到匹配的条目,则返回该条目的指针 else - return NULL; + return NULL; // 如果未找到匹配的条目,则返回 NULL } /* set unique id in PLpgSQL_expr for gpc */ -static void set_id_stmt(PLpgSQL_stmt* stmt, uint32* unique_id); -static void set_id_block(PLpgSQL_stmt_block* block, uint32* unique_id); -static void set_id_if(PLpgSQL_stmt_if* stmt, uint32* unique_id); -static void set_id_case(PLpgSQL_stmt_case* stmt, uint32* unique_id); -static void set_id_return_query(PLpgSQL_stmt_return_query* stmt, uint32* unique_id); -static void set_id_raise(PLpgSQL_stmt_raise* stmt, uint32* unique_id); -static void set_id_dynexecute(PLpgSQL_stmt_dynexecute* stmt, uint32* unique_id); -static void set_id_dynfors(PLpgSQL_stmt_dynfors* stmt, uint32* unique_id); -static void set_id_open(PLpgSQL_stmt_open* stmt, uint32* unique_id); -static void set_id_expr(PLpgSQL_expr* expr, uint32* unique_id); -static void set_id_stmts(List* stmts, uint32* unique_id); - -static void set_id_expr(PLpgSQL_expr* expr, uint32* unique_id) +static void set_id_stmt(PLpgSQL_stmt *stmt, uint32 *unique_id); +static void set_id_block(PLpgSQL_stmt_block *block, uint32 *unique_id); +static void set_id_if(PLpgSQL_stmt_if *stmt, uint32 *unique_id); +static void set_id_case(PLpgSQL_stmt_case *stmt, uint32 *unique_id); +static void set_id_return_query(PLpgSQL_stmt_return_query *stmt, uint32 *unique_id); +static void set_id_raise(PLpgSQL_stmt_raise *stmt, uint32 *unique_id); +static void set_id_dynexecute(PLpgSQL_stmt_dynexecute *stmt, uint32 *unique_id); +static void set_id_dynfors(PLpgSQL_stmt_dynfors *stmt, uint32 *unique_id); +static void set_id_open(PLpgSQL_stmt_open *stmt, uint32 *unique_id); +static void set_id_expr(PLpgSQL_expr *expr, uint32 *unique_id); +static void set_id_stmts(List *stmts, uint32 *unique_id); +/* + * 功能:为 PL/pgSQL 表达式设置唯一标识符 + * + * 参数列表: + * expr:PL/pgSQL 表达式 + * unique_id:用于分配唯一标识符的计数器 + */ +static void set_id_expr(PLpgSQL_expr *expr, uint32 *unique_id) { + // 检查输入的表达式是否为 NULL,如果是 NULL,则无需进行设置 if (expr == NULL) return; + + // 为表达式设置唯一标识符,通过递增 unique_id 指针的值 expr->idx = ++(*unique_id); } -static void set_id_stmts(List* stmts, uint32* unique_id) +/* + * 功能:为 PL/pgSQL 语句列表中的每个语句设置唯一标识符 + * + * 参数列表: + * stmts:PL/pgSQL 语句列表 + * unique_id:用于分配唯一标识符的计数器 + */ +static void set_id_stmts(List *stmts, uint32 *unique_id) { - ListCell* s = NULL; + ListCell *s = NULL; + // 遍历语句列表 foreach (s, stmts) { - set_id_stmt((PLpgSQL_stmt*)lfirst(s), unique_id); + // 为当前语句设置唯一标识符 + set_id_stmt((PLpgSQL_stmt *)lfirst(s), unique_id); } } -static void set_id_block(PLpgSQL_stmt_block* block, uint32* unique_id) +/* + * 功能:为 PL/pgSQL 块语句设置唯一标识符 + * + * 参数列表: + * block:PL/pgSQL 块语句 + * unique_id:用于分配唯一标识符的计数器 + */ +static void set_id_block(PLpgSQL_stmt_block *block, uint32 *unique_id) { + // 为块语句中的语句列表设置唯一标识符 set_id_stmts(block->body, unique_id); + // 如果存在异常处理块 if (block->exceptions != NULL) { - ListCell* e = NULL; + ListCell *e = NULL; + // 遍历异常处理块中的每个异常 foreach (e, block->exceptions->exc_list) { - PLpgSQL_exception* exc = (PLpgSQL_exception*)lfirst(e); + PLpgSQL_exception *exc = (PLpgSQL_exception *)lfirst(e); + // 为异常处理中的语句列表设置唯一标识符 set_id_stmts(exc->action, unique_id); } } } - -static void set_id_if(PLpgSQL_stmt_if* stmt, uint32* unique_id) +/* + * 功能:为 PL/pgSQL IF 语句设置唯一标识符 + * + * 参数列表: + * stmt:PL/pgSQL IF 语句 + * unique_id:用于分配唯一标识符的计数器 + */ +static void set_id_if(PLpgSQL_stmt_if *stmt, uint32 *unique_id) { - ListCell* l = NULL; + ListCell *l = NULL; + // 为 IF 语句的条件表达式设置唯一标识符 set_id_expr(stmt->cond, unique_id); + // 为 IF 语句的主体语句设置唯一标识符 set_id_stmts(stmt->then_body, unique_id); + // 遍历 ELSE IF 子句列表 foreach (l, stmt->elsif_list) { - PLpgSQL_if_elsif* elif = (PLpgSQL_if_elsif*)lfirst(l); + PLpgSQL_if_elsif *elif = (PLpgSQL_if_elsif *)lfirst(l); + // 为 ELSE IF 子句的条件表达式设置唯一标识符 set_id_expr(elif->cond, unique_id); + // 为 ELSE IF 子句的语句列表设置唯一标识符 set_id_stmts(elif->stmts, unique_id); } + // 为 IF 语句的 ELSE 子句设置唯一标识符 set_id_stmts(stmt->else_body, unique_id); } -static void set_id_case(PLpgSQL_stmt_case* stmt, uint32* unique_id) +/* + * 功能:为 PL/pgSQL CASE 语句设置唯一标识符 + * + * 参数列表: + * stmt:PL/pgSQL CASE 语句 + * unique_id:用于分配唯一标识符的计数器 + */ +static void set_id_case(PLpgSQL_stmt_case *stmt, uint32 *unique_id) { - ListCell* l = NULL; + ListCell *l = NULL; + // 为 IF 语句的条件表达式设置唯一标识符 set_id_expr(stmt->t_expr, unique_id); + // 遍历 CASE 语句的 WHEN 子句列表 foreach (l, stmt->case_when_list) { - PLpgSQL_case_when* cwt = (PLpgSQL_case_when*)lfirst(l); + PLpgSQL_case_when *cwt = (PLpgSQL_case_when *)lfirst(l); + // 为 WHEN 子句的条件表达式设置唯一标识符 set_id_expr(cwt->expr, unique_id); + // 为 WHEN 子句的语句列表设置唯一标识符 set_id_stmts(cwt->stmts, unique_id); } + // 为 CASE 语句的 ELSE 子句设置唯一标识符 set_id_stmts(stmt->else_stmts, unique_id); } - -static void set_id_open(PLpgSQL_stmt_open* stmt, uint32* unique_id) +/* + * 功能:为 PL/pgSQL OPEN 语句设置唯一标识符 + * + * 参数列表: + * stmt:PL/pgSQL OPEN 语句 + * unique_id:用于分配唯一标识符的计数器 + */ +static void set_id_open(PLpgSQL_stmt_open *stmt, uint32 *unique_id) { - ListCell* lc = NULL; + ListCell *lc = NULL; + // 为 OPEN 语句的参数查询表达式设置唯一标识符 set_id_expr(stmt->argquery, unique_id); + // 为 OPEN 语句的查询表达式设置唯一标识符 set_id_expr(stmt->query, unique_id); + // 为 OPEN 语句的动态查询表达式设置唯一标识符 set_id_expr(stmt->dynquery, unique_id); + // 遍历 OPEN 语句的参数列表 foreach (lc, stmt->params) { - set_id_expr((PLpgSQL_expr*)lfirst(lc), unique_id); + // 为参数表达式设置唯一标识符 + set_id_expr((PLpgSQL_expr *)lfirst(lc), unique_id); } } -static void set_id_return_query(PLpgSQL_stmt_return_query* stmt, uint32* unique_id) +/* + * 功能:为 PL/pgSQL RETURN QUERY 语句设置唯一标识符 + * + * 参数列表: + * stmt:PL/pgSQL RETURN QUERY 语句 + * unique_id:用于分配唯一标识符的计数器 + */ +static void set_id_return_query(PLpgSQL_stmt_return_query *stmt, uint32 *unique_id) { - ListCell* lc = NULL; + ListCell *lc = NULL; + // 为 RETURN QUERY 语句的查询表达式设置唯一标识符 set_id_expr(stmt->query, unique_id); + // 为 RETURN QUERY 语句的动态查询表达式设置唯一标识符 set_id_expr(stmt->dynquery, unique_id); + // 遍历 RETURN QUERY 语句的参数列表 foreach (lc, stmt->params) { - set_id_expr((PLpgSQL_expr*)lfirst(lc), unique_id); + // 为参数表达式设置唯一标识符 + set_id_expr((PLpgSQL_expr *)lfirst(lc), unique_id); } } -static void set_id_raise(PLpgSQL_stmt_raise* stmt, uint32* unique_id) +/* + * 功能:为 PL/pgSQL RAISE 语句设置唯一标识符 + * + * 参数列表: + * stmt:PL/pgSQL RAISE 语句 + * unique_id:用于分配唯一标识符的计数器 + */ +static void set_id_raise(PLpgSQL_stmt_raise *stmt, uint32 *unique_id) { - ListCell* lc = NULL; + ListCell *lc = NULL; + // 遍历 RAISE 语句的参数列表 foreach (lc, stmt->params) { - set_id_expr((PLpgSQL_expr*)lfirst(lc), unique_id); + // 为参数表达式设置唯一标识符 + set_id_expr((PLpgSQL_expr *)lfirst(lc), unique_id); } + // 遍历 RAISE 语句的选项列表 foreach (lc, stmt->options) { - PLpgSQL_raise_option* opt = (PLpgSQL_raise_option*)lfirst(lc); + PLpgSQL_raise_option *opt = (PLpgSQL_raise_option *)lfirst(lc); + // 为选项表达式设置唯一标识符 set_id_expr(opt->expr, unique_id); } } - -static void set_id_dynexecute(PLpgSQL_stmt_dynexecute* stmt, uint32* unique_id) +/* + * 功能:为 PL/pgSQL DYNEXECUTE 语句设置唯一标识符 + * + * 参数列表: + * stmt:PL/pgSQL DYNEXECUTE 语句 + * unique_id:用于分配唯一标识符的计数器 + */ +static void set_id_dynexecute(PLpgSQL_stmt_dynexecute *stmt, uint32 *unique_id) { - ListCell* lc = NULL; + ListCell *lc = NULL; + // 为 DYNEXECUTE 语句的查询表达式设置唯一标识符 set_id_expr(stmt->query, unique_id); + // 遍历 DYNEXECUTE 语句的参数列表 foreach (lc, stmt->params) { - set_id_expr((PLpgSQL_expr*)lfirst(lc), unique_id); + // 为参数表达式设置唯一标识符 + set_id_expr((PLpgSQL_expr *)lfirst(lc), unique_id); } } -static void set_id_dynfors(PLpgSQL_stmt_dynfors* stmt, uint32* unique_id) +/* + * 功能:为 PL/pgSQL DYNFORS 语句设置唯一标识符 + * + * 参数列表: + * stmt:PL/pgSQL DYNFORS 语句 + * unique_id:用于分配唯一标识符的计数器 + */ +static void set_id_dynfors(PLpgSQL_stmt_dynfors *stmt, uint32 *unique_id) { - ListCell* lc = NULL; + ListCell *lc = NULL; + // 为 DYNFORS 语句的主体语句设置唯一标识符 set_id_stmts(stmt->body, unique_id); + // 为 DYNFORS 语句的查询表达式设置唯一标识符 set_id_expr(stmt->query, unique_id); + // 遍历 DYNFORS 语句的参数列表 foreach (lc, stmt->params) { - set_id_expr((PLpgSQL_expr*)lfirst(lc), unique_id); + // 为参数表达式设置唯一标识符 + set_id_expr((PLpgSQL_expr *)lfirst(lc), unique_id); } } - -static void set_id_stmt(PLpgSQL_stmt* stmt, uint32* unique_id) +/* + * 功能:为 PL/pgSQL 语句设置唯一标识符 + * + * 参数列表: + * stmt:PL/pgSQL 语句 + * unique_id:用于分配唯一标识符的计数器 + */ +static void set_id_stmt(PLpgSQL_stmt *stmt, uint32 *unique_id) { switch ((enum PLpgSQL_stmt_types)stmt->cmd_type) { case PLPGSQL_STMT_BLOCK: - set_id_block((PLpgSQL_stmt_block*)stmt, unique_id); + set_id_block((PLpgSQL_stmt_block *)stmt, unique_id); break; case PLPGSQL_STMT_ASSIGN: - set_id_expr(((PLpgSQL_stmt_assign*)stmt)->expr, unique_id); + set_id_expr(((PLpgSQL_stmt_assign *)stmt)->expr, unique_id); break; case PLPGSQL_STMT_IF: - set_id_if((PLpgSQL_stmt_if*)stmt, unique_id); + set_id_if((PLpgSQL_stmt_if *)stmt, unique_id); break; case PLPGSQL_STMT_CASE: - set_id_case((PLpgSQL_stmt_case*)stmt, unique_id); + set_id_case((PLpgSQL_stmt_case *)stmt, unique_id); break; case PLPGSQL_STMT_LOOP: - set_id_stmts(((PLpgSQL_stmt_loop*)stmt)->body, unique_id); + set_id_stmts(((PLpgSQL_stmt_loop *)stmt)->body, unique_id); break; case PLPGSQL_STMT_WHILE: - set_id_expr(((PLpgSQL_stmt_while*)stmt)->cond, unique_id); - set_id_stmts(((PLpgSQL_stmt_while*)stmt)->body, unique_id); + set_id_expr(((PLpgSQL_stmt_while *)stmt)->cond, unique_id); + set_id_stmts(((PLpgSQL_stmt_while *)stmt)->body, unique_id); break; case PLPGSQL_STMT_FORI: - set_id_expr(((PLpgSQL_stmt_fori*)stmt)->lower, unique_id); - set_id_expr(((PLpgSQL_stmt_fori*)stmt)->upper, unique_id); - set_id_expr(((PLpgSQL_stmt_fori*)stmt)->step, unique_id); - set_id_stmts(((PLpgSQL_stmt_fori*)stmt)->body, unique_id); + set_id_expr(((PLpgSQL_stmt_fori *)stmt)->lower, unique_id); + set_id_expr(((PLpgSQL_stmt_fori *)stmt)->upper, unique_id); + set_id_expr(((PLpgSQL_stmt_fori *)stmt)->step, unique_id); + set_id_stmts(((PLpgSQL_stmt_fori *)stmt)->body, unique_id); break; case PLPGSQL_STMT_FORS: - set_id_stmts(((PLpgSQL_stmt_fors*)stmt)->body, unique_id); - set_id_expr(((PLpgSQL_stmt_fors*)stmt)->query, unique_id); + set_id_stmts(((PLpgSQL_stmt_fors *)stmt)->body, unique_id); + set_id_expr(((PLpgSQL_stmt_fors *)stmt)->query, unique_id); break; case PLPGSQL_STMT_FORC: - set_id_stmts(((PLpgSQL_stmt_forc*)stmt)->body, unique_id); - set_id_expr(((PLpgSQL_stmt_forc*)stmt)->argquery, unique_id); + set_id_stmts(((PLpgSQL_stmt_forc *)stmt)->body, unique_id); + set_id_expr(((PLpgSQL_stmt_forc *)stmt)->argquery, unique_id); break; case PLPGSQL_STMT_FOREACH_A: - set_id_expr(((PLpgSQL_stmt_foreach_a*)stmt)->expr, unique_id); - set_id_stmts(((PLpgSQL_stmt_foreach_a*)stmt)->body, unique_id); + set_id_expr(((PLpgSQL_stmt_foreach_a *)stmt)->expr, unique_id); + set_id_stmts(((PLpgSQL_stmt_foreach_a *)stmt)->body, unique_id); break; case PLPGSQL_STMT_EXIT: - set_id_expr(((PLpgSQL_stmt_exit*)stmt)->cond, unique_id); + set_id_expr(((PLpgSQL_stmt_exit *)stmt)->cond, unique_id); break; case PLPGSQL_STMT_RETURN: - set_id_expr(((PLpgSQL_stmt_return*)stmt)->expr, unique_id); + set_id_expr(((PLpgSQL_stmt_return *)stmt)->expr, unique_id); break; case PLPGSQL_STMT_RETURN_NEXT: - set_id_expr(((PLpgSQL_stmt_return_next*)stmt)->expr, unique_id); + set_id_expr(((PLpgSQL_stmt_return_next *)stmt)->expr, unique_id); break; case PLPGSQL_STMT_RETURN_QUERY: - set_id_return_query((PLpgSQL_stmt_return_query*)stmt, unique_id); + set_id_return_query((PLpgSQL_stmt_return_query *)stmt, unique_id); break; case PLPGSQL_STMT_RAISE: - set_id_raise((PLpgSQL_stmt_raise*)stmt, unique_id); + set_id_raise((PLpgSQL_stmt_raise *)stmt, unique_id); break; case PLPGSQL_STMT_EXECSQL: - set_id_expr(((PLpgSQL_stmt_execsql*)stmt)->sqlstmt, unique_id); + set_id_expr(((PLpgSQL_stmt_execsql *)stmt)->sqlstmt, unique_id); break; case PLPGSQL_STMT_DYNEXECUTE: - set_id_dynexecute((PLpgSQL_stmt_dynexecute*)stmt, unique_id); + set_id_dynexecute((PLpgSQL_stmt_dynexecute *)stmt, unique_id); break; case PLPGSQL_STMT_DYNFORS: - set_id_dynfors((PLpgSQL_stmt_dynfors*)stmt, unique_id); + set_id_dynfors((PLpgSQL_stmt_dynfors *)stmt, unique_id); break; case PLPGSQL_STMT_OPEN: - set_id_open((PLpgSQL_stmt_open*)stmt, unique_id); + set_id_open((PLpgSQL_stmt_open *)stmt, unique_id); break; case PLPGSQL_STMT_FETCH: - set_id_expr(((PLpgSQL_stmt_fetch*)stmt)->expr, unique_id); + set_id_expr(((PLpgSQL_stmt_fetch *)stmt)->expr, unique_id); break; case PLPGSQL_STMT_PERFORM: - set_id_expr(((PLpgSQL_stmt_perform*)stmt)->expr, unique_id); + set_id_expr(((PLpgSQL_stmt_perform *)stmt)->expr, unique_id); break; default: break; } } -void set_func_expr_unique_id(PLpgSQL_function* func) +void set_func_expr_unique_id(PLpgSQL_function *func) { uint32 unique_id = 0; int i; for (i = 0; i < func->ndatums; i++) { - PLpgSQL_datum* d = func->datums[i]; + PLpgSQL_datum *d = func->datums[i]; switch (d->dtype) { case PLPGSQL_DTYPE_VAR: { - PLpgSQL_var* var = (PLpgSQL_var*)d; + PLpgSQL_var *var = (PLpgSQL_var *)d; set_id_expr(var->default_val, &unique_id); set_id_expr(var->cursor_explicit_expr, &unique_id); } break; case PLPGSQL_DTYPE_ARRAYELEM: - set_id_expr(((PLpgSQL_arrayelem*)d)->subscript, &unique_id); + set_id_expr(((PLpgSQL_arrayelem *)d)->subscript, &unique_id); break; case PLPGSQL_DTYPE_ROW: case PLPGSQL_DTYPE_RECORD: { - PLpgSQL_row* row = (PLpgSQL_row*)d; + PLpgSQL_row *row = (PLpgSQL_row *)d; set_id_expr(row->default_val, &unique_id); } break; default: @@ -736,14 +1030,21 @@ void set_func_expr_unique_id(PLpgSQL_function* func) set_id_block(func->action, &unique_id); } } - +/* + * 功能:判断是否启用 GPC 解析 + * + * 参数列表: + * node:要解析的节点 + * + * 返回值:如果启用 GPC 解析,返回 true,否则返回 false + */ bool SPIParseEnableGPC(const Node *node) { if (node == NULL) return false; switch (nodeTag(node)) { case T_SelectStmt: - if (((SelectStmt*)node)->intoClause) + if (((SelectStmt *)node)->intoClause) return false; /* fall through */ case T_MergeStmt: @@ -760,4 +1061,3 @@ bool SPIParseEnableGPC(const Node *node) /* keep compiler quite */ return false; } - -- 2.34.1 From 108c7fd5357dfbed4193fd5eb60e4bd6db01031f Mon Sep 17 00:00:00 2001 From: nuoya <1204149038@qq.com> Date: Mon, 25 Sep 2023 16:20:00 +0800 Subject: [PATCH 24/50] Update globalplancache_inval.cpp --- .../globalplancache/globalplancache_inval.cpp | 77 ++++++++++++++----- 1 file changed, 59 insertions(+), 18 deletions(-) diff --git a/src/gausskernel/process/globalplancache/globalplancache_inval.cpp b/src/gausskernel/process/globalplancache/globalplancache_inval.cpp index 9c2568249..b2d5adc23 100644 --- a/src/gausskernel/process/globalplancache/globalplancache_inval.cpp +++ b/src/gausskernel/process/globalplancache/globalplancache_inval.cpp @@ -38,63 +38,102 @@ #include "utils/plancache.h" #include "utils/syscache.h" +/* + * 功能:检查共享缓存失效消息是否符合条件 + * + * 参数列表: + * msg:指向 SharedInvalidationMessage 结构的指针,表示共享缓存失效消息 + * + * 返回值: + * 如果共享缓存失效消息符合条件,返回 true;否则返回 false + */ bool GlobalPlanCache::MsgCheck(const SharedInvalidationMessage *msg) { if (msg->id >= 0) { + // 如果消息的 id 大于等于 0 if (msg->cc.id == PROCOID || msg->cc.id == NAMESPACEOID || msg->cc.id == OPEROID || msg->cc.id == AMOPOPID) { + // 如果消息的 cc.id 是 PROCOID、NAMESPACEOID、OPOID 或 AMOPOPID 中的任何一个,返回 true return true; } } else if (msg->id == SHAREDINVALRELCACHE_ID || msg->id == SHAREDINVALPARTCACHE_ID) { + // 如果消息的 id 是 SHAREDINVALRELCACHE_ID 或 SHAREDINVALPARTCACHE_ID,返回 true return true; } - + // 如果以上条件都不满足,返回 false return false; } - -bool GlobalPlanCache::NeedDropEntryByLocalMsg(CachedPlanSource* plansource, int tot, const int *idx, const SharedInvalidationMessage *msgs) +/* + * 功能:根据本地失效消息判断是否需要丢弃计划缓存项 + * + * 参数列表: + * plansource:指向 CachedPlanSource 结构的指针,表示计划源对象 + * tot:失效消息的总数 + * idx:失效消息的索引数组 + * msgs:指向 SharedInvalidationMessage 结构的指针,表示失效消息数组 + * + * 返回值: + * 如果需要丢弃计划缓存项,返回 true;否则返回 false + */ +bool GlobalPlanCache::NeedDropEntryByLocalMsg(CachedPlanSource *plansource, int tot, const int *idx, + const SharedInvalidationMessage *msgs) { + // 获取计划源对象所属的数据库 ID Oid database_id = plansource->gpc.key->env.plainenv.database_id; for (int j = 0; j < tot; j++) { const SharedInvalidationMessage *msg = &msgs[idx[j]]; + // 如果计划源对象具有原始解析树,并且原始解析树的类型是 TransactionStmt,则跳过该消息的处理 if ((plansource)->raw_parse_tree && IsA((plansource)->raw_parse_tree, TransactionStmt)) continue; if (msg->id >= 0) { - + // 如果消息的 id 大于等于 0 if (msg->cc.dbId == database_id || msg->cc.dbId == InvalidOid) { if (msg->cc.id == PROCOID) { + // 检查计划缓存项的失效项依赖性,并更新 CheckInvalItemDependency(plansource, msg->cc.id, msg->cc.hashValue); } else if (msg->cc.id == NAMESPACEOID || msg->cc.id == OPEROID || msg->cc.id == AMOPOPID) { + // 重置计划缓存项 ResetPlanCache(plansource); } } } else if (msg->id == SHAREDINVALRELCACHE_ID) { - if (msg->rc.dbId == database_id || msg->rc.dbId == InvalidOid) - { + // 如果消息的 id 是 SHAREDINVALRELCACHE_ID + if (msg->rc.dbId == database_id || msg->rc.dbId == InvalidOid) { + // 检查计划缓存项与关系依赖性,并更新 CheckRelDependency(plansource, msg->rc.relId); } } else if (msg->id == SHAREDINVALPARTCACHE_ID) { + // 如果消息的 id 是 SHAREDINVALPARTCACHE_ID if (msg->pc.dbId == database_id || msg->pc.dbId == InvalidOid) { + // 检查计划缓存项与分区依赖性,并更新 CheckRelDependency(plansource, msg->pc.partId); } } + // 如果计划源对象需要丢弃共享 GPC,则返回 true if (plansource->gpc.status.NeedDropSharedGPC()) { return true; } } + // 如果不需要丢弃计划缓存项,返回 false return false; - - } - +/* + * 功能:根据失效消息进行计划缓存项的失效处理 + * + * 参数列表: + * msgs:指向 SharedInvalidationMessage 结构的指针,表示失效消息数组 + * n:失效消息的数量 + */ void GlobalPlanCache::InvalMsg(const SharedInvalidationMessage *msgs, int n) { - int *idx = (int *)palloc0(n * sizeof(int)); - int tot = 0; + // 分配并初始化一个索引数组 + int *idx = (int *)palloc0(n * sizeof(int)); + int tot = 0; + // 遍历失效消息数组,筛选出需要处理的消息 for (int i = 0; i < n; i++) { const SharedInvalidationMessage *msg = &msgs[i]; @@ -103,20 +142,21 @@ void GlobalPlanCache::InvalMsg(const SharedInvalidationMessage *msgs, int n) } } + // 如果没有需要处理的消息,释放索引数组并返回 if (tot == 0) { pfree_ext(idx); - return ; + return; } /* Go through each bucket in the GPC HTAB and do some invalidation depending on the GPCInvalInfo we got.*/ - for (uint32 bucket_id = 0; bucket_id < GPC_NUM_OF_BUCKETS; bucket_id ++) { + for (uint32 bucket_id = 0; bucket_id < GPC_NUM_OF_BUCKETS; bucket_id++) { /* Ok so bucket is not empty. Get the bucket S-lock so we can iterate through it. */ int lock_id = m_array[bucket_id].lockId; LWLockAcquire(GetMainLWLockByIndex(lock_id), LW_EXCLUSIVE); MemoryContext oldcontext = MemoryContextSwitchTo(m_array[bucket_id].context); - - /* Check the number of entries in the bucket again. - * GPC Eviction might have removed the last entry while we were waiting for the shared lock. */ + + /* Check the number of entries in the bucket again. + * GPC Eviction might have removed the last entry while we were waiting for the shared lock. */ int bucketEntriesCount = m_array[bucket_id].count; if (0 == bucketEntriesCount) { MemoryContextSwitchTo(oldcontext); @@ -129,7 +169,7 @@ void GlobalPlanCache::InvalMsg(const SharedInvalidationMessage *msgs, int n) GPCEntry *entry = NULL; while ((entry = (GPCEntry *)hash_seq_search(&hash_seq)) != NULL) { - Assert (entry->val.plansource != NULL); + Assert(entry->val.plansource != NULL); /* for standby mode, Invalid Msg send by xlog thread, but xlog thread didn't set db id into MyDatabaseId. So we need check each plan's db id by gpc'key in NeedDropEntryByLocalMsg latter */ if (pmState == PM_RUN && @@ -138,7 +178,7 @@ void GlobalPlanCache::InvalMsg(const SharedInvalidationMessage *msgs, int n) } /* Atomic read the number of CachedEnvironment in this entry */ - if(NeedDropEntryByLocalMsg(entry->val.plansource, tot, idx, msgs)) { + if (NeedDropEntryByLocalMsg(entry->val.plansource, tot, idx, msgs)) { RemoveEntry(bucket_id, entry); } } @@ -147,5 +187,6 @@ void GlobalPlanCache::InvalMsg(const SharedInvalidationMessage *msgs, int n) LWLockRelease(GetMainLWLockByIndex(lock_id)); } + // 释放索引数组 pfree_ext(idx); } -- 2.34.1 From 8df0769f58abfa91866b84bace332ea047d23aab Mon Sep 17 00:00:00 2001 From: lucky <3180017453@qq.com> Date: Mon, 25 Sep 2023 16:31:25 +0800 Subject: [PATCH 25/50] -A --- src/common/backend/parser/LIST.TXT | 33 + .../backend/parser/新建文本文档.bat | 1 + .../process/postmaster/.vscode/settings.json | 8 + .../process/postmaster/aiocompleter.cpp | 343 ++-- .../process/postmaster/alarmchecker.cpp | 167 +- .../process/postmaster/autovacuum.cpp | 1462 +++++++++++------ .../process/postmaster/barrier_arch.cpp | 275 ++-- .../process/postmaster/barrier_creator.cpp | 372 +++-- .../process/postmaster/barrier_preparse.cpp | 244 +-- .../process/postmaster/bgworker.cpp | 412 +++-- .../process/postmaster/bgwriter.cpp | 502 ++++-- .../process/postmaster/cbmwriter.cpp | 131 +- .../process/postmaster/checkpointer.cpp | 808 ++++++--- .../process/postmaster/fork_process.cpp | 40 +- .../process/postmaster/globalstats.cpp | 316 +++- .../process/postmaster/twophasecleaner.cpp | 589 +++++-- .../process/postmaster/walwriter.cpp | 237 ++- .../process/postmaster/walwriterauxiliary.cpp | 162 +- .../process/postmaster/新建文本文档.bat | 1 + src/gausskernel/process/tcop/LIST.TXT | 12 + .../process/tcop/新建文本文档.bat | 1 + src/include/postmaster/autovacuum.h | 65 +- src/include/postmaster/bgworker.h | 57 +- src/include/utils/rel.h | 20 +- 24 files changed, 4161 insertions(+), 2097 deletions(-) create mode 100644 src/common/backend/parser/LIST.TXT create mode 100644 src/common/backend/parser/新建文本文档.bat create mode 100644 src/gausskernel/process/postmaster/.vscode/settings.json create mode 100644 src/gausskernel/process/postmaster/新建文本文档.bat create mode 100644 src/gausskernel/process/tcop/LIST.TXT create mode 100644 src/gausskernel/process/tcop/新建文本文档.bat diff --git a/src/common/backend/parser/LIST.TXT b/src/common/backend/parser/LIST.TXT new file mode 100644 index 000000000..351695bb8 --- /dev/null +++ b/src/common/backend/parser/LIST.TXT @@ -0,0 +1,33 @@ +analyze.cpp +CMakeLists.txt +gram.xml +gram.y +hint_gram.y +hint_scan.l +keywords.cpp +kwlookup.cpp +LIST.TXT +Makefile +parser.cpp +parse_agg.cpp +parse_clause.cpp +parse_coerce.cpp +parse_collate.cpp +parse_compatibility.cpp +parse_cte.cpp +parse_expr.cpp +parse_func.cpp +parse_hint.cpp +parse_merge.cpp +parse_node.cpp +parse_oper.cpp +parse_param.cpp +parse_relation.cpp +parse_startwith.cpp +parse_target.cpp +parse_type.cpp +parse_utilcmd.cpp +README +scan.l +scansup.cpp +½ıĵ.bat diff --git a/src/common/backend/parser/新建文本文档.bat b/src/common/backend/parser/新建文本文档.bat new file mode 100644 index 000000000..d055abd11 --- /dev/null +++ b/src/common/backend/parser/新建文本文档.bat @@ -0,0 +1 @@ +DIR *.* /B >LIST.TXT \ No newline at end of file diff --git a/src/gausskernel/process/postmaster/.vscode/settings.json b/src/gausskernel/process/postmaster/.vscode/settings.json new file mode 100644 index 000000000..decddbd7b --- /dev/null +++ b/src/gausskernel/process/postmaster/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "files.associations": { + "array": "cpp", + "string_view": "cpp", + "initializer_list": "cpp", + "utility": "cpp" + } +} \ No newline at end of file diff --git a/src/gausskernel/process/postmaster/aiocompleter.cpp b/src/gausskernel/process/postmaster/aiocompleter.cpp index 6d055f45e..9b1b3a5f8 100644 --- a/src/gausskernel/process/postmaster/aiocompleter.cpp +++ b/src/gausskernel/process/postmaster/aiocompleter.cpp @@ -1,3 +1,16 @@ +/* + 下段注释描述了文件的版权信息以及对于AIO完成线程的基本介绍。它们用于处理异步IO请求,以及如何启动和停止这些线程。 + * aiocompleter.cpp文件的整体功能是用于AIO 完成器线程完成预取(Prefetch )和后端写入(BackWrite )I/O操作 + * 使用场景:AIO 完成器线程使用 Linux Native AIO 完成 AIO 请求。 + * 单个 AIO 完成器线程为与特定 AIO 上下文和 I/O 优先级相关联的 AIO 队列提供服务。 + * 1. 停止:只能在所有工作线程或后台数据写(bgwriter)线程都已停止后,AIO完成器线程才能停止。 + * + * 2. 启动:当postmaster启动子进程结束或是在执行归档恢复的开始,AIO完成器线程会立即启动,并且持续保持开启状态,直到postemaster下达终止命令 + * [1.正常终止:SIGTERM指令:指示线程等待任何挂起的 AIO 并做好退出准备。 + * 2.紧急终止:SIGQUIT指令:当AIO完成器线程意外退出,postmaster会将其视为“后端崩溃”状态,共享内存可能遭受损坏. + * 此时通过SIGQUIT指令终止剩余的后端后启动恢复循环] + * + */ /* * Copyright (c) 2020 Huawei Technologies Co.,Ltd. * @@ -39,6 +52,7 @@ * * ------------------------------------------------------------------------- */ +/*引入所需的系统库和模块。*/ #include "postgres.h" #include "knl/knl_variable.h" #include "gssignal/gs_signal.h" @@ -52,6 +66,15 @@ #include "utils/memutils.h" #include +/* +解释了AIO completer线程的结构和数组。每个线程都有一个唯一的上下文,可能处理不同类型的请求。 +aioCompltrThread_t 结构对应于每个AIO completer线程,存储在 compltrArray 数组中,该数组包含 MAX_AIOCOMPLTR_THREADS 个槽位。 +compltrArray 数组在 postmaster 上下文中定义。 + +第一组参数 context、eventsp 和 tid 是在线程启动时设置的,它们对每个线程都是唯一的。 +cmpltrDesc 指针指向 compltrDescArray 中的 AioCompltrDesc_t 结构,其中包含了完成器的参数。同类型的完成器共享相同的描述符。 +compltrDescArray 数组中有 NUM_AIOCOMPLTR_TYPES 个 AioCompltrDesc_t 结构,每个结构代表一个 AioCompltrType。 +*/ /* * Each AIO completer thread has a unique context, and potentially * processes different types of requests. There is an aioCompltrThread_t @@ -69,46 +92,69 @@ * The compltrDescArray array is defined in the postmaster context. * */ -int CompltrReadReq(void* aioDesc, long res); -int CompltrWriteReq(void* aioDesc, long res); -int CompltrReadCUReq(void* aioDesc, long res); -int CompltrWriteCUReq(void* aioDesc, long res); +/* + 这里声明了一些函数原型,用于处理不同类型的AIO请求。 +*/ +int CompltrReadReq(void* aioDesc, long res);// 发起异步读取请求 +int CompltrWriteReq(void* aioDesc, long res);// 发起异步写入请求 +int CompltrReadCUReq(void* aioDesc, long res);// 发起异步读取CU(Control Unit,控制单元)请求 +int CompltrWriteCUReq(void* aioDesc, long res);// 发起异步写入CU请求 -ThreadId Compltrfork_exec(int compltrIdx); +ThreadId Compltrfork_exec(int compltrIdx);// 创建线程并执行异步I/O处理 /* * GUC parameters */ /* Maximum number of Completer threads -compile time define */ -#define MAX_AIOCOMPLTR_THREADS 4 +#define MAX_AIOCOMPLTR_THREADS 4 //在编译时定义的宏,用于设置异步I/O操作完成者线程池的最大线程数量。用于限制了可以同时运行的完成者线程的最大数量。 /* Number of Completer threads and the number of sets of Completers */ -const int AioCompltrThreads = 4; -int AioCompltrSets = 1; -const int AioCompltrShutdownTimeout = 1; +const int AioCompltrThreads = 4;// 整数常量,表示异步I/O操作完成者线程池中的线程数量。这里设置了4个完成者线程。 +int AioCompltrSets = 1; // 整数变量,表示完成者线程池的集数。此处设置只有一个线程池集。 +const int AioCompltrShutdownTimeout = 1;// 整数常量,表示异步I/O完成者线程池关闭的超时时间,此处设置为1秒。 /* Completer callback to handle the AIO event */ -typedef int (*AioCallback_t)(void*, long); +typedef int (*AioCallback_t)(void*, long);// 定义函数指针类型 AioCallback_t,用于表示异步I/O事件的Completer回调函数。 + // 指向一个参数为 (void*, long) 并返回 int 类型的函数 /* * Completer Thread definitions */ +/* +结构体类型 AioCompltrDesc,用于描述AIO完成器的特性、参数以及请求属性。 +它包含以下字段: + reqtype: AioCompltrType,表示完成器的类型,用于标识不同类型的AIO请求。 + callback: AioCallback_t,是一个函数指针,指向用于处理AIO事件的回调函数。 + maxevents: int,表示允许同时处理的最大AIO事件数量。 + min_nr: int,表示等待的最小事件数量,当达到此数量时,完成器可以开始处理这些事件。 + max_nr: int,表示最大可检索的事件数量,即一次从内核获取的最大事件数量。 + timeout: int,表示等待事件的最大时间,超过此时间后,完成器可以处理已获取的事件。 + reqprio: AioPriority,表示AIO请求的优先级。 +*/ typedef struct AioCompltrDesc { /* Completer characteristics */ - AioCompltrType reqtype; /* Completer type */ - AioCallback_t callback; /* Completer function */ - int maxevents; /* AIO Maximum events in progress */ + AioCompltrType reqtype; /* Completer type 表示完成器的类型,用于标识不同类型的AIO请求。*/ + AioCallback_t callback; /* Completer function 函数指针,指向用于处理AIO事件的回调函数*/ + int maxevents; /* AIO Maximum events in progress 表示允许同时处理的最大AIO事件数量*/ /* Completer parameters */ - int min_nr; /* Min number of events to wait for */ - int max_nr; /* Max number of events to retrieve */ - int timeout; /* Max time to wait */ + int min_nr; /* Min number of events to wait for 示等待的最小事件数量,当达到此数量时,完成器可以开始处理这些事件*/ + int max_nr; /* Max number of events to retrieve 表示最大可检索的事件数量,即一次从内核获取的最大事件数量*/ + int timeout; /* Max time to wait 表示等待事件的最大时间,超过此时间后,完成器可以处理已获取的事件*/ /* Request properties */ - AioPriority reqprio; /* Request priority served */ + AioPriority reqprio; /* Request priority served 表示AIO请求的优先级*/ } AioCompltrDesc_t; +/* + AioCompltrThread_t 结构体,用于表示异步I/O Completer线程的信息和状态。 + + context:io_context_t 表示异步I/O上下文,用于管理和跟踪异步I/O操作。 + eventsp:struct io_event* 指向 io_event 结构体的指针,表示异步I/O事件的指针数组。用于存储异步I/O操作的事件,以便后续处理。 + tid:ThreadId 表示异步I/O Completer线程的线程ID。用于标识和管理线程。 + compltrDescp:AioCompltrDesc_t* 指向 AioCompltrDesc_t 类型的指针,AioCompltrDesc_t用于描述AIO完成器的特性、参数以及请求属性 +*/ typedef struct { io_context_t context; /* AIO context */ struct io_event* eventsp; /* AIO events to process */ @@ -116,6 +162,8 @@ typedef struct { AioCompltrDesc_t* compltrDescp; /* Completer descriptor */ } AioCompltrThread_t; + + /* * The compltrDescArray contains the description of the different types * of completer threads. These are used to setup the context for each @@ -158,6 +206,20 @@ typedef struct { * policy employed. CFQ takes into account the priorities, but there is also a * wide gulf between the priority of sync and async i/o that dwarfs these. */ +/* +初始化compltrDescArray数组,用于配置不同类型的异步I/O Completer线程的参数。 + +- AioCompltrDesc_t:AioCompltrDesc结构体类型,用于描述AIO Completer的特性、参数以及请求属性。 +- NUM_AIOCOMPLTR_TYPES:常量,表示异步I/O Completer线程的类型数量。数组 compltrDescArray的大小 +- compltrDescArray[NUM_AIOCOMPLTR_TYPES]:包含了不同类型的异步I/O Completer线程的描述信息的数组。 +- `PageListPrefetchType`:页列表预取请求类型。该类型的异步I/O完成器线程处理后台异步读取数据页到内存的请求,以减少查询或操作时的延迟。 + +- `PageListBackWriteType`:页列表后台写入请求类型。该类型的异步I/O完成器线程处理将数据页从内存写回磁盘的请求,通常用于执行后台的数据页回写操作。 + +- `CUListPrefetchType`:CU(Column Unit)列表预取请求类型。类似于页列表预取,该类型的异步I/O完成器线程处理后台异步读取列存储单元(CU)到内存的请求,以优化查询性能。 + +- `CUListWriteType`:CU列表写入请求类型。与页列表后台写入类似,该类型的异步I/O完成器线程处理将修改后的列存储单元(CU)从内存写回磁盘的请求。 +*/ AioCompltrDesc_t compltrDescArray[NUM_AIOCOMPLTR_TYPES] = { /* reqtype, callback, maxevents, min_nr, max_nr, tsec, reqprio */ {PageListPrefetchType, CompltrReadReq, 65536, 1, 16384, 60, HighPri}, @@ -171,18 +233,25 @@ AioCompltrDesc_t compltrDescArray[NUM_AIOCOMPLTR_TYPES] = { * any AIO Completer theads. The Array contains one element for * each completer thread. */ -AioCompltrThread_t compltrArray[MAX_AIOCOMPLTR_THREADS]; + +AioCompltrThread_t compltrArray[MAX_AIOCOMPLTR_THREADS]; // MAX_AIOCOMPLTR_THREADS 4 /* * AioCompltrReady flag is set/cleared from the postmaster * context. It is used to remember the Completer state. + * 用于标记异步I/O完成者线程是否准备就绪 */ -static bool volatile AioCompltrReady = false; +static bool volatile AioCompltrReady = false;// 初始化false,表示异步I/O完成器线程尚未准备就绪。 -/* Associate a template with a thread index */ +/* Associate a template with a thread index +用于关联线程索引(threadIdx)与Completer描述数组 compltrDescArray 中的模板 +*/ #define AIOCOMPLTR_TEMPLATE(threadIdx) (&compltrDescArray[(threadIdx) % NUM_AIOCOMPLTR_TYPES]) -/* Determine a completer thread index to be used for a given type and h val */ +/* Determine a completer thread index to be used for a given type and h val +用于确定给定类型(typeIdx)和哈希值(h)的异步I/O Completer线程索引。 +通过计算出在 compltrArray 中的索引,以选择适当的Completer线程来处理特定类型的异步I/O请求。 +*/ #define AIOCOMPLTR_THREAD_IDX(typeIdx, h) ((((h) % AioCompltrSets) * NUM_AIOCOMPLTR_TYPES) + (typeIdx)) /* @@ -193,6 +262,9 @@ static bool volatile AioCompltrReady = false; * @Return: true start ok * @See also: */ +/*用于检查异步I/O完成者是否已经启动的函数。 + *返回布尔值:如果异步I/O Completers已准备就绪,则返回 true,否则返回 false +*/ bool AioCompltrIsReady(void) { return AioCompltrReady; @@ -204,6 +276,9 @@ bool AioCompltrIsReady(void) * @Return: function ptr * @See also: */ +/*用于获取特定请求类型的回调函数的函数。 + *接受一个参数 reqType,表示异步I/O completer类型; + *返回与该类型相关联的回调函数指针。*/ AioCallback_t ComptrCallback(AioCompltrType reqType) { return compltrDescArray[reqType].callback; @@ -215,6 +290,10 @@ AioCallback_t ComptrCallback(AioCompltrType reqType) * @Return: Request priority * @See also: */ +/*用于获取特定请求类型的优先级的函数。 + *接受一个参数 reqType,表示异步I/O completer类型 + *返回该类型请求的优先级。 + */ short CompltrPriority(AioCompltrType reqType) { return compltrDescArray[reqType].reqprio; @@ -228,6 +307,10 @@ short CompltrPriority(AioCompltrType reqType) * @Return:io_context * @See also: */ +/*用于获取特定请求类型的完成者上下文的函数。 + *接受两个参数,reqType 表示异步I/O completer类型,h 表示索引 + *根据类型和索引获取相应的completer上下文(io_context_t) + */ io_context_t CompltrContext(AioCompltrType reqType, int h) { return compltrArray[AIOCOMPLTR_THREAD_IDX(reqType, h)].context; @@ -237,9 +320,12 @@ io_context_t CompltrContext(AioCompltrType reqType, int h) /* * Signal handlers */ -static void CompltrConfig(SIGNAL_ARGS); -static void CompltrQuickDie(SIGNAL_ARGS); -static void CompltrShutdown(SIGNAL_ARGS); +/* +用于内部实现和处理特定的信号事件的私有函数原型声明,当信号事件发生时调用。 +*/ +static void CompltrConfig(SIGNAL_ARGS);// 用于配置异步I/O completer的相关参数和设置 +static void CompltrQuickDie(SIGNAL_ARGS);// 用于快速终止异步I/O completer线程。 +static void CompltrShutdown(SIGNAL_ARGS);// 用于在异步I/O completer线程退出时执行清理和关闭操作 /* * @Description: Compltrfork_exec() and AioCompltrStart() are used to start the @@ -260,9 +346,17 @@ static void CompltrShutdown(SIGNAL_ARGS); * @Return: thread id * @See also: */ +/* +用于启动异步I/O Completer线程。在当前的实现中,openGauss并不支持异步I/O,所以该函数只会发出错误消息表示不支持,并返回无效的线程ID。 + +该函数会格式化命令行参数列表(arglist),然后使用 fork 和 exec 函数创建一个新的线程来执行异步I/O Completer线程的处理。 +其中,compltrIdx 被转换为一个3字符的字符串,这样可以避免在代码中需要特殊处理这个参数,因为参数的传递在执行过程中已经被处理好了。 +*/ ThreadId Compltrfork_exec(int compltrIdx) { + // 发出错误消息,表示不支持异步I/O ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("openGauss current do not support AIO"))); + // 返回无效的线程ID return InvalidTid; } @@ -287,87 +381,101 @@ ThreadId Compltrfork_exec(int compltrIdx) */ int AioCompltrStart(void) { - int error = 0; - int try_times = 0; + int error = 0;// 错误码初始化 + int try_times = 0;// 尝试次数初始化 /* * Only allow MAX_AIOCOMPLTR_THREADS */ + //检查当前配置的 AIO Completer 线程数量是否超过了预定义的最大数量 MAX_AIOCOMPLTR_THREADS if (AioCompltrThreads > MAX_AIOCOMPLTR_THREADS) { - error = 1; - return error; + error = 1;// 设置错误码 + return error;// 返回错误码,中断函数执行 } - + // 调用安全内存函数memset_s 初始化 compltrArray,将内存设置为零 errno_t rc = memset_s(&compltrArray, sizeof(AioCompltrThread_t) * MAX_AIOCOMPLTR_THREADS, 0, sizeof(AioCompltrThread_t) * MAX_AIOCOMPLTR_THREADS); + //错误检查,用于确保 memset_s 函数是否执行成功。如果 rc(返回值)不为零,表示内存初始化出现了错误 securec_check(rc, "\0", "\0"); /* * Initialize the compltrArray */ + //初始化 compltrArray 数组 for (int i = 0; i < AioCompltrThreads; i++) { - try_times = 0; + try_times = 0;// 尝试次数归零 /* Assign a template to the thread descriptor */ - compltrArray[i].compltrDescp = AIOCOMPLTR_TEMPLATE(i); + compltrArray[i].compltrDescp = AIOCOMPLTR_TEMPLATE(i);// 为线程描述符分配对应的模板 /* Create the i/o queue and fill in the context */ do { + // 使用 io_setup 函数创建异步 I/O 队列,参数为当前线程的最大事件数和队列上下文。 error = io_setup(compltrArray[i].compltrDescp->maxevents, &compltrArray[i].context); + // 如果成功创建队列或者遇到的错误不是 EAGAIN(表示资源暂时不可用),则跳出循环。 if (error == 0 || error != -EAGAIN) { break; } - try_times++; + try_times++; // 尝试次数增加 + // 打印日志,记录当前尝试的线程ID、尝试次数和错误码。 ereport(LOG, (errmsg("AIO Startup, Completer thread id =%d try times=%d, error=%d", i, try_times, error))); - pg_usleep(100000L); - } while (try_times < 5); + pg_usleep(100000L);// 延迟 100,000 微秒(0.1 秒),然后继续下一次尝试。 + } while (try_times < 5);// 循环继续,直到成功创建队列或者尝试次数达到 5 次。 if (error != 0) { - goto AioCompltrStartError; + goto AioCompltrStartError;// 发生错误时跳转到错误处理标签 } /* Allocate the event array for the thread */ + /* 为线程分配事件数组的内存 用于存储事件数组 + compltrArray[i].compltrDescp->max_nr 表示事件数组的最大大小,sizeof(struct io_event) 表示单个事件的大小。*/ compltrArray[i].eventsp = (io_event*)malloc(compltrArray[i].compltrDescp->max_nr * sizeof(struct io_event)); - + // 检查内存分配是否成功,失败则进入if语句内部 if (compltrArray[i].eventsp == (struct io_event*)NULL) { /* malloc failed for some reason... */ - error = 2; + error = 2;// 设置错误码为 2,表示内存分配失败 + //打印日志,记录内存分配失败的详细信息,包括最大事件数和错误码。 ereport(LOG, (errmsg("AIO Startup malloc io_event failed: max_nr(%d), %d", compltrArray[i].compltrDescp->max_nr, error))); + // 跳转到错误处理标签 AioCompltrStartError,执行错误处理操作 goto AioCompltrStartError; } /* Start AIO Completer thread */ + //使用 Compltrfork_exec(i) 函数启动一个异步 I/O 完成线程,并将线程索引 i 作为参数传递给函数。函数返回线程的 ID。 compltrArray[i].tid = Compltrfork_exec(i); + // 检查线程是否成功启动。如果启动失败,会进入条件内部。 if (compltrArray[i].tid == (ThreadId)-1) { /* starting a thread failed */ - error = 3; + error = 3;// 设置错误码为 3,表示启动线程失败 + // 打印日志,记录启动线程失败的详细信息,包括错误码。 ereport(LOG, (errmsg("Start AIO Completer thread failed: %d", error))); - goto AioCompltrStartError; + goto AioCompltrStartError; // 发生错误时跳转到错误处理标签 } }; /* The AIO Completers are open for business */ - AioCompltrReady = true; + AioCompltrReady = true;// AIO Completers 准备就绪 /* successful return */ return 0; +//错误处理标签 AioCompltrStartError 标识错误处理的起始位置 AioCompltrStartError: /* * If anything went wrong, then stop any threads started * and deallocate the resources. */ - AioCompltrStop(SIGTERM); - ereport(LOG, (errmsg("AIO Startup Failed,error=%d", error))); + AioCompltrStop(SIGTERM);// 停止已启动的异步 I/O 完成线程并释放资源 + ereport(LOG, (errmsg("AIO Startup Failed,error=%d", error)));// 记录启动失败的错误日志 - return error; + return error;// 返回错误码 } /* @@ -380,39 +488,45 @@ AioCompltrStartError: void AioCompltrStop(int signal) { gs_thread_t thread; - AioCompltrReady = false; + AioCompltrReady = false;// 停止AIO Completer线程的标志设置为false,表示线程不再运行 /* * Stop the threads in the compltrArray. */ + // 循环遍历AIO Completer线程数组,停止所有线程 for (int i = 0; i < AioCompltrThreads; i++) { /* * Stop the threads that were started */ + // 检查线程标识符是否有效 if (compltrArray[i].tid != 0) { + // 如果线程已启动,使用gs_signal_send函数向线程发送信号停止线程,将传入的signal作为信号类型。 if (gs_signal_send(compltrArray[i].tid, signal) < 0) { + // 如果发送信号失败,记录错误日志,指明哪个线程的信号发送失败。 ereport(LOG, (errmsg("kill(%ld,%d) failed: %m", (long)(compltrArray[i].tid), signal))); } } } - + // 如果数据库正在崩溃,只需杀死completers并退出。 if (signal == SIGQUIT) { /* * if the database is crashing just kill the completers * and bail-out. */ - return; + return;// 直接返回,杀死completers并退出 } /* * Wait for the stopped threads to exit. */ + // 循环遍历AIO Completer线程数组,等待AIO Completer线程退出并进行清理 for (int i = 0; i < AioCompltrThreads; i++) { /* * Wait for the killed threads to exit */ - if (compltrArray[i].tid != 0) { - thread.thid = compltrArray[i].tid; + if (compltrArray[i].tid != 0) {// 检查线程标识符是否有效 + thread.thid = compltrArray[i].tid;// 保存线程标识符,用于等待线程退出 + // 使用gs_thread_join函数等待线程退出。如果返回值不为0,表示等待线程退出失败。 if (gs_thread_join(thread, NULL) != 0) { /* * If the thread does not exist, treat it as normal exit and we continue to @@ -420,12 +534,14 @@ void AioCompltrStop(int signal) * not know the current status of the thread and it's better to quit directly * which sames more safely. */ - if (ESRCH == pthread_kill(thread.thid, 0)) + if (ESRCH == pthread_kill(thread.thid, 0)) // 使用pthread_kill函数检查线程是否不存在 + // 如果线程不存在,记录错误日志 ereport(LOG, (errmsg("failed to join thread %lu, no such process", thread.thid))); else + // 如果线程存在但无法等待其退出,标记线程崩溃 HandleChildCrash(thread.thid, 1, "AIO process"); } - compltrArray[i].tid = (pid_t)0; + compltrArray[i].tid = (pid_t)0; // 清空线程标识符,表示线程已经退出 } } @@ -433,18 +549,20 @@ void AioCompltrStop(int signal) * Deallocate their context and event arrays, if any */ for (int i = 0; i < AioCompltrThreads; i++) { - compltrArray[i].compltrDescp = (AioCompltrDesc_t*)NULL; + compltrArray[i].compltrDescp = (AioCompltrDesc_t*)NULL;// // 清空线程描述符指针,防止悬空指针 /* destroy the AIO context */ + // 如果AIO上下文存在 if (compltrArray[i].context) { - io_destroy(compltrArray[i].context); - compltrArray[i].context = (io_context_t)NULL; + io_destroy(compltrArray[i].context);// 销毁AIO上下文,释放相关资源 + compltrArray[i].context = (io_context_t)NULL;// 清空AIO上下文指针,防止悬空指针 } /* Deallocate the events array */ + // 如果事件数组指针存在 if (compltrArray[i].eventsp) { - free(compltrArray[i].eventsp); - compltrArray[i].eventsp = (struct io_event*)NULL; + free(compltrArray[i].eventsp);// 释放事件数组的内存,防止内存泄漏 + compltrArray[i].eventsp = (struct io_event*)NULL;// 清空事件数组指针,防止悬空指针 } } @@ -460,29 +578,35 @@ void AioCompltrStop(int signal) */ void AioCompltrMain(int ac, char** av) { + // 检查命令行参数数量是否足够 if (ac < 4) { ereport(WARNING, (errmsg("invalid AIO argument num:%d", ac))); exit(1); } /* compltrIdx identifies this thread. */ + // 解析Completer线程的索引 int compltrIdx = atoi(av[3]); /* * Global thread local shortcuts to the completer descriptor * in the compltrArray, these are assigned on entry. */ - io_context_t context = compltrArray[compltrIdx].context; - io_event* eventsp = compltrArray[compltrIdx].eventsp; - AioCompltrDesc_t* compltrDescp = compltrArray[compltrIdx].compltrDescp; - int min_nr = compltrDescp->min_nr; - int max_nr = compltrDescp->max_nr; - struct timespec timeout; - struct timespec shutdown_timeout; - timeout.tv_sec = compltrDescp->timeout; - timeout.tv_nsec = 0; - shutdown_timeout.tv_sec = AioCompltrShutdownTimeout; - shutdown_timeout.tv_nsec = 0; - AioCallback_t callback = compltrDescp->callback; + /* + 使用本地变量提前获取对完成器描述符中相关信息的引用,以便后续代码中使用。从而减少冗余的操作,提高代码可读性和性能。 + 其中包括了AIO上下文、事件数组指针、最小/最大等待事件数量、超时设置和AIO回调函数等信息的获取。 + */ + io_context_t context = compltrArray[compltrIdx].context; // AIO上下文 + io_event* eventsp = compltrArray[compltrIdx].eventsp;// 事件数组指针 + AioCompltrDesc_t* compltrDescp = compltrArray[compltrIdx].compltrDescp;// 完成器描述符指针 + int min_nr = compltrDescp->min_nr;// 最小等待事件数量 + int max_nr = compltrDescp->max_nr; // 最大可检索的事件数量 + struct timespec timeout;// 事件等待超时 + struct timespec shutdown_timeout; // 关闭等待超时 + timeout.tv_sec = compltrDescp->timeout;// 设置等待事件超时的秒数 + timeout.tv_nsec = 0; // 设置等待事件超时的纳秒数 + shutdown_timeout.tv_sec = AioCompltrShutdownTimeout;// 设置关闭等待超时的秒数 + shutdown_timeout.tv_nsec = 0;// 设置关闭等待超时的纳秒数 + AioCallback_t callback = compltrDescp->callback;// 获取AIO回调函数的指针 /* * Handle signals the postmaster might send us @@ -490,27 +614,28 @@ void AioCompltrMain(int ac, char** av) * SIGQUIT causes immediate exit without cleanup. * SIGUSR1 is presently unused- reserved for future use. */ - (void)gspqsignal(SIGHUP, CompltrConfig); /* retrieve config */ - (void)gspqsignal(SIGINT, SIG_IGN); - (void)gspqsignal(SIGTERM, CompltrShutdown); /* shutdown */ - (void)gspqsignal(SIGQUIT, CompltrQuickDie); /* hard crash time */ - (void)gspqsignal(SIGALRM, SIG_IGN); - (void)gspqsignal(SIGPIPE, SIG_IGN); - (void)gspqsignal(SIGUSR1, SIG_IGN); /* reserved */ - (void)gspqsignal(SIGUSR2, SIG_IGN); + //为异步I/O Completer线程设置信号处理方式,确保线程能够适当地响应不同的信号 + (void)gspqsignal(SIGHUP, CompltrConfig); /* retrieve config */ //SIGHUP 信号将触发 CompltrConfig 函数,用于重新加载配置。 + (void)gspqsignal(SIGINT, SIG_IGN);// SIGINT 信号被忽略,不会中断线程执行。 + (void)gspqsignal(SIGTERM, CompltrShutdown); /* shutdown */ //SIGTERM 信号将触发 CompltrShutdown 函数,用于进行线程关闭和清理。 + (void)gspqsignal(SIGQUIT, CompltrQuickDie); /* hard crash time */ // SIGQUIT 信号将触发 CompltrQuickDie 函数,用于快速终止线程执行。 + (void)gspqsignal(SIGALRM, SIG_IGN);// SIGALRM 信号被忽略。 + (void)gspqsignal(SIGPIPE, SIG_IGN);// SIGPIPE 信号被忽略。 + (void)gspqsignal(SIGUSR1, SIG_IGN); /* reserved */ //SIGUSR1 信号都被忽略。 + (void)gspqsignal(SIGUSR2, SIG_IGN);// SIGUSR2 信号被忽略。 /* * Reset some signals that are accepted by postmaster but we don't * need. */ - (void)gspqsignal(SIGCHLD, SIG_DFL); - (void)gspqsignal(SIGTTIN, SIG_DFL); - (void)gspqsignal(SIGTTOU, SIG_DFL); - (void)gspqsignal(SIGCONT, SIG_DFL); - (void)gspqsignal(SIGWINCH, SIG_DFL); + (void)gspqsignal(SIGCHLD, SIG_DFL);// 恢复默认处理 SIGCHLD 信号 + (void)gspqsignal(SIGTTIN, SIG_DFL);// 恢复默认处理 SIGTTIN 信号 + (void)gspqsignal(SIGTTOU, SIG_DFL); // 恢复默认处理 SIGTTOU 信号 + (void)gspqsignal(SIGCONT, SIG_DFL);// 恢复默认处理 SIGCONT 信号 + (void)gspqsignal(SIGWINCH, SIG_DFL);// 恢复默认处理 SIGWINCH 信号 /* We allow SIGQUIT (quickdie) at all times */ - sigdelset(&t_thrd.libpq_cxt.BlockSig, SIGQUIT); + sigdelset(&t_thrd.libpq_cxt.BlockSig, SIGQUIT);// 允许 SIGQUIT (quickdie) 信号随时被接收 /* Create a resource owner to keep track of our resources (buffer * pins etc... @@ -524,20 +649,22 @@ void AioCompltrMain(int ac, char** av) /* * Unblock signals (blocked when postmaster forked us) */ - gs_signal_setmask(&t_thrd.libpq_cxt.UnBlockSig, NULL); - (void)gs_signal_unblock_sigusr2(); + gs_signal_setmask(&t_thrd.libpq_cxt.UnBlockSig, NULL);// 解除之前在主进程中阻塞的信号 + (void)gs_signal_unblock_sigusr2();// 解除对 SIGUSR2 信号的阻塞 /* Announce that the Completer has been started */ - ereport(LOG, (errmsg("AIO Completer %d STARTED.", compltrIdx))); + ereport(LOG, (errmsg("AIO Completer %d STARTED.", compltrIdx)));// 在日志中记录异步I/O Completer线程已经启动 for (;;) { - int eventsReceived; + int eventsReceived;//存储从 io_getevents 函数中获取的已完成的异步I/O事件数量或错误代码 /* * Reload configuration -if requested. */ + // 如果有重新加载配置的请求,进行相应的处理,但在此代码段中被禁用了 if (t_thrd.aio_cxt.config_requested) { /* disabled config request, not time for this now. ProcessConfigFile PGC_SIGHUP; */ + // 如果配置重载请求被设置,这段代码会禁用配置请求并略过后续的处理,以避免在当前上下文中重新加载配置文件 t_thrd.aio_cxt.config_requested = false; } @@ -548,11 +675,12 @@ void AioCompltrMain(int ac, char** av) * allow the thread to exit quickly when its time comes. * Once shutdown is requested, there is no going back. */ + // 如果收到了关闭请求 if (t_thrd.aio_cxt.shutdown_requested) { - timeout = shutdown_timeout; + timeout = shutdown_timeout;// 设置超时时间为shutdown_timeout,以便快速退出线程 - ereport(LOG, (errmsg("AIO Completer %d EXITED.", compltrIdx))); - proc_exit(0); + ereport(LOG, (errmsg("AIO Completer %d EXITED.", compltrIdx)));// 记录日志,表示AIO Completer正在退出 + proc_exit(0);// 退出线程 } /* @@ -560,12 +688,15 @@ void AioCompltrMain(int ac, char** av) * on the given context. Retry if the syscall is * interrupted. */ + // 等待一些AIO请求完成在给定的上下文中,最小等待事件数量为min_nr,最大为max_nr,最长等待时间由timeout指定 + // 返回值是一个整数,表示成功获取的事件数量。如果返回值为负数,表示发生了错误, eventsReceived = io_getevents(context, min_nr, max_nr, eventsp, &timeout); /* * If io_getevents() got interrupted, * take the opportunity to check for pending requests. * Then restart the io_getevents() call. */ + // 如果 io_getevents() 被中断,重新尝试获取事件 if (eventsReceived == -EINTR) { continue; } @@ -573,11 +704,13 @@ void AioCompltrMain(int ac, char** av) /* * io_getevents() reports errors as negative values. */ + // io_getevents() 报告错误的返回值为负数 if (eventsReceived < 0) { /* Report error */ + // 记录错误日志,发出 PANIC 级别的错误报告 ereport(PANIC, (errmsg("AIO Completer io_getevents() failed: error %d .", eventsReceived))); } - + // 断言,确保 eventsReceived 不超过最大数量 Assert(eventsReceived <= max_nr); /* @@ -585,22 +718,26 @@ void AioCompltrMain(int ac, char** av) * We expect 0 to max_nr requests. The obj here is * the I/O request and the db context. */ + // 逐个遍历、处理异步I/O事件数组中的每个事件,执行特定操作或回调函数。 for (struct io_event* eventp = eventsp; eventsReceived--; eventp++) { - callback((void*)eventp->obj, eventp->res); + callback((void*)eventp->obj, eventp->res);// 调用回调函数,传入 I/O 请求的 obj 和结果 } } + // 所有事件处理完成后,线程即将退出 + // 记录线程退出的日志信息 ereport(LOG, (errmsg("AIO Completer %d EXITED.", compltrIdx))); - exit(0); + exit(0);// 退出线程,返回状态码 0 } /* * @Description: signal handler routines for config,not used now * @See also: */ +// 信号处理程序,用于处理配置请求,目前未使用 static void CompltrConfig(SIGNAL_ARGS) { - t_thrd.aio_cxt.config_requested = true; + t_thrd.aio_cxt.config_requested = true;// 标记配置请求为true,表示配置请求已经发生 } /* @@ -609,31 +746,33 @@ static void CompltrConfig(SIGNAL_ARGS) * so we need to stop what we're doing and exit. * @See also: */ +// 快速退出的信号处理程序,当postmaster通过SIGQUIT信号发出终止请求时触发 static void CompltrQuickDie(SIGNAL_ARGS) { - PG_SETMASK(&t_thrd.libpq_cxt.BlockSig); + PG_SETMASK(&t_thrd.libpq_cxt.BlockSig);// 阻塞所有信号 /* * We DO NOT want to run proc_exit() callbacks -- we're here because * shared memory may be corrupted. Like the other postmaster * children, ...Just nail the windows shut and get out of town.... */ - on_exit_reset(); + on_exit_reset();// 重置进程退出回调 /* * Note we do exit(2) not exit(0)... * ...just like the other postmaster children. */ - exit(2); + exit(2);// 以退出码2退出,类似其他主控进程子进程的做法 } /* * @Description: CompltrShutdown() occurs when signalled SIGTERM by the postmaster. * @See also: */ +// 关闭信号处理程序,当postmaster通过SIGTERM信号发出关闭请求时触发 static void CompltrShutdown(SIGNAL_ARGS) { - t_thrd.aio_cxt.shutdown_requested = true; + t_thrd.aio_cxt.shutdown_requested = true;// 设置异步I/O Completer线程的关闭请求标志 } /** @@ -641,12 +780,14 @@ static void CompltrShutdown(SIGNAL_ARGS) * @in void * @return void */ +// 初始化异步I/O的资源 void AioResourceInitialize(void) { + // 创建一个共享内存上下文用于异步I/O资源,以便进行内存分配和管理 AdioSharedContext = AllocSetContextCreate((MemoryContext)g_instance.instance_context, - "AdioSharedMemory", - ALLOCSET_DEFAULT_MINSIZE, - ALLOCSET_DEFAULT_INITSIZE, - ALLOCSET_DEFAULT_MAXSIZE, - SHARED_CONTEXT); + "AdioSharedMemory",// 设置上下文的名称,用于标识和识别此上下文。 + ALLOCSET_DEFAULT_MINSIZE,//设置上下文的初始最小内存分配大小 + ALLOCSET_DEFAULT_INITSIZE,//设置上下文的初始内存池大小 + ALLOCSET_DEFAULT_MAXSIZE,// 设置上下文的最大内存池大小 + SHARED_CONTEXT);// 设置上下文的标志,指示它是一个共享上下文 } diff --git a/src/gausskernel/process/postmaster/alarmchecker.cpp b/src/gausskernel/process/postmaster/alarmchecker.cpp index 2ffe93696..4ba9d67ff 100644 --- a/src/gausskernel/process/postmaster/alarmchecker.cpp +++ b/src/gausskernel/process/postmaster/alarmchecker.cpp @@ -22,6 +22,7 @@ * * ------------------------------------------------------------------------- */ +// 该文件实现了openGauss的报警检查线程功能,主要用于检查数据库运行过程中的异常情况并进行相应的报警处理 #include "postgres.h" #include "knl/knl_variable.h" @@ -48,64 +49,77 @@ #include "replication/walsender.h" // declare the global variable of alarm module -int g_alarmReportInterval; -char g_alarmComponentPath[MAXPGPATH]; -int g_alarmReportMaxCount; +// 声明用于控制报警模块行为的全局变量 +int g_alarmReportInterval; // 报警上报的时间间隔(单位:秒) +char g_alarmComponentPath[MAXPGPATH]; // 报警组件路径,存储报警信息的组件的路径(长度为MAXPGPATH) +int g_alarmReportMaxCount; // 最大报警上报次数 /* seconds, interval of alarm check loop. */ -static const int AlarmCheckInterval = 1; +static const int AlarmCheckInterval = 1; // 报警检查循环的时间间隔,初始设置为1秒 -bool enable_alarm = false; +bool enable_alarm = false; // 表示是否启用报警功能。初始值为false,通过设置为true来启用报警功能 -static Alarm* DataInstAlarmList = NULL; +static Alarm* DataInstAlarmList = NULL; // 指向 Alarm 结构体的指针,表示报警项的列表。报警项是用于检测不同类型报警的配置信息和处理函数的集合。 -static int DataInstAlarmListSize = 0; +static int DataInstAlarmListSize = 0; // 报警项列表的大小,即列表中报警项的数量 -AlarmCheckResult DataOrRedoDirNotExistChecker(Alarm* alarm, AlarmAdditionalParam* additionalParam); +// 函数原型声明 +AlarmCheckResult DataOrRedoDirNotExistChecker(Alarm* alarm, AlarmAdditionalParam* additionalParam);//报警检查函数,用于检查数据目录或重做日志目录是否存在 -static void DataInstAlarmItemInitialize(void); -static void acSighupHandler(SIGNAL_ARGS); -static void acSigquitHandler(SIGNAL_ARGS); +static void DataInstAlarmItemInitialize(void); // 报警项初始化函数,用于初始化数据实例的报警项列表。 -extern AlarmCheckResult DataInstArchChecker(Alarm* alarm, AlarmAdditionalParam* additionalParam); -extern AlarmCheckResult ConnAuthMethodChecker(Alarm* alarm, AlarmAdditionalParam* additionalParam); +static void acSighupHandler(SIGNAL_ARGS); // SIGHUP信号处理函数,用于在收到SIGHUP信号时设置相关标志 +static void acSigquitHandler(SIGNAL_ARGS); // SIGQUIT信号处理函数的原型,用于在收到SIGQUIT信号时设置相关标志。 +// 数据实例归档检查函数,用于检查数据实例的归档状态 +extern AlarmCheckResult DataInstArchChecker(Alarm* alarm, AlarmAdditionalParam* additionalParam); +// 连接认证方法检查函数,用于检查连接的认证方法是否异常 +extern AlarmCheckResult ConnAuthMethodChecker(Alarm* alarm, AlarmAdditionalParam* additionalParam); +// 数据实例连接到GTM的检查函数,用于检查数据实例连接到GTM的状态 extern AlarmCheckResult DataInstConnToGTMChecker(Alarm* alarm, AlarmAdditionalParam* additionalParam); +// 初始化数据实例报警列表 void DataInstAlarmItemInitialize(void) { + // 设置数据实例报警项的数量为6 DataInstAlarmListSize = 6; + // 分配内存来存储数据实例报警项 DataInstAlarmList = (Alarm*)AlarmAlloc(sizeof(Alarm) * DataInstAlarmListSize); + // 检查内存分配是否成功,如果失败则记录错误日志并退出程序 if (NULL == DataInstAlarmList) { AlarmLog(ALM_LOG, "Out of memory: DataInstAlarmItemInitialize failed."); exit(1); } - // ALM_AI_MissingDataInstDataOrRedoDir + // 初始化各个数据实例报警项 参数列表中后三个分别为报警项的类型、严重性级别,以及对应的检查函数 + // ALM_AI_MissingDataInstDataOrRedoDir 报警项 AlarmItemInitialize( &(DataInstAlarmList[0]), ALM_AI_MissingDataInstDataOrRedoDir, ALM_AS_Normal, DataOrRedoDirNotExistChecker); - // ALM_AI_MissingDataInstWalSegmt + // ALM_AI_MissingDataInstWalSegmt 报警项 AlarmItemInitialize( &(DataInstAlarmList[1]), ALM_AI_MissingDataInstWalSegmt, ALM_AS_Normal, WalSegmentsRemovedChecker); - // ALM_AI_TooManyDataInstConn + // ALM_AI_TooManyDataInstConn 报警项 AlarmItemInitialize(&(DataInstAlarmList[2]), ALM_AI_TooManyDataInstConn, ALM_AS_Normal, ConnectionOverloadChecker); - // ALM_AI_AbnormalDataInstArch + // ALM_AI_AbnormalDataInstArch 报警项 AlarmItemInitialize(&(DataInstAlarmList[3]), ALM_AI_AbnormalDataInstArch, ALM_AS_Normal, DataInstArchChecker); - // ALM_AI_AbnormalDataInstConnAuthMethod + // ALM_AI_AbnormalDataInstConnAuthMethod 报警项 AlarmItemInitialize( &(DataInstAlarmList[4]), ALM_AI_AbnormalDataInstConnAuthMethod, ALM_AS_Normal, ConnAuthMethodChecker); - // ALM_AI_AbnormalDataInstConnToGTM + // ALM_AI_AbnormalDataInstConnToGTM 报警项 AlarmItemInitialize( &(DataInstAlarmList[5]), ALM_AI_AbnormalDataInstConnToGTM, ALM_AS_Normal, DataInstConnToGTMChecker); } +// 特定条件下启动报警检查线程,以便定期检查系统状态并进行报警处理。 ThreadId startAlarmChecker(void) { + // 如果不是在Postmaster环境下或者报警功能被禁用,则直接返回0,表示未启动报警检查线程 if (!IsPostmasterEnvironment || !enable_alarm) { return 0; } - + // 否则,调用initialize_util_thread函数启动报警检查线程,并返回线程ID return initialize_util_thread(ALARMCHECK); } +// 维护一个周期性的报警检查线程,用于及时发现系统异常情况并进行相应的处理。 NON_EXEC_STATIC void AlarmCheckerMain() { @@ -113,21 +127,23 @@ NON_EXEC_STATIC void AlarmCheckerMain() IsUnderPostmaster = true; /* reset t_thrd.proc_cxt.MyProcPid */ - t_thrd.proc_cxt.MyProcPid = gs_thread_self(); + t_thrd.proc_cxt.MyProcPid = gs_thread_self(); //将当前线程的系统级线程ID分配给MyProcPid,以标识当前线程 /* record Start Time for logging */ - t_thrd.proc_cxt.MyStartTime = time(NULL); + t_thrd.proc_cxt.MyStartTime = time(NULL); //获取当前的系统时间,即记录线程的启动时间。 /* reord my name */ - t_thrd.proc_cxt.MyProgName = "AlarmChecker"; + t_thrd.proc_cxt.MyProgName = "AlarmChecker"; // 设置线程的名称,用于标识当前线程的名称 /* Identify myself via ps */ - init_ps_display("AlarmChecker", "", "", ""); + init_ps_display("AlarmChecker", "", "", ""); // 设置线程在进程状态(ps)显示中的标识为 "AlarmChecker" - AlarmLog(ALM_LOG, "alarm checker started."); + AlarmLog(ALM_LOG, "alarm checker started."); // 记录报警检查线程启动信息 + // 初始化Latch支持,用于等待Latch的触发 InitializeLatchSupport(); /* needed for latch waits */ + // 初始化用于信号处理的私有Latch,以便在信号到达时唤醒线程执行相应的处理 /* Initialize private latch for use by signal handlers */ InitLatch(&t_thrd.alarm_cxt.AlarmCheckerLatch); @@ -139,10 +155,15 @@ NON_EXEC_STATIC void AlarmCheckerMain() * want to wait for the backends to exit, whereupon the postmaster will * tell us it's okay to shut down (via SIGUSR2). */ + // 处理了信号的设置和忽略,确保报警检查线程能够正确响应或忽略不同的信号 + // 将SIGHUP信号的处理函数设置为acSighupHandler。当收到SIGHUP信号时,会触发该信号处理函数,用于读取配置文件的标志位。 (void)gspqsignal(SIGHUP, acSighupHandler); /* set flag to read config file */ + // 将SIGINT和SIGTERM信号的处理设置为忽略状态,当收到这两个信号时,不会触发任何处理。 (void)gspqsignal(SIGINT, SIG_IGN); (void)gspqsignal(SIGTERM, SIG_IGN); + // 将SIGQUIT信号的处理函数设置为acSigquitHandler。当收到SIGQUIT信号时,会触发该信号处理函数,用于执行快速退出操作。 (void)gspqsignal(SIGQUIT, acSigquitHandler); + // 将SIGALRM、SIGPIPE、SIGUSR1、SIGUSR2信号的处理设置为忽略状态,即当收到以上信号时,不会触发任何处理。 (void)gspqsignal(SIGALRM, SIG_IGN); (void)gspqsignal(SIGPIPE, SIG_IGN); (void)gspqsignal(SIGUSR1, SIG_IGN); @@ -151,46 +172,57 @@ NON_EXEC_STATIC void AlarmCheckerMain() /* * Reset some signals that are accepted by postmaster but not here */ - (void)gspqsignal(SIGCHLD, SIG_DFL); - (void)gspqsignal(SIGTTIN, SIG_DFL); - (void)gspqsignal(SIGTTOU, SIG_DFL); - (void)gspqsignal(SIGCONT, SIG_DFL); - (void)gspqsignal(SIGWINCH, SIG_DFL); + // 重置一些信号的默认处理方式,以确保报警检查线程不会干扰其他信号的处理 + // 将信号的处理设置为默认处理方式 SIG_DFL + (void)gspqsignal(SIGCHLD, SIG_DFL); // 子进程状态变化信号 + (void)gspqsignal(SIGTTIN, SIG_DFL); // 后台进程试图从终端读取时发送的信号 + (void)gspqsignal(SIGTTOU, SIG_DFL); // 后台进程试图向终端写入时发送的信号 + (void)gspqsignal(SIGCONT, SIG_DFL); // 用于继续停止的进程的信号 + (void)gspqsignal(SIGWINCH, SIG_DFL); // 终端窗口大小发生变化时发送的信号 + // 处理信号掩码,以允许接收一些特定的信号 gs_signal_setmask(&t_thrd.libpq_cxt.UnBlockSig, NULL); (void)gs_signal_unblock_sigusr2(); /* all is done info top memory context. */ + // 切换当前线程的内存上下文到默认的内存上下文组 (void)MemoryContextSwitchTo(THREAD_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_DEFAULT)); - + // 调用函数,初始化数据实例的报警列表 DataInstAlarmItemInitialize(); + // 报警检查线程的主要工作循环,用于持续进行报警检查和处理 for (;;) { /* Clear any already-pending wakeups */ + //将报警检查线程的私有Latch重置为未触发状态,防止在等待期间可能发生的竞争条件或意外触发。 ResetLatch(&t_thrd.alarm_cxt.AlarmCheckerLatch); /* the normal shutdown case */ + // 如果收到了终止信号,退出循环 if (t_thrd.alarm_cxt.gotSigdie) break; /* * reload the postgresql.conf */ + // 如果收到了重新加载配置文件的信号 if (t_thrd.alarm_cxt.gotSighup) { + // 设置为 false,表示报警检查线程不再需要重新加载配置文件。 + // 用于处理 SIGHUP 信号执行操作,标记已经理重新加载配置文件的请求,以便线程在下次循环迭代时不会再次触发重新加载 t_thrd.alarm_cxt.gotSighup = false; - ProcessConfigFile(PGC_SIGHUP); + ProcessConfigFile(PGC_SIGHUP); // 调用ProcessConfigFile函数重新加载配置文件 } - + // 调用AlarmCheckerLoop函数进行报警检查 AlarmCheckerLoop(DataInstAlarmList, DataInstAlarmListSize); /* * Sleep until there's something to do */ + // 等待一段时间,等待Latch被设置或超时 (void)WaitLatch(&t_thrd.alarm_cxt.AlarmCheckerLatch, WL_LATCH_SET | WL_TIMEOUT, AlarmCheckInterval * 1000); } - + // 记录日志,标识报警检查线程正在关闭 AlarmLog(ALM_LOG, "alarm checker shutting down..."); - + // 调用 proc_exit 函数终止线程执行。参数 0 表示正常退出,线程将在此处终止并释放相关资源 proc_exit(0); } @@ -203,15 +235,16 @@ NON_EXEC_STATIC void AlarmCheckerMain() * Description : * Notes : */ +// 信号处理函数,用于处理 SIGHUP 信号,并设置相应的标志 static void acSighupHandler(SIGNAL_ARGS) { - int save_errno = errno; + int save_errno = errno; // 保存当前 errno 的值,以便后续恢复 - t_thrd.alarm_cxt.gotSighup = true; + t_thrd.alarm_cxt.gotSighup = true; // 将线程上下文中的 gotSighup 标志设置为 true,表示收到了 SIGHUP 信号 - SetLatch(&t_thrd.alarm_cxt.AlarmCheckerLatch); + SetLatch(&t_thrd.alarm_cxt.AlarmCheckerLatch); // 使用 SetLatch 函数触发线程的私有 Latch,以便唤醒线程并处理信号 - errno = save_errno; + errno = save_errno;// 恢复之前保存的 errno 值,确保不影响其他代码对 errno 的操作 } /* @@ -220,84 +253,96 @@ static void acSighupHandler(SIGNAL_ARGS) * Description : * Notes : */ +// 信号处理函数,用于处理 SIGTERM 或 SIGINT 信号,并设置相应的标志 static void acSigquitHandler(SIGNAL_ARGS) { - int save_errno = errno; + int save_errno = errno;// 保存当前 errno 的值,以便后续恢复 - t_thrd.alarm_cxt.gotSigdie = true; + t_thrd.alarm_cxt.gotSigdie = true;// 将线程上下文中的 gotSigdie 标志设置为 true,表示收到了 SIGTERM 或 SIGINT 信号 - SetLatch(&t_thrd.alarm_cxt.AlarmCheckerLatch); + SetLatch(&t_thrd.alarm_cxt.AlarmCheckerLatch);// 使用 SetLatch 函数触发线程的私有 Latch,以便唤醒线程并处理信号 - errno = save_errno; + errno = save_errno;// 恢复之前保存的 errno 值,确保不影响其他代码对 errno 的操作 } +// 用于检查目录是否存在,以及目录是否具有合适的属性和权限,即检查目录的有效性。 bool isDirExist(const char* dir) { + // 创建一个用于存放文件/目录属性信息的结构体 struct stat stat_buf; - + // 使用 stat 函数获取目录的状态信息,如果返回值不等于0,则表示目录不存在 if (stat(dir, &stat_buf) != 0) return false; - + // 使用 S_ISDIR 宏判断目录的文件类型是否为目录,如果不是目录类型,则返回 false if (!S_ISDIR(stat_buf.st_mode)) return false; + // 如果不在 Windows 平台且不在 Cygwin 环境中 #if !defined(WIN32) && !defined(__CYGWIN__) - + // 检查目录的拥有者是否为当前用户,如果不是则返回 false if (stat_buf.st_uid != geteuid()) return false; - + // 检查目录的权限是否为用户可读、写和执行权限,如果不是则返回 false if ((stat_buf.st_mode & S_IRWXU) != S_IRWXU) return false; #endif - + // 如果以上条件都满足,则返回 true,表示目录存在且符合要求 return true; } AlarmCheckResult DataOrRedoDirNotExistChecker(Alarm* alarm, AlarmAdditionalParam* additionalParam) { + // 检查 data 目录和 pg_xlog 目录是否存在 if (isDirExist(t_thrd.proc_cxt.DataDir) && isDirExist("pg_xlog")) { // fill the alarm message - WriteAlarmAdditionalInfo(additionalParam, - g_instance.attr.attr_common.PGXCNodeName, + // 填写报警信息 + WriteAlarmAdditionalInfo(additionalParam, //additionalParam:报警的附加参数,用于存储报警信息的详细内容 + g_instance.attr.attr_common.PGXCNodeName, // 数据库实例的名称,用于标识报警发生的实例 "", "", - alarm, - ALM_AT_Resume, - g_instance.attr.attr_common.PGXCNodeName); - return ALM_ACR_Normal; + alarm, //报警的类型或描述,用于标识具体的报警原因或问题 + ALM_AT_Resume,// 设置报警动作为“恢复”,表示报警条件已经解决 + g_instance.attr.attr_common.PGXCNodeName); //数据库实例的名称,用于填写报警信息 + return ALM_ACR_Normal;// 返回报警检查结果为“正常” } else { // fill the alarm message + // 填写报警信息 WriteAlarmAdditionalInfo(additionalParam, g_instance.attr.attr_common.PGXCNodeName, "", "", alarm, - ALM_AT_Fault, + ALM_AT_Fault, // 设置报警动作为“故障” g_instance.attr.attr_common.PGXCNodeName); - return ALM_ACR_Abnormal; + return ALM_ACR_Abnormal;// 返回报警检查结果为“异常” } } /* implementation of alarm module. */ +// 用于释放动态分配的内存,避免内存泄漏 void AlarmFree(void* pointer) { - if (pointer != NULL) - pfree(pointer); + if (pointer != NULL)// 检查指针是否非空 + pfree(pointer); // 使用 pfree 函数释放内存 } +// 用于分配指定大小的内存块 void* AlarmAlloc(size_t size) { - return palloc(size); + return palloc(size);// 调用 palloc 函数分配指定大小的内存块,并返回分配的内存块指针 } +// 日志输出函数,用于在不同级别输出报警信息 +// 输入参数分别为报警级别、前缀和报警文本,根据需要输出不同级别的报警信息以进行监控和调试。 void AlarmLogImplementation(int level, const char* prefix, const char* logtext) { + // 使用 switch 语句根据不同的级别选择不同的日志输出函数并输出信息 switch (level) { - case ALM_DEBUG: + case ALM_DEBUG:// 在 DEBUG3 级别输出报警信息,使用 errmsg 函数输出带有前缀和文本的日志 ereport(DEBUG3, (errmsg("%s%s", prefix, logtext))); break; - case ALM_LOG: + case ALM_LOG:// 在 LOG 级别输出报警信息,使用 errmsg 函数输出带有前缀和文本的日志 ereport(LOG, (errmsg("%s%s", prefix, logtext))); break; default: diff --git a/src/gausskernel/process/postmaster/autovacuum.cpp b/src/gausskernel/process/postmaster/autovacuum.cpp index a1764d695..6629ed118 100755 --- a/src/gausskernel/process/postmaster/autovacuum.cpp +++ b/src/gausskernel/process/postmaster/autovacuum.cpp @@ -127,60 +127,69 @@ #include "storage/lmgr.h" /* struct to keep tuples stat that fetchs from DataNode */ +// 定义了结构体,用于存储从 DataNode 获取的元组统计信息 typedef struct avw_info { - PgStat_StatTabKey tabkey; - int64 n_dead_tuples; - int64 n_live_tuples; - int64 changes_since_analyze; + PgStat_StatTabKey tabkey;// 表的统计信息键,包括表的标识符和统计标志 + int64 n_dead_tuples;// 表中已标记为死亡(待清理)的元组数量 + int64 n_live_tuples;// 表中当前存活的元组数量,即尚未被清理的元组数量 + int64 changes_since_analyze;// 自上次分析以来发生的表中更改(插入、更新、删除)的数量 } avw_info; /* struct to keep track of databases in worker */ + // 用于在工作者中跟踪数据库的结构体 typedef struct avw_dbase { - Oid adw_datid; - char* adw_name; - TransactionId adw_frozenxid; - MultiXactId adw_frozenmulti; - PgStat_StatDBEntry* adw_entry; + Oid adw_datid;// 数据库的 OID(对象标识符) + char* adw_name; // 数据库的名称 + TransactionId adw_frozenxid; // 数据库的冻结事务 ID + MultiXactId adw_frozenmulti;// 数据库的冻结多事务 ID + PgStat_StatDBEntry* adw_entry;// 数据库的统计信息条目指针 } avw_dbase; -NON_EXEC_STATIC void AutoVacWorkerMain(); -NON_EXEC_STATIC void AutoVacLauncherMain(); +NON_EXEC_STATIC void AutoVacWorkerMain();// 自动清理工作者的主要入口点 +NON_EXEC_STATIC void AutoVacLauncherMain();// 自动清理启动器的主要入口点 -static Oid do_start_worker(void); +static Oid do_start_worker(void);// 启动自动清理工作者 +// 根据是否可以启动工作者、是否递归以及时间间隔等因素来决定启动器的休眠时间 static void launcher_determine_sleep(bool canlaunch, bool recursing, struct timeval* nap); -static void launch_worker(TimestampTz now); -static List* get_database_list(void); -static void rebuild_database_list(Oid newdb); -static int db_comparator(const void* a, const void* b); -static void autovac_balance_cost(void); +static void launch_worker(TimestampTz now);// 启动自动清理工作者,用于执行表的清理任务 +static List* get_database_list(void);// 获取数据库列表 +static void rebuild_database_list(Oid newdb);// 重建数据库列表,用于确定哪些数据库需要进行自动清理 +static int db_comparator(const void* a, const void* b);// 数据库比较器函数,用于对数据库列表进行排序 +static void autovac_balance_cost(void);// 根据不同工作者之间的成本平衡,调整自动清理的延迟 -static void do_autovacuum(void); -static void FreeWorkerInfo(int code, Datum arg); +static void do_autovacuum(void);// 执行实际的自动清理操作 +static void FreeWorkerInfo(int code, Datum arg);// 释放工作者信息 /* add parameter toast_table_map by data partition. */ +// 重新检查自动清理和分析的表,确定是否需要执行清理操作 static autovac_table* table_recheck_autovac( vacuum_object* vacObj, HTAB* table_toast_map, HTAB* toast_table_map, TupleDesc pg_class_desc); static void relation_needs_vacanalyze(Oid relid, AutoVacOpts* relopts, Form_pg_class classForm, HeapTuple tuple, PgStat_StatTabEntry* tabentry, bool allowAnalyze, bool allowVacuum, bool is_recheck, bool* dovacuum, bool* doanalyze, bool* need_freeze); - +// 执行实际的自动清理和分析操作 static void autovacuum_do_vac_analyze(autovac_table* tab, BufferAccessStrategy bstrategy); +// 执行本地(非并行)的自动清理和分析操作 static void autovacuum_local_vac_analyze(autovac_table* tab, BufferAccessStrategy bstrategy); /* add parameter statFlag by data partition. */ +// 获取与表相关的统计信息条目 static PgStat_StatTabEntry* get_pgstat_tabentry_relid( Oid relid, bool isshared, uint32 statFlag, PgStat_StatDBEntry* shared, PgStat_StatDBEntry* dbentry); -static void autovac_report_activity(autovac_table* tab); -static void avl_sighup_handler(SIGNAL_ARGS); -static void avl_sigusr2_handler(SIGNAL_ARGS); -static void avl_sigterm_handler(SIGNAL_ARGS); -static void autovac_refresh_stats(void); +static void autovac_report_activity(autovac_table* tab);// 报告自动清理的活动状态 +static void avl_sighup_handler(SIGNAL_ARGS);// 信号处理函数,用于处理 SIGHUP 信号 +static void avl_sigusr2_handler(SIGNAL_ARGS);// 信号处理函数,用于处理 SIGUSR2 信号 +static void avl_sigterm_handler(SIGNAL_ARGS);// 信号处理函数,用于处理 SIGTERM 信号 +static void autovac_refresh_stats(void);// 刷新统计信息 +// 处理分区表的自动清理和分析需求 static void partition_needs_vacanalyze(Oid partid, AutoVacOpts* relopts, Form_pg_partition partForm, HeapTuple partTuple, at_partitioned_table* ap_entry, PgStat_StatTabEntry* tabentry, bool is_recheck, bool* dovacuum, bool* doanalyze, bool* need_freeze); +// 重新检查分区表的自动清理和分析需求 static autovac_table* partition_recheck_autovac( vacuum_object* vacObj, HTAB* table_relopt_map, HTAB* partitioned_tables_map, TupleDesc pg_class_desc); +// 执行 MPP 表的清理操作 extern void DoVacuumMppTable(VacuumStmt* stmt, const char* queryString, bool isTopLevel, bool sentToRemote); /* * Called when the AutoVacuum is ending. @@ -200,13 +209,13 @@ static void autoVacQuitAndClean(int code, Datum arg) /******************************************************************** * AUTOVACUUM LAUNCHER CODE ********************************************************************/ -#ifdef EXEC_BACKEND +#ifdef EXEC_BACKEND// 如果在执行后端代码(EXEC_BACKEND)时,需要从外部设置 /* * We need this set from the outside, before InitProcess is called */ void AutovacuumLauncherIAm(void) { - t_thrd.role = AUTOVACUUM_LAUNCHER; + t_thrd.role = AUTOVACUUM_LAUNCHER;// 设置线程角色,用于标识当前线程是自动清理启动器 } #endif @@ -215,36 +224,37 @@ void AutovacuumLauncherIAm(void) */ NON_EXEC_STATIC void AutoVacLauncherMain() { - sigjmp_buf local_sigjmp_buf; + sigjmp_buf local_sigjmp_buf;// 用于异常跳转的跳转缓冲区 /* we are a postmaster subprocess now */ - IsUnderPostmaster = true; + IsUnderPostmaster = true;// 在后台进程中设置 t_thrd.role = AUTOVACUUM_LAUNCHER; /* reset t_thrd.proc_cxt.MyProcPid */ - t_thrd.proc_cxt.MyProcPid = gs_thread_self(); + t_thrd.proc_cxt.MyProcPid = gs_thread_self();// 获取当前线程ID /* record Start Time for logging */ - t_thrd.proc_cxt.MyStartTime = time(NULL); + t_thrd.proc_cxt.MyStartTime = time(NULL);// 记录进程启动时间 - t_thrd.proc_cxt.MyProgName = "AutoVacLauncher"; - u_sess->attr.attr_common.application_name = pstrdup("AutoVacLauncher"); + t_thrd.proc_cxt.MyProgName = "AutoVacLauncher";// 设置进程名称 + u_sess->attr.attr_common.application_name = pstrdup("AutoVacLauncher"); // 设置应用程序名称 /* Identify myself via ps */ - init_ps_display("autovacuum launcher process", "", "", ""); + init_ps_display("autovacuum launcher process", "", "", "");// 设置进程状态信息 - ereport(LOG, (errmsg("autovacuum launcher started"))); + ereport(LOG, (errmsg("autovacuum launcher started")));// 输出日志,表示自动清理启动器已启动 - if (u_sess->attr.attr_security.PostAuthDelay) - pg_usleep(u_sess->attr.attr_security.PostAuthDelay * 1000000L); + if (u_sess->attr.attr_security.PostAuthDelay) // 如果在配置文件中设置了 PostAuthDelay + pg_usleep(u_sess->attr.attr_security.PostAuthDelay * 1000000L); // 设置休眠时间 - SetProcessingMode(InitProcessing); + SetProcessingMode(InitProcessing);// 设置进程处理模式为初始化 /* * Set up signal handlers. We operate on databases much like a regular * backend, so we use the same signal handling. See equivalent code in * tcop/postgres.c. */ + // 设置信号处理程序 gspqsignal(SIGHUP, avl_sighup_handler); gspqsignal(SIGINT, StatementCancelHandler); gspqsignal(SIGTERM, avl_sigterm_handler); @@ -259,7 +269,7 @@ NON_EXEC_STATIC void AutoVacLauncherMain() gspqsignal(SIGCHLD, SIG_DFL); /* Early initialization */ - BaseInit(); + BaseInit();// 初始化基础环境 /* * Create a per-backend PGPROC struct in shared memory, except in the @@ -267,17 +277,17 @@ NON_EXEC_STATIC void AutoVacLauncherMain() * this before we can use LWLocks (and in the EXEC_BACKEND case we already * had to do some stuff with LWLocks). */ -#ifndef EXEC_BACKEND - InitProcess(); +#ifndef EXEC_BACKEND// 在 EXEC_BACKEND 情况下不需要 InitProcess,因为在 SubPostmasterMain 中已经执行过了 + InitProcess();// 初始化进程 #endif + // 初始化连接数据库和用户身份 + t_thrd.proc_cxt.PostInit->SetDatabaseAndUser(NULL, InvalidOid, NULL);// 设置数据库和用户身份 + t_thrd.proc_cxt.PostInit->InitAutoVacLauncher();// 初始化自动清理启动器 - t_thrd.proc_cxt.PostInit->SetDatabaseAndUser(NULL, InvalidOid, NULL); - t_thrd.proc_cxt.PostInit->InitAutoVacLauncher(); - - SetProcessingMode(NormalProcessing); + SetProcessingMode(NormalProcessing);// 设置进程处理模式为正常 /* If we exit, first try and clean connections and memory */ - on_proc_exit(autoVacQuitAndClean, 0); + on_proc_exit(autoVacQuitAndClean, 0);// 在退出时调用清理函数 /* * Create a memory context that we will do all our work in. We do this so @@ -285,94 +295,96 @@ NON_EXEC_STATIC void AutoVacLauncherMain() * possible memory leaks. */ t_thrd.autovacuum_cxt.AutovacMemCxt = AllocSetContextCreate(t_thrd.top_mem_cxt, "Autovacuum Launcher", - ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); - (void)MemoryContextSwitchTo(t_thrd.autovacuum_cxt.AutovacMemCxt); + ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE);// 创建自动清理启动器内存上下文 + (void)MemoryContextSwitchTo(t_thrd.autovacuum_cxt.AutovacMemCxt);// 切换到自动清理启动器上下文 /* * If an exception is encountered, processing resumes here. * * This code is a stripped down version of PostgresMain error recovery. */ - int curTryCounter; - int* oldTryCounter = NULL; + int curTryCounter;// 当前的 try 块计数器 + int* oldTryCounter = NULL;// 指向旧的 try 块计数器的指针,初始化为 NULL if (sigsetjmp(local_sigjmp_buf, 1) != 0) { - gstrace_tryblock_exit(true, oldTryCounter); + gstrace_tryblock_exit(true, oldTryCounter);// 退出当前的 try 块,并进行相关清理 /* since not using PG_TRY, must reset error stack by hand */ - t_thrd.log_cxt.error_context_stack = NULL; + t_thrd.log_cxt.error_context_stack = NULL;// 手动清空错误堆栈 - t_thrd.log_cxt.call_stack = NULL; + t_thrd.log_cxt.call_stack = NULL;// 手动清空调用堆栈 /* Prevents interrupts while cleaning up */ - HOLD_INTERRUPTS(); + HOLD_INTERRUPTS();// 阻止中断,防止清理过程中被中断 /* Forget any pending QueryCancel request */ - t_thrd.int_cxt.QueryCancelPending = false; - disable_sig_alarm(true); + t_thrd.int_cxt.QueryCancelPending = false;// 清除挂起的取消请求 + disable_sig_alarm(true);// 关闭定时器中断,以免干扰清理过程 t_thrd.int_cxt.QueryCancelPending = false; /* again in case timeout occurred */ /* Report the error to the server log */ - EmitErrorReport(); + EmitErrorReport();// 将错误信息报告到服务器日志 /* Abort the current transaction in order to recover */ - AbortCurrentTransaction(); + AbortCurrentTransaction();// 中止当前事务以进行恢复 /* release resource held by lsc */ - AtEOXact_SysDBCache(false); + AtEOXact_SysDBCache(false);// 释放由 lsc(Local Storage Cache)持有的资源 /* * Now return to normal top-level context and clear ErrorContext for * next time. */ - (void)MemoryContextSwitchTo(t_thrd.autovacuum_cxt.AutovacMemCxt); - FlushErrorState(); + (void)MemoryContextSwitchTo(t_thrd.autovacuum_cxt.AutovacMemCxt);// 切换到正常的顶级内存上下文 + FlushErrorState(); // 清空错误状态 /* Flush any leaked data in the top-level context */ - MemoryContextResetAndDeleteChildren(t_thrd.autovacuum_cxt.AutovacMemCxt); + MemoryContextResetAndDeleteChildren(t_thrd.autovacuum_cxt.AutovacMemCxt);// 清空顶级上下文中的任何泄漏数据 /* don't leave dangling pointers to freed memory */ - t_thrd.autovacuum_cxt.DatabaseListCxt = NULL; + t_thrd.autovacuum_cxt.DatabaseListCxt = NULL;// 清空指向已释放内存的指针 t_thrd.autovacuum_cxt.DatabaseList = NULL; /* * Make sure pgstat also considers our stat data as gone. Note: we * mustn't use autovac_refresh_stats here. */ - pgstat_clear_snapshot(); + pgstat_clear_snapshot();// 清除统计快照数据,确保统计数据的准确性 /* Now we can allow interrupts again */ - RESUME_INTERRUPTS(); + RESUME_INTERRUPTS();// 恢复中断的允许 /* if in shutdown mode, no need for anything further; just go away */ if (t_thrd.autovacuum_cxt.got_SIGTERM) - goto shutdown; + goto shutdown;// 如果处于关闭模式,不需要进行其他操作,直接退出 /* * Sleep at least 1 second after any error. We don't want to be * filling the error logs as fast as we can. */ - pg_usleep(1000000L); + pg_usleep(1000000L); // 在错误后至少睡眠1秒,以免过快填充错误日志 } - oldTryCounter = gstrace_tryblock_entry(&curTryCounter); + oldTryCounter = gstrace_tryblock_entry(&curTryCounter); // 进入一个新的 try 块,返回旧的 try 块计数器 /* We can now handle ereport(ERROR) */ - t_thrd.log_cxt.PG_exception_stack = &local_sigjmp_buf; + t_thrd.log_cxt.PG_exception_stack = &local_sigjmp_buf; // 设置错误处理栈,可以处理 ereport(ERROR) /* must unblock signals before calling rebuild_database_list */ - gs_signal_setmask(&t_thrd.libpq_cxt.UnBlockSig, NULL); - gs_signal_unblock_sigusr2(); + gs_signal_setmask(&t_thrd.libpq_cxt.UnBlockSig, NULL);// 在调用 rebuild_database_list 之前,解除信号屏蔽 + gs_signal_unblock_sigusr2();// 解除对 SIGUSR2 信号的屏蔽 /* * Force zero_damaged_pages OFF in the autovac process, even if it is set * in postgresql.conf. We don't really want such a dangerous option being * applied non-interactively. */ + // 强制将 zero_damaged_pages 设置为 "false",以避免非交互式应用 SetConfigOption("zero_damaged_pages", "false", PGC_SUSET, PGC_S_OVERRIDE); /* * Force statement_timeout to zero to avoid a timeout setting from * preventing regular maintenance from being executed. */ + // 强制将 statement_timeout 设置为 0,以避免阻止常规维护的执行 SetConfigOption("statement_timeout", "0", PGC_SUSET, PGC_S_OVERRIDE); /* @@ -380,18 +392,20 @@ NON_EXEC_STATIC void AutoVacLauncherMain() * to pay the overhead of serializable mode, nor add any risk of causing * deadlocks or delaying other transactions. */ + + // 强制将 default_transaction_isolation 设置为 "read committed" SetConfigOption("default_transaction_isolation", "read committed", PGC_SUSET, PGC_S_OVERRIDE); /* * In emergency mode, just start a worker (unless shutdown was requested) * and go away. */ - if (!AutoVacuumingActive()) { - if (!t_thrd.autovacuum_cxt.got_SIGTERM) - do_start_worker(); + if (!AutoVacuumingActive()) { // 如果不处于自动清理模式 + if (!t_thrd.autovacuum_cxt.got_SIGTERM) // 如果没有收到关闭请求 + do_start_worker();// 启动一个新的工作进程 proc_exit(0); /* done */ } - + // 设置当前自动清理进程的进程号到共享内存中 t_thrd.autovacuum_cxt.AutoVacuumShmem->av_launcherpid = t_thrd.proc_cxt.MyProcPid; /* @@ -401,15 +415,15 @@ NON_EXEC_STATIC void AutoVacLauncherMain() * correct because the only operation is to add autovacuum_naptime to the * entry, and time always increases). */ - rebuild_database_list(InvalidOid); + rebuild_database_list(InvalidOid); // 创建初始的数据库列表,按照 next_time 的递减顺序排序 /* loop until shutdown request */ - while (!t_thrd.autovacuum_cxt.got_SIGTERM) { - struct timeval nap; - TimestampTz current_time = 0; - bool can_launch = false; - Dlelem* elem = NULL; - int rc; + while (!t_thrd.autovacuum_cxt.got_SIGTERM) {// 在没有收到关闭请求时循环执行 + struct timeval nap; // 用于计算下次循环等待的时间间隔 + TimestampTz current_time = 0;// 时间戳,用于记录当前的时间 + bool can_launch = false;// 表示是否可以启动新的自动清理工作进程 + Dlelem* elem = NULL;// 双向链表元素指针,用于遍历数据库列表 + int rc;// 用于存储WaitLatch函数的返回值 /* * This loop is a bit different from the normal use of WaitLatch, @@ -417,63 +431,66 @@ NON_EXEC_STATIC void AutoVacLauncherMain() * process. So it's WaitLatch, then ResetLatch, then check for * wakening conditions. */ - launcher_determine_sleep((t_thrd.autovacuum_cxt.AutoVacuumShmem->av_freeWorkers != NULL), false, &nap); + + launcher_determine_sleep((t_thrd.autovacuum_cxt.AutoVacuumShmem->av_freeWorkers != NULL), false, &nap);// 确定休眠时间和条件 /* * Wait until naptime expires or we get some type of signal (all the * signal handlers will wake us by calling SetLatch). */ - rc = WaitLatch(&t_thrd.proc->procLatch, WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH, - (nap.tv_sec * 1000L) + (nap.tv_usec / 1000L)); + rc = WaitLatch(&t_thrd.proc->procLatch, // 等待的标志(latch) + WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH,// 等待的条件标志 + (nap.tv_sec * 1000L) + (nap.tv_usec / 1000L));// 等待的时间 - ResetLatch(&t_thrd.proc->procLatch); + ResetLatch(&t_thrd.proc->procLatch);// 重置用于信号处理的 latch /* Process sinval catchup interrupts that happened while sleeping */ - ProcessCatchupInterrupt(); + ProcessCatchupInterrupt(); // 处理在睡眠期间发生的 sinval 中断 /* * Emergency bailout if postmaster has died. This is to avoid the * necessity for manual cleanup of all postmaster children. */ - if (((unsigned int)rc) & WL_POSTMASTER_DEATH) - proc_exit(1); + if (((unsigned int)rc) & WL_POSTMASTER_DEATH)// 收到了 postmaster 终止信号 + proc_exit(1);// 紧急退出,避免需要手动清理所有 postmaster 子进程 /* the normal shutdown case */ - if (t_thrd.autovacuum_cxt.got_SIGTERM) - break; + if (t_thrd.autovacuum_cxt.got_SIGTERM)// 收到了正常关闭信号 + break;// 退出循环,结束自动清理进程 - if (t_thrd.autovacuum_cxt.got_SIGHUP) { - t_thrd.autovacuum_cxt.got_SIGHUP = false; - ProcessConfigFile(PGC_SIGHUP); + if (t_thrd.autovacuum_cxt.got_SIGHUP) { // 收到了 SIGHUP 信号,即重新加载配置文件的信号 + t_thrd.autovacuum_cxt.got_SIGHUP = false; // 标记为已处理 + ProcessConfigFile(PGC_SIGHUP); // 重新加载配置文件 /* shutdown requested in config file? */ - if (!AutoVacuumingActive()) + if (!AutoVacuumingActive())// 如果在配置文件中请求了关闭 break; /* rebalance in case the default cost parameters changed */ - LWLockAcquire(AutovacuumLock, LW_EXCLUSIVE); - autovac_balance_cost(); - LWLockRelease(AutovacuumLock); + LWLockAcquire(AutovacuumLock, LW_EXCLUSIVE); // 获取自动清理锁,以进行排衡操作 + autovac_balance_cost(); // 执行排衡操作以重新平衡默认成本参数 + LWLockRelease(AutovacuumLock);// 释放自动清理锁 /* rebuild the list in case the naptime changed */ - rebuild_database_list(InvalidOid); + rebuild_database_list(InvalidOid); // 重建数据库列表,以防 naptime 发生变化 } /* * a worker finished, or postmaster signalled failure to start a * worker */ - if (t_thrd.autovacuum_cxt.got_SIGUSR2) { - t_thrd.autovacuum_cxt.got_SIGUSR2 = false; + if (t_thrd.autovacuum_cxt.got_SIGUSR2) {// 收到了 SIGUSR2 信号 + t_thrd.autovacuum_cxt.got_SIGUSR2 = false; // 标记为已处理信号 /* rebalance cost limits, if needed */ + // 检查自动清理共享内存中的 av_signal 数组,看是否收到了需要重新平衡清理成本限制的信号 if (t_thrd.autovacuum_cxt.AutoVacuumShmem->av_signal[AutoVacRebalance]) { - LWLockAcquire(AutovacuumLock, LW_EXCLUSIVE); - t_thrd.autovacuum_cxt.AutoVacuumShmem->av_signal[AutoVacRebalance] = false; - autovac_balance_cost(); - LWLockRelease(AutovacuumLock); + LWLockAcquire(AutovacuumLock, LW_EXCLUSIVE);// 获取自动清理锁,以进行排衡操作 + t_thrd.autovacuum_cxt.AutoVacuumShmem->av_signal[AutoVacRebalance] = false;// 标记为已处理排衡信号 + autovac_balance_cost();// 执行排衡操作以重新平衡清理成本限制 + LWLockRelease(AutovacuumLock); // 释放自动清理锁 } - + // 检查是否在尝试启动一个新的工作进程时失败 if (t_thrd.autovacuum_cxt.AutoVacuumShmem->av_signal[AutoVacForkFailed]) { /* * If the postmaster failed to start a new worker, we sleep @@ -485,9 +502,9 @@ NON_EXEC_STATIC void AutoVacLauncherMain() * I don't think it makes much sense, because a future start * of a worker will continue to fail in the same way. */ - t_thrd.autovacuum_cxt.AutoVacuumShmem->av_signal[AutoVacForkFailed] = false; - pg_usleep(1000000L); /* 1s */ - SendPostmasterSignal(PMSIGNAL_START_AUTOVAC_WORKER); + t_thrd.autovacuum_cxt.AutoVacuumShmem->av_signal[AutoVacForkFailed] = false;// 清除标志位,避免重复处理 + pg_usleep(1000000L); /* 1s */ // 休眠 1 秒,等待一段时间,以便重新尝试 + SendPostmasterSignal(PMSIGNAL_START_AUTOVAC_WORKER); // 发送信号给主控进程以启动新的工作进程 continue; } } @@ -498,14 +515,14 @@ NON_EXEC_STATIC void AutoVacLauncherMain() * launcher slot available. Second, we need to make sure that no * other worker failed while starting up. */ - current_time = GetCurrentTimestamp(); - LWLockAcquire(AutovacuumLock, LW_SHARED); - + current_time = GetCurrentTimestamp();// 获取当前时间戳 + LWLockAcquire(AutovacuumLock, LW_SHARED);// 获取自动清理锁的共享模式锁,用于同步自动清理进程之间的访问 + // 检查是否存在可用的 launcher slot(启动器槽位),用于启动工作进程 can_launch = (t_thrd.autovacuum_cxt.AutoVacuumShmem->av_freeWorkers != NULL); - if (t_thrd.autovacuum_cxt.AutoVacuumShmem->av_startingWorker != NULL) { + if (t_thrd.autovacuum_cxt.AutoVacuumShmem->av_startingWorker != NULL) {// 判断是否有正在启动的工作进程 int waittime; - WorkerInfo worker = t_thrd.autovacuum_cxt.AutoVacuumShmem->av_startingWorker; + WorkerInfo worker = t_thrd.autovacuum_cxt.AutoVacuumShmem->av_startingWorker;// 保存正在启动的工作进程信息 /* * We can't launch another worker when another one is still @@ -522,10 +539,12 @@ NON_EXEC_STATIC void AutoVacLauncherMain() * before the worker removes the WorkerInfo from the * startingWorker pointer. */ + // 计算等待时间 waittime,确保等待时间不超过 60 秒 waittime = Min(u_sess->attr.attr_storage.autovacuum_naptime, 60) * 1000; + // 如果启动时间与当前时间差超过等待时间,表明工作进程可能在启动时失败,或者启动时间过长 if (TimestampDifferenceExceeds(worker->wi_launchtime, current_time, waittime)) { - LWLockRelease(AutovacuumLock); - LWLockAcquire(AutovacuumLock, LW_EXCLUSIVE); + LWLockRelease(AutovacuumLock); // 释放之前获取的自动清理锁(共享模式) + LWLockAcquire(AutovacuumLock, LW_EXCLUSIVE); // 重新以独占模式获取锁,以进行状态更新 /* * No other process can put a worker in starting mode, so if @@ -533,39 +552,40 @@ NON_EXEC_STATIC void AutoVacLauncherMain() * we assume it's the same one we saw above (so we don't * recheck the launch time). */ - if (t_thrd.autovacuum_cxt.AutoVacuumShmem->av_startingWorker != NULL) { - worker = t_thrd.autovacuum_cxt.AutoVacuumShmem->av_startingWorker; - worker->wi_dboid = InvalidOid; - worker->wi_tableoid = InvalidOid; - worker->wi_parentoid = InvalidOid; - worker->wi_sharedrel = false; - worker->wi_proc = NULL; - worker->wi_launchtime = 0; - worker->wi_links.next = (SHM_QUEUE*)t_thrd.autovacuum_cxt.AutoVacuumShmem->av_freeWorkers; - t_thrd.autovacuum_cxt.AutoVacuumShmem->av_freeWorkers = worker; - t_thrd.autovacuum_cxt.AutoVacuumShmem->av_startingWorker = NULL; - ereport(WARNING, (errmsg("worker took too long to start; canceled"))); + if (t_thrd.autovacuum_cxt.AutoVacuumShmem->av_startingWorker != NULL) {// 检查是否仍然存在正在启动的工作进程 + worker = t_thrd.autovacuum_cxt.AutoVacuumShmem->av_startingWorker; // 获取正在启动的工作进程信息 + // 重置工作进程信息 + worker->wi_dboid = InvalidOid; // 重置数据库 OID + worker->wi_tableoid = InvalidOid; // 重置表 OID + worker->wi_parentoid = InvalidOid; // 重置父表 OID + worker->wi_sharedrel = false; // 重置是否为共享表标志 + worker->wi_proc = NULL; // 重置工作进程相关的进程信息 + worker->wi_launchtime = 0; // 重置工作进程的启动时间 + worker->wi_links.next = (SHM_QUEUE*)t_thrd.autovacuum_cxt.AutoVacuumShmem->av_freeWorkers; // 将工作进程添加到可用工作进程链表 + t_thrd.autovacuum_cxt.AutoVacuumShmem->av_freeWorkers = worker; // 更新可用工作进程链表 + t_thrd.autovacuum_cxt.AutoVacuumShmem->av_startingWorker = NULL; // 将正在启动的工作进程指针置为空 + ereport(WARNING, (errmsg("worker took too long to start; canceled"))); //发送警告消息,说明由于工作进程启动时间过长而被取消 } } else - can_launch = false; + can_launch = false; //正在启动的工作进程的启动时间未超过等待时间,将 can_launch 设为 false,表示无法启动新的工作进程 } - LWLockRelease(AutovacuumLock); /* either shared or exclusive */ + LWLockRelease(AutovacuumLock); /* either shared or exclusive */ //释放互斥锁 /* if we can't do anything, just go back to sleep */ - if (!can_launch) + if (!can_launch) // 如果无法执行任何操作,就重新进入休眠状态 continue; /* We're OK to start a new worker */ - elem = DLGetTail(t_thrd.autovacuum_cxt.DatabaseList); + elem = DLGetTail(t_thrd.autovacuum_cxt.DatabaseList); // 获取数据库列表的最后一个元素 if (elem != NULL) { - avl_dbase* avdb = (avl_dbase*)DLE_VAL(elem); + avl_dbase* avdb = (avl_dbase*)DLE_VAL(elem); // 获取数据库信息 /* * launch a worker if next_worker is right now or it is in the * past */ if (TimestampDifferenceExceeds(avdb->adl_next_worker, current_time, 0)) - launch_worker(current_time); + launch_worker(current_time); // 启动工作进程 } else { /* * Special case when the list is empty: start a worker right away. @@ -575,14 +595,14 @@ NON_EXEC_STATIC void AutoVacLauncherMain() * quickly (at most once every autovacuum_naptime when the list is * empty). */ - launch_worker(current_time); + launch_worker(current_time);// 当列表为空时,立即启动工作进程 } } /* Normal exit from the autovac launcher is here */ -shutdown: - ereport(LOG, (errmsg("autovacuum launcher shutting down"))); - t_thrd.autovacuum_cxt.AutoVacuumShmem->av_launcherpid = 0; +shutdown: // 正常从自动清理进程退出 + ereport(LOG, (errmsg("autovacuum launcher shutting down"))); // 记录日志,表示自动清理进程正在关闭 + t_thrd.autovacuum_cxt.AutoVacuumShmem->av_launcherpid = 0; // 将自动清理进程的进程 ID 置为 0 proc_exit(0); /* done */ } @@ -604,24 +624,26 @@ static void launcher_determine_sleep(bool canlaunch, bool recursing, struct time * in the past; if the first entry has too close a next_worker value, or a * time in the past, we will sleep a small nominal time. */ + // 如果不能启动新的工作进程(canlaunch = false),则将休眠时间设置为配置参数 autovacuum_naptime 所指定的时间。 if (!canlaunch) { nap->tv_sec = u_sess->attr.attr_storage.autovacuum_naptime; nap->tv_usec = 0; + // 如果数据库列表不为空,找到列表中最后一个元素(最后一个数据库)并获取其下次工作执行的时间。 } else if ((elem = DLGetTail(t_thrd.autovacuum_cxt.DatabaseList)) != NULL) { - avl_dbase* avdb = (avl_dbase*)DLE_VAL(elem); - TimestampTz current_time = GetCurrentTimestamp(); - TimestampTz next_wakeup; - long secs; - int usecs; - - next_wakeup = avdb->adl_next_worker; - TimestampDifference(current_time, next_wakeup, &secs, &usecs); + avl_dbase* avdb = (avl_dbase*)DLE_VAL(elem); // 转换元素值为avl_dbase结构体指针 + TimestampTz current_time = GetCurrentTimestamp(); // 获取当前时间 + TimestampTz next_wakeup; // 下一个工作进程应该唤醒的时间 + long secs; // 时间的秒数部分 + int usecs; // 时间的微秒数部分 + next_wakeup = avdb->adl_next_worker; // 获取下一个工作进程应该唤醒的时间 + TimestampDifference(current_time, next_wakeup, &secs, &usecs); // 计算当前时间与唤醒时间的差值 + // 设置休眠时间为差值 nap->tv_sec = secs; nap->tv_usec = usecs; } else { /* list is empty, sleep for whole autovacuum_naptime seconds */ - nap->tv_sec = u_sess->attr.attr_storage.autovacuum_naptime; + nap->tv_sec = u_sess->attr.attr_storage.autovacuum_naptime; // 如果数据库列表为空,使用autovacuum_naptime作为休眠时间 nap->tv_usec = 0; } @@ -636,6 +658,7 @@ static void launcher_determine_sleep(bool canlaunch, bool recursing, struct time * in the future, but it seems best not to trust too much on that. */ if (nap->tv_sec == 0 && nap->tv_usec == 0 && !recursing) { + // 如果休眠时间为零且不是递归调用,则重新构建数据库列表并重新计算休眠时间 rebuild_database_list(InvalidOid); launcher_determine_sleep(canlaunch, true, nap); return; @@ -643,6 +666,7 @@ static void launcher_determine_sleep(bool canlaunch, bool recursing, struct time /* The smallest time we'll allow the launcher to sleep. */ if (nap->tv_sec <= 0 && nap->tv_usec <= MIN_AUTOVAC_SLEEPTIME * 1000) { + // 如果休眠时间小于等于最小休眠时间,设置休眠时间为最小休眠时间的微秒值 nap->tv_sec = 0; nap->tv_usec = (__suseconds_t)(MIN_AUTOVAC_SLEEPTIME * 1000); } @@ -663,24 +687,25 @@ static void launcher_determine_sleep(bool canlaunch, bool recursing, struct time */ static void rebuild_database_list(Oid newdb) { - List* dblist = NIL; - ListCell* cell = NULL; - MemoryContext newcxt; - MemoryContext oldcxt; - MemoryContext tmpcxt; - HASHCTL hctl; - uint score; - uint nelems; - HTAB* dbhash = NULL; + List* dblist = NIL; // 存储数据库列表的链表 + ListCell* cell = NULL; // 链表迭代器 + MemoryContext newcxt; // 创建新的内存上下文以容纳新的数据库列表 + MemoryContext oldcxt; // 保存旧的内存上下文 + MemoryContext tmpcxt; // 临时内存上下文 + HASHCTL hctl; // 哈希表控制结构 + uint score; // 为数据库分配的得分 + uint nelems; // 数据库数量 + HTAB* dbhash = NULL; // 哈希表用于存储数据库 /* use fresh stats */ autovac_refresh_stats(); + // 为新的数据库列表和临时上下文分配内存 newcxt = AllocSetContextCreate(t_thrd.autovacuum_cxt.AutovacMemCxt, "AV dblist", ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); tmpcxt = AllocSetContextCreate( newcxt, "tmp AV dblist", ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); - oldcxt = MemoryContextSwitchTo(tmpcxt); + oldcxt = MemoryContextSwitchTo(tmpcxt); // 将当前上下文切换为临时上下文 /* * Implementing this is not as simple as it sounds, because we need to put @@ -698,54 +723,55 @@ static void rebuild_database_list(Oid newdb) * score, and finally put the array elements into the new doubly linked * list. */ - hctl.keysize = sizeof(Oid); - hctl.entrysize = sizeof(avl_dbase); - hctl.hash = oid_hash; - hctl.hcxt = tmpcxt; - dbhash = hash_create("db hash", 20, &hctl, HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT); + // 初始化哈希表控制结构 + hctl.keysize = sizeof(Oid); // 键的大小 + hctl.entrysize = sizeof(avl_dbase); // 哈希表元素的大小 + hctl.hash = oid_hash; // 使用Oid作为哈希函数 + hctl.hcxt = tmpcxt; // 哈希表的上下文使用临时上下文 + dbhash = hash_create("db hash", 20, &hctl, HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT); // 创建哈希表,20个槽位 /* start by inserting the new database */ score = 0; - if (OidIsValid(newdb)) { + if (OidIsValid(newdb)) { // 检查新数据库的有效性 avl_dbase* db = NULL; PgStat_StatDBEntry* entry = NULL; /* only consider this database if it has a pgstat entry */ - entry = pgstat_fetch_stat_dbentry(newdb); - if (entry != NULL) { + entry = pgstat_fetch_stat_dbentry(newdb); // 获取新数据库的统计信息 + if (entry != NULL) {// 仅考虑具有统计信息的数据库 /* we assume it isn't found because the hash was just created */ - db = (avl_dbase*)hash_search(dbhash, &newdb, HASH_ENTER, NULL); + db = (avl_dbase*)hash_search(dbhash, &newdb, HASH_ENTER, NULL); // 在哈希表中查找或插入新数据库的条目 /* hash_search already filled in the key */ - db->adl_score = score++; + db->adl_score = score++;// 设置数据库的分数(优先级) /* next_worker is filled in later */ } } /* Now insert the databases from the existing list */ - if (t_thrd.autovacuum_cxt.DatabaseList != NULL) { + if (t_thrd.autovacuum_cxt.DatabaseList != NULL) { // 如果已经存在数据库列表 Dlelem* elem = NULL; - elem = DLGetHead(t_thrd.autovacuum_cxt.DatabaseList); - while (elem != NULL) { + elem = DLGetHead(t_thrd.autovacuum_cxt.DatabaseList); // 从头部获取第一个元素 + while (elem != NULL) { // 遍历现有数据库列表 avl_dbase* avdb = (avl_dbase*)DLE_VAL(elem); avl_dbase* db = NULL; bool found = false; PgStat_StatDBEntry* entry = NULL; - elem = DLGetSucc(elem); + elem = DLGetSucc(elem);// 获取下一个元素 /* * skip databases with no stat entries -- in particular, this gets * rid of dropped databases */ - entry = pgstat_fetch_stat_dbentry(avdb->adl_datid); - if (entry == NULL) + entry = pgstat_fetch_stat_dbentry(avdb->adl_datid); // 获取数据库统计信息 + if (entry == NULL) // 跳过没有统计信息的数据库,特别是已删除的数据库 continue; - db = (avl_dbase*)hash_search(dbhash, &(avdb->adl_datid), HASH_ENTER, &found); + db = (avl_dbase*)hash_search(dbhash, &(avdb->adl_datid), HASH_ENTER, &found); // 在哈希表中查找或插入数据库条目 - if (!found) { + if (!found) { // 如果在哈希表中未找到数据库条目,则设置数据库的分数并递增 score 值 /* hash_search already filled in the key */ db->adl_score = score++; /* next_worker is filled in later */ @@ -754,19 +780,19 @@ static void rebuild_database_list(Oid newdb) } /* finally, insert all qualifying databases not previously inserted */ - dblist = get_database_list(); - foreach (cell, dblist) { - avw_dbase* avdb = (avw_dbase*)lfirst(cell); - avl_dbase* db = NULL; - bool found = false; - PgStat_StatDBEntry* entry = NULL; + dblist = get_database_list(); // 获取数据库列表 + foreach (cell, dblist) { // 遍历数据库列表 + avw_dbase* avdb = (avw_dbase*)lfirst(cell); // 从链表节点中获取 avw_dbase 结构体指针 + avl_dbase* db = NULL; // 用于存储哈希表中的数据库条目 + bool found = false; // 指示是否在哈希表中找到数据库条目 + PgStat_StatDBEntry* entry = NULL; // 用于存储数据库的统计信息 /* only consider databases with a pgstat entry */ - entry = pgstat_fetch_stat_dbentry(avdb->adw_datid); + entry = pgstat_fetch_stat_dbentry(avdb->adw_datid); // 仅考虑具有 pgstat 统计信息的数据库 if (entry == NULL) continue; - db = (avl_dbase*)hash_search(dbhash, &(avdb->adw_datid), HASH_ENTER, &found); + db = (avl_dbase*)hash_search(dbhash, &(avdb->adw_datid), HASH_ENTER, &found); // 在哈希表中搜索数据库条目 /* only update the score if the database was not already on the hash */ if (!found) { /* hash_search already filled in the key */ @@ -774,12 +800,13 @@ static void rebuild_database_list(Oid newdb) /* next_worker is filled in later */ } } - nelems = score; + nelems = score; // 更新数据库条目数量 /* from here on, the allocated memory belongs to the new list */ - (void)MemoryContextSwitchTo(newcxt); - t_thrd.autovacuum_cxt.DatabaseList = DLNewList(); + (void)MemoryContextSwitchTo(newcxt); // 切换当前内存上下文为新的上下文,分配的内存将属于新的列表 + t_thrd.autovacuum_cxt.DatabaseList = DLNewList(); // 创建一个新的双向链表来存储数据库列表 + // 如果数据库条目数量大于0 if (nelems > 0) { TimestampTz current_time; int millis_increment; @@ -789,6 +816,7 @@ static void rebuild_database_list(Oid newdb) uint i; int rc = 0; + // 如果数据库条目数量太大,超出了最大分配大小,报错 if (unlikely(nelems > MaxAllocSize / sizeof(avl_dbase))) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("Invalid database num:%u", nelems))); } @@ -796,11 +824,12 @@ static void rebuild_database_list(Oid newdb) /* put all the hash elements into an array */ dbary = (avl_dbase*)palloc(nelems * sizeof(avl_dbase)); + // 遍历哈希表,将元素复制到数组中 i = 0; hash_seq_init(&seq, dbhash); while ((db = (avl_dbase*)hash_seq_search(&seq)) != NULL) { rc = memcpy_s(&(dbary[i++]), sizeof(avl_dbase), db, sizeof(avl_dbase)); - securec_check(rc, "\0", "\0"); + securec_check(rc, "\0", "\0"); // 检查复制操作是否成功 } /* sort the array */ @@ -813,40 +842,43 @@ static void rebuild_database_list(Oid newdb) * coded not to allow), silently use a larger naptime (but don't touch * the GUC variable). */ + // 计算数据库之间的时间间隔,将毫秒增量设为 (autovacuum_naptime / nelems) * 1000 millis_increment = (int)(1000.0 * u_sess->attr.attr_storage.autovacuum_naptime / nelems); + // 如果计算得到的毫秒增量小于等于最小休眠时间,则将其设为 (MIN_AUTOVAC_SLEEPTIME * 1.1) + // 确保休眠时间不会低于最小休眠时间,以避免过于频繁的唤醒和执行 if (millis_increment <= MIN_AUTOVAC_SLEEPTIME) { millis_increment = (int)(MIN_AUTOVAC_SLEEPTIME * 1.1); } - current_time = GetCurrentTimestamp(); + current_time = GetCurrentTimestamp(); // 获取当前时间戳 /* * move the elements from the array into the dllist, setting the * next_worker while walking the array */ - for (i = 0; i < nelems; i++) { + for (i = 0; i < nelems; i++) { // 将数组中的元素移入双向链表中 avl_dbase* db_tmp = (avl_dbase*)&(dbary[i]); Dlelem* elem = NULL; - current_time = TimestampTzPlusMilliseconds(current_time, millis_increment); + current_time = TimestampTzPlusMilliseconds(current_time, millis_increment); // 计算下一个工作时间 db_tmp->adl_next_worker = current_time; - elem = DLNewElem(db_tmp); + elem = DLNewElem(db_tmp); // 创建链表元素并添加到链表头部 /* later elements should go closer to the head of the list */ DLAddHead(t_thrd.autovacuum_cxt.DatabaseList, elem); } } /* all done, clean up memory */ - if (t_thrd.autovacuum_cxt.DatabaseListCxt != NULL) - MemoryContextDelete(t_thrd.autovacuum_cxt.DatabaseListCxt); - MemoryContextDelete(tmpcxt); - t_thrd.autovacuum_cxt.DatabaseListCxt = newcxt; - (void)MemoryContextSwitchTo(oldcxt); + if (t_thrd.autovacuum_cxt.DatabaseListCxt != NULL) // 检查是否有旧的数据库列表内存上下文 + MemoryContextDelete(t_thrd.autovacuum_cxt.DatabaseListCxt); // 如果有则删除,释放旧的数据库列表内存 + MemoryContextDelete(tmpcxt); // 释放临时内存上下文 + t_thrd.autovacuum_cxt.DatabaseListCxt = newcxt; // 将新的内存上下文赋值给数据库列表的上下文 + (void)MemoryContextSwitchTo(oldcxt); // 切换回之前的内存上下文 } /* qsort comparator for avl_dbase, using adl_score */ -static int db_comparator(const void* a, const void* b) +static int db_comparator(const void* a, const void* b) // 排序存储在数组中的avl_dbase结构体 { if (((const avl_dbase*)a)->adl_score == ((const avl_dbase*)b)->adl_score) { return 0; @@ -868,25 +900,25 @@ static int db_comparator(const void* a, const void* b) */ static Oid do_start_worker(void) { - List* dblist = NIL; + List* dblist = NIL; // 数据库列表 ListCell* cell = NULL; - TransactionId xidForceLimit; + TransactionId xidForceLimit; // 事务 ID 限制,用于决定何时强制执行 VACUUM 操作 #ifndef ENABLE_MULTIPLE_NODES - MultiXactId multiForceLimit; + MultiXactId multiForceLimit; // 多事务 ID 限制 #endif - bool for_xid_wrap = false; - bool for_multi_wrap = false; - avw_dbase* avdb = NULL; - TimestampTz current_time; - bool skipit = false; - Oid retval = InvalidOid; - MemoryContext tmpcxt, oldcxt; + bool for_xid_wrap = false; // 用于标记是否跨越了 XID 边界 + bool for_multi_wrap = false; // 用于标记是否跨越了多事务 ID 边界 + avw_dbase* avdb = NULL; // 数据库结构体 + TimestampTz current_time; // 当前时间戳 + bool skipit = false; // 标记是否需要跳过某个数据库 + Oid retval = InvalidOid; // 函数返回的要处理的数据库 OID,默认为 InvalidOid + MemoryContext tmpcxt, oldcxt; // 临时内存上下文和旧内存上下文 /* return quickly when there are no free workers */ LWLockAcquire(AutovacuumLock, LW_SHARED); if (t_thrd.autovacuum_cxt.AutoVacuumShmem->av_freeWorkers == NULL) { LWLockRelease(AutovacuumLock); - AUTOVAC_LOG(LOG, "no free autovaccm worker"); + AUTOVAC_LOG(LOG, "no free autovaccm worker"); // 记录日志,没有可用的自动清理工作进程 return InvalidOid; } @@ -896,22 +928,25 @@ static Oid do_start_worker(void) * Create and switch to a temporary context to avoid leaking the memory * allocated for the database list. */ + // 创建并切换到临时上下文来存储数据库列表的内存分配 tmpcxt = AllocSetContextCreate(CurrentMemoryContext, "Start worker tmp cxt", ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); oldcxt = MemoryContextSwitchTo(tmpcxt); /* use fresh stats */ - autovac_refresh_stats(); + autovac_refresh_stats(); // 更新统计数据 /* Get a list of databases */ - dblist = get_database_list(); + dblist = get_database_list(); // 获取数据库列表 /* * Determine the oldest datfrozenxid64/relfrozenxid64 that we will allow to * pass without forcing a vacuum. (This limit can be tightened for * particular tables, but not loosened.) */ - t_thrd.autovacuum_cxt.recentXid = ReadNewTransactionId(); + t_thrd.autovacuum_cxt.recentXid = ReadNewTransactionId(); // 读取最新的事务 ID + // 判断最近的事务ID是否超过了阈值,用于计算在何种情况下需要强制执行VACUUM操作的最早事务ID限制 + // autovacuum_freeze_max_age 是一个GUC参数,表示执行VACUUM操作的最大事务ID范围。 if (t_thrd.autovacuum_cxt.recentXid > FirstNormalTransactionId + (uint64)g_instance.attr.attr_storage.autovacuum_freeze_max_age) xidForceLimit = t_thrd.autovacuum_cxt.recentXid - @@ -921,7 +956,8 @@ static Oid do_start_worker(void) #ifndef ENABLE_MULTIPLE_NODES /* Also determine the oldest datminmxid we will consider. */ - t_thrd.autovacuum_cxt.recentMulti = ReadNextMultiXactId(); + t_thrd.autovacuum_cxt.recentMulti = ReadNextMultiXactId(); // 读取最新的多事务 ID + // 判断最近的多事务ID是否超过了阈值 if (t_thrd.autovacuum_cxt.recentMulti > FirstMultiXactId + (uint64)g_instance.attr.attr_storage.autovacuum_freeze_max_age) multiForceLimit = t_thrd.autovacuum_cxt.recentMulti - @@ -945,6 +981,7 @@ static Oid do_start_worker(void) * isn't clear how to construct a metric that measures that and not cause * starvation for less busy databases. */ + // 初始化 avdb = NULL; for_xid_wrap = false; for_multi_wrap = false; @@ -954,15 +991,18 @@ static Oid do_start_worker(void) Dlelem* elem = NULL; /* Check to see if this one is need freeze */ + // 检查是否需要冻结当前数据库的事务ID以进行VACUUM if (TransactionIdPrecedes(tmp->adw_frozenxid, xidForceLimit)) { + // 如果当前数据库的冻结事务ID早于限制的事务ID,表示需要进行自动VACUUM,以防止事务ID耗尽 if (avdb == NULL || TransactionIdPrecedes(tmp->adw_frozenxid, avdb->adw_frozenxid)) - avdb = tmp; - for_xid_wrap = true; + avdb = tmp; // 将avdb设置为当前数据库,以选择最早需要自动VACUUM的数据库 + for_xid_wrap = true; // 设置标志变量,表示已经考虑了需要进行事务ID回绕的情况 continue; } else if (for_xid_wrap) continue; /* ignore not-at-risk DBs */ #ifndef ENABLE_MULTIPLE_NODES else if (MultiXactIdPrecedes(tmp->adw_frozenmulti, multiForceLimit)) { + // 在非多节点配置下,检查当前数据库的冻结多事务ID是否早于限制的多事务ID if (avdb == NULL || MultiXactIdPrecedes(tmp->adw_frozenmulti, avdb->adw_frozenmulti)) avdb = tmp; for_multi_wrap = true; @@ -972,7 +1012,7 @@ static Oid do_start_worker(void) #endif /* Find pgstat entry if any */ - tmp->adw_entry = pgstat_fetch_stat_dbentry(tmp->adw_datid); + tmp->adw_entry = pgstat_fetch_stat_dbentry(tmp->adw_datid); // 查找数据库的统计信息(pgstat entry) /* * Skip a database with no pgstat entry; it means it hasn't seen any @@ -994,11 +1034,12 @@ static Oid do_start_worker(void) while (elem != NULL) { avl_dbase* dbp = (avl_dbase*)DLE_VAL(elem); - if (dbp->adl_datid == tmp->adw_datid) { + if (dbp->adl_datid == tmp->adw_datid) { // 检查两个数据库的数据表是否相同 /* * Skip this database if its next_worker value falls between * the current time and the current time plus naptime. */ + // 判断是否跳过当前的数据库 if (!TimestampDifferenceExceeds(dbp->adl_next_worker, current_time, 0) && !TimestampDifferenceExceeds( current_time, dbp->adl_next_worker, u_sess->attr.attr_storage.autovacuum_naptime * 1000)) @@ -1006,7 +1047,7 @@ static Oid do_start_worker(void) break; } - elem = DLGetPred(elem); + elem = DLGetPred(elem);// 获取前一个链表元素 } if (skipit) continue; @@ -1023,38 +1064,44 @@ static Oid do_start_worker(void) if (avdb != NULL) { WorkerInfo worker = NULL; - LWLockAcquire(AutovacuumLock, LW_EXCLUSIVE); + LWLockAcquire(AutovacuumLock, LW_EXCLUSIVE); // 获取独占锁以修改自动清理相关共享内存数据 /* * Get a worker entry from the freelist. We checked above, so there * really should be a free slot -- complain very loudly if there * isn't. */ + // 从空闲工作进程链表中获取一个空闲工作进程条目 worker = t_thrd.autovacuum_cxt.AutoVacuumShmem->av_freeWorkers; if (worker == NULL) ereport(FATAL, (errmsg("no free worker found"))); + // 将获取的工作进程条目从空闲链表中移除 t_thrd.autovacuum_cxt.AutoVacuumShmem->av_freeWorkers = (WorkerInfo)worker->wi_links.next; - worker->wi_dboid = avdb->adw_datid; - worker->wi_proc = NULL; - worker->wi_launchtime = GetCurrentTimestamp(); + // 设置工作进程的相关信息 + worker->wi_dboid = avdb->adw_datid; // 设置工作进程处理的数据库标识 + worker->wi_proc = NULL; // 初始化工作进程的进程信息 + worker->wi_launchtime = GetCurrentTimestamp(); // 记录工作进程启动的时间 + // 将要启动的工作进程设置到共享内存中,以通知Postmaster启动工作进程 t_thrd.autovacuum_cxt.AutoVacuumShmem->av_startingWorker = worker; - LWLockRelease(AutovacuumLock); + LWLockRelease(AutovacuumLock); // 释放独占锁 + // 向Postmaster发送信号,通知启动自动清理工作进程 SendPostmasterSignal(PMSIGNAL_START_AUTOVAC_WORKER); - retval = avdb->adw_datid; + retval = avdb->adw_datid; // 设置返回值为要处理的数据库标识 } else if (skipit) { /* * If we skipped all databases on the list, rebuild it, because it * probably contains a dropped database. */ - rebuild_database_list(InvalidOid); + rebuild_database_list(InvalidOid); // 如果跳过了所有数据库,则重新构建数据库列表 } + // 切换回原始内存上下文并释放临时内存上下文 (void)MemoryContextSwitchTo(oldcxt); MemoryContextDelete(tmpcxt); @@ -1077,28 +1124,30 @@ static void launch_worker(TimestampTz now) Oid dbid; Dlelem* elem = NULL; - dbid = do_start_worker(); + dbid = do_start_worker(); // 选择并启动一个自动清理工作进程 if (OidIsValid(dbid)) { /* * Walk the database list and update the corresponding entry. If the * database is not on the list, we'll recreate the list. */ + // 遍历数据库列表以更新相应的数据库条目 elem = (t_thrd.autovacuum_cxt.DatabaseList == NULL) ? NULL : DLGetHead(t_thrd.autovacuum_cxt.DatabaseList); while (elem != NULL) { avl_dbase* avdb = (avl_dbase*)DLE_VAL(elem); - if (avdb->adl_datid == dbid) { + if (avdb->adl_datid == dbid) { // 查找与当前启动的数据库标识匹配的数据库条目 /* * add autovacuum_naptime seconds to the current time, and use * that as the new "next_worker" field for this database. */ + // 计算下一个工作时间并更新数据库条目的 next_worker 字段 avdb->adl_next_worker = TimestampTzPlusMilliseconds(now, u_sess->attr.attr_storage.autovacuum_naptime * 1000); - DLMoveToFront(elem); + DLMoveToFront(elem); // 将当前元素移动到链表头部 break; } - elem = DLGetSucc(elem); + elem = DLGetSucc(elem); // 获取下一个链表元素 } /* @@ -1108,6 +1157,7 @@ static void launch_worker(TimestampTz now) * pgstat entry, but this is not a problem because we don't want to * schedule workers regularly into those in any case. */ + // 如果数据库不在数据库列表中,重新构建列表 if (elem == NULL) rebuild_database_list(dbid); } @@ -1120,17 +1170,18 @@ static void launch_worker(TimestampTz now) */ void AutoVacWorkerFailed(void) { - t_thrd.autovacuum_cxt.AutoVacuumShmem->av_signal[AutoVacForkFailed] = true; + // 在无法fork出工作进程时,由Postmaster调用向自动清理进程发送信号 + t_thrd.autovacuum_cxt.AutoVacuumShmem->av_signal[AutoVacForkFailed] = true; } /* SIGHUP: set flag to re-read config file at next convenient time */ static void avl_sighup_handler(SIGNAL_ARGS) { int save_errno = errno; - + // 收到SIGHUP信号时,设置标志以在下一个方便的时间重新读取配置文件 t_thrd.autovacuum_cxt.got_SIGHUP = true; if (t_thrd.proc) - SetLatch(&t_thrd.proc->procLatch); + SetLatch(&t_thrd.proc->procLatch); // 设置进程标识以唤醒进程 errno = save_errno; } @@ -1139,10 +1190,10 @@ static void avl_sighup_handler(SIGNAL_ARGS) static void avl_sigusr2_handler(SIGNAL_ARGS) { int save_errno = errno; - + // 收到 SIGUSR2 信号时,设置标志以表示一个工作进程正在运行、刚刚完成,或者在 fork 时失败 t_thrd.autovacuum_cxt.got_SIGUSR2 = true; if (t_thrd.proc) - SetLatch(&t_thrd.proc->procLatch); + SetLatch(&t_thrd.proc->procLatch); // 设置进程标识以唤醒进程 errno = save_errno; } @@ -1151,10 +1202,10 @@ static void avl_sigusr2_handler(SIGNAL_ARGS) static void avl_sigterm_handler(SIGNAL_ARGS) { int save_errno = errno; - + // 收到 SIGTERM 信号时,设置标志以表示进程需要终止 t_thrd.autovacuum_cxt.got_SIGTERM = true; if (t_thrd.proc) - SetLatch(&t_thrd.proc->procLatch); + SetLatch(&t_thrd.proc->procLatch); // 设置进程标识以唤醒进程 errno = save_errno; } @@ -1177,12 +1228,12 @@ void AutovacuumWorkerIAm(void) */ NON_EXEC_STATIC void AutoVacWorkerMain() { - sigjmp_buf local_sigjmp_buf; + sigjmp_buf local_sigjmp_buf; // 设置跳转标志以实现跳出异常 Oid dbid; char user[NAMEDATALEN]; /* we are a postmaster subprocess now */ - IsUnderPostmaster = true; + IsUnderPostmaster = true; // 将当前进程标识为后台进程 t_thrd.role = AUTOVACUUM_WORKER; /* reset t_thrd.proc_cxt.MyProcPid */ @@ -1191,12 +1242,12 @@ NON_EXEC_STATIC void AutoVacWorkerMain() /* record Start Time for logging */ t_thrd.proc_cxt.MyStartTime = time(NULL); - t_thrd.proc_cxt.MyProgName = "AutoVacWorker"; + t_thrd.proc_cxt.MyProgName = "AutoVacWorker"; // 设置进程名 /* Identify myself via ps */ init_ps_display("autovacuum worker process", "", "", ""); - SetProcessingMode(InitProcessing); + SetProcessingMode(InitProcessing); // 设置处理模式为初始化 /* * Set up signal handlers. We operate on databases much like a regular @@ -1210,6 +1261,7 @@ NON_EXEC_STATIC void AutoVacWorkerMain() * SIGINT is used to signal canceling the current table's vacuum; SIGTERM * means abort and exit cleanly, and SIGQUIT means abandon ship. */ + // 设置信号处理函数 (void)gspqsignal(SIGINT, StatementCancelHandler); (void)gspqsignal(SIGTERM, die); (void)gspqsignal(SIGQUIT, quickdie); @@ -1245,8 +1297,8 @@ NON_EXEC_STATIC void AutoVacWorkerMain() */ int curTryCounter; int* oldTryCounter = NULL; - if (sigsetjmp(local_sigjmp_buf, 1) != 0) { - gstrace_tryblock_exit(true, oldTryCounter); + if (sigsetjmp(local_sigjmp_buf, 1) != 0) { // 使用 sigsetjmp 设置一个返回点,以便在出现异常时跳转回到此处 + gstrace_tryblock_exit(true, oldTryCounter); // 退出 gstrace_tryblock 块 /* Prevents interrupts while cleaning up */ HOLD_INTERRUPTS(); @@ -1263,12 +1315,12 @@ NON_EXEC_STATIC void AutoVacWorkerMain() */ proc_exit(0); } - oldTryCounter = gstrace_tryblock_entry(&curTryCounter); + oldTryCounter = gstrace_tryblock_entry(&curTryCounter); // 进入 gstrace_tryblock 块,记录当前尝试次数 /* We can now handle ereport(ERROR) */ t_thrd.log_cxt.PG_exception_stack = &local_sigjmp_buf; - gs_signal_setmask(&t_thrd.libpq_cxt.UnBlockSig, NULL); + gs_signal_setmask(&t_thrd.libpq_cxt.UnBlockSig, NULL); // 解除信号的阻塞状态 (void)gs_signal_unblock_sigusr2(); /* @@ -1276,12 +1328,14 @@ NON_EXEC_STATIC void AutoVacWorkerMain() * in postgresql.conf. We don't really want such a dangerous option being * applied non-interactively. */ + // 在 autovac 进程中强制关闭 zero_damaged_pages SetConfigOption("zero_damaged_pages", "false", PGC_SUSET, PGC_S_OVERRIDE); /* * Force statement_timeout to zero to avoid a timeout setting from * preventing regular maintenance from being executed. */ + // 将 statement_timeout 强制为零,以避免超时限制影响自动清理操作 SetConfigOption("statement_timeout", "0", PGC_SUSET, PGC_S_OVERRIDE); /* @@ -1289,11 +1343,13 @@ NON_EXEC_STATIC void AutoVacWorkerMain() * to pay the overhead of serializable mode, nor add any risk of causing * deadlocks or delaying other transactions. */ + // 强制 default_transaction_isolation 为 READ COMMITTED,以避免不必要的事务隔离级别 SetConfigOption("default_transaction_isolation", "read committed", PGC_SUSET, PGC_S_OVERRIDE); /* * Get the info about the database we're going to work on. */ + // 获取要处理的数据库的信息 LWLockAcquire(AutovacuumLock, LW_EXCLUSIVE); /* @@ -1302,33 +1358,35 @@ NON_EXEC_STATIC void AutoVacWorkerMain() * launcher might have decided to remove it from the queue and start * again. */ + // 检查是否存在正在启动的工作进程 if (t_thrd.autovacuum_cxt.AutoVacuumShmem->av_startingWorker != NULL) { t_thrd.autovacuum_cxt.MyWorkerInfo = t_thrd.autovacuum_cxt.AutoVacuumShmem->av_startingWorker; - dbid = t_thrd.autovacuum_cxt.MyWorkerInfo->wi_dboid; - t_thrd.autovacuum_cxt.MyWorkerInfo->wi_proc = t_thrd.proc; + dbid = t_thrd.autovacuum_cxt.MyWorkerInfo->wi_dboid; // 获取当前工作进程所处理的数据库标识 + t_thrd.autovacuum_cxt.MyWorkerInfo->wi_proc = t_thrd.proc; // 设置当前工作进程的 PGPROC 结构指针 /* insert into the running list */ - SHMQueueInsertBefore( + SHMQueueInsertBefore( // 插入运行列表 &t_thrd.autovacuum_cxt.AutoVacuumShmem->av_runningWorkers, &t_thrd.autovacuum_cxt.MyWorkerInfo->wi_links); /* * remove from the "starting" pointer, so that the launcher can start * a new worker if required */ + // 移除指针,以便启动器可以在需要时启动新的工作程序 t_thrd.autovacuum_cxt.AutoVacuumShmem->av_startingWorker = NULL; - LWLockRelease(AutovacuumLock); + LWLockRelease(AutovacuumLock); // 释放自动清理锁 - on_shmem_exit(FreeWorkerInfo, 0); - on_shmem_exit(PGXCNodeCleanAndRelease, 0); + on_shmem_exit(FreeWorkerInfo, 0); // 在退出时调用 FreeWorkerInfo 函数 + on_shmem_exit(PGXCNodeCleanAndRelease, 0); // 在退出时调用 PGXCNodeCleanAndRelease 函数 /* wake up the launcher */ - if (t_thrd.autovacuum_cxt.AutoVacuumShmem->av_launcherpid != 0) + if (t_thrd.autovacuum_cxt.AutoVacuumShmem->av_launcherpid != 0) // 如果设置了 launcher 进程 ID,则发送 SIGUSR2 信号唤醒 launcher 进程 gs_signal_send(t_thrd.autovacuum_cxt.AutoVacuumShmem->av_launcherpid, SIGUSR2); } else { /* no worker entry for me, go away */ ereport(WARNING, (errmsg("autovacuum worker started without a worker entry"))); - dbid = InvalidOid; - LWLockRelease(AutovacuumLock); + dbid = InvalidOid; // 设置数据库标识为无效值 + LWLockRelease(AutovacuumLock); // 释放自动清理锁 } if (OidIsValid(dbid)) { @@ -1343,6 +1401,7 @@ NON_EXEC_STATIC void AutoVacWorkerMain() * database, rather than making any progress on stuff it can connect * to. */ + // 在自动清理进程启动时报告信息给统计信息收集器 pgstat_report_autovac(dbid); AUTOVAC_LOG(LOG, "report autovac startup on database %u to stats collector", dbid); @@ -1352,14 +1411,16 @@ NON_EXEC_STATIC void AutoVacWorkerMain() * Note: if we have selected a just-deleted database (due to using * stale stats info), we'll fail and exit here. */ + // 连接到所选数据库 t_thrd.proc_cxt.PostInit->SetDatabaseAndUser(NULL, dbid, NULL); t_thrd.proc_cxt.PostInit->InitAutoVacWorker(); t_thrd.proc_cxt.PostInit->GetDatabaseName(dbname); - SetProcessingMode(NormalProcessing); - set_ps_display(dbname, false); - ereport(LOG, (errmsg("start autovacuum on database \"%s\"", dbname))); + SetProcessingMode(NormalProcessing); // 设置处理模式为正常处理 + set_ps_display(dbname, false); // 设置进程状态显示为数据库名 + ereport(LOG, (errmsg("start autovacuum on database \"%s\"", dbname))); // 记录日志,开始对数据库执行自动清理操作 + // 如果配置了 PostAuthDelay,则在启动后暂停一段时间 if (u_sess->attr.attr_security.PostAuthDelay) pg_usleep(u_sess->attr.attr_security.PostAuthDelay * 1000000L); @@ -1369,6 +1430,7 @@ NON_EXEC_STATIC void AutoVacWorkerMain() * t_thrd.mem_cxt.msg_mem_cxt is reset once per iteration of the main loop, ie, upon * completion of processing of each command message from the client. */ + // 在主循环中创建内存上下文 t_thrd.mem_cxt.msg_mem_cxt = AllocSetContextCreate(t_thrd.top_mem_cxt, "MessageContext", ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); @@ -1379,25 +1441,40 @@ NON_EXEC_STATIC void AutoVacWorkerMain() * Create a resource owner to keep track of our resources (currently only * buffer pins). */ + // 在自动清理进程中创建一个资源所有者,用于跟踪分配的资源 t_thrd.utils_cxt.CurrentResourceOwner = ResourceOwnerCreate(NULL, "AutoVacuumWorker", THREAD_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_STORAGE)); + /* + * 保存当前内存上下文,并切换到 SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_STORAGE)。 + * 这是为了在执行后续操作时使用合适的内存上下文。 + */ oldcontext = MemoryContextSwitchTo(SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_STORAGE)); + /* + * 如果当前进程的 MyProcPort 数据成员中保存了数据库名的内存则释放。为了避免内存泄漏。 + */ if (u_sess->proc_cxt.MyProcPort->database_name) pfree_ext(u_sess->proc_cxt.MyProcPort->database_name); if (u_sess->proc_cxt.MyProcPort->user_name) pfree_ext(u_sess->proc_cxt.MyProcPort->user_name); - u_sess->proc_cxt.MyProcPort->database_name = pstrdup(dbname); - u_sess->proc_cxt.MyProcPort->user_name = (char*)GetSuperUserName((char*)user); + u_sess->proc_cxt.MyProcPort->database_name = pstrdup(dbname); // 将数据库名复制到 MyProcPort 数据成员 + u_sess->proc_cxt.MyProcPort->user_name = (char*)GetSuperUserName((char*)user); // 将超级用户的用户名复制到 MyProcPort 数据成员中 + /* + * 切换回之前保存的内存上下文,以便继续在原来的上下文中进行后续操作。 + */ (void)MemoryContextSwitchTo(oldcontext); /* Get classified list of node Oids for do analyze in coordinator. */ + /* + * 初始化连接池句柄,准备进行分析操作。 + * 这可能是在协调节点上进行的一些操作,与分析相关。 + */ exec_init_poolhandles(); /* And do an appropriate amount of work */ - t_thrd.autovacuum_cxt.recentXid = ReadNewTransactionId(); - t_thrd.autovacuum_cxt.recentMulti = ReadNextMultiXactId(); - do_autovacuum(); + t_thrd.autovacuum_cxt.recentXid = ReadNewTransactionId(); // 读取最新的事务 ID,并将其存储在自动清理上下文中 + t_thrd.autovacuum_cxt.recentMulti = ReadNextMultiXactId(); // 读取最新的多事务 ID,并将其存储在自动清理上下文中 + do_autovacuum(); // 根据前面的设置和读取的事务 ID,进行相应的自动清理工作 } /* @@ -1413,10 +1490,10 @@ NON_EXEC_STATIC void AutoVacWorkerMain() */ static void FreeWorkerInfo(int code, Datum arg) { - if (t_thrd.autovacuum_cxt.MyWorkerInfo != NULL) { + if (t_thrd.autovacuum_cxt.MyWorkerInfo != NULL) { // 如果当前工作进程信息存在 LWLockAcquire(AutovacuumLock, LW_EXCLUSIVE); /* double check */ - if (t_thrd.autovacuum_cxt.MyWorkerInfo == NULL) { + if (t_thrd.autovacuum_cxt.MyWorkerInfo == NULL) { // 再次检查以确保没有其他进程修改了工作进程信息 LWLockRelease(AutovacuumLock); return; } @@ -1434,7 +1511,8 @@ static void FreeWorkerInfo(int code, Datum arg) */ t_thrd.autovacuum_cxt.AutovacuumLauncherPid = t_thrd.autovacuum_cxt.AutoVacuumShmem->av_launcherpid; - SHMQueueDelete(&t_thrd.autovacuum_cxt.MyWorkerInfo->wi_links); + SHMQueueDelete(&t_thrd.autovacuum_cxt.MyWorkerInfo->wi_links); // 从运行工作进程队列中删除当前工作进程信息 + // 重置工作进程信息中的各个字段 t_thrd.autovacuum_cxt.MyWorkerInfo->wi_links.next = (SHM_QUEUE*)t_thrd.autovacuum_cxt.AutoVacuumShmem->av_freeWorkers; t_thrd.autovacuum_cxt.MyWorkerInfo->wi_dboid = InvalidOid; @@ -1446,14 +1524,16 @@ static void FreeWorkerInfo(int code, Datum arg) t_thrd.autovacuum_cxt.MyWorkerInfo->wi_cost_delay = 0; t_thrd.autovacuum_cxt.MyWorkerInfo->wi_cost_limit = 0; t_thrd.autovacuum_cxt.MyWorkerInfo->wi_cost_limit_base = 0; + // 将当前工作进程信息添加到空闲工作进程队列中 t_thrd.autovacuum_cxt.AutoVacuumShmem->av_freeWorkers = t_thrd.autovacuum_cxt.MyWorkerInfo; /* not mine anymore */ - t_thrd.autovacuum_cxt.MyWorkerInfo = NULL; + t_thrd.autovacuum_cxt.MyWorkerInfo = NULL; // 解除标记,表示不再是当前工作进程信息 /* * now that we're inactive, cause a rebalancing of the surviving * workers */ + // 现在不再是活跃工作进程,通知 launcher 进程进行重平衡 t_thrd.autovacuum_cxt.AutoVacuumShmem->av_signal[AutoVacRebalance] = true; LWLockRelease(AutovacuumLock); } @@ -1465,7 +1545,7 @@ static void FreeWorkerInfo(int code, Datum arg) */ void AutoVacuumUpdateDelay(void) { - if (t_thrd.autovacuum_cxt.MyWorkerInfo) { + if (t_thrd.autovacuum_cxt.MyWorkerInfo) { // 更新成本延迟参数 u_sess->attr.attr_storage.VacuumCostDelay = t_thrd.autovacuum_cxt.MyWorkerInfo->wi_cost_delay; u_sess->attr.attr_storage.VacuumCostLimit = t_thrd.autovacuum_cxt.MyWorkerInfo->wi_cost_limit; } @@ -1487,6 +1567,7 @@ static void autovac_balance_cost(void) * note: in cost_limit, zero also means use value from elsewhere, because * zero is not a valid value. */ + // 确定各个工作进程的成本限制,以平均分配 I/O 资源 int vac_cost_limit = (u_sess->attr.attr_storage.autovacuum_vac_cost_limit > 0 ? u_sess->attr.attr_storage.autovacuum_vac_cost_limit : u_sess->attr.attr_storage.VacuumCostLimit); @@ -1498,18 +1579,22 @@ static void autovac_balance_cost(void) WorkerInfo worker = NULL; /* not set? nothing to do */ - if (vac_cost_limit <= 0 || vac_cost_delay <= 0) { + if (vac_cost_limit <= 0 || vac_cost_delay <= 0) { // 若未设置成本限制,无需操作 return; } /* caculate the total base cost limit of active workers */ + // 计算当前所有活跃工作进程的基准成本限制总和 cost_total = 0.0; + // 初始化指针,从活跃工作进程列表的开头开始遍历 worker = (WorkerInfo)SHMQueueNext(&t_thrd.autovacuum_cxt.AutoVacuumShmem->av_runningWorkers, &t_thrd.autovacuum_cxt.AutoVacuumShmem->av_runningWorkers, offsetof(WorkerInfoData, wi_links)); - while (worker != NULL) { + while (worker != NULL) { // 遍历活跃工作进程列表 + // 检查当前工作进程是否有效以及成本参数是否合法 if (worker->wi_proc != NULL && worker->wi_cost_limit_base > 0 && worker->wi_cost_delay > 0) - cost_total += (double)worker->wi_cost_limit_base / worker->wi_cost_delay; + cost_total += (double)worker->wi_cost_limit_base / worker->wi_cost_delay; // 计算当前工作进程的成本,将其加到总成本中 + // 获取下一个活跃工作进程,继续遍历 worker = (WorkerInfo)SHMQueueNext(&t_thrd.autovacuum_cxt.AutoVacuumShmem->av_runningWorkers, &worker->wi_links, offsetof(WorkerInfoData, wi_links)); } @@ -1522,6 +1607,7 @@ static void autovac_balance_cost(void) * Adjust cost limit of each active worker to balance the total of cost * limit to autovacuum_vacuum_cost_limit. */ + // 调整各个工作进程的成本限制,平衡总成本限制 cost_avail = (double)vac_cost_limit / vac_cost_delay; worker = (WorkerInfo)SHMQueueNext(&t_thrd.autovacuum_cxt.AutoVacuumShmem->av_runningWorkers, &t_thrd.autovacuum_cxt.AutoVacuumShmem->av_runningWorkers, offsetof(WorkerInfoData, wi_links)); @@ -1535,8 +1621,8 @@ static void autovac_balance_cost(void) * in these calculations, let's be sure we don't ever set * cost_limit to more than the base value. */ - worker->wi_cost_limit = Max(Min(limit, worker->wi_cost_limit_base), 1); - + worker->wi_cost_limit = Max(Min(limit, worker->wi_cost_limit_base), 1); // 确保成本限制不低于 1,也不高于基准值 + // 记录日志以便调试 AUTOVAC_LOG(LOG, "autovac_balance_cost(pid=%lu db=%u, rel=%u, parent=%u, cost_limit=%d, " "cost_limit_base=%d, cost_delay=%d)", worker->wi_proc->pid, worker->wi_dboid, worker->wi_tableoid, worker->wi_parentoid, @@ -1570,7 +1656,7 @@ static List* get_database_list(void) MemoryContext resultcxt; /* This is the context that we will allocate our output data in */ - resultcxt = CurrentMemoryContext; + resultcxt = CurrentMemoryContext;- // 获取当前内存上下文 /* * Start a transaction so we can access pg_database, and get a snapshot. @@ -1579,15 +1665,17 @@ static List* get_database_list(void) * for anything that reads heap pages, because HOT may decide to prune * them even if the process doesn't attempt to modify any tuples.) */ - StartTransactionCommand(); - (void)GetTransactionSnapshot(); + StartTransactionCommand(); // 开始一个事务 + (void)GetTransactionSnapshot(); // 获取事务快照 - rel = heap_open(DatabaseRelationId, AccessShareLock); + // 打开 pg_database 表,并开始扫描 + rel = heap_open(DatabaseRelationId, AccessShareLock); scan = tableam_scan_begin(rel, SnapshotNow, 0, NULL); + // 遍历 pg_database 表中的每个记录 while (HeapTupleIsValid(tup = (HeapTuple) tableam_scan_getnexttuple(scan, ForwardScanDirection))) { - Form_pg_database pgdatabase = (Form_pg_database)GETSTRUCT(tup); - avw_dbase* avdb = NULL; + Form_pg_database pgdatabase = (Form_pg_database)GETSTRUCT(tup); // 获取数据库记录的结构体 + avw_dbase* avdb = NULL; // 分配内存来存储 avw_dbase 结构体 MemoryContext oldcxt; /* @@ -1596,13 +1684,15 @@ static List* get_database_list(void) * context at the end, so that leaky things like heap_getnext() are * not called in a potentially long-lived context. */ - oldcxt = MemoryContextSwitchTo(resultcxt); + oldcxt = MemoryContextSwitchTo(resultcxt); // 切换到结果内存上下文 - avdb = (avw_dbase*)palloc(sizeof(avw_dbase)); + avdb = (avw_dbase*)palloc(sizeof(avw_dbase)); // 分配 avw_dbase 结构体的内存 + // 将数据库的 OID 和名称保存到 avw_dbase 结构体 avdb->adw_datid = HeapTupleGetOid(tup); avdb->adw_name = pstrdup(NameStr(pgdatabase->datname)); + // 获取数据库的冻结事务 ID bool isNull = false; TransactionId datfrozenxid; Datum xid64datum = heap_getattr(tup, Anum_pg_database_datfrozenxid64, RelationGetDescr(rel), &isNull); @@ -1615,34 +1705,41 @@ static List* get_database_list(void) } else datfrozenxid = DatumGetTransactionId(xid64datum); #ifndef ENABLE_MULTIPLE_NODES + // 获取数据库的最小多事务 ID Datum mxidDatum = heap_getattr(tup, Anum_pg_database_datminmxid, RelationGetDescr(rel), &isNull); MultiXactId datminmxid = isNull ? FirstMultiXactId : DatumGetTransactionId(mxidDatum); avdb->adw_frozenmulti = datminmxid; #endif + // 将冻结事务 ID 和相关信息保存到 avw_dbase 结构体 avdb->adw_frozenxid = datfrozenxid; /* this gets set later: */ avdb->adw_entry = NULL; + // 将 avw_dbase 结构体添加到数据库列表中 dblist = lappend(dblist, avdb); - (void)MemoryContextSwitchTo(oldcxt); + (void)MemoryContextSwitchTo(oldcxt);// 切换回原来的内存上下文 } - tableam_scan_end(scan); + // 结束扫描并关闭 pg_database 表 + tableam_scan_end(scan); heap_close(rel, AccessShareLock); - CommitTransactionCommand(); + CommitTransactionCommand(); // 提交事务 - return dblist; + return dblist; // 返回构建的数据库列表 } +// 宏定义 /* * 1. just support global statistic since local statistic is gradually abandoned * 2. just support traditional-sample analyze since percent-sample analyze is too inefficient */ +// 判断是否执行分析操作 #define DO_ANALYZE \ ((AUTOVACUUM_DO_ANALYZE_VACUUM == u_sess->attr.attr_storage.autovacuum_mode || \ AUTOVACUUM_DO_ANALYZE == u_sess->attr.attr_storage.autovacuum_mode) && \ u_sess->attr.attr_sql.enable_global_stats && 0 < default_statistics_target) +// 判断是否执行清理操作 #define DO_VACUUM \ (AUTOVACUUM_DO_ANALYZE_VACUUM == u_sess->attr.attr_storage.autovacuum_mode || \ AUTOVACUUM_DO_VACUUM == u_sess->attr.attr_storage.autovacuum_mode) @@ -1650,19 +1747,21 @@ static List* get_database_list(void) /* * check if the relation can do auto-analyze or auto-vacuum */ +// 判断一个关系是否支持自动分析(ANALYZE)和自动清理(VACUUM),以及是否为内部关系 void relation_support_autoavac(HeapTuple tuple, bool* enable_analyze, bool* enable_vacuum, bool* is_internal_relation) { - bytea* relopts = NULL; - Form_pg_class classForm = (Form_pg_class)GETSTRUCT(tuple); + bytea* relopts = NULL; // 存储关系的选项 + Form_pg_class classForm = (Form_pg_class)GETSTRUCT(tuple); // 关系的元组数据结构 Assert(PointerIsValid(enable_analyze)); Assert(PointerIsValid(enable_vacuum)); - *enable_analyze = false; - *enable_vacuum = false; - *is_internal_relation = false; + *enable_analyze = false; // 默认禁用 ANALYZE 操作 + *enable_vacuum = false; // 默认禁用 VACUUM 操作 + *is_internal_relation = false; // 默认不是内部关系 /* skip all autovac actions */ + // 如果不在协调器上或者自动清理守护进程被禁用,则跳过所有自动清理操作 if (IS_PGXC_COORDINATOR && !u_sess->attr.attr_storage.autovacuum_start_daemon) return; @@ -1670,27 +1769,31 @@ void relation_support_autoavac(HeapTuple tuple, bool* enable_analyze, bool* enab * 1. never analyze internal table since we never select them directly * 2. just analyze row/colume orientation table since analyze hdfs table is too inefficient */ + // 根据关系选项和类型判断是否启用 ANALYZE 和 VACUUM 操作 relopts = extractRelOptions(tuple, GetDefaultPgClassDesc(), InvalidOid); if (StdRelOptGetInternalMask(relopts)) { /* do nothing, but set is_internal_relation to be true */ - *is_internal_relation = true; + *is_internal_relation = true; //对于内部表,将 is_internal_relation 设置为 true /* if it is mlog or matmap then set it */ + // 如果是行/列定向表且是 mlog 或 matmap,则启用 ANALYZE 和 VACUUM 操作 if (StdRelOptIsRowStore(relopts) && (ISMATMAP(classForm->relname.data) || ISMLOG(classForm->relname.data))) { *enable_analyze = true; *enable_vacuum = true; } - } else if (StdRelOptIsColStore(relopts)) { + } else if (StdRelOptIsColStore(relopts)) { // 列定向表 *enable_analyze = true; - } else if (StdRelOptIsRowStore(relopts)) { + } else if (StdRelOptIsRowStore(relopts)) { //行定向表 *enable_analyze = true; *enable_vacuum = true; } + // 对于统计信息表、TOAST 表,或当 DO_ANALYZE 为 false 时,禁用 ANALYZE 操作 if (StatisticRelationId == HeapTupleGetOid(tuple) || RELKIND_TOASTVALUE == classForm->relkind || !DO_ANALYZE) { *enable_analyze = false; } + // 当 DO_VACUUM 为 false 时,禁用 VACUUM 操作 if (!DO_VACUUM) { *enable_vacuum = false; } @@ -1702,60 +1805,74 @@ void relation_support_autoavac(HeapTuple tuple, bool* enable_analyze, bool* enab * 1. data in temp/unlogged is short-lived, so do nothing for temp/unlogged table * 2. foreign table dose not have stat info, so just support ordinary table */ + // 判断关系是否为不支持的类型,若是则禁用相关操作 if (RELPERSISTENCE_PERMANENT != classForm->relpersistence || RELKIND_RELATION != classForm->relkind) { *enable_analyze = false; *is_internal_relation = false; } + // 对于非普通表,禁用 VACUUM 操作 if (RELKIND_RELATION != classForm->relkind) { *enable_vacuum = false; } } +// 判断是否允许自动执行 ANALYZE 操作 bool allow_autoanalyze(HeapTuple tuple) { bool enable_analyze = false; bool enable_vacuum = false; bool is_internal_relation = false; + // 调用函数判断是否支持自动执行 ANALYZE 操作 relation_support_autoavac(tuple, &enable_analyze, &enable_vacuum, &is_internal_relation); return enable_analyze; } +// 将应用程序名称添加到连接池参数中 static void AddApplicationNameToPoolerParams() { if (IS_PGXC_COORDINATOR && !IsConnFromCoord() && IsAutoVacuumWorkerProcess()) { StringInfoData str; + // 初始化一个用于构建字符串的数据结构 initStringInfo(&str); + // 向字符串中添加设置应用程序名称的 SQL 语句 appendStringInfo(&str, "SET application_name = '%s';", AUTO_VACUUM_WORKER); + // 将应用程序名称添加到连接池会话参数中 (void)register_pooler_session_param("application_name", str.data, POOL_CMD_GLOBAL_SET); - pfree_ext(str.data); + pfree_ext(str.data); // 释放字符串数据结构所占用的内存 } } +// 从连接池参数中删除应用程序名称 static void DeleteApplicationNameFromPoolerParams() { if (IS_PGXC_COORDINATOR && !IsConnFromCoord() && IsAutoVacuumWorkerProcess()) { + // 从连接池会话参数中删除应用程序名称 delete_pooler_session_params("application_name"); } } +// 从所有数据节点获取全局自动VACUUM信息 static void fetch_global_autovac_info() { - PgStat_StatTabKey tablekey; - bool connected = false; - StringInfoData buf; + PgStat_StatTabKey tablekey; // 用于统计信息表的键 + bool connected = false; // 表示是否已连接到SPI + StringInfoData buf; // 用于构建SQL查询语句的字符串缓冲区 /* * Dose not fetch global stat info from all datanodes if * 1. autovacuum = off * 2. autovacuum = on and u_sess->attr.attr_storage.autovacuum_mode = none */ + //如果autovacuum为关闭状态,或者autovacuum为打开但u_sess->attr.attr_storage.autovacuum_mode为none模式 if (!u_sess->attr.attr_storage.autovacuum_start_daemon || AUTOVACUUM_DO_NONE == u_sess->attr.attr_storage.autovacuum_mode) - return; + return; // 不从所有数据节点获取全局统计信息 - initStringInfo(&buf); + initStringInfo(&buf); // 初始化字符串缓冲区 + // 根据不同情况构建SQL查询语句 if (DO_VACUUM) { + // 构建执行VACUUM操作的SQL语句 appendStringInfo(&buf, "with f as (select nspname, relname, partname, sum(n_dead_tuples) as n_dead_tuples, " "sum(n_live_tuples) as n_live_tuples, sum(changes_since_analyze) as changes_since_analyze, " "count(1) as count from pg_total_autovac_tuples(%s) group by nspname, relname, partname), " @@ -1773,6 +1890,7 @@ static void fetch_global_autovac_info() "from t inner join f on (t.nspname = f.nspname and t.relname = f.relname " "and (t.partname = f.partname or (t.partname is null and f.partname is null))) ", "false"); } else { + // 构建执行ANALYZE操作的SQL语句 appendStringInfo(&buf, "with f as (select nspname, relname, sum(n_dead_tuples) as n_dead_tuples, " "sum(changes_since_analyze) as changes_since_analyze, count(1) as count " "from pg_total_autovac_tuples(%s) group by nspname, relname), t as(SELECT c.oid as relid, " @@ -1786,29 +1904,36 @@ static void fetch_global_autovac_info() "end)::bigint as changes_since_analyze " "from t inner join f on (t.nspname = f.nspname and t.relname = f.relname) ", "true"); } - + // 打印构建的SQL语句,用于调试 AUTOVAC_LOG(DEBUG2, "FETCH GLOABLE AUTOVAC INFO STRING: %s", buf.data); + // 开启快照,以便执行查询 PushActiveSnapshot(GetTransactionSnapshot()); + // 将应用程序名称添加到连接池参数中 AddApplicationNameToPoolerParams(); + // 异常处理,执行查询操作 PG_TRY(); { DEBUG_MOD_START_TIMER(MOD_AUTOVAC); + // 尝试连接到SPI SPI_STACK_LOG("connect", NULL, NULL); if (SPI_OK_CONNECT != SPI_connect()) { ereport(ERROR, (errcode(ERRCODE_OPERATE_FAILED), errmsg("Unable to connect to execute internal query."))); } connected = true; DEBUG_MOD_STOP_TIMER(MOD_AUTOVAC, "AUTOVAC TIMER: build SPI connect"); - + // 执行SQL查询语句 if (SPI_OK_SELECT != SPI_execute(buf.data, true, 0)) { ereport(ERROR, (errcode(ERRCODE_DATA_EXCEPTION), errmsg("fail to execute query"))); } DEBUG_MOD_STOP_TIMER(MOD_AUTOVAC, "AUTOVAC TIMER: execte SQL to fetch global autovac info"); + // 释放字符串缓冲区的内存 pfree_ext(buf.data); + // 处理查询结果,将结果存储到自动VACUUM信息哈希表中 for (uint32 i = 0; i < SPI_processed; i++) { + // 从查询结果中提取信息 Oid relid; Oid partid; int64 n_dead_tuples = 0; @@ -1818,13 +1943,15 @@ static void fetch_global_autovac_info() bool partid_isnull = true; avw_info* entry = NULL; - relid = DatumGetObjectId(SPI_getbinval(SPI_tuptable->vals[i], SPI_tuptable->tupdesc, 1, &isnull)); - partid = DatumGetObjectId(SPI_getbinval(SPI_tuptable->vals[i], SPI_tuptable->tupdesc, 2, &partid_isnull)); - n_dead_tuples = DatumGetInt64(SPI_getbinval(SPI_tuptable->vals[i], SPI_tuptable->tupdesc, 3, &isnull)); - n_live_tuples = DatumGetInt64(SPI_getbinval(SPI_tuptable->vals[i], SPI_tuptable->tupdesc, 4, &isnull)); + // 提取信息 + relid = DatumGetObjectId(SPI_getbinval(SPI_tuptable->vals[i], SPI_tuptable->tupdesc, 1, &isnull)); // 关系的唯一标识符 + partid = DatumGetObjectId(SPI_getbinval(SPI_tuptable->vals[i], SPI_tuptable->tupdesc, 2, &partid_isnull)); // 分区的唯一标识符 + n_dead_tuples = DatumGetInt64(SPI_getbinval(SPI_tuptable->vals[i], SPI_tuptable->tupdesc, 3, &isnull)); // 自上次分析以来死元组的数量 + n_live_tuples = DatumGetInt64(SPI_getbinval(SPI_tuptable->vals[i], SPI_tuptable->tupdesc, 4, &isnull)); // 活元组的数量 changes_since_analyze = - DatumGetInt64(SPI_getbinval(SPI_tuptable->vals[i], SPI_tuptable->tupdesc, 5, &isnull)); + DatumGetInt64(SPI_getbinval(SPI_tuptable->vals[i], SPI_tuptable->tupdesc, 5, &isnull)); // 自上次分析以来的变化次数 + // 构建自动VACUUM信息的键 if (partid_isnull) { tablekey.tableid = relid; tablekey.statFlag = InvalidOid; @@ -1832,37 +1959,51 @@ static void fetch_global_autovac_info() tablekey.tableid = partid; tablekey.statFlag = relid; } - + // 在自动化VACUUM统计信息哈希表中查找或插入一个条目,key是tablekey + // 返回的entry是找到的现有条目或新插入的条目 entry = (avw_info*)hash_search(t_thrd.autovacuum_cxt.pgStatAutoVacInfo, (void*)(&tablekey), HASH_ENTER, NULL); + // 将提取的信息填充到entry中 entry->n_dead_tuples = n_dead_tuples; entry->n_live_tuples = n_live_tuples; entry->changes_since_analyze = changes_since_analyze; } + // 停止AUTOVAC计时器并记录处理的SPI元组数量 DEBUG_MOD_STOP_TIMER(MOD_AUTOVAC, "AUTOVAC TIMER: process %u SPI tuples", SPI_processed); + // 断开SPI连接,将connected标记为false connected = false; + // 记录SPI操作的日志,表示操作已完成 SPI_STACK_LOG("finish", NULL, NULL); + // 完成SPI操作后,关闭SPI连接并检查是否成功关闭 if (SPI_OK_FINISH != SPI_finish()) { ereport(ERROR, (errcode(ERRCODE_OPERATE_FAILED), errmsg("SPI_finish failed"))); } + // 弹出快照,恢复事务的快照状态 PopActiveSnapshot(); + // 清空会话的调试查询ID u_sess->debug_query_id = 0; } PG_CATCH(); /* Clean up in case of error. */ { - DeleteApplicationNameFromPoolerParams(); + DeleteApplicationNameFromPoolerParams(); // 发生异常时,清除连接池中的应用程序名称参数 + // 如果之前建立了SPI连接,即connected为true,则完成SPI连接并记录操作日志 if (connected) { SPI_STACK_LOG("finish", NULL, NULL); SPI_finish(); } /* Carry on with error handling. */ + // 弹出之前快照,恢复事务的快照状态 PopActiveSnapshot(); + // 重新抛出捕获的异常,将其传递给上层的异常处理 PG_RE_THROW(); + // 清空会话的调试查询ID u_sess->debug_query_id = 0; } + // 结束异常捕获的代码块,确保异常处理逻辑的完成 PG_END_TRY(); + // 无论是否发生异常,最终都要删除连接池中的应用程序名称参数 DeleteApplicationNameFromPoolerParams(); } @@ -1874,33 +2015,33 @@ static void fetch_global_autovac_info() */ static void do_autovacuum(void) { - Relation classRel = NULL; - HeapTuple tuple = NULL; - TableScanDesc relScan = NULL; - Form_pg_database dbForm = NULL; - List* table_oids = NIL; - HASHCTL partitioned_tables_ctl; - HTAB* partitioned_tables_map = NULL; - HASHCTL table_relopt_ctl; - HTAB* table_relopt_map = NULL; - HASHCTL toast_table_ctl; - HTAB* toast_table_map = NULL; - ListCell* volatile cell = NULL; - PgStat_StatDBEntry* shared = NULL; - PgStat_StatDBEntry* dbentry = NULL; - BufferAccessStrategy bstrategy; - HASHCTL avinfo_ctl; - bool datallowconn = true; - bool local_autovacuum = true; /* just do autovacuum in current instance */ - bool freeze_autovacuum = false; /* just do vacuum since need freeze the old tuple */ - bool is_internal_relation = false; /* whether current relation is an internal relation */ - ScanKeyData key[1]; - TableScanDesc partScan; - Relation partRel; - HeapTuple partTuple; - TupleDesc pg_class_desc; - vacuum_object* vacObj = NULL; - errno_t rc = EOK; + Relation classRel = NULL; // pg_class 系统目录的 Relation + HeapTuple tuple = NULL; // 从 pg_class 中获取的元组 + TableScanDesc relScan = NULL; // 表扫描描述符 + Form_pg_database dbForm = NULL; // 从 pg_database 系统目录获取的数据库信息 + List* table_oids = NIL; // 数据库中表的 OID 列表 + HASHCTL partitioned_tables_ctl; // 用于分区表的哈希表控制结构 + HTAB* partitioned_tables_map = NULL; // 用于跟踪分区表的哈希表 + HASHCTL table_relopt_ctl; // 用于存储关系选项的哈希表控制结构 + HTAB* table_relopt_map = NULL; // 用于存储关系选项的哈希表 + HASHCTL toast_table_ctl; // 用于 TOAST 表的哈希表控制结构 + HTAB* toast_table_map = NULL; // 用于跟踪 TOAST 表的哈希表 + ListCell* volatile cell = NULL; // 用于遍历表 OID 列表的单元 + PgStat_StatDBEntry* shared = NULL; // 共享数据库统计信息条目 + PgStat_StatDBEntry* dbentry = NULL; // 数据库统计信息条目 + BufferAccessStrategy bstrategy; // 用于堆扫描的缓冲区访问策略 + HASHCTL avinfo_ctl; // 用于跟踪清理对象的哈希表控制结构 + bool datallowconn = true; // 表示是否允许连接到数据库 + bool local_autovacuum = true; // 表示是否仅在当前实例执行自动清理 + bool freeze_autovacuum = false; // 表示是否仅执行带冻结操作的清理 + bool is_internal_relation = false; // 表示当前关系是否为内部系统关系 + ScanKeyData key[1]; // 表扫描的扫描键 + TableScanDesc partScan; // 用于分区表的表扫描描述符 + Relation partRel; // 分区表的关系 + HeapTuple partTuple; // 从分区表扫描中获取的元组 + TupleDesc pg_class_desc; // pg_class 系统目录的元组描述 + vacuum_object* vacObj = NULL; // 用于跟踪清理信息的结构体 + errno_t rc = EOK; // 用于安全 C 库函数的错误代码 /* * StartTransactionCommand and CommitTransactionCommand will automatically @@ -1933,21 +2074,29 @@ static void do_autovacuum(void) * Find the pg_database entry and select the default freeze ages. We use * zero in template and nonconnectable databases, else the system-wide * default. - */ + */。 + // 在系统缓存中搜索指定数据库的元组 tuple = SearchSysCache1(DATABASEOID, ObjectIdGetDatum(u_sess->proc_cxt.MyDatabaseId)); + // 如果找不到有效的元组,报告错误 if (!HeapTupleIsValid(tuple)) ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for database %u", u_sess->proc_cxt.MyDatabaseId))); + // 获取 pg_database 元组中的数据结构 dbForm = (Form_pg_database)GETSTRUCT(tuple); + // 获取数据库属性 datallowconn(是否允许连接) datallowconn = dbForm->datallowconn; + // 根据数据库是否是模板数据库或不可连接的数据库来设置默认的冻结年龄 if (dbForm->datistemplate || !dbForm->datallowconn) { + // 对于模板数据库或不可连接的数据库,将默认冻结年龄设置为零 t_thrd.autovacuum_cxt.default_freeze_min_age = 0; t_thrd.autovacuum_cxt.default_freeze_table_age = 0; } else { + // 否则,使用系统配置的默认值作为默认冻结年龄 t_thrd.autovacuum_cxt.default_freeze_min_age = u_sess->attr.attr_storage.vacuum_freeze_min_age; t_thrd.autovacuum_cxt.default_freeze_table_age = u_sess->attr.attr_storage.vacuum_freeze_table_age; } + // 释放查询到的 pg_database 元组 ReleaseSysCache(tuple); #ifdef PGXC @@ -1957,6 +2106,12 @@ static void do_autovacuum(void) return; } + /* + 根据数据库的属性和环境,决定了自动`VACUUM`过程的执行策略: + - 如果数据库不允许连接或者是单节点模式,则自动VACUUM的本地模式开启,冻结操作的自动VACUUM关闭 + - 如果是分布式协调器且是中央协调器,则本地模式的自动VACUUM关闭,冻结操作的自动VACUUM关闭 + - 对于其他情况,本地模式的自动VACUUM开启,冻结操作的自动VACUUM开启 + */ if (false == datallowconn || IS_SINGLE_NODE) { /* for database that refuses to accpet connections, for single node mode, autovacuum remains the same as PG */ local_autovacuum = true; @@ -1973,40 +2128,52 @@ static void do_autovacuum(void) #endif /* StartTransactionCommand changed elsewhere */ + // 切换到自动VACUUM内存上下文以便于内存分配 (void)MemoryContextSwitchTo(t_thrd.autovacuum_cxt.AutovacMemCxt); + // 初始化自动VACUUM信息哈希表 t_thrd.autovacuum_cxt.pgStatAutoVacInfo = NULL; + // 如果是分布式协调器且不是本地自动VACUUM,则进行以下操作 if (IS_PGXC_COORDINATOR && false == local_autovacuum) { + // 初始化 avinfo_ctl 变量 rc = memset_s(&avinfo_ctl, sizeof(avinfo_ctl), 0, sizeof(avinfo_ctl)); securec_check(rc, "", ""); + // 设置哈希表的键大小、条目大小、内存上下文和哈希函数 avinfo_ctl.keysize = sizeof(PgStat_StatTabKey); avinfo_ctl.entrysize = sizeof(avw_info); avinfo_ctl.hcxt = t_thrd.autovacuum_cxt.AutovacMemCxt; avinfo_ctl.hash = tag_hash; + // 创建自动VACUUM信息的哈希表 t_thrd.autovacuum_cxt.pgStatAutoVacInfo = hash_create( "autovac information of user-define table", 512, &avinfo_ctl, HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT); + // 获取全局自动VACUUM信息 fetch_global_autovac_info(); } /* The database hash where pgstat keeps shared relations */ shared = pgstat_fetch_stat_dbentry(InvalidOid); + // 打开 pg_class 关系表并获取其描述符 classRel = heap_open(RelationRelationId, AccessShareLock); /* create a copy so we can use it after closing pg_class */ + // 创建副本以便在关闭 pg_class 后继续使用 pg_class_desc = CreateTupleDescCopy(RelationGetDescr(classRel)); /* create hash table for partitoned relid <-> autovac info mapping */ + // 为分区关系ID <-> 自动VACUUM 信息建立哈希表 rc = memset_s(&partitioned_tables_ctl, sizeof(partitioned_tables_ctl), 0, sizeof(partitioned_tables_ctl)); securec_check(rc, "", ""); + // 设置哈希表的键大小、条目大小和哈希函数 partitioned_tables_ctl.keysize = sizeof(Oid); partitioned_tables_ctl.entrysize = sizeof(at_partitioned_table); partitioned_tables_ctl.hash = oid_hash; + // 创建分区关系ID <-> 自动VACUUM 信息的哈希表 partitioned_tables_map = hash_create("partitioned relid to autovac info map", 10, &partitioned_tables_ctl, HASH_ELEM | HASH_FUNCTION); @@ -2014,20 +2181,24 @@ static void do_autovacuum(void) rc = memset_s(&toast_table_ctl, sizeof(toast_table_ctl), 0, sizeof(toast_table_ctl)); securec_check(rc, "", ""); + // 设置哈希表的键大小、条目大小和哈希函数 toast_table_ctl.keysize = sizeof(Oid); toast_table_ctl.entrysize = sizeof(av_toastid_mainid); toast_table_ctl.hash = oid_hash; + // 创建 TOAST 关系ID <-> 主关系ID 的哈希表 toast_table_map = hash_create("TOAST to main relid map", 100, &toast_table_ctl, HASH_ELEM | HASH_FUNCTION); /* create hash table for reloptions <-> main relid mapping */ rc = memset_s(&table_relopt_ctl, sizeof(table_relopt_ctl), 0, sizeof(table_relopt_ctl)); securec_check(rc, "", ""); + // 为主关系ID <-> 关系选项 建立哈希表 table_relopt_ctl.keysize = sizeof(Oid); table_relopt_ctl.entrysize = sizeof(av_relation); table_relopt_ctl.hash = oid_hash; + // 创建主关系ID <-> 关系选项 的哈希表 table_relopt_map = hash_create("main relid to rel options map", 100, &table_relopt_ctl, HASH_ELEM | HASH_FUNCTION); /* @@ -2049,6 +2220,7 @@ static void do_autovacuum(void) * On the first pass, we collect main tables to vacuum, and also the main * table relid to TOAST relid mapping. */ + // 第一遍扫描- 主表/分区表的处理:收集主表和TOAST表之间的映射关系以及需要自动清理的主表: while ((tuple = (HeapTuple) tableam_scan_getnexttuple(relScan, ForwardScanDirection)) != NULL) { Form_pg_class classForm = (Form_pg_class)GETSTRUCT(tuple); PgStat_StatTabEntry* tabentry = NULL; @@ -2060,11 +2232,13 @@ static void do_autovacuum(void) bool enable_analyze = false; bool enable_vacuum = false; + // 如果关系不是关系表(relations)或物化视图(materialized views),则跳过 if (classForm->relkind != RELKIND_RELATION && classForm->relkind != RELKIND_MATVIEW) continue; /* We cannot safely process other backends' temp tables, so skip 'em. */ + // 跳过其他后端会话的临时表 if (classForm->relpersistence == RELPERSISTENCE_TEMP || classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP) { continue; @@ -2073,9 +2247,11 @@ static void do_autovacuum(void) /* Here we skipped relation_support_autoavac() and relation_needs_vacanalyze() checks * for Ustore partitioned tables */ + // 在 UStore 分区表的情况下,跳过 relation_support_autoavac() 和 relation_needs_vacanalyze() 检查 bytea *rawRelopts = extractRelOptions(tuple, pg_class_desc, InvalidOid); if (rawRelopts != NULL && RelationIsTableAccessMethodUStoreType(rawRelopts) && isPartitionedRelation(classForm)) { + // 创建一个 vacuum_object 结构体并初始化 vacObj = (vacuum_object*)palloc(sizeof(vacuum_object)); vacObj->tab_oid = relid; vacObj->parent_oid = InvalidOid; @@ -2085,6 +2261,7 @@ static void do_autovacuum(void) vacObj->need_freeze = false; vacObj->is_internal_relation = false; vacObj->flags = VACFLG_MAIN_PARTITION; + // 将当前表添加到待处理表的列表中 table_oids = lappend(table_oids, vacObj); continue; } @@ -2100,23 +2277,26 @@ static void do_autovacuum(void) relation_needs_vacanalyze(relid, relopts, classForm, tuple, tabentry, enable_analyze, enable_vacuum, false, &dovacuum, &doanalyze, &need_freeze); + // 如果处于冻结自动VACUUM模式,判断是否需要进行VACUUM操作以及是否需要冻结 if (freeze_autovacuum) { dovacuum = need_freeze; doanalyze = false; } /* relations that need work are added to table_oids */ + // 如果需要进行VACUUM操作或者分析操作 if (dovacuum || doanalyze) { + // 创建一个 vacuum_object 结构体并初始化 vacObj = (vacuum_object*)palloc(sizeof(vacuum_object)); - vacObj->tab_oid = relid; - vacObj->parent_oid = InvalidOid; - vacObj->dovacuum = dovacuum; - vacObj->dovacuum_toast = false; - vacObj->doanalyze = doanalyze; - vacObj->need_freeze = need_freeze; - vacObj->is_internal_relation = is_internal_relation; - vacObj->flags = (isPartitionedRelation(classForm) ? VACFLG_MAIN_PARTITION : VACFLG_SIMPLE_HEAP); - table_oids = lappend(table_oids, vacObj); + vacObj->tab_oid = relid; // 当前表的对象ID + vacObj->parent_oid = InvalidOid; // 父表的对象ID(在处理分区表时可能有用) + vacObj->dovacuum = dovacuum; // 是否需要进行VACUUM操作 + vacObj->dovacuum_toast = false; // 是否需要对 TOAST 表进行 VACUUM + vacObj->doanalyze = doanalyze; // 是否需要进行分析操作 + vacObj->need_freeze = need_freeze; // 是否需要冻结操作 + vacObj->is_internal_relation = is_internal_relation; // 当前关系是否是内部关系 + vacObj->flags = (isPartitionedRelation(classForm) ? VACFLG_MAIN_PARTITION : VACFLG_SIMPLE_HEAP); // 表标志 + table_oids = lappend(table_oids, vacObj); // 将当前表添加到待处理表的列表中 } /* @@ -2126,15 +2306,16 @@ static void do_autovacuum(void) * 2. to avoid recompute allowvacuum falg */ if (isPartitionedRelation(classForm)) { - bool found = false; - at_partitioned_table* ap_entry = NULL; + bool found = false; // 标记是否在哈希表中找到条目 + at_partitioned_table* ap_entry = NULL; // 指向分区表自动VACUUM信息的条目 + // 在分区表到自动VACUUM信息哈希表中查找或插入一个条目 ap_entry = (at_partitioned_table*)hash_search(partitioned_tables_map, &relid, HASH_ENTER, &found); - if (!found) { - ap_entry->at_allowvacuum = enable_vacuum; - ap_entry->at_doanalyze = doanalyze; - ap_entry->at_dovacuum = dovacuum; - ap_entry->at_needfreeze = need_freeze; + if (!found) { // 如果没有找到已存在的条目,说明是新插入的 + ap_entry->at_allowvacuum = enable_vacuum; // 记录是否允许进行VACUUM操作的标志 + ap_entry->at_doanalyze = doanalyze; // 记录是否需要进行分析操作的标志 + ap_entry->at_dovacuum = dovacuum; // 记录是否需要进行VACUUM操作的标志 + ap_entry->at_needfreeze = need_freeze; // 记录是否需要进行冻结操作的标志 } } @@ -2147,18 +2328,20 @@ static void do_autovacuum(void) * and save AutoVacOpts so we can use them when we deal with parttition */ if (OidIsValid(classForm->reltoastrelid) || isPartitionedRelation(classForm)) { - av_relation* ar_entry = NULL; - av_toastid_mainid* at_entry = NULL; - bool found = false; + av_relation* ar_entry = NULL; // 指向保存关联信息的条目 + av_toastid_mainid* at_entry = NULL; // 指向保存TOAST表与主表关联信息的条目 + bool found = false; // 标记是否在哈希表中找到条目 /* Skip partitioned table's toasttable, since partitioned table * is a logic table and has no data in physical files corresponding * to its relfilenode */ + // 如果不是分区表,则处理TOAST表的关联信息 if (!isPartitionedRelation(classForm)) { + // 在 toast_table_map 中查找或插入一个条目 at_entry = (av_toastid_mainid*)hash_search(toast_table_map, &(classForm->reltoastrelid), HASH_ENTER, &found); - if (!found) { + if (!found) { // 如果没有找到已存在的条目,说明是新插入的 /* hash_search already filled in the key */ at_entry->at_relid = relid; at_entry->at_parentid = InvalidOid; @@ -2180,11 +2363,11 @@ static void do_autovacuum(void) * !!!Unlike PG, we pass relid instand of toastid to the hash table. */ ar_entry = (av_relation*)hash_search(table_relopt_map, &relid, HASH_ENTER, &found); - if (!found) { + if (!found) { // 如果没有找到已存在的条目,说明是新插入的 /* hash_search already filled in the key */ - ar_entry->ar_hasrelopts = false; + ar_entry->ar_hasrelopts = false; // 是否具有 reloptions 的标志 if (relopts != NULL) { - ar_entry->ar_hasrelopts = true; + ar_entry->ar_hasrelopts = true; // 标记具有 reloptions rc = memcpy_s(&ar_entry->ar_reloptions, sizeof(AutoVacOpts), relopts, sizeof(AutoVacOpts)); securec_check(rc, "", ""); } @@ -2192,10 +2375,11 @@ static void do_autovacuum(void) } if (relopts != NULL) { - pfree_ext(relopts); + pfree_ext(relopts); // 释放之前提取的表的 reloptions 内存 } } - tableam_scan_end(relScan); + tableam_scan_end(relScan); // 结束对 pg_class 的扫描,释放相关资源 + // 停止计时器,在日志中记录从开始扫描 pg_class 表到结束扫描的时间,以帮助调试和性能分析。 DEBUG_MOD_STOP_TIMER(MOD_AUTOVAC, "AUTOVAC TIMER: Scan pg_class to determine which tables to vacuum"); /* @@ -2203,54 +2387,58 @@ static void do_autovacuum(void) * and also the partitioned table relid to TOAST relid mapping. */ ScanKeyInit(&key[0], Anum_pg_partition_parttype, BTEqualStrategyNumber, F_CHAREQ, - CharGetDatum(PART_OBJ_TYPE_TABLE_PARTITION)); + CharGetDatum(PART_OBJ_TYPE_TABLE_PARTITION)); // 初始化扫描的搜索键,指定了搜索 pg_partition 表 - partRel = heap_open(PartitionRelationId, AccessShareLock); - partScan = tableam_scan_begin(partRel, SnapshotNow, 1, key); + partRel = heap_open(PartitionRelationId, AccessShareLock); // 打开 pg_partition 表 + partScan = tableam_scan_begin(partRel, SnapshotNow, 1, key); // 开始对 pg_partition 进行扫描 while (NULL != (partTuple = (HeapTuple) tableam_scan_getnexttuple(partScan, ForwardScanDirection))) { - Form_pg_partition partForm = (Form_pg_partition)GETSTRUCT(partTuple); - PgStat_StatTabEntry* tabentry = NULL; - AutoVacOpts* relopts = NULL; - bool dovacuum = false; - bool doanalyze = false; - bool need_freeze = false; - Oid partOid; - bool found = false; - av_relation* ar_hentry = NULL; - at_partitioned_table* ap_entry = NULL; + Form_pg_partition partForm = (Form_pg_partition)GETSTRUCT(partTuple); // 获取 pg_partition 中的数据 + PgStat_StatTabEntry* tabentry = NULL; // 用于统计信息 + AutoVacOpts* relopts = NULL; // 自动清理选项 + bool dovacuum = false; // 是否需要执行自动清理操作 + bool doanalyze = false; // 是否需要执行自动分析操作 + bool need_freeze = false; // 是否需要进行冻结操作 + Oid partOid; // 分区表的 OID + bool found = false; // 标记是否在哈希表中找到对应项 + av_relation* ar_hentry = NULL; // 主表的关联关系信息 + at_partitioned_table* ap_entry = NULL; // 分区表的自动清理信息 /* * 'found = false' means partitioned table do autovac on other coordiantor * coordiantor that analyze table partition is consistent with the coordiantor * that analyze partition table. */ + // 通过主表的 parentid 找到主表的关联关系信息 ar_hentry = (av_relation*)hash_search(table_relopt_map, &(partForm->parentid), HASH_FIND, &found); if (!found) - continue; + continue;// 若未找到主表的关联关系信息,继续下一次循环 - if (ar_hentry->ar_hasrelopts) + if (ar_hentry->ar_hasrelopts) // 如果主表有关联的 reloptions,将其赋值给 relopts relopts = &ar_hentry->ar_reloptions; + // 通过 parentid 在分区表哈希表中查找分区表的自动清理信息 ap_entry = (at_partitioned_table*)hash_search(partitioned_tables_map, &partForm->parentid, HASH_FIND, &found); if (!found) { + // 若在哈希表中未找到对应项,抛出错误 ereport(defence_errlevel(), (errcode(ERRCODE_DATA_CORRUPTED), errmsg("Oid: %u does not " "find in partitioned tables map.", partForm->parentid))); } /* Every partition table is local */ - partOid = HeapTupleGetOid(partTuple); - tabentry = get_pgstat_tabentry_relid(partOid, false, partForm->parentid, shared, dbentry); + partOid = HeapTupleGetOid(partTuple); // 获取分区表的 OID + tabentry = get_pgstat_tabentry_relid(partOid, false, partForm->parentid, shared, dbentry); // 获取分区表的统计信息 /* Check if it needs vacuum or analyze */ - partition_needs_vacanalyze( + partition_needs_vacanalyze( // 检查是否需要执行自动清理或自动分析操作 partOid, relopts, partForm, partTuple, ap_entry, tabentry, false, &dovacuum, &doanalyze, &need_freeze); - Assert(false == doanalyze); - if (freeze_autovacuum) { + Assert(false == doanalyze); // 确保不需要执行自动分析操作 + if (freeze_autovacuum) { // 如果需要进行冻结操作,设置 dovacuum 为 true dovacuum = need_freeze; } /* Partition that need work are added to table_oids */ if (dovacuum) { + // 将需要进行自动清理的分区添加到 table_oids 列表中 vacObj = (vacuum_object*)palloc(sizeof(vacuum_object)); vacObj->tab_oid = partOid; vacObj->parent_oid = partForm->parentid; @@ -2264,11 +2452,12 @@ static void do_autovacuum(void) } /* just save partitioned tableis oid as mainid for partition */ - if (OidIsValid(partForm->reltoastrelid)) { + if (OidIsValid(partForm->reltoastrelid)) { // 保存分区表的 TOAST 表关系 av_toastid_mainid* at_entry = NULL; at_entry = (av_toastid_mainid*)hash_search(toast_table_map, &(partForm->reltoastrelid), HASH_ENTER, &found); if (!found) { + // 如果未找到对应项,填充相关信息 at_entry->at_relid = partOid; at_entry->at_parentid = partForm->parentid; at_entry->at_allowvacuum = ap_entry->at_allowvacuum; @@ -2283,26 +2472,36 @@ static void do_autovacuum(void) * (e.g. alter table exchange partition) * just skip it this time. */ + // 如果在 hash 表中找到对应项,并且 parentid 不匹配 if (found && (at_entry->at_parentid != partForm->parentid)) { + // 在 toast_table_map 中查找对应项,并尝试移除 if (hash_search(toast_table_map, &(partForm->reltoastrelid), HASH_REMOVE, NULL) != NULL) { + // 若成功移除,记录日志,说明 reltoastrelid 已被更改,跳过该项 ereport(LOG, (errmsg("reltoastrelid: %u toast table map " "has been changed, skip it.", partForm->reltoastrelid))); } else { + // 若未成功移除,报告错误,表明 toast_table_map 哈希表已损坏 ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("toast table map hash table corrupted."))); } } else { + // 如果没有找到对应项,或者 parentid 匹配 确保 at_entry 的关联信息有效 Assert(OidIsValid(at_entry->at_relid) && OidIsValid(at_entry->at_parentid)); } } } /* Close the pg_partition */ - tableam_scan_end(partScan); - heap_close(partRel, AccessShareLock); + tableam_scan_end(partScan); // 结束之前开始的分区表扫描,释放相关资源 + heap_close(partRel, AccessShareLock); // 关闭之前通过 heap_open 函数打开的pg_partition表 + // 停止计时器,在日志中记录从开始扫描 pg_partition 表到结束扫描的时间,以帮助调试和性能分析。 DEBUG_MOD_STOP_TIMER(MOD_AUTOVAC, "AUTOVAC TIMER: Scan pg_partition to determine which partitions to vacuum"); /* On the third pass: check TOAST tables */ + // 第三次循环:检查 TOAST 表 + // 初始化扫描键,用于在扫描中筛选出符合特定条件的行。以便在 pg_class 表中找到 relkind 字段等于 RELKIND_TOASTVALUE 的行,也就是TOAST表的行 ScanKeyInit(&key[0], Anum_pg_class_relkind, BTEqualStrategyNumber, F_CHAREQ, CharGetDatum(RELKIND_TOASTVALUE)); + // 开始一个新的表扫描。用于从 pg_class 表中获取满足扫描键条件的行 relScan = tableam_scan_begin(classRel, SnapshotNow, 1, &key[0]); + // 遍历满足条件的 pg_class 表中的记录,即表示 TOAST 表的记录 while ((tuple = (HeapTuple) tableam_scan_getnexttuple(relScan, ForwardScanDirection)) != NULL) { Form_pg_class classForm = (Form_pg_class)GETSTRUCT(tuple); Oid relid = HeapTupleGetOid(tuple); @@ -2318,10 +2517,12 @@ static void do_autovacuum(void) av_toastid_mainid* at_entry = NULL; /* We cannot safely process other backends' temp tables, so skip 'em. */ + // 跳过其他后台进程的临时表 if (classForm->relpersistence == RELPERSISTENCE_TEMP || classForm->relpersistence == RELPERSISTENCE_GLOBAL_TEMP) continue; + // 在 toast_table_map 中查找对应项 at_entry = (av_toastid_mainid*)hash_search(toast_table_map, &(relid), HASH_FIND, &found); /* @@ -2336,15 +2537,18 @@ static void do_autovacuum(void) * fetch reloptions -- if this toast table does not have them, try the * main rel */ + // 提取 reloptions - 如果该 TOAST 表没有 reloptions,则尝试使用主表的 relopts = extract_autovac_opts(tuple, pg_class_desc); /* * we must get main table id first, and then get the * reloptions according to the main table id */ + // 如果 relopts 不为空,并且 relopts 引用的是其他表的选项 if (NULL == relopts && NULL != at_entry) { av_relation* ar_hentry = NULL; Oid MainId = at_entry->at_parentid > InvalidOid ? at_entry->at_parentid : at_entry->at_relid; + // 在 table_relopt_map 中查找主表/分区表的 relopts ar_hentry = (av_relation*)hash_search(table_relopt_map, &MainId, HASH_FIND, &found); if (found && ar_hentry->ar_hasrelopts) { relopts = &ar_hentry->ar_reloptions; @@ -2353,56 +2557,67 @@ static void do_autovacuum(void) } /* Fetch the pgstat entry for this table */ + // 获取该表的 pgstat 数据 tabentry = get_pgstat_tabentry_relid(relid, classForm->relisshared, InvalidOid, shared, dbentry); + // 检查表是否需要 VACUUM 或 ANALYZE relation_support_autoavac(tuple, &enable_analyze, &enable_vacuum, &is_internal_relation); relation_needs_vacanalyze(relid, relopts, classForm, tuple, tabentry, enable_analyze, enable_vacuum, true, &dovacuum, &doanalyze, &need_freeze); + // 如果处于冻结自动 VACUUM 模式 if (freeze_autovacuum) { + // 对于 MATMAP 表或 MLOG 表,执行 VACUUM,不执行 ANALYZE if (ISMATMAP(classForm->relname.data) || ISMLOG(classForm->relname.data)) { dovacuum = true; doanalyze = false; } else { + // 否则,执行 VACUUM 当且仅当需要冻结 dovacuum = need_freeze; doanalyze = false; } } /* vacuum main table/partition instead if toast table */ - if (dovacuum) { + if (dovacuum) { // 添加主表/分区表到 table_oids,执行 VACUUM 或者 VACUUM TOAST vacObj = (vacuum_object*)palloc(sizeof(vacuum_object)); if (local_autovacuum) { + // 对于本地自动 VACUUM,设置表的 OID 作为 tab_oid,无父表 vacObj->tab_oid = relid; vacObj->parent_oid = InvalidOid; vacObj->flags = VACFLG_SIMPLE_HEAP; vacObj->dovacuum_toast = false; } else { + // 对于全局自动 VACUUM,设置主表/分区表的 OID 作为 tab_oid,父表的 OID 作为 parent_oid vacObj->tab_oid = at_entry->at_relid; vacObj->parent_oid = at_entry->at_parentid; + // 如果存在父表 OID,则设置 VACFLG_SUB_PARTITION 标志,否则设置 VACFLG_SIMPLE_HEAP 标志 vacObj->flags = OidIsValid(at_entry->at_parentid) ? VACFLG_SUB_PARTITION : VACFLG_SIMPLE_HEAP; vacObj->dovacuum_toast = true; vacObj->is_internal_relation = at_entry->at_internal; } + // 设置是否执行 VACUUM、是否执行 ANALYZE、是否需要冻结 vacObj->dovacuum = dovacuum; vacObj->doanalyze = doanalyze; vacObj->need_freeze = need_freeze; + // 将当前 vacObj 添加到 table_oids 列表中 table_oids = lappend(table_oids, vacObj); } + // 如果 relopts 存在且不是引用其他表的选项,则释放 relopts 内存 if (relopts && !isReloptsReferenceOther) { pfree_ext(relopts); } } - tableam_scan_end(relScan); - heap_close(classRel, AccessShareLock); + tableam_scan_end(relScan); // 结束对 pg_class 表的扫描 + heap_close(classRel, AccessShareLock); // 关闭 pg_class 表 + // 停止计时,记录扫描 pg_class 表以确定哪些 TOAST 表需要进行 VACUUM 的时间 DEBUG_MOD_STOP_TIMER(MOD_AUTOVAC, "AUTOVAC TIMER: Scan pg_class to determine which toast tables to vacuum"); - /* * Create one buffer access strategy object per buffer pool for VACUUM to use. * We want to use the same one across all the vacuum operations we perform, * since the point is for VACUUM not to blow out the shared cache. */ - bstrategy = GetAccessStrategy(BAS_VACUUM); + bstrategy = GetAccessStrategy(BAS_VACUUM); // 为 VACUUM 创建每个缓冲池一个缓冲区访问策略对象 /* * create a memory context to act as fake t_thrd.mem_cxt.portal_mem_cxt, so that the @@ -2423,11 +2638,13 @@ static void do_autovacuum(void) int stdVacuumCostDelay; int stdVacuumCostLimit; + // 获取当前待处理的表的信息 vacObj = (vacuum_object*)lfirst(cell); relid = vacObj->tab_oid; parentid = vacObj->parent_oid; /* just skip all autovac actions quickly */ + // 如果未启用自动化清理守护进程且不需要进行冻结操作,则快速跳过所有自动清理操作 if (!u_sess->attr.attr_storage.autovacuum_start_daemon && !vacObj->need_freeze) break; @@ -2438,6 +2655,7 @@ static void do_autovacuum(void) * needs vacuuming. We also need the AutovacuumLock to walk the * worker array, but we'll let go of that one quickly. */ + // 获得 AutovacuumScheduleLock 和 AutovacuumLock 的独占锁,用于检查是否可以进行清理操作 LWLockAcquire(AutovacuumScheduleLock, LW_EXCLUSIVE); LWLockAcquire(AutovacuumLock, LW_SHARED); @@ -2445,6 +2663,7 @@ static void do_autovacuum(void) * Check whether the table is being vacuumed concurrently by another * worker. */ + // 检查是否由其他工作进程并发地对表进行清理操作 skipit = false; worker = (WorkerInfo)SHMQueueNext(&t_thrd.autovacuum_cxt.AutoVacuumShmem->av_runningWorkers, &t_thrd.autovacuum_cxt.AutoVacuumShmem->av_runningWorkers, offsetof(WorkerInfoData, wi_links)); @@ -2466,6 +2685,7 @@ static void do_autovacuum(void) * 1. other worker handle the main table, need check the worker's tableoid not equal the parentid; * 2. other worker handle the part table, need check the worker's parentoid not equal the parentid; */ + // 检查工作进程正在处理的表是否和当前要处理的表相同,如果是则跳过 if (parentid == InvalidOid && (worker->wi_tableoid == relid || worker->wi_parentoid == relid)) { AUTOVAC_LOG(DEBUG1, "parentoid = %u, tableoid = %u is on autovac, just skip it", parentid, relid); skipit = true; @@ -2482,6 +2702,7 @@ static void do_autovacuum(void) &worker->wi_links, offsetof(WorkerInfoData, wi_links)); } LWLockRelease(AutovacuumLock); + // 如果需要跳过清理操作,继续处理下一个表 if (skipit) { LWLockRelease(AutovacuumScheduleLock); continue; @@ -2497,8 +2718,10 @@ static void do_autovacuum(void) * that somebody just finished vacuuming this table. The window to * the race condition is not closed but it is very small. */ + // 切换到指定内存上下文以进行后续操作 (void)MemoryContextSwitchTo(t_thrd.autovacuum_cxt.AutovacMemCxt); + // 检查是否需要重新检查当前表的自动清理设置 if ((vacObj->flags & VACFLG_SIMPLE_HEAP) || (vacObj->flags & VACFLG_MAIN_PARTITION)) { tab = table_recheck_autovac(vacObj, table_relopt_map, toast_table_map, pg_class_desc); } else { @@ -2506,6 +2729,7 @@ static void do_autovacuum(void) tab = partition_recheck_autovac(vacObj, table_relopt_map, partitioned_tables_map, pg_class_desc); } + // 如果没有需要进行自动清理的表,继续处理下一个表 if (tab == NULL) { /* someone else vacuumed the table, or it went away */ LWLockRelease(AutovacuumScheduleLock); @@ -2518,6 +2742,7 @@ static void do_autovacuum(void) * Ok, good to go. Store the table in shared memory before releasing * the lock so that other workers don't vacuum it concurrently. */ + // 释放 AutovacuumScheduleLock 以允许其他工作进程操作 t_thrd.autovacuum_cxt.MyWorkerInfo->wi_tableoid = relid; t_thrd.autovacuum_cxt.MyWorkerInfo->wi_parentoid = parentid; t_thrd.autovacuum_cxt.MyWorkerInfo->wi_sharedrel = tab->at_sharedrel; @@ -2529,26 +2754,26 @@ static void do_autovacuum(void) * restore these at the bottom of the loop, else we'll compute wrong * values in the next iteration of autovac_balance_cost(). */ + // 保存当前 vacuum cost 相关参数的值以备后续恢复使用 stdVacuumCostDelay = u_sess->attr.attr_storage.VacuumCostDelay; stdVacuumCostLimit = u_sess->attr.attr_storage.VacuumCostLimit; - /* Must hold AutovacuumLock while mucking with cost balance info */ + // 保持 AutovacuumLock 状态以处理成本平衡信息 LWLockAcquire(AutovacuumLock, LW_EXCLUSIVE); - /* advertise my cost delay parameters for the balancing algorithm */ + // 设置自动化清理操作的成本限制 t_thrd.autovacuum_cxt.MyWorkerInfo->wi_cost_delay = tab->at_vacuum_cost_delay; t_thrd.autovacuum_cxt.MyWorkerInfo->wi_cost_limit = tab->at_vacuum_cost_limit; t_thrd.autovacuum_cxt.MyWorkerInfo->wi_cost_limit_base = tab->at_vacuum_cost_limit; - /* do a balance */ + // 执行成本平衡操作,以避免某些工作进程过度使用资源,导致性能下降。 autovac_balance_cost(); - /* set the active cost parameters from the result of that */ + // 更新工作进程的成本延迟,以适应系统的负载和资源使用情况 AutoVacuumUpdateDelay(); - /* done */ + // 释放 AutovacuumLock 锁 LWLockRelease(AutovacuumLock); - /* clean up memory before each iteration */ MemoryContextResetAndDeleteChildren(t_thrd.mem_cxt.portal_mem_cxt); (void)MemoryContextSwitchTo(t_thrd.mem_cxt.portal_mem_cxt); @@ -2560,20 +2785,22 @@ static void do_autovacuum(void) * Note: they must live in a long-lived memory context because we call * vacuum and analyze in different transactions. */ + // 如果当前表是分区表 if (vacuumPartition((uint32)(vacObj->flags))) { - Oid at_parentid = partid_get_parentid(tab->at_relid); + Oid at_parentid = partid_get_parentid(tab->at_relid); // 获取分区表的主表的Oid - tab->at_partname = getPartitionName(tab->at_relid, false); - tab->at_relname = get_rel_name(at_parentid); - tab->at_nspname = get_namespace_name(get_rel_namespace(at_parentid)); + tab->at_partname = getPartitionName(tab->at_relid, false); // 获取分区表的分区名称 + tab->at_relname = get_rel_name(at_parentid); // 获取主表的关系名 + tab->at_nspname = get_namespace_name(get_rel_namespace(at_parentid)); // 获取主表所属的命名空间名 } else { - tab->at_partname = NULL; - tab->at_relname = get_rel_name(tab->at_relid); - tab->at_nspname = get_namespace_name(get_rel_namespace(tab->at_relid)); + tab->at_partname = NULL; // 分区名称设置为空 + tab->at_relname = get_rel_name(tab->at_relid); // 获取关系名 + tab->at_nspname = get_namespace_name(get_rel_namespace(tab->at_relid)); // 获取命名空间名 } - + // 获取当前数据库名 tab->at_datname = get_database_name(u_sess->proc_cxt.MyDatabaseId); + // 检查获取的关系名、命名空间名、数据库名是否为空 if ((NULL == tab->at_relname) || (NULL == tab->at_nspname) || (NULL == tab->at_datname)) goto deleted; @@ -2582,39 +2809,48 @@ static void do_autovacuum(void) * and continue with the next one in schedule; in particular, this * happens if we are interrupted with SIGINT. */ + /* + 负责在执行自动清理和分析操作前做必要的设置,执行操作后进行清理,以维护数据库表的性能和空间。 + 涵盖了报告活动、事务管理、锁定数据库、设置活动快照、切换内存上下文、处理信号和执行清理操作等关键步骤。 + */ PG_TRY(); { /* * 1. Let pgstat know what we're doing * 2. in this function, statement_timestamp will be set to current time */ + // 通知 pgstat 正在进行的活动类型(vacuum/analyze)并更新 statement_timestamp autovac_report_activity(tab); + // 如果有活动快照,则弹出它 if (ActiveSnapshotSet()) PopActiveSnapshot(); - CommitTransactionCommand(); + CommitTransactionCommand(); // 提交当前事务 - StartTransactionCommand(); + StartTransactionCommand(); // 开始新事务 /* vacuum must hold RowExclusiveLock on db for a new transaction */ + // 获取数据库的 RowExclusiveLock 以执行清理操作 LockSharedObject(DatabaseRelationId, u_sess->proc_cxt.MyDatabaseId, 0, RowExclusiveLock); - PushActiveSnapshot(GetTransactionSnapshot()); + PushActiveSnapshot(GetTransactionSnapshot()); // 设置新事务的活动快照 - (void)MemoryContextSwitchTo(t_thrd.mem_cxt.portal_mem_cxt); + (void)MemoryContextSwitchTo(t_thrd.mem_cxt.portal_mem_cxt); // 切换到用于 portal 相关操作的内存上下文 + // 在分布式系统中,如果设置了 autoanalyze 超时且满足条件,启用信号闹钟 if ((IS_PGXC_COORDINATOR || IS_SINGLE_NODE) && u_sess->attr.attr_storage.autoanalyze_timeout > 0 && tab->at_doanalyze && !tab->at_dovacuum) { t_thrd.storage_cxt.timeIsPausing = false; enable_sig_alarm(u_sess->attr.attr_storage.autoanalyze_timeout * 1000, true); } + // 根据表的类型执行本地自动清理或分布式 vacuum/analyze if (local_autovacuum || vacObj->is_internal_relation) autovacuum_local_vac_analyze(tab, bstrategy); else autovacuum_do_vac_analyze(tab, bstrategy); /* Cancel any active statement timeout before committing */ - disable_sig_alarm(true); + disable_sig_alarm(true); // 在提交前取消任何活跃的语句超时 /* * Clear a possible query-cancel signal, to avoid a late reaction @@ -2622,10 +2858,18 @@ static void do_autovacuum(void) * current table (we're done with it, so it would make no sense to * cancel at this point.) */ + // 清除可能的查询取消信号,避免在清理当前表时由于取消而引起迟钝的反应 t_thrd.int_cxt.QueryCancelPending = false; } + /* + 用于捕获在自动清理和分析过程中可能发生的错误情况,并在出现错误时执行必要的清理和恢复操作。 + 会取消当前事务、重置相关标志和上下文,发出错误报告,最后重新启动事务以继续后续操作。这样做是为了保障系统的稳定性和一致性 + */ PG_CATCH(); { + // 在 PG_TRY 块中捕获到错误后的处理 + + // 检查操作过程中是否发生了超时 bool timeout_flag = (t_thrd.storage_cxt.cancel_from_timeout && u_sess->attr.attr_storage.autoanalyze_timeout); @@ -2633,6 +2877,7 @@ static void do_autovacuum(void) * Abort the transaction, start a new one, and proceed with the * next table in our list. */ + // 终止当前事务并重置必要的标志位 HOLD_INTERRUPTS(); t_thrd.int_cxt.QueryCancelPending = false; @@ -2643,24 +2888,30 @@ static void do_autovacuum(void) t_thrd.storage_cxt.timeIsPausing = false; t_thrd.storage_cxt.restimems = -1; + // 根据操作类型(vacuum 或 analyze)生成错误上下文信息 if (tab->at_dovacuum) errcontext("automatic vacuum of table \"%s.%s.%s\"", tab->at_datname, tab->at_nspname, tab->at_relname); else errcontext( "automatic analyze of table \"%s.%s.%s\"", tab->at_datname, tab->at_nspname, tab->at_relname); + // 发出错误报告并重置事务状态 EmitErrorReport(); /* this resets the PGXACT flags too */ AbortCurrentTransaction(); FlushErrorState(); + + // 重置用于消息和 portal 操作的内存上下文 MemoryContextResetAndDeleteChildren(t_thrd.mem_cxt.msg_mem_cxt); MemoryContextResetAndDeleteChildren(t_thrd.mem_cxt.portal_mem_cxt); + // 如果发生了超时,向 pgstat 报告以进行监控 if (timeout_flag) pgstat_report_autovac_timeout(vacObj->tab_oid, vacObj->parent_oid, tab->at_sharedrel); /* for some cases, we could not response any signal here, so we need unblock signals */ + // 解除信号屏蔽并重新开始事务以进行后续操作 gs_signal_setmask(&t_thrd.libpq_cxt.UnBlockSig, NULL); (void)gs_signal_unblock_sigusr2(); @@ -2668,6 +2919,7 @@ static void do_autovacuum(void) StartTransactionCommand(); RESUME_INTERRUPTS(); /* vacuum must hold RowExclusiveLock on db for a new transaction */ + // 获取数据库的 RowExclusiveLock 以进行新事务 LockSharedObject(DatabaseRelationId, u_sess->proc_cxt.MyDatabaseId, 0, RowExclusiveLock); } PG_END_TRY(); @@ -2682,6 +2934,7 @@ static void do_autovacuum(void) * settings, so we don't want to give up our share of I/O for a very * short interval and thereby thrash the global balance. */ + // 获取 AutovacuumLock 并重置当前工作进程的信息 LWLockAcquire(AutovacuumLock, LW_EXCLUSIVE); t_thrd.autovacuum_cxt.MyWorkerInfo->wi_tableoid = InvalidOid; t_thrd.autovacuum_cxt.MyWorkerInfo->wi_parentoid = InvalidOid; @@ -2690,12 +2943,15 @@ static void do_autovacuum(void) LWLockRelease(AutovacuumLock); /* restore vacuum cost GUCs for the next iteration */ + // 恢复下一次迭代所需的 vacuum cost 参数 u_sess->attr.attr_storage.VacuumCostDelay = stdVacuumCostDelay; u_sess->attr.attr_storage.VacuumCostLimit = stdVacuumCostLimit; + // 重置消息和 portal 内存上下文 MemoryContextResetAndDeleteChildren(t_thrd.mem_cxt.msg_mem_cxt); MemoryContextResetAndDeleteChildren(t_thrd.mem_cxt.portal_mem_cxt); /* reset t_thrd.vacuum_cxt.vac_context in case that invalid t_thrd.vacuum_cxt.vac_context would be used */ + // 重置 vacuum 上下文,以免在无效的上下文下进行操作 t_thrd.vacuum_cxt.vac_context = NULL; } /* @@ -2710,9 +2966,9 @@ static void do_autovacuum(void) vac_update_datfrozenxid(); /* Finally close out the last transaction. */ - if (ActiveSnapshotSet()) - PopActiveSnapshot(); - CommitTransactionCommand(); + if (ActiveSnapshotSet()) // 如果当前存在活跃的快照 + PopActiveSnapshot(); // 弹出活跃快照 + CommitTransactionCommand(); // 提交当前事务 } /* @@ -2723,19 +2979,23 @@ static void do_autovacuum(void) */ AutoVacOpts* extract_autovac_opts(HeapTuple tup, TupleDesc pg_class_desc) { - bytea* relopts = NULL; - AutoVacOpts* av = NULL; - int rc = 0; + bytea* relopts = NULL; // 存储关系的 reloptions + AutoVacOpts* av = NULL; // 存储提取的 AutoVacOpts 结构 + int rc = 0; // 用于存储 memcpy_s 函数的返回码 + // 断言:确保关系类型是表、物化视图或 TOAST 值 Assert(((Form_pg_class)GETSTRUCT(tup))->relkind == RELKIND_RELATION || ((Form_pg_class) GETSTRUCT(tup))->relkind == RELKIND_MATVIEW || ((Form_pg_class)GETSTRUCT(tup))->relkind == RELKIND_TOASTVALUE); + // 提取关系的 reloptions relopts = extractRelOptions(tup, pg_class_desc, InvalidOid); if (relopts == NULL) return NULL; + // 分配内存以存储 AutoVacOpts 结构 av = (AutoVacOpts*)palloc(sizeof(AutoVacOpts)); + // 将提取的 AutoVacOpts 复制到 av 结构中 rc = memcpy_s(av, sizeof(AutoVacOpts), &(((StdRdOptions*)relopts)->autovacuum), sizeof(AutoVacOpts)); securec_check(rc, "\0", "\0"); @@ -2744,7 +3004,7 @@ AutoVacOpts* extract_autovac_opts(HeapTuple tup, TupleDesc pg_class_desc) av->enabled = isPartitionedRelation((Form_pg_class)GETSTRUCT(tup)); } - pfree_ext(relopts); + pfree_ext(relopts); // 释放 relopts 的内存 return av; } @@ -2757,19 +3017,23 @@ AutoVacOpts* extract_autovac_opts(HeapTuple tup, TupleDesc pg_class_desc) static PgStat_StatTabEntry* get_pgstat_tabentry_relid( Oid relid, bool isshared, uint32 statFlag, PgStat_StatDBEntry* shared, PgStat_StatDBEntry* dbentry) { - PgStat_StatTabEntry* tabentry = NULL; - PgStat_StatDBEntry* dnentry = NULL; + PgStat_StatTabEntry* tabentry = NULL; // 存储表的 pgstat 统计信息项 + PgStat_StatDBEntry* dnentry = NULL; // 存储数据库的 pgstat 统计信息项 + // 根据表是否共享,选择要访问的数据库统计信息项 if (isshared) dnentry = shared; else dnentry = dbentry; + // 如果数据库统计信息项存在 if (dnentry != NULL) { - PgStat_StatTabKey tabkey; + PgStat_StatTabKey tabkey; // 存储表的统计信息键 - tabkey.statFlag = statFlag; - tabkey.tableid = relid; + // 设置表的统计信息键 + tabkey.statFlag = statFlag; // 统计信息的标志位 + tabkey.tableid = relid; // 表的 OID + // 在数据库统计信息项的散列表中查找表的统计信息项 tabentry = (PgStat_StatTabEntry*)hash_search(dnentry->tables, (void*)(&tabkey), HASH_FIND, NULL); } @@ -2786,13 +3050,14 @@ static PgStat_StatTabEntry* get_pgstat_tabentry_relid( */ static autovac_table* calculate_vacuum_cost_and_freezeages(const AutoVacOpts* avopts, bool doanalyze, bool need_freeze) { - int64 freeze_min_age; - int64 freeze_table_age; - int vac_cost_limit; - int vac_cost_delay; - autovac_table* tab = NULL; + int64 freeze_min_age; // 最小冻结年龄 + int64 freeze_table_age; // 表的冻结年龄 + int vac_cost_limit; // 清理成本限制 + int vac_cost_delay; // 清理成本延迟 + autovac_table* tab = NULL; // 存储自动清理的相关参数和冻结信息 /* -1 in autovac setting means use plain vacuum_cost_delay */ + // 如果在自动清理选项中设置了具体的值,就使用这些值;否则尝试使用 GUC 默认值 vac_cost_delay = (avopts && avopts->vacuum_cost_delay >= 0) ? avopts->vacuum_cost_delay : (u_sess->attr.attr_storage.autovacuum_vac_cost_delay >= 0) @@ -2807,21 +3072,23 @@ static autovac_table* calculate_vacuum_cost_and_freezeages(const AutoVacOpts* av : u_sess->attr.attr_storage.VacuumCostLimit; /* these do not have autovacuum-specific settings */ + // 这些参数不具有自动清理专用的设置,因此直接使用默认值或上下文中的默认值 freeze_min_age = (avopts && avopts->freeze_min_age >= 0) ? avopts->freeze_min_age : t_thrd.autovacuum_cxt.default_freeze_min_age; freeze_table_age = (avopts && avopts->freeze_table_age >= 0) ? avopts->freeze_table_age : t_thrd.autovacuum_cxt.default_freeze_table_age; + // 创建存储自动清理的相关参数和冻结信息的结构体 tab = (autovac_table*)palloc(sizeof(autovac_table)); - tab->at_doanalyze = doanalyze; - tab->at_freeze_min_age = freeze_min_age; - tab->at_freeze_table_age = freeze_table_age; - tab->at_vacuum_cost_limit = vac_cost_limit; - tab->at_vacuum_cost_delay = vac_cost_delay; - tab->at_needfreeze = need_freeze; - tab->at_relname = NULL; - tab->at_nspname = NULL; - tab->at_datname = NULL; + tab->at_doanalyze = doanalyze; // 是否执行分析操作 + tab->at_freeze_min_age = freeze_min_age; // 最小冻结年龄 + tab->at_freeze_table_age = freeze_table_age; // 表的冻结年龄 + tab->at_vacuum_cost_limit = vac_cost_limit; // 清理成本限制 + tab->at_vacuum_cost_delay = vac_cost_delay; // 清理成本延迟 + tab->at_needfreeze = need_freeze; // 是否需要冻结 + tab->at_relname = NULL; // 表名 + tab->at_nspname = NULL; // 命名空间名 + tab->at_datname = NULL; // 数据库名 return tab; } @@ -2836,46 +3103,47 @@ static autovac_table* calculate_vacuum_cost_and_freezeages(const AutoVacOpts* av static autovac_table* table_recheck_autovac( vacuum_object* vacObj, HTAB* table_relopt_map, HTAB* toast_table_map, TupleDesc pg_class_desc) { - Oid relid = vacObj->tab_oid; + Oid relid = vacObj->tab_oid; // 获取待处理表的对象ID Form_pg_class classForm; HeapTuple classTup; - bool dovacuum = false; - bool dovacuum_toast = vacObj->dovacuum_toast; - bool doanalyze = false; - bool need_freeze = false; - bool is_internal_relation = false; - bool enable_analyze = false; - bool enable_vacuum = false; - autovac_table* tab = NULL; - PgStat_StatDBEntry* shared = NULL; - PgStat_StatDBEntry* dbentry = NULL; - PgStat_StatTabEntry* tabentry = NULL; - AutoVacOpts* avopts = NULL; - bool isAvoptsRefereceOther = false; + bool dovacuum = false; // 是否执行清理操作的标志 + bool dovacuum_toast = vacObj->dovacuum_toast; // 是否执行 TOAST 表的清理操作 + bool doanalyze = false; // 是否执行分析操作的标志 + bool need_freeze = false; // 是否需要冻结的标志 + bool is_internal_relation = false; // 是否为内部关系的标志 + bool enable_analyze = false; // 是否启用分析操作的标志 + bool enable_vacuum = false; // 是否启用清理操作的标志 + autovac_table* tab = NULL; // 自动垃圾清理表信息的结构体指针 + PgStat_StatDBEntry* shared = NULL; // 共享数据库统计信息的结构体指针 + PgStat_StatDBEntry* dbentry = NULL; // 当前数据库统计信息的结构体指针 + PgStat_StatTabEntry* tabentry = NULL; // 表统计信息的结构体指针 + AutoVacOpts* avopts = NULL; // 自动垃圾清理配置选项的结构体指针 + bool isAvoptsRefereceOther = false; // 是否引用了其他结构体的标志 if (IS_SINGLE_NODE) { /* use fresh stats */ - autovac_refresh_stats(); + autovac_refresh_stats();// 刷新数据库统计信息 } - shared = pgstat_fetch_stat_dbentry(InvalidOid); - dbentry = pgstat_fetch_stat_dbentry(u_sess->proc_cxt.MyDatabaseId); + shared = pgstat_fetch_stat_dbentry(InvalidOid); // 获取共享数据库统计信息 + dbentry = pgstat_fetch_stat_dbentry(u_sess->proc_cxt.MyDatabaseId); // 获取当前数据库统计信息 /* fetch the relation's relcache entry */ - classTup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid)); + classTup = SearchSysCacheCopy1(RELOID, ObjectIdGetDatum(relid)); // 从系统缓存中获取表的元组 if (!HeapTupleIsValid(classTup)) return NULL; - classForm = (Form_pg_class)GETSTRUCT(classTup); + classForm = (Form_pg_class)GETSTRUCT(classTup); // 获取表的表单数据 bytea *rawRelopts = extractRelOptions(classTup, pg_class_desc, InvalidOid); /* this is Ustore partitioned table, use another bypass */ + // 判断是否处理的是满足特定条件的 Ustore 存储引擎的分区表 if (rawRelopts != NULL && RelationIsTableAccessMethodUStoreType(rawRelopts) && isPartitionedRelation(classForm)) { - avopts = extract_autovac_opts(classTup, pg_class_desc); - tab = calculate_vacuum_cost_and_freezeages(avopts, false, false); + avopts = extract_autovac_opts(classTup, pg_class_desc); // 获取自动垃圾清理的配置选项 + tab = calculate_vacuum_cost_and_freezeages(avopts, false, false); // 计算清理成本和冻结阈值 if (tab != NULL) { - tab->at_relid = relid; - tab->at_sharedrel = classForm->relisshared; - tab->at_dovacuum = true; + tab->at_relid = relid; // 设置表的对象ID + tab->at_sharedrel = classForm->relisshared; // 设置表是否为共享表 + tab->at_dovacuum = true; // 设置执行清理操作的标志 } return tab; } @@ -2884,7 +3152,7 @@ static autovac_table* table_recheck_autovac( * Get the applicable reloptions. If it is a TOAST table, try to get the * main table reloptions if the toast table itself doesn't have. */ - avopts = extract_autovac_opts(classTup, pg_class_desc); + avopts = extract_autovac_opts(classTup, pg_class_desc); // 提取表的自动清理选项 if (RELKIND_TOASTVALUE == classForm->relkind && (NULL == avopts) && (NULL != table_relopt_map) && (NULL != toast_table_map)) { av_relation* hentry = NULL; @@ -2892,10 +3160,12 @@ static autovac_table* table_recheck_autovac( bool found = false; Oid MainId; + // 在toast_table_map中查找是否有主表的映射 tentry = (av_toastid_mainid*)hash_search(toast_table_map, &relid, HASH_FIND, &found); if (found) { MainId = tentry->at_relid; hentry = (av_relation*)hash_search(table_relopt_map, &MainId, HASH_FIND, &found); + // 在table_relopt_map中查找主表的reloptions if (found && hentry->ar_hasrelopts) { avopts = &hentry->ar_reloptions; isAvoptsRefereceOther = true; @@ -2929,10 +3199,10 @@ static autovac_table* table_recheck_autovac( } } - heap_freetuple(classTup); + heap_freetuple(classTup); // 释放classTup所占用的内存 - if (avopts && !isAvoptsRefereceOther) { - pfree_ext(avopts); + if (avopts && !isAvoptsRefereceOther) { // 如果之前从表的元组数据中提取到了AutoVacOpts结构体avopts,并且没有被引用过 + pfree_ext(avopts); // 释放avopts占用的内存 } return tab; @@ -2949,22 +3219,37 @@ static void determine_vacuum_params(float4& vac_scale_factor, int& vac_base_thre MultiXactId& multiForceLimit, const AutoVacOpts* relopts) { /* -1 in autovac setting means use plain vacuum_cost_delay */ + // 判断是否传入了relopts结构体,以及其中的vacuum_scale_factor属性值 + // 如果有传入且值大于等于0,则使用传入值;否则使用autovacuum_vac_scale参数的值 vac_scale_factor = (relopts && relopts->vacuum_scale_factor >= 0) ? relopts->vacuum_scale_factor : u_sess->attr.attr_storage.autovacuum_vac_scale; + // 判断是否传入了relopts结构体,以及其中的vacuum_threshold属性值 + // 如果有传入且值大于等于0,则使用传入值;否则使用autovacuum_vac_thresh参数的值 vac_base_thresh = (relopts && relopts->vacuum_threshold >= 0) ? relopts->vacuum_threshold : u_sess->attr.attr_storage.autovacuum_vac_thresh; + + // 判断是否传入了relopts结构体,以及其中的analyze_scale_factor属性值 + // 如果有传入且值大于等于0,则使用传入值;否则使用autovacuum_anl_scale参数的值 anl_scale_factor = (relopts && relopts->analyze_scale_factor >= 0) ? relopts->analyze_scale_factor : u_sess->attr.attr_storage.autovacuum_anl_scale; + + // 判断是否传入了relopts结构体,以及其中的analyze_threshold属性值 + // 如果有传入且值大于等于0,则使用传入值;否则使用autovacuum_anl_thresh参数的值 anl_base_thresh = (relopts && relopts->analyze_threshold >= 0) ? relopts->analyze_threshold : u_sess->attr.attr_storage.autovacuum_anl_thresh; + // 判断是否传入了relopts结构体,以及其中的freeze_max_age属性值 + // 如果有传入且值大于等于0,则使用传入值;否则使用autovacuum_freeze_max_age参数的值 + // 但不超过全局的autovacuum_freeze_max_age参数值 freeze_max_age = (relopts && relopts->freeze_max_age >= 0) ? Min(relopts->freeze_max_age, g_instance.attr.attr_storage.autovacuum_freeze_max_age) : g_instance.attr.attr_storage.autovacuum_freeze_max_age; + // 判断是否传入了relopts结构体,以及其中的enabled属性值 + // 如果有传入,则使用传入值;否则默认为启用 av_enabled = (relopts ? relopts->enabled : true); /* Force vacuum if table need freeze the old tuple to recycle clog */ @@ -2974,6 +3259,7 @@ static void determine_vacuum_params(float4& vac_scale_factor, int& vac_base_thre xidForceLimit = FirstNormalTransactionId; #ifndef ENABLE_MULTIPLE_NODES + // 在单节点模式下,根据最近的多事务ID和freeze_max_age计算出强制执行清理的多事务ID的下限 if (t_thrd.autovacuum_cxt.recentMulti > FirstMultiXactId + (uint64)g_instance.attr.attr_storage.autovacuum_freeze_max_age) multiForceLimit = t_thrd.autovacuum_cxt.recentMulti - @@ -3027,17 +3313,17 @@ static void relation_needs_vacanalyze(Oid relid, AutoVacOpts* relopts, Form_pg_c PgStat_StatTabKey tablekey; avw_info* avwentry = NULL; bool found = false; - bool force_vacuum = false; - bool av_enabled = false; - bool userEnabled = true; + bool force_vacuum = false; // 是否需要强制执行VACUUM + bool av_enabled = false; // 是否启用了自动清理 + bool userEnabled = true; // 用户是否启用了自动清理 /* pg_class.reltuples */ float4 reltuples; /* constants from reloptions or GUC variables */ - int vac_base_thresh = 0; - int anl_base_thresh = 0; - float4 vac_scale_factor = 0.0; - float4 anl_scale_factor = 0.0; + int vac_base_thresh = 0; // VACUUM的基础阈值 + int anl_base_thresh = 0; // ANALYZE的基础阈值 + float4 vac_scale_factor = 0.0; // VACUUM阈值的缩放因子 + float4 anl_scale_factor = 0.0; // ANALYZE阈值的缩放因子 /* thresholds calculated from above constants */ float4 vacthresh; @@ -3048,24 +3334,30 @@ static void relation_needs_vacanalyze(Oid relid, AutoVacOpts* relopts, Form_pg_c int64 anltuples = 0; /* freeze parameters */ - int64 freeze_max_age = 0; - TransactionId xidForceLimit = InvalidTransactionId; - MultiXactId multiForceLimit = InvalidMultiXactId; + int64 freeze_max_age = 0; // 最大事务ID间隔 + TransactionId xidForceLimit = InvalidTransactionId; // 强制VACUUM的事务ID限制 + MultiXactId multiForceLimit = InvalidMultiXactId; // 强制VACUUM的MultiXactId限制 + // 断言,确保 classForm 和 relid 是有效的 AssertArg(classForm != NULL); AssertArg(OidIsValid(relid)); + // 从配置中获取自动清理参数,决定是否执行VACUUM操作 determine_vacuum_params(vac_scale_factor, vac_base_thresh, anl_scale_factor, anl_base_thresh, freeze_max_age, av_enabled, xidForceLimit, multiForceLimit, relopts); bool isNull = false; TransactionId relfrozenxid = InvalidTransactionId; + // 打开 pg_class 表以获取关键属性 Relation rel = heap_open(RelationRelationId, AccessShareLock); + // 获取表的 relfrozenxid64 属性 Datum xid64datum = heap_getattr(tuple, Anum_pg_class_relfrozenxid64, RelationGetDescr(rel), &isNull); + // 如果属性为空,则使用默认值 if (isNull) { relfrozenxid = classForm->relfrozenxid; + // 如果表的 relfrozenxid 不在合理范围内,设置为合理的默认值 if (TransactionIdPrecedes(t_thrd.xact_cxt.ShmemVariableCache->nextXid, relfrozenxid) || !TransactionIdIsNormal(relfrozenxid)) { relfrozenxid = FirstNormalTransactionId; @@ -3074,23 +3366,30 @@ static void relation_needs_vacanalyze(Oid relid, AutoVacOpts* relopts, Form_pg_c relfrozenxid = DatumGetTransactionId(xid64datum); } + // 判断是否需要强制执行 VACUUM force_vacuum = (TransactionIdIsNormal(relfrozenxid) && TransactionIdPrecedes(relfrozenxid, xidForceLimit)); #ifndef ENABLE_MULTIPLE_NODES + // 如果不是强制执行 VACUUM,检查 MultiXactId 也是否需要 if (!force_vacuum) { + // 获取表的 relminmxid 属性 Datum mxidDatum = heap_getattr(tuple, Anum_pg_class_relminmxid, RelationGetDescr(rel), &isNull); MultiXactId relminmxid = isNull ? FirstMultiXactId : DatumGetTransactionId(mxidDatum); + // 判断是否需要强制执行 VACUUM force_vacuum = (MultiXactIdIsValid(relminmxid) && MultiXactIdPrecedes(relminmxid, multiForceLimit)); } #endif heap_close(rel, AccessShareLock); - *need_freeze = force_vacuum; + *need_freeze = force_vacuum; // 更新是否需要强制执行冻结的标志 + // 记录是否需要强制执行冻结的情况 AUTOVAC_LOG(DEBUG2, "vac \"%s\": need freeze is %s", NameStr(classForm->relname), force_vacuum ? "true" : "false"); /* User disabled it in pg_class.reloptions? (But ignore if at risk) */ + // 如果用户已禁用自动清理或守护进程,则不执行自动清理 if (!force_vacuum && (!av_enabled || !u_sess->attr.attr_storage.autovacuum_start_daemon)) { userEnabled = false; } + // 获取统计信息的键 if (NULL != t_thrd.autovacuum_cxt.pgStatAutoVacInfo) { tablekey.statFlag = InvalidOid; tablekey.tableid = relid; @@ -3098,42 +3397,54 @@ static void relation_needs_vacanalyze(Oid relid, AutoVacOpts* relopts, Form_pg_c (avw_info*)hash_search(t_thrd.autovacuum_cxt.pgStatAutoVacInfo, (void*)(&tablekey), HASH_FIND, &found); } + // 获取表的 reltuples 属性 reltuples = classForm->reltuples; vacthresh = (float4)vac_base_thresh + vac_scale_factor * reltuples; anlthresh = (float4)anl_base_thresh + anl_scale_factor * reltuples; + // 如果既没有局部统计信息也没有全局统计信息,根据是否需要强制执行VACUUM来决定是否执行VACUUM和ANALYZE if ((avwentry == NULL) && (tabentry == NULL)) { *dovacuum = force_vacuum; *doanalyze = false; } else { + // 如果有局部统计信息 if (tabentry && (tabentry->changes_since_analyze || tabentry->n_dead_tuples)) { + // 更新变化的元组数和死元组数 anltuples = tabentry->changes_since_analyze; vactuples = tabentry->n_dead_tuples; + // 记录局部统计信息到日志中 AUTOVAC_LOG(DEBUG2, "fetch local stat info: vac \"%s\" changes_since_analyze = %ld n_dead_tuples = %ld ", NameStr(classForm->relname), tabentry->changes_since_analyze, tabentry->n_dead_tuples); } + // 如果有全局统计信息 if (avwentry && (avwentry->changes_since_analyze || avwentry->n_dead_tuples)) { + // 更新变化的元组数和死元组数 anltuples = avwentry->changes_since_analyze; vactuples = avwentry->n_dead_tuples; + // 记录全局统计信息到日志中 AUTOVAC_LOG(DEBUG2, "fetch global stat info: vac \"%s\" changes_since_analyze = %ld n_dead_tuples = %ld ", NameStr(classForm->relname), avwentry->changes_since_analyze, avwentry->n_dead_tuples); } /* Determine if this table needs vacuum. */ + // 确定是否需要执行VACUUM *dovacuum = force_vacuum; *doanalyze = false; + // 如果允许自动清理,根据阈值判断是否需要执行VACUUM if (false == *dovacuum && allowVacuum) *dovacuum = ((float4)vactuples > vacthresh); /* Determine if this table needs analyze. */ + // 如果允许分析操作,根据阈值判断是否需要执行ANALYZE if (allowAnalyze) *doanalyze = ((float4)anltuples > anlthresh); } - + // 如果用户启用了自动清理,则根据情况更新 dovacuum 的值 *dovacuum = *dovacuum && userEnabled; + // 记录自动清理和分析的决策日志 if (*dovacuum || *doanalyze) { AUTOVAC_LOG(DEBUG2, "vac \"%s\": recheck = %s need_freeze = %s dovacuum = %s (dead tuples %ld " "vacuum threshold %.0f) doanalyze = %s (changed tuples %ld analyze threshold %.0f)", @@ -3141,6 +3452,7 @@ static void relation_needs_vacanalyze(Oid relid, AutoVacOpts* relopts, Form_pg_c *dovacuum ? "true" : "false", vactuples, vacthresh, *doanalyze ? "true" : "false", anltuples, anlthresh); } + // 打印详细的自动清理日志 DEBUG_VACUUM_LOG(relid, classForm->relnamespace, LOG, "vac \"%s\": recheck = %s need_freeze = %s dovacuum = %s " "(dead tuples %ld vacuum threshold %.0f) doanalyze = %s (changed tuples %ld analyze threshold %.0f) reltuples " "= %.0f", NameStr(classForm->relname), is_recheck ? "true" : "false", *need_freeze ? "true" : "false", @@ -3153,19 +3465,27 @@ static void relation_needs_vacanalyze(Oid relid, AutoVacOpts* relopts, Form_pg_c * * fill in the vacuum statement. */ +// 填充 VACUUM 语句 static void fill_in_vac_stmt(VacuumStmt& vacstmt, const autovac_table& tab, RangeVar* rangevar) { + // 设置 VACUUM 语句的类型为 T_VacuumStmt vacstmt.type = T_VacuumStmt; + // 根据表的需要是否冻结设置 VACUUM 选项 if (!tab.at_needfreeze) vacstmt.options = VACOPT_NOWAIT; + // 根据表的需要是否执行 VACUUM 设置 VACUUM 选项 if (tab.at_dovacuum) vacstmt.options = (unsigned int)vacstmt.options | VACOPT_VACUUM; + // 根据表的需要是否执行 ANALYZE 设置 VACUUM 选项 if (tab.at_doanalyze) vacstmt.options = (unsigned int)vacstmt.options | VACOPT_ANALYZE; #ifdef ENABLE_MOT +// 如果启用了 MOT 存储引擎,设置 VACUUM 选项 vacstmt.options |= VACOPT_AUTOVAC; #endif + // 设置 VACUUM 语句的标志 vacstmt.flags = tab.at_flags; + // 设置 VACUUM 语句的 rely_oid 为无效值 vacstmt.rely_oid = InvalidOid; /* we just simple set it invalid, maybe change */ vacstmt.freeze_min_age = tab.at_freeze_min_age; vacstmt.freeze_table_age = tab.at_freeze_table_age; @@ -3178,83 +3498,85 @@ static void fill_in_vac_stmt(VacuumStmt& vacstmt, const autovac_table& tab, Rang * autovacuum_do_vac_analyze * Vacuum and/or analyze the specified table */ +// 执行自动清理中的 VACUUM 和 ANALYZE 操作 static void autovacuum_do_vac_analyze(autovac_table* tab, BufferAccessStrategy bstrategy) { - VacuumStmt vacstmt; - RangeVar rangevar; - const char* nspname = NULL; - const char* relname = NULL; - const char* partname = NULL; - StringInfoData str; - errno_t rc = EOK; + VacuumStmt vacstmt; // 用于存储 VACUUM 语句信息 + RangeVar rangevar; // 用于存储表的名称信息 + const char* nspname = NULL; // 表的命名空间名称 + const char* relname = NULL; // 表的关系名称 + const char* partname = NULL; // 分区表的名称 + StringInfoData str; // 用于构建 VACUUM 语句字符串的缓冲区 + errno_t rc = EOK; // 用于存储函数调用的返回值 /* Set up command parameters --- use local variables instead of palloc */ - rc = memset_s(&vacstmt, sizeof(vacstmt), 0, sizeof(vacstmt)); + rc = memset_s(&vacstmt, sizeof(vacstmt), 0, sizeof(vacstmt));// 清空 vacstmt 结构体 securec_check(rc, "", ""); - rc = memset_s(&rangevar, sizeof(rangevar), 0, sizeof(rangevar)); + rc = memset_s(&rangevar, sizeof(rangevar), 0, sizeof(rangevar));// 清空 rangevar 结构体 securec_check(rc, "", ""); - rangevar.schemaname = tab->at_nspname; - rangevar.relname = tab->at_relname; - if (NULL != tab->at_partname) { - rangevar.ispartition = true; - rangevar.partitionname = tab->at_partname; + rangevar.schemaname = tab->at_nspname; // 设置命名空间名称 + rangevar.relname = tab->at_relname; // 设置关系名称 + if (NULL != tab->at_partname) { // 如果存在分区名称 + rangevar.ispartition = true; // 标记为分区表 + rangevar.partitionname = tab->at_partname; // 设置分区表名称 } - rangevar.location = -1; + rangevar.location = -1; // 设置位置信息 - nspname = quote_identifier(tab->at_nspname); - relname = quote_identifier(tab->at_relname); + nspname = quote_identifier(tab->at_nspname); // 对命名空间名称进行标识符引用 + relname = quote_identifier(tab->at_relname); // 对关系名称进行标识符引用 if (NULL != tab->at_partname) - partname = quote_identifier(tab->at_partname); + partname = quote_identifier(tab->at_partname); // 对分区表名称进行标识符引用 - fill_in_vac_stmt(vacstmt, *tab, &rangevar); - initStringInfo(&str); + fill_in_vac_stmt(vacstmt, *tab, &rangevar); // 填充 VACUUM 语句信息 + initStringInfo(&str); // 初始化用于构建语句字符串的缓冲区 if (tab->at_dovacuum) - appendStringInfo(&str, "VACUUM "); + appendStringInfo(&str, "VACUUM "); // 如果需要执行 VACUUM,添加 VACUUM 关键字 if (tab->at_doanalyze) - appendStringInfo(&str, "ANALYZE "); - appendStringInfo(&str, "%s.%s", nspname, relname); + appendStringInfo(&str, "ANALYZE "); // 如果需要执行 ANALYZE,添加 ANALYZE 关键字 + appendStringInfo(&str, "%s.%s", nspname, relname); // 添加表的命名空间和关系名称 if (NULL != tab->at_partname) - appendStringInfo(&str, " PARTITION (%s)", partname); + appendStringInfo(&str, " PARTITION (%s)", partname); // 如果是分区表,添加分区表名称 - WaitStatePhase oldPhase = pgstat_report_waitstatus_phase(PHASE_AUTOVACUUM); - DEBUG_MOD_START_TIMER(MOD_AUTOVAC); - DoVacuumMppTable(&vacstmt, str.data, true, false); - DEBUG_MOD_STOP_TIMER(MOD_AUTOVAC, "AUTOVAC TIMER: %s", str.data); - pgstat_report_waitstatus_phase(oldPhase); + WaitStatePhase oldPhase = pgstat_report_waitstatus_phase(PHASE_AUTOVACUUM); // 设置等待状态阶段为自动清理 + DEBUG_MOD_START_TIMER(MOD_AUTOVAC); // 启动计时器记录自动清理耗时 + DoVacuumMppTable(&vacstmt, str.data, true, false); // 执行 VACUUM 或 ANALYZE 操作 + DEBUG_MOD_STOP_TIMER(MOD_AUTOVAC, "AUTOVAC TIMER: %s", str.data); // 停止计时器,记录自动清理耗时 + pgstat_report_waitstatus_phase(oldPhase); // 恢复等待状态阶段 } /* * autovacuum_do_vac_analyze * Vacuum and/or analyze the specified table */ +// 对指定的表执行 VACUUM 和/或 ANALYZE 操作 static void autovacuum_local_vac_analyze(autovac_table* tab, BufferAccessStrategy bstrategy) { - VacuumStmt vacstmt; - RangeVar rangevar; - errno_t rc = EOK; + VacuumStmt vacstmt; // 用于存储 VACUUM 语句信息 + RangeVar rangevar; // 用于存储表的名称信息 + errno_t rc = EOK; // 用于存储函数调用的返回值 /* Set up command parameters --- use local variables instead of palloc */ - rc = memset_s(&vacstmt, sizeof(vacstmt), 0, sizeof(vacstmt)); + rc = memset_s(&vacstmt, sizeof(vacstmt), 0, sizeof(vacstmt)); // 清空 securec_check(rc, "", ""); - rc = memset_s(&rangevar, sizeof(rangevar), 0, sizeof(rangevar)); + rc = memset_s(&rangevar, sizeof(rangevar), 0, sizeof(rangevar)); // 清空 securec_check(rc, "", ""); - rangevar.schemaname = tab->at_nspname; - rangevar.relname = tab->at_relname; - rangevar.partitionname = tab->at_partname; - rangevar.location = -1; + rangevar.schemaname = tab->at_nspname; // 设置命名空间名称 + rangevar.relname = tab->at_relname; // 设置关系名称 + rangevar.partitionname = tab->at_partname; // 设置分区表名称 + rangevar.location = -1; // 设置位置信息 - fill_in_vac_stmt(vacstmt, *tab, &rangevar); + fill_in_vac_stmt(vacstmt, *tab, &rangevar); // 填充 VACUUM 语句信息 /* Let pgstat know what we're doing */ autovac_report_activity(tab); - WaitStatePhase oldPhase = pgstat_report_waitstatus_phase(PHASE_AUTOVACUUM); - DEBUG_MOD_START_TIMER(MOD_AUTOVAC); - vacuum(&vacstmt, tab->at_relid, false, bstrategy, true); - DEBUG_MOD_STOP_TIMER(MOD_AUTOVAC, "AUTOVAC TIMER: %s", tab->at_relname); - pgstat_report_waitstatus_phase(oldPhase); + WaitStatePhase oldPhase = pgstat_report_waitstatus_phase(PHASE_AUTOVACUUM); // 设置等待状态阶段为自动清理 + DEBUG_MOD_START_TIMER(MOD_AUTOVAC); // 启动计时器记录自动清理耗时 + vacuum(&vacstmt, tab->at_relid, false, bstrategy, true); // 执行 VACUUM 或 ANALYZE 操作 + DEBUG_MOD_STOP_TIMER(MOD_AUTOVAC, "AUTOVAC TIMER: %s", tab->at_relname); // 停止计时器,记录自动清理耗时 + pgstat_report_waitstatus_phase(oldPhase); // 恢复等待状态阶段 } /* @@ -3268,15 +3590,16 @@ static void autovacuum_local_vac_analyze(autovac_table* tab, BufferAccessStrateg * done with the current one, and exit right after the last one, so we don't * bother to report "" or some such. */ +// 向 pgstat 汇报自动清理正在执行的操作状态和活动信息 static void autovac_report_activity(autovac_table* tab) { #define MAX_AUTOVAC_ACTIV_LEN (NAMEDATALEN * 2 + 56) - char activity[MAX_AUTOVAC_ACTIV_LEN]; + char activity[MAX_AUTOVAC_ACTIV_LEN]; // 存储要报告的字符串 int len; int rc = 0; /* Report the command and possible options */ - if (tab->at_dovacuum) + if (tab->at_dovacuum) // 如果需要执行 VACUUM 操作 rc = snprintf_s(activity, MAX_AUTOVAC_ACTIV_LEN, MAX_AUTOVAC_ACTIV_LEN - 1, "autovacuum: VACUUM%s", tab->at_doanalyze ? " ANALYZE" : ""); else @@ -3295,7 +3618,7 @@ static void autovac_report_activity(autovac_table* tab) /* Set statement_timestamp() to current time for pg_stat_activity */ SetCurrentStatementStartTimestamp(); - pgstat_report_activity(STATE_RUNNING, activity); + pgstat_report_activity(STATE_RUNNING, activity); // 向 pgstat 汇报自动清理正在执行的操作状态和活动信息 } /* @@ -3305,6 +3628,7 @@ static void autovac_report_activity(autovac_table* tab) */ bool AutoVacuumingActive(void) { + // 检查是否启用了自动清理守护进程和统计数据跟踪 if (!u_sess->attr.attr_storage.autovacuum_start_daemon || !u_sess->attr.attr_common.pgstat_track_counts) return false; return true; @@ -3318,6 +3642,7 @@ bool AutoVacuumingActive(void) */ void autovac_init(void) { + // 如果启用了自动清理守护进程但没有启用统计数据跟踪,发出警告 if (u_sess->attr.attr_storage.autovacuum_start_daemon && !u_sess->attr.attr_common.pgstat_track_counts) ereport(WARNING, (errmsg("autovacuum not started because of misconfiguration"), errhint("Enable the \"track_counts\" option."))); @@ -3328,20 +3653,25 @@ void autovac_init(void) * Return whether this is either a launcher autovacuum process or a worker * process. */ +// 检查是否为自动清理启动器进程 bool IsAutoVacuumLauncherProcess(void) { return t_thrd.role == AUTOVACUUM_LAUNCHER; } +// 检查是否为自动清理工作进程 bool IsAutoVacuumWorkerProcess(void) { return t_thrd.role == AUTOVACUUM_WORKER; } +// 定义自动清理工作进程的应用程序名称 const char* AUTO_VACUUM_WORKER = "AutoVacWorker"; +// 检查当前会话是否来自自动清理工作进程 bool IsFromAutoVacWoker(void) { + // 检查当前会话的应用程序名称是否与自动清理工作进程的名称相匹配 return (u_sess->attr.attr_common.application_name != NULL && strcmp(u_sess->attr.attr_common.application_name, AUTO_VACUUM_WORKER) == 0); } @@ -3350,6 +3680,7 @@ bool IsFromAutoVacWoker(void) * AutoVacuumShmemSize * Compute space needed for autovacuum-related shared memory */ +// 计算用于自动清理相关共享内存所需的空间大小 Size AutoVacuumShmemSize(void) { Size size; @@ -3357,8 +3688,8 @@ Size AutoVacuumShmemSize(void) /* * Need the fixed struct and the array of WorkerInfoData. */ - size = sizeof(AutoVacuumShmemStruct); - size = MAXALIGN(size); + size = sizeof(AutoVacuumShmemStruct); // 计算需要的空间大小 + size = MAXALIGN(size); // 对齐到最大对齐数 size = add_size(size, mul_size(g_instance.attr.attr_storage.autovacuum_max_workers, sizeof(WorkerInfoData))); return size; } @@ -3371,9 +3702,11 @@ void AutoVacuumShmemInit(void) { bool found = false; + // 分配自动清理相关的共享内存 t_thrd.autovacuum_cxt.AutoVacuumShmem = (AutoVacuumShmemStruct*)ShmemInitStruct("AutoVacuum Data", AutoVacuumShmemSize(), &found); + // 如果找到共享内存块,报告严重错误,因为不应该再次初始化 if (!IsUnderPostmaster) { WorkerInfo worker = NULL; int i = 0; @@ -3382,11 +3715,13 @@ void AutoVacuumShmemInit(void) ereport(PANIC, (errmsg("AutoVacuum Data share mem is already init"))); } + // 初始化 AutoVacuumShmem 结构的字段 t_thrd.autovacuum_cxt.AutoVacuumShmem->av_launcherpid = 0; t_thrd.autovacuum_cxt.AutoVacuumShmem->av_freeWorkers = NULL; SHMQueueInit(&t_thrd.autovacuum_cxt.AutoVacuumShmem->av_runningWorkers); t_thrd.autovacuum_cxt.AutoVacuumShmem->av_startingWorker = NULL; + // 初始化 WorkerInfo 数据 worker = (WorkerInfo)((char*)t_thrd.autovacuum_cxt.AutoVacuumShmem + MAXALIGN(sizeof(AutoVacuumShmemStruct))); /* initialize the WorkerInfo free list */ @@ -3395,6 +3730,7 @@ void AutoVacuumShmemInit(void) t_thrd.autovacuum_cxt.AutoVacuumShmem->av_freeWorkers = &worker[i]; } } else { + // 如果没有找到共享内存块,报告严重错误,因为必须在该情况下初始化 if (unlikely(!found)) { ereport(PANIC, (errmsg("AutoVacuum Data share mem is not init"))); } @@ -3413,26 +3749,30 @@ void AutoVacuumShmemInit(void) * Note: we avoid throttling in the autovac worker, as it would be * counterproductive in the recheck logic. */ +// 刷新自动清理进程的 pgstats 数据 static void autovac_refresh_stats(void) { if (IsAutoVacuumLauncherProcess()) { TimestampTz current_time; - current_time = GetCurrentTimestamp(); + current_time = GetCurrentTimestamp(); // 获取当前时间戳 + // 如果与上次读取时间间隔不超过 STATS_READ_DELAY,则不刷新 if (!TimestampDifferenceExceeds(t_thrd.autovacuum_cxt.last_read, current_time, STATS_READ_DELAY)) return; + // 更新上次读取时间 t_thrd.autovacuum_cxt.last_read = current_time; } - pgstat_clear_snapshot(); + pgstat_clear_snapshot(); // 清除 pgstats 快照数据 } - +// 用于确定分区表是否需要执行自动的VACUUM和ANALYZE操作,并根据各种参数和阈值来决定是否执行这些操作。同时,记录相关信息以便于调试和监控 static void partition_needs_vacanalyze(Oid partid, AutoVacOpts* relopts, Form_pg_partition partForm, HeapTuple partTuple, at_partitioned_table* ap_entry, PgStat_StatTabEntry* tabentry, bool is_recheck, bool* dovacuum, bool* doanalyze, bool* need_freeze) { + // 初始化和参数设置 PgStat_StatTabKey tablekey; avw_info* avwentry = NULL; bool found = false; @@ -3462,10 +3802,13 @@ static void partition_needs_vacanalyze(Oid partid, AutoVacOpts* relopts, Form_pg Oid nameSpaceOid = InvalidOid; char* partname = NULL; + // 断言确保 partForm 不为 NULL 并且 partid 为有效的 Oid AssertArg(partForm != NULL && OidIsValid(partid)); + // 获取自动VACUUM和ANALYZE操作所需的参数 determine_vacuum_params(vac_scale_factor, vac_base_thresh, anl_scale_factor, anl_base_thresh, freeze_max_age, av_enabled, xidForceLimit, multiForceLimit, relopts); /* Force vacuum if table need freeze the old tuple to recycle clog */ + // 如果需要冻结旧的元组以回收CLOG,强制进行VACUUM操作 if (t_thrd.autovacuum_cxt.recentXid > FirstNormalTransactionId + freeze_max_age) xidForceLimit = t_thrd.autovacuum_cxt.recentXid - freeze_max_age; else @@ -3473,17 +3816,21 @@ static void partition_needs_vacanalyze(Oid partid, AutoVacOpts* relopts, Form_pg bool isNull = false; TransactionId relfrozenxid = InvalidTransactionId; + // 打开 pg_partition 关系,使用 AccessShareLock 模式 Relation rel = heap_open(PartitionRelationId, AccessShareLock); + // 获取 pg_partition.relfrozenxid64 字段的值 Datum xid64datum = heap_getattr(partTuple, Anum_pg_partition_relfrozenxid64, RelationGetDescr(rel), &isNull); - if (isNull) { + if (isNull) {// 如果 xid64datum 的值为 NULL,说明 pg_partition.relfrozenxid64 字段为空 relfrozenxid = partForm->relfrozenxid; + // 如果当前的 nextXid 比 relfrozenxid 小,或者 relfrozenxid 不是正常事务ID if (TransactionIdPrecedes(t_thrd.xact_cxt.ShmemVariableCache->nextXid, relfrozenxid) || !TransactionIdIsNormal(relfrozenxid)) { relfrozenxid = FirstNormalTransactionId; } } else { + // 如果 xid64datum 的值不为 NULL,将其作为 relfrozenxid 的值 relfrozenxid = DatumGetTransactionId(xid64datum); } @@ -3491,21 +3838,26 @@ static void partition_needs_vacanalyze(Oid partid, AutoVacOpts* relopts, Form_pg if (!force_vacuum) { Datum mxidDatum = heap_getattr(partTuple, Anum_pg_partition_relminmxid, RelationGetDescr(rel), &isNull); MultiXactId relminmxid = isNull ? FirstMultiXactId : DatumGetTransactionId(mxidDatum); + // 检查 relminmxid 是否有效且早于 multiForceLimit force_vacuum = (MultiXactIdIsValid(relminmxid) && MultiXactIdPrecedes(relminmxid, multiForceLimit)); } #endif - heap_close(rel, AccessShareLock); + heap_close(rel, AccessShareLock); // 关闭关联的关系 + // 检查 relfrozenxid 是否为正常事务ID且早于 xidForceLimit force_vacuum = (TransactionIdIsNormal(relfrozenxid) && TransactionIdPrecedes(relfrozenxid, xidForceLimit)); + // 设置 need_freeze 标志,表示是否需要执行冻结操作 *need_freeze = force_vacuum; /* User disabled it in pg_class.reloptions? (But ignore if at risk) */ + // 如果用户禁用了自动VACUUM,或者禁用了autovacuum_start_daemon GUC选项 if (!force_vacuum && (!av_enabled || !u_sess->attr.attr_storage.autovacuum_start_daemon)) { + // 不执行ANALYZE和VACUUM操作 *doanalyze = false; *dovacuum = false; return; } - + // 如果不需要强制进行VACUUM操作,且不允许执行VACUUM操作或者已经执行过VACUUM操作 if (!force_vacuum && (!ap_entry->at_allowvacuum || ap_entry->at_dovacuum)) { *doanalyze = false; *dovacuum = false; @@ -3517,16 +3869,21 @@ static void partition_needs_vacanalyze(Oid partid, AutoVacOpts* relopts, Form_pg partname = NameStr(partForm->relname); reltuples = partForm->reltuples; + // 计算ANALYZE操作的阈值 anlthresh = (float4)anl_base_thresh + anl_scale_factor * reltuples; + // 计算VACUUM操作的阈值 vacthresh = (float4)vac_base_thresh + vac_scale_factor * reltuples; if (NULL != t_thrd.autovacuum_cxt.pgStatAutoVacInfo) { + // 设置表键的统计标志和表ID tablekey.statFlag = partForm->parentid; tablekey.tableid = partid; + // 在 pgStatAutoVacInfo 中查找与表键匹配的 avw_info 记录 avwentry = (avw_info*)hash_search(t_thrd.autovacuum_cxt.pgStatAutoVacInfo, (void*)(&tablekey), HASH_FIND, &found); } + // 根据不同情况计算变化和死元组的数量,并根据阈值和统计信息判断是否需要执行VACUUM和ANALYZE操作 if ((avwentry == NULL) && (tabentry == NULL)) { *doanalyze = false; *dovacuum = force_vacuum; @@ -3568,6 +3925,7 @@ static void partition_needs_vacanalyze(Oid partid, AutoVacOpts* relopts, Form_pg *doanalyze = (anltuples > anlthresh) && false; } + // 在需要执行自动VACUUM和ANALYZE操作的情况下,记录和输出相应的调试日志。 if (!is_recheck && (*dovacuum || *doanalyze)) { AUTOVAC_LOG(DEBUG2, "vac table \"%s\" partition(\"%s\"): recheck = %s need_freeze = %s " "dovacuum = %s (dead tuples %ld vacuum threshold %.0f)", @@ -3581,60 +3939,68 @@ static void partition_needs_vacanalyze(Oid partid, AutoVacOpts* relopts, Form_pg *dovacuum ? "true" : "false", vactuples, vacthresh, reltuples); } +// 用于重新检查分区表是否需要进行自动VACUUM操作,并计算需要执行的操作及参数配置。 static autovac_table* partition_recheck_autovac( vacuum_object* vacObj, HTAB* table_relopt_map, HTAB* partitioned_tables_map, TupleDesc pg_class_desc) { - Oid partid = vacObj->tab_oid; - bool dovacuum = false; - bool doanalyze = false; - bool dovacuum_toast = vacObj->dovacuum_toast; - bool need_freeze = false; - autovac_table* tab = NULL; - PgStat_StatTabEntry* tabentry = NULL; - PgStat_StatDBEntry* shared = NULL; - PgStat_StatDBEntry* dbentry = NULL; - at_partitioned_table* ap_entry = NULL; - AutoVacOpts* avopts = NULL; - av_relation* hentry = NULL; - bool found = false; - Form_pg_partition partForm; - HeapTuple partTuple; - Oid relid; + Oid partid = vacObj->tab_oid; // 分区表的OID + bool dovacuum = false; // 是否需要进行VACUUM操作 + bool doanalyze = false; // 是否需要进行ANALYZE操作 + bool dovacuum_toast = vacObj->dovacuum_toast; // 是否需要对TOAST表进行VACUUM操作 + bool need_freeze = false; // 是否需要冻结旧元组以回收CLOG + autovac_table* tab = NULL; // 存储自动VACUUM操作的信息 + PgStat_StatTabEntry* tabentry = NULL; // 统计信息中的表级别入口 + PgStat_StatDBEntry* shared = NULL; // 全局统计信息入口 + PgStat_StatDBEntry* dbentry = NULL; // 当前数据库的统计信息入口 + at_partitioned_table* ap_entry = NULL; // 分区表的信息入口 + AutoVacOpts* avopts = NULL; // 自动VACUUM的选项 + av_relation* hentry = NULL; // 表关系的信息入口 + bool found = false; // 标记是否找到相关信息 + Form_pg_partition partForm; // 分区表的表单信息 + HeapTuple partTuple; // 分区表的元组 + Oid relid; // 关联的表的OID + // 在单节点模式下刷新统计信息 if (IS_SINGLE_NODE) { /* use fresh stats */ autovac_refresh_stats(); } /* fetch the partition's syscache entry */ + // 获取分区表的syscache条目 partTuple = SearchSysCacheCopy1(PARTRELID, ObjectIdGetDatum(partid)); if (!HeapTupleIsValid(partTuple)) return NULL; - partForm = (Form_pg_partition)GETSTRUCT(partTuple); - relid = partForm->parentid; - shared = pgstat_fetch_stat_dbentry(InvalidOid); - dbentry = pgstat_fetch_stat_dbentry(u_sess->proc_cxt.MyDatabaseId); + partForm = (Form_pg_partition)GETSTRUCT(partTuple); // 获取分区表的表单信息 + relid = partForm->parentid; // 获取关联表的OID + shared = pgstat_fetch_stat_dbentry(InvalidOid); // 获取全局统计信息入口 + dbentry = pgstat_fetch_stat_dbentry(u_sess->proc_cxt.MyDatabaseId); // 获取当前数据库的统计信息入口 /* fetch the relation's syscache entry */ + // 从表关系选项映射中获取关联表的syscache条目 hentry = (av_relation*)hash_search(table_relopt_map, &relid, HASH_FIND, &found); + // 如果未找到关联表的选项信息或选项信息不存在,则报告数据损坏错误 if (!(found && hentry->ar_hasrelopts)) { ereport(defence_errlevel(), (errcode(ERRCODE_DATA_CORRUPTED), errmsg("Oid: %u does not " "find in rel options map.", relid))); } - avopts = &(hentry->ar_reloptions); + avopts = &(hentry->ar_reloptions); // 获取关联表的选项信息指针 /* fetch the pgstat table entry */ + // 从分区表映射中获取pgstat表级别的条目 ap_entry = (at_partitioned_table*)hash_search(partitioned_tables_map, &partForm->parentid, HASH_FIND, &found); if (!found) { ereport(defence_errlevel(), (errcode(ERRCODE_DATA_CORRUPTED), errmsg("Oid: %u does not " "find in partitioned tables map.", partForm->parentid))); } tabentry = get_pgstat_tabentry_relid(partid, false, relid, shared, dbentry); + // 确定分区表是否需要进行VACUUM或ANALYZE partition_needs_vacanalyze( partid, avopts, partForm, partTuple, ap_entry, tabentry, true, &dovacuum, &doanalyze, &need_freeze); Assert(false == doanalyze); /* OK, it needs something done */ + // 如果需要进行VACUUM或ANALYZE,计算所需的操作和参数 if (dovacuum || dovacuum_toast) { tab = calculate_vacuum_cost_and_freezeages(avopts, doanalyze, need_freeze); if (tab != NULL) { @@ -3644,7 +4010,7 @@ static autovac_table* partition_recheck_autovac( } } - heap_freetuple(partTuple); + heap_freetuple(partTuple); // 释放缓存中的关系元组 return tab; } diff --git a/src/gausskernel/process/postmaster/barrier_arch.cpp b/src/gausskernel/process/postmaster/barrier_arch.cpp index 51bfbaa7d..0167a0427 100755 --- a/src/gausskernel/process/postmaster/barrier_arch.cpp +++ b/src/gausskernel/process/postmaster/barrier_arch.cpp @@ -52,60 +52,72 @@ #define atolsn(x) ((XLogRecPtr)strtoul((x), NULL, 0)) +// 将barrier_id 写入到OBS(华为云对象存储)中,用于实现数据存档 static void write_barrier_id_to_obs(const char* barrier_name, ArchiveConfig *archive_obs) { - errno_t rc = 0; - ArchiveConfig obsConfig; - char pathPrefix[MAXPGPATH] = {0}; + errno_t rc = 0; // 用于存储函数调用的返回值,以便检查错误 + ArchiveConfig obsConfig; // 存储OBS配置的临时变量 + char pathPrefix[MAXPGPATH] = {0}; // 用于存储OBS路径前缀的临时变量 - ereport(LOG, (errmsg("Write barrierId <%s> to obs start", barrier_name))); + ereport(LOG, (errmsg("Write barrierId <%s> to obs start", barrier_name))); // 输出日志,表示开始写入BarrierID到OBS /* copy OBS configs to temporary variable for customising file path */ - rc = memcpy_s(&obsConfig, sizeof(ArchiveConfig), archive_obs, sizeof(ArchiveConfig)); + rc = memcpy_s(&obsConfig, sizeof(ArchiveConfig), archive_obs, sizeof(ArchiveConfig)); // 将OBS配置复制到临时变量,以便自定义文件路径 securec_check(rc, "", ""); - if (!IS_PGXC_COORDINATOR) { - rc = strcpy_s(pathPrefix, MAXPGPATH, obsConfig.archive_prefix); + if (!IS_PGXC_COORDINATOR) { // 如果不是PGXC协调器 + rc = strcpy_s(pathPrefix, MAXPGPATH, obsConfig.archive_prefix); // 将OBS路径前缀复制到pathPrefix中 securec_check(rc, "\0", "\0"); - char *p = strrchr(pathPrefix, '/'); + // 查找路径中的最后一个 '/' + char *p = strrchr(pathPrefix, '/'); if (p == NULL) { + // 如果找不到最后一个 '/',则输出错误并终止 ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("Obs path prefix is invalid"))); } + // 将找到的最后一个 '/' 替换为字符串结束符,以截取路径前缀 *p = '\0'; + // 将修改后的路径前缀赋值回obsConfig obsConfig.archive_prefix = pathPrefix; } + // 调用ArchiveWrite函数,将BarrierID写入OBS ArchiveWrite(BARRIER_FILE, barrier_name, MAX_BARRIER_ID_LENGTH - 1, &obsConfig); } +// 等待Barrier归档操作,直到达到指定的LSN static void WaitBarrierArch(XLogRecPtr barrierLsn, const char *slotName) { + // 输出日志,表示开始等待Barrier归档操作 ereport(LOG, (errmsg("WaitBarrierArch start: 0x%lx", barrierLsn))); - int cnt = 0; - const int interval = 100; + int cnt = 0; // 用于计数等待次数 + const int interval = 100; // 定义计数间隔 do { - ArchiveTaskStatus *archive_task_status = NULL; - archive_task_status = find_archive_task_status(slotName); + ArchiveTaskStatus *archive_task_status = NULL; // 用于存储归档任务状态的指针 + archive_task_status = find_archive_task_status(slotName); // 根据slotName查找归档任务状态 if (NULL == archive_task_status) { + // 如果找不到归档任务状态,输出错误信息并终止 ereport(ERROR, (errcode(ERRCODE_OPERATE_NOT_SUPPORTED), errmsg("Obs slot <%s> not exist.", slotName))); } + // 比较barrierLsn和当前已归档的LSN,如果达到了要求则跳出循环 if (XLByteLE(pg_atomic_read_u64(&barrierLsn), pg_atomic_read_u64(&archive_task_status->archived_lsn))) { break; } - CHECK_FOR_INTERRUPTS(); + CHECK_FOR_INTERRUPTS(); // 检查是否有中断请求 /* Also check stop flag */ if (t_thrd.barrier_arch.ready_to_stop) { + // 如果已经准备停止,则输出错误信息并终止 ereport(ERROR, (errcode(ERRCODE_ADMIN_SHUTDOWN), errmsg("[BarrierArch] terminating barrier arch" " due to administrator command"))); } - pg_usleep(100000L); + pg_usleep(100000L); // 等待100000微秒(0.1秒) if (t_thrd.barrier_arch.lastArchiveLoc == pg_atomic_read_u64(&archive_task_status->archived_lsn)) { + // 如果归档位置没有发生变化,计数加一,并在一定间隔输出警告信息 cnt++; if ((cnt % interval) == 0) { ereport(WARNING, (errmsg("[WaitBarrierArch] arch thread now archived" @@ -113,23 +125,27 @@ static void WaitBarrierArch(XLogRecPtr barrierLsn, const char *slotName) (uint32)t_thrd.barrier_arch.lastArchiveLoc, cnt))); } } else { - cnt = 0; + cnt = 0; // 如果归档位置发生变化,计数清零 } + // 更新归档位置,并检查是否超过了等待超时 t_thrd.barrier_arch.lastArchiveLoc = pg_atomic_read_u64(&archive_task_status->archived_lsn); if (cnt > WAIT_ARCHIVE_TIMEOUT) { + // 如果等待次数超过了设定的超时次数,输出错误信息并终止 ereport(ERROR, (errcode(ERRCODE_OPERATE_NOT_SUPPORTED), errmsg("Wait archived timeout."))); } - } while (1); + } while (1); // 无限循环,直到达到要求的LSN - ereport(LOG, (errmsg("WaitBarrierArch archive lsn end"))); + ereport(LOG, (errmsg("WaitBarrierArch archive lsn end"))); // 输出日志,表示等待归档操作结束 } /* * Make sure the current BARRIER WAL record has been archived.If not, wait until * the BARRIER WAL record has been archived. */ +// 确保当前的BARRIER WAL记录已被归档 void ProcessBarrierQueryArchive(char* id) { + // 输出日志,表示收到BARRIER QUERY消息 ereport(LOG, (errmsg("Receive BARRIER <%s> QUERY message on Coordinator or Datanode", id))); @@ -137,84 +153,99 @@ void ProcessBarrierQueryArchive(char* id) char *slotName; char *lsn; - lsn = strtok_r(id, ":", &slotName); + lsn = strtok_r(id, ":", &slotName); // 使用":"分割id,获取LSN和slotName if (lsn == NULL) { + // 如果LSN为空,输出错误信息并终止 ereport(ERROR, (errcode(ERRCODE_OPERATE_NOT_SUPPORTED), errmsg("The BARRIER QUERY ARCHIVE target lsn is null"))); } + // 将LSN转换为XLogRecPtr类型 XLogRecPtr barrierTargetLsn = atolsn(lsn); + // 输出日志,表示收到指定LSN消息 ereport(LOG, (errmsg("Receive LSN <%lx> message on Coordinator or Datanode", barrierTargetLsn))); + // 输出日志,表示收到BARRIER QUERY消息的slotName ereport(LOG, (errmsg("Receive BARRIER QUERY message slotname: %s", slotName))); + // 如果不是从协调器连接,输出错误信息并终止 if (!IsConnFromCoord()) ereport(ERROR, (errcode(ERRCODE_OPERATE_NOT_SUPPORTED), errmsg("The BARRIER QUERY ARCHIVE message is expected to " "arrive from a Coordinator"))); - if (!IS_PGXC_COORDINATOR) { - WaitBarrierArch(barrierTargetLsn, slotName); + if (!IS_PGXC_COORDINATOR) { // 如果不是PGXC协调器 + WaitBarrierArch(barrierTargetLsn, slotName); // 等待达到指定的LSN } - pq_beginmessage(&buf, 'b'); - pq_sendstring(&buf, id); - pq_endmessage(&buf); - pq_flush(); + pq_beginmessage(&buf, 'b'); // 启动一个'b'类型的消息 + pq_sendstring(&buf, id); // 向消息中添加id字符串 + pq_endmessage(&buf); // 结束消息构建 + pq_flush(); // 刷新消息 发送给客户端 } +// 处理用于终止Barrier归档的信号 static void BarrierArchWakenStop(SIGNAL_ARGS) { - t_thrd.barrier_arch.ready_to_stop = true; + t_thrd.barrier_arch.ready_to_stop = true; // 设置标志,准备终止归档 } +// 处理收到SIGHUP信号的情况 static void BarrierArchSighupHandler(SIGNAL_ARGS) { - int save_errno = errno; - t_thrd.barrier_arch.got_SIGHUP = true; - errno = save_errno; + int save_errno = errno; // 保存当前的错误码 + t_thrd.barrier_arch.got_SIGHUP = true; // 设置标志,表示收到了SIGHUP信号 + errno = save_errno; // 恢复之前保存的错误码 } /* Reset some signals that are accepted by postmaster but not here */ +// 设置不同信号的处理方式,确保程序在收到不同的信号时能够正确地处理 static void BarrierArchSetupSignalHook(void) { - (void)gspqsignal(SIGHUP, BarrierArchSighupHandler); - (void)gspqsignal(SIGINT, SIG_IGN); - (void)gspqsignal(SIGTERM, die); - (void)gspqsignal(SIGQUIT, quickdie); - (void)gspqsignal(SIGALRM, SIG_IGN); - (void)gspqsignal(SIGPIPE, SIG_IGN); - (void)gspqsignal(SIGUSR1, procsignal_sigusr1_handler); - (void)gspqsignal(SIGUSR2, BarrierArchWakenStop); + (void)gspqsignal(SIGHUP, BarrierArchSighupHandler); // 设置SIGHUP的处理函数为BarrierArchSighupHandler + (void)gspqsignal(SIGINT, SIG_IGN); // 忽略SIGINT信号 + (void)gspqsignal(SIGTERM, die); // 设置SIGTERM的处理函数为die + (void)gspqsignal(SIGQUIT, quickdie); // 设置SIGQUIT的处理函数为quickdie + (void)gspqsignal(SIGALRM, SIG_IGN); // 忽略SIGALRM信号 + (void)gspqsignal(SIGPIPE, SIG_IGN); // 忽略SIGPIPE信号 + (void)gspqsignal(SIGUSR1, procsignal_sigusr1_handler); // 设置SIGUSR1的处理函数为procsignal_sigusr1_handler + (void)gspqsignal(SIGUSR2, BarrierArchWakenStop); // 设置SIGUSR2的处理函数为BarrierArchWakenStop - (void)gspqsignal(SIGCHLD, SIG_DFL); - (void)gspqsignal(SIGTTIN, SIG_DFL); - (void)gspqsignal(SIGTTOU, SIG_DFL); - (void)gspqsignal(SIGCONT, SIG_DFL); - (void)gspqsignal(SIGWINCH, SIG_DFL); + (void)gspqsignal(SIGCHLD, SIG_DFL); // 将SIGCHLD的处理函数设置为默认值 + (void)gspqsignal(SIGTTIN, SIG_DFL); // 将SIGTTIN的处理函数设置为默认值 + (void)gspqsignal(SIGTTOU, SIG_DFL); // 将SIGTTOU的处理函数设置为默认值 + (void)gspqsignal(SIGCONT, SIG_DFL); // 将SIGCONT的处理函数设置为默认值 + (void)gspqsignal(SIGWINCH, SIG_DFL); // 将SIGWINCH的处理函数设置为默认值 /* We allow SIGQUIT (quickdie) at all times */ - (void)sigdelset(&t_thrd.libpq_cxt.BlockSig, SIGQUIT); + (void)sigdelset(&t_thrd.libpq_cxt.BlockSig, SIGQUIT); // 从阻塞信号集中移除SIGQUIT信号 } #ifdef ENABLE_MULTIPLE_NODES +// 当宏 ENABLE_MULTIPLE_NODES 被定义时,编译以下代码块 + +// 获取所有节点的连接句柄 static PGXCNodeAllHandles* GetAllNodesHandles() { + // 获取所有数据节点和协调节点的列表 List* barrierDataNodeList = GetAllDataNodes(); List* barrierCoordList = GetAllCoordNodes(); PGXCNodeAllHandles* conn_handles = NULL; + // 获取连接句柄 conn_handles = get_handles(barrierDataNodeList, barrierCoordList, false); + // 释放节点列表内存 list_free(barrierCoordList); list_free(barrierDataNodeList); - return conn_handles; + return conn_handles; // 返回连接句柄 } +// 向所有节点发送Barrier归档请求 static void SendBarrierArchRequest(const PGXCNodeAllHandles* handles, int count, ArchiveBarrierLsnInfo *barrierLsnInfo) { int conn; @@ -223,23 +254,26 @@ static void SendBarrierArchRequest(const PGXCNodeAllHandles* handles, int count, errno_t rc; char barrierInfo[BARRIER_ARCH_INFO_LEN]; + // 输出日志,表示开始向所有节点发送Barrier归档请求 ereport(LOG, (errmsg("Start to send barrier arch request to all nodes."))); - for (conn = 0; conn < count; conn++) { + for (conn = 0; conn < count; conn++) { // 循环遍历所有连接,向每个连接发送请求 PGXCNodeHandle* handle = NULL; + // 根据连接类型获取连接句柄 if (conn < handles->co_conn_count) handle = handles->coord_handles[conn]; else handle = handles->datanode_handles[conn - handles->co_conn_count]; /* Invalid connection state, return error */ - if (handle->state != DN_CONNECTION_STATE_IDLE) { + if (handle->state != DN_CONNECTION_STATE_IDLE) { // 如果连接状态无效,输出错误信息并终止 ereport(ERROR, (errcode(ERRCODE_OPERATE_FAILED), errmsg("Failed to send BARRIER request to the node"))); } + // 遍历所有barrierLsnInfo,找到匹配节点的信息 for (int i = 0; i < count; i++) { if (barrierLsnInfo[i].nodeoid == handle->nodeoid) { rc = snprintf_s(barrierInfo, BARRIER_ARCH_INFO_LEN, BARRIER_ARCH_INFO_LEN - 1, "0x%lx:%s", @@ -249,13 +283,15 @@ static void SendBarrierArchRequest(const PGXCNodeAllHandles* handles, int count, barrier_idlen = strlen(barrierInfo) + 1; + // 计算消息的总长度 msglen = 4; /* for the length itself */ msglen += barrier_idlen; msglen += 1; /* for barrier command itself */ /* msgType + msgLen */ - ensure_out_buffer_capacity(1 + msglen, handle); + ensure_out_buffer_capacity(1 + msglen, handle); // 确保输出缓冲区足够容纳消息 + // 添加消息类型 'b' Assert(handle->outBuffer != NULL); handle->outBuffer[handle->outEnd++] = 'b'; msglen = htonl(msglen); @@ -263,123 +299,147 @@ static void SendBarrierArchRequest(const PGXCNodeAllHandles* handles, int count, securec_check(rc, "\0", "\0"); handle->outEnd += 4; + // 添加Barrier归档命令 BARRIER_QUERY_ARCHIVE handle->outBuffer[handle->outEnd++] = BARRIER_QUERY_ARCHIVE; + // 添加Barrier信息 rc = memcpy_s(handle->outBuffer + handle->outEnd, handle->outSize - handle->outEnd, barrierInfo, barrier_idlen); securec_check(rc, "\0", "\0"); handle->outEnd += barrier_idlen; + // 设置连接状态为查询状态 handle->state = DN_CONNECTION_STATE_QUERY; + // 刷新连接的输出缓冲区 pgxc_node_flush(handle); } } +// 检查在所有节点上执行BARRIER ARCH查询命令的状态 static void CheckBarrierArchCommandStatus(const PGXCNodeAllHandles* conn_handles, int count, const char *id) { int conn; RemoteQueryState* combiner = NULL; + // 输出调试日志,表示正在检查BARRIER ARCH查询命令状态 ereport(DEBUG1, (errmsg("Check BARRIER ARCH QUERY <%s> command status", id))); + // 创建一个响应组合器,用于合并来自多个节点的响应 combiner = CreateResponseCombiner(count, COMBINE_TYPE_NONE); + // 循环遍历所有连接,检查每个连接的响应 for (conn = 0; conn < count; conn++) { PGXCNodeHandle* handle = NULL; + // 根据连接类型获取连接句柄 if (conn < conn_handles->co_conn_count) handle = conn_handles->coord_handles[conn]; else handle = conn_handles->datanode_handles[conn - conn_handles->co_conn_count]; + // 接收来自节点的响应 if (pgxc_node_receive(1, &handle, NULL)) ereport( ERROR, (errcode(ERRCODE_OPERATE_FAILED), errmsg("Failed to receive response from the remote side"))); + // 处理响应并检查执行状态 if (handle_response(handle, combiner) != RESPONSE_BARRIER_OK) ereport(ERROR, (errcode(ERRCODE_OPERATE_FAILED), errmsg("BARRIER ARCH QUERY failed with error %s", handle->error))); } + // 关闭响应组合器 CloseCombiner(combiner); + // 输出日志,表示在所有节点上成功完成了BARRIER ARCH查询命令 ereport(LOG, (errmsg("Successfully completed BARRIER ARCH QUERY <%s> command on " "all nodes", id))); } +// 执行归档相关的任务 static void QueryBarrierArch(PGXCNodeAllHandles* handles, ArchiveConfig *archive_obs) { int connCnt = handles->co_conn_count + handles->dn_conn_count; SpinLockAcquire(&g_instance.archive_obs_cxt.barrier_lock); + // 获取最大节点数量 int archivMaxNodeCnt = g_instance.archive_obs_cxt.max_node_cnt; + // 如果连接数超过了最大节点数 if (connCnt >= archivMaxNodeCnt) { + // 释放全局锁 SpinLockRelease(&g_instance.archive_obs_cxt.barrier_lock); + // 输出调试信息,表示当前连接数大于最大节点数 ereport(DEBUG2, (errmsg("current cn get connCnt: <%d> max than cluster connCnt: <%d>", connCnt, archivMaxNodeCnt))); return; } ArchiveBarrierLsnInfo barrierLsnInfo[g_instance.archive_obs_cxt.max_node_cnt]; - + // 如果当前的Barrier名称和已存储的名称相同,直接返回 if (strncmp(t_thrd.barrier_arch.barrierName, g_instance.archive_obs_cxt.barrierName, strlen(g_instance.archive_obs_cxt.barrierName)) == 0) { SpinLockRelease(&g_instance.archive_obs_cxt.barrier_lock); return; } - + // 复制当前的Barrier名称到全局变量 errno_t errorno = memcpy_s(t_thrd.barrier_arch.barrierName, MAX_BARRIER_ID_LENGTH, g_instance.archive_obs_cxt.barrierName, sizeof(g_instance.archive_obs_cxt.barrierName)); securec_check(errorno, "\0", "\0"); + // 检查是否所有节点的barrierLsn都不为0,如果有一个为0,直接返回 for (int i = 0; i < connCnt + 1; i++) { if (g_instance.archive_obs_cxt.barrier_lsn_info[i].barrierLsn == 0x0) { SpinLockRelease(&g_instance.archive_obs_cxt.barrier_lock); return; } } + // 复制barrierLsn信息到局部变量 errorno = memcpy_s(&barrierLsnInfo, sizeof(ArchiveBarrierLsnInfo) * g_instance.archive_obs_cxt.max_node_cnt, g_instance.archive_obs_cxt.barrier_lsn_info, sizeof(ArchiveBarrierLsnInfo) * g_instance.archive_obs_cxt.max_node_cnt); SpinLockRelease(&g_instance.archive_obs_cxt.barrier_lock); securec_check(errorno, "\0", "\0"); - + // 发送Barrier归档请求 SendBarrierArchRequest(handles, connCnt, barrierLsnInfo); - + // 检查Barrier归档命令的状态 CheckBarrierArchCommandStatus(handles, connCnt, t_thrd.barrier_arch.barrierName); - + // 等待Barrier归档完成 WaitBarrierArch(barrierLsnInfo[connCnt].barrierLsn, t_thrd.barrier_arch.slot_name); - + // 将Barrier名称写入归档存储 write_barrier_id_to_obs(t_thrd.barrier_arch.barrierName, archive_obs); } - +// 如果未定义ENABLE_MULTIPLE_NODES,则执行以下代码块 #else +// 用于在单个节点上执行Barrier归档操作,等待特定的LSN完成后将Barrier名称写入归档存储 static void SingleBarrierArch(ArchiveConfig *archive_obs) { XLogRecPtr barrierLsn; - SpinLockAcquire(&g_instance.archive_obs_cxt.barrier_lock); + SpinLockAcquire(&g_instance.archive_obs_cxt.barrier_lock); // 获取全局锁 + // 检查当前的Barrier名称是否和已存储的名称相同,如果相同则释放锁并返回 if (strncmp(t_thrd.barrier_arch.barrierName, g_instance.archive_obs_cxt.barrierName, strlen(g_instance.archive_obs_cxt.barrierName)) == 0) { SpinLockRelease(&g_instance.archive_obs_cxt.barrier_lock); return; } - + // 复制当前的Barrier名称到线程局部变量 errno_t errorno = memcpy_s(t_thrd.barrier_arch.barrierName, MAX_BARRIER_ID_LENGTH, g_instance.archive_obs_cxt.barrierName, sizeof(g_instance.archive_obs_cxt.barrierName)); securec_check(errorno, "\0", "\0"); - + // 复制BarrierLSN到局部变量 barrierLsn = g_instance.archive_obs_cxt.barrierLsn; + // 释放全局锁 SpinLockRelease(&g_instance.archive_obs_cxt.barrier_lock); - + // 等待Barrier归档完成 WaitBarrierArch(barrierLsn, t_thrd.barrier_arch.slot_name); - + // 将Barrier名称写入归档存储 write_barrier_id_to_obs(t_thrd.barrier_arch.barrierName, archive_obs); } #endif +// 归档Barrier线程的入口函数 NON_EXEC_STATIC void BarrierArchMain(knl_thread_arg* arg) { ArchiveSlotConfig *obsArchiveSlot = NULL; @@ -388,25 +448,30 @@ NON_EXEC_STATIC void BarrierArchMain(knl_thread_arg* arg) char username[NAMEDATALEN]; char *dbname = (char *)pstrdup(DEFAULT_DATABASE); - SetProcessingMode(InitProcessing); + SetProcessingMode(InitProcessing); // 设置当前线程的处理模式为初始化模式 + // 设置线程的相关信息配置 t_thrd.role = BARRIER_ARCH; t_thrd.proc_cxt.MyProgName = "BarrierArch"; t_thrd.proc_cxt.MyProcPid = gs_thread_self(); t_thrd.barrier_arch.slot_name = pstrdup((char *)arg->payload); u_sess->attr.attr_common.application_name = pstrdup("BarrierArch"); + // 输出日志,表示归档Barrier线程启动 ereport(LOG, (errmsg("[BarrierArch] barrier arch thread starts. slot name: %s", t_thrd.barrier_arch.slot_name))); - + + // 在进程退出时调用 PGXCNodeCleanAndRelease 函数 on_shmem_exit(PGXCNodeCleanAndRelease, 0); + // 设置信号处理函数 BarrierArchSetupSignalHook(); - + + // 初始化 BaseInit(); - + // 设置数据库和用户信息 t_thrd.proc_cxt.PostInit->SetDatabaseAndUser(dbname, InvalidOid, username); t_thrd.proc_cxt.PostInit->InitBarrierCreator(); - + // 创建一个内存上下文用于执行工作 t_thrd.utils_cxt.CurrentResourceOwner = ResourceOwnerCreate(NULL, "BarrierArch", THREAD_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_STORAGE)); @@ -427,119 +492,139 @@ NON_EXEC_STATIC void BarrierArchMain(knl_thread_arg* arg) * If an exception is encountered, processing resumes here. * See notes in postgres.c about the design of this coding. */ + // 捕获异常 以及进行异常处理 int curTryCounter; int* oldTryCounter = NULL; if (sigsetjmp(localSigjmpBuf, 1) != 0) { gstrace_tryblock_exit(true, oldTryCounter); /* Since not using PG_TRY, must reset error stack by hand */ - t_thrd.log_cxt.error_context_stack = NULL; + t_thrd.log_cxt.error_context_stack = NULL; // 清理错误上下文 /* Prevent interrupts while cleaning up */ - HOLD_INTERRUPTS(); + HOLD_INTERRUPTS(); // 阻止中断信号 /* Report the error to the server log */ - EmitErrorReport(); + EmitErrorReport(); // 将错误信息写入日志 + // 释放资源 /* release resource held by lsc */ - AtEOXact_SysDBCache(false); - + AtEOXact_SysDBCache(false); /* release resource */ - LWLockReleaseAll(); + LWLockReleaseAll(); /* * Now return to normal top-level context and clear ErrorContext for * next time. */ + // 切换回初始内存上下文,清空错误状态 MemoryContextSwitchTo(barrierArchContext); FlushErrorState(); MemoryContextResetAndDeleteChildren(barrierArchContext); /* Now we can allow interrupts again */ - RESUME_INTERRUPTS(); + RESUME_INTERRUPTS(); // 恢复中断处理 /* * Sleep at least 1 second after any error. A write error is likely * to be repeated, and we don't want to be filling the error logs as * fast as we can. */ + // 等待一秒,以防止错误信息频繁写入日志 pg_usleep(1000000L); } - destroy_handles(); - oldTryCounter = gstrace_tryblock_entry(&curTryCounter); + destroy_handles(); // 销毁连接句柄 + oldTryCounter = gstrace_tryblock_entry(&curTryCounter); // 设置捕获异常的计数器,以便后续异常处理 /* We can now handle ereport(ERROR) */ - t_thrd.log_cxt.PG_exception_stack = &localSigjmpBuf; + t_thrd.log_cxt.PG_exception_stack = &localSigjmpBuf; // 将当前的异常捕获状态保存到线程的异常堆栈上 /* * Unblock signals (they were blocked when the postmaster forked us) */ + // 解除在 BarrierArchSetupSignalHook 函数中阻塞的一些信号 gs_signal_setmask(&t_thrd.libpq_cxt.UnBlockSig, NULL); (void)gs_signal_unblock_sigusr2(); - + // 将处理模式设置为正常处理模式,表明线程正在正常运行并处理任务 SetProcessingMode(NormalProcessing); - + // 等待1 秒 pg_usleep_retry(1000000L, 0); - obsArchiveSlot = getArchiveReplicationSlotWithName(t_thrd.barrier_arch.slot_name); - if (obsArchiveSlot == NULL) { - t_thrd.barrier_arch.ready_to_stop = true; - ereport(WARNING, (errmsg("[BarrierArch] obs slot not created."))); + obsArchiveSlot = getArchiveReplicationSlotWithName(t_thrd.barrier_arch.slot_name); // 获取与给定名称匹配的归档复制槽 + if (obsArchiveSlot == NULL) { // 如果找不到匹配的槽 + t_thrd.barrier_arch.ready_to_stop = true; // 准备停止线程 + ereport(WARNING, (errmsg("[BarrierArch] obs slot not created."))); // 输出警告消息 return; } - exec_init_poolhandles(); + exec_init_poolhandles(); // 初始化连接池句柄 #ifdef ENABLE_MULTIPLE_NODES +// 开启多节点编译选项时执行以下代码块 do { + // 如果当前节点不是第一个协调器节点,跳出循环 if (IsFirstCn()) break; - + // 输出日志,显示当前节点不是第一个协调器节点 ereport(DEBUG1, (errmsg("[BarrierArch] Current node is not first node: %s", g_instance.attr.attr_common.PGXCNodeName))); if (IsGotPoolReload()) { + // 如果收到了连接池重载标志,执行连接池重载操作 processPoolerReload(); + // 重置连接池重载标志为 false ResetGotPoolReload(false); } + // 检查是否有中断请求 CHECK_FOR_INTERRUPTS(); + // 暂停 10 秒 pg_usleep(10000000L); } while (1); - + // 获取一个名为 barrier_lock 的自旋锁,用于保护关键资源 SpinLockAcquire(&g_instance.archive_obs_cxt.barrier_lock); + // 如果 barrier_lsn_info 为空,或者 max_node_cnt 为 0 if (g_instance.archive_obs_cxt.barrier_lsn_info == NULL || g_instance.archive_obs_cxt.max_node_cnt == 0) { - SpinLockRelease(&g_instance.archive_obs_cxt.barrier_lock); - ereport(WARNING, (errmsg("[BarrierArch] barrier_lsn_info not alloc."))); + SpinLockRelease(&g_instance.archive_obs_cxt.barrier_lock); // 释放自旋锁 + // 输出警告消息,提示 barrier_lsn_info 未分配 + ereport(WARNING, (errmsg("[BarrierArch] barrier_lsn_info not alloc."))); return; } - SpinLockRelease(&g_instance.archive_obs_cxt.barrier_lock); + SpinLockRelease(&g_instance.archive_obs_cxt.barrier_lock); // 释放自旋锁 #endif +// 结束多节点编译选项的条件编译 + // 输出日志,显示正在初始化与协调器和数据节点的连接,以及数据节点和协调器的数量 ereport(DEBUG1, (errmsg("[BarrierArch] Init connections with CN/DN, dn count : %d, cn count : %d", u_sess->pgxc_cxt.NumDataNodes, u_sess->pgxc_cxt.NumCoords))); + // 进入循环,只要 ready_to_stop 标志不为真,就一直执行循环体 while (!t_thrd.barrier_arch.ready_to_stop) { - CHECK_FOR_INTERRUPTS(); - + CHECK_FOR_INTERRUPTS(); // 检查是否有中断请求 + // 如果 barrierName 为空或长度为 0 if (g_instance.archive_obs_cxt.barrierName == NULL || strlen(g_instance.archive_obs_cxt.barrierName) == 0) { - ereport(WARNING, (errmsg("[BarrierArch] barrierName is null."))); + ereport(WARNING, (errmsg("[BarrierArch] barrierName is null."))); // 输出警告消息,提示 barrierName 为空 break; } #ifdef ENABLE_MULTIPLE_NODES + // 开启了多节点编译选项时执行以下代码块 + // 如果收到了连接池重载标志 if (IsGotPoolReload()) { - processPoolerReload(); - ResetGotPoolReload(false); - if (!IsFirstCn()) + processPoolerReload(); // 执行连接池重载操作 + ResetGotPoolReload(false); // 重置连接池重载标志为 false + if (!IsFirstCn()) // 如果当前节点不是第一个协调器节点,跳出循环 break; } - - PGXCNodeAllHandles* handles = GetAllNodesHandles(); - + // 获取所有节点的连接句柄 + PGXCNodeAllHandles* handles = GetAllNodesHandles(); + // 调用 QueryBarrierArch 函数处理Barrier归档 QueryBarrierArch(handles, &obsArchiveSlot->archive_config); - + // 释放所有节点的连接句柄内存 pfree_pgxc_all_handles(handles); #else +// 没有开启多节点编译选项时执行以下代码块 + // 调用 SingleBarrierArch 函数处理Barrie归档 SingleBarrierArch(&obsArchiveSlot->archive_config); #endif } + // 输出日志,显示障碍归档线程已退出 ereport(LOG, (errmsg("[BarrierArch] barrier arch thread exits."))); } diff --git a/src/gausskernel/process/postmaster/barrier_creator.cpp b/src/gausskernel/process/postmaster/barrier_creator.cpp index 3715ee3c8..a7eedfe61 100755 --- a/src/gausskernel/process/postmaster/barrier_creator.cpp +++ b/src/gausskernel/process/postmaster/barrier_creator.cpp @@ -52,100 +52,116 @@ const int BARRIER_NAME_LEN = 40; const char* CSN_BARRIER_PATTREN_STR = "csn_%021lu_%013ld"; const char* CSN_SWITCHOVER_BARRIER_PATTREN_STR = "csn_%021lu_dr_switchover"; +/* +作用: 生成用于Barrier归档的名称 +参数: barrierRet:字符数组指针,存储生成的 Barrier 名称 + isSwitchoverBarrier:表示是否为切换 Barrier +*/ void GetCsnBarrierName(char* barrierRet, bool isSwitchoverBarrier) { - struct timeval tv; - int rc; - CommitSeqNo csn; + struct timeval tv; // 用于存储时间的结构体变量 + int rc; // 用于存储函数返回值的变量 + CommitSeqNo csn; // 用于存储事务提交序列号的变量 + // 如果处于GTM模式,获取全局事务管理器的事务提交序列号 if (GTM_MODE) csn = GetCSNGTM(); else - csn = CommitCSNGTM(false); + csn = CommitCSNGTM(false); // 否则获取本地事务提交序列号 - gettimeofday(&tv, NULL); + gettimeofday(&tv, NULL); // 获取当前时间信息 - if (isSwitchoverBarrier) { + if (isSwitchoverBarrier) { // 如果要切换Barrier + // 构造用于切换Barrier的名称,将CSN插入到特定的格式字符串中 rc = snprintf_s(barrierRet, BARRIER_NAME_LEN, BARRIER_NAME_LEN - 1, CSN_SWITCHOVER_BARRIER_PATTREN_STR, csn); } else { + // 构造普通Barrier的名称,将CSN和时间戳信息插入到格式字符串中 rc = snprintf_s(barrierRet, BARRIER_NAME_LEN, BARRIER_NAME_LEN - 1, CSN_BARRIER_PATTREN_STR, csn, TIME_GET_MILLISEC(tv)); } - securec_check_ss_c(rc, "\0", "\0"); + securec_check_ss_c(rc, "\0", "\0"); // 检查格式化操作的返回值,确保操作成功 + // 记录调试日志,输出生成的Barrier名称和对应的CSN elog(DEBUG1, "GetCsnBarrierName csn = %lu, barrier_name = %s", csn, barrierRet); } - +/* 根据传入的CSN Barrier名称,解析出其中的CSN值,并返回 + 参数: csnBarrier 表示 CSN Barrier 的名称 + 返回值:解析出的 CSN 值(解析成功) +*/ CommitSeqNo CsnBarrierNameGetCsn(const char *csnBarrier) { CommitSeqNo csn; long ts = 0; + // 使用格式化字符串解析CSN Barrier名称,提取其中的CSN值和时间戳(如果有) if ((strstr(csnBarrier, "_dr_switchover") != NULL && sscanf_s(csnBarrier, CSN_SWITCHOVER_BARRIER_PATTREN_STR, &csn) == 1) || sscanf_s(csnBarrier, CSN_BARRIER_PATTREN_STR, &csn, &ts) == 2) { return csn; } - return 0; + return 0; // 解析失败时返回0 } - +// 根据传入的CSN Barrier名称,解析出其中的时间戳值,并返回 int64 CsnBarrierNameGetTimeStamp(const char *csnBarrier) { CommitSeqNo csn; int64 ts = 0; + // 使用格式化字符串解析CSN Barrier名称,提取其中的CSN值和时间戳(如果有) if (sscanf_s(csnBarrier, CSN_BARRIER_PATTREN_STR, &csn, &ts) == 2) { return ts; } - return 0; + return 0; // 解析失败时返回0 } - +// 判断传入的CSN Barrier名称是否为切换Barrier bool IsSwitchoverBarrier(const char *csnBarrier) { + // 判断CSN Barrier名称是否符合要求,且是否包含 "_dr_switchover" 子串 if (!IS_CSN_BARRIER(csnBarrier) || (strstr(csnBarrier, "_dr_switchover") == NULL)) { return false; } - return true; + return true; // 如果符合要求,则认为是切换Barrier } - +// 判断当前节点是否为第一个执行的协调器节点 bool IsFirstCn() { - char *firstExecNode = find_first_exec_cn(); + char *firstExecNode = find_first_exec_cn(); // 查找第一个执行的协调器节点 + // 将当前节点的名称与firstExecNode进行比较,如果两者相同,则返回true,表示当前节点是第一个执行的协调器节点;否则返回false return (strcmp(firstExecNode, g_instance.attr.attr_common.PGXCNodeName) == 0); } - +// 关闭Barrier创建线程 void barrier_creator_thread_shutdown(void) { - g_instance.barrier_creator_cxt.stop = true; - ereport(LOG, (errmsg("[BarrierCreator] barrier creator thread shutting down."))); + g_instance.barrier_creator_cxt.stop = true; // 设置标志,表示停止线程 + ereport(LOG, (errmsg("[BarrierCreator] barrier creator thread shutting down."))); // 输出日志 } - +// SIGHUP信号处理函数,用于重新加载配置 static void barrier_creator_sighup_handler(SIGNAL_ARGS) { - int save_errno = errno; - t_thrd.barrier_creator_cxt.got_SIGHUP = true; - errno = save_errno; -} + int save_errno = errno; // 保存当前errno + t_thrd.barrier_creator_cxt.got_SIGHUP = true; // 设置标志,表示收到SIGHUP信号 + errno = save_errno; // 恢复errno +} /* Reset some signals that are accepted by postmaster but not here */ static void barrier_creator_setup_signal_hook(void) { - (void)gspqsignal(SIGHUP, barrier_creator_sighup_handler); - (void)gspqsignal(SIGINT, SIG_IGN); - (void)gspqsignal(SIGTERM, die); - (void)gspqsignal(SIGQUIT, quickdie); - (void)gspqsignal(SIGALRM, SIG_IGN); - (void)gspqsignal(SIGPIPE, SIG_IGN); - (void)gspqsignal(SIGUSR1, procsignal_sigusr1_handler); - (void)gspqsignal(SIGUSR2, SIG_IGN); + (void)gspqsignal(SIGHUP, barrier_creator_sighup_handler); // 设置SIGHUP信号处理函数 + (void)gspqsignal(SIGINT, SIG_IGN); // 忽略SIGINT信号 + (void)gspqsignal(SIGTERM, die); // 设置SIGTERM信号处理函数为die + (void)gspqsignal(SIGQUIT, quickdie); // 设置SIGQUIT信号处理函数为quickdie + (void)gspqsignal(SIGALRM, SIG_IGN); // 忽略SIGALRM信号 + (void)gspqsignal(SIGPIPE, SIG_IGN); // 忽略SIGPIPE信号 + (void)gspqsignal(SIGUSR1, procsignal_sigusr1_handler); // 设置SIGUSR1信号处理函数为procsignal_sigusr1_handler + (void)gspqsignal(SIGUSR2, SIG_IGN); // 忽略SIGUSR2信号 - (void)gspqsignal(SIGCHLD, SIG_DFL); - (void)gspqsignal(SIGTTIN, SIG_DFL); - (void)gspqsignal(SIGTTOU, SIG_DFL); - (void)gspqsignal(SIGCONT, SIG_DFL); - (void)gspqsignal(SIGWINCH, SIG_DFL); + (void)gspqsignal(SIGCHLD, SIG_DFL); // 设置SIGCHLD信号处理函数为默认 + (void)gspqsignal(SIGTTIN, SIG_DFL); // 设置SIGTTIN信号处理函数为默认 + (void)gspqsignal(SIGTTOU, SIG_DFL); // 设置SIGTTOU信号处理函数为默认 + (void)gspqsignal(SIGCONT, SIG_DFL); // 设置SIGCONT信号处理函数为默认 + (void)gspqsignal(SIGWINCH, SIG_DFL); // 设置SIGWINCH信号处理函数为默认 /* We allow SIGQUIT (quickdie) at all times */ - (void)sigdelset(&t_thrd.libpq_cxt.BlockSig, SIGQUIT); + (void)sigdelset(&t_thrd.libpq_cxt.BlockSig, SIGQUIT); // 允许随时处理SIGQUIT(quickdie)信号 } - +// 从OBS中读取Barrier标识 static uint64_t read_barrier_id_from_obs(const char *slotName, long *currBarrierTime) { char barrier_name[BARRIER_NAME_LEN]; @@ -153,146 +169,153 @@ static uint64_t read_barrier_id_from_obs(const char *slotName, long *currBarrier uint64_t barrier_id; if (ArchiveReplicationReadFile(BARRIER_FILE, (char *)barrier_name, MAX_BARRIER_ID_LENGTH, slotName)) { - barrier_name[BARRIER_NAME_LEN - 1] = '\0'; + barrier_name[BARRIER_NAME_LEN - 1] = '\0'; // 将读取的数据末尾设置为字符串结束符 + // 输出日志,表示从归档存储中读取了barrier ID ereport(LOG, (errmsg("[BarrierCreator] read barrier id from obs %s", barrier_name))); } else { + // 输出日志,表示从归档存储中读取barrier ID失败,将从0开始 ereport(LOG, (errmsg("[BarrierCreator] failed to read barrier id from obs, start barrier from 0"))); return 0; } #ifdef ENABLE_MULTIPLE_NODES + // 根据不同的编译选项解析Barrier名称,更新barrier_id和currBarrierTime ret = sscanf_s(barrier_name, "csn_%021" PRIu64 "_%013ld", &barrier_id, currBarrierTime); #else ret = sscanf_s(barrier_name, "hadr_%020" PRIu64 "_%013ld", &barrier_id, currBarrierTime); #endif - + // 如果解析成功,更新barrier_id并返回 if (ret == 2) { barrier_id++; return barrier_id; } - return 0; + return 0; // 解析失败,返回0 } - +// 获取归档槽中的最大barrier索引和最新的barrier时间 uint64_t GetObsBarrierIndex(const List *archiveSlotNames, long *last_barrier_time) { - uint64_t maxIndex = 0; - long maxBarrierTime = 0; - foreach_cell(cell, archiveSlotNames) { - long currBarrierTime = 0; - char* slotName = (char*)lfirst(cell); - if (slotName == NULL || strlen(slotName) == 0) { + uint64_t maxIndex = 0; // 最大barrier索引 + long maxBarrierTime = 0; // 最大barrier时间 + foreach_cell(cell, archiveSlotNames) { // 遍历归档槽名称列表 + long currBarrierTime = 0; // 当前barrier时间 + char* slotName = (char*)lfirst(cell); // 获取归档槽名称 + if (slotName == NULL || strlen(slotName) == 0) { // 如果归档槽名称为空,跳过本次循环 continue; } - uint64_t readIndex = read_barrier_id_from_obs(slotName, &currBarrierTime); - maxIndex = (readIndex > maxIndex) ? readIndex : maxIndex; - maxBarrierTime = (currBarrierTime > maxBarrierTime) ? currBarrierTime : maxBarrierTime; + uint64_t readIndex = read_barrier_id_from_obs(slotName, &currBarrierTime); // 从归档存储中读取barrier索引和时间 + maxIndex = (readIndex > maxIndex) ? readIndex : maxIndex; // 更新最大barrier索引 + maxBarrierTime = (currBarrierTime > maxBarrierTime) ? currBarrierTime : maxBarrierTime; // 更新最大barrier时间 } - *last_barrier_time = maxBarrierTime; + *last_barrier_time = maxBarrierTime; // 将最大barrier时间赋值给指针参数 - return maxIndex; + return maxIndex; // 返回最大barrier索引 } - +// 获取归档槽中第一个协调器节点的barrier时间线 uint64 GetObsFirstCNBarrierTimeline(const List *archiveSlotNames) { - uint64 timeline = 0; + uint64 timeline = 0; // 时间线初始值为0 - foreach_cell(cell, archiveSlotNames) { - char* slotName = (char*)lfirst(cell); - if (slotName == NULL || strlen(slotName) == 0) { + foreach_cell(cell, archiveSlotNames) { // 遍历归档槽名称列表 + char* slotName = (char*)lfirst(cell); // 获取归档槽名称 + if (slotName == NULL || strlen(slotName) == 0) { // 如果归档槽名称为空,跳过本次循环 continue; } - timeline = ReadBarrierTimelineRecordFromObs(slotName); - break; + timeline = ReadBarrierTimelineRecordFromObs(slotName); // 从归档存储中读取协调器节点的barrier时间线 + break; // 跳出循环,只获取第一个归档槽的时间线 } - return timeline; + return timeline; // 返回获取的时间线 } #ifdef ENABLE_MULTIPLE_NODES +// 分配和初始化BarrierLsnInfo数组 static void AllocBarrierLsnInfo(int nodeSize) { int rc; g_instance.archive_obs_cxt.barrier_lsn_info = (ArchiveBarrierLsnInfo *)palloc0( - sizeof(ArchiveBarrierLsnInfo) * nodeSize); + sizeof(ArchiveBarrierLsnInfo) * nodeSize); // 分配内存 rc = memset_s(g_instance.archive_obs_cxt.barrier_lsn_info, sizeof(ArchiveBarrierLsnInfo) * nodeSize, 0, - sizeof(ArchiveBarrierLsnInfo) * nodeSize); - securec_check(rc, "", ""); + sizeof(ArchiveBarrierLsnInfo) * nodeSize); // 将分配的内存内容初始化为0 + securec_check(rc, "", ""); // 检查内存操作是否成功,如果不成功,输出错误信息 } #endif #ifdef ENABLE_MULTIPLE_NODES +// BarrierCreator连接池重新加载 static void BarrierCreatorPoolerReload(void) { - destroy_handles(); - processPoolerReload(); + destroy_handles(); // 销毁连接句柄 + processPoolerReload(); // 重新加载连接池 ereport(LOG, (errmsg("[BarrierCreatorPoolerReload] Reload connections with CN/DN, dn count : %d, cn count : %d", u_sess->pgxc_cxt.NumDataNodes, u_sess->pgxc_cxt.NumCoords))); if (g_instance.archive_obs_cxt.archive_slot_num == 0) { - return; + return; // 如果没有归档槽,直接返回 } int maxNodeCnt = *t_thrd.pgxc_cxt.shmemNumCoords + *t_thrd.pgxc_cxt.shmemNumDataNodes; - if (maxNodeCnt > g_instance.archive_obs_cxt.max_node_cnt) { + if (maxNodeCnt > g_instance.archive_obs_cxt.max_node_cnt) { // 如果节点数量超过最大值 SpinLockAcquire(&g_instance.archive_obs_cxt.barrier_lock); - g_instance.archive_obs_cxt.max_node_cnt = 0; + g_instance.archive_obs_cxt.max_node_cnt = 0; // 清零最大节点数量 SpinLockRelease(&g_instance.archive_obs_cxt.barrier_lock); int nodeSize = maxNodeCnt; if (g_instance.archive_obs_cxt.barrier_lsn_info != NULL) { - pfree_ext(g_instance.archive_obs_cxt.barrier_lsn_info); + pfree_ext(g_instance.archive_obs_cxt.barrier_lsn_info); // 释放旧的BarrierLsnInfo数组内存 } - AllocBarrierLsnInfo(nodeSize); + AllocBarrierLsnInfo(nodeSize); // 重新分配和初始化BarrierLsnInfo数组 SpinLockAcquire(&g_instance.archive_obs_cxt.barrier_lock); - g_instance.archive_obs_cxt.max_node_cnt = nodeSize; + g_instance.archive_obs_cxt.max_node_cnt = nodeSize; // 更新最大节点数量 SpinLockRelease(&g_instance.archive_obs_cxt.barrier_lock); } } #endif - +// 用于释放之前分配的存储归档操作的Barrier LSN信息的内存 static void FreeBarrierLsnInfo() { - SpinLockAcquire(&g_instance.archive_obs_cxt.barrier_lock); - g_instance.archive_obs_cxt.max_node_cnt = 0; - SpinLockRelease(&g_instance.archive_obs_cxt.barrier_lock); - pfree_ext(g_instance.archive_obs_cxt.barrier_lsn_info); + SpinLockAcquire(&g_instance.archive_obs_cxt.barrier_lock); // 获取全局锁 + g_instance.archive_obs_cxt.max_node_cnt = 0; // 将最大节点数设置为0 + SpinLockRelease(&g_instance.archive_obs_cxt.barrier_lock); // 释放全局锁 + pfree_ext(g_instance.archive_obs_cxt.barrier_lsn_info); // 释放分配的内存 } - +// 创建不同类型的 Barrier,以实现数据一致性和灾备需求 void barrier_creator_main(void) { - uint64_t index = 0; - long last_barrier_time = 0; - struct timeval tv; - int rc; - char barrier_name[BARRIER_NAME_LEN]; - List* archiveSlotNames; - MemoryContext barrier_creator_context; - sigjmp_buf local_sigjmp_buf; - t_thrd.barrier_creator_cxt.is_first_barrier = true; - char username[NAMEDATALEN]; - char *dbname = (char *)pstrdup(DEFAULT_DATABASE); - bool startCsnBarrier = g_instance.attr.attr_storage.auto_csn_barrier; + uint64_t index = 0; // 初始化用于记录创建Barrier的索引 + long last_barrier_time = 0; // 初始化上一个Barrier的时间 + struct timeval tv; // 用于获取当前时间 + int rc; // 用于记录函数返回值 + char barrier_name[BARRIER_NAME_LEN]; // 用于存储Barrier名称的字符数组 + List* archiveSlotNames; // 存储归档槽名的列表 + MemoryContext barrier_creator_context; // 用于存储Barrier Creator线程的内存上下文 + sigjmp_buf local_sigjmp_buf; // 用于实现异常处理跳转 + t_thrd.barrier_creator_cxt.is_first_barrier = true; // 标识是否是首次创建Barrier + char username[NAMEDATALEN]; // 存储用户名的字符数组 + char *dbname = (char *)pstrdup(DEFAULT_DATABASE); // 存储默认数据库名称的指针 + bool startCsnBarrier = g_instance.attr.attr_storage.auto_csn_barrier; // 表示是否启用自动CSN Barrier的标志 // use InnerMaintenanceTools mode to avoid deadlock with thread pool - u_sess->proc_cxt.IsInnerMaintenanceTools = true; - ereport(LOG, (errmsg("[BarrierCreator] barrier creator started"))); - g_instance.archive_obs_cxt.max_node_cnt = 0; - SetProcessingMode(InitProcessing); + u_sess->proc_cxt.IsInnerMaintenanceTools = true; // 使用InnerMaintenanceTools模式,以避免与线程池产生死锁 + ereport(LOG, (errmsg("[BarrierCreator] barrier creator started"))); // 输出日志,表示Barrier Creator线程已启动 + g_instance.archive_obs_cxt.max_node_cnt = 0; // 初始化最大节点数为0 + SetProcessingMode(InitProcessing); // 设置处理模式为InitProcessing - t_thrd.role = BARRIER_CREATOR; - t_thrd.proc_cxt.MyProgName = "BarrierCreator"; - t_thrd.proc_cxt.MyProcPid = gs_thread_self(); - u_sess->attr.attr_common.application_name = pstrdup("BarrierCreator"); - g_instance.barrier_creator_cxt.stop = false; + t_thrd.role = BARRIER_CREATOR; // 设置线程的角色为BARRIER_CREATOR + t_thrd.proc_cxt.MyProgName = "BarrierCreator"; // 设置线程的程序名称为"BarrierCreator" + t_thrd.proc_cxt.MyProcPid = gs_thread_self(); // 设置线程的进程ID为当前线程ID + u_sess->attr.attr_common.application_name = pstrdup("BarrierCreator"); // 设置应用程序名称为"BarrierCreator" + g_instance.barrier_creator_cxt.stop = false; // 初始化停止标志为false,表示Barrier Creator线程不停止 - on_shmem_exit(PGXCNodeCleanAndRelease, 0); + on_shmem_exit(PGXCNodeCleanAndRelease, 0); // 注册一个在进程退出时执行的回调函数,用于清理PGXC节点资源 - barrier_creator_setup_signal_hook(); + barrier_creator_setup_signal_hook(); // 设置信号处理函数 - BaseInit(); + BaseInit(); // 初始化 + // 设置当前线程的数据库和用户信息 t_thrd.proc_cxt.PostInit->SetDatabaseAndUser(dbname, InvalidOid, username); + // 初始化Barrier Creator模块的上下文 t_thrd.proc_cxt.PostInit->InitBarrierCreator(); - + // 创建一个新的资源拥有者,用于管理Barrier Creator线程的资源 t_thrd.utils_cxt.CurrentResourceOwner = ResourceOwnerCreate(NULL, "BarrierCreator", THREAD_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_STORAGE)); @@ -302,6 +325,7 @@ void barrier_creator_main(void) * possible memory leaks. Formerly this code just ran in * t_thrd.top_mem_cxt, but resetting that would be a really bad idea. */ + // 创建内存上下文 barrier_creator_context = AllocSetContextCreate(t_thrd.top_mem_cxt, "BarrierCreator", ALLOCSET_DEFAULT_MINSIZE, @@ -313,83 +337,92 @@ void barrier_creator_main(void) * If an exception is encountered, processing resumes here. * See notes in postgres.c about the design of this coding. */ + // 如果遇到异常,将从这里恢复 int curTryCounter; int *oldTryCounter = NULL; if (sigsetjmp(local_sigjmp_buf, 1) != 0) { - destroy_handles(); + destroy_handles(); // 销毁句柄 gstrace_tryblock_exit(true, oldTryCounter); /* Since not using PG_TRY, must reset error stack by hand */ + // 重置错误堆栈 t_thrd.log_cxt.error_context_stack = NULL; t_thrd.log_cxt.call_stack = NULL; /* Prevent interrupts while cleaning up */ - HOLD_INTERRUPTS(); + HOLD_INTERRUPTS(); // 在清理期间阻止中断 /* Report the error to the server log */ - EmitErrorReport(); + EmitErrorReport(); // 将错误报告记录到服务器日志中 /* release resource held by lsc */ - AtEOXact_SysDBCache(false); + AtEOXact_SysDBCache(false); // 释放lsc持有的资源 /* release resource */ - LWLockReleaseAll(); + // 释放资源 + LWLockReleaseAll(); // 释放所有的轻量级锁 - FreeBarrierLsnInfo(); + FreeBarrierLsnInfo(); // 释放Barrier信息结构体的内存 /* * Now return to normal top-level context and clear ErrorContext for * next time. */ - MemoryContextSwitchTo(barrier_creator_context); - FlushErrorState(); - MemoryContextResetAndDeleteChildren(barrier_creator_context); + MemoryContextSwitchTo(barrier_creator_context); // 切换到 barrier_creator_context 内存上下文 + FlushErrorState(); // 清除错误状态信息 + MemoryContextResetAndDeleteChildren(barrier_creator_context); // 重置并删除 barrier_creator_context 内存上下文的子节点 /* Now we can allow interrupts again */ - RESUME_INTERRUPTS(); + RESUME_INTERRUPTS(); // 恢复中断处理 return; } + // 进入异常处理尝试块,保存旧的异常处理计数器并获取当前计数器 oldTryCounter = gstrace_tryblock_entry(&curTryCounter); /* We can now handle ereport(ERROR) */ - t_thrd.log_cxt.PG_exception_stack = &local_sigjmp_buf; + t_thrd.log_cxt.PG_exception_stack = &local_sigjmp_buf; // 将本地的跳转缓冲区设置为异常处理的跳转缓冲区 /* * Unblock signals (they were blocked when the postmaster forked us) */ - gs_signal_setmask(&t_thrd.libpq_cxt.UnBlockSig, NULL); - (void)gs_signal_unblock_sigusr2(); - SetProcessingMode(NormalProcessing); - exec_init_poolhandles(); + gs_signal_setmask(&t_thrd.libpq_cxt.UnBlockSig, NULL); // 解除先前阻塞的信号 + (void)gs_signal_unblock_sigusr2(); // 解除对 SIGUSR2 信号的阻塞 + SetProcessingMode(NormalProcessing); // 设置处理模式为正常处理模式 + exec_init_poolhandles(); // 初始化连接池句柄 #ifdef ENABLE_MULTIPLE_NODES /* * Ensure all barrier commond execuet on first coordinator */ do { - if (IsFirstCn()) + if (IsFirstCn()) // 如果当前节点是第一个协调器节点 break; - + // 输出调试信息,指示当前节点不是第一个协调器节点 ereport(DEBUG1, (errmsg("[BarrierCreator] Current node is not first node: %s", g_instance.attr.attr_common.PGXCNodeName))); + // 如果收到重新加载连接池的信号,重新加载连接池并重置标志 if (IsGotPoolReload()) { - BarrierCreatorPoolerReload(); - ResetGotPoolReload(false); + BarrierCreatorPoolerReload(); // 重新加载连接池 + ResetGotPoolReload(false); // 重置标志,表示已处理连接池重新加载 } - CHECK_FOR_INTERRUPTS(); - pg_usleep(1000000L); + CHECK_FOR_INTERRUPTS(); // 检查是否收到中断信号,如果是则处理中断 + pg_usleep(1000000L); // 休眠1秒钟 } while (1); #endif ereport(DEBUG1, (errmsg("[BarrierCreator] Init connections with CN/DN, dn count : %d, cn count : %d", u_sess->pgxc_cxt.NumDataNodes, u_sess->pgxc_cxt.NumCoords))); + // 输出日志,指明当前节点是 barrier creator ereport(LOG, (errmsg("[BarrierCreator] %s is barrier creator", g_instance.attr.attr_common.PGXCNodeName))); + // 设置停止标志为 false,表示不停止 barrier creator 线程 g_instance.barrier_creator_cxt.stop = false; - if (g_instance.archive_obs_cxt.archive_slot_num != 0) { + if (g_instance.archive_obs_cxt.archive_slot_num != 0) { // 如果存在归档槽位 t_thrd.barrier_creator_cxt.archive_slot_names = GetAllArchiveSlotsName(); if (t_thrd.barrier_creator_cxt.archive_slot_names == NIL || t_thrd.barrier_creator_cxt.archive_slot_names->length == 0) { - return; + return; // 没有获取到归档槽位名称,直接返回 } + // 获取归档槽位的 barrier 索引和last barrier 时间 index = GetObsBarrierIndex(t_thrd.barrier_creator_cxt.archive_slot_names, &last_barrier_time); + // 获取归档槽位的 barrier 索引和最后的 barrier 时间 t_thrd.barrier_creator_cxt.first_cn_timeline = GetObsFirstCNBarrierTimeline(t_thrd.barrier_creator_cxt.archive_slot_names); /* @@ -397,83 +430,94 @@ void barrier_creator_main(void) * wait for a while to prevent barrier time rollback. */ do { - gettimeofday(&tv, NULL); + gettimeofday(&tv, NULL); // 获取当前时间 long current_time = TIME_GET_MILLISEC(tv); - if (last_barrier_time < current_time) { + if (last_barrier_time < current_time) { // 如果最后一个barrier时间比当前时间小,则跳出循环 break; } + // 计算时间差并打印日志信息 long time_diff = last_barrier_time - current_time; ereport(LOG, (errmsg("[BarrierCreator] current time %ld is smaller than barrier time %ld, and sleep %ld ms", current_time, last_barrier_time, time_diff))); - CHECK_FOR_INTERRUPTS(); - pg_usleep(time_diff * 1000L); + CHECK_FOR_INTERRUPTS(); // 检查是否收到中断信号,如果有,则处理中断 + pg_usleep(time_diff * 1000L); // 休眠指定的时间差 } while (1); + // 如果是第一个barrier,记录全局barrier列表的开始时间 if (t_thrd.barrier_creator_cxt.is_first_barrier) { gettimeofday(&tv, NULL); WriteGlobalBarrierListStartTimeOnMedia(TIME_GET_MILLISEC(tv)); } #ifdef ENABLE_MULTIPLE_NODES +// 如果启用了多节点模式 while (!START_AUTO_CSN_BARRIER) { - CHECK_FOR_INTERRUPTS(); - pg_usleep(1000000L); + // 在未收到 START_AUTO_CSN_BARRIER 信号之前,循环等待 + CHECK_FOR_INTERRUPTS(); // 检查是否收到中断信号,如果有,则处理中断 + pg_usleep(1000000L); // 等待1秒 } #endif } #ifdef ENABLE_MULTIPLE_NODES - CleanupBarrierLock(); +// 如果启用了多节点模式 + CleanupBarrierLock(); // 清理barrier锁 #endif while (!g_instance.barrier_creator_cxt.stop) { if (t_thrd.barrier_creator_cxt.got_SIGHUP) { + // 如果收到 SIGHUP 信号,执行配置文件处理 t_thrd.barrier_preparse_cxt.got_SIGHUP = false; ProcessConfigFile(PGC_SIGHUP); startCsnBarrier = g_instance.attr.attr_storage.auto_csn_barrier; } /* in hadr switchover, barrier creator thread stop creating new barriers during service truncate.*/ + // 如果归档槽数量不为0且服务截断标志为true if (g_instance.archive_obs_cxt.archive_slot_num != 0 && g_instance.archive_obs_cxt.in_service_truncate == true) { - continue; + continue; // 在服务截断期间继续循环下一次迭代 } - if (g_instance.archive_obs_cxt.archive_slot_num != 0) { + if (g_instance.archive_obs_cxt.archive_slot_num != 0) { // 如果存在归档槽 if (t_thrd.barrier_creator_cxt.barrier_update_last_time_info == NULL) { + // 如果barrier更新的时间信息为空,则分配内存并初始化为0 t_thrd.barrier_creator_cxt.barrier_update_last_time_info = (BarrierUpdateLastTimeInfo*)palloc0( sizeof(BarrierUpdateLastTimeInfo) * g_instance.attr.attr_storage.max_replication_slots); } #ifdef ENABLE_MULTIPLE_NODES - if (g_instance.archive_obs_cxt.barrier_lsn_info == NULL) { + if (g_instance.archive_obs_cxt.barrier_lsn_info == NULL) { // 如果barrier LSN 信息为空 int nodeSize = *t_thrd.pgxc_cxt.shmemNumCoords + *t_thrd.pgxc_cxt.shmemNumDataNodes; - AllocBarrierLsnInfo(nodeSize); - SpinLockAcquire(&g_instance.archive_obs_cxt.barrier_lock); - g_instance.archive_obs_cxt.max_node_cnt = nodeSize; - SpinLockRelease(&g_instance.archive_obs_cxt.barrier_lock); + AllocBarrierLsnInfo(nodeSize); // 分配barrier LSN 信息的内存 + SpinLockAcquire(&g_instance.archive_obs_cxt.barrier_lock); // 获取全局barrier锁 + g_instance.archive_obs_cxt.max_node_cnt = nodeSize; // 设置最大节点数为指定值 + SpinLockRelease(&g_instance.archive_obs_cxt.barrier_lock); // 释放全局barrier锁 } #endif - archiveSlotNames = GetAllArchiveSlotsName(); - if (archiveSlotNames == NIL || archiveSlotNames->length == 0) { + archiveSlotNames = GetAllArchiveSlotsName(); // 获取所有归档槽的名称 + if (archiveSlotNames == NIL || archiveSlotNames->length == 0) { // 如果无法获取归档槽名称,发出警告并返回 ereport(WARNING, (errmsg("[BarrierCreator] could not get archive slot name when barrier start"))); return; } - if (t_thrd.barrier_creator_cxt.archive_slot_names == NULL) { - t_thrd.barrier_creator_cxt.archive_slot_names = archiveSlotNames; + if (t_thrd.barrier_creator_cxt.archive_slot_names == NULL) { // 如果归档槽名称尚未初始化 + t_thrd.barrier_creator_cxt.archive_slot_names = archiveSlotNames; // 获取第一个协调器节点的时间线信息 t_thrd.barrier_creator_cxt.first_cn_timeline = GetObsFirstCNBarrierTimeline(t_thrd.barrier_creator_cxt.archive_slot_names); } - if (archiveSlotNames->length > t_thrd.barrier_creator_cxt.archive_slot_names->length) { + if (archiveSlotNames->length > t_thrd.barrier_creator_cxt.archive_slot_names->length) { // 如果当前归档槽数量大于之前记录的数量 t_thrd.barrier_creator_cxt.archive_slot_names = archiveSlotNames; - t_thrd.barrier_creator_cxt.is_first_barrier = true; - gettimeofday(&tv, NULL); - WriteGlobalBarrierListStartTimeOnMedia(TIME_GET_MILLISEC(tv)); + t_thrd.barrier_creator_cxt.is_first_barrier = true; // 将标志位设为true,表示是第一次创建屏障 + gettimeofday(&tv, NULL); // 获取当前时间 + WriteGlobalBarrierListStartTimeOnMedia(TIME_GET_MILLISEC(tv)); // 记录全局屏障列表的开始时间 } else if (archiveSlotNames->length < t_thrd.barrier_creator_cxt.archive_slot_names->length) { + // 如果当前归档槽数量小于之前记录的数量 t_thrd.barrier_creator_cxt.archive_slot_names = archiveSlotNames; } } - pg_usleep_retry(500000L, 0); + pg_usleep_retry(500000L, 0); if (!startCsnBarrier && g_instance.archive_obs_cxt.archive_slot_num == 0) { + // 如果不需要启动CSN barrier且归档槽数量为0 g_instance.barrier_creator_cxt.stop = true; for (int i = 0; i < g_instance.attr.attr_storage.max_replication_slots; i++) { if (g_instance.archive_thread_info.obsBarrierArchPID[i] != 0) { + // 向子进程发送退出信号 signal_child(g_instance.archive_thread_info.obsBarrierArchPID[i], SIGUSR2, -1); } } @@ -483,41 +527,47 @@ void barrier_creator_main(void) /* create barrier with increasing index */ #ifdef ENABLE_MULTIPLE_NODES - if (IsGotPoolReload()) { + if (IsGotPoolReload()) { // 如果收到重新加载的信号 BarrierCreatorPoolerReload(); ResetGotPoolReload(false); - if (!IsFirstCn()) + if (!IsFirstCn()) // 如果当前节点不是第一个协调器节点 break; } ereport(DEBUG1, (errmsg("[BarrierCreator] auto_csn_barrier: %d", startCsnBarrier))); - if (startCsnBarrier) { - rc = snprintf_s(barrier_name, BARRIER_NAME_LEN, BARRIER_NAME_LEN - 1, CSN_BARRIER_NAME); + if (startCsnBarrier) { // 如果启用了自动CSN barrier + rc = snprintf_s(barrier_name, BARRIER_NAME_LEN, BARRIER_NAME_LEN - 1, CSN_BARRIER_NAME); //构造CSN barrier名称 securec_check_ss_c(rc, "\0", "\0"); - RequestBarrier(barrier_name, NULL); - ereport(LOG, (errmsg("[BarrierCreator]barrier %s created", barrier_name))); + RequestBarrier(barrier_name, NULL); // 请求创建CSN barrier + ereport(LOG, (errmsg("[BarrierCreator]barrier %s created", barrier_name))); // 日志中记录barrier的创建 } #else + //构造CSN barrier名称 rc = snprintf_s(barrier_name, BARRIER_NAME_LEN, BARRIER_NAME_LEN - 1, "hadr_%020" PRIu64 "_%013ld", index, TIME_GET_MILLISEC(tv)); securec_check_ss_c(rc, "\0", "\0"); - DisasterRecoveryRequestBarrier(barrier_name); - ereport(LOG, (errmsg("[BarrierCreator] barrier %s created", barrier_name))); + DisasterRecoveryRequestBarrier(barrier_name); // 请求创建灾备 barrier + ereport(LOG, (errmsg("[BarrierCreator] barrier %s created", barrier_name))); // 日志中记录barrier的创建 #endif index++; } - ereport(LOG, (errmsg("[BarrierCreator] barrier creator thread exits."))); - if (t_thrd.barrier_creator_cxt.barrier_update_last_time_info != 0) { + ereport(LOG, (errmsg("[BarrierCreator] barrier creator thread exits."))); // 记录日志,指示Barrier Creator线程即将退出 + if (t_thrd.barrier_creator_cxt.barrier_update_last_time_info != 0) { // 检查是否分配了barrier_update_last_time_info结构的内存 + // 循环遍历barrier_update_last_time_info结构数组以释放资源 for (int i = 0; i < g_instance.attr.attr_storage.max_replication_slots; i++) { + // 检查当前索引处的archiveSlotName是否不为NULL if (t_thrd.barrier_creator_cxt.barrier_update_last_time_info[i].archiveSlotName != NULL) { + // 释放与archiveSlotName相关联的内存 pfree_ext(t_thrd.barrier_creator_cxt.barrier_update_last_time_info[i].archiveSlotName); } } + // 释放与barrier_update_last_time_info结构数组相关联的内存 pfree_ext(t_thrd.barrier_creator_cxt.barrier_update_last_time_info); } - destroy_handles(); - FreeBarrierLsnInfo(); + // 执行清理操作 + destroy_handles(); // 销毁句柄 + FreeBarrierLsnInfo(); // 释放Barrier LSN信息 proc_exit(0); } diff --git a/src/gausskernel/process/postmaster/barrier_preparse.cpp b/src/gausskernel/process/postmaster/barrier_preparse.cpp index 7ab25c93a..69c13a759 100644 --- a/src/gausskernel/process/postmaster/barrier_preparse.cpp +++ b/src/gausskernel/process/postmaster/barrier_preparse.cpp @@ -40,17 +40,27 @@ #include "postmaster/barrier_preparse.h" typedef struct XLogPageReadPrivate { - const char *datadir; - TimeLineID tli; + const char *datadir; // 存储数据库的数据目录路径 + TimeLineID tli; // 存储 WAL 日志所在的时间线标识符 } XLogPageReadPrivate; +/* + * 宏,用于确定是否需要将条目插入哈希表。通过检查记录类型(xl_rmid)和某些条件来决定是否插入。 + * - 如果记录类型为RM_BARRIER_ID,并且info为XLOG_BARRIER_SWITCHOVER, + * 或者节点是协调器且info为XLOG_BARRIER_COMMIT, + * 或者节点是数据节点且info为XLOG_BARRIER_CREATE + * 那么,需要将条目插入哈希表。 + */ #define NEED_INSERT_INTO_HASH \ ((record->xl_rmid == RM_BARRIER_ID) && ((info == XLOG_BARRIER_SWITCHOVER) || \ (IS_PGXC_COORDINATOR && info == XLOG_BARRIER_COMMIT) || (IS_PGXC_DATANODE && info == XLOG_BARRIER_CREATE))) +//初始化与barrier相关的哈希表的函数 static void InitBarrierHash() { + // 检查是否已经创建了barrier上下文,如果没有则创建一个 if (g_instance.csn_barrier_cxt.barrier_context == NULL) { + // 创建一个新的内存上下文,命名为CsnBarrierContext,用于存储与barrier相关信息 g_instance.csn_barrier_cxt.barrier_context = AllocSetContextCreate(g_instance.instance_context, "CsnBarrierContext", ALLOCSET_DEFAULT_MINSIZE, @@ -58,66 +68,72 @@ static void InitBarrierHash() ALLOCSET_DEFAULT_MAXSIZE, SHARED_CONTEXT); } - + // 定义HASHCTL结构,用于初始化哈希表的属性 HASHCTL ctl; errno_t rc = 0; /* Init hash table */ - rc = memset_s(&ctl, sizeof(HASHCTL), 0, sizeof(HASHCTL)); + rc = memset_s(&ctl, sizeof(HASHCTL), 0, sizeof(HASHCTL)); // 将ctl结构清零,确保结构中的各字段正确初始化 securec_check(rc, "", ""); + // 设置哈希表的键大小和每个条目的大小 ctl.keysize = MAX_BARRIER_ID_LENGTH * sizeof(char); ctl.entrysize = MAX_BARRIER_ID_LENGTH * sizeof(char); - ctl.hash = string_hash; + ctl.hash = string_hash; // 设置哈希函数为string_hash,用于计算键的哈希值 + // 设置哈希表使用的上下文为之前创建的CsnBarrierContext上下文 ctl.hcxt = g_instance.csn_barrier_cxt.barrier_context; + // 创建屏障哈希表,指定上述哈希表的名称、初始大小和属性 g_instance.csn_barrier_cxt.barrier_hash_table = hash_create("Barrier Id Storage Table", INIBARRIERCACHESIZE, &ctl, HASH_ELEM | HASH_FUNCTION | HASH_SHRCTX); + // 为屏障哈希表分配轻量级锁,用于并发访问控制 g_instance.csn_barrier_cxt.barrier_hashtbl_lock = LWLockAssign(LWTRANCHE_BARRIER_TBL); } - +// 设置barrier ID static void SetBarrieID(const char *barrierId, XLogRecPtr lsn) { errno_t rc = EOK; const uint32 shiftSize = 32; volatile WalRcvData *walrcv = t_thrd.walreceiverfuncs_cxt.WalRcv; - SpinLockAcquire(&walrcv->mutex); + SpinLockAcquire(&walrcv->mutex); // 获取互斥锁,确保数据一致性 + // 使用strncpy_s函数将barrierId复制到lastReceivedBarrierId中,确保字符串安全性 rc = strncpy_s((char *)walrcv->lastReceivedBarrierId, MAX_BARRIER_ID_LENGTH, barrierId, MAX_BARRIER_ID_LENGTH - 1); securec_check(rc, "\0", "\0"); - walrcv->lastReceivedBarrierId[MAX_BARRIER_ID_LENGTH - 1] = '\0'; - walrcv->lastReceivedBarrierLSN = lsn; - SpinLockRelease(&walrcv->mutex); + walrcv->lastReceivedBarrierId[MAX_BARRIER_ID_LENGTH - 1] = '\0'; // 在末尾添加字符串结束符 + walrcv->lastReceivedBarrierLSN = lsn; // 设置最后接收到的barrier LSN + SpinLockRelease(&walrcv->mutex); // 释放互斥锁 + // 输出日志,记录设置的barrier ID和barrier LSN ereport(LOG, (errmsg("SetBarrieID set the barrier ID is %s, the barrier LSN is %08X/%08X", barrierId, (uint32)(lsn >> shiftSize), (uint32)lsn))); } - +// 处理SIGHUP信号 static void BarrierPreParseSigHupHandler(SIGNAL_ARGS) { int save_errno = errno; - t_thrd.barrier_preparse_cxt.got_SIGHUP = true; + t_thrd.barrier_preparse_cxt.got_SIGHUP = true; // 设置标志位,表示收到SIGHUP信号 if (t_thrd.proc) { - SetLatch(&t_thrd.proc->procLatch); + SetLatch(&t_thrd.proc->procLatch); // 设置进程的Latch,用于唤醒进程处理SIGHUP信号 } errno = save_errno; } - +// 处理关闭请求信号 static void BarrierPreParseShutdownHandler(SIGNAL_ARGS) { int save_errno = errno; - t_thrd.barrier_preparse_cxt.shutdown_requested = true; + t_thrd.barrier_preparse_cxt.shutdown_requested = true; // 设置关闭请求标志为true,表示收到了关闭请求信号 if (t_thrd.proc) - SetLatch(&t_thrd.proc->procLatch); + SetLatch(&t_thrd.proc->procLatch); // 设置线程的Latch,用于唤醒线程处理关闭请求信号 errno = save_errno; } - +// 处理快速终止信号 用于在出现严重问题时强制终止进程 static void BarrierPreParseQuickDie(SIGNAL_ARGS) { - gs_signal_setmask(&t_thrd.libpq_cxt.BlockSig, NULL); + gs_signal_setmask(&t_thrd.libpq_cxt.BlockSig, NULL); // 解除对信号的阻塞,以便进行快速终止 /* * We DO NOT want to run proc_exit() callbacks -- we're here because @@ -127,7 +143,7 @@ static void BarrierPreParseQuickDie(SIGNAL_ARGS) * things by calling exit() directly, we have to reset the callbacks * explicitly to make this work as intended. */ - on_exit_reset(); + on_exit_reset(); // 重置退出回调函数 /* * Note we do exit(2) not exit(0). This is to force the postmaster into a @@ -137,105 +153,108 @@ static void BarrierPreParseQuickDie(SIGNAL_ARGS) * should ensure the postmaster sees this as a crash, too, but no harm in * being doubly sure.) */ - exit(2); + exit(2); //以代码2 退出进程 } - +// 处理SIGUSR1信号 static void BarrierPreParseSigUsr1Handler(SIGNAL_ARGS) { - int saveErrno = errno; + int saveErrno = errno; // 保存当前错误码的值 - latch_sigusr1_handler(); + latch_sigusr1_handler(); // 处理SIGUSR1信号的回调函数 - errno = saveErrno; + errno = saveErrno; // 恢复之前保存的错误码 } /* * Called when the BarrierPreParseMain is ending. */ +// 在BarrierPreParseMain结束时调用 static void ShutdownBarrierPreParse(int code, Datum arg) { + // 将BarrierPreParseLatch设为NULL,表示关闭BarrierPreParse线程 g_instance.proc_base->BarrierPreParseLatch = NULL; } - +// 设置BarrierPreParse线程的LSN void SetBarrierPreParseLsn(XLogRecPtr startptr) { - volatile WalRcvData *walrcv = t_thrd.walreceiverfuncs_cxt.WalRcv; - SpinLockAcquire(&walrcv->mutex); - walrcv->lastReceivedBarrierLSN = startptr; - SpinLockRelease(&walrcv->mutex); + volatile WalRcvData *walrcv = t_thrd.walreceiverfuncs_cxt.WalRcv; // 获取WalRcvData结构体的指针 + SpinLockAcquire(&walrcv->mutex); // 获取互斥锁,保证操作的原子性 + walrcv->lastReceivedBarrierLSN = startptr; // 设置lastReceivedBarrierLSN为指定的LSN + SpinLockRelease(&walrcv->mutex); // 释放互斥锁 } void BarrierPreParseMain(void) { - volatile WalRcvData *walrcv = t_thrd.walreceiverfuncs_cxt.WalRcv; - MemoryContext preParseContext; - XLogRecord *record = NULL; - XLogReaderState *xlogreader = NULL; - char *errormsg = NULL; - XLogPageReadPrivate readprivate; - XLogRecPtr startLSN = InvalidXLogRecPtr; - XLogRecPtr preStartLSN = InvalidXLogRecPtr; - XLogRecPtr lastReadLSN = InvalidXLogRecPtr; - bool found = false; - XLogRecPtr barrierLSN = InvalidXLogRecPtr; - char *xLogBarrierId = NULL; - char barrierId[MAX_BARRIER_ID_LENGTH] = {0}; - const uint32 shiftSize = 32; - int rc; + volatile WalRcvData *walrcv = t_thrd.walreceiverfuncs_cxt.WalRcv; // 获取WalRcvData结构体的指针 + MemoryContext preParseContext; // 内存上下文,用于分配内存 + XLogRecord *record = NULL; // XLog记录指针 + XLogReaderState *xlogreader = NULL; // XLog读取器的状态结构体指针 + char *errormsg = NULL; // 错误消息 + XLogPageReadPrivate readprivate; // XLog页读取的私有数据 + XLogRecPtr startLSN = InvalidXLogRecPtr; // 起始LSN + XLogRecPtr preStartLSN = InvalidXLogRecPtr; // 前一个起始LSN + XLogRecPtr lastReadLSN = InvalidXLogRecPtr; // 上一次读取的LSN + bool found = false; // 是否找到待处理的记录 + XLogRecPtr barrierLSN = InvalidXLogRecPtr; // barrier记录的LSN + char *xLogBarrierId = NULL; // XLog中的barrierID + char barrierId[MAX_BARRIER_ID_LENGTH] = {0}; // barrier ID字符串 + const uint32 shiftSize = 32; // 位移大小 + int rc; // 函数返回值 - ereport(LOG, (errmsg("[BarrierPreParse] barrier preparse thread started"))); + ereport(LOG, (errmsg("[BarrierPreParse] barrier preparse thread started"))); // 记录日志,标记线程开始 /* * Reset some signals that are accepted by postmaster but not here */ - (void)gspqsignal(SIGHUP, BarrierPreParseSigHupHandler); - (void)gspqsignal(SIGINT, SIG_IGN); - (void)gspqsignal(SIGTERM, BarrierPreParseShutdownHandler); - (void)gspqsignal(SIGQUIT, BarrierPreParseQuickDie); /* hard crash time */ - (void)gspqsignal(SIGALRM, SIG_IGN); - (void)gspqsignal(SIGPIPE, SIG_IGN); - (void)gspqsignal(SIGUSR1, BarrierPreParseSigUsr1Handler); - (void)gspqsignal(SIGUSR2, SIG_IGN); + (void)gspqsignal(SIGHUP, BarrierPreParseSigHupHandler); // 处理SIGHUP信号的回调函数 + (void)gspqsignal(SIGINT, SIG_IGN); // 忽略SIGINT信号 + (void)gspqsignal(SIGTERM, BarrierPreParseShutdownHandler); // 处理SIGTERM信号的回调函数 + (void)gspqsignal(SIGQUIT, BarrierPreParseQuickDie); /* hard crash time */ // 处理SIGQUIT信号的回调函数,用于快速终止 + (void)gspqsignal(SIGALRM, SIG_IGN); // 忽略SIGALRM信号 + (void)gspqsignal(SIGPIPE, SIG_IGN); // 忽略SIGPIPE信号 + (void)gspqsignal(SIGUSR1, BarrierPreParseSigUsr1Handler); // 处理SIGUSR1信号的回调函数 + (void)gspqsignal(SIGUSR2, SIG_IGN); // 忽略SIGUSR2信号 /* * Reset some signals that are accepted by postmaster but not here */ - (void)gspqsignal(SIGCHLD, SIG_DFL); - (void)gspqsignal(SIGTTIN, SIG_DFL); - (void)gspqsignal(SIGTTOU, SIG_DFL); - (void)gspqsignal(SIGCONT, SIG_DFL); - (void)gspqsignal(SIGWINCH, SIG_DFL); + (void)gspqsignal(SIGCHLD, SIG_DFL); // 恢复SIGCHLD信号的默认处理 + (void)gspqsignal(SIGTTIN, SIG_DFL); // 恢复SIGTTIN信号的默认处理 + (void)gspqsignal(SIGTTOU, SIG_DFL); // 恢复SIGTTOU信号的默认处理 + (void)gspqsignal(SIGCONT, SIG_DFL); // 恢复SIGCONT信号的默认处理 + (void)gspqsignal(SIGWINCH, SIG_DFL); // 恢复SIGWINCH信号的默认处理 /* We allow SIGQUIT (quickdie) at all times */ - (void)sigdelset(&t_thrd.libpq_cxt.BlockSig, SIGQUIT); + (void)sigdelset(&t_thrd.libpq_cxt.BlockSig, SIGQUIT); // 允许在任何时间接收SIGQUIT 信号 - on_shmem_exit(ShutdownBarrierPreParse, 0); + on_shmem_exit(ShutdownBarrierPreParse, 0); // 在进程退出时调用ShutdownBarrierPreParse函数 preParseContext = AllocSetContextCreate(t_thrd.top_mem_cxt, "Barrier PreParse", ALLOCSET_DEFAULT_MINSIZE, - ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); - (void)MemoryContextSwitchTo(preParseContext); + ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); // 创建内存上下文 + (void)MemoryContextSwitchTo(preParseContext); // 切换到preParseContext上下文 /* * Unblock signals (they were blocked when the postmaster forked us) */ - gs_signal_setmask(&t_thrd.libpq_cxt.UnBlockSig, NULL); - (void)gs_signal_unblock_sigusr2(); + gs_signal_setmask(&t_thrd.libpq_cxt.UnBlockSig, NULL); // 解除对信号的阻塞 + (void)gs_signal_unblock_sigusr2(); // 解除对SIGUSR2信号的阻塞 - g_instance.proc_base->BarrierPreParseLatch = &t_thrd.proc->procLatch; + g_instance.proc_base->BarrierPreParseLatch = &t_thrd.proc->procLatch; // 设置BarrierPreParseLatch - startLSN = walrcv->lastReceivedBarrierLSN; + startLSN = walrcv->lastReceivedBarrierLSN; // 获取上次接收的barrier LSN ereport(LOG, (errmsg("[BarrierPreParse] preparse thread start at %08X/%08X", (uint32)(startLSN >> shiftSize), - (uint32)startLSN))); + (uint32)startLSN))); // 记录日志,标记线程起始位置 - if (g_instance.csn_barrier_cxt.barrier_hash_table == NULL) { + if (g_instance.csn_barrier_cxt.barrier_hash_table == NULL) {// 如果barrier哈希表为空,初始化 InitBarrierHash(); } - readprivate.datadir = t_thrd.proc_cxt.DataDir; - readprivate.tli = GetRecoveryTargetTLI(); + readprivate.datadir = t_thrd.proc_cxt.DataDir; // 设置读取私有数据的数据目录 + readprivate.tli = GetRecoveryTargetTLI(); // 获取恢复目标的时间线ID - xlogreader = XLogReaderAllocate(&SimpleXLogPageRead, &readprivate); + xlogreader = XLogReaderAllocate(&SimpleXLogPageRead, &readprivate); // 分配XLog读取器 if (xlogreader == NULL) + // 如果分配失败,报错 ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_RESOURCES), errmsg("memory is temporarily unavailable while allocate xlog reader"))); @@ -244,91 +263,98 @@ void BarrierPreParseMain(void) */ for (;;) { /* Clear any already-pending wakeups */ - ResetLatch(&t_thrd.proc->procLatch); + ResetLatch(&t_thrd.proc->procLatch); //清除已挂起的唤醒 - if (t_thrd.barrier_preparse_cxt.got_SIGHUP) { - t_thrd.barrier_preparse_cxt.got_SIGHUP = false; - ProcessConfigFile(PGC_SIGHUP); + if (t_thrd.barrier_preparse_cxt.got_SIGHUP) { // 如果收到SIGHUP信号,重新读取配置文件 + t_thrd.barrier_preparse_cxt.got_SIGHUP = false; // 重置信号标志 + ProcessConfigFile(PGC_SIGHUP); // 处理SIGHUP信号,重新读取配置文件 } - if (t_thrd.barrier_preparse_cxt.shutdown_requested) { + if (t_thrd.barrier_preparse_cxt.shutdown_requested) { // 如果收到关闭请求,结束线程 ereport(LOG, (errmsg("[BarrierPreParse] preparse thread shut down"))); - XLogReaderFree(xlogreader); + XLogReaderFree(xlogreader); // 释放XLog读取器的资源 proc_exit(0); /* done */ } - found = false; - preStartLSN = startLSN; + found = false; // 初始化found标志为false + preStartLSN = startLSN; // 保存上一次的起始LSN + // 记录日志,标记预解析开始位置 ereport(DEBUG1, (errmsg("[BarrierPreParse] start to preparse at: %08X/%08X", (uint32)(startLSN >> shiftSize), (uint32)startLSN))); - startLSN = XLogFindNextRecord(xlogreader, startLSN); - if (XLogRecPtrIsInvalid(startLSN)) { - startLSN = preStartLSN; + startLSN = XLogFindNextRecord(xlogreader, startLSN); // 查找下一个XLog记录的LSN + if (XLogRecPtrIsInvalid(startLSN)) { // 如果找不到,回到上一个起始LSN + startLSN = preStartLSN; // 使用之前记录的起始LSN if (!XLByteEQ(walrcv->receiver_flush_location, startLSN) && !XLByteEQ(walrcv->lastRecoveredBarrierLSN, startLSN)) { /* reset startLSN */ - startLSN = walrcv->lastRecoveredBarrierLSN; + startLSN = walrcv->lastRecoveredBarrierLSN; // 使用上次恢复的Barrier LSN ereport(LOG, (errmsg("[BarrierPreParse] reset startLSN with lastRecoveredBarrierLSN: %08X/%08X", - (uint32)(startLSN >> shiftSize), (uint32)startLSN))); + (uint32)(startLSN >> shiftSize), (uint32)startLSN))); // 记录日志,标记重置startLSN } continue; } do { + // 从XLog中读取记录,从startLSN开始 record = XLogReadRecord(xlogreader, startLSN, &errormsg); - if (record == NULL) { + if (record == NULL) { // 如果读取到了NULL记录,即无法继续读取,跳出循环 break; } - lastReadLSN = xlogreader->EndRecPtr; - uint8 info = XLogRecGetInfo(xlogreader) & ~XLR_INFO_MASK; + lastReadLSN = xlogreader->EndRecPtr; // 记录最后读取的LSN + // 获取XLog记录的信息位,去除XLR_INFO_MASK标志位 + uint8 info = XLogRecGetInfo(xlogreader) & ~XLR_INFO_MASK; if (NEED_INSERT_INTO_HASH) { - xLogBarrierId = XLogRecGetData(xlogreader); - if (!IS_CSN_BARRIER(xLogBarrierId)) { + // 如果需要将记录插入到哈希表中 + xLogBarrierId = XLogRecGetData(xlogreader); // 获取XLog记录的数据部分 + if (!IS_CSN_BARRIER(xLogBarrierId)) { // 如果不是用于备机集群的barrier记录 ereport(WARNING, (errmsg("[BarrierPreParse] %s is not for standby cluster", xLogBarrierId))); } else { // insert into hash table - found = true; - barrierLSN = xlogreader->EndRecPtr; + // 插入到哈希表中 + found = true; // 标记找到适用于备用集群的barrier记录 + barrierLSN = xlogreader->EndRecPtr; // 记录barrier的LSN rc = strncpy_s((char *)barrierId, MAX_BARRIER_ID_LENGTH, xLogBarrierId, MAX_BARRIER_ID_LENGTH - 1); securec_check(rc, "\0", "\0"); - barrierId[MAX_BARRIER_ID_LENGTH - 1] = '\0'; + barrierId[MAX_BARRIER_ID_LENGTH - 1] = '\0'; // 确保barrierId字符串有效性 + // 获取哈希表锁,插入barrierId LWLockAcquire(g_instance.csn_barrier_cxt.barrier_hashtbl_lock, LW_EXCLUSIVE); - BarrierCacheInsertBarrierId(barrierId); - LWLockRelease(g_instance.csn_barrier_cxt.barrier_hashtbl_lock); + BarrierCacheInsertBarrierId(barrierId); // 将barrierId插入哈希表 + LWLockRelease(g_instance.csn_barrier_cxt.barrier_hashtbl_lock); // 释放哈希表锁 + // 记录日志,说明插入了barrierId到哈希表中 ereport(LOG, (errmsg("[BarrierPreParse] insert barrierID %s to the hash table, rmid: %d, crc: %d.", barrierId, record->xl_rmid, record->xl_crc))); } } - startLSN = InvalidXLogRecPtr; - } while (!t_thrd.barrier_preparse_cxt.shutdown_requested); + startLSN = InvalidXLogRecPtr; // 将startLSN重置为InvalidXLogRecPtr,以便下次循环处理下一个XLog记录 + } while (!t_thrd.barrier_preparse_cxt.shutdown_requested); // 收到关闭请求退出循环,否则继续 /* close xlogreadfd after circulation */ - CloseXlogFile(); + CloseXlogFile(); // 关闭当前使用的XLOG文件 - if (found) { + if (found) { // 如果找到了需要插入到哈希表的barrier,将其插入 SetBarrieID(barrierId, barrierLSN); } - startLSN = XLogRecPtrIsInvalid(lastReadLSN) ? preStartLSN : lastReadLSN; + startLSN = XLogRecPtrIsInvalid(lastReadLSN) ? preStartLSN : lastReadLSN; // 更新起始LSN - if (XLogRecPtrIsInvalid(xlogreader->ReadRecPtr) && errormsg) { + if (XLogRecPtrIsInvalid(xlogreader->ReadRecPtr) && errormsg) { // 如果在解析过程中出现错误,记录错误信息 ereport(LOG, (errmsg("[BarrierPreParse] preparse thread get an error info %s", errormsg))); } - const long sleepTime = 1000; - rc = WaitLatch(&t_thrd.proc->procLatch, WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH, sleepTime); + const long sleepTime = 1000; // 定义休眠时间为1000毫秒 + rc = WaitLatch(&t_thrd.proc->procLatch, WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH, sleepTime); // 等待信号量或超时 if (((unsigned int)rc) & WL_POSTMASTER_DEATH) { - XLogReaderFree(xlogreader); - ereport(LOG, (errmsg("[BarrierPreParse] preparse thread shut down with code 1"))); - gs_thread_exit(1); + XLogReaderFree(xlogreader); // 释放XLogReader资源 + ereport(LOG, (errmsg("[BarrierPreParse] preparse thread shut down with code 1"))); // 记录线程以代码1关闭的日志 + gs_thread_exit(1);// 以代码1退出线程 } } } - +// 用于唤醒 BarrierPreParse 后台进程 void WakeUpBarrierPreParseBackend() { - if (g_instance.pid_cxt.BarrierPreParsePID != 0) { - if (g_instance.proc_base->BarrierPreParseLatch != NULL) { - SetLatch(g_instance.proc_base->BarrierPreParseLatch); + if (g_instance.pid_cxt.BarrierPreParsePID != 0) { // 如果存在BarrierPreParse进程 + if (g_instance.proc_base->BarrierPreParseLatch != NULL) { // 如果存在BarrierPreParse进程的Latch + SetLatch(g_instance.proc_base->BarrierPreParseLatch); // 触发Latch,唤醒BarrierPreParse进程 } } } diff --git a/src/gausskernel/process/postmaster/bgworker.cpp b/src/gausskernel/process/postmaster/bgworker.cpp index f30e94618..2a29edb1d 100644 --- a/src/gausskernel/process/postmaster/bgworker.cpp +++ b/src/gausskernel/process/postmaster/bgworker.cpp @@ -25,119 +25,163 @@ #include "utils/snapmgr.h" #include "commands/dbcommands.h" #include "pgstat.h" - +// 外部函数声明: +// 函数声明:StreamSaveTxnContext,用于保存流复制事务上下文 extern void StreamSaveTxnContext(StreamTxnContext* stc); +// 函数声明:StreamRestoreTxnContext,用于恢复流复制事务上下文 extern void StreamRestoreTxnContext(StreamTxnContext* stc); +// 函数声明:CopySnapshotByCurrentMcxt,通过当前内存上下文复制快照 extern Snapshot CopySnapshotByCurrentMcxt(Snapshot snapshot); +// 函数声明:SetGlobalSnapshotData,设置全局快照数据 extern void SetGlobalSnapshotData( TransactionId xmin, TransactionId xmax, uint64 csn, GTM_Timeline timeline, bool ssNeedSyncWaitAll); +// 全局变量:最大后台工作进程数,初始值为 64 int g_max_worker_processes = 64; /* * Return true if the thread is bgworker. */ +// 判断当前进程是否为后台工作进程 +// 返回值:若为后台工作进程,返回 true;否则返回 false bool IsBgWorkerProcess(void) { - return t_thrd.role == BGWORKER; + return t_thrd.role == BGWORKER; // 返回当前线程是否为 BGWORKER(后台工作进程) } +// 内联函数定义:BgworkerPutBackToFreeList +// 功能:将后台工作进程放回空闲列表 +// 参数:bgworker - 后台工作进程指针 static inline void BgworkerPutBackToFreeList(BackgroundWorker* bgworker) { - BGW_HDR* bgworker_base = (BGW_HDR *)g_instance.bgw_base; + BGW_HDR* bgworker_base = (BGW_HDR *)g_instance.bgw_base;// 获取后台工作进程头指针 + // 使用 memset_s 清空 bgworker 结构体的内容,确保隐私信息被清除 errno_t rc = memset_s(bgworker, sizeof(BackgroundWorker), 0, sizeof(BackgroundWorker)); - securec_check(rc, "", ""); + securec_check(rc, "", ""); // 检查正确性 + // 将 bgworker 放回空闲后台工作进程链表 bgworker->links.next = (SHM_QUEUE *)bgworker_base->free_bgws; bgworker_base->free_bgws = bgworker; } +// 内联函数定义:GetFreeBgworker +// 功能:获取空闲后台工作进程 +// 返回值:获取的空闲后台工作进程指针,若没有可用的返回 NULL static inline BackgroundWorker* GetFreeBgworker() { + // 获取后台工作进程头指针 BGW_HDR* bgworker_base = (BGW_HDR *)g_instance.bgw_base; + // 若空闲后台工作进程链表为空,则返回 NULL if (!bgworker_base->free_bgws) { return NULL; } + // 从空闲后台工作进程链表中获取一个后台工作进程 BackgroundWorker* bgworker = bgworker_base->free_bgws; bgworker_base->free_bgws = (BackgroundWorker *)bgworker->links.next; return bgworker; } +// 初始化后台工作进程全局数据 void InitBgworkerGlobal(void) { - BGW_HDR* bgworker_base = NULL; - BackgroundWorker* bgws = NULL; - bool needPalloc = false; - + BGW_HDR* bgworker_base = NULL; // 后台工作进程头指针 + BackgroundWorker* bgws = NULL; // 后台工作进程指针 + bool needPalloc = false; // 是否需要进行 palloc 内存分配 + // 切换内存上下文到 MEMORY_CONTEXT_CBB 组中 MemoryContext oldContext = MemoryContextSwitchTo(INSTANCE_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_CBB)); + // 若 g_instance.bgw_base 为空,表示尚未创建后台工作进程共享结构 if (g_instance.bgw_base == NULL) { /* Create the g_instance.proc_base shared structure */ + // 创建 g_instance.bgw_base 共享结构,为其分配内存,确保地址对齐 bgworker_base = (BGW_HDR *)CACHELINEALIGN(palloc(sizeof(BGW_HDR) + PG_CACHE_LINE_SIZE)); + // 将分配的内存设置为后台工作进程共享结构 g_instance.bgw_base = (void *)bgworker_base; - needPalloc = true; + needPalloc = true;// 标记需要进行内存分配 } else { + // 将 g_instance.bgw_base 转换为 BGW_HDR 指针,表示后台工作进程共享头部 bgworker_base = (BGW_HDR *)g_instance.bgw_base; + // 断言确保 bgworker_base 的 bgws 指针不为空,即后台工作进程数组已存在 Assert(bgworker_base->bgws != NULL); } + // 初始化 bgworker_base 的 bgw_id_seq 计数为 1,用于跟踪后台工作进程的唯一标识符 pg_atomic_init_u64(&bgworker_base->bgw_id_seq, 1); - + // 如果需要进行内存分配 if (needPalloc) { + // 分配足够大小的内存用于存储后台工作进程数组,确保地址对齐 bgws = (BackgroundWorker*)CACHELINEALIGN( palloc0(g_max_worker_processes * sizeof(BackgroundWorker) + PG_CACHE_LINE_SIZE)); - bgworker_base->bgws = bgws; - } else { - bgws = bgworker_base->bgws; + bgworker_base->bgws = bgws; // 将分配的内存设置为后台工作进程数组 + } else { // 如果不需要进行内存分配 + bgws = bgworker_base->bgws; // 直接获取已存在的后台工作进程数组指针 } - + // 将所有后台工作进程放回空闲列表 for (int i = 0; i < g_max_worker_processes; i++) { BgworkerPutBackToFreeList(&bgws[i]); } - + // 初始化后台工作进程数据锁 pthread_mutex_init(&g_instance.bgw_base_lock, NULL); + // 切换回原来的内存上下文 MemoryContextSwitchTo(oldContext); } +// 设置后台工作进程的事务环境 void SetUpBgWorkerTxnEnvironment() { /* resotre transaction context. */ + // 获取后台工作进程上下文 BgWorkerContext *bwc = (BgWorkerContext *)t_thrd.bgworker_cxt.bgwcontext; - + // 恢复事务上下文 StreamRestoreTxnContext(&bwc->transactionCxt); /* transaction id. */ + // SetNextTransactionId 函数的第二个参数用于控制是否自动增加事务ID。 + // 当设为 false 时,函数不会自动增加事务ID,而是使用传递的 txnId 参数作为下一个事务ID + // 确保后续的事务使用指定的事务ID,保持一致性 SetNextTransactionId(bwc->transactionCxt.txnId, false); - StreamTxnContextSetTransactionState(&bwc->transactionCxt); + StreamTxnContextSetTransactionState(&bwc->transactionCxt); // 设置事务状态为当前状态 /* snapshot. */ - Snapshot snapshot = CopySnapshotByCurrentMcxt(bwc->transactionCxt.snapshot); - SetGlobalSnapshotData(snapshot->xmin, snapshot->xmax, snapshot->snapshotcsn, snapshot->timeline, false); - StreamTxnContextSetSnapShot(snapshot); - StreamTxnContextSetMyPgXactXmin(snapshot->xmin); + Snapshot snapshot = CopySnapshotByCurrentMcxt(bwc->transactionCxt.snapshot); // 复制当前内存上下文中的快照 + // SetGlobalSnapshotData 函数的最后一个参数用于控制是否需要等待所有事务同步完成。 + // 当设为 false 时,函数将不会等待所有事务同步完成,而是立即返回,以提高响应速度和效率 + SetGlobalSnapshotData(snapshot->xmin, snapshot->xmax, snapshot->snapshotcsn, snapshot->timeline, false); // 设置全局快照数据 + StreamTxnContextSetSnapShot(snapshot); // 将快照信息设置到流复制事务上下文中 + StreamTxnContextSetMyPgXactXmin(snapshot->xmin); // 设置 PGXACT 的 xmin /* command id. */ - SaveReceivedCommandId(bwc->transactionCxt.currentCommandId); + SaveReceivedCommandId(bwc->transactionCxt.currentCommandId); // 保存接收到的命令ID /* timestamp. */ - SetCurrentGTMDeltaTimestamp(); + SetCurrentGTMDeltaTimestamp(); // 设置当前的 GTM Delta 时间戳 } +// 保存后台工作进程的错误信息 static void BgWorkerSaveError() { + // 获取当前后台工作进程的指针 BackgroundWorker *bgw = (BackgroundWorker *)t_thrd.bgworker_cxt.bgworker; + // 获取当前错误信息的指针 ErrorData *edata = &t_thrd.log_cxt.errordata[t_thrd.log_cxt.errordata_stack_depth]; errno_t rc = EOK; int len; + // 设置错误信息和详情的默认字符串 char *failmsg = "Worker failed during parallel build index."; char *nulldetail = "N/A"; + // 将错误级别和 SQL 错误码保存到 bgw_edata 结构 bgw->bgw_edata.elevel = edata->elevel; bgw->bgw_edata.sqlerrcode = edata->sqlerrcode; + // 获取错误消息,如果为空则使用默认消息 char *message = (edata->message != NULL ? edata->message : failmsg); + // 限制消息长度,并复制到 bgw_edata 的 message 字段 len = Min(strlen(message), BGWORKER_MAX_ERROR_LEN - 1); rc = strncpy_s(bgw->bgw_edata.message, BGWORKER_MAX_ERROR_LEN, message, len); + // 确保字符串的复制操作不会造成缓冲区溢出,增加代码的健壮性 securec_check_c(rc, "", ""); - bgw->bgw_edata.message[len] = '\0'; + bgw->bgw_edata.message[len] = '\0'; // 确保字符串以 C 字符串的形式结束 + // 获取错误详情,如果为空则使用默认详情 char *detail = (edata->detail != NULL ? edata->detail : nulldetail); + // 限制详情长度,并复制到 bgw_edata 的 detail 字段 len = Min(strlen(detail), BGWORKER_MAX_ERROR_LEN - 1); rc = strncpy_s(bgw->bgw_edata.detail, BGWORKER_MAX_ERROR_LEN, detail, len); securec_check_c(rc, "", ""); @@ -147,43 +191,55 @@ static void BgWorkerSaveError() /* * Called when the Bgworker thread is ending. */ +/* + * BgworkerQuitAndClean + * 功能:当后台工作进程线程即将结束时调用 + * 参数: + * code: 退出代码 + * arg: 用户定义的参数 + */ static void BgworkerQuitAndClean(int code, Datum arg) { + // 获取当前后台工作进程的指针 BackgroundWorker *bgw = (BackgroundWorker *)t_thrd.bgworker_cxt.bgworker; + // 根据后台工作进程的状态设置相应状态 if (bgw->bgw_status == BGW_STOPPED) { - bgw->bgw_status = BGW_TERMINATED; + bgw->bgw_status = BGW_TERMINATED; // 将状态更新为 BGW_TERMINATED,表示进程已正常终止 } else { - bgw->bgw_status = BGW_FAILED; + bgw->bgw_status = BGW_FAILED; // 将状态更新为 BGW_FAILED,表示进程因某种原因失败 } } +// 后台工作进程的初始化函数 static void BackgroundWorkerInit(void) { /* we are a postmaster subprocess now */ - IsUnderPostmaster = true; - t_thrd.role = BGWORKER; + IsUnderPostmaster = true; // 将 IsUnderPostmaster 标志设置为 true,表示当前进程是在后台运行 + t_thrd.role = BGWORKER; // 设置当前线程的角色为 BGWORKER,表示当前线程是一个后台工作进程 /* reset t_thrd.proc_cxt.MyProcPid */ - t_thrd.proc_cxt.MyProcPid = gs_thread_self(); + t_thrd.proc_cxt.MyProcPid = gs_thread_self(); // 重置当前线程的进程ID,将其设置为当前线程的实际线程ID - t_thrd.proc_cxt.MyProgName = "BgWorker"; + t_thrd.proc_cxt.MyProgName = "BgWorker"; // 设置当前线程的进程名称为 "BgWorker" /* record Start Time for logging */ - t_thrd.proc_cxt.MyStartTime = time(NULL); + t_thrd.proc_cxt.MyStartTime = time(NULL); // 记录当前线程的开始时间,用于日志记录 - init_ps_display("Bgworker process", "", "", ""); + init_ps_display("Bgworker process", "", "", ""); // 初始化显示进程状态 - SetProcessingMode(InitProcessing); + SetProcessingMode(InitProcessing); // 设置当前的处理模式为初始化阶段 - on_proc_exit(BgworkerQuitAndClean, 0); + on_proc_exit(BgworkerQuitAndClean, 0); // 在进程退出时调用 BgworkerQuitAndClean 函数,执行清理操作 /* * SIGINT is used to signal canceling the current action */ + // 为 SIGINT、SIGTERM 和 SIGALRM 信号设置相应的处理函数 (void)gspqsignal(SIGINT, StatementCancelHandler); (void)gspqsignal(SIGTERM, die); (void)gspqsignal(SIGALRM, handle_sig_alarm); + // 对于 SIGQUIT、SIGPIPE、SIGUSR1 、SIGUSR2 和 SIGHUP 信号,设置忽略处理 (void)gspqsignal(SIGQUIT, SIG_IGN); (void)gspqsignal(SIGPIPE, SIG_IGN); (void)gspqsignal(SIGUSR1, SIG_IGN); @@ -191,6 +247,7 @@ static void BackgroundWorkerInit(void) (void)gspqsignal(SIGHUP, SIG_IGN); /* Reset some signals that are accepted by postmaster but not here */ + // 重置一些在 postmaster 中接受但在这里不接受的信号的处理方式 (void)gspqsignal(SIGCHLD, SIG_DFL); (void)gspqsignal(SIGTTIN, SIG_DFL); (void)gspqsignal(SIGTTOU, SIG_DFL); @@ -198,11 +255,11 @@ static void BackgroundWorkerInit(void) (void)gspqsignal(SIGWINCH, SIG_DFL); /* Early initialization */ - BaseInit(); + BaseInit(); // 执行早期初始化操作 #ifndef EXEC_BACKEND - InitProcess(); -#endif + InitProcess(); // 初始化进程数据结构和状态 +#endif } /* @@ -211,6 +268,7 @@ static void BackgroundWorkerInit(void) * This is the main entry point for background worker, to be called from * postmaster. */ +// 后台工作进程的主要入口函数,从 postmaster 被调用 void BackgroundWorkerMain(void) { BgWorkerContext *bwc = (BgWorkerContext *)t_thrd.bgworker_cxt.bgwcontext; @@ -222,57 +280,67 @@ void BackgroundWorkerMain(void) int *oldTryCounter = NULL; int curTryCounter; + // 获取后台工作进程上下文锁,用于确保安全访问 bgworker 数据结构 pthread_mutex_lock(&g_instance.bgw_base_lock); + // 如果 bgwId 与 bgw->bgw_id 不匹配,或者已经被禁用,则退出 if (bgwId != bgw->bgw_id || pg_atomic_fetch_add_u32(&bgw->disable_count, 1) > 0) { /* The leader disallowed this worker to do index build due to startup time longer than 5s. */ ereport(WARNING, (errmsg("BgWorker thread %lu was disabled for long startup time.", - t_thrd.proc_cxt.MyProcPid))); + t_thrd.proc_cxt.MyProcPid))); // 报告警告信息 /* Note that we are in the state BGW_NOT_YET_STARTED. */ - pthread_mutex_unlock(&g_instance.bgw_base_lock); + // 解锁并跳转到 out 标签,退出函数 + pthread_mutex_unlock(&g_instance.bgw_base_lock); goto out; } - pthread_mutex_unlock(&g_instance.bgw_base_lock); + pthread_mutex_unlock(&g_instance.bgw_base_lock); // 解锁 - BackgroundWorkerInit(); + BackgroundWorkerInit(); // 初始化后台工作进程 + // 创建一个内存上下文来管理后台工作进程的内存 workerContext = AllocSetContextCreate(t_thrd.top_mem_cxt, "BgWorker", ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); - (void)MemoryContextSwitchTo(workerContext); + (void)MemoryContextSwitchTo(workerContext); // 切换到工作内存上下文 /* Unblock signals (they were blocked when the postmaster forked us) */ + // 解除对信号的阻塞(在 postmaster fork 时会阻塞信号) gs_signal_setmask(&t_thrd.libpq_cxt.UnBlockSig, NULL); (void)gs_signal_unblock_sigusr2(); /* If an exception is encountered, processing resumes here. */ + // 如果遇到异常,处理将会跳转到此处 if (sigsetjmp(local_sigjmp_buf, 1) != 0) { - gstrace_tryblock_exit(true, oldTryCounter); + gstrace_tryblock_exit(true, oldTryCounter); // 退出异常处理块,恢复之前的 try counter /* Since not using PG_TRY, must reset error stack by hand */ + // 手动重置错误堆栈和调用堆栈 t_thrd.log_cxt.error_context_stack = NULL; - t_thrd.log_cxt.call_stack = NULL; /* Prevent interrupts while cleaning up */ - HOLD_INTERRUPTS(); + HOLD_INTERRUPTS(); // 在清理期间阻止中断 /* save bgworker error data for leader reporting */ - BgWorkerSaveError(); + BgWorkerSaveError(); // 保存 bgworker 错误信息 /* Report the error to the parallel leader and the server log */ - EmitErrorReport(); + EmitErrorReport(); // 报告错误给主进程和日志 /* release resource held by lsc */ - AtEOXact_SysDBCache(false); + AtEOXact_SysDBCache(false); // 释放系统缓存中的资源 /* * These operations are really just a minimal subset of * AbortTransaction(). We don't have very many resources to worry * about in bgwriter, but we do have LWLocks, buffers, and temp files. */ - LWLockReleaseAll(); - AbortBufferIO(); - UnlockBuffers(); + LWLockReleaseAll(); // 释放所有的轻量级锁 + AbortBufferIO(); // 中止所有的缓冲区输入/输出操作 + UnlockBuffers(); // 解锁所有缓冲区 /* buffer pins are released here */ + // 如果当前资源拥有者存在,进行资源释放 if (t_thrd.utils_cxt.CurrentResourceOwner != NULL) { + // 释放资源拥有者的资源,并指定 RESOURCE_RELEASE_BEFORE_LOCKS 模式 + // 第三个参数为 false,表示不释放连接级别的资源 + // 第四个参数为 true,表示在释放资源后也执行锁的释放 ResourceOwnerRelease(t_thrd.utils_cxt.CurrentResourceOwner, RESOURCE_RELEASE_BEFORE_LOCKS, false, true); } @@ -280,6 +348,7 @@ void BackgroundWorkerMain(void) * Now return to normal top-level context and clear ErrorContext for * next time. */ + // 切换回原始的内存上下文,刷新错误状态并清理内存 (void)MemoryContextSwitchTo(workerContext); FlushErrorState(); @@ -287,35 +356,44 @@ void BackgroundWorkerMain(void) MemoryContextResetAndDeleteChildren(workerContext); /* and go away */ - proc_exit(1); + proc_exit(1); // 退出进程 } + // 获取旧的 try 计数器的值,以便在异常恢复时进行恢复 oldTryCounter = gstrace_tryblock_entry(&curTryCounter); /* We can now handle ereport(ERROR) */ + // 设置异常处理机制的跳转点,以便在发生错误时进行处理 t_thrd.log_cxt.PG_exception_stack = &local_sigjmp_buf; + // 设置会话的开始时间戳 u_sess->proc_cxt.MyProcPort->SessionStartTime = GetCurrentTimestamp(); - bgw->bgw_status = BGW_STARTED; + bgw->bgw_status = BGW_STARTED; // 设置 bgworker 状态 /* General initialization. */ /* user_name and database_name in u_sess->proc_cxt.MyProcPort is under t_thrd.top_mem_cxt */ + // 切换到存储上下文,对 MyProcPort 的数据库名和用户名进行重置 oldcontext = MemoryContextSwitchTo(SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_STORAGE)); + // 如果当前 MyProcPort 的数据库名不为 NULL,则释放其内存 if (u_sess->proc_cxt.MyProcPort->database_name != NULL) { pfree_ext(u_sess->proc_cxt.MyProcPort->database_name); } + // 如果当前 MyProcPort 的用户名不为 NULL,则释放其内存 if (u_sess->proc_cxt.MyProcPort->user_name != NULL) { pfree_ext(u_sess->proc_cxt.MyProcPort->user_name); } + // 设置 MyProcPort 的数据库名和用户名 u_sess->proc_cxt.MyProcPort->database_name = pstrdup(bwc->databaseName); u_sess->proc_cxt.MyProcPort->user_name = pstrdup(bwc->userName); + // 切换回之前的上下文 (void)MemoryContextSwitchTo(oldcontext); - + // 设置数据库和用户,初始化后台工作 t_thrd.proc_cxt.PostInit->SetDatabaseAndUser(bwc->databaseName, InvalidOid, bwc->userName); t_thrd.proc_cxt.PostInit->InitBgWorker(); t_thrd.proc_cxt.PostInit->GetDatabaseName(u_sess->proc_cxt.MyProcPort->database_name); + // 记录日志,表示 bgworker 的线程 ID ereport(LOG, (errmsg("bgworker threadId is %lu.", t_thrd.proc_cxt.MyProcPid))); - StartTransactionCommand(); - SetUpBgWorkerTxnEnvironment(); + StartTransactionCommand(); // 开启事务 + SetUpBgWorkerTxnEnvironment(); // 设置后台工作的事务环境 /* * Join locking group. We must do this before anything that could try to @@ -326,21 +404,28 @@ void BackgroundWorkerMain(void) * deadlock. (If we can't join the lock group, the leader has gone away, * so just exit quietly.) */ - BecomeLockGroupMember(bwc->leader); + /* + 加入锁定组的目的是避免死锁的发生。 + 在并行处理中,如果一个进程获取了重量级锁,而另一个进程又试图获取与之冲突的锁,就可能引发死锁。 + 通过加入锁定组,可以确保所有并行进程都在同一个锁定组内,从而避免死锁的发生。 + 如果无法加入锁定组,那么可能是因为并行组的领导者已经退出,这时后台工作进程会安静地退出,而不做其他操作。 + */ + BecomeLockGroupMember(bwc->leader); // 加入锁定组,避免死锁 - u_sess->attr.attr_sql.enable_cluster_resize = bwc->enable_cluster_resize; + u_sess->attr.attr_sql.enable_cluster_resize = bwc->enable_cluster_resize; // 设置是否启用集群调整大小 /* * Now invoke the user-defined worker code */ - bwc->main_entry(bwc); + bwc->main_entry(bwc); // 调用用户定义的后台工作函数 + // 结束并重置后台工作的事务 EndParallelWorkerTransaction(); ResetTransactionInfo(); /* ... and if it returns, we're done */ - bgw->bgw_status = BGW_STOPPED; + bgw->bgw_status = BGW_STOPPED; // 设置 bgworker 状态为已停止 out: - proc_exit(0); + proc_exit(0); // 退出进程 } /* @@ -349,27 +434,46 @@ out: * This can only be called in the _PG_init function of a module library * that's loaded by shared_preload_libraries; otherwise it has no effect. */ +/* + * 在处理 shared_preload_libraries 期间注册一个新的后台工作进程。 + * + * 该函数用于在 _PG_init 函数中被调用,仅在被 shared_preload_libraries 加载的 + * 模块库中有效。它的作用是为后台工作进程注册提供了一个接口,用于准备并启动新的 + * 后台工作进程。 + * + * 参数: + * bwc - 后台工作上下文,包含了后台工作进程的相关信息 + * + * 返回值: + * 如果成功注册了新的后台工作进程,返回 true;否则,返回 false。 + */ bool RegisterBackgroundWorker(BgWorkerContext *bwc) { - BGW_HDR* bgworker_base = (BGW_HDR *)g_instance.bgw_base; + BGW_HDR* bgworker_base = (BGW_HDR *)g_instance.bgw_base; // 获取指向后台工作进程池的指针 + // 声明后台工作进程结构和后台工作进程参数结构的指针 BackgroundWorker *bgw = NULL; BackgroundWorkerArgs *bwa = NULL; + // 加锁,以便在操作后台工作进程池时保持同步 pthread_mutex_lock(&g_instance.bgw_base_lock); - bgw = GetFreeBgworker(); - if (bgw == NULL) { - pthread_mutex_unlock(&g_instance.bgw_base_lock); + bgw = GetFreeBgworker(); // 获取一个空闲的后台工作进程 + if (bgw == NULL) { // 如果没有空闲的后台工作进程可用 + pthread_mutex_unlock(&g_instance.bgw_base_lock); // 解锁后台工作进程池 + // 输出警告信息,表示没有空闲的后台工作进程可用 ereport(WARNING, (errmsg("There are no more free background workers available"))); - return false; + return false; // 返回 false,表示注册失败 } + // 为该后台工作进程分配唯一的 ID bgw->bgw_id = pg_atomic_fetch_add_u64(&bgworker_base->bgw_id_seq, 1); - pthread_mutex_unlock(&g_instance.bgw_base_lock); + pthread_mutex_unlock(&g_instance.bgw_base_lock); // 解锁后台工作进程池 + // 设置后台工作进程的状态为尚未启动,以及相关的状态持续时间和禁用计数 bgw->bgw_status = BGW_NOT_YET_STARTED; bgw->bgw_status_dur = 0; bgw->disable_count = 0; /* Construct bgworker thread args */ + // 构造后台工作线程参数 bwa = (BackgroundWorkerArgs*)MemoryContextAllocZero( INSTANCE_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_STORAGE), sizeof(BackgroundWorkerArgs)); bwa->bgwcontext = bwc; @@ -377,153 +481,221 @@ bool RegisterBackgroundWorker(BgWorkerContext *bwc) bwa->bgworkerId = bgw->bgw_id; /* Fork a new worker thread */ + // 创建一个新的工作线程 bgw->bgw_notify_pid = initialize_util_thread(BGWORKER, bwa); /* failed to fork a new thread */ + // 如果创建线程失败 if (bgw->bgw_notify_pid == 0) { - pfree_ext(bwa); - return false; + pfree_ext(bwa); // 释放分配的参数内存 + return false; // 返回注册失败 } /* Copy the registration data into the registered workers list. */ - slist_push_head(&t_thrd.bgworker_cxt.bgwlist, &bgw->rw_lnode); - return true; + slist_push_head(&t_thrd.bgworker_cxt.bgwlist, &bgw->rw_lnode); // 将注册数据复制到已注册工作线程列表中 + return true; // 返回注册成功 } +// 功能:清理后台工作进程的共享上下文 static void BgworkerCleanupSharedContext() { - Assert(!IsBgWorkerProcess()); + Assert(!IsBgWorkerProcess()); // 确保不是后台工作进程调用该函数 /* clean up backgroud shared context */ - if (t_thrd.bgworker_cxt.bgwcontext) { + if (t_thrd.bgworker_cxt.bgwcontext) { // 如果存在后台工作上下文 BgWorkerContext *bwc = (BgWorkerContext*)t_thrd.bgworker_cxt.bgwcontext; - if (bwc->exit_entry) { - bwc->exit_entry(bwc); + if (bwc->exit_entry) { // 如果有退出函数,即程序退出时执行操作 + bwc->exit_entry(bwc); // 调用 } - pfree_ext(bwc->bgshared); - pfree_ext(t_thrd.bgworker_cxt.bgwcontext); + pfree_ext(bwc->bgshared); // 释放分配的后台工作共享内存 + pfree_ext(t_thrd.bgworker_cxt.bgwcontext); // 释放分配的后台工作上下文内存 } - slist_init(&t_thrd.bgworker_cxt.bgwlist); + slist_init(&t_thrd.bgworker_cxt.bgwlist); // 初始化后台工作列表 } +/* + * 功能: 同步遍历后台工作进程列表,执行终止或管理操作 + * + * 参数: 无 + * + * 返回值: 无 + */ void BgworkerListSyncQuit() { slist_mutable_iter iter; bool alldone = false; bool sigsent = false; - + // 如果后台工作进程列表为空,直接返回 if (slist_is_empty(&t_thrd.bgworker_cxt.bgwlist)) { return; } loop: alldone = true; + // 遍历后台工作进程列表 slist_foreach_modify(iter, &t_thrd.bgworker_cxt.bgwlist) { + // 获取当前遍历到的后台工作进程 BackgroundWorker *bgw = slist_container(BackgroundWorker, rw_lnode, iter.cur); + // 如果后台工作进程状态为 BGW_FAILED 或 BGW_TERMINATED if (bgw->bgw_status == BGW_FAILED || bgw->bgw_status == BGW_TERMINATED) { - slist_delete_current(&iter); + slist_delete_current(&iter); // 从列表中删除后台工作进程 + // 获取全局后台工作进程列表互斥锁,防止多个线程同时访问列表 pthread_mutex_lock(&g_instance.bgw_base_lock); + // 将当前后台工作进程放回空闲列表 BgworkerPutBackToFreeList(bgw); + // 释放全局后台工作进程列表互斥锁,允许其他线程访问列表 pthread_mutex_unlock(&g_instance.bgw_base_lock); - } else if (bgw->bgw_status == BGW_NOT_YET_STARTED) { + } else if (bgw->bgw_status == BGW_NOT_YET_STARTED) { // 如果后台工作进程状态为 BGW_NOT_YET_STARTED alldone = false; + // 增加状态持续时间,如果超过限制且尚未禁用,则标记为失败 if (++bgw->bgw_status_dur > BGWORKER_STATUS_DURLIMIT && (pg_atomic_fetch_add_u32(&bgw->disable_count, 1) == 0)) { bgw->bgw_status = BGW_FAILED; } } else { + // 如果后台工作进程状态为其他值 + // 如果之前没有发送过信号且成功地发送了 SIGINT 信号给指定的进程 if (!sigsent && gs_signal_send(bgw->bgw_notify_pid, SIGINT) != 0) { ereport(WARNING, (errmsg("BgworkerListSyncQuit kill(pid %lu, stat %d) failed: %m", - bgw->bgw_notify_pid, bgw->bgw_status))); + bgw->bgw_notify_pid, bgw->bgw_status))); // 警告消息记录到日志 } - alldone = false; + alldone = false; // 表示尚未完成所有后台工作进程的处理。 } } + // 如果未完成遍历,等待一段时间并继续 if (!alldone) { usleep(BGWORKER_LOOP_SLEEP_TIME); sigsent = true; goto loop; } + // 清理后台工作进程的共享上下文 BgworkerCleanupSharedContext(); } +/* + * 清理未能成功启动的后台工作进程 + * + * 该函数用于遍历后台工作进程列表,如果发现某个后台工作进程的状态为 BGW_NOT_YET_STARTED, + * 即未能成功启动,就将其从列表中删除,并将其返回到空闲列表中。这样做是为了确保未能成功启动的 + * 后台工作进程不会影响后续处理。 + * + * 参数: nunstarts 未能成功启动的后台工作进程的数量 + */ static inline void CleanupUnstartBgworkers(int nunstarts) { slist_mutable_iter iter; + // 如果存在未能成功启动的后台工作进程 if (nunstarts > 0) { + // 遍历后台工作进程列表 slist_foreach_modify(iter, &t_thrd.bgworker_cxt.bgwlist) { + // 获取当前迭代器指向的后台工作进程结构 BackgroundWorker *bgw = slist_container(BackgroundWorker, rw_lnode, iter.cur); + // 如果当前后台工作进程的状态为 BGW_NOT_YET_STARTED,即未能成功启动 if (bgw->bgw_status == BGW_NOT_YET_STARTED) { /* the bgworker thread is unable to start, remove it from the waiting list */ - slist_delete_current(&iter); - pthread_mutex_lock(&g_instance.bgw_base_lock); - BgworkerPutBackToFreeList(bgw); - pthread_mutex_unlock(&g_instance.bgw_base_lock); + slist_delete_current(&iter); // 从列表中删除当前迭代器指向的元素 + pthread_mutex_lock(&g_instance.bgw_base_lock); // 获取全局互斥锁,以便对全局数据进行修改 + BgworkerPutBackToFreeList(bgw); // 将未能成功启动的后台工作进程返回到空闲列表中 + pthread_mutex_unlock(&g_instance.bgw_base_lock); // 释放全局互斥锁 } } } } +/* + * BgworkerListWaitFinish + * + * 功能:用于等待后台工作者的完成状态,直到所有的后台工作者都完成了任务为止 + * + * 参数: + * nparticipants:表示参与任务的后台工作者的数量 + */ void BgworkerListWaitFinish(int *nparticipants) { slist_iter iter; - bool alldone = false; - uint32 disable_count; - int nfinished; - int nunstarts = 0; + bool alldone = false; // 表示是否所有后台工作者都已完成任务 + uint32 disable_count; // 用于存储禁用计数的变量 + int nfinished; // 记录已完成任务的后台工作者数量 + int nunstarts = 0; // 记录未能启动的后台工作者数量 - Assert(nparticipants != NULL); + Assert(nparticipants != NULL); // 断言,确保传入的参数 nparticipants 不为空 + // 在等待状态报告中设置当前状态为等待同步的后台工作者 WaitState oldStatus = pgstat_report_waitstatus(STATE_WAIT_SYNC_BGWORKERS); + // 循环,直到所有后台工作者都完成了任务 while (!alldone) { nfinished = 0; + // 遍历后台工作者列表 slist_foreach(iter, &t_thrd.bgworker_cxt.bgwlist) { + // 获取当前迭代中的后台工作者 BackgroundWorker *bgw = slist_container(BackgroundWorker, rw_lnode, iter.cur); + // 如果后台工作者状态为 BGW_NOT_YET_STARTED,且超过了状态持续时间限制 if (bgw->bgw_status == BGW_NOT_YET_STARTED && ++bgw->bgw_status_dur > BGWORKER_STATUS_DURLIMIT) { - disable_count = pg_atomic_fetch_add_u32(&bgw->disable_count, 1); + disable_count = pg_atomic_fetch_add_u32(&bgw->disable_count, 1); // 原子操作地增加后台工作者的禁用计数 + // 如果禁用计数为 0,表示该后台工作者需要被禁用 if (disable_count == 0) { + // 输出警告信息,表示该后台工作者在 5 秒内未能启动,被禁用 ereport(WARNING, (errmsg("The bgworker thread %lu hasn't started in 5 seconds, disable it.", bgw->bgw_notify_pid))); - (*nparticipants)--; - nunstarts++; - } - } else if (bgw->bgw_status == BGW_FAILED) { + (*nparticipants)--; // 减少参与任务的后台工作者数量 + nunstarts++; // 增加未启动的后台工作者数量 + } + } else if (bgw->bgw_status == BGW_FAILED) { // 如果后台工作者状态为 BGW_FAILED + // 检查后台工作者的错误级别是否大于或等于 ERROR if (bgw->bgw_edata.elevel >= ERROR) { + // 输出错误信息,包括错误码、错误消息和错误详情 ereport(bgw->bgw_edata.elevel, (errcode(bgw->bgw_edata.sqlerrcode), errmsg("%s", bgw->bgw_edata.message), errdetail("%s", bgw->bgw_edata.detail))); } else { + // 输出错误信息,表示后台工作者在并行索引构建过程中失败了 ereport(ERROR, (errcode(ERRCODE_IN_FAILED_SQL_TRANSACTION), errmsg("Background worker failed during parallel index building."))); } - } else if (bgw->bgw_status == BGW_TERMINATED) { - nfinished++; + } else if (bgw->bgw_status == BGW_TERMINATED) { // 如果后台工作者状态为 BGW_TERMINATED + nfinished++; // 已完成任务的后台工作者数量+1 } } - alldone = (nfinished >= *nparticipants); + alldone = (nfinished >= *nparticipants); // 判断是否所有后台工作者都已完成任务 + // 如果所有后台工作者都已完成任务,进行清理未启动的后台工作者 if (alldone) { CleanupUnstartBgworkers(nunstarts); } else { + // 在等待期间检查是否有中断请求,然后进行短暂的等待 CHECK_FOR_INTERRUPTS(); usleep(BGWORKER_LOOP_SLEEP_TIME); } } - pgstat_report_waitstatus(oldStatus); + pgstat_report_waitstatus(oldStatus); // 恢复之前的等待状态报告 } +/* + * LaunchBackgroundWorkers + * + * 功能:启动指定数量的后台工作者进程,用于并行处理任务 + * + * 参数: + * nworkers: 要启动的后台工作者数量 + * bgshared: 共享给后台工作者的数据结构 + * bgmain: 后台工作者的主入口函数 + * bgexit: 后台工作者的退出入口函数 + * + * 返回值: + * 返回实际成功启动的后台工作者数量 + */ int LaunchBackgroundWorkers(int nworkers, void *bgshared, bgworker_main bgmain, bgworker_exit bgexit) { int actualWorkers = 0; MemoryContext oldcontext; BgWorkerContext *bwc; - Assert(nworkers > 0); + Assert(nworkers > 0); // 确保要启动的后台工作者数量大于 0 /* We need to be a lock group leader. */ - BecomeLockGroupLeader(); + BecomeLockGroupLeader(); // 成为锁组的领导者 /* We might be running in a short-lived memory context. */ - oldcontext = MemoryContextSwitchTo(u_sess->top_transaction_mem_cxt); + oldcontext = MemoryContextSwitchTo(u_sess->top_transaction_mem_cxt); // 保存旧的内存上下文 /* * Start workers. @@ -533,30 +705,34 @@ int LaunchBackgroundWorkers(int nworkers, void *bgshared, bgworker_main bgmain, * fails. It wouldn't help much anyway, because registering the worker in * no way guarantees that it will start up and initialize successfully. */ + // 开始创建工作者 + // 分配一个后台工作者上下文内存,并将其初始化为零 bwc = (BgWorkerContext*)MemoryContextAllocZero( INSTANCE_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_STORAGE), sizeof(BgWorkerContext)); - bwc->transactionCxt.txnId = GetCurrentTransactionIdIfAny(); - bwc->transactionCxt.snapshot = GetActiveSnapshot(); - bwc->bgshared = bgshared; - bwc->databaseName = get_database_name(u_sess->proc_cxt.MyDatabaseId); - bwc->userName = u_sess->proc_cxt.MyProcPort->user_name; + bwc->transactionCxt.txnId = GetCurrentTransactionIdIfAny(); // 设置事务上下文的事务ID + bwc->transactionCxt.snapshot = GetActiveSnapshot(); // 设置事务上下文的快照 + bwc->bgshared = bgshared; // 将共享的数据结构指针存储在上下文中 + bwc->databaseName = get_database_name(u_sess->proc_cxt.MyDatabaseId); // 获取当前数据库的名称并存储在上下文中 + bwc->userName = u_sess->proc_cxt.MyProcPort->user_name; // 获取当前会话用户的用户名并存储在上下文中 /* pass enable_cluster_resize to bgwokers to optimize parallel index building performance during redistribution */ - bwc->enable_cluster_resize = u_sess->attr.attr_sql.enable_cluster_resize; - bwc->leader = t_thrd.proc; - bwc->main_entry = bgmain; - bwc->exit_entry = bgexit; + bwc->enable_cluster_resize = u_sess->attr.attr_sql.enable_cluster_resize; // 将集群调整标志传递给后台工作者以优化并行索引构建性能 + bwc->leader = t_thrd.proc; // 设置后台工作者的领导者为当前线程 + bwc->main_entry = bgmain; // 设置后台工作者的主要入口点 + bwc->exit_entry = bgexit; // 设置后台工作者的退出入口点 - t_thrd.bgworker_cxt.bgwcontext = bwc; + t_thrd.bgworker_cxt.bgwcontext = bwc; // 将刚刚初始化的后台工作者上下文设置到全局上下文中 - StreamSaveTxnContext(&bwc->transactionCxt); + StreamSaveTxnContext(&bwc->transactionCxt); // 将事务上下文保存到流复制上下文中,以便在后续流复制进程中使用 - for (int i = 0; i < nworkers; ++i) { + for (int i = 0; i < nworkers; ++i) { // 遍历注册指定数量的后台工作者 + // 调用 RegisterBackgroundWorker 函数注册后台工作者 + // 如果注册成功,则增加 actualWorkers 计数 if (RegisterBackgroundWorker(bwc)) { actualWorkers++; } } /* Restore previous memory context. */ - MemoryContextSwitchTo(oldcontext); - return actualWorkers; + MemoryContextSwitchTo(oldcontext); // 恢复之前的内存上下文 + return actualWorkers; // 返回实际注册的后台工作者数量 } diff --git a/src/gausskernel/process/postmaster/bgwriter.cpp b/src/gausskernel/process/postmaster/bgwriter.cpp index e5b67dacf..a96269c70 100755 --- a/src/gausskernel/process/postmaster/bgwriter.cpp +++ b/src/gausskernel/process/postmaster/bgwriter.cpp @@ -88,33 +88,50 @@ const int MAX_THREAD_NAME_LEN = 128; static void drop_rel_all_forks_buffers(); static void drop_rel_one_fork_buffers(); +/* + * 功能:用于为 bgwriter 进程设置信号处理函数或信号处理方式,以适应其需求。 + * 它忽略了某些信号,设置了一些信号的处理方式为默认值,并指定了一些信号的处理函数。 + * + * 参数:无 + * + * 返回值:无 + */ static void setup_bgwriter_signalhook(void) { /* * Reset some signals that are accepted by postmaster but not here */ - (void)gspqsignal(SIGHUP, bgwriter_sighup_handler); /* set flag to read config file */ - (void)gspqsignal(SIGINT, SIG_IGN); - (void)gspqsignal(SIGTERM, bgwriter_request_shutdown_handler); /* shutdown */ - (void)gspqsignal(SIGQUIT, bgwriter_quickdie); /* hard crash time */ - (void)gspqsignal(SIGALRM, SIG_IGN); - (void)gspqsignal(SIGPIPE, SIG_IGN); - (void)gspqsignal(SIGUSR1, bgwriter_sigusr1_handler); - (void)gspqsignal(SIGUSR2, SIG_IGN); + (void)gspqsignal(SIGHUP, bgwriter_sighup_handler); // 当收到SIGHUP信号时,设置标志以重新读取配置文件 + (void)gspqsignal(SIGINT, SIG_IGN); // 忽略SIGINT信号(中断信号) + (void)gspqsignal(SIGTERM, bgwriter_request_shutdown_handler); // 当收到SIGTERM信号时,请求关闭进程 + (void)gspqsignal(SIGQUIT, bgwriter_quickdie); // 当收到SIGQUIT信号时,执行快速崩溃 + (void)gspqsignal(SIGALRM, SIG_IGN); // 忽略SIGALRM信号(定时器信号) + (void)gspqsignal(SIGPIPE, SIG_IGN); // SIGPIPE信号的默认行为是终止进程,将SIGPIPE信号的处理方式设置为忽略,不会导致进程终止,而是允许程序继续执行 + (void)gspqsignal(SIGUSR1, bgwriter_sigusr1_handler); // 当收到SIGUSR1信号时,调用相应的处理函数 + (void)gspqsignal(SIGUSR2, SIG_IGN); // 忽略SIGUSR2信号 /* * Reset some signals that are accepted by postmaster but not here */ - (void)gspqsignal(SIGCHLD, SIG_DFL); + (void)gspqsignal(SIGCHLD, SIG_DFL); // 设置SIGCHLD信号的默认处理方式 (void)gspqsignal(SIGTTIN, SIG_DFL); (void)gspqsignal(SIGTTOU, SIG_DFL); (void)gspqsignal(SIGCONT, SIG_DFL); (void)gspqsignal(SIGWINCH, SIG_DFL); /* We allow SIGQUIT (quickdie) at all times */ - sigdelset(&t_thrd.libpq_cxt.BlockSig, SIGQUIT); + sigdelset(&t_thrd.libpq_cxt.BlockSig, SIGQUIT); // 从信号屏蔽集中移除 SIGQUIT 信号 } +/* + * 功能:用于在后台写入进程遇到异常情况时执行一系列清理操作,以尽量保证进程能够继续正常运行。 + * + * 参数: + * - wb_context: 写回上下文,用于控制后台写入进程的行为。 + * - bgwriter_cxt: 后台写入进程的内存上下文。 + * + * 返回值: 无 + */ static void bgwriter_handle_exceptions(WritebackContext wb_context, MemoryContext bgwriter_cxt) { /* @@ -187,90 +204,102 @@ static void bgwriter_handle_exceptions(WritebackContext wb_context, MemoryContex * This is invoked from AuxiliaryProcessMain, which has already created the * basic execution environment, but not enabled signals yet. */ + +/* + * 功能:后台写入进程 (bgwriter) 的主要入口点。 + * 其从 AuxiliaryProcessMain 被调用的,AuxiliaryProcessMain 已经创建了基本的执行环境, + * 但还没有启用信号。 + * + * 参数: 无 + * + * 返回值: 无 + */ void BackgroundWriterMain(void) { - sigjmp_buf local_sigjmp_buf; - MemoryContext bgwriter_context; - bool prev_hibernate = false; - WritebackContext wb_context; + sigjmp_buf local_sigjmp_buf; // 声明用于保存跳转位置的缓冲区 + MemoryContext bgwriter_context; // 声明后台写入进程的内存上下文 + bool prev_hibernate = false; // 声明前一个休眠状态,用于控制休眠 + WritebackContext wb_context; // 写回上下文,用于控制后台写入进程的行为 - t_thrd.role = BGWRITER; + t_thrd.role = BGWRITER; // 设置当前线程角色为后台写入进程 - ereport(LOG, (errmsg("bgwriter started"))); + ereport(LOG, (errmsg("bgwriter started"))); // 记录日志,标记 bgwriter 开始运行 - setup_bgwriter_signalhook(); + setup_bgwriter_signalhook(); // 设置信号处理函数 /* * We just started, assume there has been either a shutdown or * end-of-recovery snapshot. */ - last_snapshot_ts = GetCurrentTimestamp(); + last_snapshot_ts = GetCurrentTimestamp(); // 获取当前时间戳作为最后快照时间戳 /* * Create a resource owner to keep track of our resources (currently only * buffer pins). */ t_thrd.utils_cxt.CurrentResourceOwner = ResourceOwnerCreate(NULL, "Background Writer", - THREAD_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_STORAGE)); + THREAD_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_STORAGE)); // 创建资源所有者并设置名称 /* * Create a memory context that we will do all our work in. We do this so * that we can reset the context during error recovery and thereby avoid * possible memory leaks. Formerly this code just ran in * t_thrd.top_mem_cxt, but resetting that would be a really bad idea. */ + // 创建内存上下文,在其中执行所有工作。便于在错误恢复期间能够重置上下文,避免可能的内存泄漏 bgwriter_context = AllocSetContextCreate(t_thrd.top_mem_cxt, "Background Writer", ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, - ALLOCSET_DEFAULT_MAXSIZE); - MemoryContextSwitchTo(bgwriter_context); + ALLOCSET_DEFAULT_MAXSIZE); // 创建后台写入进程的内存上下文 + MemoryContextSwitchTo(bgwriter_context); // 切换到新创建的内存上下文 - WritebackContextInit(&wb_context, &u_sess->attr.attr_storage.bgwriter_flush_after); + WritebackContextInit(&wb_context, &u_sess->attr.attr_storage.bgwriter_flush_after); // 初始化写回上下文 /* * If an exception is encountered, processing resumes here. * * See notes in postgres.c about the design of this coding. */ - int curTryCounter; - int* oldTryCounter = NULL; - if (sigsetjmp(local_sigjmp_buf, 1) != 0) { - gstrace_tryblock_exit(true, oldTryCounter); - bgwriter_handle_exceptions(wb_context, bgwriter_context); + // 遇到异常,从此开始执行 + int curTryCounter; // 用于保存当前的错误尝试计数器 + int* oldTryCounter = NULL; // 用于保存旧的错误尝试计数器指针 + if (sigsetjmp(local_sigjmp_buf, 1) != 0) { // 设置跳转点以处理异常 + gstrace_tryblock_exit(true, oldTryCounter); // 退出错误尝试块 + bgwriter_handle_exceptions(wb_context, bgwriter_context); // 处理异常 /* Report wait end here, when there is no further possibility of wait */ - pgstat_report_waitevent(WAIT_EVENT_END); + pgstat_report_waitevent(WAIT_EVENT_END); // 报告等待事件结束 } - oldTryCounter = gstrace_tryblock_entry(&curTryCounter); + oldTryCounter = gstrace_tryblock_entry(&curTryCounter); // 进入错误尝试块,并获取旧的错误尝试计数器 /* We can now handle ereport(ERROR) */ - t_thrd.log_cxt.PG_exception_stack = &local_sigjmp_buf; + t_thrd.log_cxt.PG_exception_stack = &local_sigjmp_buf; // 设置异常堆栈 /* * Unblock signals (they were blocked when the postmaster forked us) */ - gs_signal_setmask(&t_thrd.libpq_cxt.UnBlockSig, NULL); - (void)gs_signal_unblock_sigusr2(); + gs_signal_setmask(&t_thrd.libpq_cxt.UnBlockSig, NULL); // 解除信号掩码 + (void)gs_signal_unblock_sigusr2(); // 解除 SIGUSR2 信号的阻塞 /* * Use the recovery target timeline ID during recovery */ - if (RecoveryInProgress()) - t_thrd.xlog_cxt.ThisTimeLineID = GetRecoveryTargetTLI(); + if (RecoveryInProgress()) // 如果正在进行恢复操作 + t_thrd.xlog_cxt.ThisTimeLineID = GetRecoveryTargetTLI(); // 获取恢复目标时间线 ID /* * Reset hibernation state after any error. */ - prev_hibernate = false; + prev_hibernate = false; // 设置初始化休眠状态 - pgstat_report_appname("Background writer"); - pgstat_report_activity(STATE_IDLE, NULL); + pgstat_report_appname("Background writer"); // 报告应用程序名称到统计信息 + pgstat_report_activity(STATE_IDLE, NULL); // 报告活动状态为空闲 /* * Loop forever */ for (;;) { - bool can_hibernate = false; + bool can_hibernate = false; // 是否可以休眠的标志 int rc; /* @@ -279,25 +308,25 @@ void BackgroundWriterMain(void) */ if (pg_atomic_read_u32(&g_instance.dw_batch_cxt.dw_version) < DW_SUPPORT_REABLE_DOUBLE_WRITE && t_thrd.proc->workingVersionNum >= DW_SUPPORT_REABLE_DOUBLE_WRITE) { - dw_upgrade_renable_double_write(); + dw_upgrade_renable_double_write(); // 执行双写升级操作 } /* Clear any already-pending wakeups */ - ResetLatch(&t_thrd.proc->procLatch); + ResetLatch(&t_thrd.proc->procLatch); // 重置进程的 latch - pgstat_report_activity(STATE_RUNNING, NULL); + pgstat_report_activity(STATE_RUNNING, NULL); // 报告活动状态为运行中 - if (t_thrd.bgwriter_cxt.got_SIGHUP) { - t_thrd.bgwriter_cxt.got_SIGHUP = false; - ProcessConfigFile(PGC_SIGHUP); + if (t_thrd.bgwriter_cxt.got_SIGHUP) { // 如果收到了 SIGHUP 信号 + t_thrd.bgwriter_cxt.got_SIGHUP = false; // 清除 SIGHUP 信号标志 + ProcessConfigFile(PGC_SIGHUP); // 处理配置文件变更 } - if (t_thrd.bgwriter_cxt.shutdown_requested) { - /* + if (t_thrd.bgwriter_cxt.shutdown_requested) { // 如果请求了关闭 + /* * From here on, elog(ERROR) should end with exit(1), not send * control back to the sigsetjmp block above */ - u_sess->attr.attr_common.ExitOnAnyError = true; + u_sess->attr.attr_common.ExitOnAnyError = true; // 设置在任何错误时退出 /* Normal exit from the bgwriter is here */ proc_exit(0); /* done */ } @@ -310,16 +339,24 @@ void BackgroundWriterMain(void) /* * Send off activity statistics to the stats collector */ - pgstat_send_bgwriter(); + pgstat_send_bgwriter(); // 发送后台写入进程的统计信息 - if (FirstCallSinceLastCheckpoint()) { + if (FirstCallSinceLastCheckpoint()) { // 如果自上次检查点以来是首次调用 /* * After any checkpoint, close all smgr files. This is so we * won't hang onto smgr references to deleted files indefinitely. */ - smgrcloseall(); + smgrcloseall(); // 关闭所有 smgr 文件 } + // 描述在后台写入进程(bgwriter)中定期记录 xl_running_xacts 的目的和原因 /* + *1.维护复制一致性: 通过记录 xl_running_xacts,帮助数据库复制进程更快地达到一致状态。这对于确保主数据库和备份数据库的数据一致性非常重要,尤其是在处理子溢出的快照时。 + *2.资源清理: 定期记录有助于更频繁地清理资源,如锁和 KnownXids* 结构。这有助于减少资源占用,提高数据库性能和稳定性。 + *3.低成本: 记录 xl_running_xacts 的成本相对较低,因此可以定期执行而不会对系统性能产生显著影响。 + *4.适当的执行频率: 记录操作每分钟执行 4 次,这个频率足以维护复制一致性,同时也不会对系统的超时处理造成不必要的复杂性。 + *5.选择后台写入进程执行: 由于后台写入进程是唯一定期执行并且会一直返回到其主循环的进程,因此在此处执行记录操作是合适的。 + 其他进程,如检查点进程,通常不会频繁执行其主循环,不适合用于定期记录 xl_running_xacts。 + * Log a new xl_running_xacts every now and then so replication can get * into a consistent state faster (think of suboverflowed snapshots) * and clean up resources (locks, KnownXids*) more frequently. The @@ -340,21 +377,21 @@ void BackgroundWriterMain(void) * time. E.g. Checkpointer, when active, is barely ever in its * mainloop and thus makes it hard to log regularly. */ - if (XLogStandbyInfoActive() && !RecoveryInProgress()) { + if (XLogStandbyInfoActive() && !RecoveryInProgress()) { // 如果正在运行热备,且不在恢复过程中 TimestampTz timeout = 0; - TimestampTz now = GetCurrentTimestamp(); - timeout = TimestampTzPlusMilliseconds(last_snapshot_ts, LOG_SNAPSHOT_INTERVAL_MS); + TimestampTz now = GetCurrentTimestamp(); // 获取当前时间戳 + timeout = TimestampTzPlusMilliseconds(last_snapshot_ts, LOG_SNAPSHOT_INTERVAL_MS); // 计算下次记录时间 /* * only log if enough time has passed and some xlog record has been * inserted. */ - if (now >= timeout && !XLByteEQ(last_snapshot_lsn, GetXLogInsertRecPtr())) { - last_snapshot_lsn = LogStandbySnapshot(); - last_snapshot_ts = now; + if (now >= timeout && !XLByteEQ(last_snapshot_lsn, GetXLogInsertRecPtr())) { // 检查是否应记录 + last_snapshot_lsn = LogStandbySnapshot(); // 记录热备快照 + last_snapshot_ts = now; // 更新最后快照时间 } if (now >= timeout) { - LogCheckSlot(); + LogCheckSlot(); // 记录检查槽信息 } } @@ -368,12 +405,16 @@ void BackgroundWriterMain(void) * down with latch events that are likely to happen frequently during * normal operation. */ - pgstat_report_activity(STATE_IDLE, NULL); + pgstat_report_activity(STATE_IDLE, NULL); // 报告活动状态为空闲 rc = WaitLatch(&t_thrd.proc->procLatch, WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH, - u_sess->attr.attr_storage.BgWriterDelay /* ms */); + u_sess->attr.attr_storage.BgWriterDelay /* ms */); // 等待信号或超时 /* + *1.休眠模式延长:当没有标志事件触发并且 BgBufferSync 表示系统当前没有活动时,会进入 "休眠" 模式, + 休眠时间比 bgwriter_delay 规定的时间更长,以节省能源。 + *2.潜在的竞争条件:存在潜在的竞争条件,即后台进程可能在 BgBufferSync 观察到无缓冲区分配计数后分配缓冲区。 + 为减少错过唤醒的可能性,只有在连续两个周期没有活动时才会进入休眠,并且不会永久休眠。 * If no latch event and BgBufferSync says nothing's happening, extend * the sleep in "hibernation" mode, where we sleep for much longer * than bgwriter_delay says. Fewer wakeups save electricity. When a @@ -406,10 +447,10 @@ void BackgroundWriterMain(void) * Emergency bailout if postmaster has died. This is to avoid the * necessity for manual cleanup of all postmaster children. */ - if (rc & WL_POSTMASTER_DEATH) - gs_thread_exit(1); + if (rc & WL_POSTMASTER_DEATH) // 如果 postmaster 已经死亡 + gs_thread_exit(1); // 紧急退出 - prev_hibernate = can_hibernate; + prev_hibernate = can_hibernate; // 更新前一个休眠状态 } } @@ -423,9 +464,23 @@ void BackgroundWriterMain(void) * Some backend has bought the farm, * so we need to stop what we're doing and exit. */ + +/* + *功能: + * 在收到 postmaster 发送的 SIGQUIT 信号时触发,用于处理信号。 + * 意味着某个后端进程发生了严重错误,需要立即停止当前操作并退出。函数会关闭所有可能的资源并退出进程。 + * 这里不执行 proc_exit() 回调函数,因为共享内存可能已经损坏,所以不尝试清理事务。 + * 为了确保 postmaster 进入系统复位周期,使用 exit(2) 以代码2退出 + * + * 参数: + * SIGNAL_ARGS: 信号处理器的参数宏,用于接收信号信息。 + * + * 返回值: + * 无 + */ static void bgwriter_quickdie(SIGNAL_ARGS) { - gs_signal_setmask(&t_thrd.libpq_cxt.BlockSig, NULL); + gs_signal_setmask(&t_thrd.libpq_cxt.BlockSig, NULL); // 解除信号屏蔽 /* * We DO NOT want to run proc_exit() callbacks -- we're here because @@ -435,7 +490,7 @@ static void bgwriter_quickdie(SIGNAL_ARGS) * things by calling exit() directly, we have to reset the callbacks * explicitly to make this work as intended. */ - on_exit_reset(); + on_exit_reset(); // 重置 exit() 回调函数 /* * Note we do exit(2) not exit(0). This is to force the postmaster into a @@ -445,88 +500,139 @@ static void bgwriter_quickdie(SIGNAL_ARGS) * should ensure the postmaster sees this as a crash, too, but no harm in * being doubly sure.) */ - exit(2); + exit(2); // 使用 exit(2) 退出进程,强制 postmaster 进入系统复位周期 } /* SIGHUP: set flag to re-read config file at next convenient time */ +// 功能:当收到 SIGHUP 信号时,设置标志以在下一个合适的时机重新读取配置文件 static void bgwriter_sighup_handler(SIGNAL_ARGS) { - int save_errno = errno; + int save_errno = errno; // 保存当前的 errno 值 - t_thrd.bgwriter_cxt.got_SIGHUP = true; + t_thrd.bgwriter_cxt.got_SIGHUP = true; // 设置收到 SIGHUP 信号的标志 if (t_thrd.proc) - SetLatch(&t_thrd.proc->procLatch); + SetLatch(&t_thrd.proc->procLatch); // 设置进程的标志事件,以便重新读取配置文件 - errno = save_errno; + errno = save_errno; // 恢复之前保存的 errno 值 } /* SIGTERM: set flag to shutdown and exit */ +// 功能:当收到 SIGTERM 信号时,设置标志以请求关闭并退出 static void bgwriter_request_shutdown_handler(SIGNAL_ARGS) { - int save_errno = errno; + int save_errno = errno; // 保存当前的 errno 值 - t_thrd.bgwriter_cxt.shutdown_requested = true; + t_thrd.bgwriter_cxt.shutdown_requested = true; // 设置请求关闭的标志 if (t_thrd.proc) - SetLatch(&t_thrd.proc->procLatch); + SetLatch(&t_thrd.proc->procLatch);// 设置进程的标志事件,以便请求关闭并退出 - errno = save_errno; + errno = save_errno; // 恢复之前保存的 errno 值 } /* SIGUSR1: used for latch wakeups */ +// 功能:用于标志事件唤醒 static void bgwriter_sigusr1_handler(SIGNAL_ARGS) { - int save_errno = errno; + int save_errno = errno; // 保存当前的 errno 值 - latch_sigusr1_handler(); + latch_sigusr1_handler(); // 处理 SIGUSR1 信号,通常用于唤醒标志事件 - errno = save_errno; + errno = save_errno; // 恢复之前保存的 errno 值 } +// 检查当前进程是否为后台写入进程 +// 参数: 无 +// 返回值:bool类型 [如果当前进程的角色是后台写入进程,则返回 true] bool IsBgwriterProcess(void) { - return (t_thrd.role == BGWRITER); + return (t_thrd.role == BGWRITER); // 如果当前进程的角色是后台写入进程,则返回 true } /* bgwriter view function */ +/* + * 功能:获取当前实例的节点名称。 + * + * 参数:无 + * + * 返回值: + * 如果节点名称未定义,则返回 "not define" 的文本数据。 + * 否则返回节点名称的文本数据。 + */ Datum bgwriter_view_get_node_name() { if (g_instance.attr.attr_common.PGXCNodeName == NULL || g_instance.attr.attr_common.PGXCNodeName[0] == '\0') { - return CStringGetTextDatum("not define"); + return CStringGetTextDatum("not define");// 如果节点名称未定义,则返回 "not define" 的文本数据 } else { - return CStringGetTextDatum(g_instance.attr.attr_common.PGXCNodeName); + return CStringGetTextDatum(g_instance.attr.attr_common.PGXCNodeName); // 否则返回节点名称的文本数据 } } +/* + * 功能:获取后台写入进程执行的实际刷新次数。 + * + * 参数:无 + * + * 返回值: 返回整数 0,表示实际刷新次数。 + */ Datum bgwriter_view_get_actual_flush_num() { - return Int64GetDatum(0); + return Int64GetDatum(0); // 返回整数 0,表示实际刷新次数 } - +/* + * 功能:获取上一次后台写入进程执行的刷新次数。 + * + * 参数:无 + * + * 返回值: 返回整数 0,表示上次刷新次数。 + */ Datum bgwriter_view_get_last_flush_num() { - return Int32GetDatum(0); + return Int32GetDatum(0); // 返回整数 0,表示上次刷新次数 } +/* + * 功能:获取用于刷新的候选缓冲区总数。 + * + * 参数:无 + * + * 返回值: 返回候选缓冲区数量的整数。 + */ Datum bgwriter_view_get_candidate_nums() { int candidate_num = get_curr_candidate_nums(true) + get_curr_candidate_nums(false); - return Int32GetDatum(candidate_num); + return Int32GetDatum(candidate_num); // 返回候选缓冲区数量 } +/* + * 功能:获取用于刷新的缓冲区候选列表的数量。 + * + * 参数:无 + * + * 返回值: 返回缓冲区候选列表数量的整数。 + */ Datum bgwriter_view_get_num_candidate_list() { - return Int64GetDatum(g_instance.ckpt_cxt_ctl->get_buf_num_candidate_list); + return Int64GetDatum(g_instance.ckpt_cxt_ctl->get_buf_num_candidate_list); // 返回缓冲区候选列表数量 } +/* + * 功能:获取后台写入进程执行的缓冲区时钟扫描迭代次数。 + * + * 参数:无 + * + * 返回值: 返回缓冲区时钟扫描次数的整数。 + */ Datum bgwriter_view_get_num_clock_sweep() { - return Int64GetDatum(g_instance.ckpt_cxt_ctl->get_buf_num_clock_sweep); + return Int64GetDatum(g_instance.ckpt_cxt_ctl->get_buf_num_clock_sweep); // 返回缓冲区时钟扫描次数 } +// 定义了后台写入进程视图的列信息 const incre_ckpt_view_col g_bgwriter_view_col[INCRE_CKPT_BGWRITER_VIEW_COL_NUM] = { +// 列名 数据类型 获取数据需要调用的函数 {"node_name", TEXTOID, bgwriter_view_get_node_name}, {"bgwr_actual_flush_total_num", INT8OID, bgwriter_view_get_actual_flush_num}, {"bgwr_last_flush_num", INT4OID, bgwriter_view_get_last_flush_num}, @@ -535,17 +641,24 @@ const incre_ckpt_view_col g_bgwriter_view_col[INCRE_CKPT_BGWRITER_VIEW_COL_NUM] {"get_buf_clock_sweep", INT8OID, bgwriter_view_get_num_clock_sweep}}; -const uint THREAD_SLEEP_TIME = 10 * 60 * 1000; +const uint THREAD_SLEEP_TIME = 10 * 60 * 1000; // 后台写入进程睡眠时间(以毫秒为单位) +/* + * 功能:后台无效缓冲区写入进程的主要执行函数,负责管理无效缓冲区的清理工作。 + * + * 参数:无 + * + * 返回值: 无 + */ void invalid_buffer_bgwriter_main() { - sigjmp_buf localSigjmpBuf; - MemoryContext bgwriter_context; - char name[MAX_THREAD_NAME_LEN] = {0}; - WritebackContext wb_context; - t_thrd.role = SPBGWRITER; + sigjmp_buf localSigjmpBuf; // 用于处理异常的跳转标记 + MemoryContext bgwriter_context; // 内存上下文,用于执行工作并处理错误恢复 + char name[MAX_THREAD_NAME_LEN] = {0}; // 线程名称 + WritebackContext wb_context; // 写入上下文,用于配置写入行为 + t_thrd.role = SPBGWRITER; // 设置线程角色为 SPBGWRITER - setup_bgwriter_signalhook(); - ereport(LOG, (errmsg("invalidate buffer bgwriter started"))); + setup_bgwriter_signalhook(); // 设置信号处理函数 + ereport(LOG, (errmsg("invalidate buffer bgwriter started"))); // 记录日志,表示后台无效缓冲区写入进程已启动 errno_t err_rc = snprintf_s(name, MAX_THREAD_NAME_LEN, MAX_THREAD_NAME_LEN - 1, "%s", "spbgwriter"); securec_check_ss(err_rc, "", ""); @@ -564,10 +677,10 @@ void invalid_buffer_bgwriter_main() ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); MemoryContextSwitchTo(bgwriter_context); - WritebackContextInit(&wb_context, &u_sess->attr.attr_storage.bgwriter_flush_after); + WritebackContextInit(&wb_context, &u_sess->attr.attr_storage.bgwriter_flush_after); // 初始化写入上下文 if (sigsetjmp(localSigjmpBuf, 1) != 0) { - ereport(WARNING, (errmsg("invalidate buffer bgwriter exception occured."))); + ereport(WARNING, (errmsg("invalidate buffer bgwriter exception occured."))); // 处理异常情况 bgwriter_handle_exceptions(wb_context, bgwriter_context); } @@ -575,7 +688,7 @@ void invalid_buffer_bgwriter_main() t_thrd.log_cxt.PG_exception_stack = &localSigjmpBuf; /* Unblock signals (they were blocked when the postmaster forked us) */ - gs_signal_setmask(&t_thrd.libpq_cxt.UnBlockSig, NULL); + gs_signal_setmask(&t_thrd.libpq_cxt.UnBlockSig, NULL); (void)gs_signal_unblock_sigusr2(); /* Use the recovery target timeline ID during recovery */ @@ -583,187 +696,230 @@ void invalid_buffer_bgwriter_main() t_thrd.xlog_cxt.ThisTimeLineID = GetRecoveryTargetTLI(); } - pgstat_report_appname("InvalidBufferBgWriter"); - pgstat_report_activity(STATE_IDLE, NULL); - g_instance.bgwriter_cxt.invalid_buf_proc_latch = &t_thrd.proc->procLatch; + pgstat_report_appname("InvalidBufferBgWriter"); // 设置应用程序名称以进行统计 + pgstat_report_activity(STATE_IDLE, NULL); // 报告进程状态为空闲 + g_instance.bgwriter_cxt.invalid_buf_proc_latch = &t_thrd.proc->procLatch; // 设置 Latch 用于等待事件触发 /* Loop forever */ for (;;) { int rc; if (t_thrd.bgwriter_cxt.got_SIGHUP) { t_thrd.bgwriter_cxt.got_SIGHUP = false; - ProcessConfigFile(PGC_SIGHUP); + ProcessConfigFile(PGC_SIGHUP); // 处理 SIGHUP 信号,重新加载配置文件 } if (t_thrd.bgwriter_cxt.shutdown_requested) { - ereport(LOG, (errmsg("invalidate buffer bgwriter thread shut down"))); - u_sess->attr.attr_common.ExitOnAnyError = true; - proc_exit(0); + ereport(LOG, (errmsg("invalidate buffer bgwriter thread shut down"))); // 记录日志,表示线程即将关闭 + u_sess->attr.attr_common.ExitOnAnyError = true; + proc_exit(0); // 退出线程 } - rc = WaitLatch(&t_thrd.proc->procLatch, WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH, THREAD_SLEEP_TIME); + rc = WaitLatch(&t_thrd.proc->procLatch, WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH, THREAD_SLEEP_TIME); // 等待事件触发 if (rc & WL_POSTMASTER_DEATH) { - gs_thread_exit(1); + gs_thread_exit(1); // 如果 Postmaster 已经关闭,线程退出 } /* Clear any already-pending wakeups */ - ResetLatch(&t_thrd.proc->procLatch); - drop_rel_all_forks_buffers(); - drop_rel_one_fork_buffers(); + ResetLatch(&t_thrd.proc->procLatch); // 清除已经触发的事件 + drop_rel_all_forks_buffers(); // 执行释放所有关系的缓冲区的操作 + drop_rel_one_fork_buffers(); // 执行释放一个关系的缓冲区的操作 } } const int HASH_TABLE_ELEMENT_MIN_NUM = 512; + +/* + * 功能:创建关系文件节点哈希表,用于存储关于删除的关系文件的信息 + * + * 参数: + * name - 哈希表的名称 + * use_heap_mem - 是否使用堆内存 + * + * 返回值: + * 返回创建的哈希表 + */ HTAB *relfilenode_hashtbl_create(const char *name, bool use_heap_mem) { - HASHCTL hashCtrl; - HTAB *hashtbl = NULL; - errno_t rc; + HASHCTL hashCtrl; // 哈希表控制信息结构体 + HTAB *hashtbl = NULL; // 哈希表指针,初始化为空 + errno_t rc; // 用于错误处理的返回码 - rc = memset_s(&hashCtrl, sizeof(hashCtrl), 0, sizeof(hashCtrl)); - securec_check(rc, "", ""); - hashCtrl.hcxt = (MemoryContext)CurrentMemoryContext; - hashCtrl.hash = tag_hash; - hashCtrl.keysize = sizeof(RelFileNode); + rc = memset_s(&hashCtrl, sizeof(hashCtrl), 0, sizeof(hashCtrl)); // 初始化 hashCtrl 结构体为 0 + securec_check(rc, "", ""); // 检查 memset_s 的返回值 + hashCtrl.hcxt = (MemoryContext)CurrentMemoryContext; // 设置哈希表的内存上下文 + hashCtrl.hash = tag_hash; // 设置哈希函数 + hashCtrl.keysize = sizeof(RelFileNode); // 设置键的大小 /* keep entrysize >= keysize, stupid limits */ - hashCtrl.entrysize = sizeof(DelFileTag); + hashCtrl.entrysize = sizeof(DelFileTag); // 设置哈希表中每个条目的大小 - if (use_heap_mem) { + if (use_heap_mem) { // 根据 use_heap_mem 参数判断是否使用堆内存 hashtbl = HeapMemInitHash(name, HASH_TABLE_ELEMENT_MIN_NUM, Max(g_instance.attr.attr_common.max_files_per_process, t_thrd.storage_cxt.max_userdatafiles), &hashCtrl, - (HASH_FUNCTION | HASH_ELEM)); + (HASH_FUNCTION | HASH_ELEM)); // 在堆内存上创建哈希表 if (hashtbl == NULL) { - ereport(FATAL, (errmsg("could not initialize unlinik relation hash table"))); + ereport(FATAL, (errmsg("could not initialize unlinik relation hash table"))); // 如果创建失败,报致命错误 } } else { + // 在当前内存上下文中创建哈希表 hashtbl = hash_create(name, HASH_TABLE_ELEMENT_MIN_NUM, &hashCtrl, (HASH_CONTEXT | HASH_FUNCTION | HASH_ELEM)); } - return hashtbl; + return hashtbl; // 返回创建的哈希表指针 } +/* + * 功能:创建关系文件节点分支哈希表,用于存储关于删除的关系文件分支的信息 + * + * 参数: + * name - 哈希表的名称 + * use_heap_mem - 是否使用堆内存 + * + * 返回值: + * 返回创建的哈希表 + */ HTAB *relfilenode_fork_hashtbl_create(const char* name, bool use_heap_mem) { - HASHCTL hashCtrl; - HTAB *hashtbl = NULL; - errno_t rc; + HASHCTL hashCtrl; // 哈希表控制信息结构体 + HTAB *hashtbl = NULL; // 哈希表指针,初始化为空 + errno_t rc; // 用于错误处理的返回码 - rc = memset_s(&hashCtrl, sizeof(hashCtrl), 0, sizeof(hashCtrl)); - securec_check(rc, "", ""); - hashCtrl.hcxt = (MemoryContext)CurrentMemoryContext; - hashCtrl.hash = tag_hash; - hashCtrl.keysize = sizeof(ForkRelFileNode); + rc = memset_s(&hashCtrl, sizeof(hashCtrl), 0, sizeof(hashCtrl)); // 初始化 hashCtrl 结构体为 0 + securec_check(rc, "", ""); // 检查 memset_s 的返回值 + hashCtrl.hcxt = (MemoryContext)CurrentMemoryContext; // 设置哈希表的内存上下文 + hashCtrl.hash = tag_hash; // 设置哈希函数 + hashCtrl.keysize = sizeof(ForkRelFileNode); // 设置键的大小 /* keep entrysize >= keysize, stupid limits */ - hashCtrl.entrysize = sizeof(DelForkFileTag); + hashCtrl.entrysize = sizeof(DelForkFileTag); // 设置哈希表中每个条目的大小 - if (use_heap_mem) { + if (use_heap_mem) { // 根据 use_heap_mem 参数判断是否使用堆内存 hashtbl = HeapMemInitHash(name, HASH_TABLE_ELEMENT_MIN_NUM, Max(g_instance.attr.attr_common.max_files_per_process, t_thrd.storage_cxt.max_userdatafiles), - &hashCtrl, (HASH_FUNCTION | HASH_ELEM)); + &hashCtrl, (HASH_FUNCTION | HASH_ELEM)); // 在堆内存上创建哈希表 if (hashtbl == NULL) { ereport(FATAL, (errmsg("could not initialize unlinik relation hash table"))); } } else { hashtbl = hash_create(name, HASH_TABLE_ELEMENT_MIN_NUM, &hashCtrl, (HASH_CONTEXT | HASH_FUNCTION | HASH_ELEM)); } - return hashtbl; + return hashtbl; // 返回创建的哈希表指针 } +/* + * 功能: + * 丢弃所有表的所有分支的缓冲区,并注册相关的忘记请求 + * + * 参数: 无 + * + * 返回值:无 + */ static void drop_rel_all_forks_buffers() { - HASH_SEQ_STATUS status; - DelFileTag *entry = NULL; - DelFileTag *temp_entry = NULL; - bool found = false; - uint rel_num = 0; - HTAB *unlink_rel_hashtbl = g_instance.bgwriter_cxt.unlink_rel_hashtbl; - HTAB *rel_bak = relfilenode_hashtbl_create("unlink_rel_bak", false); + HASH_SEQ_STATUS status; // 哈希表遍历状态 + DelFileTag *entry = NULL; // 哈希表中的条目指针 + DelFileTag *temp_entry = NULL; // 临时条目指针 + bool found = false; // 是否找到标志 + uint rel_num = 0; // 关系数量 + HTAB *unlink_rel_hashtbl = g_instance.bgwriter_cxt.unlink_rel_hashtbl; // 获取哈希表指针 + HTAB *rel_bak = relfilenode_hashtbl_create("unlink_rel_bak", false); // 创建临时哈希表 /* Obtains the entry in hashtable. */ - LWLockAcquire(g_instance.bgwriter_cxt.rel_hashtbl_lock, LW_SHARED); - hash_seq_init(&status, unlink_rel_hashtbl); + LWLockAcquire(g_instance.bgwriter_cxt.rel_hashtbl_lock, LW_SHARED); // 获取共享锁 + hash_seq_init(&status, unlink_rel_hashtbl); // 初始化哈希表遍历状态 while ((temp_entry = (DelFileTag *)hash_seq_search(&status)) != NULL) { - entry = (DelFileTag*)hash_search(rel_bak, (void *)&temp_entry->rnode, HASH_ENTER, &found); + entry = (DelFileTag*)hash_search(rel_bak, (void *)&temp_entry->rnode, HASH_ENTER, &found); // 将哈希表中的数据复制到临时哈希表 if (!found) { entry->rnode = temp_entry->rnode; entry->maxSegNo = temp_entry->maxSegNo; rel_num++; } } - LWLockRelease(g_instance.bgwriter_cxt.rel_hashtbl_lock); + LWLockRelease(g_instance.bgwriter_cxt.rel_hashtbl_lock); // 释放共享锁 if (rel_num > 0) { - DropRelFileNodeAllBuffersUsingHash(rel_bak); + DropRelFileNodeAllBuffersUsingHash(rel_bak); // 释放哈希表中所有关系的缓冲区 - hash_seq_init(&status, rel_bak); - while ((temp_entry = (DelFileTag *)hash_seq_search(&status)) != NULL) { - if (temp_entry->maxSegNo == -1) { + hash_seq_init(&status, rel_bak); // 初始化哈希表遍历状态 + while ((temp_entry = (DelFileTag *)hash_seq_search(&status)) != NULL) { // 循环遍历哈希表中的每个条目 + if (temp_entry->maxSegNo == -1) { // 如果maxSegNo为-1,表示不处理该关系 ereport(DEBUG1, (errmodule(MOD_INCRE_BG), errmsg("the max segno is -1, skip forget this rel %u/%u/%u, bucketNode is %d", temp_entry->rnode.spcNode, temp_entry->rnode.dbNode, temp_entry->rnode.relNode, temp_entry->rnode.bucketNode))); - continue; + continue; // 跳过不处理的关系 } - for (int32 i = 0; i < temp_entry->maxSegNo; i++) { - for (int fork_num = 0; fork_num <= (int)MAX_FORKNUM; fork_num++) { - md_register_forget_request(temp_entry->rnode, fork_num, i); + for (int32 i = 0; i < temp_entry->maxSegNo; i++) { // 循环处理关系的每个段 + for (int fork_num = 0; fork_num <= (int)MAX_FORKNUM; fork_num++) { // 循环处理关系的每个分支 + md_register_forget_request(temp_entry->rnode, fork_num, i); // 注册忘记请求 } } - LWLockAcquire(g_instance.bgwriter_cxt.rel_hashtbl_lock, LW_EXCLUSIVE); + LWLockAcquire(g_instance.bgwriter_cxt.rel_hashtbl_lock, LW_EXCLUSIVE); // 获取锁以操作关系哈希表 + // 如果在哈希表中没有找到关系,报告数据损坏错误 if (hash_search(unlink_rel_hashtbl, (void *)&temp_entry->rnode, HASH_REMOVE, NULL) == NULL) { LWLockRelease(g_instance.bgwriter_cxt.rel_hashtbl_lock); hash_destroy(rel_bak); ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("unlink rel hash table corrupted"))); } else { + // 关系处理完成,记录日志 ereport(DEBUG1, (errmodule(MOD_INCRE_BG), errmsg("invalidate buffer has been finished for rel %u/%u/%u, bucketNode is %d", temp_entry->rnode.spcNode, temp_entry->rnode.dbNode, temp_entry->rnode.relNode, temp_entry->rnode.bucketNode))); } - LWLockRelease(g_instance.bgwriter_cxt.rel_hashtbl_lock); + LWLockRelease(g_instance.bgwriter_cxt.rel_hashtbl_lock); // 释放关系哈希表锁 } } - hash_destroy(rel_bak); + hash_destroy(rel_bak); // 销毁临时哈希表 } +/* + * 功能: + * 释放与一个特定关系的单个分叉文件相关的所有缓冲区,并将其从哈希表中移除 + * + * 参数:无 + * + * 返回值:无 + */ static void drop_rel_one_fork_buffers() { - HASH_SEQ_STATUS status; - DelForkFileTag *entry = NULL; - DelForkFileTag *temp_entry = NULL; - bool found = false; - uint rel_num = 0; - HTAB *unlink_rel_fork_hashtbl = g_instance.bgwriter_cxt.unlink_rel_fork_hashtbl; - HTAB *rel_bak = relfilenode_fork_hashtbl_create("unlink_rel_one_fork_bak", false); + HASH_SEQ_STATUS status; // 哈希表遍历状态 + DelForkFileTag *entry = NULL; // 哈希表中的条目指针 + DelForkFileTag *temp_entry = NULL; // 临时条目指针 + bool found = false; // 是否找到标志 + uint rel_num = 0; // 关系数量 + HTAB *unlink_rel_fork_hashtbl = g_instance.bgwriter_cxt.unlink_rel_fork_hashtbl; // 获取哈希表指针 + HTAB *rel_bak = relfilenode_fork_hashtbl_create("unlink_rel_one_fork_bak", false); // 创建临时哈希表 /* Obtains the entry in hashtable. */ - LWLockAcquire(g_instance.bgwriter_cxt.rel_one_fork_hashtbl_lock, LW_SHARED); - hash_seq_init(&status, unlink_rel_fork_hashtbl); - while ((temp_entry = (DelForkFileTag *)hash_seq_search(&status)) != NULL) { - entry = (DelForkFileTag*)hash_search(rel_bak, temp_entry, HASH_ENTER, &found); + LWLockAcquire(g_instance.bgwriter_cxt.rel_one_fork_hashtbl_lock, LW_SHARED); // 获取哈希表锁(共享模式) + hash_seq_init(&status, unlink_rel_fork_hashtbl); // 初始化哈希表遍历状态 + while ((temp_entry = (DelForkFileTag *)hash_seq_search(&status)) != NULL) { // 循环遍历哈希表中的每个条目 + entry = (DelForkFileTag*)hash_search(rel_bak, temp_entry, HASH_ENTER, &found); // 在临时哈希表中查找或插入条目 if (!found) { + // 如果没有找到,初始化条目数据 entry->forkrnode.rnode.spcNode = temp_entry->forkrnode.rnode.spcNode; entry->forkrnode.rnode.dbNode = temp_entry->forkrnode.rnode.dbNode; entry->forkrnode.rnode.relNode = temp_entry->forkrnode.rnode.relNode; entry->forkrnode.rnode.bucketNode = temp_entry->forkrnode.rnode.bucketNode; entry->forkrnode.forkNum = temp_entry->forkrnode.forkNum; entry->maxSegNo = temp_entry->maxSegNo; - rel_num++; + rel_num++; // 增加关系数量 } } - LWLockRelease(g_instance.bgwriter_cxt.rel_one_fork_hashtbl_lock); + LWLockRelease(g_instance.bgwriter_cxt.rel_one_fork_hashtbl_lock); // 释放哈希表锁 - if (rel_num > 0) { - DropRelFileNodeOneForkAllBuffersUsingHash(rel_bak); - hash_seq_init(&status, rel_bak); + if (rel_num > 0) { // 如果有要处理的关系 + DropRelFileNodeOneForkAllBuffersUsingHash(rel_bak); // 释放临时哈希表中所有关系的缓冲区 + hash_seq_init(&status, rel_bak); // 重新初始化哈希表遍历状态 while ((temp_entry = (DelForkFileTag *)hash_seq_search(&status)) != NULL) { + // 再次遍历临时哈希表中的每个条目 if (temp_entry->maxSegNo == -1) { + // 如果maxSegNo为-1,表示不处理该关系 ereport(DEBUG1, (errmodule(MOD_INCRE_BG), errmsg("the max segno is -1, skip forget this rel %u/%u/%u, bucketNode is %d", temp_entry->forkrnode.rnode.spcNode, temp_entry->forkrnode.rnode.dbNode, temp_entry->forkrnode.rnode.relNode, temp_entry->forkrnode.rnode.bucketNode))); - continue; + continue; // 跳过不处理的关系 } - for (int32 i = 0; i < temp_entry->maxSegNo; i++) { + for (int32 i = 0; i < temp_entry->maxSegNo; i++) { // 循环处理关系的每个段 md_register_forget_request(temp_entry->forkrnode.rnode, temp_entry->forkrnode.forkNum, i); } LWLockAcquire(g_instance.bgwriter_cxt.rel_one_fork_hashtbl_lock, LW_EXCLUSIVE); diff --git a/src/gausskernel/process/postmaster/cbmwriter.cpp b/src/gausskernel/process/postmaster/cbmwriter.cpp index c82192c3b..ad9977aee 100755 --- a/src/gausskernel/process/postmaster/cbmwriter.cpp +++ b/src/gausskernel/process/postmaster/cbmwriter.cpp @@ -56,12 +56,23 @@ static void CBMwriter_sigusr1_handler(SIGNAL_ARGS); * This is invoked from AuxiliaryProcessMain, which has already created the * basic execution environment, but not enabled signals yet. */ +/* + * 功能: + * cbmwriter 进程的主要入口点。负责管理提交位图(CBM)的持久化写入,以及处理配置文件、信号和异常。 + * + * 参数: + * 无 + * + * 返回值: + * 无 + */ void CBMWriterMain(void) { - sigjmp_buf local_sigjmp_buf; - ResourceOwner cbmwriter_resourceOwner; + sigjmp_buf local_sigjmp_buf; // 用于错误处理的跳转点 + ResourceOwner cbmwriter_resourceOwner; // 用于跟踪 CBM Writer 进程的资源 ereport(LOG, (errmsg("cbm writer started"))); + // 根据配置决定检查点超时时间 u_sess->attr.attr_storage.CheckPointTimeout = ENABLE_INCRE_CKPT ? u_sess->attr.attr_storage.incrCheckPointTimeout : u_sess->attr.attr_storage.fullCheckPointTimeout; @@ -75,31 +86,33 @@ void CBMWriterMain(void) /* * Reset some signals that are accepted by postmaster but not here */ - (void)gspqsignal(SIGHUP, CBMSigHupHandler); /* set flag to read config file */ - (void)gspqsignal(SIGINT, CBMShutdownHandler); /* request shutdown */ - (void)gspqsignal(SIGTERM, CBMShutdownHandler); /* request shutdown */ - (void)gspqsignal(SIGQUIT, CBM_quickdie); /* hard crash time */ - (void)gspqsignal(SIGALRM, SIG_IGN); - (void)gspqsignal(SIGPIPE, SIG_IGN); - (void)gspqsignal(SIGUSR1, CBMwriter_sigusr1_handler); - (void)gspqsignal(SIGUSR2, SIG_IGN); /* not used */ + (void)gspqsignal(SIGHUP, CBMSigHupHandler); /* set flag to read config file */ // 收到 SIGHUP 信号时重新读取配置文件 + (void)gspqsignal(SIGINT, CBMShutdownHandler); /* request shutdown */ // 收到 SIGINT 信号时请求正常关闭 + (void)gspqsignal(SIGTERM, CBMShutdownHandler); /* request shutdown */ // 收到 SIGTERM 信号时请求正常关闭 + (void)gspqsignal(SIGQUIT, CBM_quickdie); /* hard crash time */ // 收到 SIGQUIT 信号时执行硬崩溃操作 + (void)gspqsignal(SIGALRM, SIG_IGN); // 忽略 SIGALRM 信号 + (void)gspqsignal(SIGPIPE, SIG_IGN); // 忽略 SIGPIPE 信号 + (void)gspqsignal(SIGUSR1, CBMwriter_sigusr1_handler); // 执行自定义操作以响应 SIGUSR1 信号 + (void)gspqsignal(SIGUSR2, SIG_IGN); /* not used */ // 忽略 SIGUSR2 信号 /* * Reset some signals that are accepted by postmaster but not here */ - (void)gspqsignal(SIGCHLD, SIG_DFL); - (void)gspqsignal(SIGTTIN, SIG_DFL); - (void)gspqsignal(SIGTTOU, SIG_DFL); - (void)gspqsignal(SIGCONT, SIG_DFL); - (void)gspqsignal(SIGWINCH, SIG_DFL); + // 恢复各信号处理程序为系统默认行为 + (void)gspqsignal(SIGCHLD, SIG_DFL); // SIGCHLD 信号在子进程终止或停止时发出 + (void)gspqsignal(SIGTTIN, SIG_DFL); // SIGTTIN 信号在后台进程尝试从终端读取时发出 + (void)gspqsignal(SIGTTOU, SIG_DFL); // SIGTTOU 信号在后台进程尝试向终端写入时发出 + (void)gspqsignal(SIGCONT, SIG_DFL); // SIGCONT 信号用于继续已停止的进程 + (void)gspqsignal(SIGWINCH, SIG_DFL); // SIGWINCH 信号在终端窗口大小变化时发出 /* We allow SIGQUIT (quickdie) at all times */ - sigdelset(&t_thrd.libpq_cxt.BlockSig, SIGQUIT); + sigdelset(&t_thrd.libpq_cxt.BlockSig, SIGQUIT); // 允许 SIGQUIT信号在任何时候都可以触发 /* * Create a resource owner to keep track of our resources (not clear that * we need this, but may as well have one). */ + // 创建 ResourceOwner 用于跟踪管理资源 cbmwriter_resourceOwner = ResourceOwnerCreate(NULL, "CBM Writer", THREAD_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_STORAGE)); t_thrd.utils_cxt.CurrentResourceOwner = cbmwriter_resourceOwner; @@ -109,6 +122,7 @@ void CBMWriterMain(void) * that we can reset the context during error recovery and thereby avoid * possible memory leaks. */ + // 创建内存上下文,以便在错误恢复期间重置上下文,避免内存泄漏 t_thrd.cbm_cxt.cbmwriter_context = AllocSetContextCreate(t_thrd.top_mem_cxt, "CBM Writer", ALLOCSET_DEFAULT_MINSIZE, @@ -124,24 +138,26 @@ void CBMWriterMain(void) /* * If an exception is encountered, processing resumes here. */ + // 发生异常跳转到此处 int curTryCounter; int* oldTryCounter = NULL; if (sigsetjmp(local_sigjmp_buf, 1) != 0) { gstrace_tryblock_exit(true, oldTryCounter); /* Since not using PG_TRY, must reset error stack by hand */ + // 手动重置错误栈 t_thrd.log_cxt.error_context_stack = NULL; t_thrd.log_cxt.call_stack = NULL; /* Prevent interrupts while cleaning up */ - HOLD_INTERRUPTS(); + HOLD_INTERRUPTS(); // 防止在清理期间发生中断 /* Report the error to the server log */ - EmitErrorReport(); + EmitErrorReport(); // 报告错误到服务器日志 /* release resource held by lsc */ - AtEOXact_SysDBCache(false); + AtEOXact_SysDBCache(false); // 释放由 lsc 持有的资源 /* * These operations are really just a minimal subset of * AbortTransaction(). We don't have very many resources to worry @@ -200,7 +216,7 @@ void CBMWriterMain(void) int rc; /* Clear any already-pending wakeups */ - ResetLatch(&t_thrd.proc->procLatch); + ResetLatch(&t_thrd.proc->procLatch); // 清除挂起的唤醒请求 pgstat_report_activity(STATE_RUNNING, NULL); @@ -209,12 +225,12 @@ void CBMWriterMain(void) */ if (t_thrd.cbm_cxt.got_SIGHUP) { t_thrd.cbm_cxt.got_SIGHUP = false; - ProcessConfigFile(PGC_SIGHUP); + ProcessConfigFile(PGC_SIGHUP); // 配置文件 u_sess->attr.attr_storage.CheckPointTimeout = ENABLE_INCRE_CKPT ? u_sess->attr.attr_storage.incrCheckPointTimeout : u_sess->attr.attr_storage.fullCheckPointTimeout; } - + // 如果收到关闭请求,则退出循环 if (t_thrd.cbm_cxt.shutdown_requested) { g_instance.proc_base->cbmwriterLatch = NULL; /* Normal exit from the walwriter is here */ @@ -223,7 +239,8 @@ void CBMWriterMain(void) CBMFollowXlog(); - pgstat_report_activity(STATE_IDLE, NULL); + pgstat_report_activity(STATE_IDLE, NULL); // 报告活动状态 + // 等待事件的发生,包括信号量设置、超时、postmaster 死亡 rc = WaitLatch(&t_thrd.proc->procLatch, WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH, (long)u_sess->attr.attr_storage.CheckPointTimeout * 1000); @@ -231,6 +248,7 @@ void CBMWriterMain(void) /* Emergency bailout if postmaster has died. This is to avoid the * necessity for manual cleanup of all postmaster children. */ + // postmaster 退出时应急退出以避免手动清理所有 postmaster 子进程 if (rc & WL_POSTMASTER_DEATH) { g_instance.proc_base->cbmwriterLatch = NULL; gs_thread_exit(1); @@ -248,11 +266,21 @@ void CBMWriterMain(void) * Some backend has bought the farm, * so we need to stop what we're doing and exit. */ +/* + * 功能:处理CBM Writer进程的紧急退出。 + * + * 参数: SIGNAL_ARGS 信号处理函数的参数 + * 返回值: 无 + */ static void CBM_quickdie(SIGNAL_ARGS) { - g_instance.proc_base->cbmwriterLatch = NULL; - gs_signal_setmask(&t_thrd.libpq_cxt.BlockSig, NULL); + g_instance.proc_base->cbmwriterLatch = NULL; // 设置 cbmwriterLatch 为 NULL,防止后续的唤醒 + gs_signal_setmask(&t_thrd.libpq_cxt.BlockSig, NULL); // 解除信号的阻塞状态 + /* + 当共享内存可能已经受到损坏或出现其他严重问题时,cbmwriter进程会立即终止,而不会执行正常的清理操作或调用`proc_exit()`注册的回调函数。 + 确保迅速终止进程,以防止进一步的数据损坏或其他潜在的风险。 + */ /* * We DO NOT want to run proc_exit() callbacks -- we're here because * shared memory may be corrupted, so we don't want to try to clean up our @@ -263,6 +291,10 @@ static void CBM_quickdie(SIGNAL_ARGS) */ on_exit_reset(); + /* + 以exit(2)退出,强制 postmaster 进入系统复位循环 + 确保如果有人不小心终止了cbmwriter进程,系统会认为发生了严重问题,采取额外措施确保系统稳定性。 + */ /* * Note we do exit(2) not exit(0). This is to force the postmaster into a * system reset cycle if some idiot DBA sends a manual SIGQUIT to a random @@ -275,37 +307,64 @@ static void CBM_quickdie(SIGNAL_ARGS) } /* SIGHUP: set flag to re-read config file at next convenient time */ +/* + * 功能:SIGHUP 信号处理程序,在下一个方便的时间重新读取配置文件 + * + * 参数: + * SIGNAL_ARGS: 信号处理程序的参数列表,提供关于触发信号的上下文信息 + * + * 返回值: + * 无 + */ static void CBMSigHupHandler(SIGNAL_ARGS) { - int save_errno = errno; + int save_errno = errno; // 保存当前的错误码 - t_thrd.cbm_cxt.got_SIGHUP = true; + t_thrd.cbm_cxt.got_SIGHUP = true; // 设置标志以指示需要在下一个方便的时间重新读取配置文件 if (t_thrd.proc) - SetLatch(&t_thrd.proc->procLatch); + SetLatch(&t_thrd.proc->procLatch); // 如果进程存在,则设置进程的Latch以唤醒进程 - errno = save_errno; + errno = save_errno; // 恢复之前保存的错误码 } /* SIGTERM: set flag to exit normally */ +/* + * 功能:SIGTERM 信号处理程序,请求正常退出 + * + * 参数: + * SIGNAL_ARGS: 信号处理程序的参数列表,提供关于触发信号的上下文信息 + * + * 返回值: + * 无 + */ static void CBMShutdownHandler(SIGNAL_ARGS) { - int save_errno = errno; + int save_errno = errno; // 保存当前的错误码 - t_thrd.cbm_cxt.shutdown_requested = true; + t_thrd.cbm_cxt.shutdown_requested = true; // 设置标志以指示需要正常退出 if (t_thrd.proc) - SetLatch(&t_thrd.proc->procLatch); + SetLatch(&t_thrd.proc->procLatch); // 如果进程存在,则设置进程的Latch以唤醒进程 - errno = save_errno; + errno = save_errno; // 恢复之前保存的错误码 } /* SIGUSR1: used for latch wakeups */ +/* + * 功能:SIGUSR1 信号处理程序,用于处理Latch唤醒 + * + * 参数: + * SIGNAL_ARGS: 信号处理程序的参数列表,提供关于触发信号的上下文信息 + * + * 返回值: + * 无 + */ static void CBMwriter_sigusr1_handler(SIGNAL_ARGS) { - int save_errno = errno; + int save_errno = errno; // 保存当前的错误码 - latch_sigusr1_handler(); + latch_sigusr1_handler(); // 处理SIGUSR1信号,唤醒进程 - errno = save_errno; + errno = save_errno; // 恢复之前保存的错误码 } diff --git a/src/gausskernel/process/postmaster/checkpointer.cpp b/src/gausskernel/process/postmaster/checkpointer.cpp index c45b702ee..61b1b1f3a 100755 --- a/src/gausskernel/process/postmaster/checkpointer.cpp +++ b/src/gausskernel/process/postmaster/checkpointer.cpp @@ -115,32 +115,35 @@ * ---------- */ +// 数据结构,用于在共享内存中存储与 checkpointer 进程相关的数据 typedef struct CheckpointerShmemStruct { - ThreadId checkpointer_pid; /* PID (0 if not started) */ - slock_t ckpt_lck; /* protects all the ckpt_* fields */ - int64 fsync_start; - int64 fsync_done; - int64 fsync_request; + ThreadId checkpointer_pid; /* PID (0 if not started) */ // 存储 checkpointer 进程的线程标识符 + slock_t ckpt_lck; /* protects all the ckpt_* fields */ // 用于保护所有与检查点处理相关字段的共享锁 + int64 fsync_start; // 记录文件同步(fsync)的开始时间 + int64 fsync_done; // 记录文件同步(fsync)的结束时间 + int64 fsync_request; // 记录文件同步(fsync)的请求时间 - int ckpt_started; /* advances when checkpoint starts */ - int ckpt_done; /* advances when checkpoint done */ - int ckpt_failed; /* advances when checkpoint fails */ + // 记录检查点的状态 + int ckpt_started; /* advances when checkpoint starts */ // 在检查点开始时递增 + int ckpt_done; /* advances when checkpoint done */ // 在检查点完成时递增 + int ckpt_failed; /* advances when checkpoint fails */ // 在检查点失败时递增 int ckpt_flags; /* checkpoint flags, as defined in xlog.h */ - uint32 num_backend_writes; /* counts user backend buffer writes */ - uint32 num_backend_fsync; /* counts user backend fsync calls */ + uint32 num_backend_writes; /* counts user backend buffer writes */ // 计数用户后端进程执行的缓冲区写入的数量 + uint32 num_backend_fsync; /* counts user backend fsync calls */ // 计数用户后端进程自行执行的文件同步调用的数量 - int num_requests; /* current # of requests */ - int max_requests; /* allocated array size */ - CheckpointerRequest requests[1]; /* VARIABLE LENGTH ARRAY */ + int num_requests; /* current # of requests */ // 当前挂起的检查点请求的数量 + int max_requests; /* allocated array size */ // 已分配的检查点请求数组的最大大小 + CheckpointerRequest requests[1]; /* VARIABLE LENGTH ARRAY */ // 存储检查点请求的信息 } CheckpointerShmemStruct; #ifdef ENABLE_MOT +// 结构体,启用MOT(MemSQL引擎)时,用于存储检查点回调函数的信息 typedef struct CheckpointCallbackItem { - struct CheckpointCallbackItem* next; - CheckpointCallback callback; - void* arg; + struct CheckpointCallbackItem* next; // 指向下一个 CheckpointCallbackItem 结构体的指针,用于创建链表 + CheckpointCallback callback; // 回调函数指针,指向一个特定的函数,在检查点操作期间被调用,用于在检查点前后执行一些自定义操作 + void* arg; // 传递给回调函数的额外参数,回调函数可以使用这个参数来访问和操作特定的数据或上下文信息 } CheckpointCallbackItem; #endif @@ -169,14 +172,21 @@ static void ReqShutdownHandler(SIGNAL_ARGS); * This is invoked from AuxiliaryProcessMain, which has already created the * basic execution environment, but not enabled signals yet. */ +/* + * 功能:checkpointer进程的主要入口点,负责管理检查点进程的核心逻辑,负责处理数据库系统的检查点操作 + * + * 参数: 无 + * + * 返回值: 无 + */ void CheckpointerMain(void) { - sigjmp_buf local_sigjmp_buf; - MemoryContext checkpointer_context; - bool bgwriter_first_startup = true; - + sigjmp_buf local_sigjmp_buf; // 用于跳转回这里的信号处理 + MemoryContext checkpointer_context; // 检查点进程的内存上下文 + bool bgwriter_first_startup = true; // 标记是否是后台写入进程的第一次启动 + // 设置checkpointer进程的PID t_thrd.checkpoint_cxt.CheckpointerShmem->checkpointer_pid = t_thrd.proc_cxt.MyProcPid; - + // 根据是否启用增量检查点来设置检查点超时时间 u_sess->attr.attr_storage.CheckPointTimeout = ENABLE_INCRE_CKPT ? u_sess->attr.attr_storage.incrCheckPointTimeout : u_sess->attr.attr_storage.fullCheckPointTimeout; @@ -191,15 +201,15 @@ void CheckpointerMain(void) * want to wait for the backends to exit, whereupon the postmaster will * tell us it's okay to shut down (via SIGUSR2). */ - (void)gspqsignal(SIGHUP, ChkptSigHupHandler); /* set flag to read config + (void)gspqsignal(SIGHUP, ChkptSigHupHandler); /* set flag to read config // 用于重新加载配置文件 * file */ - (void)gspqsignal(SIGINT, ReqCheckpointHandler); /* request checkpoint */ - (void)gspqsignal(SIGTERM, SIG_IGN); /* ignore SIGTERM */ - (void)gspqsignal(SIGQUIT, chkpt_quickdie); /* hard crash time */ - (void)gspqsignal(SIGALRM, SIG_IGN); - (void)gspqsignal(SIGPIPE, SIG_IGN); - (void)gspqsignal(SIGUSR1, chkpt_sigusr1_handler); - (void)gspqsignal(SIGUSR2, ReqShutdownHandler); /* request shutdown */ + (void)gspqsignal(SIGINT, ReqCheckpointHandler); /* request checkpoint */ // 请求执行检查点 + (void)gspqsignal(SIGTERM, SIG_IGN); /* ignore SIGTERM */ // 忽略SIGTERM + (void)gspqsignal(SIGQUIT, chkpt_quickdie); /* hard crash time */ // 强制终止 + (void)gspqsignal(SIGALRM, SIG_IGN); // 忽略SIGALRM信号 + (void)gspqsignal(SIGPIPE, SIG_IGN); // 忽略SIGPIPE信号 + (void)gspqsignal(SIGUSR1, chkpt_sigusr1_handler); // 设置SIGUSR1信号处理程序为chkpt_sigusr1_handler + (void)gspqsignal(SIGUSR2, ReqShutdownHandler); /* request shutdown */ // 请求关闭 /* * Reset some signals that are accepted by postmaster but not here @@ -211,11 +221,12 @@ void CheckpointerMain(void) (void)gspqsignal(SIGWINCH, SIG_DFL); /* We allow SIGQUIT (quickdie) at all times */ - sigdelset(&t_thrd.libpq_cxt.BlockSig, SIGQUIT); + sigdelset(&t_thrd.libpq_cxt.BlockSig, SIGQUIT); // 允许随时响应 SIGQUIT 信号(用于快速终止进程) /* * Initialize so that first time-driven event happens at the correct time. */ + // 初始化时间相关的变量 t_thrd.checkpoint_cxt.last_checkpoint_time = t_thrd.checkpoint_cxt.last_truncate_log_time = t_thrd.checkpoint_cxt.last_xlog_switch_time = (pg_time_t)time(NULL); @@ -223,6 +234,7 @@ void CheckpointerMain(void) * Create a resource owner to keep track of our resources (currently only * buffer pins). */ + // 创建资源所有者,用于跟踪进程所使用的资源 t_thrd.utils_cxt.CurrentResourceOwner = ResourceOwnerCreate(NULL, "Checkpointer", THREAD_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_STORAGE)); @@ -232,6 +244,7 @@ void CheckpointerMain(void) * possible memory leaks. Formerly this code just ran in * t_thrd.top_mem_cxt, but resetting that would be a really bad idea. */ + // 创建内存上下文,用于执行进程的所有工作。以便在错误恢复期间重置内存上下文,避免可能的内存泄漏 checkpointer_context = AllocSetContextCreate(t_thrd.top_mem_cxt, "Checkpointer", ALLOCSET_DEFAULT_MINSIZE, @@ -244,9 +257,10 @@ void CheckpointerMain(void) * * See notes in postgres.c about the design of this coding. */ + // 遇到异常,从这里继续执行 int curTryCounter; int* oldTryCounter = NULL; - if (sigsetjmp(local_sigjmp_buf, 1) != 0) { + if (sigsetjmp(local_sigjmp_buf, 1) != 0) { // 如果异常被抛出 gstrace_tryblock_exit(true, oldTryCounter); /* * Close all open files after any error. This is helpful on Windows, @@ -255,69 +269,69 @@ void CheckpointerMain(void) */ /* Since not using PG_TRY, must reset error stack by hand */ - t_thrd.log_cxt.error_context_stack = NULL; + t_thrd.log_cxt.error_context_stack = NULL; // 手动重置错误栈 - t_thrd.log_cxt.call_stack = NULL; + t_thrd.log_cxt.call_stack = NULL; // 清除函数调用堆栈信息 /* Prevent interrupts while cleaning up */ - HOLD_INTERRUPTS(); + HOLD_INTERRUPTS(); //阻止中断,以确保在清理期间不会被中断 /* Report the error to the server log */ - EmitErrorReport(); + EmitErrorReport(); // 将错误信息报告到服务器日志中 /* abort async io, must before LWlock release */ - AbortAsyncListIO(); + AbortAsyncListIO(); // 中止异步I/O操作 #ifdef ENABLE_MOT /* cleanups any leftovers in other storage engines (release MOT snapshot lock if taken) */ - CallCheckpointCallback(EVENT_CHECKPOINT_ABORT, 0); + CallCheckpointCallback(EVENT_CHECKPOINT_ABORT, 0); // 清理其他存储引擎的遗留数据 #endif /* release resource held by lsc */ - AtEOXact_SysDBCache(false); + AtEOXact_SysDBCache(false); // 释放了由 lsc持有的资源 /* * These operations are really just a minimal subset of * AbortTransaction(). We don't have very many resources to worry * about in checkpointer, but we do have LWLocks, buffers, and temp * files. */ - LWLockReleaseAll(); - pgstat_report_waitevent(WAIT_EVENT_END); - AbortBufferIO(); - UnlockBuffers(); + LWLockReleaseAll(); // 释放当前进程持有的所有轻量级锁 + pgstat_report_waitevent(WAIT_EVENT_END); // 报告等待事件,用于跟踪等待事件的统计信息 + AbortBufferIO(); // 中止正在进行的缓冲区I/O 操作,以确保在异常情况下不会继续进行未完成的 I/O 操作 + UnlockBuffers(); // 解锁所有的缓冲区 /* buffer pins are released here: */ - ResourceOwnerRelease(t_thrd.utils_cxt.CurrentResourceOwner, RESOURCE_RELEASE_BEFORE_LOCKS, false, true); + ResourceOwnerRelease(t_thrd.utils_cxt.CurrentResourceOwner, RESOURCE_RELEASE_BEFORE_LOCKS, false, true); // 释放资源所有者持有的资源 /* we needn't bother with the other ResourceOwnerRelease phases */ - AtEOXact_Buffers(false); - AtEOXact_SMgr(); - AtEOXact_Files(); - AtEOXact_HashTables(false); + AtEOXact_Buffers(false); // 执行与缓冲区相关的事务结束操作,不应该强制刷新脏页 + AtEOXact_SMgr(); // 执行与存储管理器相关的事务结束操作 + AtEOXact_Files(); // 执行与文件操作相关的事务结束操作 + AtEOXact_HashTables(false); // 执行与哈希表相关的事务结束操作,不清空哈希表 /* Warn any waiting backends that the checkpoint failed. */ - if (t_thrd.checkpoint_cxt.ckpt_active) { + if (t_thrd.checkpoint_cxt.ckpt_active) { // 如果检查点操作正在进行中 /* use volatile pointer to prevent code rearrangement */ volatile CheckpointerShmemStruct* cps = t_thrd.checkpoint_cxt.CheckpointerShmem; - SpinLockAcquire(&cps->ckpt_lck); - cps->ckpt_failed++; - cps->ckpt_done = cps->ckpt_started; - SpinLockRelease(&cps->ckpt_lck); + SpinLockAcquire(&cps->ckpt_lck); //获取锁 + cps->ckpt_failed++; // 递增,表示检查点失败 + cps->ckpt_done = cps->ckpt_started; // 将 ckpt_done 设置为 ckpt_started + SpinLockRelease(&cps->ckpt_lck); // 释放锁 - t_thrd.checkpoint_cxt.ckpt_active = false; + t_thrd.checkpoint_cxt.ckpt_active = false; // 表示检查点操作不再活跃 } /* * Now return to normal top-level context and clear ErrorContext for * next time. */ - MemoryContextSwitchTo(checkpointer_context); - FlushErrorState(); + MemoryContextSwitchTo(checkpointer_context); // 将内存上下文切换回正常的顶级上下文 + FlushErrorState(); // 清除错误上下文,以准备处理下一个异常 /* Flush any leaked data in the top-level context */ - MemoryContextResetAndDeleteChildren(checkpointer_context); + MemoryContextResetAndDeleteChildren(checkpointer_context); // 刷新顶级上下文中可能存在的泄漏数据 /* Now we can allow interrupts again */ - RESUME_INTERRUPTS(); + RESUME_INTERRUPTS(); // 重新允许中断处理 /* * Sleep at least 1 second after any error. A write error is likely @@ -326,7 +340,7 @@ void CheckpointerMain(void) */ pg_usleep(1000000L); } - oldTryCounter = gstrace_tryblock_entry(&curTryCounter); + oldTryCounter = gstrace_tryblock_entry(&curTryCounter); // 记录了进入异常处理块的信息 /* We can now handle ereport(ERROR) */ t_thrd.log_cxt.PG_exception_stack = &local_sigjmp_buf; @@ -334,13 +348,14 @@ void CheckpointerMain(void) /* * Unblock signals (they were blocked when the postmaster forked us) */ + // 解除信号的阻塞 gs_signal_setmask(&t_thrd.libpq_cxt.UnBlockSig, NULL); (void)gs_signal_unblock_sigusr2(); /* * Use the recovery target timeline ID during recovery */ - if (RecoveryInProgress()) + if (RecoveryInProgress()) // 如果出于恢复模式中 t_thrd.xlog_cxt.ThisTimeLineID = GetRecoveryTargetTLI(); /* @@ -354,7 +369,7 @@ void CheckpointerMain(void) * sleeping. */ g_instance.proc_base->checkpointerLatch = &t_thrd.proc->procLatch; - + // 报告检查点进程的应用程序名称和活动状态,用于性能监视和统计 pgstat_report_appname("CheckPointer"); pgstat_report_activity(STATE_IDLE, NULL); @@ -362,27 +377,28 @@ void CheckpointerMain(void) * Loop forever */ for (;;) { - bool do_checkpoint = false; - bool do_dirty_flush = false; - int flags = 0; - pg_time_t now; - int elapsed_secs; - int cur_timeout; - int rc; + bool do_checkpoint = false; // 表示是否需要执行检查点 + bool do_dirty_flush = false; // 表示是否需要执行脏页刷新 + int flags = 0; // 存储检查点的标志 + pg_time_t now; // 记录当前时间 + int elapsed_secs; // 记录距离上次检查点已经过去的秒数 + int cur_timeout; // 记录当前等待的超时时间 + int rc; // 存储等待结果的返回值 /* Clear any already-pending wakeups */ - ResetLatch(&t_thrd.proc->procLatch); + ResetLatch(&t_thrd.proc->procLatch); // 清除当前进程的事件标志,以便等待新事件 - pgstat_report_activity(STATE_RUNNING, NULL); + pgstat_report_activity(STATE_RUNNING, NULL); // 报告当前检查点进程的活动状态,用于统计和诊断的信息 /* * Process any requests or signals received recently. */ - CkptAbsorbFsyncRequests(); + CkptAbsorbFsyncRequests(); // 处理最近接收到的文件同步请求 - if (t_thrd.checkpoint_cxt.got_SIGHUP) { - t_thrd.checkpoint_cxt.got_SIGHUP = false; - ProcessConfigFile(PGC_SIGHUP); + if (t_thrd.checkpoint_cxt.got_SIGHUP) { // 如果收到了SIGHUP信号,表示配置文件已更改,需要重新加载配置 + t_thrd.checkpoint_cxt.got_SIGHUP = false; // 表示已经处理了 SIGHUP 信号 + ProcessConfigFile(PGC_SIGHUP); // 重新加载配置文件 + // 设置参数 增量检查点超时时间 或 完整检查点超时时间 u_sess->attr.attr_storage.CheckPointTimeout = ENABLE_INCRE_CKPT ? u_sess->attr.attr_storage.incrCheckPointTimeout : u_sess->attr.attr_storage.fullCheckPointTimeout; @@ -398,20 +414,21 @@ void CheckpointerMain(void) * responsible for updating the shared memory copy if the * parameter setting changes because of SIGHUP. */ - UpdateSharedMemoryConfig(); + UpdateSharedMemoryConfig(); // 更新共享内存中的配置参数 } - + // 检查是否是数据库的首次启动且不处于恢复状态 if (bgwriter_first_startup && !RecoveryInProgress()) { - t_thrd.checkpoint_cxt.checkpoint_requested = true; - flags = CHECKPOINT_IMMEDIATE; - bgwriter_first_startup = false; + t_thrd.checkpoint_cxt.checkpoint_requested = true; // 表示需要执行检查点 + flags = CHECKPOINT_IMMEDIATE; // 表示执行一个立即检查点 + bgwriter_first_startup = false; // 表示不再是首次启动 + //记录日志消息,指示数据库首次启动并完成恢复 ereport(LOG, (errmsg("database first startup and recovery finish,so do checkpointer"))); } if (t_thrd.checkpoint_cxt.checkpoint_requested) { - t_thrd.checkpoint_cxt.checkpoint_requested = false; - do_checkpoint = true; - u_sess->stat_cxt.BgWriterStats->m_requested_checkpoints++; + t_thrd.checkpoint_cxt.checkpoint_requested = false; // 表示已经处理了检查点请求 + do_checkpoint = true; // 表示需要执行检查点 + u_sess->stat_cxt.BgWriterStats->m_requested_checkpoints++; // 增加请求检查点计数器 } if (t_thrd.checkpoint_cxt.shutdown_requested) { @@ -419,12 +436,14 @@ void CheckpointerMain(void) * From here on, elog(ERROR) should end with exit(1), not send * control back to the sigsetjmp block above */ + // 表示如果出现错误,则终止进程而不是继续处理 u_sess->attr.attr_common.ExitOnAnyError = true; /* Close down the database */ + // 正常退出之前执行关闭数据库 ShutdownXLOG(0, 0); /* Normal exit from the checkpointer is here */ - proc_exit(0); /* done */ + proc_exit(0); /* done */ // 正常退出检查点进程 } /* @@ -435,13 +454,14 @@ void CheckpointerMain(void) */ now = (pg_time_t)time(NULL); elapsed_secs = now - t_thrd.checkpoint_cxt.last_checkpoint_time; - + // 检查自上次检查点以来是否已经过去了足够长的时间 if (elapsed_secs >= u_sess->attr.attr_storage.CheckPointTimeout) { - if (!do_checkpoint) + if (!do_checkpoint) // 如果在外部没有请求检查点 + // 增加统计信息计数器 表示已执行了一个基于时间的检查点 u_sess->stat_cxt.BgWriterStats->m_timed_checkpoints++; - do_checkpoint = true; - flags |= CHECKPOINT_CAUSE_TIME; + do_checkpoint = true; // 表示需要执行检查点 + flags |= CHECKPOINT_CAUSE_TIME; // 表示检查点的触发原因是时间超时 } /* @@ -466,11 +486,11 @@ void CheckpointerMain(void) * checkpoint we should perform, and increase the started-counter * to acknowledge that we've started a new checkpoint. */ - SpinLockAcquire(&cps->ckpt_lck); + SpinLockAcquire(&cps->ckpt_lck); // 获取自旋锁 flags |= cps->ckpt_flags; cps->ckpt_flags = 0; - cps->ckpt_started++; - SpinLockRelease(&cps->ckpt_lck); + cps->ckpt_started++; // 记录了已经开始的检查点数量 + SpinLockRelease(&cps->ckpt_lck); // 释放自旋锁 /* * The end-of-recovery checkpoint is a real checkpoint that's @@ -487,8 +507,10 @@ void CheckpointerMain(void) * implementation will not generate warnings caused by * CheckPointTimeout < CheckPointWarning. */ + // 如果不是重启点且触发原因是 XLOG 记录,并且距离上次检查点时间不足 if (!do_restartpoint && (flags & CHECKPOINT_CAUSE_XLOG) && elapsed_secs < u_sess->attr.attr_storage.CheckPointWarning) + // 发出警告消息 ereport(LOG, (errmsg_plural("checkpoints are occurring too frequently (%d second apart)", "checkpoints are occurring too frequently (%d seconds apart)", @@ -500,38 +522,42 @@ void CheckpointerMain(void) * Initialize checkpointer-private variables used during * checkpoint */ - t_thrd.checkpoint_cxt.ckpt_active = true; + t_thrd.checkpoint_cxt.ckpt_active = true; // 表示检查点进程正在执行检查点操作 - if (!do_restartpoint) - t_thrd.checkpoint_cxt.ckpt_start_recptr = GetInsertRecPtr(); + if (!do_restartpoint) // 检查是否正在执行重启点 + t_thrd.checkpoint_cxt.ckpt_start_recptr = GetInsertRecPtr(); // 获取并记录当前插入记录指针 - t_thrd.checkpoint_cxt.ckpt_start_time = now; - t_thrd.checkpoint_cxt.ckpt_cached_elapsed = 0; + t_thrd.checkpoint_cxt.ckpt_start_time = now; // 记录检查点操作的开始时间 + t_thrd.checkpoint_cxt.ckpt_cached_elapsed = 0; // 记录检查点操作已经执行的时间 if (flags & CHECKPOINT_FLUSH_DIRTY) { - do_dirty_flush = true; + do_dirty_flush = true; // 表示需要执行脏页刷新操作 } /* * Do a normal checkpoint/restartpoint. */ - if (do_dirty_flush) { + if (do_dirty_flush) { // 如果需要执行脏页刷新操作 + // 生成日志消息,指示正在执行文件修复,需要刷新所有脏页 ereport(LOG, (errmsg("[file repair] request checkpoint, flush all dirty page."))); - Assert(RecoveryInProgress()); - if (ENABLE_INCRE_CKPT) { + Assert(RecoveryInProgress()); // 断言当前系统处于崩溃恢复状态 + if (ENABLE_INCRE_CKPT) { // 检查是否启用了增量检查点 + // 设置脏页队尾 g_instance.ckpt_cxt_ctl->full_ckpt_expected_flush_loc = get_dirty_page_queue_tail(); - pg_memory_barrier(); - if (get_dirty_page_num() > 0) { - g_instance.ckpt_cxt_ctl->flush_all_dirty_page = true; + pg_memory_barrier(); // 执行内存屏障操作,确保对内存的修改在多线程环境下正确同步 + if (get_dirty_page_num() > 0) { // 检查是否存在脏页 + g_instance.ckpt_cxt_ctl->flush_all_dirty_page = true; // 表示需要刷新所有脏页、 + // 生成日志消息,指示需要刷新一定数量的脏页 ereport(LOG, (errmsg("[file repair] need flush %ld pages.", get_dirty_page_num()))); - CheckPointBuffers(flags, true); + CheckPointBuffers(flags, true); // 执行检查点操作,将脏页刷新到磁盘上 } } else { CheckPointBuffers(flags, true); } - } else if (!do_restartpoint) { - CreateCheckPoint(flags); - ckpt_performed = true; - if (!bgwriter_first_startup && CheckFpwBeforeFirstCkpt()) { + } else if (!do_restartpoint) { // 执行常规检查点 + CreateCheckPoint(flags); // 执行创建检查点的操作 + ckpt_performed = true; // 表示已经执行了检查点操作 + // 检查是否需要禁用后台写入进程部分操作 + if (!bgwriter_first_startup && CheckFpwBeforeFirstCkpt()) { DisableFpwBeforeFirstCkpt(); } } else { @@ -542,36 +568,39 @@ void CheckpointerMain(void) * After any checkpoint, close all smgr files. This is so we * won't hang onto smgr references to deleted files indefinitely. */ - smgrcloseall(); + smgrcloseall(); // 关闭存储管理器的所有文件句柄,确保不再持有已删除文件的引用 /* * Indicate checkpoint completion to any waiting backends. */ - SpinLockAcquire(&cps->ckpt_lck); - cps->ckpt_done = cps->ckpt_started; - SpinLockRelease(&cps->ckpt_lck); + SpinLockAcquire(&cps->ckpt_lck); // 获取自旋锁 + cps->ckpt_done = cps->ckpt_started; // 表示检查点已经完成 + SpinLockRelease(&cps->ckpt_lck); // 释放 - if (ckpt_performed) { + if (ckpt_performed) { // 如果检查点执行成功 /* * Note we record the checkpoint start time not end time as * t_thrd.checkpoint_cxt.last_checkpoint_time. This is so that time-driven * checkpoints happen at a predictable spacing. */ + // 设置为当前时间,以便下一个基于时间的检查点在可预测的时间间隔内触发 t_thrd.checkpoint_cxt.last_checkpoint_time = now; - } else if (!do_dirty_flush) { + } else if (!do_dirty_flush) { // 如果检查点没有执行成功,且不需要执行脏页刷新 /* * We were not able to perform the restartpoint (checkpoints * throw an ERROR in case of error). Most likely because we * have not received any new checkpoint WAL records since the * last restartpoint. Try again in 15 s. */ + // 等待 15 秒后再次尝试检查点 t_thrd.checkpoint_cxt.last_checkpoint_time = now - u_sess->attr.attr_storage.CheckPointTimeout + 15; } - t_thrd.checkpoint_cxt.ckpt_active = false; + t_thrd.checkpoint_cxt.ckpt_active = false; // 表示检查点进程不再处于活动状态 } /* Check for archive_timeout and switch xlog files if necessary. */ + // 检查是否需要执行归档超时操作,如果需要切换 WAL 日志文件执行 CheckArchiveTimeout(); /* @@ -581,30 +610,36 @@ void CheckpointerMain(void) * worth the trouble to split the stats support into two independent * stats message types.) */ + // 将检查点进程的活动统计信息发送到统计收集器,以便监控系统性能 pgstat_send_bgwriter(); /* * Sleep until we are signaled or it's time for another checkpoint or * xlog file switch. */ - now = (pg_time_t)time(NULL); + now = (pg_time_t)time(NULL); // 获取当前时间 + // 计算自上一次检查点完成以来经过的秒数 elapsed_secs = now - t_thrd.checkpoint_cxt.last_checkpoint_time; + // 确保 elapsed_secs 不会为负数 elapsed_secs = (elapsed_secs < 0) ? 0 : elapsed_secs; + // 检查是否已经过了执行检查点的时间阈值 if (elapsed_secs >= u_sess->attr.attr_storage.CheckPointTimeout) - continue; /* no sleep for us ... */ - + continue; /* no sleep for us ... */ // 不需要休眠,直接跳到下一次循环 + // 计算休眠的剩余时间,即距离下一次检查点的时间 cur_timeout = u_sess->attr.attr_storage.CheckPointTimeout - elapsed_secs; - + // 检查是否启用了归档超时,并且当前不在数据库恢复状态下 if (u_sess->attr.attr_common.XLogArchiveTimeout > 0 && !RecoveryInProgress()) { + // 计算自上一次 WAL 日志文件切换以来经过的秒数 elapsed_secs = now - t_thrd.checkpoint_cxt.last_xlog_switch_time; - + // 如果超过了归档超时时间阈值 if (elapsed_secs >= u_sess->attr.attr_common.XLogArchiveTimeout) continue; /* no sleep for us ... */ - + // 更新 cur_timeout ,确保休眠的时间不超过最小值 cur_timeout = Min(cur_timeout, u_sess->attr.attr_common.XLogArchiveTimeout - elapsed_secs); } - + // 向统计收集器报告当前进程状态为闲置,以便监视系统性能 pgstat_report_activity(STATE_IDLE, NULL); + // 休眠指定的时间,等待信号或超时事件发生 rc = WaitLatch(&t_thrd.proc->procLatch, WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH, cur_timeout * 1000L /* convert to ms */); @@ -613,8 +648,8 @@ void CheckpointerMain(void) * Emergency bailout if postmaster has died. This is to avoid the * necessity for manual cleanup of all postmaster children. */ - if (rc & WL_POSTMASTER_DEATH) - gs_thread_exit(1); + if (rc & WL_POSTMASTER_DEATH) // postmaster 进程已终止 + gs_thread_exit(1); // 紧急退出当前线程,以避免手动清理postmaster所有子进程 } } @@ -625,40 +660,51 @@ void CheckpointerMain(void) * if any activity is recorded in the current WAL file, including just * a single checkpoint record. */ +/* + *功能: 检查归档超时并触发 WAL 文件切换 + * + * 参数:无 + * + * 返回值:无 + */ static void CheckArchiveTimeout(void) { pg_time_t now; pg_time_t last_time; - + // 首先检查是否启用了归档超时,并且当前是否处于数据库恢复模式 if (u_sess->attr.attr_common.XLogArchiveTimeout <= 0 || RecoveryInProgress()) - return; + return; // 不需要执行归档超时操作 - now = (pg_time_t)time(NULL); + now = (pg_time_t)time(NULL); // 获取当前时间 /* First we do a quick check using possibly-stale local state. */ + // 使用过时的本地状态信息执行快速检查 + // 检查从上一次 WAL 文件切换到现在经过的时间是否小于归档超时的阈值 if ((int)(now - t_thrd.checkpoint_cxt.last_xlog_switch_time) < u_sess->attr.attr_common.XLogArchiveTimeout) - return; + return; // 不需要执行归档超时操作 /* * Update local state ... note that t_thrd.checkpoint_cxt.last_xlog_switch_time is the last time * a switch was performed *or requested*. */ - last_time = GetLastSegSwitchTime(); - + last_time = GetLastSegSwitchTime(); // 获取上一次 WAL 文件切换的时间 + // 记录的是最近一次 WAL 文件切换或请求切换的时间 t_thrd.checkpoint_cxt.last_xlog_switch_time = Max(t_thrd.checkpoint_cxt.last_xlog_switch_time, last_time); /* Now we can do the real check */ + // 再次检查从上一次 WAL 文件切换到现在经过的时间是否大于或等于归档超时的阈值 if ((int)(now - t_thrd.checkpoint_cxt.last_xlog_switch_time) >= u_sess->attr.attr_common.XLogArchiveTimeout) { XLogRecPtr switchpoint; /* OK, it's time to switch */ - switchpoint = RequestXLogSwitch(); + switchpoint = RequestXLogSwitch(); // 请求 WAL 文件切换,并返回切换点 /* * If the returned pointer points exactly to a segment boundary, * assume nothing happened. */ - if ((switchpoint % XLogSegSize) != 0) + if ((switchpoint % XLogSegSize) != 0) // 检查切换点是否正好在一个 WAL 段边界上 + // 如果不是,生成调试日志,指示 WAL 文件切换是由于归档超时触发的 ereport(DEBUG1, (errmsg("transaction log switch forced (archive_timeout=%d)", u_sess->attr.attr_common.XLogArchiveTimeout))); @@ -667,6 +713,7 @@ static void CheckArchiveTimeout(void) * Update state in any case, so we don't retry constantly when the * system is idle. */ + // 更新时间 记录最近一次 WAL 文件切换的时间 t_thrd.checkpoint_cxt.last_xlog_switch_time = now; } } @@ -676,16 +723,24 @@ static void CheckArchiveTimeout(void) * this does not check the *current* checkpoint's IMMEDIATE flag, but whether * there is one pending behind it.) */ +/* + *功能: 用于检查是否请求立即执行检查点 + * + * 参数: 无 + * + * 返回值:如果存在请求立即执行检查点,函数返回 true;否则,返回 false + */ static bool ImmediateCheckpointRequested(void) { - if (t_thrd.checkpoint_cxt.checkpoint_requested) { + if (t_thrd.checkpoint_cxt.checkpoint_requested) { // 如果有检查点请求 + // 初始化为全局变量 用于在多个进程之间共享检查点相关的状态信息 volatile CheckpointerShmemStruct* cps = t_thrd.checkpoint_cxt.CheckpointerShmem; /* * We don't need to acquire the ckpt_lck in this case because we're * only looking at a single flag bit. */ - if (cps->ckpt_flags & CHECKPOINT_IMMEDIATE) + if (cps->ckpt_flags & CHECKPOINT_IMMEDIATE) // 如果已经请求了立即检查点 return true; } @@ -705,34 +760,46 @@ static bool ImmediateCheckpointRequested(void) * 'progress' is an estimate of how much of the work has been done, as a * fraction between 0.0 meaning none, and 1.0 meaning all done. */ +/* + *功能:控制检查点的速率, + * 以实现与 checkpoint_completion_target 配置项的目标性能一致。 + * + * 参数: + * flags:整数,表示检查点请求标志 + * progress:双精度浮点数,表示检查点进度的估计,范围在 0.0~1.0之间 + */ void CheckpointWriteDelay(int flags, double progress) { /* Do nothing if checkpoint is being executed by non-checkpointer process */ - if (!AmCheckpointerProcess()) - return; + if (!AmCheckpointerProcess()) // 检查当前是否是检查点进程在执行 + return; // 如果不是,立即返回 /* * Perform the usual duties and take a nap, unless we're behind schedule, * in which case we just try to catch up as quickly as possible. */ + // 通过检查是否不需要立即执行检查点,是否没有检查点的关闭请求, + // 是否没有立即检查点请求,检查点的进度是否按计划执行, + // 以确定是否需要执行检查点的写入操作 if (!((uint32)flags & CHECKPOINT_IMMEDIATE) && !t_thrd.checkpoint_cxt.shutdown_requested && !ImmediateCheckpointRequested() && IsCheckpointOnSchedule(progress)) { - if (t_thrd.checkpoint_cxt.got_SIGHUP) { + if (t_thrd.checkpoint_cxt.got_SIGHUP) { // 检查是否收到 SIGHUP 信号 t_thrd.checkpoint_cxt.got_SIGHUP = false; - ProcessConfigFile(PGC_SIGHUP); + ProcessConfigFile(PGC_SIGHUP); // 重新加载配置文件 /* update shmem copies of config variables */ - UpdateSharedMemoryConfig(); + UpdateSharedMemoryConfig(); // 更新共享内存中的配置变量 } - CkptAbsorbFsyncRequests(); + CkptAbsorbFsyncRequests(); // 处理挂起的 fsync 请求 + // 将 absorbCounter 计数器设置为 WRITES_PER_ABSORB,限制频繁处理 fsync 请求 t_thrd.checkpoint_cxt.absorbCounter = WRITES_PER_ABSORB; - CheckArchiveTimeout(); + CheckArchiveTimeout(); // 检查是否需要执行日志文件切换 /* * Report interim activity statistics to the stats collector. */ - pgstat_send_bgwriter(); + pgstat_send_bgwriter(); // 将当前的活动统计信息发送给统计信息收集器 /* * This sleep used to be connected to bgwriter_delay, typically 200ms. @@ -740,14 +807,17 @@ void CheckpointWriteDelay(int flags, double progress) * Checkpointer and bgwriter are no longer related so take the Big * Sleep. */ - pg_usleep(100000L); - } else if (--t_thrd.checkpoint_cxt.absorbCounter <= 0) { + pg_usleep(100000L); // 执行短暂的休眠,以控制写入速率 + } else if (--t_thrd.checkpoint_cxt.absorbCounter <= 0) { // 检查是否需要处理挂起的 fsync 请求 /* * Absorb pending fsync requests after each WRITES_PER_ABSORB write * operations even when we don't sleep, to prevent overflow of the * fsync request queue. */ + // 处理挂起的 fsync 请求 CkptAbsorbFsyncRequests(); + //将 absorbCounter 计数器重置为 WRITES_PER_ABSORB,以限制频繁处理 fsync 请求 + t_thrd.checkpoint_cxt.absorbCounter = WRITES_PER_ABSORB; } } @@ -760,18 +830,31 @@ void CheckpointWriteDelay(int flags, double progress) * checkpoint, and returns true if the progress we've made this far is greater * than the elapsed time/segments. */ +/* + *功能:检查当前是否在符合计划的时间内完成检查点 + * + * 参数: + * progress 双精度浮点数,表示当前已完成的工作进度 + * + * 返回值: + * bool类型,表示当前检查点是否按照预定计划进行。 + * 如果检查点的进度已经超过了时间或段数的计算结果,返回 true,表示检查点按计划进行。 + * 否则,返回 false,表示检查点没有按计划进行。 + */ static bool IsCheckpointOnSchedule(double progress) { - XLogRecPtr recptr; - struct timeval now; - double elapsed_xlogs, elapsed_time; + XLogRecPtr recptr; // 用于存储 WAL 插入位置 + struct timeval now; // 用于获取当前时间 + double elapsed_xlogs, elapsed_time; // 用于计算已经过去的 WAL 段数和时间 - Assert(t_thrd.checkpoint_cxt.ckpt_active); + Assert(t_thrd.checkpoint_cxt.ckpt_active); // 确保当前检查点正在活动状态 + // 据配置参数 ENABLE_INCRE_CKPT 来设置 CheckPointTimeout,用于后续的计算 u_sess->attr.attr_storage.CheckPointTimeout = ENABLE_INCRE_CKPT ? u_sess->attr.attr_storage.incrCheckPointTimeout : u_sess->attr.attr_storage.fullCheckPointTimeout; /* Scale progress according to checkpoint_completion_target. */ + // 根据配置参数 checkpoint_completion_target 对进度 progress 进行缩放 progress *= u_sess->attr.attr_storage.CheckPointCompletionTarget; /* @@ -780,6 +863,7 @@ static bool IsCheckpointOnSchedule(double progress) * neither time or WAL insert pointer moves backwards, a freshly * calculated value can only be greater than or equal to the cached value. */ + // 检查是否已经计算过当前的进度 progress if (progress < t_thrd.checkpoint_cxt.ckpt_cached_elapsed) return false; @@ -793,12 +877,14 @@ static bool IsCheckpointOnSchedule(double progress) * However, it's good enough for our purposes, we're only calculating an * estimate anyway. */ - if (!RecoveryInProgress()) { - recptr = GetInsertRecPtr(); + if (!RecoveryInProgress()) { // 如果没有在恢复模式下执行 + recptr = GetInsertRecPtr(); // 获取当前的 WAL 插入位置 recptr + // 计算已经过去的 WAL 段数 elapsed_xlogs,并与 checkpoint_segments 配置项进行比较 elapsed_xlogs = (((double)(recptr - t_thrd.checkpoint_cxt.ckpt_start_recptr)) / XLogSegSize) / u_sess->attr.attr_storage.CheckPointSegments; - if (progress < elapsed_xlogs) { + if (progress < elapsed_xlogs) { // 检查进度 progress 是否已经超过 + // 如果未超过,更新缓存的计算结果 t_thrd.checkpoint_cxt.ckpt_cached_elapsed = elapsed_xlogs; return false; } @@ -807,11 +893,13 @@ static bool IsCheckpointOnSchedule(double progress) /* * Check progress against time elapsed and checkpoint_timeout. */ - gettimeofday(&now, NULL); + gettimeofday(&now, NULL); // 获取当前时间 + // 计算已经过去的时间 elapsed_time,并与 checkpoint_timeout 配置项进行比较 elapsed_time = ((double)((pg_time_t)now.tv_sec - t_thrd.checkpoint_cxt.ckpt_start_time) + now.tv_usec / 1000000.0) / u_sess->attr.attr_storage.CheckPointTimeout; - if (progress < elapsed_time) { + if (progress < elapsed_time) { // 检查进度 progress 是否已经超过 + // 如果未超过,更新缓存的计算结果 t_thrd.checkpoint_cxt.ckpt_cached_elapsed = elapsed_time; return false; } @@ -830,9 +918,19 @@ static bool IsCheckpointOnSchedule(double progress) * Some backend has bought the farm, * so we need to stop what we're doing and exit. */ +/* + *功能:处理快速终止信号 + * 紧急退出操作,不会触发正常的清理和关闭步骤,因为共享内存可能已损坏 + * + * 参数: + * SIGNAL_ARGS: 信号处理程序的参数列表,提供关于触发信号的上下文信息 + * + * 返回值: + * 无 + */ static void chkpt_quickdie(SIGNAL_ARGS) { - gs_signal_setmask(&t_thrd.libpq_cxt.BlockSig, NULL); + gs_signal_setmask(&t_thrd.libpq_cxt.BlockSig, NULL); // 解除对信号的阻塞 /* * We DO NOT want to run proc_exit() callbacks -- we're here because @@ -842,7 +940,7 @@ static void chkpt_quickdie(SIGNAL_ARGS) * things by calling exit() directly, we have to reset the callbacks * explicitly to make this work as intended. */ - on_exit_reset(); + on_exit_reset(); // 重置退出处理回调 /* * Note we do exit(2) not exit(0). This is to force the postmaster into a @@ -852,56 +950,92 @@ static void chkpt_quickdie(SIGNAL_ARGS) * should ensure the postmaster sees this as a crash, too, but no harm in * being doubly sure.) */ - exit(2); + exit(2); // 强制 postmaster 进入系统重置周期,确保 postmaster 将这个信号视为崩溃 } /* SIGHUP: set flag to re-read config file at next convenient time */ +/* + *功能:处理 SIGHUP 信号,用于重新读取配置文件 + * + * 参数: + * SIGNAL_ARGS: 信号处理程序的参数列表,提供关于触发信号的上下文信息 + * + * 返回值: + * 无 + */ static void ChkptSigHupHandler(SIGNAL_ARGS) { int save_errno = errno; - + // 表示需要在下一个方便的时间重新读取配置文件 t_thrd.checkpoint_cxt.got_SIGHUP = true; - if (t_thrd.proc) - SetLatch(&t_thrd.proc->procLatch); + if (t_thrd.proc) // 如果当前进程存在 + SetLatch(&t_thrd.proc->procLatch); // 设置该进程的事件标志,以处理 SIGHUP 信号 - errno = save_errno; + errno = save_errno; // 保存并还原当前的 errno 值,以防干扰其他代码 } /* SIGINT: set flag to run a normal checkpoint right away */ +/* + *功能: 处理 SIGINT 信号(中断进程信号),用于请求立即执行正常的检查点 + * + * 参数: + * SIGNAL_ARGS: 信号处理程序的参数列表,提供关于触发信号的上下文信息 + * + * 返回值: + * 无 + */ static void ReqCheckpointHandler(SIGNAL_ARGS) { int save_errno = errno; - + // 表示需要立即执行正常的检查点 t_thrd.checkpoint_cxt.checkpoint_requested = true; - if (t_thrd.proc) - SetLatch(&t_thrd.proc->procLatch); + if (t_thrd.proc) // 如果当前进程存在 + SetLatch(&t_thrd.proc->procLatch); // 设置该进程事件标志,以处理 SIGINT 信号 - errno = save_errno; + errno = save_errno; // 保存并还原当前的 errno 值,以防干扰其他代码 } /* SIGUSR1: used for latch wakeups */ +/* + *功能: 处理 SIGUSR1 信号,用于唤醒进程中的等待操作 + * + * 参数: + * SIGNAL_ARGS: 信号处理程序的参数列表,提供关于触发信号的上下文信息 + * + * 返回值: + * 无 + */ static void chkpt_sigusr1_handler(SIGNAL_ARGS) { int save_errno = errno; - latch_sigusr1_handler(); + latch_sigusr1_handler(); // 处理与 latch 相关的 SIGUSR1 信号操作 - errno = save_errno; + errno = save_errno; // 保存并还原当前的 errno 值,以防干扰其他代码 } /* SIGUSR2: set flag to run a shutdown checkpoint and exit */ +/* + *功能:处理 SIGUSR2 信号,用于请求执行关闭检查点并退出进程 + * + * 参数: + * SIGNAL_ARGS: 信号处理程序的参数列表,提供关于触发信号的上下文信息 + * + * 返回值: + * 无 + */ static void ReqShutdownHandler(SIGNAL_ARGS) { int save_errno = errno; - + // 表示需要执行关闭检查点并退出进程 t_thrd.checkpoint_cxt.shutdown_requested = true; - if (t_thrd.proc) - SetLatch(&t_thrd.proc->procLatch); + if (t_thrd.proc) // 如果当前进程存在 + SetLatch(&t_thrd.proc->procLatch); // 设置该进程的事件标志,以处理 SIGUSR2 信号 - errno = save_errno; + errno = save_errno; // 保存并还原当前的 errno 值,以防干扰其他代码 } /* -------------------------------- @@ -912,7 +1046,14 @@ static void ReqShutdownHandler(SIGNAL_ARGS) * CheckpointerShmemSize * Compute space needed for checkpointer-related shared memory */ -const uint DDL_REQUEST_MAX = 100000; +const uint DDL_REQUEST_MAX = 100000; // 表示DDL请求的最大数量 +/* + *功能:计算用于检查点进程的共享内存所需的空间大小 + * + * 参数: 无 + * + * 返回值:Size 类型,表示所需的空间大小 + */ Size CheckpointerShmemSize(void) { Size size; @@ -921,11 +1062,14 @@ Size CheckpointerShmemSize(void) * Currently, the size of the requests[] array is arbitrarily set equal to * NBuffers. This may prove too large or small ... */ + // 计算 CheckpointerShmemStruct 结构体(包含检查点进程所需的各种信息)的偏移量 size = offsetof(CheckpointerShmemStruct, requests); - if (ENABLE_INCRE_CKPT) { + if (ENABLE_INCRE_CKPT) { // 如果启用增量检查点 /* incremental checkpoint, the checkpoint thread only handle the drop table request and drop db request */ + // 更新size,表示在增量检查点模式下,共享内存需要存储一定数量的DDL请求 size = add_size(size, mul_size(DDL_REQUEST_MAX, sizeof(CheckpointerRequest))); } else { + // 更新size,表示在非增量检查点模式下,共享内存需要存储一定数量的检查点请求 size = add_size(size, mul_size(TOTAL_BUFFER_NUM, sizeof(CheckpointerRequest))); } @@ -936,23 +1080,34 @@ Size CheckpointerShmemSize(void) * CheckpointerShmemInit * Allocate and initialize checkpointer-related shared memory */ +/* + *功能:分配和初始化与检查点进程相关的共享内存 + * + * 参数:无 + * + * 返回值:无 + */ void CheckpointerShmemInit(void) { - Size size = CheckpointerShmemSize(); + Size size = CheckpointerShmemSize(); // 计算所需的共享内存大小 bool found = false; - + // 分配共享内存 t_thrd.checkpoint_cxt.CheckpointerShmem = (CheckpointerShmemStruct*)ShmemInitStruct("Checkpointer Data", size, &found); - if (!found) { + if (!found) { // 检查是否找到了已存在的共享内存 /* * First time through, so initialize. Note that we zero the whole * requests array; this is so that CompactCheckpointerRequestQueue * can assume that any pad bytes in the request structs are zeroes. */ /* The memory of the memset sometimes exceeds 2 GB. so, memset_s cannot be used. */ + // 如果没有找到,表示这是第一次创建共享内存,需要进行初始化 + // 将整个共享内存区域初始化为零 MemSet((char*)t_thrd.checkpoint_cxt.CheckpointerShmem, 0, size); + // 对共享内存中的自旋锁进行初始化,用于在多个进程之间同步对共享内存的访问 SpinLockInit(&t_thrd.checkpoint_cxt.CheckpointerShmem->ckpt_lck); + // 根据是否启用了增量检查点设置 max_requests 字段的值 t_thrd.checkpoint_cxt.CheckpointerShmem->max_requests = ENABLE_INCRE_CKPT ? DDL_REQUEST_MAX : TOTAL_BUFFER_NUM; } } @@ -960,9 +1115,19 @@ void CheckpointerShmemInit(void) /* * Check wheter checkpoint proc is running while waiting request checkpoint to finish. */ +/* + *功能: 检查检查点进程是否正在运行 确保只有在检查点进程正在运行时才能发出请求 + * + * 参数:无 + * + * 返回值:无 + */ static void CheckPointProcRunning(void) { + // 检查全局变量 g_instance.pid_cxt.CheckpointerPID 是否为0 + // 如果为0,表示检查点进程没有在运行 if (g_instance.pid_cxt.CheckpointerPID == 0) { + // 发出警告 说明无法请求检查点 ereport(FATAL, (errcode(ERRCODE_WITH_CHECK_OPTION_VIOLATION), errmsg("could not request checkpoint because checkpointer not running"))); @@ -986,6 +1151,21 @@ static void CheckPointProcRunning(void) * CHECKPOINT_CAUSE_XLOG: checkpoint is requested due to xlog filling. * (This affects logging, and in particular enables CheckPointWarning.) */ +/* + *功能:请求执行检查点操作 + * + * 参数: + * flags-位掩码 ,不同二进制位具有不同含义 + * 包含以下标志的按位或操作 + * - CHECKPOINT_IS_SHUTDOWN:表示检查点是为了数据库关闭而执行的 + * - CHECKPOINT_END_OF_RECOVERY:表示检查点是为了WAL恢复结束而执行的 + * - CHECKPOINT_IMMEDIATE:表示立即完成检查点,忽略 checkpoint_completion_target 参数 + * - CHECKPOINT_FORCE:表示即使在上次检查点之后没有发生任何XLOG活动,也要强制执行检查点 + * - CHECKPOINT_WAIT:表示等待检查点完成后再返回 + * - CHECKPOINT_CAUSE_XLOG:表示由于XLOG填充而请求检查点 + * + *返回值:无 + */ void RequestCheckpoint(int flags) { /* use volatile pointer to prevent code rearrangement */ @@ -995,17 +1175,23 @@ void RequestCheckpoint(int flags) /* * If in a standalone backend, just do it ourselves. */ + // 检查当前是否在独立后台进程中运行 + // 如果是则直接执行检查点操作,以防其他后台进程干扰检查点 if (!IsPostmasterEnvironment) { + // 如果不在独立后台进程中运行 /* * There's no point in doing slow checkpoints in a standalone backend, * because there's no other backends the checkpoint could disrupt. */ + // 调用 CreateCheckPoint 函数执行检查点操作 CreateCheckPoint(flags | CHECKPOINT_IMMEDIATE); /* * After any checkpoint, close all smgr files. This is so we won't * hang onto smgr references to deleted files indefinitely. */ + // 在任何检查点之后关闭所有的 smgr 文件 + // 避免持续引用已删除文件的 smgr 引用 smgrcloseall(); return; @@ -1020,11 +1206,12 @@ void RequestCheckpoint(int flags) * a "stronger" request by another backend. The flag senses must be * chosen to make this work! */ - SpinLockAcquire(&cps->ckpt_lck); + SpinLockAcquire(&cps->ckpt_lck); // 获取自旋锁 + // 获取检查点进度值 old_failed = cps->ckpt_failed; old_started = cps->ckpt_started; cps->ckpt_flags |= flags; - SpinLockRelease(&cps->ckpt_lck); + SpinLockRelease(&cps->ckpt_lck); // 释放自旋锁 /* * Send signal to request checkpoint. It's possible that the checkpointer @@ -1033,11 +1220,15 @@ void RequestCheckpoint(int flags) * occur, we consider failure to send the signal to be nonfatal and merely * LOG it. */ + // 循环尝试向检查点进程发出信号,直至检查点进程启动 for (ntries = 0;; ntries++) { + // 检查检查点进程的PID是否为0,如果为0,则表示检查点进程尚未启动 if (t_thrd.checkpoint_cxt.CheckpointerShmem->checkpointer_pid == 0) { /* max wait CheckPointWaitTime secs */ + // 如果等待时间超过阈值或系统正在关闭 if (ntries >= (u_sess->attr.attr_storage.CheckPointWaitTimeOut * 10) || pmState == PM_SHUTDOWN_2) { if (flags & CHECKPOINT_WAIT) { + // 生成错误报告 ereport(ERROR, (errcode(ERRCODE_WITH_CHECK_OPTION_VIOLATION), errmsg("could not request checkpoint because checkpointer not running"))); @@ -1046,10 +1237,13 @@ void RequestCheckpoint(int flags) } break; } + // 向检查点进程发送 SIGINT 信号,请求执行检查点 } else if (gs_signal_send(t_thrd.checkpoint_cxt.CheckpointerShmem->checkpointer_pid, SIGINT) != 0) { /* max wait CheckPointWaitTime secs */ + // 如果等待时间超过阈值 if (ntries >= (u_sess->attr.attr_storage.CheckPointWaitTimeOut * 10)) { if (flags & CHECKPOINT_WAIT) { + // 生成错误报告 ereport(ERROR, (errcode(ERRCODE_WITH_CHECK_OPTION_VIOLATION), errmsg("could not signal for checkpoint: %m"))); } else { @@ -1060,8 +1254,9 @@ void RequestCheckpoint(int flags) } else { break; /* signal sent successfully */ } - - CHECK_FOR_INTERRUPTS(); + // 检查是否收到了中断信号,如果收到中断信号,则中断当前操作 + CHECK_FOR_INTERRUPTS(); + // 使当前进程休眠0.1秒,然后重试向检查点进程发送信号 pg_usleep(100000L); /* wait 0.1 sec, then retry */ } @@ -1069,19 +1264,20 @@ void RequestCheckpoint(int flags) * If requested, wait for completion. We detect completion according to * the algorithm given above. */ - if (flags & CHECKPOINT_WAIT) { - int new_started, new_failed; + if (flags & CHECKPOINT_WAIT) { // 如果需要等待检查点操作完成 + int new_started, new_failed; // 用于记录新的检查点开始和失败的计数 /* Wait for a new checkpoint to start. */ - for (;;) { - SpinLockAcquire(&cps->ckpt_lck); - new_started = cps->ckpt_started; - SpinLockRelease(&cps->ckpt_lck); + // 循环等待新的检查点开始 + for (;;) { + SpinLockAcquire(&cps->ckpt_lck); // 获取自旋锁 + new_started = cps->ckpt_started; // 获取检查点旧的计数 + SpinLockRelease(&cps->ckpt_lck); // 释放锁 - if (new_started != old_started) { - break; + if (new_started != old_started) { // 检查新的检查点开始计数是否与旧的计数不同 + break; // 如果不同,则表示新的检查点已经开始,跳出循环 } - + // 检查是否收到了中断信号,如果收到中断信号,则中断当前操作 CHECK_FOR_INTERRUPTS(); CheckPointProcRunning(); pg_usleep(100000L); @@ -1090,24 +1286,27 @@ void RequestCheckpoint(int flags) /* * We are waiting for ckpt_done >= new_started, in a modulo sense. */ + // 循环等待检查点操作的完成 for (;;) { int new_done; - SpinLockAcquire(&cps->ckpt_lck); + SpinLockAcquire(&cps->ckpt_lck); // 获取自旋锁 + // 获取旧的检查点信息 new_done = cps->ckpt_done; new_failed = cps->ckpt_failed; - SpinLockRelease(&cps->ckpt_lck); - + SpinLockRelease(&cps->ckpt_lck); // 释放锁 + // 如果新的检查点完成计数大于等于新的检查点开始计数 if (new_done - new_started >= 0) { - break; + break; // 表示检查点操作完成,跳出循环 } - + // 检查是否收到了中断信号,如果收到中断信号,则中断当前操作 CHECK_FOR_INTERRUPTS(); CheckPointProcRunning(); pg_usleep(100000L); } - if (new_failed != old_failed) + if (new_failed != old_failed) // 如果检查点操作失败 + // 生成错误报告,指示检查点请求失败 ereport(ERROR, (errcode(ERRCODE_WITH_CHECK_OPTION_VIOLATION), errmsg("checkpoint request failed"), @@ -1135,25 +1334,42 @@ void RequestCheckpoint(int flags) * the queue is full and contains no duplicate entries. In that case, we * let the backend know by returning false. */ +/* + * 功能: + * 将文件同步请求从后台进程传递给检查点进程 + * 以确保在下一次检查点之前将这些文件同步到磁盘 + * + * 参数: + * ftag 表示文件标签(指定要同步的文件) + * type 表示同步请求的类型 + * + * 返回值: + * bool类型,如果成功将文件同步请求添加到后台检查点进程的请求队列中,返回true + * 否则,返回false + */ bool CkptForwardSyncRequest(const FileTag *ftag, SyncRequestType type) { CheckpointerRequest* request = NULL; bool too_full = false; - if (!IsUnderPostmaster) { + if (!IsUnderPostmaster) { // 检查当前进程是否在 Postmaster 环境中运行 + // 如果不是,则返回 false return false; /* probably shouldn't even get here */ } - if (AmCheckpointerProcess()) { + if (AmCheckpointerProcess()) { // 检查当前进程是否为检查点进程 + // 如果是,则报告错误,因为检查点进程不应该调用此函数 ereport(ERROR, (errcode(ERRCODE_WITH_CHECK_OPTION_VIOLATION), errmsg("ForwardFsyncRequest must not be called in checkpointer"))); } - + // 获取轻量级锁,确保多个进程之间不会同时修改共享数据结构 LWLockAcquire(CheckpointerCommLock, LW_EXCLUSIVE); /* Count all backend writes regardless of if they fit in the queue */ + // 检查当前进程是否为后台写入进程或页面写入进程 if (!AmBackgroundWriterProcess() && !AmPageWriterProcess()) { + // 如果不是这两种进程,增加计数器,表示后台写入操作的数量 t_thrd.checkpoint_cxt.CheckpointerShmem->num_backend_writes++; } @@ -1162,6 +1378,7 @@ bool CkptForwardSyncRequest(const FileTag *ftag, SyncRequestType type) * backend will have to perform its own fsync request. But before forcing * that to happen, we can try to compact the request queue. */ + // 检查检查点进程是否未运行,或者请求队列是否已满,或者是否可压缩检查点请求队列 if (t_thrd.checkpoint_cxt.CheckpointerShmem->checkpointer_pid == 0 || (t_thrd.checkpoint_cxt.CheckpointerShmem->num_requests >= t_thrd.checkpoint_cxt.CheckpointerShmem->max_requests && @@ -1170,54 +1387,71 @@ bool CkptForwardSyncRequest(const FileTag *ftag, SyncRequestType type) * Count the subset of writes where backends have to do their own * fsync */ + // 检查当前进程是否为后台写入进程或页面写入进程 if (!AmBackgroundWriterProcess() && !AmPageWriterProcess()) { + // 如果不是这两种进程,增加计数器,表示后台写入操作的数量 t_thrd.checkpoint_cxt.CheckpointerShmem->num_backend_fsync++; } - LWLockRelease(CheckpointerCommLock); + LWLockRelease(CheckpointerCommLock); // 释放锁 return false; } /* OK, insert request */ - request = + // 如果可以将请求插入队列,将请求添加到检查点请求队列中,并更新队列计数器 + request = &t_thrd.checkpoint_cxt.CheckpointerShmem->requests[t_thrd.checkpoint_cxt.CheckpointerShmem->num_requests++]; request->ftag = *ftag; request->type = type; /* If queue is more than half full, nudge the checkpointer to empty it */ + // 检查请求队列是否已经超过一半满 too_full = (t_thrd.checkpoint_cxt.CheckpointerShmem->num_requests >= t_thrd.checkpoint_cxt.CheckpointerShmem->max_requests / 2); - - LWLockRelease(CheckpointerCommLock); + + LWLockRelease(CheckpointerCommLock); // 释放轻量级锁,允许其他进程访问共享数据 /* ... but not till after we release the lock */ + // 如果请求队列已经超过一半满,并且存在检查点进程的等待信号量 if (too_full && g_instance.proc_base->checkpointerLatch) { - SetLatch(g_instance.proc_base->checkpointerLatch); + SetLatch(g_instance.proc_base->checkpointerLatch); // 醒检查点进程 } return true; } +/* + * 功能:在一组文件同步请求中查找重复的请求并标记 + * + * 参数: + * requests:文件同步请求数组 + * num_requests:请求数量 数组长度 + * + * 返回值: + * int类型,返回重复请求数量 + */ int getDuplicateRequest(CheckpointerRequest *requests, int num_requests, bool *skip_slot) { + // 用于将文件同步请求映射到一个槽位 ,用于标记重复的请求 struct CheckpointerSlotMapping { CheckpointerRequest request; int slot; }; int n; - int num_skipped = 0; - HASHCTL ctl; - HTAB* htab = NULL; + int num_skipped = 0; // 计数器,用于记录跳过的重复请求的数量 + HASHCTL ctl; // 用于设置哈希表的控制参数 + HTAB* htab = NULL; // 用于存储请求并查找重复的请求 /* Initialize temporary hash table */ - errno_t rc = memset_s(&ctl, sizeof(ctl), 0, sizeof(ctl)); + // 初始化结构体 设置参数 + errno_t rc = memset_s(&ctl, sizeof(ctl), 0, sizeof(ctl)); securec_check(rc, "\0", "\0"); ctl.keysize = sizeof(CheckpointerRequest); ctl.entrysize = sizeof(struct CheckpointerSlotMapping); ctl.hash = tag_hash; ctl.hcxt = CurrentMemoryContext; - + // 创建哈希表 htab = hash_create("CompactRequestQueue", num_requests, &ctl, HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT); @@ -1234,7 +1468,7 @@ int getDuplicateRequest(CheckpointerRequest *requests, int num_requests, bool *s * anyhow), but it's not clear that the extra complexity would buy us * anything. */ - + // 遍历给定的文件同步请求数组 for (n = 0; n < num_requests; n++) { CheckpointerRequest* request = NULL; struct CheckpointerSlotMapping* slotmap; @@ -1247,28 +1481,34 @@ int getDuplicateRequest(CheckpointerRequest *requests, int num_requests, bool *s * CheckpointerShmemInit. Note also that RelFileNode had better * contain no pad bytes. */ - request = &requests[n]; + request = &requests[n]; // 获取当前请求 + // 用哈希表 htab 查找当前请求是否已经存在 slotmap = (CheckpointerSlotMapping*)hash_search(htab, request, HASH_ENTER, &found); if (found) { /* Duplicate, so mark the previous occurrence as skippable */ + // 如果找到相同的请求 + // 标记前一个请求的槽位为可跳过 skip_slot[slotmap->slot] = true; - num_skipped++; + num_skipped++; // 增加计数 } /* Remember slot containing latest occurrence of this request value */ + // 如果没有找到相同的请求,将当前请求插入哈希表中,同时记录该请求的槽位 slotmap->slot = n; } /* Done with the hash table. */ + // 遍历完所有请求后,销毁哈希表 hash_destroy(htab); /* If no duplicates, we're out of luck. */ if (!num_skipped) { + // 如果没有跳过的请求(即没有重复的请求) return 0; } - return num_skipped; + return num_skipped; // 否则,返回跳过的请求数量 } /* @@ -1287,47 +1527,61 @@ int getDuplicateRequest(CheckpointerRequest *requests, int num_requests, bool *s * aren't any removable entries. But that should be vanishingly rare in * practice: there's one queue entry per shared buffer. */ +/* + * 功能:管理检查点进程中用于fsync操作的请求队列 + * + * 参数:无 + * + * 返回值: + * bool类型,指示是否从请求队列中删除了任何重复的条目 + */ static bool CompactCheckpointerRequestQueue(void) { - int preserve_count; + int preserve_count; // 用于计算在删除重复项后请求队列中应保留的条目数 bool* skip_slot = NULL; - int num_skipped = 0; + int num_skipped = 0; // 跟踪找到的重复项数量 /* must hold CheckpointerCommLock in exclusive mode */ + // 以独占模式持有CheckpointerCommLock锁,用于同步对共享内存结构的访问 Assert(LWLockHeldByMe(CheckpointerCommLock)); /* Initialize skip_slot array */ + // 初始化数组,bool类型,长度等于请求队列中的请求数 skip_slot = (bool*)palloc0(sizeof(bool) * t_thrd.checkpoint_cxt.CheckpointerShmem->num_requests); + // 识别队列中的重复请求并标记,以进行删除 + // 找到的重复项数量存储在num_skipped中 num_skipped = getDuplicateRequest(t_thrd.checkpoint_cxt.CheckpointerShmem->requests, t_thrd.checkpoint_cxt.CheckpointerShmem->num_requests, skip_slot); /* If no duplicates, we're out of luck. */ - if (num_skipped == 0) { - pfree(skip_slot); - return false; + if (num_skipped == 0) { // 如果未找到重复请求 + pfree(skip_slot); // 释放数组内存 + return false; // 返回false,表示未删除任何条目 } /* We found some duplicates; remove them. */ - preserve_count = 0; - + // 如果找到重复项,则删除 + preserve_count = 0; // 用于计算删除重复项后应保留的条目数 + // 循环遍历请求队列 for (int n = 0; n < t_thrd.checkpoint_cxt.CheckpointerShmem->num_requests; n++) { - if (skip_slot[n]) + if (skip_slot[n]) // 如果该条目应跳过(即为重复项) continue; - + // 非重复项,将该条目保留在队列中 t_thrd.checkpoint_cxt.CheckpointerShmem->requests[preserve_count++] = t_thrd.checkpoint_cxt.CheckpointerShmem->requests[n]; } - + // 生成一个调试消息,报告在压缩前后的条目数量 ereport(DEBUG1, (errmsg("compacted fsync request queue from %d entries to %d entries", t_thrd.checkpoint_cxt.CheckpointerShmem->num_requests, preserve_count))); + // 更新请求队列中的请求数 t_thrd.checkpoint_cxt.CheckpointerShmem->num_requests = preserve_count; /* Cleanup. */ - pfree(skip_slot); - return true; + pfree(skip_slot); // 释放数组内存 + return true; // 返回true,表示已从请求队列中删除了重复条目 } /* @@ -1339,14 +1593,23 @@ static bool CompactCheckpointerRequestQueue(void) * we start fsync'ing. Since CreateCheckPoint sometimes runs in * non-checkpointer processes, do nothing if not checkpointer. */ +/* + * 功能: + * 检查检查点进程是否需要吸收排队的文件同步请求 + * 并将这些请求传递给本地的smgr(存储管理器)进行处理 + * + * 参数:无 + * + * 返回值:无 + */ void CkptAbsorbFsyncRequests(void) { - CheckpointerRequest* requests = NULL; - CheckpointerRequest* request = NULL; - int n; + CheckpointerRequest* requests = NULL; // 用于存储排队的同步请求 + CheckpointerRequest* request = NULL; // 用于迭代处理这些请求 + int n; // 用于表示排队请求的数量 - if (!AmCheckpointerProcess()) - return; + if (!AmCheckpointerProcess()) // 如果不是检查点进程 + return; // 返回 /* * We have to PANIC if we fail to absorb all the pending requests (eg, @@ -1355,61 +1618,79 @@ void CkptAbsorbFsyncRequests(void) * fsync. Fortunately, the hashtable is so small that the problem is * quite unlikely to arise in practice. */ + // 开始一个临界区,确保以下操作能够以原子方式执行,避免并发问题 START_CRIT_SECTION(); /* * We try to avoid holding the lock for a long time by copying the request * array. */ + // 以独占模式获取锁 LWLockAcquire(CheckpointerCommLock, LW_EXCLUSIVE); /* Transfer stats counts into pending pgstats message */ + // 跟踪后台写入和后台文件同步的统计数据 + // 将num_backend_writes和num_backend_fsync的值累加到统计信息中 u_sess->stat_cxt.BgWriterStats->m_buf_written_backend += t_thrd.checkpoint_cxt.CheckpointerShmem->num_backend_writes; u_sess->stat_cxt.BgWriterStats->m_buf_fsync_backend += t_thrd.checkpoint_cxt.CheckpointerShmem->num_backend_fsync; t_thrd.checkpoint_cxt.CheckpointerShmem->num_backend_writes = 0; t_thrd.checkpoint_cxt.CheckpointerShmem->num_backend_fsync = 0; - + // 获取排队请求的数量 n = t_thrd.checkpoint_cxt.CheckpointerShmem->num_requests; if (n > 0) { errno_t rc; + // 复制文件同步请求数据 requests = (CheckpointerRequest*)palloc(n * sizeof(CheckpointerRequest)); + // 将文件同步请求从共享内存中复制到本地内存中 rc = memcpy_s(requests, n * sizeof(CheckpointerRequest), t_thrd.checkpoint_cxt.CheckpointerShmem->requests, n * sizeof(CheckpointerRequest)); - securec_check(rc, "\0", "\0"); + securec_check(rc, "\0", "\0"); // 检查函数是否执行成功 } - + // 重置请求队列中的请求数量为零,以清除排队的请求 t_thrd.checkpoint_cxt.CheckpointerShmem->num_requests = 0; - + // 释放锁 LWLockRelease(CheckpointerCommLock); - + // 迭代处理排队的请求 for (request = requests; n > 0; request++, n--) { + // 记录需要进行同步的文件信息和操作类型 RememberSyncRequest(&request->ftag, request->type); } - if (requests != NULL) { - pfree(requests); + // 如果非空,即之前分配了内存用于存储文件同步请求数据 + if (requests != NULL) { + pfree(requests); // 释放内存 } - END_CRIT_SECTION(); + END_CRIT_SECTION(); // 结束临界区 } /* * Update any shared memory configurations based on config parameters */ +/* + * 功能:更新共享内存中的配置参数 + * + * 参数:无 + * + * 返回值:无 + */ static void UpdateSharedMemoryConfig(void) { /* update global shmem state for sync rep */ + // 更新共享内存中的关于同步复制的配置参数 SyncRepUpdateSyncStandbysDefined(); /* * If full_page_writes has been changed by SIGHUP, we update it in shared * memory and write an XLOG_FPW_CHANGE record. */ + // 检查并更新 full_page_writes 配置参数的值 + // 并在需要时记录一个 XLOG_FPW_CHANGE WAL 记录 UpdateFullPageWrites(); - + // 记录调试日志消息,指示检查点进程已经更新了共享内存中的配置参数值 ereport(DEBUG2, (errmsg("checkpointer updated shared memory configuration values"))); } @@ -1417,43 +1698,74 @@ static void UpdateSharedMemoryConfig(void) * FirstCallSinceLastCheckpoint allows a process to take an action once * per checkpoint cycle by asynchronously checking for checkpoint completion. */ +/* + * 功能:检查是否是自上次检查点以来的第一次调用 + * + * 参数:无 + * + * 返回值: + * bool类型,返回是否第一次调用该函数 + * 如果是第一次调用则返回true,否则返回false + */ bool FirstCallSinceLastCheckpoint(void) { /* use volatile pointer to prevent code rearrangement */ volatile CheckpointerShmemStruct* cps = t_thrd.checkpoint_cxt.CheckpointerShmem; - int new_done; - bool FirstCall = false; - - SpinLockAcquire(&cps->ckpt_lck); - new_done = cps->ckpt_done; - SpinLockRelease(&cps->ckpt_lck); + int new_done; // 用于存储新的检查点完成计数值 + bool FirstCall = false; // 用于表示是否是自上次检查点以来的第一次调用 + SpinLockAcquire(&cps->ckpt_lck); // 获取自旋锁 + new_done = cps->ckpt_done; // 获得检查点完成的信息 + SpinLockRelease(&cps->ckpt_lck); // 释放锁 + // 比较新旧数值,如果不相等,说明发生了新的检查点 if (new_done != t_thrd.checkpoint_cxt.ckpt_done) - FirstCall = true; + FirstCall = true; // 表示这是第一次调用 - t_thrd.checkpoint_cxt.ckpt_done = new_done; + t_thrd.checkpoint_cxt.ckpt_done = new_done; // 更新计数器信息 return FirstCall; } #ifdef ENABLE_MOT +/* + * 功能: 注册检查点回调函数 + * + * 参数: + * callback-函数指针,指向要注册的回调函数 + * arg-回调函数的参数,用于传递额外的数据给回调函数 + * + * 返回值:无 + */ void RegisterCheckpointCallback(CheckpointCallback callback, void* arg) { CheckpointCallbackItem *item; - + // 分配结构体内存 item = (CheckpointCallbackItem*)MemoryContextAlloc( INSTANCE_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_STORAGE), sizeof(CheckpointCallbackItem)); - item->callback = callback; - item->arg = arg; - item->next = g_instance.ckpt_cxt_ctl->ckptCallback; + // 参数赋值 + item->callback = callback; // 注册的回调函数的函数指针 + item->arg = arg; // 回调函数的参数 + // 当前回调函数链表的头部,表示新注册的回调函数会被添加到链表的头部 + item->next = g_instance.ckpt_cxt_ctl->ckptCallback; + // 更新全局回调函数链表 g_instance.ckpt_cxt_ctl->ckptCallback = item; } +/* + * 功能: 在检查点事件发生时调用已注册的检查点回调函数 + * + * 参数: + * checkpointEvent-表示检查点事件的类型 + * lsn-表示与检查点事件相关的逻辑日志序列号 + * + * 返回值:无 + */ void CallCheckpointCallback(CheckpointEvent checkpointEvent, XLogRecPtr lsn) { CheckpointCallbackItem* item; - + // 遍历回调函数链表 for (item = g_instance.ckpt_cxt_ctl->ckptCallback; item; item = item->next) { + // 调用每个注册的回调函数,传递相应的参数 (*item->callback) (checkpointEvent, lsn, item->arg); } } diff --git a/src/gausskernel/process/postmaster/fork_process.cpp b/src/gausskernel/process/postmaster/fork_process.cpp index 45bafed55..54f0b9551 100644 --- a/src/gausskernel/process/postmaster/fork_process.cpp +++ b/src/gausskernel/process/postmaster/fork_process.cpp @@ -43,6 +43,7 @@ pid_t fork_process(void) * Presently stdout and stderr are the only stdio output channels used by * the postmaster, so fflush'ing them should be sufficient. */ + // 刷新标准输出和标准错误流,避免输出问题 fflush(stdout); fflush(stderr); @@ -57,15 +58,16 @@ pid_t fork_process(void) getitimer(ITIMER_PROF, &prof_itimer); #endif - result = fork(); + result = fork(); // 创建子进程 - if (result == 0) { + if (result == 0) { // fork成功 /* fork succeeded, in child */ #ifdef LINUX_PROFILE - setitimer(ITIMER_PROF, &prof_itimer, NULL); + setitimer(ITIMER_PROF, &prof_itimer, NULL); // 设置性能分析计时器,以保证子进程也能进行性能分析 #endif /* + * 强调了在Linux系统中保护postmaster进程免受OOM杀死的问题,并提供了一些解决方法和建议 * By default, Linux tends to kill the postmaster in out-of-memory * situations, because it blames the postmaster for the sum of child * process sizes *including shared memory*. (This is unbelievably @@ -84,19 +86,19 @@ pid_t fork_process(void) * Use open() not stdio, to ensure we control the open flags. Some * Linux security environments reject anything but O_WRONLY. */ - int fd = open("/proc/self/oom_score_adj", O_WRONLY, 0); + int fd = open("/proc/self/oom_score_adj", O_WRONLY, 0); // 打开OOM分数调整文件 /* We ignore all errors */ if (fd >= 0) { - char buf[16]; - int rc; + char buf[16]; // 用于存储要写入文件的字符串 + int rc; // 用于存储write()函数的返回值 - errno_t rcs = snprintf_s(buf, sizeof(buf), sizeof(buf) - 1, "%d\n", LINUX_OOM_SCORE_ADJ); - securec_check_intval(rcs, ); + errno_t rcs = snprintf_s(buf, sizeof(buf), sizeof(buf) - 1, "%d\n", LINUX_OOM_SCORE_ADJ); // 格式化OOM分数调整值为字符串 + securec_check_intval(rcs, ); // 检查snprintf_s函数的返回值 - rc = write(fd, buf, strlen(buf)); - (void)rc; - close(fd); + rc = write(fd, buf, strlen(buf)); // 将OOM分数调整值写入文件 + (void)rc; // 防止编译器警告 + close(fd); // 关闭文件 } } #endif /* LINUX_OOM_SCORE_ADJ */ @@ -113,25 +115,25 @@ pid_t fork_process(void) * Use open() not stdio, to ensure we control the open flags. Some * Linux security environments reject anything but O_WRONLY. */ - int fd = open("/proc/self/oom_adj", O_WRONLY, 0); + int fd = open("/proc/self/oom_adj", O_WRONLY, 0); // 打开OOM调整文件 /* We ignore all errors */ if (fd >= 0) { - char buf[16]; - int rc; + char buf[16];// 用于存储要写入文件的字符串 + int rc; // 用于存储write()函数的返回值 errno_t rcs = snprintf_s(buf, sizeof(buf), sizeof(buf) - 1, "%d\n", LINUX_OOM_ADJ); - securec_check_intval(rcs, ); + securec_check_intval(rcs, ); // 检查snprintf_s函数的返回值 - rc = write(fd, buf, strlen(buf)); - (void)rc; - close(fd); + rc = write(fd, buf, strlen(buf)); // 将OOM调整值写入文件 + (void)rc; // 防止编译器警告 + close(fd); // 关闭文件 } } #endif /* LINUX_OOM_ADJ */ /* Binding static TLS variables for current thread */ - EarlyBindingTLSVariables(); + EarlyBindingTLSVariables(); // 绑定当前线程的静态TLS变量 } return result; diff --git a/src/gausskernel/process/postmaster/globalstats.cpp b/src/gausskernel/process/postmaster/globalstats.cpp index 921c87dc7..69c92b4f6 100644 --- a/src/gausskernel/process/postmaster/globalstats.cpp +++ b/src/gausskernel/process/postmaster/globalstats.cpp @@ -60,52 +60,94 @@ static void GlobalstatsSigusr2Handler(SIGNAL_ARGS); static void GlobalstatsSigtermHandler(SIGNAL_ARGS); /* SIGHUP: set flag to re-read config file at next convenient time */ +/* + * 功能:SIGHUP 信号处理程序,在下一个方便的时间重新读取配置文件 + * + * 参数: + * SIGNAL_ARGS: 信号处理程序的参数列表,提供关于触发信号的上下文信息 + * + * 返回值: + * 无 + */ static void GlobalstatsSighupHandler(SIGNAL_ARGS) { - int saveErrno = errno; + int saveErrno = errno; // 保存当前的错误码 - t_thrd.gstat_cxt.got_SIGHUP = true; + t_thrd.gstat_cxt.got_SIGHUP = true; // 设置标志以指示需要在下一个方便的时间重新读取配置文件 if (t_thrd.proc) - SetLatch(&t_thrd.proc->procLatch); + SetLatch(&t_thrd.proc->procLatch);// 如果进程存在,则设置进程的Latch以唤醒进程 - errno = saveErrno; + errno = saveErrno;// 恢复之前保存的错误码 } /* SIGUSR2: a worker is up and running, or just finished, or failed to fork */ +/* + * 功能:SIGUSR2 信号处理程序,表示一个工作进程已启动、运行完成或无法 fork。 + * + * 参数: + * SIGNAL_ARGS: 信号处理程序的参数列表,提供关于触发信号的上下文信息 + * + * 返回值: + * 无 + */ static void GlobalstatsSigusr2Handler(SIGNAL_ARGS) { - int saveErrno = errno; + int saveErrno = errno; // 保存当前的错误码 - t_thrd.gstat_cxt.got_SIGUSR2 = true; + t_thrd.gstat_cxt.got_SIGUSR2 = true; // 设置标志以指示接收到SIGUSR2信号 if (t_thrd.proc) - SetLatch(&t_thrd.proc->procLatch); + SetLatch(&t_thrd.proc->procLatch); // 如果进程存在,则设置进程的Latch以唤醒进程 - errno = saveErrno; + errno = saveErrno; // 恢复之前保存的错误码 } /* SIGTERM: time to die */ +/* + * 功能:SIGTERM 信号处理程序,请求正常退出 + * + * 参数: + * SIGNAL_ARGS: 信号处理程序的参数列表,提供关于触发信号的上下文信息 + * + * 返回值: + * 无 + */ static void GlobalstatsSigtermHandler(SIGNAL_ARGS) { - int saveErrno = errno; + int saveErrno = errno;// 保存当前的错误码 - t_thrd.gstat_cxt.got_SIGTERM = true; + t_thrd.gstat_cxt.got_SIGTERM = true; // 设置标志以指示需要正常退出 if (t_thrd.proc) - SetLatch(&t_thrd.proc->procLatch); + SetLatch(&t_thrd.proc->procLatch); // 如果进程存在,则设置进程的Latch以唤醒进程 - errno = saveErrno; + errno = saveErrno;// 恢复之前保存的错误码 } +/* + * 功能:准备切换全局统计信息哈希表的状态 + * + * 在切换全局统计信息哈希表之前,需要确保没有其他线程正在读取哈希表。 + * 该函数会先设置一个 quiesce 标志,然后等待所有读取哈希表的线程完成。 + * 只有当 quiesce 标志为 0 时,才能安全地进行切换。 + * + * 参数: 无 + * + * 返回值: 无 + */ static void PrepareStatsHashForSwitch() { + // 断言 quiesce 标志为 0,确保没有其他线程正在切换 Assert(pg_atomic_read_u64(&g_instance.stat_cxt.tableStat->quiesce) == 0); + // 将 quiesce 标志设置为 1,表示正在切换 pg_atomic_exchange_u64(&g_instance.stat_cxt.tableStat->quiesce, 1); - + // 获取当前读取哈希表的线程数 uint64 readers = pg_atomic_read_u64(&g_instance.stat_cxt.tableStat->readers); + // 断言读取线程数小于总线程数 Assert(readers < (uint64) GLOBAL_ALL_PROCS); /* Wait until all readers are done */ - while (readers > 0) { - pg_usleep(10000L); + while (readers > 0) { // 等待所有读取线程完成 + pg_usleep(10000L); // 等待一段时间,以免过于频繁地检查 + // 重新获取读取线程数 readers = pg_atomic_read_u64(&g_instance.stat_cxt.tableStat->readers); } } @@ -118,16 +160,31 @@ static void CompleteStatsHashSwitch() pg_atomic_exchange_u64(&g_instance.stat_cxt.tableStat->quiesce, 0); } +/* + * 功能:匹配全局统计信息哈希表的键 + * + * 用于比较两个哈希表键是否匹配。在全局统计信息哈希表中,键的类型为 PgStat_StartBlockTableKey。 + * 如果两个键的 relid、parentid 和 dbid 都相等,那么认为它们匹配。 + * + * 参数: + * - left: 左边的键 + * - right: 右边的键 + * - keysize: 键的大小 + * + * 返回值: 0 表示匹配,非 0 表示不匹配 + */ static int MatchDictItem(const void* left, const void* right, Size keysize) { + // 将左右两个键转换为 PgStat_StartBlockTableKey 类型 const PgStat_StartBlockTableKey* leftItem = (PgStat_StartBlockTableKey*)left; const PgStat_StartBlockTableKey* rightItem = (PgStat_StartBlockTableKey*)right; - Assert(leftItem != NULL && rightItem != NULL); + Assert(leftItem != NULL && rightItem != NULL); // 断言左右键都不为空 /* we just care whether the result is 0 or not. */ + // 如果 relid、parentid 和 dbid 都相等,则返回 0,表示匹配 if (leftItem->relid != rightItem->relid || leftItem->parentid != rightItem->parentid || leftItem->dbid != rightItem->dbid) { - return 1; + return 1; // 不匹配,返回非 0 值 } return 0; @@ -138,26 +195,39 @@ static uint32 HashDictItem(const void* key, Size keysize) return DatumGetUInt32(hash_any((const unsigned char*)key, sizeof(PgStat_StartBlockTableKey))); } +/* + * 功能:初始化全局统计信息追踪器 + * + * 参数: 无 + * + * 返回值: 无 + */ void GlobalStatsTrackerInit() { /* Setup a shared memory context that other backends can access. * This will hold the Global Statistics Hash */ + // 设置一个其他后端进程可以访问的共享内存上下文 g_instance.stat_cxt.tableStat->global_stats_cxt = AllocSetContextCreate((MemoryContext)g_instance.instance_context, "GlobalStatisticsContext", ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE, SHARED_CONTEXT); - + // 初始化哈希表控制结构 HASHCTL hashCtrl; errno_t rc = memset_s(&hashCtrl, sizeof(hashCtrl), 0, sizeof(hashCtrl)); securec_check(rc, "", ""); + // 设置哈希表的哈希函数和比较函数 hashCtrl.hash = (HashValueFunc)HashDictItem; hashCtrl.match = (HashCompareFunc)MatchDictItem; + // 设置哈希表键的大小和条目的大小 hashCtrl.keysize = (Size)sizeof(PgStat_StartBlockTableKey); hashCtrl.entrysize = (Size)sizeof(PgStat_StartBlockTableEntry); + // 设置哈希表的上下文为全局统计信息上下文 hashCtrl.hcxt = g_instance.stat_cxt.tableStat->global_stats_cxt; + // 设置哈希表的分区数 hashCtrl.num_partitions = NUM_STARTBLOCK_PARTITIONS; int flags = (HASH_FUNCTION | HASH_COMPARE | HASH_ELEM | HASH_SHRCTX | HASH_PARTITION); + // 创建全局统计信息哈希表 g_instance.stat_cxt.tableStat->blocks_map = hash_create("Candidate Blocks for Pruning Hash", NUM_STARTBLOCK_PARTITIONS, &hashCtrl, flags); } @@ -167,9 +237,17 @@ bool IsGlobalStatsTrackerProcess() return t_thrd.role == GLOBALSTATS_THREAD; } +/* + * 功能:全局统计信息追踪器的主要入口点,进行一系列的初始化操作, + * 设置信号处理函数,处理异常情况,以及周期性地收集全局统计信息。 + * + * 参数: 无 + * + * 返回值: 无 + */ NON_EXEC_STATIC void GlobalStatsTrackerMain() { - sigjmp_buf localSigjmpBuf; + sigjmp_buf localSigjmpBuf; // 用于保存异常跳转的上下文信息 /* we are a postmaster subprocess now */ IsUnderPostmaster = true; @@ -181,29 +259,31 @@ NON_EXEC_STATIC void GlobalStatsTrackerMain() t_thrd.proc_cxt.MyProgName = "StatsTracker"; - Assert(t_thrd.proc->pid == t_thrd.proc_cxt.MyProcPid); - init_ps_display("global stats process", "", "", ""); + Assert(t_thrd.proc->pid == t_thrd.proc_cxt.MyProcPid); // 断言当前进程的 PID 与 t_thrd 中记录的一致 + init_ps_display("global stats process", "", "", ""); // 初始化进程状态的显示 + // 输出日志,表示全局统计信息收集器已启动 ereport(LOG, (errmsg("global stats collector started"))); - SetProcessingMode(InitProcessing); + SetProcessingMode(InitProcessing); // 设置当前进程的处理模式为初始化模式,用于执行一些初始化操作 /* * Set up signal handlers. We operate on databases much like a regular * backend, so we use the same signal handling. See equivalent code in * tcop/postgres.c. */ - gspqsignal(SIGHUP, GlobalstatsSighupHandler); - gspqsignal(SIGINT, StatementCancelHandler); - gspqsignal(SIGTERM, GlobalstatsSigtermHandler); + // 设置信号处理程序 + gspqsignal(SIGHUP, GlobalstatsSighupHandler); // 用于重新读取配置文件 + gspqsignal(SIGINT, StatementCancelHandler); // 用于取消正在执行的语句 + gspqsignal(SIGTERM, GlobalstatsSigtermHandler); // 用于终止进程 - gspqsignal(SIGQUIT, quickdie); - gspqsignal(SIGALRM, handle_sig_alarm); + gspqsignal(SIGQUIT, quickdie); // 用于快速终止进程 + gspqsignal(SIGALRM, handle_sig_alarm); // 用于处理定时器信号 - gspqsignal(SIGPIPE, SIG_IGN); - gspqsignal(SIGUSR1, procsignal_sigusr1_handler); - gspqsignal(SIGUSR2, GlobalstatsSigusr2Handler); - gspqsignal(SIGFPE, FloatExceptionHandler); - gspqsignal(SIGCHLD, SIG_DFL); + gspqsignal(SIGPIPE, SIG_IGN); // 用于避免进程在写入已关闭的管道时终止 + gspqsignal(SIGUSR1, procsignal_sigusr1_handler); // 用于用户自定义的信号处理 + gspqsignal(SIGUSR2, GlobalstatsSigusr2Handler); // 用于指示一个工作进程正在运行、刚刚完成或者在 fork 失败 + gspqsignal(SIGFPE, FloatExceptionHandler); // 用于处理浮点异常 + gspqsignal(SIGCHLD, SIG_DFL); // 用于处理子进程的状态变化 /* Early initialization */ BaseInit(); @@ -215,74 +295,75 @@ NON_EXEC_STATIC void GlobalStatsTrackerMain() * had to do some stuff with LWLocks). */ #ifndef EXEC_BACKEND - InitProcess(); + InitProcess(); // 初始化进程上下文 #endif - SetProcessingMode(NormalProcessing); + SetProcessingMode(NormalProcessing); // 设置当前进程的处理模式为正常处理模式,用于正常的数据库操作 /* Unblock signals (they were blocked when the postmaster forked us) */ - gs_signal_setmask(&t_thrd.libpq_cxt.UnBlockSig, NULL); - (void)gs_signal_unblock_sigusr2(); + gs_signal_setmask(&t_thrd.libpq_cxt.UnBlockSig, NULL); // 取消了屏蔽以允许信号被处理 + (void)gs_signal_unblock_sigusr2(); // 取消对 SIGUSR2 信号[用于后台进程的状态更新]的屏蔽 /* * If an exception is encountered, processing resumes here. * * This code is a stripped down version of PostgresMain error recovery. */ - if (sigsetjmp(localSigjmpBuf, 1) != 0) { + // 错误恢复处理 + if (sigsetjmp(localSigjmpBuf, 1) != 0) { // 如果在执行过程中出现错误,将会跳转到此处 /* since not using PG_TRY, must reset error stack by hand */ t_thrd.log_cxt.error_context_stack = NULL; t_thrd.log_cxt.call_stack = NULL; /* Prevents interrupts while cleaning up */ - HOLD_INTERRUPTS(); + HOLD_INTERRUPTS(); // 禁止中断,以确保在清理操作期间不会被中断 /* Report the error to the server log */ - EmitErrorReport(); + EmitErrorReport(); // 将错误信息记录到服务器日志中 /* release resource held by lsc */ - AtEOXact_SysDBCache(false); + AtEOXact_SysDBCache(false); // 释放系统数据库缓存,以确保清理操作 - FlushErrorState(); + FlushErrorState(); // 刷新错误状态,以清除错误信息 /* Now we can allow interrupts again */ - RESUME_INTERRUPTS(); + RESUME_INTERRUPTS(); // 允许中断,以便继续正常处理 /* if in shutdown mode, no need for anything further; just go away */ - if (t_thrd.gstat_cxt.got_SIGTERM) + if (t_thrd.gstat_cxt.got_SIGTERM) // 如果收到了 SIGTERM 信号,就跳转到关闭操作 goto shutdown; /* * Sleep at least 1 second after any error. We don't want to be * filling the error logs as fast as we can. */ - pg_usleep(1000000L); + pg_usleep(1000000L); // 如果出现错误,等待至少1秒后再继续,以避免日志填充过快 } - while (!t_thrd.gstat_cxt.got_SIGTERM) { + while (!t_thrd.gstat_cxt.got_SIGTERM) { // 循环,直到收到 SIGTERM 信号 /* backup the old one so we can delete it when nobody needs it anymore */ - MemoryContext oldStatLocalContext = u_sess->stat_cxt.pgStatLocalContext; - + MemoryContext oldStatLocalContext = u_sess->stat_cxt.pgStatLocalContext; // 保存旧的统计信息内存上下文,以便稍后进行清理操作 + // 创建一个新的内存上下文用于存储全局统计信息的快照数据 u_sess->stat_cxt.pgStatLocalContext = AllocSetContextCreate(u_sess->top_mem_cxt, "Global Statistics snapshot", ALLOCSET_SMALL_MINSIZE, ALLOCSET_SMALL_INITSIZE, ALLOCSET_SMALL_MAXSIZE); - pgstat_fetch_global(); + pgstat_fetch_global(); // 从全局统计信息中抓取数据并存储在新的内存上下文中 /* switch global_stats_map to point to the newly loaded statistics */ - PrepareStatsHashForSwitch(); - g_instance.stat_cxt.tableStat->global_stats_map = u_sess->stat_cxt.pgStatDBHash; - CompleteStatsHashSwitch(); + PrepareStatsHashForSwitch(); // 执行准备切换操作,确保全局统计信息的安全切换 + g_instance.stat_cxt.tableStat->global_stats_map = u_sess->stat_cxt.pgStatDBHash; // 将全局统计信息的映射指针切换到新的数据 + CompleteStatsHashSwitch(); // 完成切换操作,确保新的数据结构已生效 /* Now destroy the old one */ - if (oldStatLocalContext) { - MemoryContextDelete(oldStatLocalContext); + if (oldStatLocalContext) { // 检查旧的统计信息内存上下文是否存在 + MemoryContextDelete(oldStatLocalContext); // 存在则释放 } - u_sess->stat_cxt.pgStatDBHash = NULL; + u_sess->stat_cxt.pgStatDBHash = NULL; // 设为NULL,确保不再引用旧的数据 - pg_usleep(u_sess->attr.attr_storage.ustats_tracker_naptime * 1000000L); + pg_usleep(u_sess->attr.attr_storage.ustats_tracker_naptime * 1000000L); // 使进程休眠,等待下一次获取全局统计信息的时间 } shutdown: @@ -290,11 +371,11 @@ shutdown: * Before the thread exits, set global_stats_map to NULL to prevent core dump when the * backend thread accesses the released memory during the prune operation. */ - PrepareStatsHashForSwitch(); - g_instance.stat_cxt.tableStat->global_stats_map = NULL; - CompleteStatsHashSwitch(); - ereport(LOG, (errmsg("global stats shutting down"))); - proc_exit(0); + PrepareStatsHashForSwitch(); // 准备切换操作,确保在关闭之前对全局统计信息进行最后的处理 + g_instance.stat_cxt.tableStat->global_stats_map = NULL; // 确保在关闭时不再访问释放的内存 + CompleteStatsHashSwitch(); // 完成切换操作,确保新的状态已生效 + ereport(LOG, (errmsg("global stats shutting down"))); // 生成日志,表示全局统计信息追踪器正在关闭 + proc_exit(0); // 正常退出当前进程 } @@ -303,24 +384,41 @@ shutdown: * relid is the partition id for a Partitioned table, otherwise it's the Relation id. * parentid is the actual Relation id for a Partitioned table, otherwise it's InvalidOid. */ +/* + *功能:获取表的全局统计信息 + * + * 参数: + * Oid dbid:数据库的唯一标识符 + * Oid relid:表的唯一标识符 + * Oid parentid:表的父表的唯一标识符 + * PgStat_StatTabEntry *tableentry:于存储获取到的统计信息 + * + * 返回值: + * bool类型 如果成功获取了表的全局统计信息,则返回 true; + * 如果无法获取,则返回 false + * + */ bool GetTableGstats(Oid dbid, Oid relid, Oid parentid, PgStat_StatTabEntry *tableentry) { /* return if stats is not ready */ + // 检查全局统计信息的映射是否为 NULL,以及是否处于 "quiesce" 状态。如果是,说明统计信息尚未准备好,返回 false if (g_instance.stat_cxt.tableStat->global_stats_map == NULL || pg_atomic_read_u64(&g_instance.stat_cxt.tableStat->quiesce) == 1) { return false; } - uint64 readers PG_USED_FOR_ASSERTS_ONLY = pg_atomic_add_fetch_u64(&g_instance.stat_cxt.tableStat->readers, 1); - Assert(readers < (uint64) GLOBAL_ALL_PROCS); + uint64 readers PG_USED_FOR_ASSERTS_ONLY = pg_atomic_add_fetch_u64(&g_instance.stat_cxt.tableStat->readers, 1); // 增加全局统计信息读取者的计数 + Assert(readers < (uint64) GLOBAL_ALL_PROCS); // 断言检查 readers 是否小于 GLOBAL_ALL_PROCS,用于限制并发访问全局统计信息的进程数量 /* recheck in case quiesce is updated between first check and increment readers */ + // 再次检查 "quiesce" 状态,以确保在增加读取者计数期间未更改状态。如果状态已更改,则减少读取者计数并返回 false if (pg_atomic_read_u64(&g_instance.stat_cxt.tableStat->quiesce) == 1) { pg_atomic_sub_fetch_u64(&g_instance.stat_cxt.tableStat->readers, 1); return false; } - Assert(g_instance.stat_cxt.tableStat->global_stats_map != NULL); + Assert(g_instance.stat_cxt.tableStat->global_stats_map != NULL); // 确保全局统计信息的映射不为 NULL + // 定义指向数据库和表统计信息条目的指针 PgStat_StatDBEntry *dbentry = NULL; PgStat_StatTabEntry *tabentry = NULL; errno_t rc = 0; @@ -329,87 +427,129 @@ bool GetTableGstats(Oid dbid, Oid relid, Oid parentid, PgStat_StatTabEntry *tabl PgStat_StatTabKey tabkey; tabkey.statFlag = parentid; tabkey.tableid = relid; - + // 通过数据库标识从全局统计信息映射中查找数据库统计信息条目。如果没有找到,dbentry 将为 NULL dbentry = (PgStat_StatDBEntry*)hash_search(g_instance.stat_cxt.tableStat->global_stats_map, (void*)&dbid, HASH_FIND, NULL); if (dbentry == NULL) { goto done; } - + // 通过构建的键从数据库统计信息中查找表的统计信息条目。如果没有找到,tabentry 将为 NULL tabentry = (PgStat_StatTabEntry*)hash_search(dbentry->tables, (void*)(&tabkey), HASH_FIND, NULL); if (tabentry == NULL) { goto done; } - + // 如果找到了统计信息条目,函数将其复制到 tableentry 中,以便返回给调用者 rc = memcpy_s(tableentry, sizeof(PgStat_StatTabEntry), tabentry, sizeof(PgStat_StatTabEntry)); securec_check(rc, "", ""); - result = true; + result = true; // 表示成功获取了统计信息 done: - readers = pg_atomic_sub_fetch_u64(&g_instance.stat_cxt.tableStat->readers, 1); + readers = pg_atomic_sub_fetch_u64(&g_instance.stat_cxt.tableStat->readers, 1); // 减少读取者计数 Assert(readers < (uint64) GLOBAL_ALL_PROCS); return result; } +/* + *功能:获取用于访问全局统计信息中 "StartBlock" 哈希表特定分区的互斥锁 + * + * 参数: + * PgStat_StartBlockTableKey *tabkey:唯一标识要访问的哈希表分区 + * LWLockMode mode:用于指定锁的模式,可以是共享锁(SHARED)或独占锁(EXCLUSIVE) + * + * 返回值: + * LWLock * 类型,,用于表示获取的锁 + * + */ static LWLock *LockStartBlockHashTablePartition(PgStat_StartBlockTableKey *tabkey, LWLockMode mode) { - uint32 hashValue = get_hash_value(g_instance.stat_cxt.tableStat->blocks_map, tabkey); - uint32 partition = hashValue % (NUM_STARTBLOCK_PARTITIONS); - uint32 lockid = (uint32)(FirstStartBlockMappingLock + partition); - LWLock* lock = &t_thrd.shemem_ptr_cxt.mainLWLockArray[lockid].lock; + uint32 hashValue = get_hash_value(g_instance.stat_cxt.tableStat->blocks_map, tabkey); // 计算哈希值 + uint32 partition = hashValue % (NUM_STARTBLOCK_PARTITIONS); // 计算分区号,用于确定哈希表中的哪个分区将用于锁定 + uint32 lockid = (uint32)(FirstStartBlockMappingLock + partition); // 计算锁标识,用于确定要锁定的锁 + LWLock* lock = &t_thrd.shemem_ptr_cxt.mainLWLockArray[lockid].lock; // 使用的是哈希分区的锁来控制对哈希表分区的访问 - LWLockAcquire(lock, mode); + LWLockAcquire(lock, mode); // 获取锁 return lock; } +/* + *功能:用于在全局统计信息的 "StartBlock" 哈希表中查找给定的 PgStat_StartBlockTableKey,并返回相应的 PgStat_StartBlockTableEntry 条目 + * + * 参数: + * PgStat_StartBlockTableKey *tabkey:用于唯一标识要查找的哈希表条目 + * + * 返回值: + * PgStat_StartBlockTableEntry * 类型,表示查找到的哈希表条目。如果没有找到匹配的条目,返回值为 NULL + */ PgStat_StartBlockTableEntry * StartBlockHashTableLookup(PgStat_StartBlockTableKey *tabkey) { - PgStat_StartBlockTableEntry *result = NULL; - bool found = false; + PgStat_StartBlockTableEntry *result = NULL; // 用于存储查找结果 + bool found = false; // 用于表示是否找到了匹配的条目 - LWLock* lock = LockStartBlockHashTablePartition(tabkey, LW_SHARED); + LWLock* lock = LockStartBlockHashTablePartition(tabkey, LW_SHARED); // 获取共享锁 + // 查找匹配的条目,如果找到了,就将它的地址赋给 result ,并将 found 设置为 true;否则,result 保持为 NULL,found 保持为 false。 result = (PgStat_StartBlockTableEntry *) hash_search(g_instance.stat_cxt.tableStat->blocks_map, tabkey, HASH_FIND, &found); - LWLockRelease(lock); + LWLockRelease(lock); // 释放之前获取的共享锁 return result; } +/* + *功能:用于向全局统计信息的 "StartBlock" 哈希表中添加或更新条目,以记录开始块的统计信息 + * + * 参数: + * PgStat_StartBlockTableKey *tabkey:用于唯一标识要添加或更新的哈希表条目 + * + * 返回值: + * PgStat_StartBlockTableEntry * 类型,表示已添加或更新的哈希表条目 + * + */ PgStat_StartBlockTableEntry * StartBlockHashTableAdd(PgStat_StartBlockTableKey *tabkey) { - bool found = true; - PgStat_StartBlockTableEntry *result = NULL; - - LWLock* lock = LockStartBlockHashTablePartition(tabkey, LW_EXCLUSIVE); + bool found = true; // 用于指示在添加条目时是否找到了已存在的匹配条目 + PgStat_StartBlockTableEntry *result = NULL; // 用于存储添加的条目或者已存在的匹配条目 + LWLock* lock = LockStartBlockHashTablePartition(tabkey, LW_EXCLUSIVE); // 获取独占锁 + // 查找匹配的条目,如果找到了,就将它的地址赋给 result ,并将 found 设置为 true;否则,result 保持为 NULL,found 保持为 false。 result = (PgStat_StartBlockTableEntry *)hash_search(g_instance.stat_cxt.tableStat->blocks_map, tabkey, HASH_ENTER, &found); - if (!found) { + if (!found) { // 如果有找到匹配的条目 + // 用于初始化 result 条目中的 starting_blocks 数组。它将数组中的元素设置为从 0 到 START_BLOCK_ARRAY_SIZE - 1 的连续整数值 for (int i = 0; i < START_BLOCK_ARRAY_SIZE; i++) { result->starting_blocks[i] = i; } } - LWLockRelease(lock); + LWLockRelease(lock); // 释放获取的独占锁 return result; } +/* + *功能:用于获取 "StartBlock" 哈希表中的条目。如果哈希表中不存在匹配的条目,则会添加一个新的条目并返回 + * + * 参数: + * PgStat_StartBlockTableKey *tabkey:用于唯一标识要获取或添加的哈希表条目 + * + * 返回值: + * PgStat_StartBlockTableEntry * 类型,表示已获取或添加的哈希表条目 + * + */ PgStat_StartBlockTableEntry * GetStartBlockHashEntry(PgStat_StartBlockTableKey *tabkey) { - PgStat_StartBlockTableEntry *result = NULL; - result = StartBlockHashTableLookup(tabkey); + PgStat_StartBlockTableEntry *result = NULL; // 用于存储获取的条目 + result = StartBlockHashTableLookup(tabkey); // 用于查找哈希表中是否存在与给定键匹配的条目 /* not found, add it */ - if (result == NULL) { - result = StartBlockHashTableAdd(tabkey); + if (result == NULL) { // 如果没有找到匹配的条目 + result = StartBlockHashTableAdd(tabkey); // 添加新的条目 } - Assert(result); + Assert(result); // 确保 result 变量不为 NULL return result; } diff --git a/src/gausskernel/process/postmaster/twophasecleaner.cpp b/src/gausskernel/process/postmaster/twophasecleaner.cpp index 4b2d357b4..015a17a42 100644 --- a/src/gausskernel/process/postmaster/twophasecleaner.cpp +++ b/src/gausskernel/process/postmaster/twophasecleaner.cpp @@ -46,32 +46,32 @@ #include "utils/memutils.h" #include "utils/timestamp.h" -#define PGXC_CLEAN_LOG_FILE "gs_clean.log" -#define PGXC_CLEAN "gs_clean" -#define CM_STATIC_CONFIG_FILE "cluster_static_config" -#define MAX_PATH_LEN 1024 +#define PGXC_CLEAN_LOG_FILE "gs_clean.log" // 指定日志文件的名称 +#define PGXC_CLEAN "gs_clean" // 指定代码中的某个进程或模块的名称 +#define CM_STATIC_CONFIG_FILE "cluster_static_config" // 指定代码中的某个配置文件的名称 +#define MAX_PATH_LEN 1024 // 最大长度 bool bSyncXactsCallGsclean = false; PGPROC* twoPhaseCleanerProc = NULL; -#ifndef ENABLE_MULTIPLE_NODES -#define MAX_ERRMSG_LENGTH 1024 +#ifndef ENABLE_MULTIPLE_NODES // 检查宏 ENABLE_MULTIPLE_NODES 是否未定义 +#define MAX_ERRMSG_LENGTH 1024 // 最大错误消息长度 -typedef struct DatabaseNames { +typedef struct DatabaseNames { // 在链表中存储数据库名称 struct DatabaseNames* next; char* databaseName; } DatabaseNames; -typedef struct TempSchemaInfo { +typedef struct TempSchemaInfo { // 在链表中存储临时模式名称 struct TempSchemaInfo* next; char* tempSchemaName; } TempSchemaInfo; -typedef struct ActiveBackendInfo { +typedef struct ActiveBackendInfo { // 在链表中存储有关活动后端的信息 struct ActiveBackendInfo* next; - int64 sessionID; - uint32 tempID; - uint32 timeLineID; + int64 sessionID; // 会话ID + uint32 tempID; // 临时ID + uint32 timeLineID; // 时间线ID } ActiveBackendInfo; #endif @@ -79,39 +79,49 @@ typedef struct ActiveBackendInfo { static void TwoPCSigHupHandler(SIGNAL_ARGS); static void TwoPCShutdownHandler(SIGNAL_ARGS); #ifdef ENABLE_MULTIPLE_NODES -static int get_prog_path(const char* argv0); +static int get_prog_path(const char* argv0); // 获取程序的路径 #endif #ifndef ENABLE_MULTIPLE_NODES -static bool DropTempNamespace(); +static bool DropTempNamespace(); // 删除临时命名空间 #endif #ifdef ENABLE_MULTIPLE_NODES +// 获取一个字符串 static const char* getGsCleanLogLevel() { + // 如果 MOD_GSCLEAN 模块的日志记录是开启的 if (module_logging_is_on(MOD_GSCLEAN)) { - return "DEBUG"; + return "DEBUG"; // 表示日志级别为调试 } else { - return "LOG"; + return "LOG"; // 表示日志级别为普通日志 } } #endif +/* + * 功能:twophasecleaner进程的主要入口函数,用于自动清理数据库中的两阶段事务 + * + * 参数:无 + * + * 返回值:无 + */ NON_EXEC_STATIC void TwoPhaseCleanerMain() { - sigjmp_buf local_sigjmp_buf; - MemoryContext twopc_context; - bool clean_successed = false; + sigjmp_buf local_sigjmp_buf; // 异常跳转点 + MemoryContext twopc_context; // 创建内存上下文 + bool clean_successed = false; // 用于跟踪清理是否成功 int rc; /* we are a postmaster subprocess now */ - IsUnderPostmaster = true; + IsUnderPostmaster = true; // 表示当前进程是 postmaster 子进程 t_thrd.proc_cxt.MyProcPid = gs_thread_self(); - twoPhaseCleanerProc = t_thrd.proc; - + twoPhaseCleanerProc = t_thrd.proc; // 标识当前线程所在的进程 + // 记录调试级别的日志消息,表示twophasecleaner进程已经启动,并显示进程的PID ereport(DEBUG5, (errmsg("twophasecleaner process is started: %lu", t_thrd.proc_cxt.MyProcPid))); + // 设置信号处理函数 (void)gspqsignal(SIGHUP, TwoPCSigHupHandler); /* set flag to read config file */ (void)gspqsignal(SIGINT, TwoPCShutdownHandler); /* request shutdown */ (void)gspqsignal(SIGTERM, TwoPCShutdownHandler); /* request shutdown */ @@ -122,6 +132,7 @@ NON_EXEC_STATIC void TwoPhaseCleanerMain() (void)gspqsignal(SIGUSR2, SIG_IGN); /* not used */ /* Reset some signals that are accepted by postmaster but not here */ + // 恢复信号默认行为 (void)gspqsignal(SIGCHLD, SIG_DFL); (void)gspqsignal(SIGTTIN, SIG_DFL); (void)gspqsignal(SIGTTOU, SIG_DFL); @@ -129,6 +140,7 @@ NON_EXEC_STATIC void TwoPhaseCleanerMain() (void)gspqsignal(SIGWINCH, SIG_DFL); /* We allow SIGQUIT (quickdie) at all times */ + // 允许 SIGQUIT 信号在任何时候都可以被处理 (void)sigdelset(&t_thrd.libpq_cxt.BlockSig, SIGQUIT); /* @@ -137,11 +149,13 @@ NON_EXEC_STATIC void TwoPhaseCleanerMain() * possible memory leaks. Formerly this code just ran in * t_thrd.top_mem_cxt, but resetting that would be a really bad idea. */ + // 创建内存上下文 twopc_context = AllocSetContextCreate(t_thrd.top_mem_cxt, "TwoPhase Cleaner", ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); + // 切换内存上下文 (void)MemoryContextSwitchTo(twopc_context); /* @@ -151,64 +165,72 @@ NON_EXEC_STATIC void TwoPhaseCleanerMain() */ int curTryCounter; int* oldTryCounter = NULL; + // 设置异常处理跳转点 if (sigsetjmp(local_sigjmp_buf, 1) != 0) { + // 关闭所有打开的文件 gstrace_tryblock_exit(true, oldTryCounter); /* Prevents interrupts while cleaning up */ - HOLD_INTERRUPTS(); + HOLD_INTERRUPTS(); // 在执行清理操作期间禁止中断 /* Report the error to the server log */ - EmitErrorReport(); + EmitErrorReport(); // 报告错误到服务器日志 /* release resource held by lsc */ - AtEOXact_SysDBCache(false); + AtEOXact_SysDBCache(false); // 释放系统数据库缓存资源 /* * Now return to normal top-level context and clear ErrorContext for * next time. */ + // 切换程序的内存上下文 (void)MemoryContextSwitchTo(twopc_context); - + // 清除错误状态,将之前可能记录的错误信息和状态重置为空 FlushErrorState(); /* Flush any leaked data in the top-level context */ + // 重置 twopc_context 上下文并删除其所有子级上下文 + // 确保在异常处理完成后释放已分配的内存,以避免内存泄漏 MemoryContextResetAndDeleteChildren(twopc_context); /* Now we can allow interrupts again */ - RESUME_INTERRUPTS(); + RESUME_INTERRUPTS(); // 允许中断信号再次被捕获 } - oldTryCounter = gstrace_tryblock_entry(&curTryCounter); + oldTryCounter = gstrace_tryblock_entry(&curTryCounter); // 记录当前的错误处理堆栈计数器 /* We can now handle ereport(ERROR) */ - t_thrd.log_cxt.PG_exception_stack = &local_sigjmp_buf; + t_thrd.log_cxt.PG_exception_stack = &local_sigjmp_buf; // 将错误处理的跳转缓冲区设置为当前线程的错误处理堆栈 /* Unblock signals (they were blocked when the postmaster forked us) */ - gs_signal_setmask(&t_thrd.libpq_cxt.UnBlockSig, NULL); - (void)gs_signal_unblock_sigusr2(); + gs_signal_setmask(&t_thrd.libpq_cxt.UnBlockSig, NULL); // 解除信号屏蔽,允许信号再次被捕获 + (void)gs_signal_unblock_sigusr2(); // 解除对 SIGUSR2 信号的屏蔽,允许接收 SIGUSR2 信号 TimestampTz gs_clean_start_time = GetCurrentTimestamp(); TimestampTz gs_clean_start_time_gtm_val = GetCurrentTimestamp(); - pgstat_report_appname("TwoPhase Cleaner"); - pgstat_report_activity(STATE_IDLE, NULL); + pgstat_report_appname("TwoPhase Cleaner"); // 报告 twophasecleaner 进程的应用程序名称,用于性能统计和监控 + pgstat_report_activity(STATE_IDLE, NULL); // 报告 twophasecleaner 进程的活动状态为 "STATE_IDLE",表示当前进程处于空闲状态 for (;;) { - TimestampTz gs_clean_current_time = GetCurrentTimestamp(); + TimestampTz gs_clean_current_time = GetCurrentTimestamp(); // 获取当前时间 /* Clear any already-pending wakeups */ - ResetLatch(&t_thrd.proc->procLatch); + ResetLatch(&t_thrd.proc->procLatch); // 清除当前进程的事件标志,以便等待新事件 /* Process any requests or signals received recently. */ + // 如果收到了 SIGHUP 信号 if (t_thrd.tpcleaner_cxt.got_SIGHUP) { t_thrd.tpcleaner_cxt.got_SIGHUP = false; - ProcessConfigFile(PGC_SIGHUP); + ProcessConfigFile(PGC_SIGHUP); // 重新读取配置文件 } - + // 如果收到了关闭请求 if (t_thrd.tpcleaner_cxt.shutdown_requested) { /* Normal exit from the twophasecleaner is here */ - proc_exit(0); + proc_exit(0); // 正常退出 } /* + * 描述在分布式 Gaussdb 中的清理行为以及在 opengauss 中的替代清理行为 + * * In distributed Gaussdb, we call gs_clean to clean the prepared * transaction every gs_clean_timeout second. * Remark: After the process is started, wait 60s to call @@ -219,6 +241,9 @@ NON_EXEC_STATIC void TwoPhaseCleanerMain() * In opengauss, instead of calling gs_clean, we use twophasecleaner * to clean up the temporary table every gs_clean_timeout second. */ + // 如果需要立即调用 gs_clean 进行清理 + // 或 启用了清理功能且上一次的清理操作失败 且 时间差超过60s + // 或 时间超过阈值 if (bSyncXactsCallGsclean || (u_sess->attr.attr_storage.gs_clean_timeout && ((!clean_successed && @@ -226,16 +251,18 @@ NON_EXEC_STATIC void TwoPhaseCleanerMain() TimestampDifferenceExceeds(gs_clean_start_time, gs_clean_current_time, u_sess->attr.attr_storage.gs_clean_timeout * 1000)))) { -#ifdef ENABLE_MULTIPLE_NODES +#ifdef ENABLE_MULTIPLE_NODES // 如果系统已启用多节点模式 int status = 0; char cmd[MAX_PATH_LEN]; - status = get_prog_path("gaussdb"); - if (status < 0) { + status = get_prog_path("gaussdb"); // 获取 gaussdb 可执行文件的路径 + if (status < 0) { // 如果获取失败 + // 输出日志消息 ereport(DEBUG5, (errmsg("failed to invoke get_prog_path()"))); } else { /* if we find explicit cn listen address, we use tcp connection instead of unix socket */ -#ifdef USE_ASSERT_CHECKING +#ifdef USE_ASSERT_CHECKING // 如果启用了断言检查 + // 命令字符串中包括详细的日志输出,并将输出追加到指定的日志文件 rc = sprintf_s(cmd, sizeof(cmd), "gs_clean -a -p %d -h localhost -v -r -j %d -l %s >> %s 2>&1", @@ -245,6 +272,7 @@ NON_EXEC_STATIC void TwoPhaseCleanerMain() t_thrd.tpcleaner_cxt.pgxc_clean_log_path); securec_check_ss(rc, "\0", "\0"); #else + // 命令字符串中只将标准输出和标准错误输出重定向到 /dev/null,即不进行详细的日志记录 rc = sprintf_s(cmd, sizeof(cmd), "gs_clean -a -p %d -h localhost -v -r -j %d -l %s > /dev/null 2>&1", @@ -253,31 +281,38 @@ NON_EXEC_STATIC void TwoPhaseCleanerMain() getGsCleanLogLevel()); securec_check_ss(rc, "\0", "\0"); #endif - socket_close_on_exec(); - + socket_close_on_exec(); // 关闭当前进程上的文件描述符 + // 报告当前进程的状态为 STATE_RUNNING,以便监控和统计 pgstat_report_activity(STATE_RUNNING, NULL); status = system(cmd); - if (status == 0) { + if (status == 0) { // 如果命令执行成功 + // 在日志中输出一条调试信息表示清理操作成功 ereport(DEBUG5, (errmsg("clean up 2pc transactions succeed"))); + // 设置标志,表示清理操作已成功执行 clean_successed = true; bSyncXactsCallGsclean = false; } else { + // 设置标志,表示清理操作未成功执行 clean_successed = false; + // 在日志中输出一条警告信息表示清理操作失败 ereport(WARNING, (errmsg("clean up 2pc transactions failed"))); } } #else - if (DropTempNamespace()) { + if (DropTempNamespace()) { // 如果成功执行清理操作 + // 在日志中输出一条调试信息表示清理操作成功 ereport(DEBUG5, (errmsg("clean up temp schemas succeed"))); + // 表示清理操作已成功执行 clean_successed = true; bSyncXactsCallGsclean = false; } else { + // 表示清理操作未成功执行 clean_successed = false; ereport(WARNING, (errmsg("clean up temp schemas failed"))); } #endif - gs_clean_start_time = GetCurrentTimestamp(); + gs_clean_start_time = GetCurrentTimestamp(); // 更新当前时间,以用于下一轮清理操作的时间计算 } /* @@ -286,86 +321,122 @@ NON_EXEC_STATIC void TwoPhaseCleanerMain() * * Remark: set gtm_conn_check_interval to ZERO to disable this function. */ + // 如果启用了 GTM 连接检查功能 且 当前时间与上次检查的时间间隔大于等于 gtm_conn_check_interval 指定的秒数 if (u_sess->attr.attr_storage.gtm_conn_check_interval && TimestampDifferenceExceeds(gs_clean_start_time_gtm_val, gs_clean_current_time, u_sess->attr.attr_storage.gtm_conn_check_interval * 1000)) { - pgstat_cancel_invalid_gtm_conn(); + // 取消那些正在运行的后端连接到已降级的 GTM的查询 + pgstat_cancel_invalid_gtm_conn(); + // // 更新当前时间,以用于下一轮清理操作的时间计算 gs_clean_start_time_gtm_val = GetCurrentTimestamp(); } - pgstat_report_activity(STATE_IDLE, NULL); + pgstat_report_activity(STATE_IDLE, NULL); // 报告当前线程的活动状态为空闲状态 + // 等待事件发生 rc = WaitLatch(&t_thrd.proc->procLatch, WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH, (long)10000 /* 10s */); - + // 重置和删除当前线程的内存上下文中的子上下文,以释放不再需要的内存资源 MemoryContextResetAndDeleteChildren(twopc_context); /* * Emergency bailout if postmaster has died. This is to avoid the * necessity for manual cleanup of all postmaster children. */ + // 如果postmaster 已经终止 if (((unsigned int)rc) & WL_POSTMASTER_DEATH) - gs_thread_exit(1); + gs_thread_exit(1); // 退出当前线程 } } /* SIGHUP: set flag to re-read config file at next convenient time */ +/* + *功能:处理 SIGHUP 信号,用于重新读取配置文件 + * + * 参数: + * SIGNAL_ARGS: 信号处理程序的参数列表,提供关于触发信号的上下文信息 + * + * 返回值: + * 无 + */ static void TwoPCSigHupHandler(SIGNAL_ARGS) { - int save_errno = errno; + int save_errno = errno; // 保存当前的错误码 - t_thrd.tpcleaner_cxt.got_SIGHUP = true; + t_thrd.tpcleaner_cxt.got_SIGHUP = true; // 表示接收到 SIGHUP 信号 - if (t_thrd.proc) - SetLatch(&t_thrd.proc->procLatch); + if (t_thrd.proc) // 如果存在进程 + SetLatch(&t_thrd.proc->procLatch); // 设置该进程的进程latch - errno = save_errno; + errno = save_errno; // 恢复之前保存的错误码 } /* SIGTERM: set flag to exit normally */ +/* + *功能:处理 SIGTERM 信号,用于正常退出 + * + * 参数: + * SIGNAL_ARGS: 信号处理程序的参数列表,提供关于触发信号的上下文信息 + * + * 返回值: + * 无 + */ static void TwoPCShutdownHandler(SIGNAL_ARGS) { - int save_errno = errno; + int save_errno = errno; // 保存当前的错误码 - t_thrd.tpcleaner_cxt.shutdown_requested = true; + t_thrd.tpcleaner_cxt.shutdown_requested = true; // 表示请求正常退出 - if (t_thrd.proc) - SetLatch(&t_thrd.proc->procLatch); + if (t_thrd.proc) // 如果存在进程 + SetLatch(&t_thrd.proc->procLatch); // 设置该进程的进程latch - errno = save_errno; + errno = save_errno; // 恢复之前保存的错误码 } #ifdef ENABLE_MULTIPLE_NODES +/* + * 功能: 获取执行程序路径和设置日志文件路径 + * + * 参数: + * argv0-用于传递程序的名称或路径 + * + * 返回值: + * 如果成功执行并且获取了执行程序的路径以及设置了日志文件路径,返回 0,表示成功 + * 否则返回-1,表示失败 + */ static int get_prog_path(const char* argv0) { - char* exec_path = NULL; - char* gausslog_dir = NULL; - char log_dir[MAX_PATH_LEN] = {0}; - char env_gausshome_val[MAX_PATH_LEN] = {0}; + char* exec_path = NULL; // 用于存储执行程序的路径 + char* gausslog_dir = NULL; // 用于存储日志文件的根目录路径 + char log_dir[MAX_PATH_LEN] = {0}; // 用于存储最终的日志文件路径 + char env_gausshome_val[MAX_PATH_LEN] = {0}; // 用于存储环境变量的值 char env_gausslog_val[MAX_PATH_LEN] = {0}; - char gaussdb_bin_path[MAX_PATH_LEN] = {0}; + char gaussdb_bin_path[MAX_PATH_LEN] = {0}; // 用于存储执行程序的路径的备份 errno_t errorno = EOK; int rc; - + // 清空 errorno = memset_s(t_thrd.tpcleaner_cxt.pgxc_clean_log_path, MAX_PATH_LEN, 0, MAX_PATH_LEN); securec_check_c(errorno, "\0", "\0"); - exec_path = gs_getenv_r("GAUSSHOME"); - - if (NULL == exec_path) { + exec_path = gs_getenv_r("GAUSSHOME"); // 检查环境变量 GAUSSHOME 是否已设置 + // 如果已设置,将 exec_path 设置为 GAUSSHOME 的值 + if (NULL == exec_path) { // 如果未设置 + // 查找当前可执行程序的路径 if (find_my_exec(argv0, gaussdb_bin_path) < 0) { ereport(WARNING, (errmsg("%s: could not locate my own executable path", argv0))); return -1; } - exec_path = gaussdb_bin_path; - check_backend_env(exec_path); + exec_path = gaussdb_bin_path; // 设置为可执行程序所在目录的路径 + check_backend_env(exec_path); // 检查后端的环境变量设置 + // 删除 exec_path 中的可执行程序名称,以获得执行程序所在目录的路径 get_parent_directory(exec_path); /* remove my executable name */ } - Assert(NULL != exec_path); + Assert(NULL != exec_path); // 确保 exec_path 不为 NULL { char realExecPath[PATH_MAX + 1] = {'\0'}; - if (realpath(exec_path, realExecPath) == NULL) { + if (realpath(exec_path, realExecPath) == NULL) { // 获取真实路径,如果获取失败 + // 输出警告信息 ereport(WARNING, (errmodule(MOD_TRANS_XACT), errcode(ERRCODE_EXTERNAL_ROUTINE_INVOCATION_EXCEPTION), errmsg("Failed to obtain environment value $GAUSSHOME!"), @@ -374,13 +445,17 @@ static int get_prog_path(const char* argv0) erraction("Please refer to backend log for more details."))); return -1; } - exec_path = NULL; - check_backend_env(realExecPath); + exec_path = NULL; // 设置为 NULL,以确保后续不再使用 + check_backend_env(realExecPath); // 检查后端的环境变量设置 + // 备份真实的执行路径 rc = snprintf_s(env_gausshome_val, sizeof(env_gausshome_val), MAX_PATH_LEN - 1, "%s", realExecPath); securec_check_ss(rc, "\0", "\0"); - gausslog_dir = gs_getenv_r("GAUSSLOG"); + gausslog_dir = gs_getenv_r("GAUSSLOG"); // 尝试获取环境变量 $GAUSSLOG 的值 + // 检查 gausslog_dir 是否为 NULL 或者为空字符串 if ((NULL == gausslog_dir) || ('\0' == gausslog_dir[0])) { + // 输出警告信息 ereport(WARNING, (errmsg("environment variable $GAUSSLOG is not set"))); + // 表示最终的日志文件路径 rc = snprintf_s(t_thrd.tpcleaner_cxt.pgxc_clean_log_path, sizeof(t_thrd.tpcleaner_cxt.pgxc_clean_log_path), MAX_PATH_LEN - 1, @@ -390,7 +465,9 @@ static int get_prog_path(const char* argv0) g_instance.attr.attr_common.PGXCNodeName); securec_check_ss(rc, "\0", "\0"); } else { + // 存储 $GAUSSLOG 环境变量的真实路径 char realGausslogDir[PATH_MAX + 1] = {'\0'}; + // 获取 gausslog_dir 的真实路径,如果获取失败 if (realpath(gausslog_dir, realGausslogDir) == NULL) { ereport(WARNING, (errmodule(MOD_TRANS_XACT), errcode(ERRCODE_EXTERNAL_ROUTINE_INVOCATION_EXCEPTION), @@ -401,18 +478,23 @@ static int get_prog_path(const char* argv0) return -1; } gausslog_dir = NULL; - check_backend_env(realGausslogDir); + check_backend_env(realGausslogDir); // 检查后端的环境变量设置 + // 备份真实的执行路径 rc = snprintf_s(env_gausslog_val, sizeof(env_gausslog_val), MAX_PATH_LEN - 1, "%s", realGausslogDir); securec_check_ss(rc, "\0", "\0"); rc = snprintf_s(log_dir, sizeof(log_dir), MAX_PATH_LEN - 1, "%s/bin/%s", env_gausslog_val, PGXC_CLEAN); securec_check_ss(rc, "\0", "\0"); /* log_dir not exist, create log_dir path */ + // 检查 log_dir 是否存在 + // 如果不存在,则使用 mkdir 函数创建该目录 if (0 != mkdir(log_dir, S_IRWXU)) { - if (EEXIST != errno) { + if (EEXIST != errno) { // 如果创建目录失败 + // 输出一条警告信息 ereport(WARNING, (errmsg("could not create directory %s: %m", log_dir))); return -1; } } + // 最终的日志文件路径 rc = snprintf_s(t_thrd.tpcleaner_cxt.pgxc_clean_log_path, sizeof(t_thrd.tpcleaner_cxt.pgxc_clean_log_path), MAX_PATH_LEN - 1, @@ -430,6 +512,21 @@ static int get_prog_path(const char* argv0) #endif #ifndef ENABLE_MULTIPLE_NODES +/* + * 功能:清除数据库中的临时模式和相关的表 + * + * 参数: + * host-接受主机名 + * port-端口号 + * user-用户名 + * password-密码 + * dbname-数据库名称 + * progname-程序名称 + * encoding-编码方式 + * + * 返回值: + * 指向 PGconn 结构的指针,表示与数据库的连接 + */ static PGconn* LoginDatabase(char* host, int port, char* user, char* password, char* dbname, const char* progname, char* encoding) { @@ -440,11 +537,12 @@ static PGconn* LoginDatabase(char* host, int port, char* user, char* password, const char* values[PARAMS_ARRAY_SIZE]; int count = 0; int retryNum = 10; - int rc; - + int rc; // 存储函数调用的返回值 + // 将端口号 port 格式化成字符串 rc = sprintf_s(portValue, sizeof(portValue), "%d", port); securec_check_ss_c(rc, "\0", "\0"); + // 设置连接数据库时的参数关键字和对应的值,用于配置数据库连接的各种参数 keywords[0] = "host"; values[0] = host; keywords[1] = "port"; @@ -467,29 +565,34 @@ static PGconn* LoginDatabase(char* host, int port, char* user, char* password, keywords[9] = NULL; values[9] = NULL; -retry: +retry: // 创建一个连接重试的循环 /* try to connect to database */ - conn = PQconnectdbParams(keywords, values, true); + conn = PQconnectdbParams(keywords, values, true); // 尝试连接到数据库 + // 检查连接的状态是否不等于 CONNECTION_OK,表示连接失败 if (PQstatus(conn) != CONNECTION_OK) { + // 增加连接尝试次数并检查是否小于最大重试次数 if (++count < retryNum) { + // 记录日志信息,表示连接数据库失败,并提供错误信息 ereport(LOG, (errmsg("Could not connect to the %s, the connection info : %s", dbname, PQerrorMessage(conn)))); - PQfinish(conn); - conn = NULL; + PQfinish(conn); // 关闭数据库连接 + conn = NULL; // 将连接指针设置为 NULL /* sleep 0.1 s */ - pg_usleep(100000L); - goto retry; + pg_usleep(100000L); // 休眠 + goto retry; // 跳转到标签,进行下一次连接尝试 } char connErrorMsg[MAX_ERRMSG_LENGTH] = {0}; errno_t rc; + // 将连接错误消息转换为字符串形式并存储下来 rc = snprintf_s(connErrorMsg, MAX_ERRMSG_LENGTH, MAX_ERRMSG_LENGTH - 1, "%s", PQerrorMessage(conn)); securec_check_ss(rc, "\0", "\0"); - PQfinish(conn); + PQfinish(conn); // 关闭数据库连接,释放相关资源 conn = NULL; + // 报告连接数据库失败的错误 ereport(ERROR, (errcode(ERRCODE_CONNECTION_TIMED_OUT), (errmsg("Could not connect to the %s, " "we have tried %d times, the connection info: %s", @@ -499,43 +602,67 @@ retry: return (conn); } +/* + * 功能:将数据库名称添加到数据库名称链表中 + * + * 参数: + * dbList-用于更新链表头 + * dbName-要添加的数据库名称 + * + * 返回值:无 + */ static void AddDatabaseInfo(DatabaseNames** dbList, char* dbName) { - DatabaseNames* tempDatabase = NULL; - + DatabaseNames* tempDatabase = NULL; // 用于临时存储要添加的数据库信息 + // 分配内存,用于存储数据库信息 tempDatabase = (DatabaseNames*)palloc(sizeof(DatabaseNames)); - tempDatabase->next = NULL; + tempDatabase->next = NULL; // 初始化 + // 保存数据库名称的副本 tempDatabase->databaseName = pstrdup(dbName); - if (*dbList == NULL) { - *dbList = tempDatabase; + if (*dbList == NULL) { // 检查传入的数据库名称列表是否为空 + *dbList = tempDatabase; // 指向了新添加的数据库信息 } else { + // 将新的数据库信息插入到列表的头部 tempDatabase->next = (*dbList)->next; (*dbList)->next = tempDatabase; } } +/* + * 功能:用于获取数据库列表 + * + * 参数: + * conn-指针,表示数据库连接 + * dbList-指针的指针,表示数据库名称链表 + * + * 返回值:无 + */ static void GetDatabaseList(PGconn* conn, DatabaseNames** dbList) { - int databaseCount; - PGresult* res = NULL; - char* dbName = NULL; + int databaseCount; // 存储从数据库中获取的数据库数量 + PGresult* res = NULL; // 存储执行 SQL 查询后的结果集 + char* dbName = NULL; // 存储从结果集中提取的数据库名称 /* SQL Statement */ + // 定义一个 SQL 查询语句,用于从系统表 PG_DATABASE 中选择数据库名称 static const char* STMT_GET_DATABASE_LIST = "SELECT DATNAME FROM PG_DATABASE;"; /* Get database list. */ + // 获取数据库名称列表 res = PQexec(conn, STMT_GET_DATABASE_LIST); + // 检查查询结果是否为 NULL 或者查询执行是否成功 if (res == NULL || PQresultStatus(res) != PGRES_TUPLES_OK) { - char resErrorMsg[MAX_ERRMSG_LENGTH] = {0}; + char resErrorMsg[MAX_ERRMSG_LENGTH] = {0}; // 用于存储查询错误消息 errno_t rc; + // 将查询错误消息复制到 resErrorMsg 中 rc = snprintf_s(resErrorMsg, MAX_ERRMSG_LENGTH, MAX_ERRMSG_LENGTH - 1, "%s", PQresultErrorMessage(res)); securec_check_ss(rc, "\0", "\0"); - PQclear(res); - PQfinish(conn); + PQclear(res); // 清除查询结果对象 res + PQfinish(conn); // 关闭数据库连接 conn = NULL; ereport(ERROR, (errcode(ERRCODE_INVALID_STATUS), errmsg("Could not obtain database list: %s", @@ -543,61 +670,93 @@ static void GetDatabaseList(PGconn* conn, DatabaseNames** dbList) return; } - databaseCount = PQntuples(res); + databaseCount = PQntuples(res); // 获取查询结果中的行数,即数据库数量 + // 循环遍历查询结果的每一行 for (int i = 0; i < databaseCount; i++) { - dbName = PQgetvalue(res, i, 0); + dbName = PQgetvalue(res, i, 0); // 获取当前行的第一个字段的值,也就是数据库名称 + // 检查数据库名称是否为 "template0" 或 "template1" if (strcmp(dbName, "template0") == 0 || strcmp(dbName, "template1") == 0) { /* Skip template0 and template1 database */ + // 如果是,表示这是系统默认的模板数据库,不需要将其添加到列表中 continue; } - AddDatabaseInfo(dbList, dbName); + AddDatabaseInfo(dbList, dbName); // 将数据库名称添加到数据库名称列表中 } - PQclear(res); + PQclear(res); //清除查询结果对象,释放资源 } +/* + * 功能:清除数据库名称链表和相关的内存 + * + * 参数: + * dbList-指针的指针,表示数据库名称链表 + * + * 返回值:无 + */ static void CleanDatabaseList(DatabaseNames** dbList) { DatabaseNames* database = *dbList; - + // 循环遍历数据库 while (database != NULL) { DatabaseNames* savedDatabase = database; - database = database->next; - pfree_ext(savedDatabase->databaseName); + database = database->next; // 迭代 + pfree_ext(savedDatabase->databaseName); // 释放内存 pfree_ext(savedDatabase); } *dbList = NULL; } +/* + * 功能:将临时模式名称添加到临时模式名称链表中 + * + * 参数: + * tempSchemaList-指针的指针,用于更新链表头 + * tempSchemaName-要添加的临时模式名称 + * + * 返回值:无 + */ static void AddTempSchemaInfo(TempSchemaInfo** tempSchemaList, char* tempSchemaName) { - TempSchemaInfo* tempSchema = NULL; - + TempSchemaInfo* tempSchema = NULL; // 用于创建新的临时模式信息节点 + // 分配内存 tempSchema = (TempSchemaInfo*)palloc(sizeof(TempSchemaInfo)); tempSchema->next = NULL; - tempSchema->tempSchemaName = pstrdup(tempSchemaName); + tempSchema->tempSchemaName = pstrdup(tempSchemaName); // 保存临时模式名称的副本 - if (*tempSchemaList == NULL) { + if (*tempSchemaList == NULL) { // 检查链表头指针 *tempSchemaList 是否为空 + // 将链表头指针 *tempSchemaList 设置为指向新创建的 tempSchema 节点 *tempSchemaList = tempSchema; } else { + // 将新的临时模式信息插入到列表的头部 tempSchema->next = (*tempSchemaList)->next; (*tempSchemaList)->next = tempSchema; } } +/* + * 功能:获取临时模式列表 + * + * 参数: + * conn-结构体指针,数据库连接 + * tempSchemaList-指向临时模式名称链表的指针 + * + * 返回值:无 + */ static void GetTempSchemaList(PGconn* conn, TempSchemaInfo** tempSchemaList) { - int tempSchemaCount; - PGresult* res = NULL; - char* nspname = NULL; + int tempSchemaCount; // 用于存储临时模式的数量 + PGresult* res = NULL; // 用于存储查询结果 + char* nspname = NULL; // 用于存储临时模式的名称 int rc; /* SQL Statement */ + // 用于存储 SQL 查询语句 char STMT_GET_TEMP_SCHEMA_LIST[NAMEDATALEN + 128] = {0}; - + // 构建 SQL 查询语句 rc = snprintf_s(STMT_GET_TEMP_SCHEMA_LIST, sizeof(STMT_GET_TEMP_SCHEMA_LIST), sizeof(STMT_GET_TEMP_SCHEMA_LIST) - 1, @@ -605,26 +764,31 @@ static void GetTempSchemaList(PGconn* conn, TempSchemaInfo** tempSchemaList) securec_check_ss_c(rc, "\0", "\0"); /* Get temp schema list. */ + // 使用给定的数据库连接 conn 执行 SQL 查询语句 res = PQexec(conn, STMT_GET_TEMP_SCHEMA_LIST); + // 检查查询是否成功以及返回结果是否包含元组数据 if (res == NULL || PQresultStatus(res) != PGRES_TUPLES_OK) { + // 如果查询失败或结果不包含元组数据 char resErrorMsg[MAX_ERRMSG_LENGTH] = {0}; errno_t rc; rc = snprintf_s(resErrorMsg, MAX_ERRMSG_LENGTH, MAX_ERRMSG_LENGTH - 1, "%s", PQresultErrorMessage(res)); securec_check_ss(rc, "\0", "\0"); - PQclear(res); - PQfinish(conn); + PQclear(res); // 清理查询结果 + PQfinish(conn); // 关闭数据库连接 conn = NULL; ereport(ERROR, (errcode(ERRCODE_INVALID_STATUS), errmsg("Could not obtain temp schema list: %s", resErrorMsg))); return; } - + // 存储查询结果 tempSchemaCount = PQntuples(res); + // 遍历查询结果 for (int i = 0; i < tempSchemaCount; i++) { - nspname = PQgetvalue(res, i, 0); + nspname = PQgetvalue(res, i, 0); // 获取第 i 行的第一个字段(临时模式名称)的值 + // 检查临时模式名称是否符合预期的格式要求 if (strchr(&nspname[7], '_') == NULL) { char resErrorMsg[MAX_ERRMSG_LENGTH] = {0}; errno_t rc; @@ -632,8 +796,8 @@ static void GetTempSchemaList(PGconn* conn, TempSchemaInfo** tempSchemaList) "%s", PQresultErrorMessage(res)); securec_check_ss(rc, "\0", "\0"); - PQclear(res); - PQfinish(conn); + PQclear(res); // 清理查询结果 + PQfinish(conn); // 关闭数据库连接 conn = NULL; ereport(ERROR, (errcode(ERRCODE_INVALID_STATUS), errmsg("Error when parse schema name: %s", @@ -641,59 +805,91 @@ static void GetTempSchemaList(PGconn* conn, TempSchemaInfo** tempSchemaList) return; } - AddTempSchemaInfo(tempSchemaList, nspname); + AddTempSchemaInfo(tempSchemaList, nspname); // 将获取到的临时模式名称添加到 TempSchemaInfo 链表中 } - PQclear(res); + PQclear(res); // 清理查询结果 } +/* + * 功能:清除临时模式名称链表和相关的内存 + * + * 参数: + * tempSchemaList-指针的指针,表示临时模式名称链表 + * + * 返回值:无 + */ static void CleanTempSchemaList(TempSchemaInfo** tempSchemaList) { TempSchemaInfo* tempSchema = *tempSchemaList; - + // 循环遍历临时模式 while (tempSchema != NULL) { TempSchemaInfo* savedTempSchema = tempSchema; - tempSchema = tempSchema->next; - pfree_ext(savedTempSchema->tempSchemaName); + tempSchema = tempSchema->next; // 迭代 + pfree_ext(savedTempSchema->tempSchemaName); // 释放内存 pfree_ext(savedTempSchema); } *tempSchemaList = NULL; } +/* + * 功能:将活跃后端信息添加到活跃后端信息链表中 + * + * 参数: + * activeBackednList-指针的指针,用于更新链表头 + * sessionID-会话ID + * tempID-临时ID + * timeLineID-时间线ID + * + * 返回值:无 + */ static void AddActiveBackendInfo(ActiveBackendInfo** activeBackednList, int64 sessionID, uint32 tempID, uint32 timeLineID) { ActiveBackendInfo* tempActiveBackend = NULL; - + // 分配内存 tempActiveBackend = (ActiveBackendInfo*)palloc(sizeof(ActiveBackendInfo)); tempActiveBackend->next = NULL; - tempActiveBackend->sessionID = sessionID; - tempActiveBackend->tempID = tempID; - tempActiveBackend->timeLineID = timeLineID; + tempActiveBackend->sessionID = sessionID; // 存储会话的唯一标识 + tempActiveBackend->tempID = tempID; // 存储临时标识符 + tempActiveBackend->timeLineID = timeLineID; // 存储时间线标识符 if (*activeBackednList == NULL) { + // 如果为空,表示列表中没有任何活动后端信息 + // 成为列表的第一个节点 *activeBackednList = tempActiveBackend; } else { + // 将新节点插入到当前列表的开头 tempActiveBackend->next = (*activeBackednList)->next; (*activeBackednList)->next = tempActiveBackend; } } +/* + * 功能:获取活跃后端信息列表 + * + * 参数: + * conn-指针,表示数据库连接 + * activeBackendList-活跃后端信息链表 + * + * 返回值:无 + */ static void GetActiveBackendList(PGconn* conn, ActiveBackendInfo** activeBackendList) { - int activeBackendCount; - PGresult* res = NULL; + int activeBackendCount; // 用于存储活动后端的数量 + PGresult* res = NULL; // 用于存储执行 SQL 查询后的结果集 int64 sessionID; uint32 tempID; uint32 timeLineID; int rc; /* SQL Statement */ + // 用于存储 SQL 查询语句 char STMT_ACTIVE_BACKEND_LIST[2 * NAMEDATALEN + 128] = {0}; - + // 构建 SQL 查询语句并存储 rc = sprintf_s(STMT_ACTIVE_BACKEND_LIST, sizeof(STMT_ACTIVE_BACKEND_LIST), "SELECT SESSIONID, TEMPID, TIMELINEID FROM PG_DATABASE D, " @@ -703,7 +899,9 @@ static void GetActiveBackendList(PGconn* conn, ActiveBackendInfo** activeBackend securec_check_ss_c(rc, "\0", "\0"); /* Get active backend list. */ + // 执行 SQL 查询,将结果存储在 res 中 res = PQexec(conn, STMT_ACTIVE_BACKEND_LIST); + // 如果 res 为空或查询结果的状态不是 PGRES_TUPLES_OK 表示查询失败 if (res == NULL || PQresultStatus(res) != PGRES_TUPLES_OK) { char resErrorMsg[MAX_ERRMSG_LENGTH] = {0}; errno_t rc; @@ -711,8 +909,8 @@ static void GetActiveBackendList(PGconn* conn, ActiveBackendInfo** activeBackend "%s", PQresultErrorMessage(res)); securec_check_ss(rc, "\0", "\0"); - PQclear(res); - PQfinish(conn); + PQclear(res); // 清理查询结果集 + PQfinish(conn); // 关闭数据库连接 conn = NULL; ereport(ERROR, (errcode(ERRCODE_INVALID_STATUS), errmsg("Could not obtain active backend list: %s", @@ -720,45 +918,66 @@ static void GetActiveBackendList(PGconn* conn, ActiveBackendInfo** activeBackend return; } - activeBackendCount = PQntuples(res); + activeBackendCount = PQntuples(res); // 获取查询结果的元组数量 + // 遍历查询结果 for (int i = 0; i < activeBackendCount; i++) { + // 获取元组中数据并做适当转换 char* result = PQgetvalue(res, i, 0); sessionID = atoll(result); result = PQgetvalue(res, i, 1); tempID = atoi(result); result = PQgetvalue(res, i, 2); timeLineID = atoi(result); - + // 将获取的活动后端信息添加到传入的 activeBackendList 列表中 AddActiveBackendInfo(activeBackendList, sessionID, tempID, timeLineID); } - PQclear(res); + PQclear(res); // 清理查询结果集 } +/* + * 功能: 清除活跃后端信息链表和相关的内存 + * + * 参数: + * activeBackendList-活跃后端信息链表 + * + * 返回值:无 + */ static void CleanActiveBackendList(ActiveBackendInfo** activeBackendList) { ActiveBackendInfo* activeBackend = *activeBackendList; + // 循环遍历活跃后端信息 while (activeBackend != NULL) { ActiveBackendInfo* savedActiveBackend = activeBackend; - activeBackend = activeBackend->next; - pfree_ext(savedActiveBackend); + activeBackend = activeBackend->next; // 迭代 + pfree_ext(savedActiveBackend); // 释放内存 } *activeBackendList = NULL; } +/* + * 功能:删除指定的临时模式 + * + * 参数: + * conn-指针,表示数据库连接 + * nspname-要删除的临时模式名称 + * + * 返回值:无 + */ static void DropTempSchema(PGconn* conn, char* nspname) { - PGresult* res = NULL; + PGresult* res = NULL; // 用于存储执行 SQL 命令后的结果集 int rc; - char toastnspName[NAMEDATALEN] = {0}; + char toastnspName[NAMEDATALEN] = {0};- // 用于存储生成的 toast 模式的名称 /* SQL Statement */ + // 用于存储 SQL 删除临时模式的语句 char STMT_DROP_TEMP_SCHEMA[2 * NAMEDATALEN + 64] = {0}; rc = snprintf_s(toastnspName, NAMEDATALEN, strlen(nspname) + 7, "pg_toast_temp_%s", nspname + 8); securec_check_ss_c(rc, "\0", "\0"); - + // 构建相关变量,用于删除指定临时模式及其关联的 toast 模式的 SQL 语句 rc = snprintf_s(STMT_DROP_TEMP_SCHEMA, sizeof(STMT_DROP_TEMP_SCHEMA), 2 * strlen(nspname) + 30, @@ -766,100 +985,123 @@ static void DropTempSchema(PGconn* conn, char* nspname) nspname, toastnspName); securec_check_ss_c(rc, "\0", "\0"); - + // 执行 SQL 删除临时模式的命令,将结果存储在 res 中 res = PQexec(conn, STMT_DROP_TEMP_SCHEMA); /* If exec is not success, give an log and go on. */ + // 检查命令是否执行成功 if (res == NULL || PQresultStatus(res) != PGRES_COMMAND_OK) { + // 如果删除操作失败 char resErrorMsg[MAX_ERRMSG_LENGTH] = {0}; errno_t rc; rc = snprintf_s(resErrorMsg, MAX_ERRMSG_LENGTH, MAX_ERRMSG_LENGTH - 1, "%s", PQresultErrorMessage(res)); securec_check_ss(rc, "\0", "\0"); - PQclear(res); - PQfinish(conn); + PQclear(res); // 清理查询结果集 + PQfinish(conn); // 关闭数据库连接 conn = NULL; ereport(ERROR, (errcode(ERRCODE_INVALID_STATUS), errmsg("Could not drop temp schema %s, %s: %s", nspname, toastnspName, resErrorMsg))); } - PQclear(res); + PQclear(res); // 清理查询结果集 } +/* + * 功能: 删除不属于活跃后端的临时模式 + * + * 参数: + * conn-指针,表示数据库连接 + * + * 返回值:无 + */ static void DropTempSchemas(PGconn* conn) { /* Store temp schema names selected from DN. */ - TempSchemaInfo* tempSchemaList = NULL; + TempSchemaInfo* tempSchemaList = NULL; // 用于存储从数据库中获取的临时模式的列表 /* Store current active backend pid to decide which temp schema should be dropped. */ - ActiveBackendInfo* activeBackendList = NULL; + ActiveBackendInfo* activeBackendList = NULL; // 用于存储当前活动的后端进程信息列表 - GetTempSchemaList(conn, &tempSchemaList); - GetActiveBackendList(conn, &activeBackendList); + GetTempSchemaList(conn, &tempSchemaList); // 从数据库中获取临时模式的列表 + GetActiveBackendList(conn, &activeBackendList); // 获取当前活动的后端进程信息列表 TempSchemaInfo* tempSchema = tempSchemaList; ActiveBackendInfo* activeBackend = NULL; char tempBuffer[NAMEDATALEN] = {0}; char tempBuffer2[NAMEDATALEN] = {0}; errno_t rc; + // 检查临时模式列表和后端进程信息列表是否为空 if (tempSchemaList == NULL || activeBackendList == NULL) return; + // 遍历临时模式列表 while (tempSchema != NULL) { /* * Get sessionID, tempID, timelineID from end to start. * Temp schema name is pg_temp_%s_%u_%u_%lu. * These items are DN'name, timelineID, tempID, sessionID. - */ + */ const char* lastPos = strrchr(tempSchema->tempSchemaName, '_'); + // 如果临时命名空间名称不符合预期格式 if (lastPos == NULL) { - PQfinish(conn); + PQfinish(conn); // 关闭数据库连接 conn = NULL; elog(ERROR, "strrchr failed, can't find '%c' in '%s'\n", '_', tempSchema->tempSchemaName); } + // 获取会话ID int64 sessionID = strtoll(lastPos + 1, NULL, 10); + // 备份 + // 将从字符串起始位置到 lastPos 之间的子字符串复制到 tempBuffer 变量 rc = strncpy_s(tempBuffer, sizeof(tempBuffer), tempSchema->tempSchemaName, lastPos - tempSchema->tempSchemaName); securec_check_c(rc, "\0", "\0"); const char* secondLastPos = strrchr(tempBuffer, '_'); + // 临时模式名称不符合预期格式 if (secondLastPos == NULL) { - PQfinish(conn); + PQfinish(conn); // 关闭数据库连接 conn = NULL; elog(ERROR, "strrchr failed, can't find '%c' in '%s'\n", '_', tempBuffer); } + // 获取临时ID uint32 tempID = strtol(secondLastPos + 1, NULL, 10); rc = strncpy_s(tempBuffer2, sizeof(tempBuffer2), tempBuffer, secondLastPos - tempBuffer); securec_check_c(rc, "\0", "\0"); const char* thirdLastPos = strrchr(tempBuffer2, '_'); + // 临时模式名称不符合预期格式 if (thirdLastPos == NULL) { - PQfinish(conn); + PQfinish(conn); // 关闭数据库连接 conn = NULL; elog(ERROR, "strrchr failed, can't find '%c' in '%s'\n", '_', tempBuffer2); } + // 获取时间线ID uint32 timeLineID = strtol(thirdLastPos + 1, NULL, 10); - activeBackend = activeBackendList; + activeBackend = activeBackendList; // 初始化指向活动后端信息列表的指针 /* * Thus, We use sessionID, timeLineID and tempID together * to judge which session a temp table belongs to, instead of sessionID only. */ + // 遍历活动后端列表 while (activeBackend != NULL) { + // 查询ID是否匹配,即临时模式是否仍然在使用中 if (sessionID == activeBackend->sessionID && tempID == activeBackend->tempID && timeLineID == activeBackend->timeLineID) break; activeBackend = activeBackend->next; } - if (activeBackend == NULL) { + if (activeBackend == NULL) { // 没有找到匹配的活动后端 /* * Now it is not an active backend, furthermore, we need to drop it; */ + // 删除该临时模式 DropTempSchema(conn, tempSchema->tempSchemaName); } - tempSchema = tempSchema->next; + tempSchema = tempSchema->next; // 迭代 } - CleanTempSchemaList(&tempSchemaList); - CleanActiveBackendList(&activeBackendList); + CleanTempSchemaList(&tempSchemaList); // 清理存储临时模式信息的链表 + CleanActiveBackendList(&activeBackendList); // 清理存储活动后端信息的链表 } /* @@ -867,28 +1109,41 @@ static void DropTempSchemas(PGconn* conn) * @in: void * @return: the result of cleaning temporary schemas */ +/* + * 功能:依次处理每个数据库中的临时模式 + * + * 参数:无 + * + * 返回值: + * bool类型 + * 如果成功执行并完成了清理工作,返回 true,表示清理操作成功; + * 否则,返回false + */ static bool DropTempNamespace() { DatabaseNames* dbList = NULL; DatabaseNames* curDatabase = NULL; PGconn* conn = NULL; + // 连接到默认数据库 conn = LoginDatabase("localhost", g_instance.attr.attr_network.PostPortNumber, NULL, NULL, DEFAULT_DATABASE, PGXC_CLEAN, "auto"); /* Get database list. */ - GetDatabaseList(conn, &dbList); + GetDatabaseList(conn, &dbList); // 获取数据库列表 - PQfinish(conn); + PQfinish(conn); // 关闭数据库连接 conn = NULL; - if (dbList != NULL) { + if (dbList != NULL) { // 如果已成功获取了数据库列表 + // 遍历每个数据库 for (curDatabase = dbList; curDatabase != NULL; curDatabase = curDatabase->next) { + // 连接到当前循环迭代的数据库 conn = LoginDatabase("localhost", g_instance.attr.attr_network.PostPortNumber, NULL, NULL, curDatabase->databaseName, PGXC_CLEAN, "auto"); - if (conn == NULL) { - PQfinish(conn); + if (conn == NULL) { // 如果连接失败 + PQfinish(conn); // 关闭数据库连接 conn = NULL; ereport(ERROR, (errcode(ERRCODE_INVALID_STATUS), (errmsg("Could not connect to the database %s.", @@ -896,16 +1151,16 @@ static bool DropTempNamespace() return false; } - DropTempSchemas(conn); + DropTempSchemas(conn); // 清理当前数据库的临时模式 ereport(DEBUG5, (errmsg("Drop temp namespace for database \"%s\" finished.", curDatabase->databaseName))); - PQfinish(conn); + PQfinish(conn); // 关闭数据库连接 conn = NULL; } } - CleanDatabaseList(&dbList); + CleanDatabaseList(&dbList); // 清理数据库列表的链表 return true; } #endif diff --git a/src/gausskernel/process/postmaster/walwriter.cpp b/src/gausskernel/process/postmaster/walwriter.cpp index f24da1b3a..f7a290f58 100755 --- a/src/gausskernel/process/postmaster/walwriter.cpp +++ b/src/gausskernel/process/postmaster/walwriter.cpp @@ -62,16 +62,18 @@ #include "utils/resowner.h" #include "gssignal/gs_signal.h" - +// 用于将毫秒和秒转换为纳秒的常量值 #define NANOSECONDS_PER_MILLISECOND 1000000L #define NANOSECONDS_PER_SECOND 1000000000L /* Signal handlers */ +// 声明信号处理函数 static void wal_quickdie(SIGNAL_ARGS); static void WalSigHupHandler(SIGNAL_ARGS); static void WalShutdownHandler(SIGNAL_ARGS); static void walwriter_sigusr1_handler(SIGNAL_ARGS); +// 表示WAL writer的睡眠超时时间 THR_LOCAL const int g_sleep_timeout_ms = 300; /* WAL writer sleep timeout in millisecond. */ /* @@ -80,20 +82,32 @@ THR_LOCAL const int g_sleep_timeout_ms = 300; /* WAL writer sleep timeout in mil * This is invoked from AuxiliaryProcessMain, which has already created the * basic execution environment, but not enabled signals yet. */ +/* + * 功能: 主要入口函数,它负责WAL写入和相关任务的处理 + * + * 参数:无 + * + * 返回值:无 + */ void WalWriterMain(void) { - sigjmp_buf local_sigjmp_buf; - MemoryContext walwriter_context; - sigset_t old_sig_mask; - bool wrote_something = true; - long times_wrote_nothing = 0; - struct timespec time_to_wait; - int sleep_times_counter = 0; - int time_out_counter = 0; + sigjmp_buf local_sigjmp_buf; // 用于实现异常跳转点 + MemoryContext walwriter_context; // 用于创建WAL writer进程的工作内存上下文 + sigset_t old_sig_mask; // 用于保存函数中的信号屏蔽状态 + bool wrote_something = true; // 用于跟踪WAL writer是否在当前循环中写入了任何内容 + long times_wrote_nothing = 0; // 用于计算WAL writer在连续循环中没有写入任何WAL记录的次数 + struct timespec time_to_wait; // 用于表示时间间隔 + int sleep_times_counter = 0; // 用于计算WAL writer执行休眠操作的次数 + int time_out_counter = 0; // 用于计算WAL writer在等待某个条件时超时的次数 - load_server_mode(); + load_server_mode(); // 加载WAL writer进程的服务器模式 + // 检查当前数据库服务器的运行模式 + // 如果服务器模式是主服务器模式或正常模式 if (t_thrd.xlog_cxt.server_mode == PRIMARY_MODE || t_thrd.xlog_cxt.server_mode == NORMAL_MODE) { + // 解释在何种情况会将 isWalWriterUp 标志设置为 true + // isWalWriterUp 用于表示WAL writer线程不仅被创建以及将WAL日志从WAL缓冲区写入到磁盘中 + // 用于告诉其他线程WAL writer正在运行并且正在刷新WAL缓冲区 /* * Different from WalWriterPID, isWalWriterUp is used to signal that * the WAL writer thread is not only created, it is also created to @@ -107,16 +121,22 @@ void WalWriterMain(void) */ g_instance.wal_cxt.isWalWriterUp = true; } - + // 生成日志消息,表明WAL writer已经启动 ereport(LOG, (errmsg("walwriter started"))); - + // 检查配置参数 walwriter_cpu_bind 是否大于等于零 + // 说明要为WAL writer指定CPU核心 if (g_instance.attr.attr_storage.walwriter_cpu_bind >= 0) { - cpu_set_t walWriterSet; - CPU_ZERO(&walWriterSet); + cpu_set_t walWriterSet; // 用于表示CPU亲和性掩码 + CPU_ZERO(&walWriterSet); // 初始化为一个空的CPU亲和性掩码 + // 将指定的CPU核心添加到 walWriterSet 中 + // 指定了WAL writer进程应该绑定到的CPU核心 + // 将WAL writer限制在这个特定的核心上运行 CPU_SET(g_instance.attr.attr_storage.walwriter_cpu_bind, &walWriterSet); - + // 将WAL writer进程绑定到CPU核心 int rc = sched_setaffinity(0, sizeof(cpu_set_t), &walWriterSet); + // 如果绑定失败 if (rc == -1) { + // 错误报告 ereport(FATAL, (errcode(ERRCODE_OPERATE_INVALID_PARAM), errmsg("Invalid attribute for thread pool."), errdetail("Current thread num %d is out of range.", g_instance.attr.attr_storage.walwriter_cpu_bind))); } @@ -129,18 +149,26 @@ void WalWriterMain(void) * * Reset some signals that are accepted by postmaster but not here. */ + // 设置信号处理程序 + // SIGHUP 重新加载配置文件 (void)gspqsignal(SIGHUP, WalSigHupHandler); /* set flag to read config file */ + // SIGINT SIGTERM 请求进程正常终止或关闭 (void)gspqsignal(SIGINT, WalShutdownHandler); /* request shutdown */ (void)gspqsignal(SIGTERM, WalShutdownHandler); /* request shutdown */ + // SIGQUIT 请求进程立即终止 (void)gspqsignal(SIGQUIT, wal_quickdie); /* hard crash time */ + // SIGALRM SIGPIPE 忽略 (void)gspqsignal(SIGALRM, SIG_IGN); (void)gspqsignal(SIGPIPE, SIG_IGN); + // SIGUSR1 用于自定义用途 (void)gspqsignal(SIGUSR1, walwriter_sigusr1_handler); + // SIGUSR2 忽略 (void)gspqsignal(SIGUSR2, SIG_IGN); /* not used */ /* * Reset some signals that are accepted by postmaster but not here. */ + // 重置默认值,以下信号在WAL writer进程中不需要特殊处理 (void)gspqsignal(SIGCHLD, SIG_DFL); (void)gspqsignal(SIGTTIN, SIG_DFL); (void)gspqsignal(SIGTTOU, SIG_DFL); @@ -148,12 +176,14 @@ void WalWriterMain(void) (void)gspqsignal(SIGWINCH, SIG_DFL); /* We allow SIGQUIT (quickdie) at all times */ + // 保证在需要的情况下能够立即终止进程 sigdelset(&t_thrd.libpq_cxt.BlockSig, SIGQUIT); /* * Create a resource owner to keep track of our resources (not clear that * we need this, but may as well have one). */ + // 创建资源管理器 t_thrd.utils_cxt.CurrentResourceOwner = ResourceOwnerCreate(NULL, "Wal Writer", THREAD_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_STORAGE)); @@ -163,11 +193,13 @@ void WalWriterMain(void) * possible memory leaks. Formerly this code just ran in * t_thrd.top_mem_cxt, but resetting that would be a really bad idea. */ + // 创建内存上下文,用于执行WAL writer进程的所有工作 walwriter_context = AllocSetContextCreate(t_thrd.top_mem_cxt, "Wal Writer", ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); + // 切换到新的内存上下文 (void)MemoryContextSwitchTo(walwriter_context); /* @@ -177,61 +209,75 @@ void WalWriterMain(void) */ int curTryCounter; int* oldTryCounter = NULL; + // 设置异常跳转点 if (sigsetjmp(local_sigjmp_buf, 1) != 0) { /* * Close all open files after any error. This is helpful on Windows, * where holding deleted files open causes various strange errors. * It's not clear we need it elsewhere, but shouldn't hurt. */ + // 关闭所有打开的文件 gstrace_tryblock_exit(true, oldTryCounter); /* We need restore the signal mask of current thread. */ + // 恢复之前保存的信号掩码,以确保信号处理恢复到之前的状态 pthread_sigmask(SIG_SETMASK, &old_sig_mask, NULL); /* Since not using PG_TRY, must reset error stack by hand */ + // 手动重置错误堆栈 t_thrd.log_cxt.error_context_stack = NULL; - t_thrd.log_cxt.call_stack = NULL; + t_thrd.log_cxt.call_stack = NULL; // 清空调用栈 /* Prevent interrupts while cleaning up */ - HOLD_INTERRUPTS(); + HOLD_INTERRUPTS(); // 在执行清理操作期间禁止中断 /* Report the error to the server log */ - EmitErrorReport(); + EmitErrorReport(); // 报告错误到服务器日志 /* abort async io, must before LWlock release */ - AbortAsyncListIO(); + AbortAsyncListIO(); // 中止异步I/O操作 /* release resource held by lsc */ - AtEOXact_SysDBCache(false); + AtEOXact_SysDBCache(false); // 释放系统数据库缓存资源 /* * These operations are really just a minimal subset of * AbortTransaction(). We don't have very many resources to worry * about in walwriter, but we do have LWLocks, and perhaps buffers? */ - LWLockReleaseAll(); - pgstat_report_waitevent(WAIT_EVENT_END); - AbortBufferIO(); - UnlockBuffers(); + LWLockReleaseAll(); // 释放锁 + pgstat_report_waitevent(WAIT_EVENT_END); // 报告等待事件结束,用于性能统计和诊断 + AbortBufferIO(); // 中止缓冲区I/O操作的函数调用 + UnlockBuffers(); // 解锁缓冲区,确保在异常情况下不会保留未解锁的缓冲区 /* buffer pins are released here: */ + // 释放资源,包括资源所有者持有的资源 ResourceOwnerRelease(t_thrd.utils_cxt.CurrentResourceOwner, RESOURCE_RELEASE_BEFORE_LOCKS, false, true); /* we needn't bother with the other ResourceOwnerRelease phases */ + // 处理事务结束的函数调用 与缓冲区相关的清理工作 AtEOXact_Buffers(false); + // 处理事务结束的函数调用 与存储管理器相关的清理工作 AtEOXact_SMgr(); + // 处理事务结束的函数调用 用于关闭与文件操作相关的资源 AtEOXact_Files(); + // 处理事务结束的函数调用 用于清理哈希表和相关数据结构 AtEOXact_HashTables(false); /* * Now return to normal top-level context and clear ErrorContext for * next time. */ + // 切换程序的内存上下文 (void)MemoryContextSwitchTo(walwriter_context); + // 清除错误状态,将之前可能记录的错误信息和状态重置为空 FlushErrorState(); /* Flush any leaked data in the top-level context */ + // 重置 walwriter_context 上下文并删除其所有子级上下文 + // 确保在异常处理完成后释放已分配的内存,以避免内存泄漏 MemoryContextResetAndDeleteChildren(walwriter_context); /* Now we can allow interrupts again */ + // 允许中断信号再次被捕获 RESUME_INTERRUPTS(); /* @@ -239,78 +285,97 @@ void WalWriterMain(void) * to be repeated, and we don't want to be filling the error logs as * fast as we can. */ - pg_usleep(1000000L); + pg_usleep(1000000L); // 休眠 } + // 记录当前的错误处理堆栈计数器 oldTryCounter = gstrace_tryblock_entry(&curTryCounter); /* We can now handle ereport(ERROR) */ + // 将错误处理的跳转缓冲区设置为当前线程的错误处理堆栈 t_thrd.log_cxt.PG_exception_stack = &local_sigjmp_buf; /* * Unblock signals (they were blocked when the postmaster forked us) */ + // 解除信号屏蔽,允许信号再次被捕获 gs_signal_setmask(&t_thrd.libpq_cxt.UnBlockSig, NULL); + // 解除对 SIGUSR2 信号的屏蔽,允许接收 SIGUSR2 信号 (void)gs_signal_unblock_sigusr2(); /* * Reset hibernation state after any error. */ + // 设置 walwriter 进程的休眠状态为不休眠 SetWalWriterSleeping(false); /* * Advertise our latch that backends can use to wake us up while we're * sleeping. */ + // 将 walwriter 进程的标识设置为当前线程的标识 + // 允许其他后台进程使用标识唤醒 walwriter 进程 g_instance.proc_base->walwriterLatch = &t_thrd.proc->procLatch; - + // 报告 walwriter 进程的应用程序名称,用于性能统计和监控 pgstat_report_appname("Wal Writer"); + // 报告 walwriter 进程的活动状态为 "STATE_IDLE",表示当前进程处于空闲状态 pgstat_report_activity(STATE_IDLE, NULL); /* * Loop forever */ for (;;) { + // 报告当前活动状态为 "STATE_RUNNING",表示进程正在执行 pgstat_report_activity(STATE_RUNNING, NULL); /* * Process any requests or signals received recently. */ + // 如果收到了 SIGHUP 信号 if (t_thrd.walwriter_cxt.got_SIGHUP) { t_thrd.walwriter_cxt.got_SIGHUP = false; - ProcessConfigFile(PGC_SIGHUP); + ProcessConfigFile(PGC_SIGHUP); // 重新读取配置文件 } - + // 如果收到了关闭请求 if (t_thrd.walwriter_cxt.shutdown_requested) { /* Normal exit from the walwriter is here */ - proc_exit(0); /* done */ + proc_exit(0); /* done */ // 正常退出 } - LWLockAcquire(WALWriteLock, LW_EXCLUSIVE); - wrote_something = XLogBackgroundFlush(); - LWLockRelease(WALWriteLock); - + LWLockAcquire(WALWriteLock, LW_EXCLUSIVE); // 获取 WAL 写锁 + wrote_something = XLogBackgroundFlush(); // 执行后台的 WAL 刷新操作 + LWLockRelease(WALWriteLock); // 释放 WAL 写锁,允许其他进程访问 WAL 日志 + // 检查是否已经连续一段时间没有写入新的 WAL 记录,并且超过了配置的阈值 if (!wrote_something && ++times_wrote_nothing > g_instance.attr.attr_storage.walwriter_sleep_threshold) { /* * Wait for the first entry after last flushed entry to be updated */ + // 获取上次刷新的 WAL 记录位置 int lastFlushedEntry = g_instance.wal_cxt.lastWalStatusEntryFlushed; + // 计算下一个要检查的 WAL 记录的位置,以查看是否已经被复制 int nextStatusEntry = GET_NEXT_STATUS_ENTRY(g_instance.attr.attr_storage.wal_insert_status_entries_power, lastFlushedEntry); volatile WalInsertStatusEntry *pCriticalEntry = &g_instance.wal_cxt.walInsertStatusTable[nextStatusEntry]; + // 如果 walwriter 进程处于活动状态,并且下一个 WAL 记录的状态为未复制 if (g_instance.wal_cxt.isWalWriterUp && pCriticalEntry->status == WAL_NOT_COPIED) { - sleep_times_counter++; + sleep_times_counter++; // 增加计数器 + // 获取临界区的互斥锁 (void)pthread_mutex_lock(&g_instance.wal_cxt.criticalEntryMutex); - g_instance.wal_cxt.isWalWriterSleeping = true; + g_instance.wal_cxt.isWalWriterSleeping = true; // 表示 walwriter 进程正在休眠 + // 循环等待下一个 WAL 记录被复制,或者等待关闭请求 while (pCriticalEntry->status == WAL_NOT_COPIED && !t_thrd.walwriter_cxt.shutdown_requested) { - (void)clock_gettime(CLOCK_MONOTONIC, &time_to_wait); + (void)clock_gettime(CLOCK_MONOTONIC, &time_to_wait); // 获取当前的时间 + // 计算等待时间 time_to_wait.tv_nsec += g_sleep_timeout_ms * NANOSECONDS_PER_MILLISECOND; + // 检查等待时间是否超过了一秒 if (time_to_wait.tv_nsec >= NANOSECONDS_PER_SECOND) { - time_to_wait.tv_nsec -= NANOSECONDS_PER_SECOND; + time_to_wait.tv_nsec -= NANOSECONDS_PER_SECOND; // 减去一秒的纳秒部分 time_to_wait.tv_sec += 1; } + // 用条件变量进行等待,如果在规定的时间内没有被唤醒,将返回 res 值 int res = pthread_cond_timedwait(&g_instance.wal_cxt.criticalEntryCV, &g_instance.wal_cxt.criticalEntryMutex, &time_to_wait); + // 如果等待成功 if (res == 0) { /* * We should not break out here because we may be notified by an @@ -318,24 +383,28 @@ void WalWriterMain(void) * entry status is WAL_NOT_COPIED. */ continue; - } else if (res == ETIMEDOUT) { - time_out_counter++; - } else { + } else if (res == ETIMEDOUT) { // 如果等待超时 + time_out_counter++; // 增加超时计数器 + } else { // 如果等待出现错误 + // 报告错误信息 ereport(WARNING, (errmsg("WAL writer pthread_cond_timedwait returned error code = %d.", errno))); } /* wakeup other producer if possible to avoid hang */ + // 唤醒其他可能正在等待的线程,以防止出现死锁 WakeupWalSemaphore(&g_instance.wal_cxt.walFlushWaitLock->l.sem); + // 唤醒其他可能正在等待的线程,以防止出现死锁 WakeupWalSemaphore(&g_instance.wal_cxt.walBufferInitWaitLock->l.sem); - CHECK_FOR_INTERRUPTS(); + CHECK_FOR_INTERRUPTS(); // 检查是否有中断请求 } g_instance.wal_cxt.isWalWriterSleeping = false; + // 释放临界区的互斥锁 (void)pthread_mutex_unlock(&g_instance.wal_cxt.criticalEntryMutex); - time_out_counter = 0; + time_out_counter = 0; // 重置超时计数器 } - times_wrote_nothing = 0; + times_wrote_nothing = 0; // 重置 跟踪连续写入WAL记录的次数 } - + // 报告活动状态 表示 walwriter 进程目前处于空闲状态 pgstat_report_activity(STATE_IDLE, NULL); } } @@ -350,13 +419,24 @@ void WalWriterMain(void) * Some backend has bought the farm, * so we need to stop what we're doing and exit. */ +/* + *功能:处理快速终止信号 + * 紧急退出操作,不会触发正常的清理和关闭步骤,因为共享内存可能已损坏 + * + * 参数: + * SIGNAL_ARGS: 信号处理程序的参数列表,提供关于触发信号的上下文信息 + * + * 返回值: + * 无 + */ static void wal_quickdie(SIGNAL_ARGS) { - gs_signal_setmask(&t_thrd.libpq_cxt.BlockSig, NULL); + gs_signal_setmask(&t_thrd.libpq_cxt.BlockSig, NULL); // 解除对信号的阻塞 - g_instance.wal_cxt.isWalWriterUp = false; - pg_memory_barrier(); + g_instance.wal_cxt.isWalWriterUp = false; // 表示 WAL Writer 进程不再处于运行状态 + pg_memory_barrier(); // 执行内存屏障操作以确保对共享内存的修改得到正确同步 /* Stop WalWriterAuxiliary from waiting. */ + // 唤醒等待在g_instance.wal_cxt.walInitSegLock->l.sem 信号量上的任何进程 WakeupWalSemaphore(&g_instance.wal_cxt.walInitSegLock->l.sem); /* @@ -367,7 +447,7 @@ static void wal_quickdie(SIGNAL_ARGS) * things by calling exit() directly, we have to reset the callbacks * explicitly to make this work as intended. */ - on_exit_reset(); + on_exit_reset(); // 重置进程退出时的回调函数 /* * Note we do exit(2) not exit(0). This is to force the postmaster into a @@ -377,47 +457,76 @@ static void wal_quickdie(SIGNAL_ARGS) * should ensure the postmaster sees this as a crash, too, but no harm in * being doubly sure.) */ - exit(2); + exit(2); // 以状态码 2 退出进程,表示进程异常退出 } /* SIGHUP: set flag to re-read config file at next convenient time */ +/* + *功能:处理 SIGHUP 信号,用于重新读取配置文件 + * + * 参数: + * SIGNAL_ARGS: 信号处理程序的参数列表,提供关于触发信号的上下文信息 + * + * 返回值: + * 无 + */ static void WalSigHupHandler(SIGNAL_ARGS) { - int save_errno = errno; + int save_errno = errno; // 保存当前的错误码 - t_thrd.walwriter_cxt.got_SIGHUP = true; + t_thrd.walwriter_cxt.got_SIGHUP = true; // 表示接收到 SIGHUP 信号 - if (t_thrd.proc) - SetLatch(&t_thrd.proc->procLatch); + if (t_thrd.proc) // 如果存在进程 + SetLatch(&t_thrd.proc->procLatch); // 设置该进程的进程latch - errno = save_errno; + errno = save_errno; // 恢复之前保存的错误码 } /* SIGTERM: set flag to exit normally */ +/* + *功能:处理 SIGTERM 信号,用于正常退出 + * + * 参数: + * SIGNAL_ARGS: 信号处理程序的参数列表,提供关于触发信号的上下文信息 + * + * 返回值: + * 无 + */ static void WalShutdownHandler(SIGNAL_ARGS) { - int save_errno = errno; + int save_errno = errno; // 保存当前的错误码 - t_thrd.walwriter_cxt.shutdown_requested = true; + t_thrd.walwriter_cxt.shutdown_requested = true; // 表示请求正常退出 - if (t_thrd.proc) - SetLatch(&t_thrd.proc->procLatch); + if (t_thrd.proc) // 如果存在进程 + SetLatch(&t_thrd.proc->procLatch); // 设置该进程的进程latch - errno = save_errno; + errno = save_errno; // 恢复之前保存的错误码 - g_instance.wal_cxt.isWalWriterUp = false; - pg_memory_barrier(); + g_instance.wal_cxt.isWalWriterUp = false; // 表示 WAL Writer 进程不再处于运行状态 + pg_memory_barrier(); // 执行内存屏障操作以确保对共享内存的修改得到正确同步 /* Stop WalWriterAuxiliary from waiting. */ + // 唤醒等待在 g_instance.wal_cxt.walInitSegLock->l.sem 信号量上的任何进程 WakeupWalSemaphore(&g_instance.wal_cxt.walInitSegLock->l.sem); } /* SIGUSR1: used for latch wakeups */ +/* + *功能: 处理 SIGUSR1 信号,用于唤醒进程中的等待操作 + * + * 参数: + * SIGNAL_ARGS: 信号处理程序的参数列表,提供关于触发信号的上下文信息 + * + * 返回值: + * 无 + */ static void walwriter_sigusr1_handler(SIGNAL_ARGS) { - int save_errno = errno; + int save_errno = errno; // 保存当前的错误码 - latch_sigusr1_handler(); - errno = save_errno; + latch_sigusr1_handler(); // 处理 SIGUSR1 信号 + + errno = save_errno; // 恢复之前保存的错误码 } diff --git a/src/gausskernel/process/postmaster/walwriterauxiliary.cpp b/src/gausskernel/process/postmaster/walwriterauxiliary.cpp index 10e7eb9b0..4cd38e3e5 100755 --- a/src/gausskernel/process/postmaster/walwriterauxiliary.cpp +++ b/src/gausskernel/process/postmaster/walwriterauxiliary.cpp @@ -54,13 +54,21 @@ static void walwriterauxiliary_sigusr1_handler(SIGNAL_ARGS); * This is invoked from AuxiliaryProcessMain, which has already created the * basic execution environment, but not enabled signals yet. */ +/* + * 功能: + * + * 参数:无 + * + * 返回值:无 + */ void WalWriterAuxiliaryMain(void) { - sigjmp_buf local_sigjmp_buf; - MemoryContext walwriterauxiliary_context; - sigset_t old_sig_mask; + sigjmp_buf local_sigjmp_buf; // 用于异常跳转点 + MemoryContext walwriterauxiliary_context; // 声明内存上下文 + sigset_t old_sig_mask; // 用于保存旧信号掩码的变量 - t_thrd.role = WALWRITERAUXILIARY; + t_thrd.role = WALWRITERAUXILIARY; // 将当前线程的角色设置为 WAL Writer 辅助进程 + // 表示 WAL Writer 辅助进程已启动 ereport(LOG, (errmsg("walwriterauxiliary started"))); /* @@ -71,6 +79,7 @@ void WalWriterAuxiliaryMain(void) * * Reset some signals that are accepted by postmaster but not here. */ + // 设置不同的信号处理函数 (void)gspqsignal(SIGHUP, WalwriterauxiliarySigHupHandler); /* set flag to read config file */ (void)gspqsignal(SIGINT, WalwriterauxiliaryShutdownHandler); /* request shutdown */ (void)gspqsignal(SIGTERM, WalwriterauxiliaryShutdownHandler); /* request shutdown */ @@ -86,12 +95,14 @@ void WalWriterAuxiliaryMain(void) (void)gspqsignal(SIGWINCH, SIG_DFL); /* We allow SIGQUIT (quickdie) at all times */ + // 移除 SIGQUIT 信号,允许在任何时候接收 SIGQUIT 信号 (void)sigdelset(&t_thrd.libpq_cxt.BlockSig, SIGQUIT); /* * Create a resource owner to keep track of our resources (not clear that * we need this, but may as well have one). */ + // 创建一个资源所有者,用于跟踪本进程的资源 t_thrd.utils_cxt.CurrentResourceOwner = ResourceOwnerCreate(NULL, "Wal Writer Auxiliary", THREAD_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_STORAGE)); @@ -101,8 +112,10 @@ void WalWriterAuxiliaryMain(void) * possible memory leaks. Formerly this code just ran in * t_thrd.top_mem_cxt, but resetting that would be a really bad idea. */ + // 为 WAL Writer 辅助进程创建一个内存上下文 walwriterauxiliary_context = AllocSetContextCreate(t_thrd.top_mem_cxt, "Wal Writer Auxiliary", ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); + // 切换上下文 (void)MemoryContextSwitchTo(walwriterauxiliary_context); /* @@ -110,55 +123,68 @@ void WalWriterAuxiliaryMain(void) * * This code is heavily based on bgwriter.c, q.v. */ + // 异常跳转点 if (sigsetjmp(local_sigjmp_buf, 1) != 0) { /* We need restore the signal mask of current thread */ + // 恢复当前线程的信号掩码为之前保存的旧信号掩码 (void)pthread_sigmask(SIG_SETMASK, &old_sig_mask, NULL); /* Since not using PG_TRY, must reset error stack by hand */ + // 手动重置错误堆栈 t_thrd.log_cxt.error_context_stack = NULL; - t_thrd.log_cxt.call_stack = NULL; + t_thrd.log_cxt.call_stack = NULL; // 清空调用栈 /* Prevent interrupts while cleaning up */ - HOLD_INTERRUPTS(); + HOLD_INTERRUPTS(); // 在执行清理操作期间禁止中断 /* Report the error to the server log */ - EmitErrorReport(); + EmitErrorReport(); // 报告错误到服务器日志 /* abort async io, must before LWlock release */ - AbortAsyncListIO(); + AbortAsyncListIO(); // 中止异步I/O操作 /* release resource held by lsc */ - AtEOXact_SysDBCache(false); + AtEOXact_SysDBCache(false); // 释放系统数据库缓存资源 /* * These operations are really just a minimal subset of * AbortTransaction(). We don't have very many resources to worry * about in walwriterauxiliary, but we do have LWLocks, and perhaps buffers? */ - LWLockReleaseAll(); - pgstat_report_waitevent(WAIT_EVENT_END); - AbortBufferIO(); - UnlockBuffers(); + LWLockReleaseAll(); // 释放锁 + pgstat_report_waitevent(WAIT_EVENT_END); // 报告等待事件结束,用于性能统计和诊断 + AbortBufferIO(); // 中止缓冲区I/O操作的函数调用 + UnlockBuffers(); // 解锁缓冲区,确保在异常情况下不会保留未解锁的缓冲区 /* buffer pins are released here: */ + // 释放资源,包括资源所有者持有的资源 ResourceOwnerRelease(t_thrd.utils_cxt.CurrentResourceOwner, RESOURCE_RELEASE_BEFORE_LOCKS, false, true); /* we needn't bother with the other ResourceOwnerRelease phases */ + // 处理事务结束的函数调用 与缓冲区相关的清理工作 AtEOXact_Buffers(false); + // 处理事务结束的函数调用 与存储管理器相关的清理工作 AtEOXact_SMgr(); + // 处理事务结束的函数调用 用于关闭与文件操作相关的资源 AtEOXact_Files(); + // 处理事务结束的函数调用 用于清理哈希表和相关数据结构 AtEOXact_HashTables(false); /* * Now return to normal top-level context and clear ErrorContext for * next time. */ + // 切换程序的内存上下文 (void)MemoryContextSwitchTo(walwriterauxiliary_context); + // 清除错误状态,将之前可能记录的错误信息和状态重置为空 FlushErrorState(); /* Flush any leaked data in the top-level context */ + // 重置 walwriter_context 上下文并删除其所有子级上下文 + // 确保在异常处理完成后释放已分配的内存,以避免内存泄漏 MemoryContextResetAndDeleteChildren(walwriterauxiliary_context); /* Now we can allow interrupts again */ + // 允许中断信号再次被捕获 RESUME_INTERRUPTS(); /* @@ -166,29 +192,34 @@ void WalWriterAuxiliaryMain(void) * to be repeated, and we don't want to be filling the error logs as * fast as we can. */ - pg_usleep(1000000L); + pg_usleep(1000000L); // 休眠 /* * Close all open files after any error. This is helpful on Windows, * where holding deleted files open causes various strange errors. * It's not clear we need it elsewhere, but shouldn't hurt. */ - smgrcloseall(); + smgrcloseall(); // 关闭所有打开的文件 } /* We can now handle ereport(ERROR) */ + // 设置异常处理栈 t_thrd.log_cxt.PG_exception_stack = &local_sigjmp_buf; /* * Unblock signals (they were blocked when the postmaster forked us) */ + // 解除信号掩码,允许接收信号 gs_signal_setmask(&t_thrd.libpq_cxt.UnBlockSig, NULL); + // 解除对 SIGUSR2 信号的阻塞,允许接收此信号 (void)gs_signal_unblock_sigusr2(); /* * Use the recovery target timeline ID during recovery */ + // v如果正在进行恢复操作 if (RecoveryInProgress()) { + // 设置当前线程的时间线ID为恢复目标的时间线ID t_thrd.xlog_cxt.ThisTimeLineID = GetRecoveryTargetTLI(); } @@ -196,50 +227,62 @@ void WalWriterAuxiliaryMain(void) * Advertise our latch that backends can use to wake us up while we're * sleeping. */ + // 将当前线程的进程Latch与 walwriterauxiliaryLatch 相关联 + // 以便其他后台进程可以使用此Latch唤醒 WalWriterAuxiliary 进程 g_instance.proc_base->walwriterauxiliaryLatch = &t_thrd.proc->procLatch; - + // 报告进程的应用程序名称为 "Wal Writer Auxiliary",用于性能统计 pgstat_report_appname("Wal Writer Auxiliary"); + // 报告进程的活动状态为 "STATE_IDLE",用于性能统计 pgstat_report_activity(STATE_IDLE, NULL); /* * Loop forever */ for (;;) { + // 获取等待超时的时间 long curTimeout = u_sess->attr.attr_storage.WalWriterDelay; - int rc = 0; + int rc = 0; // 用于存储等待结果 /* Clear any already-pending wakeups */ + // 清除任何已经挂起的Latch,以准备接收新的Latch通知 ResetLatch(&t_thrd.proc->procLatch); /* * Process any requests or signals received recently. */ + // 处理任何最近接收到的请求或信号 if (t_thrd.walwriterauxiliary_cxt.got_SIGHUP) { t_thrd.walwriterauxiliary_cxt.got_SIGHUP = false; - ProcessConfigFile(PGC_SIGHUP); + ProcessConfigFile(PGC_SIGHUP); // 重新读取配置文件 } - + // 如果接收到了 shutdown_requested 标志 if (t_thrd.walwriterauxiliary_cxt.shutdown_requested) { /* Normal exit from the walwriterauxiliary is here. */ - proc_exit(0); /* done */ + proc_exit(0); /* done */ // 正常退出 } - + // 检查 Wal Writer 进程是否正在运行 if (g_instance.wal_cxt.isWalWriterUp) { + // 等待主节点初始化Xlog文件的信号 PGSemaphoreLock(&g_instance.wal_cxt.walInitSegLock->l.sem, true); + // 执行与主节点相关的初始化操作 PreInitXlogFileForPrimary(g_instance.attr.attr_storage.wal_file_init_num); } else { + // 如果初始化的Xlog文件数量大于0且当前的运行模式为 STANDBY_MODE + // 且当前的 Postmaster 状态为 PM_RECOVERY 或 PM_HOT_STANDBY + // 且函数检查已完成恢复操作 if (g_instance.attr.attr_storage.advance_xlog_file_num > 0 && t_thrd.postmaster_cxt.HaShmData->current_mode == STANDBY_MODE && (pmState == PM_RECOVERY || pmState == PM_HOT_STANDBY) && IsRecoveryDone()) { + // 读取本地最大LSN,表示当前备节点已经恢复的进度 XLogRecPtr curMaxLsn = pg_atomic_read_u64(&g_instance.comm_cxt.predo_cxt.redoPf.local_max_lsn); - PreInitXlogFileForStandby(curMaxLsn); + PreInitXlogFileForStandby(curMaxLsn); // 执行与备节点相关的初始化操作 } } - + // 等待新的Latch通知或超时 rc = WaitLatch(&t_thrd.proc->procLatch, WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH, curTimeout); - if (rc & WL_POSTMASTER_DEATH) { - gs_thread_exit(1); + if (rc & WL_POSTMASTER_DEATH) { // 接收到 Postmaster 终止信号 + gs_thread_exit(1); // 退出线程 } } } @@ -255,9 +298,19 @@ void WalWriterAuxiliaryMain(void) * Some backend has bought the farm, * so we need to stop what we're doing and exit. */ +/* + *功能:处理快速终止信号 + * 紧急退出操作,不会触发正常的清理和关闭步骤,因为共享内存可能已损坏 + * + * 参数: + * SIGNAL_ARGS: 信号处理程序的参数列表,提供关于触发信号的上下文信息 + * + * 返回值: + * 无 + */ static void walwriterauxiliary_quickdie(SIGNAL_ARGS) { - gs_signal_setmask(&t_thrd.libpq_cxt.BlockSig, NULL); + gs_signal_setmask(&t_thrd.libpq_cxt.BlockSig, NULL); // 解除对信号的阻塞 /* * We DO NOT want to run proc_exit() callbacks -- we're here because @@ -267,7 +320,7 @@ static void walwriterauxiliary_quickdie(SIGNAL_ARGS) * things by calling exit() directly, we have to reset the callbacks * explicitly to make this work as intended. */ - on_exit_reset(); + on_exit_reset(); // 重置进程退出时的回调函数 /* * Note we do exit(2) not exit(0). This is to force the postmaster into a @@ -277,41 +330,68 @@ static void walwriterauxiliary_quickdie(SIGNAL_ARGS) * should ensure the postmaster sees this as a crash, too, but no harm in * being doubly sure.) */ - exit(2); + exit(2); // 以状态码 2 退出进程,表示进程异常退出 } /* SIGHUP: set flag to re-read config file at next convenient time */ +/* + *功能:处理 SIGHUP 信号,用于重新读取配置文件 + * + * 参数: + * SIGNAL_ARGS: 信号处理程序的参数列表,提供关于触发信号的上下文信息 + * + * 返回值: + * 无 + */ static void WalwriterauxiliarySigHupHandler(SIGNAL_ARGS) { - int save_errno = errno; + int save_errno = errno; // 保存当前的错误码 - t_thrd.walwriterauxiliary_cxt.got_SIGHUP = true; + t_thrd.walwriterauxiliary_cxt.got_SIGHUP = true; // 表示接收到 SIGHUP 信号 - if (t_thrd.proc) - SetLatch(&t_thrd.proc->procLatch); + if (t_thrd.proc) // 如果存在进程 + SetLatch(&t_thrd.proc->procLatch); // 设置该进程的进程latch - errno = save_errno; + errno = save_errno; // 恢复之前保存的错误码 } /* SIGTERM: set flag to exit normally */ +/* + *功能:处理 SIGTERM 信号,用于正常退出 + * + * 参数: + * SIGNAL_ARGS: 信号处理程序的参数列表,提供关于触发信号的上下文信息 + * + * 返回值: + * 无 + */ static void WalwriterauxiliaryShutdownHandler(SIGNAL_ARGS) { - int save_errno = errno; + int save_errno = errno; // 保存当前的错误码 - t_thrd.walwriterauxiliary_cxt.shutdown_requested = true; + t_thrd.walwriterauxiliary_cxt.shutdown_requested = true; // 表示请求正常退出 - if (t_thrd.proc) - SetLatch(&t_thrd.proc->procLatch); + if (t_thrd.proc) // 如果存在进程 + SetLatch(&t_thrd.proc->procLatch); // 设置该进程的进程latch - errno = save_errno; + errno = save_errno; // 恢复之前保存的错误码 } /* SIGUSR1: used for latch wakeups */ +/* + *功能: 处理 SIGUSR1 信号,用于唤醒进程中的等待操作 + * + * 参数: + * SIGNAL_ARGS: 信号处理程序的参数列表,提供关于触发信号的上下文信息 + * + * 返回值: + * 无 + */ static void walwriterauxiliary_sigusr1_handler(SIGNAL_ARGS) { - int save_errno = errno; + int save_errno = errno; // 保存当前的错误码 - latch_sigusr1_handler(); + latch_sigusr1_handler(); // 处理 SIGUSR1 信号 - errno = save_errno; + errno = save_errno; // 恢复之前保存的错误码 } diff --git a/src/gausskernel/process/postmaster/新建文本文档.bat b/src/gausskernel/process/postmaster/新建文本文档.bat new file mode 100644 index 000000000..d055abd11 --- /dev/null +++ b/src/gausskernel/process/postmaster/新建文本文档.bat @@ -0,0 +1 @@ +DIR *.* /B >LIST.TXT \ No newline at end of file diff --git a/src/gausskernel/process/tcop/LIST.TXT b/src/gausskernel/process/tcop/LIST.TXT new file mode 100644 index 000000000..0b534b4be --- /dev/null +++ b/src/gausskernel/process/tcop/LIST.TXT @@ -0,0 +1,12 @@ +auditfuncs.cpp +autonomoustransaction.cpp +CMakeLists.txt +dest.cpp +fastpath.cpp +LIST.TXT +Makefile +postgres.cpp +pquery.cpp +stmt_retry.cpp +utility.cpp +½ıĵ.bat diff --git a/src/gausskernel/process/tcop/新建文本文档.bat b/src/gausskernel/process/tcop/新建文本文档.bat new file mode 100644 index 000000000..d055abd11 --- /dev/null +++ b/src/gausskernel/process/tcop/新建文本文档.bat @@ -0,0 +1 @@ +DIR *.* /B >LIST.TXT \ No newline at end of file diff --git a/src/include/postmaster/autovacuum.h b/src/include/postmaster/autovacuum.h index ff967d4be..a5e73e0a6 100644 --- a/src/include/postmaster/autovacuum.h +++ b/src/include/postmaster/autovacuum.h @@ -118,28 +118,28 @@ typedef struct av_toastid_mainid { /* struct to keep track of tables to vacuum and/or analyze, in 1st pass */ typedef struct av_relation { Oid ar_relid; /* hash key - must be first */ - bool ar_hasrelopts; + bool ar_hasrelopts; // 表是否有关联的选项 AutoVacOpts ar_reloptions; /* copy of AutoVacOpts from the main table's * reloptions, or NULL if none */ } av_relation; /* struct to keep track of tables to vacuum and/or analyze, after rechecking */ typedef struct autovac_table { - Oid at_relid; - int at_flags; - bool at_dovacuum; - bool at_doanalyze; - bool at_needfreeze; - bool at_sharedrel; - int64 at_freeze_min_age; - int64 at_freeze_table_age; - int at_vacuum_cost_delay; - int at_vacuum_cost_limit; - char* at_partname; - char* at_relname; - char* at_nspname; - char* at_datname; - bool at_is_toast; + Oid at_relid; // 表的对象标识符(OID) + int at_flags; // 表状态标志位 + bool at_dovacuum; // 是否执行自动清理 + bool at_doanalyze; // 是否执行自动分析 + bool at_needfreeze; // 是否需要执行冻结操作 + bool at_sharedrel; // 表是否是共享关系 + int64 at_freeze_min_age; // 最小冻结年龄 + int64 at_freeze_table_age; // 表冻结年龄 + int at_vacuum_cost_delay; // 自动清理延迟成本 + int at_vacuum_cost_limit; // 自动清理延迟成本限制 + char* at_partname; // 分区表名 + char* at_relname; // 表名 + char* at_nspname; // 命名空间名 + char* at_datname; // 数据库名 + bool at_is_toast; // 表是否是 TOAST 表 } autovac_table; /* partitioned table's autovac state */ @@ -171,17 +171,17 @@ typedef struct at_partitioned_table { * ------------- */ typedef struct WorkerInfoData { - SHM_QUEUE wi_links; - Oid wi_dboid; - Oid wi_tableoid; - Oid wi_parentoid; - bool wi_ispartition; - bool wi_sharedrel; - PGPROC* wi_proc; - TimestampTz wi_launchtime; - int wi_cost_delay; - int wi_cost_limit; - int wi_cost_limit_base; + SHM_QUEUE wi_links; // 用于将工作进程链接到队列中 + Oid wi_dboid; // 数据库的标识符 + Oid wi_tableoid; // 表的标识符 + Oid wi_parentoid; // 父表的标识符 + bool wi_ispartition; // 标识此工作进程是否处理分区表 + bool wi_sharedrel; // 标识关联的表是否是共享的 + PGPROC* wi_proc; // 指向工作进程关联的 PGPROC 结构的指针 + TimestampTz wi_launchtime; // 工作进程启动时间的时间戳 + int wi_cost_delay; // 自动清理延迟成本 + int wi_cost_limit; // 自动清理延迟限制成本 + int wi_cost_limit_base; // 基础限制成本 } WorkerInfoData; typedef struct WorkerInfoData* WorkerInfo; @@ -213,11 +213,12 @@ typedef enum { * ------------- */ typedef struct AutoVacuumShmemStruct { - sig_atomic_t av_signal[AutoVacNumSignals]; - ThreadId av_launcherpid; - WorkerInfo av_freeWorkers; - SHM_QUEUE av_runningWorkers; - WorkerInfo av_startingWorker; + sig_atomic_t av_signal[AutoVacNumSignals]; // 用于存储与自动清理过程相关的信号或标志 + ThreadId av_launcherpid; // 自动清理启动器进程的进程ID + WorkerInfo av_freeWorkers; // 存储空闲工作进程的信息 + SHM_QUEUE av_runningWorkers; // 存储正在运行的工作进程队列 + WorkerInfo av_startingWorker; // 正在启动或初始化的工作进程的信息 + } AutoVacuumShmemStruct; #endif /* AUTOVACUUM_H */ diff --git a/src/include/postmaster/bgworker.h b/src/include/postmaster/bgworker.h index fa683da54..47081433a 100644 --- a/src/include/postmaster/bgworker.h +++ b/src/include/postmaster/bgworker.h @@ -43,45 +43,50 @@ struct BgWorkerContext; typedef void (*bgworker_main)(const BgWorkerContext *bwc); typedef void (*bgworker_exit)(const BgWorkerContext *bwc); +// 用于存储后台工作进程的上下文信息 typedef struct BgWorkerContext { - StreamTxnContext transactionCxt; - void *bgshared; - PGPROC *leader; - char *databaseName; - char *userName; - bool enable_cluster_resize; - bgworker_main main_entry; - bgworker_exit exit_entry; + StreamTxnContext transactionCxt; // 事务上下文,用于后台工作进程的事务管理 + void *bgshared; // 后台工作进程共享内存的指针 + PGPROC *leader; // 领导进程的 PGPROC 指针 + char *databaseName; // 数据库名 + char *userName; // 用户名 + bool enable_cluster_resize; // 是否启用集群调整大小 + bgworker_main main_entry; // 后台工作进程的主函数入口 + bgworker_exit exit_entry; // 后台工作进程的退出函数入口 } BgWorkerContext; +// 用于存储后台工作进程的错误信息 typedef struct BgWorkerErrorData { - int elevel; - int sqlerrcode; - char message[BGWORKER_MAX_ERROR_LEN]; - char detail[BGWORKER_MAX_ERROR_LEN]; + int elevel; // 错误的级别,通常与日志消息的严重程度相关 + int sqlerrcode; // 错误的 SQL 错误码,用于标识不同类型的错误 + char message[BGWORKER_MAX_ERROR_LEN]; // 存储错误的主要信息,以字符串的形式表示 + char detail[BGWORKER_MAX_ERROR_LEN]; // 存储错误的详细信息,以字符串的形式表示 } BgWorkerErrorData; +// 表示后台工作进程的信息和状态 typedef struct BackgroundWorker { - SHM_QUEUE links; /* list link if process is in a list */ - uint64 bgw_id; - ThreadId bgw_notify_pid; /* SIGUSR1 this backend on start/stop */ - BgwHandleStatus bgw_status; /* Status of this bgworker */ - uint64 bgw_status_dur; /* duration in this status */ - BgWorkerErrorData bgw_edata; /* error information of a bgworker */ - pg_atomic_uint32 disable_count; /* indicate whether the bgworker is disabled */ - slist_node rw_lnode; /* list link */ + SHM_QUEUE links; // 在进程列表中的链接/* list link if process is in a list */ + uint64 bgw_id; // 后台工作进程的唯一标识符 + ThreadId bgw_notify_pid; // 后台工作进程启动/停止时向该进程发送 SIGUSR1 信号的进程 ID/* SIGUSR1 this backend on start/stop */ + BgwHandleStatus bgw_status; // 后台工作进程的状态,指示其是否正在运行、已失败或已终止等 /* Status of this bgworker */ + uint64 bgw_status_dur; // 后台工作进程在当前状态下持续的时间 /* duration in this status */ + BgWorkerErrorData bgw_edata; // 记录后台工作进程错误的数据,包括错误级别和 SQL 错误码 /* error information of a bgworker */ + pg_atomic_uint32 disable_count; // 指示后台工作进程是否已被禁用的计数器/* indicate whether the bgworker is disabled */ + slist_node rw_lnode; // 用于将后台工作进程添加到列表中/* list link */ } BackgroundWorker; +// 用于管理后台工作进程的相关信息 typedef struct BGW_HDR { - pg_atomic_uint64 bgw_id_seq; - BackgroundWorker* bgws; - BackgroundWorker* free_bgws; + pg_atomic_uint64 bgw_id_seq; // 后台工作进程 ID 序列 + BackgroundWorker* bgws; // 后台工作进程数组 + BackgroundWorker* free_bgws; // 空闲后台工作进程链表 } BGW_HDR; +// 用于传递后台工作进程相关的参数和信息 typedef struct BackgroundWorkerArgs { - BgWorkerContext *bgwcontext; - BackgroundWorker *bgworker; - uint64 bgworkerId; + BgWorkerContext *bgwcontext; // 指向后台工作进程上下文的指针 + BackgroundWorker *bgworker; // 指向 BackgroundWorker 结构体的指针 + uint64 bgworkerId; // 后台工作进程的唯一标识符 } BackgroundWorkerArgs; /* Register a new bgworker during shared_preload_libraries */ diff --git a/src/include/utils/rel.h b/src/include/utils/rel.h index 6006fbdaf..1605c9edf 100644 --- a/src/include/utils/rel.h +++ b/src/include/utils/rel.h @@ -306,16 +306,16 @@ typedef struct RelationData { */ /* autovacuum-related reloptions. */ typedef struct AutoVacOpts { - bool enabled; - int vacuum_threshold; - int analyze_threshold; - int vacuum_cost_delay; - int vacuum_cost_limit; - int64 freeze_min_age; - int64 freeze_max_age; - int64 freeze_table_age; - float8 vacuum_scale_factor; - float8 analyze_scale_factor; + bool enabled; // 是否启用自动清理 + int vacuum_threshold; // 触发自动清理的阈值 + int analyze_threshold; // 触发自动分析的阈值 + int vacuum_cost_delay; // 自动清理的延迟成本 + int vacuum_cost_limit; // 自动清理的成本限制 + int64 freeze_min_age; // 最小冻结年龄 + int64 freeze_max_age; // 最大冻结年龄 + int64 freeze_table_age; // 整个表冻结年龄 + float8 vacuum_scale_factor; // 自动清理操作比例因子,用于计算清理的行数 + float8 analyze_scale_factor; // 自动分析比例因子 } AutoVacOpts; typedef enum RedisCtidType { REDIS_START_CTID = 0, REDIS_END_CTID } RedisCtidType; -- 2.34.1 From b57f14fe8a7da539a57c5d156ba1394bc15e1da3 Mon Sep 17 00:00:00 2001 From: noah <1204149038@qq.com> Date: Sat, 30 Sep 2023 13:48:19 +0800 Subject: [PATCH 26/50] about process storage upgrade --- src/common/backend/utils/init/miscinit.cpp | 397 ++--- .../globalplancache/globalplancache.cpp | 637 ++++--- .../globalplancache/globalplancache_inval.cpp | 77 +- .../globalplancache/globalplancache_util.cpp | 670 +++++-- .../process/job/gs_job_calendar.cpp | 199 ++- src/gausskernel/process/main/main.cpp | 148 +- src/gausskernel/storage/.vscode/settings.json | 5 + .../storage/access/archive/archive_am.cpp | 80 +- .../storage/access/archive/nas_am.cpp | 110 +- src/gausskernel/storage/buffer/buf_init.cpp | 29 +- src/gausskernel/storage/buffer/buf_table.cpp | 7 + src/gausskernel/storage/buffer/freelist.cpp | 157 +- src/gausskernel/storage/buffer/localbuf.cpp | 130 +- .../storage/bulkload/foreignroutine.cpp | 1525 +++++++++++----- .../storage/bulkload/importerror.cpp | 592 ++++++- src/gausskernel/storage/cmgr/cache_mgr.cpp | 44 +- .../storage/dfs/obs/obs_connector.cpp | 215 ++- .../storage/dorado_operation/dorado_fd.cpp | 134 +- .../storage/page/checksum_impl.cpp | 88 +- src/gausskernel/storage/page/gs_xlogdump.cpp | 427 +++-- src/gausskernel/storage/page/pagecompress.cpp | 624 ++++--- src/gausskernel/storage/page/pageparse.cpp | 746 ++++++-- .../storage/remote/remote_adapter.cpp | 356 +++- .../storage/remote/remote_read.cpp | 133 +- .../storage/smgr/knl_uundofile.cpp | 538 ++++-- src/gausskernel/storage/smgr/md.cpp | 883 ++++++---- src/gausskernel/storage/smgr/mmap_shared.cpp | 70 +- .../storage/smgr/page_compression.cpp | 193 +- .../storage/smgr/segment/data_file.cpp | 419 +++-- .../storage/smgr/segment/extent_group.cpp | 354 +++- .../storage/smgr/segment/inverse_ptr.cpp | 74 +- .../storage/smgr/segment/segbuffer.cpp | 498 +++++- .../storage/smgr/segment/segxlog.cpp | 574 ++++-- src/gausskernel/storage/smgr/segstore.cpp | 741 ++++++-- src/gausskernel/storage/smgr/smgr.cpp | 122 +- src/gausskernel/storage/sync/knl_usync.cpp | 142 +- src/gausskernel/storage/tcap/tcap_drop.cpp | 1518 ++++++++++------ src/gausskernel/storage/tcap/tcap_manager.cpp | 1568 +++++++++++------ .../storage/tcap/tcap_truncate.cpp | 166 +- src/gausskernel/storage/tcap/tcap_version.cpp | 469 +++-- .../xlog_share_storage/xlog_share_storage.cpp | 315 ++-- src/include/storage/smgr/segment_internal.h | 151 +- 42 files changed, 11514 insertions(+), 4811 deletions(-) create mode 100644 src/gausskernel/storage/.vscode/settings.json diff --git a/src/common/backend/utils/init/miscinit.cpp b/src/common/backend/utils/init/miscinit.cpp index 8664009a8..843a952fd 100644 --- a/src/common/backend/utils/init/miscinit.cpp +++ b/src/common/backend/utils/init/miscinit.cpp @@ -65,7 +65,7 @@ #ifdef ENABLE_MULTIPLE_NODES #include "tsdb/compaction/compaction_entry.h" -#endif /* ENABLE_MULTIPLE_NODES */ +#endif /* ENABLE_MULTIPLE_NODES */ #include "access/ustore/knl_undoworker.h" #define DIRECTORY_LOCK_FILE "postmaster.pid" @@ -84,47 +84,31 @@ Alarm alarmItemTooManyDbUserConn[1] = {ALM_AI_Unknown, ALM_AS_Normal, 0, 0, 0, 0 * ---------------------------------------------------------------- */ -void ReportAlarmTooManyDbUserConn(const char* roleName) +void ReportAlarmTooManyDbUserConn(const char *roleName) { AlarmAdditionalParam tempAdditionalParam; // Initialize the alarm item - AlarmItemInitialize(alarmItemTooManyDbUserConn, - ALM_AI_TooManyDbUserConn, - alarmItemTooManyDbUserConn->stat, - NULL, - alarmItemTooManyDbUserConn->lastReportTime, - alarmItemTooManyDbUserConn->reportCount); + AlarmItemInitialize(alarmItemTooManyDbUserConn, ALM_AI_TooManyDbUserConn, alarmItemTooManyDbUserConn->stat, NULL, + alarmItemTooManyDbUserConn->lastReportTime, alarmItemTooManyDbUserConn->reportCount); // fill the alarm message - WriteAlarmAdditionalInfo(&tempAdditionalParam, - g_instance.attr.attr_common.PGXCNodeName, - "AllDatabases", - const_cast(roleName), - alarmItemTooManyDbUserConn, - ALM_AT_Fault, - const_cast(roleName)); + WriteAlarmAdditionalInfo(&tempAdditionalParam, g_instance.attr.attr_common.PGXCNodeName, "AllDatabases", + const_cast(roleName), alarmItemTooManyDbUserConn, ALM_AT_Fault, + const_cast(roleName)); // report the alarm AlarmReporter(alarmItemTooManyDbUserConn, ALM_AT_Fault, &tempAdditionalParam); } -void ReportResumeTooManyDbUserConn(const char* roleName) +void ReportResumeTooManyDbUserConn(const char *roleName) { AlarmAdditionalParam tempAdditionalParam; // Initialize the alarm item - AlarmItemInitialize(alarmItemTooManyDbUserConn, - ALM_AI_TooManyDbUserConn, - alarmItemTooManyDbUserConn->stat, - NULL, - alarmItemTooManyDbUserConn->lastReportTime, - alarmItemTooManyDbUserConn->reportCount); + AlarmItemInitialize(alarmItemTooManyDbUserConn, ALM_AI_TooManyDbUserConn, alarmItemTooManyDbUserConn->stat, NULL, + alarmItemTooManyDbUserConn->lastReportTime, alarmItemTooManyDbUserConn->reportCount); // fill the resume message - WriteAlarmAdditionalInfo(&tempAdditionalParam, - g_instance.attr.attr_common.PGXCNodeName, - "AllDatabases", - const_cast(roleName), - alarmItemTooManyDbUserConn, - ALM_AT_Resume); + WriteAlarmAdditionalInfo(&tempAdditionalParam, g_instance.attr.attr_common.PGXCNodeName, "AllDatabases", + const_cast(roleName), alarmItemTooManyDbUserConn, ALM_AT_Resume); // report the alarm AlarmReporter(alarmItemTooManyDbUserConn, ALM_AT_Resume, &tempAdditionalParam); } @@ -137,13 +121,8 @@ void ReportAlarmDataInstLockFileExist() // Initialize the alarm item AlarmItemInitialize(alarmItem, ALM_AI_DataInstLockFileExist, ALM_AS_Reported, NULL); // fill the alarm message - WriteAlarmAdditionalInfo(&tempAdditionalParam, - g_instance.attr.attr_common.PGXCNodeName, - "", - "", - alarmItem, - ALM_AT_Fault, - g_instance.attr.attr_common.PGXCNodeName); + WriteAlarmAdditionalInfo(&tempAdditionalParam, g_instance.attr.attr_common.PGXCNodeName, "", "", alarmItem, + ALM_AT_Fault, g_instance.attr.attr_common.PGXCNodeName); // report the alarm AlarmReporter(alarmItem, ALM_AT_Fault, &tempAdditionalParam); } @@ -156,8 +135,8 @@ void ReportResumeDataInstLockFileExist() // Initialize the alarm item AlarmItemInitialize(alarmItem, ALM_AI_DataInstLockFileExist, ALM_AS_Normal, NULL); // fill the alarm message - WriteAlarmAdditionalInfo( - &tempAdditionalParam, g_instance.attr.attr_common.PGXCNodeName, "", "", alarmItem, ALM_AT_Resume); + WriteAlarmAdditionalInfo(&tempAdditionalParam, g_instance.attr.attr_common.PGXCNodeName, "", "", alarmItem, + ALM_AT_Resume); // report the alarm AlarmReporter(alarmItem, ALM_AT_Resume, &tempAdditionalParam); } @@ -167,29 +146,28 @@ void ReportResumeDataInstLockFileExist() * ---------------------------------------------------------------- */ -void SetDatabasePath(const char* path) +void SetDatabasePath(const char *path) { /* This should happen only once per process */ Assert(!u_sess->proc_cxt.DatabasePath); - u_sess->proc_cxt.DatabasePath = - MemoryContextStrdup(SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_EXECUTOR), path); + u_sess->proc_cxt.DatabasePath = MemoryContextStrdup(SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_EXECUTOR), path); } /* * Set data directory, but make sure it's an absolute path. Use this, * never set t_thrd.proc_cxt.DataDir directly. */ -void SetDataDir(const char* dir) +void SetDataDir(const char *dir) { AssertArg(dir); /* If presented path is relative, convert to absolute */ - char* newm = make_absolute_path(dir); + char *newm = make_absolute_path(dir); char real_newm[PATH_MAX + 1] = {'\0'}; - char* DataDir = (char*)MemoryContextAlloc(SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_EXECUTOR), MAXPGPATH); + char *DataDir = (char *)MemoryContextAlloc(SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_EXECUTOR), MAXPGPATH); if (realpath(newm, real_newm) == NULL) { - ereport(ERROR, (errcode(ERRCODE_FILE_READ_FAILED),errmsg("invalid path:%s", dir))); + ereport(ERROR, (errcode(ERRCODE_FILE_READ_FAILED), errmsg("invalid path:%s", dir))); } errno_t rc = strncpy_s(DataDir, MAXPGPATH, real_newm, MAXPGPATH - 1); securec_check(rc, "\0", "\0"); @@ -217,8 +195,8 @@ void ChangeToDataDir(void) AssertState(t_thrd.proc_cxt.DataDir); if (chdir(t_thrd.proc_cxt.DataDir) < 0) - ereport(FATAL, - (errcode_for_file_access(), errmsg("could not change directory to \"%s\": %m", t_thrd.proc_cxt.DataDir))); + ereport(FATAL, (errcode_for_file_access(), + errmsg("could not change directory to \"%s\": %m", t_thrd.proc_cxt.DataDir))); } /* @@ -231,9 +209,9 @@ void ChangeToDataDir(void) * should happen before doing ChangeToDataDir(), else the user will probably * not like the results. */ -char* make_absolute_path(const char* path) +char *make_absolute_path(const char *path) { - char* newm = NULL; + char *newm = NULL; size_t tmplen; /* Returning null for null input is convenient for some callers */ @@ -242,16 +220,16 @@ char* make_absolute_path(const char* path) } if (!is_absolute_path(path)) { - char* buf = NULL; + char *buf = NULL; size_t buflen; buflen = MAXPGPATH; for (;;) { #ifdef FRONTEND - buf = (char*)malloc(buflen); + buf = (char *)malloc(buflen); #else - buf = (char*)MemoryContextAlloc(SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_EXECUTOR), buflen); + buf = (char *)MemoryContextAlloc(SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_EXECUTOR), buflen); #endif if (buf == NULL) @@ -259,8 +237,7 @@ char* make_absolute_path(const char* path) if (getcwd(buf, buflen) != NULL) { break; - } - else if (errno == ERANGE) { + } else if (errno == ERANGE) { #ifdef FRONTEND free(buf); #else @@ -280,9 +257,9 @@ char* make_absolute_path(const char* path) tmplen = strlen(buf) + strlen(path) + 2; #ifdef FRONTEND - newm = (char*)malloc(tmplen); + newm = (char *)malloc(tmplen); #else - newm = (char*)MemoryContextAlloc(SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_EXECUTOR), tmplen); + newm = (char *)MemoryContextAlloc(SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_EXECUTOR), tmplen); #endif if (newm == NULL) @@ -321,12 +298,16 @@ Oid GetAuthenticatedUserId(void) * * Note: there's no SetUserId() anymore; use SetUserIdAndSecContext(). */ +/* + *功能: 这个函数用于获取当前会话的用户标识符(Oid),以便在数据库操作中标识当前用户的身份。 + */ Oid GetUserId(void) { + // 检查当前用户标识符是否有效 if (!OidIsValid(u_sess->misc_cxt.CurrentUserId)) { - ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), - errmsg("Current user id is invalid. Please try later."))); + ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("Current user id is invalid. Please try later."))); } + // 返回当前用户标识符 return u_sess->misc_cxt.CurrentUserId; } @@ -438,7 +419,7 @@ bool exist_logic_cluster() * show_nodegroup_mode - return node group mode as sting. * The function is only used in guc.cpp. */ -const char* show_nodegroup_mode(void) +const char *show_nodegroup_mode(void) { modify_nodegroup_mode(); @@ -484,7 +465,7 @@ const int GetCustomParserId() * get_current_lcgroup_name - get current logic group name. * The function return NULL in datanode because datanode don't see pgxc_group. */ -const char* get_current_lcgroup_name() +const char *get_current_lcgroup_name() { if (IS_PGXC_COORDINATOR && u_sess->attr.attr_common.current_logic_cluster_name == NULL && OidIsValid(u_sess->misc_cxt.current_logic_cluster) && t_thrd.proc_cxt.postgres_initialized) { @@ -493,8 +474,8 @@ const char* get_current_lcgroup_name() if (HeapTupleIsValid(groupTup)) { rform = (Form_pgxc_group)GETSTRUCT(groupTup); - u_sess->attr.attr_common.current_logic_cluster_name = MemoryContextStrdup( - SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_EXECUTOR), NameStr(rform->group_name)); + u_sess->attr.attr_common.current_logic_cluster_name = + MemoryContextStrdup(SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_EXECUTOR), NameStr(rform->group_name)); ReleaseSysCache(groupTup); } } @@ -521,9 +502,9 @@ static void set_current_lcgroup_oid(Oid group_oid) * show_show_lcgroup_name - show current logic group name. * The function is only used in guc.cpp. */ -const char* show_lcgroup_name() +const char *show_lcgroup_name() { - const char* name = get_current_lcgroup_name(); + const char *name = get_current_lcgroup_name(); return (name == NULL) ? "" : name; } @@ -614,7 +595,7 @@ Oid get_pgxc_logic_groupoid(Oid roleid) * Obtain PGXC Logic Group Oid for rolename * Return Invalid Oid if group does not exist */ -Oid get_pgxc_logic_groupoid(const char* rolename) +Oid get_pgxc_logic_groupoid(const char *rolename) { bool isNull = false; Datum aclDatum; @@ -693,7 +674,7 @@ static void RegisterNodeGroupCacheCallback() * and perhaps restored is indeed invalid. We have to be able to get * through AbortTransaction without asserting in case InitPostgres fails. */ -void GetUserIdAndSecContext(Oid* userid, int* sec_context) +void GetUserIdAndSecContext(Oid *userid, int *sec_context) { *userid = u_sess->misc_cxt.CurrentUserId; *sec_context = u_sess->misc_cxt.SecurityRestrictionContext; @@ -727,7 +708,7 @@ bool InSecurityRestrictedOperation(void) * pljava. We allow the userid to be set, but only when not inside a * security restriction context. */ -void GetUserIdAndContext(Oid* userid, bool* sec_def_context) +void GetUserIdAndContext(Oid *userid, bool *sec_def_context) { *userid = u_sess->misc_cxt.CurrentUserId; *sec_def_context = InLocalUserIdChange(); @@ -737,9 +718,8 @@ void SetUserIdAndContext(Oid userid, bool sec_def_context) { /* We throw the same error SET ROLE would. */ if (InSecurityRestrictedOperation()) - ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("cannot set parameter \"%s\" within security-restricted operation", "role"))); + ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("cannot set parameter \"%s\" within security-restricted operation", "role"))); u_sess->misc_cxt.CurrentUserId = userid; @@ -800,7 +780,7 @@ static void DecreaseUserCountReuse(Oid roleid, bool ispoolerreuse) /* * Initialize user identity during normal backend startup */ -void InitializeSessionUserId(const char* rolename, bool ispoolerreuse, Oid useroid) +void InitializeSessionUserId(const char *rolename, bool ispoolerreuse, Oid useroid) { HeapTuple roleTup; Form_pg_authid rform; @@ -813,8 +793,7 @@ void InitializeSessionUserId(const char* rolename, bool ispoolerreuse, Oid usero * exist yet, and they should be owned by openGauss anyway. */ if (IsBootstrapProcessingMode()) { - ereport( - ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("IsBootstrapProcessingMode"))); + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("IsBootstrapProcessingMode"))); } /* In pooler stateless reuse mode, to reset session userid */ @@ -824,10 +803,10 @@ void InitializeSessionUserId(const char* rolename, bool ispoolerreuse, Oid usero if (!isUserOidInvalid) { AssertState(false); ereport(FATAL, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("Abnormal process. UserOid has been reseted. Current userOid[%u], reset username is %s," - "useroid is %u", - u_sess->misc_cxt.AuthenticatedUserId, rolename, useroid))); + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("Abnormal process. UserOid has been reseted. Current userOid[%u], reset username is %s," + "useroid is %u", + u_sess->misc_cxt.AuthenticatedUserId, rolename, useroid))); } } @@ -850,7 +829,7 @@ void InitializeSessionUserId(const char* rolename, bool ispoolerreuse, Oid usero oldcontext = MemoryContextSwitchTo(SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_EXECUTOR)); if (u_sess->proc_cxt.MyProcPort->user_name) pfree_ext(u_sess->proc_cxt.MyProcPort->user_name); - u_sess->proc_cxt.MyProcPort->user_name = pstrdup((char*)GetSuperUserName((char*)userName)); + u_sess->proc_cxt.MyProcPort->user_name = pstrdup((char *)GetSuperUserName((char *)userName)); (void)MemoryContextSwitchTo(oldcontext); rolename = u_sess->proc_cxt.MyProcPort->user_name; } @@ -863,23 +842,20 @@ void InitializeSessionUserId(const char* rolename, bool ispoolerreuse, Oid usero securec_check_ss(rc, "", ""); rolename = roleIdStr; } - /* - * Audit user login - * it's unsafe to deal with plugins hooks as dynamic lib may be released + /* + * Audit user login + * it's unsafe to deal with plugins hooks as dynamic lib may be released */ if (!(g_instance.status > NoShutdown) && user_login_hook) { user_login_hook(u_sess->proc_cxt.MyProcPort->database_name, rolename, false, true); } - int rcs = snprintf_truncated_s(details, - sizeof(details), - "login db(%s) failed-the role(%s)does not exist", - u_sess->proc_cxt.MyProcPort->database_name, - rolename); + int rcs = snprintf_truncated_s(details, sizeof(details), "login db(%s) failed-the role(%s)does not exist", + u_sess->proc_cxt.MyProcPort->database_name, rolename); securec_check_ss(rcs, "\0", "\0"); pgaudit_user_login(FALSE, u_sess->proc_cxt.MyProcPort->database_name, details); - ereport(FATAL, - (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), errmsg("Invalid username/password,login denied."))); + ereport(FATAL, (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), + errmsg("Invalid username/password,login denied."))); } rform = (Form_pg_authid)GETSTRUCT(roleTup); @@ -925,9 +901,8 @@ void InitializeSessionUserId(const char* rolename, bool ispoolerreuse, Oid usero } if (!rform->rolcanlogin) - ereport(FATAL, - (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), - errmsg("role \"%s\" is not permitted to login", rolename))); + ereport(FATAL, (errcode(ERRCODE_INVALID_AUTHORIZATION_SPECIFICATION), + errmsg("role \"%s\" is not permitted to login", rolename))); /* * Check connection limit for this role. @@ -943,7 +918,7 @@ void InitializeSessionUserId(const char* rolename, bool ispoolerreuse, Oid usero CountUserBackends(roleid) > rform->rolconnlimit) { ReportAlarmTooManyDbUserConn(rolename); ereport(FATAL, - (errcode(ERRCODE_TOO_MANY_CONNECTIONS), errmsg("too many connections for role \"%s\"", rolename))); + (errcode(ERRCODE_TOO_MANY_CONNECTIONS), errmsg("too many connections for role \"%s\"", rolename))); } else if (!u_sess->misc_cxt.AuthenticatedUserIsSuperuser) { ReportResumeTooManyDbUserConn(rolename); } @@ -951,8 +926,8 @@ void InitializeSessionUserId(const char* rolename, bool ispoolerreuse, Oid usero /* Record username and superuser status as GUC settings too */ SetConfigOption("session_authorization", rolename, PGC_BACKEND, PGC_S_OVERRIDE); - SetConfigOption( - "is_sysadmin", u_sess->misc_cxt.AuthenticatedUserIsSuperuser ? "on" : "off", PGC_INTERNAL, PGC_S_OVERRIDE); + SetConfigOption("is_sysadmin", u_sess->misc_cxt.AuthenticatedUserIsSuperuser ? "on" : "off", PGC_INTERNAL, + PGC_S_OVERRIDE); ReleaseSysCache(roleTup); } @@ -968,14 +943,15 @@ void InitializeSessionUserIdStandalone(void) */ #ifdef ENABLE_MULTIPLE_NODES AssertState(!IsUnderPostmaster || IsAutoVacuumWorkerProcess() || IsJobSchedulerProcess() || IsJobWorkerProcess() || - AM_WAL_SENDER || IsTxnSnapCapturerProcess() || IsTxnSnapWorkerProcess() || IsUndoWorkerProcess() || - CompactionProcess::IsTsCompactionProcess() || IsRbCleanerProcess() || IsRbWorkerProcess() || - t_thrd.role == PARALLEL_DECODE || t_thrd.role == LOGICAL_READ_RECORD); -#else /* ENABLE_MULTIPLE_NODES */ + AM_WAL_SENDER || IsTxnSnapCapturerProcess() || IsTxnSnapWorkerProcess() || IsUndoWorkerProcess() || + CompactionProcess::IsTsCompactionProcess() || IsRbCleanerProcess() || IsRbWorkerProcess() || + t_thrd.role == PARALLEL_DECODE || t_thrd.role == LOGICAL_READ_RECORD); +#else /* ENABLE_MULTIPLE_NODES */ AssertState(!IsUnderPostmaster || IsAutoVacuumWorkerProcess() || IsJobSchedulerProcess() || IsJobWorkerProcess() || - AM_WAL_SENDER || IsTxnSnapCapturerProcess() || IsTxnSnapWorkerProcess() || IsUndoWorkerProcess() || IsRbCleanerProcess() || - IsRbWorkerProcess() || t_thrd.role == PARALLEL_DECODE || t_thrd.role == LOGICAL_READ_RECORD); -#endif /* ENABLE_MULTIPLE_NODES */ + AM_WAL_SENDER || IsTxnSnapCapturerProcess() || IsTxnSnapWorkerProcess() || IsUndoWorkerProcess() || + IsRbCleanerProcess() || IsRbWorkerProcess() || t_thrd.role == PARALLEL_DECODE || + t_thrd.role == LOGICAL_READ_RECORD); +#endif /* ENABLE_MULTIPLE_NODES */ /* In pooler stateless reuse mode, to reset session userid */ if (!ENABLE_STATELESS_REUSE) { @@ -1011,8 +987,8 @@ void SetSessionAuthorization(Oid userid, bool is_superuser) if (!t_thrd.xact_cxt.bInAbortTransaction && userid != u_sess->misc_cxt.AuthenticatedUserId && !u_sess->misc_cxt.AuthenticatedUserIsSuperuser && !superuser()) - ereport( - ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("permission denied to set session authorization"))); + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("permission denied to set session authorization"))); SetSessionUserId(userid, is_superuser); @@ -1077,10 +1053,10 @@ void SetCurrentRoleId(Oid roleid, bool is_superuser) /* * Get user name from user oid */ -char* GetUserNameFromId(Oid roleid) +char *GetUserNameFromId(Oid roleid) { HeapTuple tuple; - char* result = NULL; + char *result = NULL; tuple = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid)); @@ -1093,10 +1069,10 @@ char* GetUserNameFromId(Oid roleid) return result; } -char* GetUserNameById(Oid roleid) +char *GetUserNameById(Oid roleid) { HeapTuple tuple; - char* result = NULL; + char *result = NULL; tuple = SearchSysCache1(AUTHOID, ObjectIdGetDatum(roleid)); @@ -1109,7 +1085,6 @@ char* GetUserNameById(Oid roleid) return result; } - /* ------------------------------------------------------------------------- * Interlock-file support * @@ -1130,7 +1105,7 @@ char* GetUserNameById(Oid roleid) */ static void UnlinkLockFile(int status, Datum filename) { - char* fname = (char*)DatumGetPointer(filename); + char *fname = (char *)DatumGetPointer(filename); if (fname != NULL) { if (unlink(fname) != 0) { @@ -1154,7 +1129,7 @@ static void UnLockPidLockFile(int status, Datum fileDes) } } -static void CreatePidLockFile(const char* filename) +static void CreatePidLockFile(const char *filename) { int fd = -1; char pid_lock_file[MAXPGPATH] = {0}; @@ -1162,7 +1137,8 @@ static void CreatePidLockFile(const char* filename) securec_check_ss(rc, "", ""); if ((fd = open(pid_lock_file, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR)) == -1) { - ereport(FATAL, (errcode_for_file_access(), errmsg("could not create or open lock file \"%s\": %m", pid_lock_file))); + ereport(FATAL, + (errcode_for_file_access(), errmsg("could not create or open lock file \"%s\": %m", pid_lock_file))); } if (flock(fd, LOCK_EX | LOCK_NB) == -1) { @@ -1180,7 +1156,7 @@ static void CreatePidLockFile(const char* filename) * amPostmaster is used to determine how to encode the output PID. * isDDLock and refName are used to determine what error message to produce. */ -static void CreateLockFile(const char* filename, bool amPostmaster, bool isDDLock, const char* refName) +static void CreateLockFile(const char *filename, bool amPostmaster, bool isDDLock, const char *refName) { int fd = -1; char buffer[MAXPGPATH * 2 + 256]; @@ -1189,7 +1165,7 @@ static void CreateLockFile(const char* filename, bool amPostmaster, bool isDDLoc int encoded_pid; pid_t other_pid; pid_t my_pid, my_p_pid, my_gp_pid; - const char* envvar = NULL; + const char *envvar = NULL; /* Grab a file lock to establish our priority to process postmaster.pid */ if (isDDLock) { @@ -1290,18 +1266,15 @@ static void CreateLockFile(const char* filename, bool amPostmaster, bool isDDLoc if (lstat(filename, &filenameStat) >= 0) { if (0 == filenameStat.st_size) { if (remove(filename) < 0) - ereport(FATAL, - (errcode_for_file_access(), - errmsg("bogus lock file \"%s\",could not unlink it : %m", filename))); + ereport(FATAL, (errcode_for_file_access(), + errmsg("bogus lock file \"%s\",could not unlink it : %m", filename))); continue; } } - ereport(FATAL, - (errmsg("bogus data in lock file \"%s\": \"%s\", please kill the " - "instance process, than remove the damaged lock file", - filename, - buffer))); + ereport(FATAL, (errmsg("bogus data in lock file \"%s\": \"%s\", please kill the " + "instance process, than remove the damaged lock file", + filename, buffer))); } /* @@ -1338,24 +1311,20 @@ static void CreateLockFile(const char* filename, bool amPostmaster, bool isDDLoc { ReportAlarmDataInstLockFileExist(); - ereport(FATAL, - (errcode(ERRCODE_LOCK_FILE_EXISTS), - errmsg("lock file \"%s\" already exists", filename), - isDDLock - ? ((encoded_pid < 0) - ? errhint("Is another openGauss (PID %d) running in data directory \"%s\"?", - (int)other_pid, - refName) - : errhint("Is another postmaster (PID %d) running in data directory \"%s\"?", - (int)other_pid, - refName)) - : ((encoded_pid < 0) ? errhint("Is another openGauss (PID %d) \ + ereport( + FATAL, + (errcode(ERRCODE_LOCK_FILE_EXISTS), errmsg("lock file \"%s\" already exists", filename), + isDDLock + ? ((encoded_pid < 0) + ? errhint("Is another openGauss (PID %d) running in data directory \"%s\"?", + (int)other_pid, refName) + : errhint("Is another postmaster (PID %d) running in data directory \"%s\"?", + (int)other_pid, refName)) + : ((encoded_pid < 0) ? errhint("Is another openGauss (PID %d) \ using socket file \"%s\"?", - (int)other_pid, - refName) - : errhint("Is another postmaster (PID %d) using socket file \"%s\"?", - (int)other_pid, - refName)))); + (int)other_pid, refName) + : errhint("Is another postmaster (PID %d) using socket file \"%s\"?", + (int)other_pid, refName)))); } } } @@ -1372,7 +1341,7 @@ static void CreateLockFile(const char* filename, bool amPostmaster, bool isDDLoc * error. */ if (isDDLock != false) { - char* ptr = buffer; + char *ptr = buffer; unsigned long id1, id2; int lineno; @@ -1385,17 +1354,15 @@ static void CreateLockFile(const char* filename, bool amPostmaster, bool isDDLoc if (ptr != NULL && sscanf_s(ptr, "%lu %lu", &id1, &id2) == 2) { if (PGSharedMemoryIsInUse(id1, id2)) { - ereport(FATAL, - (errcode(ERRCODE_LOCK_FILE_EXISTS), - errmsg("pre-existing shared memory block " - "(key %lu, ID %lu) is still in use", - id1, - id2), - errhint("If you're sure there are no old " - "server processes still running, remove " - "the shared memory block " - "or just delete the file \"%s\".", - filename))); + ereport(FATAL, (errcode(ERRCODE_LOCK_FILE_EXISTS), + errmsg("pre-existing shared memory block " + "(key %lu, ID %lu) is still in use", + id1, id2), + errhint("If you're sure there are no old " + "server processes still running, remove " + "the shared memory block " + "or just delete the file \"%s\".", + filename))); } } } @@ -1406,12 +1373,10 @@ static void CreateLockFile(const char* filename, bool amPostmaster, bool isDDLoc * would-be creators. */ if (unlink(filename) < 0) - ereport(FATAL, - (errcode_for_file_access(), - errmsg("could not remove old lock file \"%s\": %m", filename), - errhint("The file seems accidentally left over, but " - "it could not be removed. Please remove the file " - "by hand and try again."))); + ereport(FATAL, (errcode_for_file_access(), errmsg("could not remove old lock file \"%s\": %m", filename), + errhint("The file seems accidentally left over, but " + "it could not be removed. Please remove the file " + "by hand and try again."))); } ReportResumeDataInstLockFileExist(); @@ -1422,8 +1387,8 @@ static void CreateLockFile(const char* filename, bool amPostmaster, bool isDDLoc * both datadir and socket lockfiles; although more stuff may get added to * the datadir lockfile later. */ - char* unixSocketDir = NULL; - char* pghost = gs_getenv_r("PGHOST"); + char *unixSocketDir = NULL; + char *pghost = gs_getenv_r("PGHOST"); if (pghost != NULL) { check_backend_env(pghost); } @@ -1438,18 +1403,13 @@ static void CreateLockFile(const char* filename, bool amPostmaster, bool isDDLoc } } - int rc = snprintf_s(buffer, - sizeof(buffer), - sizeof(buffer) - 1, - "%d\n%s\n%ld\n%d\n%s\n", - amPostmaster ? (int)my_pid : -((int)my_pid), - t_thrd.proc_cxt.DataDir, - (long)t_thrd.proc_cxt.MyStartTime, - g_instance.attr.attr_network.PostPortNumber, + int rc = snprintf_s(buffer, sizeof(buffer), sizeof(buffer) - 1, "%d\n%s\n%ld\n%d\n%s\n", + amPostmaster ? (int)my_pid : -((int)my_pid), t_thrd.proc_cxt.DataDir, + (long)t_thrd.proc_cxt.MyStartTime, g_instance.attr.attr_network.PostPortNumber, #ifdef HAVE_UNIX_SOCKETS - unixSocketDir + unixSocketDir #else - "" + "" #endif ); @@ -1467,13 +1427,13 @@ static void CreateLockFile(const char* filename, bool amPostmaster, bool isDDLoc pgstat_report_waitevent(WAIT_EVENT_LOCK_FILE_CREATE_WRITE); if (strlen(buffer) > 0) { if ((unsigned int)(write(fd, buffer, strlen(buffer))) != strlen(buffer)) { - int save_errno = errno; + int save_errno = errno; - close(fd); - (void)unlink(filename); - /* if write didn't set errno, assume problem is no disk space */ - errno = save_errno ? save_errno : ENOSPC; - ereport(FATAL, (errcode_for_file_access(), errmsg("could not write lock file \"%s\": %m", filename))); + close(fd); + (void)unlink(filename); + /* if write didn't set errno, assume problem is no disk space */ + errno = save_errno ? save_errno : ENOSPC; + ereport(FATAL, (errcode_for_file_access(), errmsg("could not write lock file \"%s\": %m", filename))); } } pgstat_report_waitevent(WAIT_EVENT_END); @@ -1501,7 +1461,7 @@ static void CreateLockFile(const char* filename, bool amPostmaster, bool isDDLoc * Arrange for automatic removal of lockfile at proc_exit. */ { - char* ptr = NULL; + char *ptr = NULL; ptr = MemoryContextStrdup(SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_EXECUTOR), filename); on_proc_exit(UnlinkLockFile, PointerGetDatum(ptr)); } @@ -1522,7 +1482,7 @@ void CreateDataDirLockFile(bool amPostmaster) /* * Create a lockfile for the specified Unix socket file. */ -void CreateSocketLockFile(const char* socketfile, bool amPostmaster, bool is_create_psql_sock) +void CreateSocketLockFile(const char *socketfile, bool amPostmaster, bool is_create_psql_sock) { char lockfile[MAXPGPATH]; @@ -1532,8 +1492,7 @@ void CreateSocketLockFile(const char* socketfile, bool amPostmaster, bool is_cre CreateLockFile(lockfile, amPostmaster, false, socketfile); /* Save name of lockfile for TouchSocketLockFile */ errno_t rcs = strcpy_s((is_create_psql_sock ? u_sess->misc_cxt.socketLockFile : u_sess->misc_cxt.hasocketLockFile), - MAXPGPATH, - lockfile); + MAXPGPATH, lockfile); securec_check_c(rcs, "\0", "\0"); } @@ -1545,7 +1504,7 @@ void CreateSocketLockFile(const char* socketfile, bool amPostmaster, bool is_cre * from being removed by overenthusiastic /tmp-directory-cleaner daemons. * (Another reason we should never have put the socket file in /tmp...) */ -void TouchSocketLockFileInternel(const char* socketLockFile) +void TouchSocketLockFileInternel(const char *socketLockFile) { /* Do nothing if we did not create a socket... */ if (socketLockFile[0] != '\0') { @@ -1589,12 +1548,12 @@ void TouchSocketLockFile(void) * Caution: this erases all following lines. In current usage that is OK * because lines are added in order. We could improve it if needed. */ -void AddToDataDirLockFile(int target_line, const char* str) +void AddToDataDirLockFile(int target_line, const char *str) { int fd = -1; int len; int lineno; - char* ptr = NULL; + char *ptr = NULL; char buffer[BLCKSZ]; fd = open(DIRECTORY_LOCK_FILE, O_RDWR | PG_BINARY, 0); @@ -1623,11 +1582,8 @@ void AddToDataDirLockFile(int target_line, const char* str) for (lineno = 1; lineno < target_line; lineno++) { if ((ptr = strchr(ptr, '\n')) == NULL) { - ereport(LOG, - (errmsg("incomplete data in \"%s\": found only %d newlines while trying to add line %d", - DIRECTORY_LOCK_FILE, - lineno - 1, - target_line))); + ereport(LOG, (errmsg("incomplete data in \"%s\": found only %d newlines while trying to add line %d", + DIRECTORY_LOCK_FILE, lineno - 1, target_line))); close(fd); return; } @@ -1683,15 +1639,15 @@ void AddToDataDirLockFile(int target_line, const char* str) * * If compatible, return. Otherwise, ereport(FATAL). */ -void ValidatePgVersion(const char* path) +void ValidatePgVersion(const char *path) { char full_path[MAXPGPATH]; - FILE* file = NULL; + FILE *file = NULL; int ret; long file_major, file_minor; long my_major = 0, my_minor = 0; - char* endptr = NULL; - const char* version_string = PG_VERSION; + char *endptr = NULL; + const char *version_string = PG_VERSION; errno_t rc; my_major = strtol(version_string, &endptr, 10); @@ -1707,9 +1663,8 @@ void ValidatePgVersion(const char* path) if (file == NULL) { if (errno == ENOENT) ereport(FATAL, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("\"%s\" is not a valid data directory", path), - errdetail("File \"%s\" is missing.", full_path))); + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("\"%s\" is not a valid data directory", path), + errdetail("File \"%s\" is missing.", full_path))); else ereport(FATAL, (errcode_for_file_access(), errmsg("could not open file \"%s\": %m", full_path))); } @@ -1717,23 +1672,17 @@ void ValidatePgVersion(const char* path) ret = fscanf_s(file, "%ld.%ld", &file_major, &file_minor); if (ret != 2) - ereport(FATAL, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("\"%s\" is not a valid data directory", path), - errdetail("File \"%s\" does not contain valid data.", full_path), - errhint("You might need to initdb."))); + ereport(FATAL, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("\"%s\" is not a valid data directory", path), + errdetail("File \"%s\" does not contain valid data.", full_path), + errhint("You might need to initdb."))); FreeFile(file); if (my_major != file_major || my_minor != file_minor) - ereport(FATAL, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("database files are incompatible with server"), - errdetail("The data directory was initialized by PostgreSQL version %ld.%ld, " - "which is not compatible with this version %s.", - file_major, - file_minor, - version_string))); + ereport(FATAL, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("database files are incompatible with server"), + errdetail("The data directory was initialized by PostgreSQL version %ld.%ld, " + "which is not compatible with this version %s.", + file_major, file_minor, version_string))); } /* ------------------------------------------------------------------------- @@ -1746,12 +1695,12 @@ void ValidatePgVersion(const char* path) * 'gucname': name of GUC variable, for error reports * 'restricted': if true, force libraries to be in $libdir/plugins/ */ -static void load_libraries(const char* libraries, const char* gucname, bool restricted) +static void load_libraries(const char *libraries, const char *gucname, bool restricted) { - char* rawstring = NULL; - List* elemlist = NULL; + char *rawstring = NULL; + List *elemlist = NULL; int elevel; - ListCell* l = NULL; + ListCell *l = NULL; if (libraries == NULL || libraries[0] == '\0') { return; /* nothing to do */ @@ -1783,21 +1732,21 @@ static void load_libraries(const char* libraries, const char* gucname, bool rest elevel = LOG; foreach (l, elemlist) { - char* tok = (char*)lfirst(l); - char* filename = NULL; + char *tok = (char *)lfirst(l); + char *filename = NULL; errno_t rc; filename = pstrdup(tok); - if (strcmp(filename, "security_plugin") == 0 && WorkingGrandVersionNum < 92076) { + if (strcmp(filename, "security_plugin") == 0 && WorkingGrandVersionNum < 92076) { continue; - } + } canonicalize_path(filename); /* If restricting, insert $libdir/plugins if not mentioned already */ if (restricted && first_dir_separator(filename) == NULL) { - char* expanded = NULL; + char *expanded = NULL; - expanded = (char*)palloc(strlen("$libdir/plugins/") + strlen(filename) + 1); + expanded = (char *)palloc(strlen("$libdir/plugins/") + strlen(filename) + 1); rc = strcpy_s(expanded, strlen("$libdir/plugins/") + strlen(filename) + 1, "$libdir/plugins/"); securec_check_c(rc, "\0", "\0"); rc = strcat_s(expanded, strlen("$libdir/plugins/") + strlen(filename) + 1, filename); @@ -1818,12 +1767,11 @@ static void load_libraries(const char* libraries, const char* gucname, bool rest /* * process shared preloaded libraries internal */ -void -process_shared_preload_libraries_internal(void) +void process_shared_preload_libraries_internal(void) { #ifdef ENABLE_MULTIPLE_NODES if (is_streaming_engine_available()) { - load_libraries("streaming", "shared_preload_libraries", false); + load_libraries("streaming", "shared_preload_libraries", false); } #endif return; @@ -1848,7 +1796,7 @@ void process_local_preload_libraries(void) load_libraries(u_sess->attr.attr_common.local_preload_libraries_string, "local_preload_libraries", true); } -void pg_bindtextdomain(const char* domain) +void pg_bindtextdomain(const char *domain) { #ifdef ENABLE_NLS @@ -1875,7 +1823,8 @@ void Reset_Pseudo_CurrentUserId(void) * During connection obtaining, the agent_send_connection_params_parallel function * is used to synchronize the version number. */ -void register_backend_version(uint32 backend_version){ +void register_backend_version(uint32 backend_version) +{ if (IsBootstrapProcessingMode() || IsInitProcessingMode() || !IS_PGXC_COORDINATOR) { return; } @@ -1890,7 +1839,7 @@ void register_backend_version(uint32 backend_version){ ereport(ERROR, (errcode(ERRCODE_SET_QUERY), errmsg("backend_version is a error value: %d", backend_version))); } securec_check_ss_c(ret, "\0", "\0"); - if (PoolManagerSetCommand(POOL_CMD_GLOBAL_SET, sql_tmp, "backend_version") < 0){ + if (PoolManagerSetCommand(POOL_CMD_GLOBAL_SET, sql_tmp, "backend_version") < 0) { ereport(ERROR, (errmodule(MOD_TRANS_HANDLE), errcode(ERRCODE_SET_QUERY), errmsg("ERROR SET backend_version"))); } } @@ -1898,8 +1847,8 @@ void register_backend_version(uint32 backend_version){ /* * Check whether the version contains the backend_version parameter. */ -bool contain_backend_version(uint32 version_number) { - return ((version_number >= V5R1C20_BACKEND_VERSION_NUM && - version_number < V5R2C00_START_VERSION_NUM) || +bool contain_backend_version(uint32 version_number) +{ + return ((version_number >= V5R1C20_BACKEND_VERSION_NUM && version_number < V5R2C00_START_VERSION_NUM) || (version_number >= V5R2C00_BACKEND_VERSION_NUM)); } diff --git a/src/gausskernel/process/globalplancache/globalplancache.cpp b/src/gausskernel/process/globalplancache/globalplancache.cpp index 96f6ecf75..0f39685ef 100644 --- a/src/gausskernel/process/globalplancache/globalplancache.cpp +++ b/src/gausskernel/process/globalplancache/globalplancache.cpp @@ -22,7 +22,6 @@ * ------------------------------------------------------------------------- */ - #include "postgres.h" #include "knl/knl_variable.h" @@ -45,12 +44,20 @@ #include "nodes/pg_list.h" #include "commands/sqladvisor.h" -template void GlobalPlanCache::RemovePlanSource(CachedPlanSource* plansource, const char* stmt_name); +template void GlobalPlanCache::RemovePlanSource(CachedPlanSource *plansource, const char *stmt_name); -template void GlobalPlanCache::RemovePlanSource(CachedPlanSource* plansource, const char* stmt_name); - -template void GlobalPlanCache::RemovePlanSource(CachedPlanSource* plansource, const char* stmt_name); +template void GlobalPlanCache::RemovePlanSource(CachedPlanSource *plansource, const char *stmt_name); +template void GlobalPlanCache::RemovePlanSource(CachedPlanSource *plansource, const char *stmt_name); +/* + * 功能:检查两个列表是否具有不同的模式 + * + * 参数列表: + * list1:第一个列表 + * list2:第二个列表 + * + * 返回值:如果两个列表具有不同的模式,返回 true,否则返回 false + */ static bool has_diff_schema(const List *list1, const List *list2) { const ListCell *cell = NULL; @@ -65,42 +72,68 @@ static bool has_diff_schema(const List *list1, const List *list2) } return false; } - -static bool -CompareSearchPath(struct OverrideSearchPath* path1, struct OverrideSearchPath* path2) +/* + * 功能:比较两个搜索路径OverrideSearchPath是否相同 + * + * 参数列表: + * path1:第一个搜索路径OverrideSearchPath + * path2:第二个搜索路径OverrideSearchPath + * + * 返回值:如果两个搜索路径相同,返回 true,否则返回 false + */ +static bool CompareSearchPath(struct OverrideSearchPath *path1, struct OverrideSearchPath *path2) { + // 检查第一个搜索路径是否为NULL Assert(path1 != NULL); + // 如果第二个搜索路径为NULL,与当前搜索路径进行比较 if (path2 == NULL) { return OverrideSearchPathMatchesCurrent(path1); } + // 如果两个搜索路径指向相同的内存位置,它们一定相同 if (path1 == path2) { return true; } + // 检查是否添加了临时模式 if (path1->addTemp != path2->addTemp) { return false; } + // 检查是否添加了系统模式 if (path1->addCatalog != path2->addCatalog) { return false; } + // 检查模式列表是否具有不同的模式 if (has_diff_schema(path1->schemas, path2->schemas)) { return false; } + // 检查模式列表是否具有不同的模式 if (has_diff_schema(path2->schemas, path1->schemas)) { return false; } + // 如果上述条件都不满足,说明两个搜索路径相同 return true; } - -static bool GPCCompareParam(Oid* params1, Oid* params2, int paramNum) +/* + * 功能:比较两个参数列表是否相同 + * + * 参数列表: + * params1:第一个参数列表的Oid数组 + * params2:第二个参数列表的Oid数组 + * paramNum:参数的数量 + * + * 返回值:如果两个参数列表相同,返回 true,否则返回 false + */ +static bool GPCCompareParam(Oid *params1, Oid *params2, int paramNum) { for (int i = 0; i < paramNum; i++) { + // 如果两个参数列表的相应参数不相等,说明参数列表不同 if (params1[i] != params2[i]) { return false; } } + // 如果循环完成后都没有发现不同的参数,说明参数列表相同 return true; } @@ -108,16 +141,14 @@ static bool GPCCompareParam(Oid* params1, Oid* params2, int paramNum) * Return false when the given compilation environment matches the current * session compilation environment, mainly compares GUC parameter settings. */ -static bool -GPCCompareEnv(GPCEnv *env1, GPCEnv *env2) +static bool GPCCompareEnv(GPCEnv *env1, GPCEnv *env2) { - Assert (env1 != NULL); - Assert (env2 != NULL); - if (memcmp(&env1->plainenv, &env2->plainenv, sizeof(GPCPlainEnv)) == 0 - && strncmp(env1->default_storage_nodegroup, env2->default_storage_nodegroup, NAMEDATALEN) == 0 - && strncmp(env1->expected_computing_nodegroup, env2->expected_computing_nodegroup, NAMEDATALEN) == 0 - && env1->num_params == env2->num_params) - { + Assert(env1 != NULL); + Assert(env2 != NULL); + if (memcmp(&env1->plainenv, &env2->plainenv, sizeof(GPCPlainEnv)) == 0 && + strncmp(env1->default_storage_nodegroup, env2->default_storage_nodegroup, NAMEDATALEN) == 0 && + strncmp(env1->expected_computing_nodegroup, env2->expected_computing_nodegroup, NAMEDATALEN) == 0 && + env1->num_params == env2->num_params) { if (!GPCCompareParam(env1->param_types, env2->param_types, env1->num_params)) { return false; } @@ -134,23 +165,43 @@ GPCCompareEnv(GPCEnv *env1, GPCEnv *env2) return false; } - +/* + * 功能:计算一个GPCKey对象的哈希值 + * + * 参数列表: + * key:指向GPCKey结构的指针 + * keysize:key参数的大小 + * + * 返回值:计算得到的哈希值 + */ uint32 GPCHashFunc(const void *key, Size keysize) { - const GPCKey *item = (const GPCKey *) key; + // 将key强制转换为GPCKey类型的指针 + const GPCKey *item = (const GPCKey *)key; + // 分别计算三个不同部分的哈希值 uint32 val1 = DatumGetUInt32(hash_any((const unsigned char *)item->query_string, item->query_length)); uint32 val2 = DatumGetUInt32(hash_any((const unsigned char *)(&item->env.plainenv), sizeof(GPCPlainEnv))); uint32 val3 = DatumGetUInt32(hash_any((const unsigned char *)(&item->spi_signature), sizeof(SPISign))); + // 对这三个哈希值进行异或操作,得到最终的哈希值 val1 ^= val2; val1 ^= val3; return val1; } - +/* + * 功能:比较两个GPCKey对象是否相等 + * + * 参数列表: + * left:指向第一个GPCKey结构的指针 + * right:指向第二个GPCKey结构的指针 + * keysize:key参数的大小 + * + * 返回值:如果两个GPCKey对象相等,返回0;否则返回非0值 + */ int GPCKeyMatch(const void *left, const void *right, Size keysize) { - GPCKey *leftItem = (GPCKey*)left; - GPCKey *rightItem = (GPCKey*)right; + GPCKey *leftItem = (GPCKey *)left; + GPCKey *rightItem = (GPCKey *)right; Assert(NULL != leftItem); Assert(NULL != rightItem); @@ -159,17 +210,23 @@ int GPCKeyMatch(const void *left, const void *right, Size keysize) return 1; } - if(strncmp(leftItem->query_string, rightItem->query_string, leftItem->query_length)) { + if (strncmp(leftItem->query_string, rightItem->query_string, leftItem->query_length)) { return 1; } - if(GPCCompareEnv(&(leftItem->env), &(rightItem->env)) == false) { + if (GPCCompareEnv(&(leftItem->env), &(rightItem->env)) == false) { return 1; } return 0; } - +/* + * 功能:深度复制一个GPCKey对象到另一个对象 + * + * 参数列表: + * srcGpckey:源GPCKey对象的指针 + * destGpckey:目标GPCKey对象的指针 + */ void GPCKeyDeepCopy(const GPCKey *srcGpckey, GPCKey *destGpckey) { *destGpckey = *srcGpckey; @@ -177,10 +234,10 @@ void GPCKeyDeepCopy(const GPCKey *srcGpckey, GPCKey *destGpckey) destGpckey->query_string = pstrdup(srcGpckey->query_string); if (destGpckey->env.num_params > 0) { - destGpckey->env.param_types = (Oid*)palloc(sizeof(Oid) * destGpckey->env.num_params); + destGpckey->env.param_types = (Oid *)palloc(sizeof(Oid) * destGpckey->env.num_params); errno_t rc = 0; - rc = memcpy_s(destGpckey->env.param_types, sizeof(Oid) * destGpckey->env.num_params, - srcGpckey->env.param_types, sizeof(Oid) * destGpckey->env.num_params); + rc = memcpy_s(destGpckey->env.param_types, sizeof(Oid) * destGpckey->env.num_params, srcGpckey->env.param_types, + sizeof(Oid) * destGpckey->env.num_params); securec_check(rc, "", ""); } @@ -201,13 +258,17 @@ GlobalPlanCache::GlobalPlanCache() } GlobalPlanCache::~GlobalPlanCache() -{ -} - +{} +/* + * 功能:初始化全局计划缓存 + * + * 参数列表:无 + */ void GlobalPlanCache::Init() { HASHCTL ctl; errno_t rc = 0; + // 初始化ctl结构体,用于配置哈希表的属性 rc = memset_s(&ctl, sizeof(ctl), 0, sizeof(ctl)); securec_check(rc, "\0", "\0"); ctl.keysize = sizeof(GPCKey); @@ -215,68 +276,79 @@ void GlobalPlanCache::Init() ctl.hash = (HashValueFunc)GPCHashFunc; ctl.match = (HashCompareFunc)GPCKeyMatch; + // 设置哈希表的标志 int flags = HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT | HASH_COMPARE | HASH_EXTERN_CONTEXT | HASH_NOEXCEPT; - m_array = (GPCHashCtl *) MemoryContextAllocZero(GLOBAL_PLANCACHE_MEMCONTEXT, - sizeof(GPCHashCtl) * GPC_NUM_OF_BUCKETS); + // 分配内存用于存储哈希表数组 + m_array = + (GPCHashCtl *)MemoryContextAllocZero(GLOBAL_PLANCACHE_MEMCONTEXT, sizeof(GPCHashCtl) * GPC_NUM_OF_BUCKETS); + // 初始化每个哈希表桶 for (uint32 i = 0; i < GPC_NUM_OF_BUCKETS; i++) { m_array[i].count = 0; m_array[i].lockId = FirstGPCMappingLock + i; - + /* - * Create a MemoryContext per hash bucket so that all entries, plans etc under the bucket will live - * under this Memory context. This is for performance purposes. We do not want everything to be under - * the shared GlobalPlanCacheContext because more threads would need to synchronize everytime it needs a chunk - * of memory and that would become a bottleneck. - */ - m_array[i].context = AllocSetContextCreate(GLOBAL_PLANCACHE_MEMCONTEXT, - "GPC_Plan_Bucket_Context", - ALLOCSET_DEFAULT_MINSIZE, - ALLOCSET_DEFAULT_INITSIZE, - ALLOCSET_DEFAULT_MAXSIZE, - SHARED_CONTEXT); - + * Create a MemoryContext per hash bucket so that all entries, plans etc under the bucket will live + * under this Memory context. This is for performance purposes. We do not want everything to be under + * the shared GlobalPlanCacheContext because more threads would need to synchronize everytime it needs a chunk + * of memory and that would become a bottleneck. + */ + m_array[i].context = + AllocSetContextCreate(GLOBAL_PLANCACHE_MEMCONTEXT, "GPC_Plan_Bucket_Context", ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE, SHARED_CONTEXT); + // 配置哈希表的上下文 ctl.hcxt = m_array[i].context; - m_array[i].hash_tbl = hash_create("Global_Plan_Cache", - GPC_HTAB_SIZE, - &ctl, - flags); - + // 创建哈希表 + m_array[i].hash_tbl = hash_create("Global_Plan_Cache", GPC_HTAB_SIZE, &ctl, flags); } + // 初始化无效计划的列表 m_invalid_list = NULL; } - -bool GlobalPlanCache::TryStore(CachedPlanSource *plansource, PreparedStatement *ps) +/* + * 功能:尝试存储全局计划缓存 + * + * 参数列表: + * plansource:计划源对象 + * ps:预处理语句对象 + * + * 返回值:存储是否成功 + */ +bool GlobalPlanCache::TryStore(CachedPlanSource *plansource, PreparedStatement *ps) { - Assert (plansource != NULL); - Assert (plansource->magic == CACHEDPLANSOURCE_MAGIC); - Assert (!plansource->gpc.status.InShareTable()); - Assert (plansource->gpc.status.IsSharePlan()); - Assert (plansource->is_support_gplan || (plansource->gplan == NULL && plansource->cplan == NULL)); + // 断言参数有效性 + Assert(plansource != NULL); + Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC); + Assert(!plansource->gpc.status.InShareTable()); + Assert(plansource->gpc.status.IsSharePlan()); + Assert(plansource->is_support_gplan || (plansource->gplan == NULL && plansource->cplan == NULL)); - GPCKey* key = plansource->gpc.key; + // 获取计划的关键字 + GPCKey *key = plansource->gpc.key; - uint32 hashCode = GPCHashFunc((const void *) key, sizeof(*key)); + // 计算关键字的哈希值 + int32 hashCode = GPCHashFunc((const void *)key, sizeof(*key)); + // 根据哈希值获取哈希桶的编号 uint32 bucket_id = GetBucket(hashCode); - Assert (bucket_id >= 0 && bucket_id < GPC_NUM_OF_BUCKETS); + Assert(bucket_id >= 0 && bucket_id < GPC_NUM_OF_BUCKETS); int lock_id = m_array[bucket_id].lockId; + // 获取哈希桶的锁 (void)LWLockAcquire(GetMainLWLockByIndex(lock_id), LW_EXCLUSIVE); MemoryContext oldcontext = MemoryContextSwitchTo(m_array[bucket_id].context); bool found = false; - GPCEntry *entry = (GPCEntry *)hash_search_with_hash_value(m_array[bucket_id].hash_tbl, - (const void*)key, hashCode, HASH_ENTER, &found); + GPCEntry *entry = (GPCEntry *)hash_search_with_hash_value(m_array[bucket_id].hash_tbl, (const void *)key, hashCode, + HASH_ENTER, &found); + // 如果无法创建哈希表条目,报错并返回失败 if (entry == NULL) { MemoryContextSwitchTo(oldcontext); LWLockRelease(GetMainLWLockByIndex(lock_id)); - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_PSTATEMENT), - errmsg("store global plan source failed due to memory allocation failed"))); + ereport(ERROR, (errcode(ERRCODE_UNDEFINED_PSTATEMENT), + errmsg("store global plan source failed due to memory allocation failed"))); return false; } @@ -293,7 +365,7 @@ bool GlobalPlanCache::TryStore(CachedPlanSource *plansource, PreparedStatement plansource->next_saved = NULL; plansource->is_checked_opfusion = true; if (plansource->opFusionObj != NULL) { - OpFusion::SaveInGPC((OpFusion*)(plansource->opFusionObj)); + OpFusion::SaveInGPC((OpFusion *)(plansource->opFusionObj)); } /* initialize the ref count .*/ #ifdef ENABLE_MULTIPLE_NODES @@ -310,15 +382,15 @@ bool GlobalPlanCache::TryStore(CachedPlanSource *plansource, PreparedStatement Assert(plansource->query_context->is_shared); MemoryContextSeal(plansource->query_context); if (plansource->gplan) { - pg_atomic_fetch_add_u32((volatile uint32*)&plansource->gplan->global_refcount, 1); + pg_atomic_fetch_add_u32((volatile uint32 *)&plansource->gplan->global_refcount, 1); plansource->gplan->is_share = true; Assert(plansource->gplan->context->is_shared); MemoryContextSeal(plansource->gplan->context); } #ifdef USE_ASSERT_CHECKING else { - Assert(IS_PGXC_COORDINATOR && plansource->single_exec_node && - plansource->gplan == NULL && plansource->cplan == NULL); + Assert(IS_PGXC_COORDINATOR && plansource->single_exec_node && plansource->gplan == NULL && + plansource->cplan == NULL); } #endif plansource->gpc.status.SetLoc(GPC_SHARE_IN_SHARE_TABLE); @@ -328,11 +400,11 @@ bool GlobalPlanCache::TryStore(CachedPlanSource *plansource, PreparedStatement } else { /* some guys win. */ if (ps == NULL) { - Assert (IS_PGXC_DATANODE); + Assert(IS_PGXC_DATANODE); GPC_LOG("drop cache plan in try store", plansource, 0); DropCachedPlan(plansource); } else { - CachedPlanSource* newsource = entry->val.plansource; + CachedPlanSource *newsource = entry->val.plansource; ps->plansource = newsource; newsource->gpc.status.AddRefcount(); INSTR_TIME_SET_CURRENT(entry->val.last_use_time); @@ -354,9 +426,20 @@ bool GlobalPlanCache::TryStore(CachedPlanSource *plansource, PreparedStatement LWLockRelease(GetMainLWLockByIndex(lock_id)); return true; } - -CachedPlanSource* GlobalPlanCache::Fetch(const char *query_string, uint32 query_len, - int num_params, Oid* paramTypes, SPISign* spi_sign_ptr) +/* + * 功能:从全局计划缓存中获取缓存计划源对象 + * + * 参数列表: + * query_string:查询字符串 + * query_len:查询字符串长度 + * num_params:参数数量 + * paramTypes:参数类型数组 + * spi_sign_ptr:SPI签名指针 + * + * 返回值:获取到的缓存计划源对象,或者NULL表示未找到 + */ +CachedPlanSource *GlobalPlanCache::Fetch(const char *query_string, uint32 query_len, int num_params, Oid *paramTypes, + SPISign *spi_sign_ptr) { GPCKey key; key.env.filled = false; @@ -371,30 +454,33 @@ CachedPlanSource* GlobalPlanCache::Fetch(const char *query_string, uint32 query_ else key.spi_signature = {(uint32)-1, 0, (uint32)-1, -1}; - uint32 hashCode = GPCHashFunc((const void *) &key, sizeof(key)); + // 计算关键字的哈希值 + uint32 hashCode = GPCHashFunc((const void *)&key, sizeof(key)); + // 根据哈希值获取哈希桶的编号 uint32 bucket_id = GetBucket(hashCode); - Assert (bucket_id >= 0 && bucket_id < GPC_NUM_OF_BUCKETS); + Assert(bucket_id >= 0 && bucket_id < GPC_NUM_OF_BUCKETS); int lock_id = m_array[bucket_id].lockId; + // 获取哈希桶的锁 (void)LWLockAcquire(GetMainLWLockByIndex(lock_id), LW_SHARED); MemoryContext oldcontext = MemoryContextSwitchTo(m_array[bucket_id].context); bool foundCachedEntry = false; - GPCEntry *entry = (GPCEntry *) hash_search_with_hash_value(m_array[bucket_id].hash_tbl, - (const void*)(&key), - hashCode, - HASH_FIND, - &foundCachedEntry); + GPCEntry *entry = (GPCEntry *)hash_search_with_hash_value(m_array[bucket_id].hash_tbl, (const void *)(&key), + hashCode, HASH_FIND, &foundCachedEntry); + // 如果未找到缓存条目,释放锁并返回NULL if (!foundCachedEntry) { MemoryContextSwitchTo(oldcontext); LWLockRelease(GetMainLWLockByIndex(lock_id)); return NULL; } else { - CachedPlanSource* psrc = entry->val.plansource; + // 找到缓存计划源对象并增加引用计数 + CachedPlanSource *psrc = entry->val.plansource; psrc->gpc.status.AddRefcount(); if (!psrc->gpc.status.IsValid()) { + // 如果计划源对象无效,释放锁,将其移到无效计划列表,减少引用计数,并返回NULL MemoryContextSwitchTo(oldcontext); LWLockRelease(GetMainLWLockByIndex(lock_id)); MoveIntoInvalidPlanList(psrc); @@ -403,6 +489,7 @@ CachedPlanSource* GlobalPlanCache::Fetch(const char *query_string, uint32 query_ } if (ENABLE_DN_GPC) u_sess->pcache_cxt.private_refcount++; + // 增加计划源对象的使用计数 pg_atomic_fetch_add_u32(&entry->val.used_count, 1); MemoryContextSwitchTo(oldcontext); LWLockRelease(GetMainLWLockByIndex(lock_id)); @@ -411,39 +498,57 @@ CachedPlanSource* GlobalPlanCache::Fetch(const char *query_string, uint32 query_ return NULL; } - -void GlobalPlanCache::AddInvalidList(CachedPlanSource* plansource) +/* + * 功能:将计划源对象添加到无效计划列表中 + * + * 参数列表: + * plansource:要添加的计划源对象 + */ +void GlobalPlanCache::AddInvalidList(CachedPlanSource *plansource) { + // 获取清除锁以独占方式 (void)LWLockAcquire(GPCClearLock, LW_EXCLUSIVE); MemoryContext oldcontext = MemoryContextSwitchTo(GLOBAL_PLANCACHE_MEMCONTEXT); START_CRIT_SECTION(); + // 设置计划源对象的位置为GPC_SHARE_IN_SHARE_TABLE_INVALID_LIST plansource->gpc.status.SetLoc(GPC_SHARE_IN_SHARE_TABLE_INVALID_LIST); + // 将计划源对象添加到无效计划列表中 m_invalid_list = dlappend(m_invalid_list, plansource); + // 设置计划源对象的状态为GPC_INVALID plansource->gpc.status.SetStatus(GPC_INVALID); END_CRIT_SECTION(); MemoryContextSwitchTo(oldcontext); + // 释放清除锁 LWLockRelease(GPCClearLock); } - +/* + * 功能:清除无效的全局计划缓存项 + */ void GlobalPlanCache::DropInvalid() { + // 获取清除锁以独占方式 (void)LWLockAcquire(GPCClearLock, LW_EXCLUSIVE); if (m_invalid_list != NULL) { DListCell *cell = m_invalid_list->head; while (cell != NULL) { CachedPlanSource *curr = (CachedPlanSource *)cell->data.ptr_value; + // 检查计划源对象的引用计数是否为零 if (curr->gpc.status.RefCountZero()) { Assert(curr->next_saved == NULL); DListCell *next = cell->next; GPC_LOG("drop invalid shared plancache", curr, curr->stmt_name); + // 从无效计划列表中移除该计划源对象 m_invalid_list = dlist_delete_cell(m_invalid_list, cell, false); + // 删除该计划源对象 DropCachedPlanInternal(curr); curr->magic = 0; + // 解除内存上下文的封印 MemoryContextUnSeal(curr->context); MemoryContextUnSeal(curr->query_context); if (curr->opFusionObj) { - OpFusion::DropGlobalOpfusion((OpFusion*)(curr->opFusionObj)); + OpFusion::DropGlobalOpfusion((OpFusion *)(curr->opFusionObj)); } + // 删除内存上下文 MemoryContextDelete(curr->context); cell = next; @@ -452,40 +557,60 @@ void GlobalPlanCache::DropInvalid() } } } + // 释放清除锁 LWLockRelease(GPCClearLock); } - -template -void GlobalPlanCache::RemovePlanSource(CachedPlanSource* plansource, const char* stmt_name) +/* + * 功能:根据给定的动作类型移除计划源对象 + * + * 参数: + * action_type:动作类型,可以是 ACTION_RECREATE、ACTION_RELOAD 等 + * plansource:要移除的计划源对象 + * stmt_name:语句名 + */ +template +void GlobalPlanCache::RemovePlanSource(CachedPlanSource *plansource, const char *stmt_name) { Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC); - if(plansource->gpc.status.InShareTable()) { - GPCKey* key = plansource->gpc.key; - uint32 hashCode = GPCHashFunc((const void *) key, sizeof(*key)); + // 如果计划源对象在全局计划缓存中 + if (plansource->gpc.status.InShareTable()) { + GPCKey *key = plansource->gpc.key; + uint32 hashCode = GPCHashFunc((const void *)key, sizeof(*key)); uint32 bucket_id = GetBucket(hashCode); int lock_id = m_array[bucket_id].lockId; + // 获取锁以独占方式 (void)LWLockAcquire(GetMainLWLockByIndex(lock_id), LW_EXCLUSIVE); + // 如果计划源对象不在无效计划列表中 if (plansource->gpc.status.InShareTableInvalidList() == false) { bool found = false; + // 从哈希表中移除计划源对象 hash_search(m_array[bucket_id].hash_tbl, (void *)key, HASH_REMOVE, &found); + // 如果未找到计划源对象,发出 PANIC 级别错误 if (unlikely(found == false)) elog(PANIC, "should found plan in gpc when RemovePlanSource"); + // 减少计划缓存的计数 m_array[bucket_id].count--; + // 将计划源对象添加到无效计划列表中 AddInvalidList(plansource); } /* Has hold refcount for ACTION_RECREATE */ if (action_type == ACTION_RECREATE) plansource->gpc.status.SubRefCount(); + // 清除无效计划 DropInvalid(); + // 释放锁 LWLockRelease(GetMainLWLockByIndex(lock_id)); + // 如果启用了分布式计算,更新私有引用计数 if (ENABLE_DN_GPC) { u_sess->pcache_cxt.private_refcount--; + // 检查私有引用计数是否正确 if (u_sess->pcache_cxt.private_refcount != 0) elog(PANIC, "wrong refcount in subrefcount"); } + // 如果动作类型是 ACTION_RELOAD if (action_type == ACTION_RELOAD) { /* clear Datanode statements */ #ifdef ENABLE_MULTIPLE_NODES @@ -495,12 +620,14 @@ void GlobalPlanCache::RemovePlanSource(CachedPlanSource* plansource, const char* GPCDropLPIfNecessary(stmt_name, true, true, NULL); #endif } - - } else { + + } else { // 如果计划源对象不在全局计划缓存中 if (action_type == ACTION_RECREATE) { + // 从私有计划缓存中移除计划源对象 GPC_LOG("remove private plansource", plansource, plansource->stmt_name); DropCachedPlan(plansource); } else { + // 标记计划源对象为无效 CN_GPC_LOG("invalid plan", plansource, stmt_name); plansource->gpc.status.SetStatus(GPC_INVALID); plansource->is_valid = false; @@ -508,29 +635,52 @@ void GlobalPlanCache::RemovePlanSource(CachedPlanSource* plansource, const char* plansource->gplan->is_valid = false; Assert(!plansource->gplan->isShared()); } + // 如果动作类型是 ACTION_RELOAD if (action_type == ACTION_RELOAD) { + // 删除计划源对象 DropCachedPlanInternal(plansource); + // 如果计划源对象没有全局计划 if (plansource->gplan == NULL) GPCDropLPIfNecessary(stmt_name, true, true, NULL); } } } } - +/* + * 功能:移除全局计划缓存中的条目 + * + * 参数: + * htblIdx:哈希表索引 + * entry:要移除的条目 + */ void GlobalPlanCache::RemoveEntry(uint32 htblIdx, GPCEntry *entry) { CachedPlanSource *plansource = entry->val.plansource; + // 从哈希表中移除条目 bool found = false; - hash_search(m_array[htblIdx].hash_tbl, (void *) &(entry->key), HASH_REMOVE, &found); + hash_search(m_array[htblIdx].hash_tbl, (void *)&(entry->key), HASH_REMOVE, &found); + // 断言移除操作成功 Assert(found == true); + // 减少计划缓存的计数 m_array[htblIdx].count--; + // 将计划源对象添加到无效计划列表中 AddInvalidList(plansource); + // 清除无效计划 DropInvalid(); } - -bool GlobalPlanCache::CheckRecreateCachePlan(CachedPlanSource* psrc, bool* hasGetLock) +/* + * 功能:检查是否需要重新创建缓存计划 + * + * 参数: + * psrc:缓存计划源 + * hasGetLock:指示是否已获取锁的标志 + * + * 返回值: + * 如果需要重新创建缓存计划,返回true;否则返回false。 + */ +bool GlobalPlanCache::CheckRecreateCachePlan(CachedPlanSource *psrc, bool *hasGetLock) { /* * Start up a transaction command so we can run parse analysis etc. (Note @@ -577,13 +727,21 @@ bool GlobalPlanCache::CheckRecreateCachePlan(CachedPlanSource* psrc, bool* hasGe return false; } - +/* + * 功能:检查是否需要重新创建SPI缓存计划 + * + * 参数: + * spi_plan:SPI计划指针 + * + * 返回值: + * 如果需要重新创建SPI缓存计划,返回true;否则返回false。 + */ bool GlobalPlanCache::CheckRecreateSPICachePlan(SPIPlanPtr spi_plan) { - ListCell* cell = NULL; + ListCell *cell = NULL; Assert(spi_plan->magic == _SPI_PLAN_MAGIC); - foreach(cell, spi_plan->plancache_list) { - CachedPlanSource* plansource = (CachedPlanSource*)lfirst(cell); + foreach (cell, spi_plan->plancache_list) { + CachedPlanSource *plansource = (CachedPlanSource *)lfirst(cell); bool hasGetLock = false; if (CheckRecreateCachePlan(plansource, &hasGetLock)) { if (hasGetLock) { @@ -597,10 +755,18 @@ bool GlobalPlanCache::CheckRecreateSPICachePlan(SPIPlanPtr spi_plan) } return false; } - +/* + * 功能:重新创建SPI缓存计划 + * + * 参数: + * spiplan:SPI计划指针 + * + * 说明: + * 这个函数用于重新创建SPI缓存计划。它遍历SPI计划中的每个缓存计划源,对于每个计划源,如果它不在共享表中(不需要重新创建),则继续下一个计划源;否则,调用RecreateCachePlan函数重新创建缓存计划。 + */ void GlobalPlanCache::RecreateSPICachePlan(SPIPlanPtr spiplan) { - ListCell* cell = NULL; + ListCell *cell = NULL; Assert(spiplan->magic == _SPI_PLAN_MAGIC); /* push error context stack */ ErrorContextCallback spi_err_context; @@ -608,8 +774,8 @@ void GlobalPlanCache::RecreateSPICachePlan(SPIPlanPtr spiplan) spi_err_context.arg = NULL; /* we'll fill this below */ spi_err_context.previous = t_thrd.log_cxt.error_context_stack; t_thrd.log_cxt.error_context_stack = &spi_err_context; - foreach(cell, spiplan->plancache_list) { - CachedPlanSource* oldsource = (CachedPlanSource*)lfirst(cell); + foreach (cell, spiplan->plancache_list) { + CachedPlanSource *oldsource = (CachedPlanSource *)lfirst(cell); if (!oldsource->gpc.status.InShareTable()) continue; GPC_LOG("recreate spi cachedplan", oldsource, 0); @@ -619,12 +785,24 @@ void GlobalPlanCache::RecreateSPICachePlan(SPIPlanPtr spiplan) t_thrd.log_cxt.error_context_stack = spi_err_context.previous; Assert(SPIPlanCacheTableLookup(u_sess->SPI_cxt._current->spi_hash_key)); } - -void GlobalPlanCache::MoveIntoInvalidPlanList(CachedPlanSource* psrc) +/* + * 功能:将计划源移到无效计划列表 + * + * 参数: + * psrc:待移动的计划源指针 + * + * 说明: + * 这个函数用于将计划源移到无效计划列表。首先检查计划源是否在共享表中并且不是有效的,如果是,则继续执行下面的操作: + * 1. 获取计划源的哈希码和锁ID以确定它所在的哈希表和锁。 + * 2. 获取锁,确保在处理计划源时没有并发操作。 + * 3.检查计划源是否已经在无效计划列表中,如果不在,则从哈希表中删除它,减少计划源所在哈希表的计数器,并将其添加到无效计划列表中。 + * 4. 释放锁,完成移动操作。 + */ +void GlobalPlanCache::MoveIntoInvalidPlanList(CachedPlanSource *psrc) { if (psrc->gpc.status.InShareTable() && !psrc->gpc.status.IsValid()) { - GPCKey* key = psrc->gpc.key; - uint32 hashCode = GPCHashFunc((const void *) key, sizeof(*key)); + GPCKey *key = psrc->gpc.key; + uint32 hashCode = GPCHashFunc((const void *)key, sizeof(*key)); uint32 bucket_id = GetBucket(hashCode); int lock_id = m_array[bucket_id].lockId; (void)LWLockAcquire(GetMainLWLockByIndex(lock_id), LW_EXCLUSIVE); @@ -639,13 +817,26 @@ void GlobalPlanCache::MoveIntoInvalidPlanList(CachedPlanSource* psrc) LWLockRelease(GetMainLWLockByIndex(lock_id)); } } - -void GlobalPlanCache::RecreateCachePlan(CachedPlanSource* oldsource, const char* stmt_name, PreparedStatement *entry, - SPIPlanPtr spiplan, ListCell* spiplanCell, bool hasGetLock) +/* + * 功能:重新创建缓存计划 + * + * 参数: + * oldsource:原计划源指针,需要重新创建的计划源 + * stmt_name:语句名称 + * entry:预处理语句入口 + * spiplan:SPI计划指针(可选) + * spiplanCell:SPI计划单元指针(可选) + * hasGetLock:是否已获取锁标志 + * + * 注意:这个函数使用了PG_TRY、PG_CATCH和PG_END_TRY等PostgreSQL异常处理机制来处理错误情况, + * 并将无效的计划源移入全局计划缓存的无效计划列表中。 + */ +void GlobalPlanCache::RecreateCachePlan(CachedPlanSource *oldsource, const char *stmt_name, PreparedStatement *entry, + SPIPlanPtr spiplan, ListCell *spiplanCell, bool hasGetLock) { GPC_LOG("recreate plan", oldsource, oldsource->stmt_name); /* these operator may throw error, make sure shared plan is invalid first */ - CachedPlanSource *newsource = NULL; + CachedPlanSource *newsource = NULL; // 新的计划源 PG_TRY(); { if (hasGetLock) { @@ -654,15 +845,18 @@ void GlobalPlanCache::RecreateCachePlan(CachedPlanSource* oldsource, const char* AcquireExecutorLocks(oldsource->gplan->stmt_list, false); } } + // 复制原计划源以创建新的计划源 newsource = CopyCachedPlan(oldsource, true); + // 切换到新计划源的内存上下文 MemoryContext oldcxt = MemoryContextSwitchTo(newsource->context); + // 设置新计划源的相关属性 newsource->stream_enabled = IsStreamSupport(); u_sess->exec_cxt.CurrentOpFusionObj = NULL; - Assert (oldsource->gpc.status.IsSharePlan()); + Assert(oldsource->gpc.status.IsSharePlan()); newsource->gpc.status.ShareInit(); // If the planSource is set to invalid, the AST must be analyzed again // because the meta has changed. - newsource->is_valid = false; + newsource->is_valid = false; // 如果计划源设置为无效,必须重新进行AST分析 bool has_lp = false; if (spiplan != NULL) { @@ -686,6 +880,7 @@ void GlobalPlanCache::RecreateCachePlan(CachedPlanSource* oldsource, const char* } #endif } + // 执行语法分析以确保新计划源是有效的 (void)RevalidateCachedQuery(newsource, has_lp); MemoryContextSwitchTo(oldcxt); } @@ -718,17 +913,24 @@ void GlobalPlanCache::RecreateCachePlan(CachedPlanSource* oldsource, const char* RemovePlanSource(oldsource, stmt_name); } - +/* + * 功能:提交全局计划缓存的更改 + * + * 说明: + * 这个函数用于提交全局计划缓存的更改,具体的操作取决于是否处于多节点环境。 + * 如果是协调节点(Coordinator),则调用CNCommit()函数提交更改; + * 如果是数据节点(Datanode),则调用DNCommit()函数提交更改。 + */ void GlobalPlanCache::Commit() { #ifdef ENABLE_MULTIPLE_NODES if (IS_PGXC_COORDINATOR) { - CNCommit(); + CNCommit(); // 提交全局计划缓存更改(协调节点) } else { - DNCommit(); + DNCommit(); // 提交全局计划缓存更改(数据节点) } #else - CNCommit(); + CNCommit(); // 提交全局计划缓存更改(单节点环境) #endif } @@ -753,9 +955,8 @@ void GlobalPlanCache::DNCommit() Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC); Assert(!plansource->gpc.status.InShareTable()); if (unlikely(plansource->magic != CACHEDPLANSOURCE_MAGIC)) { - ereport(PANIC, - (errcode(ERRCODE_UNDEFINED_PSTATEMENT), - errmsg("In gpc commit stage, plansource has already been freed"))); + ereport(PANIC, (errcode(ERRCODE_UNDEFINED_PSTATEMENT), + errmsg("In gpc commit stage, plansource has already been freed"))); } next_plansource = plansource->next_saved; @@ -777,6 +978,16 @@ void GlobalPlanCache::DNCommit() } } +/* + * 功能:提交协调节点的全局计划缓存更改 + * + * 说明: + * 这个函数用于提交协调节点的全局计划缓存更改。 + * 它首先获取并处理 u_sess->pcache_cxt.first_saved_plan 中的计划缓存源(plansource), + * 然后根据不同的情况将这些计划缓存源保存在不同的列表中,例如 ungpc_saved_plan、first_saved_plan 等。 + * 最后,对于特定的计划缓存源,根据其是否有 SPI 签名等情况,进行相应的处理。 + * 具体处理包括:保存为 SPI 计划、尝试存储为全局计划缓存、保持在 first_saved_plan 列表中等。 + */ void GlobalPlanCache::CNCommit() { CachedPlanSource *next_plansource = NULL; @@ -785,53 +996,58 @@ void GlobalPlanCache::CNCommit() while (plansource != NULL) { Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC); Assert(!plansource->gpc.status.InShareTable()); + if (unlikely(plansource->magic != CACHEDPLANSOURCE_MAGIC)) { - ereport(PANIC, - (errcode(ERRCODE_UNDEFINED_PSTATEMENT), - errmsg("In gpc commit stage, plansource has already been freed"))); + ereport(PANIC, (errcode(ERRCODE_UNDEFINED_PSTATEMENT), + errmsg("In gpc commit stage, plansource has already been freed"))); } next_plansource = plansource->next_saved; bool has_lp = false; + #ifdef ENABLE_MULTIPLE_NODES - has_lp = plansource->single_exec_node && - plansource->gplan == NULL && plansource->cplan == NULL && plansource->stmt_name; + // 检查是否为轻量级代理计划 + has_lp = plansource->single_exec_node && plansource->gplan == NULL && plansource->cplan == NULL && + plansource->stmt_name; if (has_lp) { has_lp = (lightProxy::locateLpByStmtName(plansource->stmt_name) != NULL); } #endif + if (!plansource->gpc.status.IsSharePlan() || (plansource->gplan == NULL && plansource->cplan)) { - /* stream or private plan or cplan need put into ungpc_save_plan */ + // 对于非共享计划、或者 gplan 为空且 cplan 存在的计划,将其保存到 ungpc_saved_plan 列表中 plansource->is_saved = true; if (!plansource->is_support_gplan && plansource->gpc.status.IsSharePlan()) plansource->gpc.status.SetKind(GPC_CPLAN); - Assert (!plansource->gpc.status.IsSharePlan()); + Assert(!plansource->gpc.status.IsSharePlan()); plansource->gpc.status.SetLoc(GPC_SHARE_IN_LOCAL_UNGPC_PLAN_LIST); plansource->next_saved = u_sess->pcache_cxt.ungpc_saved_plan; u_sess->pcache_cxt.ungpc_saved_plan = plansource; } else if (!plansource->is_valid || (plansource->gplan && !plansource->gplan->is_valid)) { + // 对于无效计划(is_valid 为 false)或者 gplan 有效但 gplan 无效的计划,将其保存到 first_saved_plan 列表中 plansource->is_valid = false; plansource->is_saved = true; - Assert (plansource->gpc.status.IsSharePlan()); + Assert(plansource->gpc.status.IsSharePlan()); plansource->gpc.status.SetStatus(GPC_INVALID); plansource->gpc.status.SetLoc(GPC_SHARE_IN_LOCAL_SAVE_PLAN_LIST); plansource->next_saved = u_sess->pcache_cxt.first_saved_plan; u_sess->pcache_cxt.first_saved_plan = plansource; } else if (plansource->gplan == NULL && plansource->cplan == NULL && !has_lp) { - /* get commit before create cachedplan or lp, not init gpckey. keep in first_saved_plan */ + // 对于不具备 gplan 或 cplan,且非轻量级代理计划的计划,将其保存到 first_saved_plan 列表中 plansource->is_saved = true; - Assert (plansource->gpc.status.IsSharePlan()); + Assert(plansource->gpc.status.IsSharePlan()); plansource->gpc.status.SetLoc(GPC_SHARE_IN_LOCAL_SAVE_PLAN_LIST); plansource->next_saved = u_sess->pcache_cxt.first_saved_plan; u_sess->pcache_cxt.first_saved_plan = plansource; } else { + // 其他情况,根据是否有 SPI 签名等情况进行处理 if (plansource->spi_signature.spi_key != INVALID_SPI_KEY) { Assert(!has_lp); Assert(plansource->gplan); Assert(plansource->is_support_gplan); - g_instance.plan_cache->SPICommit(plansource); + g_instance.plan_cache->SPICommit(plansource); // 保存为 SPI 计划 } else { - PreparedStatement* ps = FetchPreparedStatement(plansource->stmt_name, true, false); + PreparedStatement *ps = FetchPreparedStatement(plansource->stmt_name, true, false); if (unlikely(ps == NULL)) { #ifdef MEMORY_CONTEXT_CHECKING ereport(PANIC, @@ -841,42 +1057,57 @@ void GlobalPlanCache::CNCommit() (errcode(ERRCODE_UNDEFINED_PSTATEMENT), errmsg("In gpc commit stage, fail to fetch prepare statement:%s", plansource->stmt_name))); } - TryStore(plansource, ps); + TryStore(plansource, ps); // 尝试存储为全局计划缓存 } } plansource = next_plansource; } } - -void GlobalPlanCache::SPITryStore(CachedPlanSource* plansource, SPIPlanPtr spiplan, int nth) +/* + * 功能:尝试将计划源存储到全局计划缓存中 + * + * 参数列表: + * plansource:待存储的计划源 + * spiplan:SPI 计划 + * nth:SPI 计划列表中的索引 + * + * 注意: + * 函数首先进行一系列断言来确保参数和状态的有效性。 + * 然后,计算计划源的哈希值(hashCode),以及确定存储桶的索引(bucket_id)和锁的标识(lock_id)。 + * 接着,函数获取锁并切换内存上下文。 + * 它检查是否在全局计划缓存中找到了与计划源匹配的计划(found 变量)。 + * 如果没有找到匹配的计划,则将计划源存储到全局计划缓存中。 + * 如果找到了匹配的计划,则将计划源替换为匹配计划,并将原计划源从缓存中移除。 + * 最后,函数释放锁并切换回原来的内存上下文。 + */ +void GlobalPlanCache::SPITryStore(CachedPlanSource *plansource, SPIPlanPtr spiplan, int nth) { - Assert (plansource != NULL); - Assert (plansource->magic == CACHEDPLANSOURCE_MAGIC); - Assert (!plansource->gpc.status.InShareTable()); - Assert (plansource->gpc.status.IsSharePlan()); - Assert (spiplan->saved); + Assert(plansource != NULL); + Assert(plansource->magic == CACHEDPLANSOURCE_MAGIC); + Assert(!plansource->gpc.status.InShareTable()); + Assert(plansource->gpc.status.IsSharePlan()); + Assert(spiplan->saved); - GPCKey* key = plansource->gpc.key; + GPCKey *key = plansource->gpc.key; - uint32 hashCode = GPCHashFunc((const void *) key, sizeof(*key)); + uint32 hashCode = GPCHashFunc((const void *)key, sizeof(*key)); uint32 bucket_id = GetBucket(hashCode); - Assert (bucket_id >= 0 && bucket_id < GPC_NUM_OF_BUCKETS); + Assert(bucket_id >= 0 && bucket_id < GPC_NUM_OF_BUCKETS); int lock_id = m_array[bucket_id].lockId; (void)LWLockAcquire(GetMainLWLockByIndex(lock_id), LW_EXCLUSIVE); MemoryContext oldcontext = MemoryContextSwitchTo(m_array[bucket_id].context); bool found = false; - GPCEntry *entry = (GPCEntry *)hash_search_with_hash_value(m_array[bucket_id].hash_tbl, - (const void*)key, hashCode, HASH_ENTER, &found); + GPCEntry *entry = (GPCEntry *)hash_search_with_hash_value(m_array[bucket_id].hash_tbl, (const void *)key, hashCode, + HASH_ENTER, &found); if (entry == NULL) { MemoryContextSwitchTo(oldcontext); LWLockRelease(GetMainLWLockByIndex(lock_id)); - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_PSTATEMENT), - errmsg("store global plan source failed due to memory allocation failed"))); + ereport(ERROR, (errcode(ERRCODE_UNDEFINED_PSTATEMENT), + errmsg("store global plan source failed due to memory allocation failed"))); } if (found == false) { @@ -886,14 +1117,14 @@ void GlobalPlanCache::SPITryStore(CachedPlanSource* plansource, SPIPlanPtr spipl entry->key.spi_signature = key->spi_signature; /* Set the magic number. */ entry->val.plansource = plansource; - //off the link + // off the link plansource->next_saved = NULL; INSTR_TIME_SET_CURRENT(entry->val.last_use_time); - //initialize the ref count . + // initialize the ref count . plansource->gpc.status.AddRefcount(); m_array[bucket_id].count++; - pg_atomic_fetch_add_u32((volatile uint32*)&plansource->gplan->global_refcount, 1); + pg_atomic_fetch_add_u32((volatile uint32 *)&plansource->gplan->global_refcount, 1); plansource->gplan->is_share = true; Assert(plansource->context->is_shared); MemoryContextSeal(plansource->context); @@ -904,10 +1135,10 @@ void GlobalPlanCache::SPITryStore(CachedPlanSource* plansource, SPIPlanPtr spipl plansource->gpc.status.SetLoc(GPC_SHARE_IN_SHARE_TABLE); } else { - //some guys win. - CachedPlanSource* newsource = entry->val.plansource; - ListCell* n_cell = list_nth_cell(spiplan->plancache_list, nth); - n_cell->data.ptr_value = (void*)newsource; + // some guys win. + CachedPlanSource *newsource = entry->val.plansource; + ListCell *n_cell = list_nth_cell(spiplan->plancache_list, nth); + n_cell->data.ptr_value = (void *)newsource; newsource->gpc.status.AddRefcount(); // purge old one. @@ -919,17 +1150,31 @@ void GlobalPlanCache::SPITryStore(CachedPlanSource* plansource, SPIPlanPtr spipl MemoryContextSwitchTo(oldcontext); LWLockRelease(GetMainLWLockByIndex(lock_id)); } - -void GlobalPlanCache::SPICommit(CachedPlanSource* plansource) +/* + * Commit a SPI plan to the Global Plan Cache. + * + * Parameters: + * - plansource: The CachedPlanSource to commit. + * + * Functionality: + * - This function commits a SPI plan to the Global Plan Cache. + * - It first checks various assertions to ensure the validity of parameters and states. + * - Then, it looks up the SPI plan in the SPIPlanCacheTable. + * - If found, it iterates through the SPI plan list and tries to store the plansource. + * - If it successfully stores the plansource, it sets the 'has_cachedplan' flag to true. + * - If it fails to find a matching plan or storing fails, it reports an error. + * - Finally, it performs additional assertions to check the consistency of the SPI plans. + */ +void GlobalPlanCache::SPICommit(CachedPlanSource *plansource) { - Assert (u_sess->SPI_cxt.SPICacheTable != NULL); - plpgsql_SPIPlanCacheEnt* entry = SPIPlanCacheTableLookup(plansource->spi_signature.spi_key); + Assert(u_sess->SPI_cxt.SPICacheTable != NULL); + plpgsql_SPIPlanCacheEnt *entry = SPIPlanCacheTableLookup(plansource->spi_signature.spi_key); Assert(entry != NULL); Assert(entry->func_oid != InvalidOid); - List* spiplan_list = entry->SPIplan_list; - ListCell* cell = NULL; + List *spiplan_list = entry->SPIplan_list; + ListCell *cell = NULL; bool has_cachedplan = false; - foreach(cell, spiplan_list) { + foreach (cell, spiplan_list) { SPIPlanPtr spi_plan = (SPIPlanPtr)lfirst(cell); if (spi_plan->id == plansource->spi_signature.spi_id) { SPITryStore(plansource, spi_plan, plansource->spi_signature.plansource_id); @@ -950,13 +1195,13 @@ void GlobalPlanCache::SPICommit(CachedPlanSource* plansource) } #ifdef USE_ASSERT_CHECKING - foreach(cell, spiplan_list) { + foreach (cell, spiplan_list) { SPIPlanPtr spi_plan = (SPIPlanPtr)lfirst(cell); - ListCell* cl = NULL; + ListCell *cl = NULL; if (list_length(spi_plan->plancache_list) == 0) continue; - foreach(cl, spi_plan->plancache_list) { - CachedPlanSource *cur = (CachedPlanSource*)(cl->data.ptr_value); + foreach (cl, spi_plan->plancache_list) { + CachedPlanSource *cur = (CachedPlanSource *)(cl->data.ptr_value); Assert(cur->magic == CACHEDPLANSOURCE_MAGIC); } } @@ -965,18 +1210,18 @@ void GlobalPlanCache::SPICommit(CachedPlanSource* plansource) void GlobalPlanCache::RemovePlanCacheInSPIPlan(SPIPlanPtr plan) { - Assert (plan->magic == _SPI_PLAN_MAGIC); + Assert(plan->magic == _SPI_PLAN_MAGIC); if (list_length(plan->plancache_list) > 0) { - ListCell* cell = NULL; - foreach(cell, plan->plancache_list) { - CachedPlanSource* plansource = (CachedPlanSource*)lfirst(cell); + ListCell *cell = NULL; + foreach (cell, plan->plancache_list) { + CachedPlanSource *plansource = (CachedPlanSource *)lfirst(cell); if (plansource->gpc.status.InShareTable()) { Assert(plan->saved); CN_GPC_LOG("drop shared spi plan, subrefcount", plansource, 0); /* move plansource into invalid list if during delet func */ if (u_sess->plsql_cxt.is_delete_function) { - GPCKey* gpckey = plansource->gpc.key; - uint32 hashCode = GPCHashFunc((const void *) gpckey, sizeof(*gpckey)); + GPCKey *gpckey = plansource->gpc.key; + uint32 hashCode = GPCHashFunc((const void *)gpckey, sizeof(*gpckey)); uint32 bucket_id = GetBucket(hashCode); int lock_id = m_array[bucket_id].lockId; (void)LWLockAcquire(GetMainLWLockByIndex(lock_id), LW_EXCLUSIVE); @@ -1005,11 +1250,10 @@ void GlobalPlanCache::RemovePlanCacheInSPIPlan(SPIPlanPtr plan) void GlobalPlanCache::CleanUpByTime() { List *gpckey_list = NULL; - const int maxlen_gpckey_list = 100; + const int maxlen_gpckey_list = 100; instr_time curTime; INSTR_TIME_SET_CURRENT(curTime); - for (uint32 bucket_id = 0; bucket_id < GPC_NUM_OF_BUCKETS; bucket_id++) - { + for (uint32 bucket_id = 0; bucket_id < GPC_NUM_OF_BUCKETS; bucket_id++) { int lock_id = m_array[bucket_id].lockId; /* Step 1: Try to find the code plan cache */ @@ -1020,10 +1264,10 @@ void GlobalPlanCache::CleanUpByTime() } HASH_SEQ_STATUS hash_seq; GPCEntry *entry = NULL; - CachedPlanSource* cur_plansource = NULL; + CachedPlanSource *cur_plansource = NULL; hash_seq_init(&hash_seq, m_array[bucket_id].hash_tbl); - while ((entry = (GPCEntry*)hash_seq_search(&hash_seq)) != NULL) { + while ((entry = (GPCEntry *)hash_seq_search(&hash_seq)) != NULL) { if (entry->val.used_count > 0) { entry->val.last_use_time = curTime; entry->val.used_count = 0; @@ -1032,8 +1276,8 @@ void GlobalPlanCache::CleanUpByTime() cur_plansource = entry->val.plansource; if (cur_plansource->gpc.status.RefCountZero() && INSTR_TIME_GET_DOUBLE(curTime) - INSTR_TIME_GET_DOUBLE(entry->val.last_use_time) > - u_sess->attr.attr_common.gpc_clean_timeout) { - GPCKey *dest_gpckey = (GPCKey *)palloc(sizeof(GPCKey)); + u_sess->attr.attr_common.gpc_clean_timeout) { + GPCKey *dest_gpckey = (GPCKey *)palloc(sizeof(GPCKey)); GPCKeyDeepCopy(&entry->key, dest_gpckey); gpckey_list = lappend(gpckey_list, dest_gpckey); @@ -1049,25 +1293,25 @@ void GlobalPlanCache::CleanUpByTime() /* Step 2: Try to remove plan cache */ if (gpckey_list && list_length(gpckey_list) > 0) { LWLockAcquire(GetMainLWLockByIndex(lock_id), LW_EXCLUSIVE); - ListCell* l = NULL; - foreach(l, gpckey_list) { + ListCell *l = NULL; + foreach (l, gpckey_list) { bool found = false; - GPCKey *key = (GPCKey *)lfirst(l); + GPCKey *key = (GPCKey *)lfirst(l); GPCEntry *entry = NULL; entry = (GPCEntry *)hash_search(m_array[bucket_id].hash_tbl, (void *)key, HASH_FIND, &found); if (entry) { cur_plansource = entry->val.plansource; if (cur_plansource->gpc.status.RefCountZero() && INSTR_TIME_GET_DOUBLE(curTime) - INSTR_TIME_GET_DOUBLE(entry->val.last_use_time) > - u_sess->attr.attr_common.gpc_clean_timeout) { + u_sess->attr.attr_common.gpc_clean_timeout) { GPC_LOG("drop shared plancache by time", cur_plansource, cur_plansource->stmt_name); DropCachedPlanInternal(cur_plansource); - hash_search(m_array[bucket_id].hash_tbl, (void *) key, HASH_REMOVE, &found); + hash_search(m_array[bucket_id].hash_tbl, (void *)key, HASH_REMOVE, &found); cur_plansource->magic = 0; MemoryContextUnSeal(cur_plansource->context); MemoryContextUnSeal(cur_plansource->query_context); if (cur_plansource->opFusionObj) { - OpFusion::DropGlobalOpfusion((OpFusion*)(cur_plansource->opFusionObj)); + OpFusion::DropGlobalOpfusion((OpFusion *)(cur_plansource->opFusionObj)); } MemoryContextDelete(cur_plansource->context); m_array[bucket_id].count--; @@ -1086,7 +1330,7 @@ void GlobalPlanCache::CleanUpByTime() } } -void CleanSessGPCPtr(knl_session_context* currentSession) +void CleanSessGPCPtr(knl_session_context *currentSession) { CachedPlanSource *psrc = currentSession->pcache_cxt.cur_stmt_psrc; currentSession->pcache_cxt.cur_stmt_psrc = NULL; @@ -1100,7 +1344,7 @@ void CleanSessGPCPtr(knl_session_context* currentSession) elog(PANIC, "wrong refcount"); } -void CleanSessionGPCDetach(knl_session_context* currentSession) +void CleanSessionGPCDetach(knl_session_context *currentSession) { if (IS_PGXC_COORDINATOR) return; @@ -1108,15 +1352,15 @@ void CleanSessionGPCDetach(knl_session_context* currentSession) elog(PANIC, "session's cur_stmt_psrc should be null when detach"); } - CachedPlanSource* plansource = currentSession->pcache_cxt.first_saved_plan; + CachedPlanSource *plansource = currentSession->pcache_cxt.first_saved_plan; while (plansource != NULL) { /* * When turing on the enable_global_plancache, there are some cases that * we cannot insert the plancache in the shared HTAB. No Prepare Statement - * on DN, so we can just drop shared plan and wait for next parse message + * on DN, so we can just drop shared plan and wait for next parse message * to create it again. */ - CachedPlanSource* next_plansource = plansource->next_saved; + CachedPlanSource *next_plansource = plansource->next_saved; if (plansource->gpc.status.IsPrivatePlan()) { /* private plan has reference on pointer like unname_stmt_psrc or spiplan */ GPC_LOG("invalid plan in sess detach", plansource, plansource->stmt_name); @@ -1133,4 +1377,3 @@ void CleanSessionGPCDetach(knl_session_context* currentSession) currentSession->pcache_cxt.first_saved_plan = NULL; currentSession->pcache_cxt.gpc_in_ddl = false; } - diff --git a/src/gausskernel/process/globalplancache/globalplancache_inval.cpp b/src/gausskernel/process/globalplancache/globalplancache_inval.cpp index 9c2568249..b2d5adc23 100644 --- a/src/gausskernel/process/globalplancache/globalplancache_inval.cpp +++ b/src/gausskernel/process/globalplancache/globalplancache_inval.cpp @@ -38,63 +38,102 @@ #include "utils/plancache.h" #include "utils/syscache.h" +/* + * 功能:检查共享缓存失效消息是否符合条件 + * + * 参数列表: + * msg:指向 SharedInvalidationMessage 结构的指针,表示共享缓存失效消息 + * + * 返回值: + * 如果共享缓存失效消息符合条件,返回 true;否则返回 false + */ bool GlobalPlanCache::MsgCheck(const SharedInvalidationMessage *msg) { if (msg->id >= 0) { + // 如果消息的 id 大于等于 0 if (msg->cc.id == PROCOID || msg->cc.id == NAMESPACEOID || msg->cc.id == OPEROID || msg->cc.id == AMOPOPID) { + // 如果消息的 cc.id 是 PROCOID、NAMESPACEOID、OPOID 或 AMOPOPID 中的任何一个,返回 true return true; } } else if (msg->id == SHAREDINVALRELCACHE_ID || msg->id == SHAREDINVALPARTCACHE_ID) { + // 如果消息的 id 是 SHAREDINVALRELCACHE_ID 或 SHAREDINVALPARTCACHE_ID,返回 true return true; } - + // 如果以上条件都不满足,返回 false return false; } - -bool GlobalPlanCache::NeedDropEntryByLocalMsg(CachedPlanSource* plansource, int tot, const int *idx, const SharedInvalidationMessage *msgs) +/* + * 功能:根据本地失效消息判断是否需要丢弃计划缓存项 + * + * 参数列表: + * plansource:指向 CachedPlanSource 结构的指针,表示计划源对象 + * tot:失效消息的总数 + * idx:失效消息的索引数组 + * msgs:指向 SharedInvalidationMessage 结构的指针,表示失效消息数组 + * + * 返回值: + * 如果需要丢弃计划缓存项,返回 true;否则返回 false + */ +bool GlobalPlanCache::NeedDropEntryByLocalMsg(CachedPlanSource *plansource, int tot, const int *idx, + const SharedInvalidationMessage *msgs) { + // 获取计划源对象所属的数据库 ID Oid database_id = plansource->gpc.key->env.plainenv.database_id; for (int j = 0; j < tot; j++) { const SharedInvalidationMessage *msg = &msgs[idx[j]]; + // 如果计划源对象具有原始解析树,并且原始解析树的类型是 TransactionStmt,则跳过该消息的处理 if ((plansource)->raw_parse_tree && IsA((plansource)->raw_parse_tree, TransactionStmt)) continue; if (msg->id >= 0) { - + // 如果消息的 id 大于等于 0 if (msg->cc.dbId == database_id || msg->cc.dbId == InvalidOid) { if (msg->cc.id == PROCOID) { + // 检查计划缓存项的失效项依赖性,并更新 CheckInvalItemDependency(plansource, msg->cc.id, msg->cc.hashValue); } else if (msg->cc.id == NAMESPACEOID || msg->cc.id == OPEROID || msg->cc.id == AMOPOPID) { + // 重置计划缓存项 ResetPlanCache(plansource); } } } else if (msg->id == SHAREDINVALRELCACHE_ID) { - if (msg->rc.dbId == database_id || msg->rc.dbId == InvalidOid) - { + // 如果消息的 id 是 SHAREDINVALRELCACHE_ID + if (msg->rc.dbId == database_id || msg->rc.dbId == InvalidOid) { + // 检查计划缓存项与关系依赖性,并更新 CheckRelDependency(plansource, msg->rc.relId); } } else if (msg->id == SHAREDINVALPARTCACHE_ID) { + // 如果消息的 id 是 SHAREDINVALPARTCACHE_ID if (msg->pc.dbId == database_id || msg->pc.dbId == InvalidOid) { + // 检查计划缓存项与分区依赖性,并更新 CheckRelDependency(plansource, msg->pc.partId); } } + // 如果计划源对象需要丢弃共享 GPC,则返回 true if (plansource->gpc.status.NeedDropSharedGPC()) { return true; } } + // 如果不需要丢弃计划缓存项,返回 false return false; - - } - +/* + * 功能:根据失效消息进行计划缓存项的失效处理 + * + * 参数列表: + * msgs:指向 SharedInvalidationMessage 结构的指针,表示失效消息数组 + * n:失效消息的数量 + */ void GlobalPlanCache::InvalMsg(const SharedInvalidationMessage *msgs, int n) { - int *idx = (int *)palloc0(n * sizeof(int)); - int tot = 0; + // 分配并初始化一个索引数组 + int *idx = (int *)palloc0(n * sizeof(int)); + int tot = 0; + // 遍历失效消息数组,筛选出需要处理的消息 for (int i = 0; i < n; i++) { const SharedInvalidationMessage *msg = &msgs[i]; @@ -103,20 +142,21 @@ void GlobalPlanCache::InvalMsg(const SharedInvalidationMessage *msgs, int n) } } + // 如果没有需要处理的消息,释放索引数组并返回 if (tot == 0) { pfree_ext(idx); - return ; + return; } /* Go through each bucket in the GPC HTAB and do some invalidation depending on the GPCInvalInfo we got.*/ - for (uint32 bucket_id = 0; bucket_id < GPC_NUM_OF_BUCKETS; bucket_id ++) { + for (uint32 bucket_id = 0; bucket_id < GPC_NUM_OF_BUCKETS; bucket_id++) { /* Ok so bucket is not empty. Get the bucket S-lock so we can iterate through it. */ int lock_id = m_array[bucket_id].lockId; LWLockAcquire(GetMainLWLockByIndex(lock_id), LW_EXCLUSIVE); MemoryContext oldcontext = MemoryContextSwitchTo(m_array[bucket_id].context); - - /* Check the number of entries in the bucket again. - * GPC Eviction might have removed the last entry while we were waiting for the shared lock. */ + + /* Check the number of entries in the bucket again. + * GPC Eviction might have removed the last entry while we were waiting for the shared lock. */ int bucketEntriesCount = m_array[bucket_id].count; if (0 == bucketEntriesCount) { MemoryContextSwitchTo(oldcontext); @@ -129,7 +169,7 @@ void GlobalPlanCache::InvalMsg(const SharedInvalidationMessage *msgs, int n) GPCEntry *entry = NULL; while ((entry = (GPCEntry *)hash_seq_search(&hash_seq)) != NULL) { - Assert (entry->val.plansource != NULL); + Assert(entry->val.plansource != NULL); /* for standby mode, Invalid Msg send by xlog thread, but xlog thread didn't set db id into MyDatabaseId. So we need check each plan's db id by gpc'key in NeedDropEntryByLocalMsg latter */ if (pmState == PM_RUN && @@ -138,7 +178,7 @@ void GlobalPlanCache::InvalMsg(const SharedInvalidationMessage *msgs, int n) } /* Atomic read the number of CachedEnvironment in this entry */ - if(NeedDropEntryByLocalMsg(entry->val.plansource, tot, idx, msgs)) { + if (NeedDropEntryByLocalMsg(entry->val.plansource, tot, idx, msgs)) { RemoveEntry(bucket_id, entry); } } @@ -147,5 +187,6 @@ void GlobalPlanCache::InvalMsg(const SharedInvalidationMessage *msgs, int n) LWLockRelease(GetMainLWLockByIndex(lock_id)); } + // 释放索引数组 pfree_ext(idx); } diff --git a/src/gausskernel/process/globalplancache/globalplancache_util.cpp b/src/gausskernel/process/globalplancache/globalplancache_util.cpp index b3c0927cf..f8e8c9462 100644 --- a/src/gausskernel/process/globalplancache/globalplancache_util.cpp +++ b/src/gausskernel/process/globalplancache/globalplancache_util.cpp @@ -1,26 +1,26 @@ /* -* 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. -* ------------------------------------------------------------------------- -* -* globalplancache_util.cpp -* global plan cache -* -* IDENTIFICATION -* src/gausskernel/process/globalplancache/globalplancache_util.cpp -* -* ------------------------------------------------------------------------- -*/ + * 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. + * ------------------------------------------------------------------------- + * + * globalplancache_util.cpp + * global plan cache + * + * IDENTIFICATION + * src/gausskernel/process/globalplancache/globalplancache_util.cpp + * + * ------------------------------------------------------------------------- + */ #include "postgres.h" #include "knl/knl_variable.h" @@ -38,10 +38,17 @@ #include "utils/memutils.h" #include "utils/plancache.h" #include "utils/syscache.h" -void -GlobalPlanCache::FillClassicEnvSignatures(GPCEnv *env) +/* + * 功能:填充经典环境签名 + * + * 参数列表: + * env:指向 GPCEnv 结构的指针,表示环境配置信息 + */ +void GlobalPlanCache::FillClassicEnvSignatures(GPCEnv *env) { + // 初始化环境签名为0 env->plainenv.env_signature = 0; + // 逐个设置环境签名的各位,每一位表示一个环境配置的开关状态 env->plainenv.env_signature |= u_sess->attr.attr_sql.enable_fast_numeric; env->plainenv.env_signature |= u_sess->attr.attr_sql.enable_global_stats << 1; env->plainenv.env_signature |= u_sess->attr.attr_sql.enable_hdfs_predicate_pushdown << 2; @@ -74,17 +81,14 @@ GlobalPlanCache::FillClassicEnvSignatures(GPCEnv *env) env->plainenv.env_signature |= g_instance.attr.attr_sql.enable_orc_cache << 29; env->plainenv.env_signature |= u_sess->attr.attr_sql.acceleration_with_compute_pool << 30; env->plainenv.env_signature |= u_sess->attr.attr_sql.enable_extrapolation_stats << 31; - } - /* * @Description:Fill in the environment signatures which are bitmaps for the boolean type GUC parameters * @in num: GPCEnv * @return - void -*/ -void -GlobalPlanCache::FillEnvSignatures(GPCEnv *env) + */ +void GlobalPlanCache::FillEnvSignatures(GPCEnv *env) { /* We should only call this function if env is not NULL and it's not filled */ Assert(env && !env->filled); @@ -126,19 +130,27 @@ GlobalPlanCache::FillEnvSignatures(GPCEnv *env) env->plainenv.env_signature2 |= u_sess->attr.attr_sql.enable_opfusion << 17; env->plainenv.env_signature2 |= u_sess->attr.attr_sql.enable_partition_opfusion << 18; } - -void -GlobalPlanCache::EnvFill(GPCEnv *env, bool depends_on_role) +/* + * 功能:填充环境信息 + * + * 参数列表: + * env:指向 GPCEnv 结构的指针,表示环境配置信息 + * depends_on_role:布尔值,表示是否依赖于角色 + */ +void GlobalPlanCache::EnvFill(GPCEnv *env, bool depends_on_role) { /* We should only call this function if env is not NULL and it's not filled */ + // 断言:确保 env 不为 NULL,且未被填充过 Assert(env && !env->filled); - + // 初始化 env 为0 errno_t rc = memset_s(env, sizeof(GPCEnv), 0, sizeof(GPCEnv)); securec_check(rc, "\0", "\0"); + // 填充环境签名 FillEnvSignatures(env); + // 逐个设置环境配置参数 env->plainenv.best_agg_plan = u_sess->attr.attr_sql.best_agg_plan; env->plainenv.query_dop_tmp = u_sess->attr.attr_sql.query_dop_tmp; env->plainenv.rewrite_rule = u_sess->attr.attr_sql.rewrite_rule; @@ -174,46 +186,47 @@ GlobalPlanCache::EnvFill(GPCEnv *env, bool depends_on_role) env->plainenv.sql_beta_feature = u_sess->attr.attr_sql.sql_beta_feature; /* new GUC parameters which affect the plan */ + // 填充新的 GUC 参数 env->plainenv.qrw_inlist2join_optmode = u_sess->opt_cxt.qrw_inlist2join_optmode; env->plainenv.skew_strategy_store = u_sess->attr.attr_sql.skew_strategy_store; env->plainenv.database_id = u_sess->proc_cxt.MyDatabaseId; env->plainenv.plancachemode = u_sess->attr.attr_sql.g_planCacheMode; GlobalPlanCache::GetSchemaName(env); - + // 填充预期的计算节点组和默认的存储节点组 if (u_sess->attr.attr_sql.expected_computing_nodegroup) { - int rc = memcpy_s(env->expected_computing_nodegroup, - NAMEDATALEN, - u_sess->attr.attr_sql.expected_computing_nodegroup, - strlen(u_sess->attr.attr_sql.expected_computing_nodegroup)); + int rc = + memcpy_s(env->expected_computing_nodegroup, NAMEDATALEN, u_sess->attr.attr_sql.expected_computing_nodegroup, + strlen(u_sess->attr.attr_sql.expected_computing_nodegroup)); securec_check(rc, "", ""); } else { - env->expected_computing_nodegroup[0] = '\0'; + env->expected_computing_nodegroup[0] = '\0'; } if (u_sess->attr.attr_sql.default_storage_nodegroup) { - int rc = memcpy_s(env->default_storage_nodegroup, - NAMEDATALEN, - u_sess->attr.attr_sql.default_storage_nodegroup, - strlen(u_sess->attr.attr_sql.default_storage_nodegroup)); + int rc = memcpy_s(env->default_storage_nodegroup, NAMEDATALEN, u_sess->attr.attr_sql.default_storage_nodegroup, + strlen(u_sess->attr.attr_sql.default_storage_nodegroup)); securec_check(rc, "", ""); } else { env->default_storage_nodegroup[0] = '\0'; } + // 设置依赖角色信息和用户 ID env->depends_on_role = depends_on_role; env->user_oid = GetUserId(); } - -void -GlobalPlanCache::GetSchemaName(GPCEnv *env) +/* + * 功能:获取当前模式的模式名称 + * + * 参数列表: + * env:指向 GPCEnv 结构的指针,表示环境配置信息 + */ +void GlobalPlanCache::GetSchemaName(GPCEnv *env) { /* get schema name */ if (u_sess->attr.attr_common.namespace_current_schema) { - int rc = memcpy_s(env->schema_name, - NAMEDATALEN, - u_sess->attr.attr_common.namespace_current_schema, + int rc = memcpy_s(env->schema_name, NAMEDATALEN, u_sess->attr.attr_common.namespace_current_schema, strlen(u_sess->attr.attr_common.namespace_current_schema)); securec_check(rc, "", ""); } else { @@ -221,46 +234,77 @@ GlobalPlanCache::GetSchemaName(GPCEnv *env) } } - +/* + * 功能:全局计划缓存重置函数 + * + * 说明:此函数用于重置全局计划缓存,并清除相关内存上下文中的数据。 + */ void GPCResetAll() { pthread_mutex_lock(&g_instance.gpc_reset_lock); for (int i = 0; i < MAX_GLOBAL_CACHEMEM_NUM; ++i) { + // 解除内存上下文中的子上下文封印,以便重置 MemoryContextUnSealChildren(g_instance.cache_cxt.global_plancache_mem[i]); + // 重置内存上下文,清除其中的数据 MemoryContextReset(g_instance.cache_cxt.global_plancache_mem[i]); } + // 初始化全局计划缓存 g_instance.plan_cache->Init(); pthread_mutex_unlock(&g_instance.gpc_reset_lock); + // 记录日志,表示全局计划缓存已重置 GPC_LOG("gpc reset all", 0, 0); } - -void GPCCleanDatanodeStatement(int dn_stmt_num, const char* stmt_name) +/* + * 功能:清理数据节点上的语句 + * + * 参数列表: + * dn_stmt_num:要清理的语句数量 + * stmt_name:待清理语句的名称 + * + * 说明:此函数用于清理数据节点上的指定语句。仅在协调器节点且提供了有效的语句名称时执行。 + */ +void GPCCleanDatanodeStatement(int dn_stmt_num, const char *stmt_name) { + // 如果语句名称为空、为空字符串或不在协调器节点上,直接返回 if (stmt_name == NULL || stmt_name[0] == '\0' || !IS_PGXC_COORDINATOR) return; int n = 0; char *tmp_name = NULL; + // 循环清理指定数量的语句 for (n = 0; n < dn_stmt_num; n++) { + // 获取当前要清理的语句名称 tmp_name = get_datanode_statement_name(stmt_name, n); + // 调用 DropDatanodeStatement 函数来清理语句 DropDatanodeStatement(tmp_name); + // 释放临时语句名称的内存 pfree_ext(tmp_name); } } - -void GPCReGplan(CachedPlanSource* plansource) +/* + * 功能:将 CachedPlanSource 切换为全局计划(gplan) + * + * 参数列表: + * plansource:要切换的 CachedPlanSource 对象 + * + * 说明:此函数用于将 CachedPlanSource 切换为全局计划(gplan)。如果该计划支持 gplan 且当前不是共享状态,则进行切换。 + * 切换后,将该计划移动到首个已保存的计划中,以便进行全局共享。 + */ +void GPCReGplan(CachedPlanSource *plansource) { /* if is unshared for has cplan, and create gplan this time, * reset to shared and move to first_saved_plan */ if (!plansource->is_support_gplan || !plansource->gpc.status.IsUnShareCplan()) return; Assert(plansource->is_saved); + // 设置计划为共享状态 plansource->gpc.status.SetKind(GPC_SHARED); /* move into first_saved_plan */ if (u_sess->pcache_cxt.ungpc_saved_plan == plansource) { u_sess->pcache_cxt.ungpc_saved_plan = plansource->next_saved; } else { - CachedPlanSource* psrc = NULL; + CachedPlanSource *psrc = NULL; + // 寻找并更新未共享计划列表 for (psrc = u_sess->pcache_cxt.ungpc_saved_plan; psrc; psrc = psrc->next_saved) { if (psrc->next_saved == plansource) { psrc->next_saved = plansource->next_saved; @@ -268,72 +312,104 @@ void GPCReGplan(CachedPlanSource* plansource) } } } + // 设置计划的位置为在本地已保存计划列表中 plansource->gpc.status.SetLoc(GPC_SHARE_IN_LOCAL_SAVE_PLAN_LIST); + // 将计划移动到首个已保存计划中 plansource->next_saved = u_sess->pcache_cxt.first_saved_plan; u_sess->pcache_cxt.first_saved_plan = plansource; } - +/* + * 功能:清理与会话相关的全局计划缓存 + * + * 参数列表:无 + * + * 说明:此函数用于清理与会话相关的全局计划缓存。它首先检查是否启用了 CN-GPC(全局计划缓存), + * 如果未启用则直接返回。然后,它会删除所有的已经准备好的语句(prepared statements)。 + * 如果计划处于共享内存中,则删除对应的内存上下文。 + */ void CNGPCCleanUpSession() { + // 如果未启用 CN-GPC,则直接返回 if (!ENABLE_CN_GPC) { return; } + // 删除所有已经准备好的语句 DropAllPreparedStatements(); /* if in shared memory, delete context. */ - CachedPlanSource* psrc = u_sess->pcache_cxt.ungpc_saved_plan; - CachedPlanSource* next = NULL; + CachedPlanSource *psrc = u_sess->pcache_cxt.ungpc_saved_plan; + CachedPlanSource *next = NULL; while (psrc != NULL) { next = psrc->next_saved; - Assert (!psrc->gpc.status.InShareTable()); + // 断言计划不在共享表中 + Assert(!psrc->gpc.status.InShareTable()); + // 如果计划不是私有计划,则删除 if (!psrc->gpc.status.IsPrivatePlan()) DropCachedPlan(psrc); psrc = next; } + // 清理已保存计划列表中的计划 psrc = u_sess->pcache_cxt.first_saved_plan; while (psrc != NULL) { next = psrc->next_saved; - Assert (!psrc->gpc.status.InShareTable()); + // 断言计划不在共享表中 + Assert(!psrc->gpc.status.InShareTable()); + // 如果计划不是私有计划,则删除 if (!psrc->gpc.status.IsPrivatePlan()) DropCachedPlan(psrc); psrc = next; } } - +/* + * 功能:清理与会话相关的已保存计划 + * + * 参数列表:无 + * + * 说明:此函数用于清理与会话相关的已保存计划。它首先检查是否启用了 GPC(全局计划缓存), + * 如果未启用或者没有需要清理的计划,则直接返回。然后,它减少未命名语句(unnamed statement) + * 的引用计数,如果该语句在共享表中。最后,它清理已保存计划列表和未共享计划列表中的计划, + * 如果这些计划不是私有计划的话。 + */ void GPCCleanUpSessionSavedPlan() { + // 如果未启用 GPC 或没有需要清理的计划,则直接返回 if (!ENABLE_GPC) { return; } - if (u_sess->pcache_cxt.first_saved_plan == NULL && - u_sess->pcache_cxt.unnamed_stmt_psrc == NULL && + if (u_sess->pcache_cxt.first_saved_plan == NULL && u_sess->pcache_cxt.unnamed_stmt_psrc == NULL && u_sess->pcache_cxt.ungpc_saved_plan == NULL) { return; } /* unnamed_stmt_psrc only save shared gpc plan or private plan, * so we only need to sub refcount for shared plan. */ + // 如果未命名语句存在并且在共享表中,则减少引用计数并置为NULL if (u_sess->pcache_cxt.unnamed_stmt_psrc && u_sess->pcache_cxt.unnamed_stmt_psrc->gpc.status.InShareTable()) { u_sess->pcache_cxt.unnamed_stmt_psrc->gpc.status.SubRefCount(); u_sess->pcache_cxt.unnamed_stmt_psrc = NULL; } /* if in shared memory, delete context. */ /* For DN and CN */ - CachedPlanSource* psrc = u_sess->pcache_cxt.first_saved_plan; - CachedPlanSource* next = NULL; + CachedPlanSource *psrc = u_sess->pcache_cxt.first_saved_plan; + CachedPlanSource *next = NULL; u_sess->pcache_cxt.first_saved_plan = NULL; while (psrc != NULL) { next = psrc->next_saved; - Assert (!psrc->gpc.status.InShareTable()); + // 断言计划不在共享表中 + Assert(!psrc->gpc.status.InShareTable()); + // 如果计划不是私有计划,则删除 if (!psrc->gpc.status.IsPrivatePlan()) DropCachedPlan(psrc); psrc = next; } /* For CN */ + // 清理未共享计划列表(For CN) psrc = u_sess->pcache_cxt.ungpc_saved_plan; next = NULL; while (psrc != NULL) { next = psrc->next_saved; - Assert (!psrc->gpc.status.InShareTable()); + // 断言计划不在共享表中 + Assert(!psrc->gpc.status.InShareTable()); + // 如果计划不是私有计划,则删除 if (!psrc->gpc.status.IsPrivatePlan()) DropCachedPlan(psrc); psrc = next; @@ -341,75 +417,128 @@ void GPCCleanUpSessionSavedPlan() } /* incase change shared plan in execute stage, copy stmt into sess */ -List* CopyLocalStmt(const List* stmt_list, const MemoryContext parent_cxt, MemoryContext* plan_context) +/* + * 功能:复制本地语句列表 + * + * 参数列表: + * stmt_list:待复制的本地语句列表 + * parent_cxt:父内存上下文,新创建的内存上下文将以其为父上下文 + * plan_context:用于存储新创建的内存上下文 + * + * 返回值:复制后的本地语句列表 + * + * 说明:此函数用于复制给定的本地语句列表,并将复制后的语句列表存储在新的内存上下文中。 + * 新内存上下文以 parent_cxt 为父上下文创建。复制完成后,函数会将新的内存上下文存储在 + * plan_context 指针所指向的位置。 + */ +List *CopyLocalStmt(const List *stmt_list, const MemoryContext parent_cxt, MemoryContext *plan_context) { - *plan_context = AllocSetContextCreate(parent_cxt, - "CopyedStmt", - ALLOCSET_DEFAULT_MINSIZE, - ALLOCSET_DEFAULT_INITSIZE, + // 创建新的内存上下文,以 parent_cxt 为父上下文 + *plan_context = AllocSetContextCreate(parent_cxt, "CopyedStmt", ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); /* * Copy plan into the new context. */ MemoryContext oldcxt = MemoryContextSwitchTo(*plan_context); - List* stmts = (List*)copyObject(stmt_list); + List *stmts = (List *)copyObject(stmt_list); (void)MemoryContextSwitchTo(oldcxt); return stmts; } /* function for modify SPICacheTable, global procedure plancache */ - +/* + * 功能:计算 SPI 缓存哈希值 + * + * 参数列表: + * key:用于计算哈希值的键值 + * keysize:键值的大小 + * + * 返回值:计算得到的哈希值 + * + * 说明:此函数用于计算 SPI 缓存中的哈希值。它接受一个键值(key)和键值的大小(keysize)作为输入, + * 并使用哈希算法计算出一个哈希值作为返回结果。如果键值为 NULL,函数将返回一个特殊值 INVALID_SPI_KEY。 + */ uint32 SPICacheHashFunc(const void *key, Size keysize) { if (unlikely(key == NULL)) { + // 如果键值为 NULL,返回特殊值 INVALID_SPI_KEY return INVALID_SPI_KEY; } + // 使用哈希算法计算哈希值 uint32 val1 = DatumGetUInt32(hash_any((const unsigned char *)key, (int)keysize)); return val1; } /* add new func */ +/* + * 功能:向 SPI 缓存表中插入条目 + * + * 参数列表: + * key:要插入的条目的键 + * func_oid:要插入的条目关联的函数 OID + * + * 说明:此函数用于向 SPI 缓存表中插入新的条目。它接受一个键(key)和一个函数 OID(func_oid)作为输入, + * 并将它们关联在一起插入到 SPI 缓存表中。如果 SPI 缓存表为空或键为特殊值 INVALID_SPI_KEY,则函数不执行任何操作。 + * 如果已经存在相同键的条目,函数会删除该条目并发出警告信息。 + */ void SPICacheTableInsert(uint32 key, Oid func_oid) { if (unlikely(u_sess->SPI_cxt.SPICacheTable == NULL || key == INVALID_SPI_KEY)) return; + // 检查是否已经存在相同的键 bool found = false; - SPIPlanCacheEnt* hentry = (SPIPlanCacheEnt*)hash_search(u_sess->SPI_cxt.SPICacheTable, - (void*)(&key), HASH_ENTER, &found); + SPIPlanCacheEnt *hentry = + (SPIPlanCacheEnt *)hash_search(u_sess->SPI_cxt.SPICacheTable, (void *)(&key), HASH_ENTER, &found); SPI_GPC_LOG("insert spiplan entry", NULL, func_oid); if (found) { + // 如果已经存在相同键的条目,释放相关的资源并发出警告信息 list_free_ext(hentry->SPIplan_list); hentry->SPIplan_list = NULL; elog(WARNING, "should not has same old function entry in SPICacheTable"); } else { hentry->SPIplan_list = NULL; } + // 关联函数 OID 到条目 hentry->func_oid = func_oid; } /* add plan for spi keep plan */ +/* + * 功能:向 SPI 缓存表中的条目插入 SPI 计划 + * + * 参数列表: + * key:要插入的条目的键 + * spi_plan:要插入的 SPI 计划 + * + * 说明:此函数用于向 SPI 缓存表中的指定条目插入新的 SPI 计划。它接受一个键(key)和一个 SPI 计划(spi_plan) + * 作为输入,将 SPI 计划与相应的条目关联起来。如果 SPI 缓存表为空,函数不执行任何操作。 + * 如果已经存在相同键的条目,函数会将 SPI 计划追加到该条目的计划列表中。 + */ void SPICacheTableInsertPlan(uint32 key, SPIPlanPtr spi_plan) { if (unlikely(u_sess->SPI_cxt.SPICacheTable == NULL)) return; + // 检查是否已经存在相同的键 bool found = false; - Assert (spi_plan->saved); - SPIPlanCacheEnt* hentry = (SPIPlanCacheEnt*)hash_search(u_sess->SPI_cxt.SPICacheTable, - (void*)(&key), HASH_ENTER, &found); + Assert(spi_plan->saved); + SPIPlanCacheEnt *hentry = + (SPIPlanCacheEnt *)hash_search(u_sess->SPI_cxt.SPICacheTable, (void *)(&key), HASH_ENTER, &found); if (found) { #ifdef USE_ASSERT_CHECKING /* cannot has same spiplan in list */ - ListCell* cell = NULL; + // 不能在列表中包含相同的 SPI 计划 + ListCell *cell = NULL; if (hentry->SPIplan_list != NULL) { - foreach(cell, hentry->SPIplan_list) { + foreach (cell, hentry->SPIplan_list) { SPIPlanPtr cur = (SPIPlanPtr)lfirst(cell); - Assert (cur->id != spi_plan->id); - Assert (cur != spi_plan); + Assert(cur->id != spi_plan->id); + Assert(cur != spi_plan); } } #endif SPI_GPC_LOG("insert spiplan into entry", spi_plan, hentry->func_oid); + // 切换内存上下文并将 SPI 计划追加到列表中 MemoryContext old_cxt = MemoryContextSwitchTo(u_sess->SPI_cxt.SPICacheTable->hcxt); hentry->SPIplan_list = lappend(hentry->SPIplan_list, spi_plan); (void)MemoryContextSwitchTo(old_cxt); @@ -419,60 +548,100 @@ void SPICacheTableInsertPlan(uint32 key, SPIPlanPtr spi_plan) } /* only use when delete function, plancache will be freed in SPI_freeplan */ +/* + * 功能:从 SPI 缓存表中删除指定键的条目 + * + * 参数列表: + * key:要删除的条目的键 + * + * 说明:此函数用于从 SPI 缓存表中删除指定键的条目。它接受一个键(key)作为输入, + * 并在 SPI 缓存表中查找匹配的条目,如果找到则删除它。如果 SPI 缓存表为空或未找到匹配的条目, + * 函数不执行任何操作。 + */ void SPIPlanCacheTableDelete(uint32 key) { if (unlikely(u_sess->SPI_cxt.SPICacheTable == NULL)) { return; } + // 查找并删除指定键的条目 bool found = false; - SPIPlanCacheEnt* hentry = (SPIPlanCacheEnt*)hash_search(u_sess->SPI_cxt.SPICacheTable, - (void*)(&key), HASH_REMOVE, &found); + SPIPlanCacheEnt *hentry = + (SPIPlanCacheEnt *)hash_search(u_sess->SPI_cxt.SPICacheTable, (void *)(&key), HASH_REMOVE, &found); if (hentry) { SPI_GPC_LOG("delete spiplan entry", NULL, hentry->func_oid); + // 释放计划列表中的内存 list_free_ext(hentry->SPIplan_list); } } - +/* + * 功能:从 SPI 缓存表中的指定键的条目中删除特定 SPI 计划 + * + * 参数列表: + * key:要查找的条目的键 + * plan:要删除的 SPI 计划 + * + * 说明:此函数用于从 SPI 缓存表中的指定键的条目中删除特定的 SPI 计划。 + * 它接受一个键(key)和一个 SPI 计划(plan)作为输入,并在指定的条目中查找匹配的 SPI 计划, + * 如果找到则删除它。如果 SPI 缓存表为空、未找到匹配的条目或未找到匹配的 SPI 计划, + * 函数不执行任何操作。 + */ void SPIPlanCacheTableDeletePlan(uint32 key, SPIPlanPtr plan) { if (unlikely(u_sess->SPI_cxt.SPICacheTable == NULL)) return; - SPIPlanCacheEnt* entry = SPIPlanCacheTableLookup(key); + // 查找指定键的条目 + SPIPlanCacheEnt *entry = SPIPlanCacheTableLookup(key); if (entry != NULL) { #ifdef USE_ASSERT_CHECKING - ListCell* cell = NULL; - foreach(cell, entry->SPIplan_list) { + ListCell *cell = NULL; + foreach (cell, entry->SPIplan_list) { SPIPlanPtr spiplan = (SPIPlanPtr)lfirst(cell); + // 断言确保要删除的计划与查找到的计划匹配 if (spiplan->id == plan->id) Assert(plan == spiplan); } #endif SPI_GPC_LOG("delete spiplan from entry", plan, entry->func_oid); - entry->SPIplan_list = list_delete_ptr(entry->SPIplan_list, (void*)plan); + // 从列表中删除指定的 SPI 计划 + entry->SPIplan_list = list_delete_ptr(entry->SPIplan_list, (void *)plan); } } - +/* + * 功能:使 SPI 缓存表中指定键的计划无效 + * + * 参数列表: + * key:要查找的条目的键 + * + * 说明:此函数用于使 SPI 缓存表中指定键的计划无效。它接受一个键(key)作为输入, + * 查找匹配的 SPI 缓存表条目,并将所有相关的计划标记为无效。 + * 如果 SPI 缓存表为空或未找到匹配的条目,函数不执行任何操作。 + */ void SPIPlanCacheTableInvalidPlan(uint32 key) { if (unlikely(u_sess->SPI_cxt.SPICacheTable == NULL)) { return; } bool found = false; - SPIPlanCacheEnt* hentry = (SPIPlanCacheEnt*)hash_search(u_sess->SPI_cxt.SPICacheTable, - (void*)(&key), HASH_REMOVE, &found); + // 查找指定键的条目 + SPIPlanCacheEnt *hentry = + (SPIPlanCacheEnt *)hash_search(u_sess->SPI_cxt.SPICacheTable, (void *)(&key), HASH_REMOVE, &found); if (hentry) { SPI_GPC_LOG("invalid each spiplan entry", NULL, hentry->func_oid); - ListCell* cell = NULL; + ListCell *cell = NULL; foreach (cell, hentry->SPIplan_list) { SPIPlanPtr spiplan = (SPIPlanPtr)lfirst(cell); + // 检查计划是否具有 plancache_list if (list_length(spiplan->plancache_list) == 0) continue; - ListCell* cl = NULL; + ListCell *cl = NULL; + // 遍历计划中的每个 CachedPlanSource foreach (cl, spiplan->plancache_list) { - CachedPlanSource* plansource = (CachedPlanSource*)lfirst(cl); + CachedPlanSource *plansource = (CachedPlanSource *)lfirst(cl); + // 如果计划在共享表中,将其状态标记为无效 if (plansource->gpc.status.InShareTable()) { plansource->gpc.status.SetStatus(GPC_INVALID); } else { + // 否则,将计划标记为无效 plansource->is_valid = false; if (plansource->gplan) plansource->gplan->is_valid = false; @@ -481,7 +650,13 @@ void SPIPlanCacheTableInvalidPlan(uint32 key) } } } - +/* + * 功能:初始化 SPI 计划缓存表 + * + * 说明:此函数用于初始化 SPI 计划缓存表。SPI 计划缓存表用于存储与 SQL 函数相关的计划, + * 其中每个计划都与一个唯一的键关联。在初始化过程中,函数会设置合适的哈希控制结构(ctl), + * 并创建哈希表来存储计划缓存条目。 + */ void SPICacheTableInit() { HASHCTL ctl; @@ -496,236 +671,355 @@ void SPICacheTableInit() ctl.entrysize = sizeof(SPIPlanCacheEnt); ctl.hash = uint32_hash; ctl.hcxt = u_sess->cache_mem_cxt; + // 创建 SPI 计划缓存表 u_sess->SPI_cxt.SPICacheTable = hash_create("SPIPlanCacheTable", func_per_user, &ctl, HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT); SPI_GPC_LOG("init spiplan cache table", NULL, 0); } - -SPIPlanCacheEnt* SPIPlanCacheTableLookup(uint32 key) +/* + * 功能:查找 SPI 计划缓存表中的条目 + * + * 说明:此函数用于在 SPI 计划缓存表中查找与给定键相关的 SPIPlanCacheEnt 条目。 + * 如果找到匹配的条目,则返回该条目的指针;如果未找到匹配的条目,则返回 NULL。 + */ +SPIPlanCacheEnt *SPIPlanCacheTableLookup(uint32 key) { if (unlikely(u_sess->SPI_cxt.SPICacheTable == NULL)) return NULL; bool found = false; - SPIPlanCacheEnt* hentry = (SPIPlanCacheEnt*)hash_search( - u_sess->SPI_cxt.SPICacheTable, (void*)(&key), HASH_FIND, &found); + // 使用哈希查找在 SPI 计划缓存表中查找匹配的条目 + SPIPlanCacheEnt *hentry = + (SPIPlanCacheEnt *)hash_search(u_sess->SPI_cxt.SPICacheTable, (void *)(&key), HASH_FIND, &found); if (found) - return hentry; + return hentry; // 如果找到匹配的条目,则返回该条目的指针 else - return NULL; + return NULL; // 如果未找到匹配的条目,则返回 NULL } /* set unique id in PLpgSQL_expr for gpc */ -static void set_id_stmt(PLpgSQL_stmt* stmt, uint32* unique_id); -static void set_id_block(PLpgSQL_stmt_block* block, uint32* unique_id); -static void set_id_if(PLpgSQL_stmt_if* stmt, uint32* unique_id); -static void set_id_case(PLpgSQL_stmt_case* stmt, uint32* unique_id); -static void set_id_return_query(PLpgSQL_stmt_return_query* stmt, uint32* unique_id); -static void set_id_raise(PLpgSQL_stmt_raise* stmt, uint32* unique_id); -static void set_id_dynexecute(PLpgSQL_stmt_dynexecute* stmt, uint32* unique_id); -static void set_id_dynfors(PLpgSQL_stmt_dynfors* stmt, uint32* unique_id); -static void set_id_open(PLpgSQL_stmt_open* stmt, uint32* unique_id); -static void set_id_expr(PLpgSQL_expr* expr, uint32* unique_id); -static void set_id_stmts(List* stmts, uint32* unique_id); - -static void set_id_expr(PLpgSQL_expr* expr, uint32* unique_id) +static void set_id_stmt(PLpgSQL_stmt *stmt, uint32 *unique_id); +static void set_id_block(PLpgSQL_stmt_block *block, uint32 *unique_id); +static void set_id_if(PLpgSQL_stmt_if *stmt, uint32 *unique_id); +static void set_id_case(PLpgSQL_stmt_case *stmt, uint32 *unique_id); +static void set_id_return_query(PLpgSQL_stmt_return_query *stmt, uint32 *unique_id); +static void set_id_raise(PLpgSQL_stmt_raise *stmt, uint32 *unique_id); +static void set_id_dynexecute(PLpgSQL_stmt_dynexecute *stmt, uint32 *unique_id); +static void set_id_dynfors(PLpgSQL_stmt_dynfors *stmt, uint32 *unique_id); +static void set_id_open(PLpgSQL_stmt_open *stmt, uint32 *unique_id); +static void set_id_expr(PLpgSQL_expr *expr, uint32 *unique_id); +static void set_id_stmts(List *stmts, uint32 *unique_id); +/* + * 功能:为 PL/pgSQL 表达式设置唯一标识符 + * + * 参数列表: + * expr:PL/pgSQL 表达式 + * unique_id:用于分配唯一标识符的计数器 + */ +static void set_id_expr(PLpgSQL_expr *expr, uint32 *unique_id) { + // 检查输入的表达式是否为 NULL,如果是 NULL,则无需进行设置 if (expr == NULL) return; + + // 为表达式设置唯一标识符,通过递增 unique_id 指针的值 expr->idx = ++(*unique_id); } -static void set_id_stmts(List* stmts, uint32* unique_id) +/* + * 功能:为 PL/pgSQL 语句列表中的每个语句设置唯一标识符 + * + * 参数列表: + * stmts:PL/pgSQL 语句列表 + * unique_id:用于分配唯一标识符的计数器 + */ +static void set_id_stmts(List *stmts, uint32 *unique_id) { - ListCell* s = NULL; + ListCell *s = NULL; + // 遍历语句列表 foreach (s, stmts) { - set_id_stmt((PLpgSQL_stmt*)lfirst(s), unique_id); + // 为当前语句设置唯一标识符 + set_id_stmt((PLpgSQL_stmt *)lfirst(s), unique_id); } } -static void set_id_block(PLpgSQL_stmt_block* block, uint32* unique_id) +/* + * 功能:为 PL/pgSQL 块语句设置唯一标识符 + * + * 参数列表: + * block:PL/pgSQL 块语句 + * unique_id:用于分配唯一标识符的计数器 + */ +static void set_id_block(PLpgSQL_stmt_block *block, uint32 *unique_id) { + // 为块语句中的语句列表设置唯一标识符 set_id_stmts(block->body, unique_id); + // 如果存在异常处理块 if (block->exceptions != NULL) { - ListCell* e = NULL; + ListCell *e = NULL; + // 遍历异常处理块中的每个异常 foreach (e, block->exceptions->exc_list) { - PLpgSQL_exception* exc = (PLpgSQL_exception*)lfirst(e); + PLpgSQL_exception *exc = (PLpgSQL_exception *)lfirst(e); + // 为异常处理中的语句列表设置唯一标识符 set_id_stmts(exc->action, unique_id); } } } - -static void set_id_if(PLpgSQL_stmt_if* stmt, uint32* unique_id) +/* + * 功能:为 PL/pgSQL IF 语句设置唯一标识符 + * + * 参数列表: + * stmt:PL/pgSQL IF 语句 + * unique_id:用于分配唯一标识符的计数器 + */ +static void set_id_if(PLpgSQL_stmt_if *stmt, uint32 *unique_id) { - ListCell* l = NULL; + ListCell *l = NULL; + // 为 IF 语句的条件表达式设置唯一标识符 set_id_expr(stmt->cond, unique_id); + // 为 IF 语句的主体语句设置唯一标识符 set_id_stmts(stmt->then_body, unique_id); + // 遍历 ELSE IF 子句列表 foreach (l, stmt->elsif_list) { - PLpgSQL_if_elsif* elif = (PLpgSQL_if_elsif*)lfirst(l); + PLpgSQL_if_elsif *elif = (PLpgSQL_if_elsif *)lfirst(l); + // 为 ELSE IF 子句的条件表达式设置唯一标识符 set_id_expr(elif->cond, unique_id); + // 为 ELSE IF 子句的语句列表设置唯一标识符 set_id_stmts(elif->stmts, unique_id); } + // 为 IF 语句的 ELSE 子句设置唯一标识符 set_id_stmts(stmt->else_body, unique_id); } -static void set_id_case(PLpgSQL_stmt_case* stmt, uint32* unique_id) +/* + * 功能:为 PL/pgSQL CASE 语句设置唯一标识符 + * + * 参数列表: + * stmt:PL/pgSQL CASE 语句 + * unique_id:用于分配唯一标识符的计数器 + */ +static void set_id_case(PLpgSQL_stmt_case *stmt, uint32 *unique_id) { - ListCell* l = NULL; + ListCell *l = NULL; + // 为 IF 语句的条件表达式设置唯一标识符 set_id_expr(stmt->t_expr, unique_id); + // 遍历 CASE 语句的 WHEN 子句列表 foreach (l, stmt->case_when_list) { - PLpgSQL_case_when* cwt = (PLpgSQL_case_when*)lfirst(l); + PLpgSQL_case_when *cwt = (PLpgSQL_case_when *)lfirst(l); + // 为 WHEN 子句的条件表达式设置唯一标识符 set_id_expr(cwt->expr, unique_id); + // 为 WHEN 子句的语句列表设置唯一标识符 set_id_stmts(cwt->stmts, unique_id); } + // 为 CASE 语句的 ELSE 子句设置唯一标识符 set_id_stmts(stmt->else_stmts, unique_id); } - -static void set_id_open(PLpgSQL_stmt_open* stmt, uint32* unique_id) +/* + * 功能:为 PL/pgSQL OPEN 语句设置唯一标识符 + * + * 参数列表: + * stmt:PL/pgSQL OPEN 语句 + * unique_id:用于分配唯一标识符的计数器 + */ +static void set_id_open(PLpgSQL_stmt_open *stmt, uint32 *unique_id) { - ListCell* lc = NULL; + ListCell *lc = NULL; + // 为 OPEN 语句的参数查询表达式设置唯一标识符 set_id_expr(stmt->argquery, unique_id); + // 为 OPEN 语句的查询表达式设置唯一标识符 set_id_expr(stmt->query, unique_id); + // 为 OPEN 语句的动态查询表达式设置唯一标识符 set_id_expr(stmt->dynquery, unique_id); + // 遍历 OPEN 语句的参数列表 foreach (lc, stmt->params) { - set_id_expr((PLpgSQL_expr*)lfirst(lc), unique_id); + // 为参数表达式设置唯一标识符 + set_id_expr((PLpgSQL_expr *)lfirst(lc), unique_id); } } -static void set_id_return_query(PLpgSQL_stmt_return_query* stmt, uint32* unique_id) +/* + * 功能:为 PL/pgSQL RETURN QUERY 语句设置唯一标识符 + * + * 参数列表: + * stmt:PL/pgSQL RETURN QUERY 语句 + * unique_id:用于分配唯一标识符的计数器 + */ +static void set_id_return_query(PLpgSQL_stmt_return_query *stmt, uint32 *unique_id) { - ListCell* lc = NULL; + ListCell *lc = NULL; + // 为 RETURN QUERY 语句的查询表达式设置唯一标识符 set_id_expr(stmt->query, unique_id); + // 为 RETURN QUERY 语句的动态查询表达式设置唯一标识符 set_id_expr(stmt->dynquery, unique_id); + // 遍历 RETURN QUERY 语句的参数列表 foreach (lc, stmt->params) { - set_id_expr((PLpgSQL_expr*)lfirst(lc), unique_id); + // 为参数表达式设置唯一标识符 + set_id_expr((PLpgSQL_expr *)lfirst(lc), unique_id); } } -static void set_id_raise(PLpgSQL_stmt_raise* stmt, uint32* unique_id) +/* + * 功能:为 PL/pgSQL RAISE 语句设置唯一标识符 + * + * 参数列表: + * stmt:PL/pgSQL RAISE 语句 + * unique_id:用于分配唯一标识符的计数器 + */ +static void set_id_raise(PLpgSQL_stmt_raise *stmt, uint32 *unique_id) { - ListCell* lc = NULL; + ListCell *lc = NULL; + // 遍历 RAISE 语句的参数列表 foreach (lc, stmt->params) { - set_id_expr((PLpgSQL_expr*)lfirst(lc), unique_id); + // 为参数表达式设置唯一标识符 + set_id_expr((PLpgSQL_expr *)lfirst(lc), unique_id); } + // 遍历 RAISE 语句的选项列表 foreach (lc, stmt->options) { - PLpgSQL_raise_option* opt = (PLpgSQL_raise_option*)lfirst(lc); + PLpgSQL_raise_option *opt = (PLpgSQL_raise_option *)lfirst(lc); + // 为选项表达式设置唯一标识符 set_id_expr(opt->expr, unique_id); } } - -static void set_id_dynexecute(PLpgSQL_stmt_dynexecute* stmt, uint32* unique_id) +/* + * 功能:为 PL/pgSQL DYNEXECUTE 语句设置唯一标识符 + * + * 参数列表: + * stmt:PL/pgSQL DYNEXECUTE 语句 + * unique_id:用于分配唯一标识符的计数器 + */ +static void set_id_dynexecute(PLpgSQL_stmt_dynexecute *stmt, uint32 *unique_id) { - ListCell* lc = NULL; + ListCell *lc = NULL; + // 为 DYNEXECUTE 语句的查询表达式设置唯一标识符 set_id_expr(stmt->query, unique_id); + // 遍历 DYNEXECUTE 语句的参数列表 foreach (lc, stmt->params) { - set_id_expr((PLpgSQL_expr*)lfirst(lc), unique_id); + // 为参数表达式设置唯一标识符 + set_id_expr((PLpgSQL_expr *)lfirst(lc), unique_id); } } -static void set_id_dynfors(PLpgSQL_stmt_dynfors* stmt, uint32* unique_id) +/* + * 功能:为 PL/pgSQL DYNFORS 语句设置唯一标识符 + * + * 参数列表: + * stmt:PL/pgSQL DYNFORS 语句 + * unique_id:用于分配唯一标识符的计数器 + */ +static void set_id_dynfors(PLpgSQL_stmt_dynfors *stmt, uint32 *unique_id) { - ListCell* lc = NULL; + ListCell *lc = NULL; + // 为 DYNFORS 语句的主体语句设置唯一标识符 set_id_stmts(stmt->body, unique_id); + // 为 DYNFORS 语句的查询表达式设置唯一标识符 set_id_expr(stmt->query, unique_id); + // 遍历 DYNFORS 语句的参数列表 foreach (lc, stmt->params) { - set_id_expr((PLpgSQL_expr*)lfirst(lc), unique_id); + // 为参数表达式设置唯一标识符 + set_id_expr((PLpgSQL_expr *)lfirst(lc), unique_id); } } - -static void set_id_stmt(PLpgSQL_stmt* stmt, uint32* unique_id) +/* + * 功能:为 PL/pgSQL 语句设置唯一标识符 + * + * 参数列表: + * stmt:PL/pgSQL 语句 + * unique_id:用于分配唯一标识符的计数器 + */ +static void set_id_stmt(PLpgSQL_stmt *stmt, uint32 *unique_id) { switch ((enum PLpgSQL_stmt_types)stmt->cmd_type) { case PLPGSQL_STMT_BLOCK: - set_id_block((PLpgSQL_stmt_block*)stmt, unique_id); + set_id_block((PLpgSQL_stmt_block *)stmt, unique_id); break; case PLPGSQL_STMT_ASSIGN: - set_id_expr(((PLpgSQL_stmt_assign*)stmt)->expr, unique_id); + set_id_expr(((PLpgSQL_stmt_assign *)stmt)->expr, unique_id); break; case PLPGSQL_STMT_IF: - set_id_if((PLpgSQL_stmt_if*)stmt, unique_id); + set_id_if((PLpgSQL_stmt_if *)stmt, unique_id); break; case PLPGSQL_STMT_CASE: - set_id_case((PLpgSQL_stmt_case*)stmt, unique_id); + set_id_case((PLpgSQL_stmt_case *)stmt, unique_id); break; case PLPGSQL_STMT_LOOP: - set_id_stmts(((PLpgSQL_stmt_loop*)stmt)->body, unique_id); + set_id_stmts(((PLpgSQL_stmt_loop *)stmt)->body, unique_id); break; case PLPGSQL_STMT_WHILE: - set_id_expr(((PLpgSQL_stmt_while*)stmt)->cond, unique_id); - set_id_stmts(((PLpgSQL_stmt_while*)stmt)->body, unique_id); + set_id_expr(((PLpgSQL_stmt_while *)stmt)->cond, unique_id); + set_id_stmts(((PLpgSQL_stmt_while *)stmt)->body, unique_id); break; case PLPGSQL_STMT_FORI: - set_id_expr(((PLpgSQL_stmt_fori*)stmt)->lower, unique_id); - set_id_expr(((PLpgSQL_stmt_fori*)stmt)->upper, unique_id); - set_id_expr(((PLpgSQL_stmt_fori*)stmt)->step, unique_id); - set_id_stmts(((PLpgSQL_stmt_fori*)stmt)->body, unique_id); + set_id_expr(((PLpgSQL_stmt_fori *)stmt)->lower, unique_id); + set_id_expr(((PLpgSQL_stmt_fori *)stmt)->upper, unique_id); + set_id_expr(((PLpgSQL_stmt_fori *)stmt)->step, unique_id); + set_id_stmts(((PLpgSQL_stmt_fori *)stmt)->body, unique_id); break; case PLPGSQL_STMT_FORS: - set_id_stmts(((PLpgSQL_stmt_fors*)stmt)->body, unique_id); - set_id_expr(((PLpgSQL_stmt_fors*)stmt)->query, unique_id); + set_id_stmts(((PLpgSQL_stmt_fors *)stmt)->body, unique_id); + set_id_expr(((PLpgSQL_stmt_fors *)stmt)->query, unique_id); break; case PLPGSQL_STMT_FORC: - set_id_stmts(((PLpgSQL_stmt_forc*)stmt)->body, unique_id); - set_id_expr(((PLpgSQL_stmt_forc*)stmt)->argquery, unique_id); + set_id_stmts(((PLpgSQL_stmt_forc *)stmt)->body, unique_id); + set_id_expr(((PLpgSQL_stmt_forc *)stmt)->argquery, unique_id); break; case PLPGSQL_STMT_FOREACH_A: - set_id_expr(((PLpgSQL_stmt_foreach_a*)stmt)->expr, unique_id); - set_id_stmts(((PLpgSQL_stmt_foreach_a*)stmt)->body, unique_id); + set_id_expr(((PLpgSQL_stmt_foreach_a *)stmt)->expr, unique_id); + set_id_stmts(((PLpgSQL_stmt_foreach_a *)stmt)->body, unique_id); break; case PLPGSQL_STMT_EXIT: - set_id_expr(((PLpgSQL_stmt_exit*)stmt)->cond, unique_id); + set_id_expr(((PLpgSQL_stmt_exit *)stmt)->cond, unique_id); break; case PLPGSQL_STMT_RETURN: - set_id_expr(((PLpgSQL_stmt_return*)stmt)->expr, unique_id); + set_id_expr(((PLpgSQL_stmt_return *)stmt)->expr, unique_id); break; case PLPGSQL_STMT_RETURN_NEXT: - set_id_expr(((PLpgSQL_stmt_return_next*)stmt)->expr, unique_id); + set_id_expr(((PLpgSQL_stmt_return_next *)stmt)->expr, unique_id); break; case PLPGSQL_STMT_RETURN_QUERY: - set_id_return_query((PLpgSQL_stmt_return_query*)stmt, unique_id); + set_id_return_query((PLpgSQL_stmt_return_query *)stmt, unique_id); break; case PLPGSQL_STMT_RAISE: - set_id_raise((PLpgSQL_stmt_raise*)stmt, unique_id); + set_id_raise((PLpgSQL_stmt_raise *)stmt, unique_id); break; case PLPGSQL_STMT_EXECSQL: - set_id_expr(((PLpgSQL_stmt_execsql*)stmt)->sqlstmt, unique_id); + set_id_expr(((PLpgSQL_stmt_execsql *)stmt)->sqlstmt, unique_id); break; case PLPGSQL_STMT_DYNEXECUTE: - set_id_dynexecute((PLpgSQL_stmt_dynexecute*)stmt, unique_id); + set_id_dynexecute((PLpgSQL_stmt_dynexecute *)stmt, unique_id); break; case PLPGSQL_STMT_DYNFORS: - set_id_dynfors((PLpgSQL_stmt_dynfors*)stmt, unique_id); + set_id_dynfors((PLpgSQL_stmt_dynfors *)stmt, unique_id); break; case PLPGSQL_STMT_OPEN: - set_id_open((PLpgSQL_stmt_open*)stmt, unique_id); + set_id_open((PLpgSQL_stmt_open *)stmt, unique_id); break; case PLPGSQL_STMT_FETCH: - set_id_expr(((PLpgSQL_stmt_fetch*)stmt)->expr, unique_id); + set_id_expr(((PLpgSQL_stmt_fetch *)stmt)->expr, unique_id); break; case PLPGSQL_STMT_PERFORM: - set_id_expr(((PLpgSQL_stmt_perform*)stmt)->expr, unique_id); + set_id_expr(((PLpgSQL_stmt_perform *)stmt)->expr, unique_id); break; default: break; } } -void set_func_expr_unique_id(PLpgSQL_function* func) +void set_func_expr_unique_id(PLpgSQL_function *func) { uint32 unique_id = 0; int i; for (i = 0; i < func->ndatums; i++) { - PLpgSQL_datum* d = func->datums[i]; + PLpgSQL_datum *d = func->datums[i]; switch (d->dtype) { case PLPGSQL_DTYPE_VAR: { - PLpgSQL_var* var = (PLpgSQL_var*)d; + PLpgSQL_var *var = (PLpgSQL_var *)d; set_id_expr(var->default_val, &unique_id); set_id_expr(var->cursor_explicit_expr, &unique_id); } break; case PLPGSQL_DTYPE_ARRAYELEM: - set_id_expr(((PLpgSQL_arrayelem*)d)->subscript, &unique_id); + set_id_expr(((PLpgSQL_arrayelem *)d)->subscript, &unique_id); break; case PLPGSQL_DTYPE_ROW: case PLPGSQL_DTYPE_RECORD: { - PLpgSQL_row* row = (PLpgSQL_row*)d; + PLpgSQL_row *row = (PLpgSQL_row *)d; set_id_expr(row->default_val, &unique_id); } break; default: @@ -736,14 +1030,21 @@ void set_func_expr_unique_id(PLpgSQL_function* func) set_id_block(func->action, &unique_id); } } - +/* + * 功能:判断是否启用 GPC 解析 + * + * 参数列表: + * node:要解析的节点 + * + * 返回值:如果启用 GPC 解析,返回 true,否则返回 false + */ bool SPIParseEnableGPC(const Node *node) { if (node == NULL) return false; switch (nodeTag(node)) { case T_SelectStmt: - if (((SelectStmt*)node)->intoClause) + if (((SelectStmt *)node)->intoClause) return false; /* fall through */ case T_MergeStmt: @@ -760,4 +1061,3 @@ bool SPIParseEnableGPC(const Node *node) /* keep compiler quite */ return false; } - diff --git a/src/gausskernel/process/job/gs_job_calendar.cpp b/src/gausskernel/process/job/gs_job_calendar.cpp index a9a8f85a0..e88508f14 100644 --- a/src/gausskernel/process/job/gs_job_calendar.cpp +++ b/src/gausskernel/process/job/gs_job_calendar.cpp @@ -23,10 +23,10 @@ * ------------------------------------------------------------------------- */ - #include "postgres.h" - #include "miscadmin.h" - #include "utils/builtins.h" - #include "utils/dbe_scheduler.h" +#include "postgres.h" +#include "miscadmin.h" +#include "utils/builtins.h" +#include "utils/dbe_scheduler.h" /* * repeat_interval = frequency_clause @@ -53,7 +53,7 @@ */ /* Initialize calendaring fields */ -static bool IsLegalIntervalStr(const char* str, bool numeric_only = false); +static bool IsLegalIntervalStr(const char *str, bool numeric_only = false); static char *get_calendar_clause_val(char **tokens, const char *clause, bool numeric_only = false); static char **tokenize_str(char *src, const char *delims, int fields); static int validate_field_names(char **toks); @@ -66,9 +66,9 @@ static bool get_calendar_freqency(Calendar calendar, char **tokens); static void get_calendar_n_interval(Calendar calendar, char **tokens); static char *get_calendar_bymonth_val(Calendar calendar, char **tokens); static void get_calendar_bymonth(Calendar calendar, char **tokens); -static void get_calendar_byweekno(Calendar calendar, char **tokens); /* unsupported */ +static void get_calendar_byweekno(Calendar calendar, char **tokens); /* unsupported */ static void get_calendar_byyearday(Calendar calendar, char **tokens); /* unsupported */ -static void get_calendar_bydate(Calendar calendar, char **tokens); /* unsupported */ +static void get_calendar_bydate(Calendar calendar, char **tokens); /* unsupported */ static char *get_calendar_bymonthday_val(Calendar calendar, char **tokens); static void get_calendar_bymonthday(Calendar calendar, char **tokens); static void get_calendar_byday(Calendar calendar, char **tokens); /* unsupported */ @@ -97,10 +97,10 @@ static bool find_nearest_calendar_time(Calendar calendar, TimestampTz *timeline, /* Calendaring Interval Calculator */ static void prepare_calendar_period(Calendar calendar, TimestampTz base_date, TimestampTz *timeline); static void evaluate_calendar_bymonth(Calendar calendar, TimestampTz *timeline, int *cnt); -static void evaluate_calendar_byweekno(Calendar calendar, TimestampTz *timeline, int *cnt); /* unsupported */ +static void evaluate_calendar_byweekno(Calendar calendar, TimestampTz *timeline, int *cnt); /* unsupported */ static void evaluate_calendar_byyearday(Calendar calendar, TimestampTz *timeline, int *cnt); /* unsupported */ static void evaluate_calendar_bymonthday(Calendar calendar, TimestampTz *timeline, int *cnt); -static void evaluate_calendar_byhour(Calendar calendar, TimestampTz *timeline, int *cnt); /* sub_timeline */ +static void evaluate_calendar_byhour(Calendar calendar, TimestampTz *timeline, int *cnt); /* sub_timeline */ static void evaluate_calendar_byminute(Calendar calendar, TimestampTz *timeline, int *cnt); /* sub_timeline */ static void evaluate_calendar_bysecond(Calendar calendar, TimestampTz *timeline, int *cnt); /* sub_timeline */ static bool evaluate_calendar_period(Calendar calendar, TimestampTz *timeline, TimestampTz *sub_timeline, @@ -115,7 +115,7 @@ static TimestampTz evaluate_calendar_interval(Calendar calendar, TimestampTz sta * @return true legal * @return false illegal */ -static bool IsLegalIntervalStr(const char* str, bool numeric_only) +static bool IsLegalIntervalStr(const char *str, bool numeric_only) { size_t NBytes = (unsigned int)strlen(str); if (NBytes > (MAX_CALENDAR_FIELD_LEN)) { @@ -141,7 +141,6 @@ static bool IsLegalIntervalStr(const char* str, bool numeric_only) return true; } - /* * @brief get_calendar_clause * Get calendar clause and return its value; @@ -154,7 +153,7 @@ static char *get_calendar_clause_val(char **tokens, const char *clause, bool num char *val = NULL; for (int i = 0; i < MAX_CALENDAR_FIELDS; i += 2) { if (tokens[i] != NULL && pg_strcasecmp(tokens[i], clause) == 0) { - val = tokens[i + 1]; /* get clause's value */ + val = tokens[i + 1]; /* get clause's value */ break; } } @@ -163,10 +162,10 @@ static char *get_calendar_clause_val(char **tokens, const char *clause, bool num } if (!IsLegalIntervalStr(val, numeric_only)) { - ereport(ERROR, (errmodule(MOD_JOB), errcode(ERRCODE_OPERATE_FAILED), - errmsg("Fail to evaluate calendaring string."), - errdetail("Invalid value string for clause \'%s\'", clause), errcause("N/A"), - erraction("Please modify the calendaring string."))); + ereport(ERROR, + (errmodule(MOD_JOB), errcode(ERRCODE_OPERATE_FAILED), errmsg("Fail to evaluate calendaring string."), + errdetail("Invalid value string for clause \'%s\'", clause), errcause("N/A"), + erraction("Please modify the calendaring string."))); } return val; } @@ -205,10 +204,10 @@ static bool get_calendar_freqency(Calendar calendar, char **tokens) calendar->frequency = SECONDLY; } else { pfree_ext(tokens); - ereport(ERROR, (errmodule(MOD_JOB), errcode(ERRCODE_OPERATE_FAILED), - errmsg("Fail to evaluate calendaring string."), - errdetail("Invalid frequency value \'%s\'.", val), errcause("N/A"), - erraction("Please modify the calendaring string."))); + ereport(ERROR, + (errmodule(MOD_JOB), errcode(ERRCODE_OPERATE_FAILED), errmsg("Fail to evaluate calendaring string."), + errdetail("Invalid frequency value \'%s\'.", val), errcause("N/A"), + erraction("Please modify the calendaring string."))); } return true; } @@ -223,7 +222,7 @@ static bool get_calendar_freqency(Calendar calendar, char **tokens) */ static void get_calendar_n_interval(Calendar calendar, char **tokens) { - calendar->interval = 1; /* we ALWAYS set interval to 1 */ + calendar->interval = 1; /* we ALWAYS set interval to 1 */ char *val = get_calendar_clause_val(tokens, "interval", true); if (val == NULL) { return; @@ -232,10 +231,10 @@ static void get_calendar_n_interval(Calendar calendar, char **tokens) int num = atoi(val); if (num < 1 || num > MAX_CALENDAR_INTERVAL_NUM) { pfree_ext(tokens); - ereport(ERROR, (errmodule(MOD_JOB), errcode(ERRCODE_OPERATE_FAILED), - errmsg("Fail to evaluate calendaring string."), - errdetail("Interval \'%d\' not in range [1, 99].", num), errcause("N/A"), - erraction("Please modify the calendaring string."))); + ereport(ERROR, + (errmodule(MOD_JOB), errcode(ERRCODE_OPERATE_FAILED), errmsg("Fail to evaluate calendaring string."), + errdetail("Interval \'%d\' not in range [1, 99].", num), errcause("N/A"), + erraction("Please modify the calendaring string."))); } calendar->interval = num; } @@ -306,10 +305,10 @@ static void get_calendar_bymonth(Calendar calendar, char **tokens) } } if (month == 0) { - ereport(ERROR, (errmodule(MOD_JOB), errcode(ERRCODE_OPERATE_FAILED), - errmsg("Fail to evaluate calendaring string."), - errdetail("Invalid month token \'%s\'.", val), errcause("N/A"), - erraction("Please modify the calendaring string."))); + ereport(ERROR, + (errmodule(MOD_JOB), errcode(ERRCODE_OPERATE_FAILED), + errmsg("Fail to evaluate calendaring string."), errdetail("Invalid month token \'%s\'.", val), + errcause("N/A"), erraction("Please modify the calendaring string."))); } } else { /* numeric in */ @@ -352,10 +351,10 @@ static void get_calendar_byweekno(Calendar calendar, char **tokens) { char *val = get_calendar_clause_val(tokens, "byweekno", true); if (val != NULL) { - ereport(ERROR, (errmodule(MOD_JOB), errcode(ERRCODE_OPERATE_FAILED), - errmsg("Fail to evaluate calendaring string."), - errdetail("BYWEEKNO clause is currently unsupported."), errcause("N/A"), - erraction("Please modify the calendaring string."))); + ereport(ERROR, + (errmodule(MOD_JOB), errcode(ERRCODE_OPERATE_FAILED), errmsg("Fail to evaluate calendaring string."), + errdetail("BYWEEKNO clause is currently unsupported."), errcause("N/A"), + erraction("Please modify the calendaring string."))); } } @@ -373,10 +372,10 @@ static void get_calendar_byyearday(Calendar calendar, char **tokens) { char *val = get_calendar_clause_val(tokens, "byyearday", true); if (val != NULL) { - ereport(ERROR, (errmodule(MOD_JOB), errcode(ERRCODE_OPERATE_FAILED), - errmsg("Fail to evaluate calendaring string."), - errdetail("BYYEARDAY clause is currently unsupported."), errcause("N/A"), - erraction("Please modify the calendaring string."))); + ereport(ERROR, + (errmodule(MOD_JOB), errcode(ERRCODE_OPERATE_FAILED), errmsg("Fail to evaluate calendaring string."), + errdetail("BYYEARDAY clause is currently unsupported."), errcause("N/A"), + erraction("Please modify the calendaring string."))); } } @@ -393,16 +392,26 @@ static void get_calendar_bydate(Calendar calendar, char **tokens) { char *val = get_calendar_clause_val(tokens, "byweekno", true); if (val != NULL) { - ereport(ERROR, (errmodule(MOD_JOB), errcode(ERRCODE_OPERATE_FAILED), - errmsg("Fail to evaluate calendaring string."), - errdetail("BYDATE clause is currently unsupported."), errcause("N/A"), - erraction("Please modify the calendaring string."))); + ereport(ERROR, + (errmodule(MOD_JOB), errcode(ERRCODE_OPERATE_FAILED), errmsg("Fail to evaluate calendaring string."), + errdetail("BYDATE clause is currently unsupported."), errcause("N/A"), + erraction("Please modify the calendaring string."))); } } - +/* + * 功能:获取日历规则中的 "byhour" 子句的值 + * + * 参数列表: + * calendar:日历规则 + * tokens:日历规则中的标记 + * + * 返回值: + * 如果存在 "byhour" 子句,则返回其值;否则返回 NULL。 + */ static char *get_calendar_bymonthday_val(Calendar calendar, char **tokens) { + // 获取 "byhour" 子句的值 char *val = get_calendar_clause_val(tokens, "bymonthday", true); if (val != NULL) { /* apply bymonthday rule if bymonthday is specified */ @@ -421,10 +430,10 @@ static char *get_calendar_bymonthday_val(Calendar calendar, char **tokens) calendar->monthday_len = 0; } /* We cannot optimize any further since monthday/yearday are not perfectly periodic */ + // 没有指定 "byhour" 子句,返回 NULL return NULL; } - /* * @brief get_calendar_bymonth * Get bymonthday_clause. @@ -449,10 +458,10 @@ static void get_calendar_bymonthday(Calendar calendar, char **tokens) while (tok != NULL) { int monthday = atoi(tok); if (monthday < -(DAYS_PER_MONTH + 1) || monthday > (DAYS_PER_MONTH + 1) || monthday == 0) { - ereport(ERROR, (errmodule(MOD_JOB), errcode(ERRCODE_OPERATE_FAILED), - errmsg("Fail to evaluate calendaring string."), - errdetail("Invalid monthday \'%d\'.", monthday), errcause("N/A"), - erraction("Please modify the calendaring string."))); + ereport(ERROR, + (errmodule(MOD_JOB), errcode(ERRCODE_OPERATE_FAILED), + errmsg("Fail to evaluate calendaring string."), errdetail("Invalid monthday \'%d\'.", monthday), + errcause("N/A"), erraction("Please modify the calendaring string."))); } tok = strtok_s(NULL, ",", &context); @@ -505,14 +514,13 @@ static void get_calendar_byday(Calendar calendar, char **tokens) { char *val = get_calendar_clause_val(tokens, "byday", true); if (val != NULL) { - ereport(ERROR, (errmodule(MOD_JOB), errcode(ERRCODE_OPERATE_FAILED), - errmsg("Fail to evaluate calendaring string."), - errdetail("BYDAY clause is currently unsupported."), errcause("N/A"), - erraction("Please modify the calendaring string."))); + ereport(ERROR, + (errmodule(MOD_JOB), errcode(ERRCODE_OPERATE_FAILED), errmsg("Fail to evaluate calendaring string."), + errdetail("BYDAY clause is currently unsupported."), errcause("N/A"), + erraction("Please modify the calendaring string."))); } } - static char *get_calendar_byhour_val(Calendar calendar, char **tokens) { char *val = get_calendar_clause_val(tokens, "byhour", true); @@ -568,10 +576,10 @@ static void get_calendar_byhour(Calendar calendar, char **tokens) while (tok != NULL) { int hour = atoi(tok); if (hour < 0 || hour >= HOURS_PER_DAY) { - ereport(ERROR, (errmodule(MOD_JOB), errcode(ERRCODE_OPERATE_FAILED), - errmsg("Fail to evaluate calendaring string."), - errdetail("Invalid time \'%d\' o\' clock.", hour), errcause("N/A"), - erraction("Please modify the calendaring string."))); + ereport(ERROR, + (errmodule(MOD_JOB), errcode(ERRCODE_OPERATE_FAILED), + errmsg("Fail to evaluate calendaring string."), errdetail("Invalid time \'%d\' o\' clock.", hour), + errcause("N/A"), erraction("Please modify the calendaring string."))); } tok = strtok_s(NULL, ",", &context); @@ -590,9 +598,19 @@ static void get_calendar_byhour(Calendar calendar, char **tokens) calendar->byfields |= INTERVAL_BYHOUR; } - +/* + * 功能:获取日历规则中的 "byminute" 子句的值 + * + * 参数列表: + * calendar:日历规则 + * tokens:日历规则中的标记 + * + * 返回值: + * 如果存在 "byminute" 子句,则返回其值;否则返回 NULL。 + */ static char *get_calendar_byminute_val(Calendar calendar, char **tokens) { + // 获取 "byminute" 子句的值 char *val = get_calendar_clause_val(tokens, "byminute", true); if (val != NULL) { /* apply byminute rule if byminute is specified */ @@ -621,6 +639,7 @@ static char *get_calendar_byminute_val(Calendar calendar, char **tokens) calendar->minute_len *= -1; calendar->byminute[0] = mod; } + // 没有指定 "byminute" 子句,返回 NULL return NULL; } @@ -667,15 +686,26 @@ static void get_calendar_byminute(Calendar calendar, char **tokens) calendar->time_depth *= (calendar->minute_len == 0) ? 1 : calendar->minute_len; calendar->byfields |= INTERVAL_BYMINUTE; } - +/* + * 功能:获取日历规则中的 "bysecond" 子句的值 + * + * 参数列表: + * calendar:日历规则 + * tokens:日历规则中的标记 + * + * 返回值: + * 如果存在 "bysecond" 子句,则返回其值;否则返回 NULL。 + */ static char *get_calendar_bysecond_val(Calendar calendar, char **tokens) { + // 获取 "bysecond" 子句的值 char *val = get_calendar_clause_val(tokens, "bysecond", true); if (val != NULL) { /* apply bysecond rule if bysecond is specified */ return val; } + // 断言,确保频率不高于 SECONDLY Assert(calendar->frequency <= SECONDLY); /* Even higher frequency is unavailable */ if (calendar->frequency < SECONDLY) { @@ -693,6 +723,7 @@ static char *get_calendar_bysecond_val(Calendar calendar, char **tokens) calendar->second_len *= -1; calendar->bysecond[0] = mod; } + // 没有指定 "bysecond" 子句,返回 NULL return NULL; } @@ -740,7 +771,6 @@ static void get_calendar_bysecond(Calendar calendar, char **tokens) calendar->byfields |= INTERVAL_BYSECOND; } - /* * @brief tokenize_str * Tokenize string with given delimiters. @@ -766,13 +796,21 @@ static char **tokenize_str(char *src, const char *delims, int fields) } return tokens; } - +/* + * 功能:验证日历规则中的字段名是否有效 + * + * 参数列表: + * toks:日历规则中的标记数组 + * + * 返回值: + * 如果字段名有效,则返回 -1;否则返回字段名在标记数组中的位置。 + */ static int validate_field_names(char **toks) { bool valid = false; const int name_pos_step = 2; - const char *supported_fields[SUPPORTED_FIELDS] = {"freq", "interval", "bymonth", "bymonthday", "byhour", - "byminute", "bysecond"}; + const char *supported_fields[SUPPORTED_FIELDS] = {"freq", "interval", "bymonth", "bymonthday", + "byhour", "byminute", "bysecond"}; bool fields_used[SUPPORTED_FIELDS] = {0}; for (int i = 0; i < MAX_CALENDAR_FIELDS; i += name_pos_step) { for (int j = 0; j < SUPPORTED_FIELDS; j++) { @@ -791,7 +829,7 @@ static int validate_field_names(char **toks) fields_used[j] = true; valid = true; } - break; /* here is way pass guarding condition, break it */ + break; /* here is way pass guarding condition, break it */ } if (!valid) { return i; @@ -815,19 +853,18 @@ Calendar interpret_calendar_interval(char *calendar_str) /* Make token lists */ char **str_toks = tokenize_str(calendar_str, " =;", MAX_CALENDAR_FIELDS); if (str_toks == NULL) { - ereport(ERROR, (errmodule(MOD_JOB), errcode(ERRCODE_OPERATE_FAILED), - errmsg("Fail to evaluate calendaring string."), - errdetail("Unable to parse calendaring string."), - errcause("Calendaring string is too long/invalid"), - erraction("Please modify the calendaring string."))); + ereport(ERROR, + (errmodule(MOD_JOB), errcode(ERRCODE_OPERATE_FAILED), errmsg("Fail to evaluate calendaring string."), + errdetail("Unable to parse calendaring string."), errcause("Calendaring string is too long/invalid"), + erraction("Please modify the calendaring string."))); } int pos = validate_field_names(str_toks); if (pos >= 0) { - ereport(ERROR, (errmodule(MOD_JOB), errcode(ERRCODE_OPERATE_FAILED), - errmsg("Fail to evaluate calendaring string."), - errdetail("Incorrect/duplicate clause name '%s'.", str_toks[pos]), errcause("N/A"), - erraction("Please modify the calendaring string."))); + ereport(ERROR, + (errmodule(MOD_JOB), errcode(ERRCODE_OPERATE_FAILED), errmsg("Fail to evaluate calendaring string."), + errdetail("Incorrect/duplicate clause name '%s'.", str_toks[pos]), errcause("N/A"), + erraction("Please modify the calendaring string."))); } /* Make Calendar */ @@ -1028,7 +1065,7 @@ static void evaluate_calendar_bymonthday(Calendar calendar, TimestampTz *timelin *cnt = 0; int tz = 0; fsec_t fsec; - struct pg_tm tt, *tm = &tt; /* POSIX time struct, see NOTE above */ + struct pg_tm tt, *tm = &tt; /* POSIX time struct, see NOTE above */ copy_calendar_dates(timeline, calendar->monthday_len, chunk); for (int i = 0; i < calendar->monthday_len; i++) { if (calendar->bymonthday[i] < 0) { @@ -1222,8 +1259,7 @@ static void fastforward_calendar_period(Calendar calendar, TimestampTz *start_da errmsg("Cannot evaluate calendar clause."), errdetail("Broken interval clause."), errcause("N/A"), erraction("Please modify the calendaring string."))); } - Datum pace_datum = DirectFunctionCall2(interval_part, CStringGetTextDatum("epoch"), - PointerGetDatum(period)); + Datum pace_datum = DirectFunctionCall2(interval_part, CStringGetTextDatum("epoch"), PointerGetDatum(period)); int pace = (int)DatumGetFloat8(pace_datum); pfree_ext(period); @@ -1237,8 +1273,8 @@ static void fastforward_calendar_period(Calendar calendar, TimestampTz *start_da Interval *ff_span = get_calendar_period(calendar, num_of_periods); if (ff_span == NULL) { ereport(ERROR, (errmodule(MOD_JOB), errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), - errmsg("Cannot evaluate calendar clause."), errdetail("Broken interval clause."), - errcause("N/A"), erraction("Please modify the calendaring string."))); + errmsg("Cannot evaluate calendar clause."), errdetail("Broken interval clause."), + errcause("N/A"), erraction("Please modify the calendaring string."))); } new_start_date = DatumGetTimestampTz(timestamp_pl_interval(*start_date, ff_span)); pfree_ext(ff_span); @@ -1397,7 +1433,7 @@ static void prepare_calendar_period(Calendar calendar, TimestampTz base_date, Ti } fsec_t fsec; - struct pg_tm tt, *tm = &tt; /* POSIX time struct, see NOTE above */ + struct pg_tm tt, *tm = &tt; /* POSIX time struct, see NOTE above */ int tz; if (timestamp2tm(base_date, &tz, tm, &fsec, NULL, NULL) != 0) { ereport(ERROR, (errmodule(MOD_JOB), errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), @@ -1431,7 +1467,6 @@ static TimestampTz get_next_calendar_period(Calendar calendar, TimestampTz base_ return base_date; } - /* * @brief evaluate_calendar_interval * Calculate next date base on start date. @@ -1516,9 +1551,9 @@ Datum evaluate_repeat_interval(Datum calendar_in, Datum start_date, Datum date_a */ Datum evaluate_calendar_string_internal(PG_FUNCTION_ARGS) { - Datum string = PG_GETARG_DATUM(0); /* calendar string */ - Datum start_date = PG_GETARG_DATUM(1); /* start date */ - Datum date_after = PG_GETARG_DATUM(2); /* return date after */ + Datum string = PG_GETARG_DATUM(0); /* calendar string */ + Datum start_date = PG_GETARG_DATUM(1); /* start date */ + Datum date_after = PG_GETARG_DATUM(2); /* return date after */ Datum new_next_date = evaluate_repeat_interval(string, start_date, date_after); PG_RETURN_DATUM(new_next_date); } \ No newline at end of file diff --git a/src/gausskernel/process/main/main.cpp b/src/gausskernel/process/main/main.cpp index 6a6ca1027..81528d302 100755 --- a/src/gausskernel/process/main/main.cpp +++ b/src/gausskernel/process/main/main.cpp @@ -61,60 +61,63 @@ THR_LOCAL bool IsInitdb = false; size_t mmap_threshold = (size_t)0xffffffff; -const char* progname = NULL; +const char *progname = NULL; -static void startup_hacks(const char* progname); -static void help(const char* progname); -static void check_root(const char* progname); -static char* get_current_username(const char* progname); +static void startup_hacks(const char *progname); +static void help(const char *progname); +static void check_root(const char *progname); +static char *get_current_username(const char *progname); static void syscall_lock_init(void); -extern int encrypte_main(int argc, char* const argv[]); +extern int encrypte_main(int argc, char *const argv[]); /* * Any openGauss server process begins execution here. */ -int main(int argc, char* argv[]) +/* + * 功能:GaussDB 主函数 + * + * 参数列表: + * argc:命令行参数的数量 + * argv:命令行参数的字符串数组 + * + * 注意: + * 该函数负责 GaussDB 的启动和主要控制流程。它会根据命令行参数分发到不同的子程序,如 + * initdb、Postmaster、GucInfoMain等。 在启动过程中,会进行一系列初始化操作,包括内存管理、信号处理、地区设置等。 + * 根据命令行参数,可能执行初始化数据库、显示配置信息或启动主 Postmaster 进程。 + */ +int main(int argc, char *argv[]) { - char* mmap_env = NULL; - syscall_lock_init(); + char *mmap_env = NULL; + syscall_lock_init(); // 初始化系统调用锁 mmap_env = gs_getenv_r("GAUSS_MMAP_THRESHOLD"); if (mmap_env != NULL) { - check_backend_env(mmap_env); - mmap_threshold = (size_t)atol(mmap_env); + check_backend_env(mmap_env); // 检查后端环境变量 + mmap_threshold = (size_t)atol(mmap_env); // 设置内存映射阈值 } - knl_instance_init(); + knl_instance_init(); // 初始化内核实例 + // 创建增量检查点上下文 g_instance.increCheckPoint_context = AllocSetContextCreate( - INSTANCE_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_STORAGE), - "IncreCheckPointContext", - ALLOCSET_DEFAULT_MINSIZE, - ALLOCSET_DEFAULT_INITSIZE, - ALLOCSET_DEFAULT_MAXSIZE, - SHARED_CONTEXT); + INSTANCE_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_STORAGE), "IncreCheckPointContext", ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE, SHARED_CONTEXT); - g_instance.account_context = AllocSetContextCreate(g_instance.instance_context, - "StandbyAccontContext", - ALLOCSET_DEFAULT_MINSIZE, - ALLOCSET_DEFAULT_INITSIZE, - ALLOCSET_DEFAULT_MAXSIZE, - SHARED_CONTEXT); - - g_instance.comm_cxt.comm_global_mem_cxt = AllocSetContextCreate(g_instance.instance_context, - "CommunnicatorGlobalMemoryContext", - ALLOCSET_DEFAULT_MINSIZE, - ALLOCSET_DEFAULT_INITSIZE, - ALLOCSET_DEFAULT_MAXSIZE, - SHARED_CONTEXT); + // 创建帐户上下文 + g_instance.account_context = + AllocSetContextCreate(g_instance.instance_context, "StandbyAccontContext", ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE, SHARED_CONTEXT); - g_instance.builtin_proc_context = AllocSetContextCreate(g_instance.instance_context, - "builtin_procGlobalMemoryContext", - ALLOCSET_DEFAULT_MINSIZE, - ALLOCSET_DEFAULT_INITSIZE, - ALLOCSET_DEFAULT_MAXSIZE, - SHARED_CONTEXT); + // 创建通信全局内存上下文 + g_instance.comm_cxt.comm_global_mem_cxt = + AllocSetContextCreate(g_instance.instance_context, "CommunnicatorGlobalMemoryContext", ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE, SHARED_CONTEXT); + + // 创建内置过程全局内存上下文 + g_instance.builtin_proc_context = + AllocSetContextCreate(g_instance.instance_context, "builtin_procGlobalMemoryContext", ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE, SHARED_CONTEXT); /* * Fire up essential subsystems: error and memory management * @@ -126,8 +129,9 @@ int main(int argc, char* argv[]) PmTopMemoryContext = t_thrd.top_mem_cxt; - knl_thread_init(MASTER_THREAD); + knl_thread_init(MASTER_THREAD); // 初始化内核线程 + // 创建伪会话上下文 t_thrd.fake_session = create_session_context(t_thrd.top_mem_cxt, 0); t_thrd.fake_session->status = KNL_SESS_FAKE; @@ -137,19 +141,21 @@ int main(int argc, char* argv[]) MemoryContextSwitchTo(THREAD_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_DEFAULT)); - progname = get_progname(argv[0]); + progname = get_progname(argv[0]); // 获取程序名 /* * Platform-specific startup hacks */ + // 平台特定的启动操作 startup_hacks(progname); /* if gaussdb's name is gs_encrypt, so run in encrypte_main() */ + // 如果程序名是 "gs_encrypt",则执行 encrypte_main() 函数 if (!strcmp(progname, "gs_encrypt")) { return encrypte_main(argc, argv); } - init_plog_global_mem(); + init_plog_global_mem(); // 初始化全局日志内存 /* * Remember the physical location of the initially given argv[] array for @@ -191,7 +197,7 @@ int main(int argc, char* argv[]) * environment. If there is nothing there we fall back on the codepage. */ { - char* env_locale = NULL; + char *env_locale = NULL; if ((env_locale = gs_getenv_r("LC_COLLATE")) != NULL) { check_backend_env(env_locale); @@ -258,8 +264,8 @@ int main(int argc, char* argv[]) pgwin32_signal_initialize(); #endif - t_thrd.mem_cxt.gs_signal_mem_cxt = AllocSetContextCreate( - t_thrd.top_mem_cxt, "gs_signal", ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); + t_thrd.mem_cxt.gs_signal_mem_cxt = AllocSetContextCreate(t_thrd.top_mem_cxt, "gs_signal", ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); if (NULL == t_thrd.mem_cxt.gs_signal_mem_cxt) { ereport(LOG, (errmsg("could not start a new thread, because of no enough system resource. "))); proc_exit(1); @@ -310,7 +316,7 @@ int main(int argc, char* argv[]) * is too brain-dead to provide a standard C execution environment * without help. Avoid adding more here, if you can. */ -static void startup_hacks(const char* progname) +static void startup_hacks(const char *progname) { /* * On some platforms, unaligned memory accesses result in a kernel trap; @@ -363,7 +369,8 @@ static void startup_hacks(const char* progname) * Help display should match the options accepted by PostmasterMain() * and PostgresMain(). */ -static void help(const char* progname) +// 此函数用于对它支持的命令行选项和用法进行说明。这有助于理解代码的功能和如何使用这些选项来运行程序。 +static void help(const char *progname) { printf(_("%s is the gaussdb server.\n\n"), progname); printf(_("Usage:\n %s [OPTION]...\n\n"), progname); @@ -462,7 +469,7 @@ static void help(const char* progname) #endif } -static void check_root(const char* progname) +static void check_root(const char *progname) { #ifndef WIN32 if (geteuid() == 0) { @@ -496,29 +503,49 @@ static void check_root(const char* progname) } #endif /* WIN32 */ } - -static char* get_current_username(const char* progname) +/* + * 功能:获取当前操作系统用户的用户名 + * + * 参数列表: + * progname:程序的名称,用于错误消息 + * + * 返回值: + * 当前用户的用户名,以字符串形式返回 + * + * 注意: + * 该函数在不同的操作系统下使用不同的方法获取用户名。 + * 在 Unix-like 系统下,使用 getpwuid 函数获取用户名。 + * 在 Windows 系统下,使用 GetUserName 函数获取用户名。 + * 函数内部包含线程安全措施,以确保在多线程环境中的正确性。 + */ +static char *get_current_username(const char *progname) { #ifndef WIN32 - struct passwd* pw = NULL; - char* pRet = NULL; + struct passwd *pw = NULL; + char *pRet = NULL; + /* 获取 getpwuid 函数的锁,以确保线程安全 */ (void)syscalllockAcquire(&getpwuid_lock); + /* 获取当前用户的密码项 */ pw = getpwuid(geteuid()); if (pw == NULL) { + /* 释放锁并报告错误,如果获取密码项失败 */ (void)syscalllockRelease(&getpwuid_lock); write_stderr("%s: invalid effective UID: %d\n", progname, (int)geteuid()); exit(1); } /* Allocate new memory because later getpwuid() calls can overwrite it. */ pRet = MemoryContextStrdup(SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_CBB), pw->pw_name); + /* 释放锁并返回用户名 */ (void)syscalllockRelease(&getpwuid_lock); return pRet; #else unsigned long namesize = 256 /* UNLEN */ + 1; - char* name = NULL; + char *name = NULL; + /* 在内存上分配空间以存储用户名 */ name = MemoryContextAlloc(SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_CBB), namesize); + /* 尝试获取 Windows 用户名 */ if (!GetUserName(name, &namesize)) { write_stderr("%s: could not determine user name (GetUserName failed)\n", progname); exit(1); @@ -527,12 +554,29 @@ static char* get_current_username(const char* progname) return name; #endif } - +/* + * 功能:初始化系统调用锁 + * + * 参数:无 + * + * 注意: + * 该函数用于初始化多个系统调用锁,以确保在多线程环境中的正确性。 + * 每个锁用于保护不同的系统调用,以避免并发调用时的竞争条件。 + */ static void syscall_lock_init(void) { + /* 初始化获取用户密码项的锁 */ syscalllockInit(&getpwuid_lock); + + /* 初始化环境变量锁 */ syscalllockInit(&env_lock); + + /* 初始化 dlerror 函数的锁 */ syscalllockInit(&dlerror_lock); + + /* 初始化 Kerberos 连接锁 */ syscalllockInit(&kerberos_conn_lock); + + /* 初始化读取加密数据的锁 */ syscalllockInit(&read_cipher_lock); } diff --git a/src/gausskernel/storage/.vscode/settings.json b/src/gausskernel/storage/.vscode/settings.json new file mode 100644 index 000000000..ce8d15f93 --- /dev/null +++ b/src/gausskernel/storage/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "files.associations": { + "condition_variable": "cpp" + } +} \ No newline at end of file diff --git a/src/gausskernel/storage/access/archive/archive_am.cpp b/src/gausskernel/storage/access/archive/archive_am.cpp index 9d6280ef0..0ec329e13 100644 --- a/src/gausskernel/storage/access/archive/archive_am.cpp +++ b/src/gausskernel/storage/access/archive/archive_am.cpp @@ -11,6 +11,7 @@ * * ------------------------------------------------------------------------- */ +// 包含的必要的头文件 #include #include #include @@ -23,83 +24,88 @@ #include "replication/slot.h" - -size_t ArchiveRead(const char* fileName, const int offset, char *buffer, const int length, - ArchiveConfig *archive_config) +// 定义的函数,用于从归档储存中读取数据 +size_t ArchiveRead(const char *fileName, const int offset, char *buffer, const int length, + ArchiveConfig *archive_config) // archive_config是用于存储归档存储配置信息的数据结构。 { - if (archive_config == NULL) { + if (archive_config == NULL) { // 如果未指定archive_config,则无法读取数据 return 0; } - if (archive_config->media_type == ARCHIVE_OBS) { - return obsRead(fileName, offset, buffer, length, archive_config); - } else if (archive_config->media_type == ARCHIVE_NAS) { - return NasRead(fileName, offset, buffer, length, archive_config); + // 根据归档存储介质类型执行相应的读取函数 + if (archive_config->media_type == ARCHIVE_OBS) { // 判断归档存储介质的类型——对象储存 + return obsRead(fileName, offset, buffer, length, archive_config); // OBS储存介质的读取函数 + } else if (archive_config->media_type == ARCHIVE_NAS) { // 用于区分归档存储操作是在网络附加存储上进行的 + return NasRead(fileName, offset, buffer, length, archive_config); // NSA储存介质的读取函数 } - return 0; + return 0; // 如果介质类型未知,返回0 } - -int ArchiveWrite(const char* fileName, const char *buffer, const int bufferLength, ArchiveConfig *archive_config) +// 定义了一个函数用于向归档存储中写入数据 +int ArchiveWrite(const char *fileName, const char *buffer, const int bufferLength, ArchiveConfig *archive_config) { int ret = -1; if (archive_config == NULL) { - return ret; + return ret; // 如果归档配置为空,直接返回 -1 表示写入失败 } - + // 根据归档存储介质类型执行相应的写入函数 if (archive_config->media_type == ARCHIVE_OBS) { - ret = obsWrite(fileName, buffer, bufferLength, archive_config); + ret = obsWrite(fileName, buffer, bufferLength, archive_config); // OBS储存介质的写入函数 } else if (archive_config->media_type == ARCHIVE_NAS) { - ret = NasWrite(fileName, buffer, bufferLength, archive_config); + ret = NasWrite(fileName, buffer, bufferLength, archive_config); // NSA储存介质的写入函数 } - return ret; + return ret; // 根据返回结果可以判断是否写入成功,成功(非负数),失败(-1) } - -int ArchiveDelete(const char* fileName, ArchiveConfig *archive_config) +// 定义了一个函数用于删除归档存储中的数据 +int ArchiveDelete(const char *fileName, ArchiveConfig *archive_config) { int ret = -1; if (archive_config == NULL) { - return ret; + return ret; // 如果归档配置为空,直接返回 -1 表示删除失败 } - + // 根据归档存储介质类型执行相应的删除函数 if (archive_config->media_type == ARCHIVE_OBS) { - ret = obsDelete(fileName, archive_config); + ret = obsDelete(fileName, archive_config); // 调用OBS存储的删除函数 } else if (archive_config->media_type == ARCHIVE_NAS) { - ret = NasDelete(fileName, archive_config); + ret = NasDelete(fileName, archive_config); // 调用NAS存储的删除函数 } - return ret; + return ret; // 根据返回结果可以判断是否删除成功,成功(非负数),失败(-1) } - -List* ArchiveList(const char* prefix, ArchiveConfig *archive_config, bool reportError, bool shortenConnTime) +// 定义了一个函数,用于列出归档存储中的文件列表 +List *ArchiveList(const char *prefix, ArchiveConfig *archive_config, bool reportError, bool shortenConnTime) { - List* fileNameList = NIL; + List *fileNameList = NIL; if (archive_config == NULL) { return fileNameList; } - + // 根据归档存储介质类型执行相应的列出文件列表函数 if (archive_config->media_type == ARCHIVE_OBS) { fileNameList = obsList(prefix, archive_config, reportError, shortenConnTime); + // 调用OBS储存的文件列表,传入reporError(指定是否在列出文件列表过程中报告错误)以及shortenConnTime(指定是否缩短连接时间) + // 从而提供更多的控制和灵活性,从而灵活的获得文件列表 } else if (archive_config->media_type == ARCHIVE_NAS) { - fileNameList = NasList(prefix, archive_config); + fileNameList = NasList(prefix, archive_config); // 调用NAS存储的列出文件列表函数 } - return fileNameList; + return fileNameList; // 返回文件名列表 } - -bool ArchiveFileExist(const char* file_path, ArchiveConfig *archive_config) +// 定义了一个函数用于检查归档存储中的文件是否存在 +bool ArchiveFileExist(const char *file_path, ArchiveConfig *archive_config) { bool ret = false; if (archive_config == NULL) { - ereport(WARNING, (errmsg("when check file exist, the archive config is null"))); + ereport( + WARNING, + (errmsg("when check file exist, the archive config is null"))); // 如果归档配置为空,会发出警告并返回false return ret; } - + // 根据归档存储介质类型执行相应的检查文件存在函数 if (archive_config->media_type == ARCHIVE_OBS) { - ret = checkOBSFileExist(file_path, archive_config); + ret = checkOBSFileExist(file_path, archive_config); // 调用OBS存储的检查文件存在函数 } else if (archive_config->media_type == ARCHIVE_NAS) { - ret = checkNASFileExist(file_path, archive_config); + ret = checkNASFileExist(file_path, archive_config); // 调用NAS存储的检查文件存在函数 } - return ret; -} + return ret; // 返回检查结果,可能是存在(true)或不存在(false) +} \ No newline at end of file diff --git a/src/gausskernel/storage/access/archive/nas_am.cpp b/src/gausskernel/storage/access/archive/nas_am.cpp index 9c823f265..d69c65adc 100644 --- a/src/gausskernel/storage/access/archive/nas_am.cpp +++ b/src/gausskernel/storage/access/archive/nas_am.cpp @@ -46,11 +46,22 @@ #include "postmaster/alarmchecker.h" #include "replication/walreceiver.h" +//ļ·󳤶ȣʹά #define MAX_PATH_LEN 1024 +//һͷ static int headerLen = 22; +// һڴNAS洢жȡļ size_t NasRead(const char* fileName, const int offset, char *buffer, const int length, ArchiveConfig *nas_config) { + /* + fileNameҪȡļ + offsetȡʼƫ + bufferڴ洢ȡݵĻ + lengthҪȡݳȡ + nas_config鵵Ϣָ룬ָ ArchiveConfig ṹйش洢λúõϢ + */ + //ʼ size_t readLength = 0; ArchiveConfig *archive_nas = NULL; char file_path[MAXPGPATH] = {0}; @@ -58,40 +69,45 @@ size_t NasRead(const char* fileName, const int offset, char *buffer, const int l FILE *fp = NULL; struct stat statbuf; - if ((fileName == NULL) || (buffer == NULL)) { + if ((fileName == NULL) || (buffer == NULL)) {//ļͻΪջᱨ ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), errmsg("The parameter cannot be NULL"))); } - + //ȡNAS洢Ϣ if (nas_config != NULL) { archive_nas = nas_config; } else { archive_nas = getArchiveConfig(); } - if (archive_nas == NULL) { + if (archive_nas == NULL) {//޷ȡ鵵Ϣʱ ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("Cannot get archive config from replication slots"))); } - if (strncmp(fileName, "global_barrier_records", headerLen) != 0) { + if (strncmp(fileName, "global_barrier_records", headerLen) != 0) {//ļ· + //snprintf_sһļ·洢 file_path ret = snprintf_s(file_path, MAXPGPATH, MAXPGPATH - 1, "%s/%s", archive_nas->archive_prefix, fileName); - securec_check_ss(ret, "\0", "\0"); + // retڴsnprintf_s ķֵֵʾѸʽַijȡ + securec_check_ss(ret, "\0", "\0");//󽫻 ret з '\0' Ӷλλá + //ڼ snprintf_sķֵаȫԼ飬ȷûзʽ } else { - char pathPrefix[MAXPGPATH] = {0}; + char pathPrefix[MAXPGPATH] = {0};//һյcharΪpathPrefixڴ鵵õĴ浵ǰ׺ ret = strcpy_s(pathPrefix, MAXPGPATH, archive_nas->archive_prefix); + // archive_nas->archive_prefix pathPrefix securec_check_ss(ret, "\0", "\0"); if (!IS_PGXC_COORDINATOR) { - char *p = strrchr(pathPrefix, '/'); + char *p = strrchr(pathPrefix, '/');//pathPrefixҵһбܵλ if (p == NULL) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("Obs path prefix is invalid"))); } - *p = '\0'; + *p = '\0';//һб滻Ϊ'\0' } ret = snprintf_s(file_path, MAXPGPATH, MAXPGPATH - 1, "%s/%s", pathPrefix, fileName); securec_check_ss(ret, "\0", "\0"); + // · snprintf_s ķֵ } - + //ļǷ if (stat(file_path, &statbuf)) { if (errno != ENOENT) { ereport(ERROR, (errcode_for_file_access(), errmsg("could not stat file \"%s\": %m", fileName))); @@ -99,9 +115,10 @@ size_t NasRead(const char* fileName, const int offset, char *buffer, const int l ereport(ERROR, (errcode_for_file_access(), errmsg("The file \"%s\" not exists", fileName))); return readLength; } - + //ļȡ canonicalize_path(file_path); fp = fopen(file_path, "rb"); + //ڴļĴд if (fp == NULL) { ereport(ERROR, (errcode_for_file_access(), errmsg("could not read file \"%s\": %m", fileName))); return readLength; @@ -111,90 +128,99 @@ size_t NasRead(const char* fileName, const int offset, char *buffer, const int l ereport(ERROR, (errcode_for_file_access(), errmsg("file size is wrong, \"%s\": %m", fileName))); return readLength; } - + + //ȡݵ readLength = fread(buffer, 1, statbuf.st_size, fp); fclose(fp); return readLength; } - +//úڽдļڱҪʱбݺ int NasWrite(const char* fileName, const char *buffer, const int bufferLength, ArchiveConfig *nas_config) { int ret = 0; - ArchiveConfig *archive_nas = NULL; - char file_path[MAXPGPATH] = {0}; - char file_path_bak[MAXPGPATH] = {0}; - char *origin_file_path = NULL; - char *base_path = NULL; - FILE *fp = NULL; - + ArchiveConfig *archive_nas = NULL;//洢NASõָ + char file_path[MAXPGPATH] = {0};//洢ļ· + char file_path_bak[MAXPGPATH] = {0};//洢ļ· + char *origin_file_path = NULL;//洢淶ļ·ĸ + char *base_path = NULL;//洢ļĻ· + FILE *fp = NULL;//ļָ룬ڲļ + + //鴫IJǷΪգļͻΪʱ if ((fileName == NULL) || (buffer == NULL)) { ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), errmsg("The parameter cannot be NULL"))); } - + //ȡ鵵ϢĹ鵵òΪNULLԴĬλûȡ if (nas_config != NULL) { archive_nas = nas_config; } else { archive_nas = getArchiveConfig(); } - + //ȡĹ鵵ϢǷЧЧʱ if (archive_nas == NULL) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("Cannot get archive config from replication slots"))); } - - if (strncmp(fileName, "global_barrier_records", headerLen) != 0) { + //ļļ· + if (strncmp(fileName, "global_barrier_records", headerLen) != 0) {//жļǷΪ"global_barrier_records" ret = snprintf_s(file_path, MAXPGPATH, MAXPGPATH - 1, "%s/%s", archive_nas->archive_prefix, fileName); securec_check_ss(ret, "\0", "\0"); + //·snprintf_sķֵ } else { - char pathPrefix[MAXPGPATH] = {0}; - ret = strcpy_s(pathPrefix, MAXPGPATH, archive_nas->archive_prefix); + char pathPrefix[MAXPGPATH] = {0};//ڴ洢·ǰ׺ + ret = strcpy_s(pathPrefix, MAXPGPATH, archive_nas->archive_prefix);// archive_nas->archive_prefix pathPrefix securec_check_ss(ret, "\0", "\0"); - if (!IS_PGXC_COORDINATOR) { - char *p = strrchr(pathPrefix, '/'); - if (p == NULL) { + if (!IS_PGXC_COORDINATOR) {// Эڵ + char *p = strrchr(pathPrefix, '/');//pathPrefixвһ'/' + if (p == NULL) {//ûҵ'/'ᱨ ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("Obs path prefix is invalid"))); } - *p = '\0'; + *p = '\0';//һб滻Ϊ'\0' } ret = snprintf_s(file_path, MAXPGPATH, MAXPGPATH - 1, "%s/%s", pathPrefix, fileName); securec_check_ss(ret, "\0", "\0"); + // · snprintf_s ķֵ } - canonicalize_path(file_path); + canonicalize_path(file_path);//淶ļ·ȥַ'.''..' - origin_file_path = pstrdup(file_path); - base_path = dirname(origin_file_path); + origin_file_path = pstrdup(file_path);//ļ·origin_file_path + base_path = dirname(origin_file_path);//ȡorigin_file_pathļ·ĸ·· + //·Ƿڣ򴴽 if (!isDirExist(base_path)) { - if (pg_mkdir_p(base_path, S_IRWXU) != 0) { - pfree_ext(origin_file_path); + // pg_mkdir_p ԴĿ¼S_IRWXU ָȨ޲ + if (pg_mkdir_p(base_path, S_IRWXU) != 0) {//ֵΪ0˵Ŀ¼ʧ + // Ŀ¼ʧܣͷڴ沢 + pfree_ext(origin_file_path);//ͷռõڴռ ereport(LOG, (errmsg("could not create path \"%s\"", base_path))); - return -1; + return -1;// -1 ʾĿ¼ʧ } } - + //ļ· ".bak" ӵ file_path ret = snprintf_s(file_path_bak, MAXPGPATH, MAXPGPATH - 1, "%s.bak", file_path); securec_check_ss(ret, "\0", "\0"); - fp = fopen(file_path_bak, "wb"); - if (fp == NULL) { + fp = fopen(file_path_bak, "wb");//򿪱ļԶдģʽ + if (fp == NULL) {//򿪱ļʧܣͷڴ沢 pfree_ext(origin_file_path); ereport(LOG, (errmsg("could not create file \"%s\": %m", fileName))); return -1; } - + //д뱸ļ if (fwrite(buffer, bufferLength, 1, fp) != 1) { ereport(LOG, (errmsg("could not write file \"%s\": %m", fileName))); pfree_ext(origin_file_path); fclose(fp); return -1; } + //ˢļȷд if (fflush(fp) != 0) { ereport(LOG, (errmsg("could not fflush file \"%s\": %m", fileName))); (void)fclose(fp); pfree_ext(origin_file_path); return -1; } + //ļΪʽļ if (rename(file_path_bak, file_path) < 0) { ereport(LOG, (errmsg("could not rename file \"%s\": %m", fileName))); (void)fclose(fp); @@ -202,8 +228,8 @@ int NasWrite(const char* fileName, const char *buffer, const int bufferLength, A return -1; } - pfree_ext(origin_file_path); - fclose(fp); + pfree_ext(origin_file_path);//ͷűռõڴռ + fclose(fp);//رļ return 0; } @@ -420,4 +446,4 @@ bool checkNASFileExist(const char* file_path, ArchiveConfig *nas_config) } return true; -} \ No newline at end of file +} diff --git a/src/gausskernel/storage/buffer/buf_init.cpp b/src/gausskernel/storage/buffer/buf_init.cpp index 6956b35d7..ef78c756e 100644 --- a/src/gausskernel/storage/buffer/buf_init.cpp +++ b/src/gausskernel/storage/buffer/buf_init.cpp @@ -13,6 +13,7 @@ * * ------------------------------------------------------------------------- */ + //Ҫͷļ #include "storage/dfs/dfscache_mgr.h" #include "postgres.h" @@ -26,7 +27,7 @@ #include "postmaster/pagewriter.h" #include "postmaster/bgwriter.h" #include "utils/palloc.h" - +// ڿƻĶв۵ı const int PAGE_QUEUE_SLOT_MULTI_NBUFFERS = 5; /* @@ -59,37 +60,39 @@ const int PAGE_QUEUE_SLOT_MULTI_NBUFFERS = 5; * Pins must be released before end of transaction. For efficiency the * shared refcount isn't increased if a individual backend pins a buffer * multiple times. Check the PrivateRefCount infrastructure in bufmgr.c. - */ -/* + * * Initialize shared buffer pool * * This is called once during shared-memory initialization (either in the * postmaster, or in a standalone backend). */ + // ʼ void InitBufferPool(void) { + // ָʾǷҵڴṹı bool found_bufs = false; bool found_descs = false; bool found_buf_ckpt = false; uint64 buffer_size; - + //ڹгʼ t_thrd.storage_cxt.BufferDescriptors = (BufferDescPadded *)CACHELINEALIGN( ShmemInitStruct("Buffer Descriptors", TOTAL_BUFFER_NUM * sizeof(BufferDescPadded) + PG_CACHE_LINE_SIZE, &found_descs)); /* Init candidate buffer list and candidate buffer free map */ + // ʼѡбͺѡӳ candidate_buf_init(); -#ifdef __aarch64__ +#ifdef __aarch64__// ڹڴз仺ڴ buffer_size = TOTAL_BUFFER_NUM * (Size)BLCKSZ + PG_CACHE_LINE_SIZE; t_thrd.storage_cxt.BufferBlocks = (char *)CACHELINEALIGN(ShmemInitStruct("Buffer Blocks", buffer_size, &found_bufs)); -#else +#else//ڷaarch64ʱִ´룬Ϊ乲ڴ档 buffer_size = TOTAL_BUFFER_NUM * (Size)BLCKSZ; t_thrd.storage_cxt.BufferBlocks = (char *)ShmemInitStruct("Buffer Blocks", buffer_size, &found_bufs); #endif - + // ǷҪԹк if (BBOX_BLACKLIST_SHARE_BUFFER) { /* Segment Buffer is exclued from the black list, as it contains many critical information for debug */ bbox_blacklist_add(SHARED_BUFFER, t_thrd.storage_cxt.BufferBlocks, NORMAL_SHARED_BUFFER_NUM * (Size)BLCKSZ); @@ -102,24 +105,30 @@ void InitBufferPool(void) * the checkpointer is restarted, memory allocation failures would be * painful. */ + // ڹڴгʼ㻺 ID g_instance.ckpt_cxt_ctl->CkptBufferIds = (CkptSortItem *)ShmemInitStruct("Checkpoint BufferIds", TOTAL_BUFFER_NUM * sizeof(CkptSortItem), &found_buf_ckpt); - + + //㣬ҳδ䣬ͷ䲢ʼöС if (ENABLE_INCRE_CKPT && g_instance.ckpt_cxt_ctl->dirty_page_queue == NULL) { + // ҳĴС g_instance.ckpt_cxt_ctl->dirty_page_queue_size = TOTAL_BUFFER_NUM * PAGE_QUEUE_SLOT_MULTI_NBUFFERS; + // лڴԷ MemoryContext oldcontext = MemoryContextSwitchTo(g_instance.increCheckPoint_context); - + // ҳеڴС Size queue_mem_size = g_instance.ckpt_cxt_ctl->dirty_page_queue_size * sizeof(DirtyPageQueueSlot); + // ʹ 'palloc_huge' Ϊҳзڴ g_instance.ckpt_cxt_ctl->dirty_page_queue = (DirtyPageQueueSlot *)palloc_huge(CurrentMemoryContext, queue_mem_size); /* The memory of the memset sometimes exceeds 2 GB. so, memset_s cannot be used. */ MemSet((char*)g_instance.ckpt_cxt_ctl->dirty_page_queue, 0, queue_mem_size); + // лԭʼڴ (void)MemoryContextSwitchTo(oldcontext); } - + // ʼ̨дĹϣ if (g_instance.bgwriter_cxt.unlink_rel_hashtbl == NULL) { g_instance.bgwriter_cxt.unlink_rel_hashtbl = relfilenode_hashtbl_create("unlink_rel_hashtbl", true); } diff --git a/src/gausskernel/storage/buffer/buf_table.cpp b/src/gausskernel/storage/buffer/buf_table.cpp index 2d69f0fd0..d26e77944 100644 --- a/src/gausskernel/storage/buffer/buf_table.cpp +++ b/src/gausskernel/storage/buffer/buf_table.cpp @@ -20,6 +20,13 @@ * * ------------------------------------------------------------------------- */ +/* + *δʵ˽BufferTagsӳ䵽عܡ + *һЩڹϣвҡɾӳĺ + *Щڹǩʵʻ֮ӳϵ + *ӶϵͳݻǩٶλӦĻ + * + */ #include "postgres.h" #include "knl/knl_variable.h" diff --git a/src/gausskernel/storage/buffer/freelist.cpp b/src/gausskernel/storage/buffer/freelist.cpp index af45abcab..3084f646b 100644 --- a/src/gausskernel/storage/buffer/freelist.cpp +++ b/src/gausskernel/storage/buffer/freelist.cpp @@ -71,15 +71,15 @@ const int MAX_RETRY_TIMES = 1000; const float NEED_DELAY_RETRY_GET_BUF = 0.8; /* Prototypes for internal functions */ -static BufferDesc* GetBufferFromRing(BufferAccessStrategy strategy, uint32* buf_state); -static void AddBufferToRing(BufferAccessStrategy strategy, volatile BufferDesc* buf); -void PageListBackWrite(uint32* bufList, int32 n, - /* buffer list, bufs to scan, */ - uint32 flags = 0, /* opt flags */ - SMgrRelation use_smgrReln = NULL, /* opt relation */ - int32* bufs_written = NULL, /* opt written count returned */ - int32* bufs_reusable = NULL); /* opt reusable count returned */ -static BufferDesc* get_buf_from_candidate_list(BufferAccessStrategy strategy, uint32* buf_state); +static BufferDesc *GetBufferFromRing(BufferAccessStrategy strategy, uint32 *buf_state); +static void AddBufferToRing(BufferAccessStrategy strategy, volatile BufferDesc *buf); +void PageListBackWrite(uint32 *bufList, int32 n, + /* buffer list, bufs to scan, */ + uint32 flags = 0, /* opt flags */ + SMgrRelation use_smgrReln = NULL, /* opt relation */ + int32 *bufs_written = NULL, /* opt written count returned */ + int32 *bufs_reusable = NULL); /* opt reusable count returned */ +static BufferDesc *get_buf_from_candidate_list(BufferAccessStrategy strategy, uint32 *buf_state); static void perform_delay(StrategyDelayStatus *status) { @@ -99,7 +99,6 @@ static void perform_delay(StrategyDelayStatus *status) return; } - /* * ClockSweepTick - Helper routine for StrategyGetBuffer() * @@ -177,7 +176,7 @@ static inline uint32 ClockSweepTick(int max_nbuffer_can_use) * If the fraction is too small, we will increase dynamiclly to avoid elog(ERROR) * in `Startup' process because of ERROR will promote to FATAL. */ -BufferDesc* StrategyGetBuffer(BufferAccessStrategy strategy, uint32* buf_state) +BufferDesc *StrategyGetBuffer(BufferAccessStrategy strategy, uint32 *buf_state) { BufferDesc *buf = NULL; int bgwproc_no; @@ -185,8 +184,8 @@ BufferDesc* StrategyGetBuffer(BufferAccessStrategy strategy, uint32* buf_state) uint32 local_buf_state = 0; /* to avoid repeated (de-)referencing */ int max_buffer_can_use; bool am_standby = RecoveryInProgress(); - StrategyDelayStatus retry_lock_status = { 0, 0 }; - StrategyDelayStatus retry_buf_status = { 0, 0 }; + StrategyDelayStatus retry_lock_status = {0, 0}; + StrategyDelayStatus retry_buf_status = {0, 0}; /* * If given a strategy object, see whether it can select a buffer. We @@ -352,7 +351,7 @@ int StrategySyncStart(uint32 *complete_passes, uint32 *num_buf_alloc) * Additionally add the number of wraparounds that happened before * completePasses could be incremented. C.f. ClockSweepTick(). */ - *complete_passes += next_victim_buffer / (unsigned int) NORMAL_SHARED_BUFFER_NUM; + *complete_passes += next_victim_buffer / (unsigned int)NORMAL_SHARED_BUFFER_NUM; } if (num_buf_alloc != NULL) { @@ -487,7 +486,7 @@ BufferAccessStrategy GetAccessStrategy(BufferAccessStrategyType btype) break; case BAS_VACUUM: ring_size = g_instance.attr.attr_storage.NBuffers / 32 / - Max(g_instance.attr.attr_storage.autovacuum_max_workers, 1); + Max(g_instance.attr.attr_storage.autovacuum_max_workers, 1); break; case BAS_REPAIR: ring_size = Min(g_instance.attr.attr_storage.NBuffers, MIN_REPAIR_FILE_SLOT_NUM); @@ -609,7 +608,7 @@ RETRY: if (retry_times < Min(MAX_RETRY_RING_TIMES, strategy->ring_size * MAX_RETRY_RING_PCT)) { goto RETRY; } else if (get_curr_candidate_nums(false) >= (uint32)g_instance.attr.attr_storage.NBuffers * - u_sess->attr.attr_storage.candidate_buf_percent_target){ + u_sess->attr.attr_storage.candidate_buf_percent_target) { strategy->current_was_in_ring = false; return NULL; } @@ -617,8 +616,7 @@ RETRY: local_buf_state = LockBufHdr(buf); if (BUF_STATE_GET_REFCOUNT(local_buf_state) == 0 && BUF_STATE_GET_USAGECOUNT(local_buf_state) <= 1 && - (backend_can_flush_dirty_page() || !(local_buf_state & BM_DIRTY)) && - !(local_buf_state & BM_IS_META)) { + (backend_can_flush_dirty_page() || !(local_buf_state & BM_DIRTY)) && !(local_buf_state & BM_IS_META)) { strategy->current_was_in_ring = true; *buf_state = local_buf_state; return buf; @@ -673,26 +671,59 @@ bool StrategyRejectBuffer(BufferAccessStrategy strategy, BufferDesc *buf) return true; } - +/* + * 功能:根据缓冲区访问策略获取环形预取的数量和触发器值 + * + * 参数列表: + * strategy:缓冲区访问策略 + * quantity:用于存储预取数量的指针 + * trigger:用于存储预取触发器值的指针 + * + * 返回值: + * 无 + * + * 注意: + * 此函数用于确定环形预取的数量和触发器值,以优化磁盘I/O性能。 + * 如果传入的策略为空或者不是BAS_BULKREAD类型,则不进行预取设置。 + * 预取数量和触发器值将基于环形缓冲区的大小和配置参数来计算。 + */ void StrategyGetRingPrefetchQuantityAndTrigger(BufferAccessStrategy strategy, int *quantity, int *trigger) { int threshold; - int prefetch_trigger = u_sess->attr.attr_storage.prefetch_quantity; + int prefetch_trigger = u_sess->attr.attr_storage.prefetch_quantity; // 获取预取触发器的值,从配置参数中获取 if (strategy == NULL || strategy->btype != BAS_BULKREAD) { - return; + return; // 如果传入的策略为空或者不是BAS_BULKREAD类型,直接返回,不进行预取设置 } + + // 预取阈值设置为缓冲策略的环形缓冲区大小的1/4 threshold = strategy->ring_size / 4; + if (quantity != NULL) { + // 如果传入的quantity参数不为空,则设置quantity为配置参数中的prefetch_quantity或者threshold中的较小值 *quantity = (threshold > u_sess->attr.attr_storage.prefetch_quantity) ? u_sess->attr.attr_storage.prefetch_quantity : threshold; } + if (trigger != NULL) { + // 如果传入的trigger参数不为空,则设置trigger为prefetch_trigger或者threshold中的较小值 *trigger = (threshold > prefetch_trigger) ? prefetch_trigger : threshold; } } - +/* + * 功能:唤醒页写入线程 + * + * 参数列表: + * 无 + * + * 返回值: + * 无 + * + * 注意: + * 该函数用于唤醒页写入线程,以便其继续处理待写入的数据。 + * 它检查页写入线程是否存在,如果存在则触发线程的处理。 + */ void wakeup_pagewriter_thread() { PageWriterProc *pgwr = &g_instance.ckpt_cxt_ctl->pgwr_procs.writer_proc[0]; @@ -703,45 +734,66 @@ void wakeup_pagewriter_thread() return; } -const int CANDIDATE_DIRTY_LIST_LEN = 100; -const float HIGH_WATER = 0.75; -static BufferDesc* get_buf_from_candidate_list(BufferAccessStrategy strategy, uint32* buf_state) +const int CANDIDATE_DIRTY_LIST_LEN = 100; // 定义候选脏页列表的最大长度 +const float HIGH_WATER = 0.75; // 定义脏页高水位线比例 +/* + * 功能:从候选列表中获取缓冲区描述符 + * + * 参数列表: + * strategy:缓冲区访问策略 + * buf_state:指向存储缓冲区状态的变量的指针 + * + * 返回值: + * 指向缓冲区描述符的指针,如果没有可用缓冲区则返回 NULL + * + * 注意: + * 该函数用于从候选列表中获取缓冲区描述符,并根据策略选择合适的缓冲区。 + * 在获取缓冲区之前,它会检查缓冲区的可用性和状态,并可能触发页写入线程。 + */ +static BufferDesc *get_buf_from_candidate_list(BufferAccessStrategy strategy, uint32 *buf_state) { - BufferDesc* buf = NULL; - uint32 local_buf_state; - int buf_id = 0; - int list_num = g_instance.ckpt_cxt_ctl->pgwr_procs.sub_num; - int list_id = 0; - volatile PgBackendStatus* beentry = t_thrd.shemem_ptr_cxt.MyBEEntry; - Buffer *candidate_dirty_list = NULL; - int dirty_list_num = 0; - bool enable_available = false; - bool need_push_dirst_list = false; + BufferDesc *buf = NULL; // 缓冲区描述符指针 + uint32 local_buf_state; // 本地缓冲区状态 + int buf_id = 0; // 缓冲区ID + int list_num = g_instance.ckpt_cxt_ctl->pgwr_procs.sub_num; // 子进程数量 + int list_id = 0; // 列表ID + volatile PgBackendStatus *beentry = t_thrd.shemem_ptr_cxt.MyBEEntry; // 获取当前线程的后端状态信息 + Buffer *candidate_dirty_list = NULL; // 存储候选脏页的列表 + int dirty_list_num = 0; // 候选脏页列表中的脏页数量 + bool enable_available = false; // 是否可用标志 + bool need_push_dirst_list = false; // 是否需要将缓冲区添加到候选脏页列表的标志 bool need_scan_dirty = - (g_instance.ckpt_cxt_ctl->actual_dirty_page_num / (float)(g_instance.attr.attr_storage.NBuffers) > HIGH_WATER) - && backend_can_flush_dirty_page(); + (g_instance.ckpt_cxt_ctl->actual_dirty_page_num / (float)(g_instance.attr.attr_storage.NBuffers) > + HIGH_WATER) && + backend_can_flush_dirty_page(); // 是否需要扫描脏页的标志,根据脏页占用比例和是否允许刷新脏页决定 + if (need_scan_dirty) { - /*Not return the dirty page when there are few dirty pages */ - candidate_dirty_list = (Buffer*)palloc0(sizeof(Buffer) * CANDIDATE_DIRTY_LIST_LEN); + /* 分配用于保存脏页的候选列表 */ + candidate_dirty_list = (Buffer *)palloc0(sizeof(Buffer) * CANDIDATE_DIRTY_LIST_LEN); } + /* 计算列表 ID */ list_id = beentry->st_tid > 0 ? (beentry->st_tid % list_num) : (beentry->st_sessionid % list_num); + /* 遍历候选列表 */ for (int i = 0; i < list_num; i++) { - /* the pagewriter sub thread store normal buffer pool, sub thread starts from 1 */ + /* 子进程的ID,从1开始 */ int thread_id = (list_id + i) % list_num + 1; Assert(thread_id > 0 && thread_id <= list_num); + while (candidate_buf_pop(&buf_id, thread_id)) { Assert(buf_id < SegmentBufferStartID); - buf = GetBufferDescriptor(buf_id); - local_buf_state = LockBufHdr(buf); + buf = GetBufferDescriptor(buf_id); // 获取缓冲区描述符 + local_buf_state = LockBufHdr(buf); // 锁定缓冲区头部 if (g_instance.ckpt_cxt_ctl->candidate_free_map[buf_id]) { g_instance.ckpt_cxt_ctl->candidate_free_map[buf_id] = false; enable_available = BUF_STATE_GET_REFCOUNT(local_buf_state) == 0 && !(local_buf_state & BM_IS_META); - need_push_dirst_list = need_scan_dirty && dirty_list_num < CANDIDATE_DIRTY_LIST_LEN && - free_space_enough(buf_id); + need_push_dirst_list = + need_scan_dirty && dirty_list_num < CANDIDATE_DIRTY_LIST_LEN && free_space_enough(buf_id); + if (enable_available) { + /* 如果缓冲区可用,将其添加到策略环中 */ if (NEED_CONSIDER_USECOUNT && BUF_STATE_GET_USAGECOUNT(local_buf_state) != 0) { local_buf_state -= BUF_USAGECOUNT_ONE; } else if (!(local_buf_state & BM_DIRTY)) { @@ -758,10 +810,11 @@ static BufferDesc* get_buf_from_candidate_list(BufferAccessStrategy strategy, ui } } } - UnlockBufHdr(buf, local_buf_state); + UnlockBufHdr(buf, local_buf_state); // 解锁缓冲区头部 } } + /* 唤醒 PageWriter 线程 */ wakeup_pagewriter_thread(); if (need_scan_dirty) { @@ -769,9 +822,11 @@ static BufferDesc* get_buf_from_candidate_list(BufferAccessStrategy strategy, ui buf_id = candidate_dirty_list[i]; buf = GetBufferDescriptor(buf_id); local_buf_state = LockBufHdr(buf); - enable_available = (BUF_STATE_GET_REFCOUNT(local_buf_state) == 0) && !(local_buf_state & BM_IS_META) - && free_space_enough(buf_id); + enable_available = (BUF_STATE_GET_REFCOUNT(local_buf_state) == 0) && !(local_buf_state & BM_IS_META) && + free_space_enough(buf_id); + if (enable_available) { + /* 如果缓冲区可用,将其添加到策略环中 */ if (strategy != NULL) { AddBufferToRing(strategy, buf); } @@ -779,13 +834,13 @@ static BufferDesc* get_buf_from_candidate_list(BufferAccessStrategy strategy, ui pfree(candidate_dirty_list); return buf; } - UnlockBufHdr(buf, local_buf_state); + UnlockBufHdr(buf, local_buf_state); // 解锁缓冲区头部 } } if (candidate_dirty_list != NULL) { - pfree(candidate_dirty_list); + pfree(candidate_dirty_list); // 释放候选脏页列表内存 } - return NULL; -} + return NULL; // 返回 NULL,表示没有可用的缓冲区 +} diff --git a/src/gausskernel/storage/buffer/localbuf.cpp b/src/gausskernel/storage/buffer/localbuf.cpp index c14078840..e9d61e4f2 100644 --- a/src/gausskernel/storage/buffer/localbuf.cpp +++ b/src/gausskernel/storage/buffer/localbuf.cpp @@ -60,7 +60,7 @@ void LocalPrefetchBuffer(SMgrRelation smgr, ForkNumber forkNum, BlockNumber bloc InitLocalBuffers(); /* See if the desired buffer already exists */ - hresult = (LocalBufferLookupEnt*)hash_search(u_sess->storage_cxt.LocalBufHash, (void*)&new_tag, HASH_FIND, NULL); + hresult = (LocalBufferLookupEnt *)hash_search(u_sess->storage_cxt.LocalBufHash, (void *)&new_tag, HASH_FIND, NULL); if (hresult != NULL) { /* Yes, so nothing to do */ return; @@ -95,7 +95,17 @@ void LocalBufferFlushForExtremRTO(BufferDesc *bufHdr) } FlushBuffer(bufHdr, NULL, WITH_LOCAL_CACHE); } - +/* + * 功能:刷新所有本地缓冲区中的脏缓冲区 + * + * 参数列表:无 + * + * 返回值:无 + * + * 注意: + * 此函数遍历所有本地缓冲区,检查并刷新所有脏缓冲区。 + * 本地缓冲区是特定于本地文件系统的缓冲区。 + */ void LocalBufferFlushAllBuffer() { int i; @@ -104,25 +114,45 @@ void LocalBufferFlushAllBuffer() BufferDesc *bufHdr = &u_sess->storage_cxt.LocalBufferDescriptors[i]; uint32 buf_state; + /* 获取缓冲区状态 */ buf_state = pg_atomic_read_u32(&bufHdr->state); + + /* 断言:本地引用计数为0,确保没有被其他地方引用 */ Assert(u_sess->storage_cxt.LocalRefCount[i] == 0); + /* 如果缓冲区是有效的且脏的 */ if ((buf_state & BM_VALID) && (buf_state & BM_DIRTY)) { + /* 执行极端RTO时的本地缓冲区刷新 */ LocalBufferFlushForExtremRTO(bufHdr); + /* 清除脏标志位 */ buf_state &= ~BM_DIRTY; pg_atomic_write_u32(&bufHdr->state, buf_state); + /* 更新统计信息:本地块写入计数 */ u_sess->instr_cxt.pg_buffer_usage->local_blks_written++; } } } - +/* + * 功能:检查本地缓冲区的一致性 + * + * 参数列表: + * tag1:第一个缓冲区标签 + * tag2:第二个缓冲区标签 + * + * 返回值:无 + * + * 注意: + * 此函数用于检查两个本地缓冲区标签是否相等,如果不相等,则引发数据损坏错误。 + * 本地缓冲区标签用于标识特定的本地缓冲区。 + */ static void LocalBufferSanityCheck(BufferTag tag1, BufferTag tag2) { + /* 如果两个缓冲区标签不相等 */ if (!BUFFERTAGS_EQUAL(tag1, tag2)) { - ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), - (errmsg("local buffer hash tag mismatch.")))); + /* 引发数据损坏错误 */ + ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), (errmsg("local buffer hash tag mismatch.")))); } } @@ -135,6 +165,22 @@ static void LocalBufferSanityCheck(BufferTag tag1, BufferTag tag2) * does not get set. Lastly, we support only default access strategy * (hence, usage_count is always advanced). */ +/* + * 功能:分配本地缓冲区并返回缓冲区描述符 + * + * 参数列表: + * smgr:存储管理器关系 + * forkNum:分叉号 + * blockNum:块号 + * foundPtr:指向存储是否找到的指针的指针 + * + * 返回值: + * 指向缓冲区描述符的指针,如果未找到可用缓冲区则返回 NULL + * + * 注意: + * 该函数用于分配本地缓冲区,并返回与给定块标识符关联的缓冲区描述符。 + * 如果缓冲区已存在,则直接返回,否则分配新的缓冲区。 + */ BufferDesc *LocalBufferAlloc(SMgrRelation smgr, ForkNumber forkNum, BlockNumber blockNum, bool *foundPtr) { BufferTag new_tag; /* identity of requested block */ @@ -152,7 +198,7 @@ BufferDesc *LocalBufferAlloc(SMgrRelation smgr, ForkNumber forkNum, BlockNumber InitLocalBuffers(); /* See if the desired buffer already exists */ - hresult = (LocalBufferLookupEnt*)hash_search(u_sess->storage_cxt.LocalBufHash, (void*)&new_tag, HASH_FIND, NULL); + hresult = (LocalBufferLookupEnt *)hash_search(u_sess->storage_cxt.LocalBufHash, (void *)&new_tag, HASH_FIND, NULL); if (hresult != NULL) { b = hresult->id; buf_desc = &u_sess->storage_cxt.LocalBufferDescriptors[b]; @@ -257,8 +303,8 @@ BufferDesc *LocalBufferAlloc(SMgrRelation smgr, ForkNumber forkNum, BlockNumber pg_atomic_write_u32(&buf_desc->state, buf_state); } - hresult = (LocalBufferLookupEnt *)hash_search(u_sess->storage_cxt.LocalBufHash, (void *)&new_tag, HASH_ENTER, - &found); + hresult = + (LocalBufferLookupEnt *)hash_search(u_sess->storage_cxt.LocalBufHash, (void *)&new_tag, HASH_ENTER, &found); if (found) /* shouldn't happen */ ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), (errmsg("local buffer hash table corrupted.")))); hresult->id = b; @@ -325,8 +371,8 @@ void DropRelFileNodeLocalBuffers(const RelFileNode &rnode, ForkNumber forkNum, B int i; for (i = 0; i < u_sess->storage_cxt.NLocBuffer; i++) { - BufferDesc* buf_desc = &u_sess->storage_cxt.LocalBufferDescriptors[i]; - LocalBufferLookupEnt* hresult = NULL; + BufferDesc *buf_desc = &u_sess->storage_cxt.LocalBufferDescriptors[i]; + LocalBufferLookupEnt *hresult = NULL; uint32 buf_state; buf_state = pg_atomic_read_u32(&buf_desc->state); @@ -335,15 +381,14 @@ void DropRelFileNodeLocalBuffers(const RelFileNode &rnode, ForkNumber forkNum, B buf_desc->tag.forkNum == forkNum && buf_desc->tag.blockNum >= firstDelBlock) { if (u_sess->storage_cxt.LocalRefCount[i] != 0) { ereport(ERROR, - (errcode(ERRCODE_INVALID_BUFFER_REFERENCE), - (errmsg("block %u of %s is still referenced (local %d)", - buf_desc->tag.blockNum, - relpathbackend(buf_desc->tag.rnode, BackendIdForTempRelations, buf_desc->tag.forkNum), - u_sess->storage_cxt.LocalRefCount[i])))); + (errcode(ERRCODE_INVALID_BUFFER_REFERENCE), + (errmsg("block %u of %s is still referenced (local %d)", buf_desc->tag.blockNum, + relpathbackend(buf_desc->tag.rnode, BackendIdForTempRelations, buf_desc->tag.forkNum), + u_sess->storage_cxt.LocalRefCount[i])))); } /* Remove entry from hashtable */ - hresult = (LocalBufferLookupEnt*)hash_search( - u_sess->storage_cxt.LocalBufHash, (void*)&buf_desc->tag, HASH_REMOVE, NULL); + hresult = (LocalBufferLookupEnt *)hash_search(u_sess->storage_cxt.LocalBufHash, (void *)&buf_desc->tag, + HASH_REMOVE, NULL); if (hresult == NULL) /* shouldn't happen */ ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), (errmsg("local buffer hash table corrupted.")))); /* Mark buffer invalid */ @@ -367,8 +412,8 @@ void DropRelFileNodeAllLocalBuffers(const RelFileNode &rnode) int i; for (i = 0; i < u_sess->storage_cxt.NLocBuffer; i++) { - BufferDesc* buf_desc = &u_sess->storage_cxt.LocalBufferDescriptors[i]; - LocalBufferLookupEnt* hresult = NULL; + BufferDesc *buf_desc = &u_sess->storage_cxt.LocalBufferDescriptors[i]; + LocalBufferLookupEnt *hresult = NULL; uint32 buf_state; buf_state = pg_atomic_read_u32(&buf_desc->state); @@ -380,15 +425,14 @@ void DropRelFileNodeAllLocalBuffers(const RelFileNode &rnode) errmsg("fork number should not be less than zero"))); } ereport(ERROR, - (errcode(ERRCODE_INVALID_BUFFER_REFERENCE), - (errmsg("block %u of %s is still referenced (local %d)", - buf_desc->tag.blockNum, - relpathbackend(buf_desc->tag.rnode, BackendIdForTempRelations, buf_desc->tag.forkNum), - u_sess->storage_cxt.LocalRefCount[i])))); + (errcode(ERRCODE_INVALID_BUFFER_REFERENCE), + (errmsg("block %u of %s is still referenced (local %d)", buf_desc->tag.blockNum, + relpathbackend(buf_desc->tag.rnode, BackendIdForTempRelations, buf_desc->tag.forkNum), + u_sess->storage_cxt.LocalRefCount[i])))); } /* Remove entry from hashtable */ - hresult = (LocalBufferLookupEnt*)hash_search( - u_sess->storage_cxt.LocalBufHash, (void*)&buf_desc->tag, HASH_REMOVE, NULL); + hresult = (LocalBufferLookupEnt *)hash_search(u_sess->storage_cxt.LocalBufHash, (void *)&buf_desc->tag, + HASH_REMOVE, NULL); if (hresult == NULL) /* shouldn't happen */ ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), (errmsg("local buffer hash table corrupted.")))); /* Mark buffer invalid */ @@ -413,12 +457,12 @@ static void InitLocalBuffers(void) int i; /* Allocate and zero buffer headers and auxiliary arrays */ - u_sess->storage_cxt.LocalBufferDescriptors = (BufferDesc*)MemoryContextAllocZero( + u_sess->storage_cxt.LocalBufferDescriptors = (BufferDesc *)MemoryContextAllocZero( SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_STORAGE), (unsigned int)nbufs * sizeof(BufferDesc)); - u_sess->storage_cxt.LocalBufferBlockPointers = (Block*)MemoryContextAllocZero( + u_sess->storage_cxt.LocalBufferBlockPointers = (Block *)MemoryContextAllocZero( SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_STORAGE), (unsigned int)nbufs * sizeof(Block)); - u_sess->storage_cxt.LocalRefCount = (int32*)MemoryContextAllocZero( - SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_STORAGE), (unsigned int)nbufs * sizeof(int32)); + u_sess->storage_cxt.LocalRefCount = (int32 *)MemoryContextAllocZero(SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_STORAGE), + (unsigned int)nbufs * sizeof(int32)); if (!u_sess->storage_cxt.LocalBufferDescriptors || !u_sess->storage_cxt.LocalBufferBlockPointers || !u_sess->storage_cxt.LocalRefCount) ereport(FATAL, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("out of memory"))); @@ -427,7 +471,7 @@ static void InitLocalBuffers(void) /* initialize fields that need to start off nonzero */ for (i = 0; i < nbufs; i++) { - BufferDesc* buf = &u_sess->storage_cxt.LocalBufferDescriptors[i]; + BufferDesc *buf = &u_sess->storage_cxt.LocalBufferDescriptors[i]; /* * negative to indicate local buffer. This is tricky: shared buffers @@ -446,12 +490,11 @@ static void InitLocalBuffers(void) info.hash = tag_hash; info.hcxt = SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_STORAGE); - u_sess->storage_cxt.LocalBufHash = hash_create( - "Local Buffer Lookup Table", nbufs, &info, HASH_ELEM | HASH_CONTEXT | HASH_FUNCTION); + u_sess->storage_cxt.LocalBufHash = + hash_create("Local Buffer Lookup Table", nbufs, &info, HASH_ELEM | HASH_CONTEXT | HASH_FUNCTION); if (!u_sess->storage_cxt.LocalBufHash) { - ereport(ERROR, (errcode(ERRCODE_INITIALIZE_FAILED), - (errmsg("could not initialize local buffer hash table.")))); + ereport(ERROR, (errcode(ERRCODE_INITIALIZE_FAILED), (errmsg("could not initialize local buffer hash table.")))); } /* Initialization done, mark buffers allocated */ @@ -483,11 +526,9 @@ static Block GetLocalBufferStorage(void) * output. Create the context on first use. */ if (u_sess->storage_cxt.LocalBufferContext == NULL) - u_sess->storage_cxt.LocalBufferContext = AllocSetContextCreate(u_sess->top_mem_cxt, - "LocalBufferContext", - ALLOCSET_DEFAULT_MINSIZE, - ALLOCSET_DEFAULT_INITSIZE, - ALLOCSET_DEFAULT_MAXSIZE); + u_sess->storage_cxt.LocalBufferContext = + AllocSetContextCreate(u_sess->top_mem_cxt, "LocalBufferContext", ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); /* Start with a 16-buffer request; subsequent ones double each time */ num_bufs = Max(u_sess->storage_cxt.num_bufs_in_block * 2, 16); @@ -497,7 +538,7 @@ static Block GetLocalBufferStorage(void) num_bufs = Min((unsigned int)(num_bufs), MaxAllocSize / BLCKSZ); u_sess->storage_cxt.cur_block = - (char*)MemoryContextAlloc(u_sess->storage_cxt.LocalBufferContext, num_bufs * BLCKSZ); + (char *)MemoryContextAlloc(u_sess->storage_cxt.LocalBufferContext, num_bufs * BLCKSZ); u_sess->storage_cxt.next_buf_in_block = 0; u_sess->storage_cxt.num_bufs_in_block = num_bufs; } @@ -560,10 +601,10 @@ void AtProcExit_LocalBuffers(void) void ForgetLocalBuffer(RelFileNode rnode, ForkNumber forkNum, BlockNumber blockNum) { SMgrRelation smgr = smgropen(rnode, t_thrd.proc_cxt.MyBackendId); - BufferTag tag; /* identity of target block */ + BufferTag tag; /* identity of target block */ LocalBufferLookupEnt *hresult; BufferDesc *bufHdr; - uint32 bufState; + uint32 bufState; /* * If somehow this is the first request in the session, there's nothing to @@ -577,8 +618,7 @@ void ForgetLocalBuffer(RelFileNode rnode, ForkNumber forkNum, BlockNumber blockN INIT_BUFFERTAG(tag, smgr->smgr_rnode.node, forkNum, blockNum); /* see if the block is in the local buffer pool */ - hresult = (LocalBufferLookupEnt *) - hash_search(t_thrd.storage_cxt.LocalBufHash, (void *) &tag, HASH_REMOVE, NULL); + hresult = (LocalBufferLookupEnt *)hash_search(t_thrd.storage_cxt.LocalBufHash, (void *)&tag, HASH_REMOVE, NULL); /* didn't find it, so nothing to do */ if (!hresult) { diff --git a/src/gausskernel/storage/bulkload/foreignroutine.cpp b/src/gausskernel/storage/bulkload/foreignroutine.cpp index ce506a572..d470504e1 100644 --- a/src/gausskernel/storage/bulkload/foreignroutine.cpp +++ b/src/gausskernel/storage/bulkload/foreignroutine.cpp @@ -88,14 +88,31 @@ void checkGSOBSPrefixNoAllowRegionOption(bool hasRegion, List *LocationOPt); /* * all stuffs used for bulkload(end). */ -static void estimate_costs(PlannerInfo *root, const RelOptInfo *baserel, const DistImportPlanState *fdw_private, Cost *startup_cost, - Cost *total_cost); +static void estimate_costs(PlannerInfo *root, const RelOptInfo *baserel, const DistImportPlanState *fdw_private, + Cost *startup_cost, Cost *total_cost); +/* + * 功能:估算外部表的大小和行数 + * + * 参数列表: + * root:PlannerInfo 结构体,查询计划的上下文信息 + * baserel:RelOptInfo 结构体,关于外部表的基本关系信息 + * fdw_private:DistImportPlanState 结构体,外部表的私有状态信息 + * + * 注意: + * 该函数用于估算外部表的大小和行数,并存储在 fdw_private 结构体中。 + * 它首先尝试从 pg_class 表中获取外部表的页数和元组数估算值, + * 如果无法获取,就使用默认的估算值。 + * 步骤: + * 尝试从 pg_class 表中获取外部表的页数和元组数估算值。 + * 如果无法获取,使用默认的估算值。 + * 估算行数并存储在 RelOptInfo 结构体中。 + */ static void estimate_size(PlannerInfo *root, RelOptInfo *baserel, DistImportPlanState *fdw_private) { - struct stat stat_buf; - BlockNumber pages; - double ntuples; - double nrows; + struct stat stat_buf; // 存储文件信息的结构体 + BlockNumber pages; // 文件占用的数据块数 + double ntuples; // 元组数估算值 + double nrows; // 行数估算值 /* * Estimate the number of tuples in the file. @@ -150,13 +167,32 @@ static void estimate_size(PlannerInfo *root, RelOptInfo *baserel, DistImportPlan /* Save the output-rows estimate for the planner */ baserel->rows = nrows; } - -static void estimate_costs(PlannerInfo *root, const RelOptInfo *baserel, const DistImportPlanState *fdw_private, Cost *startup_cost, - Cost *total_cost) +/* + * 功能:估算外部表的访问成本 + * + * 参数列表: + * root:PlannerInfo 结构体,查询计划的上下文信息 + * baserel:RelOptInfo 结构体,关于外部表的基本关系信息 + * fdw_private:DistImportPlanState 结构体,外部表的私有状态信息 + * startup_cost:Cost 指针,用于存储启动成本估算值 + * total_cost:Cost 指针,用于存储总成本估算值 + * + * 注意: + * 该函数用于估算外部表的访问成本,并将结果存储在 startup_cost 和 total_cost 中。 + * 它估算成本的方式类似于 cost_seqscan() 函数,假设 I/O 成本等效于相同大小的常规表文件。 + * 不过,为了考虑解析记录的成本,将每元组的 CPU 成本设置为 seqscan 的10倍。 + * 步骤: + * 1.获取外部表的数据块数和元组数估算值。 + * 2.获取外部表的数据块数和元组数估算值。 + * 3.基于元组数估算 CPU 成本,并考虑解析记录的成本。 + * 4.计算总成本,包括启动成本和运行成本。 + */ +static void estimate_costs(PlannerInfo *root, const RelOptInfo *baserel, const DistImportPlanState *fdw_private, + Cost *startup_cost, Cost *total_cost) { - BlockNumber pages = fdw_private->pages; - double ntuples = fdw_private->ntuples; - Cost run_cost = 0; + BlockNumber pages = fdw_private->pages; // 外部表的数据块数 + double ntuples = fdw_private->ntuples; // 外部表的元组数估算值 + Cost run_cost = 0; // 运行成本 Cost cpu_per_tuple; /* @@ -167,42 +203,56 @@ static void estimate_costs(PlannerInfo *root, const RelOptInfo *baserel, const D */ run_cost += u_sess->attr.attr_sql.seq_page_cost * pages; - *startup_cost = baserel->baserestrictcost.startup; + *startup_cost = baserel->baserestrictcost.startup; // 启动成本 cpu_per_tuple = u_sess->attr.attr_sql.cpu_tuple_cost * 10 + baserel->baserestrictcost.per_tuple; - run_cost += cpu_per_tuple * ntuples; - *total_cost = *startup_cost + run_cost; + run_cost += cpu_per_tuple * ntuples; // CPU 成本 + *total_cost = *startup_cost + run_cost; // 总成本 } +/* + * 功能:该函数用于解析格式化选项字符串,并将其转化为一个列表,其中包含多个 Position 结构的元素。 + * 每个 Position 结构包括列名、开始偏移量和字段长度等信息。 + * + * 参数: + * def: 一个 DefElem 结构,包含了格式化选项字符串。 + * + * 注意: + * 该函数将解析后的结果存储在 def->arg 中,替换了原始的字符串参数。 + * 如果解析失败,函数会抛出错误。 + */ void UntransformFormatterOption(DefElem *def) { - char *s = pstrdup(strVal(def->arg)); - List *entries = NIL; - List *result = NIL; - ListCell *lc = NULL; - char *p = NULL; - char *token = NULL; + char *s = pstrdup(strVal(def->arg)); // 复制传入的字符串参数 + List *entries = NIL; // 用于存储分割后的子字符串 + List *result = NIL; // 用于存储解析后的结果 + ListCell *lc = NULL; // 用于遍历 entries 列表 + char *p = NULL; // 字符串切割的指针 + char *token = NULL; // 用于保存分割后的子字符串 - // get position string + // 使用 strtok_r 函数将字符串按 "." 分割为多个子字符串,存储在 entries 列表中 token = strtok_r(s, ".", &p); while (token != NULL) { entries = lappend(entries, token); token = strtok_r(NULL, ".", &p); } + // 遍历 entries 列表中的每个子字符串,依次解析格式化选项并将其转化为 Position 结构 foreach (lc, entries) { - Position *pos = (Position *)palloc0(sizeof(Position)); - char *end = NULL; + Position *pos = (Position *)palloc0(sizeof(Position)); // 创建 Position 结构 + char *end = NULL; // 用于检查转化是否成功 - s = (char *)lfirst(lc); + s = (char *)lfirst(lc); // 获取当前子字符串 - // Get column name + // 获取列名,列名位于子字符串中的 "(" 之前 p = strchr(s, '('); if (p == NULL) ereport(ERROR, (errcode(ERRCODE_OPERATE_INVALID_PARAM), errmsg("Invalid formatter options \"%s\"", (char *)lfirst(lc)))); + + // 提取列名并存储在 Position 结构的 colname 字段中 pos->colname = pnstrdup(s, (Size)(p - s)); - // Get begin offset + // 获取开始偏移量,开始偏移量位于 "(" 和 "," 之间 s = p + 1; p = strchr(s, ','); if (p == NULL) @@ -210,12 +260,14 @@ void UntransformFormatterOption(DefElem *def) errmsg("Invalid formatter options \"%s\"", (char *)lfirst(lc)))); s = pnstrdup(s, p - s); pos->position = (int)strtol(s, &end, 10); + + // 检查转化是否成功 if ((end == NULL) || (*end != '\0')) ereport(ERROR, (errcode(ERRCODE_OPERATE_INVALID_PARAM), errmsg("Invalid formatter options \"%s\"", (char *)lfirst(lc)))); - pfree(s); + pfree(s); // 释放临时字符串 - // Get length of field + // 获取字段长度,字段长度位于 "," 和 ")" 之间 s = p + 1; p = strchr(s, ')'); if (p == NULL) @@ -223,13 +275,21 @@ void UntransformFormatterOption(DefElem *def) errmsg("Invalid formatter options \"%s\"", (char *)lfirst(lc)))); s = pnstrdup(s, p - s); pos->fixedlen = (int)strtol(s, &end, 10); + + // 检查转化是否成功 if ((end == NULL) || *end != '\0') ereport(ERROR, (errcode(ERRCODE_OPERATE_INVALID_PARAM), errmsg("Invalid formatter options \"%s\"", (char *)lfirst(lc)))); - pfree(s); + pfree(s); // 释放临时字符串 + + // 将完整的 Position 结构添加到 result 列表中 result = lappend(result, pos); } + + // 释放 entries 列表中的字符串 list_free_ext(entries); + + // 更新 def->arg 为解析后的 result 列表 def->arg = (Node *)result; } @@ -253,30 +313,70 @@ static const int URI_protocols_num = sizeof(URI_protocols) / sizeof(URI_protocol * @Return: index within global protocol map * @See also: */ -static int search_existing_procotols(const char *protocol) +/* + * 功能:搜索现有协议数组,查找指定协议的索引 + * + * 参数列表: + * protocol:const char 指针,要查找的协议名称 + * + * 返回值: + * int 值,指定协议的索引位置 + * + * 注意: + * 该函数用于搜索现有协议数组,以查找指定协议的索引位置。 + * 如果找到匹配的协议,返回协议在数组中的索引位置。 + * 如果找不到匹配的协议,使用 Assert 断言触发错误。 + * 步骤: + * 1. 初始化变量 i 为 0。 + * 2. 使用循环遍历协议数组 URI_protocols。 + * 3. 如果找到与 protocol 相等的协议,跳出循环。 + * 4. 使用 Assert 断言确保索引 i 的值在合法范围内。 + * 5. 返回协议的索引位置 i。 + */ +static int search_existing_protocols(const char *protocol) { - int i = 0; + int i = 0; // 初始化变量 i 为 0 for (; i < URI_protocols_num; i++) { - if (0 == strcmp(protocol, URI_protocols[i])) { + if (0 == strcmp(protocol, URI_protocols[i])) { // 如果找到与 protocol 相等的协议,跳出循环 break; } } - Assert(i >= 0 && i <= URI_protocols_num - 1); - return i; + Assert(i >= 0 && i <= URI_protocols_num - 1); // 使用 Assert 断言确保索引 i 的值在合法范围内 + return i; // 返回协议的索引位置 i } +/* + * 功能:检查协议是否存在冲突 + * + * 参数列表: + * tmpuri:GDSUri 结构体,表示当前处理的 URI 信息 + * first_protocol_idx:int 值,表示第一个协议的索引位置 + * + * 返回值: + * bool 值,表示是否存在冲突 + * + * 注意: + * 该函数用于检查协议是否存在冲突。 + * 如果第一个协议不是 LOCAL_PREFIX,但后面的协议是 LOCAL_PREFIX,则存在冲突。 + * 如果第一个协议与后面的协议不同,则存在冲突。 + * 步骤: + * 1. 如果 tmpuri 中的协议为空,并且第一个协议索引不是 0,表示存在冲突,返回 true。 + * 2. 如果 tmpuri 中的协议不为空,并且与第一个协议不相等,表示存在冲突,返回 true。 + * 3. 如果以上两种情况都不满足,表示没有冲突,返回 false。 + */ + static bool ProtocolHasConflict(const GDSUri &tmpuri, int first_protocol_idx) { - /* conflict: the first is not LOCAL_PREFIX but the later one is */ + /* 冲突:第一个协议不是 LOCAL_PREFIX,但后面的协议是 LOCAL_PREFIX */ if (tmpuri.m_protocol == NULL && first_protocol_idx != 0) { return true; } - /* conflict: the remaining are different from the first */ + /* 冲突:后面的协议与第一个协议不同 */ if (tmpuri.m_protocol != NULL && strcmp(tmpuri.m_protocol, URI_protocols[first_protocol_idx]) != 0) { return true; } - return false; + return false; // 没有冲突 } /* @@ -285,6 +385,25 @@ static bool ProtocolHasConflict(const GDSUri &tmpuri, int first_protocol_idx) * @IN lcs: LOCATION options list * @See also: */ +/* + * 功能:验证 URI 列表中的位置信息 + * + * 参数列表: + * lcs:List 结构体指针,包含位置信息的 URI 列表 + * + * 注意: + * 该函数用于验证 URI 列表中的位置信息是否合法。 + * 首先检查第一个位置是否合法,然后逐一检查后续位置是否与第一个位置协议相同。 + * 如果发现任何位置不合法或与第一个位置协议不同,将引发错误。 + * + * 步骤: + * 1. 获取第一个位置信息,并将其转换为字符串。 + * 2. 检查第一个位置是否合法,如果不合法,引发错误。 + * 3. 解析第一个位置的 URI,获取其协议,并在已知协议列表中查找对应的索引。 + * 4. 遍历 URI 列表中的每个位置。 + * 5. 对于每个位置,解析其 URI,检查协议是否与第一个位置相同,如果不同,引发错误。 + */ + static void VerifyLocations(const List *lcs) { ListCell *lc = NULL; @@ -299,7 +418,7 @@ static void VerifyLocations(const List *lcs) GDSUri uri; uri.Parse(first); if (uri.m_protocol) { - first_protocol_idx = search_existing_procotols(uri.m_protocol); + first_protocol_idx = search_existing_protocols(uri.m_protocol); } else { /* at default it's a local protocol */ first_protocol_idx = 0; @@ -321,6 +440,23 @@ static void VerifyLocations(const List *lcs) * @param lcs location options list * @return void */ +/* + * 功能:检查 URI 列表中是否存在重复的位置信息 + * + * 参数列表: + * lcs:List 结构体指针,包含位置信息的 URI 列表 + * + * 注意: + * 该函数用于检查 URI 列表中是否存在重复的位置信息。 + * 它遍历 URI 列表,对每个位置信息进行解析,并比较端口号和主机名是否相同。 + * 如果发现重复的位置信息,将引发错误。 + * + * 步骤: + * 1. 遍历 URI 列表中的每个位置。 + * 2. 解析每个位置的 URI,并忽略无效的位置信息。 + * 3. 对于每个有效的位置信息,检查是否与之前的位置信息重复。 + * 4. 如果发现重复的位置信息,引发错误。 + */ static void CheckDupLocations(const List *lcs) { ListCell *lc = NULL; @@ -370,33 +506,73 @@ DesErr: } } +/* + * 功能:验证文件头部的位置信息是否合法 + * + * 参数列表: + * path:const char 指针,包含文件头部位置信息的路径 + * + * 注意: + * 该函数用于验证文件头部的位置信息是否合法。 + * 它首先解析路径中的 URI 信息,然后检查协议和路径是否满足要求。 + * 如果位置信息不合法,将引发错误。 + */ + static void VerifyFileHeader(const char *path) { GDSUri uri; + // 解析路径中的 URI 信息 uri.Parse(path); + + // 检查协议是否存在 if (uri.m_protocol != NULL) { + // 检查路径是否以斜杠开头 if (uri.m_path == NULL || uri.m_path[0] != '/') { ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("invalid file header location \"%s\"", uri.m_uri))); } } } +/* + * 功能:验证文件名前缀是否合法 + * + * 参数列表: + * name:const char 指针,要验证的文件名前缀 + * isExport:bool,表示是否是导出操作 + * + * 注意: + * 该函数用于验证文件名前缀是否合法。 + * 验证的规则包括以下几个方面: + * 1. 如果不是导出操作,文件名前缀只允许在写入操作的外部表中使用,否则引发错误。 + * 2. 文件名前缀不允许为空,否则引发错误。 + * 3. 文件名前缀不允许包含以下特殊字符:'/', '?', '*', ':', '|', '\\', '<', '>', '@', '#', '$', '&', '(', ')', '+', + * '-'。 + * 4. 文件名前缀不允许与以下字符串相同:"con", "aux", "nul", "prn", "com0", "com1", "com2", "com3", + * "com4", "com5", "com6", "com7", "com8", "com9", "lpt0", "lpt1", "lpt2", "lpt3", "lpt4", "lpt5", "lpt6", "lpt7", + * "lpt8", "lpt9"。 + * 5. 文件名前缀只允许包含字母、数字和下划线字符 ('_'),其他字符均不允许。 + */ static void VerifyFilenamePrefix(const char *name, bool isExport) { + // 检查参数是否有效 Assert(PointerIsValid(name)); + // 如果不是导出操作,文件名前缀只允许在写入操作的外部表中使用,否则引发错误 if (!isExport) { ereport(ERROR, (errcode(ERRCODE_OPERATE_INVALID_PARAM), errmsg("out_filename_prefix is only allowed in write-only foreign tables"))); } + // 检查文件名前缀是否为空,如果为空则引发错误 if (strlen(name) == 0) { ereport(ERROR, (errcode(ERRCODE_OPERATE_INVALID_PARAM), errmsg("out_filename_prefix should not be empty"))); } - const char illegalChars[16] = { '/', '?', '*', ':', '|', '\\', '<', '>', '@', '#', '$', '&', '(', ')', '+', '-' }; + // 定义不允许包含的特殊字符数组 + const char illegalChars[16] = {'/', '?', '*', ':', '|', '\\', '<', '>', '@', '#', '$', '&', '(', ')', '+', '-'}; + // 遍历不允许包含的特殊字符数组,如果文件名前缀中包含其中之一,引发错误 for (int i = 0; i < 16; i++) { if (strchr(name, illegalChars[i]) != NULL) { ereport(ERROR, (errcode(ERRCODE_OPERATE_INVALID_PARAM), @@ -404,10 +580,12 @@ static void VerifyFilenamePrefix(const char *name, bool isExport) } } - const char *illegalStrings[24] = { - "con", "aux", "nul", "prn", "com0", "com1", "com2", "com3", "com4", "com5", "com6", "com7", - "com8", "com9", "lpt0", "lpt1", "lpt2", "lpt3", "lpt4", "lpt5", "lpt6", "lpt7", "lpt8", "lpt9" - }; + // 定义不允许与之相同的字符串数组 + const char *illegalStrings[24] = {"con", "aux", "nul", "prn", "com0", "com1", "com2", "com3", + "com4", "com5", "com6", "com7", "com8", "com9", "lpt0", "lpt1", + "lpt2", "lpt3", "lpt4", "lpt5", "lpt6", "lpt7", "lpt8", "lpt9"}; + + // 遍历不允许与之相同的字符串数组,如果文件名前缀与其中之一相同,引发错误 for (int i = 0; i < 24; i++) { if (strcmp(name, illegalStrings[i]) == 0) { ereport(ERROR, (errcode(ERRCODE_OPERATE_INVALID_PARAM), @@ -415,6 +593,7 @@ static void VerifyFilenamePrefix(const char *name, bool isExport) } } + // 遍历文件名前缀的每个字符,如果包含非字母、数字和下划线字符 ('_'),引发错误 for (const char *c = name; *c; c++) { if (!((isalnum(*c) || (*c == '_')))) { ereport(ERROR, (errcode(ERRCODE_OPERATE_INVALID_PARAM), @@ -423,29 +602,67 @@ static void VerifyFilenamePrefix(const char *name, bool isExport) } } +/* + * 功能:验证对齐方式是否合法 + * + * 参数列表: + * alignment:const char 指针,要验证的对齐方式字符串 + * isExport:bool,表示是否是导出操作 + * format:FileFormat,表示文件格式 + * + * 注意: + * 该函数用于验证对齐方式是否合法。 + * 验证的规则包括以下几个方面: + * 1. 如果不是导出操作,对齐方式只允许在写入操作的外部表中使用,否则引发错误。 + * 2. 只有在文件格式为 FIXED 时,才允许使用对齐方式,否则引发错误。 + * 3. 对齐方式只允许为 "align_left" 或 "align_right",其他值均不允许。 + */ static void VerifyFixAlignment(const char *alignment, bool isExport, FileFormat format) { + // 检查参数是否有效 Assert(PointerIsValid(alignment)); + + // 如果不是导出操作,对齐方式只允许在写入操作的外部表中使用,否则引发错误 if (!isExport) { ereport(ERROR, (errcode(ERRCODE_OPERATE_INVALID_PARAM), errmsg("out_fix_alignment is only allowed in write-only foreign tables"))); } + // 只有在文件格式为 FIXED 时,才允许使用对齐方式,否则引发错误 if (format != FORMAT_FIXED) { ereport(ERROR, (errcode(ERRCODE_OPERATE_INVALID_PARAM), errmsg("out_fix_alignment is only allowed with fixed format"))); } - if (!(((strcmp(alignment, "align_left") == 0) || (strcmp(alignment, "align_right") == 0)))) { + // 对齐方式只允许为 "align_left" 或 "align_right",其他值均不允许 + if (!((strcmp(alignment, "align_left") == 0) || (strcmp(alignment, "align_right") == 0))) { ereport(ERROR, (errcode(ERRCODE_OPERATE_INVALID_PARAM), errmsg("Only \"align_left\" and \"align_right\" is allowed in out_fix_alignment option"))); } } +/* + * 功能:验证用户定义名称是否合法 + * + * 参数列表: + * input:const char 指针,要验证的用户定义名称字符串 + * + * 返回值: + * 合法返回 true,否则返回 false + * + * 注意: + * 该函数用于验证用户定义名称是否合法。 + * 验证的规则包括以下几个方面: + * 1. 用户定义名称的第一个字符不能是数字('0' 到 '9')或美元符号 ('$'),否则返回 false。 + * 2. 用户定义名称只能包含小写字母、大写字母、数字、下划线 ('_')、美元符号 ('$') 和句点 + * ('.'),其他字符均不允许,否则返回 false。 + * 3. 如果用户定义名称符合上述规则,则返回 true,表示合法;否则返回 false,表示不合法。 + */ static bool IsValidUserDefineName(const char *input) { char c = input[0]; - /* The first character id numbers or dollar */ + + // 用户定义名称的第一个字符不能是数字('0' 到 '9')或美元符号 ('$'),否则返回 false if ((c >= '1' && c <= '9') || c == '$') { return false; } @@ -453,6 +670,9 @@ static bool IsValidUserDefineName(const char *input) int len = (int)strlen(input); for (int i = 0; i < len; i++) { c = input[i]; + + // 用户定义名称只能包含小写字母、大写字母、数字、下划线 ('_')、美元符号 ('$') 和句点 + // ('.'),其他字符均不允许,否则返回 false if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_' || c == '$' || c == '.') { continue; @@ -469,14 +689,31 @@ static bool IsValidUserDefineName(const char *input) * @in LocationOPt, the location option list to be given. * @return none. */ +/* + * 功能:检查是否允许设置 region 选项 + * + * 参数列表: + * hasRegion:bool,表示是否存在 region 选项 + * LocationOPt:List 指针,包含外部表的 location 选项列表 + * + * 注意: + * 该函数用于检查是否允许设置 region 选项。如果外部表的 location 选项列表中包含 "gsobs" 前缀,则不允许设置 region + * 选项。 如果存在 region 选项且包含 "gsobs" 前缀,则会抛出错误。 + * + * 返回值: + * 无 + */ void checkGSOBSPrefixNoAllowRegionOption(bool hasRegion, List *LocationOPt) { + // 如果没有 region 选项,直接返回 if (hasRegion == false) { return; } + char *firstLocation = strVal(lfirst(list_head(LocationOPt))); char *tmpLocation = TrimStr(firstLocation); + // 检查 location 选项是否以 "gsobs" 前缀开头,如果是,则抛出错误 if (0 == pg_strncasecmp(tmpLocation, GSOBS_PREFIX, strlen(GSOBS_PREFIX))) { pfree(tmpLocation); ereport(ERROR, (errcode(ERRCODE_FDW_DYNAMIC_PARAMETER_VALUE_NEEDED), errmodule(MOD_OBS), @@ -487,39 +724,77 @@ void checkGSOBSPrefixNoAllowRegionOption(bool hasRegion, List *LocationOPt) pfree(tmpLocation); } -void processOBSNoAllowOptions(bool specifyMode, const char *fileheader, - const char *OutputFilenamePrefix, const char *OutputFixAlignment, bool doLogRemote) +/* + * 功能:处理不允许的 OBS 外部表选项 + * + * 参数列表: + * specifyMode:bool,指示是否指定了 mode 选项 + * fileheader:const char 指针,表示文件头选项值 + * OutputFilenamePrefix:const char 指针,表示输出文件前缀选项值 + * OutputFixAlignment:const char 指针,表示输出固定对齐选项值 + * doLogRemote:bool,指示是否设置了 remote_log 选项 + * + * 注意: + * 该函数用于处理不允许的 OBS 外部表选项。如果指定了不允许的选项,函数会抛出错误。 + */ +void processOBSNoAllowOptions(bool specifyMode, const char *fileheader, const char *OutputFilenamePrefix, + const char *OutputFixAlignment, bool doLogRemote) { + // 如果指定了 mode 选项,抛出错误 if (specifyMode) { ereport(ERROR, (errcode(ERRCODE_OPERATE_INVALID_PARAM), errmsg("OBS foreign table does not support '%s' option", optMode))); - } else if (fileheader != NULL) { + } + // 如果指定了 fileheader 选项,抛出错误 + else if (fileheader != NULL) { ereport(ERROR, (errcode(ERRCODE_OPERATE_INVALID_PARAM), errmsg("OBS foreign table does not support '%s' option", optFileHeader))); - } else if (OutputFilenamePrefix != NULL) { + } + // 如果指定了 OutputFilenamePrefix 选项,抛出错误 + else if (OutputFilenamePrefix != NULL) { ereport(ERROR, (errcode(ERRCODE_OPERATE_INVALID_PARAM), errmsg("OBS foreign table does not support '%s' option", optOutputFilePrefix))); - } else if (OutputFixAlignment != NULL) { + } + // 如果指定了 OutputFixAlignment 选项,抛出错误 + else if (OutputFixAlignment != NULL) { ereport(ERROR, (errcode(ERRCODE_OPERATE_INVALID_PARAM), errmsg("OBS foreign table does not support '%s' option", optOutputFixAlignment))); - } else if (doLogRemote) { + } + // 如果设置了 remote_log 选项,抛出错误 + else if (doLogRemote) { ereport(ERROR, (errcode(ERRCODE_OPERATE_INVALID_PARAM), errmsg("OBS foreign table does not support remote_log option"))); } } - -void processOBSHaveToOptions(const char *accessKeyStr, const char *secretAccessKeyStr, - FileFormat format, const char *encryptStr, const char *chunksizeStr, bool writeOnly) +/* + * 功能:处理必需的 OBS 外部表选项 + * + * 参数列表: + * accessKeyStr:const char 指针,表示 accessKey 选项值 + * secretAccessKeyStr:const char 指针,表示 secretAccessKey 选项值 + * format:FileFormat,表示文件格式选项值 + * encryptStr:const char 指针,表示 encrypt 选项值 + * chunksizeStr:const char 指针,表示 chunksize 选项值 + * writeOnly:bool,指示是否是只写模式 + * + * 注意: + * 该函数用于处理必需的 OBS 外部表选项。如果必需的选项未指定或选项值无效,函数会抛出错误。 + */ +void processOBSHaveToOptions(const char *accessKeyStr, const char *secretAccessKeyStr, FileFormat format, + const char *encryptStr, const char *chunksizeStr, bool writeOnly) { // default FORMAT_UNKNOWN value is TEXT if (format == FORMAT_UNKNOWN) { format = FORMAT_TEXT; } + // 检查 accessKey 选项是否指定 if (accessKeyStr == NULL) { ereport(ERROR, (errcode(ERRCODE_OPERATE_INVALID_PARAM), errmsg("OBS foreign table have to specify '%s' option", optAccessKey))); - } else if (secretAccessKeyStr == NULL) { + } + // 检查 secretAccessKey 选项是否指定 + else if (secretAccessKeyStr == NULL) { ereport(ERROR, (errcode(ERRCODE_OPERATE_INVALID_PARAM), errmsg("OBS foreign table have to specify '%s' option", optSecretAccessKey))); } @@ -548,17 +823,29 @@ void processOBSHaveToOptions(const char *accessKeyStr, const char *secretAccessK /* Error-out unallowed rage */ if (chunksize < 8 || chunksize > 512) { - ereport(ERROR, - (errcode(ERRCODE_OPERATE_INVALID_PARAM), - (errmsg("Invalid 'chunksize' option value '%s' for OBS Read-Only table, valid range [8, 512] in MB", - chunksizeStr)))); + ereport( + ERROR, + (errcode(ERRCODE_OPERATE_INVALID_PARAM), + (errmsg("Invalid 'chunksize' option value '%s' for OBS Read-Only table, valid range [8, 512] in MB", + chunksizeStr)))); } else if (writeOnly) { ereport(ERROR, (errcode(ERRCODE_OPERATE_INVALID_PARAM), (errmsg("Option 'chunksize' is not allowed in OBS write-only table")))); } } } - +/* + * 功能:处理 OBS 外部表的位置选项 + * + * 参数列表: + * writeOnly:bool,指示是否是只写模式 + * source:List 指针,包含外部表的位置选项列表 + * + * 注意: + * 该函数用于处理 OBS 外部表的位置选项。根据不同模式(只读或只写),进行不同的验证。 + * 在只写模式下,不允许指定多个位置。 + * 在只读模式下,会检查位置选项是否有重复。 + */ void processOBSLocationOptions(bool writeOnly, List *source) { if (writeOnly) { @@ -606,7 +893,7 @@ void processOBSLocationOptions(bool writeOnly, List *source) temp_location_list = lappend(temp_location_list, makeString(temp_location)); } -DesErr: + DesErr: foreach (temp_location_cell, temp_location_list) { char *tempstr = strVal(lfirst(temp_location_cell)); pfree(tempstr); @@ -616,9 +903,22 @@ DesErr: ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("duplicated URL \"%s\" in LOCATION", location))); } } - -void processNoneOBSOptions(const char *chunksizeStr, const char *encryptStr, - const char *accessKeyStr, const char *secretAccessKeyStr, bool hasRegion) +/* + * 功能:处理非 OBS 外部表的选项 + * + * 参数列表: + * chunksizeStr:const char 指针,指向选项 chunksize 的字符串值 + * encryptStr:const char 指针,指向选项 encrypt 的字符串值 + * accessKeyStr:const char 指针,指向选项 accessKey 的字符串值 + * secretAccessKeyStr:const char 指针,指向选项 secretAccessKey 的字符串值 + * hasRegion:bool,指示是否包含 region 选项 + * + * 注意: + * 该函数用于处理非 OBS 外部表的选项。根据不同选项,执行相应的验证。 + * 如果选项不支持,将生成错误消息报告。 + */ +void processNoneOBSOptions(const char *chunksizeStr, const char *encryptStr, const char *accessKeyStr, + const char *secretAccessKeyStr, bool hasRegion) { if (chunksizeStr != NULL) { ereport(ERROR, (errcode(ERRCODE_OPERATE_INVALID_PARAM), @@ -637,7 +937,18 @@ void processNoneOBSOptions(const char *chunksizeStr, const char *encryptStr, errmsg("None OBS foreign table does not support '%s' option", OPTION_NAME_REGION))); } } - +/* + * 功能:处理分布式导入计划的选项 + * + * 参数列表: + * planstate:DistImportPlanState 结构体指针,用于存储导入计划的状态信息 + * options:选项列表,包含用于配置导入操作的各种选项 + * isPropagateToFE:布尔值,表示是否需要将选项传播到前端 + * isValidate:布尔值,表示是否执行验证操作 + * + * 注意: + * 该函数用于处理导入计划的各种选项,包括验证选项的合法性、设置默认值、引发错误或警告等。 + */ void ProcessDistImportOptions(DistImportPlanState *planstate, List *options, bool isPropagateToFE, bool isValidate) { ListCell *lc = NULL; @@ -679,372 +990,520 @@ void ProcessDistImportOptions(DistImportPlanState *planstate, List *options, boo planstate->fileEncoding = -1; + // 遍历选项列表 foreach (lc, options) { DefElem *def = (DefElem *)lfirst(lc); + // 检查选项名称是否为 optChunkSize if (pg_strcasecmp(def->defname, optChunkSize) == 0) { chunksizeStr = defGetString(def); - } else if (pg_strcasecmp(def->defname, optEncrypt) == 0) { + } + // 检查选项名称是否为 optEncrypt + else if (pg_strcasecmp(def->defname, optEncrypt) == 0) { encryptStr = defGetString(def); - } else if (pg_strcasecmp(def->defname, optAccessKey) == 0) { + } + // 检查选项名称是否为 optAccessKey + else if (pg_strcasecmp(def->defname, optAccessKey) == 0) { accessKeyStr = defGetString(def); - } else if (pg_strcasecmp(def->defname, optSecretAccessKey) == 0) { + } + // 检查选项名称是否为 optSecretAccessKey + else if (pg_strcasecmp(def->defname, optSecretAccessKey) == 0) { secretAccessKeyStr = defGetString(def); - } else if (pg_strcasecmp(def->defname, optLocation) == 0) { + } + // 检查选项名称是否为 optLocation + else if (pg_strcasecmp(def->defname, optLocation) == 0) { locations = defGetString(def); planstate->filename = pstrdup(locations); planstate->source = DeserializeLocations(locations); - } else if (pg_strcasecmp(def->defname, OPTION_NAME_REGION) == 0) { - hasRegion = true; - } else if (pg_strcasecmp(def->defname, optMode) == 0) { - if (strcasecmp(strVal(def->arg), "normal") == 0) - planstate->mode = MODE_NORMAL; - else if (strcasecmp(strVal(def->arg), "shared") == 0) - planstate->mode = MODE_SHARED; - else if (strcasecmp(strVal(def->arg), "private") == 0) - planstate->mode = MODE_PRIVATE; - else - ereport(ERROR, (errcode(ERRCODE_OPERATE_INVALID_PARAM), - errmsg("Loading mode \"%s\" not recognized", strVal(def->arg)))); - specifyMode = true; - rmList = lappend(rmList, def); - } else if (pg_strcasecmp(def->defname, optRejectLimit) == 0) { - if (rejectLimitSpecified) - ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"))); - if (pg_strcasecmp(defGetString(def), "unlimited") == 0) - planstate->rejectLimit = REJECT_UNLIMITED; - else { - char *value = defGetString(def); - int limit = pg_strtoint32(value); - planstate->rejectLimit = (limit > 0 ? limit : 0); - } - rejectLimitSpecified = true; - rmList = lappend(rmList, def); - } else if (pg_strcasecmp(def->defname, optErrorRel) == 0) { - if (planstate->errorName != NULL) - ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"))); - planstate->errorName = defGetString(def); - rmList = lappend(rmList, def); - } else if (pg_strcasecmp(def->defname, optWriteOnly) == 0) { - if (writeOnlySpecified) - ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"))); - writeOnlySpecified = true; - planstate->writeOnly = defGetBoolean(def); - rmList = lappend(rmList, def); - } else if (pg_strcasecmp(def->defname, optEncoding) == 0) { - // We just get the encoding here. Do not remove it from the options list. - if (planstate->fileEncoding >= 0) - ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"))); - planstate->fileEncoding = pg_char_to_encoding(defGetString(def)); - if (planstate->fileEncoding < 0) - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("argument to option \"%s\" must be a valid encoding name", def->defname))); - } else if (pg_strcasecmp(def->defname, optFormat) == 0) { - char *fmt = defGetString(def); - - if (strcasecmp(fmt, "text") == 0) - format = FORMAT_TEXT; - else if (strcasecmp(fmt, "csv") == 0) - format = FORMAT_CSV; - else if (strcasecmp(fmt, "fixed") == 0) - format = FORMAT_FIXED; - else - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("LOAD format \"%s\" not recognized", fmt))); - } else if (pg_strcasecmp(def->defname, optFormatter) == 0) - UntransformFormatterOption(def); - else if (pg_strcasecmp(def->defname, optFileHeader) == 0) { - if (fileheader != NULL) - ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"))); - fileheader = defGetString(def); - } else if (pg_strcasecmp(def->defname, optOutputFilePrefix) == 0) { - if (OutputFilenamePrefix != NULL) - ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"))); - OutputFilenamePrefix = defGetString(def); - } else if (pg_strcasecmp(def->defname, optOutputFixAlignment) == 0) { - if (OutputFixAlignment != NULL) - ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"))); - OutputFixAlignment = defGetString(def); - } else if (pg_strcasecmp(def->defname, optHeader) == 0) { - hasHeader = defGetBoolean(def); - } else if (pg_strcasecmp(def->defname, optLogRemote) == 0) { - if (planstate->doLogRemote) - ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"))); - planstate->remoteName = defGetString(def); - planstate->doLogRemote = true; - rmList = lappend(rmList, def); - } else if (pg_strcasecmp(def->defname, optFillMissFields) == 0) - specifyFillMissing = true; - else if (pg_strcasecmp(def->defname, optIgnoreExtraData) == 0) - specifyIgnoreExtraData = true; - /* - * check whether bulkload compatible illegal chars option exists or not. - */ - else if (pg_strcasecmp(def->defname, optCompatibleIllegalChars) == 0) - specifyCompatibleIllegalChars = true; - /* - * check whether bulkload datetime format options exists or not. - */ - else if (pg_strcasecmp(def->defname, optDateFormat) == 0) { - specifyDateFormat = true; - /* - * check whether date format is valid; - */ - check_datetime_format(defGetString(def)); - } else if (pg_strcasecmp(def->defname, optTimeFormat) == 0) { - specifyTimeFormat = true; - /* - * check whether time format is valid; - */ - check_datetime_format(defGetString(def)); - } else if (pg_strcasecmp(def->defname, optTimestampFormat) == 0) { - specifyTimestampFormat = true; - /* - * check whether timestamp format is valid; - */ - check_datetime_format(defGetString(def)); - } else if (pg_strcasecmp(def->defname, optSmalldatetimeFormat) == 0) { - specifySmalldatetimeFormat = true; - /* - * check whether smalldatetime format is valid; - */ - check_datetime_format(defGetString(def)); - } else if (pg_strcasecmp(def->defname, optFix) == 0) { - char *end = NULL; - force_fix_width = (int)strtol(defGetString(def), &end, 10); - if (*end != '\0') - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("invalid value of FIX"))); } + // 检查选项名称是否为 OPTION_NAME_REGION + else if (pg_strcasecmp(def->defname, OPTION_NAME_REGION) == 0) { + hasRegion = true; + } + // 检查选项名称是否为 optMode } + else if (pg_strcasecmp(def->defname, optMode) == 0) + { + if (strcasecmp(strVal(def->arg), "normal") == 0) + planstate->mode = MODE_NORMAL; + else if (strcasecmp(strVal(def->arg), "shared") == 0) + planstate->mode = MODE_SHARED; + else if (strcasecmp(strVal(def->arg), "private") == 0) + planstate->mode = MODE_PRIVATE; + else + ereport(ERROR, (errcode(ERRCODE_OPERATE_INVALID_PARAM), + errmsg("Loading mode \"%s\" not recognized", strVal(def->arg)))); + specifyMode = true; + rmList = lappend(rmList, def); + } // 检查选项名称是否为 optRejectLimit + else if (pg_strcasecmp(def->defname, optRejectLimit) == 0) + { + // 如果已经指定了 rejectLimit 选项,则报告冲突或多余的选项错误 + if (rejectLimitSpecified) + ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"))); + + // 如果选项值为 "unlimited",则将 planstate->rejectLimit 设置为 REJECT_UNLIMITED + if (pg_strcasecmp(defGetString(def), "unlimited") == 0) + planstate->rejectLimit = REJECT_UNLIMITED; + else { + // 否则,尝试将选项值解析为整数 + char *value = defGetString(def); + int limit = pg_strtoint32(value); + // 如果解析成功,将 limit 设置为正数,否则设置为 0 + planstate->rejectLimit = (limit > 0 ? limit : 0); + } + + // 标记 rejectLimitSpecified 为 true,表示已经指定了 rejectLimit 选项 + rejectLimitSpecified = true; + + // 将当前选项添加到 rmList 列表中,以便稍后从选项列表中移除 + rmList = lappend(rmList, def); + } + // 检查选项名称是否为 optErrorRel + else if (pg_strcasecmp(def->defname, optErrorRel) == 0) + { + // 如果 planstate->errorName 已经被设置,则报告冲突或多余的选项错误 + if (planstate->errorName != NULL) + ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"))); + + // 将选项值设置为 planstate->errorName,表示错误关系的名称 + planstate->errorName = defGetString(def); + + // 将当前选项添加到 rmList 列表中,以便稍后从选项列表中移除 + rmList = lappend(rmList, def); + } + // 检查选项名称是否为 optWriteOnly + else if (pg_strcasecmp(def->defname, optWriteOnly) == 0) + { + // 如果已经指定了 writeOnly 选项,则报告冲突或多余的选项错误 + if (writeOnlySpecified) + ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"))); + + // 标记 writeOnlySpecified 为 true,表示已经指定了 writeOnly 选项 + writeOnlySpecified = true; + + // 将选项值设置为 planstate->writeOnly,表示是否为只写操作 + planstate->writeOnly = defGetBoolean(def); + + // 将当前选项添加到 rmList 列表中,以便稍后从选项列表中移除 + rmList = lappend(rmList, def); + } + // 检查选项名称是否为 optEncoding + else if (pg_strcasecmp(def->defname, optEncoding) == 0) + { + // 仅获取编码,不从选项列表中移除它 + // 如果已经设置了 planstate->fileEncoding,则报告冲突或多余的选项错误 + if (planstate->fileEncoding >= 0) + ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"))); + + // 将选项值解析为编码值,并设置为 planstate->fileEncoding + planstate->fileEncoding = pg_char_to_encoding(defGetString(def)); + + // 如果编码无效,报告无效的参数值错误 + if (planstate->fileEncoding < 0) + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("argument to option \"%s\" must be a valid encoding name", def->defname))); + } + // 检查选项名称是否为 optFormat + else if (pg_strcasecmp(def->defname, optFormat) == 0) + { + // 获取选项值作为文件格式的字符串表示 + char *fmt = defGetString(def); + + // 根据字符串值设置文件格式 + if (strcasecmp(fmt, "text") == 0) + format = FORMAT_TEXT; + else if (strcasecmp(fmt, "csv") == 0) + format = FORMAT_CSV; + else if (strcasecmp(fmt, "fixed") == 0) + format = FORMAT_FIXED; + else + // 如果格式不被识别,报告无效的参数值错误 + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("LOAD format \"%s\" not recognized", fmt))); + } + // 检查选项名称是否为 optFormatter,并调用 UntransformFormatterOption 进行处理 + else if (pg_strcasecmp(def->defname, optFormatter) == 0) UntransformFormatterOption(def); + // 检查选项名称是否为 optFileHeader + else if (pg_strcasecmp(def->defname, optFileHeader) == 0) + { + // 如果已经指定了 fileheader 选项,则报告冲突或多余的选项错误 + if (fileheader != NULL) + ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"))); + + // 将选项值设置为 fileheader,表示文件头信息 + fileheader = defGetString(def); + } + // 检查选项名称是否为 optOutputFilePrefix + else if (pg_strcasecmp(def->defname, optOutputFilePrefix) == 0) + { + // 如果已经指定了 OutputFilenamePrefix 选项,则报告冲突或多余的选项错误 + if (OutputFilenamePrefix != NULL) + ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"))); + + // 将选项值设置为 OutputFilenamePrefix,表示输出文件名前缀 + OutputFilenamePrefix = defGetString(def); + } + // 检查选项名称是否为 optOutputFixAlignment + else if (pg_strcasecmp(def->defname, optOutputFixAlignment) == 0) + { + // 如果已经指定了 OutputFixAlignment 选项,则报告冲突或多余的选项错误 + if (OutputFixAlignment != NULL) + ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"))); + + // 将选项值设置为 OutputFixAlignment,表示输出固定宽度对齐 + OutputFixAlignment = defGetString(def); + } + // 检查选项名称是否为 optHeader + else if (pg_strcasecmp(def->defname, optHeader) == 0) + { + // 获取选项值,表示是否存在文件头 + hasHeader = defGetBoolean(def); + } + // 检查选项名称是否为 optLogRemote + else if (pg_strcasecmp(def->defname, optLogRemote) == 0) + { + // 如果已经指定了 planstate->doLogRemote 选项,则报告冲突或多余的选项错误 + if (planstate->doLogRemote) + ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("conflicting or redundant options"))); + + // 将选项值设置为 planstate->remoteName,表示远程日志记录名称 + planstate->remoteName = defGetString(def); + + // 标记 planstate->doLogRemote 为 true,表示需要记录远程操作 + planstate->doLogRemote = true; + + // 将当前选项添加到 rmList 列表中,以便稍后从选项列表中移除 + rmList = lappend(rmList, def); + } + // 检查选项名称是否为 optFillMissFields + else if (pg_strcasecmp(def->defname, optFillMissFields) == 0) + // 标记 specifyFillMissing 为 true,表示需要填充缺失字段 + specifyFillMissing = true; + // 检查选项名称是否为 optIgnoreExtraData + else if (pg_strcasecmp(def->defname, optIgnoreExtraData) == 0) + // 标记 specifyIgnoreExtraData 为 true,表示需要忽略多余数据 + specifyIgnoreExtraData = true; /* - * Validate OBS related optionsi, we only have to so in coodinator node like - * DDL planning for a OBS fdw table access. + * check whether bulkload compatible illegal chars option exists or not. */ - if (IS_PGXC_COORDINATOR && !isValidate) { - if (locations == NULL || planstate->source == NULL) - ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("LOCATION is required for the foreign tables"))); - - if (is_obs_protocol(locations)) { - /* First, blocks all OBS foreign table not allowed options */ - processOBSNoAllowOptions(specifyMode, fileheader, OutputFilenamePrefix, OutputFixAlignment, - planstate->doLogRemote); - - checkGSOBSPrefixNoAllowRegionOption(hasRegion, planstate->source); - - /* Second, blocking any OBS have-to have options that user doesn't specify */ - processOBSHaveToOptions(accessKeyStr, secretAccessKeyStr, format, encryptStr, chunksizeStr, - planstate->writeOnly); - - /* Verify duplicated location and write only options */ - processOBSLocationOptions(planstate->writeOnly, planstate->source); - - if (planstate->writeOnly) { - /* Do write only table verification */ - if (list_length(planstate->source) > 1) { - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("can not specify multiple locations"))); - } - } - } else { - /* - * Also not allow none none-OBS table with chunksize, async, access_key, - * secure_access_key, encrypt, region options - */ - processNoneOBSOptions(chunksizeStr, encryptStr, accessKeyStr, secretAccessKeyStr, hasRegion); - } + else if (pg_strcasecmp(def->defname, optCompatibleIllegalChars) == 0) specifyCompatibleIllegalChars = true; + /* + * check whether bulkload datetime format options exists or not. + */ + else if (pg_strcasecmp(def->defname, optDateFormat) == 0) + { + specifyDateFormat = true; + /* + * check whether date format is valid; + */ + check_datetime_format(defGetString(def)); } - - // set default values - if (planstate->writeOnly) - planstate->mode = MODE_INVALID; - else if (planstate->mode == MODE_INVALID) - planstate->mode = MODE_NORMAL; - - if (!rejectLimitSpecified) - planstate->rejectLimit = 0; - - if (planstate->fileEncoding < 0) - planstate->fileEncoding = pg_get_client_encoding(); - - /* In dist_fdw_validator(), verify options just for FOREIGN TABLE. */ - if (!isValidate) { - // verify options - if (locations == NULL || planstate->source == NULL) - ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("LOCATION is required for the foreign tables"))); - - VerifyLocations(planstate->source); - - if (fileheader != NULL) { - VerifyFileHeader(fileheader); - } - if (OutputFilenamePrefix != NULL) { - VerifyFilenamePrefix(OutputFilenamePrefix, planstate->writeOnly); - } - if (OutputFixAlignment != NULL) { - VerifyFixAlignment(OutputFixAlignment, planstate->writeOnly, format); - } - - if (rejectLimitSpecified && (planstate->rejectLimit == 0 || planstate->rejectLimit < REJECT_UNLIMITED)) - ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("PER NODE REJECT LIMIT must be greater than 0"))); - - if (planstate->remoteName && !IsValidUserDefineName(planstate->remoteName)) - ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("Invalid name \'%s\' in REMOTE LOG", planstate->remoteName))); - if (rejectLimitSpecified && planstate->writeOnly) - ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("PER NODE REJECT LIMIT only available on READ ONLY foreign table"))); - if (rejectLimitSpecified && !planstate->errorName && !planstate->doLogRemote) - ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("PER NODE REJECT LIMIT only available with LOG INTO or REMOTE LOG"))); - if (planstate->doLogRemote && planstate->writeOnly) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), errmsg("REMOTE LOG only available on READ ONLY foreign table"))); - if (planstate->doLogRemote && is_local_location(strVal(lfirst(list_head(planstate->source))))) - ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("REMOTE LOG only available on in NORMAL mode"))); - if (planstate->doLogRemote && planstate->writeOnly) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), errmsg("REMOTE LOG only available on READ ONLY foreign table"))); - if (specifyFillMissing && planstate->writeOnly) - ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("FILL_MISSING_FIELDS only available on READ ONLY foreign table"))); - if (specifyIgnoreExtraData && planstate->writeOnly) - ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("IGNORE_EXTRA_DATA only available on READ ONLY foreign table"))); + else if (pg_strcasecmp(def->defname, optTimeFormat) == 0) + { + specifyTimeFormat = true; /* - * bulkload compatible illegal chars option isn't allowed for exporting. + * check whether time format is valid; */ - if (specifyCompatibleIllegalChars && planstate->writeOnly) - ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("COMPATIBLE_ILLEGAL_CHARS only available on READ ONLY foreign table"))); - /* - * bulkload datetime format options aren't allowed for exporting. - */ - if (specifyDateFormat && planstate->writeOnly) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), errmsg("DATE_FORMAT only available on READ ONLY foreign table"))); - if (specifyTimeFormat && planstate->writeOnly) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), errmsg("TIME_FORMAT only available on READ ONLY foreign table"))); - if (specifyTimestampFormat && planstate->writeOnly) - ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("TIMESTAMP_FORMAT only available on READ ONLY foreign table"))); - if (specifySmalldatetimeFormat && planstate->writeOnly) - ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("SMALLDATETIME_FORMAT only available on READ ONLY foreign table"))); - if (specifyMode && planstate->writeOnly) - ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("MODE only available on READ ONLY foreign table"))); - if (planstate->errorName && planstate->writeOnly) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), errmsg("LOG INTO only available on READ ONLY foreign table"))); - /* - * Verify the location option. - * Remote location(gsfs://xxxx) can not be used in SHARED and PRIVATE mode. - * */ - foreach (lc, planstate->source) { - if (!is_valid_location(strVal(lfirst(lc)))) - ereport(ERROR, - (errcode(ERRCODE_SYNTAX_ERROR), errmsg("location \"%s\" is invalid", strVal(lfirst(lc))))); - if (!is_local_location(strVal(lfirst(lc))) && IS_SHARED_MODE(planstate->mode)) - ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("SHARED mode can not use location \"%s\"", strVal(lfirst(lc))))); - if (!is_local_location(strVal(lfirst(lc))) && !is_roach_location(strVal(lfirst(lc))) && - IS_PRIVATE_MODE(planstate->mode)) - ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("PRIVATE mode can not use location \"%s\"", strVal(lfirst(lc))))); - if (is_local_location(strVal(lfirst(lc))) && IS_NORMAL_MODE(planstate->mode)) - ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("Normal mode can not use location \"%s\"", strVal(lfirst(lc))))); - } - - // maybe multi-locations are given, just gsfs locations are avaliable. - // can only specify one local location. - Assert(planstate->source); - Assert(list_length(planstate->source) >= 1); - if (planstate->writeOnly && is_local_location(strVal(lfirst(list_head(planstate->source))))) { - if (list_length(planstate->source) > 1) { - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("can not specify multiple local locations"))); - } - - const char *localpath = strVal(lfirst(list_head(planstate->source))); - if (localpath[0] == '.') { - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("can not specify relative local locations"))); - } - } - - if (is_roach_location(strVal(lfirst(list_head(planstate->source))))) { - if (list_length(planstate->source) > 1) { - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("can not specify multiple local locations"))); - } - } - - // for gsfs locations, we should specify FILEHEADER at a time - if (planstate->writeOnly && hasHeader && (fileheader == NULL) && - !is_local_location(strVal(lfirst(list_head(planstate->source))))) - ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), - errmsg("HEADER needs FILEHEADER specification in WRITE ONLY foreign table"))); - - if (format == FORMAT_CSV && planstate->mode == MODE_SHARED) - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("SHARED mode can not be used with CSV format"))); - - /* for gds import/export, duplication locations are not allowed. - * Note: This check depends on the above verifications. - */ - if (IS_NORMAL_MODE(planstate->mode) || IS_INVALID_MODE(planstate->mode)) - CheckDupLocations(planstate->source); - -#ifndef ENABLE_MULTIPLE_NODES - /* GDS foreign tables are simply no longer needed in single node mode */ - if ((planstate->mode == MODE_NORMAL) && (!is_obs_protocol(locations))) { - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("Un-supported feature"), - errdetail("Gauss Data Service(GDS) are not supported in single node mode."))); - } -#endif - - /* - * To achieve warning checking and propagation only when a read-only GDS foreign table - * is created with format fixed but no specified fix option, we will have to do duplicate - * save and check in here and above, due to our poorly-implemented option check. But - * still we cannot skip this warning if any other option pop an error due to the current - * option check as well as the error stack mechanism. - */ - if (isPropagateToFE && !planstate->writeOnly && (format == FORMAT_FIXED) && force_fix_width == 0) - ereport(WARNING, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("The above Read-Only foreign table is using FIXED mode without specifying 'fix' option."), - errhint("Please use 'fix' option to specify expected fixed record length in order to parser the " - "data file correctly."))); - - if (((IS_SHARED_MODE(planstate->mode) || IS_PRIVATE_MODE(planstate->mode)) && !initialuser()) - && !(isOperatoradmin(GetUserId()) && u_sess->attr.attr_security.operation_mode)) { - ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - errmsg("Shared mode and private mode are only available for the supper user and Operatoradmin"))); - } + check_datetime_format(defGetString(def)); + } + else if (pg_strcasecmp(def->defname, optTimestampFormat) == 0) + { + specifyTimestampFormat = true; + /* + * check whether timestamp format is valid; + */ + check_datetime_format(defGetString(def)); + } + else if (pg_strcasecmp(def->defname, optSmalldatetimeFormat) == 0) + { + specifySmalldatetimeFormat = true; + /* + * check whether smalldatetime format is valid; + */ + check_datetime_format(defGetString(def)); + } + else if (pg_strcasecmp(def->defname, optFix) == 0) + { + char *end = NULL; + force_fix_width = (int)strtol(defGetString(def), &end, 10); + if (*end != '\0') + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("invalid value of FIX"))); } - - // Remove the options that only available in foreign table - foreach (lc, rmList) - options = list_delete(options, lfirst(lc)); - if (rmList != NIL) - list_free_deep(rmList); - - planstate->options = options; } +/* + * Validate OBS related optionsi, we only have to so in coodinator node like + * DDL planning for a OBS fdw table access. + */ +if (IS_PGXC_COORDINATOR && !isValidate) { + if (locations == NULL || planstate->source == NULL) + ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("LOCATION is required for the foreign tables"))); + + if (is_obs_protocol(locations)) { + /* First, blocks all OBS foreign table not allowed options */ + processOBSNoAllowOptions(specifyMode, fileheader, OutputFilenamePrefix, OutputFixAlignment, + planstate->doLogRemote); + + checkGSOBSPrefixNoAllowRegionOption(hasRegion, planstate->source); + + /* Second, blocking any OBS have-to have options that user doesn't specify */ + processOBSHaveToOptions(accessKeyStr, secretAccessKeyStr, format, encryptStr, chunksizeStr, + planstate->writeOnly); + + /* Verify duplicated location and write only options */ + processOBSLocationOptions(planstate->writeOnly, planstate->source); + + if (planstate->writeOnly) { + /* Do write only table verification */ + if (list_length(planstate->source) > 1) { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("can not specify multiple locations"))); + } + } + } else { + /* + * Also not allow none none-OBS table with chunksize, async, access_key, + * secure_access_key, encrypt, region options + */ + processNoneOBSOptions(chunksizeStr, encryptStr, accessKeyStr, secretAccessKeyStr, hasRegion); + } +} + +// set default values +if (planstate->writeOnly) + planstate->mode = MODE_INVALID; +else if (planstate->mode == MODE_INVALID) + planstate->mode = MODE_NORMAL; + +if (!rejectLimitSpecified) + planstate->rejectLimit = 0; + +if (planstate->fileEncoding < 0) + planstate->fileEncoding = pg_get_client_encoding(); + +/* In dist_fdw_validator(), verify options just for FOREIGN TABLE. */ +// 如果不是验证模式(isValidate 为假),则执行以下操作 +if (!isValidate) { + // verify options + if (locations == NULL || planstate->source == NULL) + ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("LOCATION is required for the foreign tables"))); + + // 验证文件位置的有效性 + VerifyLocations(planstate->source); + + // 如果存在文件头信息,验证文件头的有效性 + if (fileheader != NULL) { + VerifyFileHeader(fileheader); + } + // 如果存在输出文件名前缀,验证其有效性 + if (OutputFilenamePrefix != NULL) { + VerifyFilenamePrefix(OutputFilenamePrefix, planstate->writeOnly); + } + // 如果存在输出固定宽度对齐选项,验证其有效性 + if (OutputFixAlignment != NULL) { + VerifyFixAlignment(OutputFixAlignment, planstate->writeOnly, format); + } + + // 验证 PER NODE REJECT LIMIT 选项的有效性 + if (rejectLimitSpecified && (planstate->rejectLimit == 0 || planstate->rejectLimit < REJECT_UNLIMITED)) + ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("PER NODE REJECT LIMIT must be greater than 0"))); + + // 验证远程日志记录名称的有效性 + if (planstate->remoteName && !IsValidUserDefineName(planstate->remoteName)) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), errmsg("Invalid name \'%s\' in REMOTE LOG", planstate->remoteName))); + + // 验证 PER NODE REJECT LIMIT 选项与写入操作的兼容性 + if (rejectLimitSpecified && planstate->writeOnly) + ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("PER NODE REJECT LIMIT only available on READ ONLY foreign table"))); + + // 验证 PER NODE REJECT LIMIT 选项与日志记录选项的兼容性 + if (rejectLimitSpecified && !planstate->errorName && !planstate->doLogRemote) + ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("PER NODE REJECT LIMIT only available with LOG INTO or REMOTE LOG"))); + + // 验证 REMOTE LOG 选项与写入操作的兼容性 + if (planstate->doLogRemote && planstate->writeOnly) + ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("REMOTE LOG only available on READ ONLY foreign table"))); + + // 验证 REMOTE LOG 选项是否在 NORMAL 模式下有效 + if (planstate->doLogRemote && is_local_location(strVal(lfirst(list_head(planstate->source))))) + ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("REMOTE LOG only available on in NORMAL mode"))); + + // 验证 REMOTE LOG 选项与写入操作的兼容性 + if (planstate->doLogRemote && planstate->writeOnly) + ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("REMOTE LOG only available on READ ONLY foreign table"))); + + // 验证 FILL_MISSING_FIELDS 选项是否在写入操作下有效 + if (specifyFillMissing && planstate->writeOnly) + ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("FILL_MISSING_FIELDS only available on READ ONLY foreign table"))); + + // 验证 IGNORE_EXTRA_DATA 选项是否在写入操作下有效 + if (specifyIgnoreExtraData && planstate->writeOnly) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), errmsg("IGNORE_EXTRA_DATA only available on READ ONLY foreign table"))); + + // 验证 COMPATIBLE_ILLEGAL_CHARS 选项是否在写入操作下有效 + if (specifyCompatibleIllegalChars && planstate->writeOnly) + ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("COMPATIBLE_ILLEGAL_CHARS only available on READ ONLY foreign table"))); + + // 验证 DATE_FORMAT 选项是否在写入操作下有效 + if (specifyDateFormat && planstate->writeOnly) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), errmsg("DATE_FORMAT only available on READ ONLY foreign table"))); + + // 验证 TIME_FORMAT 选项是否在写入操作下有效 + if (specifyTimeFormat && planstate->writeOnly) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), errmsg("TIME_FORMAT only available on READ ONLY foreign table"))); + + // 验证 TIMESTAMP_FORMAT 选项是否在写入操作下有效 + if (specifyTimestampFormat && planstate->writeOnly) + ereport(ERROR, + (errcode(ERRCODE_SYNTAX_ERROR), errmsg("TIMESTAMP_FORMAT only available on READ ONLY foreign table"))); + + // 验证 SMALLDATETIME_FORMAT 选项是否在写入操作下有效 + if (specifySmalldatetimeFormat && planstate->writeOnly) + ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("SMALLDATETIME_FORMAT only available on READ ONLY foreign table"))); + + // 验证 MODE 选项是否在写入操作下有效 + if (specifyMode && planstate->writeOnly) + ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("MODE only available on READ ONLY foreign table"))); + + // 验证 LOG INTO 选项是否在写入操作下有效 + if (planstate->errorName && planstate->writeOnly) + ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("LOG INTO only available on READ ONLY foreign table"))); + /* + * Verify the location option. + * Remote location(gsfs://xxxx) can not be used in SHARED and PRIVATE mode. + * */ + foreach (lc, planstate->source) { + if (!is_valid_location(strVal(lfirst(lc)))) + ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("location \"%s\" is invalid", strVal(lfirst(lc))))); + if (!is_local_location(strVal(lfirst(lc))) && IS_SHARED_MODE(planstate->mode)) + ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("SHARED mode can not use location \"%s\"", strVal(lfirst(lc))))); + if (!is_local_location(strVal(lfirst(lc))) && !is_roach_location(strVal(lfirst(lc))) && + IS_PRIVATE_MODE(planstate->mode)) + ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("PRIVATE mode can not use location \"%s\"", strVal(lfirst(lc))))); + if (is_local_location(strVal(lfirst(lc))) && IS_NORMAL_MODE(planstate->mode)) + ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("Normal mode can not use location \"%s\"", strVal(lfirst(lc))))); + } + + // maybe multi-locations are given, just gsfs locations are avaliable. + // can only specify one local location. + Assert(planstate->source); + Assert(list_length(planstate->source) >= 1); + if (planstate->writeOnly && is_local_location(strVal(lfirst(list_head(planstate->source))))) { + if (list_length(planstate->source) > 1) { + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("can not specify multiple local locations"))); + } + + const char *localpath = strVal(lfirst(list_head(planstate->source))); + if (localpath[0] == '.') { + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("can not specify relative local locations"))); + } + } + + if (is_roach_location(strVal(lfirst(list_head(planstate->source))))) { + if (list_length(planstate->source) > 1) { + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("can not specify multiple local locations"))); + } + } + + // for gsfs locations, we should specify FILEHEADER at a time + // 针对 gsfs 位置,一次只能指定一个 FILEHEADER 选项 + if (planstate->writeOnly && hasHeader && (fileheader == NULL) && + !is_local_location(strVal(lfirst(list_head(planstate->source))))) + ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), + errmsg("HEADER needs FILEHEADER specification in WRITE ONLY foreign table"))); + + // 验证 CSV 格式与 SHARED 模式的兼容性 + if (format == FORMAT_CSV && planstate->mode == MODE_SHARED) + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("SHARED mode can not be used with CSV format"))); + + /* for gds import/export, duplication locations are not allowed. + * Note: This check depends on the above verifications. + */ + if (IS_NORMAL_MODE(planstate->mode) || IS_INVALID_MODE(planstate->mode)) + CheckDupLocations(planstate->source); + +#ifndef ENABLE_MULTIPLE_NODES + /* GDS foreign tables are simply no longer needed in single node mode */ + if ((planstate->mode == MODE_NORMAL) && (!is_obs_protocol(locations))) { + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("Un-supported feature"), + errdetail("Gauss Data Service(GDS) are not supported in single node mode."))); + } +#endif + + /* + * To achieve warning checking and propagation only when a read-only GDS foreign table + * is created with format fixed but no specified fix option, we will have to do duplicate + * save and check in here and above, due to our poorly-implemented option check. But + * still we cannot skip this warning if any other option pop an error due to the current + * option check as well as the error stack mechanism. + */ + if (isPropagateToFE && !planstate->writeOnly && (format == FORMAT_FIXED) && force_fix_width == 0) + ereport(WARNING, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("The above Read-Only foreign table is using FIXED mode without specifying 'fix' option."), + errhint("Please use 'fix' option to specify expected fixed record length in order to parser the " + "data file correctly."))); + + // 如果是共享模式或私有模式,并且不是初始用户,则需要检查特权 + if (((IS_SHARED_MODE(planstate->mode) || IS_PRIVATE_MODE(planstate->mode)) && !initialuser()) && + !(isOperatoradmin(GetUserId()) && u_sess->attr.attr_security.operation_mode)) { + ereport(ERROR, + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), + errmsg("Shared mode and private mode are only available for the supper user and Operatoradmin"))); + } +} + +// Remove the options that only available in foreign table +foreach (lc, rmList) + options = list_delete(options, lfirst(lc)); +if (rmList != NIL) + list_free_deep(rmList); + +planstate->options = options; +} +/* + * 功能:解密密钥字符串并存储解密后的结果 + * + * 参数列表: + * keyStr:const char 指针,指向需要解密的密钥字符串 + * destplainStr:char 数组,用于存储解密后的结果的目标字符串 + * destplainLength:uint32,目标字符串的长度 + * obskey:const char 指针,用于解密的密钥 + * + * 注意: + * 该函数用于解密密钥字符串,根据字符串前缀进行不同处理。 + * 如果字符串以指定前缀开头,将使用密钥进行解密,并将结果存储在 destplainStr 中。 + * 否则,将原始字符串复制到 destplainStr 中。 + * 请确保在调用此函数之前已分配足够的内存来存储解密后的结果。 + */ void decryptKeyString(const char *keyStr, char destplainStr[], uint32 destplainLength, const char *obskey) { + // 定义一个前缀用于识别加密字符串 #define ENCRYPT_STR_PREFIX "encryptstr" if (unlikely(keyStr == NULL)) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("Invalid key string"))); } if (strncmp(keyStr, ENCRYPT_STR_PREFIX, strlen(ENCRYPT_STR_PREFIX)) == 0) { + // 如果字符串以指定前缀开头,进行解密 keyStr = keyStr + strlen(ENCRYPT_STR_PREFIX); } else { + // 否则,将原始字符串复制到目标字符串中 errno_t rc = memcpy_s(destplainStr, destplainLength, keyStr, strlen(keyStr) + 1); securec_check(rc, "\0", "\0"); return; @@ -1061,56 +1520,86 @@ void decryptKeyString(const char *keyStr, char destplainStr[], uint32 destplainL * tuple into pg_foreign_table when creating foreign tables in obs protocol. * @Input: pointer to options list */ +/* + * 功能:解密 OBS 外部表选项中的密钥字符串 + * + * 参数列表: + * options:指向选项列表的指针,包含需要解密的选项 + * + * 注意: + * 该函数用于从 OBS 外部表选项中解密密钥字符串。 + * 它首先检查选项列表中是否存在 "secret_access_key" 选项, + * 如果存在,则将其解密并替换为解密后的值。 + * 解密后的值将作为新的选项添加到选项列表中。 + * 请确保在调用此函数之前已分配足够的内存来存储解密后的值。 + */ void decryptOBSForeignTableOption(List **options) { - char *keyStr = NULL; - GS_UINT32 keyStrLen = 0; + char *keyStr = NULL; // 存储密钥字符串 + GS_UINT32 keyStrLen = 0; // 密钥字符串长度 - /* The maximum string length of the encrypt access key or encrypt access key is 1024 */ - char decryptSecretAccessKeyStr[DEST_CIPHER_LENGTH] = "\0"; - char decryptpassWordStr[DEST_CIPHER_LENGTH] = "\0"; - bool haveSecretAccessKey = false; + /* 加密后的密钥字符串最大长度为 1024 */ + char decryptSecretAccessKeyStr[DEST_CIPHER_LENGTH] = "\0"; // 解密后的密钥字符串 + char decryptpassWordStr[DEST_CIPHER_LENGTH] = "\0"; // 解密后的密码字符串 + bool haveSecretAccessKey = false; // 标志是否存在 "secret_access_key" 选项 errno_t rc = EOK; - ListCell *lc = NULL; - ListCell *prev = NULL; + ListCell *lc = NULL; // 用于遍历选项列表的列表元素指针 + ListCell *prev = NULL; // 用于保存上一个列表元素指针 foreach (lc, *options) { - DefElem *def = (DefElem *)lfirst(lc); + DefElem *def = (DefElem *)lfirst(lc); // 获取列表元素中的选项定义 + + // 检查选项名称是否为 "secret_access_key" if (pg_strcasecmp(def->defname, optSecretAccessKey) == 0) { - haveSecretAccessKey = true; + haveSecretAccessKey = true; // 标志找到了 "secret_access_key" 选项 - keyStr = defGetString(def); - decryptKeyString(keyStr, decryptSecretAccessKeyStr, DEST_CIPHER_LENGTH, NULL); + keyStr = defGetString(def); // 获取加密的密钥字符串 + decryptKeyString(keyStr, decryptSecretAccessKeyStr, DEST_CIPHER_LENGTH, NULL); // 解密密钥字符串 - *options = list_delete_cell(*options, lc, prev); + *options = list_delete_cell(*options, lc, prev); // 从选项列表中删除 "secret_access_key" 选项 break; } - prev = lc; + prev = lc; // 保存当前列表元素指针,以便在删除选项时使用 } + // 如果存在 "secret_access_key" 选项,将解密后的值作为新选项添加到列表中 if (haveSecretAccessKey) { - *options = lappend(*options, makeDefElem(pstrdup(optSecretAccessKey), - (Node *)makeString(pstrdup(decryptSecretAccessKeyStr)))); + *options = lappend( + *options, makeDefElem(pstrdup(optSecretAccessKey), (Node *)makeString(pstrdup(decryptSecretAccessKeyStr)))); } if (keyStr != NULL) { - /* safty concern, empty keyStr manually. */ + /* 安全考虑,手动清空 keyStr。 */ keyStrLen = strlen(keyStr); rc = memset_s(keyStr, keyStrLen, 0, keyStrLen); securec_check(rc, "", ""); - pfree(keyStr); + pfree(keyStr); // 释放加密的密钥字符串的内存 } - /* safty concern, empty decryptAccessKeyStr & decryptSecretAccessKeyStr manually. */ + /* 安全考虑,手动清空 decryptSecretAccessKeyStr 和 decryptpassWordStr。 */ rc = memset_s(decryptSecretAccessKeyStr, DEST_CIPHER_LENGTH, 0, DEST_CIPHER_LENGTH); securec_check(rc, "", ""); rc = memset_s(decryptpassWordStr, DEST_CIPHER_LENGTH, '\0', DEST_CIPHER_LENGTH); securec_check(rc, "", ""); } - +/* + * 功能:获取分布式导入计划的选项 + * + * 参数列表: + * relOid:外部表关联的对象标识符(OID) + * planstate:DistImportPlanState 结构的指针,用于保存导入计划的状态信息 + * fOptions:ForeignOptions 结构的指针,包含外部表的选项 + * + * 注意: + * 该函数用于获取分布式导入计划的选项,以便执行分布式数据导入操作。 + * 如果 fOptions 为 NULL,则从外部表、外部服务器和外部数据包装器中提取选项,并合并为一个选项列表。 + * 如果 fOptions 不为 NULL,则直接使用 fOptions 中的选项列表。 + * 在获取选项后,还会对 OBS 存储访问密钥和安全访问密钥进行解密处理。 + * 最后,调用 ProcessDistImportOptions 函数处理选项,以初始化导入计划的状态。 + */ void GetDistImportOptions(Oid relOid, DistImportPlanState *planstate, ForeignOptions *fOptions = NULL) { - List *options = NIL; + List *options = NIL; // 用于存储选项的列表 if (fOptions == NULL) { ForeignTable *table = NULL; ForeignServer *server = NULL; @@ -1124,45 +1613,59 @@ void GetDistImportOptions(Oid relOid, DistImportPlanState *planstate, ForeignOpt * no point in examining anything except the foreign table's own options. * Simplify?) */ - table = GetForeignTable(relOid); - server = GetForeignServer(table->serverid); - wrapper = GetForeignDataWrapper(server->fdwid); + table = GetForeignTable(relOid); // 获取外部表信息 + server = GetForeignServer(table->serverid); // 获取外部服务器信息 + wrapper = GetForeignDataWrapper(server->fdwid); // 获取外部数据包装器信息 - options = NIL; - options = list_concat(options, wrapper->options); - options = list_concat(options, server->options); - options = list_concat(options, table->options); + options = NIL; // 初始化选项列表 + options = list_concat(options, wrapper->options); // 合并包装器选项 + options = list_concat(options, server->options); // 合并服务器选项 + options = list_concat(options, table->options); // 合并外部表选项 - options = adaptOBSURL(options); + options = adaptOBSURL(options); // 处理 OBS URL 选项 if (table->write_only) options = lappend(options, makeDefElem(pstrdup(optWriteOnly), (Node *)makeString(pstrdup("true")))); } else { Assert(fOptions->fOptions != NULL); - options = fOptions->fOptions; + options = fOptions->fOptions; // 直接使用提供的选项列表 } + // 为 OBS 存储访问密钥和安全访问密钥添加解密处理 // Add decrpyt function for obs access key and security access key in obs options decryptOBSForeignTableOption(&options); - ProcessDistImportOptions(planstate, options, false); + ProcessDistImportOptions(planstate, options, false); // 处理选项,初始化导入计划状态 } /* * distImportGetRelSize * Obtain relation size estimates for a foreign table */ +/* + * 功能:计算分布式导入外部表的关系大小估算 + * + * 参数列表: + * root:PlannerInfo 结构的指针,包含查询规划信息 + * baserel:RelOptInfo 结构的指针,包含基本关系的查询规划信息 + * foreigntableid:外部表的对象标识符(OID) + * + * 注意: + * 该函数用于计算分布式导入外部表的关系大小估算。首先获取外部表的选项,然后执行大小估算。 + * 函数分配了 DistImportPlanState 结构并将其赋给 baserel->fdw_private 以保存导入计划的状态信息。 + * 最后,调用 estimate_size 函数进行关系大小估算。 + */ void distImportGetRelSize(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid) { - DistImportPlanState *planstate = NULL; + DistImportPlanState *planstate = NULL; // 存储导入计划的状态信息的指针 /* * Fetch options. We only need filename at this point, but we might as * well get everything and not need to re-fetch it later in planning. */ - planstate = (DistImportPlanState *)palloc0(sizeof(DistImportPlanState)); - GetDistImportOptions(foreigntableid, planstate); - baserel->fdw_private = (void *)planstate; + planstate = (DistImportPlanState *)palloc0(sizeof(DistImportPlanState)); // 分配导入计划状态结构的内存 + GetDistImportOptions(foreigntableid, planstate); // 获取外部表选项 + baserel->fdw_private = (void *)planstate; // 将导入计划状态赋给基本关系的 fdw_private 字段 /* Estimate relation size */ estimate_size(root, baserel, planstate); @@ -1176,13 +1679,26 @@ void distImportGetRelSize(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntab * possible access path, which simply returns all records in the order in * the data file. */ +/* + * 功能:生成分布式导入外部表的查询路径 + * + * 参数列表: + * root:PlannerInfo 结构的指针,包含查询规划信息 + * baserel:RelOptInfo 结构的指针,包含基本关系的查询规划信息 + * foreigntableid:外部表的对象标识符(OID) + * + * 注意: + * 该函数用于生成分布式导入外部表的查询路径。它首先检查是否应选择性地执行二进制转换,然后估算成本。 + * 接下来,它创建一个 ForeignPath 节点,并将其添加为唯一的可能路径。它使用 fdw_private 列表来携带 convert_selectively + * 选项, 该选项将传播到 Plan 节点的 fdw_private 列表中。 + */ void distImportGetPaths(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid) { - DistImportPlanState *planstate = (DistImportPlanState *)baserel->fdw_private; + DistImportPlanState *planstate = (DistImportPlanState *)baserel->fdw_private; // 获取导入计划的状态信息 Cost startup_cost; Cost total_cost; - List *columns = NIL; - List *coptions = NIL; + List *columns = NIL; // 存储需要进行二进制转换的列 + List *coptions = NIL; // 存储 convert_selectively 选项 /* Decide whether to selectively perform binary conversion */ if (check_selective_binary_conversion(baserel, foreigntableid, &columns)) @@ -1206,6 +1722,21 @@ void distImportGetPaths(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntable * distGetForeignPlan * Create a ForeignScan plan node for scanning the foreign table */ +/* + * 功能:生成分布式导入外部表的查询计划 + * + * 参数列表: + * root:PlannerInfo 结构的指针,包含查询规划信息 + * baserel:RelOptInfo 结构的指针,包含基本关系的查询规划信息 + * foreigntableid:外部表的对象标识符(OID) + * best_path:ForeignPath 结构的指针,包含最佳查询路径信息 + * tlist:目标列表,表示查询的目标列 + * scan_clauses:扫描子句列表,表示扫描约束条件 + * + * 注意: + * 该函数用于生成分布式导入外部表的查询计划。它首先检查执行环境和约束条件, + * 然后创建 ForeignScan 节点,设置其属性,并返回该节点。 + */ ForeignScan *distImportGetPlan(PlannerInfo *root, RelOptInfo *baserel, Oid foreigntableid, ForeignPath *best_path, List *tlist, List *scan_clauses) { @@ -1290,6 +1821,16 @@ ForeignScan *distImportGetPlan(PlannerInfo *root, RelOptInfo *baserel, Oid forei * distExplainForeignScan * Produce extra output for EXPLAIN */ +/* + * 功能:生成用于解释查询计划的信息 + * + * 参数列表: + * node:ForeignScanState 结构的指针,包含外部扫描的状态信息 + * es:ExplainState 结构的指针,包含生成解释信息的状态信息 + * + * 注意: + * 该函数用于生成用于解释查询计划的信息。它提取文件名等选项信息,并将其包含在解释输出中。 + */ void distImportExplain(ForeignScanState *node, ExplainState *es) { char *filename = NULL; @@ -1333,6 +1874,18 @@ void distImportExplain(ForeignScanState *node, ExplainState *es) * @OUT obs_copy_options: struct for ObsCopyOptions * @IN options: options list */ +/* + * 功能:从外部表的选项列表中提取 OBS 外部表的选项 + * + * 参数列表: + * obs_copy_options:ObsCopyOptions 结构的指针,用于存储 OBS 外部表的选项 + * options:外部表选项列表,包含 OBS 外部表的相关选项 + * + * 注意: + * 该函数用于从外部表的选项列表中提取 OBS 外部表的选项。它遍历选项列表,将选项的值解析并存储在 obs_copy_options + * 结构中, 这些选项包括 chunksize、encrypt、access_key 和 secret_access_key。如果选项未提供,将使用默认值。 + * 函数还检查选项的合法性,并在发现无效值时生成错误消息报告。 + */ void getOBSOptions(ObsCopyOptions *obs_copy_options, List *options) { char *accessKeyStr = NULL; @@ -1350,9 +1903,7 @@ void getOBSOptions(ObsCopyOptions *obs_copy_options, List *options) if (pg_strcasecmp(def->defname, optChunkSize) == 0) { uint32_t chunksize = atoi(defGetString(def)); if (chunksize > (PG_UINT32_MAX / (1024 * 1024))) { - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("obs option chunksize is too large"))); + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("obs option chunksize is too large"))); } obs_copy_options->chunksize = chunksize * 1024 * 1024; } else if (pg_strcasecmp(def->defname, optEncrypt) == 0) { @@ -1368,11 +1919,32 @@ void getOBSOptions(ObsCopyOptions *obs_copy_options, List *options) } /* Set obs options for data structure */ +/* + * 功能:初始化 OBS 导入过程的执行状态 + * + * 参数列表: + * importState:DistImportExecutionState 结构的指针,用于存储导入过程的执行状态 + * planstate:DistImportPlanState 结构的指针,包含导入计划的相关信息 + * + * 注意: + * 该函数用于初始化 OBS 导入过程的执行状态。在导入过程中,根据导入计划中的文件名判断是否使用 OBS 协议。 + * 如果使用 OBS 协议,函数会提取并存储 OBS 导入选项,如 chunksize、encrypt、access_key 和 secret_access_key。 + * 这些选项将在后续的导入过程中使用。如果不使用 OBS 协议,则不执行任何操作。 + * + * OBS 导入选项解释: + * - chunksize:指定导入时的块大小(以字节为单位),用于控制导入数据的分块处理。 + * - encrypt:指定是否使用加密来传输数据。 + * - access_key:用于访问 OBS 存储的访问密钥。 + * - secret_access_key:用于访问 OBS 存储的秘密访问密钥。 + */ void distOBSImportBegin(DistImportExecutionState *importState, DistImportPlanState *planstate) { + // 确保传入的指针参数不为空 Assert(importState && planstate); + // 检查导入文件是否使用 OBS 协议 if (is_obs_protocol(planstate->filename)) { + // 如果使用 OBS 协议,则提取 OBS 导入选项并存储到 importState 中 getOBSOptions(&importState->obs_copy_options, planstate->options); } } @@ -1381,6 +1953,19 @@ void distOBSImportBegin(DistImportExecutionState *importState, DistImportPlanSta * distBeginForeignScan * Initiate access to the file by creating CopyState */ +/* + * 功能:初始化分布式导入的执行状态并设置导入选项 + * + * 参数列表: + * node:ForeignScanState 结构的指针,表示分布式导入的执行状态 + * eflags:执行标志,用于判断是否在 EXPLAIN 模式下执行 + * + * 注意: + * 该函数用于初始化分布式导入的执行状态,同时根据导入计划中的选项设置执行状态的属性。函数将分布式导入 + * 的执行状态存储在 node->fdw_state 中,以便在导入过程中使用。该函数还负责提取 OBS 导入选项,如果导入 + * 的文件使用 OBS 协议。 + * + */ void distImportBegin(ForeignScanState *node, int eflags) { DistImportExecutionState *importState = NULL; @@ -1419,8 +2004,10 @@ void distImportBegin(ForeignScanState *node, int eflags) * It means that the current node has meta data. need to get options * from catalog. */ - GetDistImportOptions(RelationGetRelid(node->ss.ss_currentRelation), &planstate, NULL); + // 如果当前节点没有元数据,从目录中获取选项 + GGetDistImportOptions(RelationGetRelid(node->ss.ss_currentRelation), &planstate, NULL); } else { + // 否则,使用 ForeignScan 节点中的选项 GetDistImportOptions(RelationGetRelid(node->ss.ss_currentRelation), &planstate, foreignScan->options); } @@ -1494,7 +2081,7 @@ void distImportBegin(ForeignScanState *node, int eflags) * Foreign Scan Thread don't unlink this cache file, * it just open and write error data into this cache file. */ - ErrLogInfo err_info = { u_sess->stream_cxt.smp_id, false }; + ErrLogInfo err_info = {u_sess->stream_cxt.smp_id, false}; importState->needSaveError = scan->needSaveError; if (!importState->needSaveError) @@ -1506,8 +2093,8 @@ void distImportBegin(ForeignScanState *node, int eflags) logger = New(CurrentMemoryContext) GDSErrorLogger; importState->elogger = lappend(importState->elogger, logger); if (strlen(planstate.remoteName) == 0) - remoteName = (char*)importState->cur_relname; - logger->Initialize((void*)importState->io_stream, (TupleDesc)remoteName, err_info); + remoteName = (char *)importState->cur_relname; + logger->Initialize((void *)importState->io_stream, (TupleDesc)remoteName, err_info); } if (scan->errCache != NULL) { @@ -1520,23 +2107,51 @@ void distImportBegin(ForeignScanState *node, int eflags) } } } - +/* + * 功能:检测并报告非法字符异常的阈值 + * + * 注意: + * 该函数用于监测非法字符异常的数量是否超过了预定的阈值,并在超过阈值时生成警告消息。 + * 这有助于警告用户可能存在的数据损坏或解码问题。如果非法字符异常的数量超过了阈值, + * 则函数将生成一条警告消息,提醒用户检查数据的编码设置是否正确。 + */ void ReportIllegalCharExceptionThreshold() { + // 检查非法字符异常数量是否超过阈值 if (t_thrd.bulk_cxt.illegal_character_err_cnt >= ILLEGAL_CHARACTERS_ERR_THRESHOLD && !t_thrd.bulk_cxt.illegal_character_err_threshold_reported) { + // 如果超过阈值且尚未报告,则生成警告消息 ereport(WARNING, (errmodule(MOD_GDS), errmsg("Number of illegal character exceptions exceed the threshold."), errdetail("Please make sure that you are using right encoding setting to decode incomming data."))); + // 标记已报告过阈值警告,以避免重复报告 t_thrd.bulk_cxt.illegal_character_err_threshold_reported = true; } } - /* * distterateForeignScan * Read next record from the data file and store it into the * ScanTupleSlot as a virtual tuple */ +/* + * 功能:执行分布式导入操作,读取外部数据并填充元组表槽 + * + * 参数: + * node:外部扫描状态 + * + * 返回值: + * TupleTableSlot:填充的元组表槽 + * + * 注意: + * 该函数用于执行分布式导入操作,从外部数据源读取数据,并将数据填充到元组表槽中。 + * 函数使用外部扫描状态中的信息来管理导入过程。它在每次调用时从外部数据源读取一行数据, + * 将数据填充到元组表槽中,并返回该元组表槽。 + * 函数还处理了非法字符异常和错误恢复的情况。 + * + * 示例用法: + * 通常在执行外部数据导入操作时,可以重复调用此函数以逐行读取外部数据并填充元组表槽。 + * 如果发生非法字符异常或其他错误,函数会进行适当的处理和恢复。 + */ TupleTableSlot *distExecImport(ForeignScanState *node) { DistImportExecutionState *importState = (DistImportExecutionState *)node->fdw_state; @@ -1633,6 +2248,19 @@ retry: * distEndForeignScan * Finish scanning foreign table and dispose objects used for this scan */ +/* + * 功能:结束分布式导入操作,释放相关资源 + * + * 参数: + * node:外部扫描状态 + * + * 注意: + * 该函数用于结束分布式导入操作,并释放与之相关的资源。它接收外部扫描状态作为参数, + * 并根据该状态中的信息进行清理和资源释放操作。 + * + * 示例用法: + * 通常在执行完分布式导入操作后,可以调用此函数来确保资源的释放和清理。 + */ void distImportEnd(ForeignScanState *node) { DistImportExecutionState *importState = (DistImportExecutionState *)node->fdw_state; @@ -1649,4 +2277,3 @@ void distReImport(ForeignScanState *node) { // not implement yet } - diff --git a/src/gausskernel/storage/bulkload/importerror.cpp b/src/gausskernel/storage/bulkload/importerror.cpp index 85f26684b..4680cea78 100644 --- a/src/gausskernel/storage/bulkload/importerror.cpp +++ b/src/gausskernel/storage/bulkload/importerror.cpp @@ -49,16 +49,38 @@ char *generate_unique_cache_name_prefix(Oid oid, uint32 distSessionKey) tmpname[MAXPGPATH - 1] = '\0'; return pstrdup(tmpname); } - +/* + * 功能:生成唯一的缓存名称前缀 + * + * 参数列表: + * relname:关系名称,用于构建缓存名称的一部分 + * + * 返回值: + * 指向生成的缓存名称前缀的指针 + * + * 注意: + * 该函数用于创建一个唯一的缓存名称前缀,通常用于标识缓存数据的唯一性。 + * 缓存名称前缀包括关系名、拷贝ID和进程ID。 + */ char *generate_unique_cache_name_prefix(const char *relname) { + // 创建一个临时字符数组用于存储缓存名称前缀 char tmpname[MAXPGPATH]; + + // 生成一个唯一的拷贝ID,通常用于标识数据副本 uint32 copyId = generate_unique_id(>_copyId); - ; + + // 使用snprintf_s函数构造缓存名称前缀字符串,包括关系名、拷贝ID和进程ID int rc = snprintf_s(tmpname, sizeof(tmpname), sizeof(tmpname) - 1, "%s.%u.%lu.cache", relname, copyId, t_thrd.proc_cxt.MyProcPid); + + // 检查snprintf_s的返回值,确保格式化操作没有发生错误 securec_check_ss(rc, "", ""); + + // 将tmpname字符串的最后一个字符设为终止符,以确保字符串的正确终止 tmpname[MAXPGPATH - 1] = '\0'; + + // 使用pstrdup函数为生成的字符串分配内存并返回 return pstrdup(tmpname); } @@ -90,185 +112,340 @@ void unlink_local_cache_file(const char *prefix, const uint32 smpId) UnlinkCacheFile(localfile); } +/* + * 功能:将BaseError对象的数据序列化到StringInfo缓冲区中 + * + * 参数列表: + * buf:用于存储序列化数据的StringInfo缓冲区 + * + * 注意: + * 该函数用于将BaseError对象的数据序列化,以便后续传输或存储。它首先处理最大值, + * 然后对每个属性进行处理,根据属性的类型和长度发送相应的数据到缓冲区中。 + */ void BaseError::Serialize(StringInfo buf) { + // 获取关系描述和属性数组 Form_pg_attribute *attrs = m_desc->attrs; int natts = m_desc->natts; + // 断言属性数量与最大值匹配 Assert(m_desc->natts == MaxNumOfValue); + + // 调用 serializeMaxNumOfValue 函数,将某种限制值序列化到缓冲区 serializeMaxNumOfValue(buf); + // 循环处理每个属性 for (int i = 0; i < natts; ++i) { - Datum attr = m_values[i]; + Datum attr = m_values[i]; // 获取属性的数据 if (m_isNull[i]) { + // 如果属性为NULL,发送特殊标志 UNSIGNED_MINUS_ONE pq_sendint32(buf, UNSIGNED_MINUS_ONE); continue; } if (attrs[i]->attlen > 0 && attrs[i]->attlen <= 8) { - pq_sendint32(buf, attrs[i]->attlen); - pq_sendbytes(buf, (char *)&attr, attrs[i]->attlen); + // 如果属性长度大于0且小于等于8,发送属性长度和属性数据 + pq_sendint32(buf, attrs[i]->attlen); // 发送属性长度 + pq_sendbytes(buf, (char *)&attr, attrs[i]->attlen); // 发送属性数据 } else if (attrs[i]->attlen > 8) { - pq_sendint32(buf, attrs[i]->attlen); - pq_sendbytes(buf, DatumGetPointer(attr), attrs[i]->attlen); + // 如果属性长度大于8,发送属性长度和属性数据 + pq_sendint32(buf, attrs[i]->attlen); // 发送属性长度 + pq_sendbytes(buf, DatumGetPointer(attr), attrs[i]->attlen); // 发送属性数据 } else { - pq_sendint32(buf, VARSIZE(attr) - VARHDRSZ); - pq_sendbytes(buf, VARDATA(attr), VARSIZE(attr) - VARHDRSZ); + // 处理变长属性,发送属性数据的长度和属性数据 + pq_sendint32(buf, VARSIZE(attr) - VARHDRSZ); // 发送属性数据的长度 + pq_sendbytes(buf, VARDATA(attr), VARSIZE(attr) - VARHDRSZ); // 发送属性数据 } } } +/* + * 功能:从StringInfo缓冲区中反序列化数据到BaseError对象 + * + * 参数列表: + * buf:包含序列化数据的StringInfo缓冲区 + * + * 注意: + * 该函数用于从StringInfo缓冲区中读取序列化数据,并将其反序列化到BaseError对象中。 + */ void BaseError::Deserialize(StringInfo buf) { - int natts; - Form_pg_attribute *attrs = m_desc->attrs; + int natts; // 属性数量 + Form_pg_attribute *attrs = m_desc->attrs; // 关系描述中的属性数组 + + // 从缓冲区中获取属性数量 natts = pq_getmsgint(buf, 2); + + // 检查属性数量是否合法 if (natts != MaxNumOfValue || natts != m_desc->natts) { - ereport(ERROR, (errcode(ERRCODE_OPERATE_RESULT_NOT_EXPECTED), errmsg("Found invalid error recored"))); + ereport(ERROR, (errcode(ERRCODE_OPERATE_RESULT_NOT_EXPECTED), errmsg("Found invalid error record"))); } + + // 循环处理每个属性 for (int i = 0; i < natts; ++i) { - int len = pq_getmsgint(buf, 4); + int len = pq_getmsgint(buf, 4); // 获取属性数据的长度 + if (len == -1) { + // 如果长度为-1,表示属性为NULL m_isNull[i] = true; m_values[i] = (Datum)0; continue; } if (unlikely(len < 0)) { - ereport(ERROR, - (errcode(ERRCODE_OPERATE_RESULT_NOT_EXPECTED), - errmsg("Found invalid error recored: negative length that is not -1."))); + // 长度小于0是无效的,抛出错误 + ereport(ERROR, (errcode(ERRCODE_OPERATE_RESULT_NOT_EXPECTED), + errmsg("Found invalid error record: negative length that is not -1."))); } + m_isNull[i] = false; + if (attrs[i]->attlen > 0 && attrs[i]->attlen <= 8) { + // 处理定长属性 if (unlikely(len != m_desc->attrs[i]->attlen)) { - ereport(ERROR, - (errcode(ERRCODE_OPERATE_RESULT_NOT_EXPECTED), - errmsg("Found invalid error recored: length is not the same as the attribute length."))); + // 长度不等于属性长度是无效的,抛出错误 + ereport(ERROR, (errcode(ERRCODE_OPERATE_RESULT_NOT_EXPECTED), + errmsg("Found invalid error record: length is not the same as the attribute length."))); } - pq_copymsgbytes(buf, (char*)&m_values[i], len); + pq_copymsgbytes(buf, (char *)&m_values[i], len); // 从缓冲区复制属性数据 } else if (attrs[i]->attlen > 8) { + // 处理变长属性 if (unlikely(len != m_desc->attrs[i]->attlen)) { - ereport(ERROR, - (errcode(ERRCODE_OPERATE_RESULT_NOT_EXPECTED), - errmsg("Found invalid error recored: length is not the same as the attribute length."))); + // 长度不等于属性长度是无效的,抛出错误 + ereport(ERROR, (errcode(ERRCODE_OPERATE_RESULT_NOT_EXPECTED), + errmsg("Found invalid error record: length is not the same as the attribute length."))); } - m_values[i] = (Datum)palloc(len); - pq_copymsgbytes(buf, DatumGetPointer(m_values[i]), len); + m_values[i] = (Datum)palloc(len); // 分配内存以存储属性数据 + pq_copymsgbytes(buf, DatumGetPointer(m_values[i]), len); // 从缓冲区复制属性数据 } else { - m_values[i] = (Datum)palloc(VARHDRSZ + len); - SET_VARSIZE(m_values[i], VARHDRSZ + len); - pq_copymsgbytes(buf, VARDATA(m_values[i]), len); + // 处理变长属性 + m_values[i] = (Datum)palloc(VARHDRSZ + len); // 分配内存以存储属性数据(包括长度信息) + SET_VARSIZE(m_values[i], VARHDRSZ + len); // 设置变长数据的长度信息 + pq_copymsgbytes(buf, VARDATA(m_values[i]), len); // 从缓冲区复制属性数据 } } } +/* + * 功能:重置BaseError对象的状态 + * + * 注意: + * 该函数用于将BaseError对象的状态重置,包括将所有属性的IsNull标志和值清零。 + */ void BaseError::Reset() { errno_t rc; + + // 使用memset_s函数将 m_isNull 数组的所有元素清零 rc = memset_s(m_isNull, sizeof(bool) * MaxNumOfValue, 0, sizeof(bool) * MaxNumOfValue); securec_check(rc, "", ""); + + // 使用memset_s函数将 m_values 数组的所有元素清零 rc = memset_s(m_values, sizeof(Datum) * MaxNumOfValue, 0, sizeof(Datum) * MaxNumOfValue); securec_check(rc, "", ""); } +/* + * 功能:将最大值(MaxNumOfValue)序列化到StringInfo缓冲区中 + * + * 参数列表: + * buf:用于存储序列化数据的StringInfo缓冲区 + * + * 注意: + * 该函数用于将预定义的最大值(MaxNumOfValue)序列化到给定的StringInfo缓冲区中, + * 以便在序列化错误信息时使用。 + */ void ImportError::serializeMaxNumOfValue(StringInfo buf) { + // 使用 pq_sendint16 函数将 MaxNumOfValue 序列化为一个16位整数,并写入缓冲区 pq_sendint16(buf, MaxNumOfValue); } +/* + * 功能:将最大值(MaxNumOfValue)序列化到StringInfo缓冲区中 + * + * 参数列表: + * buf:用于存储序列化数据的StringInfo缓冲区 + * + * 注意: + * 该函数用于将预定义的最大值(MaxNumOfValue)序列化到给定的StringInfo缓冲区中, + * 以便在序列化拷贝错误信息时使用。 + */ void CopyError::serializeMaxNumOfValue(StringInfo buf) { + // 使用 pq_sendint 函数将 MaxNumOfValue 序列化为一个2字节整数,并写入缓冲区 pq_sendint(buf, MaxNumOfValue, 2); } - +/* + * 功能:从文件中读取错误记录并将其填充到BaseError对象中 + * + * 参数列表: + * edata:用于存储错误记录的BaseError对象 + * + * 返回值: + * 0 表示成功,EOF 表示已读取到文件末尾,其他值表示出错 + * + * 注意: + * 该函数用于从文件中读取错误记录,将其反序列化,并将结果填充到给定的BaseError对象中。 + * 如果读取或处理出错,将抛出相应的错误。 + */ int BaseErrorLogger::FetchError(BaseError *edata) { - AutoContextSwitch memGuard(m_memCxt); - int nread = 0; - uint32 len = 0; - Assert(m_buffer != NULL && edata != NULL); - MemoryContextReset(m_memCxt); - resetStringInfo(m_buffer); + AutoContextSwitch memGuard(m_memCxt); // 自动内存上下文切换 + int nread = 0; // 读取的字节数 + uint32 len = 0; // 错误记录的长度 + Assert(m_buffer != NULL && edata != NULL); // 断言检查输入参数的有效性 + MemoryContextReset(m_memCxt); // 重置内存上下文 + resetStringInfo(m_buffer); // 重置StringInfo缓冲区 + + // 从文件中读取错误记录的长度(4字节) nread = FilePRead(m_fd, (char *)&len, 4, m_offset); if (nread == 0) { - return EOF; + return EOF; // 已读取到文件末尾 } else if (nread < 0) { - ereport(ERROR, (errcode_for_file_access(), errmsg("could not fetch error record:%m"))); + ereport(ERROR, (errcode_for_file_access(), errmsg("could not fetch error record:%m"))); // 文件读取错误 } else if (nread < 4) { - ereport(ERROR, (errcode_for_file_access(), errmsg("could not fetch expected length:%m"))); + ereport(ERROR, + (errcode_for_file_access(), errmsg("could not fetch expected length:%m"))); // 未读取到预期的长度 } - m_offset += 4; - len = ntohl(len); - enlargeStringInfo(m_buffer, len + 1); + m_offset += 4; // 更新文件偏移量 + len = ntohl(len); // 将长度从网络字节顺序转换为主机字节顺序 + enlargeStringInfo(m_buffer, len + 1); // 扩展StringInfo缓冲区以容纳错误记录 + // 从文件中读取错误记录数据,直到缓冲区长度达到错误记录长度 while ((uint32)m_buffer->len < len) { nread = FilePRead(m_fd, m_buffer->data + m_buffer->len, len - m_buffer->len, m_offset); if (nread == 0) { - ereport(ERROR, (errcode_for_file_access(), errmsg("incomplete error record"))); + ereport(ERROR, (errcode_for_file_access(), errmsg("incomplete error record"))); // 未完整读取错误记录 } else if (nread < 0) { - ereport(ERROR, (errcode_for_file_access(), errmsg("could not fetch error record:%m"))); + ereport(ERROR, (errcode_for_file_access(), errmsg("could not fetch error record:%m"))); // 文件读取错误 } - m_buffer->len += nread; - m_offset += nread; + m_buffer->len += nread; // 更新缓冲区长度 + m_offset += nread; // 更新文件偏移量 } - m_buffer->data[m_buffer->len] = '\0'; - edata->Deserialize(m_buffer); - return 0; + m_buffer->data[m_buffer->len] = '\0'; // 将缓冲区的最后一个字符设置为'\0',以便作为字符串使用 + edata->Deserialize(m_buffer); // 反序列化错误记录并填充到BaseError对象中 + return 0; // 成功读取并处理错误记录 } +/* + * 功能:将BaseError对象的错误信息保存到文件中 + * + * 参数列表: + * edata:包含错误信息的BaseError对象 + * + * 注意: + * 该函数用于将给定的BaseError对象的错误信息序列化并保存到文件中。 + * 函数通过将错误信息的长度前置到数据之前,并写入文件来实现保存操作。 + */ void BaseErrorLogger::SaveError(BaseError *edata) { - int nwrite = 0; - int len = 0; + int nwrite = 0; // 写入的字节数 + int len = 0; // 当前已写入的字节数 - Assert(m_buffer != NULL && edata != NULL); - resetStringInfo(m_buffer); - appendStringInfoSpaces(m_buffer, 4); - edata->Serialize(m_buffer); - *((uint32 *)m_buffer->data) = htonl((uint32)(m_buffer->len - 4)); + Assert(m_buffer != NULL && edata != NULL); // 断言检查输入参数的有效性 + resetStringInfo(m_buffer); // 重置StringInfo缓冲区 + appendStringInfoSpaces(m_buffer, 4); // 在缓冲区前添加4个空字节(用于存储错误信息的长度) + edata->Serialize(m_buffer); // 序列化错误信息并追加到缓冲区 + *((uint32 *)m_buffer->data) = htonl((uint32)(m_buffer->len - 4)); // 将错误信息的长度写入缓冲区 + // 循环将错误信息写入文件,直到所有数据都写入 while (len < m_buffer->len) { nwrite = FilePWrite(m_fd, m_buffer->data + len, m_buffer->len - len, m_offset); if (nwrite == 0 || nwrite < 0) { - ereport(ERROR, (errcode_for_file_access(), errmsg("could not cache error info:%m"))); + ereport(ERROR, (errcode_for_file_access(), errmsg("could not cache error info:%m"))); // 文件写入错误 } - len += nwrite; - m_offset += nwrite; + len += nwrite; // 更新已写入的字节数 + m_offset += nwrite; // 更新文件偏移量 } } +/* + * 功能:初始化 ImportErrorLogger 对象 + * + * 参数列表: + * output:输出参数(未使用) + * errDesc:错误描述的元组描述符 + * errInfo:错误日志信息(未使用) + * + * 注意: + * 该函数用于初始化 ImportErrorLogger 对象,设置相关的上下文和属性。 + * 输出参数 `output` 和错误日志信息 `errInfo` 目前未被使用。 + */ void ImportErrorLogger::Initialize(const void *output, TupleDesc errDesc, ErrLogInfo &errInfo) { + // 创建内存上下文 m_memCxt m_memCxt = AllocSetContextCreate(CurrentMemoryContext, "Import Error Context", ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); + + // 设置错误描述的元组描述符 m_errDesc m_errDesc = errDesc; + + // 忽略未使用的 errInfo 参数 (void)errInfo; } +/* + * 功能:销毁 ImportErrorLogger 对象 + * + * 注意: + * 该函数用于销毁 ImportErrorLogger 对象,释放相关的资源和内存上下文。 + */ void ImportErrorLogger::Destroy() { + // 删除内存上下文 m_memCxt,同时释放相关资源 MemoryContextDelete(m_memCxt); + + // 将错误描述的元组描述符 m_errDesc 设置为 NULL m_errDesc = NULL; } +/* + * 功能:初始化 GDSErrorLogger 对象 + * + * 参数列表: + * output:输出参数,通常是一个 GDSStream 指针 + * errDesc:错误描述的元组描述符 + * errInfo:错误日志信息 + * + * 注意: + * 该函数用于初始化 GDSErrorLogger 对象,设置相关的属性和上下文。 + * 输出参数 `output` 通常是一个 GDSStream 指针,`errDesc` 用于错误描述的元组描述符, + * `errInfo` 用于错误日志信息(未使用)。 + */ void GDSErrorLogger::Initialize(const void *output, TupleDesc errDesc, ErrLogInfo &errInfo) { + // 将输出参数强制转换为 GDSStream 指针并存储在 m_output 中 m_output = (GDSStream *)output; + + // 复制错误描述的元组描述符的名称并存储在 m_name 中 m_name = pstrdup((char *)errDesc); + + // 调用 ImportErrorLogger 的 Initialize 函数初始化对象 ImportErrorLogger::Initialize(output, NULL, errInfo); + + // 在使用断言检查时,初始化计数器 m_counter 为 0 #ifdef USE_ASSERT_CHECKING m_counter = 0; #endif } +/* + * 功能:销毁 GDSErrorLogger 对象 + * + * 注意: + * 该函数用于销毁 GDSErrorLogger 对象,同时输出日志信息(当使用断言检查时)。 + */ void GDSErrorLogger::Destroy() { + // 在使用断言检查时,输出日志信息,显示已发送的错误日志行数 #ifdef USE_ASSERT_CHECKING ereport(LOG, (errcode(ERRCODE_LOG), errmsg("Error Log send %d lines", m_counter))); #endif + + // 调用 ImportErrorLogger 的 Destroy 函数销毁对象 ImportErrorLogger::Destroy(); } @@ -280,65 +457,144 @@ int GDSErrorLogger::FetchError(BaseError *edata) return 0; } +/* + * 功能:保存错误信息到 GDSStream + * + * 参数列表: + * edata:包含错误信息的BaseError对象 + * + * 注意: + * 该函数用于将给定的错误信息保存到 GDSStream 中,用于远程日志记录。 + * 它通过获取错误信息的原始数据,将其封装为 CmdRemoteLog 结构并序列化, + * 然后将序列化后的数据写入 GDSStream 的输出缓冲区,并刷新缓冲区。 + * 在使用断言检查时,增加计数器 m_counter。 + */ void GDSErrorLogger::SaveError(BaseError *edata) { - CmdRemoteLog dat; - Datum rawdata; + CmdRemoteLog dat; // 用于远程日志记录的数据结构 + Datum rawdata; // 原始数据 - Assert(edata != NULL); + Assert(edata != NULL); // 断言检查输入参数的有效性 + + // 如果错误信息的原始数据为NULL,则不进行保存 if (edata->m_isNull[BaseError::RawDataIdx]) return; + // 获取错误信息的原始数据 rawdata = edata->m_values[BaseError::RawDataIdx]; + + // 填充远程日志记录数据结构 dat.m_type = CMD_TYPE_REMOTELOG; dat.m_datasize = VARSIZE_ANY_EXHDR(DatumGetPointer(rawdata)); dat.m_data = VARDATA_ANY(DatumGetPointer(rawdata)); - dat.m_name = pstrdup(m_name); + dat.m_name = pstrdup(m_name); // 复制错误描述的名称 + + // 序列化数据结构并将其写入 GDSStream 的输出缓冲区 SerializeCmd(&dat, m_output->m_outBuf); + + // 刷新输出缓冲区,将数据发送到远程日志服务器 m_output->Flush(); + + // 在使用断言检查时,增加计数器,用于记录已发送的错误日志行数 #ifdef USE_ASSERT_CHECKING ++m_counter; #endif } +/* + * 功能:生成导入错误的错误信息 + * + * 参数列表: + * cstate:拷贝状态信息 + * begintime:导入开始时间 + * edata:导入错误信息的BaseError对象 + * + * 注意: + * 该函数用于生成导入错误的错误信息,将其存储在给定的BaseError对象中。 + * 它首先将BaseError对象重置,然后根据拷贝状态中的信息生成原始数据, + * 并将其存储在BaseError对象中。同时,将一些标志位设置为指示错误信息是否为空。 + */ void GDSErrorLogger::FormError(CopyState cstate, Datum begintime, ImportError *edata) { - Assert(edata != NULL); + Assert(edata != NULL); // 断言检查输入参数的有效性 + // 重置BaseError对象的状态 edata->Reset(); + errno_t rc; + // 使用 memset_s 函数将 BaseError 对象的 isNull 数组的所有元素设置为 1(表示空值) rc = memset_s(edata->m_isNull, sizeof(bool) * ImportError::MaxNumOfValue, 1, sizeof(bool) * ImportError::MaxNumOfValue); securec_check(rc, "", ""); if (cstate->line_buf.len > 0) { int len = cstate->line_buf.len + VARHDRSZ + 1; + + // 分配内存用于存储原始数据,并设置其长度 edata->m_values[ImportError::RawDataIdx] = (Datum)palloc(len); SET_VARSIZE(edata->m_values[ImportError::RawDataIdx], len); + + // 将拷贝状态中的数据复制到原始数据中,并添加换行符 rc = memcpy_s(((char *)edata->m_values[ImportError::RawDataIdx]) + VARHDRSZ, len - VARHDRSZ, cstate->line_buf.data, cstate->line_buf.len); securec_check(rc, "", ""); ((char *)edata->m_values[ImportError::RawDataIdx])[len - 1] = '\n'; + + // 将原始数据的空值标志设置为 false(表示非空) edata->m_isNull[ImportError::RawDataIdx] = false; } } +/* + * 功能:初始化 LocalErrorLogger 对象 + * + * 参数列表: + * filename:缓存文件的文件名 + * errDesc:错误描述的元组描述符 + * errInfo:错误日志信息 + * + * 注意: + * 该函数用于初始化 LocalErrorLogger 对象,设置相关的属性和上下文。 + * 它通过生成本地缓存文件的文件名,并打开该文件来存储错误日志。 + * 然后,它调用 ImportErrorLogger 的 Initialize 函数初始化对象。 + */ void LocalErrorLogger::Initialize(const void *filename, TupleDesc errDesc, ErrLogInfo &errInfo) { - char cache_file[MAXPGPATH]; + char cache_file[MAXPGPATH]; // 本地缓存文件的文件名 + + // 生成本地缓存文件的文件名 generate_local_cache_file((const char *)filename, errInfo.smp_id, cache_file); + // 打开缓存文件,获取文件描述符 m_fd = OpenCacheFile(cache_file, errInfo.unlink_owner); + + // 创建 StringInfo 缓冲区 m_buffer = makeStringInfo(); + + // 调用 ImportErrorLogger 的 Initialize 函数初始化对象,传递缓存文件的文件名 ImportErrorLogger::Initialize(cache_file, errDesc, errInfo); } +/* + * 功能:销毁 LocalErrorLogger 对象 + * + * 注意: + * 该函数用于销毁 LocalErrorLogger 对象,同时释放相关的资源。 + * 它首先调用 ImportErrorLogger 的 Destroy 函数销毁对象,然后释放 + * StringInfo 缓冲区的内存,关闭缓存文件的文件描述符,但不删除物理文件。 + */ void LocalErrorLogger::Destroy() { + // 调用 ImportErrorLogger 的 Destroy 函数销毁对象 ImportErrorLogger::Destroy(); + + // 如果 StringInfo 缓冲区不为空 if (m_buffer != NULL) { + // 如果缓冲区的数据不为空,释放数据内存 if (m_buffer->data != NULL) pfree(m_buffer->data); + + // 释放 StringInfo 缓冲区的内存 pfree(m_buffer); m_buffer = NULL; } @@ -351,82 +607,161 @@ void LocalErrorLogger::Destroy() FileClose(m_fd); } +/* + * 功能:生成本地错误日志的错误信息 + * + * 参数列表: + * cstate:拷贝状态信息 + * begintime:导入开始时间 + * ierror:导入错误信息的ImportError对象 + * + * 注意: + * 该函数用于生成本地错误日志的错误信息,将其存储在给定的ImportError对象中。 + * 它首先通过CopyErrorData函数获取错误信息,然后将相关信息填充到ImportError对象中。 + * 如果有原始数据,将其存储在RawData字段中,如果有详细信息,将其存储在Detail字段中。 + */ void LocalErrorLogger::FormError(CopyState cstate, Datum begintime, ImportError *ierror) { - ErrorData *edata = NULL; - int len = 0; - const char *detail = NULL; - int sqlerrcode; + ErrorData *edata = NULL; // 错误数据 + int len = 0; // 长度 + const char *detail = NULL; // 详细信息 + int sqlerrcode; // SQL错误代码 errno_t rc; - Assert(ierror != NULL); + Assert(ierror != NULL); // 断言检查输入参数的有效性 + + // 获取错误信息 edata = CopyErrorData(); + + // 获取错误详细信息和SQL错误代码 detail = edata->message; sqlerrcode = edata->sqlerrcode; + + // 重置ImportError对象的状态 ierror->Reset(); + + // 设置ImportError对象的描述符 ierror->m_desc = m_errDesc; + + // 填充ImportError对象的字段值 ierror->m_values[ImportError::NodeIdIdx] = u_sess->pgxc_cxt.PGXCNodeId; ierror->m_values[ImportError::StartTimeIdx] = begintime; + + // 填充文件名字段 len = strlen(cstate->filename) + VARHDRSZ; ierror->m_values[ImportError::FileNameIdx] = (Datum)palloc(len); SET_VARSIZE(ierror->m_values[ImportError::FileNameIdx], len); rc = memcpy_s(((char *)ierror->m_values[ImportError::FileNameIdx]) + VARHDRSZ, len - VARHDRSZ, cstate->filename, len - VARHDRSZ); securec_check(rc, "", ""); + + // 填充行号字段 ierror->m_values[ImportError::LineNOIdx] = cstate->cur_lineno; + // 如果存在原始数据并且SQL错误代码不表示字符不在编码中或无法转换字符 if (cstate->line_buf.len > 0 && sqlerrcode != ERRCODE_CHARACTER_NOT_IN_REPERTOIRE && sqlerrcode != ERRCODE_UNTRANSLATABLE_CHARACTER) { char *rawDataVal = NULL; int rawDataValLen = 0; + + // 获取限制长度的原始数据 rawDataVal = limit_printout_length(cstate->line_buf.data); rawDataValLen = strlen(rawDataVal); + + // 填充原始数据字段 len = cstate->line_buf.len + VARHDRSZ; ierror->m_values[ImportError::RawDataIdx] = (Datum)palloc(len); SET_VARSIZE(ierror->m_values[ImportError::RawDataIdx], len); rc = memcpy_s(((char *)ierror->m_values[ImportError::RawDataIdx]) + VARHDRSZ, len - VARHDRSZ, cstate->line_buf.data, rawDataValLen); securec_check(rc, "", ""); - } else + } else { + // 原始数据为空 ierror->m_isNull[ImportError::RawDataIdx] = true; + } + + // 如果存在详细信息,填充详细信息字段 if (detail != NULL) { char *detailVal = limit_printout_length(detail); int leng = strlen(detailVal); + ierror->m_values[ImportError::DetailIdx] = (Datum)palloc(leng + VARHDRSZ); SET_VARSIZE(ierror->m_values[ImportError::DetailIdx], leng + VARHDRSZ); rc = memcpy_s(((char *)ierror->m_values[ImportError::DetailIdx]) + VARHDRSZ, leng, detail, leng); securec_check(rc, "", ""); - } else + } else { + // 详细信息为空 ierror->m_isNull[ImportError::DetailIdx] = true; + } } - +/* + * 功能:初始化 CopyErrorLogger 对象 + * + * 参数列表: + * cstate:拷贝状态信息 + * + * 注意: + * 该函数用于初始化 CopyErrorLogger 对象,设置相关的属性和上下文。 + * 它通过生成一个唯一的缓存文件名,并打开该文件来存储拷贝错误日志。 + * 同时,创建 StringInfo 缓冲区,分配内存上下文,并获取错误表的元组描述符。 + */ void CopyErrorLogger::Initialize(CopyState cstate) { /* Get ourselves a cache file */ char *cache_file = generate_unique_cache_name_prefix(RelationGetRelationName(cstate->rel)); + // 打开缓存文件,获取文件描述符,并设置自动删除标志为 true m_fd = OpenCacheFile(cache_file, true); + + // 创建 StringInfo 缓冲区 m_buffer = makeStringInfo(); + // 创建内存上下文 m_memCxt = AllocSetContextCreate(CurrentMemoryContext, "Copy Import Error Context", ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); + + // 获取错误表的元组描述符 m_errDesc = RelationGetDescr(cstate->err_table); } +/* + * 功能:重置 CopyErrorLogger 对象的状态 + * + * 注意: + * 该函数用于重置 CopyErrorLogger 对象的状态,包括缓冲区、内存上下文和偏移量。 + * 它将 StringInfo 缓冲区重置为空,重置内存上下文,并将偏移量设置为 0。 + */ void CopyErrorLogger::Reset() { + // 重置 StringInfo 缓冲区为空 resetStringInfo(m_buffer); + + // 重置内存上下文 MemoryContextReset(m_memCxt); + + // 将偏移量设置为 0 m_offset = 0; } - +/* + * 功能:销毁 CopyErrorLogger 对象 + * + * 注意: + * 该函数用于销毁 CopyErrorLogger 对象,释放相关资源,包括内存上下文、缓冲区、错误描述符和文件描述符。 + * 它会删除内存上下文、释放缓冲区的内存、关闭文件描述符。 + */ void CopyErrorLogger::Destroy() { + // 删除内存上下文 MemoryContextDelete(m_memCxt); + + // 清空错误描述符 m_errDesc = NULL; + + // 释放缓冲区相关的内存 if (m_buffer != NULL) { if (m_buffer->data != NULL) pfree(m_buffer->data); + pfree(m_buffer); m_buffer = NULL; } @@ -438,8 +773,21 @@ void CopyErrorLogger::Destroy() } } +/* + * 功能:根据错误信息生成 CopyError 对象 + * + * 参数列表: + * cstate:当前的复制状态 + * begintime:开始时间 + * ierror:指向 CopyError 对象的指针,用于存储生成的错误信息 + * + * 注意: + * 该函数用于生成 CopyError 对象,将错误信息填充到 CopyError 中。 + * 它根据复制状态、开始时间、错误描述等信息创建错误记录。 + */ void CopyErrorLogger::FormError(CopyState cstate, Datum begintime, CopyError *ierror) { + // 声明 ErrorData 指针和其他变量 ErrorData *edata = NULL; int len = 0; const char *detail = NULL; @@ -448,28 +796,38 @@ void CopyErrorLogger::FormError(CopyState cstate, Datum begintime, CopyError *ie errno_t rc; char *source = NULL; + // 断言确保传入的 CopyError 对象非空 Assert(ierror != NULL); + + // 获取当前错误信息 edata = CopyErrorData(); detail = edata->message; sqlerrcode = edata->sqlerrcode; + + // 重置 CopyError 对象,清空之前的数据 ierror->Reset(); + // 设置 CopyError 的描述符为错误描述符 ierror->m_desc = m_errDesc; + // 构建关系名,格式为 "命名空间.表名" relname = (char *)palloc(MAX_NAME_LEN); rc = snprintf_s(relname, MAX_NAME_LEN, MAX_NAME_LEN - 1, "%s.%s", cstate->logger->m_namespace, RelationGetRelationName(cstate->rel)); securec_check_ss(rc, "\0", "\0"); + // 分配并设置关系名 len = strlen(relname) + VARHDRSZ; ierror->m_values[CopyError::RelNameIdx] = (Datum)palloc(len); SET_VARSIZE(ierror->m_values[CopyError::RelNameIdx], len); - rc = memcpy_s(((char *)ierror->m_values[CopyError::RelNameIdx]) + VARHDRSZ, len - VARHDRSZ, relname, - len - VARHDRSZ); + rc = + memcpy_s(((char *)ierror->m_values[CopyError::RelNameIdx]) + VARHDRSZ, len - VARHDRSZ, relname, len - VARHDRSZ); securec_check_c(rc, "\0", "\0"); + // 设置开始时间 ierror->m_values[CopyError::StartTimeIdx] = begintime; + // 设置文件名 if (cstate->filename) source = cstate->filename; else { @@ -477,13 +835,15 @@ void CopyErrorLogger::FormError(CopyState cstate, Datum begintime, CopyError *ie source = "STDIN"; } + // 分配并设置文件名 len = strlen(source) + VARHDRSZ; ierror->m_values[CopyError::FileNameIdx] = (Datum)palloc(len); SET_VARSIZE(ierror->m_values[CopyError::FileNameIdx], len); - rc = memcpy_s(((char *)ierror->m_values[CopyError::FileNameIdx]) + VARHDRSZ, len - VARHDRSZ, source, - len - VARHDRSZ); + rc = + memcpy_s(((char *)ierror->m_values[CopyError::FileNameIdx]) + VARHDRSZ, len - VARHDRSZ, source, len - VARHDRSZ); securec_check_c(rc, "\0", "\0"); + // 设置行号 ierror->m_values[CopyError::LineNOIdx] = cstate->cur_lineno; /* save the raw data here */ @@ -503,6 +863,7 @@ void CopyErrorLogger::FormError(CopyState cstate, Datum begintime, CopyError *ie ierror->m_isNull[CopyError::RawDataIdx] = true; } + // 保存详细信息(如果可用),注意限制数据长度 if (detail != NULL) { char *detailVal = limit_printout_length(detail); int leng = strlen(detailVal); @@ -513,30 +874,65 @@ void CopyErrorLogger::FormError(CopyState cstate, Datum begintime, CopyError *ie } else ierror->m_isNull[CopyError::DetailIdx] = true; + // 释放关系名的内存 pfree(relname); } -void CopyErrorLogger::FormWhenLog(CopyState cstate, Datum begintime, CopyError *ierror) +/* + * 功能:根据错误信息生成 CopyError 对象 + * + * 参数列表: + * cstate:当前的复制状态 + * begintime:开始时间 + * ierror:指向 CopyError 对象的指针,用于存储生成的错误信息 + * + * 注意: + * 该函数用于生成 CopyError 对象,将错误信息填充到 CopyError 中。 + * 它根据复制状态、开始时间、错误描述等信息创建错误记录。 + */ +void CopyErrorLogger::FormError(CopyState cstate, Datum begintime, CopyError *ierror) { + // 声明 ErrorData 指针和其他变量 + ErrorData *edata = NULL; + int len = 0; const char *detail = NULL; char *relname = NULL; + int sqlerrcode; errno_t rc; char *source = NULL; + // 断言确保传入的 CopyError 对象非空 Assert(ierror != NULL); - detail = "COPY_WHEN_ROWS"; + + // 获取当前错误信息 + edata = CopyErrorData(); + detail = edata->message; + sqlerrcode = edata->sqlerrcode; + + // 重置 CopyError 对象,清空之前的数据 ierror->Reset(); + // 设置 CopyError 的描述符为错误描述符 ierror->m_desc = m_errDesc; + // 构建关系名,格式为 "命名空间.表名" relname = (char *)palloc(MAX_NAME_LEN); rc = snprintf_s(relname, MAX_NAME_LEN, MAX_NAME_LEN - 1, "%s.%s", cstate->logger->m_namespace, RelationGetRelationName(cstate->rel)); securec_check_ss(rc, "\0", "\0"); - ierror->m_values[CopyError::RelNameIdx] = PointerGetDatum(cstring_to_text_with_len(relname, strlen(relname))); + // 分配并设置关系名 + len = strlen(relname) + VARHDRSZ; + ierror->m_values[CopyError::RelNameIdx] = (Datum)palloc(len); + SET_VARSIZE(ierror->m_values[CopyError::RelNameIdx], len); + rc = + memcpy_s(((char *)ierror->m_values[CopyError::RelNameIdx]) + VARHDRSZ, len - VARHDRSZ, relname, len - VARHDRSZ); + securec_check_c(rc, "\0", "\0"); + + // 设置开始时间 ierror->m_values[CopyError::StartTimeIdx] = begintime; + // 设置文件名 if (cstate->filename) source = cstate->filename; else { @@ -544,25 +940,45 @@ void CopyErrorLogger::FormWhenLog(CopyState cstate, Datum begintime, CopyError * source = "STDIN"; } - ierror->m_values[CopyError::FileNameIdx] = PointerGetDatum(cstring_to_text_with_len(source, strlen(source))); + // 分配并设置文件名 + len = strlen(source) + VARHDRSZ; + ierror->m_values[CopyError::FileNameIdx] = (Datum)palloc(len); + SET_VARSIZE(ierror->m_values[CopyError::FileNameIdx], len); + rc = + memcpy_s(((char *)ierror->m_values[CopyError::FileNameIdx]) + VARHDRSZ, len - VARHDRSZ, source, len - VARHDRSZ); + securec_check_c(rc, "\0", "\0"); + + // 设置行号 ierror->m_values[CopyError::LineNOIdx] = cstate->cur_lineno; /* save the raw data here */ - if (cstate->line_buf.len > 0 && cstate->logErrorsData) { + if (cstate->line_buf.len > 0 && sqlerrcode != ERRCODE_CHARACTER_NOT_IN_REPERTOIRE && + sqlerrcode != ERRCODE_UNTRANSLATABLE_CHARACTER && cstate->logErrorsData) { char *rawDataVal = NULL; + int rawDataValLen = 0; rawDataVal = limit_printout_length(cstate->line_buf.data); - ierror->m_values[CopyError::RawDataIdx] = - PointerGetDatum(cstring_to_text_with_len(rawDataVal, strlen(rawDataVal))); - pfree(rawDataVal); + rawDataValLen = strlen(rawDataVal); + len = rawDataValLen + VARHDRSZ; + ierror->m_values[CopyError::RawDataIdx] = (Datum)palloc(len); + SET_VARSIZE(ierror->m_values[CopyError::RawDataIdx], len); + rc = memcpy_s(((char *)ierror->m_values[CopyError::RawDataIdx]) + VARHDRSZ, len - VARHDRSZ, rawDataVal, + rawDataValLen); + securec_check(rc, "", ""); } else { ierror->m_isNull[CopyError::RawDataIdx] = true; } + // 保存详细信息(如果可用),注意限制数据长度 if (detail != NULL) { - ierror->m_values[CopyError::DetailIdx] = PointerGetDatum(cstring_to_text_with_len(detail, strlen(detail))); - } else { + char *detailVal = limit_printout_length(detail); + int leng = strlen(detailVal); + ierror->m_values[CopyError::DetailIdx] = (Datum)palloc(leng + VARHDRSZ); + SET_VARSIZE(ierror->m_values[CopyError::DetailIdx], leng + VARHDRSZ); + rc = memcpy_s(((char *)ierror->m_values[CopyError::DetailIdx]) + VARHDRSZ, leng, detail, leng); + securec_check_c(rc, "\0", "\0"); + } else ierror->m_isNull[CopyError::DetailIdx] = true; - } + // 释放关系名的内存 pfree(relname); } diff --git a/src/gausskernel/storage/cmgr/cache_mgr.cpp b/src/gausskernel/storage/cmgr/cache_mgr.cpp index 048232838..b111c2684 100644 --- a/src/gausskernel/storage/cmgr/cache_mgr.cpp +++ b/src/gausskernel/storage/cmgr/cache_mgr.cpp @@ -93,12 +93,18 @@ int CacheMgrNumLocks(int64 cache_size, uint32 each_block_size) */ int64 CacheMgrCalcSizeByType(MgrCacheType type) { + // ԪݻСΪcstoreС1/4󲻳2G + /* + * g_instance.attr.attr_storage.cstore_buffersһò + * ʾ cstore 洢ĻСԿΪλ + */ int64 size = g_instance.attr.attr_storage.cstore_buffers * 1024LL * 1 / 4; int64 cache_size = Min(size, MAX_METADATA_CACHE_SIZE); - if (!g_instance.attr.attr_sql.enable_orc_cache) { + // ORC 棨ò enable_orc_cache ƣ򽫻СΪ 1MB + if (!g_instance.attr.attr_sql.enable_orc_cache) { cache_size = 1024 * 1024; } - + // Ϊݻ棬СΪʣĴС if (type == MGR_CACHE_TYPE_DATA) { cache_size = g_instance.attr.attr_storage.cstore_buffers * 1024LL - cache_size; } @@ -118,19 +124,36 @@ void CacheMgr::Init(int64 cache_size, uint32 each_block_size, MgrCacheType type, int i = 0; int trancheId = LWTRANCHE_UNKNOWN; int32 total_slots = 0; - + // û m_cache_type = type; /* Must be greater than 0 */ m_CaccheSlotMax = 1; - m_cstoreCurrentSize = 0; - m_cstoreMaxSize = cache_size; - + m_cstoreCurrentSize = 0;//ʾǰʵʴСʼֵΪ 0ʾڳʼʱδʹá + m_cstoreMaxSize = cache_size;//ʾôСɴݸ cache_size ָʾϵͳ󻺴С + + // õĻ۵Ϊ MAX_CACHE_SLOT_COUNT total_slots = Min(cache_size / each_block_size, MAX_CACHE_SLOT_COUNT); + + /* + *дһڴ飬ڴ洢۵ݡ + * total_slots ʾ۵ + * each_slot_length ʾÿ۵ijȣ˻õҪڴС + * palloc0 һڴ亯ڴʼΪ + * 齫ڴ洢ÿ۵ݡ + */ m_CacheSlots = (char *)palloc0(total_slots * each_slot_length); + + //дһ飬ڴ洢 m_CacheDesc = (CacheDesc *)palloc0(total_slots * sizeof(CacheDesc)); + + //д뽫 m_CacheSlotsNum Ϊ۵ԱĻʹֵ m_CacheSlotsNum = total_slots; + + //д뽫 m_slot_length Ϊÿ۵ijȣԱںпȷÿ۵ݡ m_slot_length = each_slot_length; + + // еĻۣʼز for (i = 0; i < total_slots; ++i) { m_CacheDesc[i].m_usage_count = 0; m_CacheDesc[i].m_refcount = 0; @@ -144,11 +167,18 @@ void CacheMgr::Init(int64 cache_size, uint32 each_block_size, MgrCacheType type, } else if (type == MGR_CACHE_TYPE_INDEX) { trancheId = (int)LWTRANCHE_META_CACHE; } + + //ֱΪǰе m_iobusy_lock m_compress_lock ֶη뻺ص m_CacheDesc[i].m_iobusy_lock = LWLockAssign(trancheId); m_CacheDesc[i].m_compress_lock = LWLockAssign(trancheId); + + //д뽫е m_refreshing ֶγʼΪ falseֶαʾǰǷˢУˢָ¼ػݡ m_CacheDesc[i].m_refreshing = false; + + //е m_datablock_size ֶγʼΪ 0ֶڼ¼ݵĴС m_CacheDesc[i].m_datablock_size = 0; - + + //ʼ˻е m_slot_hdr_lockڻб۵ͷϢ SpinLockInit(&m_CacheDesc[i].m_slot_hdr_lock); } diff --git a/src/gausskernel/storage/dfs/obs/obs_connector.cpp b/src/gausskernel/storage/dfs/obs/obs_connector.cpp index 041485b67..99d2b4eee 100644 --- a/src/gausskernel/storage/dfs/obs/obs_connector.cpp +++ b/src/gausskernel/storage/dfs/obs/obs_connector.cpp @@ -30,9 +30,9 @@ #include "utils/plog.h" #if defined(__LP64__) || defined(__64BIT__) - typedef unsigned int GS_UINT32; +typedef unsigned int GS_UINT32; #else - typedef unsigned long GS_UINT32; +typedef unsigned long GS_UINT32; #endif #define OBS_NOT_IMPLEMENT \ @@ -41,25 +41,50 @@ HTAB *OBSConnectorCache = NULL; namespace dfs { +/* + * 功能:创建一个 OBS 连接器对象 + * + * 参数列表: + * ctx:内存上下文,用于内存管理 + * foreignTableId:外部表的对象标识符 + */ OBSConnector::OBSConnector(MemoryContext ctx, Oid foreignTableId) : m_memcontext(ctx), m_handler(NULL), srvType(T_INVALID) { // build obs handler m_handler = searchConnectorCache(foreignTableId); } - +/* + * 功能:创建一个 OBS 连接器对象 + * + * 参数列表: + * ctx:内存上下文,用于内存管理 + * obsOptions:OBS 连接选项,包含连接到 OBS 所需的配置信息 + */ OBSConnector::OBSConnector(MemoryContext ctx, ObsOptions *obsOptions) : m_memcontext(ctx), m_handler(NULL), srvType(T_INVALID) { // build obs handler m_handler = createRWHandler(obsOptions); } - +/* + * 功能:销毁 OBS 连接器对象 + * + * 注意:在对象销毁时,调用了 Destroy() 函数以执行必要的清理操作。 + */ OBSConnector::~OBSConnector() { Destroy(); } - +/* + * 功能:销毁 OBS 连接器对象 + * + * 注意: + * - 这个函数用于销毁 OBS 连接器对象,包括清理与对象相关的资源。 + * - 在销毁前,将 m_handler->m_object_info.key 设置为 NULL,以避免悬挂指针。 + * - 调用 DestroyObsReadWriteHandler() 函数销毁 OBS 读写处理程序,并传递 true 参数以确保释放所有相关资源。 + * - 最后,将 m_handler 指针设置为 NULL,防止重复销毁。 + */ void OBSConnector::Destroy() { // IMPORT: m_handler->m_prefix not alloc on this class 's memeory context @@ -315,47 +340,99 @@ List *OBSConnector::listObjectsStat(char *searchPath, const char *primitivePrefi return objectList; } - +/* + * 功能:获取指定文件的 DFS 块信息 + * + * 参数列表: + * filePath:要查询块信息的文件路径 + * + * 注意: + * - 该函数尚未实现,只是一个占位符。在实现此功能之前,它返回 NULL。 + * - 一旦实现了获取文件的 DFS 块信息的功能,应该更新此函数以提供正确的实现。 + * - DFS 块信息通常包括块的位置、大小等相关信息。 + */ DFSBlockInfo *OBSConnector::getBlockLocations(char *filePath) { + // 使用 OBS_NOT_IMPLEMENT 标记函数未实现 OBS_NOT_IMPLEMENT; + // 返回 NULL,因为函数未实现 return NULL; } - +/* + * 功能:删除指定路径的目录(未实现) + * + * 参数列表: + * path:要删除的目录路径 + * recursive:是否递归删除子目录(1 表示递归删除,0 表示不递归) + */ int OBSConnector::dropDirectory(const char *path, int recursive) { + // 使用 OBS_NOT_IMPLEMENT 标记函数未实现 OBS_NOT_IMPLEMENT; + // 返回 0,表示删除成功(占位返回值,实际删除未实现) return 0; } - +/* + * 功能:创建指定路径的目录(未实现) + * + * 参数列表: + * path:要创建的目录路径 + */ int OBSConnector::createDirectory(const char *path) { + // 使用 OBS_NOT_IMPLEMENT 标记函数未实现 OBS_NOT_IMPLEMENT; + // 返回 0,表示创建成功(占位返回值,实际创建未实现) return 0; } - +/* + * 功能:打开指定路径的文件 + * + * 参数列表: + * path:要打开的文件路径 + * flag:文件打开标志,O_RDONLY 表示只读,其他表示其他操作 + */ int OBSConnector::openFile(const char *path, int flag) { + // 声明 bucket 和 prefix 字符串指针,并初始化为 NULL char *bucket = NULL; char *prefix = NULL; + // 通过 FetchUrlPropertiesForQuery 函数获取路径中的 bucket 和 prefix FetchUrlPropertiesForQuery(path, &bucket, &prefix); + // 设置存储处理器的对象信息的 key 为 prefix m_handler->m_object_info.key = prefix; + + // 如果存储处理器的选项中的桶名称为空,将其设置为 bucket if (m_handler->m_option.bucket_options.bucket_name == NULL) m_handler->m_option.bucket_options.bucket_name = bucket; + // 根据打开标志设置存储处理器的类型为读取或写入 if (flag == O_RDONLY) ObsReadWriteHandlerSetType(m_handler, OBS_READ); else ObsReadWriteHandlerSetType(m_handler, OBS_WRITE); + + // 返回 0,表示打开成功 return 0; } - +/* + * 功能:删除指定路径的文件或目录 + * + * 参数列表: + * path:要删除的文件或目录的路径 + * recursive:递归删除标志,1 表示递归删除,0 表示只删除指定文件或目录 + * + * 返回值: + * 0 表示删除成功,其他值表示删除失败 + */ int OBSConnector::deleteFile(const char *path, int recursive) { int ret = 0; + // 调用 deleteOBSObject 函数删除指定路径的文件或目录,并将结果赋给 ret ret = deleteOBSObject(m_handler); + // 返回删除操作的结果 return ret; } @@ -435,27 +512,57 @@ bool OBSConnector::existsFile(const char *path) return isExists; } - +/* + * 功能:检查是否存在有效文件 + * + * 返回值: + * false 表示不存在有效文件 + */ bool OBSConnector::hasValidFile() const { /* OBS_NOT_IMPLEMENT; */ return false; } - +/* + * 功能:将数据写入当前文件 + * + * 参数列表: + * buffer:要写入的数据的缓冲区 + * length:要写入的数据的长度 + * + * 返回值: + * 0 表示写入成功,其他值表示写入失败 + */ int OBSConnector::writeCurrentFile(const char *buffer, int length) { int ret = 0; + // 调用 writeObsTempFile 函数将数据写入当前文件,并将结果赋给 ret ret = writeObsTempFile(m_handler, buffer, length); + + // 返回写入操作的结果 return ret; } - +/* + * 功能:从当前文件中读取数据到缓冲区,直到读取指定长度的数据或达到文件末尾 + * + * 参数列表: + * buffer:用于存储读取数据的缓冲区 + * length:要读取的数据的长度 + * offset:读取数据的起始偏移量(未使用) + * + * 返回值: + * 0 表示成功读取指定长度的数据,-1 表示读取失败或达到文件末尾 + */ int OBSConnector::readCurrentFileFully(char *buffer, int length, int64 offset) { int ret = 0; + // 调用 read_bucket_object 函数从当前文件中读取数据到缓冲区,返回已读取的数据长度 int readSize = (int)read_bucket_object(m_handler, buffer, length); + // 如果已读取的数据长度不等于指定的长度,将返回值设置为-1,表示读取失败或达到文件末尾 if (readSize != length) { ret = -1; } + // 返回读取操作的结果 return ret; } @@ -533,19 +640,37 @@ OBSReadWriteHandler *OBSConnector::searchConnectorCache(Oid foreignTableId) return handler; } - +/* + * 功能:创建 OBS 读写处理程序 + * + * 参数列表: + * obsOptions:包含 OBS 配置信息的选项结构 + * + * 返回值: + * 返回新创建的 OBS 读写处理程序的指针 + */ OBSReadWriteHandler *OBSConnector::createRWHandler(ObsOptions *obsOptions) { + // 切换内存上下文到当前类的内存上下文 AutoContextSwitch memGuard(m_memcontext); OBSReadWriteHandler *handler = NULL; + // 创建 OBS 读写处理程序,并为查询进行配置 handler = CreateObsReadWriteHandlerForQuery(obsOptions); handler->in_computing = true; return handler; } - +/* + * 从缓存中获取 OBS 连接选项 + * + * 参数列表: + * foreignTableId:外部表的 OID + * + * 返回值: + * 返回 ObsOptions 结构的指针,包含 OBS 连接选项 + */ ObsOptions *OBSConnector::getOptionFromCache(Oid foreignTableId) { bool found = false; @@ -565,6 +690,7 @@ ObsOptions *OBSConnector::getOptionFromCache(Oid foreignTableId) if (found && (entry && entry->access_key != NULL && entry->secret_access_key != NULL && entry->address != NULL)) { GS_UINT32 outPutLen; ObsOptions *retOption = copyObsOptions(entry); + // 解码加密的秘密访问密钥 pfree(retOption->secret_access_key); retOption->secret_access_key = SEC_decodeBase64(entry->secret_access_key, &outPutLen); return retOption; @@ -576,9 +702,11 @@ ObsOptions *OBSConnector::getOptionFromCache(Oid foreignTableId) /* Put the new connector info into the hash table. */ (void)LWLockAcquire(DfsConnectorCacheLock, LW_EXCLUSIVE); entry = (ObsOptions *)hash_search(OBSConnectorCache, (void *)&key, HASH_ENTER, &found); + // 如果未能成功添加到哈希表,报错 if (entry == NULL) ereport(PANIC, (errcode(ERRCODE_UNDEFINED_OBJECT), errmodule(MOD_OBS), errmsg("build global OBS connect cache hash table failed"))); + // 如果在哈希表中未找到连接选项,将外部表的选项复制到哈希表中 if (!found) { Assert(obsOptions != NULL); entry->address = obsOptions->address; @@ -591,6 +719,7 @@ ObsOptions *OBSConnector::getOptionFromCache(Oid foreignTableId) } LWLockRelease(DfsConnectorCacheLock); + // 返回连接选项的副本,并解码加密的秘密访问密钥 ObsOptions *retOption = copyObsOptions(entry); GS_UINT32 outPutLen; pfree(retOption->secret_access_key); @@ -608,52 +737,82 @@ int OBSConnector::getType() { return (int)OBS_CONNECTOR; } - +/* + * 功能:用于数据结构(如哈希表或搜索树)中,以帮助确定元素的位置或查找元素 + * 参数说明: + * key1:指向第一个Oid的指针。 + * key2:指向第二个Oid的指针。 + * keySize:表示Oid的大小,通常是sizeof(Oid)。 + */ static int matchOid(const void *key1, const void *key2, Size keySize) { return (int)(*(Oid *)key1 - *(Oid *)key2); } - +/* + * 功能:初始化OBS连接器缓存锁 + * + * 无参数 + */ void InitOBSConnectorCacheLock() { - HASHCTL ctl; - errno_t rc = 0; + HASHCTL ctl; // 哈希表的控制结构 + errno_t rc = 0; // 错误码 if (OBSConnectorCache == NULL) { + // 清零控制结构 rc = memset_s(&ctl, sizeof(ctl), 0, sizeof(ctl)); securec_check(rc, "\0", "\0"); - ctl.hcxt = g_instance.instance_context; - ctl.keysize = sizeof(Oid); - ctl.entrysize = sizeof(ObsOptions); - ctl.match = (HashCompareFunc)matchOid; - ctl.hash = (HashValueFunc)oid_hash; - OBSConnectorCache = hash_create("OBS connector cache", 50, &ctl, - HASH_ELEM | HASH_FUNCTION | HASH_COMPARE | HASH_SHRCTX); + ctl.hcxt = g_instance.instance_context; // 分配内存的上下文 + ctl.keysize = sizeof(Oid); // 键的大小,通常是Oid的大小 + ctl.entrysize = sizeof(ObsOptions); // 哈希表中每个条目的大小 + ctl.match = (HashCompareFunc)matchOid; // 用于比较键的函数 + ctl.hash = (HashValueFunc)oid_hash; // 计算哈希值的函数 + // 创建哈希表,带有指定的控制选项 + OBSConnectorCache = + hash_create("OBS connector cache", 50, &ctl, HASH_ELEM | HASH_FUNCTION | HASH_COMPARE | HASH_SHRCTX); + // 如果哈希表创建失败,发出 PANIC 级别的错误信息 if (OBSConnectorCache == NULL) ereport(PANIC, (errmodule(MOD_HDFS), errmsg("could not initialize OBS connector hash table"))); } } - +/* + * 功能:使 OBS 连接器缓存无效 + * + * 参数列表: + * serverOid:外部表所在的服务器的对象标识符(OID) + * + * 返回值: + * 如果成功清理缓存,则返回 true;否则返回 false + */ bool InvalidOBSConnectorCache(Oid serverOid) { - bool realClean = false; + bool realClean = false; // 用于标记是否真正清理了缓存 + // 获取 DfsConnectorCacheLock 锁,以独占模式 (void)LWLockAcquire(DfsConnectorCacheLock, LW_EXCLUSIVE); + // 在哈希表 OBSConnectorCache 中查找并移除指定的键(serverOid) ObsOptions *entry = (ObsOptions *)hash_search(OBSConnectorCache, (void *)&serverOid, HASH_REMOVE, NULL); + // 如果找到了指定的键(entry 不为 NULL),则进行清理操作 if (entry != NULL) { + // 如果 access_key 不为 NULL,释放其内存 if (entry->access_key != NULL) { pfree_ext(entry->access_key); } + // 如果 secret_access_key 不为 NULL,使用 OPENSSL_free 释放其内存,并将其置为 NULL if (entry->secret_access_key != NULL) { OPENSSL_free(entry->secret_access_key); entry->secret_access_key = NULL; } + // 如果 address 不为 NULL,释放其内存 if (entry->address != NULL) { pfree_ext(entry->address); } + // 标记已经成功清理缓存 realClean = true; } + // 释放 DfsConnectorCacheLock 锁 LWLockRelease(DfsConnectorCacheLock); + // 返回是否成功清理缓存的标志 return realClean; } } // namespace dfs diff --git a/src/gausskernel/storage/dorado_operation/dorado_fd.cpp b/src/gausskernel/storage/dorado_operation/dorado_fd.cpp index 9a075d771..fbfd04698 100644 --- a/src/gausskernel/storage/dorado_operation/dorado_fd.cpp +++ b/src/gausskernel/storage/dorado_operation/dorado_fd.cpp @@ -34,58 +34,96 @@ void DoradoReadCtlInfo(ShareStorageXLogCtl *ctlInfo); int DoradoWriteXLog(XLogRecPtr startLsn, char *buf, int writeLen); int DoradoReadXLog(XLogRecPtr startLsn, char *buf, int expectReadLen); void DoradoFsync(); - +/* + * 功能:定义一个静态常量结构体,并初始化其成员函数指针 + */ static const ShareStorageOperateIf doradoOperateIf = { DoradoReadCtlInfo, DoradoWriteCtlInfo, DoradoReadXLog, DoradoWriteXLog, DoradoFsync, }; - +/* + * 功能:计算 Xlog 位置 + * + * 参数列表: + * expect:表示期望的 Xlog 位置 + */ static inline uint64 GetXlogPos(uint64 expect) { + // 计算 Xlog 位置,通过将期望的位置加上常量 DORADO_XLOG_START_POS 得到 return (expect + DORADO_XLOG_START_POS); } - +/* + * 功能:初始化 Dorado 存储 + * + * 参数列表: + * filePath:Xlog 文件的路径 + * fileSize:Xlog 文件的大小 + */ void InitDoradoStorage(char *filePath, uint64 fileSize) { + // 断言确保共享存储操作控制块没有被初始化 Assert(!g_instance.xlog_cxt.shareStorageopCtl.isInit); + // 设置 Xlog 文件路径为传入的 filePath g_instance.xlog_cxt.shareStorageopCtl.xlogFilePath = filePath; - + + // 设置块大小为 MEMORY_ALIGNED_SIZE(一个常量) g_instance.xlog_cxt.shareStorageopCtl.blkSize = MEMORY_ALIGNED_SIZE; + // 设置操作接口为 doradoOperateIf(之前定义的结构体) g_instance.xlog_cxt.shareStorageopCtl.opereateIf = &doradoOperateIf; + // 规范化文件路径,确保它是绝对路径 canonicalize_path(filePath); + // 打开 Xlog 文件,以读写方式打开,二进制模式,使用 O_DIRECT 标志 + // S_IRUSR | S_IWUSR 表示文件权限,S_IRUSR 表示用户有读权限,S_IWUSR 表示用户有写权限 g_instance.xlog_cxt.shareStorageopCtl.fd = open(filePath, O_RDWR | PG_BINARY | O_DIRECT, S_IRUSR | S_IWUSR); + // 如果文件打开失败,报告错误并且终止程序(PANIC) if (g_instance.xlog_cxt.shareStorageopCtl.fd < 0) { ereport(PANIC, (errcode_for_file_access(), errmsg("could not open xlog file \"%s\" : %m", filePath))); } + // 设置共享存储操作控制块的初始化标志为 true g_instance.xlog_cxt.shareStorageopCtl.isInit = true; + // 如果正在进行数据库初始化(IsInitdb 为真),设置 Xlog 文件大小为传入的 fileSize if (IsInitdb) { g_instance.xlog_cxt.shareStorageopCtl.xlogFileSize = fileSize; } else { + // 否则,如果不是初始化数据库,断言确保共享存储 XLog 控制块不为空 Assert(g_instance.xlog_cxt.shareStorageXLogCtl != NULL); + // 调用 DoradoReadCtlInfo 函数读取共享存储 XLog 控制块的信息,并设置 Xlog 文件大小 DoradoReadCtlInfo(g_instance.xlog_cxt.shareStorageXLogCtl); g_instance.xlog_cxt.shareStorageopCtl.xlogFileSize = g_instance.xlog_cxt.shareStorageXLogCtl->xlogFileSize; } } - +/* + * 功能:将共享存储控制信息写入文件 + * + * 参数列表: + * ctlInfo:共享存储 XLog 控制块的指针 + */ void DoradoWriteCtlInfo(const ShareStorageXLogCtl *ctlInfo) { + // 断言确保文件描述符大于 0,即文件已经成功打开 Assert(g_instance.xlog_cxt.shareStorageopCtl.fd > 0); + // 检查共享存储块大小是否与控制块大小对齐,如果不对齐,报告错误并终止程序 if (!IS_TYPE_ALIGINED(g_instance.xlog_cxt.shareStorageopCtl.blkSize, ctlInfo)) { ereport(PANIC, (errcode_for_file_access(), errmsg("dorado write control info ptr(%p) is not match,mask is %X", ctlInfo, g_instance.xlog_cxt.shareStorageopCtl.blkSize))); } + // 检查控制块的魔数和检验码是否匹配,如果不匹配,报告致命错误 if (ctlInfo->magic != SHARE_STORAGE_CTL_MAGIC || ctlInfo->checkNumber != SHARE_STORAGE_CTL_CHCK_NUMBER) { ereport(FATAL, (errmsg("ShareStorageXLogCtl info in memory maybe damaged"))); } + // 计算控制块的 CRC 校验值 pg_crc32c crc = CalShareStorageCtlInfoCrc(ctlInfo); + // 检查计算得到的 CRC 是否与控制块中的 CRC 相匹配,如果不匹配,报告致命错误 if (!EQ_CRC32C(crc, ctlInfo->crc)) { ereport(FATAL, (errmsg("crc check fail for ShareStorageXLogCtl in DoradoWriteCtlInfo"))); } // write 512bytes + // 使用 pwrite 函数将控制块写入文件,写入大小为 DORADO_CTL_WRITE_SIZE 字节,偏移量为 0 ssize_t actualBytes = pwrite(g_instance.xlog_cxt.shareStorageopCtl.fd, ctlInfo, DORADO_CTL_WRITE_SIZE, 0); + // 检查实际写入的字节数是否与期望的字节数相匹配,如果不匹配,报告错误 if (actualBytes != (ssize_t)DORADO_CTL_WRITE_SIZE) { /* if write didn't set errno, assume no disk space */ if (errno == 0) { @@ -97,41 +135,68 @@ void DoradoWriteCtlInfo(const ShareStorageXLogCtl *ctlInfo) (unsigned long)actualBytes, (unsigned long)DORADO_CTL_WRITE_SIZE))); } } - +/* + * 功能:从文件中读取共享存储控制信息 + * + * 参数列表: + * ctlInfo:共享存储 XLog 控制块的指针 + */ void DoradoReadCtlInfo(ShareStorageXLogCtl *ctlInfo) { + // 断言确保文件描述符大于 0,即文件已经成功打开 Assert(g_instance.xlog_cxt.shareStorageopCtl.fd > 0); + // 检查共享存储块大小是否与控制块大小对齐,如果不对齐,报告错误并终止程序 if (!IS_TYPE_ALIGINED(g_instance.xlog_cxt.shareStorageopCtl.blkSize, ctlInfo)) { ereport(PANIC, (errcode_for_file_access(), errmsg("dorado read control info ptr(%p) is not match,mask is %X", ctlInfo, g_instance.xlog_cxt.shareStorageopCtl.blkSize))); } + // 使用 pread 函数从已打开的文件中读取控制块信息,读取大小为 DORADO_CTL_WRITE_SIZE 字节,偏移量为 0 ssize_t actualBytes = pread(g_instance.xlog_cxt.shareStorageopCtl.fd, ctlInfo, DORADO_CTL_WRITE_SIZE, 0); + // 检查实际读取的字节数是否与期望的字节数相匹配,如果不匹配,报告错误并终止程序 if (actualBytes != (ssize_t)DORADO_CTL_WRITE_SIZE) { ereport(PANIC, (errcode_for_file_access(), errmsg("could not read dorado ctl info: %m"))); } + // 检查控制块的魔数和检验码是否匹配,如果不匹配,报告致命错误 if (ctlInfo->magic != SHARE_STORAGE_CTL_MAGIC || ctlInfo->checkNumber != SHARE_STORAGE_CTL_CHCK_NUMBER) { ereport(FATAL, (errmsg("dorado ctl info maybe damaged"))); } + // 计算控制块的 CRC 校验值 pg_crc32c crc = CalShareStorageCtlInfoCrc(ctlInfo); + // 检查计算得到的 CRC 是否与控制块中的 CRC 相匹配,如果不匹配,报告致命错误 if (!EQ_CRC32C(crc, ctlInfo->crc)) { ereport(FATAL, (errmsg("crc check fail for ShareStorageXLogCtl in DoradoReadCtlInfo"))); } } - +/* + * 功能:从文件中读取 XLog 数据 + * + * 参数列表: + * startLsn:开始读取的 XLog 记录位置 + * buf:用于存储读取数据的缓冲区 + * expectReadLen:期望读取的数据长度 + * + * 返回值:成功读取的字节数,或者错误时返回负值 + */ int DoradoReadXLog(XLogRecPtr startLsn, char *buf, int expectReadLen) { + // 断言确保文件描述符大于 0,即文件已经成功打开 Assert(g_instance.xlog_cxt.shareStorageopCtl.fd > 0); + // 检查共享存储块大小是否与缓冲区大小对齐,如果不对齐,报告错误并终止程序 if (!IS_TYPE_ALIGINED(g_instance.xlog_cxt.shareStorageopCtl.blkSize, buf)) { ereport(PANIC, (errcode_for_file_access(), errmsg("dorado read xlog ptr(%p) is not match,mask is %X", buf, g_instance.xlog_cxt.shareStorageopCtl.blkSize))); } + // 计算起始位置,即 startLsn 对 XLog 文件大小取模 uint64 startPos = startLsn % g_instance.xlog_cxt.shareStorageopCtl.xlogFileSize; + // 如果期望读取的数据不会超过文件末尾 if ((startPos + expectReadLen) <= g_instance.xlog_cxt.shareStorageopCtl.xlogFileSize) { + // 使用 pread 函数从已打开的文件中读取数据,读取大小为 expectReadLen 字节,起始位置为 GetXlogPos(startPos) ssize_t actualBytes = pread(g_instance.xlog_cxt.shareStorageopCtl.fd, buf, expectReadLen, GetXlogPos(startPos)); + // 如果读取失败,报告错误并终止程序 if (actualBytes < 0) { uint32 shiftSize = 32; ereport(PANIC, (errcode_for_file_access(), errmsg("read xlog(start:%X/%X, pos:%lu len:%d) failed : %m", @@ -139,11 +204,16 @@ int DoradoReadXLog(XLogRecPtr startLsn, char *buf, int expectReadLen) static_cast(startLsn), startPos, expectReadLen))); } + // 返回成功读取的字节数 return static_cast(actualBytes); - } else { + } else { // 如果期望读取的数据会超过文件末尾,需要分两次读取 + // 第一次读取的大小,从 startPos 到文件末尾 int firstReadSize = g_instance.xlog_cxt.shareStorageopCtl.xlogFileSize - startPos; + // 第二次读取的大小,剩余部分 int secondReadSize = expectReadLen - firstReadSize; + // 第一次读取数据,起始位置为 GetXlogPos(startPos) ssize_t actualBytes = pread(g_instance.xlog_cxt.shareStorageopCtl.fd, buf, firstReadSize, GetXlogPos(startPos)); + // 如果第一次读取失败,报告错误并终止程序 if (actualBytes < 0) { uint32 shiftSize = 32; ereport(PANIC, (errcode_for_file_access(), errmsg("first read xlog(start:%X/%X, pos:%lu len:%d) failed:%m", @@ -151,11 +221,14 @@ int DoradoReadXLog(XLogRecPtr startLsn, char *buf, int expectReadLen) static_cast(startLsn), startPos, firstReadSize))); } + // 如果第一次读取的字节数不足,返回实际读取的字节数 if (actualBytes < firstReadSize) { return static_cast(actualBytes); } + // 第二次读取数据,起始位置为 GetXlogPos(0) actualBytes = pread(t_thrd.xlog_cxt.openLogFile, buf + firstReadSize, secondReadSize, GetXlogPos(0)); + // 如果第二次读取失败,报告错误并终止程序 if (actualBytes < 0) { uint32 shiftSize = 32; XLogRecPtr nextStartLsn = startLsn + firstReadSize; @@ -164,23 +237,40 @@ int DoradoReadXLog(XLogRecPtr startLsn, char *buf, int expectReadLen) static_cast(nextStartLsn), secondReadSize))); } + // 返回成功读取的字节数,包括第一次和第二次读取的部分 return static_cast(actualBytes + firstReadSize); } } - +/* + * 功能:向文件中写入 XLog 数据 + * + * 参数列表: + * startLsn:要写入的 XLog 记录的起始位置 + * buf:包含要写入的数据的缓冲区 + * writeLen:要写入的数据长度 + * + * 返回值:成功写入的数据长度,或者错误时返回负值 + */ int DoradoWriteXLog(XLogRecPtr startLsn, char *buf, int writeLen) { + // 断言确保文件描述符大于 0,即文件已经成功打开 Assert(g_instance.xlog_cxt.shareStorageopCtl.fd > 0); + // 计算起始位置,即 startLsn 对 XLog 文件大小取模 uint64 startPos = startLsn % g_instance.xlog_cxt.shareStorageopCtl.xlogFileSize; + // 检查共享存储块大小是否与缓冲区大小对齐,如果不对齐,报告错误并终止程序 if (!IS_TYPE_ALIGINED(g_instance.xlog_cxt.shareStorageopCtl.blkSize, buf)) { ereport(PANIC, (errcode_for_file_access(), errmsg("dorado write xlog ptr(%p) is not match,mask is %X", buf, g_instance.xlog_cxt.shareStorageopCtl.blkSize))); } + // 如果写入的数据不会超过文件末尾 if ((startPos + writeLen) <= g_instance.xlog_cxt.shareStorageopCtl.xlogFileSize) { + // 使用 pwrite 函数向已打开的文件中写入数据,写入大小为 writeLen 字节,起始位置为 GetXlogPos(startPos) ssize_t actualBytes = pwrite(g_instance.xlog_cxt.shareStorageopCtl.fd, buf, writeLen, GetXlogPos(startPos)); + // 如果写入的字节数不等于期望的字节数,报告错误并终止程序 if (actualBytes != writeLen) { + // 如果写入未设置 errno,假定是磁盘空间不足 if (errno == 0) { errno = ENOSPC; } @@ -190,13 +280,18 @@ int DoradoWriteXLog(XLogRecPtr startLsn, char *buf, int writeLen) static_cast(startLsn >> shiftSize), static_cast(startLsn), startPos, writeLen))); } - } else { + } else { // 如果写入的数据会超过文件末尾,需要分两次写入 + // 第一次写入的大小,从 startPos 到文件末尾 int firstWriteSize = g_instance.xlog_cxt.shareStorageopCtl.xlogFileSize - startPos; + // 第二次写入的大小,剩余部分 int secondWriteSize = writeLen - firstWriteSize; - ssize_t actualBytes = pwrite(g_instance.xlog_cxt.shareStorageopCtl.fd, buf, firstWriteSize, - GetXlogPos(startPos)); + // 第一次写入数据,起始位置为 GetXlogPos(startPos) + ssize_t actualBytes = + pwrite(g_instance.xlog_cxt.shareStorageopCtl.fd, buf, firstWriteSize, GetXlogPos(startPos)); + // 如果第一次写入的字节数不等于第一次写入的大小,报告错误并终止程序 if (actualBytes != firstWriteSize) { + // 如果写入未设置 errno,假定是磁盘空间不足 if (errno == 0) { errno = ENOSPC; } @@ -207,9 +302,12 @@ int DoradoWriteXLog(XLogRecPtr startLsn, char *buf, int writeLen) static_cast(startLsn), startPos, firstWriteSize))); } - actualBytes = pwrite(g_instance.xlog_cxt.shareStorageopCtl.fd, buf + firstWriteSize, secondWriteSize, - GetXlogPos(0)); + // 第二次写入数据,起始位置为 GetXlogPos(0) + actualBytes = + pwrite(g_instance.xlog_cxt.shareStorageopCtl.fd, buf + firstWriteSize, secondWriteSize, GetXlogPos(0)); + // 如果第二次写入的字节数不等于第二次写入的大小,报告错误并终止程序 if (actualBytes != secondWriteSize) { + // 如果写入未设置 errno,假定是磁盘空间不足 if (errno == 0) { errno = ENOSPC; } @@ -222,12 +320,20 @@ int DoradoWriteXLog(XLogRecPtr startLsn, char *buf, int writeLen) } } + // 返回成功写入的数据长度 return writeLen; } +/* + * 功能:将共享存储文件的数据同步到磁盘 + */ + void DoradoFsync() { + // 断言确保文件描述符大于 0,即文件已经成功打开 Assert(g_instance.xlog_cxt.shareStorageopCtl.fd > 0); + + // 使用 fsync 函数将共享存储文件的数据同步到磁盘 if (fsync(g_instance.xlog_cxt.shareStorageopCtl.fd) != 0) { ereport(PANIC, (errcode_for_file_access(), errmsg("could not fsync dorado file %s: %m", g_instance.xlog_cxt.shareStorageopCtl.xlogFilePath))); diff --git a/src/gausskernel/storage/page/checksum_impl.cpp b/src/gausskernel/storage/page/checksum_impl.cpp index bd7b4df4f..fc7498c89 100644 --- a/src/gausskernel/storage/page/checksum_impl.cpp +++ b/src/gausskernel/storage/page/checksum_impl.cpp @@ -17,20 +17,41 @@ #include "storage/checksum_impl.h" void ChecksumForZeroPadding(uint32 *sums, const uint32 *dataArr, uint32 currentLeft, uint32 alignSize); - +/* + * 功能:初始化校验和计算 + * + * 参数列表: + * seed:用于初始化校验和计算的种子值,通常是一个32位整数 + * value:要计算校验和的值,通常是一个32位整数 + * + * 注意事项: + * - 这是一个静态内联函数,通常用于在编译时进行优化,以提高性能。 + */ static inline uint32 pg_checksum_init(uint32 seed, uint32 value) { - CHECKSUM_COMP(seed, value); - return seed; + CHECKSUM_COMP(seed, value); // 使用宏CHECKSUM_COMP执行校验和计算 + return seed; // 返回初始化后的校验和种子值 } - -uint32 DataBlockChecksum(char* data, uint32 size, bool zeroing) +/* + * 功能:计算数据块的校验和 + * + * 参数列表: + * data:指向数据块的指针 + * size:数据块的大小,以字节为单位 + * zeroing:一个布尔值,指示是否需要进行零填充 + * + * 注意事项: + * - 该函数用于计算给定数据块的校验和。 + * - 如果需要进行零填充,则根据数据块的实际大小进行填充。 + * - 函数使用一系列校验和计算步骤,包括初始化、主计算和最终混合。 + */ +uint32 DataBlockChecksum(char *data, uint32 size, bool zeroing) { - uint32 sums[N_SUMS]; - uint32* dataArr = (uint32*)data; - uint32 result = 0; + uint32 sums[N_SUMS]; // 用于存储校验和的数组 + uint32 *dataArr = (uint32 *)data; // 将数据块转换为uint32数组 + uint32 result = 0; // 用于存储最终的校验和结果 uint32 i, j; - uint32 currentLeft = size; + uint32 currentLeft = size; // 用于跟踪尚未处理的数据量 /* ensure that the size is compatible with the algorithm */ uint32 alignSize = sizeof(uint32) * N_SUMS; @@ -42,10 +63,10 @@ uint32 DataBlockChecksum(char* data, uint32 size, bool zeroing) uint32 *initUint32 = NULL; char usedForInit[sizeof(uint32) * N_SUMS] = {0}; if (zeroing && size < alignSize) { - errno_t rc = memcpy_s(usedForInit, alignSize, (char *) dataArr, realSize); + errno_t rc = memcpy_s(usedForInit, alignSize, (char *)dataArr, realSize); securec_check(rc, "", ""); currentLeft -= realSize; - initUint32 = (uint32*)usedForInit; + initUint32 = (uint32 *)usedForInit; } else { initUint32 = dataArr; currentLeft -= alignSize; @@ -83,24 +104,53 @@ uint32 DataBlockChecksum(char* data, uint32 size, bool zeroing) return result; } - +/* + * 功能:用于处理数据块的零填充情况的校验和计算 + * + * 参数列表: + * - sums:校验和数组,用于存储校验和的中间结果 + * - dataArr:指向数据块的uint32数组指针 + * - currentLeft:尚未处理的数据块大小(以字节为单位) + * - alignSize:对齐的大小,通常为sizeof(uint32) * N_SUMS + * + * 注意事项: + * - 该函数用于处理数据块的零填充情况,它从数据块的末尾获取未处理的数据, + * 并进行校验和计算以适应零填充。 + * - 函数首先将未处理的数据复制到一个字符数组中,然后按照N_SUMS的偏移值 + * 计算校验和,将结果存储在sums数组中。 + */ void ChecksumForZeroPadding(uint32 *sums, const uint32 *dataArr, uint32 currentLeft, uint32 alignSize) { auto maxLen = sizeof(uint32) * N_SUMS; char currentLeftChars[maxLen] = {0}; errno_t rc = memcpy_s(currentLeftChars, maxLen, (char *)dataArr, currentLeft); securec_check(rc, "", ""); + // 针对复制后的字符数组,按照N_SUMS的偏移值计算校验和 for (int j = 0; j < N_SUMS; j += 2) { CHECKSUM_COMP(sums[j], ((uint32 *)currentLeftChars)[j]); CHECKSUM_COMP(sums[j + 1], ((uint32 *)currentLeftChars)[j + 1]); } } - -uint32 pg_checksum_block(char* data, uint32 size) +/* + * 功能:对数据块进行校验和计算 + * + * 参数列表: + * - data:指向数据块的指针 + * - size:数据块的大小,以字节为单位 + * + * 返回值: + * - 返回一个32位整数,表示计算得到的校验和值 + * + * 注意事项: + * - 该函数用于对给定的数据块进行校验和计算。 + * - 函数使用一系列的校验和计算步骤,包括初始化、主计算和最终混合。 + * - 如果数据块的大小不是sizeof(uint32) * N_SUMS的倍数,可能会导致断言错误。 + */ +uint32 pg_checksum_block(char *data, uint32 size) { - uint32 sums[N_SUMS]; - uint32* dataArr = (uint32*)data; - uint32 result = 0; + uint32 sums[N_SUMS]; // 用于存储校验和的数组 + uint32 *dataArr = (uint32 *)data; // 将数据块转换为uint32数组 + uint32 result = 0; // 用于存储最终的校验和结果 uint32 i, j; #ifndef ROACH_COMMON @@ -133,7 +183,7 @@ uint32 pg_checksum_block(char* data, uint32 size) result ^= sums[j]; } - return result; + return result; // 返回最终的校验和结果 } /* @@ -144,7 +194,7 @@ uint32 pg_checksum_block(char* data, uint32 size) * somehow moved to a different location), the page header (excluding the * checksum itself), and the page data. */ -uint16 pg_checksum_page(char* page, BlockNumber blkno) +uint16 pg_checksum_page(char *page, BlockNumber blkno) { PageHeader phdr = (PageHeader)page; uint16 save_checksum; diff --git a/src/gausskernel/storage/page/gs_xlogdump.cpp b/src/gausskernel/storage/page/gs_xlogdump.cpp index 3f3e92aa5..3827de052 100644 --- a/src/gausskernel/storage/page/gs_xlogdump.cpp +++ b/src/gausskernel/storage/page/gs_xlogdump.cpp @@ -40,93 +40,173 @@ #include "utils/palloc.h" #include "pageparse.h" +// 用来在程序中组织和传递有关数据库日志 (XLog) 的私有信息 typedef struct XLogPrivate { - const char *datadir; - TimeLineID tli; + const char *datadir; // 储存数据目录的路径 + TimeLineID tli; // 存储时间线标识 } XLogPrivate; +// 用于在 XLog 处理中指定过滤条件 typedef struct XLogFilter { - TransactionId by_xid; - bool by_xid_enabled; - bool by_tablepath_enabled; - bool by_block; - RelFileNode by_relfilenode; - BlockNumber blocknum; + TransactionId by_xid; // 存储事务标识 (Transaction ID) 的变量 + bool by_xid_enabled; // 用于表示是否启用了按事务标识过滤的功能 + bool by_tablepath_enabled; // 用于表示是否启用了按表路径过滤的功能 + bool by_block; // 用于表示是否按照块 (block) 进行过滤 + RelFileNode by_relfilenode; // 用于存储关系文件节点 (RelFileNode) 的变量 + BlockNumber blocknum; // 用于表示在表中的特定块 } XLogFilter; - +/* + * 功能:生成输出文件名 + * + * 参数列表: + * outputFilename:用于存储生成的输出文件名的字符数组 + * start_lsn_str:起始LSN(Log Sequence Number)的字符串表示 + * send_lsn_str:结束LSN的字符串表示 + * + * 注意事项: + * - 该函数用于生成一个输出文件名,根据起始LSN和结束LSN构建文件名。 + * - 函数内部会对输入的LSN字符串进行处理以提取需要的部分,并构建文件名。 + */ static void GenerateOutputFileName(char *outputFilename, char *start_lsn_str, char *end_lsn_str) { - List *elemlist = NIL; + List *elemlist = NIL; // 初始化一个空的列表 + // 调用 SplitIdentifierString 函数分割起始LSN字符串 SplitIdentifierString(start_lsn_str, '/', &elemlist); - char *start_lsn_str_p2 = (char *)lsecond(elemlist); - list_free_ext(elemlist); + char *start_lsn_str_p2 = (char *)lsecond(elemlist); // 获取分割后的第二部分 + list_free_ext(elemlist); // 释放分割结果列表的内存 + // 再次调用 SplitIdentifierString 函数分割结束LSN字符串 SplitIdentifierString(end_lsn_str, '/', &elemlist); - char *end_lsn_str_p2 = (char *)lsecond(elemlist); + char *end_lsn_str_p2 = (char *)lsecond(elemlist); // 获取分割后的第二部分 + // 使用 snprintf_s 函数构建输出文件名 int rc = snprintf_s(outputFilename + (int)strlen(outputFilename), MAXFILENAME, MAXFILENAME - 1, "%s/%s_%s.xlog", - t_thrd.proc_cxt.DataDir, start_lsn_str_p2, end_lsn_str_p2); - securec_check_ss(rc, "\0", "\0"); + t_thrd.proc_cxt.DataDir, start_lsn_str_p2, end_lsn_str_p2); + securec_check_ss(rc, "\0", "\0"); // 检查 snprintf_s 的返回值以确保安全 } - +/* + * 功能:验证LSN(Log Sequence Number)字符串并解析为XLogRecPtr + * + * 参数列表: + * lsn_str:LSN字符串表示 + * lsn_ptr:用于存储解析后的XLogRecPtr的指针 + * + * 注意事项: + * - 该函数用于验证LSN字符串的格式,并将其解析为XLogRecPtr数据类型。 + * - LSN字符串通常以十六进制数字形式表示,如 "0/80001234"。 + * - 函数首先调用 validate_xlog_location 函数验证LSN字符串的格式。 + * - 然后使用 sscanf_s 函数解析LNS字符串并存储到 lsn_ptr 中。 + * - 如果解析失败,函数会报错。 + */ static void ValidateLSN(char *lsn_str, XLogRecPtr *lsn_ptr) { - uint32 hi = 0; - uint32 lo = 0; - validate_xlog_location(lsn_str); - + uint32 hi = 0; // 初始化高位部分为0 + uint32 lo = 0; // 初始化低位部分为0 + validate_xlog_location(lsn_str); // 调用 validate_xlog_location 函数验证LSN字符串的格式 + // 使用 sscanf_s 函数解析LSN字符串,将解析结果存储在 hi 和 lo 中 if (sscanf_s(lsn_str, "%X/%X", &hi, &lo) != TWO) ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("could not parse xlog location \"%s\"", lsn_str))); + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("could not parse xlog location \"%s\"", lsn_str))); + // 构造 XLogRecPtr,将高位部分左移 32 位后与低位部分合并,并存储到 lsn_ptr 中 *lsn_ptr = (((uint64)hi) << XIDTHIRTYTWO) | lo; } - +/* + * 功能:验证起始LSN和结束LSN字符串,并比较它们的大小关系 + * + * 参数列表: + * start_lsn_str:起始LSN(Log Sequence Number)字符串表示 + * end_lsn_str:结束LSN字符串表示 + * start_lsn:用于存储解析后的起始LSN的指针 + * end_lsn:用于存储解析后的结束LSN的指针 + * + * 注意事项: + * - 该函数用于验证起始LSN和结束LSN字符串的格式,并比较它们的大小关系。 + * - 首先调用 ValidateLSN 函数分别验证起始LSN和结束LSN。 + * - 然后比较两者的大小关系,确保起始LSN不大于结束LSN。 + * - 如果不满足条件,函数会报错。 + */ static void ValidateStartEndLSN(char *start_lsn_str, char *end_lsn_str, XLogRecPtr *start_lsn, XLogRecPtr *end_lsn) { + // 首先验证起始LSN和结束LSN ValidateLSN(start_lsn_str, start_lsn); ValidateLSN(end_lsn_str, end_lsn); + // 比较起始LSN和结束LSN的大小关系 if (XLByteLT(*end_lsn, *start_lsn)) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("start xlog location %X/%X should be smaller than or equal to end xlog location %X/%X", - (uint32)(*start_lsn >> XIDTHIRTYTWO), (uint32)(*start_lsn), (uint32)(*end_lsn >> XIDTHIRTYTWO), - (uint32)(*end_lsn)))); + errmsg("start xlog location %X/%X should be smaller than or equal to end xlog location %X/%X", + (uint32)(*start_lsn >> XIDTHIRTYTWO), (uint32)(*start_lsn), + (uint32)(*end_lsn >> XIDTHIRTYTWO), (uint32)(*end_lsn)))); } - +/* + * 功能:获取最小的LSN + * + * 注意事项: + * - 该函数用于获取最小的LSN,通常用于确定要保留的XLog日志的起始位置。 + * - 函数首先获取最后一个已删除的XLog段的段号,然后计算当前LSN。 + * - 最小的LSN通常是下一个XLog段的起始位置。 + */ static XLogRecPtr GetMinLSN() { - XLogSegNo lastRemovedSegNo = XLogGetLastRemovedSegno(); - XLogRecPtr current_recptr = (lastRemovedSegNo + 1) * XLogSegSize; - return current_recptr; + XLogSegNo lastRemovedSegNo = XLogGetLastRemovedSegno(); // 获取最后一个已删除的XLog段的段号 + XLogRecPtr current_recptr = (lastRemovedSegNo + 1) * XLogSegSize; // 计算当前LSN + return current_recptr; // 返回最小的LSN } - +/* + * 功能:获取最大的LSN(Log Sequence Number) + * + * 返回值: + * - 返回一个XLogRecPtr类型的值,表示最大的LSN + * + * 注意事项: + * - 该函数用于获取最大的LSN,通常用于确定当前系统中的最新XLog位置。 + * - 在恢复过程中不能获取最大的LSN,会导致错误。 + * - 函数首先检查是否处于恢复状态,如果是,则报错。 + * - 否则,函数调用 GetXLogWriteRecPtr 函数获取当前的LSN,并返回它。 + */ static XLogRecPtr GetMaxLSN() { - if (RecoveryInProgress()) + if (RecoveryInProgress()) // 检查是否处于恢复状态 ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("recovery is in progress"), - errhint("Can't get local max LSN during recovery."))); - XLogRecPtr current_recptr = GetXLogWriteRecPtr(); - return current_recptr; + errhint("Can't get local max LSN during recovery."))); + XLogRecPtr current_recptr = GetXLogWriteRecPtr(); // 获取当前的LSN + return current_recptr; // 返回最大的LSN } - +/* + * 功能:显示XLog记录的信息 + * + * 参数列表: + * record:XLog记录的阅读状态(XLogReaderState) + * strOutput:用于存储XLog记录信息的字符数组 + * + * 注意事项: + * - 该函数用于将XLog记录的信息格式化为字符串并存储在 strOutput 中。 + * - 函数首先使用 snprintf_s + * 函数格式化记录的起始LSN、结束LSN、事务ID、Term、总长度和描述等信息,并将其追加到strOutput。 + * - 然后初始化一个字符串缓冲区 buf,并使用相应的回调函数格式化XLog记录的描述信息,并追加到 strOutput。 + * - 如果XLog记录没有引用任何块(block),函数直接返回。 + * - 如果有块引用,函数会遍历每个块引用并追加相关信息到 strOutput。 + */ static void XLogDumpDisplayRecord(XLogReaderState *record, char *strOutput) { - errno_t rc = snprintf_s(strOutput + (int)strlen(strOutput), MAXOUTPUTLEN, MAXOUTPUTLEN - 1, - "start_lsn: %X/%X \nend_lsn: %X/%X \nxid: " XID_FMT " \nterm: %u \ntotal length: %u \ndesc: %s - ", - (uint32)(record->ReadRecPtr >> XIDTHIRTYTWO), (uint32)record->ReadRecPtr, - (uint32)(record->EndRecPtr >> XIDTHIRTYTWO), (uint32)record->EndRecPtr, XLogRecGetXid(record), - XLogRecGetTerm(record), XLogRecGetTotalLen(record), RmgrTable[XLogRecGetRmid(record)].rm_name); + errno_t rc = + snprintf_s(strOutput + (int)strlen(strOutput), MAXOUTPUTLEN, MAXOUTPUTLEN - 1, + "start_lsn: %X/%X \nend_lsn: %X/%X \nxid: " XID_FMT " \nterm: %u \ntotal length: %u \ndesc: %s - ", + (uint32)(record->ReadRecPtr >> XIDTHIRTYTWO), (uint32)record->ReadRecPtr, + (uint32)(record->EndRecPtr >> XIDTHIRTYTWO), (uint32)record->EndRecPtr, XLogRecGetXid(record), + XLogRecGetTerm(record), XLogRecGetTotalLen(record), RmgrTable[XLogRecGetRmid(record)].rm_name); securec_check_ss(rc, "\0", "\0"); StringInfoData buf; initStringInfo(&buf); - RmgrTable[XLogRecGetRmid(record)].rm_desc(&buf, record); + // 使用回调函数将XLog记录的描述信息追加到 buf 中 + RmgrTable[XLogRecGetRmid(record)].rm_desc(&buf, record); // 将 buf 中的描述信息追加到 strOutput 中 rc = strcat_s(strOutput, MAXOUTPUTLEN, buf.data); securec_check(rc, "\0", "\0"); - if (!XLogRecHasAnyBlockRefs(record)) { + if (!XLogRecHasAnyBlockRefs(record)) { // 如果XLog记录没有块引用,直接返回 rc = strcat_s(strOutput, MAXOUTPUTLEN, "\n\n"); securec_check(rc, "\0", "\0"); return; } - if (record->max_block_id >= 0) { + if (record->max_block_id >= 0) { // 如果XLog记录引用了块,添加相关信息 rc = strcat_s(strOutput, MAXOUTPUTLEN, "\nothers: "); securec_check(rc, "\0", "\0"); } @@ -135,21 +215,22 @@ static void XLogDumpDisplayRecord(XLogReaderState *record, char *strOutput) if (!XLogRecHasBlockRef(record, block_id)) continue; /* Print format: others: rel %u/%u/%u/%d fork %s blk %u */ + // 获取块引用的相关信息,并将其追加到 strOutput 中 RelFileNode rnode; ForkNumber forknum; BlockNumber blk; if (!XLogRecGetBlockTag(record, block_id, &rnode, &forknum, &blk)) continue; rc = snprintf_s(strOutput + (int)strlen(strOutput), MAXOUTPUTLEN, MAXOUTPUTLEN - 1, "\n\trel %u/%u/%u", - rnode.spcNode, rnode.dbNode, rnode.relNode); + rnode.spcNode, rnode.dbNode, rnode.relNode); securec_check_ss(rc, "\0", "\0"); if (IsBucketFileNode(rnode)) { /* check between InvalidBktId and SegmentBktId */ - rc = snprintf_s(strOutput + (int)strlen(strOutput), MAXOUTPUTLEN, MAXOUTPUTLEN - 1, - "/%d", rnode.bucketNode); + rc = + snprintf_s(strOutput + (int)strlen(strOutput), MAXOUTPUTLEN, MAXOUTPUTLEN - 1, "/%d", rnode.bucketNode); securec_check_ss(rc, "\0", "\0"); } rc = snprintf_s(strOutput + (int)strlen(strOutput), MAXOUTPUTLEN, MAXOUTPUTLEN - 1, ", fork %s", - forkNames[forknum]); + forkNames[forknum]); securec_check_ss(rc, "\0", "\0"); rc = snprintf_s(strOutput + (int)strlen(strOutput), MAXOUTPUTLEN, MAXOUTPUTLEN - 1, ", blk %u", blk); securec_check_ss(rc, "\0", "\0"); @@ -157,77 +238,153 @@ static void XLogDumpDisplayRecord(XLogReaderState *record, char *strOutput) XLogRecPtr lsn; XLogRecGetBlockLastLsn(record, block_id, &lsn); rc = snprintf_s(strOutput + (int)strlen(strOutput), MAXOUTPUTLEN, MAXOUTPUTLEN - 1, ", lastlsn %X/%X", - (uint32)(lsn >> XIDTHIRTYTWO), (uint32)lsn); + (uint32)(lsn >> XIDTHIRTYTWO), (uint32)lsn); securec_check_ss(rc, "\0", "\0"); } rc = strcat_s(strOutput, MAXOUTPUTLEN, "\n\n"); securec_check(rc, "\0", "\0"); } - +/* + * 功能:检查文件是否成功打开 + * + * 参数列表: + * outputfile:指向已打开文件的指针 + * outputFilename:文件名或路径的字符串表示 + * + * 注意事项: + * - 该函数用于检查文件是否成功打开,如果文件指针为 NULL,则表示文件未成功打开, + * 函数将生成一个错误报告并指明无法读取的文件名。 + */ void CheckOpenFile(FILE *outputfile, char *outputFilename) { - if (outputfile == NULL) + if (outputfile == NULL) // 检查文件指针是否为 NULL ereport(ERROR, (errcode(ERRCODE_FILE_READ_FAILED), (errmsg("Cannot read %s", outputFilename)))); } - +/* + * 功能:检查文件写入是否成功 + * + * 参数列表: + * result:写入操作的结果,通常是写入的字节数 + * cnt_len:期望的写入字节数 + * outputFilename:文件名或路径的字符串表示 + * + * 注意事项: + * - + * 该函数用于检查文件写入操作是否成功,如果写入字节数不等于期望的字节数,函数将生成一个错误报告并指明无法写入的文件名。 + */ void CheckWriteFile(int result, int cnt_len, char *outputFilename) { - if (result != cnt_len) + if (result != cnt_len) // 检查写入操作的结果是否等于期望的字节数 ereport(ERROR, (errcode(ERRCODE_FILE_WRITE_FAILED), (errmsg("Cannot write %s", outputFilename)))); } - +/* + * 功能:检查文件关闭是否成功 + * + * 参数列表: + * result:关闭操作的结果,通常是0表示成功,非0表示失败 + * outputFilename:文件名或路径的字符串表示 + * + * 注意事项: + * - 该函数用于检查文件关闭操作是否成功,如果关闭操作结果非0, + * 函数将生成一个错误报告并指明无法关闭的文件名。 + */ void CheckCloseFile(int result, char *outputFilename) { - if (0 != result) + if (0 != result) // 检查关闭操作的结果是否非0 ereport(ERROR, (errcode(ERRCODE_IO_ERROR), (errmsg("Cannot close %s", outputFilename)))); } - +/* + * 功能:检查XLog记录是否符合过滤条件 + * + * 参数列表: + * xlogreader_state:XLog记录的阅读状态 + * filter:XLog过滤器 + * + * 注意事项: + * - 该函数用于检查XLog记录是否符合给定的过滤条件。 + * - 函数会遍历XLog记录引用的所有块,比较块的文件节点和块号与过滤器中的条件。 + * - 如果找到匹配的块,根据过滤器中的参数判断是否符合条件。 + * - 如果符合条件,函数返回true,否则返回false。 + */ static bool CheckValidRecord(XLogReaderState *xlogreader_state, XLogFilter *filter) { - bool found = false; + bool found = false; // 初始化 found 变量为 false for (int i = 0; i <= xlogreader_state->max_block_id; i++) { RelFileNode rnode; ForkNumber forknum; BlockNumber blk; if (!XLogRecGetBlockTag(xlogreader_state, i, &rnode, &forknum, &blk)) continue; - if (RelFileNodeEquals(rnode, filter->by_relfilenode)) + if (RelFileNodeEquals(rnode, filter->by_relfilenode)) // 检查文件节点是否匹配过滤器中的条件 /* if equal to specific block or check all blocks, found = ture; */ - found = (!filter->by_block) || (blk == filter->blocknum); + found = (!filter->by_block) || (blk == filter->blocknum); // 检查块号是否匹配过滤器中的条件 if (found) { - return found; + return found; // 如果找到匹配的块,根据过滤器中的参数判断是否符合条件,如果符合条件,返回true } } - return found; + return found; // 如果没有找到匹配的块,返回false } +/* + * 功能:更新下一个LSN + * + * 参数列表: + * cur_lsn:当前LSN + * end_lsn:结束LSN + * xlogreader_state:XLog记录的阅读状态(XLogReaderState) + * found:一个指向布尔值的指针,用于指示是否找到下一个记录 + * + * 注意事项: + * - 该函数用于更新下一个LSN,以便继续寻找符合条件的XLog记录。 + * - 函数在给定的尝试次数内,从当前LSN开始查找下一个记录,确保它在指定的结束LSN之前。 + * - 如果找到符合条件的记录,将通过 found 指针返回 true。 + * - 如果在尝试次数内未找到符合条件的记录,函数返回InvalidXLogRecPtr。 + */ static XLogRecPtr UpdateNextLSN(XLogRecPtr cur_lsn, XLogRecPtr end_lsn, XLogReaderState *xlogreader_state, bool *found) { - XLogRecPtr next_record = InvalidXLogRecPtr; - for (int tryTimes = 0; tryTimes < FIVE; tryTimes++) { - XLogRecPtr start_lsn = Max(cur_lsn, (g_instance.comm_cxt.predo_cxt.redoPf.oldest_segment) * XLogSegSize); - next_record = XLogFindNextRecord(xlogreader_state, start_lsn); - if (!XLByteEQ(next_record, InvalidXLogRecPtr) && XLByteLT(next_record, end_lsn)) { - *found = true; - return next_record; + XLogRecPtr next_record = InvalidXLogRecPtr; // 初始化下一个LSN为InvalidXLogRecPtr + + for (int tryTimes = 0; tryTimes < FIVE; tryTimes++) { // 进行最多5次尝试 + XLogRecPtr start_lsn = + Max(cur_lsn, (g_instance.comm_cxt.predo_cxt.redoPf.oldest_segment) * XLogSegSize); // 计算起始LSN + next_record = XLogFindNextRecord(xlogreader_state, start_lsn); // 寻找下一个记录 + if (!XLByteEQ(next_record, InvalidXLogRecPtr) && XLByteLT(next_record, end_lsn)) { // 如果找到符合条件的记录 + *found = true; // 设置 found 为 true + return next_record; // 返回下一个LSN } } - return next_record; + return next_record; // 如果在尝试次数内未找到符合条件的记录,返回InvalidXLogRecPtr } - +/* + * 功能:将XLog记录转储到文件 + * + * 参数列表: + * start_lsn:要开始转储的XLog记录的LSN + * end_lsn:要结束转储的XLog记录的LSN + * filter:XLog过滤器,用于筛选记录 + * outputFilename:要写入的输出文件名 + * + * 注意事项: + * - 该函数用于将XLog记录转储到指定的输出文件。 + * - 函数首先分配XLog阅读器状态,然后查找第一个符合条件的XLog记录。 + * - 如果未找到第一个记录,函数会尝试多次查找,直到找到合适的记录。 + * - 接着,函数逐个读取符合条件的XLog记录,并将其写入输出文件。 + * - 最后,函数生成总结信息,包括读取的记录数量和有效的起始和结束LSN。 + */ static void XLogDump(XLogRecPtr start_lsn, XLogRecPtr end_lsn, XLogFilter *filter, char *outputFilename) { /* start reading */ errno_t rc = EOK; XLogPrivate readprivate; - rc = memset_s(&readprivate, sizeof(XLogPrivate), 0, sizeof(XLogPrivate)); + rc = memset_s(&readprivate, sizeof(XLogPrivate), 0, sizeof(XLogPrivate)); // 初始化读取私有数据 securec_check_c(rc, "\0", "\0"); - readprivate.datadir = t_thrd.proc_cxt.DataDir; - readprivate.tli = 1; + readprivate.datadir = t_thrd.proc_cxt.DataDir; // 设置数据目录路径 + readprivate.tli = 1; // 设置时间线ID + // 分配XLog阅读器状态 XLogReaderState *xlogreader_state = XLogReaderAllocate(&SimpleXLogPageRead, &readprivate); if (!xlogreader_state) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_RESOURCES), - (errmsg("memory is temporarily unavailable while allocate xlog reader")))); + (errmsg("memory is temporarily unavailable while allocate xlog reader")))); /* get the first valid xlog record location */ XLogRecPtr first_record = XLogFindNextRecord(xlogreader_state, start_lsn); @@ -235,32 +392,32 @@ static void XLogDump(XLogRecPtr start_lsn, XLogRecPtr end_lsn, XLogFilter *filte * Hence, we need to update the min_lsn */ if (XLByteEQ(first_record, InvalidXLogRecPtr)) { ereport(WARNING, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - (errmsg("XLogFindNextRecord: could not find a valid record after %X/%X. Retry.", - (uint32)(start_lsn >> XIDTHIRTYTWO), (uint32)start_lsn)))); + (errmsg("XLogFindNextRecord: could not find a valid record after %X/%X. Retry.", + (uint32)(start_lsn >> XIDTHIRTYTWO), (uint32)start_lsn)))); bool found = false; + // 尝试查找下一个记录 first_record = UpdateNextLSN(start_lsn, end_lsn, xlogreader_state, &found); if (!found) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - (errmsg("XLogFindNextRecord: could not find a valid record between %X/%X and %X/%X.", - (uint32)(start_lsn >> XIDTHIRTYTWO), (uint32)start_lsn, - (uint32)(end_lsn >> XIDTHIRTYTWO), (uint32)end_lsn)))); + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + (errmsg("XLogFindNextRecord: could not find a valid record between %X/%X and %X/%X.", + (uint32)(start_lsn >> XIDTHIRTYTWO), (uint32)start_lsn, + (uint32)(end_lsn >> XIDTHIRTYTWO), (uint32)end_lsn)))); } XLogRecPtr valid_start_lsn = first_record; XLogRecPtr valid_end_lsn = valid_start_lsn; - FILE *outputfile = fopen(outputFilename, "w"); - CheckOpenFile(outputfile, outputFilename); + FILE *outputfile = fopen(outputFilename, "w"); // 打开输出文件 + CheckOpenFile(outputfile, outputFilename); // 检查文件是否成功打开 char *strOutput = (char *)palloc(MAXOUTPUTLEN * sizeof(char)); rc = memset_s(strOutput, MAXOUTPUTLEN, 0, MAXOUTPUTLEN); securec_check(rc, "\0", "\0"); /* valid first record is not the given one */ if (!XLByteEQ(first_record, start_lsn) && (start_lsn % XLogSegSize) != 0) { rc = snprintf_s(strOutput + (int)strlen(strOutput), MAXOUTPUTLEN, MAXOUTPUTLEN - 1, - "first record is after %X/%X, at %X/%X, skipping over %lu bytes\n", (uint32)(start_lsn >> XIDTHIRTYTWO), - (uint32)start_lsn, (uint32)(first_record >> XIDTHIRTYTWO), (uint32)first_record, - XLByteDifference(first_record, start_lsn)); + "first record is after %X/%X, at %X/%X, skipping over %lu bytes\n", + (uint32)(start_lsn >> XIDTHIRTYTWO), (uint32)start_lsn, (uint32)(first_record >> XIDTHIRTYTWO), + (uint32)first_record, XLByteDifference(first_record, start_lsn)); securec_check_ss(rc, "\0", "\0"); } CheckWriteFile(fwrite(strOutput, 1, strlen(strOutput), outputfile), strlen(strOutput), outputFilename); @@ -279,27 +436,23 @@ static void XLogDump(XLogRecPtr start_lsn, XLogRecPtr end_lsn, XLogFilter *filte XLogRecPtr temp_start_lsn = Max(xlogreader_state->EndRecPtr, start_lsn); first_record = UpdateNextLSN(temp_start_lsn, end_lsn, xlogreader_state, &found); if (found) { - ereport(WARNING, (errcode(ERRCODE_WARNING), - (errmsg("We cannot read %X/%X. After retried, we jump to read the next available %X/%X. " \ - "The missing part might be recycled or removed.", - (uint32)(temp_start_lsn >> XIDTHIRTYTWO), (uint32)temp_start_lsn, - (uint32)(first_record >> XIDTHIRTYTWO), (uint32)first_record)))); + ereport(WARNING, + (errcode(ERRCODE_WARNING), + (errmsg("We cannot read %X/%X. After retried, we jump to read the next available %X/%X. " + "The missing part might be recycled or removed.", + (uint32)(temp_start_lsn >> XIDTHIRTYTWO), (uint32)temp_start_lsn, + (uint32)(first_record >> XIDTHIRTYTWO), (uint32)first_record)))); continue; } if (errormsg != NULL) - ereport(LOG, - (errcode(ERRCODE_LOG), - (errmsg("could not read WAL record at %X/%X: %s", - (uint32)(xlogreader_state->ReadRecPtr >> XIDTHIRTYTWO), - (uint32)xlogreader_state->ReadRecPtr, - errormsg)))); + ereport(LOG, (errcode(ERRCODE_LOG), (errmsg("could not read WAL record at %X/%X: %s", + (uint32)(xlogreader_state->ReadRecPtr >> XIDTHIRTYTWO), + (uint32)xlogreader_state->ReadRecPtr, errormsg)))); else - ereport(LOG, - (errcode(ERRCODE_LOG), - (errmsg("could not read WAL record at %X/%X", - (uint32)(xlogreader_state->ReadRecPtr >> XIDTHIRTYTWO), - (uint32)xlogreader_state->ReadRecPtr)))); + ereport(LOG, (errcode(ERRCODE_LOG), (errmsg("could not read WAL record at %X/%X", + (uint32)(xlogreader_state->ReadRecPtr >> XIDTHIRTYTWO), + (uint32)xlogreader_state->ReadRecPtr)))); break; } first_record = InvalidXLogRecPtr; /* No explicit start point; read the record after the one we just read */ @@ -333,9 +486,9 @@ static void XLogDump(XLogRecPtr start_lsn, XLogRecPtr end_lsn, XLogFilter *filte /* Summary(xx total): valid start_lsn: xxx, valid end_lsn: xxx */ rc = snprintf_s(strOutput + (int)strlen(strOutput), MAXOUTPUTLEN, MAXOUTPUTLEN - 1, - "\nSummary (%d total): valid start_lsn: %X/%X, valid end_lsn: %X/%X\n", count, - (uint32)(valid_start_lsn >> XIDTHIRTYTWO), (uint32)(valid_start_lsn), (uint32)(valid_end_lsn >> XIDTHIRTYTWO), - (uint32)(valid_end_lsn)); + "\nSummary (%d total): valid start_lsn: %X/%X, valid end_lsn: %X/%X\n", count, + (uint32)(valid_start_lsn >> XIDTHIRTYTWO), (uint32)(valid_start_lsn), + (uint32)(valid_end_lsn >> XIDTHIRTYTWO), (uint32)(valid_end_lsn)); securec_check_ss(rc, "\0", "\0"); /* generate output file */ CheckWriteFile(fwrite(strOutput, 1, strlen(strOutput), outputfile), strlen(strOutput), outputFilename); @@ -345,29 +498,45 @@ static void XLogDump(XLogRecPtr start_lsn, XLogRecPtr end_lsn, XLogFilter *filte } /* There are only two parameters in PG_FUNCTION_ARGS: start_lsn and end_lsn */ +/* + * 功能:将指定LSN范围内的XLog记录转储到文件中 + * + * 参数列表: + * start_lsn_str:指定范围的起始LSN的字符串表示 + * end_lsn_str:指定范围的结束LSN的字符串表示 + * + * 返回值: + * - 返回一个文本字符串,表示生成的输出文件名 + * + * 注意事项: + * - 该函数用于将指定LSN范围内的XLog记录转储到文件中,以便分析和检查。 + * - 函数会验证LSN范围,生成输出文件名,并基于当前的最小LSN和最大LSN来更新范围。 + * - 输出文件名将包含数据目录路径和LSN范围。 + * - 通常由用户调用,以便根据LSN范围来查看XLog记录。 + */ Datum gs_xlogdump_lsn(PG_FUNCTION_ARGS) { errno_t rc = EOK; /* check user's right */ const char fName[MAXFNAMELEN] = "gs_xlogdump_lsn"; - CheckUser(fName); + CheckUser(fName); // 检查用户权限 /* read in parameters */ - char *start_lsn_str = text_to_cstring(PG_GETARG_TEXT_P(0)); - char *end_lsn_str = text_to_cstring(PG_GETARG_TEXT_P(1)); + char *start_lsn_str = text_to_cstring(PG_GETARG_TEXT_P(0)); // 读取起始LSN字符串 + char *end_lsn_str = text_to_cstring(PG_GETARG_TEXT_P(1)); // 读取结束LSN字符串 /* validate lsn */ XLogRecPtr start_lsn, end_lsn; - ValidateStartEndLSN(start_lsn_str, end_lsn_str, &start_lsn, &end_lsn); + ValidateStartEndLSN(start_lsn_str, end_lsn_str, &start_lsn, &end_lsn); // 验证起始和结束LSN - char *outputFilename = (char *)palloc(MAXFILENAME * sizeof(char)); - rc = memset_s(outputFilename, MAXFILENAME, 0, MAXFILENAME); + char *outputFilename = (char *)palloc(MAXFILENAME * sizeof(char)); // 分配用于存储输出文件名的内存 + rc = memset_s(outputFilename, MAXFILENAME, 0, MAXFILENAME); // 初始化输出文件名 securec_check(rc, "\0", "\0"); - GenerateOutputFileName(outputFilename, start_lsn_str, end_lsn_str); + GenerateOutputFileName(outputFilename, start_lsn_str, end_lsn_str); // 生成输出文件名 /* update start_lsn and end_lsn based on cur min_lsn and max_lsn */ - XLogRecPtr min_lsn = GetMinLSN(); - XLogRecPtr max_lsn = GetMaxLSN(); + XLogRecPtr min_lsn = GetMinLSN(); // 获取最小LSN + XLogRecPtr max_lsn = GetMaxLSN(); // 获取最大LSN if (XLByteLT(start_lsn, min_lsn)) { start_lsn = min_lsn; @@ -377,13 +546,28 @@ Datum gs_xlogdump_lsn(PG_FUNCTION_ARGS) } XLogFilter filter; - rc = memset_s(&filter, sizeof(XLogFilter), 0, sizeof(XLogFilter)); + rc = memset_s(&filter, sizeof(XLogFilter), 0, sizeof(XLogFilter)); // 初始化XLog过滤器 securec_check_ss(rc, "\0", "\0"); - XLogDump(start_lsn, end_lsn, &filter, outputFilename); - PG_RETURN_TEXT_P(cstring_to_text(outputFilename)); + XLogDump(start_lsn, end_lsn, &filter, outputFilename); // 执行XLog记录转储操作 + PG_RETURN_TEXT_P(cstring_to_text(outputFilename)); // 将输出文件名转换为文本并返回 } /* There are only one parameter in PG_FUNCTION_ARGS: c_xid */ +/* + * 功能:将指定事务ID(XID)范围内的XLog记录转储到文件中 + * + * 参数列表: + * c_xid:要转储的起始事务ID + * + * 返回值: + * - 返回一个文本字符串,表示生成的输出文件名 + * + * 注意事项: + * - 该函数用于将指定事务ID范围内的XLog记录转储到文件中,以便分析和检查。 + * - 函数会验证事务ID范围,生成输出文件名,并基于当前的最小LSN和最大LSN来更新范围。 + * - 输出文件名将包含数据目录路径和事务ID。 + * - 通常由用户调用,以便根据事务ID范围来查看XLog记录。 + */ Datum gs_xlogdump_xid(PG_FUNCTION_ARGS) { errno_t rc = EOK; @@ -396,15 +580,15 @@ Datum gs_xlogdump_xid(PG_FUNCTION_ARGS) /* check parameters */ TransactionId topXid = GetTopTransactionId(); if (TransactionIdPrecedes(topXid, c_xid)) - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - (errmsg("Xid should less than or equal to %lu.", topXid)))); + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), (errmsg("Xid should less than or equal to %lu.", topXid)))); /* generate output file name */ char *outputFilename = (char *)palloc(MAXFILENAME * sizeof(char)); rc = memset_s(outputFilename, MAXFILENAME, 0, MAXFILENAME); securec_check(rc, "\0", "\0"); rc = snprintf_s(outputFilename + (int)strlen(outputFilename), MAXFILENAME, MAXFILENAME - 1, "%s/%lu.xlog", - t_thrd.proc_cxt.DataDir, c_xid); + t_thrd.proc_cxt.DataDir, c_xid); securec_check_ss(rc, "\0", "\0"); /* update start_lsn and end_lsn based on cur min_lsn and max_lsn */ XLogRecPtr min_lsn = GetMinLSN(); @@ -433,7 +617,7 @@ Datum gs_xlogdump_tablepath(PG_FUNCTION_ARGS) /* check parameters */ if (blocknum > MaxBlockNumber || blocknum < -1) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - (errmsg("Blocknum should be between -1 and %u.", MaxBlockNumber)))); + (errmsg("Blocknum should be between -1 and %u.", MaxBlockNumber)))); /* update start_lsn and end_lsn based on cur min_lsn and max_lsn */ XLogRecPtr min_lsn = GetMinLSN(); @@ -516,7 +700,8 @@ Datum gs_xlogdump_parsepage_tablepath(PG_FUNCTION_ARGS) securec_check(rc, "\0", "\0"); rc = snprintf_s(result + (int)strlen(result), outputLen, outputLen - 1, - "Output file for parsing xlog: %s\nOutput file for parsing data page: %s", outputFilename, outputFilenamePage); + "Output file for parsing xlog: %s\nOutput file for parsing data page: %s", outputFilename, + outputFilenamePage); securec_check_ss(rc, "\0", "\0"); pfree_ext(outputFilename); PG_RETURN_TEXT_P(cstring_to_text(result)); diff --git a/src/gausskernel/storage/page/pagecompress.cpp b/src/gausskernel/storage/page/pagecompress.cpp index f1d2112e0..b6232c491 100644 --- a/src/gausskernel/storage/page/pagecompress.cpp +++ b/src/gausskernel/storage/page/pagecompress.cpp @@ -50,32 +50,26 @@ static Datum int32Plus(Datum arg1, int64 arg2); static Datum uint32Plus(Datum arg1, int64 arg2); static Datum int64Plus(Datum arg1, int64 arg2); -static const NumberTypeOpt* binarySearch(Oid typeOid); +static const NumberTypeOpt *binarySearch(Oid typeOid); /* const array for number type. */ static const NumberTypeOpt g_number_types[] = { - { 20, int64Compare, int64Minus, int64Plus }, /* int8 */ - { 21, int16Compare, int16Minus, int16Plus }, /* int2 */ - { 23, int32Compare, int32Minus, int32Plus }, /* int4 */ - { 26, uint32Compare, uint32Minus, uint32Plus }, /* oid */ - { 1082, int32Compare, int32Minus, int32Plus }, /* date */ - { 1083, int64Compare, int64Minus, int64Plus }, /* time */ - { 1114, int64Compare, int64Minus, int64Plus }, /* timestamp */ - { 1184, int64Compare, int64Minus, int64Plus }, /* timestamptz */ - { 12089, int64Compare, int64Minus, int64Plus }, /* time_stamp */ + {20, int64Compare, int64Minus, int64Plus}, /* int8 */ + {21, int16Compare, int16Minus, int16Plus}, /* int2 */ + {23, int32Compare, int32Minus, int32Plus}, /* int4 */ + {26, uint32Compare, uint32Minus, uint32Plus}, /* oid */ + {1082, int32Compare, int32Minus, int32Plus}, /* date */ + {1083, int64Compare, int64Minus, int64Plus}, /* time */ + {1114, int64Compare, int64Minus, int64Plus}, /* timestamp */ + {1184, int64Compare, int64Minus, int64Plus}, /* timestamptz */ + {12089, int64Compare, int64Minus, int64Plus}, /* time_stamp */ }; static const int g_number_type_count = sizeof(g_number_types) / sizeof(NumberTypeOpt); /* Map about max values in byte for delta compression */ -static const int64 g_max_value_in_bytes[] = {0x0000000000000000, - 0x00000000000000FF, - 0x000000000000FFFF, - 0x0000000000FFFFFF, - 0x00000000FFFFFFFF, - 0x000000FFFFFFFFFF, - 0x0000FFFFFFFFFFFF, - 0x00FFFFFFFFFFFFFF - }; +static const int64 g_max_value_in_bytes[] = {0x0000000000000000, 0x00000000000000FF, 0x000000000000FFFF, + 0x0000000000FFFFFF, 0x00000000FFFFFFFF, 0x000000FFFFFFFFFF, + 0x0000FFFFFFFFFFFF, 0x00FFFFFFFFFFFFFF}; static const int64 g_number_max_value_in_bytes = (int64)(sizeof(g_max_value_in_bytes) / sizeof(int64)); #define getTupleSpace(_tuple) (sizeof(ItemIdData) + MAXALIGN((_tuple)->t_len)) @@ -123,10 +117,10 @@ static uint32 getBitsOfDelta(uint64 delta) #define DICT_ITEMID_SIZE 1 #define DICT_MISS_THRESHOLD 2 -static int matchDictItem(const void* left, const void* right, Size keysize) +static int matchDictItem(const void *left, const void *right, Size keysize) { - const DictItemData* leftItem = (DictItemData*)left; - const DictItemData* rightItem = (DictItemData*)right; + const DictItemData *leftItem = (DictItemData *)left; + const DictItemData *rightItem = (DictItemData *)right; Assert(leftItem != NULL && rightItem != NULL); /* we just care whether the result is 0 or not. */ @@ -137,14 +131,14 @@ static int matchDictItem(const void* left, const void* right, Size keysize) return memcmp(leftItem->itemData, rightItem->itemData, leftItem->itemSize); } -static uint32 hashDictItem(const void* key, Size keysize) +static uint32 hashDictItem(const void *key, Size keysize) { - const DictItemData* item = (const DictItemData*)key; - return DatumGetUInt32(hash_any((const unsigned char*)item->itemData, item->itemSize)); + const DictItemData *item = (const DictItemData *)key; + return DatumGetUInt32(hash_any((const unsigned char *)item->itemData, item->itemSize)); } /* create hash table for building dictionary */ -static HTAB* hashCreate(const char* name, MemoryContext memCxt, int nelem, int keysize, int entrysize) +static HTAB *hashCreate(const char *name, MemoryContext memCxt, int nelem, int keysize, int entrysize) { HASHCTL hashCtrl; errno_t rc = memset_s(&hashCtrl, sizeof(hashCtrl), 0, sizeof(hashCtrl)); @@ -159,29 +153,63 @@ static HTAB* hashCreate(const char* name, MemoryContext memCxt, int nelem, int k return hash_create(name, nelem, &hashCtrl, flags); } - +/* + * 功能:构建字典项哈希表 + * + * 参数列表: + * memCxt:内存上下文,用于分配哈希表的内存 + * + * 注意事项: + * - 该函数用于在给定的内存上下文中构建一个哈希表,用于存储字典项数据。 + * - 字典项哈希表用于快速查找和访问字典项数据。 + * - 字典项的数量必须不超过最大限制(MAX_DICITEMID)。 + * - 如果字典项在哈希表中已存在,则会更新其ID。 + */ void DictCmprMeta::BuildHashTbl(MemoryContext memCxt) { + // 创建一个哈希表并分配内存上下文 m_hash = hashCreate("DictInfo Hash", memCxt, dictItemNum + 16, sizeof(DictItemData), sizeof(ItemEntry)); + // 确保字典项数量不超过最大限制 Assert(dictItemNum <= MAX_DICITEMID); + // 遍历字典项数组,将每个字典项插入哈希表 for (int i = 0; i < dictItemNum; ++i) { - ItemEntry* dicItemEntry = (ItemEntry*)hash_search(m_hash, (const void*)(dictItems + i), HASH_ENTER, NULL); - Assert(dicItemEntry); - dicItemEntry->id = (unsigned char)i; + // 尝试在哈希表中查找字典项,如果不存在则创建并插入 + ItemEntry *dicItemEntry = (ItemEntry *)hash_search(m_hash, (const void *)(dictItems + i), HASH_ENTER, NULL); + Assert(dicItemEntry); // 确保成功插入字典项 + dicItemEntry->id = (unsigned char)i; // 设置字典项的ID } } - -unsigned char DictCmprMeta::GetDictItemId(DictItemData* key, bool& found) +/* + * 功能:根据字典项查找其ID + * + * 参数列表: + * key:要查找的字典项 + * found:用于返回查找结果的布尔值,如果找到则为true,否则为false + * + * 注意事项: + * - 该函数用于根据给定的字典项查找其在哈希表中的ID。 + * - 参数 found 会指示是否找到了字典项。 + */ +unsigned char DictCmprMeta::GetDictItemId(DictItemData *key, bool &found) { - ItemEntry* dicItemEntry = (ItemEntry*)hash_search(m_hash, (const void*)key, HASH_FIND, &found); + // 在哈希表中查找字典项 + ItemEntry *dicItemEntry = (ItemEntry *)hash_search(m_hash, (const void *)key, HASH_FIND, &found); + // 如果找到字典项,则返回其ID;否则返回0 if (found) return dicItemEntry->id; return 0; } - +/* + * 功能:销毁字典项哈希表 + * + * 注意事项: + * - 该函数用于销毁字典项哈希表,释放相关的内存资源。 + * - 在不再需要字典项哈希表时,应调用此函数以避免内存泄漏。 + */ void DictCmprMeta::Destroy() { + // 如果哈希表存在,则销毁它并将指针设为NULL if (m_hash) { hash_destroy(m_hash); m_hash = NULL; @@ -189,30 +217,55 @@ void DictCmprMeta::Destroy() } /* write all the dict metadata into given buffer */ -void DictCmprMeta::GetDictItems(char* buf) const +/* + * 功能:获取字典项数据 + * + * 参数列表: + * - buf:用于存储字典项数据的缓冲区 + * + * 注意事项: + * - 该函数用于将字典项数据复制到指定的缓冲区中。 + * - 缓冲区应具有足够的空间来容纳所有字典项数据。 + */ +void DictCmprMeta::GetDictItems(char *buf) const { int offset = 0; errno_t rc = EOK; + // 遍历字典项数组,并将每个字典项的数据复制到缓冲区中 for (int i = 0; i < dictItemNum; ++i) { rc = memcpy_s(buf + offset, (size_t)(BLCKSZ - offset), dictItems[i].itemData, (size_t)(dictItems[i].itemSize)); securec_check(rc, "", ""); offset += dictItems[i].itemSize; } } - +/* + * 功能:将数字字符串进行压缩 + * + * 参数列表: + * src:要压缩的源数据 + * outSize:输出的压缩数据大小 + * + * 注意事项: + * - 该函数用于将数字字符串进行压缩,以减小存储空间的需求。 + * - 压缩规则是将两个数字字符合并成一个字节,并使用高四位表示第一个数字,低四位表示第二个数字。 + * - 如果输入的字符串长度为奇数,将使用高四位的最高位表示最后一个数字为原始数据。 + * - 如果输入的字符串不是数字字符串,函数将返回0作为错误标志。 + */ template -Datum PageCompress::NumstrCompress(Datum src, int& outSize) +Datum PageCompress::NumstrCompress(Datum src, int &outSize) { uint32 evenLen; int destLen = 0; uint32 srcLen = 0; - char* srcPtr = NULL; - unsigned char* outBuf = NULL; - unsigned char* destPtr = NULL; + char *srcPtr = NULL; + unsigned char *outBuf = NULL; + unsigned char *destPtr = NULL; + // 断言检查 attlen 只能为 -1 或 -2 Assert(attlen == -1 || attlen == -2); + // 根据 attlen 的值获取源数据的指针和长度 if (attlen == -1) { - char* p = DatumGetPointer(src); + char *p = DatumGetPointer(src); Assert(p != NULL); srcPtr = VARDATA_ANY(p); srcLen = VARSIZE_ANY_EXHDR(p); @@ -233,7 +286,7 @@ Datum PageCompress::NumstrCompress(Datum src, int& outSize) * If the srcLen is odd, we need set the highest 4 bits for 1 for last byte * in order to distinguish the last byte is one original byte */ - unsigned char* srcData = (unsigned char*)srcPtr; + unsigned char *srcData = (unsigned char *)srcPtr; if (srcLen % 2 != 0) { if (!isDigital(srcData[srcLen - 1])) { outSize = 0; @@ -249,7 +302,7 @@ Datum PageCompress::NumstrCompress(Datum src, int& outSize) Assert(destLen >= 0); if (attlen == -1) { - outBuf = (unsigned char*)palloc((Size)(destLen + VARHDRSZ)); + outBuf = (unsigned char *)palloc((Size)(destLen + VARHDRSZ)); if (destLen + VARHDRSZ_SHORT <= VARATT_SHORT_MAX) { outSize = (int)(destLen + VARHDRSZ_SHORT); SET_VARSIZE_SHORT(outBuf, outSize); @@ -262,7 +315,7 @@ Datum PageCompress::NumstrCompress(Datum src, int& outSize) } else { /* Last byte has been set '\0' */ outSize = destLen + 1; - destPtr = outBuf = (unsigned char*)palloc0((Size)outSize); + destPtr = outBuf = (unsigned char *)palloc0((Size)outSize); } /* Two bytes combine one byte */ @@ -291,16 +344,33 @@ Datum PageCompress::NumstrCompress(Datum src, int& outSize) return PointerGetDatum(outBuf); } - -Datum PageCompress::NumstrUncompress( - Datum src, int attlen, int& cmprValSize, char* uncmprValBuf, int* dataLenWithPadding, char attalign) +/* + * 功能:解压缩数字字符串 + * + * 参数列表: + * src:要解压缩的源数据 + * attlen:源数据类型的长度,取值为 -1 或 -2 + * cmprValSize:输出的解压缩数据大小 + * uncmprValBuf:解压缩后的数据缓冲区 + * dataLenWithPadding:包括填充字节的数据长度 + * attalign:数据对齐方式 + * + * 注意事项: + * - 该函数用于解压缩压缩过的数字字符串。 + * - 压缩规则是将两个数字字符合并成一个字节,并使用高四位表示第一个数字,低四位表示第二个数字。 + * - 如果输入的字符串长度为奇数,压缩数据的最后一个字节的高四位用来表示最后一个数字为原始数据。 + * - attlen 参数指定了输出数据的类型长度,-1 表示变长类型,-2 表示定长类型。 + * - 如果输入的字符串不是数字字符串,函数将返回0作为错误标志。 + */ +Datum PageCompress::NumstrUncompress(Datum src, int attlen, int &cmprValSize, char *uncmprValBuf, + int *dataLenWithPadding, char attalign) { Assert(attlen == -1 || attlen == -2); uint32 srcLen = 0; - char* srcPtr = NULL; + char *srcPtr = NULL; if (attlen == -1) { - char* p = DatumGetPointer(src); + char *p = DatumGetPointer(src); Assert(p != NULL); srcPtr = VARDATA_ANY(p); srcLen = VARSIZE_ANY_EXHDR(p); @@ -312,9 +382,9 @@ Datum PageCompress::NumstrUncompress( } Assert(srcPtr != NULL && srcLen > 0 && cmprValSize > 0); - unsigned char* outBuf = NULL; - unsigned char* destPtr = NULL; - unsigned char* srcData = (unsigned char*)srcPtr; + unsigned char *outBuf = NULL; + unsigned char *destPtr = NULL; + unsigned char *srcData = (unsigned char *)srcPtr; int destLen; int padding_size = 0; int datum_size = 0; @@ -334,14 +404,14 @@ Datum PageCompress::NumstrUncompress( * NB: nothing to do if the datum header is 1B; */ if (uncmprValBuf) { - outBuf = (unsigned char*)uncmprValBuf; + outBuf = (unsigned char *)uncmprValBuf; Assert(dataLenWithPadding != NULL); *outBuf = 0; - outBuf = (unsigned char*)att_align_nominal(outBuf, attalign); - padding_size = int(outBuf - (unsigned char*)uncmprValBuf); + outBuf = (unsigned char *)att_align_nominal(outBuf, attalign); + padding_size = int(outBuf - (unsigned char *)uncmprValBuf); } else { - outBuf = (unsigned char*)palloc((Size)(destLen + VARHDRSZ)); + outBuf = (unsigned char *)palloc((Size)(destLen + VARHDRSZ)); } datum_size = destLen + VARHDRSZ; @@ -351,9 +421,9 @@ Datum PageCompress::NumstrUncompress( /* Last byte has been set '\0' */ datum_size = destLen + 1; if (uncmprValBuf) - destPtr = outBuf = (unsigned char*)uncmprValBuf; + destPtr = outBuf = (unsigned char *)uncmprValBuf; else - destPtr = outBuf = (unsigned char*)palloc((Size)datum_size); + destPtr = outBuf = (unsigned char *)palloc((Size)datum_size); errno_t rc = memset_s(destPtr, (size_t)datum_size, 0, (size_t)datum_size); securec_check(rc, "", ""); @@ -406,22 +476,22 @@ PageCompress::PageCompress(Relation rel, MemoryContext memCtx) MemoryContext oldMemCnxt = MemoryContextSwitchTo(memCtx); m_parentMemCxt = memCtx; - m_cmprHeaderData = (char*)palloc(BLCKSZ); - m_pageMemCnxt = AllocSetContextCreate( - memCtx, "PageCompression", ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); + m_cmprHeaderData = (char *)palloc(BLCKSZ); + m_pageMemCnxt = AllocSetContextCreate(memCtx, "PageCompression", ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); int nattrs = RelationGetNumberOfAttributes(m_rel); - m_cmprMode = (char*)palloc0((Size)(nattrs * sizeof(char))); - m_cmprMeta = (void**)palloc0((Size)(nattrs * sizeof(void*))); - m_numTypeOpt = (const NumberTypeOpt**)palloc((Size)(nattrs * sizeof(NumberTypeOpt*))); - m_dictMiss = (uint32*)palloc0((Size)(nattrs * sizeof(uint32))); - m_dictItemId = (unsigned char*)palloc((Size)(nattrs * sizeof(unsigned char))); - m_deformVals = (Datum*)palloc((Size)(nattrs * sizeof(Datum))); - m_deformNulls = (bool*)palloc((Size)(nattrs * sizeof(bool))); + m_cmprMode = (char *)palloc0((Size)(nattrs * sizeof(char))); + m_cmprMeta = (void **)palloc0((Size)(nattrs * sizeof(void *))); + m_numTypeOpt = (const NumberTypeOpt **)palloc((Size)(nattrs * sizeof(NumberTypeOpt *))); + m_dictMiss = (uint32 *)palloc0((Size)(nattrs * sizeof(uint32))); + m_dictItemId = (unsigned char *)palloc((Size)(nattrs * sizeof(unsigned char))); + m_deformVals = (Datum *)palloc((Size)(nattrs * sizeof(Datum))); + m_deformNulls = (bool *)palloc((Size)(nattrs * sizeof(bool))); (void)MemoryContextSwitchTo(oldMemCnxt); - Form_pg_attribute* attrs = m_rel->rd_att->attrs; + Form_pg_attribute *attrs = m_rel->rd_att->attrs; for (int col = 0; col < nattrs; ++col) { m_numTypeOpt[col] = binarySearch(attrs[col]->atttypid); } @@ -453,16 +523,16 @@ PageCompress::~PageCompress() m_pageMemCnxt = NULL; } -ColArray* PageCompress::DeformTups(HeapTuple* tups, int nTups) +ColArray *PageCompress::DeformTups(HeapTuple *tups, int nTups) { int attrNum = RelationGetNumberOfAttributes(m_rel); TupleDesc desc = RelationGetDescr(m_rel); - ColArray* colArray = (ColArray*)palloc((Size)(sizeof(ColArray) * attrNum)); + ColArray *colArray = (ColArray *)palloc((Size)(sizeof(ColArray) * attrNum)); for (int i = 0; i < attrNum; ++i) { - colArray[i].nulls = (bool*)palloc0((Size)(sizeof(bool) * nTups)); - colArray[i].val = (Datum*)palloc0((Size)(sizeof(Datum) * nTups)); + colArray[i].nulls = (bool *)palloc0((Size)(sizeof(bool) * nTups)); + colArray[i].val = (Datum *)palloc0((Size)(sizeof(Datum) * nTups)); colArray[i].num = nTups; } @@ -491,11 +561,11 @@ ColArray* PageCompress::DeformTups(HeapTuple* tups, int nTups) * Delta Compression/Decompression Methods * --------------------------------------------------------------- */ -DeltaCmprMeta* PageCompress::DeltaGetCmprMeta(ColArray* colArray, const NumberTypeOpt* opt) const +DeltaCmprMeta *PageCompress::DeltaGetCmprMeta(ColArray *colArray, const NumberTypeOpt *opt) const { Datum minVal, maxVal; - Datum* values = colArray->val; - bool* isnulls = colArray->nulls; + Datum *values = colArray->val; + bool *isnulls = colArray->nulls; /* init min/max value. */ int i = 0; @@ -523,7 +593,7 @@ DeltaCmprMeta* PageCompress::DeltaGetCmprMeta(ColArray* colArray, const NumberTy return NULL; } - DeltaCmprMeta* deltaInfo = (DeltaCmprMeta*)palloc(sizeof(DeltaCmprMeta)); + DeltaCmprMeta *deltaInfo = (DeltaCmprMeta *)palloc(sizeof(DeltaCmprMeta)); deltaInfo->MinVal = minVal; deltaInfo->MaxVal = maxVal; deltaInfo->bytes = bytes; @@ -538,8 +608,8 @@ DeltaCmprMeta* PageCompress::DeltaGetCmprMeta(ColArray* colArray, const NumberTy * metaInfo, opt and val are the input parameters; and deltaBuf is the output * returning false means that delta compress fails. */ -bool PageCompress::DeltaCompress( - DeltaCmprMeta* metaInfo, const NumberTypeOpt* opt, Datum val, unsigned char* deltaBuf) const +bool PageCompress::DeltaCompress(DeltaCmprMeta *metaInfo, const NumberTypeOpt *opt, Datum val, + unsigned char *deltaBuf) const { int64 diff = opt->minus(val, metaInfo->MinVal); @@ -572,10 +642,10 @@ bool PageCompress::DeltaCompress( /* [IN]: metaInfo && opt && deltaBuf[] * delta decompress. */ -Datum PageCompress::DeltaUncompress(DeltaCmprMeta* metaInfo, const NumberTypeOpt* opt, unsigned char* deltaBuf) +Datum PageCompress::DeltaUncompress(DeltaCmprMeta *metaInfo, const NumberTypeOpt *opt, unsigned char *deltaBuf) { uint64 diff = 0; - unsigned char* delta = (unsigned char*)deltaBuf; + unsigned char *delta = (unsigned char *)deltaBuf; diff |= delta[0]; for (int i = 1; i < metaInfo->bytes; i++) { @@ -586,11 +656,11 @@ Datum PageCompress::DeltaUncompress(DeltaCmprMeta* metaInfo, const NumberTypeOpt return opt->plus(metaInfo->MinVal, (int64)diff); } -Datum PageCompress::DictUncompress(DictCmprMeta* metaInfo, const char* compressBuf, int attlen) +Datum PageCompress::DictUncompress(DictCmprMeta *metaInfo, const char *compressBuf, int attlen) { Datum res = 0; - unsigned char id = *((unsigned char*)compressBuf); - DictItemData* val = metaInfo->dictItems + id; + unsigned char id = *((unsigned char *)compressBuf); + DictItemData *val = metaInfo->dictItems + id; /* refer to fetch_att() */ if (attlen > 0) { @@ -600,20 +670,20 @@ Datum PageCompress::DictUncompress(DictCmprMeta* metaInfo, const char* compressB break; } case sizeof(int16): { - res = Int16GetDatum(*(int16*)val->itemData); + res = Int16GetDatum(*(int16 *)val->itemData); break; } case sizeof(int32): { - res = Int32GetDatum(*(int32*)val->itemData); + res = Int32GetDatum(*(int32 *)val->itemData); break; } case sizeof(Datum): { - res = *(Datum*)val->itemData; + res = *(Datum *)val->itemData; break; } default: - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("unsupported byval length: %d", (int)(attlen)))); + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("unsupported byval length: %d", (int)(attlen)))); } } else res = PointerGetDatum(val->itemData); @@ -628,15 +698,15 @@ Datum PageCompress::DictUncompress(DictCmprMeta* metaInfo, const char* compressB template int PageCompress::PrefixLen(Datum str1, Datum str2) { - char* strData1 = NULL; - char* strData2 = NULL; + char *strData1 = NULL; + char *strData2 = NULL; int maxPrefixLen = 0; int prefixLen = 0; /* Note that numeric column should not do this. */ if (attlen == -1) { - char* ptr1 = DatumGetPointer(str1); - char* ptr2 = DatumGetPointer(str2); + char *ptr1 = DatumGetPointer(str1); + char *ptr2 = DatumGetPointer(str2); strData1 = VARDATA_ANY(ptr1); strData2 = VARDATA_ANY(ptr2); maxPrefixLen = Min(VARSIZE_ANY_EXHDR(ptr1), VARSIZE_ANY_EXHDR(ptr2)); @@ -659,12 +729,12 @@ int PageCompress::PrefixLen(Datum str1, Datum str2) * This function outputs prefixLen and prefix string */ template -char* PageCompress::PrefixGetCmprMeta(ColArray* colArray, int& prefixLen) +char *PageCompress::PrefixGetCmprMeta(ColArray *colArray, int &prefixLen) { int prev = 0; int next = 0; int arrayLen = colArray->num; - char* prefixStr = NULL; + char *prefixStr = NULL; /* Initialize the prefixLen by comparing the first and second tuple */ while (prev < arrayLen && colArray->nulls[prev]) @@ -709,65 +779,81 @@ char* PageCompress::PrefixGetCmprMeta(ColArray* colArray, int& prefixLen) } return prefixStr; } - +/* + * 功能:对输入的数据进行前缀压缩 + * + * 参数列表: + * attlen:输入数据的类型长度,取值为 -1 或 -2 + * prefix:前缀压缩元数据结构 + * val:输入的数据 + * cmprsVal:压缩后的数据 + * cmprsValSize:压缩后的数据大小 + * + * 注意事项: + * - 该函数用于对输入数据进行前缀压缩,根据输入的 attlen 参数确定输入数据的类型长度。 + * - 如果前缀与输入数据的前缀匹配,函数将进行压缩,否则返回 false。 + * - 压缩后的数据存储在 cmprsVal 中,其大小存储在 cmprsValSize 中。 + */ template -bool PageCompress::PrefixCompress(PrefixCmprMeta* prefix, Datum val, Datum& cmprsVal, int& cmprsValSize) +bool PageCompress::PrefixCompress(PrefixCmprMeta *prefix, Datum val, Datum &cmprsVal, int &cmprsValSize) { - char* attStart = NULL; - int attSize; + char *attStart = NULL; // 输入数据的起始地址 + int attSize; // 输入数据的大小 if (-1 == attlen) { - char* ptr = DatumGetPointer(val); + char *ptr = DatumGetPointer(val); // 获取输入数据的指针 Assert(ptr != NULL); - attStart = VARDATA_ANY(ptr); - attSize = VARSIZE_ANY_EXHDR(ptr); + attStart = VARDATA_ANY(ptr); // 如果输入数据是变长类型,获取数据的有效部分 + attSize = VARSIZE_ANY_EXHDR(ptr); // 获取有效数据的大小(不包括头部信息) } else { - attStart = DatumGetCString(val); - attSize = (int)(strlen(attStart) + 1); + attStart = DatumGetCString(val); // 如果输入数据是定长类型,直接获取字符串指针 + attSize = (int)(strlen(attStart) + 1); // 计算字符串的大小(包括终止符) } - Assert(attStart != NULL && attSize >= 0); - Assert(prefix != NULL && prefix->len > 0 && prefix->prefixStr != NULL); + Assert(attStart != NULL && attSize >= 0); // 断言输入数据合法性 + Assert(prefix != NULL && prefix->len > 0 && prefix->prefixStr != NULL); // 断言前缀元数据合法性 if (prefix->len > attSize || memcmp(prefix->prefixStr, attStart, (size_t)prefix->len)) { + // 如果前缀与输入数据的前缀不匹配,返回 false return false; } if (-1 == attlen) { - int suffixLen = attSize - prefix->len; - Assert(suffixLen >= 0); - char* suffix = (char*)palloc((Size)(suffixLen + VARHDRSZ)); + int suffixLen = attSize - prefix->len; // 计算剩余数据的大小 + Assert(suffixLen >= 0); // 断言剩余数据大小非负 + char *suffix = (char *)palloc((Size)(suffixLen + VARHDRSZ)); // 分配内存用于存储压缩后的数据 errno_t errorno = EOK; /* use one byte var-header as possible as much */ if (suffixLen + VARHDRSZ_SHORT <= VARATT_SHORT_MAX) { - SET_VARSIZE_SHORT(suffix, suffixLen + VARHDRSZ_SHORT); + SET_VARSIZE_SHORT(suffix, suffixLen + VARHDRSZ_SHORT); // 设置变长头部大小 if (suffixLen > 0) { - errorno = memcpy_s((suffix + VARHDRSZ_SHORT), (size_t)suffixLen, (attStart + prefix->len), (size_t)suffixLen); - securec_check(errorno, "\0", "\0"); + errorno = + memcpy_s((suffix + VARHDRSZ_SHORT), (size_t)suffixLen, (attStart + prefix->len), (size_t)suffixLen); + securec_check(errorno, "\0", "\0"); // 复制数据到压缩后的内存 } - cmprsValSize = (int)(suffixLen + VARHDRSZ_SHORT); + cmprsValSize = (int)(suffixLen + VARHDRSZ_SHORT); // 计算压缩后的数据大小 } else { - SET_VARSIZE(suffix, suffixLen + VARHDRSZ); + SET_VARSIZE(suffix, suffixLen + VARHDRSZ); // 设置变长头部大小 errorno = memcpy_s((suffix + VARHDRSZ), (size_t)suffixLen, (attStart + prefix->len), (size_t)suffixLen); - securec_check(errorno, "\0", "\0"); - cmprsValSize = suffixLen + VARHDRSZ; + securec_check(errorno, "\0", "\0"); // 复制数据到压缩后的内存 + cmprsValSize = suffixLen + VARHDRSZ; // 计算压缩后的数据大小 } - cmprsVal = PointerGetDatum(suffix); + cmprsVal = PointerGetDatum(suffix); // 将压缩后的数据指针存储在 cmprsVal 中 } else { - cmprsVal = CStringGetDatum(attStart + prefix->len); - cmprsValSize = (int)(strlen(attStart + prefix->len) + 1); + cmprsVal = CStringGetDatum(attStart + prefix->len); // 如果输入数据是定长类型,直接获取剩余部分的指针 + cmprsValSize = (int)(strlen(attStart + prefix->len) + 1); // 计算剩余部分的大小(包括终止符) } - Assert(cmprsValSize > 0); + Assert(cmprsValSize > 0); // 断言压缩后的数据大小非负 - return true; + return true; // 返回 true 表示成功进行前缀压缩 } -Datum PageCompress::PrefixUncompress(int attlen, PrefixCmprMeta* prefix, char* start, int* attrCmprsValSize, - char* uncmprValBuf, int* dataLenWithPadding, char attalign) +Datum PageCompress::PrefixUncompress(int attlen, PrefixCmprMeta *prefix, char *start, int *attrCmprsValSize, + char *uncmprValBuf, int *dataLenWithPadding, char attalign) { int attsize; - char* attdata = NULL; - char* attr = NULL; + char *attdata = NULL; + char *attr = NULL; int datum_size; int padding_size = 0; errno_t rc = EOK; @@ -777,7 +863,7 @@ Datum PageCompress::PrefixUncompress(int attlen, PrefixCmprMeta* prefix, char* s Assert(start != NULL && attrCmprsValSize != NULL); if (-1 == attlen) { - char* ptr = DatumGetPointer(start); + char *ptr = DatumGetPointer(start); attdata = VARDATA_ANY(ptr); attsize = VARSIZE_ANY_EXHDR(ptr); *attrCmprsValSize = VARSIZE_ANY(ptr); @@ -796,10 +882,10 @@ Datum PageCompress::PrefixUncompress(int attlen, PrefixCmprMeta* prefix, char* s Assert(dataLenWithPadding != NULL); *attr = 0; - attr = (char*)att_align_nominal(attr, attalign); + attr = (char *)att_align_nominal(attr, attalign); padding_size = (int)(attr - uncmprValBuf); } else { - attr = (char*)palloc((Size)(attRawLen + VARHDRSZ)); + attr = (char *)palloc((Size)(attRawLen + VARHDRSZ)); } datum_size = attRawLen + VARHDRSZ; @@ -823,7 +909,7 @@ Datum PageCompress::PrefixUncompress(int attlen, PrefixCmprMeta* prefix, char* s if (uncmprValBuf) attr = uncmprValBuf; else - attr = (char*)palloc((Size)datum_size); + attr = (char *)palloc((Size)datum_size); rc = memcpy_s(attr, (size_t)datum_size, prefix->prefixStr, (size_t)prefix->len); securec_check(rc, "", ""); if (attsize > 0) { @@ -845,7 +931,7 @@ Datum PageCompress::PrefixUncompress(int attlen, PrefixCmprMeta* prefix, char* s /* choose compression method between delta and dict. * we prefer delta method to dict method. */ -void PageCompress::ChooseMethodForFixedLenType(ColArray* colArray, int col) +void PageCompress::ChooseMethodForFixedLenType(ColArray *colArray, int col) { int hintMode = m_rel->rd_att->attrs[col]->attcmprmode; @@ -865,11 +951,11 @@ void PageCompress::ChooseMethodForFixedLenType(ColArray* colArray, int col) } template -void PageCompress::FillHashKey(DictItemData& key, const int& attlen, const Datum& attval) +void PageCompress::FillHashKey(DictItemData &key, const int &attlen, const Datum &attval) { if (attFlag > 0) { key.itemSize = (int16)attlen; - key.itemData = (char*)palloc((Size)attlen); + key.itemData = (char *)palloc((Size)attlen); store_att_byval(key.itemData, attval, attlen); } else if (-1 == attFlag) { key.itemData = DatumGetPointer(attval); @@ -882,7 +968,7 @@ void PageCompress::FillHashKey(DictItemData& key, const int& attlen, const Datum /* Choose dictinary items and put it into DictCmprMeta */ template -DictCmprMeta* PageCompress::DictGetCmprMeta(ColArray* colArray, int col, int attlen) +DictCmprMeta *PageCompress::DictGetCmprMeta(ColArray *colArray, int col, int attlen) { int i; @@ -893,10 +979,10 @@ DictCmprMeta* PageCompress::DictGetCmprMeta(ColArray* colArray, int col, int att if (m_dictMiss[col] > DICT_MISS_THRESHOLD) return NULL; - DictCmprMeta* dicInfo = (DictCmprMeta*)palloc0(sizeof(DictCmprMeta)); + DictCmprMeta *dicInfo = (DictCmprMeta *)palloc0(sizeof(DictCmprMeta)); int pos = 0; bool found = false; - HTAB* dictHash = + HTAB *dictHash = hashCreate("DictHash", m_pageMemCnxt, colArray->num + 16, sizeof(DictItemData), sizeof(DictItemEntry)); for (i = 0; i < colArray->num; ++i) { @@ -913,7 +999,7 @@ DictCmprMeta* PageCompress::DictGetCmprMeta(ColArray* colArray, int col, int att FillHashKey<-2>(key, attlen, colArray->val[i]); } - DictItemEntry* dicItemEntry = (DictItemEntry*)hash_search(dictHash, (const void*)&key, HASH_ENTER, &found); + DictItemEntry *dicItemEntry = (DictItemEntry *)hash_search(dictHash, (const void *)&key, HASH_ENTER, &found); Assert(dicItemEntry != NULL); if (!found) { dicItemEntry->hitCount = 1; @@ -951,23 +1037,23 @@ DictCmprMeta* PageCompress::DictGetCmprMeta(ColArray* colArray, int col, int att } template -bool PageCompress::NumstrEvaluate(ColArray* colArray) +bool PageCompress::NumstrEvaluate(ColArray *colArray) { int n = colArray->num; int numStr = 0; bool flag = true; - Datum* val = colArray->val; - bool* nulls = colArray->nulls; + Datum *val = colArray->val; + bool *nulls = colArray->nulls; for (int i = 0; i < n; ++i) { if (nulls[i]) continue; int len = 0; - char* dataPtr = NULL; + char *dataPtr = NULL; if (attlen == -1) { - char* p = DatumGetPointer(val[i]); + char *p = DatumGetPointer(val[i]); dataPtr = VARDATA_ANY(p); len = VARSIZE_ANY_EXHDR(p); } else if (attlen == -2) { @@ -1008,7 +1094,7 @@ bool PageCompress::NumstrEvaluate(ColArray* colArray) /* choose compression method between prefix and dict. * we prefer prefix method to dict method. */ -void PageCompress::ChooseMethodForVaryLenType(ColArray* colArray, int col) +void PageCompress::ChooseMethodForVaryLenType(ColArray *colArray, int col) { int hintMode = m_rel->rd_att->attrs[col]->attcmprmode; @@ -1039,15 +1125,15 @@ void PageCompress::ChooseMethodForVaryLenType(ColArray* colArray, int col) * we take it as a hint, and first try to get compression metadata using it. * but maybe it fails, and we have to try the other methods then. */ -bool PageCompress::SetCmprMethod(ColArray* colArray, int col, int mode) +bool PageCompress::SetCmprMethod(ColArray *colArray, int col, int mode) { - Form_pg_attribute* attrs = m_rel->rd_att->attrs; + Form_pg_attribute *attrs = m_rel->rd_att->attrs; int attlen = attrs[col]->attlen; switch (mode) { case CMPR_DELTA: { Assert(m_numTypeOpt[col] != NULL && attlen > 1); - DeltaCmprMeta* deltaInfo = DeltaGetCmprMeta(colArray, m_numTypeOpt[col]); + DeltaCmprMeta *deltaInfo = DeltaGetCmprMeta(colArray, m_numTypeOpt[col]); if (deltaInfo == NULL) return false; @@ -1068,7 +1154,7 @@ bool PageCompress::SetCmprMethod(ColArray* colArray, int col, int mode) } case CMPR_DICT: { - DictCmprMeta* dicInfo = NULL; + DictCmprMeta *dicInfo = NULL; if (-2 == attlen) dicInfo = DictGetCmprMeta<-2>(colArray, col, attlen); else if (-1 == attlen) @@ -1087,7 +1173,7 @@ bool PageCompress::SetCmprMethod(ColArray* colArray, int col, int mode) case CMPR_PREFIX: { int prefixLen = 0; - char* prefixStr = NULL; + char *prefixStr = NULL; Assert(-1 == attlen || -2 == attlen); if (-2 == attlen) @@ -1097,9 +1183,9 @@ bool PageCompress::SetCmprMethod(ColArray* colArray, int col, int mode) /* If this array has common prefix, we fill prefix compression meta information */ if (prefixLen > 0 && prefixLen < MAX_PREFIX_LEN) { - PrefixCmprMeta* prefixInfo = (PrefixCmprMeta*)palloc(sizeof(PrefixCmprMeta)); + PrefixCmprMeta *prefixInfo = (PrefixCmprMeta *)palloc(sizeof(PrefixCmprMeta)); prefixInfo->len = prefixLen; - prefixInfo->prefixStr = (char*)palloc((Size)prefixLen); + prefixInfo->prefixStr = (char *)palloc((Size)prefixLen); errno_t rc = memcpy_s(prefixInfo->prefixStr, (size_t)prefixLen, prefixStr, (size_t)prefixLen); securec_check(rc, "", ""); @@ -1132,10 +1218,10 @@ bool PageCompress::SetCmprMethod(ColArray* colArray, int col, int mode) } } -void PageCompress::ChooseCmprMethod(ColArray* colArrays) +void PageCompress::ChooseCmprMethod(ColArray *colArrays) { int attrNum = RelationGetNumberOfAttributes(m_rel); - Form_pg_attribute* attrs = m_rel->rd_att->attrs; + Form_pg_attribute *attrs = m_rel->rd_att->attrs; for (int col = 0; col < attrNum; ++col) { /* continue if this attribute shouldn't be compressed specified by user; */ @@ -1162,7 +1248,7 @@ void PageCompress::ChooseCmprMethod(ColArray* colArrays) void PageCompress::SetCmprHeaderData(void) { - Form_pg_attribute* atts = RelationGetDescr(m_rel)->attrs; + Form_pg_attribute *atts = RelationGetDescr(m_rel)->attrs; int nattrs = RelationGetNumberOfAttributes(m_rel); int offset = 0; int rawCols = 0; @@ -1173,7 +1259,7 @@ void PageCompress::SetCmprHeaderData(void) switch (m_cmprMode[col]) { case CMPR_DELTA: { - DeltaCmprMeta* deltaInfo = (DeltaCmprMeta*)m_cmprMeta[col]; + DeltaCmprMeta *deltaInfo = (DeltaCmprMeta *)m_cmprMeta[col]; int attrLen = atts[col]->attlen; Assert(deltaInfo->bytes < g_number_max_value_in_bytes); Assert(attrLen > 0); @@ -1187,7 +1273,7 @@ void PageCompress::SetCmprHeaderData(void) } case CMPR_DICT: { - DictCmprMeta* dicInfo = (DictCmprMeta*)m_cmprMeta[col]; + DictCmprMeta *dicInfo = (DictCmprMeta *)m_cmprMeta[col]; Assert(dicInfo->dictItemNum < 0xFF); /* how many dictionary items */ m_cmprHeaderData[offset++] = (char)dicInfo->dictItemNum; @@ -1199,12 +1285,13 @@ void PageCompress::SetCmprHeaderData(void) case CMPR_PREFIX: { Assert(-2 == atts[col]->attlen || -1 == atts[col]->attlen); - PrefixCmprMeta* prefixInfo = (PrefixCmprMeta*)m_cmprMeta[col]; + PrefixCmprMeta *prefixInfo = (PrefixCmprMeta *)m_cmprMeta[col]; Assert(prefixInfo->len > 0 && prefixInfo->len < MAX_PREFIX_LEN); /* how many bytes is used for prefix string */ m_cmprHeaderData[offset++] = (char)prefixInfo->len; /* data of prefix string */ - rc = memcpy_s(m_cmprHeaderData + offset, (size_t)(BLCKSZ - offset), prefixInfo->prefixStr, (size_t)(prefixInfo->len)); + rc = memcpy_s(m_cmprHeaderData + offset, (size_t)(BLCKSZ - offset), prefixInfo->prefixStr, + (size_t)(prefixInfo->len)); securec_check(rc, "", ""); offset += prefixInfo->len; break; @@ -1235,15 +1322,15 @@ void PageCompress::SetCmprHeaderData(void) * metaSize [OUT]: size of compression metadata within page for each attribute; * mode [OUT]: compression method; */ -void* PageCompress::FetchAttrCmprMeta(char* metaStart, const int attrRawLen, int* metaSize, char* mode) +void *PageCompress::FetchAttrCmprMeta(char *metaStart, const int attrRawLen, int *metaSize, char *mode) { int offset = 1; *mode = metaStart[0]; - void* retInfo = NULL; + void *retInfo = NULL; switch (*mode) { case CMPR_DELTA: { - DeltaCmprMeta* deltaInfo = &t_thrd.storage_cxt.cmprMetaInfo->deltaInfo; + DeltaCmprMeta *deltaInfo = &t_thrd.storage_cxt.cmprMetaInfo->deltaInfo; /* how many bytes is used for delta value */ deltaInfo->bytes = metaStart[offset]; @@ -1254,11 +1341,11 @@ void* PageCompress::FetchAttrCmprMeta(char* metaStart, const int attrRawLen, int deltaInfo->MinVal = fetch_att(metaStart + offset, true, attrRawLen); offset += attrRawLen; *metaSize = offset; - retInfo = (void*)deltaInfo; + retInfo = (void *)deltaInfo; break; } case CMPR_DICT: { - DictCmprMeta* dicInfo = &t_thrd.storage_cxt.cmprMetaInfo->dicInfo; + DictCmprMeta *dicInfo = &t_thrd.storage_cxt.cmprMetaInfo->dicInfo; dicInfo->dictItemNum = metaStart[offset++]; if (attrRawLen > 0) { @@ -1281,32 +1368,32 @@ void* PageCompress::FetchAttrCmprMeta(char* metaStart, const int attrRawLen, int } } *metaSize = offset; - retInfo = (void*)dicInfo; + retInfo = (void *)dicInfo; break; } case CMPR_PREFIX: { Assert(-2 == attrRawLen || -1 == attrRawLen); - PrefixCmprMeta* prefixInfo = &t_thrd.storage_cxt.cmprMetaInfo->prefixInfo; + PrefixCmprMeta *prefixInfo = &t_thrd.storage_cxt.cmprMetaInfo->prefixInfo; prefixInfo->len = (metaStart[offset] & 0x00FF); ++offset; prefixInfo->prefixStr = metaStart + offset; offset += prefixInfo->len; *metaSize = offset; - retInfo = (void*)prefixInfo; + retInfo = (void *)prefixInfo; break; } case CMPR_NUMSTR: case CMPR_NONE: { *metaSize = offset; - retInfo = (void*)NULL; + retInfo = (void *)NULL; break; } default: { Assert(false); - retInfo = (void*)NULL; + retInfo = (void *)NULL; break; } } @@ -1317,16 +1404,16 @@ void* PageCompress::FetchAttrCmprMeta(char* metaStart, const int attrRawLen, int /* according to compression mode and meta information, return the value size * of this attribute in the tuple. */ -int PageCompress::GetAttrCmprValSize(char mode, int attlen, void* metaInfo, const char* attrStart) +int PageCompress::GetAttrCmprValSize(char mode, int attlen, void *metaInfo, const char *attrStart) { switch (mode) { case CMPR_DELTA: { - DeltaCmprMeta* deltaInfo = (DeltaCmprMeta*)metaInfo; + DeltaCmprMeta *deltaInfo = (DeltaCmprMeta *)metaInfo; return deltaInfo->bytes; } case CMPR_DICT: { - DictCmprMeta* dicInfo = (DictCmprMeta*)metaInfo; + DictCmprMeta *dicInfo = (DictCmprMeta *)metaInfo; return (dicInfo->dictItemNum == 1) ? 0 : DICT_ITEMID_SIZE; } @@ -1356,8 +1443,8 @@ bool PageCompress::NeedExternalBuf(char mode) return ((mode == CMPR_PREFIX) || (mode == CMPR_NUMSTR)); } -int PageCompress::UncompressOneAttr( - char mode, char attalign, int attlen, void* metaInfo, char* attrStart, int* attrCmprsValSize, char* uncmprValBuf) +int PageCompress::UncompressOneAttr(char mode, char attalign, int attlen, void *metaInfo, char *attrStart, + int *attrCmprsValSize, char *uncmprValBuf) { int dataLenWithPadding = 0; @@ -1366,15 +1453,15 @@ int PageCompress::UncompressOneAttr( switch (mode) { case CMPR_PREFIX: { - PrefixCmprMeta* prefixInfo = (PrefixCmprMeta*)metaInfo; - (void)PrefixUncompress( - attlen, prefixInfo, attrStart, attrCmprsValSize, uncmprValBuf, &dataLenWithPadding, attalign); + PrefixCmprMeta *prefixInfo = (PrefixCmprMeta *)metaInfo; + (void)PrefixUncompress(attlen, prefixInfo, attrStart, attrCmprsValSize, uncmprValBuf, &dataLenWithPadding, + attalign); break; } case CMPR_NUMSTR: { - (void)NumstrUncompress( - PointerGetDatum(attrStart), attlen, *attrCmprsValSize, uncmprValBuf, &dataLenWithPadding, attalign); + (void)NumstrUncompress(PointerGetDatum(attrStart), attlen, *attrCmprsValSize, uncmprValBuf, + &dataLenWithPadding, attalign); break; } @@ -1395,12 +1482,12 @@ int PageCompress::UncompressOneAttr( * attrStart [IN]: start of attr data; * attrCmprsValSize [OUT]: length of compressed value; */ -Datum PageCompress::UncompressOneAttr( - char mode, void* metaInfo, Oid typeOid, int attlen, char* attrStart, int* attrCmprsValSize) +Datum PageCompress::UncompressOneAttr(char mode, void *metaInfo, Oid typeOid, int attlen, char *attrStart, + int *attrCmprsValSize) { switch (mode) { case CMPR_DELTA: { - DeltaCmprMeta* deltaInfo = (DeltaCmprMeta*)metaInfo; + DeltaCmprMeta *deltaInfo = (DeltaCmprMeta *)metaInfo; if (deltaInfo->bytes == 0) { *attrCmprsValSize = 0; return deltaInfo->MinVal; @@ -1420,12 +1507,12 @@ Datum PageCompress::UncompressOneAttr( return (Datum)0; } - const NumberTypeOpt* opt = binarySearch(typeOid); - return DeltaUncompress(deltaInfo, opt, (unsigned char*)attrStart); + const NumberTypeOpt *opt = binarySearch(typeOid); + return DeltaUncompress(deltaInfo, opt, (unsigned char *)attrStart); } case CMPR_DICT: { - DictCmprMeta* dicInfo = (DictCmprMeta*)metaInfo; + DictCmprMeta *dicInfo = (DictCmprMeta *)metaInfo; if (dicInfo->dictItemNum == 1) { *attrCmprsValSize = 0; @@ -1437,7 +1524,7 @@ Datum PageCompress::UncompressOneAttr( } case CMPR_PREFIX: { - PrefixCmprMeta* prefixInfo = (PrefixCmprMeta*)metaInfo; + PrefixCmprMeta *prefixInfo = (PrefixCmprMeta *)metaInfo; return PrefixUncompress(attlen, prefixInfo, attrStart, attrCmprsValSize); } @@ -1458,11 +1545,11 @@ Datum PageCompress::UncompressOneAttr( } /* Get dictionary item ID */ -bool PageCompress::DictCompress(int col, Datum val, unsigned char& dicItemId) +bool PageCompress::DictCompress(int col, Datum val, unsigned char &dicItemId) { bool found = false; int attlen = m_rel->rd_att->attrs[col]->attlen; - DictCmprMeta* dicInfo = (DictCmprMeta*)m_cmprMeta[col]; + DictCmprMeta *dicInfo = (DictCmprMeta *)m_cmprMeta[col]; DictItemData key; if (attlen > 0) @@ -1481,7 +1568,7 @@ bool PageCompress::DictCompress(int col, Datum val, unsigned char& dicItemId) } /* compress the col's attribute of one tuple, ouput data is written into formTuple */ -void PageCompress::CompressOneAttr(Datum val, bool null, FormCmprTupleData* formTuple, int col) +void PageCompress::CompressOneAttr(Datum val, bool null, FormCmprTupleData *formTuple, int col) { formTuple->compressed[col] = false; @@ -1491,15 +1578,15 @@ void PageCompress::CompressOneAttr(Datum val, bool null, FormCmprTupleData* form return; } - Form_pg_attribute* attrs = m_rel->rd_att->attrs; + Form_pg_attribute *attrs = m_rel->rd_att->attrs; formTuple->values[col] = val; formTuple->isnulls[col] = false; switch (m_cmprMode[col]) { case CMPR_DELTA: { - DeltaCmprMeta* metaInfo = (DeltaCmprMeta*)m_cmprMeta[col]; - const NumberTypeOpt* opt = m_numTypeOpt[col]; - unsigned char* deltaBuf = (unsigned char*)palloc((Size)metaInfo->bytes); + DeltaCmprMeta *metaInfo = (DeltaCmprMeta *)m_cmprMeta[col]; + const NumberTypeOpt *opt = m_numTypeOpt[col]; + unsigned char *deltaBuf = (unsigned char *)palloc((Size)metaInfo->bytes); if (DeltaCompress(metaInfo, opt, val, deltaBuf)) { formTuple->compressed[col] = true; if (metaInfo->bytes == 0) { @@ -1524,7 +1611,7 @@ void PageCompress::CompressOneAttr(Datum val, bool null, FormCmprTupleData* form case CMPR_DICT: { if (DictCompress(col, val, m_dictItemId[col])) { formTuple->compressed[col] = true; - DictCmprMeta* dicInfo = (DictCmprMeta*)m_cmprMeta[col]; + DictCmprMeta *dicInfo = (DictCmprMeta *)m_cmprMeta[col]; if (dicInfo->dictItemNum == 1) { formTuple->valsize[col] = 0; formTuple->values[col] = (Datum)0; @@ -1539,7 +1626,7 @@ void PageCompress::CompressOneAttr(Datum val, bool null, FormCmprTupleData* form case CMPR_PREFIX: { int attrLen = attrs[col]->attlen; Assert(attrLen == -1 || attrLen == -2); - PrefixCmprMeta* prefixInfo = (PrefixCmprMeta*)m_cmprMeta[col]; + PrefixCmprMeta *prefixInfo = (PrefixCmprMeta *)m_cmprMeta[col]; Datum cmprsVal; int cmprsValSize; bool successful = false; @@ -1604,11 +1691,11 @@ bool PageCompress::CompressOnePage(void) int thisToasts; MemoryContext oldMemCnxt; - ColArray* colArray = NULL; + ColArray *colArray = NULL; TupleDesc desc = RelationGetDescr(m_rel); FormCmprTupleData formTupleData; - Datum* values = NULL; - bool* isnulls = NULL; + Datum *values = NULL; + bool *isnulls = NULL; bool fillOnePage = false; bool hascmpr = false; bool remainBeforeCmpr = true; @@ -1677,10 +1764,10 @@ bool PageCompress::CompressOnePage(void) #endif /* Step 4: Compress the current batch rows. */ - formTupleData.compressed = (bool*)palloc((Size)(sizeof(bool) * attrNum)); - formTupleData.valsize = (int*)palloc((Size)(sizeof(int) * attrNum)); - formTupleData.values = (Datum*)palloc((Size)(sizeof(Datum) * attrNum)); - formTupleData.isnulls = (bool*)palloc((Size)(sizeof(bool) * attrNum)); + formTupleData.compressed = (bool *)palloc((Size)(sizeof(bool) * attrNum)); + formTupleData.valsize = (int *)palloc((Size)(sizeof(int) * attrNum)); + formTupleData.values = (Datum *)palloc((Size)(sizeof(Datum) * attrNum)); + formTupleData.isnulls = (bool *)palloc((Size)(sizeof(bool) * attrNum)); size = (int)MAXALIGN(SizeOfHeapPageHeaderData) + (int)MAXALIGN((uint32)m_cmprHeaderSize); for (row = 0; row < m_mappedTupsNum; ++row) { @@ -1699,7 +1786,9 @@ bool PageCompress::CompressOnePage(void) * or all compressed tuples fill part of one page. */ if (hascmpr) { - fillOnePage = Dispatch(m_mappedTups[row], (HeapTuple)tableam_tops_form_cmprs_tuple(desc, &formTupleData, HEAP_TUPLE), size, blksize); + fillOnePage = + Dispatch(m_mappedTups[row], (HeapTuple)tableam_tops_form_cmprs_tuple(desc, &formTupleData, HEAP_TUPLE), + size, blksize); } else { fillOnePage = Dispatch(m_mappedTups[row], size, blksize); } @@ -1723,8 +1812,8 @@ bool PageCompress::CompressOnePage(void) } Assert(row == m_mappedTupsNum); - values = (Datum*)palloc((Size)(sizeof(Datum) * attrNum)); - isnulls = (bool*)palloc((Size)(sizeof(bool) * attrNum)); + values = (Datum *)palloc((Size)(sizeof(Datum) * attrNum)); + isnulls = (bool *)palloc((Size)(sizeof(bool) * attrNum)); /* continue to read and compress following tuples, until one page is filled */ for (row = m_current + m_mappedTupsNum + (m_toastTupsNum - prevToastNum); row < m_inTupsNum; ++row) { @@ -1746,7 +1835,8 @@ bool PageCompress::CompressOnePage(void) } if (hascmpr) { - fillOnePage = Dispatch(raw, (HeapTuple)tableam_tops_form_cmprs_tuple(desc, &formTupleData, HEAP_TUPLE), size, blksize); + fillOnePage = Dispatch(raw, (HeapTuple)tableam_tops_form_cmprs_tuple(desc, &formTupleData, HEAP_TUPLE), + size, blksize); } else { fillOnePage = Dispatch(raw, size, blksize); } @@ -1805,9 +1895,7 @@ handle_remains: * step 1: collect these toasts in the front, make sure they are free * after they are written into pages by heap_multi_insert(). */ - rc = memcpy_s(m_inTups + m_current, - (size_t)(sizeof(HeapTuple) * thisToasts), - m_toastTups + prevToastNum, + rc = memcpy_s(m_inTups + m_current, (size_t)(sizeof(HeapTuple) * thisToasts), m_toastTups + prevToastNum, (size_t)(sizeof(HeapTuple) * thisToasts)); securec_check(rc, "", ""); m_current += thisToasts; @@ -1816,9 +1904,7 @@ handle_remains: /* step 2: collect raw tuples in the end, make sure they are moved ahead next buffer. */ if (remainBeforeCmpr) { /* case 3.2.1: raw tuples are all in m_mappedTups[m_mappedTupsNum] */ - rc = memcpy_s(m_inTups + m_current, - (size_t)(sizeof(HeapTuple) * m_mappedTupsNum), - m_mappedTups, + rc = memcpy_s(m_inTups + m_current, (size_t)(sizeof(HeapTuple) * m_mappedTupsNum), m_mappedTups, (size_t)(sizeof(HeapTuple) * m_mappedTupsNum)); securec_check(rc, "", ""); } else { @@ -1828,9 +1914,7 @@ handle_remains: */ int offset = m_current; if (m_cmprTupsNum > 0) { - rc = memcpy_s(m_inTups + offset, - (size_t)(sizeof(HeapTuple) * m_cmprTupsNum), - m_mappedTups, + rc = memcpy_s(m_inTups + offset, (size_t)(sizeof(HeapTuple) * m_cmprTupsNum), m_mappedTups, (size_t)(sizeof(HeapTuple) * m_cmprTupsNum)); securec_check(rc, "", ""); offset += m_cmprTupsNum; @@ -1899,18 +1983,16 @@ void PageCompress::OutputRawTupsAfterCmpr(int nToasts) m_outputTups = m_mappedTups; int uncmprNum = m_inTupsNum - m_uncmprTupIdx; if (uncmprNum > 0) { - errno_t rc = memcpy_s(m_mappedTups + m_cmprTupsNum, - (size_t)(sizeof(HeapTuple) * uncmprNum), - m_cmprTups + m_uncmprTupIdx, - (size_t)(sizeof(HeapTuple) * uncmprNum)); + errno_t rc = memcpy_s(m_mappedTups + m_cmprTupsNum, (size_t)(sizeof(HeapTuple) * uncmprNum), + m_cmprTups + m_uncmprTupIdx, (size_t)(sizeof(HeapTuple) * uncmprNum)); securec_check(rc, "", ""); } Assert((m_cmprTupsNum + uncmprNum) <= m_mappedTupsNum); m_outputTupsNum = m_mappedTupsNum; #ifdef USE_ASSERT_CHECKING - CheckOutputTups( - m_mappedTups, m_cmprTupsNum, (m_mappedTups + m_cmprTupsNum), (m_mappedTupsNum - m_cmprTupsNum), nToasts); + CheckOutputTups(m_mappedTups, m_cmprTupsNum, (m_mappedTups + m_cmprTupsNum), (m_mappedTupsNum - m_cmprTupsNum), + nToasts); #endif #ifdef TRACE_COMPRESS ++nLoops; @@ -1937,10 +2019,8 @@ void PageCompress::OutputCmprTups(int nToasts) else if (m_cmprTupsNum == m_uncmprTupIdx) m_outputTupsNum = m_inTupsNum; else { - errno_t rc = memmove_s(m_cmprTups + m_cmprTupsNum, - (size_t)(sizeof(HeapTuple) * uncmprNum), - m_cmprTups + m_uncmprTupIdx, - (size_t)(sizeof(HeapTuple) * uncmprNum)); + errno_t rc = memmove_s(m_cmprTups + m_cmprTupsNum, (size_t)(sizeof(HeapTuple) * uncmprNum), + m_cmprTups + m_uncmprTupIdx, (size_t)(sizeof(HeapTuple) * uncmprNum)); securec_check(rc, "", ""); m_outputTupsNum = m_cmprTupsNum + uncmprNum; } @@ -1975,7 +2055,7 @@ void PageCompress::OutputToasts(void) Assert(m_cmprTupsNum == 0); } -void PageCompress::SetBatchTuples(HeapTuple* tups, int ntups, bool isLast) +void PageCompress::SetBatchTuples(HeapTuple *tups, int ntups, bool isLast) { m_inTups = tups; m_inTupsNum = ntups; @@ -1988,9 +2068,9 @@ void PageCompress::SetBatchTuples(HeapTuple* tups, int ntups, bool isLast) */ MemoryContext oldMemCnxt = MemoryContextSwitchTo(m_parentMemCxt); - m_toastTups = (HeapTuple*)palloc((Size)(sizeof(HeapTuple) * ntups)); - m_mappedTups = (HeapTuple*)palloc((Size)(sizeof(HeapTuple) * ntups)); - m_cmprTups = (HeapTuple*)palloc((Size)(sizeof(HeapTuple) * ntups)); + m_toastTups = (HeapTuple *)palloc((Size)(sizeof(HeapTuple) * ntups)); + m_mappedTups = (HeapTuple *)palloc((Size)(sizeof(HeapTuple) * ntups)); + m_cmprTups = (HeapTuple *)palloc((Size)(sizeof(HeapTuple) * ntups)); m_toastTupsNum = 0; m_nextBatch = false; @@ -2004,7 +2084,7 @@ void PageCompress::ResetPerPage(void) /* Free everything by destroying dictionary */ for (int att = 0; att < attNum; ++att) { if (m_cmprMode[att] == CMPR_DICT && m_cmprMeta[att]) { - ((DictCmprMeta*)m_cmprMeta[att])->Destroy(); + ((DictCmprMeta *)m_cmprMeta[att])->Destroy(); m_cmprMeta[att] = NULL; } } @@ -2058,12 +2138,11 @@ void PageCompress::ForwardWrite(void) Assert(dest->t_hoff == src->t_hoff); Assert(HeapTupleHeaderGetNatts(dest, m_rel->rd_att) == HeapTupleHeaderGetNatts(src, m_rel->rd_att)); Assert((0x000F & dest->t_infomask) == (0x000F & src->t_infomask)); - Assert( - (0 == (dest->t_infomask & HEAP_HASNULL)) || - 0 == memcmp( - (char*)dest->t_bits, (char*)src->t_bits, BITMAPLEN(HeapTupleHeaderGetNatts(src, m_rel->rd_att)))); + Assert((0 == (dest->t_infomask & HEAP_HASNULL)) || + 0 == memcmp((char *)dest->t_bits, (char *)src->t_bits, + BITMAPLEN(HeapTupleHeaderGetNatts(src, m_rel->rd_att)))); - rc = memcpy_s((char*)dest, src->t_hoff, (char*)src, src->t_hoff); + rc = memcpy_s((char *)dest, src->t_hoff, (char *)src, src->t_hoff); securec_check(rc, "", ""); HEAP_TUPLE_SET_COMPRESSED(dest); } @@ -2093,7 +2172,7 @@ void PageCompress::BackWrite(void) Assert((0x000F & dest->t_infomask) == (0x000F & src->t_infomask)); Assert(HeapTupleHasNulls(m_mappedTups[i]) == HeapTupleHasNulls(m_cmprTups[i])); - rc = memcpy_s((char*)dest, src->t_hoff, (char*)src, src->t_hoff); + rc = memcpy_s((char *)dest, src->t_hoff, (char *)src, src->t_hoff); securec_check(rc, "", ""); HEAP_TUPLE_CLEAR_COMPRESSED(dest); m_mappedTups[i]->t_self = m_cmprTups[i]->t_self; @@ -2104,7 +2183,7 @@ void PageCompress::BackWrite(void) } } -bool PageCompress::Dispatch(HeapTuple raw, HeapTuple cmpr, int& nowSize, const int& blksize) +bool PageCompress::Dispatch(HeapTuple raw, HeapTuple cmpr, int &nowSize, const int &blksize) { Assert(HEAP_TUPLE_IS_COMPRESSED(cmpr->t_data)); int needSize = (int)getTupleSpace(cmpr); @@ -2123,7 +2202,7 @@ bool PageCompress::Dispatch(HeapTuple raw, HeapTuple cmpr, int& nowSize, const i return false; } -bool PageCompress::Dispatch(HeapTuple raw, int& nowSize, const int& blksize) +bool PageCompress::Dispatch(HeapTuple raw, int &nowSize, const int &blksize) { Assert(!HEAP_TUPLE_IS_COMPRESSED(raw->t_data)); int needSize = (int)getTupleSpace(raw); @@ -2186,8 +2265,8 @@ void PageCompress::CheckCmprDatum(Datum arg1, Datum arg2, Form_pg_attribute attr } } - char* ptr1 = DatumGetPointer(arg1); - char* ptr2 = DatumGetPointer(arg2); + char *ptr1 = DatumGetPointer(arg1); + char *ptr2 = DatumGetPointer(arg2); int size1 = 0; int size2 = 0; @@ -2204,7 +2283,7 @@ void PageCompress::CheckCmprDatum(Datum arg1, Datum arg2, Form_pg_attribute attr return; } -void PageCompress::CheckCmprAttr(Datum rawVal, bool rawNull, int col, FormCmprTupleData* formTuple) +void PageCompress::CheckCmprAttr(Datum rawVal, bool rawNull, int col, FormCmprTupleData *formTuple) { Assert(rawNull == formTuple->isnulls[col]); if (rawNull) { @@ -2218,15 +2297,11 @@ void PageCompress::CheckCmprAttr(Datum rawVal, bool rawNull, int col, FormCmprTu } Assert(m_cmprMode[col] != CMPR_UNDEF); - Form_pg_attribute* atts = RelationGetDescr(m_rel)->attrs; + Form_pg_attribute *atts = RelationGetDescr(m_rel)->attrs; Assert(1 != atts[col]->attlen); int cmprSize; - Datum value = UncompressOneAttr(m_cmprMode[col], - m_cmprMeta[col], - atts[col]->atttypid, - atts[col]->attlen, - DatumGetPointer(formTuple->values[col]), - &cmprSize); + Datum value = UncompressOneAttr(m_cmprMode[col], m_cmprMeta[col], atts[col]->atttypid, atts[col]->attlen, + DatumGetPointer(formTuple->values[col]), &cmprSize); Assert(cmprSize == formTuple->valsize[col]); CheckCmprDatum(rawVal, value, atts[col]); @@ -2235,13 +2310,13 @@ void PageCompress::CheckCmprAttr(Datum rawVal, bool rawNull, int col, FormCmprTu void PageCompress::CheckCmprTuple(HeapTuple rawTup, HeapTuple cmprTup) { TupleDesc desc = RelationGetDescr(m_rel); - Form_pg_attribute* atts = desc->attrs; + Form_pg_attribute *atts = desc->attrs; int nattrs = desc->natts; - Datum* values = (Datum*)palloc(sizeof(Datum) * nattrs); - Datum* values2 = (Datum*)palloc(sizeof(Datum) * nattrs); - bool* isnulls = (bool*)palloc(sizeof(bool) * nattrs); - bool* isnulls2 = (bool*)palloc(sizeof(bool) * nattrs); + Datum *values = (Datum *)palloc(sizeof(Datum) * nattrs); + Datum *values2 = (Datum *)palloc(sizeof(Datum) * nattrs); + bool *isnulls = (bool *)palloc(sizeof(bool) * nattrs); + bool *isnulls2 = (bool *)palloc(sizeof(bool) * nattrs); heap_deform_cmprs_tuple(cmprTup, desc, values, isnulls, m_cmprHeaderData); heap_deform_tuple(rawTup, desc, values2, isnulls2); @@ -2269,10 +2344,10 @@ void PageCompress::CheckCmprHeaderData(void) return; } - Form_pg_attribute* atts = RelationGetDescr(m_rel)->attrs; + Form_pg_attribute *atts = RelationGetDescr(m_rel)->attrs; int nattrs = RelationGetNumberOfAttributes(m_rel); int cmprsOff = 0; /* pointer to the start of compression meta */ - void* metaInfo = NULL; + void *metaInfo = NULL; char mode = 0; for (int col = 0; col < nattrs; ++col) { @@ -2283,8 +2358,8 @@ void PageCompress::CheckCmprHeaderData(void) Assert(mode != CMPR_UNDEF && m_cmprMode[col] == mode); switch (mode) { case CMPR_DELTA: { - DeltaCmprMeta* deltaInfo = (DeltaCmprMeta*)metaInfo; - DeltaCmprMeta* target = (DeltaCmprMeta*)m_cmprMeta[col]; + DeltaCmprMeta *deltaInfo = (DeltaCmprMeta *)metaInfo; + DeltaCmprMeta *target = (DeltaCmprMeta *)m_cmprMeta[col]; Assert(deltaInfo->bytes == target->bytes); Assert(deltaInfo->MinVal == target->MinVal); @@ -2292,8 +2367,8 @@ void PageCompress::CheckCmprHeaderData(void) } case CMPR_DICT: { - DictCmprMeta* dictMeta = (DictCmprMeta*)metaInfo; - DictCmprMeta* target = (DictCmprMeta*)m_cmprMeta[col]; + DictCmprMeta *dictMeta = (DictCmprMeta *)metaInfo; + DictCmprMeta *target = (DictCmprMeta *)m_cmprMeta[col]; Assert(dictMeta->dictItemNum == target->dictItemNum); for (int i = 0; i < dictMeta->dictItemNum; ++i) { @@ -2306,8 +2381,8 @@ void PageCompress::CheckCmprHeaderData(void) } case CMPR_PREFIX: { - PrefixCmprMeta* prefixMeta = (PrefixCmprMeta*)metaInfo; - PrefixCmprMeta* target = (PrefixCmprMeta*)m_cmprMeta[col]; + PrefixCmprMeta *prefixMeta = (PrefixCmprMeta *)metaInfo; + PrefixCmprMeta *target = (PrefixCmprMeta *)m_cmprMeta[col]; Assert(prefixMeta->len == target->len); Assert(0 == memcmp(prefixMeta->prefixStr, target->prefixStr, target->len)); @@ -2333,7 +2408,7 @@ void PageCompress::CheckCmprHeaderData(void) * 3. compress and then mapped to mapped arrays; * Important: call before m_current is modified and changed. */ -void PageCompress::CheckOutputTups(HeapTuple* mapped, int nMapped, HeapTuple* uncmpr, int nUncmpr, int nToasts) +void PageCompress::CheckOutputTups(HeapTuple *mapped, int nMapped, HeapTuple *uncmpr, int nUncmpr, int nToasts) { int start = m_current; int end = m_current + m_outputTupsNum + nToasts; @@ -2390,15 +2465,15 @@ void PageCompress::CheckOutputTups(HeapTuple* mapped, int nMapped, HeapTuple* un #ifdef TRACE_COMPRESS -void PageCompress::TraceCmprPage( - int flag, HeapTuple* cmpr, HeapTuple* mapped, int nCmpr, HeapTuple* uncmpr, int nUncmpr, int nToasts) +void PageCompress::TraceCmprPage(int flag, HeapTuple *cmpr, HeapTuple *mapped, int nCmpr, HeapTuple *uncmpr, + int nUncmpr, int nToasts) { int start = m_current; int end = m_current + m_outputTupsNum + nToasts; int nattrs = RelationGetNumberOfAttributes(m_rel); - Form_pg_attribute* atts = RelationGetDescr(m_rel)->attrs; + Form_pg_attribute *atts = RelationGetDescr(m_rel)->attrs; - char* tag = NULL; + char *tag = NULL; if (flag == 0) tag = "Tag: Raw Tuples Before Compress"; else if (flag == 1) @@ -2409,9 +2484,9 @@ void PageCompress::TraceCmprPage( tag = "Tag: Toast Tuples"; ereport(INFO, (errmsg("Loop %d, range[%d - %d], %s", nLoops, start, (end - 1), tag))); - DeltaCmprMeta* deltaMeta = NULL; - PrefixCmprMeta* prefixMeta = NULL; - DictCmprMeta* dictMeta = NULL; + DeltaCmprMeta *deltaMeta = NULL; + PrefixCmprMeta *prefixMeta = NULL; + DictCmprMeta *dictMeta = NULL; for (int col = 0; col < nattrs; ++col) { switch (m_cmprMode[col]) { @@ -2420,12 +2495,9 @@ void PageCompress::TraceCmprPage( break; case CMPR_DELTA: - deltaMeta = (DeltaCmprMeta*)m_cmprMeta[col]; - ereport(INFO, - (errmsg("COL %d: Delta Compress, attrlen %d, delta bytes %d", - col, - atts[col]->attlen, - deltaMeta->bytes))); + deltaMeta = (DeltaCmprMeta *)m_cmprMeta[col]; + ereport(INFO, (errmsg("COL %d: Delta Compress, attrlen %d, delta bytes %d", col, atts[col]->attlen, + deltaMeta->bytes))); break; case CMPR_NUMSTR: @@ -2433,14 +2505,14 @@ void PageCompress::TraceCmprPage( break; case CMPR_PREFIX: - prefixMeta = (PrefixCmprMeta*)m_cmprMeta[col]; + prefixMeta = (PrefixCmprMeta *)m_cmprMeta[col]; ereport(INFO, (errmsg("COL %d: Prefix Compress, len %d", col, prefixMeta->len))); break; case CMPR_DICT: - dictMeta = (DictCmprMeta*)m_cmprMeta[col]; + dictMeta = (DictCmprMeta *)m_cmprMeta[col]; char str[0xFF * (sizeof(int16) + 1) + 1] = {0}; - char* p = str; + char *p = str; for (int i = 0; i < dictMeta->dictItemNum; ++i) { (void)sprintf_s(p, sizeof(str), "%2d ", dictMeta->dictItems[i].itemSize); p = p + 3; @@ -2488,7 +2560,7 @@ void PageCompress::TraceCmprPage( /* binary search. * negative will be returned if search fails. Otherwise return the position of oid. */ -static const NumberTypeOpt* binarySearch(Oid typeOid) +static const NumberTypeOpt *binarySearch(Oid typeOid) { int left = 0; int right = g_number_type_count - 1; diff --git a/src/gausskernel/storage/page/pageparse.cpp b/src/gausskernel/storage/page/pageparse.cpp index 7cfd4130a..db2aaf144 100644 --- a/src/gausskernel/storage/page/pageparse.cpp +++ b/src/gausskernel/storage/page/pageparse.cpp @@ -48,17 +48,13 @@ #include "utils/relmapper.h" #include "pageparse.h" -typedef enum { - BTREE_INDEX = 0, - UBTREE_INDEX, - INDEX_BOTT -} INDEX_TYPE; +typedef enum { BTREE_INDEX = 0, UBTREE_INDEX, INDEX_BOTT } INDEX_TYPE; void CheckUser(const char *fName) { if (!superuser() && !(isOperatoradmin(GetUserId()) && u_sess->attr.attr_security.operation_mode)) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - (errmsg("Must be system admin or operator admin in operation mode can use %s.", fName)))); + (errmsg("Must be system admin or operator admin in operation mode can use %s.", fName)))); } static void ParseUHeapPageHeader(void *page, BlockNumber blkno, BlockNumber endBlk, char *output); @@ -82,28 +78,63 @@ static void ParseIndexPageSpecialInfo(void *page, int type, char *output); static void ParseIndexPageData(void *page, int type, char *output); static void ParseIndexPage(void *page, int type, BlockNumber blkno, BlockNumber endBlk, char *output); - +/* + * 功能:将二进制位图格式化为字符串表示 + * + * 参数列表: + * start:指向二进制位图数据的指针 + * len:二进制位图数据的长度 + * bit1:表示位为 1 时使用的字符 + * bit0:表示位为 0 时使用的字符 + * strOutput:用于存储格式化后的字符串的缓冲区 + * + * 注意事项: + * - 该函数用于将二进制位图数据转换为字符串表示,以便输出和显示。 + * - 参数 `bit1` 和 `bit0` 分别指定了位为 1 和 0 时使用的字符。 + * - 函数将二进制位图中的每个位都格式化为字符,每 8 位之间添加一个空格。 + * - 结果字符串将存储在 `strOutput` 中。 + */ static void formatBitmap(const unsigned char *start, int len, char bit1, char bit0, char *strOutput) { errno_t rc = EOK; for (int i = 0; i < len; ++i) { - unsigned char ch = start[i]; - unsigned char bitmask = 1; + unsigned char ch = start[i]; // 获取二进制位图中的一个字节 + unsigned char bitmask = 1; // 初始化掩码为 1 /* print 8 bits within a loop */ do { rc = snprintf_s(strOutput + (int)strlen(strOutput), MAXOUTPUTLEN, MAXOUTPUTLEN - 1, "%c", - ((ch & bitmask) ? bit1 : bit0)); - securec_check_ss(rc, "\0", "\0"); - bitmask <<= 1; - } while (bitmask != 0); - rc = snprintf_s(strOutput + (int)strlen(strOutput), MAXOUTPUTLEN, MAXOUTPUTLEN - 1, " "); - securec_check_ss(rc, "\0", "\0"); + ((ch & bitmask) ? bit1 : bit0)); // 根据位值选择相应的字符并添加到结果字符串 + securec_check_ss(rc, "\0", "\0"); // 检查 snprintf_s 的返回值 + bitmask <<= 1; // 移动掩码到下一个位 + } while (bitmask != 0); // 继续直到处理完所有 8 位 + rc = snprintf_s(strOutput + (int)strlen(strOutput), MAXOUTPUTLEN, MAXOUTPUTLEN - 1, + " "); // 在每 8 位之后添加一个空格 + securec_check_ss(rc, "\0", "\0"); // 检查 snprintf_s 的返回值 } } /* init RelFileNode and outputFilename */ +/* + * 功能:为读取文件做准备,设置文件路径、表关联类型等信息 + * + * 参数列表: + * path:文件路径 + * blocknum:块号 + * relation_type:表关联类型,例如 "segment" + * outputFilename:输出文件名的缓冲区 + * relnode:表关联的文件节点信息 + * parse_page:是否解析页面(布尔值) + + * 注意事项: + * - 该函数用于为读取文件做准备,设置输出文件名、文件节点信息等。 + * - 如果计算压缩主分支的大小不为 0,则抛出错误,不允许在这里使用压缩表文件。 + * - 根据表关联类型设置 `relnode->bucketNode`,并从文件路径中提取桶号。 + * - 根据文件路径计算 `relfilenode`。 + * - 如果表空间 oid 为 0,则抛出错误。 + * - 根据解析页面标志和表空间信息设置输出文件名。 + */ void PrepForRead(char *path, int64 blocknum, char *relation_type, char *outputFilename, RelFileNode *relnode, - bool parse_page) + bool parse_page) { if (CalculateCompressMainForkSize(path, true) != 0) { ereport(ERROR, @@ -120,7 +151,7 @@ void PrepForRead(char *path, int64 blocknum, char *relation_type, char *outputFi bucketNodestr += TWO; /* delete first two chars: _b */ relnode->bucketNode = (int4)pg_strtouint64(bucketNodestr, NULL, TENBASE); rc = strncpy_s(pathFirstpart, strlen(path) - strlen(bucketNodestr) - 1, path, - strlen(path) - strlen(bucketNodestr) - TWO); + strlen(path) - strlen(bucketNodestr) - TWO); securec_check(rc, "\0", "\0"); } } else { @@ -134,40 +165,63 @@ void PrepForRead(char *path, int64 blocknum, char *relation_type, char *outputFi } if (relfilenode.rnode.node.spcNode == 0) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - (errmsg("The tablespace oid is 0. Please check the first parameter path. " - "If you are not sure about the table path, please check pg_relation_filepath.")))); + (errmsg("The tablespace oid is 0. Please check the first parameter path. " + "If you are not sure about the table path, please check pg_relation_filepath.")))); RelFileNodeRelCopy(*relnode, relfilenode.rnode.node); relnode->opt = 0; char *pagesuffix = "page"; char *xlogsuffix = "xlog"; rc = snprintf_s(outputFilename + (int)strlen(outputFilename), MAXFILENAME, MAXFILENAME - 1, "%s/%u_%u_%u_%d.%s", - t_thrd.proc_cxt.DataDir, relnode->spcNode, relnode->dbNode, relnode->relNode, blocknum, - (parse_page ? pagesuffix : xlogsuffix)); + t_thrd.proc_cxt.DataDir, relnode->spcNode, relnode->dbNode, relnode->relNode, blocknum, + (parse_page ? pagesuffix : xlogsuffix)); securec_check_ss(rc, "\0", "\0"); pfree_ext(pathFirstpart); } +/* + * 功能:解析堆页面头信息并生成字符串输出 + * + * 参数列表: + * - page:堆页面头指针 + * - strOutput:用于存储输出信息的缓冲区 + * - blockNum:块号 + * - block_endpoint:块的结束号 + * 注意事项: + * - 该函数用于解析堆页面头的信息并将结果存储在输出字符串中。 + * - 使用 snprintf_s 函数格式化输出信息到 `strOutput` 缓冲区。 + * - 输出块号和块结束号作为页面信息的一部分。 + * - 计算和输出页面的 LSN(日志序列号)。 + * - 检查页面的校验和是否匹配。 + * - 输出页面标志的相关信息,如是否有空闲行指针、页面是否满、是否全部可见等。 + * - 输出页面的边界信息和大小。 + * - 输出页面的特殊数据块大小和版本信息。 + * - 输出页面头的其他信息,如 xid 基数、多版本基数、清理 xid 等。 + */ static void ParseHeapHeader(const PageHeader page, char *strOutput, BlockNumber blockNum, BlockNumber block_endpoint) { errno_t rc = EOK; + // 输出块号和块结束号 rc = snprintf_s(strOutput + (int)strlen(strOutput), MAXOUTPUTLEN, MAXOUTPUTLEN - 1, - "Page information of block %u/%u", blockNum, block_endpoint); + "Page information of block %u/%u", blockNum, block_endpoint); securec_check_ss(rc, "\0", "\0"); + // 输出页面的 LSN rc = snprintf_s(strOutput + (int)strlen(strOutput), MAXOUTPUTLEN, MAXOUTPUTLEN - 1, "\n\tpd_lsn: %X/%X", - (uint32)(PageGetLSN(page) >> XIDTHIRTYTWO), (uint32)PageGetLSN(page)); + (uint32)(PageGetLSN(page) >> XIDTHIRTYTWO), (uint32)PageGetLSN(page)); securec_check_ss(rc, "\0", "\0"); bool checksum_matched = false; + // 检查页面校验和是否匹配 if (CheckPageZeroCases(page)) { uint16 checksum = pg_checksum_page((char *)page, (BlockNumber)blockNum); checksum_matched = (checksum == page->pd_checksum); } rc = snprintf_s(strOutput + (int)strlen(strOutput), MAXOUTPUTLEN, MAXOUTPUTLEN - 1, - "\n\tpd_checksum: 0x%X, verify %s", page->pd_checksum, checksum_matched ? "success" : "fail"); + "\n\tpd_checksum: 0x%X, verify %s", page->pd_checksum, checksum_matched ? "success" : "fail"); securec_check_ss(rc, "\0", "\0"); rc = strcat_s(strOutput, MAXOUTPUTLEN, "\n\tpd_flags: "); securec_check(rc, "\0", "\0"); + // 输出页面标志的相关信息 if (PageHasFreeLinePointers(page)) { rc = strcat_s(strOutput, MAXOUTPUTLEN, "PD_HAS_FREE_LINES "); securec_check(rc, "\0", "\0"); @@ -192,79 +246,109 @@ static void ParseHeapHeader(const PageHeader page, char *strOutput, BlockNumber rc = strcat_s(strOutput, MAXOUTPUTLEN, "PD_ENCRYPT_PAGE "); securec_check(rc, "\0", "\0"); } + // 输出页面的边界信息和是否为空 rc = snprintf_s(strOutput + (int)strlen(strOutput), MAXOUTPUTLEN, MAXOUTPUTLEN - 1, "\n\tpd_lower: %u, %s", - page->pd_lower, PageIsEmpty(page) ? "empty" : "non-empty"); + page->pd_lower, PageIsEmpty(page) ? "empty" : "non-empty"); securec_check_ss(rc, "\0", "\0"); rc = snprintf_s(strOutput + (int)strlen(strOutput), MAXOUTPUTLEN, MAXOUTPUTLEN - 1, "\n\tpd_upper: %u, %s", - page->pd_upper, PageIsNew(page) ? "new" : "old"); + page->pd_upper, PageIsNew(page) ? "new" : "old"); securec_check_ss(rc, "\0", "\0"); + // 输出页面的特殊数据块大小和版本信息 rc = snprintf_s(strOutput + (int)strlen(strOutput), MAXOUTPUTLEN, MAXOUTPUTLEN - 1, "\n\tpd_special: %u, size %u", - page->pd_special, PageGetSpecialSize(page)); + page->pd_special, PageGetSpecialSize(page)); securec_check_ss(rc, "\0", "\0"); rc = snprintf_s(strOutput + (int)strlen(strOutput), MAXOUTPUTLEN, MAXOUTPUTLEN - 1, - "\n\tPage size & version: %u, %u", (uint16)PageGetPageSize(page), (uint16)PageGetPageLayoutVersion(page)); + "\n\tPage size & version: %u, %u", (uint16)PageGetPageSize(page), + (uint16)PageGetPageLayoutVersion(page)); securec_check_ss(rc, "\0", "\0"); + // 输出页面头的其他信息,如 xid 基数、多版本基数、清理 xid 等 rc = snprintf_s(strOutput + (int)strlen(strOutput), MAXOUTPUTLEN, MAXOUTPUTLEN - 1, - "\n\tpd_xid_base: %lu, pd_multi_base: %lu", ((HeapPageHeader)(page))->pd_xid_base, - ((HeapPageHeader)(page))->pd_multi_base); + "\n\tpd_xid_base: %lu, pd_multi_base: %lu", ((HeapPageHeader)(page))->pd_xid_base, + ((HeapPageHeader)(page))->pd_multi_base); securec_check_ss(rc, "\0", "\0"); rc = snprintf_s(strOutput + (int)strlen(strOutput), MAXOUTPUTLEN, MAXOUTPUTLEN - 1, "\n\tpd_prune_xid: %lu", - ((HeapPageHeader)(page))->pd_prune_xid + ((HeapPageHeader)(page))->pd_xid_base); + ((HeapPageHeader)(page))->pd_prune_xid + ((HeapPageHeader)(page))->pd_xid_base); securec_check_ss(rc, "\0", "\0"); } - +/* + * 功能:打印堆元组头的 infomask 信息 + * + * 参数列表: + * - tup:堆元组头指针 + * - strOutput:用于存储输出信息的缓冲区 + * + * 注意事项: + * - 该函数用于解析并打印堆元组头的 infomask 字段的信息。 + * - 使用 strcat_s 和 snprintf_s 函数将信息添加到 `strOutput` 缓冲区中。 + * - 输出包括 infomask 的各个标志位,如是否包含 NULL、是否有变宽字段、是否有外部字段等。 + * - 还输出了其他标志位,如是否包含 OID、是否压缩、是否为 ComboCID 等。 + * - 最后输出 t_infomask2 的信息,包括是否热更新、是否仅为唯一元组。 + */ static void PrintInfomask(HeapTupleHeader tup, char *strOutput) { errno_t rc = EOK; + // 输出是否包含 NULL 标志位 if (tup->t_infomask & HEAP_HASNULL) { rc = strcat_s(strOutput, MAXOUTPUTLEN, "HEAP_HASNULL "); securec_check(rc, "\0", "\0"); } + // 输出是否有变宽字段标志位 if (tup->t_infomask & HEAP_HASVARWIDTH) { rc = strcat_s(strOutput, MAXOUTPUTLEN, "HEAP_HASVARWIDTH "); securec_check(rc, "\0", "\0"); } + // 输出是否有外部字段标志位 if (tup->t_infomask & HEAP_HASEXTERNAL) { rc = strcat_s(strOutput, MAXOUTPUTLEN, "HEAP_HASEXTERNAL "); securec_check(rc, "\0", "\0"); } + // 输出是否包含 OID 以及 OID 的值 if (tup->t_infomask & HEAP_HASOID) { rc = snprintf_s(strOutput + (int)strlen(strOutput), MAXOUTPUTLEN, MAXOUTPUTLEN - 1, "HEAP_HASOID(%d) ", - HeapTupleHeaderGetOid(tup)); + HeapTupleHeaderGetOid(tup)); securec_check_ss(rc, "\0", "\0"); } + // 输出是否压缩标志位 if (tup->t_infomask & HEAP_COMPRESSED) { rc = strcat_s(strOutput, MAXOUTPUTLEN, "HEAP_COMPRESSED "); securec_check(rc, "\0", "\0"); } + // 输出是否为 ComboCID 标志位 if (tup->t_infomask & HEAP_COMBOCID) { rc = strcat_s(strOutput, MAXOUTPUTLEN, "HEAP_COMBOCID "); securec_check(rc, "\0", "\0"); } + // 输出是否具有排他锁标志位 if (tup->t_infomask & HEAP_XMAX_EXCL_LOCK) { rc = strcat_s(strOutput, MAXOUTPUTLEN, "HEAP_XMAX_EXCL_LOCK "); securec_check(rc, "\0", "\0"); } + // 输出是否具有共享锁标志位 if (tup->t_infomask & HEAP_XMAX_SHARED_LOCK) { rc = strcat_s(strOutput, MAXOUTPUTLEN, "HEAP_XMAX_SHARED_LOCK "); securec_check(rc, "\0", "\0"); } + // 输出是否 XMIN 为已提交标志位 if (tup->t_infomask & HEAP_XMIN_COMMITTED) { rc = strcat_s(strOutput, MAXOUTPUTLEN, "HEAP_XMIN_COMMITTED "); securec_check(rc, "\0", "\0"); } + // 输出是否 XMIN 为无效标志位 if (tup->t_infomask & HEAP_XMIN_INVALID) { rc = strcat_s(strOutput, MAXOUTPUTLEN, "HEAP_XMIN_INVALID "); securec_check(rc, "\0", "\0"); } + // 输出是否 XMAX 为已提交标志位 if (tup->t_infomask & HEAP_XMAX_COMMITTED) { rc = strcat_s(strOutput, MAXOUTPUTLEN, "HEAP_XMAX_COMMITTED "); securec_check(rc, "\0", "\0"); } + // 输出是否 XMAX 为无效标志位 if (tup->t_infomask & HEAP_XMAX_INVALID) { rc = strcat_s(strOutput, MAXOUTPUTLEN, "HEAP_XMAX_INVALID "); securec_check(rc, "\0", "\0"); } + // 输出是否 XMAX 为多版本标志位 if (tup->t_infomask & HEAP_XMAX_IS_MULTI) { rc = strcat_s(strOutput, MAXOUTPUTLEN, "HEAP_XMAX_IS_MULTI "); securec_check(rc, "\0", "\0"); @@ -291,154 +375,276 @@ static void PrintInfomask(HeapTupleHeader tup, char *strOutput) securec_check(rc, "\0", "\0"); } } - +/* + * 功能:解析并打印堆元组头的信息 + * + * 参数列表: + * page:页头指针,包含堆元组的信息 + * lineno:元组在页中的行号 + * strOutput:用于存储输出信息的缓冲区 + * + * 注意事项: + * - 该函数用于解析并打印堆元组的信息,包括元组编号、长度、偏移量等。 + * - 使用 snprintf_s 函数将信息添加到 `strOutput` 缓冲区中。 + * - 输出包括元组编号、长度、偏移量、Xmin、Xmax、CID 等信息。 + * - 还输出了 ctid(块号和偏移量)以及 t_infomask 的信息。 + * - 调用 `PrintInfomask` 函数打印 t_infomask 中各个标志位的信息。 + * - 输出属性数量和 t_hoff(属性偏移量)。 + * - 最后输出 t_bits 的信息,通过调用 `formatBitmap` 函数打印位图信息。 + */ static void ParseTupleHeader(const PageHeader page, uint lineno, char *strOutput) { errno_t rc = EOK; + // 获取元组对应的 ItemId ItemId lp = PageGetItemId(page, lineno); + // 输出元组的基本信息,包括编号、长度和偏移量 rc = snprintf_s(strOutput + (int)strlen(strOutput), MAXOUTPUTLEN, MAXOUTPUTLEN - 1, - "\n\n\t\tTuple #%u is normal: length %u, offset %u", lineno, ItemIdGetLength(lp), ItemIdGetOffset(lp)); + "\n\n\t\tTuple #%u is normal: length %u, offset %u", lineno, ItemIdGetLength(lp), + ItemIdGetOffset(lp)); securec_check_ss(rc, "\0", "\0"); + // 创建一个虚拟 HeapTupleData 结构 HeapTupleData dummyTuple; HeapTupleHeader tup = (HeapTupleHeader)(PageGetItem(page, lp)); dummyTuple.t_data = tup; dummyTuple.t_xid_base = ((HeapPageHeader)(page))->pd_xid_base; dummyTuple.t_multi_base = ((HeapPageHeader)(page))->pd_multi_base; + // 输出 Xmin、Xmax 和 CID 的信息 rc = snprintf_s(strOutput + (int)strlen(strOutput), MAXOUTPUTLEN, MAXOUTPUTLEN - 1, - "\n\t\t\tt_xmin/t_xmax/t_cid: %lu/%lu/%u", HeapTupleGetRawXmin(&dummyTuple), HeapTupleGetRawXmax(&dummyTuple), - HeapTupleHeaderGetRawCommandId(tup)); + "\n\t\t\tt_xmin/t_xmax/t_cid: %lu/%lu/%u", HeapTupleGetRawXmin(&dummyTuple), + HeapTupleGetRawXmax(&dummyTuple), HeapTupleHeaderGetRawCommandId(tup)); securec_check_ss(rc, "\0", "\0"); + // 输出 ctid(块号和偏移量)的信息 rc = snprintf_s(strOutput + (int)strlen(strOutput), MAXOUTPUTLEN, MAXOUTPUTLEN - 1, - "\n\t\t\tctid:(block %u/%u, offset %u)", tup->t_ctid.ip_blkid.bi_hi, tup->t_ctid.ip_blkid.bi_lo, - tup->t_ctid.ip_posid); + "\n\t\t\tctid:(block %u/%u, offset %u)", tup->t_ctid.ip_blkid.bi_hi, tup->t_ctid.ip_blkid.bi_lo, + tup->t_ctid.ip_posid); securec_check_ss(rc, "\0", "\0"); + // 输出 t_infomask 的信息 rc = strcat_s(strOutput, MAXOUTPUTLEN, "\n\t\t\tt_infomask: "); securec_check(rc, "\0", "\0"); + // 调用 PrintInfomask 函数打印 t_infomask 的各个标志位信息 PrintInfomask(tup, strOutput); + // 输出属性数量 rc = snprintf_s(strOutput + (int)strlen(strOutput), MAXOUTPUTLEN, MAXOUTPUTLEN - 1, "Attrs Num: %d", - HeapTupleHeaderGetNatts(tup, NULL)); + HeapTupleHeaderGetNatts(tup, NULL)); securec_check_ss(rc, "\0", "\0"); + // 输出属性偏移量 rc = snprintf_s(strOutput + (int)strlen(strOutput), MAXOUTPUTLEN, MAXOUTPUTLEN - 1, "\n\t\t\tt_hoff: %u", - tup->t_hoff); + tup->t_hoff); securec_check_ss(rc, "\0", "\0"); + // 输出 t_bits 的位图信息 rc = strcat_s(strOutput, MAXOUTPUTLEN, "\n\t\t\tt_bits: "); securec_check(rc, "\0", "\0"); + // 调用 formatBitmap 函数打印位图信息 formatBitmap((const unsigned char *)tup->t_bits, BITMAPLEN(HeapTupleHeaderGetNatts(tup, NULL)), 'V', 'N', - strOutput); + strOutput); } - +/* + * 功能:解析并打印堆页面的信息,包括页头和元组信息 + * + * 参数列表: + * page:页头指针,包含堆页面的信息 + * blockNum:当前页面的块编号 + * strOutput:用于存储输出信息的缓冲区 + * block_endpoint:块的结束位置 + * + * 注意事项: + * - 该函数用于解析并打印堆页面的信息,包括页头信息、元组头信息以及元组数据。 + * - 使用 snprintf_s 函数将信息添加到 `strOutput` 缓冲区中。 + * - 首先检查页面指针是否有效,以及页面数据是否损坏。 + * - 调用 `ParseHeapHeader` 函数打印页面的页头信息。 + * - 然后解析元组头信息,包括元组编号、长度、偏移量、Xmin、Xmax、CID 等信息。 + * - 最后输出元组的总数、正常元组数、未使用元组数和死亡元组数。 + */ static void ParseHeapPage(const PageHeader page, BlockNumber blockNum, char *strOutput, BlockNumber block_endpoint) { errno_t rc = EOK; + // 检查页面指针是否有效,以及页面数据是否损坏 if (page->pd_lower < GetPageHeaderSize(page) || page->pd_lower > page->pd_upper || page->pd_upper > page->pd_special || page->pd_special > BLCKSZ || page->pd_special != MAXALIGN(page->pd_special)) { - rc = snprintf_s(strOutput + (int)strlen(strOutput), - MAXOUTPUTLEN, - MAXOUTPUTLEN - 1, - "The page data is corrupted, corrupted page pointers: lower = %u, upper = %u, special = %u\n", - page->pd_lower, - page->pd_upper, - page->pd_special); + rc = snprintf_s(strOutput + (int)strlen(strOutput), MAXOUTPUTLEN, MAXOUTPUTLEN - 1, + "The page data is corrupted, corrupted page pointers: lower = %u, upper = %u, special = %u\n", + page->pd_lower, page->pd_upper, page->pd_special); securec_check_ss(rc, "\0", "\0"); return; } + // 解析并打印堆页面的页头信息 ParseHeapHeader(page, strOutput, blockNum, block_endpoint); /* parse tuple header */ + // 输出堆页面中的元组信息部分标题 rc = strcat_s(strOutput, MAXOUTPUTLEN, "\n\n\tHeap tuple information on this page"); securec_check(rc, "\0", "\0"); + // 获取页面中的元组总数 uint nline = PageGetMaxOffsetNumber((Page)page); uint nunused = 0, nnormal = 0, ndead = 0; + // 遍历页面中的每个元组 for (uint i = (OffsetNumber)1; i <= nline; i++) { ItemId lp = PageGetItemId(page, i); + // 如果元组标记为正常 if (ItemIdIsNormal(lp)) { nnormal++; + // 解析并打印元组头信息 ParseTupleHeader(page, i, strOutput); - } else if (ItemIdIsDead(lp)) { + } + // 如果元组标记为死亡 + else if (ItemIdIsDead(lp)) { ndead++; + // 如果元组标记为死亡 rc = snprintf_s(strOutput + (int)strlen(strOutput), MAXOUTPUTLEN, MAXOUTPUTLEN - 1, - "\n\n\t\tTuple #%u is dead: length %u, offset %u", i, ItemIdGetLength(lp), ItemIdGetOffset(lp)); + "\n\n\t\tTuple #%u is dead: length %u, offset %u", i, ItemIdGetLength(lp), + ItemIdGetOffset(lp)); securec_check_ss(rc, "\0", "\0"); - } else { + } + // 如果元组标记为未使用 + else { nunused++; + // 输出未使用元组的信息,包括编号 rc = snprintf_s(strOutput + (int)strlen(strOutput), MAXOUTPUTLEN, MAXOUTPUTLEN - 1, - "\n\n\t\tTuple #%u is nunused: ", i); + "\n\n\t\tTuple #%u is nunused: ", i); securec_check_ss(rc, "\0", "\0"); } } - rc = snprintf_s(strOutput + (int)strlen(strOutput), MAXOUTPUTLEN, MAXOUTPUTLEN - 1, + // 输出元组的总数、正常元组数、未使用元组数和死亡元组数的汇总信息 + rc = snprintf_s( + strOutput + (int)strlen(strOutput), MAXOUTPUTLEN, MAXOUTPUTLEN - 1, "\n\tSummary (%u total): %u normal, %u unused, %u dead\n\nNormal Heap Page, special space is %u\n\n", nline, nnormal, nunused, ndead, (uint)PageGetSpecialSize(page)); securec_check_ss(rc, "\0", "\0"); } - +/* + * 功能:解析并打印一个页面的信息,根据关系类型不同采用不同的解析方式 + * + * 参数列表: + * page:页头指针,包含要解析的页面的信息 + * blockNum:当前页面的块编号 + * strOutput:用于存储输出信息的缓冲区 + * relation_type:页面所属的关系类型,如 "heap", "uheap", "btree" 等 + * block_endpoint:块的结束位置 + * + * 注意事项: + * - 该函数根据关系类型选择不同的解析方式,支持的关系类型包括 "heap", "uheap", "btree", "ubtree", "segment"。 + * - 使用 strcmp 函数比较关系类型,根据不同类型选择相应的解析函数。 + * - 如果关系类型不在支持范围内,将产生错误报告。 + */ static void ParseOnePage(const PageHeader page, BlockNumber blockNum, char *strOutput, char *relation_type, - BlockNumber block_endpoint) + BlockNumber block_endpoint) { errno_t rc = EOK; + // 检查关系类型并根据不同类型选择相应的解析方式 if (strcmp(relation_type, "heap") == 0) { + // 检查关系类型并根据不同类型选择相应的解析方式 if (PG_HEAP_PAGE_LAYOUT_VERSION != (uint16)PageGetPageLayoutVersion(page) || PageGetSpecialSize(page) != 0) { + // 检查页面的布局版本和特殊空间大小,如果不符合预期,产生错误报告 ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - (errmsg("The target page is not heap, the given page version is: %u", - (uint16)PageGetPageLayoutVersion(page))))); + (errmsg("The target page is not heap, the given page version is: %u", + (uint16)PageGetPageLayoutVersion(page))))); } + // 调用解析堆页面的函数 ParseHeapPage(page, blockNum, strOutput, block_endpoint); } else if (strcmp(relation_type, "uheap") == 0) { + // 如果是无锁堆关系类型 if (PG_UHEAP_PAGE_LAYOUT_VERSION != (uint16)PageGetPageLayoutVersion(page) || PageGetSpecialSize(page) != 0) { + // 检查页面的布局版本和特殊空间大小,如果不符合预期,产生错误报告 ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - (errmsg("The target page is not uheap, the given page version is: %u", - (uint16)PageGetPageLayoutVersion(page))))); + (errmsg("The target page is not uheap, the given page version is: %u", + (uint16)PageGetPageLayoutVersion(page))))); } + // 调用解析无锁堆页面的函数 ParseUHeapPage((void *)page, blockNum, block_endpoint, strOutput); } else if (strcmp(relation_type, "btree") == 0) { + // 如果是B树关系类型 if (PG_COMM_PAGE_LAYOUT_VERSION != (uint16)PageGetPageLayoutVersion(page) || PageGetSpecialSize(page) == 0) { + // 检查页面的布局版本和特殊空间大小,如果不符合预期,产生错误报告 ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - (errmsg("The target page is not btree, the given page version is: %u", - (uint16)PageGetPageLayoutVersion(page))))); + (errmsg("The target page is not btree, the given page version is: %u", + (uint16)PageGetPageLayoutVersion(page))))); } + // 调用解析B树页面的函数 ParseIndexPage((void *)page, BTREE_INDEX, blockNum, block_endpoint, strOutput); } else if (strcmp(relation_type, "ubtree") == 0) { + // 如果是无锁B树关系类型 if (PG_COMM_PAGE_LAYOUT_VERSION != (uint16)PageGetPageLayoutVersion(page) || PageGetSpecialSize(page) == 0) { + // 检查页面的布局版本和特殊空间大小,如果不符合预期,产生错误报告 ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - (errmsg("The target page is not ubtree, the given page version is: %u", - (uint16)PageGetPageLayoutVersion(page))))); + (errmsg("The target page is not ubtree, the given page version is: %u", + (uint16)PageGetPageLayoutVersion(page))))); } + // 调用解析无锁B树页面的函数 ParseIndexPage((void *)page, UBTREE_INDEX, blockNum, block_endpoint, strOutput); } else if (strcmp(relation_type, "segment") == 0) { + // 如果是段关系类型 rc = strcat_s(strOutput, MAXOUTPUTLEN, "Parse segment table."); securec_check(rc, "\0", "\0"); + // 调用解析堆页面的函数 ParseHeapPage(page, blockNum, strOutput, block_endpoint); } else { + // 如果关系类型不在支持范围内,产生错误报告 ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_PARAMETER), (errmsg("Only support heap, uheap, btree, ubtree, segment.")))); + (errcode(ERRCODE_UNDEFINED_PARAMETER), (errmsg("Only support heap, uheap, btree, ubtree, segment.")))); } } - +/* + * 功能:检查SMGR读取状态,根据状态不同生成相应的输出信息 + * + * 参数列表: + * rdStatus:SMGR读取状态,表示读取页面的结果状态 + * blockNum:当前页面的块编号 + * page:页头指针,包含要检查的页面的信息 + * strOutput1:用于存储输出信息的缓冲区 + * + * 注意事项: + * - 该函数根据SMGR读取状态生成不同的输出信息,主要用于检查页面的读取状态。 + * - 如果SMGR读取状态为SMGR_RD_CRC_ERROR,表示页面校验失败,会计算校验和并与期望的校验和进行比较。 + * - 如果SMGR读取状态为SMGR_RD_NO_BLOCK,表示页面不存在。 + * - 输出信息存储在strOutput1缓冲区中。 + */ static void check_rdStatus(SMGR_READ_STATUS rdStatus, BlockNumber blockNum, const PageHeader page, char *strOutput1) { errno_t rc = EOK; if (rdStatus == SMGR_RD_CRC_ERROR) { + // 如果SMGR读取状态为CRC校验错误 uint16 checksum = pg_checksum_page((char *)page, blockNum); + // 生成包含校验结果的输出信息 rc = snprintf_s(strOutput1, SHORTOUTPUTLEN, SHORTOUTPUTLEN - 1, "\nFor page %u, page verification failed, calculated checksum 0x%X but expected 0x%X.\n", blockNum, checksum, page->pd_checksum); securec_check_ss(rc, "\0", "\0"); } else if (rdStatus == SMGR_RD_NO_BLOCK) { + // 如果SMGR读取状态为未找到块 + // 生成页面不存在的输出信息 rc = snprintf_s(strOutput1, SHORTOUTPUTLEN, SHORTOUTPUTLEN - 1, "\tThe page %u does not exist.\n", blockNum); securec_check_ss(rc, "\0", "\0"); } } - +/* + * 功能:从内存中读取页面内容,如果页面在内存中存在,则生成输出信息并返回true;否则返回false + * + * 参数列表: + * smgr:SMgrRelation结构,表示存储管理器关系 + * forkNum:ForkNumber,表示页面所属的Fork类型 + * blockNum:BlockNumber,表示页面的块编号 + * relation_type:关系类型的字符串,如 "segment"、"heap"、"uheap" 等 + * strOutput:用于存储输出信息的缓冲区 + * outputfile:文件指针,用于输出信息到文件 + * outputFilename:输出文件的名称 + * + * 注意事项: + * - 该函数首先检查页面是否在内存中存在,如果存在则生成输出信息,并返回true;否则返回false。 + * - 如果页面在内存中存在,函数会根据关系类型调用不同的解析函数生成输出信息。 + * - 函数会使用锁来避免并发读/写。 + * - 生成的输出信息存储在strOutput缓冲区中,然后输出到文件中。 + * - 函数返回true表示成功读取页面并生成了输出信息,返回false表示页面不存在或读取失败。 + */ static bool readFromMemory(SMgrRelation smgr, ForkNumber forkNum, BlockNumber blockNum, char *relation_type, - char *strOutput, FILE *outputfile, char *outputFilename) + char *strOutput, FILE *outputfile, char *outputFilename) { errno_t rc = EOK; SegPageLocation loc; @@ -485,52 +691,101 @@ static bool readFromMemory(SMgrRelation smgr, ForkNumber forkNum, BlockNumber bl LWLockRelease(partition_lock); return false; } - +/* + * 功能:检查段文件的有效性和一致性 + * + * 参数列表: + * relnode:RelFileNode 结构,表示段文件的节点信息 + * forkNum:ForkNumber,表示段文件的Fork类型 + * + * 注意事项: + * - 该函数用于检查段文件的有效性和一致性,确保页面存在,并且页头与段类型匹配。 + * - 如果段文件不存在或页面不匹配,则函数会抛出错误。 + */ static void CheckSegment(RelFileNode *relnode, ForkNumber forkNum) { SegSpace *spc = spc_open(relnode->spcNode, relnode->dbNode, false); + // 检查段是否存在 if (spc == NULL || !spc_datafile_exist(spc, 1, forkNum)) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), (errmsg("Page doesn't exist.")))); BlockNumber size = spc_size(spc, 1, forkNum); + // 检查目标页面编号是否在有效范围内 if (relnode->relNode >= size) - ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - (errmsg("The target page %u doesn't exist, the current max is %u.", relnode->relNode, size - 1)))); + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + (errmsg("The target page %u doesn't exist, the current max is %u.", relnode->relNode, size - 1)))); + // 创建用于访问段头的RelFileNode RelFileNode *relnodeHead = (RelFileNode *)palloc(sizeof(RelFileNode)); relnodeHead->spcNode = relnode->spcNode; relnodeHead->dbNode = relnode->dbNode; - relnodeHead->relNode = 1; + relnodeHead->relNode = 1; // 段头的块编号为1 relnodeHead->bucketNode = relnode->bucketNode; relnodeHead->opt = relnode->opt; + // 读取段头的缓冲区 Buffer buffer_temp = ReadBufferFast(spc, *relnodeHead, forkNum, relnode->relNode, RBM_NORMAL); + // 检查段头缓冲区是否有效 if (!BufferIsValid(buffer_temp)) - ereport(ERROR, (errcode_for_file_access(), errmsg("Segment Head is invalid %u/%u/%u %d %u", - relnodeHead->spcNode, relnodeHead->dbNode, relnodeHead->relNode, forkNum, relnode->relNode))); + ereport(ERROR, (errcode_for_file_access(), + errmsg("Segment Head is invalid %u/%u/%u %d %u", relnodeHead->spcNode, relnodeHead->dbNode, + relnodeHead->relNode, forkNum, relnode->relNode))); + // 释放段头缓冲区 pfree_ext(relnodeHead); SegmentHead *head = (SegmentHead *)PageGetContents(BufferGetPage(buffer_temp)); SegReleaseBuffer(buffer_temp); + // 检查段头是否匹配段类型 if (!(IsNormalSegmentHead(head) && relnode->bucketNode == SegmentBktId) && !(IsBucketMainHead(head) && IsBucketFileNode(*relnode))) - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - (errmsg("Page header does not match with corresponding segment type.")))); + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + (errmsg("Page header does not match with corresponding segment type.")))); } - +/* + * 功能:验证参数路径与文件路径的一致性 + * + * 参数列表: + * rnode:RelFileNode 结构,表示文件的节点信息 + * str:char 指针,表示要验证的文件路径字符串 + * + * 注意事项: + * - 该函数用于验证参数路径与实际文件路径是否一致,如果不一致则抛出错误。 + * - 函数首先根据 rnode 构建实际文件路径,然后将其与传入的 str 进行比较。 + * - 如果不一致,则抛出错误。 + */ void ValidateParameterPath(RelFileNode rnode, char *str) { + // 根据 rnode 构建实际文件路径 char *path = relpathbackend(rnode, InvalidBackendId, MAIN_FORKNUM); + // 检查参数路径与实际文件路径是否一致,如果不一致则抛出错误 if ((strcmp(path, str) != 0)) ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("\"%s\" is invalid input", str))); } - +/* + * 功能:解析指定文件的一个或多个页面,输出解析结果到文件 + * + * 参数列表: + * path:char 指针,表示要解析的文件路径 + * blocknum:int64,表示要解析的页面的块号,-1 表示解析所有页面 + * relation_type:char 指针,表示文件的关系类型 + * read_memory:bool,表示是否尝试从内存中读取页面 + * + * 注意事项: + * - 该函数用于解析指定文件的一个或多个页面,输出解析结果到文件。 + * - 函数首先验证参数,然后初始化一些必要的数据结构。 + * - 如果指定的页面在内存中可用且 read_memory 为 true,则尝试从内存中读取页面, + * 否则从磁盘中读取页面。 + * - 如果解析所有页面,则循环读取并解析所有页面。 + * - 最后,函数返回输出结果的文件路径。 + */ char *ParsePage(char *path, int64 blocknum, char *relation_type, bool read_memory) { + // 验证参数是否合法 errno_t rc = EOK; /* check parameters */ if (blocknum > MaxBlockNumber || blocknum < -1) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - (errmsg("Blocknum should be between -1 and %u.", MaxBlockNumber)))); + (errmsg("Blocknum should be between -1 and %u.", MaxBlockNumber)))); /* initialize */ + // 初始化输出字符串和文件相关的数据结构 char *strOutput = (char *)palloc(MAXOUTPUTLEN * sizeof(char)); rc = memset_s(strOutput, MAXOUTPUTLEN, 0, MAXOUTPUTLEN); securec_check(rc, "\0", "\0"); @@ -541,6 +796,7 @@ char *ParsePage(char *path, int64 blocknum, char *relation_type, bool read_memor rc = memset_s(outputFilename, MAXFILENAME, 0, MAXFILENAME); securec_check(rc, "\0", "\0"); + // 准备解析所需的数据 PrepForRead(path, blocknum, relation_type, outputFilename, relnode, true); ValidateParameterPath(*relnode, path); @@ -549,21 +805,25 @@ char *ParsePage(char *path, int64 blocknum, char *relation_type, bool read_memor ForkNumber forkNum = MAIN_FORKNUM; SMgrRelation smgr = smgropen(*relnode, InvalidBackendId, GetColumnNum(forkNum)); + // 针对特殊情况进行验证 if (strcmp(relation_type, "segment") == 0) CheckSegment(relnode, forkNum); BlockNumber maxBlockNum = smgrnblocks(smgr, forkNum) - 1; + // 验证 blocknum 是否在合法范围内 if (blocknum > maxBlockNum) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - (errmsg("Blocknum should be between -1 and %u.", maxBlockNum)))); + (errmsg("Blocknum should be between -1 and %u.", maxBlockNum)))); pfree_ext(relnode); BlockNumber blockNum = 0; BlockNumber block_endpoint = 0; /* only parse one block, check if it is in memory */ + // 如果 read_memory 为 true,则尝试从内存中读取页面 if (read_memory) { blockNum = (BlockNumber)blocknum; if (readFromMemory(smgr, forkNum, blockNum, relation_type, strOutput, outputfile, outputFilename)) { /* found in memory */ + // 从内存中找到页面,关闭文件并返回 CheckCloseFile(fclose(outputfile), outputFilename); smgrcloseall(); return outputFilename; @@ -574,6 +834,7 @@ char *ParsePage(char *path, int64 blocknum, char *relation_type, bool read_memor securec_check(rc, "\0", "\0"); CheckWriteFile(fwrite(strOutput, 1, strlen(strOutput), outputfile), strlen(strOutput), outputFilename); pfree_ext(strOutput); + // 如果 blocknum >= 0,只解析一个页面,否则解析所有页面 if (blocknum >= 0) { /* only parse one block */ blockNum = blocknum; block_endpoint = blocknum; @@ -581,19 +842,24 @@ char *ParsePage(char *path, int64 blocknum, char *relation_type, bool read_memor block_endpoint = maxBlockNum; } /* if not declare a single block, then loop all blocks */ + // 循环读取并解析页面 while (blockNum <= block_endpoint) { char *strOutput = (char *)palloc(MAXOUTPUTLEN * sizeof(char)); rc = memset_s(strOutput, MAXOUTPUTLEN, 0, MAXOUTPUTLEN); securec_check(rc, "\0", "\0"); char *buffer = (char *)palloc0(BLCKSZ); + // 从磁盘中读取页面 SMGR_READ_STATUS rdStatus = smgrread(smgr, forkNum, blockNum, buffer); const PageHeader page = (const PageHeader)buffer; char *strOutput1 = (char *)palloc(MAXOUTPUTLEN * sizeof(char)); rc = memset_s(strOutput1, SHORTOUTPUTLEN, 0, SHORTOUTPUTLEN); securec_check(rc, "\0", "\0"); + // 检查读取页面的状态 check_rdStatus(rdStatus, blockNum, page, strOutput1); + // 将解析结果写入文件 CheckWriteFile(fwrite(strOutput1, 1, strlen(strOutput1), outputfile), strlen(strOutput1), outputFilename); pfree_ext(strOutput1); + // 根据关系类型解析页面 if (strcmp(relation_type, "segment") == 0) { SegPageLocation loc = seg_get_physical_location(smgr->smgr_rnode.node, forkNum, blockNum); SegPageLocation endloc = seg_get_physical_location(smgr->smgr_rnode.node, forkNum, block_endpoint); @@ -601,16 +867,30 @@ char *ParsePage(char *path, int64 blocknum, char *relation_type, bool read_memor } else ParseOnePage(page, blockNum, strOutput, relation_type, block_endpoint); + // 将解析结果写入文件 CheckWriteFile(fwrite(strOutput, 1, strlen(strOutput), outputfile), strlen(strOutput), outputFilename); pfree_ext(strOutput); pfree_ext(buffer); blockNum++; } + // 关闭文件和释放资源 CheckCloseFile(fclose(outputfile), outputFilename); smgrcloseall(); return outputFilename; } - +/* + * 功能:解析 UHeap 页面头部信息 + * + * 参数列表: + * page:void 指针,表示要解析的页面 + * blkno:BlockNumber,表示页面的块号 + * endBlk:BlockNumber,表示结束块号 + * output:char 指针,表示输出解析结果的字符串 + * + * 注意事项: + * - 该函数用于解析 UHeap 页面的头部信息,包括页面的各种属性。 + * - 函数将解析结果输出到指定的字符串 `output` 中。 + */ static void ParseUHeapPageHeader(void *page, BlockNumber blkno, BlockNumber endBlk, char *output) { errno_t rc = EOK; @@ -621,23 +901,28 @@ static void ParseUHeapPageHeader(void *page, BlockNumber blkno, BlockNumber endB return; } + // 将输入的页面转换为 UHeapPageHeader 类型 pageHeader = (UHeapPageHeader)page; + // 输出页面块号信息 rc = snprintf_s(output + (int)strlen(output), MAXOUTPUTLEN, MAXOUTPUTLEN, "Page information of block %u/%u\n", - blkno, endBlk); + blkno, endBlk); securec_check_ss(rc, "\0", "\0"); + // 输出 LSN 信息 rc = snprintf_s(output + (int)strlen(output), MAXOUTPUTLEN, MAXOUTPUTLEN, "\tpd_lsn: %X/%X\n", - (uint32)(PageGetLSN(pageHeader) >> XIDTHIRTYTWO), (uint32)PageGetLSN(pageHeader)); + (uint32)(PageGetLSN(pageHeader) >> XIDTHIRTYTWO), (uint32)PageGetLSN(pageHeader)); securec_check_ss(rc, "\0", "\0"); + // 如果页面的校验和不为空,验证校验和 if (CheckPageZeroCases((PageHeader)pageHeader)) { uint16 checksum = pg_checksum_page((char *)pageHeader, (BlockNumber)blkno); chksumResult = (checksum == pageHeader->pd_checksum); } rc = snprintf_s(output + (int)strlen(output), MAXOUTPUTLEN, MAXOUTPUTLEN, "\tpd_checksum: 0x%X, verify %s\n", - pageHeader->pd_checksum, chksumResult ? "success" : "fail"); + pageHeader->pd_checksum, chksumResult ? "success" : "fail"); securec_check_ss(rc, "\0", "\0"); + // 输出页面的其他属性信息 if (UPageHasFreeLinePointers(pageHeader)) { rc = snprintf_s(output + (int)strlen(output), MAXOUTPUTLEN, MAXOUTPUTLEN, "PAGE_HAS_FREE_LINES."); securec_check_ss(rc, "\0", "\0"); @@ -649,27 +934,39 @@ static void ParseUHeapPageHeader(void *page, BlockNumber blkno, BlockNumber endB } rc = snprintf_s(output + (int)strlen(output), MAXOUTPUTLEN, MAXOUTPUTLEN, - "\tpd_lower: %u, %s\n\tpd_upper: %u, %s\n\tpd_special: %u, size %u\n", pageHeader->pd_lower, - PageIsEmpty(pageHeader) ? "empty" : "non-empty", pageHeader->pd_upper, - PageIsNew(pageHeader) ? "new page" : "old page", pageHeader->pd_special, PageGetSpecialSize(pageHeader)); + "\tpd_lower: %u, %s\n\tpd_upper: %u, %s\n\tpd_special: %u, size %u\n", pageHeader->pd_lower, + PageIsEmpty(pageHeader) ? "empty" : "non-empty", pageHeader->pd_upper, + PageIsNew(pageHeader) ? "new page" : "old page", pageHeader->pd_special, + PageGetSpecialSize(pageHeader)); securec_check_ss(rc, "\0", "\0"); rc = snprintf_s(output + (int)strlen(output), MAXOUTPUTLEN, MAXOUTPUTLEN, "\tPage size & version: %u, %u\n", - (uint16)PageGetPageSize(pageHeader), (uint16)PageGetPageLayoutVersion(pageHeader)); + (uint16)PageGetPageSize(pageHeader), (uint16)PageGetPageLayoutVersion(pageHeader)); securec_check_ss(rc, "\0", "\0"); rc = snprintf_s(output + (int)strlen(output), MAXOUTPUTLEN, MAXOUTPUTLEN, "\tpotential_freespace: %u\n", - pageHeader->potential_freespace); + pageHeader->potential_freespace); securec_check_ss(rc, "\0", "\0"); rc = snprintf_s(output + (int)strlen(output), MAXOUTPUTLEN, MAXOUTPUTLEN, "\ttd_count: %u\n", pageHeader->td_count); securec_check_ss(rc, "\0", "\0"); rc = snprintf_s(output + (int)strlen(output), MAXOUTPUTLEN, MAXOUTPUTLEN, "\tpd_prune_xid: %lu\n", - pageHeader->pd_prune_xid); + pageHeader->pd_prune_xid); securec_check_ss(rc, "\0", "\0"); rc = snprintf_s(output + (int)strlen(output), MAXOUTPUTLEN, MAXOUTPUTLEN, - "\tpd_xid_base: %lu, pd_multi_base: %lu\n", pageHeader->pd_xid_base, pageHeader->pd_multi_base); + "\tpd_xid_base: %lu, pd_multi_base: %lu\n", pageHeader->pd_xid_base, pageHeader->pd_multi_base); securec_check_ss(rc, "\0", "\0"); return; } - +/* + * 功能:解析 UHeap 页面中的 TD(Transaction Delta)信息 + * + * 参数列表: + * page:void 指针,表示要解析的页面 + * output:char 指针,表示输出解析结果的字符串 + * + * 注意事项: + * - 该函数用于解析 UHeap 页面中的 TD 信息,包括 TD Slot 的数量、每个 TD Slot 的事务 + * ID(xid)和撤销记录指针(undo_record_ptr)。 + * - 函数将解析结果输出到指定的字符串 `output` 中。 + */ static void ParseUHeapPageTDInfo(void *page, char *output) { TD *tdInfo = NULL; @@ -682,37 +979,56 @@ static void ParseUHeapPageTDInfo(void *page, char *output) return; } + // 将输入的页面转换为 UHeapPageHeader 类型 pageHeader = (UHeapPageHeader)page; + // 获取页面中的 TD 信息 tdPtr = (UHeapPageTDData *)PageGetTDPointer(page); tdCount = pageHeader->td_count; + // 输出 TD 信息的标题 rc = snprintf_s(output + (int)strlen(output), MAXOUTPUTLEN, MAXOUTPUTLEN, - "\n\n\tUHeap Page TD information, nTDSlots = %u\n", tdCount); + "\n\n\tUHeap Page TD information, nTDSlots = %u\n", tdCount); securec_check_ss(rc, "\0", "\0"); + // 遍历每个 TD Slot,输出事务 ID(xid)和撤销记录指针(undo_record_ptr) for (uint16 i = 0; i < tdCount; i++) { tdInfo = &(tdPtr->td_info[i]); rc = snprintf_s(output + (int)strlen(output), MAXOUTPUTLEN, MAXOUTPUTLEN, - "\n\t\t TD Slot #%d, xid:%lu, urp:%lu\n", i + 1, tdInfo->xactid, tdInfo->undo_record_ptr); + "\n\t\t TD Slot #%d, xid:%lu, urp:%lu\n", i + 1, tdInfo->xactid, tdInfo->undo_record_ptr); securec_check_ss(rc, "\0", "\0"); } return; } - +/* + * 功能:解析 UHeap 页面中的项(item)信息 + * + * 参数列表: + * item:Item,表示要解析的项 + * tuple:UHeapTuple,表示 UHeap 元组 + * output:char 指针,表示输出解析结果的字符串 + * + * 注意事项: + * - 该函数用于解析 UHeap 页面中的项,包括项的属性如事务 ID(xid)、锁定者的 TD(Transaction Delta)ID 等。 + * - 函数将解析结果输出到指定的字符串 `output` 中。 + */ static void ParseUHeapPageItem(Item item, UHeapTuple tuple, char *output) { if (item == NULL || tuple == NULL || output == NULL) { return; } + // 将输入的项转换为 UHeapDiskTuple 类型 UHeapDiskTuple diskTuple = (UHeapDiskTuple)item; errno_t rc = EOK; + // 将元组的 disk_tuple 指针设置为解析后的 UHeapDiskTuple tuple->disk_tuple = diskTuple; + // 输出项的信息,包括事务 ID(xid)、TD ID、锁定者 TD ID 等 rc = snprintf_s(output + (int)strlen(output), MAXOUTPUTLEN, MAXOUTPUTLEN, "\t\t\txid:%lu, td:%d, locker_td %d\n", - UHeapTupleGetRawXid(tuple), diskTuple->td_id, diskTuple->locker_td_id); + UHeapTupleGetRawXid(tuple), diskTuple->td_id, diskTuple->locker_td_id); securec_check_ss(rc, "\0", "\0"); rc = snprintf_s(output + (int)strlen(output), MAXOUTPUTLEN, MAXOUTPUTLEN, "\t\t\tFlag:%u", diskTuple->flag); securec_check_ss(rc, "\0", "\0"); + // 检查项的标志并输出相应的信息 if (diskTuple->flag & UHEAP_HAS_NULL) { rc = snprintf_s(output + (int)strlen(output), MAXOUTPUTLEN, MAXOUTPUTLEN, "\n\t\t\tUHEAP_HASNULL"); securec_check_ss(rc, "\0", "\0"); @@ -755,20 +1071,31 @@ static void ParseUHeapPageItem(Item item, UHeapTuple tuple, char *output) } if (diskTuple->flag & SINGLE_LOCKER_XID_IS_SUBXACT) { rc = snprintf_s(output + (int)strlen(output), MAXOUTPUTLEN, MAXOUTPUTLEN, - "\n\t\t\tSINGLE_LOCKER_XID_IS_SUBXACT "); + "\n\t\t\tSINGLE_LOCKER_XID_IS_SUBXACT "); securec_check_ss(rc, "\0", "\0"); } rc = snprintf_s(output + (int)strlen(output), MAXOUTPUTLEN, MAXOUTPUTLEN, "\n\t\t\tFlag2:"); securec_check_ss(rc, "\0", "\0"); + // 输出项的属性,包括列数和 Hoff 值 rc = snprintf_s(output + (int)strlen(output), MAXOUTPUTLEN, MAXOUTPUTLEN, "\n\t\t\tNumber of columns:: %d\n", - UHeapTupleHeaderGetNatts(diskTuple)); + UHeapTupleHeaderGetNatts(diskTuple)); securec_check_ss(rc, "\0", "\0"); rc = snprintf_s(output + (int)strlen(output), MAXOUTPUTLEN, MAXOUTPUTLEN, "\t\t\tHoff: %u\n", diskTuple->t_hoff); securec_check_ss(rc, "\0", "\0"); return; } - +/* + * 功能:解析 UHeap 页面上的数据,包括 UHeap 元组的信息 + * + * 参数列表: + * page:void 指针,表示 UHeap 页面的指针 + * output:char 指针,表示输出解析结果的字符串 + * + * 注意事项: + * - 该函数用于解析 UHeap 页面上的数据,包括各个 UHeap 元组的信息,如存储状态、长度、偏移等。 + * - 函数将解析结果输出到指定的字符串 `output` 中。 + */ static void ParseUHeapPageData(void *page, char *output) { errno_t rc; @@ -787,7 +1114,9 @@ static void ParseUHeapPageData(void *page, char *output) return; } + // 将输入的页面指针转换为 UHeapPageHeader 类型 pageHeader = (UHeapPageHeader)page; + // 计算行指针(RowPtr)的数量 if (pageHeader->pd_lower <= SizeOfUHeapPageHeaderData) { rowPtrCnt = 0; } else { @@ -796,9 +1125,10 @@ static void ParseUHeapPageData(void *page, char *output) } rc = snprintf_s(output + (int)strlen(output), MAXOUTPUTLEN, MAXOUTPUTLEN, - "\n\tUHeap tuple information on this page\n"); + "\n\tUHeap tuple information on this page\n"); securec_check_ss(rc, "\0", "\0"); + // 遍历 UHeap 页面上的每个行指针 for (uint32 i = FirstOffsetNumber; i <= rowPtrCnt; i++) { rowptr = UPageGetRowPtr(pageHeader, i); if (RowPtrIsUsed(rowptr)) { @@ -807,38 +1137,54 @@ static void ParseUHeapPageData(void *page, char *output) } if (RowPtrIsNormal(rowptr)) { + // 如果行指针标志为正常(normal),则解析相应的 UHeap 元组信息 rc = snprintf_s(output + (int)strlen(output), MAXOUTPUTLEN, MAXOUTPUTLEN, - "\n\t\tTuple #%u is normal: length %u, offset %u\n", i, RowPtrGetLen(rowptr), - RowPtrGetOffset(rowptr)); + "\n\t\tTuple #%u is normal: length %u, offset %u\n", i, RowPtrGetLen(rowptr), + RowPtrGetOffset(rowptr)); securec_check_ss(rc, "\0", "\0"); normalCnt++; + // 获取元组数据项,并进行解析 item = UPageGetRowData(pageHeader, rowptr); UHeapTupleCopyBaseFromPage(&utuple, pageHeader); ParseUHeapPageItem(item, &utuple, output); } else if (RowPtrIsDead(rowptr)) { + // 如果行指针标志为已删除(dead),则输出相应信息 rc = snprintf_s(output + (int)strlen(output), MAXOUTPUTLEN, MAXOUTPUTLEN, - "\n\t\tTuple #%u is dead: length %u, offset %u", i, RowPtrGetLen(rowptr), RowPtrGetOffset(rowptr)); + "\n\t\tTuple #%u is dead: length %u, offset %u", i, RowPtrGetLen(rowptr), + RowPtrGetOffset(rowptr)); securec_check_ss(rc, "\0", "\0"); deadCnt++; } else { + // 否则,行指针标志为重定向(redirect),输出相应信息 rc = snprintf_s(output + (int)strlen(output), MAXOUTPUTLEN, MAXOUTPUTLEN, - "\n\t\tTuple #%u is redirected: length %u, offset %u", i, RowPtrGetLen(rowptr), - RowPtrGetOffset(rowptr)); + "\n\t\tTuple #%u is redirected: length %u, offset %u", i, RowPtrGetLen(rowptr), + RowPtrGetOffset(rowptr)); securec_check_ss(rc, "\0", "\0"); redirectCnt++; } } else { + // 行指针未使用,输出相应信息 unusedCnt++; rc = snprintf_s(output + (int)strlen(output), MAXOUTPUTLEN, MAXOUTPUTLEN, "\n\t\tTuple #%u is unused\n", i); securec_check_ss(rc, "\0", "\0"); } } + // 输出 UHeap 页面数据的汇总信息,包括未使用、正常、已删除和重定向的元组数量 rc = snprintf_s(output + (int)strlen(output), MAXOUTPUTLEN, MAXOUTPUTLEN, - "\tSummary (%u total): %u unused, %u normal, %u dead, %u redirect\n", rowPtrCnt, unusedCnt, normalCnt, deadCnt, - redirectCnt); + "\tSummary (%u total): %u unused, %u normal, %u dead, %u redirect\n", rowPtrCnt, unusedCnt, + normalCnt, deadCnt, redirectCnt); securec_check_ss(rc, "\0", "\0"); } - +/* + * 功能:解析 UHeap 页面上的特殊信息 + * + * 参数列表: + * page:void 指针,表示 UHeap 页面的指针 + * output:char 指针,表示输出解析结果的字符串 + * + * 注意事项: + * - 该函数用于解析 UHeap 页面上的特殊信息,但当前版本未使用特殊信息,因此输出 "Not used currently."。 + */ static void ParseUHeapPageSpecialInfo(void *page, char *output) { errno_t rc = 0; @@ -847,13 +1193,27 @@ static void ParseUHeapPageSpecialInfo(void *page, char *output) return; } + // 输出特殊信息部分的标题 rc = snprintf_s(output + (int)strlen(output), MAXOUTPUTLEN, MAXOUTPUTLEN, - "\n\tSpecial area information on this page\n"); + "\n\tSpecial area information on this page\n"); securec_check_ss(rc, "\0", "\0"); + // 输出特殊信息的具体内容,当前版本未使用特殊信息,因此输出提示信息 rc = snprintf_s(output + (int)strlen(output), MAXOUTPUTLEN, MAXOUTPUTLEN, "\n\tNot used currently.\n"); securec_check_ss(rc, "\0", "\0"); } - +/* + * 功能:解析 UHeap 页面的各个部分信息 + * + * 参数列表: + * page:void 指针,表示 UHeap 页面的指针 + * blkno:BlockNumber,表示当前处理的块号 + * endBlk:BlockNumber,表示结束的块号 + * output:char 指针,表示输出解析结果的字符串 + * + * 注意事项: + * - 该函数用于解析 UHeap 页面的各个部分信息,包括页面头、TD slot 信息、元组数据和特殊区域。 + * - 如果页面数据不合法或已被标记为新页面,则只解析页面头部分。 + */ static void ParseUHeapPage(void *page, BlockNumber blkno, BlockNumber endBlk, char *output) { uint16 pageHeaderSize = 0; @@ -873,8 +1233,8 @@ static void ParseUHeapPage(void *page, BlockNumber blkno, BlockNumber endBlk, ch pageHeader->pd_upper > pageHeader->pd_special || pageHeader->pd_special > BLCKSZ || pageHeader->pd_special != MAXALIGN(pageHeader->pd_special)) { rc = snprintf_s(output + (int)strlen(output), MAXOUTPUTLEN, MAXOUTPUTLEN, - "The page data is corrupted, corrupted page pointers: lower = %u, upper = %u, special = %u\n", - pageHeader->pd_lower, pageHeader->pd_upper, pageHeader->pd_special); + "The page data is corrupted, corrupted page pointers: lower = %u, upper = %u, special = %u\n", + pageHeader->pd_lower, pageHeader->pd_upper, pageHeader->pd_special); securec_check_ss(rc, "\0", "\0"); return; } @@ -891,7 +1251,19 @@ static void ParseUHeapPage(void *page, BlockNumber blkno, BlockNumber endBlk, ch /* Parse special area of uheap page. */ ParseUHeapPageSpecialInfo(pageHeader, output); } - +/* + * 功能:解析索引页的头部信息 + * + * 参数列表: + * page:void 指针,表示索引页的指针 + * type:int,表示索引的类型(BTREE_INDEX 或 UBtree) + * blkno:BlockNumber,表示当前处理的块号 + * endBlk:BlockNumber,表示结束的块号 + * output:char 指针,表示输出解析结果的字符串 + * + * 注意事项: + * - 该函数用于解析索引页的头部信息,包括页面头、校验和、标志位等。 + */ static void ParseIndexPageHeader(void *page, int type, BlockNumber blkno, BlockNumber endBlk, char *output) { errno_t rc = 0; @@ -904,10 +1276,11 @@ static void ParseIndexPageHeader(void *page, int type, BlockNumber blkno, BlockN pageHeader = (PageHeader)page; rc = snprintf_s(output + (int)strlen(output), MAXOUTPUTLEN, MAXOUTPUTLEN, - "%s index page information of block %u/%u\n", (type == BTREE_INDEX) ? "Btree" : "UBtree", blkno, endBlk); + "%s index page information of block %u/%u\n", (type == BTREE_INDEX) ? "Btree" : "UBtree", blkno, + endBlk); securec_check_ss(rc, "\0", "\0"); rc = snprintf_s(output + (int)strlen(output), MAXOUTPUTLEN, MAXOUTPUTLEN, "\tpd_lsn: %X/%X\n", - (uint32)(PageGetLSN((Page)pageHeader) >> XIDTHIRTYTWO), (uint32)PageGetLSN((Page)pageHeader)); + (uint32)(PageGetLSN((Page)pageHeader) >> XIDTHIRTYTWO), (uint32)PageGetLSN((Page)pageHeader)); securec_check_ss(rc, "\0", "\0"); if (CheckPageZeroCases(pageHeader)) { @@ -916,7 +1289,7 @@ static void ParseIndexPageHeader(void *page, int type, BlockNumber blkno, BlockN } rc = snprintf_s(output + (int)strlen(output), MAXOUTPUTLEN, MAXOUTPUTLEN, "\tpd_checksum: 0x%X, verify %s\n", - pageHeader->pd_checksum, chksumResult ? "success" : "fail"); + pageHeader->pd_checksum, chksumResult ? "success" : "fail"); securec_check_ss(rc, "\0", "\0"); if (PageHasFreeLinePointers((Page)pageHeader)) { @@ -945,25 +1318,37 @@ static void ParseIndexPageHeader(void *page, int type, BlockNumber blkno, BlockN } rc = snprintf_s(output + (int)strlen(output), MAXOUTPUTLEN, MAXOUTPUTLEN, - "\tpd_lower: %u, %s\n\tpd_upper: %u, %s\n\tpd_special: %u, size %u\n", pageHeader->pd_lower, - PageIsEmpty((Page)pageHeader) ? "empty" : "non-empty", pageHeader->pd_upper, - PageIsNew((Page)pageHeader) ? "new page" : "old page", pageHeader->pd_special, - PageGetSpecialSize((Page)pageHeader)); + "\tpd_lower: %u, %s\n\tpd_upper: %u, %s\n\tpd_special: %u, size %u\n", pageHeader->pd_lower, + PageIsEmpty((Page)pageHeader) ? "empty" : "non-empty", pageHeader->pd_upper, + PageIsNew((Page)pageHeader) ? "new page" : "old page", pageHeader->pd_special, + PageGetSpecialSize((Page)pageHeader)); securec_check_ss(rc, "\0", "\0"); rc = snprintf_s(output + (int)strlen(output), MAXOUTPUTLEN, MAXOUTPUTLEN, "\tPage size & version: %u, %u\n", - (uint16)PageGetPageSize(page), (uint16)PageGetPageLayoutVersion(page)); + (uint16)PageGetPageSize(page), (uint16)PageGetPageLayoutVersion(page)); securec_check_ss(rc, "\0", "\0"); rc = snprintf_s(output + (int)strlen(output), MAXOUTPUTLEN, MAXOUTPUTLEN, "\tpd_prune_xid: %lu\n", - pageHeader->pd_prune_xid + ((HeapPageHeader)(pageHeader))->pd_xid_base); + pageHeader->pd_prune_xid + ((HeapPageHeader)(pageHeader))->pd_xid_base); securec_check_ss(rc, "\0", "\0"); rc = snprintf_s(output + (int)strlen(output), MAXOUTPUTLEN, MAXOUTPUTLEN, "\tpd_xid_base: %lu, pd_multi_base: %lu\n", - ((HeapPageHeader)(pageHeader))->pd_xid_base, ((HeapPageHeader)(pageHeader))->pd_multi_base); + ((HeapPageHeader)(pageHeader))->pd_xid_base, ((HeapPageHeader)(pageHeader))->pd_multi_base); securec_check_ss(rc, "\0", "\0"); return; } - +/* + * 功能:解析索引页中的项目信息 + * + * 参数列表: + * item:Item,表示索引项的指针 + * type:int,表示索引的类型(BTREE_INDEX 或 UBTREE_INDEX) + * len:uint32,表示索引项的长度 + * output:char 指针,表示输出解析结果的字符串 + * + * 注意事项: + * - 该函数用于解析索引页中的索引项信息,包括是否包含 NULL 值、是否包含可变长度属性等。 + * - 如果索引项长度与给定的 len 不匹配,则输出附加信息(对于 UBTREE_INDEX 类型)。 + */ static void ParseIndexPageItem(Item item, int type, uint32 len, char *output) { if (item == NULL || output == NULL) { @@ -978,14 +1363,14 @@ static void ParseIndexPageItem(Item item, int type, uint32 len, char *output) if (type == UBTREE_INDEX) { UstoreIndexXid uxid = (UstoreIndexXid)UstoreIndexTupleGetXid(itup); rc = snprintf_s(output + (int)strlen(output), MAXOUTPUTLEN, MAXOUTPUTLEN, - "\t\t\txmin:%d xmax:%d Heap Tid: block %u/%u, offset %u\n", uxid->xmin, uxid->xmax, - itup->t_tid.ip_blkid.bi_hi, itup->t_tid.ip_blkid.bi_lo, itup->t_tid.ip_posid); + "\t\t\txmin:%d xmax:%d Heap Tid: block %u/%u, offset %u\n", uxid->xmin, uxid->xmax, + itup->t_tid.ip_blkid.bi_hi, itup->t_tid.ip_blkid.bi_lo, itup->t_tid.ip_posid); securec_check_ss(rc, "\0", "\0"); } } else { rc = snprintf_s(output + (int)strlen(output), MAXOUTPUTLEN, MAXOUTPUTLEN, - "\t\t\tHeap Tid: block %u/%u, offset %u\n", itup->t_tid.ip_blkid.bi_hi, itup->t_tid.ip_blkid.bi_lo, - itup->t_tid.ip_posid); + "\t\t\tHeap Tid: block %u/%u, offset %u\n", itup->t_tid.ip_blkid.bi_hi, + itup->t_tid.ip_blkid.bi_lo, itup->t_tid.ip_posid); securec_check_ss(rc, "\0", "\0"); } @@ -1006,7 +1391,20 @@ static void ParseIndexPageItem(Item item, int type, uint32 len, char *output) securec_check_ss(rc, "\0", "\0"); return; } - +/* + * 功能:解析索引页的特殊信息 + * + * 参数列表: + * page:void 指针,表示索引页的数据 + * type:int,表示索引的类型(BTREE_INDEX 或 UBTREE_INDEX) + * output:char 指针,表示输出解析结果的字符串 + * + * 注意事项: + * - 该函数用于解析索引页的特殊信息,包括 BTree 或 UBTree 的特有信息。 + * - 输出包括左兄弟页号、右兄弟页号、树的级别、标志位、循环 ID 等。 + * - 如果索引页被标记为已删除(P_ISDELETED),则输出下一个事务 ID(xact)。 + * - 如果是 UBTree 类型的索引页,还会输出活跃元组数(activeTupleCount)。 + */ static void ParseIndexPageSpecialInfo(void *page, int type, char *output) { errno_t rc = 0; @@ -1025,27 +1423,27 @@ static void ParseIndexPageSpecialInfo(void *page, int type, char *output) } rc = snprintf_s(output + (int)strlen(output), MAXOUTPUTLEN, MAXOUTPUTLEN, "\n%s index special information:\n", - (type == BTREE_INDEX) ? "BTree" : "UBTree"); + (type == BTREE_INDEX) ? "BTree" : "UBTree"); securec_check_ss(rc, "\0", "\0"); rc = snprintf_s(output + (int)strlen(output), MAXOUTPUTLEN, MAXOUTPUTLEN, "\tbtree left sibling: %u\n", - opaque->btpo_prev); + opaque->btpo_prev); securec_check_ss(rc, "\0", "\0"); rc = snprintf_s(output + (int)strlen(output), MAXOUTPUTLEN, MAXOUTPUTLEN, "\tbtree right sibling: %u\n", - opaque->btpo_next); + opaque->btpo_next); securec_check_ss(rc, "\0", "\0"); if (!P_ISDELETED(opaque)) { rc = snprintf_s(output + (int)strlen(output), MAXOUTPUTLEN, MAXOUTPUTLEN, "\tbtree tree level: %u\n", - opaque->btpo.level); + opaque->btpo.level); securec_check_ss(rc, "\0", "\0"); } else { if (uopaque) { rc = snprintf_s(output + (int)strlen(output), MAXOUTPUTLEN, MAXOUTPUTLEN, "\tnext txid (deleted): %lu\n", - ((UBTPageOpaque)uopaque)->xact); + ((UBTPageOpaque)uopaque)->xact); securec_check_ss(rc, "\0", "\0"); } else { rc = snprintf_s(output + (int)strlen(output), MAXOUTPUTLEN, MAXOUTPUTLEN, "\tnext txid (deleted): %lu\n", - ((BTPageOpaque)opaque)->xact); + ((BTPageOpaque)opaque)->xact); securec_check_ss(rc, "\0", "\0"); } } @@ -1080,18 +1478,30 @@ static void ParseIndexPageSpecialInfo(void *page, int type, char *output) securec_check_ss(rc, "\0", "\0"); rc = snprintf_s(output + (int)strlen(output), MAXOUTPUTLEN, MAXOUTPUTLEN, "\tbtree cycle ID: %u\n ", - opaque->btpo_cycleid); + opaque->btpo_cycleid); securec_check_ss(rc, "\0", "\0"); if (uopaque) { rc = snprintf_s(output + (int)strlen(output), MAXOUTPUTLEN, MAXOUTPUTLEN, "\tubtree active tuples: %d\n", - uopaque->activeTupleCount); + uopaque->activeTupleCount); securec_check_ss(rc, "\0", "\0"); } return; } - +/* + * 功能:解析索引页上的数据信息 + * + * 参数列表: + * page:void 指针,表示索引页的数据 + * type:int,表示索引的类型(BTREE_INDEX 或 UBTREE_INDEX) + * output:char 指针,表示输出解析结果的字符串 + * + * 注意事项: + * - 该函数用于解析索引页上存储的索引元组信息,包括存储元组的类型、偏移量等。 + * - 输出包括元组的类型(正常、冻结、死亡、重定向)以及元组的长度和偏移量。 + * - 如果是 UBTree 类型的索引,还会调用 ParseIndexPageItem 函数进一步解析元组信息。 + */ static void ParseIndexPageData(void *page, int type, char *output) { errno_t rc; @@ -1113,7 +1523,7 @@ static void ParseIndexPageData(void *page, int type, char *output) rowPtrCnt = PageGetMaxOffsetNumber((Page)pageHeader); rc = snprintf_s(output + (int)strlen(output), MAXOUTPUTLEN, MAXOUTPUTLEN, - "\n\tIndex tuple information on this page\n"); + "\n\tIndex tuple information on this page\n"); securec_check_ss(rc, "\0", "\0"); for (uint32 i = FirstOffsetNumber; i <= rowPtrCnt; i++) { @@ -1124,21 +1534,25 @@ static void ParseIndexPageData(void *page, int type, char *output) } if (ItemIdIsNormal(lp) || (IndexItemIdIsFrozen(lp))) { rc = ItemIdIsNormal(lp) ? (snprintf_s(output + (int)strlen(output), MAXOUTPUTLEN, MAXOUTPUTLEN, - "\n\t\tTuple #%u is normal: length %u, offset %u\n", i, ItemIdGetLength(lp), ItemIdGetOffset(lp))) : - (snprintf_s(output + (int)strlen(output), MAXOUTPUTLEN, MAXOUTPUTLEN, - "\n\t\tTuple #%u is frozen: length %u, offset %u\n", i, ItemIdGetLength(lp), ItemIdGetOffset(lp))); + "\n\t\tTuple #%u is normal: length %u, offset %u\n", i, + ItemIdGetLength(lp), ItemIdGetOffset(lp))) + : (snprintf_s(output + (int)strlen(output), MAXOUTPUTLEN, MAXOUTPUTLEN, + "\n\t\tTuple #%u is frozen: length %u, offset %u\n", i, + ItemIdGetLength(lp), ItemIdGetOffset(lp))); securec_check_ss(rc, "\0", "\0"); normalCnt++; item = PageGetItem((Page)pageHeader, lp); ParseIndexPageItem(item, type, ItemIdGetLength(lp), output); } else if (ItemIdIsDead(lp)) { rc = snprintf_s(output + (int)strlen(output), MAXOUTPUTLEN, MAXOUTPUTLEN, - "\n\t\tTuple #%u is dead: length %u, offset %u", i, ItemIdGetLength(lp), ItemIdGetOffset(lp)); + "\n\t\tTuple #%u is dead: length %u, offset %u", i, ItemIdGetLength(lp), + ItemIdGetOffset(lp)); securec_check_ss(rc, "\0", "\0"); deadCnt++; } else { rc = snprintf_s(output + (int)strlen(output), MAXOUTPUTLEN, MAXOUTPUTLEN, - "\n\t\tTuple #%u is redirected: length %u, offset %u", i, ItemIdGetLength(lp), ItemIdGetOffset(lp)); + "\n\t\tTuple #%u is redirected: length %u, offset %u", i, ItemIdGetLength(lp), + ItemIdGetOffset(lp)); securec_check_ss(rc, "\0", "\0"); redirectCnt++; } @@ -1150,12 +1564,25 @@ static void ParseIndexPageData(void *page, int type, char *output) } rc = snprintf_s(output + (int)strlen(output), MAXOUTPUTLEN, MAXOUTPUTLEN, - "\tSummary (%u total): %u unused, %u normal, %u dead, %u redirect\n", rowPtrCnt, notusedCnt, normalCnt, deadCnt, - redirectCnt); + "\tSummary (%u total): %u unused, %u normal, %u dead, %u redirect\n", rowPtrCnt, notusedCnt, + normalCnt, deadCnt, redirectCnt); securec_check_ss(rc, "\0", "\0"); return; } - +/* + * 功能:解析索引页的各个部分(页头、数据、特殊信息) + * + * 参数列表: + * page:void 指针,表示索引页的数据 + * type:int,表示索引的类型(BTREE_INDEX 或 UBTREE_INDEX) + * blkno:BlockNumber,表示当前索引页的块号 + * endBlk:BlockNumber,表示索引页的结束块号 + * output:char 指针,表示输出解析结果的字符串 + * + * 注意事项: + * - 该函数首先检查索引页的页头是否有效,然后依次解析页头、数据和特殊信息。 + * - 如果索引页是新页(PageIsNew),则不进行进一步解析。 + */ static void ParseIndexPage(void *page, int type, BlockNumber blkno, BlockNumber endBlk, char *output) { PageHeader pageHeader = NULL; @@ -1172,8 +1599,8 @@ static void ParseIndexPage(void *page, int type, BlockNumber blkno, BlockNumber pageHeader->pd_upper > pageHeader->pd_special || pageHeader->pd_special > BLCKSZ || pageHeader->pd_special != MAXALIGN(pageHeader->pd_special)) { rc = snprintf_s(output + (int)strlen(output), MAXOUTPUTLEN, MAXOUTPUTLEN, - "The page data is corrupted, corrupted page pointers: lower = %u, upper = %u, special = %u\n", - pageHeader->pd_lower, pageHeader->pd_upper, pageHeader->pd_special); + "The page data is corrupted, corrupted page pointers: lower = %u, upper = %u, special = %u\n", + pageHeader->pd_lower, pageHeader->pd_upper, pageHeader->pd_special); securec_check_ss(rc, "\0", "\0"); return; } @@ -1187,7 +1614,22 @@ static void ParseIndexPage(void *page, int type, BlockNumber blkno, BlockNumber ParseIndexPageSpecialInfo((void *)page, type, output); return; } - +/* + * gs_parse_page_bypath - Parses a page on disk and returns the parsed information as text. + * + * This function is used to parse a page on disk and return the parsed information as text. + * It performs the following steps: + * 1. Checks the user's rights. + * 2. Reads input parameters. + * 3. Parses the requested page and writes the results to a file. + * 4. Returns the filename of the output file as text. + * + * Parameters: + * - PG_FUNCTION_ARGS: The standard input arguments for a PostgreSQL function. + * + * Returns: + * A text value containing the filename of the output file. + */ Datum gs_parse_page_bypath(PG_FUNCTION_ARGS) { /* check user's right */ diff --git a/src/gausskernel/storage/remote/remote_adapter.cpp b/src/gausskernel/storage/remote/remote_adapter.cpp index 0adcf7d73..7ccd60901 100755 --- a/src/gausskernel/storage/remote/remote_adapter.cpp +++ b/src/gausskernel/storage/remote/remote_adapter.cpp @@ -44,9 +44,9 @@ #include "utils/aiomem.h" const int DEFAULT_WAIT_TIMES = 60; -int ReadFileForRemote(RemoteReadFileKey *key, XLogRecPtr lsn, bytea** fileData, int timeout); +int ReadFileForRemote(RemoteReadFileKey *key, XLogRecPtr lsn, bytea **fileData, int timeout); -int ReadCOrCsnFileForRemote(RelFileNode rnode, bytea** fileData); +int ReadCOrCsnFileForRemote(RelFileNode rnode, bytea **fileData); /* * @Description: wait lsn to replay @@ -66,12 +66,9 @@ int XLogWaitForReplay(uint64 primary_insert_lsn, int timeout = DEFAULT_WAIT_TIME /* if sleep to much times */ if (wait_times >= timeout) { ereport(LOG, - (errmodule(MOD_REMOTE), - errmsg("replay slow. requre lsn %X/%X, replayed lsn %X/%X", - (uint32)(primary_insert_lsn >> 32), - (uint32)primary_insert_lsn, - (uint32)(standby_replay_lsn >> 32), - (uint32)standby_replay_lsn))); + (errmodule(MOD_REMOTE), errmsg("replay slow. requre lsn %X/%X, replayed lsn %X/%X", + (uint32)(primary_insert_lsn >> 32), (uint32)primary_insert_lsn, + (uint32)(standby_replay_lsn >> 32), (uint32)standby_replay_lsn))); return REMOTE_READ_NEED_WAIT; } @@ -100,7 +97,7 @@ Datum gs_read_block_from_remote(PG_FUNCTION_ARGS) uint32 blockSize; uint64 lsn; bool isForCU = false; - bytea* result = NULL; + bytea *result = NULL; int timeout = 0; if (GetUserId() != BOOTSTRAP_SUPERUSERID) { @@ -152,7 +149,7 @@ Datum gs_read_block_from_remote_compress(PG_FUNCTION_ARGS) uint64 lsn; int timeout = 0; bool isForCU = false; - bytea* result = NULL; + bytea *result = NULL; if (GetUserId() != BOOTSTRAP_SUPERUSERID) { ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), (errmsg("must be initial account to read files")))); @@ -176,7 +173,7 @@ Datum gs_read_block_from_remote_compress(PG_FUNCTION_ARGS) } else { (void)StandbyReadPageforPrimary(key, blockSize, lsn, &result, timeout, NULL); } - + if (NULL != result) { PG_RETURN_BYTEA_P(result); } else { @@ -198,8 +195,7 @@ Datum gs_read_block_from_remote_compress(PG_FUNCTION_ARGS) * @Return: remote read error code * @See also: */ -int StandbyReadCUforPrimary(RepairBlockKey key, uint64 offset, int32 size, uint64 lsn, int32 timeout, - bytea** cudata) +int StandbyReadCUforPrimary(RepairBlockKey key, uint64 offset, int32 size, uint64 lsn, int32 timeout, bytea **cudata) { Assert(cudata); @@ -214,14 +210,14 @@ int StandbyReadCUforPrimary(RepairBlockKey key, uint64 offset, int32 size, uint6 } } - RelFileNode relfilenode {key.relfilenode.spcNode, key.relfilenode.dbNode, key.relfilenode.relNode, InvalidBktId}; + RelFileNode relfilenode{key.relfilenode.spcNode, key.relfilenode.dbNode, key.relfilenode.relNode, InvalidBktId}; { /* read from disk */ CFileNode cfilenode(relfilenode, key.forknum, MAIN_FORKNUM); - CUStorage* custorage = New(CurrentMemoryContext) CUStorage(cfilenode); - CU* cu = New(CurrentMemoryContext) CU(); + CUStorage *custorage = New(CurrentMemoryContext) CUStorage(cfilenode); + CU *cu = New(CurrentMemoryContext) CU(); cu->m_inCUCache = false; custorage->LoadCU(cu, offset, size, false, false); @@ -232,7 +228,7 @@ int StandbyReadCUforPrimary(RepairBlockKey key, uint64 offset, int32 size, uint6 ereport(ERROR, (errmodule(MOD_REMOTE), errmsg("check CU crc error."))); ret_code = REMOTE_READ_CRC_ERROR; } else { - bytea* buf = (bytea*)palloc0(VARHDRSZ + size); + bytea *buf = (bytea *)palloc0(VARHDRSZ + size); SET_VARSIZE(buf, size + VARHDRSZ); errno_t rc = memcpy_s(VARDATA(buf), size, cu->m_compressedLoadBuf, size); if (rc != EOK) { @@ -263,8 +259,8 @@ int StandbyReadCUforPrimary(RepairBlockKey key, uint64 offset, int32 size, uint6 * @Return: remote read error code * @See also: */ -int StandbyReadPageforPrimary(RepairBlockKey key, uint32 blocksize, uint64 lsn, bytea** pagedata, - int timeout, const XLogPhyBlock *pblk) +int StandbyReadPageforPrimary(RepairBlockKey key, uint32 blocksize, uint64 lsn, bytea **pagedata, int timeout, + const XLogPhyBlock *pblk) { Assert(pagedata); @@ -287,18 +283,17 @@ int StandbyReadPageforPrimary(RepairBlockKey key, uint32 blocksize, uint64 lsn, if (NULL != pblk) { SegPageLocation loc = seg_get_physical_location(relfilenode, key.forknum, key.blocknum); - uint8 standby_relNode = (uint8) EXTENT_SIZE_TO_TYPE(loc.extent_size); + uint8 standby_relNode = (uint8)EXTENT_SIZE_TO_TYPE(loc.extent_size); BlockNumber standby_block = loc.blocknum; if (standby_relNode != pblk->relNode || standby_block != pblk->block) { - ereport(ERROR, (errmodule(MOD_REMOTE), - errmsg("Standby page file is invalid! Standby relnode is %u, " - "master is %u; Standby block is %u, master is %u.", - standby_relNode, pblk->relNode, standby_block, pblk->block))); + ereport(ERROR, (errmodule(MOD_REMOTE), errmsg("Standby page file is invalid! Standby relnode is %u, " + "master is %u; Standby block is %u, master is %u.", + standby_relNode, pblk->relNode, standby_block, pblk->block))); return REMOTE_READ_BLCKSZ_NOT_SAME; } } - bytea* pageData = (bytea*)palloc(BLCKSZ + VARHDRSZ); + bytea *pageData = (bytea *)palloc(BLCKSZ + VARHDRSZ); SET_VARSIZE(pageData, BLCKSZ + VARHDRSZ); if (IsSegmentPhysicalRelNode(relfilenode)) { Buffer buffer = InvalidBuffer; @@ -351,13 +346,25 @@ int StandbyReadPageforPrimary(RepairBlockKey key, uint32 blocksize, uint64 lsn, if (ret_code == REMOTE_READ_OK) { *pagedata = pageData; - PageSetChecksumInplace((Page) VARDATA(*pagedata), key.blocknum); + PageSetChecksumInplace((Page)VARDATA(*pagedata), key.blocknum); } return ret_code; } const int RES_COL_NUM = 2; +/* + * 功能:从远程服务器读取文件并返回文件内容及当前LSN,该函数适用于特权用户,非特权用户无法调用 + * + * 参数列表: + * PG_FUNCTION_ARGS:PostgreSQL扩展函数参数列表 + * + * 注意事项: + * - 调用者必须是超级用户(BOOTSTRAP_SUPERUSERID)才能使用此函数。 + * - 如果表空间节点不是1或2,函数将尝试读取数据文件,否则将尝试读取其他类型的文件。 + * - 如果递归标志为1,将递归删除目录及其内容。 + * - 如果文件读取失败,函数将返回 NULL。 + */ Datum gs_read_file_from_remote(PG_FUNCTION_ARGS) { RelFileNode rnode; @@ -365,7 +372,7 @@ Datum gs_read_file_from_remote(PG_FUNCTION_ARGS) int32 forknum; uint32 blockstart; uint64 lsn; - bytea* result = NULL; + bytea *result = NULL; Datum values[RES_COL_NUM]; bool nulls[RES_COL_NUM] = {false}; HeapTuple tuple = NULL; @@ -374,58 +381,77 @@ Datum gs_read_file_from_remote(PG_FUNCTION_ARGS) int32 timeout; int parano = 0; XLogRecPtr current_lsn = InvalidXLogRecPtr; - + // 检查调用者是否为超级用户 if (GetUserId() != BOOTSTRAP_SUPERUSERID) { ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), (errmsg("must be initial account to read files")))); } - /* handle optional arguments */ + // 从函数参数中获取表空间、数据库、表/关系、块位置等信息 rnode.spcNode = PG_GETARG_UINT32(parano++); rnode.dbNode = PG_GETARG_UINT32(parano++); rnode.relNode = PG_GETARG_UINT32(parano++); rnode.bucketNode = PG_GETARG_INT32(parano++); rnode.opt = 0; + // 获取 fork 号和块的起始位置 forknum = PG_GETARG_INT32(parano++); blockstart = PG_GETARG_INT32(parano++); + // 获取事务的 LSN(Log Sequence Number) lsn = (uint64)PG_GETARG_TRANSACTIONID(parano++); + // 获取超时时间 timeout = PG_GETARG_INT32(parano++); - + // 如果表空间节点不是1或2,说明要读取数据文件 if (rnode.spcNode != 1 && rnode.spcNode != 2) { - /* get tale data file */ + // 设置 RemoteReadFileKey 结构体 key.relfilenode = rnode; key.forknum = forknum; key.blockstart = blockstart; + + // 如果 fork 号不是主 fork,则发出警告并返回 NULL if (forknum != MAIN_FORKNUM) { ereport(WARNING, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - (errmsg("Forknum should be 0. Now is %d. \n", forknum)))); + (errmsg("Forknum should be 0. Now is %d. \n", forknum)))); PG_RETURN_NULL(); } + + // 调用 ReadFileForRemote 函数尝试读取远程文件 ret_code = ReadFileForRemote(&key, lsn, &result, timeout); } else { + // 如果表空间节点是1或2,说明要读取其他类型的文件 ret_code = ReadCOrCsnFileForRemote(rnode, &result); } - + // 如果文件读取失败,返回 NULL if (ret_code != REMOTE_READ_OK) { PG_RETURN_NULL(); } - + // 如果不处于恢复模式下,获取当前的 LSN(Log Sequence Number) if (!RecoveryInProgress()) { current_lsn = GetXLogInsertRecPtr(); } - + // 创建结果元组的描述信息 tupdesc = CreateTemplateTupleDesc(RES_COL_NUM, false, TAM_HEAP); parano = 1; + // 初始化结果元组的每个列 TupleDescInitEntry(tupdesc, (AttrNumber)parano++, "file", BYTEAOID, -1, 0); TupleDescInitEntry(tupdesc, (AttrNumber)parano++, "lsn", XIDOID, -1, 0); + // 设置元组的值和 nulls 标志 values[0] = PointerGetDatum(result); nulls[0] = false; values[1] = UInt64GetDatum(current_lsn); nulls[1] = false; - + // 创建元组 tupdesc = BlessTupleDesc(tupdesc); tuple = heap_form_tuple(tupdesc, values, nulls); + // 返回结果元组 PG_RETURN_DATUM(HeapTupleGetDatum(tuple)); } - +/* + * 功能:获取远程文件的大小 + * + * 参数列表: + * PG_FUNCTION_ARGS:PostgreSQL扩展函数参数列表 + * + * 注意事项: + * - 调用者必须是超级用户(BOOTSTRAP_SUPERUSERID)才能使用此函数。 + */ Datum gs_read_file_size_from_remote(PG_FUNCTION_ARGS) { RelFileNode rnode; @@ -436,29 +462,56 @@ Datum gs_read_file_size_from_remote(PG_FUNCTION_ARGS) int parano = 0; int ret_code = REMOTE_READ_OK; + // 检查调用者是否为超级用户 if (GetUserId() != BOOTSTRAP_SUPERUSERID) { ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), (errmsg("must be initial account to read files")))); } /* handle optional arguments */ + // 从函数参数中获取表空间、数据库、表/关系、块位置等信息 rnode.spcNode = PG_GETARG_UINT32(parano++); rnode.dbNode = PG_GETARG_UINT32(parano++); rnode.relNode = PG_GETARG_UINT32(parano++); rnode.bucketNode = PG_GETARG_INT32(parano++); rnode.opt = 0; + // 获取 fork 号 forknum = PG_GETARG_INT32(parano++); + // 获取事务的 LSN(Log Sequence Number) lsn = (uint64)PG_GETARG_TRANSACTIONID(parano++); + // 获取超时时间 timeout = PG_GETARG_INT32(parano++); /* get file size */ ret_code = ReadFileSizeForRemote(rnode, forknum, lsn, &size, timeout); + // 如果成功获取文件大小,返回大小值 if (ret_code == REMOTE_READ_OK) { PG_RETURN_INT64(size); } else { + // 如果获取失败,返回 NULL PG_RETURN_NULL(); } } - -int ReadFileSizeForRemote(RelFileNode rnode, int32 forknum, XLogRecPtr lsn, int64* res, int timeout) +/* + * 功能:获取远程文件的大小 + * + * 参数列表: + * rnode:待获取文件的文件节点信息(包括表空间、数据库、表/关系等信息) + * forknum:文件的 fork 号 + * lsn:用于等待事务日志回放的 LSN(仅在恢复模式下使用) + * res:用于存储文件大小的指针 + * timeout:等待回放的超时时间 + * + * 返回值: + * 成功获取文件大小时,将文件大小存储在 res 指针指向的位置,并返回 REMOTE_READ_OK。 + * 如果出现错误或文件不存在,将返回适当的错误代码。 + * + * 注意事项: + * - 在恢复模式下,函数将等待指定 LSN 的事务日志回放完成。 + * - 函数将首先检查文件是否存在,如果文件不存在,将返回文件大小为 -1。 + * - 对于非分段文件节点,函数将打开文件并计算块数以获取文件大小。 + * - 对于分段文件节点,函数将通过 spc_open 和 spc_size 获取文件大小。 + * - 文件大小将以字节为单位存储在 res 指针指向的位置。 + */ +int ReadFileSizeForRemote(RelFileNode rnode, int32 forknum, XLogRecPtr lsn, int64 *res, int timeout) { SMgrRelation smgr = NULL; int64 nblock = 0; @@ -477,7 +530,7 @@ int ReadFileSizeForRemote(RelFileNode rnode, int32 forknum, XLogRecPtr lsn, int6 /* check whether the file exists. not exist, return size -1 */ struct stat statBuf; - char* path = relpathperm(rnode, forknum); + char *path = relpathperm(rnode, forknum); if (stat(path, &statBuf) < 0 && errno == ENOENT) { *res = -1; pfree(path); @@ -485,22 +538,42 @@ int ReadFileSizeForRemote(RelFileNode rnode, int32 forknum, XLogRecPtr lsn, int6 } pfree(path); - if (!IsSegmentFileNode(rnode)) { - smgr = smgropen(rnode, InvalidBackendId); - nblock = smgrnblocks(smgr, forknum); - smgrcloseall(); - } else { - SegSpace *spc = spc_open(rnode.spcNode, rnode.dbNode, true, true); - spc_datafile_create(spc, rnode.relNode, forknum); - - nblock = spc_size(spc, rnode.relNode, forknum); + // 如果不是分段文件节点,打开文件并计算块数 + if (!IsSegmentFileNode(rnode)) { // 如果不是分段文件节点 + smgr = smgropen(rnode, InvalidBackendId); // 打开文件的存储管理器 + nblock = smgrnblocks(smgr, forknum); // 获取文件的块数 + smgrcloseall(); // 关闭所有的存储管理器 + } else { // 如果是分段文件节点 + SegSpace *spc = spc_open(rnode.spcNode, rnode.dbNode, true, true); // 打开分段空间 + spc_datafile_create(spc, rnode.relNode, forknum); // 创建数据文件 + nblock = spc_size(spc, rnode.relNode, forknum); // 获取文件大小 } + // 计算文件大小并存储在 res 指针指向的位置 *res = nblock * BLCKSZ; return ret_code; } - -int ReadFileByReadBufferComom(RemoteReadFileKey *key, bytea* pageData, uint32 nblock) +/* + * 功能:通过 ReadBuffer 读取远程文件的多个数据块并将数据存储在 pageData 中。 + * 每个数据块的大小为 BLCKSZ 字节。函数可以处理一个块范围内的数据块读取请求。 + * + * 参数列表: + * key:远程文件读取请求的关键信息,包括文件节点、fork 号和块起始位置 + * pageData:用于存储读取数据块内容的字节数组 + * nblock:文件中的总数据块数量 + * + * 返回值: + * 如果成功读取数据块,将返回 REMOTE_READ_OK。 + * 如果读取过程中发生错误,将返回适当的错误代码。 + * + * 注意事项: + * - 函数从 blk_start 到 blk_end 之间的数据块范围内读取数据。 + * - 每个数据块的大小为 BLCKSZ 字节。 + * - 函数在读取数据块时会验证数据块的有效性。 + * - 如果读取数据块失败,函数将返回适当的错误代码。 + * - 函数使用了缓冲策略(BufferAccessStrategy)来提高读取效率。 + */ +int ReadFileByReadBufferComom(RemoteReadFileKey *key, bytea *pageData, uint32 nblock) { int ret_code = REMOTE_READ_OK; uint32 i = 0; @@ -508,7 +581,7 @@ int ReadFileByReadBufferComom(RemoteReadFileKey *key, bytea* pageData, uint32 nb uint32 blk_start; uint32 blk_end; bool hit = false; - char* bufBlock = NULL; + char *bufBlock = NULL; errno_t rc; /* get the segno file block start and block end */ @@ -526,8 +599,9 @@ int ReadFileByReadBufferComom(RemoteReadFileKey *key, bytea* pageData, uint32 nb } LockBuffer(buf, BUFFER_LOCK_SHARE); - bufBlock = (char*)BufferGetBlock(buf); + bufBlock = (char *)BufferGetBlock(buf); + // 将读取的数据块内容复制到 pageData 中 rc = memcpy_s(VARDATA(pageData) + j * BLCKSZ, BLCKSZ, bufBlock, BLCKSZ); if (rc != EOK) { ereport(WARNING, (errmodule(MOD_REMOTE), errmsg("repair file failed, memcpy_s error, retcode=%d", rc))); @@ -539,28 +613,50 @@ int ReadFileByReadBufferComom(RemoteReadFileKey *key, bytea* pageData, uint32 nb LockBuffer(buf, BUFFER_LOCK_UNLOCK); ReleaseBuffer(buf); - PageSetChecksumInplace((Page) (VARDATA(pageData) + j * BLCKSZ), i); + PageSetChecksumInplace((Page)(VARDATA(pageData) + j * BLCKSZ), i); } return ret_code; } const int MAX_RETRY_TIMES = 60; -int ReadFileByReadDisk(SegSpace* spc, RemoteReadFileKey *key, char* bufBlock, BlockNumber blocknum) +/* + * 功能:通过直接从磁盘读取数据块修复远程文件,它可以处理分段文件和非分段文件 + * + * 参数列表: + * spc:分段空间的信息(仅对分段文件有效) + * key:远程文件读取请求的关键信息,包括文件节点、fork 号和块号 + * bufBlock:用于存储读取数据块内容的缓冲区 + * blocknum:要读取的数据块号 + * + * 返回值: + * 如果成功修复损坏块,将返回 REMOTE_READ_OK。 + * 如果修复过程中发生错误,将返回适当的错误代码。 + * + * 注意事项: + * - 函数首先检查文件节点是否属于分段文件,以确定采用何种读取方式。 + * - 对于分段文件,函数使用 seg_physical_read 从磁盘读取数据块,并进行 CRC 验证。 + * - 如果 CRC 验证失败,函数将尝试最多 MAX_RETRY_TIMES 次来修复损坏块。 + * - 对于非分段文件,函数使用 smgrread 从磁盘读取数据块,并进行 CRC 验证。 + * - 如果 CRC 验证失败,函数将返回适当的错误代码。 + * - 函数会在每次重试之间休眠 10 毫秒。 + * - 修复失败后,函数将释放 bufBlock 并返回错误代码。 + */ +int ReadFileByReadDisk(SegSpace *spc, RemoteReadFileKey *key, char *bufBlock, BlockNumber blocknum) { int ret_code = REMOTE_READ_OK; int pageStatus; int retryTimes = 0; + // 检查文件是否为分段文件 if (IsSegmentFileNode(key->relfilenode)) { - RelFileNode fakenode = { - .spcNode = key->relfilenode.spcNode, - .dbNode = key->relfilenode.dbNode, - .relNode = key->relfilenode.relNode, - .bucketNode = SegmentBktId, - .opt = 0 - }; -SEG_RETRY: + RelFileNode fakenode = {.spcNode = key->relfilenode.spcNode, + .dbNode = key->relfilenode.dbNode, + .relNode = key->relfilenode.relNode, + .bucketNode = SegmentBktId, + .opt = 0}; + SEG_RETRY: + // 从磁盘读取数据块 seg_physical_read(spc, fakenode, key->forknum, blocknum, (char *)bufBlock); retryTimes++; if (PageIsVerified((Page)bufBlock, blocknum)) { @@ -574,9 +670,10 @@ SEG_RETRY: } else { pfree(bufBlock); ereport(WARNING, (errmodule(MOD_REMOTE), - errmsg("repair file failed, read page crc check error, page: %u/%u/%u/%d, " - "forknum is %d, block num is %u", key->relfilenode.spcNode, key->relfilenode.dbNode, - key->relfilenode.relNode, key->relfilenode.bucketNode, key->forknum, blocknum))); + errmsg("repair file failed, read page crc check error, page: %u/%u/%u/%d, " + "forknum is %d, block num is %u", + key->relfilenode.spcNode, key->relfilenode.dbNode, key->relfilenode.relNode, + key->relfilenode.bucketNode, key->forknum, blocknum))); ret_code = REMOTE_READ_CRC_ERROR; return ret_code; } @@ -589,9 +686,10 @@ SEG_RETRY: if (pageStatus != SMGR_RD_OK) { pfree(bufBlock); ereport(WARNING, (errmodule(MOD_REMOTE), - errmsg("repair file failed, read page crc check error, page: %u/%u/%u/%d, " - "forknum is %d, block num is %u", key->relfilenode.spcNode, key->relfilenode.dbNode, - key->relfilenode.relNode, key->relfilenode.bucketNode, key->forknum, blocknum))); + errmsg("repair file failed, read page crc check error, page: %u/%u/%u/%d, " + "forknum is %d, block num is %u", + key->relfilenode.spcNode, key->relfilenode.dbNode, key->relfilenode.relNode, + key->relfilenode.bucketNode, key->forknum, blocknum))); ret_code = REMOTE_READ_CRC_ERROR; smgrclose(smgr); return ret_code; @@ -600,14 +698,52 @@ SEG_RETRY: } return ret_code; } - -int ReadFileForRemote(RemoteReadFileKey *key, XLogRecPtr lsn, bytea** fileData, int timeout) +/* + * 功能:从远程文件中读取数据块 + * + * 该函数用于从远程文件中读取数据块,支持分段文件和非分段文件。如果正在进行故障恢复,则会等待请求的LSN进行重放。 + * 函数会检查请求的数据块范围,并使用合适的方式来读取数据块,然后将数据存储在 fileData 中。 + * + * 参数列表: + * key:远程文件读取请求的关键信息,包括文件节点、fork 号和块起始位置 + * lsn:请求的LSN,用于等待重放 + * fileData:用于存储读取数据块内容的 bytea 类型数据 + * timeout:等待重放LSN的超时时间 + * + * 返回值: + * 如果成功读取数据块,将返回 REMOTE_READ_OK。 + * 如果读取过程中发生错误,将返回适当的错误代码。 + * + * 注意事项: + * - 函数首先等待请求的LSN进行重放,以确保在读取之前数据已被正确写入。 + * - 对于非分段文件,函数会获取文件的总数据块数量(nblock)并检查请求的数据块范围。 + * - 函数将会分批次读取数据块,每次最多读取 MAX_BATCH_READ_BLOCKNUM 个数据块。 + * - 对于主数据库,函数使用 ReadFileByReadBufferComom 函数从缓冲区中读取数据块,否则使用 ReadFileByReadDisk + * 函数从磁盘中读取。 + * - 函数会检查 CRC 验证,并在验证失败时进行重试(最多 MAX_RETRY_TIMES 次)。 + * - 在读取失败或出现错误时,函数会释放资源并返回适当的错误代码。 + * + * 详细步骤: + * 1. 等待请求的LSN进行重放,以确保数据的一致性。 + * 2. 在故障恢复或对分段文件的请求上,触发强制检查点,以确保最新的数据已持久化到磁盘。 + * 3. 获取文件的总数据块数量(nblock)并检查请求的数据块范围是否合法。 + * 4. 根据数据块范围计算起始和结束块号(blk_start 和 blk_end)。 + * 5. 为存储数据块内容的缓冲区分配内存并初始化。 + * 6. 如果是主数据库,使用 ReadFileByReadBufferComom 函数从缓冲区中读取数据块。 + * - 如果读取失败,释放资源并返回错误。 + * 7. 如果是分段文件或故障恢复,使用 ReadFileByReadDisk 函数从磁盘中读取数据块。 + * - 如果读取失败,释放资源并返回错误。 + * 8. 将读取的数据块内容复制到 pageData 中。 + * 9. 释放临时缓冲区和资源。 + * 10. 返回读取结果或错误代码。 + */ +int ReadFileForRemote(RemoteReadFileKey *key, XLogRecPtr lsn, bytea **fileData, int timeout) { int ret_code = REMOTE_READ_OK; SMgrRelation smgr = NULL; - SegSpace* spc = NULL; - bytea* pageData = NULL; - char* bufBlock = NULL; + SegSpace *spc = NULL; + bytea *pageData = NULL; + char *bufBlock = NULL; uint32 nblock = 0; uint32 blk_start = 0; uint32 blk_end = 0; @@ -635,9 +771,8 @@ int ReadFileForRemote(RemoteReadFileKey *key, XLogRecPtr lsn, bytea** fileData, } else { spc = spc_open(key->relfilenode.spcNode, key->relfilenode.dbNode, false, false); if (!spc) { - ereport(WARNING, (errmodule(MOD_REMOTE), - errmsg("Spc open failed. spcNode is: %u, dbNode is %u", - key->relfilenode.spcNode, key->relfilenode.dbNode))); + ereport(WARNING, (errmodule(MOD_REMOTE), errmsg("Spc open failed. spcNode is: %u, dbNode is %u", + key->relfilenode.spcNode, key->relfilenode.dbNode))); return REMOTE_READ_IO_ERROR; } nblock = spc_size(spc, key->relfilenode.relNode, key->forknum); @@ -652,7 +787,7 @@ int ReadFileForRemote(RemoteReadFileKey *key, XLogRecPtr lsn, bytea** fileData, blk_start = key->blockstart; blk_end = (nblock >= blk_start + MAX_BATCH_READ_BLOCKNUM ? blk_start + MAX_BATCH_READ_BLOCKNUM : nblock); - pageData = (bytea*)palloc((blk_end - blk_start) * BLCKSZ + VARHDRSZ); + pageData = (bytea *)palloc((blk_end - blk_start) * BLCKSZ + VARHDRSZ); SET_VARSIZE(pageData, ((blk_end - blk_start) * BLCKSZ + VARHDRSZ)); /* primary read page, need read page by ReadBuffer_common */ @@ -678,8 +813,8 @@ int ReadFileForRemote(RemoteReadFileKey *key, XLogRecPtr lsn, bytea** fileData, if (ret_code != REMOTE_READ_OK) { pfree(bufBlock); pfree(pageData); - ereport(ERROR, (errmodule(MOD_REMOTE), errmsg("repair file failed, read block %u error, retcode=%d", - i, rc))); + ereport(ERROR, + (errmodule(MOD_REMOTE), errmsg("repair file failed, read block %u error, retcode=%d", i, rc))); return ret_code; } rc = memcpy_s(VARDATA(pageData) + j * BLCKSZ, BLCKSZ, bufBlock, BLCKSZ); @@ -707,60 +842,92 @@ int ReadFileForRemote(RemoteReadFileKey *key, XLogRecPtr lsn, bytea** fileData, const int REGR_MCR_SIZE_1MB = 1048576; const int CLOG_NODE = 1; const int CSN_NODE = 2; -int ReadCOrCsnFileForRemote(RelFileNode rnode, bytea** fileData) +/* + * 功能:从远程读取 CLOG 或 CSN 文件内容 + * + * 该函数用于从远程节点读取 CLOG 或 CSN 文件的内容,并将其存储在 fileData 中。这些文件用于事务日志记录和 CSN 控制。 + * 函数首先确定要读取的文件类型(CLOG 或 CSN),然后构造文件路径并打开文件。如果文件不存在,则返回错误。 + * 否则,函数将文件内容读取到内存缓冲区中,将其复制到 bytea 类型的数据结构中,并将其返回。 + * + * 参数列表: + * rnode:要读取文件的关联文件节点信息,包括空间ID、数据库ID和文件ID + * fileData:用于存储读取文件内容的 bytea 类型数据的指针 + * + * 返回值: + * 如果成功读取文件,将返回 0。 + * 如果读取失败或出现错误,将返回 -1。 + * + * 注意事项: + * - 函数根据文件节点的空间ID来确定文件类型,CLOG 或 CSN。 + * - 函数构造文件路径并尝试打开文件。如果文件不存在,将返回 -1。 + * - 函数使用 pread 从文件中读取内容,然后将其复制到 bytea 数据结构中。 + * - 如果文件读取失败,函数将返回 -1,并记录错误消息。 + * - 如果文件内容超过预定义的最大大小(logSize),函数将返回 -1。 + * - 在成功读取文件后,函数将文件内容存储在 fileData 中并返回 0。 + * - 函数负责释放分配的内存并关闭文件描述符。 + */ +int ReadCOrCsnFileForRemote(RelFileNode rnode, bytea **fileData) { uint32 flags = O_RDWR | PG_BINARY; int fd = -1; - char* logType = NULL; - char* path = (char*)palloc0(MAX_PATH); + char *logType = NULL; + char *path = (char *)palloc0(MAX_PATH); errno_t rc; uint32 logSize = 16 * REGR_MCR_SIZE_1MB; - char *buffer = (char*)palloc(logSize); + char *buffer = (char *)palloc(logSize); int result = -1; + // 确定文件类型(CLOG 或 CSN) if (rnode.spcNode == CLOG_NODE) { logType = "pg_clog"; } else if (rnode.spcNode == CSN_NODE) { logType = "pg_csnlog"; } else { - ereport(LOG, (errmodule(MOD_SEGMENT_PAGE), errmsg("File type\"%u\" does not exist, stop read here.", - rnode.spcNode))); + // 未知文件类型,记录日志并返回错误 + ereport(LOG, (errmodule(MOD_SEGMENT_PAGE), + errmsg("File type\"%u\" does not exist, stop read here.", rnode.spcNode))); } + // 构造文件路径 rc = snprintf_s(path, MAX_PATH, MAX_PATH - 1, "%s/%012u", logType, rnode.relNode); securec_check_ss(rc, "\0", "\0"); + // 尝试打开文件 fd = BasicOpenFile(path, flags, S_IWUSR | S_IRUSR); if (fd < 0) { pfree(buffer); if (errno != ENOENT) { + // 打开文件失败,记录错误消息并返回 -1 ereport(ERROR, (errcode_for_file_access(), errmsg("could not open file \"%s\": %m", path))); } - // The file does not exist, break. - ereport(LOG, - (errmodule(MOD_SEGMENT_PAGE), errmsg("File \"%s\" does not exist, stop read here.", path))); + // 文件不存在,记录日志并返回 -1 + ereport(LOG, (errmodule(MOD_SEGMENT_PAGE), errmsg("File \"%s\" does not exist, stop read here.", path))); pfree(path); return -1; } + + // 读取文件内容到内存缓冲区 pgstat_report_waitevent(WAIT_EVENT_DATA_FILE_READ); uint32 nbytes = pread(fd, buffer, logSize, 0); pgstat_report_waitevent(WAIT_EVENT_END); + + // 关闭文件 if (close(fd)) { pfree(path); pfree(buffer); ereport(ERROR, (errcode_for_file_access(), errmsg("could not close file \"%s\": %m", path))); } + // 检查读取的文件内容是否超过预定义的最大大小 if (nbytes > logSize) { pfree(buffer); - ereport(ERROR, - (errcode(MOD_SEGMENT_PAGE), - errcode_for_file_access(), + ereport(ERROR, (errcode(MOD_SEGMENT_PAGE), errcode_for_file_access(), errmsg("could not read file %s. nbytes:%u, logSize:%u", path, nbytes, logSize))); pfree(path); return -1; } else { - bytea* pageData = (bytea*)palloc(nbytes + VARHDRSZ); + // 分配内存并复制文件内容到 bytea 数据结构中 + bytea *pageData = (bytea *)palloc(nbytes + VARHDRSZ); SET_VARSIZE(pageData, (nbytes + VARHDRSZ)); rc = memcpy_s(VARDATA(pageData), nbytes, buffer, nbytes); if (rc != EOK) { @@ -770,10 +937,13 @@ int ReadCOrCsnFileForRemote(RelFileNode rnode, bytea** fileData) ereport(ERROR, (errmodule(MOD_REMOTE), errmsg("repair file failed, memcpy_s error, retcode=%d", rc))); return -1; } else { + // 将文件内容返回给调用者 *fileData = pageData; result = 0; } } + + // 释放分配的内存并返回结果 pfree(path); pfree(buffer); return result; diff --git a/src/gausskernel/storage/remote/remote_read.cpp b/src/gausskernel/storage/remote/remote_read.cpp index 505e159a7..6b547ca26 100755 --- a/src/gausskernel/storage/remote/remote_read.cpp +++ b/src/gausskernel/storage/remote/remote_read.cpp @@ -37,9 +37,23 @@ * @IN error_code: remote read error code * @Return: remote read error message */ -const char* RemoteReadErrMsg(int error_code) +/* + * 功能:获取远程文件读取错误码对应的错误消息 + * + * 参数列表: + * error_code:远程文件读取错误码,通常是一个整数值表示特定的错误类型 + * + * 返回值: + * 返回一个字符串指针,表示与错误码对应的错误消息 + * + * 注意事项: + * - 该函数根据传入的错误码返回相应的错误消息,用于解释错误原因。 + * - 错误码通常是一个整数值,对应不同的错误类型。 + * - 如果传入的错误码没有匹配的错误消息,函数将返回一个默认的错误消息。 + */ +const char *RemoteReadErrMsg(int error_code) { - const char* error_msg = ""; + const char *error_msg = ""; switch (error_code) { case REMOTE_READ_OK: error_msg = "normal"; @@ -79,27 +93,40 @@ const char* RemoteReadErrMsg(int error_code) return error_msg; } +/* + * 功能:获取主服务的地址 + * + * 参数列表: + * address:用于存储主服务地址的字符串 + * address_len:字符串地址的最大长度 + * + * 注意事项: + * - 如果传入的地址为空,地址长度为 0,或者 WalRcv 数据结构为空,函数将直接返回。 + * - 函数通过互斥锁保证多线程环境下的数据访问安全。 + * - 如果 WalRcv 的进程 ID 为 0 或者 WalRcv 不在运行状态,函数将直接返回。 + * - 函数将格式化主服务的地址字符串,并存储在传入的 address 参数中。 + */ void GetPrimaryServiceAddress(char *address, size_t address_len) { if (address == NULL || address_len == 0 || t_thrd.walreceiverfuncs_cxt.WalRcv == NULL) - return; + return; // 如果传入的地址为空,地址长度为 0,或者 WalRcv 为空,直接返回 - bool is_running = false; - volatile WalRcvData *walrcv = t_thrd.walreceiverfuncs_cxt.WalRcv; - int rc = 0; + bool is_running = false; // 用于标识 WalRcv 是否正在运行 + volatile WalRcvData *walrcv = t_thrd.walreceiverfuncs_cxt.WalRcv; // 获取 WalRcv 数据结构的指针 + int rc = 0; // 用于存储 snprintf_s 函数的返回值 - SpinLockAcquire(&walrcv->mutex); - is_running = walrcv->isRuning; - SpinLockRelease(&walrcv->mutex); + SpinLockAcquire(&walrcv->mutex); // 获取互斥锁以访问 WalRcv 数据结构 + is_running = walrcv->isRuning; // 获取 WalRcv 是否正在运行的状态 + SpinLockRelease(&walrcv->mutex); // 释放互斥锁 if (walrcv->pid == 0 || !is_running) - return; + return; // 如果 WalRcv 的进程 ID 为 0 或者 WalRcv 不在运行状态,直接返回 - SpinLockAcquire(&walrcv->mutex); + SpinLockAcquire(&walrcv->mutex); // 再次获取互斥锁以访问 WalRcv 数据结构 rc = snprintf_s(address, address_len, (address_len - 1), "%s@%d", walrcv->conn_channel.remotehost, - walrcv->conn_channel.remoteport); - securec_check_ss(rc, "\0", "\0"); - SpinLockRelease(&walrcv->mutex); + walrcv->conn_channel.remoteport); // 使用 snprintf_s 格式化地址字符串 + securec_check_ss(rc, "\0", "\0"); // 检查 snprintf_s 函数的返回值 + SpinLockRelease(&walrcv->mutex); // 释放互斥锁 } /* @@ -108,7 +135,27 @@ void GetPrimaryServiceAddress(char *address, size_t address_len) * @IN/OUT second_address: second_address * @IN address_len: address len */ -void GetRemoteReadAddress(char* firstAddress, char* secondAddress, size_t addressLen) +/* + * 功能:获取远程读取地址 + * + * 参数列表: + * firstAddress:用于存储第一个远程读取地址的缓冲区 + * secondAddress:用于存储第二个远程读取地址的缓冲区 + * addressLen:缓冲区的长度 + * + * 注意事项: + * - 该函数用于获取用于远程读取的地址信息。 + * - 函数首先确保传入的第一个地址缓冲区有效且长度大于0。 + * - 然后根据服务器的运行模式选择相应的操作: + * - 如果服务器处于DN_DUMMY_STANDYS_MODE下,检查是否为PRIMARY_MODE并确保主备节点准备好进行远程读取。 + * 如果是,将第一个地址缓冲区设置为主备节点的远程主机地址。 + * - 如果服务器处于DN_MULTI_STANDYS_MODE下,检查服务器模式: + * - 如果是PRIMARY_MODE,调用 GetFastestReplayStandByServiceAddress 获取最快的备用节点服务地址, + * 并将第一个和第二个地址缓冲区设置为相应的主机地址。 + * - 如果是STANDBY_MODE,调用 GetPrimaryServiceAddress 获取主节点服务地址,并将第一个地址缓冲区设置为主机地址。 + * - 如果服务器模式不匹配以上任何一种情况,函数将返回。 + */ +void GetRemoteReadAddress(char *firstAddress, char *secondAddress, size_t addressLen) { char ip[MAX_IPADDR_LEN] = {0}; char port[MAX_IPADDR_LEN] = {0}; @@ -118,7 +165,7 @@ void GetRemoteReadAddress(char* firstAddress, char* secondAddress, size_t addres if (firstAddress == NULL || addressLen == 0) return; - volatile HaShmemData* hashmdata = t_thrd.postmaster_cxt.HaShmData; + volatile HaShmemData *hashmdata = t_thrd.postmaster_cxt.HaShmData; ServerMode serverMode = hashmdata->current_mode; if (IS_DN_DUMMY_STANDYS_MODE()) { @@ -127,8 +174,8 @@ void GetRemoteReadAddress(char* firstAddress, char* secondAddress, size_t addres } if (t_thrd.postmaster_cxt.ReplConnArray[1]) { - rc = snprintf_s(firstAddress, addressLen, (addressLen - 1), - "%s@%d", t_thrd.postmaster_cxt.ReplConnArray[1]->remotehost, + rc = snprintf_s(firstAddress, addressLen, (addressLen - 1), "%s@%d", + t_thrd.postmaster_cxt.ReplConnArray[1]->remotehost, t_thrd.postmaster_cxt.ReplConnArray[1]->remoteport); securec_check_ss(rc, "", ""); } @@ -159,21 +206,35 @@ void GetRemoteReadAddress(char* firstAddress, char* secondAddress, size_t addres } } -void GetIPAndPort(char* address, char* ip, char* port, size_t len) +/* + * 功能:从地址字符串中提取 IP 地址和端口号 + * + * 参数列表: + * address:包含 IP 地址和端口号的地址字符串 + * ip:用于存储提取的 IP 地址的字符串 + * port:用于存储提取的端口号的字符串 + * len:存储地址的缓冲区的最大长度 + * + * 注意事项: + * - 函数使用 "@" 符号将地址字符串分割成 IP 地址和端口号两部分。 + * - 如果分割成功,并且提取的 IP 地址和端口号不为空且总长度小于 len,将它们复制到对应的缓冲区中。 + * - 如果分割失败或者复制过程中出现错误,将不进行任何操作。 + */ +void GetIPAndPort(char *address, char *ip, char *port, size_t len) { - char* outerPtr = NULL; - errno_t rc = EOK; - char* tmpIp; - char* tempPort; + char *outerPtr = NULL; // 用于 strtok_r 函数的外部指针 + errno_t rc = EOK; // 用于存储函数返回值的错误码 + char *tmpIp; // 用于存储提取的 IP 地址的临时变量 + char *tempPort; // 用于存储提取的端口号的临时变量 - tmpIp = strtok_r(address, "@", &outerPtr); - tempPort = strtok_r(NULL, "@", &outerPtr); + tmpIp = strtok_r(address, "@", &outerPtr); // 使用 "@" 符号分割地址字符串,并获取 IP 地址部分 + tempPort = strtok_r(NULL, "@", &outerPtr); // 获取剩余的字符串,即端口号部分 if (tmpIp != NULL && tmpIp[0] != '\0' && tempPort != NULL && tempPort[0] != '\0' && strlen(tmpIp) + strlen(tempPort) + 1 < len) { - rc = strcpy_s(ip, MAX_IPADDR_LEN, tmpIp); - securec_check(rc, "", ""); - rc = strcpy_s(port, MAX_IPADDR_LEN, tempPort); - securec_check(rc, "", ""); + rc = strcpy_s(ip, MAX_IPADDR_LEN, tmpIp); // 将提取的 IP 地址复制到 ip 缓冲区 + securec_check(rc, "", ""); // 检查复制过程中的错误 + rc = strcpy_s(port, MAX_IPADDR_LEN, tempPort); // 将提取的端口号复制到 port 缓冲区 + securec_check(rc, "", ""); // 检查复制过程中的错误 } return; } @@ -184,7 +245,7 @@ void GetIPAndPort(char* address, char* ip, char* port, size_t len) */ bool CanRemoteRead() { - volatile HaShmemData* hashmdata = t_thrd.postmaster_cxt.HaShmData; + volatile HaShmemData *hashmdata = t_thrd.postmaster_cxt.HaShmData; ServerMode serveMode = hashmdata->current_mode; if (IsRemoteReadModeOn() && !IS_DN_WITHOUT_STANDBYS_MODE() && IS_PGXC_DATANODE && serveMode != NORMAL_MODE && @@ -214,7 +275,17 @@ int SetRemoteReadModeOffAndGetOldMode() g_instance.attr.attr_storage.remote_read_mode = REMOTE_READ_OFF; return oldRemoteRead; } - +/* + * 功能:设置远程读取模式 + * + * 参数列表: + * mode:要设置的远程读取模式,通常是一个整数值表示模式 + * + * 注意事项: + * - 该函数用于设置远程读取模式,以影响后续的远程文件读取行为。 + * - 远程读取模式通常是一个整数值,不同的模式可能对读取操作产生不同的影响。 + * - 函数简单地将传入的 mode 参数赋值给全局变量,以实现模式的设置。 + */ void SetRemoteReadMode(int mode) { g_instance.attr.attr_storage.remote_read_mode = mode; diff --git a/src/gausskernel/storage/smgr/knl_uundofile.cpp b/src/gausskernel/storage/smgr/knl_uundofile.cpp index 5cc6123e2..1785d0349 100644 --- a/src/gausskernel/storage/smgr/knl_uundofile.cpp +++ b/src/gausskernel/storage/smgr/knl_uundofile.cpp @@ -30,16 +30,15 @@ #define UNDOFORMAT(f) UNDODEBUGSTR f UNDODEBUGINFO /* Populate a file tag describing an undofile.cpp segment file. */ -#define INIT_UNDO_FILE_TAG(tag, rNode, segNo) \ - do \ - { \ - errno_t errorno = EOK; \ - errorno = memset_s(&(tag), sizeof(FileTag), (0), sizeof(FileTag)); \ - securec_check(errorno, "\0", "\0"); \ - (tag).handler = SYNC_HANDLER_UNDO; \ - (tag).forknum = MAIN_FORKNUM; \ - (tag).rnode = (rNode); \ - (tag).segno = (segNo); \ +#define INIT_UNDO_FILE_TAG(tag, rNode, segNo) \ + do { \ + errno_t errorno = EOK; \ + errorno = memset_s(&(tag), sizeof(FileTag), (0), sizeof(FileTag)); \ + securec_check(errorno, "\0", "\0"); \ + (tag).handler = SYNC_HANDLER_UNDO; \ + (tag).forknum = MAIN_FORKNUM; \ + (tag).rnode = (rNode); \ + (tag).segno = (segNo); \ } while (false); /* @@ -77,66 +76,106 @@ static const int UNDO_FILE_EXTEND_PAGES = 8; /* local routines */ static UndoFileState *AllocUndoFileState(void); static void GetUndoFilePath(int zoneId, uint32 dbId, int segno, char *path, int len); -static UndoFileState *OpenUndoFile(SMgrRelation reln, ForkNumber forknum, BlockNumber blockNum, ExtensionBehavior behavior); +static UndoFileState *OpenUndoFile(SMgrRelation reln, ForkNumber forknum, BlockNumber blockNum, + ExtensionBehavior behavior); static void RegisterDirtyUndoSegment(SMgrRelation reln, const UndoFileState *state); static void RegisterForgetUndoRequests(RelFileNodeBackend rnode, uint32 segno); static void RegisterUnlinkUndoRequests(RelFileNodeBackend rnode, uint32 segno); static BlockNumber GetUndoFileBlocks(SMgrRelation reln, ForkNumber forknum, const UndoFileState *state); void CheckUndoFileDirectory(UndoPersistence upersistence); - +/* + * 功能:根据数据库ID确定UNDO文件的大小 + * + * 参数列表: + * dbId:数据库ID,用于确定UNDO文件大小的依据 + */ static inline uint32 UNDO_FILE_SIZE(uint32 dbId) { if (dbId == UNDO_DB_OID) { - return UNDO_FILE_MAXSIZE; + return UNDO_FILE_MAXSIZE; // 如果数据库ID等于UNDO_DB_OID,则返回UNDO_FILE_MAXSIZE } - return UNDO_META_MAXSIZE; + return UNDO_META_MAXSIZE; // 否则返回UNDO_META_MAXSIZE } +/* + * 功能:根据数据库ID确定UNDO文件的块数 + * + * 参数列表: + * dbId:数据库ID,用于确定UNDO文件块数的依据 + */ static inline uint32 UNDO_FILE_BLOCK(uint32 dbId) { if (dbId == UNDO_DB_OID) { - return UNDO_FILE_BLOCKS; + return UNDO_FILE_BLOCKS; // 如果数据库ID等于UNDO_DB_OID,则返回UNDO_FILE_BLOCKS } - return UNDO_META_BLOCKS; + return UNDO_META_BLOCKS; // 否则返回UNDO_META_BLOCKS } /* allocate UndoFileState memory. */ +/* + * 功能:分配UndoFileState结构体的内存并返回指针 + * + * 参数列表:无 + */ static UndoFileState *AllocUndoFileState(void) { MemoryContext current; if (EnableLocalSysCache()) { - current = t_thrd.lsc_cxt.lsc->lsc_mydb_memcxt; + current = t_thrd.lsc_cxt.lsc->lsc_mydb_memcxt; // 获取本地系统缓存的内存上下文 } else { - current = u_sess->storage_cxt.UndoFileCxt; + current = u_sess->storage_cxt.UndoFileCxt; // 获取全局UndoFile上下文 } - return (UndoFileState *)MemoryContextAlloc(current, sizeof(UndoFileState)); + return (UndoFileState *)MemoryContextAlloc(current, + sizeof(UndoFileState)); // 分配UndoFileState结构体的内存并返回指针 } - +/* + * 功能:设置UndoFileState结构体的字段值 + * + * 参数列表: + * state:指向UndoFileState结构体的指针 + * segno:文件段号 + * file:文件句柄 + */ static inline void SetUndoFileState(UndoFileState *state, int segno, File file) { - state->segno = segno; - state->file = file; + state->segno = segno; // 设置UndoFileState结构体的segno字段 + state->file = file; // 设置UndoFileState结构体的file字段 } - +/* + * 功能:初始化Undo文件相关的内存上下文 + * + * 参数列表:无 + */ void InitUndoFile(void) { + // 如果启用了本地系统缓存,直接返回,不需要初始化全局Undo文件上下文 if (EnableLocalSysCache()) { return; } + // 确保全局Undo文件上下文尚未初始化 Assert(u_sess->storage_cxt.UndoFileCxt == NULL); - u_sess->storage_cxt.UndoFileCxt = AllocSetContextCreate(u_sess->top_mem_cxt, "UndoFileSmgr", ALLOCSET_DEFAULT_SIZES); + // 创建全局Undo文件上下文,基于当前会话的顶级内存上下文 u_sess->top_mem_cxt + u_sess->storage_cxt.UndoFileCxt = + AllocSetContextCreate(u_sess->top_mem_cxt, "UndoFileSmgr", ALLOCSET_DEFAULT_SIZES); } - +/* + * 功能:获取Undo文件的目录路径 + * + * 参数列表: + * path:用于存储目录路径的缓冲区 + * len:缓冲区长度 + * upersistence:Undo文件的持久性,可以是UNDO_PERMANENT、UNDO_UNLOGGED或UNDO_TEMP + */ void GetUndoFileDirectory(char *path, int len, UndoPersistence upersistence) { Assert(len >= UNDO_FILE_DIR_LEN); errno_t rc = EOK; if (upersistence == UNDO_PERMANENT) { - rc = snprintf_s(path, len, len - 1, UNDO_PERMANENT_DIR); + rc = snprintf_s(path, len, len - 1, UNDO_PERMANENT_DIR); // 设置目录路径为永久Undo文件目录 } else if (upersistence == UNDO_UNLOGGED) { - rc = snprintf_s(path, len, len - 1, UNDO_UNLOGGED_DIR); + rc = snprintf_s(path, len, len - 1, UNDO_UNLOGGED_DIR); // 设置目录路径为非日志Undo文件目录 } else { - rc = snprintf_s(path, len, len - 1, UNDO_TEMP_DIR); + rc = snprintf_s(path, len, len - 1, UNDO_TEMP_DIR); // 设置目录路径为临时Undo文件目录- } securec_check_ss(rc, "\0", "\0"); return; @@ -144,11 +183,13 @@ void GetUndoFileDirectory(char *path, int len, UndoPersistence upersistence) void CheckUndoDirectory(void) { + // 用于测试的条件编译,模拟UNDO_CHECK_DIRECTORY_FAILED错误情况 WHITEBOX_TEST_STUB(UNDO_CHECK_DIRECTORY_FAILED, WhiteboxDefaultErrorEmit); + // 尝试创建Undo文件的根目录,如果创建失败并且错误码不是EEXIST(目录已存在),则报告错误 if (mkdir(UNDO_FILE_DIR_PREFIX, S_IRWXU) < 0 && errno != EEXIST) { - ereport(ERROR, (errcode_for_file_access(), - errmsg("could not create directory \"%s\": %m", UNDO_FILE_DIR_PREFIX))); + ereport(ERROR, + (errcode_for_file_access(), errmsg("could not create directory \"%s\": %m", UNDO_FILE_DIR_PREFIX))); } return; } @@ -156,28 +197,39 @@ void CheckUndoDirectory(void) void CheckUndoFileDirectory(UndoPersistence upersistence) { char dir[UNDO_FILE_DIR_LEN]; - GetUndoFileDirectory(dir, UNDO_FILE_DIR_LEN, upersistence); + GetUndoFileDirectory(dir, UNDO_FILE_DIR_LEN, upersistence); // 获取Undo文件目录路径 + // 用于测试的条件编译,模拟UNDO_CHECK_FILE_DIRECTORY_FAILED错误情况 WHITEBOX_TEST_STUB(UNDO_CHECK_FILE_DIRECTORY_FAILED, WhiteboxDefaultErrorEmit); + // 尝试创建Undo文件目录,如果创建失败并且错误码不是EEXIST(目录已存在),则进行额外处理 if (mkdir(dir, S_IRWXU) != 0 && errno != EEXIST) { + // 如果错误码不是ENOENT(目录不存在)或者不处于XLOG恢复状态,则报告错误 if (errno != ENOENT || !t_thrd.xlog_cxt.InRecovery) { ereport(ERROR, (errcode_for_file_access(), errmsg("could not create directory \"%s\": %m", dir))); } + // 尝试创建Undo文件的根目录,如果创建失败并且错误码不是EEXIST(目录已存在),则报告错误 if (mkdir(UNDO_FILE_DIR_PREFIX, S_IRWXU) < 0 && errno != EEXIST) { - ereport(ERROR, (errcode_for_file_access(), errmsg("could not create directory \"%s\": %m", UNDO_FILE_DIR_PREFIX))); + ereport(ERROR, + (errcode_for_file_access(), errmsg("could not create directory \"%s\": %m", UNDO_FILE_DIR_PREFIX))); } + // 再次尝试创建Undo文件目录,如果创建失败并且错误码不是EEXIST(目录已存在),则报告错误 if (mkdir(dir, S_IRWXU) != 0 && errno != EEXIST) { ereport(ERROR, (errcode_for_file_access(), errmsg("could not create directory \"%s\": %m", dir))); } } return; } - +/* + * 功能:清理Undo文件的目录,如果目录存在的话 + * + * 参数列表: + * upersistence:Undo文件的持久性,可以是UNDO_PERMANENT、UNDO_UNLOGGED或UNDO_TEMP + */ void CleanUndoFileDirectory(UndoPersistence upersistence) { char dir[UNDO_FILE_DIR_LEN]; - GetUndoFileDirectory(dir, UNDO_FILE_DIR_LEN, upersistence); + GetUndoFileDirectory(dir, UNDO_FILE_DIR_LEN, upersistence); // 获取Undo文件目录路径 if (rmdir(dir) < 0 && errno != ENOENT) { /* try again */ if ((rmdir(dir) < 0) && (errno != ENOENT)) { @@ -186,15 +238,29 @@ void CleanUndoFileDirectory(UndoPersistence upersistence) } return; } - +/* + * 功能:获取Undo文件的完整路径 + * + * 参数列表: + * zoneId:Undo文件的区域ID + * dbId:数据库ID,用于区分数据文件和元数据文件 + * segno:文件段号 + * path:用于存储完整路径的缓冲区 + * len:缓冲区长度 + */ void GetUndoFilePath(int zoneId, uint32 dbId, int segno, char *path, int len) { + // 确保缓冲区长度足够大 Assert(len >= UNDO_FILE_PATH_LEN); char dir[UNDO_FILE_DIR_LEN]; errno_t rc = EOK; DECLARE_NODE_COUNT(); - GET_UPERSISTENCE_BY_ZONEID(zoneId, nodeCount); + GET_UPERSISTENCE_BY_ZONEID(zoneId, nodeCount); // 获取持久性类型 + + // 获取Undo文件目录路径 GetUndoFileDirectory(dir, UNDO_FILE_DIR_LEN, upersistence); + + // 根据数据库ID的不同,设置不同的文件名格式 if (dbId == UNDO_DB_OID) { rc = snprintf_s(path, len, len - 1, "%s/%05X.%07zX", dir, zoneId, segno); } else { @@ -203,38 +269,66 @@ void GetUndoFilePath(int zoneId, uint32 dbId, int segno, char *path, int len) securec_check_ss(rc, "\0", "\0"); return; } - +/* + * 功能:获取Undo文件的块数 + * + * 参数列表: + * reln:SMgrRelation对象,用于表示关联的Undo文件 + * forknum:ForkNumber,表示文件的分叉类型 + * + * 返回值:最大块号(MaxBlockNumber),表示文件的块数 + */ BlockNumber GetUndoFileNblocks(SMgrRelation reln, ForkNumber forknum) { - return MaxBlockNumber; + return MaxBlockNumber; // 返回最大块号,表示文件的块数 } /* Get number of blocks present in a single disk undofile. */ +/* + * 功能:获取Undo文件的块数 + * + * 参数列表: + * reln:SMgrRelation对象,用于表示关联的Undo文件 + * forknum:ForkNumber,表示文件的分叉类型 + * state:UndoFileState对象,表示Undo文件的状态信息 + * + * 返回值:BlockNumber,表示文件的块数 + */ static BlockNumber GetUndoFileBlocks(SMgrRelation reln, ForkNumber forknum, const UndoFileState *state) { + // 确保传入的UndoFileState对象不为空 Assert(state != NULL); - + // 获取文件的完整路径 char *fileName = FilePathName(state->file); + // 使用FileSeek函数获取文件的长度 off_t len = FileSeek(state->file, 0L, SEEK_END); + // 获取Undo文件的期望大小 uint32 undoFileSize = UNDO_FILE_SIZE(reln->smgr_rnode.node.dbNode); - + // 用于测试的条件编译,模拟UNDO_GET_FILE_BLOCKS_FAILED错误情况 WHITEBOX_TEST_STUB(UNDO_GET_FILE_BLOCKS_FAILED, WhiteboxDefaultErrorEmit); - + // 检查文件长度是否小于0,如果是则报告错误并关闭文件 if (len < 0) { CloseUndoFile(reln, forknum, InvalidBlockNumber); ereport(ERROR, (errmsg(UNDOFORMAT("could not seek to end of file \"%s\": %m."), fileName))); } - + // 检查文件长度是否符合期望的块大小,如果不符合则报告错误并关闭文件 if (len % BLCKSZ != 0) { CloseUndoFile(reln, forknum, InvalidBlockNumber); ereport(ERROR, (errmsg(UNDOFORMAT("The expected size of file \"%s\" is %d, but the actual size is %ld."), - fileName, undoFileSize, len))); + fileName, undoFileSize, len))); } /* note that this calculation will ignore any partial block at EOF */ - return (BlockNumber)(len / BLCKSZ); + return (BlockNumber)(len / BLCKSZ); // 返回文件的块数 } - +/* + * 功能:创建Undo文件 + * + * 参数列表: + * reln:SMgrRelation对象,用于表示关联的Undo文件 + * forknum:ForkNumber,表示文件的分叉类型 + * isRedo:一个布尔值,指示是否在重做中创建Undo文件 + */ void CreateUndoFile(SMgrRelation reln, ForkNumber forknum, bool isRedo) { /* Undo file creation is managed by ExtendUndoFile. */ @@ -242,13 +336,26 @@ void CreateUndoFile(SMgrRelation reln, ForkNumber forknum, bool isRedo) } /* Create an undo file, expand the file by 8 pages until the file size reaches 1 MB. */ +/* + * 功能:扩展Undo文件 + * + * 参数列表: + * reln:SMgrRelation对象,用于表示关联的Undo文件 + * forknum:ForkNumber,表示文件的分叉类型 + * blockno:要扩展到的块号 + * buffer:用于填充的缓冲区(未使用) + * skipFsync:一个布尔值,指示是否跳过fsync + */ void ExtendUndoFile(SMgrRelation reln, ForkNumber forknum, BlockNumber blockno, char *buffer, bool skipFsync) { + // 确保SMgrRelation对象不为空 Assert(reln != NULL); - + // 获取Undo文件的状态信息 UndoFileState *state = (UndoFileState *)reln->fileState; + // 获取Undo文件的块大小和期望大小 uint32 undoFileBlocks = UNDO_FILE_BLOCK(reln->smgr_rnode.node.dbNode); uint32 undoFileSize = UNDO_FILE_SIZE(reln->smgr_rnode.node.dbNode); + // 初始化变量 int segno = -1; char path[UNDO_FILE_PATH_LEN]; File fd; @@ -258,55 +365,64 @@ void ExtendUndoFile(SMgrRelation reln, ForkNumber forknum, BlockNumber blockno, struct stat statBuffer; BlockNumber blockNum; RelFileNodeForkNum filenode; - + // 如果传入的块号无效,则报告错误 if (blockno == InvalidBlockNumber) { ereport(ERROR, (errmsg(UNDOFORMAT("cannot extend undo file beyond %u blocks."), InvalidBlockNumber))); } - + // 如果Undo文件的状态信息为空,则分配一个新的状态信息对象 if (state == NULL) { state = AllocUndoFileState(); reln->fileState = state; } else if (state->file > 0) { FileClose(state->file); } + // 设置状态信息 SetUndoFileState(state, -1, -1); WHITEBOX_TEST_STUB(UNDO_EXTEND_FILE_FAILED, WhiteboxDefaultErrorEmit); - - ADIO_RUN() { + // 条件编译,根据是否支持ADIO设置标志 + ADIO_RUN() + { flags |= O_DIRECT; } ADIO_END(); - + // 计算扩展块所在的段号 segno = (int)(blockno / undoFileBlocks); - GetUndoFilePath(reln->smgr_rnode.node.relNode, reln->smgr_rnode.node.dbNode, - segno, path, UNDO_FILE_PATH_LEN); + // 获取Undo文件的完整路径 + GetUndoFilePath(reln->smgr_rnode.node.relNode, reln->smgr_rnode.node.dbNode, segno, path, UNDO_FILE_PATH_LEN); + // 构建文件节点对象 filenode = RelFileNodeForkNumFill(reln->smgr_rnode, forknum, segno); + // 打开文件 fd = DataFileIdOpenFile(path, filenode, (int)flags, S_IRUSR | S_IWUSR); + // 如果打开文件失败,则进行错误处理 if (fd < 0) { int saveErrno = errno; DECLARE_NODE_COUNT(); GET_UPERSISTENCE_BY_ZONEID((int)reln->smgr_rnode.node.relNode, nodeCount); CheckUndoFileDirectory(upersistence); fd = DataFileIdOpenFile(path, filenode, (int)flags, S_IRUSR | S_IWUSR); + // 如果再次打开文件失败,则报告错误 if (fd < 0) { /* be sure to report the error reported by create, not open */ + // 如果再次打开文件失败,则报告错误 errno = saveErrno; CloseUndoFile(reln, forknum, InvalidBlockNumber); ereport(ERROR, (errmsg(UNDOFORMAT("could not create file \"%s\": %m."), path))); } } - + // 获取文件状态信息 if (fstat(GetVfdCache()[fd].fd, &statBuffer) < 0) { CloseUndoFile(reln, forknum, InvalidBlockNumber); ereport(ERROR, (errmsg(UNDOFORMAT("could not stat file \"%s\": %m."), path))); } - + // 设置状态信息 SetUndoFileState(state, segno, fd); + // 获取文件的当前位置 seekpos = statBuffer.st_size; char undoBuffer[BLCKSZ] = {'\0'}; /* Extend file to undoFileSize. */ + // 扩展文件到期望大小 while (seekpos < (off_t)undoFileSize) { off_t diffSize = (off_t)undoFileSize - seekpos; if (diffSize < BLCKSZ) { @@ -314,21 +430,25 @@ void ExtendUndoFile(SMgrRelation reln, ForkNumber forknum, BlockNumber blockno, } else { nbytes = FilePWrite(fd, (char *)undoBuffer, BLCKSZ, seekpos, WAIT_EVENT_UNDO_FILE_EXTEND); } + // 扩展文件到期望大小 if (nbytes < 0) { CloseUndoFile(reln, forknum, InvalidBlockNumber); + // 删除创建失败的Undo文件 if (unlink(path) != 0) { - ereport(ERROR, (errmsg(UNDOFORMAT("could not delete undo file during initialization \"%s\": %m."), path))); + ereport(ERROR, + (errmsg(UNDOFORMAT("could not delete undo file during initialization \"%s\": %m."), path))); } ereport(ERROR, (errmsg(UNDOFORMAT("could not initialize undo log segment file \"%s\": %m."), path))); } seekpos += (off_t)nbytes; } - + // 如果不跳过fsync并且不是临时文件,则注册Dirty Undo Segment if (!skipFsync && !SmgrIsTemp(reln)) { RegisterDirtyUndoSegment(reln, state); } - + // 获取文件的块数 blockNum = GetUndoFileBlocks(reln, forknum, state); + // 如果文件的块数不等于期望的块数,则报告PANIC错误 if (blockNum != undoFileBlocks) { ereport(PANIC, (errmsg(UNDOFORMAT("The undo file \"%s\" size is incorrect, blockNum=%u."), path, blockNum))); } @@ -336,26 +456,46 @@ void ExtendUndoFile(SMgrRelation reln, ForkNumber forknum, BlockNumber blockno, } /* Open the undo file and return the UndoFileState. */ -static UndoFileState *OpenUndoFile(SMgrRelation reln, ForkNumber forknum, BlockNumber blockno, ExtensionBehavior behavior) +/* + * 功能:打开Undo文件 + * + * 参数列表: + * reln:SMgrRelation对象,用于表示关联的Undo文件 + * forknum:ForkNumber,表示文件的分叉类型 + * blockno:要打开的块号 + * behavior:扩展行为(用于处理文件不存在的情况) + * + * 返回值:打开的Undo文件的状态信息(UndoFileState对象) + */ +static UndoFileState *OpenUndoFile(SMgrRelation reln, ForkNumber forknum, BlockNumber blockno, + ExtensionBehavior behavior) { + // 确保SMgrRelation对象不为空 Assert(reln != NULL); + // 获取Undo文件的状态信息 UndoFileState *state = (UndoFileState *)reln->fileState; + + // 获取Undo文件的块大小 uint32 undoFileBlocks = UNDO_FILE_BLOCK(reln->smgr_rnode.node.dbNode); + + // 构建Undo文件的路径 char path[UNDO_FILE_PATH_LEN]; File fd; uint32 flags = O_RDWR | PG_BINARY; + // 计算块所在的段号 int segno = (int)(blockno / undoFileBlocks); BlockNumber blockNum; RelFileNodeForkNum filenode; WHITEBOX_TEST_STUB(UNDO_OPEN_FILE_FAILED, WhiteboxDefaultErrorEmit); - + // 如果传入的块号无效,则报告错误 if (blockno == InvalidBlockNumber) { ereport(ERROR, (errmsg(UNDOFORMAT("cannot open undo file beyond %u blocks."), InvalidBlockNumber))); } /* No work if already open */ + // 如果文件已经打开,则直接返回状态信息 if (state != NULL) { if (state->file > 0) { if (state->segno == segno) { @@ -368,35 +508,43 @@ static UndoFileState *OpenUndoFile(SMgrRelation reln, ForkNumber forknum, BlockN state = AllocUndoFileState(); reln->fileState = state; } + // 设置状态信息 SetUndoFileState(state, -1, -1); - - ADIO_RUN() { + // 条件编译,根据是否支持ADIO设置标志 + ADIO_RUN() + { flags |= O_DIRECT; } ADIO_END(); - - GetUndoFilePath(reln->smgr_rnode.node.relNode, reln->smgr_rnode.node.dbNode, - segno, path, UNDO_FILE_PATH_LEN); + // 获取Undo文件的完整路径 + GetUndoFilePath(reln->smgr_rnode.node.relNode, reln->smgr_rnode.node.dbNode, segno, path, UNDO_FILE_PATH_LEN); + // 构建文件节点对象 filenode = RelFileNodeForkNumFill(reln->smgr_rnode, forknum, segno); + // 打开文件 fd = DataFileIdOpenFile(path, filenode, (int)flags, S_IRUSR | S_IWUSR); - + // 如果打开文件失败,则进行错误处理 if (fd < 0) { - if ((behavior == EXTENSION_RETURN_NULL) && FILE_POSSIBLY_DELETED(errno)) { + if ((behavior == EXTENSION_RETURN_NULL) && FILE_POSSIBLY_DELETED(errno)) { // 文件可能已被删除 return NULL; } + // 检查系统是否处于引导处理模式或者正在进行恢复操作 + // 如果是其中之一,表示系统可能处于初始化或者恢复阶段,此时尝试创建文件 if (IsBootstrapProcessingMode() || t_thrd.xlog_cxt.InRecovery) { DECLARE_NODE_COUNT(); GET_UPERSISTENCE_BY_ZONEID((int)reln->smgr_rnode.node.relNode, nodeCount); - CheckUndoFileDirectory(upersistence); - ExtendUndoFile(reln, forknum, blockno, NULL, false); - fd = DataFileIdOpenFile(path, filenode, (int)flags, S_IRUSR | S_IWUSR); + CheckUndoFileDirectory(upersistence); // 用于检查Undo文件目录是否存在,如果不存在则创建它 + ExtendUndoFile(reln, forknum, blockno, NULL, false); // 扩展Undo文件,以适应要读取的块 + fd = DataFileIdOpenFile(path, filenode, (int)flags, + S_IRUSR | S_IWUSR); // 尝试再次打开文件的操作,使用相同的路径和文件节点 } if (fd < 0) { - CloseUndoFile(reln, forknum, InvalidBlockNumber); + CloseUndoFile(reln, forknum, InvalidBlockNumber); // 关闭Undo文件 + // 检查 fetchRecord 是否为 true,表示当前上下文正在尝试获取Undo记录 if (t_thrd.undo_cxt.fetchRecord == true) { - t_thrd.undo_cxt.fetchRecord = false; + t_thrd.undo_cxt.fetchRecord = false; // 表示不再尝试获取Undo记录 if (t_thrd.storage_cxt.InProgressBuf != NULL) { Buffer buffer = BufferDescriptorGetBuffer(t_thrd.storage_cxt.InProgressBuf); + // 终止缓冲区IO操作,但不标记为有效 TerminateBufferIO(t_thrd.storage_cxt.InProgressBuf, false, BM_VALID); ReleaseBuffer(buffer); } @@ -406,16 +554,21 @@ static UndoFileState *OpenUndoFile(SMgrRelation reln, ForkNumber forknum, BlockN } } } - + // 设置状态信息 SetUndoFileState(state, segno, fd); + // 获取文件的块数 blockNum = GetUndoFileBlocks(reln, forknum, state); + // 如果文件的块数小于期望的块数,则报告警告并扩展文件 if (blockNum < undoFileBlocks) { ereport(WARNING, (errmsg(UNDOFORMAT("The undo file \"%s\" size is small than undoFileBlocks, " - "file blockNum=%u, we will extend undo file."), path, blockNum))); + "file blockNum=%u, we will extend undo file."), + path, blockNum))); ExtendUndoFile(reln, forknum, blockno, NULL, false); } else if (blockNum > undoFileBlocks) { + // 如果文件的块数小于期望的块数,则报告警告并扩展文件 ereport(WARNING, (errmsg(UNDOFORMAT("The undo file \"%s\" size is big than undoFileBlocks, " - "file blockNum=%u, we will extend undo file."), path, blockNum))); + "file blockNum=%u, we will extend undo file."), + path, blockNum))); /* close undofile before unlink undo file */ CloseUndoFile(reln, forknum, InvalidBlockNumber); UnlinkUndoFile(reln->smgr_rnode, forknum, true, blockno); @@ -425,43 +578,67 @@ static UndoFileState *OpenUndoFile(SMgrRelation reln, ForkNumber forknum, BlockN } /* Read the specified block from a undo file. */ +/* + * 功能:从Undo文件中读取数据块 + * + * 参数列表: + * reln:SMgrRelation对象,表示关联的Undo文件 + * forknum:ForkNumber,表示文件的分叉类型 + * blockno:要读取的块号 + * buffer:用于存储读取数据的缓冲区 + * + */ SMGR_READ_STATUS ReadUndoFile(SMgrRelation reln, ForkNumber forknum, BlockNumber blockno, char *buffer) { + // 确保缓冲区不为空 Assert(buffer != NULL); - + // 用于存储Undo文件的状态信息 UndoFileState *state = NULL; char *fileName = NULL; off_t seekpos; int nbytes; + // 获取Undo文件的块大小 uint32 undoFileBlocks = UNDO_FILE_BLOCK(reln->smgr_rnode.node.dbNode); - + // 打开Undo文件,如果文件不存在则返回错误 state = OpenUndoFile(reln, forknum, blockno, EXTENSION_FAIL); + // 计算要读取的位置 seekpos = (off_t)BLCKSZ * (blockno % undoFileBlocks); fileName = FilePathName(state->file); - + // 断言要读取的位置在文件有效范围内 Assert(seekpos < (off_t)(undoFileBlocks * BLCKSZ)); - + // 条件编译,根据是否支持ADIO设置标志 WHITEBOX_TEST_STUB(UNDO_READ_FILE_FAILED, WhiteboxDefaultErrorEmit); - + // 从Undo文件中读取数据块 nbytes = FilePRead(state->file, buffer, BLCKSZ, seekpos, WAIT_EVENT_UNDO_FILE_READ); + // 如果读取的字节数不等于期望的块大小,报告错误 if (nbytes != BLCKSZ) { CloseUndoFile(reln, forknum, InvalidBlockNumber); if (nbytes < 0) { ereport(ERROR, (errmsg(UNDOFORMAT("could not read block %u in file \"%s\": %m."), blockno, fileName))); } - ereport(ERROR, (errmsg(UNDOFORMAT("could not read block %u in file \"%s\": read only %d of %d bytes."), - blockno, fileName, nbytes, BLCKSZ))); + ereport(ERROR, (errmsg(UNDOFORMAT("could not read block %u in file \"%s\": read only %d of %d bytes."), blockno, + fileName, nbytes, BLCKSZ))); } - + // 检查读取的数据块是否通过校验,返回相应的状态 if (PageIsVerified((Page)buffer, blockno)) { return SMGR_RD_OK; } else { return SMGR_RD_CRC_ERROR; } } - +/* + * 功能:将数据写入Undo文件指定块 + * + * 参数列表: + * reln:SMgrRelation对象,表示关联的Undo文件 + * forknum:ForkNumber,表示文件的分叉类型 + * blockNum:要写入的块号 + * buffer:包含待写入数据的缓冲区 + * skipFsync:是否跳过Fsync操作的标志 + */ void WriteUndoFile(SMgrRelation reln, ForkNumber forknum, BlockNumber blockNum, const char *buffer, bool skipFsync) { + // 检查输入缓冲区是否为空 Assert(buffer != NULL); UndoFileState *state = NULL; @@ -469,23 +646,31 @@ void WriteUndoFile(SMgrRelation reln, ForkNumber forknum, BlockNumber blockNum, int nbytes; off_t seekpos; uint32 undoFileBlocks = UNDO_FILE_BLOCK(reln->smgr_rnode.node.dbNode); - + // 打开或创建Undo文件,如果文件不存在则返回NULL state = OpenUndoFile(reln, forknum, blockNum, EXTENSION_FAIL); seekpos = (off_t)BLCKSZ * (blockNum % undoFileBlocks); fileName = FilePathName(state->file); - + // 检查写入位置是否合法 Assert(seekpos < (off_t)(undoFileBlocks * BLCKSZ)); - + // 模拟白盒测试:模拟写入文件失败 + /* + * 白盒测试是一种测试手段,用于模拟在测试环境中产生写文件失败的情况, + * 以确保系统在面对异常情况时有适当的处理机制。 + */ WHITEBOX_TEST_STUB(UNDO_WRITE_FILE_FAILED, WhiteboxDefaultErrorEmit); - + // 使用FilePWrite函数将数据写入Undo文件的指定位置 nbytes = FilePWrite(state->file, buffer, BLCKSZ, seekpos, WAIT_EVENT_UNDO_FILE_WRITE); + // 如果写入的字节数不等于块大小,表示写入失败 if (nbytes != BLCKSZ) { + // 关闭Undo文件 CloseUndoFile(reln, forknum, InvalidBlockNumber); + // 如果写入出错,报错并记录错误信息 if (nbytes < 0) { ereport(ERROR, (errmsg(UNDOFORMAT("could not write block %u in file \"%s\": %m."), blockNum, fileName))); } + // 如果写入不完整,报错并记录错误信息 ereport(ERROR, (errmsg(UNDOFORMAT("could not write block %u in file \"%s\": wrote only %d of %d bytes."), - blockNum, fileName, nbytes, BLCKSZ))); + blockNum, fileName, nbytes, BLCKSZ))); } /* Tell checkpointer this file is dirty. */ @@ -494,83 +679,122 @@ void WriteUndoFile(SMgrRelation reln, ForkNumber forknum, BlockNumber blockNum, } return; } - +/* + * 功能:将 Undo 文件的一个或多个数据块写回磁盘 + * + * 参数列表: + * reln:SMgrRelation对象,表示关联的Undo文件 + * forknum:ForkNumber,表示文件的分叉类型 + * blockno:要写回的块的起始块号 + * nblocks:要写回的块的数量 + */ void WritebackUndoFile(SMgrRelation reln, ForkNumber forknum, BlockNumber blockno, BlockNumber nblocks) { + // 获取 Undo 文件块的大小,通常是 8KB uint32 undoFileBlocks = UNDO_FILE_BLOCK(reln->smgr_rnode.node.dbNode); - + // 获取 Undo 文件块的大小,通常是 8KB while (nblocks > 0) { UndoFileState *state = NULL; int segStart; int segEnd; BlockNumber nflush = nblocks; off_t seekpos; - + // 打开或创建 Undo 文件,如果文件不存在,函数返回 NULL state = OpenUndoFile(reln, forknum, blockno, EXTENSION_RETURN_NULL); + // 计算当前写回块的起始段和结束段 segStart = blockno / undoFileBlocks; segEnd = (blockno + nblocks - 1) / undoFileBlocks; /* * We might be flushing buffers of already removed relations, that's * ok, just ignore that case. */ + // 如果当前 Undo 文件不存在(可能已被删除),直接返回 if (state == NULL) { return; } - + // 如果当前写回跨越多个段,计算出要写回的块数量 if (segStart != segEnd) { nflush = undoFileBlocks - (blockno % undoFileBlocks); } - + // 断言要写回的块数量在合理范围内 Assert(nflush >= 1); Assert(nflush <= nblocks); - + // 计算要写回的块的偏移量 seekpos = (off_t)BLCKSZ * (blockno % undoFileBlocks); + // 调用 FileWriteback 函数将块写回 Undo 文件 FileWriteback(state->file, seekpos, (off_t)BLCKSZ * nflush); - + // 更新剩余块数和块号 nblocks -= nflush; blockno += nflush; } } - +/* + * 功能:预取(Prefetch)Undo文件的一个数据块到内存中 + * + * 参数列表: + * reln:SMgrRelation对象,表示关联的Undo文件 + * forknum:ForkNumber,表示文件的分叉类型 + * blockNum:要预取的块号 + */ void PrefetchUndoFile(SMgrRelation reln, ForkNumber forknum, BlockNumber blockNum) { + /* + *检查是否定义了 USE_PREFETCH 宏。如果在编译时定义了这个宏,那么条件成立,后面的代码块会被编译和执行。 + */ #ifdef USE_PREFETCH + // 定义一个指向Undo文件状态结构的指针 UndoFileState *state = NULL; + // 计算要预取的块的偏移量 off_t seekpos; + // 获取Undo文件块的大小,通常是8KB uint32 undoFileBlocks = UNDO_FILE_BLOCK(reln->smgr_rnode.node.dbNode); - + // 打开或创建Undo文件,如果文件不存在,函数返回NULL state = OpenUndoFile(reln, forknum, blockNum, EXTENSION_FAIL); + // 打开或创建Undo文件,如果文件不存在,函数返回NULL seekpos = (off_t)BLCKSZ * (blockNum % undoFileBlocks); - + // 断言块的偏移量在合理范围内 Assert(seekpos < (off_t)(undoFileBlocks * BLCKSZ)); - + // 调用FilePrefetch函数将块预取到内存中 (void)FilePrefetch(state->file, seekpos, BLCKSZ, WAIT_EVENT_UNDO_FILE_PREFETCH); + + // 表示条件编译的结束标记 #endif /* USE_PREFETCH */ } -void UnlinkUndoFile(const RelFileNodeBackend& rnode, ForkNumber forkNum, bool isRedo, BlockNumber blockNum) +void UnlinkUndoFile(const RelFileNodeBackend &rnode, ForkNumber forkNum, bool isRedo, BlockNumber blockNum) { + // 断言块号不为InvalidBlockNumber,即要操作的块号合法 Assert(blockNum != InvalidBlockNumber); + // 定义一个用于存储Undo文件路径的字符数组 char path[UNDO_FILE_PATH_LEN]; + // 获取Undo文件块的大小,通常是8KB uint32 undoFileBlocks = UNDO_FILE_BLOCK(rnode.node.dbNode); + // 用于存储unlink函数的返回值 int ret; + // 从RelFileNodeBackend对象中获取关联Undo文件的Zone ID int zid = rnode.node.relNode; + // 计算块所在的Undo文件段号 int segno = blockNum / undoFileBlocks; + // 获取Undo文件的路径 GetUndoFilePath(zid, rnode.node.dbNode, segno, path, UNDO_FILE_PATH_LEN); - + // 如果正在执行redo操作,或者正在进行原地升级,或者文件分叉类型不是MAIN_FORKNUM if (isRedo || u_sess->attr.attr_common.IsInplaceUpgrade || forkNum != MAIN_FORKNUM || RelFileNodeBackendIsTemp(rnode) || rnode.node.bucketNode != InvalidBktId) { + // 如果文件不是临时文件,注册一个忘记Undo请求 if (!RelFileNodeBackendIsTemp(rnode)) { RegisterForgetUndoRequests(rnode, segno); } + // 报告等待事件,表示正在执行Undo文件删除操作 pgstat_report_waitevent(WAIT_EVENT_UNDO_FILE_UNLINK); + // 尝试删除Undo文件,如果删除失败,尝试再次删除 if (unlink(path) < 0 && errno != ENOENT) { /* try again */ if ((unlink(path) < 0) && (errno != ENOENT) && !isRedo) { ereport(WARNING, (errmsg(UNDOFORMAT("could not remove file \"%s\": %m."), path))); } } + // 报告等待事件,表示Undo文件删除操作结束 pgstat_report_waitevent(WAIT_EVENT_END); } else { /* truncate(2) would be easier here, but Windows hasn't got it */ @@ -578,6 +802,7 @@ void UnlinkUndoFile(const RelFileNodeBackend& rnode, ForkNumber forkNum, bool is fd = BasicOpenFile(path, O_RDWR | PG_BINARY, 0); if (fd >= 0) { int save_errno; + // 截断Undo文件,将其大小截断为0 ret = ftruncate(fd, 0); save_errno = errno; (void)close(fd); @@ -585,16 +810,25 @@ void UnlinkUndoFile(const RelFileNodeBackend& rnode, ForkNumber forkNum, bool is } else { ret = -1; } + // 如果截断操作失败并且不是因为文件不存在,则报错 if (ret < 0 && errno != ENOENT) { ereport(WARNING, (errcode_for_file_access(), errmsg("could not truncate file \"%s\": %m", path))); } /* Register request to unlink first segment later */ + // 注册一个请求,稍后删除第一个Undo文件段 RegisterUnlinkUndoRequests(rnode, segno); } return; } - +/* + * 功能:关闭Undo文件 + * + * 参数列表: + * reln:SMgrRelation对象,表示关联的Undo文件 + * forkNum:ForkNumber,表示文件的分叉类型 + * blockNum:要关闭的块号(可选参数,用于指定关闭哪个块,如果不需要关闭特定块,可以传入InvalidBlockNumber) + */ void CloseUndoFile(SMgrRelation reln, ForkNumber forkNum, BlockNumber blockNum) { Assert(reln != NULL); @@ -605,7 +839,7 @@ void CloseUndoFile(SMgrRelation reln, ForkNumber forkNum, BlockNumber blockNum) if (state == NULL) { return; } - reln->fileState = NULL; /* prevent dangling pointer after error */ + reln->fileState = NULL; /* prevent dangling pointer after error */ /* if not closed already */ if (state->file >= 0) { @@ -615,62 +849,122 @@ void CloseUndoFile(SMgrRelation reln, ForkNumber forkNum, BlockNumber blockNum) } /* Sync undo file. */ +/* + * 功能:同步(刷盘)Undo文件 + * + * 参数列表: + * tag:指向FileTag结构的指针,用于标识Undo文件的位置和属性 + * path:用于存储Undo文件路径的字符数组 + * + */ int SyncUndoFile(const FileTag *tag, char *path) { + // 使用smgropen打开指定Undo文件,reln为SMgrRelation对象 SMgrRelation reln = smgropen(tag->rnode, InvalidBackendId); - uint32 undoFileBlocks = UNDO_FILE_BLOCK(tag->rnode.dbNode); + uint32 undoFileBlocks = UNDO_FILE_BLOCK(tag->rnode.dbNode); // 获取Undo文件块数 - GetUndoFilePath(tag->rnode.relNode, tag->rnode.dbNode, tag->segno, - path, UNDO_FILE_PATH_LEN); - UndoFileState *state = OpenUndoFile(reln, tag->forknum, - tag->segno * undoFileBlocks, EXTENSION_RETURN_NULL); + // 获取指定Undo文件的路径 + GetUndoFilePath(tag->rnode.relNode, tag->rnode.dbNode, tag->segno, path, UNDO_FILE_PATH_LEN); + // 打开指定Undo文件的状态,如果文件不存在则返回NULL + UndoFileState *state = OpenUndoFile(reln, tag->forknum, tag->segno * undoFileBlocks, EXTENSION_RETURN_NULL); + // 如果文件状态为空,表示文件不存在或者未被打开,返回-1表示同步失败 if (state == NULL) { return -1; } - + // 调用FileSync函数对文件进行同步(刷盘)操作,等待同步完成 return FileSync(state->file, WAIT_EVENT_UNDO_FILE_SYNC); } - +/* + * 功能:尝试删除(解链接)Undo文件 + * + * 参数列表: + * tag:指向FileTag结构的指针,用于标识Undo文件的位置和属性 + * path:用于存储Undo文件路径的字符数组 + */ int SyncUnlinkUndoFile(const FileTag *tag, char *path) { - GetUndoFilePath(tag->rnode.relNode, tag->rnode.dbNode, tag->segno, - path, UNDO_FILE_PATH_LEN); + // 获取指定Undo文件的路径 + GetUndoFilePath(tag->rnode.relNode, tag->rnode.dbNode, tag->segno, path, UNDO_FILE_PATH_LEN); /* Try to unlink the file. */ + // 尝试删除(解链接)指定路径的文件,成功返回0,失败返回-1 return unlink(path); } - +/* + * 功能:注册Undo文件的删除请求 + * + * 参数列表: + * rnode:RelFileNodeBackend 结构,表示Undo文件的相关信息 + * segno:uint32,表示Undo文件的段号 + */ static void RegisterUnlinkUndoRequests(RelFileNodeBackend rnode, uint32 segno) { FileTag tag; + // 初始化一个FileTag结构,用于标识Undo文件的位置和属性 INIT_UNDO_FILE_TAG(tag, rnode.node, segno); - (void) RegisterSyncRequest(&tag, SYNC_UNLINK_REQUEST, true /* retryOnError */ ); + // 向同步请求队列中注册删除请求,设置retryOnError参数为true + (void)RegisterSyncRequest(&tag, SYNC_UNLINK_REQUEST, true /* retryOnError */); } - +/* + * 功能:注册忘记Undo文件的请求 + * + * 参数列表: + * rnode:RelFileNodeBackend 结构,表示Undo文件的相关信息 + * segno:uint32,表示Undo文件的段号 + */ static void RegisterForgetUndoRequests(RelFileNodeBackend rnode, uint32 segno) { FileTag tag; + // 初始化一个FileTag结构,用于标识Undo文件的位置和属性 INIT_UNDO_FILE_TAG(tag, rnode.node, segno); - (void) RegisterSyncRequest(&tag, SYNC_FORGET_REQUEST, true /* retryOnError */ ); + // 初始化一个FileTag结构,用于标识Undo文件的位置和属性 + (void)RegisterSyncRequest(&tag, SYNC_FORGET_REQUEST, true /* retryOnError */); } - +/* + * 功能:注册 "脏" 的Undo段的同步请求 + * + * 参数列表: + * reln:SMgrRelation 结构,表示Undo文件的关联关系 + * state:UndoFileState 结构,表示Undo文件的状态信息 + * + * 说明: + * 该函数会将一个 "脏" 的Undo段的同步请求添加到同步请求队列中,以确保Undo段中的数据被写入持久存储。 + * 如果同步请求队列已满,函数将尝试立即执行文件同步操作。 + */ static void RegisterDirtyUndoSegment(SMgrRelation reln, const UndoFileState *state) { /* Temp relations should never be fsync'd */ Assert(!SmgrIsTemp(reln)); FileTag tag; + // 初始化一个FileTag结构,用于标识Undo文件段的位置和属性 INIT_UNDO_FILE_TAG(tag, reln->smgr_rnode.node, state->segno); - - if (!RegisterSyncRequest(&tag, SYNC_REQUEST, false /* retryOnError */ )) { + // 向同步请求队列中注册 "脏" Undo段的同步请求,设置retryOnError参数为false + if (!RegisterSyncRequest(&tag, SYNC_REQUEST, false /* retryOnError */)) { ereport(DEBUG5, (errmsg(UNDOFORMAT("could not forward fsync request because request queue is full.")))); + // 如果同步请求队列已满,尝试立即执行文件同步操作 if (FileSync(state->file, WAIT_EVENT_DATA_FILE_SYNC) < 0) { ereport(ERROR, (errmsg(UNDOFORMAT("could not fsync file \"%s\": %m."), FilePathName(state->file)))); } } } - +/* + * 功能:检查指定的Undo文件是否存在 + * + * 参数列表: + * reln:SMgrRelation 结构,表示Undo文件的关联关系 + * forkNum:ForkNumber,表示文件的分叉类型 + * blockNum:要检查的块号 + * + * 返回值: + * 如果Undo文件存在,返回true;否则,返回false。 + * + * 说明: + * 该函数首先关闭指定的Undo文件,以确保我们注意到如果在打开文件后已被删除。 + * 然后,它尝试打开Undo文件。如果成功打开文件,它将返回true,否则返回false。 + * 最后,它再次关闭Undo文件,以确保文件状态不受影响。 + */ bool CheckUndoFileExists(SMgrRelation reln, ForkNumber forkNum, BlockNumber blockNum) { /* @@ -680,9 +974,11 @@ bool CheckUndoFileExists(SMgrRelation reln, ForkNumber forkNum, BlockNumber bloc CloseUndoFile(reln, forkNum, blockNum); bool isExist = false; + // 尝试打开Undo文件,如果成功打开文件,则将isExist设置为true if (OpenUndoFile(reln, forkNum, blockNum, EXTENSION_RETURN_NULL) != NULL) { isExist = true; } + // 再次关闭文件,以确保文件状态不受影响 CloseUndoFile(reln, forkNum, blockNum); return isExist; } diff --git a/src/gausskernel/storage/smgr/md.cpp b/src/gausskernel/storage/smgr/md.cpp index 51bd5d1d6..3302a0484 100644 --- a/src/gausskernel/storage/smgr/md.cpp +++ b/src/gausskernel/storage/smgr/md.cpp @@ -44,16 +44,15 @@ #include "pgstat.h" /* Populate a file tag describing an md.cpp segment file. */ -#define INIT_MD_FILETAG(tag, rNode, forkNum, segNo) \ - do \ - { \ - errno_t errorno = EOK; \ - errorno = memset_s(&(tag), sizeof(FileTag), (0), sizeof(FileTag)); \ - securec_check(errorno, "\0", "\0"); \ - (tag).handler = SYNC_HANDLER_MD; \ - (tag).forknum = (forkNum); \ - (tag).rnode = (rNode); \ - (tag).segno = (segNo); \ +#define INIT_MD_FILETAG(tag, rNode, forkNum, segNo) \ + do { \ + errno_t errorno = EOK; \ + errorno = memset_s(&(tag), sizeof(FileTag), (0), sizeof(FileTag)); \ + securec_check(errorno, "\0", "\0"); \ + (tag).handler = SYNC_HANDLER_MD; \ + (tag).forknum = (forkNum); \ + (tag).rnode = (rNode); \ + (tag).segno = (segNo); \ } while (false); constexpr mode_t FILE_RW_PERMISSION = 0600; @@ -105,8 +104,8 @@ inline static uint4 PageCompressChunkSize(SMgrRelation reln) */ typedef struct _MdfdVec { File mdfd_vfd; /* fd number in fd.c's pool */ - File mdfd_vfd_pca; /* page compression address file 's fd number in fd.cpp's pool */ - File mdfd_vfd_pcd; /* page compression data file 's fd number in fd.cpp's pool */ + File mdfd_vfd_pca; /* page compression address file 's fd number in fd.cpp's pool */ + File mdfd_vfd_pcd; /* page compression data file 's fd number in fd.cpp's pool */ BlockNumber mdfd_segno; /* segment number, from 0 */ struct _MdfdVec *mdfd_chain; /* next segment, or NULL */ } MdfdVec; @@ -117,71 +116,156 @@ static MdfdVec *mdopen(SMgrRelation reln, ForkNumber forknum, ExtensionBehavior static MdfdVec *_fdvec_alloc(void); static char *_mdfd_segpath(const SMgrRelation reln, ForkNumber forknum, BlockNumber segno); static MdfdVec *_mdfd_openseg(SMgrRelation reln, ForkNumber forkno, BlockNumber segno, int oflags); -static MdfdVec *_mdfd_getseg(SMgrRelation reln, ForkNumber forkno, BlockNumber blkno, bool skipFsync, ExtensionBehavior behavior); +static MdfdVec *_mdfd_getseg(SMgrRelation reln, ForkNumber forkno, BlockNumber blkno, bool skipFsync, + ExtensionBehavior behavior); static BlockNumber _mdnblocks(SMgrRelation reln, ForkNumber forknum, const MdfdVec *seg); static void register_dirty_segment(SMgrRelation reln, ForkNumber forknum, const MdfdVec *seg); static void register_unlink_segment(RelFileNodeBackend rnode, ForkNumber forknum, BlockNumber segno); /* function of compressed table */ static int sync_pcmap(PageCompressHeader *pcMap, uint32 wait_event_info); - +/* + * 功能:检查指定的Undo文件是否存在 + * + * 参数列表: + * rnode:RelFileNode 结构,表示Undo文件的关联关系 + * forknum:ForkNumber,表示文件的分叉类型 + */ bool check_unlink_rel_hashtbl(RelFileNode rnode, ForkNumber forknum) { - HTAB* relfilenode_hashtbl = g_instance.bgwriter_cxt.unlink_rel_hashtbl; - HTAB* relfilenode_fork_hashtbl = g_instance.bgwriter_cxt.unlink_rel_fork_hashtbl; + // 获取名为unlink_rel_hashtbl的哈希表的指针 + HTAB *relfilenode_hashtbl = g_instance.bgwriter_cxt.unlink_rel_hashtbl; + // 获取名为unlink_rel_fork_hashtbl的哈希表的指针 + HTAB *relfilenode_fork_hashtbl = g_instance.bgwriter_cxt.unlink_rel_fork_hashtbl; + + // 定义一个结构体变量entry_key,用于存储关键字信息 ForkRelFileNode entry_key; + // 布尔变量found用于表示是否找到了关键字,初始值为false bool found = false; + // 获取名为rel_hashtbl_lock的轻量级锁(LWLock),以确保安全访问unlink_rel_hashtbl哈希表 LWLockAcquire(g_instance.bgwriter_cxt.rel_hashtbl_lock, LW_SHARED); + + // 在unlink_rel_hashtbl哈希表中搜索关键字rnode,并将搜索结果存储在found变量中 (void)hash_search(relfilenode_hashtbl, &(rnode), HASH_FIND, &found); + + // 释放rel_hashtbl_lock锁,允许其他线程访问unlink_rel_hashtbl哈希表 LWLockRelease(g_instance.bgwriter_cxt.rel_hashtbl_lock); + // 如果在unlink_rel_hashtbl哈希表中没有找到关键字rnode if (!found) { + // 设置entry_key的rnode和forkNum字段 entry_key.rnode = rnode; entry_key.forkNum = forknum; + + // 获取名为rel_one_fork_hashtbl_lock的轻量级锁(LWLock),以确保安全访问unlink_rel_fork_hashtbl哈希表 LWLockAcquire(g_instance.bgwriter_cxt.rel_one_fork_hashtbl_lock, LW_SHARED); + + // 在unlink_rel_fork_hashtbl哈希表中搜索关键字entry_key,并将搜索结果存储在found变量中 (void)hash_search(relfilenode_fork_hashtbl, &(entry_key), HASH_FIND, &found); + + // 释放rel_one_fork_hashtbl_lock锁,允许其他线程访问unlink_rel_fork_hashtbl哈希表 LWLockRelease(g_instance.bgwriter_cxt.rel_one_fork_hashtbl_lock); } + + // 返回found变量的值,表示是否找到了关键字 return found; } - -static int OpenPcaFile(const char *path, const RelFileNodeBackend &node, const ForkNumber &forkNum, const uint32 &segNo, int oflags = 0) +/* + * 功能:打开指定的PCA文件 + * + * 参数列表: + * path:PCA文件的路径 + * node:RelFileNodeBackend 结构,表示Undo文件的关联关系 + * forkNum:ForkNumber,表示文件的分叉类型 + * segNo:要打开的段号 + * oflags:打开文件的标志位(默认值为0) + */ +static int OpenPcaFile(const char *path, const RelFileNodeBackend &node, const ForkNumber &forkNum, const uint32 &segNo, + int oflags = 0) { + // 断言,确保node的opt字段不为0且forkNum为MAIN_FORKNUM Assert(node.node.opt != 0 && forkNum == MAIN_FORKNUM); + + // 定义一个字符数组dst,用于存储目标文件路径 char dst[MAXPGPATH]; + + // 调用CopyCompressedPath函数,将path复制到dst,并添加COMPRESSED_TABLE_PCA_FILE后缀 CopyCompressedPath(dst, path, COMPRESSED_TABLE_PCA_FILE); + + // 定义一个整数变量flags,用于表示文件打开的标志位 + // O_RDWR表示以可读写方式打开文件 + // PG_BINARY表示以二进制模式打开文件 + // oflags表示其他用户自定义的标志位 uint32 flags = O_RDWR | PG_BINARY | oflags; + + // 调用DataFileIdOpenFile函数,以指定的路径和参数打开文件,并返回文件描述符 return DataFileIdOpenFile(dst, RelFileNodeForkNumFill(node, PCA_FORKNUM, segNo), (int)flags, S_IRUSR | S_IWUSR); } - -static int OpenPcdFile(const char *path, const RelFileNodeBackend &node, const ForkNumber &forkNum, const uint32 &segNo, int oflags = 0) +/* + * 功能:打开指定的PCD文件 + * + * 参数列表: + * path:PCD文件的路径 + * node:RelFileNodeBackend 结构,表示Undo文件的关联关系 + * forkNum:ForkNumber,表示文件的分叉类型 + * segNo:要打开的段号 + * oflags:打开文件的标志位(默认值为0) + */ +static int OpenPcdFile(const char *path, const RelFileNodeBackend &node, const ForkNumber &forkNum, const uint32 &segNo, + int oflags = 0) { + // 断言,确保node的opt字段不为0且forkNum为MAIN_FORKNUM Assert(node.node.opt != 0 && forkNum == MAIN_FORKNUM); + + // 定义一个字符数组dst,用于存储目标文件路径 char dst[MAXPGPATH]; + + // 调用CopyCompressedPath函数,将path复制到dst,并添加COMPRESSED_TABLE_PCD_FILE后缀 CopyCompressedPath(dst, path, COMPRESSED_TABLE_PCD_FILE); + + // 定义一个整数变量flags,用于表示文件打开的标志位 + // O_RDWR表示以可读写方式打开文件 + // PG_BINARY表示以二进制模式打开文件 + // oflags表示其他用户自定义的标志位 uint32 flags = O_RDWR | PG_BINARY | oflags; + + // 调用DataFileIdOpenFile函数,以指定的路径和参数打开文件,并返回文件描述符 return DataFileIdOpenFile(dst, RelFileNodeForkNumFill(node, PCD_FORKNUM, segNo), (int)flags, S_IRUSR | S_IWUSR); } - +/* + * 功能:注册脏段并执行同步操作 + * + * 参数列表: + * reln:SMgrRelation 结构,表示与脏段关联的存储管理器关系 + * forknum:ForkNumber,表示文件的分叉类型 + * seg:MdfdVec 结构,表示脏段的元数据文件描述符向量 + */ static void RegisterCompressDirtySegment(SMgrRelation reln, ForkNumber forknum, const MdfdVec *seg) { + // 获取压缩页的内存映射 PageCompressHeader *pcMap = GetPageCompressMemoryMap(seg->mdfd_vfd_pca, PageCompressChunkSize(reln)); + // 同步压缩页内存映射到磁盘,等待COMPRESS_ADDRESS_FILE_SYNC事件 if (sync_pcmap(pcMap, WAIT_EVENT_COMPRESS_ADDRESS_FILE_SYNC) != 0) { + // 如果同步失败并且需要取消链接文件,输出DEBUG级别的错误消息,然后返回 if (check_unlink_rel_hashtbl(reln->smgr_rnode.node, forknum)) { ereport(DEBUG1, (errmsg("could not fsync file \"%s\": %m", FilePathName(seg->mdfd_vfd)))); return; } - ereport(data_sync_elevel(ERROR), (errcode_for_file_access(), errmsg("could not msync file \"%s\": %m", - FilePathName(seg->mdfd_vfd_pca)))); + // 否则,输出ERROR级别的错误消息,表示同步失败 + ereport(data_sync_elevel(ERROR), (errcode_for_file_access(), + errmsg("could not msync file \"%s\": %m", FilePathName(seg->mdfd_vfd_pca)))); } + // 同步脏段的数据文件到磁盘,等待DATA_FILE_SYNC事件 if (FileSync(seg->mdfd_vfd_pcd, WAIT_EVENT_DATA_FILE_SYNC) < 0) { + // 如果同步失败并且需要取消链接文件,输出DEBUG级别的错误消息,然后返回 if (check_unlink_rel_hashtbl(reln->smgr_rnode.node, forknum)) { ereport(DEBUG1, (errmsg("could not fsync file \"%s\": %m", FilePathName(seg->mdfd_vfd)))); return; } - ereport(data_sync_elevel(ERROR), (errcode_for_file_access(), errmsg("could not fsync file \"%s\": %m", - FilePathName(seg->mdfd_vfd_pcd)))); + // 否则,输出ERROR级别的错误消息,表示同步失败 + ereport(data_sync_elevel(ERROR), (errcode_for_file_access(), + errmsg("could not fsync file \"%s\": %m", FilePathName(seg->mdfd_vfd_pcd)))); } } /* @@ -213,8 +297,8 @@ static void register_dirty_segment(SMgrRelation reln, ForkNumber forknum, const ereport(DEBUG1, (errmsg("could not fsync file \"%s\": %m", FilePathName(seg->mdfd_vfd)))); return; } - ereport(data_sync_elevel(ERROR), (errcode_for_file_access(), - errmsg("could not fsync file \"%s\": %m", FilePathName(seg->mdfd_vfd)))); + ereport(data_sync_elevel(ERROR), (errcode_for_file_access(), errmsg("could not fsync file \"%s\": %m", + FilePathName(seg->mdfd_vfd)))); } } } @@ -223,15 +307,25 @@ static void register_dirty_segment(SMgrRelation reln, ForkNumber forknum, const /* * register_unlink_segment() -- Schedule a file to be deleted after next checkpoint */ +/* + * 功能:注册取消链接段的同步请求 + * + * 参数列表: + * rnode:RelFileNodeBackend 结构,表示与脏段关联的存储管理器关系 + * forknum:ForkNumber,表示文件的分叉类型 + * segno:要取消链接的段号 + */ static void register_unlink_segment(RelFileNodeBackend rnode, ForkNumber forknum, BlockNumber segno) { FileTag tag; - + // 初始化文件标签tag,用于标识要取消链接的文件段 INIT_MD_FILETAG(tag, rnode.node, forknum, segno); /* Should never be used with temp relations */ + // 初始化文件标签tag,用于标识要取消链接的文件段 Assert(!RelFileNodeBackendIsTemp(rnode)); - + // 注册同步请求,要求取消链接指定的文件段 + // SYNC_UNLINK_REQUEST表示取消链接请求,retryOnError表示在错误情况下是否重试 RegisterSyncRequest(&tag, SYNC_UNLINK_REQUEST, true /* retryOnError */); } @@ -245,46 +339,70 @@ void md_register_forget_request(RelFileNode rnode, ForkNumber forknum, BlockNumb RegisterSyncRequest(&tag, SYNC_FORGET_REQUEST, true /* retryOnError */); } - +/* + * 功能:检查分配的块数和块号是否有效 + * + * 参数列表: + * pcAddr:PageCompressAddr 结构,表示压缩页的地址信息 + * chunk_size:uint32,表示块的大小 + * blocknum:BlockNumber,表示块的编号 + * v:MdfdVec 结构,表示脏段的元数据文件描述符向量 + */ static void allocate_chunk_check(PageCompressAddr *pcAddr, uint32 chunk_size, BlockNumber blocknum, MdfdVec *v) { /* check allocated chunk number */ Assert(chunk_size == BLCKSZ / 2 || chunk_size == BLCKSZ / 4 || chunk_size == BLCKSZ / 8 || - chunk_size == BLCKSZ / 16); + chunk_size == BLCKSZ / 16); // 断言,确保chunk_size是有效的块大小,可以是BLCKSZ的2、4、8或16分之一 if (pcAddr->allocated_chunks > BLCKSZ / chunk_size) { - ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("invalid chunks %u of block %u in file \"%s\"", - pcAddr->allocated_chunks, blocknum, - FilePathName(v->mdfd_vfd_pca)))); + // 如果分配的块数大于文件块大小与块大小的比值,报错 + ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("invalid chunks %u of block %u in file \"%s\"", pcAddr->allocated_chunks, blocknum, + FilePathName(v->mdfd_vfd_pca)))); } auto maxChunkNumbers = MAX_CHUNK_NUMBER(chunk_size); for (auto i = 0; i < pcAddr->allocated_chunks; i++) { if (pcAddr->chunknos[i] <= 0 || pcAddr->chunknos[i] > maxChunkNumbers) { - ereport(ERROR, - (errcode(ERRCODE_DATA_CORRUPTED), errmsg("invalid chunk number %u of block %u in file \"%s\"", - pcAddr->chunknos[i], blocknum, - FilePathName(v->mdfd_vfd_pca)))); + // 如果分配的块号小于等于0或大于最大允许块号,报错 + ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("invalid chunk number %u of block %u in file \"%s\"", pcAddr->chunknos[i], blocknum, + FilePathName(v->mdfd_vfd_pca)))); } } } - -int openrepairfile(char* path, RelFileNodeForkNum filenode) +/* + * 功能:打开用于修复的文件 + * + * 参数列表: + * path:要打开的文件路径 + * filenode:RelFileNodeForkNum 结构,表示文件的关联关系和分叉类型 + */ +int openrepairfile(char *path, RelFileNodeForkNum filenode) { + // 初始化文件描述符为-1 int fd = -1; const int TEMPLEN = 8; + // 定义并初始化打开文件的标志位,以支持读写和二进制模式 volatile uint32 repair_flags = O_RDWR | PG_BINARY; + // 分配用于构建临时文件路径的内存 char *temppath = (char *)palloc(strlen(path) + TEMPLEN); + // 使用sprintf_s构建临时文件路径,并检查是否出错 errno_t rc = sprintf_s(temppath, strlen(path) + TEMPLEN, "%s.repair", path); securec_check_ss(rc, "", ""); + // 根据编译器支持的标志位,决定是否使用O_DIRECT ADIO_RUN() { repair_flags |= O_DIRECT; } ADIO_END(); + // 调用DataFileIdOpenFile函数,以临时文件路径和filenode参数打开文件 + // 并返回文件描述符 fd = DataFileIdOpenFile(temppath, filenode, (int)repair_flags, 0600); + // 如果打开文件失败,输出警告消息 if (fd < 0) { ereport(WARNING, (errmsg("[file repair] could not open repair file %s: %m", temppath))); } + // 释放分配的内存 pfree(temppath); return fd; } @@ -317,7 +435,7 @@ bool mdexists(SMgrRelation reln, ForkNumber forkNum, BlockNumber blockNum) return (mdopen(reln, forkNum, EXTENSION_RETURN_NULL) != NULL); } -static int RetryDataFileIdOpenFile(bool isRedo, char* path, const RelFileNodeForkNum &filenode, uint32 flags) +static int RetryDataFileIdOpenFile(bool isRedo, char *path, const RelFileNodeForkNum &filenode, uint32 flags) { int save_errno = errno; int fd = -1; @@ -353,7 +471,6 @@ static int RetryDataFileIdOpenFile(bool isRedo, char* path, const RelFileNodeFor ereport(ERROR, (errcode_for_file_access(), errmsg("could not create file \"%s\": %m", path))); } return fd; - } /* @@ -363,7 +480,7 @@ static int RetryDataFileIdOpenFile(bool isRedo, char* path, const RelFileNodeFor */ void mdcreate(SMgrRelation reln, ForkNumber forkNum, bool isRedo) { - char* path = NULL; + char *path = NULL; File fd; RelFileNodeForkNum filenode; volatile uint32 flags = O_RDWR | O_CREAT | O_EXCL | PG_BINARY; @@ -441,7 +558,7 @@ void mdcreate(SMgrRelation reln, ForkNumber forkNum, bool isRedo) reln->md_fd[forkNum] = _fdvec_alloc(); reln->md_fd[forkNum]->mdfd_vfd_pca = fd_pca; - reln->md_fd[forkNum] ->mdfd_vfd_pcd = fd_pcd; + reln->md_fd[forkNum]->mdfd_vfd_pcd = fd_pcd; reln->md_fd[forkNum]->mdfd_vfd = fd; reln->md_fd[forkNum]->mdfd_segno = 0; reln->md_fd[forkNum]->mdfd_chain = NULL; @@ -494,7 +611,7 @@ void mdcreate(SMgrRelation reln, ForkNumber forkNum, bool isRedo) * Note: any failure should be reported as WARNING not ERROR, because * we are usually not in a transaction anymore when this is called. */ -void mdunlink(const RelFileNodeBackend& rnode, ForkNumber forkNum, bool isRedo, uint32 segno) +void mdunlink(const RelFileNodeBackend &rnode, ForkNumber forkNum, bool isRedo, uint32 segno) { Assert(segno == InvalidBlockNumber); @@ -507,35 +624,59 @@ void mdunlink(const RelFileNodeBackend& rnode, ForkNumber forkNum, bool isRedo, mdunlinkfork(rnode, forkNum, isRedo); } } - +/* + * 功能:设置指定关系和分叉的最大段号 + * + * 参数列表: + * max_segno:要设置的最大段号 + * rnode:RelFileNode 结构,表示关系的关联关系 + * forknum:ForkNumber,表示文件的分叉类型 + */ void set_max_segno_delrel(int max_segno, RelFileNode rnode, ForkNumber forknum) { + // 获取名为unlink_rel_hashtbl的哈希表的指针 HTAB *relfilenode_hashtbl = g_instance.bgwriter_cxt.unlink_rel_hashtbl; - HTAB* relfilenode_fork_hashtbl = g_instance.bgwriter_cxt.unlink_rel_fork_hashtbl; + // 获取名为unlink_rel_fork_hashtbl的哈希表的指针 + HTAB *relfilenode_fork_hashtbl = g_instance.bgwriter_cxt.unlink_rel_fork_hashtbl; + // 定义一个DelFileTag结构体指针entry,用于存储哈希表中的关键字信息 DelFileTag *entry = NULL; + // 定义一个ForkRelFileNode结构体变量entry_key,用于存储关键字信息 ForkRelFileNode entry_key; + // 定义一个DelForkFileTag结构体指针fork_entry,用于存储哈希表中的关键字信息 DelForkFileTag *fork_entry = NULL; + // 布尔变量found用于表示是否找到了关键字,初始值为false bool found = false; - + // 获取名为rel_hashtbl_lock的轻量级锁(LWLock),以确保安全访问unlink_rel_hashtbl哈希表 LWLockAcquire(g_instance.bgwriter_cxt.rel_hashtbl_lock, LW_SHARED); - entry = (DelFileTag*)hash_search(relfilenode_hashtbl, &(rnode), HASH_FIND, &found); + // 在unlink_rel_hashtbl哈希表中搜索关键字rnode,并将搜索结果存储在entry中 + entry = (DelFileTag *)hash_search(relfilenode_hashtbl, &(rnode), HASH_FIND, &found); + // 如果找到了关键字 if (found) { + // 比较max_segno和entry->maxSegNo,如果max_segno更大,更新entry->maxSegNo if (max_segno > entry->maxSegNo) { entry->maxSegNo = max_segno; } } + // 释放rel_hashtbl_lock锁,允许其他线程访问unlink_rel_hashtbl哈希表 LWLockRelease(g_instance.bgwriter_cxt.rel_hashtbl_lock); + // 如果在unlink_rel_hashtbl哈希表中没有找到关键字rnode if (!found) { + // 设置entry_key的rnode和forkNum字段 entry_key.rnode = rnode; entry_key.forkNum = forknum; + // 获取名为rel_one_fork_hashtbl_lock的轻量级锁(LWLock),以确保安全访问unlink_rel_fork_hashtbl哈希表 LWLockAcquire(g_instance.bgwriter_cxt.rel_one_fork_hashtbl_lock, LW_SHARED); - fork_entry = (DelForkFileTag*)hash_search(relfilenode_fork_hashtbl, &(entry_key), HASH_FIND, &found); + // 在unlink_rel_fork_hashtbl哈希表中搜索关键字entry_key,并将搜索结果存储在fork_entry中 + fork_entry = (DelForkFileTag *)hash_search(relfilenode_fork_hashtbl, &(entry_key), HASH_FIND, &found); + // 如果找到了关键字 if (found) { + // 比较max_segno和fork_entry->maxSegNo,如果max_segno更大,更新fork_entry->maxSegNo if (max_segno > fork_entry->maxSegNo) { fork_entry->maxSegNo = max_segno; } } + // 释放rel_one_fork_hashtbl_lock锁,允许其他线程访问unlink_rel_fork_hashtbl哈希表 LWLockRelease(g_instance.bgwriter_cxt.rel_one_fork_hashtbl_lock); } return; @@ -543,7 +684,7 @@ void set_max_segno_delrel(int max_segno, RelFileNode rnode, ForkNumber forknum) /** * set all zero to pca file - * @param fd pca file fd + * @param fd pca file fd * @param chunkSize chunkSize * @param path for errport * @return 0 for success and other for failed @@ -578,7 +719,7 @@ static int ResetPcaFileInner(int fd, int chunkSize, char *path) } /** - * + * * @param fd pca_file fd * @param chunkSize chunkSize destination * @param path for ereport @@ -597,94 +738,143 @@ static bool ReadChunkSizeFromFile(int fd, uint16 *chunkSize, char *path) } return true; } - +/* + * 功能:重置指定路径的压缩页映射文件 + * + * 参数列表: + * path:要重置的文件的路径 + * rnode:RelFileNodeBackend 结构,表示文件的关联关系 + */ static int ResetPcMap(char *path, const RelFileNodeBackend &rnode) { int ret = 0; char pcfile_path[MAXPGPATH]; + // 使用snprintf_s构建压缩页映射文件的路径,并检查是否出错 int rc = snprintf_s(pcfile_path, MAXPGPATH, MAXPGPATH - 1, PCA_SUFFIX, path); securec_check_ss(rc, "\0", "\0"); + // 打开压缩页映射文件,以可读写和二进制模式打开 int fd_pca = BasicOpenFile(pcfile_path, O_RDWR | PG_BINARY, 0); + // 如果打开文件失败,返回-1 if (fd_pca < 0) { return -1; } uint16 chunkSize; + // 从压缩页映射文件中读取块大小 if (ReadChunkSizeFromFile(fd_pca, &chunkSize, pcfile_path)) { + // 从压缩页映射文件中读取块大小 ret = ResetPcaFileInner(fd_pca, chunkSize, pcfile_path); } else { ret = -1; } + // 保存当前的errno值 int save_errno = errno; + // 关闭打开的文件 (void)close(fd_pca); + // 恢复之前保存的errno值 errno = save_errno; - + return ret; } - -static void UnlinkCompressedFile(const RelFileNode& node, ForkNumber forkNum, char* path) +/* + * 功能:删除压缩文件(包括PCA和PCD文件) + * + * 参数列表: + * node:RelFileNode 结构,表示文件的关联关系 + * forkNum:ForkNumber,表示文件的分叉类型 + * path:文件的路径 + */ +static void UnlinkCompressedFile(const RelFileNode &node, ForkNumber forkNum, char *path) { + // 检查文件是否为压缩文件,如果不是,直接返回 if (!IS_COMPRESSED_RNODE(node, forkNum)) { return; } /* remove pca */ char pcfile_path[MAXPGPATH]; + // 检查文件是否为压缩文件,如果不是,直接返回 errno_t rc = snprintf_s(pcfile_path, MAXPGPATH, MAXPGPATH - 1, PCA_SUFFIX, path); securec_check_ss(rc, "\0", "\0"); + // 调用unlink函数,删除PCA文件 int ret = unlink(pcfile_path); + // 如果删除失败,并且错误码不是ENOENT(文件不存在),输出警告消息 if (ret < 0 && errno != ENOENT) { ereport(WARNING, (errcode_for_file_access(), errmsg("could not remove file \"%s\": %m", pcfile_path))); } /* remove pcd */ rc = snprintf_s(pcfile_path, MAXPGPATH, MAXPGPATH - 1, PCD_SUFFIX, path); securec_check_ss(rc, "\0", "\0"); + // 调用unlink函数,删除PCD文件 ret = unlink(pcfile_path); + // 如果删除失败,并且错误码不是ENOENT(文件不存在),输出警告消息 if (ret < 0 && errno != ENOENT) { ereport(WARNING, (errcode_for_file_access(), errmsg("could not remove file \"%s\": %m", pcfile_path))); } } - +/* + * 功能:删除与指定段路径相关的修复文件 + * + * 参数列表: + * segpath:段路径 + */ static void mdcleanrepairfile(char *segpath) { + // 定义常量TEMPELN,表示临时文件名长度 const int TEMPLEN = 8; struct stat statBuf; - + // 分配内存用于构建临时文件路径,其长度为段路径的长度加上TEMPELN char *temppath = (char *)palloc(strlen(segpath) + TEMPLEN); + // 使用sprintf_s构建临时文件路径,并检查是否出错 errno_t rc = sprintf_s(temppath, strlen(segpath) + TEMPLEN, "%s.repair", segpath); securec_check_ss(rc, "", ""); + // 使用stat函数检查临时文件是否存在 if (stat(temppath, &statBuf) >= 0) { + // 如果存在,使用unlink函数删除临时文件 (void)unlink(temppath); - ereport(LOG, (errcode_for_file_access(), - errmsg("remove repair file \"%s\"", temppath))); + // 输出日志消息,表示已删除修复文件 + ereport(LOG, (errcode_for_file_access(), errmsg("remove repair file \"%s\"", temppath))); } + // 释放分配的内存 pfree(temppath); } - -static void mdunlinkfork(const RelFileNodeBackend& rnode, ForkNumber forkNum, bool isRedo) +/* + * 功能:删除指定关联关系和分叉类型的文件 + * + * 参数列表: + * rnode:RelFileNodeBackend 结构,表示文件的关联关系 + * forkNum:ForkNumber,表示文件的分叉类型 + * isRedo:指示是否在重做过程中操作的标志 + */ +static void mdunlinkfork(const RelFileNodeBackend &rnode, ForkNumber forkNum, bool isRedo) { - char* path = NULL; + char *path = NULL; int ret; - + // 获取文件的路径 path = relpath(rnode, forkNum); - + /* * Delete or truncate the first segment. */ Assert(IsHeapFileNode(rnode.node)); + // 如果在重做过程中操作,或者是 inplace 升级,或者不是主分叉,或者文件关联关系是临时的 if (isRedo || u_sess->attr.attr_common.IsInplaceUpgrade || forkNum != MAIN_FORKNUM || RelFileNodeBackendIsTemp(rnode)) { /* First, forget any pending sync requests for the first segment */ + // 如果文件不是临时的,取消对第一个段的同步请求 if (!RelFileNodeBackendIsTemp(rnode)) { md_register_forget_request(rnode.node, forkNum, 0 /* first segment */); } /* Next unlink the file */ - ret = unlink(path); + ret = unlink(path); // 删除文件 + // 如果删除失败,并且错误码不是ENOENT(文件不存在),输出警告消息 if (ret < 0 && errno != ENOENT) { ereport(WARNING, (errcode_for_file_access(), errmsg("could not remove file \"%s\": %m", path))); } + // 如果删除失败,并且错误码不是ENOENT(文件不存在),输出警告消息 if (isRedo) { mdcleanrepairfile(path); } + // 调用UnlinkCompressedFile函数删除压缩文件 UnlinkCompressedFile(rnode.node, forkNum, path); } else { /* truncate(2) would be easier here, but Windows hasn't got it */ @@ -694,16 +884,18 @@ static void mdunlinkfork(const RelFileNodeBackend& rnode, ForkNumber forkNum, bo if (fd >= 0) { int save_errno; - ret = ftruncate(fd, 0); + ret = ftruncate(fd, 0); // 使用ftruncate函数截断文件,用于删除文件内容 save_errno = errno; (void)close(fd); errno = save_errno; } else { ret = -1; } + // 如果截断失败,并且错误码不是ENOENT(文件不存在),输出警告消息 if (ret < 0 && errno != ENOENT) { ereport(WARNING, (errcode_for_file_access(), errmsg("could not truncate file \"%s\": %m", path))); } + // 如果文件是压缩文件,调用ResetPcMap函数重置压缩页映射文件 if (IS_COMPRESSED_RNODE(rnode.node, forkNum)) { // dont truncate pca! pca may be occupied by other threads by mmap ret = ResetPcMap(path, rnode); @@ -722,6 +914,7 @@ static void mdunlinkfork(const RelFileNodeBackend& rnode, ForkNumber forkNum, bo } else { ret = -1; } + // 如果删除失败,并且错误码不是ENOENT,输出警告消息 if (ret < 0 && errno != ENOENT) { ereport(WARNING, (errcode_for_file_access(), errmsg("could not truncate file \"%s\": %m", dataPath))); } @@ -757,7 +950,7 @@ static void mdunlinkfork(const RelFileNodeBackend& rnode, ForkNumber forkNum, bo /* ENOENT is expected after the last segment... */ if (errno != ENOENT) { ereport(WARNING, (errcode_for_file_access(), - errmsg("could not stat file \"%s\" before removing: %m", segpath))); + errmsg("could not stat file \"%s\" before removing: %m", segpath))); } break; } @@ -777,8 +970,7 @@ static void mdunlinkfork(const RelFileNodeBackend& rnode, ForkNumber forkNum, bo rc = sprintf_s(segpath, strlen(path) + 12, "%s.%u", path, segno); securec_check_ss(rc, "", ""); if (unlink(segpath) < 0) { - ereport(WARNING, (errcode_for_file_access(), - errmsg("could not remove file \"%s\": %m", segpath))); + ereport(WARNING, (errcode_for_file_access(), errmsg("could not remove file \"%s\": %m", segpath))); } if (IS_COMPRESSED_RNODE(rnode.node, forkNum)) { char pcfile_segpath[MAXPGPATH]; @@ -806,21 +998,35 @@ static void mdunlinkfork(const RelFileNodeBackend& rnode, ForkNumber forkNum, bo pfree(path); } - -static inline void ExtendChunksOfBlock(PageCompressHeader* pcMap, PageCompressAddr* pcAddr, int needChunks, MdfdVec* v) +/* + * 功能:为指定的块分配额外的压缩块,并将压缩块的状态同步到映射文件 + * + * 参数列表: + * pcMap:PageCompressHeader 结构,表示页压缩映射头部信息 + * pcAddr:PageCompressAddr 结构,表示页的压缩地址信息 + * needChunks:需要分配的压缩块数量 + * v:MdfdVec 结构,表示元数据文件描述符向量 + */ +static inline void ExtendChunksOfBlock(PageCompressHeader *pcMap, PageCompressAddr *pcAddr, int needChunks, MdfdVec *v) { + // 如果已分配的压缩块数量小于所需数量 if (pcAddr->allocated_chunks < needChunks) { auto allocateNumber = needChunks - pcAddr->allocated_chunks; + // 使用原子操作分配压缩块编号 int chunkno = (pc_chunk_number_t)pg_atomic_fetch_add_u32(&pcMap->allocated_chunks, allocateNumber) + 1; + // 分配压缩块编号给地址结构中未分配的位置 for (int i = pcAddr->allocated_chunks; i < needChunks; ++i, ++chunkno) { pcAddr->chunknos[i] = chunkno; } pcAddr->allocated_chunks = needChunks; - + // 如果已分配的压缩块数量与上次同步时的数量之差超过指定阈值 if (pg_atomic_read_u32(&pcMap->allocated_chunks) - pg_atomic_read_u32(&pcMap->last_synced_allocated_chunks) > COMPRESS_ADDRESS_FLUSH_CHUNKS) { + // 标记映射文件需要同步 pcMap->sync = false; + // 标记映射文件需要同步 if (sync_pcmap(pcMap, WAIT_EVENT_COMPRESS_ADDRESS_FILE_FLUSH) != 0) { + // 输出错误消息,表示同步失败 ereport(data_sync_elevel(ERROR), (errcode_for_file_access(), errmsg("could not msync file \"%s\": %m", FilePathName(v->mdfd_vfd_pca)))); } @@ -832,14 +1038,14 @@ static inline void ExtendChunksOfBlock(PageCompressHeader* pcMap, PageCompressAd * mdextend_pc() -- Add a block to the specified page compressed relation. * */ -static void mdextend_pc(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, const char* buffer, bool skipFsync) +static void mdextend_pc(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, const char *buffer, bool skipFsync) { #ifdef CHECK_WRITE_VS_EXTEND Assert(blocknum >= mdnblocks(reln, forknum)); #endif Assert(IS_COMPRESSED_MAINFORK(reln, forknum)); - MdfdVec* v = _mdfd_getseg(reln, MAIN_FORKNUM, blocknum, skipFsync, EXTENSION_CREATE); + MdfdVec *v = _mdfd_getseg(reln, MAIN_FORKNUM, blocknum, skipFsync, EXTENSION_CREATE); RelFileCompressOption option; TransCompressOptions(reln->smgr_rnode.node, &option); uint32 chunk_size = CHUNK_SIZE_LIST[option.compressChunkSize]; @@ -849,23 +1055,22 @@ static void mdextend_pc(SMgrRelation reln, ForkNumber forknum, BlockNumber block Assert(blocknum % RELSEG_SIZE >= pg_atomic_read_u32(&pcMap->nblocks)); uint32 maxAllocChunkNum = (uint32)(BLCKSZ / chunk_size - 1); - PageCompressAddr* pcAddr = GET_PAGE_COMPRESS_ADDR(pcMap, chunk_size, blocknum); + PageCompressAddr *pcAddr = GET_PAGE_COMPRESS_ADDR(pcMap, chunk_size, blocknum); prealloc_chunk = (prealloc_chunk > maxAllocChunkNum) ? maxAllocChunkNum : prealloc_chunk; /* check allocated chunk number */ if (pcAddr->allocated_chunks > BLCKSZ / chunk_size) { - ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("invalid chunks %u of block %u in file \"%s\"", - pcAddr->allocated_chunks, blocknum, - FilePathName(v->mdfd_vfd_pca)))); + ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("invalid chunks %u of block %u in file \"%s\"", pcAddr->allocated_chunks, blocknum, + FilePathName(v->mdfd_vfd_pca)))); } for (int i = 0; i < pcAddr->allocated_chunks; ++i) { if (pcAddr->chunknos[i] <= 0 || pcAddr->chunknos[i] > (BLCKSZ / chunk_size) * RELSEG_SIZE) { - ereport(ERROR, - (errcode(ERRCODE_DATA_CORRUPTED), errmsg("invalid chunk number %u of block %u in file \"%s\"", - pcAddr->chunknos[i], blocknum, - FilePathName(v->mdfd_vfd_pca)))); + ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("invalid chunk number %u of block %u in file \"%s\"", pcAddr->chunknos[i], blocknum, + FilePathName(v->mdfd_vfd_pca)))); } } @@ -875,21 +1080,19 @@ static void mdextend_pc(SMgrRelation reln, ForkNumber forknum, BlockNumber block if (!PageIsNew(buffer)) { int work_buffer_size = CompressPageBufferBound(buffer, algorithm); if (work_buffer_size < 0) { - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("mdextend_pc unrecognized compression algorithm %d", algorithm))); + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("mdextend_pc unrecognized compression algorithm %d", algorithm))); } - work_buffer = (char *) palloc(work_buffer_size); + work_buffer = (char *)palloc(work_buffer_size); int compressed_page_size = CompressPage(buffer, work_buffer, work_buffer_size, option); if (compressed_page_size < 0) { - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("mdextend_pc unrecognized compression algorithm %d", algorithm))); + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("mdextend_pc unrecognized compression algorithm %d", algorithm))); } nchunks = (compressed_page_size - 1) / chunk_size + 1; if (nchunks * chunk_size >= BLCKSZ) { pfree(work_buffer); - work_buffer = (char *) buffer; + work_buffer = (char *)buffer; nchunks = BLCKSZ / chunk_size; } else { /* fill zero in the last chunk */ @@ -909,8 +1112,8 @@ static void mdextend_pc(SMgrRelation reln, ForkNumber forknum, BlockNumber block * worker_buffer = NULL -> nchunks = 0 */ for (int i = 0; i < nchunks; i++) { - char* buffer_pos = work_buffer + chunk_size * i; - off_t seekpos = (off_t) OFFSET_OF_PAGE_COMPRESS_CHUNK(chunk_size, pcAddr->chunknos[i]); + char *buffer_pos = work_buffer + chunk_size * i; + off_t seekpos = (off_t)OFFSET_OF_PAGE_COMPRESS_CHUNK(chunk_size, pcAddr->chunknos[i]); // write continuous chunks int range = 1; while (i < nchunks - 1 && pcAddr->chunknos[i + 1] == pcAddr->chunknos[i] + 1) { @@ -921,14 +1124,15 @@ static void mdextend_pc(SMgrRelation reln, ForkNumber forknum, BlockNumber block int nbytes; if ((nbytes = FileWrite(v->mdfd_vfd_pcd, buffer_pos, write_amount, seekpos)) != write_amount) { if (nbytes < 0) { - ereport(ERROR, (errcode_for_file_access(), errmsg("could not extend file \"%s\": %m", - FilePathName(v->mdfd_vfd_pcd)), errhint( - "Check free disk space."))); + ereport(ERROR, (errcode_for_file_access(), + errmsg("could not extend file \"%s\": %m", FilePathName(v->mdfd_vfd_pcd)), + errhint("Check free disk space."))); } /* short write: complain appropriately */ - ereport(ERROR, (errcode(ERRCODE_DISK_FULL), errmsg( - "could not extend file \"%s\": wrote only %d of %d bytes at block %u", FilePathName(v->mdfd_vfd_pcd), - nbytes, write_amount, blocknum), errhint("Check free disk space."))); + ereport(ERROR, (errcode(ERRCODE_DISK_FULL), + errmsg("could not extend file \"%s\": wrote only %d of %d bytes at block %u", + FilePathName(v->mdfd_vfd_pcd), nbytes, write_amount, blocknum), + errhint("Check free disk space."))); } } @@ -940,7 +1144,6 @@ static void mdextend_pc(SMgrRelation reln, ForkNumber forknum, BlockNumber block /* write checksum */ pcAddr->checksum = AddrChecksum32(blocknum, pcAddr, chunk_size); - if (pg_atomic_read_u32(&pcMap->nblocks) < blocknum % RELSEG_SIZE + 1) { pg_atomic_write_u32(&pcMap->nblocks, blocknum % RELSEG_SIZE + 1); } @@ -954,7 +1157,7 @@ static void mdextend_pc(SMgrRelation reln, ForkNumber forknum, BlockNumber block register_dirty_segment(reln, forknum, v); } - Assert(_mdnblocks(reln, forknum, v) <= ((BlockNumber) RELSEG_SIZE)); + Assert(_mdnblocks(reln, forknum, v) <= ((BlockNumber)RELSEG_SIZE)); } /* @@ -966,8 +1169,7 @@ static void mdextend_pc(SMgrRelation reln, ForkNumber forknum, BlockNumber block * EOF). Note that we assume writing a block beyond current EOF * causes intervening file space to become filled with zeroes. */ -void mdextend(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, - char *buffer, bool skipFsync) +void mdextend(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, char *buffer, bool skipFsync) { off_t seekpos; int nbytes; @@ -1011,20 +1213,19 @@ void mdextend(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, */ if ((nbytes = FilePWrite(v->mdfd_vfd, buffer, BLCKSZ, seekpos, WAIT_EVENT_DATA_FILE_EXTEND)) != BLCKSZ) { if (check_unlink_rel_hashtbl(reln->smgr_rnode.node, forknum)) { - ereport(DEBUG1, - (errmsg("could not extend file \"%s\": %m, this relation has been removed", - FilePathName(v->mdfd_vfd)))); + ereport(DEBUG1, (errmsg("could not extend file \"%s\": %m, this relation has been removed", + FilePathName(v->mdfd_vfd)))); } else { if (nbytes < 0) { - ereport(ERROR, - (errcode_for_file_access(), errmsg("could not extend file \"%s\": %m", FilePathName(v->mdfd_vfd)), - errhint("Check free disk space."))); + ereport(ERROR, (errcode_for_file_access(), + errmsg("could not extend file \"%s\": %m", FilePathName(v->mdfd_vfd)), + errhint("Check free disk space."))); } /* short write: complain appropriately */ ereport(ERROR, (errcode(ERRCODE_DISK_FULL), - errmsg("could not extend file \"%s\": wrote only %d of %d bytes at block %u", - FilePathName(v->mdfd_vfd), nbytes, BLCKSZ, blocknum), - errhint("Check free disk space."))); + errmsg("could not extend file \"%s\": wrote only %d of %d bytes at block %u", + FilePathName(v->mdfd_vfd), nbytes, BLCKSZ, blocknum), + errhint("Check free disk space."))); } } @@ -1082,15 +1283,15 @@ static File mdopenagain(SMgrRelation reln, ForkNumber forknum, ExtensionBehavior return fd; } -static int MdOpenRetryOpenFile(char* path, const RelFileNodeForkNum &filenode, ExtensionBehavior behavior, uint32 flags) +static int MdOpenRetryOpenFile(char *path, const RelFileNodeForkNum &filenode, ExtensionBehavior behavior, uint32 flags) { int fd = -1; /* - * During bootstrap, there are cases where a system relation will be - * accessed (by internal backend processes) before the bootstrap - * script nominally creates it. Therefore, accept mdopen() as a - * substitute for mdcreate() in bootstrap mode only. (See mdcreate) - */ + * During bootstrap, there are cases where a system relation will be + * accessed (by internal backend processes) before the bootstrap + * script nominally creates it. Therefore, accept mdopen() as a + * substitute for mdcreate() in bootstrap mode only. (See mdcreate) + */ if (IsBootstrapProcessingMode()) { flags |= (O_CREAT | O_EXCL); fd = DataFileIdOpenFile(path, filenode, (int)flags, FILE_RW_PERMISSION); @@ -1199,9 +1400,17 @@ static MdfdVec *mdopen(SMgrRelation reln, ForkNumber forknum, ExtensionBehavior /* * mdclose() -- Close the specified relation, if it isn't closed already. */ +/* + * 功能:关闭指定关联关系、分叉类型和块号的文件描述符 + * + * 参数列表: + * reln:SMgrRelation 结构,表示文件的存储管理器关系 + * forknum:ForkNumber,表示文件的分叉类型 + * blockNum:BlockNumber,表示块号 + */ void mdclose(SMgrRelation reln, ForkNumber forknum, BlockNumber blockNum) { - MdfdVec *v = reln->md_fd[forknum]; + MdfdVec *v = reln->md_fd[forknum]; // 获取文件描述符向量 /* No work if already closed */ if (v == NULL) { @@ -1210,20 +1419,20 @@ void mdclose(SMgrRelation reln, ForkNumber forknum, BlockNumber blockNum) reln->md_fd[forknum] = NULL; /* prevent dangling pointer after error */ - while (v != NULL) { + while (v != NULL) { // 获取文件描述符向量 MdfdVec *ov = v; /* if not closed already */ if (IS_COMPRESSED_MAINFORK(reln, forknum)) { - if (v->mdfd_vfd_pca >= 0) { - FileClose(v->mdfd_vfd_pca); + if (v->mdfd_vfd_pca >= 0) { // 如果压缩地址文件描述符有效 + FileClose(v->mdfd_vfd_pca); // 关闭压缩地址文件 } - if (v->mdfd_vfd_pcd >= 0) { - FileClose(v->mdfd_vfd_pcd); + if (v->mdfd_vfd_pcd >= 0) { // 如果压缩地址文件描述符有效 + FileClose(v->mdfd_vfd_pca); // 关闭压缩地址文件 } } else { - if (v->mdfd_vfd >= 0) { - FileClose(v->mdfd_vfd); + if (v->mdfd_vfd >= 0) { // 如果文件描述符有效 + FileClose(v->mdfd_vfd); // 关闭文件 } } @@ -1236,21 +1445,29 @@ void mdclose(SMgrRelation reln, ForkNumber forknum, BlockNumber blockNum) /* * mdprefetch() -- Initiate asynchronous read of the specified block of a relation */ +/* + * 功能:预取指定关联关系、分叉类型和块号的文件块数据 + * + * 参数列表: + * reln:SMgrRelation 结构,表示文件的存储管理器关系 + * forknum:ForkNumber,表示文件的分叉类型 + * blocknum:BlockNumber,表示块号 + */ void mdprefetch(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum) { -#ifdef USE_PREFETCH +#ifdef USE_PREFETCH // 如果启用了预取功能 off_t seekpos; MdfdVec *v = NULL; - v = _mdfd_getseg(reln, forknum, blocknum, false, EXTENSION_FAIL); + v = _mdfd_getseg(reln, forknum, blocknum, false, EXTENSION_FAIL); // 获取文件描述符向量 if (v == NULL) { return; } - if (IS_COMPRESSED_MAINFORK(reln, forknum)) { - int chunk_size = PageCompressChunkSize(reln); - PageCompressHeader *pcMap = GetPageCompressMemoryMap(v->mdfd_vfd_pca, chunk_size); - PageCompressAddr *pcAddr = GET_PAGE_COMPRESS_ADDR(pcMap, chunk_size, blocknum); + if (IS_COMPRESSED_MAINFORK(reln, forknum)) { // 如果是压缩文件 + int chunk_size = PageCompressChunkSize(reln); // 如果是压缩文件 + PageCompressHeader *pcMap = GetPageCompressMemoryMap(v->mdfd_vfd_pca, chunk_size); // 获取压缩映射头部 + PageCompressAddr *pcAddr = GET_PAGE_COMPRESS_ADDR(pcMap, chunk_size, blocknum); // 获取压缩地址信息 /* check chunk number */ if (pcAddr->nchunks < 0 || pcAddr->nchunks > BLCKSZ / chunk_size) { if (u_sess->attr.attr_security.zero_damaged_pages || t_thrd.xlog_cxt.InRecovery) { @@ -1263,6 +1480,7 @@ void mdprefetch(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum) } for (uint8 i = 0; i < pcAddr->nchunks; i++) { + /* 检查压缩块编号是否合法 */ if (pcAddr->chunknos[i] <= 0 || pcAddr->chunknos[i] > (uint32)(BLCKSZ / chunk_size) * RELSEG_SIZE) { if (u_sess->attr.attr_security.zero_damaged_pages || t_thrd.xlog_cxt.InRecovery) { return; @@ -1278,13 +1496,14 @@ void mdprefetch(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum) range++; i++; } + /* 检查压缩块编号是否合法 */ (void)FilePrefetch(v->mdfd_vfd_pcd, seekpos, chunk_size * range, WAIT_EVENT_DATA_FILE_PREFETCH); } - } else { + } else { // 如果不是压缩文件 seekpos = (off_t)BLCKSZ * (blocknum % ((BlockNumber)RELSEG_SIZE)); Assert(seekpos < (off_t)BLCKSZ * RELSEG_SIZE); - + /* 使用FilePrefetch函数预取文件数据 */ (void)FilePrefetch(v->mdfd_vfd, seekpos, BLCKSZ, WAIT_EVENT_DATA_FILE_PREFETCH); } #endif /* USE_PREFETCH */ @@ -1361,8 +1580,8 @@ void mdwriteback(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, Bl FileWriteback(v->mdfd_vfd_pcd, seekpos, (off_t)nchunks * chunk_size); } } else { - seekpos = (off_t) BLCKSZ * (blocknum % ((BlockNumber) RELSEG_SIZE)); - FileWriteback(v->mdfd_vfd, seekpos, (off_t) BLCKSZ * nflush); + seekpos = (off_t)BLCKSZ * (blocknum % ((BlockNumber)RELSEG_SIZE)); + FileWriteback(v->mdfd_vfd, seekpos, (off_t)BLCKSZ * nflush); } nblocks -= nflush; blocknum += nflush; @@ -1614,82 +1833,106 @@ int CompltrWriteReq(void *aioDesc, long res) return 0; } -const int FILE_NAME_LEN = 128; +const int FILE_NAME_LEN = 128; // 定义文件名的最大长度为128 +/* + * 功能:检查指定文件的状态信息并将结果记录到日志中 + * + * 参数列表: + * file_name:char 指针,表示要检查的文件名 + */ static void check_file_stat(char *file_name) { int rc; - struct stat stat_buf; - char file_path[MAX_PATH_LEN] = {0}; - char strfbuf[FILE_NAME_LEN]; + struct stat stat_buf; // 定义文件名的最大长度为128 + char file_path[MAX_PATH_LEN] = {0}; // 定义文件名的最大长度为128 + char strfbuf[FILE_NAME_LEN]; // 存储时间戳的字符数组 + // 如果数据目录或文件名为空,则直接返回 if (t_thrd.proc_cxt.DataDir == NULL || file_name == NULL) { return; } + // 构建文件的完整路径 rc = snprintf_s(file_path, MAX_PATH_LEN, MAX_PATH_LEN - 1, "%s/%s", t_thrd.proc_cxt.DataDir, file_name); securec_check_ss(rc, "", ""); + // 使用stat函数获取文件的状态信息 if (stat(file_path, &stat_buf) == 0) { - pg_time_t stamp_time = (pg_time_t)stat_buf.st_mtime; + pg_time_t stamp_time = (pg_time_t)stat_buf.st_mtime; // 使用stat函数获取文件的状态信息 if (log_timezone != NULL) { - struct pg_tm *tm = pg_localtime(&stamp_time, log_timezone); + struct pg_tm *tm = pg_localtime(&stamp_time, log_timezone); // 转换时间戳为本地时间 if (tm != NULL) { + // 格式化时间戳为字符串,包括日期、时间和时区信息 (void)pg_strftime(strfbuf, sizeof(strfbuf), "%Y-%m-%d %H:%M:%S %Z", tm); ereport(LOG, (errmsg("file \"%s\" size is %ld bytes, last modify time is %s.", file_name, stat_buf.st_size, strfbuf))); } else { + // 如果无法获取本地时间,只记录文件名和大小 ereport(LOG, (errmsg("file \"%s\" size is %ld bytes.", file_name, stat_buf.st_size))); } } else { + // 如果无法获取时区信息,只记录文件名和大小 ereport(LOG, (errmsg("file \"%s\" size is %ld bytes.", file_name, stat_buf.st_size))); } } else { + // 如果无法获取文件状态信息,记录错误信息 ereport(LOG, (errmsg("could not stat the file : \"%s\".", file_name))); } } -#define CONTINUOUS_ASSIGN_2(a, b, value) do { \ - (a) = (value); \ - (b) = (value); \ -} while (0) +#define CONTINUOUS_ASSIGN_2(a, b, value) \ + do { \ + (a) = (value); \ + (b) = (value); \ + } while (0) -#define CONTINUOUS_ASSIGN_3(a, b, c, value) do { \ - (a) = (value); \ - (b) = (value); \ - (c) = (value); \ -} while (0) +#define CONTINUOUS_ASSIGN_3(a, b, c, value) \ + do { \ + (a) = (value); \ + (b) = (value); \ + (c) = (value); \ + } while (0) /* * mdread_pc() -- Read the specified block from a page compressed relation. */ +/* + * 功能: 从压缩存储中读取压缩块并解压到给定缓冲区中 + * + * 功能列表: + * reln: 存储管理器关系对象 + * forknum: 文件句柄编号(例如主要句柄或其他句柄) + * blocknum: 要读取的块号 + * buffer: 存储读取并解压后的数据的缓冲区 + */ bool mdread_pc(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, char *buffer) { - Assert(IS_COMPRESSED_MAINFORK(reln, forknum)); + Assert(IS_COMPRESSED_MAINFORK(reln, forknum)); // 确保文件是压缩文件 - MdfdVec *v = _mdfd_getseg(reln, forknum, blocknum, false, EXTENSION_FAIL); + MdfdVec *v = _mdfd_getseg(reln, forknum, blocknum, false, EXTENSION_FAIL); // 获取文件段描述符 RelFileCompressOption option; - TransCompressOptions(reln->smgr_rnode.node, &option); - uint32 chunk_size = CHUNK_SIZE_LIST[option.compressChunkSize]; - uint8 algorithm = option.compressAlgorithm; - PageCompressHeader *pcMap = GetPageCompressMemoryMap(v->mdfd_vfd_pca, chunk_size); - PageCompressAddr *pcAddr = GET_PAGE_COMPRESS_ADDR(pcMap, chunk_size, blocknum); - uint8 nchunks = pcAddr->nchunks; + TransCompressOptions(reln->smgr_rnode.node, &option); // 转换压缩选项 + uint32 chunk_size = CHUNK_SIZE_LIST[option.compressChunkSize]; // 获取压缩块大小 + uint8 algorithm = option.compressAlgorithm; // 获取压缩算法 + PageCompressHeader *pcMap = GetPageCompressMemoryMap(v->mdfd_vfd_pca, chunk_size); // 获取压缩页内存映射 + PageCompressAddr *pcAddr = GET_PAGE_COMPRESS_ADDR(pcMap, chunk_size, blocknum); // 获取压缩页地址信息 + uint8 nchunks = pcAddr->nchunks; // 压缩页中块的数量 if (nchunks == 0) { MemSet(buffer, 0, BLCKSZ); - return true; + return true; // 如果块的数量为0,直接填充0并返回 } if (nchunks > BLCKSZ / chunk_size) { if (u_sess->attr.attr_security.zero_damaged_pages || t_thrd.xlog_cxt.InRecovery) { MemSet(buffer, 0, BLCKSZ); - return true; + return true; // 如果块的数量超出预期,并且允许零损坏页面或者处于恢复模式,直接填充0并返回 } else { #ifndef ENABLE_MULTIPLE_NODES if (RecoveryInProgress()) { - return false; + return false; // 如果处于恢复模式,返回false } #endif - ereport(ERROR, - (errcode(ERRCODE_DATA_CORRUPTED), errmsg("invalid chunks %u of block %u in file \"%s\"", nchunks, - blocknum, FilePathName(v->mdfd_vfd_pca)))); + // 否则,报告数据损坏错误 + ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("invalid chunks %u of block %u in file \"%s\"", + nchunks, blocknum, FilePathName(v->mdfd_vfd_pca)))); } } @@ -1697,18 +1940,19 @@ bool mdread_pc(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, char if (pcAddr->chunknos[i] <= 0 || pcAddr->chunknos[i] > MAX_CHUNK_NUMBER(chunk_size)) { if (u_sess->attr.attr_security.zero_damaged_pages || t_thrd.xlog_cxt.InRecovery) { MemSet(buffer, 0, BLCKSZ); - return true; + return true; // 如果块的编号无效,并且允许零损坏页面或者处于恢复模式,直接填充0并返回 } else { - check_file_stat(FilePathName(v->mdfd_vfd_pcd)); + check_file_stat(FilePathName(v->mdfd_vfd_pcd)); // 检查文件状态 force_backtrace_messages = true; #ifndef ENABLE_MULTIPLE_NODES if (RecoveryInProgress()) { - return false; + return false; // 检查文件状态 } #endif - ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("invalid chunks %u of block %u in file \"%s\"", - nchunks, blocknum, - FilePathName(v->mdfd_vfd_pca)))); + // 检查文件状态 + ereport(ERROR, + (errcode(ERRCODE_DATA_CORRUPTED), errmsg("invalid chunks %u of block %u in file \"%s\"", + nchunks, blocknum, FilePathName(v->mdfd_vfd_pca)))); } } } @@ -1717,10 +1961,10 @@ bool mdread_pc(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, char char *buffer_pos = NULL; uint8 start; int read_amount; - char *compress_buffer = (char*)palloc(chunk_size * nchunks); + char *compress_buffer = (char *)palloc(chunk_size * nchunks); // 检查文件状态 for (uint8 i = 0; i < nchunks; ++i) { buffer_pos = compress_buffer + chunk_size * i; - off_t seekpos = (off_t) OFFSET_OF_PAGE_COMPRESS_CHUNK(chunk_size, pcAddr->chunknos[i]); + off_t seekpos = (off_t)OFFSET_OF_PAGE_COMPRESS_CHUNK(chunk_size, pcAddr->chunknos[i]); start = i; while (i < nchunks - 1 && pcAddr->chunknos[i + 1] == pcAddr->chunknos[i] + 1) { i++; @@ -1741,9 +1985,8 @@ bool mdread_pc(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, char return false; } #endif - ereport(ERROR, - (errcode_for_file_access(), errmsg("could not read block %u in file \"%s\": %m", blocknum, - FilePathName(v->mdfd_vfd_pcd)))); + ereport(ERROR, (errcode_for_file_access(), errmsg("could not read block %u in file \"%s\": %m", + blocknum, FilePathName(v->mdfd_vfd_pcd)))); } /* * Short read: we are at or past EOF, or we read a partial block at @@ -1765,9 +2008,9 @@ bool mdread_pc(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, char return false; } #endif - ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg( - "could not read block %u in file \"%s\": read only %d of %d bytes", blocknum, - FilePathName(v->mdfd_vfd_pcd), nbytes, read_amount))); + ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("could not read block %u in file \"%s\": read only %d of %d bytes", blocknum, + FilePathName(v->mdfd_vfd_pcd), nbytes, read_amount))); } } } @@ -1781,9 +2024,9 @@ bool mdread_pc(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, char nbytes = DecompressPage(compress_buffer, buffer, algorithm); if (nbytes != BLCKSZ) { if (nbytes == -2) { - ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg( - "could not recognized compression algorithm %d for file \"%s\"", algorithm, - FilePathName(v->mdfd_vfd_pcd)))); + ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("could not recognized compression algorithm %d for file \"%s\"", algorithm, + FilePathName(v->mdfd_vfd_pcd)))); } if (u_sess->attr.attr_security.zero_damaged_pages || t_thrd.xlog_cxt.InRecovery) { pfree(compress_buffer); @@ -1798,9 +2041,9 @@ bool mdread_pc(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, char return false; } #endif - ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg( - "could not decompress block %u in file \"%s\": decompress %d of %d bytes", blocknum, - FilePathName(v->mdfd_vfd_pcd), nbytes, BLCKSZ))); + ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("could not decompress block %u in file \"%s\": decompress %d of %d bytes", + blocknum, FilePathName(v->mdfd_vfd_pcd), nbytes, BLCKSZ))); } } } @@ -1811,60 +2054,73 @@ bool mdread_pc(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, char /* * mdread() -- Read the specified block from a relation. */ +/* + * 功能:从存储管理器中读取指定块的内容到缓冲区,并进行校验。 + * + * 参数列表: + * reln: SMgrRelation 结构体,表示存储管理器关系。 + * forknum: ForkNumber 值,表示要读取的分支号。 + * blocknum: BlockNumber 值,表示要读取的块号。 + * buffer: char 指针,用于接收读取的块数据的缓冲区。 + * + * 返回值: + * SMGR_READ_STATUS 枚举,表示读取操作的结果状态。 + */ SMGR_READ_STATUS mdread(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, char *buffer) { - off_t seekpos; - int nbytes; - MdfdVec *v = NULL; + off_t seekpos; // 声明变量 seekpos,用于文件定位 + int nbytes; // 声明变量 nbytes,用于存储读取的字节数 + MdfdVec *v = NULL; // 声明文件描述符向量指针,并初始化为 NULL - instr_time startTime; - instr_time endTime; - PgStat_Counter timeDiff = 0; - static THR_LOCAL PgStat_Counter msgCount = 0; - static THR_LOCAL PgStat_Counter sumPage = 0; - static THR_LOCAL PgStat_Counter sumTime = 0; - static THR_LOCAL PgStat_Counter lstTime = 0; - static THR_LOCAL PgStat_Counter minTime = 0; - static THR_LOCAL PgStat_Counter maxTime = 0; + instr_time startTime; // 计时开始 + instr_time endTime; // 计时结束 + PgStat_Counter timeDiff = 0; // 存储时间差 + static THR_LOCAL PgStat_Counter msgCount = 0; // 消息计数 + static THR_LOCAL PgStat_Counter sumPage = 0; // 总页数 + static THR_LOCAL PgStat_Counter sumTime = 0; // 总时间 + static THR_LOCAL PgStat_Counter lstTime = 0; // 上次时间 + static THR_LOCAL PgStat_Counter minTime = 0; // 最小时间 + static THR_LOCAL PgStat_Counter maxTime = 0; // 最大时间 static THR_LOCAL Oid lstFile = InvalidOid; static THR_LOCAL Oid lstDb = InvalidOid; static THR_LOCAL Oid lstSpc = InvalidOid; if (IS_COMPRESSED_MAINFORK(reln, forknum)) { + // 如果是压缩的主分支,调用 mdread_pc 函数进行读取和校验 bool success = mdread_pc(reln, forknum, blocknum, buffer); if (success && PageIsVerified((Page)buffer, blocknum)) { - return SMGR_RD_OK; + return SMGR_RD_OK; // 读取成功且通过校验 } else { - return SMGR_RD_CRC_ERROR; + return SMGR_RD_CRC_ERROR; // 读取失败或未通过校验 } } - (void)INSTR_TIME_SET_CURRENT(startTime); + (void)INSTR_TIME_SET_CURRENT(startTime); // 记录开始时间 TRACE_POSTGRESQL_SMGR_MD_READ_START(forknum, blocknum, reln->smgr_rnode.node.spcNode, reln->smgr_rnode.node.dbNode, reln->smgr_rnode.node.relNode, reln->smgr_rnode.backend); - v = _mdfd_getseg(reln, forknum, blocknum, false, EXTENSION_FAIL); + v = _mdfd_getseg(reln, forknum, blocknum, false, EXTENSION_FAIL); // 获取文件描述符向量 if (v == NULL) { - return SMGR_RD_NO_BLOCK; + return SMGR_RD_NO_BLOCK; // 未找到块 } - seekpos = (off_t)BLCKSZ * (blocknum % ((BlockNumber)RELSEG_SIZE)); + seekpos = (off_t)BLCKSZ * (blocknum % ((BlockNumber)RELSEG_SIZE)); // 计算文件定位位置 - nbytes = FilePRead(v->mdfd_vfd, buffer, BLCKSZ, seekpos, WAIT_EVENT_DATA_FILE_READ); + nbytes = FilePRead(v->mdfd_vfd, buffer, BLCKSZ, seekpos, WAIT_EVENT_DATA_FILE_READ); // 从文件中读取数据 TRACE_POSTGRESQL_SMGR_MD_READ_DONE(forknum, blocknum, reln->smgr_rnode.node.spcNode, reln->smgr_rnode.node.dbNode, reln->smgr_rnode.node.relNode, reln->smgr_rnode.backend, nbytes, BLCKSZ); - (void)INSTR_TIME_SET_CURRENT(endTime); - INSTR_TIME_SUBTRACT(endTime, startTime); - timeDiff = INSTR_TIME_GET_MICROSEC(endTime); + (void)INSTR_TIME_SET_CURRENT(endTime); // 记录结束时间 + INSTR_TIME_SUBTRACT(endTime, startTime); // 计算时间差 + timeDiff = INSTR_TIME_GET_MICROSEC(endTime); // 获取微秒级时间差 if (msgCount == 0) { - lstFile = reln->smgr_rnode.node.relNode; - lstDb = reln->smgr_rnode.node.dbNode; - lstSpc = reln->smgr_rnode.node.spcNode; - CONTINUOUS_ASSIGN_2(msgCount, sumPage, 1); - CONTINUOUS_ASSIGN_3(sumTime, minTime, maxTime, timeDiff); + lstFile = reln->smgr_rnode.node.relNode; // 设置上次文件 + lstDb = reln->smgr_rnode.node.dbNode; // 设置上次数据库 + lstSpc = reln->smgr_rnode.node.spcNode; // 设置上次空间 + CONTINUOUS_ASSIGN_2(msgCount, sumPage, 1); // 分配值给计数和总页数 + CONTINUOUS_ASSIGN_3(sumTime, minTime, maxTime, timeDiff); // 分配值给总时间、最小时间和最大时间 } else if (msgCount % STAT_MSG_BATCH == 0 || lstFile != reln->smgr_rnode.node.relNode) { PgStat_MsgFile msg; errno_t rc; @@ -1881,28 +2137,28 @@ SMGR_READ_STATUS mdread(SMgrRelation reln, ForkNumber forknum, BlockNumber block msg.maxtim = maxTime; reportFileStat(&msg); - rc = memset_s(&msg, sizeof(PgStat_MsgFile), 0, sizeof(PgStat_MsgFile)); + rc = memset_s(&msg, sizeof(PgStat_MsgFile), 0, sizeof(PgStat_MsgFile)); // 清零消息结构 securec_check(rc, "", ""); - CONTINUOUS_ASSIGN_2(msgCount, sumPage, 1); - sumTime = timeDiff; + CONTINUOUS_ASSIGN_2(msgCount, sumPage, 1); // 分配值给计数和总页数 + sumTime = timeDiff; // 更新总时间 if (lstFile != reln->smgr_rnode.node.relNode) { - lstFile = reln->smgr_rnode.node.relNode; - lstDb = reln->smgr_rnode.node.dbNode; - lstSpc = reln->smgr_rnode.node.spcNode; + lstFile = reln->smgr_rnode.node.relNode; // 更新上次文件 + lstDb = reln->smgr_rnode.node.dbNode; // 更新上次数据库 + lstSpc = reln->smgr_rnode.node.spcNode; // 更新上次空间 CONTINUOUS_ASSIGN_3(sumTime, minTime, maxTime, timeDiff); } } else { - msgCount++; - sumPage++; - sumTime += timeDiff; + msgCount++; // 增加消息计数 + sumPage++; // 增加总页数 + sumTime += timeDiff; // 增加总时间 } - lstTime = timeDiff; + lstTime = timeDiff; // 更新上次时间 if (minTime > timeDiff) { - minTime = timeDiff; + minTime = timeDiff; // 更新最小时间 } if (maxTime < timeDiff) { - maxTime = timeDiff; + maxTime = timeDiff; // 更新最大时间 } if (nbytes != BLCKSZ) { @@ -1919,7 +2175,7 @@ SMGR_READ_STATUS mdread(SMgrRelation reln, ForkNumber forknum, BlockNumber block * update a block that was later truncated away. */ if (u_sess->attr.attr_security.zero_damaged_pages || t_thrd.xlog_cxt.InRecovery) { - MemSet(buffer, 0, BLCKSZ); + MemSet(buffer, 0, BLCKSZ); // 在特定条件下,填充缓冲区为零 } else { check_file_stat(FilePathName(v->mdfd_vfd)); force_backtrace_messages = true; @@ -1930,7 +2186,7 @@ SMGR_READ_STATUS mdread(SMgrRelation reln, ForkNumber forknum, BlockNumber block } } - if (PageIsVerified((Page) buffer, blocknum)) { + if (PageIsVerified((Page)buffer, blocknum)) { return SMGR_RD_OK; } else { return SMGR_RD_CRC_ERROR; @@ -1954,10 +2210,9 @@ static void mdwrite_pc(SMgrRelation reln, ForkNumber forknum, BlockNumber blockn bool mmapSync = false; MdfdVec *v = _mdfd_getseg(reln, forknum, blocknum, skipFsync, EXTENSION_FAIL); - if (check_unlink_rel_hashtbl(reln->smgr_rnode.node, forknum)) { - ereport(DEBUG1, (errmsg("could not write block %u in file \"%s\": %m, this relation has been removed", - blocknum, FilePathName(v->mdfd_vfd)))); + ereport(DEBUG1, (errmsg("could not write block %u in file \"%s\": %m, this relation has been removed", blocknum, + FilePathName(v->mdfd_vfd)))); /* this file need skip sync */ return; } @@ -1982,16 +2237,18 @@ static void mdwrite_pc(SMgrRelation reln, ForkNumber forknum, BlockNumber blockn /* compress page */ auto work_buffer_size = CompressPageBufferBound(buffer, algorithm); if (work_buffer_size < 0) { - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg( - "mdwrite_pc unrecognized compression algorithm %d,chunk_size:%ud,level:%d,prealloc_chunk:%ud", algorithm, - chunk_size, level, prealloc_chunk))); + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("mdwrite_pc unrecognized compression algorithm %d,chunk_size:%ud,level:%d,prealloc_chunk:%ud", + algorithm, chunk_size, level, prealloc_chunk))); } - char *work_buffer = (char *) palloc(work_buffer_size); + char *work_buffer = (char *)palloc(work_buffer_size); auto compress_buffer_size = CompressPage(buffer, work_buffer, work_buffer_size, option); if (compress_buffer_size < 0) { - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg( - "mdwrite_pc unrecognized compression algorithm %d,chunk_size:%ud,level:%d,prealloc_chunk:%ud", algorithm, - chunk_size, level, prealloc_chunk))); + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("mdwrite_pc unrecognized compression algorithm %d,chunk_size:%ud,level:%d,prealloc_chunk:%ud", + algorithm, chunk_size, level, prealloc_chunk))); } uint8 nchunks = (compress_buffer_size - 1) / chunk_size + 1; @@ -1999,11 +2256,11 @@ static void mdwrite_pc(SMgrRelation reln, ForkNumber forknum, BlockNumber blockn if (bufferSize >= BLCKSZ) { /* store original page if can not save space? */ pfree(work_buffer); - work_buffer = (char *) buffer; + work_buffer = (char *)buffer; nchunks = BLCKSZ / chunk_size; } else { /* fill zero in the last chunk */ - if ((uint32) compress_buffer_size < bufferSize) { + if ((uint32)compress_buffer_size < bufferSize) { auto leftSize = bufferSize - compress_buffer_size; errno_t rc = memset_s(work_buffer + compress_buffer_size, leftSize, 0, leftSize); securec_check(rc, "", ""); @@ -2016,7 +2273,7 @@ static void mdwrite_pc(SMgrRelation reln, ForkNumber forknum, BlockNumber blockn // write chunks of compressed page for (auto i = 0; i < nchunks; ++i) { auto buffer_pos = work_buffer + chunk_size * i; - off_t seekpos = (off_t) OFFSET_OF_PAGE_COMPRESS_CHUNK(chunk_size, pcAddr->chunknos[i]); + off_t seekpos = (off_t)OFFSET_OF_PAGE_COMPRESS_CHUNK(chunk_size, pcAddr->chunknos[i]); auto start = i; while (i < nchunks - 1 && pcAddr->chunknos[i + 1] == pcAddr->chunknos[i] + 1) { i++; @@ -2033,14 +2290,14 @@ static void mdwrite_pc(SMgrRelation reln, ForkNumber forknum, BlockNumber blockn if (nbytes != write_amount) { if (nbytes < 0) { - ereport(ERROR, - (errcode_for_file_access(), errmsg("could not write block %u in file \"%s\": %m", blocknum, - FilePathName(v->mdfd_vfd_pcd)))); + ereport(ERROR, (errcode_for_file_access(), errmsg("could not write block %u in file \"%s\": %m", + blocknum, FilePathName(v->mdfd_vfd_pcd)))); } /* short write: complain appropriately */ - ereport(ERROR, (errcode(ERRCODE_DISK_FULL), errmsg( - "could not write block %u in file \"%s\": wrote only %d of %d bytes", blocknum, - FilePathName(v->mdfd_vfd_pcd), nbytes, BLCKSZ), errhint("Check free disk space."))); + ereport(ERROR, (errcode(ERRCODE_DISK_FULL), + errmsg("could not write block %u in file \"%s\": wrote only %d of %d bytes", blocknum, + FilePathName(v->mdfd_vfd_pcd), nbytes, BLCKSZ), + errhint("Check free disk space."))); } } @@ -2060,13 +2317,11 @@ static void mdwrite_pc(SMgrRelation reln, ForkNumber forknum, BlockNumber blockn pfree(work_buffer); } - if (!skipFsync && !SmgrIsTemp(reln)) { register_dirty_segment(reln, forknum, v); } } - /* * mdwrite() -- Write the supplied block at the appropriate location. * @@ -2176,19 +2431,19 @@ void mdwrite(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, const if (nbytes != BLCKSZ) { if (check_unlink_rel_hashtbl(reln->smgr_rnode.node, forknum)) { ereport(DEBUG1, (errmsg("could not write block %u in file \"%s\": %m, this relation has been removed", - blocknum, FilePathName(v->mdfd_vfd)))); + blocknum, FilePathName(v->mdfd_vfd)))); /* this file need skip sync */ skipFsync = true; } else { if (nbytes < 0) { - ereport(ERROR, (errcode_for_file_access(), errmsg("could not write block %u in file \"%s\": %m", blocknum, - FilePathName(v->mdfd_vfd)))); + ereport(ERROR, (errcode_for_file_access(), errmsg("could not write block %u in file \"%s\": %m", + blocknum, FilePathName(v->mdfd_vfd)))); } /* short write: complain appropriately */ ereport(ERROR, (errcode(ERRCODE_DISK_FULL), - errmsg("could not write block %u in file \"%s\": wrote only %d of %d bytes", blocknum, - FilePathName(v->mdfd_vfd), nbytes, BLCKSZ), - errhint("Check free disk space."))); + errmsg("could not write block %u in file \"%s\": wrote only %d of %d bytes", blocknum, + FilePathName(v->mdfd_vfd), nbytes, BLCKSZ), + errhint("Check free disk space."))); } } @@ -2258,7 +2513,7 @@ BlockNumber mdnblocks(SMgrRelation reln, ForkNumber forknum) if (v->mdfd_chain == NULL) { if (check_unlink_rel_hashtbl(reln->smgr_rnode.node, forknum)) { ereport(DEBUG1, (errmsg("\"%s\": %m, this relation has been removed", - _mdfd_segpath(reln, forknum, segno)))); + _mdfd_segpath(reln, forknum, segno)))); return 0; } ereport(ERROR, (errcode_for_file_access(), @@ -2288,7 +2543,7 @@ void mdtruncate(SMgrRelation reln, ForkNumber forknum, BlockNumber nblocks) */ curnblk = mdnblocks(reln, forknum); if (curnblk == 0) { - return; + return; } if (nblocks > curnblk) { /* Bogus request ... but no complaint if InRecovery */ @@ -2315,7 +2570,7 @@ void mdtruncate(SMgrRelation reln, ForkNumber forknum, BlockNumber nblocks) * from the mdfd_chain). We truncate the file, but do not delete * it, for reasons explained in the header comments. */ - if (IS_COMPRESSED_MAINFORK(reln, forknum)) { + if (IS_COMPRESSED_MAINFORK(reln, forknum)) { chunk_size = PageCompressChunkSize(reln); pcMap = GetPageCompressMemoryMap(v->mdfd_vfd_pca, chunk_size); pg_atomic_write_u32(&pcMap->nblocks, 0); @@ -2340,18 +2595,18 @@ void mdtruncate(SMgrRelation reln, ForkNumber forknum, BlockNumber nblocks) errmsg("could not truncate file \"%s\": %m", FilePathName(v->mdfd_vfd_pcd)))); } } else { - if (FileTruncate(v->mdfd_vfd, 0, WAIT_EVENT_DATA_FILE_TRUNCATE) < 0) { - if (check_unlink_rel_hashtbl(reln->smgr_rnode.node, forknum)) { - ereport(DEBUG1, (errmsg("could not truncate file \"%s\": %m, this relation has been removed", - FilePathName(v->mdfd_vfd)))); - FileClose(ov->mdfd_vfd); - pfree(ov); - break; - } - ereport(ERROR, (errcode_for_file_access(), - errmsg("could not truncate file \"%s\": %m", FilePathName(v->mdfd_vfd)))); - } - } + if (FileTruncate(v->mdfd_vfd, 0, WAIT_EVENT_DATA_FILE_TRUNCATE) < 0) { + if (check_unlink_rel_hashtbl(reln->smgr_rnode.node, forknum)) { + ereport(DEBUG1, (errmsg("could not truncate file \"%s\": %m, this relation has been removed", + FilePathName(v->mdfd_vfd)))); + FileClose(ov->mdfd_vfd); + pfree(ov); + break; + } + ereport(ERROR, (errcode_for_file_access(), + errmsg("could not truncate file \"%s\": %m", FilePathName(v->mdfd_vfd)))); + } + } if (!SmgrIsTemp(reln)) { register_dirty_segment(reln, forknum, v); @@ -2377,7 +2632,7 @@ void mdtruncate(SMgrRelation reln, ForkNumber forknum, BlockNumber nblocks) */ BlockNumber last_seg_blocks = nblocks - prior_blocks; if (IS_COMPRESSED_MAINFORK(reln, forknum)) { - pc_chunk_number_t max_used_chunkno = (pc_chunk_number_t) 0; + pc_chunk_number_t max_used_chunkno = (pc_chunk_number_t)0; uint32 allocated_chunks; chunk_size = PageCompressChunkSize(reln); pcMap = GetPageCompressMemoryMap(v->mdfd_vfd_pca, chunk_size); @@ -2391,27 +2646,26 @@ void mdtruncate(SMgrRelation reln, ForkNumber forknum, BlockNumber nblocks) pcMap->sync = false; if (sync_pcmap(pcMap, WAIT_EVENT_COMPRESS_ADDRESS_FILE_SYNC) != 0) { ereport(data_sync_elevel(ERROR), - (errcode_for_file_access(), errmsg("could not msync file \"%s\": %m", - FilePathName(v->mdfd_vfd_pca)))); + (errcode_for_file_access(), + errmsg("could not msync file \"%s\": %m", FilePathName(v->mdfd_vfd_pca)))); } allocated_chunks = pg_atomic_read_u32(&pcMap->allocated_chunks); /* find the max used chunkno */ - for (BlockNumber blk = (BlockNumber) 0; blk < (BlockNumber) last_seg_blocks; blk++) { + for (BlockNumber blk = (BlockNumber)0; blk < (BlockNumber)last_seg_blocks; blk++) { pcAddr = GET_PAGE_COMPRESS_ADDR(pcMap, chunk_size, blk); /* check allocated_chunks for one page */ if (pcAddr->allocated_chunks > BLCKSZ / chunk_size) { - ereport(ERROR, - (errcode(ERRCODE_DATA_CORRUPTED), errmsg("invalid chunks %u of block %u in file \"%s\"", - pcAddr->allocated_chunks, blk, - FilePathName(v->mdfd_vfd_pca)))); + ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("invalid chunks %u of block %u in file \"%s\"", pcAddr->allocated_chunks, + blk, FilePathName(v->mdfd_vfd_pca)))); } /* check chunknos for one page */ for (i = 0; i < pcAddr->allocated_chunks; i++) { if (pcAddr->chunknos[i] == 0 || pcAddr->chunknos[i] > allocated_chunks) { - ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg( - "invalid chunk number %u of block %u in file \"%s\"", pcAddr->chunknos[i], blk, - FilePathName(v->mdfd_vfd_pca)))); + ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("invalid chunk number %u of block %u in file \"%s\"", + pcAddr->chunknos[i], blk, FilePathName(v->mdfd_vfd_pca)))); } if (pcAddr->chunknos[i] > max_used_chunkno) { @@ -2456,10 +2710,20 @@ void mdtruncate(SMgrRelation reln, ForkNumber forknum, BlockNumber nblocks) prior_blocks += RELSEG_SIZE; } } - -static bool CompressMdImmediateSync(SMgrRelation reln, ForkNumber forknum, MdfdVec* v) +/* + * 功能:执行立即同步操作,确保对压缩文件的修改被同步到磁盘上 + * + * 参数列表: + * reln:SMgrRelation 结构,表示文件所属的 SMgrRelation + * forknum:ForkNumber 枚举,表示文件的分支号 + * v:MdfdVec 结构指针,表示文件的 MdfdVec 结构 + * + */ +static bool CompressMdImmediateSync(SMgrRelation reln, ForkNumber forknum, MdfdVec *v) { - PageCompressHeader* pcMap = GetPageCompressMemoryMap(v->mdfd_vfd_pca, PageCompressChunkSize(reln)); + // 获取压缩文件的页压缩头部的指针 + PageCompressHeader *pcMap = GetPageCompressMemoryMap(v->mdfd_vfd_pca, PageCompressChunkSize(reln)); + // 同步 PageCompressHeader 结构到磁盘 if (sync_pcmap(pcMap, WAIT_EVENT_COMPRESS_ADDRESS_FILE_SYNC) != 0) { if (check_unlink_rel_hashtbl(reln->smgr_rnode.node, forknum)) { ereport(DEBUG1, (errmsg("could not fsync file \"%s\": %m, this relation has been removed", @@ -2469,6 +2733,7 @@ static bool CompressMdImmediateSync(SMgrRelation reln, ForkNumber forknum, MdfdV ereport(data_sync_elevel(ERROR), (errcode_for_file_access(), errmsg("could not msync file \"%s\": %m", FilePathName(v->mdfd_vfd_pca)))); } + // 同步 PageCompressData 到磁盘 if (FileSync(v->mdfd_vfd_pcd, WAIT_EVENT_DATA_FILE_IMMEDIATE_SYNC) < 0) { if (check_unlink_rel_hashtbl(reln->smgr_rnode.node, forknum)) { ereport(DEBUG1, (errmsg("could not fsync file \"%s\": %m, this relation has been removed", @@ -2499,21 +2764,21 @@ void mdimmedsync(SMgrRelation reln, ForkNumber forknum) v = mdopen(reln, forknum, EXTENSION_FAIL); while (v != NULL) { - if (IS_COMPRESSED_MAINFORK(reln, forknum)) { + if (IS_COMPRESSED_MAINFORK(reln, forknum)) { if (!CompressMdImmediateSync(reln, forknum, v)) { break; } } else { - if (FileSync(v->mdfd_vfd, WAIT_EVENT_DATA_FILE_IMMEDIATE_SYNC) < 0) { - if (check_unlink_rel_hashtbl(reln->smgr_rnode.node, forknum)) { - ereport(DEBUG1, (errmsg("could not fsync file \"%s\": %m, this relation has been removed", - FilePathName(v->mdfd_vfd)))); - break; - } - ereport(data_sync_elevel(ERROR), (errcode_for_file_access(), - errmsg("could not fsync file \"%s\": %m", FilePathName(v->mdfd_vfd)))); - } - } + if (FileSync(v->mdfd_vfd, WAIT_EVENT_DATA_FILE_IMMEDIATE_SYNC) < 0) { + if (check_unlink_rel_hashtbl(reln->smgr_rnode.node, forknum)) { + ereport(DEBUG1, (errmsg("could not fsync file \"%s\": %m, this relation has been removed", + FilePathName(v->mdfd_vfd)))); + break; + } + ereport(data_sync_elevel(ERROR), (errcode_for_file_access(), errmsg("could not fsync file \"%s\": %m", + FilePathName(v->mdfd_vfd)))); + } + } v = v->mdfd_chain; } } @@ -2641,7 +2906,7 @@ static MdfdVec *_mdfd_openseg(SMgrRelation reln, ForkNumber forknum, BlockNumber pfree(fullpath); return NULL; } - + int fd_pca = -1; int fd_pcd = -1; if (IS_COMPRESSED_MAINFORK(reln, forknum)) { @@ -2755,7 +3020,7 @@ static MdfdVec *_mdfd_getseg(SMgrRelation reln, ForkNumber forknum, BlockNumber } if (check_unlink_rel_hashtbl(reln->smgr_rnode.node, forknum)) { ereport(DEBUG1, (errmsg("could not open file \"%s\" (target block %u): %m", - _mdfd_segpath(reln, forknum, nextsegno), blkno))); + _mdfd_segpath(reln, forknum, nextsegno), blkno))); return NULL; } @@ -2784,13 +3049,13 @@ static BlockNumber _mdnblocks(SMgrRelation reln, ForkNumber forknum, const MdfdV off_t len; if (IS_COMPRESSED_MAINFORK(reln, forknum)) { PageCompressHeader *pcMap = GetPageCompressMemoryMap(seg->mdfd_vfd_pca, PageCompressChunkSize(reln)); - return (BlockNumber) pg_atomic_read_u32(&pcMap->nblocks); + return (BlockNumber)pg_atomic_read_u32(&pcMap->nblocks); } len = FileSeek(seg->mdfd_vfd, 0L, SEEK_END); if (len < 0) { if (check_unlink_rel_hashtbl(reln->smgr_rnode.node, forknum)) { ereport(DEBUG1, (errmsg("could not seek to end of file \"%s\": %m, this relation has been removed", - FilePathName(seg->mdfd_vfd)))); + FilePathName(seg->mdfd_vfd)))); return 0; } ereport(ERROR, (errcode_for_file_access(), @@ -2810,14 +3075,14 @@ static BlockNumber _mdnblocks(SMgrRelation reln, ForkNumber forknum, const MdfdV int SyncMdFile(const FileTag *ftag, char *path) { SMgrRelation reln = smgropen(ftag->rnode, InvalidBackendId, GetColumnNum(ftag->forknum)); - MdfdVec *v; - char *p; + MdfdVec *v; + char *p; File file = -1; File pcaFd = -1; File pcdFd = -1; int result; int savedErrno; - bool needClose = false; + bool needClose = false; /* Provide the path for informational messages. */ p = _mdfd_segpath(reln, ftag->forknum, ftag->segno); @@ -2825,8 +3090,7 @@ int SyncMdFile(const FileTag *ftag, char *path) pfree(p); /* Try to open the requested segment. */ - v = _mdfd_getseg(reln, ftag->forknum, ftag->segno * (BlockNumber) RELSEG_SIZE, - false, EXTENSION_RETURN_NULL); + v = _mdfd_getseg(reln, ftag->forknum, ftag->segno * (BlockNumber)RELSEG_SIZE, false, EXTENSION_RETURN_NULL); if (IS_COMPRESSED_RNODE(ftag->rnode, ftag->forknum)) { if (v == NULL) { pcaFd = OpenPcaFile(path, reln->smgr_rnode, ftag->forknum, ftag->segno); @@ -2904,7 +3168,7 @@ int SyncMdFile(const FileTag *ftag, char *path) */ int UnlinkMdFile(const FileTag *ftag, char *path) { - char *p; + char *p; /* Compute the path. */ p = relpathperm(ftag->rnode, MAIN_FORKNUM); @@ -2931,19 +3195,29 @@ bool MatchMdFileTag(const FileTag *ftag, const FileTag *candidate) */ return ftag->rnode.dbNode == candidate->rnode.dbNode; } - +/* + * 功能:同步页压缩内存映射的数据到磁盘 + * + * 参数列表: + * pcMap:PageCompressHeader 结构的指针,表示页压缩内存映射的头部信息 + * wait_event_info:一个表示等待事件的标识,用于记录等待事件的类型 + */ static int sync_pcmap(PageCompressHeader *pcMap, uint32 wait_event_info) { + // 如果已经标记为需要同步,则直接返回成功 if (pg_atomic_read_u32(&pcMap->sync) == true) { return 0; } int returnCode; uint32 nblocks, allocated_chunks, last_synced_nblocks, last_synced_allocated_chunks; + // 如果已经标记为需要同步,则直接返回成功 nblocks = pg_atomic_read_u32(&pcMap->nblocks); allocated_chunks = pg_atomic_read_u32(&pcMap->allocated_chunks); last_synced_nblocks = pg_atomic_read_u32(&pcMap->last_synced_nblocks); last_synced_allocated_chunks = pg_atomic_read_u32(&pcMap->last_synced_allocated_chunks); + // 调用 pc_msync 函数执行真正的内存映射同步操作 returnCode = pc_msync(pcMap); + // 如果同步成功,更新已同步的属性值 if (returnCode == 0) { if (last_synced_nblocks != nblocks) { pg_atomic_write_u32(&pcMap->last_synced_nblocks, nblocks); @@ -2953,6 +3227,7 @@ static int sync_pcmap(PageCompressHeader *pcMap, uint32 wait_event_info) pg_atomic_write_u32(&pcMap->last_synced_allocated_chunks, allocated_chunks); } } + // 标记为已同步 pcMap->sync = true; return returnCode; } \ No newline at end of file diff --git a/src/gausskernel/storage/smgr/mmap_shared.cpp b/src/gausskernel/storage/smgr/mmap_shared.cpp index 4d8c85c73..02aee4c38 100644 --- a/src/gausskernel/storage/smgr/mmap_shared.cpp +++ b/src/gausskernel/storage/smgr/mmap_shared.cpp @@ -54,96 +54,148 @@ static inline pthread_mutex_t *MmapPartitionLock(size_t hashCode) { return &mmapLockArray[hashCode % LOCK_ARRAY_SIZE]; } - +/* + * 功能:同步页压缩内存映射的数据到磁盘 + * + * 参数列表: + * pcMap:PageCompressHeader 结构的指针,表示页压缩内存映射的头部信息 + * wait_event_info:一个表示等待事件的标识,用于记录等待事件的类型 + */ static inline PageCompressHeader *MmapSharedMapFile(Vfd *vfdP, uint16 chunkSize, uint2 opt, bool readonly) { + // 调用 pc_mmap_real_size 函数映射共享内存文件,返回一个指向 PageCompressHeader 结构体的指针 map。 auto map = pc_mmap_real_size(vfdP->fd, SIZE_OF_PAGE_COMPRESS_ADDR_FILE(chunkSize), false); + // 检查映射的文件区域是否有效,若无效则进行初始化设置。 if (map->chunk_size == 0 || map->algorithm == 0) { + // 检查映射的文件区域是否有效,若无效则进行初始化设置。 map->chunk_size = chunkSize; map->algorithm = GET_COMPRESS_ALGORITHM(opt); + // 使用 pc_msync 函数将映射区域的数据同步到文件中。 if (pc_msync(map) != 0) { + // 如果同步失败,则生成错误报告。 ereport(data_sync_elevel(ERROR), (errcode_for_file_access(), errmsg("could not msync file \"%s\": %m", vfdP->fileName))); } } + // 如果正在进行恢复操作且映射区域未同步,则调用 CheckAndRepairCompressAddress 函数进行检查和修复。 if (RecoveryInProgress() && !map->sync) { CheckAndRepairCompressAddress(map, chunkSize, map->algorithm, vfdP->fileName); } + // 返回映射区域的指针 map。 return map; } - +/* + * 功能:初始化页压缩内存映射的锁数组 + */ void RealInitialMMapLockArray() { + // 遍历锁数组,为每个元素初始化一个互斥锁 for (size_t i = 0; i < LOCK_ARRAY_SIZE; ++i) { pthread_mutex_init(&mmapLockArray[i], NULL); } - + // 初始化哈希表控制结构体 HASHCTL ctl; /* hash accessed by database file id */ + // 使用 memset_s 函数将 ctl 结构体清零 errno_t rc = memset_s(&ctl, sizeof(ctl), 0, sizeof(ctl)); securec_check(rc, "", ""); - + // 设置哈希键的大小和每个条目的大小 ctl.keysize = sizeof(RelFileNodeForkNum); ctl.entrysize = sizeof(MmapEntry); ctl.hash = tag_hash; ctl.num_partitions = LOCK_ARRAY_SIZE; const size_t initLen = 256; + // 初始化内存哈希表,用于页压缩内存映射的缓存 g_instance.mmapCache = HeapMemInitHash( "mmap hash", initLen, (Max(g_instance.attr.attr_common.max_files_per_process, t_thrd.storage_cxt.max_userdatafiles)) / 2, &ctl, HASH_ELEM | HASH_FUNCTION | HASH_PARTITION); } - +/* + * 功能:获取页压缩内存映射的头部信息 + * + * 参数列表: + * vfd:指向文件描述符信息的指针 + * chunkSize:uint16 类型,表示页面压缩的块大小 + * relFileNodeForkNum:RelFileNodeForkNum 结构,表示文件的标识信息 + */ PageCompressHeader *GetPageCompressHeader(void *vfd, uint16 chunkSize, const RelFileNodeForkNum &relFileNodeForkNum) { + // 将传入的 vfd 转换为 Vfd 结构体指针 Vfd *currentVfd = (Vfd *)vfd; + // 计算哈希码以用于锁分区 uint32 hashCode = MmapTableHashCode(relFileNodeForkNum); + // 使用 AutoMutexLock 自动加锁,锁定相应的分区 AutoMutexLock mmapLock(MmapPartitionLock(hashCode)); - + // 加锁 mmapLock.lock(); + // 初始化查找结果标志 bool find = false; + // 在内存哈希表中搜索文件标识,并返回相应的 MmapEntry 结构指针 MmapEntry *mmapEntry = (MmapEntry *)hash_search_with_hash_value(g_instance.mmapCache, (void *)&relFileNodeForkNum, hashCode, HASH_ENTER, &find); + // 如果未找到对应条目,则进行初始化设置 if (!find) { mmapEntry->pcmap = NULL; mmapEntry->reference = 0; } + // 如果 pcmap 为空,则调用 MmapSharedMapFile 函数映射文件 if (mmapEntry->pcmap == NULL) { mmapEntry->pcmap = MmapSharedMapFile(currentVfd, chunkSize, relFileNodeForkNum.rnode.node.opt, false); } + // 增加对映射区域的引用计数 ++mmapEntry->reference; + // 增加对映射区域的引用计数 mmapLock.unLock(); + // 返回映射区域的指针 return mmapEntry->pcmap; } - +/* + * 功能:解除页压缩内存映射的引用计数,并在不再使用时释放资源 + * + * 参数列表: + * vfd:指向文件描述符信息的指针 + */ void UnReferenceAddrFile(void *vfd) { + // 将传入的 vfd 转换为 Vfd 结构体指针 Vfd *currentVfd = (Vfd *)vfd; + // 获取文件标识信息 RelFileNodeForkNum relFileNodeForkNum = currentVfd->fileNode; + // 计算哈希码以用于锁分区 uint32 hashCode = MmapTableHashCode(relFileNodeForkNum); + // 使用 AutoMutexLock 自动加锁,锁定相应的分区 AutoMutexLock mmapLock(MmapPartitionLock(hashCode)); mmapLock.lock(); + // 在内存哈希表中搜索文件标识,并返回相应的 MmapEntry 结构指针(HASH_FIND 模式) MmapEntry *mmapEntry = (MmapEntry *)hash_search_with_hash_value(g_instance.mmapCache, (void *)&relFileNodeForkNum, hashCode, HASH_FIND, NULL); + // 如果没有找到对应的条目,则生成错误报告 if (mmapEntry == NULL) { ereport(ERROR, (errcode_for_file_access(), errmsg("UnReferenceAddrFile failed! mmap not found, filePath: %s", currentVfd->fileName))); } + // 减少对映射区域的引用计数 --mmapEntry->reference; + // 如果引用计数降为零,释放资源 if (mmapEntry->reference == 0) { + // 调用 pc_munmap 函数释放映射区域的资源 if (pc_munmap(mmapEntry->pcmap) != 0) { ereport(ERROR, (errcode_for_file_access(), errmsg("could not munmap file \"%s\": %m", currentVfd->fileName))); } + // 从内存哈希表中移除对应的哈希键 if (hash_search_with_hash_value(g_instance.mmapCache, (void *)&relFileNodeForkNum, hashCode, HASH_REMOVE, NULL) == NULL) { ereport(ERROR, (errcode_for_file_access(), errmsg("UnReferenceAddrFile failed! remove hash key failed, filePath: %s", currentVfd->fileName))); } - } else if (mmapEntry->reference < 0) { + } + // 如果引用计数小于零,生成致命错误报告 + else if (mmapEntry->reference < 0) { ereport(FATAL, (errcode_for_file_access(), errmsg("could not munmap file \"%s\": %m", currentVfd->fileName))); } - mmapLock.unLock(); + mmapLock.unLock(); // 解锁 } \ No newline at end of file diff --git a/src/gausskernel/storage/smgr/page_compression.cpp b/src/gausskernel/storage/smgr/page_compression.cpp index f83fc46fa..513379277 100644 --- a/src/gausskernel/storage/smgr/page_compression.cpp +++ b/src/gausskernel/storage/smgr/page_compression.cpp @@ -35,39 +35,58 @@ #include "storage/checksum.h" #include "storage/page_compression.h" #include "storage/page_compression_impl.h" - -static void CheckHeaderOfCompressAddr(PageCompressHeader* pcMap, uint16 chunk_size, uint8 algorithm, const char* path) +/* + * 功能:检查页压缩内存映射的头部信息是否合法,如果不合法则根据配置进行处理 + * + * 参数列表: + * pcMap:PageCompressHeader 结构的指针,表示页压缩内存映射的头部信息 + * chunk_size:uint16 类型,表示页面压缩的块大小 + * algorithm:uint8 类型,表示压缩算法 + * path:const char* 类型,表示文件路径 + */ +static void CheckHeaderOfCompressAddr(PageCompressHeader *pcMap, uint16 chunk_size, uint8 algorithm, const char *path) { + // 检查页压缩内存映射的块大小和压缩算法是否与预期值相符 if (pcMap->chunk_size != chunk_size || pcMap->algorithm != algorithm) { + // 如果启用了 zero_damaged_pages 配置,生成警告并重新初始化映射头部信息 if (u_sess->attr.attr_security.zero_damaged_pages) { ereport(WARNING, - (errcode(ERRCODE_DATA_CORRUPTED), - errmsg("invalid chunk_size %u or algorithm %u in head of compress relation address file \"%s\", " - "and reinitialized it.", - pcMap->chunk_size, - pcMap->algorithm, - path))); + (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("invalid chunk_size %u or algorithm %u in head of compress relation address file \"%s\", " + "and reinitialized it.", + pcMap->chunk_size, pcMap->algorithm, path))); + // 重新设置压缩算法、nblocks、allocated_chunks 和 last_synced_allocated_chunks pcMap->algorithm = algorithm; pg_atomic_write_u32(&pcMap->nblocks, RELSEG_SIZE); pg_atomic_write_u32(&pcMap->allocated_chunks, 0); pg_atomic_write_u32(&pcMap->last_synced_allocated_chunks, 0); pcMap->chunk_size = chunk_size; } else { + // 如果未启用 zero_damaged_pages 配置,生成错误报告 ereport(ERROR, - (errcode(ERRCODE_DATA_CORRUPTED), - errmsg("invalid chunk_size %u or algorithm %u in head of compress relation address file \"%s\"", - pcMap->chunk_size, - pcMap->algorithm, - path))); + (errcode(ERRCODE_DATA_CORRUPTED), + errmsg("invalid chunk_size %u or algorithm %u in head of compress relation address file \"%s\"", + pcMap->chunk_size, pcMap->algorithm, path))); } } } - +/* + * 功能:检查和修复页压缩内存映射的地址信息,以及更新相关信息 + * + * 参数列表: + * pcMap:PageCompressHeader 结构的指针,表示页压缩内存映射的头部信息 + * chunk_size:uint16 类型,表示页面压缩的块大小 + * algorithm:uint8 类型,表示压缩算法 + * path:const char* 类型,表示文件路径 + */ void CheckAndRepairCompressAddress(PageCompressHeader *pcMap, uint16 chunk_size, uint8 algorithm, const char *path) { + // 获取上次恢复的时间 TimestampTz lastRecoveryTime = pcMap->last_recovery_start_time; + // 获取数据库启动的时间 TimestampTz pgStartTime = t_thrd.time_cxt.pg_start_time; + // 错误码变量 error_t rc; /* if the relation had been checked in this startup, skip */ if (lastRecoveryTime == pgStartTime) { @@ -76,21 +95,25 @@ void CheckAndRepairCompressAddress(PageCompressHeader *pcMap, uint16 chunk_size, /* check head of compress address file */ CheckHeaderOfCompressAddr(pcMap, chunk_size, algorithm, path); - + // 读取头部信息中的块数和已分配块数 uint32 nblocks = pg_atomic_read_u32(&pcMap->nblocks); uint32 allocated_chunks = pg_atomic_read_u32(&pcMap->allocated_chunks); + // 为全局块号数组分配内存 BlockNumber *global_chunknos = (BlockNumber *)palloc0(MAX_CHUNK_NUMBER(chunk_size) * sizeof(BlockNumber)); - + // 初始化块号变量 BlockNumber max_blocknum = (BlockNumber)-1; BlockNumber max_nonzero_blocknum = (BlockNumber)-1; BlockNumber max_allocated_chunkno = (pc_chunk_number_t)0; /* check compress address of every pages */ for (BlockNumber blocknum = 0; blocknum < (BlockNumber)RELSEG_SIZE; ++blocknum) { + // 获取当前页面的压缩地址信息 PageCompressAddr *pcAddr = GET_PAGE_COMPRESS_ADDR(pcMap, chunk_size, blocknum); + // 获取当前页面的压缩地址信息 if (pcAddr->checksum != AddrChecksum32(blocknum, pcAddr, chunk_size)) { ereport(WARNING, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("invalid checkum %u of block %u in file \"%s\"", pcAddr->checksum, blocknum, path))); + // 将该块的压缩地址信息清零 pcAddr->allocated_chunks = pcAddr->nchunks = 0; for (int i = 0; i < BLCKSZ / chunk_size; ++i) { pcAddr->chunknos[i] = 0; @@ -104,8 +127,10 @@ void CheckAndRepairCompressAddress(PageCompressHeader *pcMap, uint16 chunk_size, */ /* check allocated_chunks for one page */ + // 检查每个页面的已分配块数 if (pcAddr->allocated_chunks > BLCKSZ / chunk_size) { if (u_sess->attr.attr_security.zero_damaged_pages) { + // 如果启用了 zero_damaged_pages 配置,将该块的压缩地址信息清零 rc = memset_s((void *)pcAddr, SIZE_OF_PAGE_COMPRESS_ADDR(chunk_size), 0, SIZE_OF_PAGE_COMPRESS_ADDR(chunk_size)); securec_check_c(rc, "\0", "\0"); @@ -114,6 +139,7 @@ void CheckAndRepairCompressAddress(PageCompressHeader *pcMap, uint16 chunk_size, pcAddr->allocated_chunks, blocknum, path))); continue; } else { + // 如果未启用 zero_damaged_pages 配置,释放内存并生成错误报告 pfree(global_chunknos); ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("invalid allocated_chunks %u of block %u in file \"%s\"", @@ -122,10 +148,12 @@ void CheckAndRepairCompressAddress(PageCompressHeader *pcMap, uint16 chunk_size, } /* check chunknos for one page */ + // 检查每个页面的块号是否有效 for (int i = 0; i < pcAddr->allocated_chunks; ++i) { /* check for invalid chunkno */ if (pcAddr->chunknos[i] == 0 || pcAddr->chunknos[i] > MAX_CHUNK_NUMBER(chunk_size)) { if (u_sess->attr.attr_security.zero_damaged_pages) { + // 如果启用了 zero_damaged_pages 配置,将该块的压缩地址信息清零 rc = memset_s((void *)pcAddr, SIZE_OF_PAGE_COMPRESS_ADDR(chunk_size), 0, SIZE_OF_PAGE_COMPRESS_ADDR(chunk_size)); securec_check_c(rc, "\0", "\0"); @@ -134,6 +162,7 @@ void CheckAndRepairCompressAddress(PageCompressHeader *pcMap, uint16 chunk_size, pcAddr->chunknos[i], blocknum, path))); continue; } else { + // 如果未启用 zero_damaged_pages 配置,释放内存并生成错误报告 pfree(global_chunknos); ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("invalid chunk number %u of block %u in file \"%s\"", pcAddr->chunknos[i], @@ -144,6 +173,7 @@ void CheckAndRepairCompressAddress(PageCompressHeader *pcMap, uint16 chunk_size, /* check for duplicate chunkno */ if (global_chunknos[pcAddr->chunknos[i] - 1] != 0) { if (u_sess->attr.attr_security.zero_damaged_pages) { + // 如果启用了 zero_damaged_pages 配置,将该块的压缩地址信息清零 rc = memset_s((void *)pcAddr, SIZE_OF_PAGE_COMPRESS_ADDR(chunk_size), 0, SIZE_OF_PAGE_COMPRESS_ADDR(chunk_size)); securec_check_c(rc, "\0", "\0"); @@ -155,6 +185,7 @@ void CheckAndRepairCompressAddress(PageCompressHeader *pcMap, uint16 chunk_size, pcAddr->chunknos[i], blocknum, global_chunknos[pcAddr->chunknos[i] - 1], path))); continue; } else { + // 如果未启用 zero_damaged_pages 配置,释放内存并生成错误报告 pfree(global_chunknos); ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), @@ -177,6 +208,7 @@ void CheckAndRepairCompressAddress(PageCompressHeader *pcMap, uint16 chunk_size, /* check nchunks for one page */ if (pcAddr->nchunks > pcAddr->allocated_chunks) { if (u_sess->attr.attr_security.zero_damaged_pages) { + // 如果启用了 zero_damaged_pages 配置,将该块的压缩地址信息清零 rc = memset_s((void *)pcAddr, SIZE_OF_PAGE_COMPRESS_ADDR(chunk_size), 0, SIZE_OF_PAGE_COMPRESS_ADDR(chunk_size)); securec_check_c(rc, "\0", "\0"); @@ -187,6 +219,7 @@ void CheckAndRepairCompressAddress(PageCompressHeader *pcMap, uint16 chunk_size, pcAddr->nchunks, pcAddr->allocated_chunks, blocknum, path))); continue; } else { + // 如果未启用 zero_damaged_pages 配置,释放内存并生成错误报告 pfree(global_chunknos); ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("nchunks %u exceeds allocated_chunks %u of block %u in file \"%s\"", @@ -194,11 +227,13 @@ void CheckAndRepairCompressAddress(PageCompressHeader *pcMap, uint16 chunk_size, } } + // 更新块号信息 max_blocknum = blocknum; if (pcAddr->nchunks > 0) { max_nonzero_blocknum = blocknum; } + // 更新全局块号数组 for (int i = 0; i < pcAddr->allocated_chunks; ++i) { global_chunknos[pcAddr->chunknos[i] - 1] = blocknum + 1; if (pcAddr->chunknos[i] > max_allocated_chunkno) { @@ -286,16 +321,24 @@ void CheckAndRepairCompressAddress(PageCompressHeader *pcMap, uint16 chunk_size, pcMap->last_recovery_start_time = pgStartTime; } -int64 CalculateMainForkSize(char* pathName, RelFileNode* rnode, ForkNumber forkNumber) +int64 CalculateMainForkSize(char *pathName, RelFileNode *rnode, ForkNumber forkNumber) { Assert(IS_COMPRESSED_RNODE((*rnode), forkNumber)); Assert(rnode->bucketNode == -1); return CalculateCompressMainForkSize(pathName); } - -void CopyCompressedPath(char dst[MAXPGPATH], const char* pathName, CompressedFileType compressFileType) +/* + * 功能:复制压缩文件的路径 + * + * 参数列表: + * dst:目标路径的字符数组,用于存储复制后的路径 + * pathName:源路径的字符串,表示压缩文件的路径 + * compressFileType:压缩文件的类型,可以是 COMPRESSED_TABLE_PCA_FILE 或 COMPRESSED_TABLE_PCD_FILE + */ +void CopyCompressedPath(char dst[MAXPGPATH], const char *pathName, CompressedFileType compressFileType) { int rc; + // 根据压缩文件类型构造目标路径 if (compressFileType == COMPRESSED_TABLE_PCA_FILE) { rc = snprintf_s(dst, MAXPGPATH, MAXPGPATH - 1, PCA_SUFFIX, pathName); } else { @@ -303,52 +346,91 @@ void CopyCompressedPath(char dst[MAXPGPATH], const char* pathName, CompressedFil } securec_check_ss(rc, "\0", "\0"); } - -int64 CalculateCompressMainForkSize(char* pathName, bool suppressedENOENT) +/* + * 功能:计算压缩主分支文件的大小 + * + * 参数列表: + * pathName:压缩文件的路径字符串 + * suppressedENOENT:一个布尔值,表示是否忽略 ENOENT 错误 + * + */ +int64 CalculateCompressMainForkSize(char *pathName, bool suppressedENOENT) { int64 totalsize = 0; char pcFilePath[MAXPGPATH]; + // 构造压缩主分支文件的路径,并计算其大小并累加到 totalsize CopyCompressedPath(pcFilePath, pathName, COMPRESSED_TABLE_PCA_FILE); totalsize += CalculateFileSize(pcFilePath, MAXPGPATH, suppressedENOENT); - + // 构造压缩主分支文件的路径,并计算其大小并累加到 totalsize CopyCompressedPath(pcFilePath, pathName, COMPRESSED_TABLE_PCD_FILE); totalsize += CalculateFileSize(pcFilePath, MAXPGPATH, suppressedENOENT); return totalsize; } - -uint16 ReadChunkSize(FILE* pcaFile, char* pcaFilePath, size_t len) +/* + * 功能:从压缩主分支文件中读取块大小 + * + * 参数列表: + * pcaFile:已打开的压缩主分支文件的文件指针 + * pcaFilePath:压缩主分支文件的路径字符串 + * len:文件长度 + * + * 返回值: + * 读取到的块大小(uint16类型) + */ +uint16 ReadChunkSize(FILE *pcaFile, char *pcaFilePath, size_t len) { uint16 chunkSize; + // 将文件指针定位到 PageCompressHeader 结构中的 chunk_size 字段 if (fseeko(pcaFile, (off_t)offsetof(PageCompressHeader, chunk_size), SEEK_SET) != 0) { ereport(ERROR, (errcode_for_file_access(), errmsg("could not seek in file \"%s\": \"%lu\": %m", pcaFilePath, len))); } + // 从文件中读取块大小 if (fread(&chunkSize, sizeof(chunkSize), 1, pcaFile) <= 0) { ereport(ERROR, (errcode_for_file_access(), errmsg("could not open file \"%s\": \"%lu\": %m", pcaFilePath, len))); } return chunkSize; } - -int64 CalculateFileSize(char* pathName, size_t size, bool suppressedENOENT) +/* + * 功能:计算文件的大小 + * + * 参数列表: + * pathName:文件的路径字符串 + * size:文件的默认大小 + * suppressedENOENT:一个布尔值,表示是否忽略 ENOENT 错误 + */ +int64 CalculateFileSize(char *pathName, size_t size, bool suppressedENOENT) { struct stat structstat; + // 获取文件的状态信息,并将其存储在 structstat 结构中 if (stat(pathName, &structstat)) { if (errno == ENOENT) { if (suppressedENOENT) { return 0; } + // 如果文件不存在且不忽略 ENOENT 错误,则报告文件未找到错误 ereport(ERROR, (errcode_for_file_access(), errmsg("could not FIND file \"%s\": %m", pathName))); } else { + // 如果发生其他错误,则报告无法获取文件状态信息的错误 ereport(ERROR, (errcode_for_file_access(), errmsg("could not stat file \"%s\": %m", pathName))); } } return structstat.st_size; } - +/* + * 功能:将压缩块大小转换为索引值 + * + * 参数列表: + * compressedChunkSize:压缩块大小 + * success:一个布尔指针,用于指示转换是否成功 + * + * 返回值: + * 压缩块大小对应的索引值(uint1类型) + */ uint1 ConvertChunkSize(uint32 compressedChunkSize, bool *success) { uint1 chunkSize = INDEX_OF_HALF_BLCKSZ; @@ -366,28 +448,38 @@ uint1 ConvertChunkSize(uint32 compressedChunkSize, bool *success) chunkSize = INDEX_OF_SIXTEENTHS_BLCKSZ; break; default: + // 如果压缩块大小不在预定义的范围内,标记转换失败并返回默认索引值 *success = false; return chunkSize; } + // 标记转换成功并返回索引值 *success = true; return chunkSize; } constexpr int MAX_RETRY_LIMIT = 60; constexpr long RETRY_SLEEP_TIME = 1000000L; - -size_t ReadAllChunkOfBlock(char *dst, size_t destLen, BlockNumber blockNumber, ReadBlockChunksStruct& rbStruct) +/* + * 功能:从压缩文件中读取指定块号的所有块数据 + * + * 参数列表: + * dst:用于存储读取数据的目标缓冲区 + * destLen:目标缓冲区的长度 + * blockNumber:要读取的块号 + * rbStruct:包含读取操作所需信息的结构体 + */ +size_t ReadAllChunkOfBlock(char *dst, size_t destLen, BlockNumber blockNumber, ReadBlockChunksStruct &rbStruct) { - PageCompressHeader* header = rbStruct.header; + PageCompressHeader *header = rbStruct.header; + // 检查块号是否超出最大块号,如果是则报错 if (blockNumber >= header->nblocks) { ereport(ERROR, - (ERRCODE_INVALID_PARAMETER_VALUE, - errmsg("blocknum \"%u\" exceeds max block number", blockNumber))); + (ERRCODE_INVALID_PARAMETER_VALUE, errmsg("blocknum \"%u\" exceeds max block number", blockNumber))); } - const char* fileName = rbStruct.fileName; + const char *fileName = rbStruct.fileName; decltype(PageCompressHeader::chunk_size) chunkSize = header->chunk_size; decltype(ReadBlockChunksStruct::segmentNo) segmentNo = rbStruct.segmentNo; - PageCompressAddr* currentAddr = GET_PAGE_COMPRESS_ADDR(header, chunkSize, blockNumber); + PageCompressAddr *currentAddr = GET_PAGE_COMPRESS_ADDR(header, chunkSize, blockNumber); size_t tryCount = 0; /* for empty chunks write */ @@ -399,25 +491,30 @@ size_t ReadAllChunkOfBlock(char *dst, size_t destLen, BlockNumber blockNumber, R for (uint8 i = 0; i < nchunks; ++i) { off_t seekPos = (off_t)OFFSET_OF_PAGE_COMPRESS_CHUNK(chunkSize, currentAddr->chunknos[i]); uint8 start = i; + // 寻找连续的块,合并读取 while (i < nchunks - 1 && currentAddr->chunknos[i + 1] == currentAddr->chunknos[i] + 1) { i++; } + // 将文件指针定位到块的位置 if (fseeko(rbStruct.fp, seekPos, SEEK_SET) != 0) { ReleaseMap(header, fileName); ereport(ERROR, (errcode_for_file_access(), errmsg("could not seek in file \"%s\": %m", fileName))); } size_t readAmount = chunkSize * (i - start + 1); + // 从文件中读取块数据,并将其写入目标缓冲区 if (fread(dst + start * chunkSize, 1, readAmount, rbStruct.fp) != readAmount && ferror(rbStruct.fp)) { ReleaseMap(header, fileName); ereport(ERROR, (errcode_for_file_access(), errmsg("could not read file \"%s\": %m", fileName))); } } + // 如果块没有数据,则跳出循环 if (nchunks == 0) { break; } char *data = NULL; size_t dataLen; uint32 crc32; + // 从目标缓冲区中获取数据、数据长度和校验和 if (PageIs8BXidHeapVersion(dst)) { HeapPageCompressData *heapPageData = (HeapPageCompressData *)dst; data = heapPageData->data; @@ -429,6 +526,7 @@ size_t ReadAllChunkOfBlock(char *dst, size_t destLen, BlockNumber blockNumber, R dataLen = heapPageData->size; crc32 = heapPageData->crc32; } + // 从目标缓冲区中获取数据、数据长度和校验和 if (DataBlockChecksum(data, dataLen, true) == crc32) { break; } @@ -439,28 +537,31 @@ size_t ReadAllChunkOfBlock(char *dst, size_t destLen, BlockNumber blockNumber, R } else { ReleaseMap(header, fileName); ereport(ERROR, - (errcode_for_file_access(), - errmsg("base backup cheksum or Decompressed blockno %u failed in file \"%s\", aborting backup. " - "nchunks: %u, allocatedChunks: %u, segno: %d.", - blockNumber, - fileName, - nchunks, - allocatedChunks, - segmentNo))); + (errcode_for_file_access(), + errmsg("base backup cheksum or Decompressed blockno %u failed in file \"%s\", aborting backup. " + "nchunks: %u, allocatedChunks: %u, segno: %d.", + blockNumber, fileName, nchunks, allocatedChunks, segmentNo))); } } while (true); + // 如果已分配的块数大于实际块数,将多余的块数据清零 if (allocatedChunks > nchunks) { auto currentWriteSize = nchunks * chunkSize; securec_check( memset_s(dst + currentWriteSize, destLen - currentWriteSize, 0, (allocatedChunks - nchunks) * chunkSize), - "", - ""); + "", ""); } return allocatedChunks * chunkSize; } - -void ReleaseMap(PageCompressHeader* map, const char* fileName) +/* + * 功能:释放页压缩内存映射 + * + * 参数列表: + * map:PageCompressHeader 结构的指针,表示页压缩内存映射的头部信息 + * fileName:文件名,表示要释放的映射对应的文件 + */ +void ReleaseMap(PageCompressHeader *map, const char *fileName) { + // 检查映射指针是否有效,并尝试解除映射 if (map != NULL && pc_munmap(map) != 0) { ereport(WARNING, (errcode_for_file_access(), errmsg("could not munmap file \"%s\": %m", fileName))); } diff --git a/src/gausskernel/storage/smgr/segment/data_file.cpp b/src/gausskernel/storage/smgr/segment/data_file.cpp index d3268e23d..15db84a42 100644 --- a/src/gausskernel/storage/smgr/segment/data_file.cpp +++ b/src/gausskernel/storage/smgr/segment/data_file.cpp @@ -55,11 +55,16 @@ static int dv_open_file(char *filename, uint32 flags, int mode) errno = err; return fd; } - +/* + * 功能:关闭文件描述符并生成日志信息 + * + * 参数列表: + * fd:int,表示文件描述符 + */ static void dv_close_file(int fd) { - close(fd); - ereport(LOG, (errmsg("dv_close_file fd is %d", fd))); + close(fd); // 关闭文件描述符 + ereport(LOG, (errmsg("dv_close_file fd is %d", fd))); // 生成日志信息,记录关闭的文件描述符的值 } /* Return a palloc string, and callers should free it */ @@ -100,17 +105,13 @@ void df_create_file(SegLogicFile *sf, bool redo) // File not exists uint32 flags = O_RDWR | O_CREAT | O_EXCL | PG_BINARY; if (sf->segfiles != NULL) { - ereport(LOG, - (errmodule(MOD_SEGMENT_PAGE), - errmsg("[segpage] sf->segfiles is not null, last invocation of df_create_file must be failed " - "halfway. spc/db/relnode/fork: %u/%u/%d/%d", - sf->relNode.spcNode, - sf->relNode.dbNode, - sf->relNode.relNode, - sf->forknum))); + ereport(LOG, (errmodule(MOD_SEGMENT_PAGE), + errmsg("[segpage] sf->segfiles is not null, last invocation of df_create_file must be failed " + "halfway. spc/db/relnode/fork: %u/%u/%d/%d", + sf->relNode.spcNode, sf->relNode.dbNode, sf->relNode.relNode, sf->forknum))); } else { MemoryContext oldcnxt = MemoryContextSwitchTo(INSTANCE_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_STORAGE)); - sf->segfiles = (SegPhysicalFile*)palloc(sizeof(SegPhysicalFile) * DF_ARRAY_EXTEND_STEP); + sf->segfiles = (SegPhysicalFile *)palloc(sizeof(SegPhysicalFile) * DF_ARRAY_EXTEND_STEP); MemoryContextSwitchTo(oldcnxt); for (int i = 0; i < DF_ARRAY_EXTEND_STEP; i++) { @@ -136,17 +137,28 @@ void df_create_file(SegLogicFile *sf, bool redo) } pfree(filename); } - +/* + * 功能:获取分段逻辑文件的物理文件 + * + * 参数列表: + * sf:SegLogicFile*,指向分段逻辑文件的指针 + * sliceno:int,分片号 + * target_block:BlockNumber,目标块号 + * + * 返回值:SegPhysicalFile,表示分段物理文件的结构 + */ static SegPhysicalFile df_get_physical_file(SegLogicFile *sf, int sliceno, BlockNumber target_block) { - AutoMutexLock filelock(&sf->filelock); + AutoMutexLock filelock(&sf->filelock); // 使用自动互斥锁,确保线程安全 filelock.lock(); + // 检查目标块号是否为无效块号 if (target_block == InvalidBlockNumber) { SegmentCheck(0); - ereport(ERROR, - (errcode(ERRCODE_DATA_EXCEPTION), errmsg("df_get_physical_file target_block is InvalidBlockNumber!\n"))); + ereport(ERROR, (errcode(ERRCODE_DATA_EXCEPTION), + errmsg("df_get_physical_file target_block is InvalidBlockNumber!\n"))); } + // 如果目标块号大于文件的总块数,尝试扩展文件 if (sf->total_blocks <= target_block) { ereport(LOG, (errmodule(MOD_SEGMENT_PAGE), errmsg("Try to access file %s block %u, exceeds the file total blocks %u", @@ -157,12 +169,21 @@ static SegPhysicalFile df_get_physical_file(SegLogicFile *sf, int sliceno, Block } } + // 检查分片号是否有效 SegmentCheck(sliceno < sf->file_num); + // 获取指定分片的物理文件 SegPhysicalFile spf = sf->segfiles[sliceno]; return spf; } - +/* + * 功能:刷新分段逻辑文件中的数据到物理文件 + * + * 参数列表: + * sf:SegLogicFile*,指向分段逻辑文件的指针 + * blocknum:BlockNumber,起始块号 + * nblocks:BlockNumber,块的数量 + */ void df_flush_data(SegLogicFile *sf, BlockNumber blocknum, BlockNumber nblocks) { int128 remainBlocks = nblocks; @@ -176,12 +197,15 @@ void df_flush_data(SegLogicFile *sf, BlockNumber blocknum, BlockNumber nblocks) nflush = DF_FILE_SLICE_BLOCKS - (blocknum % DF_FILE_SLICE_BLOCKS); } + // 获取分片的物理文件 SegPhysicalFile spf = df_get_physical_file(sf, slice_start, blocknum); if (spf.fd < 0) { return; } + // 计算物理文件中的偏移位置 off_t seekpos = (off_t)BLCKSZ * (blocknum % DF_FILE_SLICE_BLOCKS); + // 调用底层函数刷新数据到物理文件 pg_flush_data(spf.fd, seekpos, (off_t)nflush * BLCKSZ); remainBlocks -= nflush; @@ -209,7 +233,13 @@ void df_extend_file_vector(SegLogicFile *sf) sf->segfiles = newfiles; sf->vector_capacity = new_capacity; } - +/* + * 功能:关闭所有分段逻辑文件中的文件描述符 + * + * 参数列表: + * key:RepairFileKey,表示需要修复的文件的关键信息 + * max_sliceno:int32,最大分片号 + */ void df_close_all_file(RepairFileKey key, int32 max_sliceno) { Oid relNode = key.relfilenode.relNode; @@ -218,10 +248,12 @@ void df_close_all_file(RepairFileKey key, int32 max_sliceno) SegSpace *spc = spc_init_space_node(key.relfilenode.spcNode, key.relfilenode.dbNode); Assert(relNode <= EXTENT_8192 && relNode > 0); + // 获取分段空间并加锁 AutoMutexLock spc_lock(&spc->lock); spc_lock.lock(); SegExtentGroup *eg = &spc->extent_group[relNode - 1][forknum]; + // 获取分段组并加锁 AutoMutexLock lock(&eg->lock); lock.lock(); @@ -234,11 +266,11 @@ void df_close_all_file(RepairFileKey key, int32 max_sliceno) if (stat(tempfilename, &statBuf) < 0) { /* ENOENT is expected after the last segment... */ if (errno != ENOENT) { - ereport(WARNING, (errcode_for_file_access(), - errmsg("could not stat file \"%s\": %m", tempfilename))); + ereport(WARNING, (errcode_for_file_access(), errmsg("could not stat file \"%s\": %m", tempfilename))); } } else { if (eg->segfile->segfiles[i].fd > 0) { + // 关闭文件描述符 dv_close_file(eg->segfile->segfiles[i].fd); eg->segfile->segfiles[i].fd = -1; } @@ -254,7 +286,13 @@ void df_close_all_file(RepairFileKey key, int32 max_sliceno) spc_lock.unLock(); return; } - +/* + * 功能:清除并关闭所有分段逻辑文件中的文件描述符 + * + * 参数列表: + * key:RepairFileKey,表示需要修复的文件的关键信息 + * max_sliceno:int32,最大分片号 + */ void df_clear_and_close_all_file(RepairFileKey key, int32 max_sliceno) { Oid relNode = key.relfilenode.relNode; @@ -262,10 +300,12 @@ void df_clear_and_close_all_file(RepairFileKey key, int32 max_sliceno) SegSpace *spc = spc_init_space_node(key.relfilenode.spcNode, key.relfilenode.dbNode); Assert(relNode <= EXTENT_8192 && relNode > 0); + // 获取分段空间并加锁 AutoMutexLock spc_lock(&spc->lock); spc_lock.lock(); SegExtentGroup *eg = &spc->extent_group[relNode - 1][forknum]; + // 获取分段组并加锁 AutoMutexLock lock(&eg->lock); lock.lock(); @@ -277,13 +317,12 @@ void df_clear_and_close_all_file(RepairFileKey key, int32 max_sliceno) tempfilename = slice_filename(eg->segfile->filename, i); int ret = unlink(tempfilename); if (ret < 0 && errno != ENOENT) { - ereport(WARNING, (errcode_for_file_access(), - errmsg("could not remove file \"%s\": %m", tempfilename))); + ereport(WARNING, (errcode_for_file_access(), errmsg("could not remove file \"%s\": %m", tempfilename))); } if (ret >= 0) { - ereport(LOG, (errcode_for_file_access(), - errmsg("[file repair] clear segment file \"%s\"", tempfilename))); + ereport(LOG, (errcode_for_file_access(), errmsg("[file repair] clear segment file \"%s\"", tempfilename))); if (eg->segfile->segfiles[i].fd > 0) { + // 关闭文件描述符 dv_close_file(eg->segfile->segfiles[i].fd); eg->segfile->segfiles[i].fd = -1; } @@ -299,7 +338,13 @@ void df_clear_and_close_all_file(RepairFileKey key, int32 max_sliceno) spc_lock.unLock(); return; } - +/* + * 功能:打开所有分段逻辑文件中的文件描述符 + * + * 参数列表: + * key:RepairFileKey,表示需要修复的文件的关键信息 + * max_sliceno:int32,最大分片号 + */ void df_open_all_file(RepairFileKey key, int32 max_sliceno) { int fd = -1; @@ -310,10 +355,12 @@ void df_open_all_file(RepairFileKey key, int32 max_sliceno) SegSpace *spc = spc_init_space_node(key.relfilenode.spcNode, key.relfilenode.dbNode); Assert(relNode <= EXTENT_8192 && relNode > 0); + // 获取分段空间并加锁 AutoMutexLock spc_lock(&spc->lock); spc_lock.lock(); SegExtentGroup *eg = &spc->extent_group[relNode - 1][forknum]; + // 获取分段组并加锁 AutoMutexLock eg_lock(&eg->lock); eg_lock.lock(); @@ -349,20 +396,32 @@ void df_open_all_file(RepairFileKey key, int32 max_sliceno) /* * sliceno == 0, means opening all files; otherwises open until the target slice. */ +/* + * 功能:打开目标文件并初始化相应的文件描述符和相关信息 + * + * 参数列表: + * sf:SegLogicFile*,分段逻辑文件结构指针 + * targetno:int,目标文件号 + */ static void df_open_target_files(SegLogicFile *sf, int targetno) { - int sliceno = sf->file_num; + int sliceno = sf->file_num; // 当前文件数即分片号 uint32 flags = O_RDWR | PG_BINARY; + // 循环打开目标文件和后续文件 for (;;) { + // 如果指定了目标文件号并且已经达到了目标文件号,则退出循环 if (targetno != 0 && targetno < sliceno) { break; } + // 构建文件名 char *filename = slice_filename(sf->filename, sliceno); + // 如果分片号超出了向量的容量,扩展向量 if (sliceno >= sf->vector_capacity) { df_extend_file_vector(sf); } + // 尝试打开文件 int fd = dv_open_file(filename, flags, SEGMENT_FILE_MODE); if (fd < 0) { if (errno != ENOENT) { @@ -375,13 +434,16 @@ static void df_open_target_files(SegLogicFile *sf, int targetno) break; } + // 初始化文件描述符和总块数 sf->segfiles[sliceno].fd = fd; sf->segfiles[sliceno].sliceno = sliceno; off_t size = lseek(fd, 0L, SEEK_END); sf->total_blocks += size / BLCKSZ; + // 释放临时文件名内存 pfree(filename); + // 更新分片号和文件数 sliceno++; sf->file_num++; } @@ -404,33 +466,41 @@ void df_open_files(SegLogicFile *sf) /* * Extend logic file once. Each time we extend at most DF_FILE_SLICE_SIZE. */ +/* + * 功能:在逻辑文件的末尾扩展文件大小 + * + * 参数列表: + * sf:SegLogicFile*,分段逻辑文件结构指针 + */ void df_extend_internal(SegLogicFile *sf) { - int fd = sf->segfiles[sf->file_num - 1].fd; + int fd = sf->segfiles[sf->file_num - 1].fd; // 获取最后一个文件的文件描述符 - off_t last_file_size = lseek(fd, 0L, SEEK_END); - SegmentCheck(last_file_size <= DF_FILE_SLICE_SIZE); + off_t last_file_size = lseek(fd, 0L, SEEK_END); // 获取最后一个文件的当前大小 + SegmentCheck(last_file_size <= DF_FILE_SLICE_SIZE); // 检查是否超出分片大小 - if (last_file_size == DF_FILE_SLICE_SIZE) { - int new_sliceno = sf->file_num; - char *filename = slice_filename(sf->filename, new_sliceno); + if (last_file_size == DF_FILE_SLICE_SIZE) { // 如果最后一个文件已满 + int new_sliceno = sf->file_num; // 新分片号 + char *filename = slice_filename(sf->filename, new_sliceno); // 构建新文件名 + // 如果新分片号超出向量容量,扩展向量 if (new_sliceno >= sf->vector_capacity) { df_extend_file_vector(sf); } + // 创建新文件 int new_fd = dv_open_file(filename, O_RDWR | O_CREAT, SEGMENT_FILE_MODE); if (new_fd < 0) { ereport(ERROR, (errcode_for_file_access(), errmsg("[segpage] could not create file \"%s\": %m", filename))); } + // 扩展新文件大小 if (ftruncate(new_fd, DF_FILE_EXTEND_STEP_SIZE) != 0) { dv_close_file(new_fd); - ereport(ERROR, - (errmodule(MOD_SEGMENT_PAGE), - errcode_for_file_access(), - errmsg("ftuncate file %s failed during df_extend due to %s", filename, strerror(errno)), - errdetail("file path: %s", sf->filename))); + ereport(ERROR, (errmodule(MOD_SEGMENT_PAGE), errcode_for_file_access(), + errmsg("ftuncate file %s failed during df_extend due to %s", filename, strerror(errno)), + errdetail("file path: %s", sf->filename))); } + // 更新分片信息 sf->segfiles[new_sliceno] = {.fd = new_fd, .sliceno = new_sliceno}; sf->file_num++; sf->total_blocks += DF_FILE_EXTEND_STEP_BLOCKS; @@ -445,11 +515,13 @@ void df_extend_internal(SegLogicFile *sf) SegmentCheck(new_size <= DF_FILE_SLICE_SIZE); + // 扩展最后一个文件大小 if (ftruncate(fd, new_size) != 0) { char *filename = slice_filename(sf->filename, sf->file_num - 1); ereport(ERROR, (errmsg("ftuncate file %s failed during df_extend due to %s", filename, strerror(errno)))); } + // 更新总块数 sf->total_blocks += (new_size - last_file_size) / BLCKSZ; } } @@ -457,22 +529,29 @@ void df_extend_internal(SegLogicFile *sf) /* * Extend a logic file to target blocks. */ +/* + * 功能:根据目标块数扩展逻辑文件 + * + * 参数列表: + * sf:SegLogicFile*,分段逻辑文件结构指针 + * target_blocks:BlockNumber,目标块数 + */ void df_extend(SegLogicFile *sf, BlockNumber target_blocks) { - if (target_blocks == InvalidBlockNumber) { + if (target_blocks == InvalidBlockNumber) { // 检查目标块数是否合法 SegmentCheck(0); - ereport(ERROR, - (errcode(ERRCODE_DATA_EXCEPTION), errmsg("df_extend target_blocks is InvalidBlockNumber!\n"))); + ereport(ERROR, (errcode(ERRCODE_DATA_EXCEPTION), errmsg("df_extend target_blocks is InvalidBlockNumber!\n"))); } /* align target blocks to DF_FILE_EXTEND_STEP_BLOCKS */ target_blocks = CM_ALIGN_ANY(target_blocks, DF_FILE_EXTEND_STEP_BLOCKS); - AutoMutexLock lock(&sf->filelock); + AutoMutexLock lock(&sf->filelock); // 获取逻辑文件的锁 lock.lock(); if (sf->total_blocks < target_blocks) { if (!RecoveryInProgress()) { + // 检查是否超过最大大小 uint64 requestSize = 1LLU * BLCKSZ * (target_blocks - sf->total_blocks); TableSpaceUsageManager::IsExceedMaxsize(sf->relNode.spcNode, requestSize, true); } @@ -483,20 +562,26 @@ void df_extend(SegLogicFile *sf, BlockNumber target_blocks) SegmentCheck(sf->file_num > 0); while (sf->total_blocks < target_blocks) { - df_extend_internal(sf); + df_extend_internal(sf); // 循环扩展逻辑文件大小 } ereport(LOG, (errmsg("extend data file %s to %u blocks", sf->filename, target_blocks))); } - +/* + * 功能:缩小逻辑文件大小至目标块数 + * + * 参数列表: + * sf:SegLogicFile*,分段逻辑文件结构指针 + * target:BlockNumber,目标块数 + */ void df_shrink(SegLogicFile *sf, BlockNumber target) { - AutoMutexLock lock(&sf->filelock); + AutoMutexLock lock(&sf->filelock); // 获取逻辑文件的锁 lock.lock(); ssize_t last_file_size; ssize_t last_file_blocks; BlockNumber shrink_blocks; - SegmentCheck(target % DF_FILE_EXTEND_STEP_BLOCKS == 0); + SegmentCheck(target % DF_FILE_EXTEND_STEP_BLOCKS == 0); // 检查目标块数是否按规则对齐 if (sf->total_blocks < target) { return; } @@ -527,8 +612,8 @@ void df_shrink(SegLogicFile *sf, BlockNumber target) if (sf->segfiles[i].fd < 0) { ereport(PANIC, (errmsg("Unlink file %s failed and unable to read it again.", filename))); } else { - ereport( - ERROR, (errmsg("unlink file %s failed during df_shrink due to %s", filename, strerror(errno)))); + ereport(ERROR, + (errmsg("unlink file %s failed during df_shrink due to %s", filename, strerror(errno)))); } } sf->file_num--; @@ -565,63 +650,83 @@ void df_pread_block(SegLogicFile *sf, char *buffer, BlockNumber blocknum) int nbytes = pread(spf.fd, buffer, BLCKSZ, roffset); pgstat_report_waitevent(WAIT_EVENT_END); if (nbytes != BLCKSZ) { - ereport(ERROR, - (errcode(MOD_SEGMENT_PAGE), - errcode_for_file_access(), - errmsg("could not read segment block %d in file %s", blocknum, sf->filename), - errdetail("errno: %d", errno))); + ereport(ERROR, (errcode(MOD_SEGMENT_PAGE), errcode_for_file_access(), + errmsg("could not read segment block %d in file %s", blocknum, sf->filename), + errdetail("errno: %d", errno))); } } - +/* + * 功能:向分段逻辑文件中的指定块写入数据 + * + * 参数列表: + * sf:SegLogicFile*,分段逻辑文件结构指针 + * buffer:const char*,要写入的数据缓冲区指针 + * blocknum:BlockNumber,要写入的块号 + */ void df_pwrite_block(SegLogicFile *sf, const char *buffer, BlockNumber blocknum) { - off_t offset = ((off_t)blocknum) * BLCKSZ; - int sliceno = DF_OFFSET_TO_SLICENO(offset); - off_t roffset = DF_OFFSET_TO_SLICE_OFFSET(offset); + off_t offset = ((off_t)blocknum) * BLCKSZ; // 计算块号对应的偏移量 + int sliceno = DF_OFFSET_TO_SLICENO(offset); // 计算偏移量对应的切片号 + off_t roffset = DF_OFFSET_TO_SLICE_OFFSET(offset); // 计算偏移量对应的切片内偏移量 - pgstat_report_waitevent(WAIT_EVENT_DATA_FILE_WRITE); + pgstat_report_waitevent(WAIT_EVENT_DATA_FILE_WRITE); // 报告等待事件,表示正在进行数据文件写入操作 + + // 获取物理文件句柄 SegPhysicalFile spf = df_get_physical_file(sf, sliceno, blocknum); - int nbytes = pwrite(spf.fd, buffer, BLCKSZ, roffset); - pgstat_report_waitevent(WAIT_EVENT_END); + int nbytes = pwrite(spf.fd, buffer, BLCKSZ, roffset); // 执行写入操作 + pgstat_report_waitevent(WAIT_EVENT_END); // 结束等待事件的报告 + // 检查写入的字节数是否与期望的块大小相等,如果不相等,则抛出错误 if (nbytes != BLCKSZ) { - ereport(ERROR, - (errcode(MOD_SEGMENT_PAGE), - errcode_for_file_access(), - errmsg("could not write segment block %d in file %s, ", blocknum, sf->filename), - errdetail("errno: %d", errno))); + ereport(ERROR, (errcode(MOD_SEGMENT_PAGE), errcode_for_file_access(), + errmsg("could not write segment block %d in file %s, ", blocknum, sf->filename), + errdetail("errno: %d", errno))); } - seg_register_dirty_file(sf, sliceno); + seg_register_dirty_file(sf, sliceno); // 标记文件为脏文件,需要刷新到磁盘 } - +/* + * 功能:初始化分段逻辑文件控制结构 + * + * 参数列表: + * sf:SegLogicFile*,分段逻辑文件结构指针 + * relNode:RelFileNode,关系文件节点标识 + * forknum:ForkNumber,分叉号 + */ void df_ctrl_init(SegLogicFile *sf, RelFileNode relNode, ForkNumber forknum) { - sf->file_num = 0; - sf->segfiles = NULL; - sf->vector_capacity = 0; - sf->total_blocks = 0; + sf->file_num = 0; // 初始化文件数量为0 + sf->segfiles = NULL; // 初始化分段文件结构数组为空 + sf->vector_capacity = 0; // 初始化容量为0 + sf->total_blocks = 0; // 初始化总块数为0 - sf->relNode = relNode; - sf->forknum = forknum; + sf->relNode = relNode; // 设置关系文件节点标识 + sf->forknum = forknum; // 设置分叉号 - char *filename = relpathperm(relNode, forknum); - errno_t er = strcpy_s(sf->filename, MAXPGPATH, filename); - securec_check(er, "\0", "\0"); - pfree(filename); + char *filename = relpathperm(relNode, forknum); // 获取文件路径 + errno_t er = strcpy_s(sf->filename, MAXPGPATH, filename); // 复制文件路径到结构中 + securec_check(er, "\0", "\0"); // 检查字符串复制操作的错误 + pfree(filename); // 释放文件路径内存 - pthread_mutex_init(&sf->filelock, NULL); + pthread_mutex_init(&sf->filelock, NULL); // 初始化文件锁 } - +/* + * 功能:同步分段逻辑文件到磁盘 + * + * 参数列表: + * sf:SegLogicFile*,分段逻辑文件结构指针 + */ void df_fsync(SegLogicFile *sf) { - AutoMutexLock filelock(&sf->filelock); - filelock.lock(); + AutoMutexLock filelock(&sf->filelock); // 创建文件锁对象并自动加锁 + filelock.lock(); // 加锁以保证独占文件操作 for (int i = 0; i < sf->file_num; i++) { if (pg_fsync(sf->segfiles[i].fd)) { - ereport(data_sync_elevel(ERROR), (errcode_for_file_access(), errmodule(MOD_SEGMENT_PAGE), - errmsg("recover failed could not fsync segment data file"), - errdetail( "filename \"%s\", slot id %d, error message: %m", sf->filename, i))); + ereport(data_sync_elevel(ERROR), // 报告错误级别 + (errcode_for_file_access(), // 设置文件访问错误码 + errmodule(MOD_SEGMENT_PAGE), // 设置模块标识 + errmsg("recover failed could not fsync segment data file"), // 错误消息 + errdetail("filename \"%s\", slot id %d, error message: %m", sf->filename, i))); // 详细错误信息 } } } @@ -638,12 +743,12 @@ void df_unlink(SegLogicFile *sf) filelock.lock(); /* - * We first record "drop tablespace" xlog, then do the unlink. When the recovery thread + * We first record "drop tablespace" xlog, then do the unlink. When the recovery thread * replay the drop tablespace xlog, it will reopen the space, i.e., finding all data files - * from 0 to n. Thus, we should unlink files from back to front in case of failure happens + * from 0 to n. Thus, we should unlink files from back to front in case of failure happens * on half, so that the recovery thread can still unlink all files. */ - for (int i = sf->file_num-1; i >= 0; i--) { + for (int i = sf->file_num - 1; i >= 0; i--) { dv_close_file(sf->segfiles[i].fd); sf->segfiles[i].fd = -1; @@ -672,87 +777,109 @@ void forget_space_fsync_request(SegSpace *spc) } } } - +/* + * 功能:向同步管理器注册忘记请求,用于处理分段逻辑文件的回收 + * + * 参数列表: + * sf:SegLogicFile*,分段逻辑文件结构指针 + * segno:int,分段编号 + */ void seg_register_forget_request(SegLogicFile *sf, int segno) { - FileTag ftag; - errno_t er = memset_s(&ftag, sizeof(FileTag), 0, sizeof(FileTag)); - securec_check(er, "", ""); + FileTag ftag; // 创建文件标签结构 + errno_t er = memset_s(&ftag, sizeof(FileTag), 0, sizeof(FileTag)); // 初始化文件标签结构 + securec_check(er, "", ""); // 检查memset_s的返回值,确保安全 - ftag.rnode = sf->relNode; - ftag.forknum = sf->forknum; - ftag.handler = SYNC_HANDLER_SEGMENT; - ftag.segno = segno; + ftag.rnode = sf->relNode; // 设置文件标签的关联文件节点 + ftag.forknum = sf->forknum; // 设置文件标签的分支号 + ftag.handler = SYNC_HANDLER_SEGMENT; // 设置文件标签的处理程序为分段处理程序 + ftag.segno = segno; // 设置文件标签的分段编号 RegisterSyncRequest(&ftag, SYNC_FORGET_REQUEST, true /* retryOnError */); } - +/* + * 功能:向同步管理器注册脏文件请求,用于处理分段逻辑文件的脏数据 + * + * 参数列表: + * sf:SegLogicFile*,分段逻辑文件结构指针 + * segno:int,分段编号 + */ void seg_register_dirty_file(SegLogicFile *sf, int segno) { - FileTag ftag; - errno_t er = memset_s(&ftag, sizeof(FileTag), 0, sizeof(FileTag)); - securec_check(er, "", ""); + FileTag ftag; // 创建文件标签结构 + errno_t er = memset_s(&ftag, sizeof(FileTag), 0, sizeof(FileTag)); // 初始化文件标签结构 + securec_check(er, "", ""); // 检查memset_s的返回值,确保安全 /* Initialize ftag */ - ftag.rnode = sf->relNode; - ftag.forknum = sf->forknum; - ftag.rnode.bucketNode = SegmentBktId; - ftag.handler = SYNC_HANDLER_SEGMENT; - ftag.segno = segno; + ftag.rnode = sf->relNode; // 设置文件标签的关联文件节点 + ftag.forknum = sf->forknum; // 设置文件标签的分支号 + ftag.rnode.bucketNode = SegmentBktId; // 设置文件标签的Bucket节点 + ftag.handler = SYNC_HANDLER_SEGMENT; // 设置文件标签的处理程序为分段处理程序 + ftag.segno = segno; // 设置文件标签的分段编号 - if (!RegisterSyncRequest(&ftag, SYNC_REQUEST, false)) { - ereport(DEBUG5, - (errmodule(MOD_SEGMENT_PAGE), errmsg("could not forward fsync request because request queue is full"))); + if (!RegisterSyncRequest(&ftag, SYNC_REQUEST, + false)) { // 向同步管理器注册脏文件请求,用于处理分段逻辑文件的脏数据,设置重试选项为false + ereport(DEBUG5, (errmodule(MOD_SEGMENT_PAGE), errmsg("could not forward fsync request because request queue is " + "full"))); // 如果注册请求失败,输出DEBUG5级别的日志信息 - if (pg_fsync(sf->segfiles[segno].fd) < 0) { - char *filename = slice_filename(sf->filename, segno); + if (pg_fsync(sf->segfiles[segno].fd) < 0) { // 否则,通过pg_fsync函数尝试同步文件数据 + char *filename = slice_filename(sf->filename, segno); // 获取分段逻辑文件的文件名 ereport(data_sync_elevel(ERROR), - (errcode_for_file_access(), errmsg("could not fsync file \"%s\": %m", filename))); - pfree(filename); + (errcode_for_file_access(), + errmsg("could not fsync file \"%s\": %m", filename))); // 输出同步文件数据失败的错误日志信息 + pfree(filename); // 释放分配的内存空间 } } } - +/* + * 功能:通过文件标签同步分段文件 + * + * 参数列表: + * ftag:const FileTag*,文件标签指针,用于标识要同步的文件 + * path:char*,用于存储文件路径的字符数组 + * + */ int seg_sync_filetag(const FileTag *ftag, char *path) { /* Open tablespace */ SegSpace *spc = spc_open(ftag->rnode.spcNode, ftag->rnode.dbNode, false); - if (spc == NULL) { + if (spc == NULL) { // 如果表空间为空 ereport(LOG, (errmodule(MOD_SEGMENT_PAGE), errmsg("[segpage] seg_sync_filetag %s, ftag->segno is %u, but space is empty", relpathperm(ftag->rnode, ftag->forknum), ftag->segno))); - errno = ENOENT; - return -1; + errno = ENOENT; // 设置错误码为ENOENT + return -1; // 返回-1表示同步失败 } - int extent_size = EXTENT_TYPE_TO_SIZE(ftag->rnode.relNode); - int egid = EXTENT_SIZE_TO_GROUPID(extent_size); + int extent_size = EXTENT_TYPE_TO_SIZE(ftag->rnode.relNode); // 获取文件的大小 + int egid = EXTENT_SIZE_TO_GROUPID(extent_size); // 获取文件的组ID - SegLogicFile *lf = spc->extent_group[egid][ftag->forknum].segfile; - strlcpy(path, lf->filename, MAXPGPATH); + SegLogicFile *lf = spc->extent_group[egid][ftag->forknum].segfile; // 获取文件的大小 + int egid = EXTENT_SIZE_TO_GROUPID(extent_size); // 获取文件的组ID - AutoMutexLock lock(&lf->filelock); - lock.lock(); + AutoMutexLock lock(&lf->filelock); // 创建自动互斥锁 + lock.lock(); // 锁定互斥锁 - if (ftag->segno >= (uint32)lf->file_num) { + if (ftag->segno >= (uint32)lf->file_num) { // 如果要同步的分段编号大于等于已有的文件数 /* File may be deleted by spc_shrink or space delete */ - ereport(LOG, + ereport( + LOG, (errmodule(MOD_SEGMENT_PAGE), - errmsg("[segpage] seg_sync_filetag %s, ftag->segno is %u, but existing file number is %u, the file may " - "be deleted by spc_shrink or drop tablespace", - path, ftag->segno, lf->file_num))); - errno = ENOENT; - return -1; + errmsg("[segpage] seg_sync_filetag %s, ftag->segno is %u, but existing file number is %u, the file may " + "be deleted by spc_shrink or drop tablespace", + path, ftag->segno, lf->file_num))); // 输出错误日志信息 + errno = ENOENT; // 设置错误码为ENOENT + return -1; // 返回-1表示同步失败 } /* can not sync the file not exist */ - SegPhysicalFile *sf = &lf->segfiles[ftag->segno]; + SegPhysicalFile *sf = &lf->segfiles[ftag->segno]; // 获取分段文件结构指针 - SegmentCheck((uint32)sf->sliceno == ftag->segno); - int ret = pg_fsync(sf->fd); + SegmentCheck((uint32)sf->sliceno == ftag->segno); // 检查分段文件的分段编号是否匹配 + int ret = pg_fsync(sf->fd); // 调用pg_fsync函数进行文件同步操作 - ereport(DEBUG1, (errmsg("segment fsync %s", path))); + ereport(DEBUG1, (errmsg("segment fsync %s", path))); // 输出DEBUG1级别的日志信息 - return ret; + return ret; // 返回同步操作的结果 } int seg_unlink_filetag(const FileTag *ftag, char *path) @@ -760,20 +887,30 @@ int seg_unlink_filetag(const FileTag *ftag, char *path) SegmentCheck(0); return 0; } - +/* + * 功能:忘记指定数据库的文件同步请求 + * + * 参数列表: + * dbid:Oid,要忘记同步请求的数据库标识 + */ void segForgetDatabaseFsyncRequests(Oid dbid) { - FileTag tag; - RelFileNode rnode = {.spcNode = 0, .dbNode = dbid, .relNode = 0, .bucketNode = InvalidBktId, .opt = 0}; + FileTag tag; // 创建文件标签结构体 + RelFileNode rnode = { + .spcNode = 0, .dbNode = dbid, .relNode = 0, .bucketNode = InvalidBktId, .opt = 0}; // 创建文件节点结构体 - tag.rnode = rnode; - tag.handler = SYNC_HANDLER_SEGMENT; - tag.forknum = InvalidForkNumber; - tag.segno = InvalidBlockNumber; + tag.rnode = rnode; // 设置文件标签的节点信息为指定数据库 + tag.handler = SYNC_HANDLER_SEGMENT; // 设置文件标签的处理程序为SEGMENT + tag.forknum = InvalidForkNumber; // 设置文件标签的分支号为无效分支号 + tag.segno = InvalidBlockNumber; // 设置文件标签的分段号为无效分段号 RegisterSyncRequest(&tag, SYNC_FILTER_REQUEST, true /*retry on error */); } - +/* 功能:进行比较,以确定文件标签是否满足特定的条件。 + * 参数列表: + * ftag:一个指向 FileTag 结构的指针,表示要比较的目标 FileTag。 + * candidate:一个指向 FileTag 结构的指针,表示要与目标 ftag 进行比较的 FileTag。 + */ bool seg_filetag_matches(const FileTag *ftag, const FileTag *candidate) { return ftag->rnode.dbNode == candidate->rnode.dbNode; diff --git a/src/gausskernel/storage/smgr/segment/extent_group.cpp b/src/gausskernel/storage/smgr/segment/extent_group.cpp index d674ff8ff..a7817ab3a 100644 --- a/src/gausskernel/storage/smgr/segment/extent_group.cpp +++ b/src/gausskernel/storage/smgr/segment/extent_group.cpp @@ -92,44 +92,76 @@ void eg_init_df_ctrl(SegExtentGroup *seg) seg->segfile = sf; } - +/* + * 功能:获取 SegExtentGroup 中关联的 SegLogicFile 的总块数 + * + * 参数列表: + * seg:SegExtentGroup 指针,表示一个段扩展组 + * + */ BlockNumber eg_df_size(SegExtentGroup *seg) { + // 使用 AutoMutexLock 来对 seg->lock 进行自动加锁,确保线程安全 AutoMutexLock lock(&seg->lock); lock.lock(); + // 获取 seg->segfile,即关联的 SegLogicFile 结构 SegLogicFile *sf = seg->segfile; SegmentCheck(sf != NULL); + // 返回 SegLogicFile 结构的 total_blocks 成员,表示总块数 return sf->total_blocks; } - +/* + * 功能:检查 SegExtentGroup 中是否存在关联的 SegLogicFile + * + * 参数列表: + * seg:SegExtentGroup 指针,表示一个段扩展组 + * + * 返回值:bool,如果存在关联的 SegLogicFile,返回 true;否则返回 false。 + */ bool eg_df_exists(SegExtentGroup *seg) { + // 使用 AutoMutexLock 来对 seg->lock 进行自动加锁,确保线程安全 AutoMutexLock lock(&seg->lock); lock.lock(); SegLogicFile *sf = seg->segfile; SegmentCheck(sf != NULL); + // 返回判断关联的 SegLogicFile 是否存在的结果 return (sf->file_num > 0); } - +/* + * 功能:创建 SegExtentGroup 关联的 SegLogicFile,如果尚未创建 + * + * 参数列表: + * seg:SegExtentGroup 指针,表示一个段扩展组 + */ void eg_create_df(SegExtentGroup *seg) { + // 使用 AutoMutexLock 来对 seg->lock 进行自动加锁,确保线程安全 AutoMutexLock lock(&seg->lock); lock.lock(); SegLogicFile *sf = seg->segfile; SegmentCheck(sf != NULL); + // 如果 sf 中的文件数目为 0,说明尚未创建 SegLogicFile,需要创建 if (sf->file_num == 0) { df_create_file(sf, false); } } - +/* + * 功能:扩展 SegExtentGroup 关联的 SegLogicFile 到指定的文件大小 + * + * 参数列表: + * seg:SegExtentGroup 指针,表示一个段扩展组 + * target_file_size:BlockNumber,目标文件大小(块数) + */ void eg_extend_df(SegExtentGroup *seg, BlockNumber target_file_size) { + // 使用 AutoMutexLock 来对 seg->lock 进行自动加锁,确保线程安全 AutoMutexLock lock(&seg->lock); lock.lock(); @@ -137,49 +169,86 @@ void eg_extend_df(SegExtentGroup *seg, BlockNumber target_file_size) SegLogicFile *sf = seg->segfile; SegmentCheck(sf != NULL); + // 调用 df_extend 函数来扩展 SegLogicFile 到指定的文件大小 df_extend(sf, target_file_size); } - +/* + * 功能:将 SegExtentGroup 关联的 SegLogicFile 缩小到指定的目标大小 + * + * 参数列表: + * seg:SegExtentGroup 指针,表示一个段扩展组 + * target:BlockNumber,目标文件大小(块数),缩小到这个大小 + */ void eg_shrink_df(SegExtentGroup *seg, BlockNumber target) { AutoMutexLock lock(&seg->lock); lock.lock(); - + SegLogicFile *sf = seg->segfile; SegmentCheck(sf != NULL); + // 调用 df_shrink 函数来将 SegLogicFile 缩小到指定的目标大小 df_shrink(sf, target); } /* * Check current data file status; this function is usually called when the space is first accessed. */ +/* + * 功能:获取 SegExtentGroup 关联的 SegLogicFile 的状态 + * + * 参数列表: + * seg:SegExtentGroup 指针,表示一个段扩展组 + * + */ SpaceDataFileStatus eg_status(SegExtentGroup *seg) { + // 调用 eg_df_exists 函数检查 SegLogicFile 是否存在 if (!eg_df_exists(seg)) { + // 调用 eg_df_exists 函数检查 SegLogicFile 是否存在 return SpaceDataFileStatus::EMPTY; } + // 调用 eg_df_valid 函数检查 SegLogicFile 是否有效 if (eg_df_valid(seg)) { + // 调用 eg_df_valid 函数检查 SegLogicFile 是否有效 return SpaceDataFileStatus::NORMAL; } + // 如果 SegLogicFile 既不为空也不有效,返回 CRASHED 状态 return SpaceDataFileStatus::CRASHED; } /* Initialize information in SegExtentGroup structure */ +/* + * 功能:用于初始化 SegExtentGroup 结构体,表示一组关联的数据文件的信息 + * + * 参数列表: + * spc:SegSpace 结构体,表示这组数据文件所属的 SegSpace。 + * seg:SegExtentGroup 结构体,要进行初始化的目标结构体。 + * extent_size:int,每个数据文件的大小。 + * forknum:ForkNumber,与数据文件关联的 fork 类型。 + */ void eg_ctrl_init(SegSpace *spc, SegExtentGroup *seg, int extent_size, ForkNumber forknum) { + // 确保 seg 正确关联到 spc 的 extent_group 数组中的位置 SegmentCheck(&spc->extent_group[EXTENT_SIZE_TO_GROUPID(extent_size)][forknum] == seg); + // 设置 seg 的 extent_size 字段,表示每个数据文件的大小 seg->extent_size = extent_size; + // 设置 seg 的 space 字段,表示这组数据文件所属的 SegSpace 结构体 seg->space = spc; + // 为 seg 的 rnode 字段分配一个 RelFileNode 结构体,用于表示文件节点的相关信息 seg->rnode = {.spcNode = spc->spcNode, .dbNode = spc->dbNode, .relNode = EXTENT_SIZE_TO_TYPE(extent_size), .bucketNode = SegmentBktId, .opt = 0}; + // 设置 seg 的 forknum 字段,表示与数据文件关联的 fork 类型 seg->forknum = forknum; + // 设置 seg 的 map_head_entry 字段的初始值 seg->map_head_entry = DF_MAP_HEAD_PAGE; + // 初始化 seg 的互斥锁,确保对 seg 的并发访问的线程安全性 pthread_mutex_init(&seg->lock, NULL); + // 可能是一个初始化数据文件控制结构的函数,用于进一步初始化与这组数据文件关联的控制信息 eg_init_df_ctrl(seg); } @@ -190,36 +259,61 @@ void eg_init_datafile_head(SegExtentGroup *seg) * if necessary in the future. */ } - +/* + * 功能:用于初始化映射头页的内容,这个映射头页包含了一些关于映射的元信息 + * + * 参数列表: + * map_head_page:Page,映射头页的指针。 + * extent_size:int,每个数据文件的大小。 + */ void eg_init_map_head_page_content(Page map_head_page, int extent_size) { + // 使用 SegPageInit 函数初始化映射头页,设置页大小为 BLCKSZ SegPageInit(map_head_page, BLCKSZ); + // 获取映射头页的内容指针 char *content = PageGetContents(map_head_page); + // 将内容解释为 df_map_head_t 结构体,这是映射头页的结构 df_map_head_t *map_head = (df_map_head_t *)content; - map_head->bit_unit = extent_size; - map_head->group_count = 0; - map_head->free_group = 0; - map_head->allocated_extents = 0; + // 设置映射头页的字段值 + map_head->bit_unit = extent_size; // 每个位图单元的大小,即数据文件的大小 + map_head->group_count = 0; // 组的数量,初始为 0 + map_head->free_group = 0; // 空闲组的数量,初始为 0 + map_head->allocated_extents = 0; // 已分配的扩展数量,初始为 0 + // 获取映射头页的页头,并将 pd_lower 字段增加 df_map_head_t 结构体的大小 PageHeader page_header = (PageHeader)map_head_page; page_header->pd_lower += sizeof(df_map_head_t); } - +/* + * 功能:用于初始化映射头,包括创建映射头页并写入相关信息 + * + * 参数列表: + * seg:SegExtentGroup*,扩展组的指针,包含了映射头的相关信息。 + * rec_ptr:XLogRecPtr,用于设置映射头页的LSN。 + */ static void eg_init_map_head(SegExtentGroup *seg, XLogRecPtr rec_ptr) { + // 设置映射头所在的页号 BlockNumber pageno = DF_MAP_HEAD_PAGE; + // 分配一个新的页并初始化为0 Page page = (Page)palloc(BLCKSZ); errno_t er = memset_s((void *)page, BLCKSZ, 0, BLCKSZ); securec_check(er, "", ""); + // 初始化映射头页的内容 eg_init_map_head_page_content(page, seg->extent_size); + // 设置映射头页的LSN PageSetLSN(page, rec_ptr); + // 计算并设置映射头页的校验和 PageSetChecksumInplace(page, pageno); + // 将映射头页写入扩展组的数据文件中 df_pwrite_block(seg->segfile, (char *)page, pageno); + // 释放分配的内存 pfree(page); + // 更新扩展组中的映射头页信息 seg->map_head_entry = pageno; seg->map_head = NULL; } @@ -228,16 +322,26 @@ void eg_clean_data_files(SegExtentGroup *seg) { df_unlink(seg->segfile); } - +/* + * 功能:初始化扩展组的数据文件 + * + * 参数列表: + * eg:SegExtentGroup*,扩展组的指针,包含了数据文件和映射头的相关信息。 + * redo:bool,表示是否处于重做阶段。 + * rec_ptr:XLogRecPtr,用于设置映射头页的LSN。 + */ void eg_init_data_files(SegExtentGroup *eg, bool redo, XLogRecPtr rec_ptr) { SEGMENTTEST(EXTENT_GROUP_INIT_DATA, (errmsg("EXTENT_GROUP_INIT_DATA %s: the first time for create segment tb!\n", - g_instance.attr.attr_common.PGXCNodeName))); + g_instance.attr.attr_common.PGXCNodeName))); /* create file will ensure the file has enough space */ df_create_file(eg->segfile, redo); - eg_init_datafile_head(eg); // initialize data file - eg_init_map_head(eg, rec_ptr); // initialize map head + // 初始化数据文件头 + eg_init_datafile_head(eg); // initialize data file + // 初始化映射头 + eg_init_map_head(eg, rec_ptr); // initialize map head + // 文件同步 df_fsync(eg->segfile); } @@ -273,33 +377,56 @@ SpaceMapLocation eg_locate_map_by_pageid(SegExtentGroup *seg, BlockNumber page_i return result; } - +/* + * 功能:用于设置位图页中指定位置的位,并更新位图页的相关信息 + * + * 参数列表: + * map_page:df_map_page_t*,位图页的指针。 + * bit_id:uint16,要设置的位的位置。 + */ void eg_set_bitmap_page(df_map_page_t *map_page, uint16 bit_id) { + // 检查位图页中指定位置的位是否已经被设置 SegmentCheck(DF_MAP_FREE(map_page->bitmap, bit_id)); + // 设置位图页中指定位置的位 DF_MAP_SET(map_page->bitmap, bit_id); + // 更新位图页的空闲位数 map_page->free_bits--; + // 如果空闲位的起始位置是指定位置,更新空闲位的起始位置 if (map_page->free_begin == bit_id) { map_page->free_begin++; } + // 如果脏位的位置小于指定位置,更新脏位的位置 if (map_page->dirty_last < bit_id) { map_page->dirty_last = bit_id; } } - +/* + * 功能:初始化位图页的内容 + * + * 参数列表: + * bitmap_page:Page,位图页的指针。 + * first_page:BlockNumber,第一个数据页的块号。 + */ void eg_init_bitmap_page_content(Page bitmap_page, BlockNumber first_page) { + // 初始化位图页的内容,将其全部设置为0 SegPageInit(bitmap_page, BLCKSZ); + // 获取位图页的内容 df_map_page_t *df_map_page = (df_map_page_t *)PageGetContents(bitmap_page); + // 设置位图页的起始位置、脏位位置、空闲位数和第一个数据页的块号 df_map_page->free_begin = 0; df_map_page->dirty_last = 0; df_map_page->free_bits = DF_MAP_BIT_CNT; df_map_page->first_page = first_page; + // 获取位图页的页头信息 PageHeader bitmap_page_header = (PageHeader)bitmap_page; + // 检查位图页的页头上限是否等于BLCKSZ SegmentCheck(bitmap_page_header->pd_upper = BLCKSZ); + // 设置位图页的页头下限等于页头上限,标记位图页已经被初始化 bitmap_page_header->pd_lower = bitmap_page_header->pd_upper; } @@ -356,15 +483,28 @@ BlockNumber eg_alloc_extent_from_map_internal(SegExtentGroup *seg, Buffer map_bu return pagenum; } - +/* + * 功能:从位图中分配一个数据扩展块 + * + * 参数列表: + * seg:SegExtentGroup*,分段空间组的指针。 + * map_page_id:BlockNumber,位图页的块号。 + * target_file_size:BlockNumber*,用于返回目标文件的大小。 + * map_group:uint32,位图组的标识。 + * free_page:uint32,空闲页的标识。 + * + */ BlockNumber eg_alloc_extent_from_map(SegExtentGroup *seg, BlockNumber map_page_id, BlockNumber *target_file_size, - uint32 map_group, uint32 free_page) + uint32 map_group, uint32 free_page) { + // 读取位图页并加锁,以便进行操作 Buffer map_buffer = ReadBufferFast(seg->space, seg->rnode, seg->forknum, map_page_id, RBM_NORMAL); LockBuffer(map_buffer, BUFFER_LOCK_EXCLUSIVE); + // 获取位图页的内容 df_map_page_t *map_page = (df_map_page_t *)PageGetContents(BufferGetPage(map_buffer)); + // 检查位图是否已满,如果已满则释放缓冲区并返回无效块号 if (map_page->free_bits == 0) { SegUnlockReleaseBuffer(map_buffer); return InvalidBlockNumber; @@ -379,6 +519,7 @@ BlockNumber eg_alloc_extent_from_map(SegExtentGroup *seg, BlockNumber map_page_i curr++; } + // 如果没有可用位,则释放缓冲区并返回无效块号 if (curr >= DF_MAP_BIT_CNT) { /* current map does not contain a free bit */ SegUnlockReleaseBuffer(map_buffer); @@ -388,11 +529,20 @@ BlockNumber eg_alloc_extent_from_map(SegExtentGroup *seg, BlockNumber map_page_i /* internal function will release and unlock the MapPage buffer */ return eg_alloc_extent_from_map_internal(seg, map_buffer, curr, target_file_size, map_group, free_page); } - +/* + * 功能:从预分配块中分配一个数据扩展块 + * + * 参数列表: + * seg:SegExtentGroup*,分段空间组的指针。 + * preassigned_block:BlockNumber,预分配的数据块号。 + * target_file_size:BlockNumber*,用于返回目标文件的大小。 + */ bool eg_alloc_preassigned_block(SegExtentGroup *seg, BlockNumber preassigned_block, BlockNumber *target_file_size) { + // 通过数据块号定位位图位置 SpaceMapLocation location = eg_locate_map_by_pageid(seg, preassigned_block, seg->map_head); + // 检查位图位置是否有效,如果无效则返回false if (MapLocationIsInvalid(location)) { return false; } @@ -400,13 +550,16 @@ bool eg_alloc_preassigned_block(SegExtentGroup *seg, BlockNumber preassigned_blo BlockNumber map_blocknum = location.map_id + location.group.first_map; + // 读取位图页并加锁,以便进行操作 Buffer map_buffer = ReadBufferFast(seg->space, seg->rnode, seg->forknum, map_blocknum, RBM_NORMAL); LockBuffer(map_buffer, BUFFER_LOCK_EXCLUSIVE); df_map_page_t *map_page = (df_map_page_t *)PageGetContents(BufferGetPage(map_buffer)); + // 检查位图中的位是否可用,如果可用则继续分配,否则报错 if (DF_MAP_FREE(map_page->bitmap, bit_id)) { BlockNumber pagenum = map_page->first_page + bit_id * seg->map_head->bit_unit; // check whether the allocated block exceeds the file size + // 检查分配的块是否超过文件大小 if ((pagenum + seg->extent_size) > seg->segfile->total_blocks) { *target_file_size = pagenum + seg->extent_size; SegUnlockReleaseBuffer(map_buffer); @@ -415,52 +568,83 @@ bool eg_alloc_preassigned_block(SegExtentGroup *seg, BlockNumber preassigned_blo } else { ereport(PANIC, (errmsg("The preassigned segment block %u is already in use", preassigned_block))); } + // 获取当前位图组和页号 uint32 free_group = seg->map_head->free_group; uint32 free_page = seg->map_head->groups[free_group].free_page; SegmentCheck(free_group < seg->map_head->group_count && free_page < seg->map_head->groups[free_group].page_count); + // 调用内部函数进行分配,该函数会释放和解锁MapPage缓冲区 (void)eg_alloc_extent_from_map_internal(seg, map_buffer, bit_id, target_file_size, free_group, free_page); return true; } - +/* + * 功能:初始化位图页 + * + * 参数列表: + * seg:SegExtentGroup*,分段空间组的指针。 + * pageno:BlockNumber,位图页号。 + * first_page:BlockNumber,位图对应的数据文件中的起始页号。 + */ void eg_init_bitmap_page(SegExtentGroup *seg, BlockNumber pageno, BlockNumber first_page) { + // 读取位图页并加锁,以便进行操作 Buffer buffer = ReadBufferFast(seg->space, seg->rnode, seg->forknum, pageno, RBM_NORMAL); LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE); + // 开始事务 START_CRIT_SECTION(); + // 标记缓冲区为脏页,表示需要写回磁盘 SegMarkBufferDirty(buffer); + // 调用函数初始化位图页的内容 eg_init_bitmap_page_content(BufferGetPage(buffer), first_page); /* XLog Issue */ + // 插入XLog记录 XLogBeginInsert(); XLogRegisterData((char *)&first_page, sizeof(BlockNumber)); XLogRegisterBuffer(0, buffer, REGBUF_WILL_INIT); XLogRecPtr rec_ptr = XLogInsert(RM_SEGPAGE_ID, XLOG_SEG_INIT_MAPPAGE, SegmentBktId); + // 设置页LSN PageSetLSN(BufferGetPage(buffer), rec_ptr); + // 设置页LSN END_CRIT_SECTION(); + // 解锁并释放缓冲区 SegUnlockReleaseBuffer(buffer); } - +/* + * 功能:初始化逆向指针页 + * + * 参数列表: + * seg:SegExtentGroup*,分段空间组的指针。 + * pageno:BlockNumber,逆向指针页号。 + */ void eg_init_invrsptr_page(SegExtentGroup *seg, BlockNumber pageno) { + // 读取逆向指针页并清零内容,同时加锁 Buffer buffer = ReadBufferFast(seg->space, seg->rnode, seg->forknum, pageno, RBM_ZERO_AND_LOCK); Page page = BufferGetPage(buffer); + // 开始事务 START_CRIT_SECTION(); + // 标记缓冲区为脏页,表示需要写回磁盘 SegMarkBufferDirty(buffer); + // 初始化页内容,将其清零 SegPageInit(page, BLCKSZ); + // 插入XLog记录 XLogBeginInsert(); XLogRegisterBuffer(0, buffer, REGBUF_WILL_INIT); XLogRecPtr rec_ptr = XLogInsert(RM_SEGPAGE_ID, XLOG_SEG_INIT_INVRSPTR_PAGE, SegmentBktId); + // 设置页LSN PageSetLSN(page, rec_ptr); + // 结束事务 END_CRIT_SECTION(); + // 解锁并释放缓冲区 SegUnlockReleaseBuffer(buffer); } @@ -473,24 +657,37 @@ BlockNumber eg_next_group_start(df_map_head_t *map_head) return last_map_group.first_map + last_map_group.page_count + IPBLOCK_GROUP_SIZE + DF_MAP_GROUP_EXTENTS * (uint32)map_head->bit_unit; } - +/* + * 功能:向分段空间组添加新的映射组 + * + * 参数列表: + * seg:SegExtentGroup*,分段空间组的指针。 + * pageno:BlockNumber,新映射组的起始页号。 + * group_size:uint8,映射组的大小。 + * old_group_count:int,之前的映射组数量。 + */ void eg_add_map_group(SegExtentGroup *seg, BlockNumber pageno, uint8 group_size, int old_group_count) { + // 打印日志,记录添加新映射组的信息 ereport(LOG, (errmsg("Extent group (%u, %u, %u) add new group #%d, start pageno: %u ", seg->rnode.spcNode, seg->rnode.dbNode, seg->rnode.relNode, old_group_count, pageno))); + // 获取映射头缓冲区 Buffer map_head_buffer = seg->map_head_buffer; Page page = BufferGetPage(map_head_buffer); df_map_head_t *map_head = (df_map_head_t *)PageGetContents(page); + // 如果旧的映射组数量小于当前的数量,说明其他操作已经扩展了映射组,直接返回 if (old_group_count < map_head->group_count) { /* Some one else extend the extent group for us */ return; } + // 检查映射组数量是否匹配 SegmentCheck(old_group_count == map_head->group_count); SegmentCheck(group_size == DF_MAP_GROUP_SIZE); BlockNumber start_map_no = pageno; BlockNumber data_start = pageno + group_size + IPBLOCK_GROUP_SIZE; + // 初始化位图页和逆向指针页 for (uint32 i = 0; i < group_size; i++) { eg_init_bitmap_page(seg, pageno, data_start); data_start += DF_MAP_BIT_CNT * seg->extent_size; @@ -501,10 +698,12 @@ void eg_add_map_group(SegExtentGroup *seg, BlockNumber pageno, uint8 group_size, eg_init_invrsptr_page(seg, pageno); pageno++; } + // 打印测试信息 SEGMENTTEST(EXTENT_GROUP_ADD_NEW_GROUP, (errmsg("EXTENT_GROUP_ADD_NEW_GROUP %s: add new group success!\n", - g_instance.attr.attr_common.PGXCNodeName))); + g_instance.attr.attr_common.PGXCNodeName))); /* XLog issue */ { + // 开始事务 START_CRIT_SECTION(); /* Update map head */ df_map_group_t *bitmap_group = &map_head->groups[map_head->group_count]; @@ -513,8 +712,10 @@ void eg_add_map_group(SegExtentGroup *seg, BlockNumber pageno, uint8 group_size, bitmap_group->page_count = group_size; bitmap_group->free_page = 0; + // 标记映射头缓冲区为脏页,表示需要写回磁盘 SegMarkBufferDirty(map_head_buffer); + // 创建XLog记录 xl_new_map_group_info_t new_map_group_info = {.first_map_pageno = start_map_no, .extent_size = seg->extent_size, .group_count = map_head->group_count, @@ -525,11 +726,15 @@ void eg_add_map_group(SegExtentGroup *seg, BlockNumber pageno, uint8 group_size, XLogRegisterBuffer(0, map_head_buffer, REGBUF_STANDARD); XLogRecPtr recptr = XLogInsert(RM_SEGPAGE_ID, XLOG_SEG_ADD_NEW_GROUP, SegmentBktId); + // 设置页LSN PageSetLSN(page, recptr); + // 结束事务 END_CRIT_SECTION(); } - SEGMENTTEST(EXTENT_GROUP_ADD_NEW_GROUP_XLOG, (errmsg("EXTENT_GROUP_ADD_NEW_GROUP_XLOG %s: add new group xlog success!\n", - g_instance.attr.attr_common.PGXCNodeName))); + // 打印测试信息 + SEGMENTTEST(EXTENT_GROUP_ADD_NEW_GROUP_XLOG, + (errmsg("EXTENT_GROUP_ADD_NEW_GROUP_XLOG %s: add new group xlog success!\n", + g_instance.attr.attr_common.PGXCNodeName))); } /* @@ -551,8 +756,7 @@ BlockNumber eg_try_alloc_extent(SegExtentGroup *seg, BlockNumber preassigned_blo df_map_head_t *map_head = seg->map_head; seg->map_head = map_head; - SegmentCheck(map_head->group_count > 0 && - map_head->free_group < map_head->group_count); + SegmentCheck(map_head->group_count > 0 && map_head->free_group < map_head->group_count); /* preassigned block */ if (BlockNumberIsValid(preassigned_block)) { @@ -592,31 +796,48 @@ BlockNumber eg_try_alloc_extent(SegExtentGroup *seg, BlockNumber preassigned_blo } return InvalidBlockNumber; } - +/* + * 功能:检查分段空间组是否为空 + * + * 参数列表: + * seg:SegExtentGroup*,分段空间组的指针。 + */ bool eg_empty(SegExtentGroup *seg) { + // 如果分段数据文件不存在,认为分段空间组为空 if (!eg_df_exists(seg)) { return true; } int result; + // 读取映射头缓冲区并共享锁定 Buffer buffer = eg_read_maphead_buffer(seg); LockBuffer(buffer, BUFFER_LOCK_SHARE); + // 获取映射头信息 seg->map_head = (df_map_head_t *)PageGetContents(BufferGetPage(buffer)); result = seg->map_head->allocated_extents; + // 释放映射头缓冲区的锁定 SegUnlockReleaseBuffer(buffer); #ifdef USE_ASSERT_CHECKING + // 使用断言检查,如果有已分配的分段,则打印日志记录 if (result != 0) { ereport(LOG, (errmsg("Extent group %d is not empty, there are %d extents used", seg->extent_size, result))); } #endif + // 返回结果,如果已分配的分段数为0,则认为分段空间组为空 return result == 0; } - +/* + * 功能:获取分段空间组的存储统计信息 + * + * 参数列表: + * seg:SegExtentGroup*,分段空间组的指针。 + */ SegmentSpaceStat eg_storage_stat(SegExtentGroup *seg) { + // 如果分段数据文件不存在,返回默认的存储统计信息 if (!eg_df_exists(seg)) { return {.extent_size = (uint32)seg->extent_size, .forknum = seg->forknum, @@ -626,9 +847,11 @@ SegmentSpaceStat eg_storage_stat(SegExtentGroup *seg) .utilization = 0, .high_water_mark = 0}; } + // 读取映射头缓冲区并共享锁定 Buffer buffer = eg_read_maphead_buffer(seg); LockBuffer(buffer, BUFFER_LOCK_SHARE); + // 获取映射头信息 df_map_head_t *map_head = (df_map_head_t *)PageGetContents(BufferGetPage(buffer)); BlockNumber total_blocks = seg->segfile->total_blocks; @@ -651,9 +874,20 @@ SegmentSpaceStat eg_storage_stat(SegExtentGroup *seg) SegUnlockReleaseBuffer(buffer); return result; } - +/* + * 功能:获取分段空间组中所有的逆指针和数据块号 + * + * 参数列表: + * seg:SegExtentGroup*,分段空间组的指针。 + * cnt:uint32*,返回找到的逆指针和数据块号的数量。 + * iptrs:ExtentInversePointer**,返回逆指针数组的指针。 + * extents:BlockNumber**,返回数据块号数组的指针。 + * + * 注意:调用者需要在不再需要逆指针和数据块号时释放它们的内存。 + */ void GetAllInversePointer(SegExtentGroup *seg, uint32 *cnt, ExtentInversePointer **iptrs, BlockNumber **extents) { + // 如果分段数据文件不存在,返回空结果 if (!eg_df_exists(seg)) { *cnt = 0; *iptrs = NULL; @@ -665,9 +899,11 @@ void GetAllInversePointer(SegExtentGroup *seg, uint32 *cnt, ExtentInversePointer *extents = (BlockNumber *)palloc(sizeof(BlockNumber) * len); *cnt = 0; + // 获取映射头信息 Buffer buffer = eg_read_maphead_buffer(seg); LockBuffer(buffer, BUFFER_LOCK_SHARE); + // 获取映射头信息 df_map_head_t *map_head = (df_map_head_t *)PageGetContents(BufferGetPage(buffer)); /* copy meta-data from map head, to avoid locking it for long time. */ @@ -676,6 +912,7 @@ void GetAllInversePointer(SegExtentGroup *seg, uint32 *cnt, ExtentInversePointer for (int i = 0; i < group_count; i++) { groups[i] = map_head->groups[i]; } + // 释放映射头缓冲区的锁定 SegUnlockReleaseBuffer(buffer); Buffer ipbuf = InvalidBuffer; @@ -715,40 +952,53 @@ void GetAllInversePointer(SegExtentGroup *seg, uint32 *cnt, ExtentInversePointer SegUnlockReleaseBuffer(map_buffer); } } + // 如果逆指针缓冲区有效,则释放它 if (BufferIsValid(ipbuf)) { SegReleaseBuffer(ipbuf); } } - +/* + * 功能:用于检查分段空间组是否存在,如果不存在则创建之。创建分段空间组时,还会进行一些附加的操作 + * + * 参数列表: + * seg:SegExtentGroup*,分段空间组的指针 + */ void eg_create_if_necessary(SegExtentGroup *seg) { + // 如果分段数据文件不存在 if (!eg_df_exists(seg)) { AutoMutexLock lock(&seg->lock); lock.lock(); + // 检查是否已经有其他进程创建了分段数据文件 if (seg->segfile->file_num > 0) { /* Someone else has created it */ return; } - TablespaceCreateDbspace(seg->rnode.spcNode, seg->rnode.dbNode, false); + // 创建表空间和检查表空间限额 + TablespaceCreateDbspace(seg->rnode.spcNode, seg->rnode.dbNode, false); /* Ensure tablespace limits at first. */ uint64 requestSize = DF_FILE_EXTEND_STEP_SIZE; TableSpaceUsageManager::IsExceedMaxsize(seg->rnode.spcNode, requestSize, true); + // 开始临界区 START_CRIT_SECTION(); t_thrd.pgxact->delayChkpt = true; + // 写入XLog记录,标记分段空间组的创建 XLogBeginInsert(); XLogRegisterData((char *)&seg->rnode, sizeof(RelFileNode)); XLogRegisterData((char *)&seg->forknum, sizeof(ForkNumber)); XLogRecPtr xlog = XLogInsert(RM_SEGPAGE_ID, XLOG_SEG_CREATE_EXTENT_GROUP, SegmentBktId); XLogWaitFlush(xlog); + // 初始化数据文件和映射头 eg_init_data_files(seg, false, xlog); SEGMENTTEST(EXTENT_GROUP_CREATE_EXTENT, (errmsg("EXTENT_GROUP_CREATE_EXTENT %s: create segment file success!\n", - g_instance.attr.attr_common.PGXCNodeName))); + g_instance.attr.attr_common.PGXCNodeName))); t_thrd.pgxact->delayChkpt = false; + // 结束临界区 END_CRIT_SECTION(); } } @@ -756,8 +1006,17 @@ void eg_create_if_necessary(SegExtentGroup *seg) /* * Extent Group Layer APIS: allocate/free extents; auto extend if the space is used up. */ +/* + * 功能:分配一个分段空间组的扩展 + * + * 参数列表: + * seg:SegExtentGroup*,分段空间组的指针 + * preassigned_block:BlockNumber,预分配的块号 + * iptr:ExtentInversePointer,扩展块的逆指针 + */ BlockNumber eg_alloc_extent(SegExtentGroup *seg, BlockNumber preassigned_block, ExtentInversePointer iptr) { + // 如果需要的话,创建分段空间组 eg_create_if_necessary(seg); BlockNumber blocknum = InvalidBlockNumber; @@ -766,6 +1025,7 @@ BlockNumber eg_alloc_extent(SegExtentGroup *seg, BlockNumber preassigned_block, seg->map_head_buffer = eg_read_maphead_buffer(seg); LockBuffer(seg->map_head_buffer, BUFFER_LOCK_EXCLUSIVE); seg->map_head = (df_map_head_t *)PageGetContents(BufferGetBlock(seg->map_head_buffer)); + // 如果映射组数量为0,创建第一个映射组 if (seg->map_head->group_count == 0) { // create the first map group eg_add_map_group(seg, DF_MAP_HEAD_PAGE + 1, DF_MAP_GROUP_SIZE, 0); @@ -776,8 +1036,9 @@ BlockNumber eg_alloc_extent(SegExtentGroup *seg, BlockNumber preassigned_block, // Get an extent, we do not have to release the map head buffer. XLogAtomicOpCommit will do it for us. // set inverse point and return SetInversePointer(seg, blocknum, iptr); - SEGMENTTEST(EXTENT_GROUP_CRITICAL_SECTION, (errmsg("EXTENT_GROUP_CRITICAL_SECTION %s: alloc extent end, begin critical section!\n", - g_instance.attr.attr_common.PGXCNodeName))); + SEGMENTTEST(EXTENT_GROUP_CRITICAL_SECTION, + (errmsg("EXTENT_GROUP_CRITICAL_SECTION %s: alloc extent end, begin critical section!\n", + g_instance.attr.attr_common.PGXCNodeName))); return blocknum; } @@ -797,12 +1058,21 @@ BlockNumber eg_alloc_extent(SegExtentGroup *seg, BlockNumber preassigned_block, /* * Free extent should be reentrant */ +/* + * 功能:用于取消位图页中的指定位设置,将其标记为未使用。 + * + * 参数列表: + * map_page:df_map_page_t*,位图页的指针 + * bit_id:uint16,要取消设置的位的ID + */ void eg_unset_bitmap_page(df_map_page_t *map_page, uint16 bit_id) { SegmentCheck(DF_MAP_NOT_FREE(map_page->bitmap, bit_id)); + // 取消设置位 DF_MAP_UNSET(map_page->bitmap, bit_id); map_page->free_bits++; + // 更新空闲位的起始位置 if (map_page->free_begin > bit_id) { map_page->free_begin = bit_id; } @@ -811,12 +1081,21 @@ void eg_unset_bitmap_page(df_map_page_t *map_page, uint16 bit_id) /* * Free an extent in the extent group. 'allocated_extents' will be updated. */ +/* + * 功能:释放分配的扩展块 + * + * 参数列表: + * seg:SegExtentGroup*,扩展组的指针 + * blocknum:BlockNumber,要释放的块的编号 + */ void eg_free_extent(SegExtentGroup *seg, BlockNumber blocknum) { + // 读取映射头缓冲区 seg->map_head_buffer = ReadBufferFast(seg->space, seg->rnode, seg->forknum, seg->map_head_entry, RBM_NORMAL); LockBuffer(seg->map_head_buffer, BUFFER_LOCK_EXCLUSIVE); df_map_head_t *map_head = (df_map_head_t *)PageGetContents(BufferGetPage(seg->map_head_buffer)); + // 定位要释放的块的位置 SpaceMapLocation location = eg_locate_map_by_pageid(seg, blocknum, map_head); if (MapLocationIsInvalid(location)) { ereport(PANIC, (errmsg("Free an already freed extent"))); @@ -828,6 +1107,7 @@ void eg_free_extent(SegExtentGroup *seg, BlockNumber blocknum) LockBuffer(map_buffer, BUFFER_LOCK_EXCLUSIVE); df_map_page_t *map_page = (df_map_page_t *)PageGetContents(BufferGetPage(map_buffer)); + // 取消位图页中的位设置,将其标记为未使用 eg_unset_bitmap_page(map_page, bit_id); map_head->allocated_extents--; diff --git a/src/gausskernel/storage/smgr/segment/inverse_ptr.cpp b/src/gausskernel/storage/smgr/segment/inverse_ptr.cpp index 4f68b5806..40dd5b089 100644 --- a/src/gausskernel/storage/smgr/segment/inverse_ptr.cpp +++ b/src/gausskernel/storage/smgr/segment/inverse_ptr.cpp @@ -32,7 +32,13 @@ static const char *ExtentUsageName[] = {"Not used", "Non-bucket table segment he /* must be last one */ "Invalid Usage Type"}; - +/* + * 功能:获取扩展块的InversePointer块位置 + * + * 参数列表: + * extent:BlockNumber,扩展块的编号 + * extent_size:uint32,扩展块的大小 + */ IpBlockLocation GetIpBlock(BlockNumber extent, uint32 extent_size) { /* Exclude MapHead */ @@ -43,7 +49,7 @@ IpBlockLocation GetIpBlock(BlockNumber extent, uint32 extent_size) uint32 group_id = extent / group_total_blocks; /* - * Group inverse pointer start, plus + * Group inverse pointer start, plus * 1. DF_MAP_HEAD_PAGE * 2. previous groups * 3. Map pages in this group @@ -56,16 +62,20 @@ IpBlockLocation GetIpBlock(BlockNumber extent, uint32 extent_size) SegmentCheck(extent_size > 0); uint32 extent_id = group_offset / extent_size; - IpBlockLocation result = { - .ipblock = group_ip_start + extent_id / EXTENTS_PER_IPBLOCK, - .offset = extent_id % EXTENTS_PER_IPBLOCK - }; + IpBlockLocation result = {.ipblock = group_ip_start + extent_id / EXTENTS_PER_IPBLOCK, + .offset = extent_id % EXTENTS_PER_IPBLOCK}; return result; } - +/* + * 功能:获取扩展块使用情况的名称 + * + * 参数列表: + * iptr:ExtentInversePointer,InversePointer块的指针 + */ const char *GetExtentUsageName(ExtentInversePointer iptr) { int usage = SPC_INVRSPTR_GET_USAGE(iptr); + // 如果使用情况大于或等于INVALID_EXTNT_USAGE,将其设置为INVALID_EXTNT_USAGE if (usage >= INVALID_EXTNT_USAGE) { usage = INVALID_EXTNT_USAGE; } @@ -74,32 +84,58 @@ const char *GetExtentUsageName(ExtentInversePointer iptr) } /* Inverse pointer read buffer */ +/* + * 功能:用于读取扩展组的InversePointer块的缓冲区,并可以选择是否在需要时进行扩展。 + * + * 参数列表: + * seg:SegExtentGroup指针,表示扩展组的信息 + * blocknum:BlockNumber,表示要读取的块号 + * extend:bool,表示是否在需要时进行扩展 + */ Buffer ip_readbuf(SegExtentGroup *seg, BlockNumber blocknum, bool extend) { #ifdef USE_ASSERT_CHECKING + // 检查块号是否小于数据文件的大小 BlockNumber df_size = eg_df_size(seg); SegmentCheck(blocknum < df_size); #endif + // 读取块的缓冲区 Buffer buf = ReadBufferFast(seg->space, seg->rnode, seg->forknum, blocknum, RBM_NORMAL); + // 如果页面是新的,则进行初始化 if (PageIsNew(BufferGetPage(buf))) { SegPageInit(BufferGetPage(buf), BLCKSZ); } return buf; } - +/* + * 功能:用于设置指定块号的InversePointer。它会读取InversePointer块的缓冲区, + * 将指定的InversePointer存储在合适的位置,并进行XLog记录以确保持久性。 + * + * 参数列表: + * seg:SegExtentGroup指针,表示扩展组的信息 + * extent:BlockNumber,表示要设置InversePointer的块号 + * iptr:ExtentInversePointer,表示要设置的InversePointer值 + * + * 注意:函数内部会使用GetIpBlock和ip_readbuf函数来定位和读取InversePointer块 + */ void SetInversePointer(SegExtentGroup *seg, BlockNumber extent, ExtentInversePointer iptr) { + // 使用GetIpBlock函数获取InversePointer块的位置 IpBlockLocation loc = GetIpBlock(extent, seg->extent_size); + // 使用ip_readbuf函数读取InversePointer块的缓冲区,并可以选择在需要时进行扩展 Buffer ipBuffer = ip_readbuf(seg, loc.ipblock, true); SegmentCheck(BufferIsValid(ipBuffer)); + // 锁定缓冲区以进行写操作 LockBuffer(ipBuffer, BUFFER_LOCK_EXCLUSIVE); + // 获取InversePointer块的内容并设置指定位置的值 ExtentInversePointer *eips = (ExtentInversePointer *)PageGetContents(BufferGetBlock(ipBuffer)); eips[loc.offset] = iptr; + // 注册XLog记录以确保持久性 XLogAtomicOpRegisterBuffer(ipBuffer, REGBUF_KEEP_DATA, SPCXLOG_SET_INVERSE_POINTER, XLOG_COMMIT_UNLOCK_RELEASE_BUFFER); XLogAtomicOpRegisterBufData((char *)&loc.offset, sizeof(uint32)); @@ -114,9 +150,26 @@ void SetInversePointer(SegExtentGroup *seg, BlockNumber extent, ExtentInversePoi * is a valid buffer containing the inverse pointer of the given extent. The caller is responsible * to release the buf after it has done scanning. */ +/* + * 功能:用于获取指定块号的InversePointer。它会根据传入的缓冲区指针(如果存在) + * 或者创建新的缓冲区来读取InversePointer,并返回所需的InversePointer值。 + * + * 参数列表: + * seg:SegExtentGroup指针,表示扩展组的信息 + * extent:BlockNumber,表示要获取InversePointer的块号 + * buf:Buffer指针的指针,用于传递或接收InversePointer块的缓冲区 + * 如果传入的缓冲区无效,函数会创建一个新的缓冲区 + * + * 注意: + * - 如果传入的缓冲区不为空且缓冲区与块号不匹配,旧缓冲区会被释放并置为无效。 + * - 函数内部会使用GetIpBlock和ip_readbuf函数来定位和读取InversePointer块。 + * - 函数会确保返回的InversePointer值有效。 + */ ExtentInversePointer GetInversePointer(SegExtentGroup *seg, BlockNumber extent, Buffer *buf) { + // 检查传入的缓冲区指针是否为空 SegmentCheck(buf != NULL); + // 使用GetIpBlock函数获取InversePointer块的位置 IpBlockLocation loc = GetIpBlock(extent, seg->extent_size); if (BufferIsValid(*buf)) { @@ -129,16 +182,21 @@ ExtentInversePointer GetInversePointer(SegExtentGroup *seg, BlockNumber extent, } } + // 如果传入的缓冲区无效,创建一个新的缓冲区并读取InversePointer块 if (BufferIsInvalid(*buf)) { *buf = ip_readbuf(seg, loc.ipblock, false); } + // 检查缓冲区是否有效 SegmentCheck(BufferIsValid(*buf)); + // 锁定缓冲区以进行读取操作,并获取InversePointer值 LockBuffer(*buf, BUFFER_LOCK_SHARE); ExtentInversePointer *eips = (ExtentInversePointer *)PageGetContents(BufferGetBlock(*buf)); ExtentInversePointer res = eips[loc.offset]; + // 解锁缓冲区 LockBuffer(*buf, BUFFER_LOCK_UNLOCK); + // 确保返回的InversePointer值有效 SegmentCheck(InversePointerIsValid(res)); return res; diff --git a/src/gausskernel/storage/smgr/segment/segbuffer.cpp b/src/gausskernel/storage/smgr/segment/segbuffer.cpp index df84a6ba0..027c1a0f4 100644 --- a/src/gausskernel/storage/smgr/segment/segbuffer.cpp +++ b/src/gausskernel/storage/smgr/segment/segbuffer.cpp @@ -34,7 +34,7 @@ #include "tsan_annotation.h" #include "pgstat.h" -/* +/* * Segment buffer, used for segment meta data, e.g., segment head, space map head. We separate segment * meta data buffer and normal data buffer (in bufmgr.cpp) to avoid potential dead locks. */ @@ -54,89 +54,142 @@ static void SegTerminateBufferIO(BufferDesc *buf, bool clear_dirty, uint32 set_f extern PrivateRefCountEntry *GetPrivateRefCountEntry(Buffer buffer, bool create, bool do_move); extern void ForgetPrivateRefCountEntry(PrivateRefCountEntry *ref); - +/* + * 功能:中止分段缓冲区IO操作 + */ void AbortSegBufferIO(void) { + // 检查是否存在正在进行的IO操作 if (InProgressBuf != NULL) { + // 获取正在进行IO操作的锁 LWLockAcquire(InProgressBuf->io_in_progress_lock, LW_EXCLUSIVE); + // 调用SegTerminateBufferIO函数终止IO操作,将IO错误标志设置为BM_IO_ERROR SegTerminateBufferIO(InProgressBuf, false, BM_IO_ERROR); } } - +/* + * 功能:等待缓冲区的IO操作完成 + * + * 参数列表: + * buf:BufferDesc 结构,表示待等待IO操作完成的缓冲区 + * + * 注意:该函数不会主动终止缓冲区的IO操作,仅用于等待IO操作完成。 + */ static void WaitIO(BufferDesc *buf) { while (true) { uint32 buf_state; + // 获取缓冲区的状态 buf_state = LockBufHdr(buf); + // 释放对缓冲区状态的锁定 UnlockBufHdr(buf, buf_state); + // 检查缓冲区是否仍在进行IO操作 if (!(buf_state & BM_IO_IN_PROGRESS)) { + // 如果IO操作已完成,则退出循环 break; } + // 获取IO操作的锁以等待IO完成 LWLockAcquire(buf->io_in_progress_lock, LW_SHARED); + // 释放IO操作的锁 LWLockRelease(buf->io_in_progress_lock); } } - +/* + * 功能:开始缓冲区的IO操作 + * + * 参数列表: + * buf:BufferDesc 结构,表示待进行IO操作的缓冲区 + * forInput:bool 值,表示是否是输入操作(读取) + * + * 注意:函数调用者应该在适当的时候释放IO操作锁和修改缓冲区状态。 + */ static bool SegStartBufferIO(BufferDesc *buf, bool forInput) { uint32 buf_state; + // 检查全局变量,确保没有其他缓冲区的IO操作正在进行 SegmentCheck(!InProgressBuf); while (true) { + // 获取缓冲区的IO操作锁(独占模式) LWLockAcquire(buf->io_in_progress_lock, LW_EXCLUSIVE); + // 获取缓冲区的状态 buf_state = LockBufHdr(buf); if (!(buf_state & BM_IO_IN_PROGRESS)) { + // 如果缓冲区的IO操作尚未开始,则退出循环 break; } + // 释放对缓冲区状态的锁定 UnlockBufHdr(buf, buf_state); + // 释放IO操作锁 LWLockRelease(buf->io_in_progress_lock); + // 等待其他IO操作完成 WaitIO(buf); } if (forInput ? (buf_state & BM_VALID) : !(buf_state & BM_DIRTY)) { /* IO finished */ UnlockBufHdr(buf, buf_state); + // 释放IO操作锁 LWLockRelease(buf->io_in_progress_lock); return false; } + // 设置缓冲区的状态标志,表示IO操作已开始 buf_state |= BM_IO_IN_PROGRESS; UnlockBufHdr(buf, buf_state); + // 设置全局变量,指向正在进行IO操作的缓冲区 InProgressBuf = buf; isForInput = forInput; return true; } - +/* + * 功能:终止缓冲区的IO操作 + * + * 参数列表: + * buf:BufferDesc 结构,表示待终止IO操作的缓冲区 + * clear_dirty:bool 值,表示是否清除脏标志 + * set_flag_bits:uint32 值,表示要设置的标志位 + * + * 注意:函数调用者应该在适当的时候释放IO操作锁和修改缓冲区状态。 + */ static void SegTerminateBufferIO(BufferDesc *buf, bool clear_dirty, uint32 set_flag_bits) { + // 检查传入的缓冲区是否与全局变量 InProgressBuf 匹配 SegmentCheck(buf == InProgressBuf); + // 获取缓冲区的状态 uint32 buf_state = LockBufHdr(buf); + // 检查缓冲区的IO操作是否已开始 SegmentCheck(buf_state & BM_IO_IN_PROGRESS); + // 清除 BM_IO_IN_PROGRESS 和 BM_IO_ERROR 标志位 buf_state &= ~(BM_IO_IN_PROGRESS | BM_IO_ERROR); if (clear_dirty) { if (ENABLE_INCRE_CKPT) { + // 检查是否需要从脏页队列中移除该缓冲区 if (!XLogRecPtrIsInvalid(pg_atomic_read_u64(&buf->rec_lsn))) { remove_dirty_page_from_queue(buf); } else { + // 如果缓冲区脏标志被设置但不在脏页队列中,抛出 PANIC 错误 ereport(PANIC, (errmodule(MOD_INCRE_CKPT), errcode(ERRCODE_INVALID_BUFFER), (errmsg("buffer is dirty but not in dirty page queue in TerminateBufferIO_common")))); } + // 检查 BM_JUST_DIRTIED 标志位,如果设置了,表示不需要检查点 if ((buf_state & BM_JUST_DIRTIED)) { buf_state &= ~BM_CHECKPOINT_NEEDED; + // 将缓冲区添加到待刷新队列中 if (!push_pending_flush_queue(BufferDescriptorGetBuffer(buf))) { ereport(PANIC, (errmodule(MOD_INCRE_CKPT), errcode(ERRCODE_INVALID_BUFFER), (errmsg("TerminateBufferIO_common, dirty page queue is full when trying to " @@ -145,51 +198,76 @@ static void SegTerminateBufferIO(BufferDesc *buf, bool clear_dirty, uint32 set_f } } + // 如果 BM_JUST_DIRTIED 没有设置,清除 BM_DIRTY 和 BM_CHECKPOINT_NEEDED 标志位 if (!(buf_state & BM_JUST_DIRTIED)) { buf_state &= ~(BM_DIRTY | BM_CHECKPOINT_NEEDED); } } + // 设置指定的标志位 buf_state |= set_flag_bits; + // 释放缓冲区状态锁 UnlockBufHdr(buf, buf_state); + // 清除全局变量 InProgressBuf,表示IO操作已结束 InProgressBuf = NULL; + // 释放IO操作锁 LWLockRelease(buf->io_in_progress_lock); } - +/* + * 功能:用于等待缓冲区头部解锁 + * + * 参数列表: + * buf:BufferDesc 结构,表示待等待的缓冲区头部 + */ static uint32 SegWaitBufHdrUnlocked(BufferDesc *buf) { #ifndef ENABLE_THREAD_CHECK + // 初始化自旋等待状态,用于记录自旋等待的进度 SpinDelayStatus delayStatus = init_spin_delay(buf); #endif uint32 buf_state; + // 读取缓冲区头部的状态 buf_state = pg_atomic_read_u32(&buf->state); + // 循环检查缓冲区头部的状态,直到解锁 while (buf_state & BM_LOCKED) { #ifndef ENABLE_THREAD_CHECK + // 执行自旋等待 perform_spin_delay(&delayStatus); #endif + // 重新读取缓冲区头部的状态 buf_state = pg_atomic_read_u32(&buf->state); } #ifndef ENABLE_THREAD_CHECK + // 结束自旋等待 finish_spin_delay(&delayStatus); #endif /* ENABLE_THREAD_CHECK only, acqurie semantic */ + // 使用 TsAnnotateHappensAfter 注释确保获取语义 TsAnnotateHappensAfter(&buf->state); + // 返回最终的缓冲区头部状态 return buf_state; } - +/* + * 功能:增加缓冲区的引用计数 + * + * 参数列表: + * buf:BufferDesc 结构,表示待增加引用计数的缓冲区 + */ bool SegPinBuffer(BufferDesc *buf) { + // 打印调试信息 ereport(DEBUG5, (errmodule(MOD_SEGMENT_PAGE), errmsg("[SegPinBuffer] (%u %u %u %d) %d %u ", buf->tag.rnode.spcNode, buf->tag.rnode.dbNode, buf->tag.rnode.relNode, buf->tag.rnode.bucketNode, buf->tag.forkNum, buf->tag.blockNum))); bool result; - PrivateRefCountEntry * ref = GetPrivateRefCountEntry(BufferDescriptorGetBuffer(buf), true, true); + // 获取缓冲区的私有引用计数项 + PrivateRefCountEntry *ref = GetPrivateRefCountEntry(BufferDescriptorGetBuffer(buf), true, true); SegmentCheck(ref != NULL); if (ref->refcount == 0) { @@ -198,114 +276,194 @@ bool SegPinBuffer(BufferDesc *buf) for (;;) { if (old_buf_state & BM_LOCKED) { + // 如果缓冲区已被锁定,等待直到锁定被释放 old_buf_state = SegWaitBufHdrUnlocked(buf); } buf_state = old_buf_state; + // 增加引用计数 buf_state += BUF_REFCOUNT_ONE; + // 使用原子操作比较并交换来设置新的缓冲区状态 if (pg_atomic_compare_exchange_u32(&buf->state, &old_buf_state, buf_state)) { + // 设置结果为缓冲区是否有效 result = old_buf_state & BM_VALID; break; } } } else { + // 如果引用计数不为零,返回 true result = true; } + // 增加私有引用计数项的引用计数 ref->refcount++; + // 扩展当前资源所有者的缓冲区列表并记录缓冲区 ResourceOwnerEnlargeBuffers(t_thrd.utils_cxt.CurrentResourceOwner); ResourceOwnerRememberBuffer(t_thrd.utils_cxt.CurrentResourceOwner, BufferDescriptorGetBuffer(buf)); return result; } - +/* + * 功能:锁定并引用缓冲区,这是一个重要的操作,用于 + * 保证在访问缓冲区时的线程安全性。 + * + * 参数列表: + * buf:BufferDesc 结构,表示待锁定和引用的缓冲区 + * tag:BufferTag 结构,表示缓冲区的标签信息 + * + */ static bool SegPinBufferLocked(BufferDesc *buf, const BufferTag *tag) { ereport(DEBUG5, (errmodule(MOD_SEGMENT_PAGE), errmsg("[SegPinBufferLocked] (%u %u %u %d) %d %u ", tag->rnode.spcNode, tag->rnode.dbNode, tag->rnode.relNode, tag->rnode.bucketNode, tag->forkNum, tag->blockNum))); + // 确保缓冲区已经被锁定 SegmentCheck(BufHdrLocked(buf)); - PrivateRefCountEntry * ref = GetPrivateRefCountEntry(BufferDescriptorGetBuffer(buf), true, true); + // 获取缓冲区的引用计数结构 + PrivateRefCountEntry *ref = GetPrivateRefCountEntry(BufferDescriptorGetBuffer(buf), true, true); + // 确保引用计数结构有效 SegmentCheck(ref != NULL); + // 读取缓冲区的状态 uint32 buf_state = pg_atomic_read_u32(&buf->state); + // 如果缓冲区的引用计数为0,增加引用计数 if (ref->refcount == 0) { buf_state += BUF_REFCOUNT_ONE; } + // 解锁缓冲区,并设置新的状态 UnlockBufHdr(buf, buf_state); + // 增加引用计数 ref->refcount++; + // 扩展当前资源所有者的缓冲区列表 ResourceOwnerEnlargeBuffers(t_thrd.utils_cxt.CurrentResourceOwner); + // 记录缓冲区到当前资源所有者 ResourceOwnerRememberBuffer(t_thrd.utils_cxt.CurrentResourceOwner, BufferDescriptorGetBuffer(buf)); + // 返回缓冲区状态是否有效 return buf_state & BM_VALID; } - +/* + * 功能:解除缓冲区引用,用于确保在多线程环境中正确处理缓冲区的引用和状态。 + * + * 参数列表: + * buf:BufferDesc 结构,表示待解除引用的缓冲区 + * + * 注意: + * 如果缓冲区的引用计数归零,该函数会解锁缓冲区,但不会从内存中释放缓冲区。 + */ void SegUnpinBuffer(BufferDesc *buf) { ereport(DEBUG5, (errmodule(MOD_SEGMENT_PAGE), errmsg("[SegUnpinBuffer] (%u %u %u %d) %d %u ", buf->tag.rnode.spcNode, buf->tag.rnode.dbNode, buf->tag.rnode.relNode, buf->tag.rnode.bucketNode, buf->tag.forkNum, buf->tag.blockNum))); - PrivateRefCountEntry * ref = GetPrivateRefCountEntry(BufferDescriptorGetBuffer(buf), true, true); + // 获取缓冲区的引用计数结构 + PrivateRefCountEntry *ref = GetPrivateRefCountEntry(BufferDescriptorGetBuffer(buf), true, true); + // 确保引用计数结构有效 SegmentCheck(ref != NULL); + // 从当前资源所有者的缓冲区列表中移除缓冲区 ResourceOwnerForgetBuffer(t_thrd.utils_cxt.CurrentResourceOwner, BufferDescriptorGetBuffer(buf)); + // 减少引用计数 ref->refcount--; + // 如果引用计数归零 if (ref->refcount == 0) { uint32 buf_state; uint32 old_buf_state; + // 读取缓冲区的状态 old_buf_state = pg_atomic_read_u32(&buf->state); for (;;) { + // 如果缓冲区被锁定,等待其解锁 if (old_buf_state & BM_LOCKED) { old_buf_state = SegWaitBufHdrUnlocked(buf); } buf_state = old_buf_state; + // 确保引用计数大于0 SegmentCheck(BUF_STATE_GET_REFCOUNT(buf_state) > 0); + // 减少引用计数 buf_state -= BUF_REFCOUNT_ONE; + // 使用原子操作更新缓冲区状态 if (pg_atomic_compare_exchange_u32(&buf->state, &old_buf_state, buf_state)) { break; } } - + + // 确保缓冲区没有等待的引用计数 SegmentCheck(!(buf_state & BM_PIN_COUNT_WAITER)); + // 重置引用计数 ref->refcount = 0; + // 释放引用计数结构 ForgetPrivateRefCountEntry(ref); } } - +/* + * 功能:释放缓冲区 + * + * 参数列表: + * buffer:Buffer 数据类型,表示待释放的缓冲区 + * + * 注意: + * 该函数用于释放一个缓冲区,确保不再需要访问该缓冲区的内容,以免内存泄漏。 + */ void SegReleaseBuffer(Buffer buffer) { + // 检查缓冲区是否是有效的段缓冲区 SegmentCheck(IsSegmentBufferID(buffer - 1)); + // 调用 SegUnpinBuffer 函数来解除对缓冲区的引用 SegUnpinBuffer(GetBufferDescriptor(buffer - 1)); } - -void SegUnlockReleaseBuffer(Buffer buffer) +/* + * 功能:解锁并释放缓冲区 + * + * 参数列表: + * buffer:Buffer 数据类型,表示待解锁并释放的缓冲区 + * + * 注意: + * 该函数适用于需要解锁并释放缓冲区的情况,确保不再需要访问该缓冲区的内容, + * 以免内存泄漏。 + */ +void SegUnlockReleaseBuffer(Buffer buffer) { - LockBuffer(buffer, BUFFER_LOCK_UNLOCK); + // 通过 LockBuffer 函数将缓冲区解锁 + LockBuffer(buffer, BUFFER_LOCK_UNLOCK); + // 调用 SegUnpinBuffer 函数来解除对缓冲区的引用 SegUnpinBuffer(GetBufferDescriptor(buffer - 1)); } - +/* + * 功能:将缓冲区标记为脏页脏页会在以后的时刻被同步到磁盘上,以保证数据的持久性。 + * + * 参数列表: + * buf:Buffer 数据类型,表示待标记为脏页的缓冲区 + * + * 注意: + * 1. 该函数假定缓冲区的内容锁(content_lock)已经由调用者获取,因此没有进行锁的获取操作。 + * 2. 函数会检查脏页是否已经在脏页队列中,如果不在,则将其添加到脏页队列中等待同步。 + */ void SegMarkBufferDirty(Buffer buf) { uint32 old_buf_state, buf_state; BufferDesc *bufHdr; + // 获取缓冲区描述符 bufHdr = GetBufferDescriptor(buf - 1); + // 进行一系列断言检查,确保缓冲区的合法性 SegmentCheck(IsSegmentBufferID(bufHdr->buf_id)); /* unfortunately we can't check if the lock is held exclusively */ SegmentCheck(LWLockHeldByMe(bufHdr->content_lock)); + // 获取缓冲区的旧状态 old_buf_state = LockBufHdr(bufHdr); + // 设置缓冲区的新状态,标记为脏页(BM_DIRTY)和刚刚脏化(BM_JUST_DIRTIED) buf_state = old_buf_state | (BM_DIRTY | BM_JUST_DIRTIED); /* @@ -320,19 +478,33 @@ void SegMarkBufferDirty(Buffer buf) break; } + // 如果脏页队列不满,并且成功将脏页推送到队列中,就退出循环 if (!is_dirty_page_queue_full(bufHdr) && push_pending_flush_queue(buf)) { break; } + // 解锁缓冲区描述符,让其他线程有机会获取锁 UnlockBufHdr(bufHdr, old_buf_state); + // 短暂休眠一段时间,然后再次尝试获取锁 pg_usleep(TEN_MICROSECOND); old_buf_state = LockBufHdr(bufHdr); } } + // 解锁缓冲区描述符,并设置新状态 UnlockBufHdr(bufHdr, buf_state); } - +/* + * 功能:将缓冲区的数据刷新到磁盘上,以确保数据的持久性。 + * + * 参数列表: + * buf:BufferDesc 数据类型,表示待刷新到磁盘的缓冲区 + * reln:SMgrRelation 数据类型,表示关联的存储管理器关系,如果为NULL,则从缓冲区的标签中获取 + * + * 注意: + * 1. 如果在刷新缓冲区时出现错误,函数会进行错误上下文回调和错误处理。 + * 2. 函数会检查是否有其他线程已经刷新了该缓冲区,如果是,则不再重复刷新。 + */ void SegFlushBuffer(BufferDesc *buf, SMgrRelation reln) { if (!SegStartBufferIO(buf, false)) { @@ -354,7 +526,7 @@ void SegFlushBuffer(BufferDesc *buf, SMgrRelation reln) char *buf_to_write = NULL; RedoBufferInfo buffer_info; - + SegSpace *spc; if (reln == NULL || reln->seg_space == NULL) { spc = spc_open(buf->tag.rnode.spcNode, buf->tag.rnode.dbNode, false); @@ -368,27 +540,33 @@ void SegFlushBuffer(BufferDesc *buf, SMgrRelation reln) UnlockBufHdr(buf, buf_state); + // 获取缓冲区的刷新信息 GetFlushBufferInfo(buf, &buffer_info, &buf_state, WITH_NORMAL_CACHE); /* Must be segment-page metadata page */ SegmentCheck(PageIsSegmentVersion(buffer_info.pageinfo.page) || PageIsNew(buffer_info.pageinfo.page)); + // 如果启用了FORCE_FINISH_ENABLED,更新最大的页面刷新LSN if (FORCE_FINISH_ENABLED) { update_max_page_flush_lsn(buffer_info.lsn, t_thrd.proc_cxt.MyProcPid, false); } + // 等待XLog刷新,确保数据写入磁盘之前已经提交到WAL日志 XLogWaitFlush(buffer_info.lsn); /* page data encrypt */ buf_to_write = PageDataEncryptForBuffer(buffer_info.pageinfo.page, buf, true); + // 如果数据指针发生变化,需要重新计算校验和 if (unlikely(buf_to_write != (char *)buffer_info.pageinfo.page)) { PageSetChecksumInplace((Page)buf_to_write, buffer_info.blockinfo.blkno); } else { buf_to_write = PageSetChecksumCopy((Page)buf_to_write, buffer_info.blockinfo.blkno, true); } + // 将数据写入磁盘 seg_physical_write(spc, buf->tag.rnode, buf->tag.forkNum, buf->tag.blockNum, (char *)buf_to_write, false); + // 完成缓冲区的IO操作 SegTerminateBufferIO(buf, true, 0); /* Pop the error context stack, if it was set before */ @@ -396,41 +574,69 @@ void SegFlushBuffer(BufferDesc *buf, SMgrRelation reln) t_thrd.log_cxt.error_context_stack = errcontext.previous; } } - +/* + * 功能:报告无效的页面 + * + * 参数列表: + * key:RepairBlockKey 数据类型,表示无效页面的关键信息,包括文件节点、分支号、块号等。 + * + * 注意: + * 1. 修复无效页面的过程中,会记录错误信息,并在修复完成后输出警告信息。 + * 2. 如果不满足页修复的条件,函数将触发一个错误,报告无效页面。 + */ void ReportInvalidPage(RepairBlockKey key) { /* record bad page, wait the pagerepair thread repair the page */ - if (CheckVerionSupportRepair() && (AmStartupProcess() || AmPageRedoWorker()) && - IsPrimaryClusterStandbyDN() && g_instance.repair_cxt.support_repair) { + if (CheckVerionSupportRepair() && (AmStartupProcess() || AmPageRedoWorker()) && IsPrimaryClusterStandbyDN() && + g_instance.repair_cxt.support_repair) { + // 如果环境支持页修复,且当前进程是启动进程或页重做工作者,并且是主集群的备用数据节点,并且页修复功能被启用 XLogPhyBlock pblk_bak = {0}; - RedoPageRepairCallBack(key, pblk_bak); - log_invalid_page(key.relfilenode, key.forknum, key.blocknum, CRC_CHECK_ERROR, NULL); - ereport(WARNING, (errcode(ERRCODE_DATA_CORRUPTED), - errmsg("invalid page in block %u of relation %s", - key.blocknum, relpathperm(key.relfilenode, key.forknum)))); + RedoPageRepairCallBack(key, pblk_bak); // 调用页修复回调函数,进行修复操作 + log_invalid_page(key.relfilenode, key.forknum, key.blocknum, CRC_CHECK_ERROR, NULL); // 记录无效页面的信息 + ereport(WARNING, + (errcode(ERRCODE_DATA_CORRUPTED), errmsg("invalid page in block %u of relation %s", key.blocknum, + relpathperm(key.relfilenode, key.forknum)))); return; } - ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("invalid page in block %u of relation %s", - key.blocknum, relpathperm(key.relfilenode, key.forknum)))); + // 如果不满足页修复条件,报告无效页面的错误 + ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("invalid page in block %u of relation %s", key.blocknum, + relpathperm(key.relfilenode, key.forknum)))); return; } - +/* + * 功能:快速读取缓冲区的内容 + * + * 参数列表: + * spc:SegSpace 数据类型,表示段存储空间。 + * rnode:RelFileNode 数据类型,表示文件节点信息。 + * forkNum:ForkNumber 数据类型,表示分支号。 + * blockNum:BlockNumber 数据类型,表示要读取的块号。 + * mode:ReadBufferMode 数据类型,表示读取缓冲区的模式,包括零填充和锁定等。 + * + * 注意: + * 1. 如果块号不存在于缓冲池中,函数会根据模式进行初始化,包括零填充或读取存储介质中的数据。 + * 2. 如果读取到的块无效,函数会触发一个错误报告。 + */ Buffer ReadBufferFast(SegSpace *spc, RelFileNode rnode, ForkNumber forkNum, BlockNumber blockNum, ReadBufferMode mode) { bool found = false; + // 尝试在缓冲池中分配或获取指定块号的缓冲区描述符 BufferDesc *bufHdr = SegBufferAlloc(spc, rnode, forkNum, blockNum, &found); - if (!found) { + if (!found) { // 如果块号在缓冲池中不存在 SegmentCheck(!(pg_atomic_read_u32(&bufHdr->state) & BM_VALID)); char *bufBlock = (char *)BufHdrGetBlock(bufHdr); if (mode == RBM_ZERO_AND_LOCK || mode == RBM_ZERO_AND_CLEANUP_LOCK) { + // 零填充模式或零填充并锁定模式 errno_t er = memset_s((char *)bufBlock, BLCKSZ, 0, BLCKSZ); securec_check(er, "", ""); } else { + // 从存储介质中读取块的内容 seg_physical_read(spc, rnode, forkNum, blockNum, bufBlock); if (!PageIsVerified(bufBlock, blockNum)) { + // 验证读取的块是否有效,如果无效则报告错误 RepairBlockKey key; key.relfilenode = rnode; key.forknum = forkNum; @@ -439,58 +645,107 @@ Buffer ReadBufferFast(SegSpace *spc, RelFileNode rnode, ForkNumber forkNum, Bloc return InvalidBuffer; } if (!PageIsSegmentVersion(bufBlock) && !PageIsNew(bufBlock)) { + // 检查页面的版本是否与段存储版本兼容,如果不兼容则报告错误 ereport(PANIC, (errmsg("Read segment-page metadata buffer, block %u of relation %s, but page version is %d", blockNum, relpathperm(rnode, forkNum), PageGetPageLayoutVersion(bufBlock)))); } } - bufHdr->lsn_on_disk = PageGetLSN(bufBlock); + bufHdr->lsn_on_disk = PageGetLSN(bufBlock); // 记录块上的LSN #ifdef USE_ASSERT_CHECKING - bufHdr->lsn_dirty = InvalidXLogRecPtr; + bufHdr->lsn_dirty = InvalidXLogRecPtr; // 初始化LSN为无效值 #endif - SegTerminateBufferIO(bufHdr, false, BM_VALID); + SegTerminateBufferIO(bufHdr, false, BM_VALID); // 终止缓冲区的IO操作 } if (mode == RBM_ZERO_AND_LOCK || mode == RBM_ZERO_AND_CLEANUP_LOCK) { + // 如果模式要求锁定缓冲区,则进行锁定操作 LWLockAcquire(BufferDescriptorGetContentLock(bufHdr), LW_EXCLUSIVE); } - SegmentCheck(SegBufferIsPinned(bufHdr)); + SegmentCheck(SegBufferIsPinned(bufHdr)); // 确保缓冲区被锁定 return BufferDescriptorGetBuffer(bufHdr); } - +/* + * 功能:获取两个轻量级锁的互斥锁 + * + * 参数列表: + * new_partition_lock:新的轻量级锁的指针 + * old_partition_lock:旧的轻量级锁的指针 + * + * 注意: + * 1. 这个函数的目的是确保在获取锁时不会出现死锁,按照锁地址的大小顺序获取锁。 + * 2. 如果两个锁的地址相同,说明只有一个分区,只需获取一个锁。 + */ void LockTwoLWLock(LWLock *new_partition_lock, LWLock *old_partition_lock) { if (old_partition_lock < new_partition_lock) { + // 先获取较小的锁,再获取较大的锁 (void)LWLockAcquire(old_partition_lock, LW_EXCLUSIVE); (void)LWLockAcquire(new_partition_lock, LW_EXCLUSIVE); } else if (old_partition_lock > new_partition_lock) { + // 先获取较小的锁,再获取较大的锁 (void)LWLockAcquire(new_partition_lock, LW_EXCLUSIVE); (void)LWLockAcquire(old_partition_lock, LW_EXCLUSIVE); } else { /* only one partition, only one lock */ + // 如果两个锁的地址相同,说明只有一个分区,只需获取一个锁。 (void)LWLockAcquire(new_partition_lock, LW_EXCLUSIVE); } } +/* + * 功能:在哈希表中查找缓冲区描述符 + * + * 参数列表: + * buf_id:要查找的缓冲区的标识符。 + * new_partition_lock:新的轻量级锁的指针。 + * foundPtr:用于指示是否找到缓冲区描述符的指针。 -BufferDesc * FoundBufferInHashTable(int buf_id, LWLock *new_partition_lock, bool *foundPtr) + * 注意: + * 1. 这个函数用于查找和锁定缓冲区描述符,以便进行后续操作。 + * 2. 如果缓冲区描述符有效,则函数将释放一个轻量级锁,并将“foundPtr”设置为“true”。 + * 3. 如果缓冲区描述符无效,则函数会尝试启动缓冲区的IO操作,并将“foundPtr”设置为“false”。 + */ +BufferDesc *FoundBufferInHashTable(int buf_id, LWLock *new_partition_lock, bool *foundPtr) { + // 获取缓冲区描述符 BufferDesc *buf = GetBufferDescriptor(buf_id); + // 尝试锁定(Pin)缓冲区并判断其是否有效 bool valid = SegPinBuffer(buf); + // 释放新的轻量级锁 LWLockRelease(new_partition_lock); + // 设置“foundPtr”为true *foundPtr = true; + // 如果缓冲区无效 if (!valid) { + // 尝试启动缓冲区的IO操作 if (SegStartBufferIO(buf, true)) { + // 如果成功启动IO操作,则设置“foundPtr”为false *foundPtr = false; } } + // 返回缓冲区描述符 return buf; } +/* + * 功能:分配一个新的缓冲区并返回其缓冲区描述符 + * + * 参数列表: + * spc:段空间(SegSpace)指针,用于表示缓冲区所属的段空间。 + * rnode:文件节点(RelFileNode)。 + * forkNum:分叉号(ForkNumber)。 + * blockNum:块号(BlockNumber)。 + * foundPtr:用于指示是否找到缓冲区描述符的指针。 -BufferDesc *SegBufferAlloc(SegSpace *spc, RelFileNode rnode, ForkNumber forkNum, BlockNumber blockNum, - bool *foundPtr) + * 注意: + * 1. 这个函数首先尝试在哈希表中查找缓冲区描述符,如果找到,则返回该描述符。 + * 2. 如果没有找到缓冲区描述符,函数将分配一个新的缓冲区描述符,并标记为新缓冲区。 + * 3. 在尝试查找或分配缓冲区描述符时,函数会根据需要启动缓冲区的IO操作。 + * 4. 函数最终返回缓冲区描述符,并通过“foundPtr”指示是否找到描述符。 + */ +BufferDesc *SegBufferAlloc(SegSpace *spc, RelFileNode rnode, ForkNumber forkNum, BlockNumber blockNum, bool *foundPtr) { BufferDesc *buf; BufferTag new_tag, old_tag; @@ -501,29 +756,37 @@ BufferDesc *SegBufferAlloc(SegSpace *spc, RelFileNode rnode, ForkNumber forkNum, LWLock *old_partition_lock; bool old_flag_valid; + // 初始化新的缓冲区标签 INIT_BUFFERTAG(new_tag, rnode, forkNum, blockNum); + // 计算新缓冲区标签的哈希值并获取相应的分区锁 new_hash = BufTableHashCode(&new_tag); new_partition_lock = BufMappingPartitionLock(new_hash); + // 尝试以共享模式获取分区锁并查找缓冲区描述符 LWLockAcquire(new_partition_lock, LW_SHARED); int buf_id = BufTableLookup(&new_tag, new_hash); + // 如果找到缓冲区描述符,则返回它 if (buf_id >= 0) { return FoundBufferInHashTable(buf_id, new_partition_lock, foundPtr); } + // 标记未找到缓冲区描述符,并释放分区锁 *foundPtr = FALSE; LWLockRelease(new_partition_lock); for (;;) { + // 从策略中获取一个缓冲区描述符 buf = SegStrategyGetBuffer(&buf_state); SegmentCheck(BUF_STATE_GET_REFCOUNT(buf_state) == 0); old_flags = buf_state & BUF_FLAG_MASK; + // 锁定(Pin)新的缓冲区 SegPinBufferLocked(buf, &new_tag); + // 如果缓冲区标记为脏(BM_DIRTY),则执行相应操作 if (old_flags & BM_DIRTY) { /* backend should not flush dirty pages if working version less than DW_SUPPORT_NEW_SINGLE_FLUSH */ if (!backend_can_flush_dirty_page()) { @@ -548,6 +811,7 @@ BufferDesc *SegBufferAlloc(SegSpace *spc, RelFileNode rnode, ForkNumber forkNum, old_hash = BufTableHashCode(&old_tag); old_partition_lock = BufMappingPartitionLock(old_hash); + // 获取两个分区锁,如果标签有效则获取旧分区锁,否则只获取新分区锁 LockTwoLWLock(new_partition_lock, old_partition_lock); } else { /* if it wasn't valid, we need only the new partition */ @@ -557,9 +821,11 @@ BufferDesc *SegBufferAlloc(SegSpace *spc, RelFileNode rnode, ForkNumber forkNum, old_partition_lock = NULL; } + // 在哈希表中插入新标签,如果成功则返回缓冲区描述符 buf_id = BufTableInsert(&new_tag, new_hash, buf->buf_id); if (buf_id >= 0) { + // 释放缓冲区描述符并返回已找到的描述符 SegUnpinBuffer(buf); if (old_flag_valid && old_partition_lock != new_partition_lock) LWLockRelease(old_partition_lock); @@ -567,6 +833,7 @@ BufferDesc *SegBufferAlloc(SegSpace *spc, RelFileNode rnode, ForkNumber forkNum, return FoundBufferInHashTable(buf_id, new_partition_lock, foundPtr); } + // 如果缓冲区被引用,或者标记为脏(BM_DIRTY),则继续循环 buf_state = LockBufHdr(buf); old_flags = buf_state & BUF_FLAG_MASK; @@ -582,6 +849,7 @@ BufferDesc *SegBufferAlloc(SegSpace *spc, RelFileNode rnode, ForkNumber forkNum, SegUnpinBuffer(buf); } + // 更新缓冲区描述符的标签和状态 buf->tag = new_tag; buf_state &= ~(BM_VALID | BM_DIRTY | BM_JUST_DIRTIED | BM_CHECKPOINT_NEEDED | BM_IO_ERROR | BM_PERMANENT | BUF_USAGECOUNT_MASK); @@ -589,19 +857,30 @@ BufferDesc *SegBufferAlloc(SegSpace *spc, RelFileNode rnode, ForkNumber forkNum, UnlockBufHdr(buf, buf_state); if (old_flag_valid) { + // 从哈希表中删除旧标签 BufTableDelete(&old_tag, old_hash); if (old_partition_lock != new_partition_lock) { LWLockRelease(old_partition_lock); } } + // 释放新分区锁 LWLockRelease(new_partition_lock); + // 设置“foundPtr”为缓冲区是否需要启动IO操作的标志 *foundPtr = !SegStartBufferIO(buf, true); + // 返回缓冲区描述符 return buf; } static uint32 next_victim_buffer = 0; - +/* + * 功能:Clock算法的一个步骤,用于选择下一个被淘汰的缓冲区 + * + * 注意: + * 1. Clock算法是一种用于缓冲区淘汰策略的算法,通常用于管理缓冲区池中的缓冲区。 + * 2. 该函数通过原子操作从next_victim_buffer中获取一个递增的整数,表示下一个被淘汰的缓冲区。 + * 3. 如果计算得到的victim超过了SEGMENT_BUFFER_NUM(缓冲区池的大小),则将其重新映射到0以保持在有效范围内。 + */ static inline uint32 ClockSweepTick(void) { uint32 victim = pg_atomic_fetch_add_u32(&next_victim_buffer, 1); @@ -610,73 +889,102 @@ static inline uint32 ClockSweepTick(void) } return victim; } - -static BufferDesc* get_segbuf_from_candidate_list(uint32* buf_state) +/* + * 功能:从候选缓冲区列表中获取一个缓冲区描述符 + * + * 参数列表: + * buf_state:指向用于存储缓冲区状态的指针,通过此参数返回缓冲区的状态 + * + * 注意: + * 1. 该函数用于从候选缓冲区列表中获取一个可用的缓冲区描述符,通常用于高效的缓冲区管理。 + * 2. 如果ENABLE_INCRE_CKPT开启,并且有页写入进程在运行,函数会尝试从候选缓冲区列表中获取缓冲区。 + * 3. 函数会遍历候选缓冲区列表,并找到一个满足条件的可用缓冲区,返回其描述符。 + * 4. 候选缓冲区的条件包括:引用计数为0、不是元数据缓冲区、未标记为脏缓冲区。 + * 5. 如果没有找到满足条件的缓冲区,函数返回NULL。 + */ +static BufferDesc *get_segbuf_from_candidate_list(uint32 *buf_state) { - BufferDesc* buf = NULL; - uint32 local_buf_state; - int buf_id = 0; + BufferDesc *buf = NULL; // 缓冲区描述符 + uint32 local_buf_state; // 本地缓冲区状态 + int buf_id = 0; // 缓冲区标识符 + // 如果启用增量检查点并且有页写入进程正在运行 if (ENABLE_INCRE_CKPT && pg_atomic_read_u32(&g_instance.ckpt_cxt_ctl->current_page_writer_count) > 0) { int list_num = g_instance.ckpt_cxt_ctl->pgwr_procs.sub_num; - volatile PgBackendStatus* beentry = t_thrd.shemem_ptr_cxt.MyBEEntry; + volatile PgBackendStatus *beentry = t_thrd.shemem_ptr_cxt.MyBEEntry; int list_id = beentry->st_tid > 0 ? (beentry->st_tid % list_num) : (beentry->st_sessionid % list_num); + // 遍历页写入进程的子线程 for (int i = 0; i < list_num; i++) { /* the pagewriter sub thread store normal buffer pool, sub thread starts from 1 */ int thread_id = (list_id + i) % list_num + 1; Assert(thread_id > 0 && thread_id <= list_num); + // 从候选缓冲区列表中获取一个缓冲区标识符 while (seg_candidate_buf_pop(&buf_id, thread_id)) { - buf = GetBufferDescriptor(buf_id); - local_buf_state = LockBufHdr(buf); + buf = GetBufferDescriptor(buf_id); // 获取缓冲区描述符 + local_buf_state = LockBufHdr(buf); // 锁定缓冲区描述符的状态 SegmentCheck(buf_id >= SegmentBufferStartID); + // 如果该缓冲区已被标记为候选 if (g_instance.ckpt_cxt_ctl->candidate_free_map[buf_id]) { g_instance.ckpt_cxt_ctl->candidate_free_map[buf_id] = false; - bool available_buffer = BUF_STATE_GET_REFCOUNT(local_buf_state) == 0 - && !(local_buf_state & BM_IS_META) - && !(local_buf_state & BM_DIRTY); + bool available_buffer = BUF_STATE_GET_REFCOUNT(local_buf_state) == 0 && + !(local_buf_state & BM_IS_META) && !(local_buf_state & BM_DIRTY); if (available_buffer) { - *buf_state = local_buf_state; - return buf; + *buf_state = local_buf_state; // 存储缓冲区的状态 + return buf; // 返回满足条件的缓冲区描述符 } } - UnlockBufHdr(buf, local_buf_state); + UnlockBufHdr(buf, local_buf_state); // 解锁缓冲区描述符 } } - wakeup_pagewriter_thread(); + wakeup_pagewriter_thread(); // 唤醒页写入进程 } - return NULL; + return NULL; // 未找到满足条件的缓冲区,返回NULL } /* lock the buffer descriptor before return */ const int RETRY_COUNT = 3; -static BufferDesc *SegStrategyGetBuffer(uint32 *buf_state) +/* + * 功能:根据策略获取一个缓冲区描述符 + * + * 参数列表: + * buf_state:指向用于存储缓冲区状态的指针,通过此参数返回缓冲区的状态 + * + * 注意: + * 1. 该函数用于根据缓冲区策略获取一个可用的缓冲区描述符。 + * 2. 首先尝试从候选缓冲区列表中获取可用的缓冲区,如果获取成功,则返回。 + * 3. 如果候选缓冲区列表中没有可用的缓冲区,函数会循环尝试通过ClockSweepTick策略来获取可用缓冲区。 + * 4. 获取到的缓冲区必须满足引用计数为0的条件,否则会继续尝试直到成功或达到最大尝试次数。 + * 5. 如果获取了一个可用缓冲区,则将其状态存储在buf_state参数中,并返回该缓冲区的描述符。 + * 6. 如果没有可用缓冲区,则抛出错误提示“no unpinned buffers available”。 + */ +static BufferDesc *SegStrategyGetBuffer(uint32 *buf_state) { // todo: add free list - BufferDesc *buf = get_segbuf_from_candidate_list(buf_state); - int try_counter = SEGMENT_BUFFER_NUM * RETRY_COUNT; + BufferDesc *buf = get_segbuf_from_candidate_list(buf_state); // 尝试从候选缓冲区列表中获取缓冲区 + int try_counter = SEGMENT_BUFFER_NUM * RETRY_COUNT; // 重试计数器 if (buf != NULL) { (void)pg_atomic_fetch_add_u64(&g_instance.ckpt_cxt_ctl->seg_get_buf_num_candidate_list, 1); - return buf; + return buf; // 如果成功获取了候选缓冲区,则返回 } for (;;) { - int buf_id = BufferIdOfSegmentBuffer(ClockSweepTick()); - buf = GetBufferDescriptor(buf_id); + int buf_id = BufferIdOfSegmentBuffer(ClockSweepTick()); // 根据ClockSweep策略获取缓冲区ID + buf = GetBufferDescriptor(buf_id); // 获取缓冲区描述符 - uint32 state = LockBufHdr(buf); + uint32 state = LockBufHdr(buf); // 锁定缓冲区描述符的状态 - if (BUF_STATE_GET_REFCOUNT(state) == 0) { - *buf_state = state; + if (BUF_STATE_GET_REFCOUNT(state) == 0) { // 如果缓冲区引用计数为0 + *buf_state = state; // 存储缓冲区状态 (void)pg_atomic_fetch_add_u64(&g_instance.ckpt_cxt_ctl->seg_get_buf_num_clock_sweep, 1); - return buf; + return buf; // 返回可用缓冲区描述符 } else if (--try_counter == 0) { - UnlockBufHdr(buf, state); + UnlockBufHdr(buf, state); // 解锁缓冲区描述符 ereport(ERROR, (errcode(ERRCODE_INVALID_BUFFER), (errmsg("no unpinned buffers available")))); } - UnlockBufHdr(buf, state); + UnlockBufHdr(buf, state); // 解锁缓冲区描述符 ereport(DEBUG5, (errmodule(MOD_SEGMENT_PAGE), (errmsg("SegStrategyGetBuffer get a pinned buffer, %d, buffer tag <%u, %u, %u, %u>.%d.%u, state %u", @@ -684,7 +992,19 @@ static BufferDesc *SegStrategyGetBuffer(uint32 *buf_state) buf->tag.rnode.bucketNode, buf->tag.forkNum, buf->tag.blockNum, state)))); } } - +/* + * 功能:释放指定存储空间的元数据缓冲区 + * + * 参数列表: + * spcNode:要释放的存储空间的Oid + * dbNode:要释放的数据库的Oid + * + * 注意: + * 1. 该函数用于释放指定存储空间中的元数据缓冲区。 + * 2. 首先关闭所有段头文件。 + * 3. 然后遍历所有缓冲区描述符,查找并释放与指定存储空间和数据库匹配的元数据缓冲区。 + * 4. 对于匹配的缓冲区,如果其状态为“BM_DIRTY”和“BM_VALID”,则将其标记为无效,否则解锁缓冲区。 + */ void SegDropSpaceMetaBuffers(Oid spcNode, Oid dbNode) { int i; @@ -692,7 +1012,7 @@ void SegDropSpaceMetaBuffers(Oid spcNode, Oid dbNode) // Release segment head buffer smgrcloseall(); for (i = SegmentBufferStartID; i < TOTAL_BUFFER_NUM; i++) { - BufferDesc *buf_desc = GetBufferDescriptor(i); + BufferDesc *buf_desc = GetBufferDescriptor(i); // 获取缓冲区描述符 uint32 buf_state; /* * As in DropRelFileNodeBuffers, an unlocked precheck should be safe @@ -702,19 +1022,26 @@ void SegDropSpaceMetaBuffers(Oid spcNode, Oid dbNode) continue; } - buf_state = LockBufHdr(buf_desc); - if (buf_desc->tag.rnode.spcNode == spcNode && buf_desc->tag.rnode.dbNode == dbNode && - (buf_state & BM_DIRTY) && (buf_state & BM_VALID)) { + buf_state = LockBufHdr(buf_desc); // 锁定缓冲区描述符的状态 + if (buf_desc->tag.rnode.spcNode == spcNode && buf_desc->tag.rnode.dbNode == dbNode && (buf_state & BM_DIRTY) && + (buf_state & BM_VALID)) { InvalidateBuffer(buf_desc); /* releases spinlock */ } else { - UnlockBufHdr(buf_desc, buf_state); + UnlockBufHdr(buf_desc, buf_state); // 解锁缓冲区描述符 } } } - +/* + * 功能:刷新单个段缓冲区 + * + * 参数列表: + * buffer:要刷新的缓冲区标识符。 + * + */ void FlushOneSegmentBuffer(Buffer buffer) { BufferDesc *buf_desc = GetBufferDescriptor(buffer - 1); + // 检查缓冲区中的页面是否为段版本页面或新页面,否则引发 PANIC 错误 if (!PageIsSegmentVersion(BufferGetBlock(buffer)) && !PageIsNew(BufferGetBlock(buffer))) { ereport(PANIC, (errmsg("Flush segment-page metadata buffer, block %u of relation %s, but page version is %d", buf_desc->tag.blockNum, relpathperm(buf_desc->tag.rnode, buf_desc->tag.forkNum), @@ -724,37 +1051,46 @@ void FlushOneSegmentBuffer(Buffer buffer) if (dw_enabled() && pg_atomic_read_u32(&g_instance.ckpt_cxt_ctl->current_page_writer_count) > 0) { uint32 pos = 0; bool flush_old_file = false; + // 获取 Double Write 位置 pos = seg_dw_single_flush(buf_desc, &flush_old_file); t_thrd.proc->dw_pos = pos; t_thrd.proc->flush_new_dw = !flush_old_file; + // 调用 SegFlushBuffer 函数刷新缓冲区 SegFlushBuffer(buf_desc, NULL); if (flush_old_file) { g_instance.dw_single_cxt.recovery_buf.single_flush_state[pos] = true; } else { g_instance.dw_single_cxt.single_flush_state[pos] = true; } - t_thrd.proc->dw_pos = -1; + t_thrd.proc->dw_pos = -1; // 清除 Double Write 位置 } else { - SegFlushBuffer(buf_desc, NULL); + SegFlushBuffer(buf_desc, NULL); // 直接调用 SegFlushBuffer 函数刷新缓冲区 } } /* Flush a data buffer. If double write is on, it will invoke single-page-dw first. Caller should lock the buffer. */ +/* + * 功能:刷新单个缓冲区,包括 Double Write 操作 + * + * 参数列表: + * buf_desc:要刷新的缓冲区描述符。 + */ void FlushOneBufferIncludeDW(BufferDesc *buf_desc) { if (dw_enabled()) { bool flush_old_file = false; - uint32 pos = seg_dw_single_flush(buf_desc, &flush_old_file); + uint32 pos = seg_dw_single_flush(buf_desc, &flush_old_file); // 获取 Double Write 位置 t_thrd.proc->dw_pos = pos; t_thrd.proc->flush_new_dw = !flush_old_file; + // 调用 FlushBuffer 函数刷新缓冲区 FlushBuffer(buf_desc, NULL); if (flush_old_file) { g_instance.dw_single_cxt.recovery_buf.single_flush_state[pos] = true; } else { g_instance.dw_single_cxt.single_flush_state[pos] = true; } - t_thrd.proc->dw_pos = -1; + t_thrd.proc->dw_pos = -1; // 清除 Double Write 位置 } else { - FlushBuffer(buf_desc, NULL); + FlushBuffer(buf_desc, NULL); // 直接调用 FlushBuffer 函数刷新缓冲区 } } diff --git a/src/gausskernel/storage/smgr/segment/segxlog.cpp b/src/gausskernel/storage/smgr/segment/segxlog.cpp index 05954cd08..bf85b9e9a 100644 --- a/src/gausskernel/storage/smgr/segment/segxlog.cpp +++ b/src/gausskernel/storage/smgr/segment/segxlog.cpp @@ -35,22 +35,45 @@ /* * Truncate segment size */ +/* + * 功能:处理重做操作中的截断记录 + * + * 参数列表: + * record:XLog 重做记录的状态信息。 + * + * 注意: + * 1. 该函数用于处理重做操作中的截断记录。 + * 2. 首先,通过调用 XLogReadBufferForRedo 获取待处理的缓冲区信息及重做操作类型。 + * 3. 如果重做操作类型为 BLK_NEEDS_REDO,表示需要执行重做操作: + * a. 从 XLog 记录中获取截断后的块数 nblocks。 + * b. 获取缓冲区对应的页面,并将该页面的头部信息转换为 SegmentHead 结构。 + * c. 更新 seg_head->nblocks 为 nblocks,并设置页面的 LSN。 + * d. 标记缓冲区为脏。 + * 4. 最后,如果缓冲区有效,则解锁并释放缓冲区。 + */ static void redo_truncate(XLogReaderState *record) { RedoBufferInfo buffer_info; XLogRedoAction redo_action = XLogReadBufferForRedo(record, 0, &buffer_info); if (redo_action == BLK_NEEDS_REDO) { + // 获取被截断的块数 BlockNumber nblocks = *(BlockNumber *)XLogRecGetBlockData(record, 0, NULL); + // 获取与缓冲区相关联的页面 Page page = buffer_info.pageinfo.page; + // 将页面头信息视为SegmentHead结构 SegmentHead *seg_head = (SegmentHead *)PageGetContents(page); + // 使用截断块数更新seg_head->nblocks seg_head->nblocks = nblocks; + // 设置页面的LSN,以记录重做操作的完成 PageSetLSN(page, buffer_info.lsn); + // 标记缓冲区为脏页,表示页面内容已被修改 SegMarkBufferDirty(buffer_info.buf); } if (BufferIsValid(buffer_info.buf)) { + // 如果缓冲区有效,则解锁并释放它,确保内存管理的一致性 SegUnlockReleaseBuffer(buffer_info.buf); } } @@ -58,17 +81,36 @@ static void redo_truncate(XLogReaderState *record) /* * Move a list of buckets. */ +/* + * 功能该函数负责处理重做期间的移动桶记录。 + * + * 参数列表: + * buffer:要处理的缓冲区。 + * data:XLog重做记录中的数据。 + * + * 注意: + * 1. 该函数用于在重做操作期间处理移动桶记录。 + * 2. 函数从XLog记录中获取一组桶项的信息,然后更新相关缓冲区的映射块。 + * 3. 映射块用于跟踪桶的位置和头信息。 + * 4. 函数遍历每个桶项,将其标识(bktentry_id)映射到新的桶头信息(bktentry_header)。 + */ static void redo_move_buckets(Buffer buffer, const char *data) { + // 获取桶项数据的指针 xl_seg_bktentry_tag_t *bktentry; + // 从XLog记录中获取桶项数量 uint32 nentry; nentry = *(uint32 *)data; bktentry = (xl_seg_bktentry_tag_t *)(data + sizeof(uint32)); + // 获取与缓冲区关联的映射块 BktHeadMapBlock *mapblock = (BktHeadMapBlock *)PageGetContents(BufferGetPage(buffer)); + // 遍历每个桶项 for (uint32 i = 0; i < nentry; i++) { + // 获取桶项的标识 uint32 mapentry_id = bktentry[i].bktentry_id; + // 更新映射块中的头信息,将桶项标识映射到新的桶头信息 mapblock->head_block[mapentry_id] = bktentry[i].bktentry_header; } } @@ -76,60 +118,123 @@ static void redo_move_buckets(Buffer buffer, const char *data) /* * Record redis info for bucket relation. */ +/* + * 功能:该函数负责处理重做期间的添加Redis信息记录。 + * + * 参数列表: + * buffer:要处理的缓冲区 + * data:XLog重做记录中的数据,包含Redis信息 + * + * 注意: + * 1. 该函数用于在重做操作期间处理添加Redis信息记录。 + * 2. 函数从XLog记录中获取Redis信息并将其添加到相关缓冲区中。 + * 3. 如果Redis信息中的单词数量为0,函数将刷新与缓冲区关联的表(relation)的所有缓冲区。 + * 4. 最后,函数将Redis信息添加到缓冲区的头部。 + */ static void redo_bucket_add_redisinfo(Buffer buffer, const char *data) { - SegRedisInfo *redis_info = (SegRedisInfo *)data; + // 从XLog记录中获取Redis信息 + SegRedisInfo *redis_info = (SegRedisInfo *)data; - if (redis_info->nwords == 0) { + // 如果Redis信息中的单词数量为0 + if (redis_info->nwords == 0) { + // 获取缓冲区描述符 BufferDesc *buf_desc = GetBufferDescriptor(buffer - 1); + // 创建虚拟的关系缓存条目 Relation reln = CreateFakeRelcacheEntry(buf_desc->tag.rnode); + // 打开关系的存储管理器 RelationOpenSmgr(reln); + // 刷新与关系关联的所有缓冲区 flush_all_buffers(reln, InvalidOid); + // 释放虚拟的关系缓存条目 FreeFakeRelcacheEntry(reln); } + // 获取缓冲区的主头部 BktMainHead *head = (BktMainHead *)PageGetContents(BufferGetPage(buffer)); - head->redis_info = *redis_info; + // 将Redis信息添加到缓冲区的头部 + head->redis_info = *redis_info; } - +/* + * 功能:该函数负责处理重做期间的取消设置位图记录。 + * + * 参数列表: + * buffer:要处理的缓冲区 + * data:XLog重做记录中的数据,包含取消设置位图信息 + * + * 注意: + * 1. 该函数用于在重做操作期间处理取消设置位图记录。 + * 2. 函数从XLog记录中获取取消设置位图的位标识(bitid)以及相关位图信息。 + * 3. 函数检查MapPage的第一个页是否一致,如果不一致,将引发PANIC错误。 + * 4. 如果尝试取消设置一个已经空闲的位图,则函数将引发PANIC错误。 + * 5. 最后,函数更新MapPage的相关信息,并取消设置指定的位图。 + */ static void redo_unset_bitmap(Buffer buffer, const char *data) { + // 从XLog记录中获取位图标识(bitid) uint16 bitid = *(uint16 *)data; data += sizeof(uint16); + // 获取XLog记录中的MapPage信息 df_map_page_t *log_map_page = (df_map_page_t *)data; + // 获取缓冲区中的MapPage df_map_page_t *map_page = (df_map_page_t *)PageGetContents(BufferGetPage(buffer)); + // 检查MapPage的第一个页是否一致,如果不一致,引发PANIC错误 if (map_page->first_page != log_map_page->first_page) { ereport(PANIC, (errmsg("MapPage's first_page is not consistent, it's %u on disk but should be %u according to xlog", map_page->first_page, log_map_page->first_page))); } - + + // 如果尝试取消设置一个已经空闲的位图,引发PANIC错误 if (DF_MAP_FREE(map_page->bitmap, bitid)) { ereport(PANIC, (errmsg("Try to unset bitmap which is free: %u", bitid))); } + // 更新MapPage的相关信息 map_page->dirty_last = log_map_page->dirty_last; map_page->free_begin = log_map_page->free_begin; map_page->free_bits = log_map_page->free_bits; + // 取消设置指定的位图 DF_MAP_UNSET(map_page->bitmap, bitid); } - +/* + * 功能:处理重做操作中的设置位图记录 + * + * 参数列表: + * buftag:缓冲区标记,用于确定操作的缓冲区位置。 + * buffer:待处理的缓冲区。 + * data:从 XLog 记录中提取的数据。 + * + * 注意: + * 1. 该函数用于处理重做操作中的设置位图记录。 + * 2. 首先,从数据中获取位图的位号 bitid。 + * 3. 获取缓冲区对应的页面,将其转换为 df_map_page_t 结构。 + * 4. 检查页面的 first_page 是否与 XLog 记录中的 log_map_page->first_page 一致,如果不一致,报告 PANIC 错误。 + * 5. 检查指定位是否已被设置,如果已被设置,报告 PANIC 错误。 + * 6. 将页面的 dirty_last、free_begin、free_bits 更新为 log_map_page 中的对应值,并设置指定位。 + * 7. 根据位图的 bitid 计算出对应数据文件的块号 blkno。 + * 8. 根据块号计算出目标块号 target,并使用 spc_extend_file 扩展数据文件到目标块号。 + */ static void redo_set_bitmap(RedoBufferTag buftag, Buffer buffer, const char *data) { + // 从 XLog 记录的数据中获取位图的位号 bitid uint16 bitid = *(uint16 *)data; data += sizeof(uint16); + // 获取 log_map_page 和当前缓冲区的 map_page df_map_page_t *log_map_page = (df_map_page_t *)data; df_map_page_t *map_page = (df_map_page_t *)PageGetContents(BufferGetPage(buffer)); + // 检查页面的 first_page 是否一致,不一致报告 PANIC 错误 if (map_page->first_page != log_map_page->first_page) { ereport(PANIC, (errmsg("MapPage's first_page is not consistent, it's %u on disk but should be %u according to xlog", map_page->first_page, log_map_page->first_page), errhint("segment-page may have bug"))); } + // 检查指定位是否已被设置,如果已被设置,报告 PANIC 错误 if (DF_MAP_NOT_FREE(map_page->bitmap, bitid)) { ereport(PANIC, (errmsg("Try to set bitmap which is not free: %u", bitid), errhint("segment-page may have bug"))); @@ -140,39 +245,92 @@ static void redo_set_bitmap(RedoBufferTag buftag, Buffer buffer, const char *dat DF_MAP_SET(map_page->bitmap, bitid); /* Extend data file if necessary. First get the extent start */ + // 计算数据文件的块号 blkno,以及目标块号 target BlockNumber blkno = map_page->first_page + EXTENT_TYPE_TO_SIZE(buftag.rnode.relNode) * bitid; /* Then plus one extent length */ BlockNumber target = blkno + EXTENT_TYPE_TO_SIZE(buftag.rnode.relNode); + // 打开 SegSpace 并调用 spc_extend_file 扩展数据文件到目标块号 SegSpace *spc = spc_open(buftag.rnode.spcNode, buftag.rnode.dbNode, false); SegmentCheck(spc != NULL); spc_extend_file(spc, buftag.rnode.relNode, buftag.forknum, target); } - +/* + * 功能:处理重做操作中的映射头分配扩展记录 + * + * 参数列表: + * buffer:待处理的缓冲区。 + * data:从 XLog 记录中提取的数据。 + * + * 注意: + * 1. 该函数用于处理重做操作中的映射头分配扩展记录。 + * 2. 从 XLog 记录数据中获取 XLogDataSpaceAllocateExtent 结构,包含分配扩展所需的信息。 + * 3. 获取缓冲区对应的页面,并将其转换为 df_map_head_t 结构。 + * 4. 更新映射头的 allocated_extents、free_group、groups[free_group].free_page 和 high_water_mark 字段 + * 以反映 XLog 记录中的数据。 + */ static void redo_maphead_allocated_extents(Buffer buffer, const char *data) { + // 从 XLog 记录数据中获取 XLogDataSpaceAllocateExtent 结构 XLogDataSpaceAllocateExtent *xlog_data = (XLogDataSpaceAllocateExtent *)data; + // 获取缓冲区对应的页面,并将其转换为 df_map_head_t 结构 df_map_head_t *map_head = (df_map_head_t *)PageGetContents(BufferGetPage(buffer)); + // 更新映射头的 allocated_extents、free_group、groups[free_group].free_page 和 high_water_mark 字段 map_head->allocated_extents = xlog_data->allocated_extents; map_head->free_group = xlog_data->free_group; - map_head->groups[xlog_data->free_group].free_page = xlog_data->free_page; + map_head->groups[xlog_data->free_group].free_page = xlog_data->free_page; map_head->high_water_mark = xlog_data->hwm; } - +/* + * 功能:处理重做操作中的初始化段头记录 + * + * 参数列表: + * buffer:待处理的缓冲区。 + * data:从 XLog 记录中提取的数据。 + * + * 注意: + * 1. 该函数用于处理重做操作中的初始化段头记录。 + * 2. 获取缓冲区描述符,对应于参数 buffer 的编号减一。 + * 3. 使用 SegmentCheck 确保缓冲区的关联关系,即 rnode.relNode 必须等于 SEGMENT_HEAD_EXTENT_SIZE。 + * 4. 从 XLog 记录中获取 LSN,并使用 eg_init_segment_head_buffer_content 初始化缓冲区的内容。 + */ static void redo_init_seghead(Buffer buffer, const char *data) { + // 获取缓冲区描述符,对应于参数 buffer 的编号减一 BufferDesc *buf_desc = GetBufferDescriptor(buffer - 1); + // 使用 SegmentCheck 确保缓冲区的关联关系 SegmentCheck(buf_desc->tag.rnode.relNode == EXTENT_SIZE_TO_TYPE(SEGMENT_HEAD_EXTENT_SIZE)); + // 从 XLog 记录中获取 LSN XLogRecPtr lsn = *(XLogRecPtr *)(data); + // 使用 eg_init_segment_head_buffer_content 初始化缓冲区的内容 eg_init_segment_head_buffer_content(buffer, buf_desc->tag.blockNum, lsn); } - +/* + * 功能:处理重做操作中的更新段头记录 + * + * 参数列表: + * buffer:待处理的缓冲区 + * data:从 XLog 记录中提取的数据 + * + * 注意: + * 1. 该函数用于处理重做操作中的更新段头记录。 + * 2. 从 XLog 记录中获取重做数据 XLogDataUpdateSegmentHead。 + * 3. 获取缓冲区对应的段头信息。 + * 4. 检查 xlog_data 中的 nblocks 是否与段头中的 nblocks 一致,如果不一致,报错。 + * 5. 如果 xlog_data 中的 level0_slot 不小于0,将 level0_slot 位置的值更新为 xlog_data 中的 level0_value。 + * 6. 如果 xlog_data 中的 level1_slot 不小于0,将 level1_slot 位置的值更新为 xlog_data 中的 level1_value。 + * 7. 更新段头的 nextents、total_blocks 属性。 + * 8. 如果 total_blocks 为 0,表示所有 extents 都已释放,将段头刷新到磁盘上。 + */ static void redo_update_seghead(Buffer buffer, const char *data) { + // 从 XLog 记录中获取重做数据 XLogDataUpdateSegmentHead XLogDataUpdateSegmentHead *xlog_data = (XLogDataUpdateSegmentHead *)data; + // 获取缓冲区对应的段头信息 SegmentHead *head = (SegmentHead *)PageGetContents(BufferGetPage(buffer)); + // 检查 xlog_data 中的 nblocks 是否与段头中的 nblocks 一致,如果不一致,报错 if (xlog_data->nblocks != head->nblocks) { ereport(PANIC, (errmsg("redo update seghead, but target head's nblocks is %u, but should be %u according to xlog", @@ -180,174 +338,297 @@ static void redo_update_seghead(Buffer buffer, const char *data) errhint("segment-page may have bug"))); } + // 如果 xlog_data 中的 level0_slot 不小于0,将 level0_slot 位置的值更新为 xlog_data 中的 level0_value if (xlog_data->level0_slot >= 0) { head->level0_slots[xlog_data->level0_slot] = xlog_data->level0_value; } + // 如果 xlog_data 中的 level1_slot 不小于0,将 level1_slot 位置的值更新为 xlog_data 中的 level1_value if (xlog_data->level1_slot >= 0) { head->level1_slots[xlog_data->level1_slot] = xlog_data->level1_value; } + // 更新段头的 nextents、total_blocks 属性 head->nextents = xlog_data->nextents; head->total_blocks = xlog_data->total_blocks; /* If all extents is freed, flush segment header to disk */ + // 如果 total_blocks 为 0,表示所有 extents 都已释放,将段头刷新到磁盘上 if (head->total_blocks == 0) { SegmentCheck(head->nextents == 0); FlushOneSegmentBuffer(buffer); } } - +/* + * 功能:处理重做操作中的新建 level0 页记录 + * + * 参数列表: + * buffer:待处理的缓冲区 + * data:从 XLog 记录中提取的数据 + * + */ static void redo_new_level0_page(Buffer buffer, const char *data) { + // 从 XLog 记录中获取重做数据,即第一个块的块号 first_extent BlockNumber first_extent = *(BlockNumber *)data; + // 获取待处理缓冲区对应的 level0 页 Page level0_page = BufferGetPage(buffer); + // 初始化 level0 页的内容,设置页面大小为 BLCKSZ SegPageInit(level0_page, BLCKSZ); + // 更新页面头部信息 PageHeader header = (PageHeader)level0_page; header->pd_lower += sizeof(BMTLevel0Page); + // 获取 level0 页的内容,将第一个槽 slots[0] 设置为 first_extent BMTLevel0Page *bmt_level0_page = (BMTLevel0Page *)PageGetContents(level0_page); bmt_level0_page->slots[0] = first_extent; + // 设置 bmt_level0_page 的 magic 属性为 BMTLEVEL0_MAGIC bmt_level0_page->magic = BMTLEVEL0_MAGIC; } - +/* + * 功能:处理重做操作中的向 level0 页添加 extent 记录 + * + * 参数列表: + * buffer:待处理的缓冲区 + * data:从 XLog 记录中提取的数据 + */ static void redo_level0_page_add_extent(Buffer buffer, const char *data) { + // 从 XLog 记录中获取重做数据,即要添加的 extent 的槽位号和块号 XLogDataSetLevel0Page *xlog_data = (XLogDataSetLevel0Page *)data; + // 获取待处理缓冲区对应的 level0 页 BMTLevel0Page *bmt_level0_page = (BMTLevel0Page *)PageGetContents(BufferGetPage(buffer)); + // 将 level0 页中指定槽位 slots[slot] 设置为新的 extent bmt_level0_page->slots[xlog_data->slot] = xlog_data->extent; } - +/* + * 功能:处理重做操作中添加分支段的记录 + * + * 参数列表: + * buffer:待处理的缓冲区 + * data:XLog 重做记录中的数据 + */ static void redo_seghead_add_fork_segment(Buffer buffer, const char *data) { - int forknum = *(int *)data; + int forknum = *(int *)data; // 从记录数据中获取分支号 data += sizeof(int); - BlockNumber forkhead = *(BlockNumber *)data; + BlockNumber forkhead = *(BlockNumber *)data; // 从记录数据中获取分支头块号 - SegmentHead *seghead = (SegmentHead *)PageGetContents(BufferGetPage(buffer)); - seghead->fork_head[forknum] = forkhead; + SegmentHead *seghead = (SegmentHead *)PageGetContents(BufferGetPage(buffer)); // 获取缓冲区对应的段头信息 + seghead->fork_head[forknum] = forkhead; // 将分支头块号 forkhead 存储到相应分支号 forknum 的位置 } - +/* + * 功能:处理重做操作中取消链接段头指针的记录 + * + * 参数列表: + * buffer:待处理的缓冲区 + * data:XLog 重做记录中的数据 + */ static void redo_unlink_seghead_ptr(Buffer buffer, const char *data) { - off_t offset = *(off_t *)data; - + off_t offset = *(off_t *)data; // 从记录数据中获取偏移量 + // 获取缓冲区对应的数据,并计算指向偏移量的指针 BlockNumber *owner_pointer = (BlockNumber *)((char *)PageGetContents(BufferGetPage(buffer)) + offset); + // 将偏移量 offset 处的 BlockNumber 设置为 InvalidBlockNumber,取消链接段头指针 *owner_pointer = InvalidBlockNumber; } +/* + * 功能:处理重做操作中初始化桶的主头部记录 + * + * 参数列表: + * buffer:待处理的缓冲区 + * data:XLog 重做记录中的数据 + * + * 注意: + * 1. 该函数用于处理重做操作中初始化桶的主头部记录。 + * 2. 在使用此函数时,请确保传入的缓冲区是合适的,并且数据格式正确。 + */ static void redo_init_bucket_main_head(Buffer buffer, const char *data) { Page main_head_page = BufferGetPage(buffer); - SegPageInit(main_head_page, BLCKSZ); - ((PageHeader)main_head_page)->pd_lower += sizeof(BktMainHead); + SegPageInit(main_head_page, BLCKSZ); // 初始化页面 + ((PageHeader)main_head_page)->pd_lower += sizeof(BktMainHead); // 调整页面头部信息 - BktMainHead *main_head = (BktMainHead *)PageGetContents(main_head_page); - main_head->magic = BUCKET_SEGMENT_MAGIC; - main_head->lsn = *(XLogRecPtr *)data; - main_head->redis_info.redis_xid = InvalidTransactionId; - main_head->redis_info.nwords = 0; + BktMainHead *main_head = (BktMainHead *)PageGetContents(main_head_page); // 获取主头部结构指针 + main_head->magic = BUCKET_SEGMENT_MAGIC; // 设置魔术值 + main_head->lsn = *(XLogRecPtr *)data; // 从重做记录中获取LSN + main_head->redis_info.redis_xid = InvalidTransactionId; // 设置事务ID为无效 + main_head->redis_info.nwords = 0; // 初始化单词数为0 } +/* + * 功能:处理重做操作中释放桶的位图块记录 + * + * 参数列表: + * buffer:待处理的缓冲区 + * data:XLog 重做记录中的数据 + * + * 注意: + * 1. 该函数用于处理重做操作中释放桶的位图块记录。 + * 2. 在使用此函数时,请确保传入的缓冲区和数据格式正确。 + */ static void redo_bucket_free_mapblock(Buffer buffer, const char *data) { - uint32 map_id = *(uint32 *)data; - BktMainHead *main_head = (BktMainHead *)PageGetContents(BufferGetPage(buffer)); - main_head->bkt_map[map_id] = InvalidBlockNumber; + uint32 map_id = *(uint32 *)data; // 从重做记录中获取位图块的ID + BktMainHead *main_head = (BktMainHead *)PageGetContents(BufferGetPage(buffer)); // 获取主头部指针 + main_head->bkt_map[map_id] = InvalidBlockNumber; // 将位图块的ID标记为无效 } - +/* + * 功能:处理重做操作中添加桶的位图块记录 + * + * 参数列表: + * buffer:待处理的缓冲区 + * data:XLog 重做记录中的数据 + * + * 注意: + * 1. 该函数用于处理重做操作中添加桶的位图块记录。 + * 2. 在使用此函数时,请确保传入的缓冲区和数据格式正确。 + */ static void redo_bucket_add_mapblock(Buffer buffer, const char *data) { - uint32 blockid = *(uint32 *)data; + uint32 blockid = *(uint32 *)data; // 从重做记录中获取桶的ID data += sizeof(uint32); - BlockNumber mapblock = *(BlockNumber *)data; + BlockNumber mapblock = *(BlockNumber *)data; // 从重做记录中获取位图块的块号 - BktMainHead *main_head = (BktMainHead *)PageGetContents(BufferGetPage(buffer)); - main_head->bkt_map[blockid] = mapblock; + BktMainHead *main_head = (BktMainHead *)PageGetContents(BufferGetPage(buffer)); // 获取主头部指针 + main_head->bkt_map[blockid] = mapblock; // 将桶的位图块关联起来 } +/* + * 功能:处理重做操作中初始化桶的位图块记录 + * + * 参数列表: + * buffer:待处理的缓冲区 + * data:XLog 重做记录中的数据 + */ static void redo_bucket_init_mapblock(Buffer buffer, const char *data) { - XLogRecPtr lsn = *(XLogRecPtr *)data; - bucket_init_map_page(buffer, lsn); + XLogRecPtr lsn = *(XLogRecPtr *)data; // 从重做记录中获取LSN + bucket_init_map_page(buffer, lsn); // 调用 bucket_init_map_page 函数进行位图块初始化 } +/* + * 功能:处理重做操作中添加桶头映射块的记录 + * + * 参数列表: + * buffer:待处理的缓冲区 + * data:XLog 重做记录中的数据 + */ static void redo_bucket_add_bkthead(Buffer buffer, const char *data) { - int map_entry_id = *(int *)data; + int map_entry_id = *(int *)data; // 从记录数据中获取映射条目ID data += sizeof(int); - BlockNumber head_block = *(BlockNumber *)data; + BlockNumber head_block = *(BlockNumber *)data; // 从记录数据中获取头块号 BktHeadMapBlock *map_block = (BktHeadMapBlock *)PageGetContents(BufferGetPage(buffer)); - map_block->head_block[map_entry_id] = head_block; + map_block->head_block[map_entry_id] = head_block; // 将映射条目ID对应的头块号设置到映射块中 } - +/* + * 功能:处理重做操作中更新空间高水位标记的记录 + * + * 参数列表: + * buffer:待处理的缓冲区 + * data:XLog 重做记录中的数据 + */ static void redo_space_update_hwm(Buffer buffer, const char *data) { XLogDataUpdateSpaceHWM *xlog_data = (XLogDataUpdateSpaceHWM *)data; df_map_head_t *map_head = (df_map_head_t *)PageGetContents(BufferGetPage(buffer)); + // 检查旧高水位标记是否与记录中的值一致 if (map_head->high_water_mark != xlog_data->old_hwm) { ereport(PANIC, (errmsg("update space high water mark, old hwm is %u, but should be %u according to xlog", map_head->high_water_mark, xlog_data->old_hwm), errhint("segment-page may have bug"))); } + // 检查旧映射组数是否与记录中的值一致 if (map_head->group_count != xlog_data->old_groupcnt) { ereport(PANIC, (errmsg("update map group count, old count is %u, but should be %u according to xlog", - map_head->group_count, xlog_data->old_groupcnt))); + map_head->group_count, xlog_data->old_groupcnt))); } + // 更新高水位标记和映射组数为记录中的新值 map_head->high_water_mark = xlog_data->new_hwm; map_head->group_count = xlog_data->new_groupcnt; } - +/* + * 功能:处理重做操作中设置反向指针的记录 + * + * 参数列表: + * buffer:待处理的缓冲区 + * data:XLog 重做记录中的数据 + */ static void redo_set_inverse_pointer(Buffer buffer, const char *data) { - uint32 offset = *(uint32 *)data; + uint32 offset = *(uint32 *)data; // 从记录数据中获取偏移量 data += sizeof(uint32); - ExtentInversePointer iptr = *(ExtentInversePointer *)data; + ExtentInversePointer iptr = *(ExtentInversePointer *)data; // 从记录数据中获取反向指针 ExtentInversePointer *eips = (ExtentInversePointer *)PageGetContents(BufferGetBlock(buffer)); - eips[offset] = iptr; + eips[offset] = iptr; // 将反向指针设置到指定偏移量的位置 } - +/* + * 功能:处理重做操作中缩减段头更新记录 + * + * 参数列表: + * buffer:待处理的缓冲区 + * data:XLog 重做记录中的数据 + */ static void redo_shrink_seghead_update(Buffer buffer, const char *data) { - XLogMoveExtent *xlog_data = (XLogMoveExtent *)data; - SegmentHead *seghead = (SegmentHead *)PageGetContents(BufferGetBlock(buffer)); - int extent_id = xlog_data->extent_id; + XLogMoveExtent *xlog_data = (XLogMoveExtent *)data; // 获取XLog记录中的数据 + SegmentHead *seghead = (SegmentHead *)PageGetContents(BufferGetBlock(buffer)); // 获取缓冲区对应的段头 + int extent_id = xlog_data->extent_id; // 获取要更新的Extent的ID if (extent_id < BMT_HEADER_LEVEL0_SLOTS) { /* Level0 extent, needs updating segment head */ - SegmentCheck(seghead->level0_slots[extent_id] == xlog_data->old_extent); - seghead->level0_slots[extent_id] = xlog_data->new_extent; + SegmentCheck(seghead->level0_slots[extent_id] == xlog_data->old_extent); // 检查旧的Extent是否匹配 + seghead->level0_slots[extent_id] = xlog_data->new_extent; // 更新段头中Level0 extent的信息 } + // 在SEGMENT_REDO_UPDATE_SEGHEAD测试模式下记录错误信息 SEGMENTTEST(SEGMENT_REDO_UPDATE_SEGHEAD, (errmsg("error happens when replaying segment head update in shrink"))); } #define REL_NODE_FORMAT(rnode) rnode.spcNode, rnode.dbNode, rnode.relNode, rnode.bucketNode /* create hash table using shared memory when first needed */ -struct HTAB* redo_create_remain_segs_htbl() +struct HTAB *redo_create_remain_segs_htbl() { HASHCTL ctl; - + + // 初始化哈希表控制结构 errno_t errorno = memset_s(&ctl, sizeof(ctl), 0, sizeof(ctl)); securec_check(errorno, "", ""); - - ctl.keysize = sizeof(RemainExtentHashTag); - ctl.entrysize = sizeof(ExtentTag); - ctl.hash = tag_hash; - int flag = HASH_ELEM | HASH_FUNCTION; - + + ctl.keysize = sizeof(RemainExtentHashTag); // 设置键的大小 + ctl.entrysize = sizeof(ExtentTag); // 设置条目的大小 + ctl.hash = tag_hash; // 设置哈希函数 + int flag = HASH_ELEM | HASH_FUNCTION; // 标记为哈希元素和哈希函数 + + // 使用HeapMemInitHash函数创建并初始化哈希表 return HeapMemInitHash("remain_segs", 1000, DF_MAP_GROUP_EXTENTS, &ctl, flag); } - +/* + * 功能:处理重做操作中的分配段日志记录 + * + * 参数列表: + * buffer:待处理的缓冲区 + * xid:分配段的事务 ID + * + * 注意: + * 1. 这个函数用于处理重做操作中的分配段日志记录。 + * 2. 函数首先获取重做操作对应的缓冲区描述符。 + * 3. 然后构建一个 RemainExtentHashTag 结构,用于标识剩余段的哈希表条目。 + * 4. 接下来,函数尝试在哈希表中查找该条目,如果找到表示该段已存在,输出警告信息。 + * 5. 如果未找到,将该段信息添加到哈希表中,包括事务 ID、剩余段类型、fork 号和 LSN。 + * 6. 最后,释放锁。 + */ static void redo_xlog_log_alloc_seg(Buffer buffer, TransactionId xid) { AutoMutexLock remainSegsLock(&g_instance.xlog_cxt.remain_segs_lock); remainSegsLock.lock(); Assert(TransactionIdIsValid(xid)); - + if (t_thrd.xlog_cxt.remain_segs == NULL) { t_thrd.xlog_cxt.remain_segs = redo_create_remain_segs_htbl(); } @@ -358,28 +639,44 @@ static void redo_xlog_log_alloc_seg(Buffer buffer, TransactionId xid) remainExtentHashTag.rnode = bufDesc->tag.rnode; remainExtentHashTag.rnode.relNode = bufDesc->tag.blockNum; remainExtentHashTag.extentType = bufDesc->tag.rnode.relNode; - + bool found = false; - ExtentTag* extentTag = (ExtentTag *)hash_search(t_thrd.xlog_cxt.remain_segs, (void *)&remainExtentHashTag, - HASH_ENTER, &found); + ExtentTag *extentTag = + (ExtentTag *)hash_search(t_thrd.xlog_cxt.remain_segs, (void *)&remainExtentHashTag, HASH_ENTER, &found); if (found) { - ereport(WARNING, (errmsg("Segment [%u, %u, %u, %d] already existed in remain segs, Xid %lu," - "remainExtentType %u.", REL_NODE_FORMAT(remainExtentHashTag.rnode), extentTag->xid, - extentTag->remainExtentType))); + ereport(WARNING, + (errmsg("Segment [%u, %u, %u, %d] already existed in remain segs, Xid %lu," + "remainExtentType %u.", + REL_NODE_FORMAT(remainExtentHashTag.rnode), extentTag->xid, extentTag->remainExtentType))); } else { extentTag->remainExtentType = ALLOC_SEGMENT; extentTag->xid = xid; extentTag->forkNum = InvalidForkNumber; extentTag->lsn = InvalidXLogRecPtr; - + ereport(DEBUG5, (errmodule(MOD_SEGMENT_PAGE), errmsg("Segment [%u, %u, %u, %d] is alloced, cur xid %lu.", - REL_NODE_FORMAT(remainExtentHashTag.rnode), xid))); + REL_NODE_FORMAT(remainExtentHashTag.rnode), xid))); } remainSegsLock.unLock(); } - -static void redo_xlog_forget_alloc_seg(Buffer buffer, const char* data, int data_len) +/* + * 功能:处理重做操作中忘记分配段的记录 + * + * 参数列表: + * buffer:待处理的缓冲区 + * data:XLog 重做记录中的数据 + * data_len:数据长度 + * + * 注意: + * 1. 函数首先检查数据长度是否符合预期,如果不符合则返回。 + * 2. 然后从数据中获取块号,根据块号构建一个 RemainExtentHashTag 结构,用于标识剩余段的哈希表条目。 + * 3. 获取剩余段的哈希表锁,并尝试在哈希表中查找该条目,如果找到表示该段已释放,输出调试信息。 + * 4. 如果未找到,输出调试信息表示未找到该段。 + * 5. 最后,释放锁。 + */ +static void redo_xlog_forget_alloc_seg(Buffer buffer, const char *data, int data_len) { + // 首先检查数据长度是否符合预期,如果不符合则返回。 int first_part_data_len = sizeof(uint16) + sizeof(df_map_page_t); if (data_len <= first_part_data_len) { Assert(data_len == first_part_data_len); @@ -387,14 +684,14 @@ static void redo_xlog_forget_alloc_seg(Buffer buffer, const char* data, int data } Assert(data_len == (sizeof(uint16) + sizeof(df_map_page_t) + sizeof(BlockNumber))); - BlockNumber* blk_num = (BlockNumber *)(data + sizeof(uint16) + sizeof(df_map_page_t)); + BlockNumber *blk_num = (BlockNumber *)(data + sizeof(uint16) + sizeof(df_map_page_t)); BufferDesc *bufDesc = GetBufferDescriptor(buffer - 1); RemainExtentHashTag remainExtentHashTag; remainExtentHashTag.rnode = bufDesc->tag.rnode; remainExtentHashTag.rnode.relNode = *blk_num; remainExtentHashTag.extentType = bufDesc->tag.rnode.relNode; - + AutoMutexLock remain_segs_lock(&g_instance.xlog_cxt.remain_segs_lock); remain_segs_lock.lock(); if (t_thrd.xlog_cxt.remain_segs == NULL) { @@ -402,26 +699,40 @@ static void redo_xlog_forget_alloc_seg(Buffer buffer, const char* data, int data } bool found = false; - ExtentTag* extentTag = (ExtentTag *)hash_search(t_thrd.xlog_cxt.remain_segs, (void *)&(remainExtentHashTag), - HASH_REMOVE, &found); + // 在哈希表中查找该条目 + ExtentTag *extentTag = + (ExtentTag *)hash_search(t_thrd.xlog_cxt.remain_segs, (void *)&(remainExtentHashTag), HASH_REMOVE, &found); + // 如果找到,表示该段已释放,输出调试信息 if (found) { ereport(DEBUG5, (errmodule(MOD_SEGMENT_PAGE), errmsg("Segment [%u, %u, %u, %d] is really freed after" - "drop trxn committed, xid %lu, remainExtentType %u.", REL_NODE_FORMAT(remainExtentHashTag.rnode), - extentTag->xid, extentTag->remainExtentType))); + "drop trxn committed, xid %lu, remainExtentType %u.", + REL_NODE_FORMAT(remainExtentHashTag.rnode), extentTag->xid, + extentTag->remainExtentType))); } else { + // 如果未找到,输出调试信息表示未找到该段 ereport(DEBUG5, (errmodule(MOD_SEGMENT_PAGE), errmsg("Segment [%u, %u, %u, %d] is not found" - "in remain segs htbl.", REL_NODE_FORMAT(remainExtentHashTag.rnode)))); + "in remain segs htbl.", + REL_NODE_FORMAT(remainExtentHashTag.rnode)))); } + // 释放哈希表锁 remain_segs_lock.unLock(); } - -static void redo_xlog_log_shrink_extent(Buffer buffer, const char* data) +/* + * 功能:处理重做操作中收缩段的记录 + * + * 参数列表: + * buffer:待处理的缓冲区 + * data:XLog 重做记录中的数据 + */ +static void redo_xlog_log_shrink_extent(Buffer buffer, const char *data) { XLogMoveExtent *xlog_data = (XLogMoveExtent *)data; SegmentCheck(xlog_data->old_extent != InvalidBlockNumber); + // 获取剩余段哈希表锁,确保线程安全 AutoMutexLock remain_segs_lock(&g_instance.xlog_cxt.remain_segs_lock); remain_segs_lock.lock(); + // 如果剩余段哈希表不存在,则创建一个新的哈希表 if (t_thrd.xlog_cxt.remain_segs == NULL) { t_thrd.xlog_cxt.remain_segs = redo_create_remain_segs_htbl(); } @@ -436,120 +747,186 @@ static void redo_xlog_log_shrink_extent(Buffer buffer, const char* data) remainExtentHashTag.rnode.bucketNode = SegmentBktId; /* extentType is calculated by extent id */ remainExtentHashTag.extentType = EXTENT_SIZE_TO_TYPE(ExtentSizeByCount(xlog_data->extent_id)); - - ExtentTag* extentTag = (ExtentTag *)hash_search(t_thrd.xlog_cxt.remain_segs, (void *)&remainExtentHashTag, - HASH_ENTER, &found); + + // 在哈希表中查找该条目,如果找到,输出警告信息表示已找到该段 + ExtentTag *extentTag = + (ExtentTag *)hash_search(t_thrd.xlog_cxt.remain_segs, (void *)&remainExtentHashTag, HASH_ENTER, &found); if (found) { - ereport(WARNING, (errmsg("Segment [%u, %u, %u, %d] Extent %u should not be repeatedly founded, xid %lu," - "remainExtentType %u", REL_NODE_FORMAT(remainExtentHashTag.rnode), xlog_data->old_extent, extentTag->xid, - extentTag->remainExtentType))); + ereport(WARNING, (errmsg("Segment [%u, %u, %u, %d] Extent %u should not be repeatedly founded, xid %lu," + "remainExtentType %u", + REL_NODE_FORMAT(remainExtentHashTag.rnode), xlog_data->old_extent, extentTag->xid, + extentTag->remainExtentType))); } else { + // 如果未找到,将该段的信息添加到哈希表,并标记为 SHRINK_EXTENT 类型 extentTag->remainExtentType = SHRINK_EXTENT; extentTag->forkNum = xlog_data->forknum; - + extentTag->xid = InvalidTransactionId; extentTag->lsn = InvalidXLogRecPtr; - ereport(DEBUG5, (errmodule(MOD_SEGMENT_PAGE), errmsg("Segment [%u, %u, %u, %d] Extent %u is replaced " - "during shrinking in shrink extents.", REL_NODE_FORMAT(remainExtentHashTag.rnode), xlog_data->old_extent))); + ereport(DEBUG5, (errmodule(MOD_SEGMENT_PAGE), + errmsg("Segment [%u, %u, %u, %d] Extent %u is replaced " + "during shrinking in shrink extents.", + REL_NODE_FORMAT(remainExtentHashTag.rnode), xlog_data->old_extent))); } + // 释放哈希表锁 remain_segs_lock.unLock(); } - -void redo_xlog_deal_alloc_seg(uint8 opCode, Buffer buffer, const char* data, int data_len, TransactionId xid) +/* + * 处理重做操作中的分配段和释放段的记录 + * + * 参数列表: + * opCode:操作码,指示重做记录的类型 + * buffer:待处理的缓冲区 + * data:XLog 重做记录中的数据 + * data_len:XLog 重做记录数据的长度 + * xid:事务 ID + */ +void redo_xlog_deal_alloc_seg(uint8 opCode, Buffer buffer, const char *data, int data_len, TransactionId xid) { if (opCode == SPCXLOG_INIT_SEGHEAD) { + // 检查是否是初始化段头记录 unsigned char is_seg_head = *(unsigned char *)(data + sizeof(XLogRecPtr)); if (is_seg_head == 0) { return; } + // 调用 redo_xlog_log_alloc_seg 处理初始化段头的记录 redo_xlog_log_alloc_seg(buffer, xid); } else if (opCode == SPCXLOG_INIT_BUCKET_HEAD) { + // 处理初始化桶头记录,调用 redo_xlog_log_alloc_seg 处理 redo_xlog_log_alloc_seg(buffer, xid); } else if (opCode == SPCXLOG_FREE_BITMAP) { + // 处理释放段的记录,调用 redo_xlog_forget_alloc_seg 处理 redo_xlog_forget_alloc_seg(buffer, data, data_len); } else if (opCode == SPCXLOG_SHRINK_SEGHEAD_UPDATE) { + // 处理分配段的记录,调用 redo_xlog_log_shrink_extent 处理 redo_xlog_log_shrink_extent(buffer, data); } } - +/* + * 功能:处理原子重做日志分发 + * + * 参数列表: + * opCode:重做操作的代码 + * redo_buf:重做缓冲区信息 + * data:XLog 重做记录中的数据 + * + * 注意: + * 1. 该函数用于根据重做操作的代码分发不同的处理函数。 + * 2. 根据 opCode 的不同,调用相应的处理函数来处理重做操作。 + */ void redo_atomic_xlog_dispatch(uint8 opCode, RedoBufferInfo *redo_buf, const char *data) { Buffer buffer = redo_buf->buf; + /* 输出调试信息,显示 opCode */ ereport(DEBUG5, (errmodule(MOD_SEGMENT_PAGE), errmsg("redo_atomic_xlog_dispatch opCode: %u", opCode))); if (opCode == SPCXLOG_SET_BITMAP) { + /* 处理设置位图的重做操作 */ redo_set_bitmap(redo_buf->blockinfo, buffer, data); } else if (opCode == SPCXLOG_FREE_BITMAP) { + /* 处理设置位图的重做操作 */ redo_unset_bitmap(buffer, data); } else if (opCode == SPCXLOG_MAPHEAD_ALLOCATED_EXTENTS) { + /* 处理映射头分配扩展的重做操作 */ redo_maphead_allocated_extents(buffer, data); } else if (opCode == SPCXLOG_INIT_SEGHEAD) { + /* 处理初始化段头的重做操作 */ redo_init_seghead(buffer, data); } else if (opCode == SPCXLOG_UPDATE_SEGHEAD) { + /* 处理更新段头的重做操作 */ redo_update_seghead(buffer, data); } else if (opCode == SPCXLOG_NEW_LEVEL0_PAGE) { + /* 处理创建新的 Level0 页的重做操作 */ redo_new_level0_page(buffer, data); } else if (opCode == SPCXLOG_LEVEL0_PAGE_ADD_EXTENT) { + /* 处理 Level0 页添加扩展的重做操作 */ redo_level0_page_add_extent(buffer, data); } else if (opCode == SPCXLOG_SEGHEAD_ADD_FORK_SEGMENT) { + /* 处理段头添加分段的重做操作 */ redo_seghead_add_fork_segment(buffer, data); } else if (opCode == SPCXLOG_UNLINK_SEGHEAD_PTR) { + /* 处理取消链接段头指针的重做操作 */ redo_unlink_seghead_ptr(buffer, data); } else if (opCode == SPCXLOG_INIT_BUCKET_HEAD) { + /* 处理初始化桶主头的重做操作 */ redo_init_bucket_main_head(buffer, data); } else if (opCode == SPCXLOG_BUCKET_FREE_MAPBLOCK) { + /* 处理释放桶地图块的重做操作 */ redo_bucket_free_mapblock(buffer, data); } else if (opCode == SPCXLOG_BUCKET_ADD_MAPBLOCK) { + /* 处理添加桶地图块的重做操作 */ redo_bucket_add_mapblock(buffer, data); } else if (opCode == SPCXLOG_BUCKET_INIT_MAPBLOCK) { + /* 处理初始化桶地图块的重做操作 */ redo_bucket_init_mapblock(buffer, data); } else if (opCode == SPCXLOG_BUCKET_ADD_BKTHEAD) { + /* 处理添加桶主头的重做操作 */ redo_bucket_add_bkthead(buffer, data); } else if (opCode == SPCXLOG_SPACE_UPDATE_HWM) { + /* 处理更新空间高水位标记的重做操作 */ redo_space_update_hwm(buffer, data); } else if (opCode == SPCXLOG_SET_INVERSE_POINTER) { + /* 处理设置逆向指针的重做操作 */ redo_set_inverse_pointer(buffer, data); } else if (opCode == SPCXLOG_SEG_MOVE_BUCKETS) { + /* 处理段移动桶的重做操作 */ redo_move_buckets(buffer, data); } else if (opCode == SPCXLOG_BUCKET_ADD_REDISINFO) { + /* 处理添加桶 Redis 信息的重做操作 */ redo_bucket_add_redisinfo(buffer, data); } else { + /* 默认处理收缩段头更新的重做操作 */ SegmentCheck(opCode == SPCXLOG_SHRINK_SEGHEAD_UPDATE); redo_shrink_seghead_update(buffer, data); } } - +/* + * 刷新移动的段到磁盘 + * + * 参数列表: + * xlog_data:重做记录中的移动段数据 + */ void move_extent_flush_buffer(XLogMoveExtent *xlog_data) { BlockNumber logic_start = ExtentIdToLogicBlockNum(xlog_data->extent_id); - for (int i=0; iextent_id); i++) { + for (int i = 0; i < ExtentSizeByCount(xlog_data->extent_id); i++) { BlockNumber blk = logic_start + i; + // 如果块号超过了段的块数,跳出循环 if (blk >= xlog_data->nblocks) { break; } + // 尝试获取移动后的页面缓冲区 Buffer buffer = try_get_moved_pagebuf(&xlog_data->logic_rnode, xlog_data->forknum, blk); if (BufferIsValid(buffer)) { BlockNumber old_seg_blockno = xlog_data->old_extent + i; BlockNumber new_seg_blockno = xlog_data->new_extent + i; BufferDesc *buf_desc = BufferGetBufferDescriptor(buffer); + // 如果缓冲区的段块号等于旧段块号 if (buf_desc->seg_blockno == old_seg_blockno) { uint32 buf_state = LockBufHdr(buf_desc); + // 如果缓冲区标记为脏 if (buf_state & BM_DIRTY) { /* spin-lock should be released before IO */ + // 释放自旋锁以允许 IO 操作 UnlockBufHdr(buf_desc, buf_state); + // 锁定缓冲区以进行独占刷新 LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE); /* Flush data to the old block */ + // 刷新数据到旧块 FlushOneBufferIncludeDW(buf_desc); + // 解锁缓冲区 LockBuffer(buffer, BUFFER_LOCK_UNLOCK); } else { UnlockBufHdr(buf_desc, buf_state); } /* It's dirty, but we must unpin buffer before InvalidateBuffer */ + // 在无效化缓冲区之前,必须解除缓冲区的引用 UnpinBuffer(buf_desc, true); buf_state = LockBufHdr(buf_desc); + // 如果缓冲区的节点信息匹配 if (RelFileNodeEquals(buf_desc->tag.rnode, xlog_data->logic_rnode) && buf_desc->tag.forkNum == xlog_data->forknum && buf_desc->tag.blockNum == blk) { InvalidateBuffer(buf_desc); @@ -558,6 +935,7 @@ void move_extent_flush_buffer(XLogMoveExtent *xlog_data) } } else { /* Get here only because standby read after we modifiy the segment head */ + // 只有在主备模式下读取到我们修改段头后才会执行到这里 SegmentCheck(buf_desc->seg_blockno == new_seg_blockno); UnpinBuffer(buf_desc, true); } @@ -607,7 +985,7 @@ static void redo_atomic_xlog(XLogReaderState *record) PageSetLSN(redo_buf.pageinfo.page, redo_buf.lsn); SegMarkBufferDirty(redo_buf.buf); } - + for (int j = 0; j < decoded_op.operations; j++) { if (decoded_op.op[j] == SPCXLOG_SHRINK_SEGHEAD_UPDATE) { need_flush_buffer_for_shrink = true; @@ -615,8 +993,7 @@ static void redo_atomic_xlog(XLogReaderState *record) } if (is_need_log_remain_segs) { - redo_xlog_deal_alloc_seg(decoded_op.op[j], redo_buf.buf, - decoded_op.data[j], decoded_op.data_len[j], + redo_xlog_deal_alloc_seg(decoded_op.op[j], redo_buf.buf, decoded_op.data[j], decoded_op.data_len[j], XLogRecGetXid(record)); } } @@ -795,7 +1172,6 @@ void seg_redo_new_page_copy_and_flush(BufferTag *tag, char *data, XLogRecPtr lsn t_thrd.proc->dw_pos = -1; } - /* * This xlog only copy data to the new block, without modifying data in buffer. If the logic block being in the * buffer pool, its pblk points to the old block. The buffer descriptor can not have the logic blocknumber and the new diff --git a/src/gausskernel/storage/smgr/segstore.cpp b/src/gausskernel/storage/smgr/segstore.cpp index f2ddbfc34..5c6ec005a 100755 --- a/src/gausskernel/storage/smgr/segstore.cpp +++ b/src/gausskernel/storage/smgr/segstore.cpp @@ -94,14 +94,25 @@ * 1. drop table. * 2. delete a list of buckets during redistributing. */ +/* + * 功能:更新共享内存段的时间轴信息。 + */ static void seg_update_timeline() { - pg_atomic_add_fetch_u32(&g_instance.segment_cxt.segment_drop_timeline, 1); + // 使用 pg_atomic_add_fetch_u32 函数来原子地增加一个32位无符号整数的值 + pg_atomic_add_fetch_u32(&g_instance.segment_cxt.segment_drop_timeline, 1); // 原子操作函数 } - +/* + * 功能:获取共享内存段的时间轴信息 + */ static uint32 seg_get_drop_timeline() { + // 创建一个名为 expected 的本地变量,初始化为 0 uint32 expected = 0; + // 创建一个名为 expected 的本地变量,初始化为 0 + // 这个函数的目的是将 g_instance.segment_cxt.segment_drop_timeline 的值与 expected 进行比较 + // 如果相等,则将 g_instance.segment_cxt.segment_drop_timeline 的值设置为 0 + // 这个操作是原子的,确保在多线程或多进程环境中的一致性 pg_atomic_compare_exchange_u32(&g_instance.segment_cxt.segment_drop_timeline, &expected, 0); return expected; } @@ -111,29 +122,55 @@ struct SegmentHeadLockPartitionTag { Oid dbNode; BlockNumber head; }; - +/* + * 功能:锁定分段头部的特定分区,以进行同步访问 + * + * 参数列表: + * spcNode:表示表空间节点的唯一标识符 + * dbNode:表示数据库节点的唯一标识符 + * head:表示分段头部的块号 + * mode:表示锁的模式,例如共享锁或排他锁 + */ void LockSegmentHeadPartition(Oid spcNode, Oid dbNode, BlockNumber head, LWLockMode mode) { + // 创建一个 SegmentHeadLockPartitionTag 结构体 tag,并初始化其成员变量。 SegmentHeadLockPartitionTag tag = {.spcNode = spcNode, .dbNode = dbNode, head = head}; + + // 使用哈希函数 hashquickany 计算一个哈希码,以便用于锁的选择。 uint32 hashcode = hashquickany(0xFFFFFFFF, (unsigned char *)&tag, sizeof(tag)); + + // 根据哈希码选择一个特定的 LWLock。 LWLock *lock = SegmentHeadPartitionLock(hashcode); + + // 获取指定的 LWLock,使用给定的锁模式 mode。 LWLockAcquire(lock, mode); } - +/* + * 功能:解锁分段头部的特定分区,以允许其他进程或线程访问 + * + * 参数列表: + * spcNode:表示表空间节点的唯一标识符 + * dbNode:表示数据库节点的唯一标识符 + * head:表示分段头部的块号 + */ void UnlockSegmentHeadPartition(Oid spcNode, Oid dbNode, BlockNumber head) { + // 创建一个 SegmentHeadLockPartitionTag 结构体 tag,并初始化其成员变量。 SegmentHeadLockPartitionTag tag = {.spcNode = spcNode, .dbNode = dbNode, head = head}; + // 使用哈希函数 hashquickany 计算一个哈希码,以便用于锁的选择。 uint32 hashcode = hashquickany(0xFFFFFFFF, (unsigned char *)&tag, sizeof(tag)); + // 根据哈希码选择一个特定的 LWLock。 LWLock *lock = SegmentHeadPartitionLock(hashcode); + // 释放指定的 LWLock,允许其他进程或线程访问。 LWLockRelease(lock); } #define IsBucketSMgrRelation(reln) IsBucketFileNode((reln)->smgr_rnode.node) -#define ReadSegmentBuffer(spc, blockno) \ +#define ReadSegmentBuffer(spc, blockno) \ ReadBufferFast((spc), EXTENT_GROUP_RNODE((spc), SEGMENT_HEAD_EXTENT_SIZE), MAIN_FORKNUM, (blockno), RBM_NORMAL) -#define ReadLevel0Buffer(spc, blockno) \ +#define ReadLevel0Buffer(spc, blockno) \ ReadBufferFast((spc), EXTENT_GROUP_RNODE((spc), LEVEL0_PAGE_EXTENT_SIZE), MAIN_FORKNUM, (blockno), RBM_NORMAL) /* @@ -165,20 +202,51 @@ inline static void SegLogicPageIdToExtentId(BlockNumber logic_id, uint32 *extent *offset = logic_id % EXT_SIZE_8192; } } - +/* + * 功能:记录分段中的桶移动操作到XLOG + * + * 参数列表: + * mapentry:指向 xl_seg_bktentry_tag_t 结构的指针数组,表示桶移动的信息 + * nentry:表示 mapentry 数组中的条目数量 + * buffer:表示要记录的缓冲区 + */ void log_move_segment_buckets(xl_seg_bktentry_tag_t *mapentry, uint32 nentry, Buffer buffer) { + // 使用 XLogAtomicOpRegisterBuffer 函数来注册一个缓冲区以记录 XLOG 信息。 + // 参数包括要注册的缓冲区,指定不记录缓冲区图像,指定记录的操作类型,以及指定在提交时解锁并释放缓冲区。 XLogAtomicOpRegisterBuffer(buffer, REGBUF_NO_IMAGE, SPCXLOG_SEG_MOVE_BUCKETS, XLOG_COMMIT_UNLOCK_RELEASE_BUFFER); + + // 使用 XLogAtomicOpRegisterBufData 函数来注册一个或多个数据块以记录在 XLOG 中。 + // 在这里,首先注册 nentry 的值,表示后续记录的 mapentry 数组的条目数量。 XLogAtomicOpRegisterBufData((char *)&nentry, sizeof(uint32)); + + // 接下来,注册 mapentry 数组的数据,数据的长度为 sizeof(xl_seg_bktentry_tag_t) * nentry。 XLogAtomicOpRegisterBufData((char *)mapentry, sizeof(xl_seg_bktentry_tag_t) * nentry); } - +/* + * 功能:记录分段的Redis信息到XLOG + * + * 参数列表: + * dredis:指向 SegRedisInfo 结构的指针,表示目标分段的Redis信息 + * sredis:指向 SegRedisInfo 结构的指针,表示源分段的Redis信息 + * dbuffer:表示目标分段的缓冲区 + * sbuffer:表示源分段的缓冲区 + */ void log_move_segment_redisinfo(SegRedisInfo *dredis, SegRedisInfo *sredis, Buffer dbuffer, Buffer sbuffer) { - XLogAtomicOpRegisterBuffer(dbuffer, REGBUF_NO_IMAGE, SPCXLOG_BUCKET_ADD_REDISINFO, XLOG_COMMIT_UNLOCK_RELEASE_BUFFER); + // 使用 XLogAtomicOpRegisterBuffer 函数来注册目标分段的缓冲区以记录 XLOG 信息。 + // 参数包括要注册的缓冲区,指定不记录缓冲区图像,指定记录的操作类型,以及指定在提交时解锁并释放缓冲区。 + XLogAtomicOpRegisterBuffer(dbuffer, REGBUF_NO_IMAGE, SPCXLOG_BUCKET_ADD_REDISINFO, + XLOG_COMMIT_UNLOCK_RELEASE_BUFFER); + + // 使用 XLogAtomicOpRegisterBufData 函数来注册目标分段的 Redis 信息的数据块。 XLogAtomicOpRegisterBufData((char *)dredis, sizeof(SegRedisInfo)); - XLogAtomicOpRegisterBuffer(sbuffer, REGBUF_NO_IMAGE, SPCXLOG_BUCKET_ADD_REDISINFO, XLOG_COMMIT_UNLOCK_RELEASE_BUFFER); + // 使用 XLogAtomicOpRegisterBuffer 函数来注册源分段的缓冲区以记录 XLOG 信息。 + XLogAtomicOpRegisterBuffer(sbuffer, REGBUF_NO_IMAGE, SPCXLOG_BUCKET_ADD_REDISINFO, + XLOG_COMMIT_UNLOCK_RELEASE_BUFFER); + + // 使用 XLogAtomicOpRegisterBufData 函数来注册源分段的 Redis 信息的数据块。 XLogAtomicOpRegisterBufData((char *)sredis, sizeof(SegRedisInfo)); } @@ -190,22 +258,33 @@ RelFileNode EXTENT_GROUP_RNODE(SegSpace *spc, ExtentSize extentSize) .bucketNode = SegmentBktId, .opt = 0}; } - -void seg_head_update_xlog(Buffer head_buffer, SegmentHead *seg_head, int level0_slot, - int level1_slot) +/* + * 功能:记录更新分段头部信息的XLOG + * + * 参数列表: + * head_buffer:表示分段头部的缓冲区 + * seg_head:指向 SegmentHead 结构的指针,表示分段头部信息 + * level0_slot:表示要更新的 level 0 插槽的索引,如果不需要更新,则为负数 + * level1_slot:表示要更新的 level 1 插槽的索引,如果不需要更新,则为负数 + */ +void seg_head_update_xlog(Buffer head_buffer, SegmentHead *seg_head, int level0_slot, int level1_slot) { + // 创建 XLogDataUpdateSegmentHead 结构体 xlog_data,并将其初始化为零 XLogDataUpdateSegmentHead xlog_data; errno_t rc = memset_s(&xlog_data, sizeof(xlog_data), 0, sizeof(xlog_data)); securec_check(rc, "\0", "\0"); + // 设置要记录到 XLOG 的数据 xlog_data.level0_slot = level0_slot; xlog_data.level1_slot = level1_slot; + // 如果 level0_slot 大于等于0,则记录 seg_head 中对应索引的 level 0 插槽的值,否则设置为 InvalidBlockNumber if (level0_slot >= 0) { xlog_data.level0_value = seg_head->level0_slots[level0_slot]; } else { xlog_data.level0_value = InvalidBlockNumber; } + // 如果 level1_slot 大于等于0,则记录 seg_head 中对应索引的 level 1 插槽的值,否则设置为 InvalidBlockNumber if (level1_slot >= 0) { xlog_data.level1_value = seg_head->level1_slots[level1_slot]; } else { @@ -221,28 +300,43 @@ void seg_head_update_xlog(Buffer head_buffer, SegmentHead *seg_head, int level0_ XLogAtomicOpRegisterBuffer(head_buffer, REGBUF_KEEP_DATA, SPCXLOG_UPDATE_SEGHEAD, XLOG_COMMIT_KEEP_BUFFER_STATE); XLogAtomicOpRegisterBufData((char *)&xlog_data, sizeof(xlog_data)); } - +/* + * 功能:初始化新的 Level 0 页并记录到XLOG + * + * 参数列表: + * spc:指向 SegSpace 结构的指针,表示分段空间信息 + * new_extent_id:新的范围页扩展标识符 + * seg_head_buffer:表示分段头部的缓冲区 + * new_level0_page:新的 Level 0 页号 + * first_extent:第一个范围页号 + */ void seg_init_new_level0_page(SegSpace *spc, uint32_t new_extent_id, Buffer seg_head_buffer, BlockNumber new_level0_page, BlockNumber first_extent) { + // 从 seg_head_buffer 中获取分段头部信息 SegmentHead *seg_head = (SegmentHead *)PageGetContents(BufferGetPage(seg_head_buffer)); + // 读取新的 Level 0 缓冲区并以排他锁方式锁定它 Buffer new_level0_buffer = ReadLevel0Buffer(spc, new_level0_page); LockBuffer(new_level0_buffer, BUFFER_LOCK_EXCLUSIVE); Page level0_page = BufferGetPage(new_level0_buffer); + // 初始化 Level 0 页 SegPageInit(level0_page, BLCKSZ); PageHeader header = (PageHeader)level0_page; header->pd_lower += sizeof(BMTLevel0Page); + // 获取 Level 0 页的内容 BMTLevel0Page *bmt_level0_page = (BMTLevel0Page *)PageGetContents(level0_page); bmt_level0_page->slots[0] = first_extent; bmt_level0_page->magic = BMTLEVEL0_MAGIC; + // 计算新范围页的 Level 1 插槽 uint32 level1_slot = ExtentIdToLevel1Slot(new_extent_id); seg_head->level1_slots[level1_slot] = new_level0_page; // XLog issue + // 处理 XLog 问题 { // new level0 page, we do not use level0 page buffer anymore, release it. XLogAtomicOpRegisterBuffer(new_level0_buffer, REGBUF_WILL_INIT | REGBUF_KEEP_DATA, SPCXLOG_NEW_LEVEL0_PAGE, @@ -253,20 +347,33 @@ void seg_init_new_level0_page(SegSpace *spc, uint32_t new_extent_id, Buffer seg_ seg_head_update_xlog(seg_head_buffer, seg_head, -1, level1_slot); } } - +/* + * 功能:记录新范围页在 Level 0 页上的信息到XLOG + * + * 参数列表: + * spc:指向 SegSpace 结构的指针,表示分段空间信息 + * seg_head_buffer:表示分段头部的缓冲区 + * new_extent_id:新的范围页扩展标识符 + * new_extent_first_pageno:新范围页的第一个页号 + */ void seg_record_new_extent_on_level0_page(SegSpace *spc, Buffer seg_head_buffer, uint32 new_extent_id, BlockNumber new_extent_first_pageno) { + // 从 seg_head_buffer 中获取分段头部信息 SegmentHead *seg_head = (SegmentHead *)PageGetContents(BufferGetPage(seg_head_buffer)); + // 计算新范围页的 Level 1 插槽 uint32 level1_slot = ExtentIdToLevel1Slot(new_extent_id); + // 获取对应的 Level 0 页号和在 Level 0 页中的偏移量 BlockNumber level0_pageno = seg_head->level1_slots[level1_slot]; uint32 level0_offset = ExtentIdToLevel0PageOffset(new_extent_id); + // 读取并锁定 Level 0 缓冲区,并获取其内容 Buffer level0_buffer = ReadLevel0Buffer(spc, level0_pageno); BMTLevel0Page *bmt_level0_page = (BMTLevel0Page *)PageGetContents(BufferGetPage(level0_buffer)); LockBuffer(level0_buffer, BUFFER_LOCK_EXCLUSIVE); + // 更新 Level 0 页上对应偏移量的 slot 为新范围页的第一个页号 bmt_level0_page->slots[level0_offset] = new_extent_first_pageno; // XLogIssue @@ -325,31 +432,52 @@ void seg_extend_segment(SegSpace *spc, ForkNumber forknum, Buffer seg_head_buffe } } SEGMENTTEST(SEG_STORE_EXTEND_EXTENT, (errmsg("SEG_STORE_EXTEND_EXTENT %s: segment extend an extent!\n", - g_instance.attr.attr_common.PGXCNodeName))); + g_instance.attr.attr_common.PGXCNodeName))); XLogAtomicOpCommit(); END_CRIT_SECTION(); } - +/* + * 功能:获取范围页的位置信息 + * + * 参数列表: + * spc:指向 SegSpace 结构的指针,表示分段空间信息 + * seg_head:指向 SegmentHead 结构的指针,表示分段头部信息 + * extent_id:范围页扩展标识符 + */ BlockNumber seg_extent_location(SegSpace *spc, SegmentHead *seg_head, int extent_id) { + // 如果 extent_id 小于 BMT_HEADER_LEVEL0_SLOTS,则直接返回 seg_head 中对应索引的 level 0 插槽的值 if (extent_id < BMT_HEADER_LEVEL0_SLOTS) { return seg_head->level0_slots[extent_id]; } else { + // 如果 extent_id 小于 BMT_HEADER_LEVEL0_SLOTS,则直接返回 seg_head 中对应索引的 level 0 插槽的值 BlockNumber level0_page_id = ExtentIdToLevel0PageNumber(seg_head, extent_id); + // 读取 Level 0 缓冲区并以共享锁方式锁定它 Buffer buffer = ReadLevel0Buffer(spc, level0_page_id); LockBuffer(buffer, BUFFER_LOCK_SHARE); + // 获取 Level 0 页的内容 BMTLevel0Page *level0_page = (BMTLevel0Page *)PageGetContents(BufferGetPage(buffer)); + // 计算在 Level 0 页中的偏移量 extent_id -= BMT_HEADER_LEVEL0_SLOTS; + // 获取范围页的起始页号,并释放 Level 0 缓冲区 BlockNumber start = level0_page->slots[extent_id % BMT_LEVEL0_SLOTS]; SegUnlockReleaseBuffer(buffer); + // 返回范围页的起始页号 return start; } } - +/* + * 功能:将逻辑页号映射到物理页号并返回相关信息 + * + * 参数列表: + * reln:SMgrRelation 结构,表示存储管理器关系信息 + * seg_head:指向 SegmentHead 结构的指针,表示分段头部信息 + * logic_id:逻辑页号 + */ SegPageLocation seg_logic_to_physic_mapping(SMgrRelation reln, SegmentHead *seg_head, BlockNumber logic_id) { uint32 extent_id; @@ -363,11 +491,15 @@ SegPageLocation seg_logic_to_physic_mapping(SMgrRelation reln, SegmentHead *seg_ errhint("cannot do segment address translation during recovery"))); } + // 将逻辑页号转换为范围页扩展标识符、偏移量和范围页大小 SegLogicPageIdToExtentId(logic_id, &extent_id, &offset, &extent_size); + // 获取范围页的起始页号 BlockNumber extent_start = seg_extent_location(reln->seg_space, seg_head, extent_id); + // 计算物理页号 blocknum = extent_start + offset; + // 返回 SegPageLocation 结构,包含物理页号和相关信息 return { .extent_size = extent_size, .extent_id = extent_id, @@ -397,7 +529,7 @@ static bool open_segment(SMgrRelation reln, ForkNumber forknum, bool create, XLo /* Normal Table Segment Head API */ static bool normal_open_segment(SMgrRelation reln, int forknum, bool create); static BlockNumber normal_alloc_segment(Oid tablespace_id, Oid database_id, BlockNumber preassigned_block, - ExtentInversePointer iptr, bool is_heap_seg_head=false); + ExtentInversePointer iptr, bool is_heap_seg_head = false); void eg_init_segment_head_buffer_content(Buffer seg_head_buffer, BlockNumber seg_head_blocknum, XLogRecPtr lsn) { @@ -479,11 +611,12 @@ static bool normal_open_segment(SMgrRelation reln, int forknum, bool create) main_buffer = ReadSegmentBuffer(reln->seg_space, reln->seg_desc[MAIN_FORKNUM]->head_blocknum); main_head = (SegmentHead *)PageGetContents(BufferGetBlock(main_buffer)); if (unlikely(!IsNormalSegmentHead(main_head))) { - ereport(PANIC, (errmodule(MOD_SEGMENT_PAGE), errmsg("Segment head magic value 0x%lx is invalid," - "head lsn 0x%lx(maybe wrong). Rnode [%u, %u, %u, %d], head blocknum %u.", - main_head->magic, main_head->lsn, reln->smgr_rnode.node.spcNode, reln->smgr_rnode.node.dbNode, - reln->smgr_rnode.node.relNode, reln->smgr_rnode.node.bucketNode, - reln->seg_desc[MAIN_FORKNUM]->head_blocknum))); + ereport(PANIC, (errmodule(MOD_SEGMENT_PAGE), + errmsg("Segment head magic value 0x%lx is invalid," + "head lsn 0x%lx(maybe wrong). Rnode [%u, %u, %u, %d], head blocknum %u.", + main_head->magic, main_head->lsn, reln->smgr_rnode.node.spcNode, + reln->smgr_rnode.node.dbNode, reln->smgr_rnode.node.relNode, + reln->smgr_rnode.node.bucketNode, reln->seg_desc[MAIN_FORKNUM]->head_blocknum))); } /* @@ -520,8 +653,9 @@ static bool normal_open_segment(SMgrRelation reln, int forknum, bool create) fork_head_blocknum = new_head_blocknum; XLogAtomicOpCommit(); - SEGMENTTEST(FREE_EXTENT_ADD_FSM_FORK, (errmsg("FREE_EXTENT_ADD_FSM_FORK %s: add fsm fork, free extent success!\n", - g_instance.attr.attr_common.PGXCNodeName))); + SEGMENTTEST(FREE_EXTENT_ADD_FSM_FORK, + (errmsg("FREE_EXTENT_ADD_FSM_FORK %s: add fsm fork, free extent success!\n", + g_instance.attr.attr_common.PGXCNodeName))); ereport(DEBUG5, (errmodule(MOD_SEGMENT_PAGE), errmsg("Add fork %d for segment %u, fork head is %u", forknum, @@ -540,8 +674,7 @@ CREATE_DESC: /* * Initialize the segment descriptor in SMgrRelationData. */ - SegmentDesc *fork_desc = - (SegmentDesc *)MemoryContextAlloc(LocalSmgrStorageMemoryCxt(), sizeof(SegmentDesc)); + SegmentDesc *fork_desc = (SegmentDesc *)MemoryContextAlloc(LocalSmgrStorageMemoryCxt(), sizeof(SegmentDesc)); fork_desc->head_blocknum = fork_head_blocknum; fork_desc->timeline = seg_get_drop_timeline(); SegmentCheck(fork_head_blocknum >= DF_MAP_GROUP_SIZE); @@ -594,10 +727,11 @@ static void free_last_extent(SegSpace *spc, ForkNumber forknum, Buffer head_buff END_CRIT_SECTION(); SEGMENTTEST(FREE_EXTENT_DROP_EXTENTS, (errmsg("FREE_EXTENT_DROP_EXTENTS %s: " - "drop some extents,remain extents can drop !\n", g_instance.attr.attr_common.PGXCNodeName))); + "drop some extents,remain extents can drop !\n", + g_instance.attr.attr_common.PGXCNodeName))); XLogAtomicOpCommit(); - /* If all extents is freed, flush segment header to disk */ + /* If all extents is freed, flush segment header to disk */ if (head->total_blocks == 0) { SegmentCheck(head->nextents == 0); FlushOneSegmentBuffer(head_buffer); @@ -746,103 +880,165 @@ static void bucket_get_mapentry(BktMainHead *main_head, int4 bucketid, int forkn *map_blocknum = main_head->bkt_map[blockid]; SegmentCheck(*map_blocknum != 0); } - +/* + * 功能:初始化桶位图页并设置相应的信息 + * + * 参数列表: + * map_buffer:表示桶位图页的缓冲区 + * lsn:XLog记录位置 + */ void bucket_init_map_page(Buffer map_buffer, XLogRecPtr lsn) { + // 获取桶位图页 Page map_page = BufferGetPage(map_buffer); + // 初始化桶位图页,设置大小为 BLCKSZ(块大小) SegPageInit(map_page, BLCKSZ); + // 初始化桶位图页,设置大小为 BLCKSZ(块大小) BktHeadMapBlock *map_block = (BktHeadMapBlock *)PageGetContents(map_page); + // 设置桶位图页的魔术数字和记录位置(lsn) map_block->magic = BUCKETMAP_MAGIC; map_block->lsn = lsn; + // 初始化桶位图页中的桶位图项为无效块号 for (uint32 j = 0; j < BktMapEntryNumberPerBlock; j++) { map_block->head_block[j] = InvalidBlockNumber; } + // 增加桶位图页的 pd_lower,以表示数据的结束位置 ((PageHeader)map_page)->pd_lower += sizeof(BktHeadMapBlock); } - +/* + * 功能:分配一个新的桶段 + * + * 参数列表: + * tablespace_id:表空间的标识符 + * database_id:数据库的标识符 + * preassigned_block:预分配的块号 + */ static BlockNumber bucket_alloc_segment(Oid tablespace_id, Oid database_id, BlockNumber preassigned_block) { + // 打开指定表空间和数据库的分段空间 SegSpace *spc = spc_open(tablespace_id, database_id, true); + // 创建一个逆向指针,标识为桶段(BUCKET_SEGMENT),并设置所有者为无效块号 ExtentInversePointer iptr = {.flag = SPC_INVRSPTR_ASSEMBLE_FLAG(BUCKET_SEGMENT, 0), .owner = InvalidBlockNumber}; /* Allocate BktMainHead first */ + // 开始一个原子操作 XLogAtomicOpStart(); + // 分配 BktMainHead 所需的块号,大小为 SEGMENT_HEAD_EXTENT_SIZE BlockNumber main_head_blocknum = spc_alloc_extent(spc, SEGMENT_HEAD_EXTENT_SIZE, MAIN_FORKNUM, InvalidBlockNumber, iptr); + // 读取分配的块号对应的缓冲区 Buffer main_head_buffer = ReadSegmentBuffer(spc, main_head_blocknum); + // 锁定分配的块号对应的缓冲区,以排他锁方式 LockBuffer(main_head_buffer, BUFFER_LOCK_EXCLUSIVE); + // 获取 BktMainHead 的页面 Page main_head_page = BufferGetPage(main_head_buffer); + // 获取 BktMainHead 的页面 SegPageInit(main_head_page, BLCKSZ); + // 增加 BktMainHead 页面的 pd_lower,以表示数据的结束位置 ((PageHeader)main_head_page)->pd_lower += sizeof(BktMainHead); + // 获取 BktMainHead 结构的指针 BktMainHead *main_head = (BktMainHead *)PageGetContents(main_head_page); + // 获取当前 XLog 记录位置(lsn) XLogRecPtr lsn = GetXLogInsertRecPtr(); + // 设置 BktMainHead 的魔术数字、lsn、redis_info 信息 main_head->magic = BUCKET_SEGMENT_MAGIC; main_head->lsn = lsn; main_head->redis_info.redis_xid = InvalidTransactionId; main_head->redis_info.nwords = 0; + // 注册要记录的缓冲区,并指定将初始化(WILL_INIT)缓冲区,以及记录初始化的 XLOG 信息 XLogAtomicOpRegisterBuffer(main_head_buffer, REGBUF_WILL_INIT, SPCXLOG_INIT_BUCKET_HEAD, XLOG_COMMIT_KEEP_BUFFER_STATE); + // 注册要记录的数据,即记录当前 XLog 记录位置(lsn) XLogAtomicOpRegisterBufData((char *)&lsn, sizeof(XLogRecPtr)); + // 提交原子操作 XLogAtomicOpCommit(); + // 为 BktMainHead 分配桶位图块(BktMapBlock),并设置相关 XLOG 记录 for (uint32 i = 0; i < BktMapBlockNumber; i++) { bucket_ensure_mapblock(spc, main_head_buffer, i, lsn); } - + // 增加桶位图页的 pd_lower,以表示数据的结束位置 SegUnlockReleaseBuffer(main_head_buffer); return main_head_blocknum; } - +/* + * 功能:加载桶的主头部信息 + * + * 参数列表: + * spc:指向 SegSpace 结构的指针,表示分段空间信息 + * blocknum:桶的块号 + * buffer:用于返回加载的缓冲区 + * head:用于返回加载的 BktMainHead 结构指针 + */ static inline void bucket_load_main_head(SegSpace *spc, BlockNumber blocknum, Buffer *buffer, BktMainHead **head) { *buffer = ReadSegmentBuffer(spc, blocknum); *head = (BktMainHead *)PageGetContents(BufferGetPage(*buffer)); } - +/* + * 功能:打开或创建一个桶段(bucket segment) + * + * 参数列表: + * reln:SMgrRelation 结构,表示存储管理器关系信息 + * forknum:分叉号 + * create:是否创建 + * lsn:XLog 记录位置 + */ static bool bucket_open_segment(SMgrRelation reln, int forknum, bool create, XLogRecPtr lsn) { + // 如果已经存在对应 forknum 的分段描述符,则直接返回 true if (reln->seg_desc[forknum] != NULL) { return true; } + // 用于存储桶的主头部信息的缓冲区和指针 Buffer main_buffer; BktMainHead *main_head; + // 通过块号加载桶的主头部信息 bucket_load_main_head(reln->seg_space, reln->smgr_rnode.node.relNode, &main_buffer, &main_head); + // 检查加载的主头部信息是否符合预期 SegmentCheck(IsBucketMainHead(main_head)); + // 获取桶的编号 int4 bucketid = reln->smgr_rnode.node.bucketNode; /* find the map block */ + // 查找桶位图中对应的桶位图块号和桶位图项 ID BlockNumber map_blocknum; int map_entry_id; bucket_get_mapentry(main_head, bucketid, forknum, &map_blocknum, &map_entry_id); - if (map_blocknum == InvalidBlockNumber) - { + // 如果桶位图块号为无效块号,且提供的 XLog 记录位置有效,则返回 false + if (map_blocknum == InvalidBlockNumber) { SegmentCheck(XLogRecPtrIsValid(lsn)); SegReleaseBuffer(main_buffer); return false; } /* get the entry */ + // 获取桶位图块的缓冲区并以共享锁方式锁定 Buffer map_buffer = ReadSegmentBuffer(reln->seg_space, map_blocknum); LockBuffer(map_buffer, BUFFER_LOCK_SHARE); BktHeadMapBlock *map_block = (BktHeadMapBlock *)PageGetContents(BufferGetPage(map_buffer)); + // 如果提供的 XLog 记录位置有效,并且不满足条件,返回 false if (XLogRecPtrIsValid(lsn) && (XLByteLE(lsn, map_block->lsn) || map_block->magic != BUCKETMAP_MAGIC)) { SegUnlockReleaseBuffer(map_buffer); SegReleaseBuffer(main_buffer); return false; } + // 获取桶的主块号 BlockNumber head_blocknum = map_block->head_block[map_entry_id]; + // 如果主块号为无效块号 if (head_blocknum == InvalidBlockNumber) { /* the entry is not initialized */ + // 如果不允许创建,则返回 false if (create == false) { SegUnlockReleaseBuffer(map_buffer); SegReleaseBuffer(main_buffer); @@ -859,69 +1055,103 @@ static bool bucket_open_segment(SMgrRelation reln, int forknum, bool create, XLo * require/acquire above */ if (head_blocknum == InvalidBlockNumber) { + // 开始一个原子操作 XLogAtomicOpStart(); + // 创建逆向指针,标识为桶头部(BUCKET_HEAD) ExtentInversePointer iptr = { .flag = SPC_INVRSPTR_ASSEMBLE_FLAG(BUCKET_HEAD, forknum * MAX_BUCKETMAPLEN + reln->smgr_rnode.node.bucketNode), .owner = reln->smgr_rnode.node.relNode}; + // 分配主块号 head_blocknum = normal_alloc_segment(reln->smgr_rnode.node.spcNode, reln->smgr_rnode.node.dbNode, InvalidBlockNumber, iptr); + // 设置桶位图块中对应桶位图项的主块号 map_block->head_block[map_entry_id] = head_blocknum; + // 注册要记录的桶位图块的缓冲区,并指定保持数据(KEEP_DATA),以及记录初始化的 XLOG 信息 XLogAtomicOpRegisterBuffer(map_buffer, REGBUF_KEEP_DATA, SPCXLOG_BUCKET_ADD_BKTHEAD, XLOG_COMMIT_KEEP_BUFFER_STATE); + // 注册要记录的数据,包括桶位图项 ID 和主块号 XLogAtomicOpRegisterBufData((char *)&map_entry_id, sizeof(int)); XLogAtomicOpRegisterBufData((char *)&head_blocknum, sizeof(BlockNumber)); XLogAtomicOpCommit(); + // 用于测试的宏,用于记录插入一个桶中的所有值 SEGMENTTEST(FREE_EXTENT_INSERT_ONE_BUCKET, (errmsg("FREE_EXTENT_INSERT_ONE_BUCKET %s: " - "all values insert into one bucket!\n", g_instance.attr.attr_common.PGXCNodeName))); + "all values insert into one bucket!\n", + g_instance.attr.attr_common.PGXCNodeName))); } } - SegmentDesc *seg_desc = - (SegmentDesc *)MemoryContextAlloc(LocalSmgrStorageMemoryCxt(), sizeof(SegmentDesc)); + // 分配并初始化分段描述符 + SegmentDesc *seg_desc = (SegmentDesc *)MemoryContextAlloc(LocalSmgrStorageMemoryCxt(), sizeof(SegmentDesc)); seg_desc->head_blocknum = head_blocknum; seg_desc->timeline = seg_get_drop_timeline(); + // 检查主块号是否大于等于 DF_MAP_GROUP_SIZE,如果是,表示有效 SegmentCheck(head_blocknum >= DF_MAP_GROUP_SIZE); + // 将分段描述符保存到 SMgrRelation 结构中 reln->seg_desc[forknum] = seg_desc; + // 解锁并释放桶位图块的缓冲区 SegUnlockReleaseBuffer(map_buffer); + // 释放桶的主块号的缓冲区 SegReleaseBuffer(main_buffer); return true; } - +/* + * 功能:断开一个桶(bucket)与相关段(segment)的连接 + * + * 参数列表: + * spc:指向 SegSpace 结构的指针,表示分段空间信息 + * rnode:RelFileNode 结构,表示关系文件节点信息 + */ static void bucket_unlink_one_bucket(SegSpace *spc, const RelFileNode &rnode) { + // 用于存储桶的主头部信息的缓冲区和指针 Buffer main_buffer; BktMainHead *main_head; + // 通过块号加载桶的主头部信息 bucket_load_main_head(spc, rnode.relNode, &main_buffer, &main_head); + // 检查加载的主头部信息是否符合预期 SegmentCheck(IsBucketMainHead(main_head)); + // 以共享锁方式锁定主头部信息的缓冲区 LockBuffer(main_buffer, BUFFER_LOCK_SHARE); + // 遍历所有可能的分叉号 for (int i = 0; i <= SEGMENT_MAX_FORKNUM; i++) { /* Locate the segment head for this fork */ BlockNumber map_blocknum; int map_entry_id; + // 查找桶位图块中对应的桶位图块块号和桶位图项 ID bucket_get_mapentry(main_head, rnode.bucketNode, i, &map_blocknum, &map_entry_id); + // 如果桶位图块块号不为 0 if (map_blocknum != 0) { + // 获取桶位图块的缓冲区并以独占锁方式锁定 Buffer map_buffer = ReadSegmentBuffer(spc, map_blocknum); LockBuffer(map_buffer, BUFFER_LOCK_EXCLUSIVE); BktHeadMapBlock *map_block = (BktHeadMapBlock *)PageGetContents(BufferGetPage(map_buffer)); + // 检查加载的桶位图块是否符合预期 SegmentCheck(IsBucketMapBlock(map_block)); BlockNumber head_blocknum = map_block->head_block[map_entry_id]; + // 如果主块号不为无效块号 if (head_blocknum != InvalidBlockNumber) { + // 获取主块的缓冲区并以独占锁方式锁定 Buffer head_buffer = ReadSegmentBuffer(spc, head_blocknum); LockBuffer(head_buffer, BUFFER_LOCK_EXCLUSIVE); + // 计算桶位图块中 head_block 数组的偏移量 off_t offset = offsetof(BktHeadMapBlock, head_block) + sizeof(BlockNumber) * map_entry_id; + // 释放一个段 free_one_segment(spc, i, head_buffer, head_blocknum, map_buffer, offset); + // 解锁并释放主块的缓冲区 SegUnlockReleaseBuffer(head_buffer); } + // 解锁并释放桶位图块的缓冲区 SegUnlockReleaseBuffer(map_buffer); } } + // 解锁并释放主头部信息的缓冲区 SegUnlockReleaseBuffer(main_buffer); } @@ -985,13 +1215,10 @@ static void bucket_unlink_segment(SegSpace *spc, RelFileNode rnode, Buffer main_ } } ereport(LOG, - (errmsg("rnode <%u %u %u %u>, xid [%lu, %lu, %lu], redis nwords %u, redis is [%s]", - rnode.spcNode, rnode.dbNode, rnode.relNode, rnode.bucketNode, - t_thrd.xact_cxt.ShmemVariableCache->oldestXid, - main_head->redis_info.redis_xid, - GetCurrentTransactionIdIfAny(), - main_head->redis_info.nwords, - redis_committed ? "committed" : "abort"))); + (errmsg("rnode <%u %u %u %u>, xid [%lu, %lu, %lu], redis nwords %u, redis is [%s]", rnode.spcNode, + rnode.dbNode, rnode.relNode, rnode.bucketNode, t_thrd.xact_cxt.ShmemVariableCache->oldestXid, + main_head->redis_info.redis_xid, GetCurrentTransactionIdIfAny(), main_head->redis_info.nwords, + redis_committed ? "committed" : "abort"))); } LockBuffer(main_buffer, BUFFER_LOCK_EXCLUSIVE); @@ -1016,7 +1243,8 @@ static void bucket_unlink_segment(SegSpace *spc, RelFileNode rnode, Buffer main_ ForkNumber forknum = (i * BktMapEntryNumberPerBlock + k) / MAX_BUCKETMAPLEN; free_one_segment(spc, forknum, head_buffer, head_blocknum, map_buffer, offset); SEGMENTTEST(FREE_EXTENT_DROP_BUCKETS, (errmsg("FREE_EXTENT_DROP_BUCKETS %s: " - "drop some buckets,remain buckets can drop !\n", g_instance.attr.attr_common.PGXCNodeName))); + "drop some buckets,remain buckets can drop !\n", + g_instance.attr.attr_common.PGXCNodeName))); SegUnlockReleaseBuffer(head_buffer); } } @@ -1031,70 +1259,115 @@ static void bucket_unlink_segment(SegSpace *spc, RelFileNode rnode, Buffer main_ LockBuffer(main_buffer, BUFFER_LOCK_UNLOCK); } - +/* + * 功能:计算指定分支的所有桶段的总块数 + * + * 参数列表: + * reln:SMgrRelation 结构,表示存储管理器关系信息 + * forknum:ForkNumber,表示分支号 + * main_buffer:Buffer,表示桶的主头部信息的缓冲区 + */ BlockNumber bucket_totalblocks(SMgrRelation reln, ForkNumber forknum, Buffer main_buffer) { + // 打开分段空间 SMgrOpenSpace(reln); + // 获取桶的主头部信息 BktMainHead *main_head = (BktMainHead *)PageGetContents(BufferGetPage(main_buffer)); + // 获取分段空间信息 SegSpace *spc = reln->seg_space; + // 以共享锁方式锁定主头部信息的缓冲区 LockBuffer(main_buffer, BUFFER_LOCK_SHARE); + // 初始化结果变量 BlockNumber result = 0; + // 用于存储当前桶位图块的块号和上一个桶位图块的块号 BlockNumber map_blocknum, last_map_blocknum = InvalidBlockNumber; + // 用于存储桶位图项 ID int map_entry_id; + // 初始化桶位图块缓冲区为无效缓冲区 Buffer map_buffer = InvalidBuffer; + // 遍历所有可能的桶位图项 for (int i = 0; i < MAX_BUCKETMAPLEN; i++) { + // 获取桶位图块中的桶位图项信息 bucket_get_mapentry(main_head, i, forknum, &map_blocknum, &map_entry_id); + // 如果当前桶位图块块号与上一个不同 if (map_blocknum != last_map_blocknum) { + // 如果上一个桶位图块缓冲区不是无效缓冲区,则解锁并释放它 if (map_buffer != InvalidBuffer) { SegUnlockReleaseBuffer(map_buffer); } + // 获取当前桶位图块的缓冲区并以共享锁方式锁定 map_buffer = ReadSegmentBuffer(spc, map_blocknum); LockBuffer(map_buffer, BUFFER_LOCK_SHARE); + // 更新上一个桶位图块块号 last_map_blocknum = map_blocknum; } + // 获取桶位图块的内容 BktHeadMapBlock *map_block = (BktHeadMapBlock *)PageGetContents(BufferGetPage(map_buffer)); + // 检查加载的桶位图块是否符合预期 SegmentCheck(IsBucketMapBlock(map_block)); + // 获取桶段的主块号 BlockNumber head_blocknum = map_block->head_block[map_entry_id]; + // 如果主块号不为无效块号 if (head_blocknum != InvalidBlockNumber) { + // 获取主块的缓冲区 Buffer head_buffer = ReadSegmentBuffer(spc, head_blocknum); + // 获取主块的内容 SegmentHead *seg_head = (SegmentHead *)PageGetContents(BufferGetPage(head_buffer)); + // 以共享锁方式锁定主块的缓冲区,并累加桶段的块数到结果变量 LockBuffer(head_buffer, BUFFER_LOCK_SHARE); result += seg_head->nblocks; + // 解锁并释放主块的缓冲区 SegUnlockReleaseBuffer(head_buffer); } } + // 如果最后一个桶位图块的缓冲区不是无效缓冲区,则解锁并释放它 if (map_buffer != InvalidBuffer) { SegUnlockReleaseBuffer(map_buffer); } + // 解锁主头部信息的缓冲区 LockBuffer(main_buffer, BUFFER_LOCK_UNLOCK); return result; } - +/* + * 功能:根据逻辑块号获取分段的物理位置信息 + * + * 参数列表: + * rnode:RelFileNode 结构,表示文件节点信息 + * forknum:ForkNumber,表示分支号 + * blocknum:BlockNumber,表示逻辑块号 + */ SegPageLocation seg_get_physical_location(RelFileNode rnode, ForkNumber forknum, BlockNumber blocknum) { SMgrRelation reln; + // 如果正在进行恢复过程,报错,不允许在恢复过程中获取分段的地址映射 if (RecoveryInProgress()) { ereport(ERROR, (errcode(ERRCODE_OBJECT_NOT_IN_PREREQUISITE_STATE), errmsg("recovery is in progress"), errhint("cannot get segment address translation during recovery"))); } + // 打开存储管理器关系 reln = smgropen(rnode, InvalidBackendId); + // 读取分段的主头部信息缓冲区,不进行创建 Buffer buffer = read_head_buffer(reln, forknum, false); + // 检查获取的缓冲区是否有效 SegmentCheck(BufferIsValid(buffer)); + // 获取分段的主头部信息 SegmentHead *head = (SegmentHead *)PageGetContents(BufferGetBlock(buffer)); + // 调用 seg_logic_to_physic_mapping 函数获取逻辑块号对应的物理位置信息 SegPageLocation loc = seg_logic_to_physic_mapping(reln, head, blocknum); + // 释放获取的主头部信息缓冲区 SegReleaseBuffer(buffer); return loc; } @@ -1117,8 +1390,10 @@ BlockNumber seg_alloc_segment(Oid tablespace_id, Oid database_id, bool isbucket, ExtentInversePointer iptr = {.flag = SPC_INVRSPTR_ASSEMBLE_FLAG(SEGMENT_HEAD, 0), .owner = InvalidBlockNumber}; XLogAtomicOpStart(); result = normal_alloc_segment(tablespace_id, database_id, preassigned_block, iptr, true); - SEGMENTTEST(FREE_EXTENT_CREATE_SEGMENT_UNCOMMIT, (errmsg("FREE_EXTENT_CREATE_SEGMENT_UNCOMMIT %s: alloc segment success, " - "transation uncommit can drop segment!\n", g_instance.attr.attr_common.PGXCNodeName))); + SEGMENTTEST(FREE_EXTENT_CREATE_SEGMENT_UNCOMMIT, + (errmsg("FREE_EXTENT_CREATE_SEGMENT_UNCOMMIT %s: alloc segment success, " + "transation uncommit can drop segment!\n", + g_instance.attr.attr_common.PGXCNodeName))); XLogAtomicOpCommit(); } return result; @@ -1187,7 +1462,14 @@ static Buffer read_head_buffer(SMgrRelation reln, ForkNumber forknum, bool creat return buffer; } - +/* + * 功能:在重做期间读取分段的主头部信息缓冲区 + * + * 参数列表: + * reln:SMgrRelation 结构,表示存储管理器关系信息 + * forknum:ForkNumber,表示分支号 + * lsn:XLogRecPtr,表示逻辑复制点的位置信息 + */ static Buffer read_head_buffer_redo(SMgrRelation reln, ForkNumber forknum, XLogRecPtr lsn) { ASSERT_NORMAL_FORK(forknum); @@ -1201,23 +1483,34 @@ static Buffer read_head_buffer_redo(SMgrRelation reln, ForkNumber forknum, XLogR return buffer; } - - +/* + * 功能:删除分段 + * + * 参数列表: + * rnode:RelFileNode 结构,表示文件节点信息 + */ static void seg_unlink_segment(const RelFileNode &rnode) { + // 更新分段删除时间线 seg_update_timeline(); + // 打开分段空间 SegSpace *spc = spc_open(rnode.spcNode, rnode.dbNode, false); + // 检查分段空间是否有效 SegmentCheck(spc != NULL); + // 函数判断分段类型是普通表还是桶表,并分别调用相应的函数来删除分段 if (!IsBucketFileNode(rnode)) { + // 读取分段的缓冲区,并获取分段头部信息 Buffer buffer = ReadSegmentBuffer(spc, rnode.relNode); SegmentHead *head = (SegmentHead *)PageGetContents(BufferGetBlock(buffer)); + // 根据分段头部信息的魔术数判断分段类型 if (head->magic == SEGMENT_HEAD_MAGIC) { // normal table SEGMENTTEST(FREE_SEGMENT_DROP_SEGMENT, (errmsg("FREE_SEGMENT_DROP_SEGMENT %s: " - "drop segment tb success!\n", g_instance.attr.attr_common.PGXCNodeName))); + "drop segment tb success!\n", + g_instance.attr.attr_common.PGXCNodeName))); normal_unlink_segment(spc, rnode, buffer); } else if (head->magic == BUCKET_SEGMENT_MAGIC) { // Delete all buckets in the table @@ -1232,11 +1525,22 @@ static void seg_unlink_segment(const RelFileNode &rnode) bucket_unlink_one_bucket(spc, rnode); } } - +/* + * 功能:记录存储管理器 API 调用的日志信息 + * + * 参数列表: + * smgr_rnode:RelFileNodeBackend 结构,表示带有后端信息的文件节点信息 + * forknum:ForkNumber,表示分支号 + * blocknum:BlockNumber,表示块号 + * func_name:const char*,表示调用的 API 函数名称 + * elevel:int,表示日志级别,默认为 DEBUG2 + */ static inline void LOG_SMGR_API(RelFileNodeBackend smgr_rnode, ForkNumber forknum, BlockNumber blocknum, const char *func_name, int elevel = DEBUG2) { + // 获取文件节点信息 RelFileNode rnode = smgr_rnode.node; + // 记录日志信息 ereport(DEBUG2, (errmodule(MOD_SEGMENT_PAGE), errmsg("Segment-page smgr api: %s is invoked, SMgrRelation: <%u, %u, %u, %u> %d %u", func_name, rnode.spcNode, rnode.dbNode, rnode.relNode, rnode.bucketNode, forknum, blocknum))); @@ -1254,84 +1558,129 @@ void seg_shutdown() { // do nothing } - +/* + * 功能:关闭分段 + * + * 参数列表: + * reln:SMgrRelation 结构,表示存储管理器关系信息 + * forknum:ForkNumber,表示分支号 + * blockNum:BlockNumber,表示块号 + */ void seg_close(SMgrRelation reln, ForkNumber forknum, BlockNumber blockNum) { + // 断言块号为无效块号 SegmentCheck(blockNum == InvalidBlockNumber); + // 记录存储管理器 API 调用的日志信息 LOG_SMGR_API(reln->smgr_rnode, forknum, InvalidBlockNumber, "seg_close"); + // 如果存储管理器关系中存在分段描述信息且分段描述信息不为空 if (reln->seg_desc && reln->seg_desc[forknum] != NULL) { /* release memory */ pfree(reln->seg_desc[forknum]); reln->seg_desc[forknum] = NULL; } } - +/* + * 功能:创建分段 + * + * 参数列表: + * reln:SMgrRelation 结构,表示存储管理器关系信息 + * forknum:ForkNumber,表示分支号 + * isRedo:bool,表示是否在重做中创建 + */ void seg_create(SMgrRelation reln, ForkNumber forknum, bool isRedo) { + // 记录存储管理器 API 调用的日志信息 LOG_SMGR_API(reln->smgr_rnode, forknum, InvalidBlockNumber, "seg_create"); + // 打开分段,如果分段已存在则返回 true bool res = open_segment(reln, forknum, true); - SegmentCheck(res == true); - (void)res; // keep compiler silent + SegmentCheck(res == true); // 确保成功打开或创建了分段 + // 防止编译器警告,保持编译器静默 + (void)res; // keep compiler silent } - +/* + * 功能:检查分段是否存在 + * + * 参数列表: + * reln:SMgrRelation 结构,表示存储管理器关系信息 + * forknum:ForkNumber,表示分支号 + * blockNum:BlockNumber,表示块号 + */ bool seg_exists(SMgrRelation reln, ForkNumber forknum, BlockNumber blockNum) { + // 记录存储管理器 API 调用的日志信息 LOG_SMGR_API(reln->smgr_rnode, forknum, InvalidBlockNumber, "seg_exists"); + // 调用 open_segment 函数检查分段是否存在,并返回结果 return open_segment(reln, forknum, false); } - +/* + * 功能:删除分段 + * + * 参数列表: + * rnode:RelFileNodeBackend 结构,表示关系文件节点的后端信息 + * forknum:ForkNumber,表示分支号 + * isRedo:bool,表示是否在重做中删除 + * blockNum:BlockNumber,表示块号 + */ void seg_unlink(const RelFileNodeBackend &rnode, ForkNumber forknum, bool isRedo, BlockNumber blockNum) { if (isRedo) { return; } + // 记录存储管理器 API 调用的日志信息,使用 LOG 级别 LOG_SMGR_API(rnode, forknum, InvalidBlockNumber, "seg_unlink", LOG); + // 断言分支号为无效分支号,确保只能删除整个分段而不是单个块 SegmentCheck(forknum == InvalidForkNumber); + // 调用 seg_unlink_segment 函数删除整个分段 seg_unlink_segment(rnode.node); } - +// 结构体 ExtendStat struct ExtendStat { - private: - instr_time start_time; - instr_time end_time; - Oid _spc; - Oid _db; - Oid _file; +private: + instr_time start_time; // 记录起始时间的数据结构 + instr_time end_time; // 记录结束时间的数据结构 + Oid _spc; // 存储空间的标识符 + Oid _db; // 数据库的标识符 + Oid _file; // 文件的标识符 - public: +public: + // 设置文件标识符的方法 void set_file(Oid spc, Oid db, Oid file) { _spc = spc; _db = db; _file = file; } - + // 记录起始时间的方法 void start() { (void)INSTR_TIME_SET_CURRENT(start_time); } - + // 记录结束时间的方法 void end() { (void)INSTR_TIME_SET_CURRENT(end_time); INSTR_TIME_SUBTRACT(end_time, start_time); PgStat_Counter time_diff = (PgStat_Counter)INSTR_TIME_GET_MICROSEC(end_time); + // 创建 PgStat_MsgFile 结构体并初始化为0 PgStat_MsgFile msg; errno_t rc = memset_s(&msg, sizeof(msg), 0, sizeof(msg)); securec_check(rc, "", ""); - msg.dbid = _db; - msg.spcid = _spc; - msg.fn = _file; - msg.rw = 'w'; - msg.cnt = 1; - msg.blks = 1; - msg.tim = time_diff; - msg.lsttim = time_diff; - msg.mintim = time_diff; - msg.maxtim = time_diff; + // 设置 PgStat_MsgFile 结构体的成员变量 + msg.dbid = _db; // 数据库标识符 + msg.spcid = _spc; // 存储空间标识符 + msg.fn = _file; // 文件标识符 + msg.rw = 'w'; // 读写操作类型,这里表示写 + msg.cnt = 1; // 计数器,表示操作次数 + msg.blks = 1; // 操作的块数 + msg.tim = time_diff; // 操作的时间差(以微秒为单位) + msg.lsttim = time_diff; // 最后一次操作的时间差 + msg.mintim = time_diff; // 最小操作时间差 + msg.maxtim = time_diff; // 最大操作时间差 + + // 调用 reportFileStat 函数报告文件统计信息 reportFileStat(&msg); } }; @@ -1454,16 +1803,34 @@ void seg_prefetch(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum) LOG_SMGR_API(reln->smgr_rnode, forknum, blocknum, "seg_prefetch"); /* Not support prefetch yet, just return */ } - +/* + * 功能: 从segment file中读取指定块(数据块)的函数。 + * + * 参数: + * reln: SMgrRelation,表示与段文件相关的SMgrRelation对象。 + * forknum: ForkNumber,表示数据文件的分叉号。 + * blocknum: BlockNumber,要读取的块号。 + * buffer: char*,用于存储读取到的块数据的缓冲区。 + * + * 返回值: + * SMGR_READ_STATUS,表示读取操作的状态,可能的取值包括: + * - SMGR_RD_OK: 读取成功。 + * - SMGR_RD_CRC_ERROR: 块的校验和校验失败,读取失败。 + * + * 注意:函数会自动处理并发访问冲突,确保数据的一致性。 + */ SMGR_READ_STATUS seg_read(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, char *buffer) { + // 记录函数调用信息 LOG_SMGR_API(reln->smgr_rnode, forknum, blocknum, "seg_read"); + // 读取段头部的缓冲区 Buffer seg_buffer = read_head_buffer(reln, forknum, false); SegmentCheck(BufferIsValid(seg_buffer)); SegmentHead *seg_head = (SegmentHead *)PageGetContents(BufferGetBlock(seg_buffer)); SegmentCheck(IsNormalSegmentHead(seg_head)); + // 检查要读取的块号是否超出段的大小 if (seg_head->nblocks <= blocknum) { SegReleaseBuffer(seg_buffer); ereport(ERROR, (errmodule(MOD_SEGMENT_PAGE), errcode(ERRCODE_DATA_CORRUPTED), @@ -1473,15 +1840,19 @@ SMGR_READ_STATUS seg_read(SMgrRelation reln, ForkNumber forknum, BlockNumber blo blocknum, seg_head->nblocks))); } + // 获取段头部的锁 LockSegmentHeadPartition(reln->seg_space->spcNode, reln->seg_space->dbNode, reln->seg_desc[forknum]->head_blocknum, LW_SHARED); + // 将逻辑块号映射到物理块号 SegPageLocation loc = seg_logic_to_physic_mapping(reln, seg_head, blocknum); SegmentCheck(loc.blocknum != InvalidBlockNumber); + // 读取块数据 SegSpace *spc = reln->seg_space; spc_read_block(spc, EXTENT_GROUP_RNODE(spc, loc.extent_size), forknum, buffer, loc.blocknum); + // 释放段头部的锁 UnlockSegmentHeadPartition(reln->seg_space->spcNode, reln->seg_space->dbNode, reln->seg_desc[forknum]->head_blocknum); @@ -1489,6 +1860,7 @@ SMGR_READ_STATUS seg_read(SMgrRelation reln, ForkNumber forknum, BlockNumber blo SegmentCheck(loc.extent_size != 1); /* Set physical location in buffer descriptor, which is used for XLog */ + // 设置缓冲区描述符中的物理位置,用于 XLog 记录 Buffer buf = BlockGetBuffer(buffer); if (BufferIsValid(buf)) { BufferDesc *buf_desc = BufferGetBufferDescriptor(buf); @@ -1496,24 +1868,40 @@ SMGR_READ_STATUS seg_read(SMgrRelation reln, ForkNumber forknum, BlockNumber blo buf_desc->seg_blockno = loc.blocknum; } + // 释放段头部的缓冲区 SegReleaseBuffer(seg_buffer); + // 如果通过校验则返回 SMGR_RD_OK,否则返回 SMGR_RD_CRC_ERROR if (PageIsVerified((Page)buffer, loc.blocknum)) { return SMGR_RD_OK; } else { return SMGR_RD_CRC_ERROR; } } - +/* + * 功能:将数据写入分段文件 + * + * 参数列表: + * reln:SMgrRelation 结构,表示分段文件的SMgrRelation对象 + * forknum:ForkNumber,表示分支号 + * blocknum:BlockNumber,表示要写入的块号 + * buffer:const char*,包含要写入的数据的缓冲区 + * skipFsync:bool,如果为true,表示可以跳过fsync同步以提高性能 + * + * 注意:该函数负责将指定块的数据写入分段文件,管理并发访问冲突以确保数据一致性。 + */ void seg_write(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, const char *buffer, bool skipFsync) { + // 记录SMGR API调用日志 LOG_SMGR_API(reln->smgr_rnode, forknum, blocknum, "seg_write"); + // 读取分段头部缓冲区 Buffer seg_buffer = read_head_buffer(reln, forknum, true); SegmentCheck(BufferIsValid(seg_buffer)); SegmentHead *seg_head = (SegmentHead *)PageGetContents(BufferGetBlock(seg_buffer)); SegmentCheck(IsNormalSegmentHead(seg_head)); + // 检查块号是否超过分段大小 if (seg_head->nblocks <= blocknum) { SegReleaseBuffer(seg_buffer); ereport(ERROR, (errmodule(MOD_SEGMENT_PAGE), errcode(ERRCODE_DATA_CORRUPTED), @@ -1523,42 +1911,62 @@ void seg_write(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, cons blocknum, seg_head->nblocks))); } + // 锁定分段头部所在的分区 LockSegmentHeadPartition(reln->seg_space->spcNode, reln->seg_space->dbNode, reln->seg_desc[forknum]->head_blocknum, LW_SHARED); + // 逻辑到物理地址映射 SegPageLocation loc = seg_logic_to_physic_mapping(reln, seg_head, blocknum); SegmentCheck(loc.blocknum != InvalidBlockNumber); + // 解锁分段头部所在的分区 UnlockSegmentHeadPartition(reln->seg_space->spcNode, reln->seg_space->dbNode, reln->seg_desc[forknum]->head_blocknum); // TODO: remove PageSetChecksumInplace invocation outside SMGR. PageSetChecksumInplace((Page)buffer, loc.blocknum); SegSpace *spc = reln->seg_space; + // 写入数据块 spc_write_block(spc, EXTENT_GROUP_RNODE(spc, loc.extent_size), forknum, buffer, loc.blocknum); + // 释放分段头部缓冲区 SegReleaseBuffer(seg_buffer); } - +/* + * 功能:将指定分段的数据写回磁盘 + * + * 参数列表: + * reln:SMgrRelation 结构,表示分段文件的SMgrRelation对象 + * forknum:ForkNumber,表示分支号 + * blocknum:BlockNumber,表示要写回的起始块号 + * nblocks:BlockNumber,表示要写回的块数量 + * + * 注意:该函数用于将指定分段的数据写回磁盘,可用于缓冲区刷新操作。如果传递的 rNode 表示物理分段文件,则使用 + * spc_writeback 函数,如果表示逻辑分段文件,则逐个逻辑区块写回磁盘。 + */ void seg_writeback(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, BlockNumber nblocks) { + // 记录SMGR API调用日志 LOG_SMGR_API(reln->smgr_rnode, forknum, InvalidBlockNumber, "seg_writeback"); RelFileNode rNode = reln->smgr_rnode.node; if (IsSegmentPhysicalRelNode(rNode)) { SMgrOpenSpace(reln); + // 记录SMGR API调用日志 if (reln->seg_space == NULL) { ereport(ERROR, (errmodule(MOD_SEGMENT_PAGE), errcode(ERRCODE_DATA_CORRUPTED), - errmsg("when write back, segment space [%u, %u] doesn't exist.", reln->smgr_rnode.node.spcNode, - reln->smgr_rnode.node.dbNode))); + errmsg("when write back, segment space [%u, %u] doesn't exist.", + reln->smgr_rnode.node.spcNode, reln->smgr_rnode.node.dbNode))); return; } + // 使用 spc_writeback 函数将数据写回磁盘 spc_writeback(reln->seg_space, EXTENT_TYPE_TO_SIZE(rNode.relNode), forknum, blocknum, nblocks); } else { /* * Logical writes are continues in each extent. * XXX: Continues extents can be merged to reduece system call. */ + // 读取分段头部缓冲区 Buffer seg_buffer = read_head_buffer(reln, forknum, true); SegmentCheck(BufferIsValid(seg_buffer)); SegmentHead *seg_head = (SegmentHead *)PageGetContents(BufferGetBlock(seg_buffer)); @@ -1566,6 +1974,7 @@ void seg_writeback(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, SegmentCheck(blocknum + nblocks < seg_head->nblocks); /* Get the start extent id */ + // 获取起始逻辑区块的映射信息 SegPageLocation loc1 = seg_logic_to_physic_mapping(reln, seg_head, blocknum); SegmentCheck(loc1.blocknum != InvalidBlockNumber); uint32 curr_ext_id = loc1.extent_id; @@ -1590,23 +1999,35 @@ void seg_writeback(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, SegReleaseBuffer(seg_buffer); } } - +/* + * 功能:获取分段中的块数量 + * + * 参数列表: + * reln:SMgrRelation 结构,表示分段文件的SMgrRelation对象 + * forknum:ForkNumber,表示分支号 + * + * 注意:该函数用于获取分段文件中的块数量,根据 forknum 参数区分不同分支。如果分段文件不存在或不包含有效数据,返回0。 + */ BlockNumber seg_nblocks(SMgrRelation reln, ForkNumber forknum) { + // 记录SMGR API调用日志 LOG_SMGR_API(reln->smgr_rnode, forknum, InvalidBlockNumber, "seg_nblocks"); ASSERT_NORMAL_FORK(forknum); + // 检查分段文件是否存在 bool seg_exist = open_segment(reln, forknum, false); if (!seg_exist) { return 0; } + // 检查分段文件是否存在 Buffer seg_buffer = read_head_buffer(reln, forknum, false); SegmentCheck(BufferIsValid(seg_buffer)); SegmentHead *seg_head = (SegmentHead *)PageGetContents(BufferGetBlock(seg_buffer)); SegmentCheck(IsNormalSegmentHead(seg_head)); + // 锁定分段头部缓冲区并获取块数量 LockBuffer(seg_buffer, BUFFER_LOCK_SHARE); BlockNumber nblocks = seg_head->nblocks; SegUnlockReleaseBuffer(seg_buffer); @@ -1618,11 +2039,21 @@ void seg_truncate(SMgrRelation reln, ForkNumber forknum, BlockNumber nblocks) { SegmentCheck(0); } - +/* + * 功能:移动分段中的存储桶 + * + * 参数列表: + * dest:RelFileNodeBackend 结构,表示目标分段文件节点的后端信息 + * src:RelFileNodeBackend 结构,表示源分段文件节点的后端信息 + * bucketList:List 结构,包含要移动的存储桶的列表 + * + * 注意:该函数用于在两个分段之间移动存储桶的数据。源分段中的存储桶数据将被复制到目标分段中。 + */ void seg_move_buckets(const RelFileNodeBackend &dest, const RelFileNodeBackend &src, List *bucketList) { + // 记录存储桶Redis信息的XLOG LOG_SMGR_API(dest, InvalidForkNumber, InvalidBlockNumber, "seg_move_buckets"); - uint32 i,j; + uint32 i, j; uint32 nentry; ListCell *cell = NULL; BktMainHead *shead, *dhead; @@ -1634,16 +2065,18 @@ void seg_move_buckets(const RelFileNodeBackend &dest, const RelFileNodeBackend & SegmentCheck(dest.node.spcNode == src.node.spcNode && dest.node.dbNode == src.node.dbNode); SegSpace *spc = spc_open(dest.node.spcNode, dest.node.dbNode, false); + // 读取目标和源分段的主头部 bucket_load_main_head(spc, dest.node.relNode, &dbuffer, &dhead); bucket_load_main_head(spc, src.node.relNode, &sbuffer, &shead); + // 锁定目标和源分段的主头部 LockBuffer(dbuffer, BUFFER_LOCK_EXCLUSIVE); LockBuffer(sbuffer, BUFFER_LOCK_EXCLUSIVE); XLogAtomicOpStart(); /* Add redis info for both source and dest bucket segment header */ - dhead->redis_info.redis_xid = redis_xid; - shead->redis_info.redis_xid = redis_xid; + dhead->redis_info.redis_xid = redis_xid; + shead->redis_info.redis_xid = redis_xid; dhead->redis_info.nwords = BktBitMaxMapCnt; shead->redis_info.nwords = 0; for (i = 0; i < BktBitMaxMapCnt; i++) { @@ -1695,18 +2128,30 @@ void seg_move_buckets(const RelFileNodeBackend &dest, const RelFileNodeBackend & SegUnlockReleaseBuffer(smapbuffer); } + // 记录存储桶Redis信息的XLOG log_move_segment_redisinfo(&dhead->redis_info, &shead->redis_info, dbuffer, sbuffer); XLogAtomicOpCommit(); } - +/* + * 功能:获取分段头部的LSN + * + * 参数列表: + * spc:SegSpace 结构,表示段空间 + * blockNum:BlockNumber,表示块号 + * isbucket:bool,表示是否为存储桶分段 + * + * 注意:该函数用于获取分段头部的LSN,用于检查分段是否已经被写入日志。 + */ XLogRecPtr seg_get_headlsn(SegSpace *spc, BlockNumber blockNum, bool isbucket) { + // 读取分段头部的缓冲区 Buffer buffer = ReadSegmentBuffer(spc, blockNum); LockBuffer(buffer, BUFFER_LOCK_SHARE); SegmentHead *head = (SegmentHead *)PageGetContents(BufferGetPage(buffer)); uint64 magic = isbucket ? BUCKET_SEGMENT_MAGIC : SEGMENT_HEAD_MAGIC; XLogRecPtr lsn = InvalidXLogRecPtr; + // 检查分段头部的魔数和日志序列号是否有效 if (PageIsSegmentVersion(BufferGetPage(buffer)) && head->magic == magic) { lsn = head->lsn; SegmentCheck(XLogRecPtrIsValid(lsn)); @@ -1717,48 +2162,61 @@ XLogRecPtr seg_get_headlsn(SegSpace *spc, BlockNumber blockNum, bool isbucket) } void seg_immedsync(SMgrRelation reln, ForkNumber forknum) -{ -} +{} void seg_pre_ckpt(void) -{ -} +{} void seg_sync(void) -{ -} +{} void seg_post_ckpt(void) -{ -} +{} void seg_async_read(SMgrRelation reln, ForkNumber forknum, AioDispatchDesc_t **dList, int32 dn) -{ -} +{} void seg_async_write(SMgrRelation reln, ForkNumber forknum, AioDispatchDesc_t **dList, int32 dn) -{ -} - +{} +/* + * 功能:获取指定分支的分段总块数 + * + * 参数列表: + * reln:SMgrRelation 结构,表示SMgrRelation对象 + * forknum:ForkNumber,表示分支号 + * + * 注意:该函数用于获取指定分支的分段总块数,如果分段不存在,则返回0。 + */ BlockNumber seg_fork_totalblocks(SMgrRelation reln, ForkNumber forknum) { + // 检查指定分支的有效性 bool seg_exist = open_segment(reln, forknum, false); if (!seg_exist) { return 0; } + // 读取指定分支的分段头部缓冲区 Buffer seg_buffer = read_head_buffer(reln, forknum, false); SegmentCheck(BufferIsValid(seg_buffer)); SegmentHead *seg_head = (SegmentHead *)PageGetContents(BufferGetBlock(seg_buffer)); + // 获取分段总块数并释放缓冲区 LockBuffer(seg_buffer, BUFFER_LOCK_SHARE); BlockNumber nblocks = seg_head->nblocks; SegUnlockReleaseBuffer(seg_buffer); return nblocks; } - +/* + * 功能:获取指定分支的分段总块数 + * + * 参数列表: + * reln:SMgrRelation 结构,表示SMgrRelation对象 + * forknum:ForkNumber,表示分支号 + * + * 注意:该函数用于获取指定分支的分段总块数,如果分段不存在,则返回0。 + */ BlockNumber seg_totalblocks(SMgrRelation reln, ForkNumber forknum) { LOG_SMGR_API(reln->smgr_rnode, forknum, InvalidBlockNumber, "seg_totalblocks"); @@ -1768,21 +2226,34 @@ BlockNumber seg_totalblocks(SMgrRelation reln, ForkNumber forknum) } ASSERT_NORMAL_FORK(forknum); + // 读取主分支的分段头部缓冲区 Buffer seg_buffer = read_head_buffer(reln, MAIN_FORKNUM, false); SegmentCheck(BufferIsValid(seg_buffer)); SegmentHead *seg_head = (SegmentHead *)PageGetContents(BufferGetBlock(seg_buffer)); BlockNumber res = 0; + // 根据分段类型调用不同的获取总块数函数 if (seg_head->magic == BUCKET_SEGMENT_MAGIC) { res = bucket_totalblocks(reln, forknum, seg_buffer); } else { res = seg_fork_totalblocks(reln, forknum); } + // 释放分段头部缓冲区 SegReleaseBuffer(seg_buffer); return res; } - +/* + * 功能:检查指定分支的分段是否存在 + * + * 参数列表: + * spc:SegSpace 结构,表示SegSpace对象 + * reln:SMgrRelation 结构,表示SMgrRelation对象 + * forknum:ForkNumber,表示分支号 + * pblk:XLogPhyBlock 结构,表示物理块号和日志序列号 + * + * 注意:该函数用于检查指定分支的分段是否存在,存在则返回true,否则返回false。 + */ bool seg_fork_exists(SegSpace *spc, SMgrRelation reln, ForkNumber forknum, const XLogPhyBlock *pblk) { ASSERT_NORMAL_FORK(forknum); @@ -1790,6 +2261,7 @@ bool seg_fork_exists(SegSpace *spc, SMgrRelation reln, ForkNumber forknum, const RelFileNode rnode = reln->smgr_rnode.node; BlockNumber lastblock = spc_size(spc, pblk->relNode, forknum); + // 如果块号大于等于最后一个块号,返回false if (pblk->block >= lastblock) { return false; } @@ -1801,7 +2273,7 @@ bool seg_fork_exists(SegSpace *spc, SMgrRelation reln, ForkNumber forknum, const /* check if main relation is still here */ XLogRecPtr seglsn = seg_get_headlsn(spc, rnode.relNode, IsBucketFileNode(rnode)); - + // 如果LSN无效或者pblk的LSN小于等于分段头部的LSN,返回false if (XLogRecPtrIsInvalid(seglsn) || XLByteLE(pblk->lsn, seglsn)) { /* segment header is reused */ return false; @@ -1809,6 +2281,7 @@ bool seg_fork_exists(SegSpace *spc, SMgrRelation reln, ForkNumber forknum, const /* check if relation or bucket's fork is still here */ Buffer seg_buffer = read_head_buffer_redo(reln, forknum, pblk->lsn); + // 如果分支缓冲区无效,返回false if (!BufferIsValid(seg_buffer)) { /* bucket or fork is dropped */ return false; @@ -1816,6 +2289,7 @@ bool seg_fork_exists(SegSpace *spc, SMgrRelation reln, ForkNumber forknum, const SegmentHead *seg_head = (SegmentHead *)PageGetContents(BufferGetBlock(seg_buffer)); LockBuffer(seg_buffer, BUFFER_LOCK_SHARE); + // 如果分支缓冲区不是分段版本或者分段头部的魔数不是SEGMENT_HEAD_MAGIC,返回false if (!PageIsSegmentVersion(BufferGetBlock(seg_buffer)) || seg_head->magic != SEGMENT_HEAD_MAGIC) { /* segment header reused */ SegUnlockReleaseBuffer(seg_buffer); @@ -1834,25 +2308,64 @@ bool seg_fork_exists(SegSpace *spc, SMgrRelation reln, ForkNumber forknum, const } /* APIs for others */ - +/* + * 功能:为指定的分支预分配扩展空间 + * + * 参数列表: + * rNode:RelFileNode 结构,表示关系文件节点 + * forkNum:ForkNumber,表示分支号 + * blkno:BlockNumber,表示块号 + * + * 注意:该函数用于为指定的分支预分配扩展空间。 + */ void seg_preextend(RelFileNode &rNode, ForkNumber forkNum, BlockNumber blkno) { + // 打开关系文件并进行预分配扩展 SMgrRelation reln = smgropen(rNode, InvalidBackendId); seg_extend_internal(reln, forkNum, blkno); } - +/* + * 功能:物理读取指定分支的指定块数据 + * + * 参数列表: + * spc:SegSpace 结构指针,表示分段空间 + * rNode:RelFileNode 结构,表示关系文件节点 + * forknum:ForkNumber,表示分支号 + * blocknum:BlockNumber,表示块号 + * buffer:char 指针,用于存储读取的数据 + * + * 注意:该函数用于物理读取指定分支的指定块数据。 + */ void seg_physical_read(SegSpace *spc, RelFileNode &rNode, ForkNumber forknum, BlockNumber blocknum, char *buffer) { + // 检查关系文件节点是否为物理文件类型 SegmentCheck(IsSegmentPhysicalRelNode(rNode)); + // 检查分段空间是否有效 SegmentCheck(spc != NULL); + // 调用 spc_read_block 函数进行物理读取 spc_read_block(spc, rNode, forknum, buffer, blocknum); } - +/* + * 功能:物理写入指定分支的指定块数据 + * + * 参数列表: + * spc:SegSpace 结构指针,表示分段空间 + * rNode:RelFileNode 结构,表示关系文件节点 + * forknum:ForkNumber,表示分支号 + * blocknum:BlockNumber,表示块号 + * buffer:const char 指针,用于存储要写入的数据 + * skipFsync:bool,表示是否跳过文件同步操作 + * + * 注意:该函数用于物理写入指定分支的指定块数据。 + */ void seg_physical_write(SegSpace *spc, RelFileNode &rNode, ForkNumber forknum, BlockNumber blocknum, const char *buffer, bool skipFsync) { + // 检查关系文件节点是否为物理文件类型 SegmentCheck(IsSegmentPhysicalRelNode(rNode)); + // 检查分段空间是否有效 SegmentCheck(spc != NULL); + // 调用 spc_write_block 函数进行物理写入 spc_write_block(spc, rNode, forknum, buffer, blocknum); } diff --git a/src/gausskernel/storage/smgr/smgr.cpp b/src/gausskernel/storage/smgr/smgr.cpp index b76bbfe3b..5ad1e44e3 100644 --- a/src/gausskernel/storage/smgr/smgr.cpp +++ b/src/gausskernel/storage/smgr/smgr.cpp @@ -63,63 +63,17 @@ typedef struct f_smgr { static const f_smgr smgrsw[] = { /* magnetic disk */ - { mdinit, - NULL, - mdclose, - mdcreate, - mdexists, - mdunlink, - mdextend, - mdprefetch, - mdread, - mdwrite, - mdwriteback, - mdnblocks, - mdtruncate, - mdimmedsync, - mdasyncread, - mdasyncwrite, - NULL - }, + {mdinit, NULL, mdclose, mdcreate, mdexists, mdunlink, mdextend, mdprefetch, mdread, mdwrite, mdwriteback, mdnblocks, + mdtruncate, mdimmedsync, mdasyncread, mdasyncwrite, NULL}, /* undo file */ - { - InitUndoFile, - NULL, - CloseUndoFile, - CreateUndoFile, - CheckUndoFileExists, - UnlinkUndoFile, - ExtendUndoFile, - PrefetchUndoFile, - ReadUndoFile, - WriteUndoFile, - WritebackUndoFile, - GetUndoFileNblocks, - NULL, - NULL - }, + {InitUndoFile, NULL, CloseUndoFile, CreateUndoFile, CheckUndoFileExists, UnlinkUndoFile, ExtendUndoFile, + PrefetchUndoFile, ReadUndoFile, WriteUndoFile, WritebackUndoFile, GetUndoFileNblocks, NULL, NULL}, /* segment-page */ - { - seg_init, - seg_shutdown, - seg_close, - seg_create, - seg_exists, - seg_unlink, - seg_extend, - seg_prefetch, - seg_read, - seg_write, - seg_writeback, - seg_nblocks, - seg_truncate, - seg_immedsync, - seg_async_read, - seg_async_write, - seg_move_buckets - }, + {seg_init, seg_shutdown, seg_close, seg_create, seg_exists, seg_unlink, seg_extend, seg_prefetch, seg_read, + seg_write, seg_writeback, seg_nblocks, seg_truncate, seg_immedsync, seg_async_read, seg_async_write, + seg_move_buckets}, }; static const int NSmgr = lengthof(smgrsw); @@ -137,7 +91,6 @@ static inline int ChooseSmgrManager(RelFileNode rnode) return MD_MANAGER; } - /* * smgrinit(), smgrshutdown() -- Initialize or shut down storage * managers. @@ -183,7 +136,7 @@ void smgrshutdown(int code, Datum arg) * * This does not attempt to actually open the underlying file. */ -SMgrRelation smgropen(const RelFileNode& rnode, BackendId backend, int col /* = 0 */) +SMgrRelation smgropen(const RelFileNode &rnode, BackendId backend, int col /* = 0 */) { RelFileNodeBackend brnode; SMgrRelation reln; @@ -207,16 +160,15 @@ SMgrRelation smgropen(const RelFileNode& rnode, BackendId backend, int col /* = hashCtrl.hash = tag_hash; if (EnableLocalSysCache()) { hashCtrl.hcxt = t_thrd.lsc_cxt.lsc->lsc_mydb_memcxt; - t_thrd.lsc_cxt.lsc->SMgrRelationHash = hash_create("smgr relation table", 400, - &hashCtrl, HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT); + t_thrd.lsc_cxt.lsc->SMgrRelationHash = + hash_create("smgr relation table", 400, &hashCtrl, HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT); dlist_init(&t_thrd.lsc_cxt.lsc->unowned_reln); } else { hashCtrl.hcxt = u_sess->cache_mem_cxt; - u_sess->storage_cxt.SMgrRelationHash = hash_create("smgr relation table", 400, - &hashCtrl, HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT); + u_sess->storage_cxt.SMgrRelationHash = + hash_create("smgr relation table", 400, &hashCtrl, HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT); dlist_init(&u_sess->storage_cxt.unowned_reln); } - } START_CRIT_SECTION(); @@ -224,7 +176,7 @@ SMgrRelation smgropen(const RelFileNode& rnode, BackendId backend, int col /* = /* Look up or create an entry */ brnode.node = rnode; brnode.backend = backend; - reln = (SMgrRelation)hash_search(GetSMgrRelationHash(), (void*)&brnode, HASH_ENTER, &found); + reln = (SMgrRelation)hash_search(GetSMgrRelationHash(), (void *)&brnode, HASH_ENTER, &found); /* Initialize it if not present before */ if (!found) { @@ -238,8 +190,8 @@ SMgrRelation smgropen(const RelFileNode& rnode, BackendId backend, int col /* = reln->smgr_vm_nblocks = InvalidBlockNumber; reln->encrypt = false; temp = col + 1; - reln->smgr_bcm_nblocks = (BlockNumber*)MemoryContextAllocZero( - LocalSmgrStorageMemoryCxt(), temp * sizeof(BlockNumber)); + reln->smgr_bcm_nblocks = + (BlockNumber *)MemoryContextAllocZero(LocalSmgrStorageMemoryCxt(), temp * sizeof(BlockNumber)); reln->smgr_bcmarry_size = temp; for (colnum = 0; colnum < reln->smgr_bcmarry_size; colnum++) { reln->smgr_bcm_nblocks[colnum] = InvalidBlockNumber; @@ -253,13 +205,13 @@ SMgrRelation smgropen(const RelFileNode& rnode, BackendId backend, int col /* = reln->seg_space = NULL; if (reln->smgr_which == SEGMENT_MANAGER) { reln->md_fdarray_size = fdNeeded; - reln->seg_desc = (struct SegmentDesc **)MemoryContextAllocZero( - LocalSmgrStorageMemoryCxt(), fdNeeded * sizeof(struct SegmentDesc *)); + reln->seg_desc = (struct SegmentDesc **)MemoryContextAllocZero(LocalSmgrStorageMemoryCxt(), + fdNeeded * sizeof(struct SegmentDesc *)); reln->md_fd = NULL; } else if (reln->smgr_which == MD_MANAGER) { reln->md_fdarray_size = fdNeeded; - reln->md_fd = (struct _MdfdVec**)MemoryContextAllocZero( - LocalSmgrStorageMemoryCxt(), fdNeeded * sizeof(struct _MdfdVec*)); + reln->md_fd = (struct _MdfdVec **)MemoryContextAllocZero(LocalSmgrStorageMemoryCxt(), + fdNeeded * sizeof(struct _MdfdVec *)); reln->seg_desc = NULL; } else { reln->md_fdarray_size = 1; @@ -284,7 +236,8 @@ SMgrRelation smgropen(const RelFileNode& rnode, BackendId backend, int col /* = int old_bcmarry_size = reln->smgr_bcmarry_size; temp = reln->smgr_bcmarry_size * 2; temp = Max(temp, (col + 1)); - reln->smgr_bcm_nblocks = (BlockNumber *)repalloc((void *)reln->smgr_bcm_nblocks, temp * sizeof(BlockNumber)); + reln->smgr_bcm_nblocks = + (BlockNumber *)repalloc((void *)reln->smgr_bcm_nblocks, temp * sizeof(BlockNumber)); reln->smgr_bcmarry_size = temp; for (int colnum = old_bcmarry_size; colnum < reln->smgr_bcmarry_size; colnum++) @@ -300,7 +253,7 @@ SMgrRelation smgropen(const RelFileNode& rnode, BackendId backend, int col /* = int old_fdarray_size = reln->md_fdarray_size; temp = reln->md_fdarray_size * 2; temp = Max(temp, fdNeeded); - reln->md_fd = (struct _MdfdVec**)repalloc((void*)reln->md_fd, temp * sizeof(struct _MdfdVec*)); + reln->md_fd = (struct _MdfdVec **)repalloc((void *)reln->md_fd, temp * sizeof(struct _MdfdVec *)); reln->md_fdarray_size = temp; for (int forknum = old_fdarray_size; forknum < reln->md_fdarray_size; forknum++) @@ -387,7 +340,7 @@ static bool smgrhaschildern(SMgrRelation reln) void smgrclose(SMgrRelation reln, BlockNumber blockNum) { ereport(DEBUG5, (errmsg("smgr close %p", reln))); - SMgrRelation* owner = NULL; + SMgrRelation *owner = NULL; int forknum; for (forknum = 0; forknum < (int)(reln->md_fdarray_size); forknum++) { @@ -435,7 +388,7 @@ void smgrclose(SMgrRelation reln, BlockNumber blockNum) * smgrcloseall() -- Close all existing SMgrRelation objects. */ void smgrcloseall(void) -{ +{ HASH_SEQ_STATUS status; SMgrRelation reln; @@ -449,7 +402,7 @@ void smgrcloseall(void) while ((reln = (SMgrRelation)hash_seq_search(&status)) != NULL) { if (smgrhaschildern(reln)) { - /* + /* * if the smgr node has children, it may incur close of all its children. * we can not close it in the first loop, otherwise the hashtable iterator is broken. */ @@ -541,9 +494,7 @@ void smgrcreate(SMgrRelation reln, ForkNumber forknum, bool isRedo) * should be here and not in commands/tablespace.c? But that would imply * importing a lot of stuff that smgr.c oughtn't know, either. */ - TablespaceCreateDbspace(reln->smgr_rnode.node.spcNode, - reln->smgr_rnode.node.dbNode, - isRedo); + TablespaceCreateDbspace(reln->smgr_rnode.node.spcNode, reln->smgr_rnode.node.dbNode, isRedo); (*(smgrsw[reln->smgr_which].smgr_create))(reln, forknum, isRedo); } @@ -668,8 +619,7 @@ void smgrdounlinkfork(SMgrRelation reln, ForkNumber forknum, bool isRedo) * EOF). Note that we assume writing a block beyond current EOF * causes intervening file space to become filled with zeroes. */ -void smgrextend(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, - char *buffer, bool skipFsync) +void smgrextend(SMgrRelation reln, ForkNumber forknum, BlockNumber blocknum, char *buffer, bool skipFsync) { (*(smgrsw[reln->smgr_which].smgr_extend))(reln, forknum, blocknum, buffer, skipFsync); } @@ -764,15 +714,13 @@ BlockNumber smgrnblocks(SMgrRelation reln, ForkNumber forknum) * Returns an InvalidBlockNumber when not in recovery and when the relation * fork size is not cached. Now, we only support cache main fork. */ -BlockNumber -smgrnblocks_cached(SMgrRelation reln, ForkNumber forknum) +BlockNumber smgrnblocks_cached(SMgrRelation reln, ForkNumber forknum) { /* * For now, we only use cached values in recovery due to lack of a shared * invalidation mechanism for changes in file size. */ - if (RecoveryInProgress() && forknum == MAIN_FORKNUM && - reln->smgr_cached_nblocks != InvalidBlockNumber) { + if (RecoveryInProgress() && forknum == MAIN_FORKNUM && reln->smgr_cached_nblocks != InvalidBlockNumber) { return reln->smgr_cached_nblocks; } @@ -860,7 +808,6 @@ void smgrimmedsync(SMgrRelation reln, ForkNumber forknum) (*(smgrsw[reln->smgr_which].smgr_immedsync))(reln, forknum); } - /* * smgrmovebuckets() -- Move buckets between two relation. */ @@ -943,7 +890,13 @@ ScalarToDatum GetTransferFuncByTypeOid(Oid attTypeOid) } } } - +/* + * 功能:将要删除的关系的某个分支信息添加到哈希表中 + * + * 参数列表: + * node:RelFileNode,表示关系文件节点信息 + * forkNum:ForkNumber,表示分支号 + */ static void push_unlink_rel_one_fork_to_hashtbl(RelFileNode node, ForkNumber forkNum) { HTAB *relfilenode_hashtbl = g_instance.bgwriter_cxt.unlink_rel_fork_hashtbl; @@ -956,7 +909,7 @@ static void push_unlink_rel_one_fork_to_hashtbl(RelFileNode node, ForkNumber for key.forkNum = forkNum; LWLockAcquire(g_instance.bgwriter_cxt.rel_one_fork_hashtbl_lock, LW_EXCLUSIVE); - entry = (DelForkFileTag*)hash_search(relfilenode_hashtbl, &(key), HASH_ENTER, &found); + entry = (DelForkFileTag *)hash_search(relfilenode_hashtbl, &(key), HASH_ENTER, &found); if (!found) { entry->forkrnode.rnode.spcNode = key.rnode.spcNode; entry->forkrnode.rnode.dbNode = key.rnode.dbNode; @@ -974,4 +927,3 @@ static void push_unlink_rel_one_fork_to_hashtbl(RelFileNode node, ForkNumber for } return; } - diff --git a/src/gausskernel/storage/sync/knl_usync.cpp b/src/gausskernel/storage/sync/knl_usync.cpp index 17f78738e..f2c73f059 100644 --- a/src/gausskernel/storage/sync/knl_usync.cpp +++ b/src/gausskernel/storage/sync/knl_usync.cpp @@ -54,7 +54,7 @@ * (Regular backends do not track pending operations locally, but forward * them to the checkpointer.) */ -typedef uint16 CycleCtr; /* can be any convenient integer size */ +typedef uint16 CycleCtr; /* can be any convenient integer size */ typedef struct { FileTag tag; /* identifies handler and file */ @@ -109,11 +109,16 @@ static const SyncOps SYNCSW[] = { }; static const int NSync = lengthof(SYNCSW); - +/* + * 功能:初始化待处理操作的哈希表,用于跟踪需要执行的文件系统同步操作 + * + * 参数列表:无 + */ void InitPendingOps(void) { + // 如果不处于Postmaster进程下,或者是启动进程、检查点进程、页写进程,则执行以下操作 if (!IsUnderPostmaster || AmStartupProcess() || AmCheckpointerProcess() || AmPageWriterMainProcess()) { - HASHCTL hashCtl; + HASHCTL hashCtl; errno_t rc; /* @@ -125,17 +130,18 @@ void InitPendingOps(void) * Fortunately the hash table is small so that's unlikely to happen in * practice. */ - u_sess->storage_cxt.pendingOpsCxt = AllocSetContextCreate(u_sess->top_mem_cxt, - "Pending ops context", ALLOCSET_DEFAULT_SIZES); - MemoryContextAllowInCriticalSection(u_sess->storage_cxt.pendingOpsCxt, true); - rc = memset_s(&hashCtl, sizeof(hashCtl), 0, sizeof(hashCtl)); - securec_check(rc, "", ""); - hashCtl.keysize = sizeof(FileTag); - hashCtl.entrysize = sizeof(PendingFsyncEntry); - hashCtl.hcxt = u_sess->storage_cxt.pendingOpsCxt; - hashCtl.hash = tag_hash; - u_sess->storage_cxt.pendingOps = hash_create("Pending Ops Table", - 100L, &hashCtl, HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); + u_sess->storage_cxt.pendingOpsCxt = + AllocSetContextCreate(u_sess->top_mem_cxt, "Pending ops context", + ALLOCSET_DEFAULT_SIZES); // 创建内存上下文来管理待处理操作的内存 + MemoryContextAllowInCriticalSection(u_sess->storage_cxt.pendingOpsCxt, true); // 允许在临界区内分配内存 + rc = memset_s(&hashCtl, sizeof(hashCtl), 0, sizeof(hashCtl)); // 初始化hashCtl结构体 + securec_check(rc, "", ""); // 检查memset_s是否失败 + hashCtl.keysize = sizeof(FileTag); // 哈希键的大小为FileTag的大小 + hashCtl.entrysize = sizeof(PendingFsyncEntry); // 哈希表项的大小为PendingFsyncEntry的大小 + hashCtl.hcxt = u_sess->storage_cxt.pendingOpsCxt; // 哈希表使用上面创建的内存上下文 + hashCtl.hash = tag_hash; // 哈希函数使用tag_hash来计算哈希值 + u_sess->storage_cxt.pendingOps = hash_create( + "Pending Ops Table", 100L, &hashCtl, HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); // 创建哈希表来存储待处理操作 } } @@ -189,7 +195,7 @@ void SyncPostCheckpoint(void) absorbCounter = UNLINKS_PER_ABSORB; while (u_sess->storage_cxt.pendingUnlinks != NIL) { - PendingUnlinkEntry *entry = (PendingUnlinkEntry *) linitial(u_sess->storage_cxt.pendingUnlinks); + PendingUnlinkEntry *entry = (PendingUnlinkEntry *)linitial(u_sess->storage_cxt.pendingUnlinks); char path[MAXPGPATH]; /* @@ -215,8 +221,7 @@ void SyncPostCheckpoint(void) * the possibility that we delete the file first. */ if (errno != ENOENT) { - ereport(WARNING, (errcode_for_file_access(), - errmsg("could not remove file \"%s\": %m", path))); + ereport(WARNING, (errcode_for_file_access(), errmsg("could not remove file \"%s\": %m", path))); } } @@ -287,7 +292,7 @@ static void HandleAbnormalSyncExit(bool syncInProgress) if (syncInProgress) { /* prior try failed, so update any stale cycle_ctr values */ hash_seq_init(&hstat, u_sess->storage_cxt.pendingOps); - while ((entry = (PendingFsyncEntry *) hash_seq_search(&hstat)) != NULL) { + while ((entry = (PendingFsyncEntry *)hash_seq_search(&hstat)) != NULL) { entry->cycle_ctr = u_sess->storage_cxt.sync_cycle_ctr; } } @@ -305,13 +310,13 @@ void ProcessSyncRequests(void) int absorbCounter; /* Statistics on sync times */ - int processed = 0; - instr_time syncStart; - instr_time syncEnd; - instr_time syncDiff; - uint64 elapsed; - uint64 longest = 0; - uint64 totalElapsed = 0; + int processed = 0; + instr_time syncStart; + instr_time syncEnd; + instr_time syncDiff; + uint64 elapsed; + uint64 longest = 0; + uint64 totalElapsed = 0; /* * This is only called during checkpoints, and checkpoints should only @@ -342,7 +347,7 @@ void ProcessSyncRequests(void) /* Now scan the hashtable for fsync requests to process */ absorbCounter = FSYNCS_PER_ABSORB; hash_seq_init(&hstat, u_sess->storage_cxt.pendingOps); - while ((entry = (PendingFsyncEntry *) hash_seq_search(&hstat)) != NULL) { + while ((entry = (PendingFsyncEntry *)hash_seq_search(&hstat)) != NULL) { int failures; /* @@ -364,7 +369,7 @@ void ProcessSyncRequests(void) } /* Else assert we haven't missed it */ - Assert((CycleCtr) (entry->cycle_ctr + 1) == u_sess->storage_cxt.sync_cycle_ctr); + Assert((CycleCtr)(entry->cycle_ctr + 1) == u_sess->storage_cxt.sync_cycle_ctr); /* * If in checkpointer, we want to absorb pending requests every so @@ -405,10 +410,10 @@ void ProcessSyncRequests(void) processed++; if (u_sess->attr.attr_common.log_checkpoints) { - ereport(DEBUG1, (errmsg("checkpoint sync: number=%d file=%s time=%.3f msec", - processed, path, (double) elapsed / MSEC_PER_MICROSEC))); + ereport(DEBUG1, (errmsg("checkpoint sync: number=%d file=%s time=%.3f msec", processed, path, + (double)elapsed / MSEC_PER_MICROSEC))); } - break; /* out of retry loop */ + break; /* out of retry loop */ } /* @@ -418,8 +423,7 @@ void ProcessSyncRequests(void) */ if (!FILE_POSSIBLY_DELETED(errno) || failures > 0) { if (check_unlink_rel_hashtbl(entry->tag.rnode, entry->tag.forknum)) { - ereport(DEBUG1, - (errmsg("could not fsync file \"%s\": %m, this relation has been remove", path))); + ereport(DEBUG1, (errmsg("could not fsync file \"%s\": %m, this relation has been remove", path))); break; } @@ -432,13 +436,12 @@ void ProcessSyncRequests(void) break; } /* treat it as truncate */ - ereport(data_sync_elevel(ERROR), (errcode_for_file_access(), - errmsg("could not fsync file \"%s\": %m", path))); + ereport(data_sync_elevel(ERROR), + (errcode_for_file_access(), errmsg("could not fsync file \"%s\": %m", path))); break; } else { ereport(DEBUG1, - (errcode_for_file_access(), - errmsg("could not fsync file \"%s\" but retrying: %m", path))); + (errcode_for_file_access(), errmsg("could not fsync file \"%s\" but retrying: %m", path))); } /* @@ -475,11 +478,10 @@ void ProcessUnlinkList(const FileTag *ftag) if (!AmPageWriterMainProcess()) { prev = NULL; for (cell = list_head(u_sess->storage_cxt.pendingUnlinks); cell; cell = next) { - PendingUnlinkEntry *entry = (PendingUnlinkEntry *) lfirst(cell); + PendingUnlinkEntry *entry = (PendingUnlinkEntry *)lfirst(cell); next = lnext(cell); if (entry->tag.handler == ftag->handler && SYNCSW[ftag->handler].matchfiletag(ftag, &entry->tag)) { - u_sess->storage_cxt.pendingUnlinks = - list_delete_cell(u_sess->storage_cxt.pendingUnlinks, cell, prev); + u_sess->storage_cxt.pendingUnlinks = list_delete_cell(u_sess->storage_cxt.pendingUnlinks, cell, prev); pfree(entry); } else { prev = cell; @@ -505,7 +507,7 @@ void RememberSyncRequest(const FileTag *ftag, SyncRequestType type) PendingFsyncEntry *entry; /* Cancel previously entered request */ - entry = (PendingFsyncEntry *) hash_search(u_sess->storage_cxt.pendingOps, (void *) ftag, HASH_FIND, NULL); + entry = (PendingFsyncEntry *)hash_search(u_sess->storage_cxt.pendingOps, (void *)ftag, HASH_FIND, NULL); if (entry != NULL) { entry->canceled = true; } @@ -515,9 +517,8 @@ void RememberSyncRequest(const FileTag *ftag, SyncRequestType type) /* Cancel matching fsync requests */ hash_seq_init(&hstat, u_sess->storage_cxt.pendingOps); - while ((entry = (PendingFsyncEntry *) hash_seq_search(&hstat)) != NULL) { - if (entry->tag.handler == ftag->handler && - SYNCSW[ftag->handler].matchfiletag(ftag, &entry->tag)) { + while ((entry = (PendingFsyncEntry *)hash_seq_search(&hstat)) != NULL) { + if (entry->tag.handler == ftag->handler && SYNCSW[ftag->handler].matchfiletag(ftag, &entry->tag)) { entry->canceled = true; } } @@ -528,7 +529,7 @@ void RememberSyncRequest(const FileTag *ftag, SyncRequestType type) Assert(!AmPageWriterMainProcess()); /* Unlink request: put it in the linked list */ MemoryContext oldcxt = MemoryContextSwitchTo(u_sess->storage_cxt.pendingOpsCxt); - PendingUnlinkEntry *entry = (PendingUnlinkEntry*)palloc(sizeof(PendingUnlinkEntry)); + PendingUnlinkEntry *entry = (PendingUnlinkEntry *)palloc(sizeof(PendingUnlinkEntry)); entry->tag = *ftag; entry->cycle_ctr = u_sess->storage_cxt.checkpoint_cycle_ctr; u_sess->storage_cxt.pendingUnlinks = lappend(u_sess->storage_cxt.pendingUnlinks, entry); @@ -541,12 +542,12 @@ void RememberSyncRequest(const FileTag *ftag, SyncRequestType type) Assert(type == SYNC_REQUEST); - entry = (PendingFsyncEntry *) hash_search(u_sess->storage_cxt.pendingOps, (void *) ftag, HASH_ENTER, &found); + entry = (PendingFsyncEntry *)hash_search(u_sess->storage_cxt.pendingOps, (void *)ftag, HASH_ENTER, &found); /* if new entry, initialize it */ if (!found) { entry->cycle_ctr = u_sess->storage_cxt.sync_cycle_ctr; entry->canceled = false; - entry->tag = *ftag; // ZheapTodo initialize tag first + entry->tag = *ftag; // ZheapTodo initialize tag first } /* @@ -594,8 +595,7 @@ static bool ForwardSyncRequest(const FileTag *ftag, SyncRequestType type) ret = CkptForwardSyncRequest(ftag, type); break; default: - ereport(ERROR, - (errmsg("Incremental ckpt, Error SyncRequestType, the type is %d", type))); + ereport(ERROR, (errmsg("Incremental ckpt, Error SyncRequestType, the type is %d", type))); break; } } @@ -672,7 +672,7 @@ void ForgetDatabaseSyncRequests(Oid dbid) /* * We need two tags to forget two kinds of fsync requests generated by segment store and heap store respectively */ - for (int i=0; iincre_ckpt_sync_shmem; /* Initialize skip_slot array */ - skip_slot = (bool*)palloc0(sizeof(bool) * incre_ckpt_sync_shmem->num_requests); + skip_slot = (bool *)palloc0(sizeof(bool) * incre_ckpt_sync_shmem->num_requests); /* must hold the request queue in exclusive mode */ Assert(LWLockHeldByMe(incre_ckpt_sync_shmem->sync_queue_lwlock)); @@ -710,29 +710,27 @@ static bool CompactPageWriterRequestQueue(void) incre_ckpt_sync_shmem->requests[preserve_count++] = incre_ckpt_sync_shmem->requests[n]; } - ereport(DEBUG1, - (errmsg("pagewriter compacted fsync request queue from %d entries to %d entries", - incre_ckpt_sync_shmem->num_requests, preserve_count))); + ereport(DEBUG1, (errmsg("pagewriter compacted fsync request queue from %d entries to %d entries", + incre_ckpt_sync_shmem->num_requests, preserve_count))); incre_ckpt_sync_shmem->num_requests = preserve_count; pfree(skip_slot); return true; } - /* PgwrForwardSyncRequest +/* PgwrForwardSyncRequest * Forward a file-fsync request from a backend to the pagewriter. */ bool PgwrForwardSyncRequest(const FileTag *ftag, SyncRequestType type) { - CheckpointerRequest* request = NULL; + CheckpointerRequest *request = NULL; bool too_full = false; IncreCkptSyncShmemStruct *incre_ckpt_sync_shmem = g_instance.ckpt_cxt_ctl->incre_ckpt_sync_shmem; LWLock *sync_queue_lwlock = incre_ckpt_sync_shmem->sync_queue_lwlock; if (AmPageWriterMainProcess()) { - ereport(ERROR, - (errcode(ERRCODE_WITH_CHECK_OPTION_VIOLATION), - errmsg("PgwrForwardSyncRequest must not be called in pagewriter main thread"))); + ereport(ERROR, (errcode(ERRCODE_WITH_CHECK_OPTION_VIOLATION), + errmsg("PgwrForwardSyncRequest must not be called in pagewriter main thread"))); } LWLockAcquire(sync_queue_lwlock, LW_EXCLUSIVE); @@ -742,8 +740,9 @@ bool PgwrForwardSyncRequest(const FileTag *ftag, SyncRequestType type) * backend will have to perform its own fsync request. But before forcing * that to happen, we can try to compact the request queue. */ - if (incre_ckpt_sync_shmem->pagewritermain_pid == 0 || (incre_ckpt_sync_shmem->num_requests >= - incre_ckpt_sync_shmem->max_requests && !CompactPageWriterRequestQueue())) { + if (incre_ckpt_sync_shmem->pagewritermain_pid == 0 || + (incre_ckpt_sync_shmem->num_requests >= incre_ckpt_sync_shmem->max_requests && + !CompactPageWriterRequestQueue())) { LWLockRelease(sync_queue_lwlock); return false; } @@ -771,8 +770,8 @@ bool PgwrForwardSyncRequest(const FileTag *ftag, SyncRequestType type) */ void PgwrAbsorbFsyncRequests(void) { - CheckpointerRequest* requests = NULL; - CheckpointerRequest* request = NULL; + CheckpointerRequest *requests = NULL; + CheckpointerRequest *request = NULL; IncreCkptSyncShmemStruct *incre_ckpt_sync_shmem = g_instance.ckpt_cxt_ctl->incre_ckpt_sync_shmem; LWLock *sync_queue_lwlock = incre_ckpt_sync_shmem->sync_queue_lwlock; int n; @@ -795,9 +794,9 @@ void PgwrAbsorbFsyncRequests(void) if (n > 0) { errno_t rc; - requests = (CheckpointerRequest*)palloc(n * sizeof(CheckpointerRequest)); + requests = (CheckpointerRequest *)palloc(n * sizeof(CheckpointerRequest)); rc = memcpy_s(requests, n * sizeof(CheckpointerRequest), incre_ckpt_sync_shmem->requests, - n * sizeof(CheckpointerRequest)); + n * sizeof(CheckpointerRequest)); securec_check(rc, "\0", "\0"); } @@ -820,7 +819,7 @@ void PgwrAbsorbFsyncRequests(void) */ void PageWriterSyncWithAbsorption(void) { - volatile IncreCkptSyncShmemStruct* cps = g_instance.ckpt_cxt_ctl->incre_ckpt_sync_shmem; + volatile IncreCkptSyncShmemStruct *cps = g_instance.ckpt_cxt_ctl->incre_ckpt_sync_shmem; SpinLockAcquire(&cps->sync_lock); cps->fsync_start++; @@ -833,10 +832,10 @@ void PageWriterSyncWithAbsorption(void) SpinLockRelease(&cps->sync_lock); } -const int WAIT_THREAD_START = 600; /* every time sleep 0.1 sec, the 1min = 600 * 0.1 sec */ +const int WAIT_THREAD_START = 600; /* every time sleep 0.1 sec, the 1min = 600 * 0.1 sec */ void RequestPgwrSync(void) { - volatile IncreCkptSyncShmemStruct* cps = g_instance.ckpt_cxt_ctl->incre_ckpt_sync_shmem; + volatile IncreCkptSyncShmemStruct *cps = g_instance.ckpt_cxt_ctl->incre_ckpt_sync_shmem; /* * Send signal to request sync. It's possible that the pagewriter main thread @@ -855,9 +854,8 @@ void RequestPgwrSync(void) } else if (gs_signal_send(cps->pagewritermain_pid, SIGINT) != 0) { /* max wait 1min */ if (ntries >= WAIT_THREAD_START) { - ereport(LOG, - (errmsg("could not signal for pagewriter main thread: %m, thread pid is %lu", - cps->pagewritermain_pid))); + ereport(LOG, (errmsg("could not signal for pagewriter main thread: %m, thread pid is %lu", + cps->pagewritermain_pid))); break; } } else { @@ -887,7 +885,7 @@ void PageWriterSync(void) int64 old_fsync_start = 0; int64 new_fsync_start = 0; int64 new_fsync_done = 0; - volatile IncreCkptSyncShmemStruct* cps = g_instance.ckpt_cxt_ctl->incre_ckpt_sync_shmem; + volatile IncreCkptSyncShmemStruct *cps = g_instance.ckpt_cxt_ctl->incre_ckpt_sync_shmem; SpinLockAcquire(&cps->sync_lock); old_fsync_start = cps->fsync_start; SpinLockRelease(&cps->sync_lock); diff --git a/src/gausskernel/storage/tcap/tcap_drop.cpp b/src/gausskernel/storage/tcap/tcap_drop.cpp index a6cb8515c..3fb4390ac 100644 --- a/src/gausskernel/storage/tcap/tcap_drop.cpp +++ b/src/gausskernel/storage/tcap/tcap_drop.cpp @@ -92,142 +92,170 @@ #include "storage/tcap.h" #include "storage/tcap_impl.h" - +/* + * 功能该函数用于重命名数据库中的表。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示要重命名的对象的地址信息。 + * newName:const char指针,表示新的对象名称。 + */ static void TrRenameClass(TrObjDesc *baseDesc, ObjectAddress *object, const char *newName) { - Relation rel; - HeapTuple tup; - HeapTuple newtup; - char rbname[NAMEDATALEN]; - Datum values[Natts_pg_class] = { 0 }; - bool nulls[Natts_pg_class] = { false }; - bool replaces[Natts_pg_class] = { false }; - Oid relid = object->objectId; - errno_t rc = EOK; + Relation rel; // 用于表示关系(表)的关系对象 + HeapTuple tup; // 用于存储从系统缓存中检索的堆元组 + HeapTuple newtup; // 用于存储修改后的堆元组 + char rbname[NAMEDATALEN]; // 存储新的对象名称的字符数组 + Datum values[Natts_pg_class] = { 0 }; // 存储待更新的列值 + bool nulls[Natts_pg_class] = { false }; // 标志位数组,指示每列是否为NULL + bool replaces[Natts_pg_class] = { false }; // 标志位数组,指示哪些列将被替换 + Oid relid = object->objectId; // 要重命名的表的OID(对象标识符) + errno_t rc = EOK; // 用于处理字符串复制的错误码 if (newName) { - rc = strncpy_s(rbname, NAMEDATALEN, newName, strlen(newName)); - securec_check(rc, "\0", "\0"); + rc = strncpy_s(rbname, NAMEDATALEN, newName, strlen(newName)); // 将新名称复制到rbname中 + securec_check(rc, "\0", "\0"); // 检查字符串复制是否出错 } else { - TrGenObjName(rbname, object->classId, relid); + TrGenObjName(rbname, object->classId, relid); // 根据对象的类和OID生成新名称 } - replaces[Anum_pg_class_relname - 1] = true; - values[Anum_pg_class_relname - 1] = CStringGetDatum(rbname); + replaces[Anum_pg_class_relname - 1] = true; // 标记表的名称列将被替换 + values[Anum_pg_class_relname - 1] = CStringGetDatum(rbname); // 设置新的表名值 - rel = heap_open(RelationRelationId, RowExclusiveLock); + rel = heap_open(RelationRelationId, RowExclusiveLock); // 打开pg_class关系,获取用于表的操作句柄 - tup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); + tup = SearchSysCache1(RELOID, ObjectIdGetDatum(relid)); // 从系统缓存中获取表的元组 if (!HeapTupleIsValid(tup)) { - ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for relation %u", relid))); + ereport(ERROR, (errcode(ERRCODE_CACHE_LOOKUP_FAILED), errmsg("cache lookup failed for relation %u", relid))); // 如果找不到表的元组,报错 } - newtup = heap_modify_tuple(tup, RelationGetDescr(rel), values, nulls, replaces); + newtup = heap_modify_tuple(tup, RelationGetDescr(rel), values, nulls, replaces); // 修改元组的值 - simple_heap_update(rel, &newtup->t_self, newtup); + simple_heap_update(rel, &newtup->t_self, newtup); // 在堆中更新修改后的元组 - CatalogUpdateIndexes(rel, newtup); + CatalogUpdateIndexes(rel, newtup); // 更新索引 - ReleaseSysCache(tup); + ReleaseSysCache(tup); // 释放系统缓存中的元组 - heap_freetuple_ext(newtup); + heap_freetuple_ext(newtup); // 释放修改后的元组的内存 - heap_close(rel, RowExclusiveLock); + heap_close(rel, RowExclusiveLock); // 关闭pg_class关系 } +/** + * 功能:通用重命名函数,用于重命名数据库中的某些对象。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示要重命名的对象的地址信息。 + * relid:要操作的关系(表)的OID。 + * natts:待操作的属性数量。 + * oidAttrNum:OID属性的序号。 + * oidIndexId:OID属性对应的索引的OID。 + * objTag:对象的标签,用于报错信息。 + */ static void TrRenameCommon(TrObjDesc *baseDesc, ObjectAddress *object, Oid relid, int natts, int oidAttrNum, Oid oidIndexId, char *objTag) { - Relation rel; - HeapTuple tup; - HeapTuple newtup; - char rbname[NAMEDATALEN]; - Datum *values = (Datum *)palloc0(sizeof(Datum) * natts); - bool *nulls = (bool *)palloc0(sizeof(bool) * natts); - bool *replaces = (bool *)palloc0(sizeof(bool) * natts); - ScanKeyData skey[1]; - SysScanDesc sd; + Relation rel; // 用于表示关系(表)的关系对象 + HeapTuple tup; // 用于存储从系统缓存中检索的堆元组 + HeapTuple newtup; // 用于存储修改后的堆元组 + char rbname[NAMEDATALEN]; // 存储新的对象名称的字符数组 + Datum *values = (Datum *)palloc0(sizeof(Datum) * natts); // 存储待更新的列值,初始化为0 + bool *nulls = (bool *)palloc0(sizeof(bool) * natts); // 标志位数组,指示每列是否为NULL,初始化为false + bool *replaces = (bool *)palloc0(sizeof(bool) * natts); // 标志位数组,指示哪些列将被替换,初始化为false + ScanKeyData skey[1]; // 扫描键数据数组 + SysScanDesc sd; // 系统扫描描述符 - TrGenObjName(rbname, object->classId, object->objectId); + TrGenObjName(rbname, object->classId, object->objectId); // 根据对象的类和OID生成新名称 - replaces[oidAttrNum - 1] = true; - values[oidAttrNum - 1] = CStringGetDatum(rbname); + replaces[oidAttrNum - 1] = true; // 标记要被替换的属性 + values[oidAttrNum - 1] = CStringGetDatum(rbname); // 设置新的属性值 - rel = heap_open(relid, RowExclusiveLock); + rel = heap_open(relid, RowExclusiveLock); // 打开要操作的关系(表) - ScanKeyInit(&skey[0], ObjectIdAttributeNumber, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(object->objectId)); - sd = systable_beginscan(rel, oidIndexId, true, NULL, 1, skey); + ScanKeyInit(&skey[0], ObjectIdAttributeNumber, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(object->objectId)); // 初始化扫描键 + sd = systable_beginscan(rel, oidIndexId, true, NULL, 1, skey); // 开始系统表扫描 - tup = systable_getnext(sd); + tup = systable_getnext(sd); // 获取扫描结果 if (!HeapTupleIsValid(tup)) { pfree(values); pfree(nulls); pfree(replaces); ereport(ERROR, - (errcode(ERRCODE_NO_DATA_FOUND), errmsg("could not find tuple for %s %u", objTag, object->objectId))); + (errcode(ERRCODE_NO_DATA_FOUND), errmsg("could not find tuple for %s %u", objTag, object->objectId))); // 如果找不到对应元组,报错 } - newtup = heap_modify_tuple(tup, RelationGetDescr(rel), values, nulls, replaces); + newtup = heap_modify_tuple(tup, RelationGetDescr(rel), values, nulls, replaces); // 修改元组的值 - simple_heap_update(rel, &newtup->t_self, newtup); + simple_heap_update(rel, &newtup->t_self, newtup); // 在堆中更新修改后的元组 - CatalogUpdateIndexes(rel, newtup); + CatalogUpdateIndexes(rel, newtup); // 更新索引 - heap_freetuple_ext(newtup); + heap_freetuple_ext(newtup); // 释放修改后的元组的内存 - systable_endscan(sd); + systable_endscan(sd); // 结束系统表扫描 - heap_close(rel, RowExclusiveLock); + heap_close(rel, RowExclusiveLock); // 关闭关系(表) pfree(values); pfree(nulls); pfree(replaces); } + +/* + * 功能:用于删除指定 baseid 对应的记录。 + * + * 参数列表: + * baseid:要删除的记录的 baseid。 + */ static void TrDeleteBaseid(Oid baseid) { - Relation rbRel; - SysScanDesc sd; - ScanKeyData skey[1]; - HeapTuple tup; + Relation rbRel; // 用于表示回收站关系(表)的关系对象 + SysScanDesc sd; // 系统扫描描述符 + ScanKeyData skey[1]; // 扫描键数据数组 + HeapTuple tup; // 用于存储从系统表中检索的堆元组 - rbRel = heap_open(RecyclebinRelationId, RowExclusiveLock); + rbRel = heap_open(RecyclebinRelationId, RowExclusiveLock); // 打开回收站 - ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcybaseid, BTEqualStrategyNumber, F_INT8EQ, ObjectIdGetDatum(baseid)); + ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcybaseid, BTEqualStrategyNumber, F_INT8EQ, ObjectIdGetDatum(baseid)); // 初始化扫描键 - sd = systable_beginscan(rbRel, RecyclebinBaseidIndexId, true, NULL, 1, skey); + sd = systable_beginscan(rbRel, RecyclebinBaseidIndexId, true, NULL, 1, skey); // 开始系统表扫描 while (HeapTupleIsValid(tup = systable_getnext(sd))) { - simple_heap_delete(rbRel, &tup->t_self); + simple_heap_delete(rbRel, &tup->t_self); // 删除扫描到的元组 } - systable_endscan(sd); - heap_close(rbRel, RowExclusiveLock); - - /* - * CommandCounterIncrement here to ensure that preceding changes are all - * visible to the next deletion step. - */ + systable_endscan(sd); // 结束系统表扫描 + heap_close(rbRel, RowExclusiveLock); // 关闭回收站 CommandCounterIncrement(); } + +/* + * 功能:用于删除指定 id 对应的记录。 + * + * 参数列表: + * id:要删除的记录的 id。 + */ static void TrDeleteId(Oid id) { - Relation rbRel; - SysScanDesc sd; - ScanKeyData skey[1]; - HeapTuple tup; + Relation rbRel; // 用于表示回收站关系(表)的关系对象 + SysScanDesc sd; // 系统扫描描述符 + ScanKeyData skey[1]; // 扫描键数据数组 + HeapTuple tup; // 用于存储从系统表中检索的堆元组 - rbRel = heap_open(RecyclebinRelationId, RowExclusiveLock); + rbRel = heap_open(RecyclebinRelationId, RowExclusiveLock); // 打开回收站关系(表) - ScanKeyInit(&skey[0], ObjectIdAttributeNumber, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(id)); + ScanKeyInit(&skey[0], ObjectIdAttributeNumber, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(id)); // 初始化扫描键 - sd = systable_beginscan(rbRel, RecyclebinIdIndexId, true, NULL, 1, skey); + sd = systable_beginscan(rbRel, RecyclebinIdIndexId, true, NULL, 1, skey); // 开始系统表扫描 if (HeapTupleIsValid(tup = systable_getnext(sd))) { - simple_heap_delete(rbRel, &tup->t_self); + simple_heap_delete(rbRel, &tup->t_self); // 删除扫描到的元组 } - systable_endscan(sd); - heap_close(rbRel, RowExclusiveLock); + systable_endscan(sd); // 结束系统表扫描 + heap_close(rbRel, RowExclusiveLock); // 关闭回收站关系(表) /* * CommandCounterIncrement here to ensure that preceding changes are all @@ -235,28 +263,42 @@ static void TrDeleteId(Oid id) */ CommandCounterIncrement(); } - +/* + * 功能:判断是否需要进行逻辑删除。 + * + * 参数列表: + * object:ObjectAddress结构,表示对象的地址信息。 + * + */ static inline bool TrNeedLogicDrop(const ObjectAddress *object) { return object->rbDropMode == RB_DROP_MODE_LOGIC; } +/** + * 功能:判断是否可以进行清除操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + * relKind:表示对象的关系类型。 + */ static bool TrCanPurge(const TrObjDesc *baseDesc, const ObjectAddress *object, char relKind) { - Relation depRel; - SysScanDesc sd; - HeapTuple tuple; - ScanKeyData key[3]; - int nkeys; - bool found = false; + Relation depRel; // 用于表示依赖关系(表)的关系对象 + SysScanDesc sd; // 系统扫描描述符 + HeapTuple tuple; // 用于存储从系统表中检索的堆元组 + ScanKeyData key[3]; // 扫描键数据数组 + int nkeys; // 扫描键的数量 + bool found = false; // 表示是否找到匹配依赖关系 if (relKind != RELKIND_INDEX && relKind != RELKIND_GLOBAL_INDEX && relKind != RELKIND_RELATION) { - return false; + return false; // 如果关系类型不是合法类型,直接返回false } - depRel = heap_open(DependRelationId, AccessShareLock); + depRel = heap_open(DependRelationId, AccessShareLock); // 打开依赖关系(表) - ScanKeyInit(&key[0], Anum_pg_depend_classid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(object->classId)); + ScanKeyInit(&key[0], Anum_pg_depend_classid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(object->classId)); // 初始化扫描键 ScanKeyInit(&key[1], Anum_pg_depend_objid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(object->objectId)); nkeys = 2; if (object->objectSubId != 0) { @@ -265,85 +307,100 @@ static bool TrCanPurge(const TrObjDesc *baseDesc, const ObjectAddress *object, c nkeys = 3; } - sd = systable_beginscan(depRel, DependDependerIndexId, true, NULL, nkeys, key); + sd = systable_beginscan(depRel, DependDependerIndexId, true, NULL, nkeys, key); // 开始系统表扫描 while (HeapTupleIsValid(tuple = systable_getnext(sd))) { Form_pg_depend depForm = (Form_pg_depend)GETSTRUCT(tuple); - if (depForm->refclassid == RelationRelationId && depForm->refobjid == baseDesc->relid) { - if (depForm->deptype != DEPENDENCY_AUTO) { + if (depForm->refclassid == RelationRelationId && depForm->refobjid == baseDesc->relid) { // 判断是否为依赖于当前表的依赖关系 + if (depForm->deptype != DEPENDENCY_AUTO) { // 如果依赖类型不是自动依赖,不可清除 found = false; break; } - found = true; + found = true; // 找到匹配的依赖关系 } } - systable_endscan(sd); - heap_close(depRel, AccessShareLock); + systable_endscan(sd); // 结束系统表扫描 + heap_close(depRel, AccessShareLock); // 关闭依赖关系(表) return found; } + +/* + * 功能:执行删除索引的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ static void TrDoDropIndex(TrObjDesc *baseDesc, ObjectAddress *object) { - Assert(object->objectSubId == 0); + Assert(object->objectSubId == 0); // 断言,确保对象的子标识符为0(索引没有子标识符) - if (TrNeedLogicDrop(object)) { - TrObjDesc desc = *baseDesc; + if (TrNeedLogicDrop(object)) { // 如果需要逻辑删除 + TrObjDesc desc = *baseDesc; // 复制基本描述信息 if (!TR_IS_BASE_OBJ(baseDesc, object)) { - /* Deletion lock already accquired before single object drop. */ - Relation rel = relation_open(object->objectId, NoLock); + /* 单个对象删除之前已获取删除锁。*/ + Relation rel = relation_open(object->objectId, NoLock); // 打开要删除的关系(表) - TrDescInit(rel, &desc, RB_OPER_DROP, TrGetObjType(RelationGetNamespace(rel), RELKIND_INDEX), + TrDescInit(rel, &desc, RB_OPER_DROP, TrGetObjType(RelationGetNamespace(rel), RELKIND_INDEX), // 初始化描述信息 TrCanPurge(baseDesc, object, RelationGetRelkind(rel))); - relation_close(rel, NoLock); + relation_close(rel, NoLock); // 关闭关系(表) - TrDescWrite(&desc); + TrDescWrite(&desc); // 写入描述信息 } - TrRenameClass(baseDesc, object, desc.name); + TrRenameClass(baseDesc, object, desc.name); // 重命名对象 } else { - index_drop(object->objectId, false); + index_drop(object->objectId, false); // 直接删除索引 } return; } +/* + * 功能:执行删除表的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + * relKind:表示对象的关系类型,可以是RELKIND_RELATION、RELKIND_VIEW、RELKIND_COMPOSITE_TYPE或RELKIND_FOREIGN_TABLE。 + */ static void TrDoDropTable(TrObjDesc *baseDesc, ObjectAddress *object, char relKind) { - if (TrNeedLogicDrop(object)) { + if (TrNeedLogicDrop(object)) { // 如果需要逻辑删除 TrObjDesc desc; if (object->objectSubId != 0 || relKind == RELKIND_VIEW || relKind == RELKIND_COMPOSITE_TYPE || - relKind == RELKIND_FOREIGN_TABLE) { + relKind == RELKIND_FOREIGN_TABLE) { // 对于子对象或特定类型的对象,直接重命名并返回 TrRenameClass(baseDesc, object, NULL); return; } - desc = *baseDesc; + desc = *baseDesc; // 复制基本描述信息 if (!TR_IS_BASE_OBJ(baseDesc, object)) { - /* Deletion lock already accquired before single object drop. */ - Relation rel = relation_open(object->objectId, NoLock); + /* 单个对象删除之前已获取删除锁。*/ + Relation rel = relation_open(object->objectId, NoLock); // 打开要删除的关系(表) - TrDescInit(rel, &desc, RB_OPER_DROP, TrGetObjType(InvalidOid, relKind), + TrDescInit(rel, &desc, RB_OPER_DROP, TrGetObjType(InvalidOid, relKind), // 初始化描述信息 TrCanPurge(baseDesc, object, relKind)); - relation_close(rel, NoLock); + relation_close(rel, NoLock); // 关闭关系(表) - TrDescWrite(&desc); + TrDescWrite(&desc); // 写入描述信息 } - TrRenameClass(baseDesc, object, desc.name); + TrRenameClass(baseDesc, object, desc.name); // 重命名对象 return; } - /* * relation_open() must be before the heap_drop_with_catalog(). If you reload * relation after drop, it may cause other exceptions during the drop process. */ if (object->objectSubId != 0) - RemoveAttributeById(object->objectId, object->objectSubId); + RemoveAttributeById(object->objectId, object->objectSubId); // 删除子属性 else - heap_drop_with_catalog(object->objectId); + heap_drop_with_catalog(object->objectId); // 删除表 /* * IMPORANT: The relation must not be reloaded after heap_drop_with_catalog() @@ -354,394 +411,636 @@ static void TrDoDropTable(TrObjDesc *baseDesc, ObjectAddress *object, char relKi return; } +/* + * 功能:执行删除类型的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ static void TrDoDropType(TrObjDesc *baseDesc, ObjectAddress *object) { - if (TrNeedLogicDrop(object)) { - TrRenameCommon(baseDesc, object, TypeRelationId, Natts_pg_type, Anum_pg_type_typname, TypeOidIndexId, "type"); + if (TrNeedLogicDrop(object)) { // 如果需要逻辑删除 + TrRenameCommon(baseDesc, object, TypeRelationId, Natts_pg_type, Anum_pg_type_typname, TypeOidIndexId, "type"); // 调用通用重命名函数 } else { - RemoveTypeById(object->objectId); - } - - return; -} - -static void TrDoDropConstraint(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - TrRenameCommon(baseDesc, object, ConstraintRelationId, Natts_pg_constraint, Anum_pg_constraint_conname, - ConstraintOidIndexId, "constraint"); - } else { - RemoveConstraintById(object->objectId); - } - - return; -} - -static void TrDoDropTrigger(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - TrRenameCommon(baseDesc, object, TriggerRelationId, Natts_pg_trigger, Anum_pg_trigger_tgname, TriggerOidIndexId, - "trigger"); - } else { - RemoveTriggerById(object->objectId); - } - - return; -} - -static void TrDoDropRewrite(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - /* nothing to do as rule-based view requires that origin rule name preserved. */ - } else { - RemoveRewriteRuleById(object->objectId); - } - - return; -} - -static void TrDoDropAttrdef(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - /* nothing to do as no name attribute in system catalog */ - } else { - RemoveAttrDefaultById(object->objectId); - } - - return; -} - -static void TrDoDropProc(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - TrRenameCommon(baseDesc, object, ProcedureRelationId, Natts_pg_proc, Anum_pg_proc_proname, ProcedureOidIndexId, - "procedure"); - } else { - RemoveFunctionById(object->objectId); - } - - return; -} - -static void TrDoDropCast(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - /* nothing to do as no name attribute in system catalog */ - } else { - DropCastById(object->objectId); - } - - return; -} - -static void TrDoDropCollation(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - TrRenameCommon(baseDesc, object, CollationRelationId, Natts_pg_collation, Anum_pg_collation_collname, - CollationOidIndexId, "collation"); - } else { - RemoveCollationById(object->objectId); - } - - return; -} - -static void TrDoDropConversion(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - TrRenameCommon(baseDesc, object, ConversionRelationId, Natts_pg_conversion, Anum_pg_conversion_conname, - ConversionOidIndexId, "conversion"); - } else { - RemoveConversionById(object->objectId); - } - - return; -} - - -static void TrDoDropProceduralLanguage(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - TrRenameCommon(baseDesc, object, LanguageRelationId, Natts_pg_language, Anum_pg_language_lanname, - LanguageOidIndexId, "language"); - } else { - DropProceduralLanguageById(object->objectId); - } - - return; -} - -static void TrDoDropLargeObject(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - /* nothing to do as no name attribute in system catalog */ - } else { - LargeObjectDrop(object->objectId); - } - - return; -} - -static void TrDoDropOperator(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - TrRenameCommon(baseDesc, object, OperatorRelationId, Natts_pg_operator, Anum_pg_operator_oprname, - OperatorOidIndexId, "operator"); - } else { - RemoveOperatorById(object->objectId); - } - - return; -} - -static void TrDoDropOpClass(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - TrRenameCommon(baseDesc, object, OperatorClassRelationId, Natts_pg_opclass, Anum_pg_opclass_opcname, - OpclassOidIndexId, "opclass"); - } else { - RemoveOpClassById(object->objectId); - } - - return; -} - -static void TrDoDropOpFamily(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - TrRenameCommon(baseDesc, object, OperatorFamilyRelationId, Natts_pg_opfamily, Anum_pg_opfamily_opfname, - OpfamilyOidIndexId, "opfamily"); - } else { - RemoveOpFamilyById(object->objectId); - } - - return; -} - - -static void TrDoDropAmOp(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - /* nothing to do as no name attribute in system catalog */ - } else { - RemoveAmOpEntryById(object->objectId); - } - - return; -} - -static void TrDoDropAmProc(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - /* nothing to do as no name attribute in system catalog */ - } else { - RemoveAmProcEntryById(object->objectId); - } - - return; -} - -static void TrDoDropSchema(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - TrRenameCommon(baseDesc, object, NamespaceRelationId, Natts_pg_namespace, Anum_pg_namespace_nspname, - NamespaceOidIndexId, "namespace"); - } else { - RemoveSchemaById(object->objectId); - } - - return; -} - -static void TrDoDropTSParser(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - TrRenameCommon(baseDesc, object, TSParserRelationId, Natts_pg_ts_parser, Anum_pg_ts_parser_prsname, - TSParserOidIndexId, "ts parser"); - } else { - RemoveTSParserById(object->objectId); - } - - return; -} - -static void TrDoDropTSDictionary(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - TrRenameCommon(baseDesc, object, TSDictionaryRelationId, Natts_pg_ts_dict, Anum_pg_ts_dict_dictname, - TSDictionaryOidIndexId, "ts dictionary"); - } else { - RemoveTSDictionaryById(object->objectId); - } - - return; -} - -static void TrDoDropTSTemplate(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - TrRenameCommon(baseDesc, object, TSTemplateRelationId, Natts_pg_ts_template, Anum_pg_ts_template_tmplname, - TSTemplateOidIndexId, "ts template"); - } else { - RemoveTSTemplateById(object->objectId); - } - - return; -} - -static void TrDoDropTSConfiguration(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - TrRenameCommon(baseDesc, object, TSConfigRelationId, Natts_pg_ts_config, Anum_pg_ts_config_cfgname, - TSConfigOidIndexId, "ts configuration"); - } else { - RemoveTSConfigurationById(object->objectId); - } - - return; -} - -static void TrDoDropForeignDataWrapper(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - TrRenameCommon(baseDesc, object, ForeignDataWrapperRelationId, Natts_pg_foreign_data_wrapper, - Anum_pg_foreign_data_wrapper_fdwname, ForeignDataWrapperOidIndexId, "foreign data wrapper"); - } else { - RemoveForeignDataWrapperById(object->objectId); - } - - return; -} - -static void TrDoDropForeignServer(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - TrRenameCommon(baseDesc, object, ForeignServerRelationId, Natts_pg_foreign_server, - Anum_pg_foreign_server_srvname, ForeignServerOidIndexId, "foreign server"); - } else { - RemoveForeignServerById(object->objectId); - } - - return; -} - -static void TrDoDropUserMapping(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - /* nothing to do as no name attribute in system catalog */ - } else { - RemoveUserMappingById(object->objectId); - } - - return; -} - - -static void TrDoDropDefaultACL(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - /* nothing to do as no name attribute in system catalog */ - } else { - RemoveDefaultACLById(object->objectId); - } - - return; -} - -static void TrDoDropPgxcClass(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - /* nothing to do as no name attribute in system catalog */ - } else { - RemovePgxcClass(object->objectId); - } - - return; -} - -static void TrDoDropExtension(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - TrRenameCommon(baseDesc, object, ExtensionRelationId, Natts_pg_extension, Anum_pg_extension_extname, - ExtensionOidIndexId, "extension"); - } else { - RemoveExtensionById(object->objectId); - } - - return; -} - -static void TrDoDropDataSource(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - TrRenameCommon(baseDesc, object, DataSourceRelationId, Natts_pg_extension_data_source, - Anum_pg_extension_data_source_srcname, DataSourceOidIndexId, "extension data source"); - } else { - RemoveDataSourceById(object->objectId); - } - - return; -} - -static void TrDoDropDirectory(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - TrRenameCommon(baseDesc, object, PgDirectoryRelationId, Natts_pg_directory, Anum_pg_directory_directory_name, - PgDirectoryOidIndexId, "directory"); - } else { - RemoveDirectoryById(object->objectId); - } - - return; -} - -static void TrDoDropRlsPolicy(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - TrRenameCommon(baseDesc, object, RlsPolicyRelationId, Natts_pg_rlspolicy, Anum_pg_rlspolicy_polname, - PgRlspolicyOidIndex, "rlspolicy"); - } else { - RemoveRlsPolicyById(object->objectId); - } - - return; -} - -static void TrDoDropJob(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - /* nothing to do as no name attribute in system catalog */ - } else { - RemoveJobById(object->objectId); - } - - return; -} - -static void TrDoDropSynonym(TrObjDesc *baseDesc, ObjectAddress *object) -{ - if (TrNeedLogicDrop(object)) { - TrRenameCommon(baseDesc, object, PgSynonymRelationId, Natts_pg_synonym, Anum_pg_synonym_synname, - SynonymOidIndexId, "synonym"); - } else { - RemoveSynonymById(object->objectId); + RemoveTypeById(object->objectId); // 直接删除类型 } return; } +/* + * T功能执行删除约束的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropConstraint(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { // 如果需要逻辑删除 + TrRenameCommon(baseDesc, object, ConstraintRelationId, Natts_pg_constraint, Anum_pg_constraint_conname, + ConstraintOidIndexId, "constraint"); // 调用通用重命名函数 + } else { + RemoveConstraintById(object->objectId); // 直接删除约束 + } + + return; +} + + +/* + * 功能:执行删除触发器的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropTrigger(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { // 如果需要逻辑删除 + TrRenameCommon(baseDesc, object, TriggerRelationId, Natts_pg_trigger, Anum_pg_trigger_tgname, TriggerOidIndexId, + "trigger"); // 调用通用重命名函数 + } else { + RemoveTriggerById(object->objectId); // 直接删除触发器 + } + + return; +} + +/* + * 功能:执行删除重写规则的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropRewrite(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { + /* 无需操作,因为基于规则的视图要求保留原始规则名称。*/ + } else { + RemoveRewriteRuleById(object->objectId); // 直接删除重写规则 + } + + return; +} + + +/* + * 功能:执行删除属性默认值的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropAttrdef(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { + /* 无需操作,因为系统目录中没有名称属性。*/ + } else { + RemoveAttrDefaultById(object->objectId); // 直接删除属性默认值 + } + + return; +} + +/* + * 功能:执行删除过程的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropProc(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { // 如果需要逻辑删除 + TrRenameCommon(baseDesc, object, ProcedureRelationId, Natts_pg_proc, Anum_pg_proc_proname, ProcedureOidIndexId, + "procedure"); // 重命名 + } else { + RemoveFunctionById(object->objectId); // 直接删除过程 + } + + return; +} + + +/* + * 功能:执行删除类型转换的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropCast(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { + /* 无需操作,因为系统目录中没有名称属性。*/ + } else { + DropCastById(object->objectId); // 直接删除类型转换 + } + + return; +} + +/* + * 功能:执行删除排序规则的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropCollation(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { // 如果需要逻辑删除 + TrRenameCommon(baseDesc, object, CollationRelationId, Natts_pg_collation, Anum_pg_collation_collname, + CollationOidIndexId, "collation"); // 重命名 + } else { + RemoveCollationById(object->objectId); // 直接删除排序规则 + } + + return; +} + + +/* + * 功能:执行删除类型转换的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropConversion(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { // 如果需要逻辑删除 + TrRenameCommon(baseDesc, object, ConversionRelationId, Natts_pg_conversion, Anum_pg_conversion_conname, + ConversionOidIndexId, "conversion"); // 重命名 + } else { + RemoveConversionById(object->objectId); // 直接删除类型转换 + } + + return; +} + +/* + * 功能:执行删除过程语言的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropProceduralLanguage(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { // 如果需要逻辑删除 + TrRenameCommon(baseDesc, object, LanguageRelationId, Natts_pg_language, Anum_pg_language_lanname, + LanguageOidIndexId, "language"); // 重命名 + } else { + DropProceduralLanguageById(object->objectId); // 直接删除过程语言 + } + + return; +} + + +/* + * 功能:执行删除大对象的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropLargeObject(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { + /* 无需操作,因为系统目录中没有名称属性。*/ + } else { + LargeObjectDrop(object->objectId); // 直接删除对象 + } + + return; +} + +/* + * 功能:执行删除操作符的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropOperator(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { // 如果需要逻辑删除 + TrRenameCommon(baseDesc, object, OperatorRelationId, Natts_pg_operator, Anum_pg_operator_oprname, + OperatorOidIndexId, "operator"); // 重命名 + } else { + RemoveOperatorById(object->objectId); // 直接删除操作符 + } + + return; +} + + +/* + * 功能:执行删除操作符类的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropOpClass(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { // 如果需要逻辑删除 + TrRenameCommon(baseDesc, object, OperatorClassRelationId, Natts_pg_opclass, Anum_pg_opclass_opcname, + OpclassOidIndexId, "opclass"); // 重命名 + } else { + RemoveOpClassById(object->objectId); // 直接删除操作符类 + } + + return; +} + +/* + * 功能:执行删除操作符族的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropOpFamily(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { // 如果需要逻辑删除 + TrRenameCommon(baseDesc, object, OperatorFamilyRelationId, Natts_pg_opfamily, Anum_pg_opfamily_opfname, + OpfamilyOidIndexId, "opfamily"); // 重命名 + } else { + RemoveOpFamilyById(object->objectId); // 直接删除操作符族 + } + + return; +} + + + +/* + * 功能:执行删除操作符访问方法项的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropAmOp(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { + /* 无需操作,因为系统目录中没有名称属性。*/ + } else { + RemoveAmOpEntryById(object->objectId); // 直接删除操作符访问方法项 + } + + return; +} + +/* + * 功能:执行删除过程访问方法项的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropAmProc(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { + /* 无需操作,因为系统目录中没有名称属性。*/ + } else { + RemoveAmProcEntryById(object->objectId); // 直接删除过程访问方法项 + } + + return; +} + + +/* + * 功能:执行删除模式的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropSchema(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { // 如果需要逻辑删除 + TrRenameCommon(baseDesc, object, NamespaceRelationId, Natts_pg_namespace, Anum_pg_namespace_nspname, + NamespaceOidIndexId, "namespace"); // 重命名 + } else { + RemoveSchemaById(object->objectId); // 直接删除模式 + } + + return; +} + +/* + * 功能:执行删除文本搜索解析器的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropTSParser(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { // 如果需要逻辑删除 + TrRenameCommon(baseDesc, object, TSParserRelationId, Natts_pg_ts_parser, Anum_pg_ts_parser_prsname, + TSParserOidIndexId, "ts parser"); // 重命名 + } else { + RemoveTSParserById(object->objectId); // 直接删除文本搜索解析器 + } + + return; +} + +/* + * 功能:执行删除文本搜索字典的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropTSDictionary(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { // 如果需要逻辑删除 + TrRenameCommon(baseDesc, object, TSDictionaryRelationId, Natts_pg_ts_dict, Anum_pg_ts_dict_dictname, + TSDictionaryOidIndexId, "ts dictionary"); // 重命名 + } else { + RemoveTSDictionaryById(object->objectId); // 直接删除文本搜索字典 + } + + return; +} + +/* + * 功能:执行删除文本搜索模板的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropTSTemplate(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { // 如果需要逻辑删除 + TrRenameCommon(baseDesc, object, TSTemplateRelationId, Natts_pg_ts_template, Anum_pg_ts_template_tmplname, + TSTemplateOidIndexId, "ts template"); // 重命名 + } else { + RemoveTSTemplateById(object->objectId); // 直接删除文本搜索模板 + } + + return; +} + + +/* + * 功能:执行删除文本搜索配置的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropTSConfiguration(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { // 如果需要逻辑删除 + TrRenameCommon(baseDesc, object, TSConfigRelationId, Natts_pg_ts_config, Anum_pg_ts_config_cfgname, + TSConfigOidIndexId, "ts configuration"); // 重命名 + } else { + RemoveTSConfigurationById(object->objectId); // 直接删除文本搜索配置 + } + + return; +} + +/* + * 功能:执行删除外部数据封装器的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropForeignDataWrapper(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { // 如果需要逻辑删除 + TrRenameCommon(baseDesc, object, ForeignDataWrapperRelationId, Natts_pg_foreign_data_wrapper, + Anum_pg_foreign_data_wrapper_fdwname, ForeignDataWrapperOidIndexId, "foreign data wrapper"); // 重命名 + } else { + RemoveForeignDataWrapperById(object->objectId); // 直接删除外部数据封装器 + } + + return; +} + +/* + * 功能:执行删除外部服务器的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropForeignServer(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { // 如果需要逻辑删除 + TrRenameCommon(baseDesc, object, ForeignServerRelationId, Natts_pg_foreign_server, + Anum_pg_foreign_server_srvname, ForeignServerOidIndexId, "foreign server"); // 重命名 + } else { + RemoveForeignServerById(object->objectId); // 直接删除外部服务器 + } + + return; +} + +/* + * 功能:执行删除用户映射的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropUserMapping(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { + /* 无需操作,因为系统目录中没有名称属性。*/ + } else { + RemoveUserMappingById(object->objectId); // 直接删除用户映射 + } + + return; +} + + + +/* + * 功能:执行删除默认访问控制列表的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropDefaultACL(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { + /* 无需操作,因为系统目录中没有名称属性。*/ + } else { + RemoveDefaultACLById(object->objectId); // 直接删除默认访问控制列表 + } + + return; +} + +/* + * 功能:执行删除分布式表信息的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropPgxcClass(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { + /* 无需操作,因为系统目录中没有名称属性。*/ + } else { + RemovePgxcClass(object->objectId); // 直接删除分布式表信息 + } + + return; +} + +/* + * 功能:执行删除扩展的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropExtension(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { // 如果需要逻辑删除 + TrRenameCommon(baseDesc, object, ExtensionRelationId, Natts_pg_extension, Anum_pg_extension_extname, + ExtensionOidIndexId, "extension"); // 重命名 + } else { + RemoveExtensionById(object->objectId); // 直接删除扩展 + } + + return; +} + +/* + * 功能:执行删除扩展数据源的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropDataSource(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { // 如果需要逻辑删除 + TrRenameCommon(baseDesc, object, DataSourceRelationId, Natts_pg_extension_data_source, + Anum_pg_extension_data_source_srcname, DataSourceOidIndexId, "extension data source"); // 重命名 + } else { + RemoveDataSourceById(object->objectId); // 直接删除扩展数据源 + } + + return; +} + + +/* + * 功能:执行删除目录的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropDirectory(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { // 如果需要逻辑删除 + TrRenameCommon(baseDesc, object, PgDirectoryRelationId, Natts_pg_directory, Anum_pg_directory_directory_name, + PgDirectoryOidIndexId, "directory"); // 重命名 + } else { + RemoveDirectoryById(object->objectId); // 直接删除目录 + } + + return; +} + +/* + * 功能:执行删除行级安全策略的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropRlsPolicy(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { // 如果需要逻辑删除 + TrRenameCommon(baseDesc, object, RlsPolicyRelationId, Natts_pg_rlspolicy, Anum_pg_rlspolicy_polname, + PgRlspolicyOidIndex, "rlspolicy"); // 重命名 + } else { + RemoveRlsPolicyById(object->objectId); // 直接删除行级安全策略 + } + + return; +} + +/* + * 功能:执行删除作业的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropJob(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { + /* 无需操作,因为系统目录中没有名称属性。*/ + } else { + RemoveJobById(object->objectId); // 直接删除作业 + } + + return; +} + +/* + * 功能:执行删除同义词的操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ +static void TrDoDropSynonym(TrObjDesc *baseDesc, ObjectAddress *object) +{ + if (TrNeedLogicDrop(object)) { // 如果需要逻辑删除 + TrRenameCommon(baseDesc, object, PgSynonymRelationId, Natts_pg_synonym, Anum_pg_synonym_synname, + SynonymOidIndexId, "synonym"); // 重命名 + } else { + RemoveSynonymById(object->objectId); // 直接删除同义词 + } + + return; +} + + /* * doDeletion: delete a single object * return false if logic deleted, * return true if physical deleted, */ +/* + * 功能:执行删除对象的操作,根据对象的类别选择相应的删除函数。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + */ static void TrDoDrop(TrObjDesc *baseDesc, ObjectAddress *object) { switch (getObjectClass(object)) { case OCLASS_CLASS: { char relKind = get_rel_relkind(object->objectId); if (relKind == RELKIND_INDEX) { - TrDoDropIndex(baseDesc, object); + TrDoDropIndex(baseDesc, object);// 删除索引 } else { /* * We use a unified entry for others: @@ -749,93 +1048,93 @@ static void TrDoDrop(TrObjDesc *baseDesc, ObjectAddress *object) * RELKIND_TOASTVALUE, RELKIND_VIEW, * RELKIND_COMPOSITE_TYPE, RELKIND_FOREIGN_TABLE */ - TrDoDropTable(baseDesc, object, relKind); + TrDoDropTable(baseDesc, object, relKind);// 删除表 } break; } case OCLASS_TYPE: - TrDoDropType(baseDesc, object); + TrDoDropType(baseDesc, object); // 删除类型 break; case OCLASS_CONSTRAINT: - TrDoDropConstraint(baseDesc, object); + TrDoDropConstraint(baseDesc, object); // 删除约束 break; case OCLASS_TRIGGER: - TrDoDropTrigger(baseDesc, object); + TrDoDropTrigger(baseDesc, object); // 删除触发器 break; case OCLASS_REWRITE: - TrDoDropRewrite(baseDesc, object); + TrDoDropRewrite(baseDesc, object); // 删除规则 break; case OCLASS_DEFAULT: - TrDoDropAttrdef(baseDesc, object); + TrDoDropAttrdef(baseDesc, object); // 删除默认值 break; case OCLASS_PROC: - TrDoDropProc(baseDesc, object); + TrDoDropProc(baseDesc, object); // 删除函数 break; case OCLASS_CAST: - TrDoDropCast(baseDesc, object); + TrDoDropCast(baseDesc, object); // 删除转换 break; case OCLASS_COLLATION: - TrDoDropCollation(baseDesc, object); + TrDoDropCollation(baseDesc, object); // 删除排序规则 break; case OCLASS_CONVERSION: - TrDoDropConversion(baseDesc, object); + TrDoDropConversion(baseDesc, object); // 删除转换规则 break; case OCLASS_LANGUAGE: - TrDoDropProceduralLanguage(baseDesc, object); + TrDoDropProceduralLanguage(baseDesc, object); // 删除过程化语言 break; case OCLASS_LARGEOBJECT: - TrDoDropLargeObject(baseDesc, object); + TrDoDropLargeObject(baseDesc, object); // 删除大对象 break; case OCLASS_OPERATOR: - TrDoDropOperator(baseDesc, object); + TrDoDropOperator(baseDesc, object); // 删除操作符 break; case OCLASS_OPCLASS: - TrDoDropOpClass(baseDesc, object); + TrDoDropOpClass(baseDesc, object); // 删除操作符类 break; case OCLASS_OPFAMILY: - TrDoDropOpFamily(baseDesc, object); + TrDoDropOpFamily(baseDesc, object); // 删除操作符族 break; case OCLASS_AMOP: - TrDoDropAmOp(baseDesc, object); + TrDoDropAmOp(baseDesc, object); // 删除操作符映射 break; case OCLASS_AMPROC: - TrDoDropAmProc(baseDesc, object); + TrDoDropAmProc(baseDesc, object); // 删除操作符处理函数 break; case OCLASS_SCHEMA: - TrDoDropSchema(baseDesc, object); + TrDoDropSchema(baseDesc, object); // 删除模式 break; case OCLASS_TSPARSER: - TrDoDropTSParser(baseDesc, object); + TrDoDropTSParser(baseDesc, object); // 删除文本搜索解析器 break; case OCLASS_TSDICT: - TrDoDropTSDictionary(baseDesc, object); + TrDoDropTSDictionary(baseDesc, object); // 删除文本搜索字典 break; case OCLASS_TSTEMPLATE: - TrDoDropTSTemplate(baseDesc, object); + TrDoDropTSTemplate(baseDesc, object); // 删除文本搜索模板 break; case OCLASS_TSCONFIG: - TrDoDropTSConfiguration(baseDesc, object); + TrDoDropTSConfiguration(baseDesc, object); // 删除文本搜索配置 break; /* @@ -844,48 +1143,48 @@ static void TrDoDrop(TrObjDesc *baseDesc, ObjectAddress *object) */ case OCLASS_FDW: - TrDoDropForeignDataWrapper(baseDesc, object); + TrDoDropForeignDataWrapper(baseDesc, object); // 删除外部数据封装器 break; case OCLASS_FOREIGN_SERVER: - TrDoDropForeignServer(baseDesc, object); + TrDoDropForeignServer(baseDesc, object); // 删除外部服务器 break; case OCLASS_USER_MAPPING: - TrDoDropUserMapping(baseDesc, object); + TrDoDropUserMapping(baseDesc, object); // 删除用户映射 break; case OCLASS_DEFACL: - TrDoDropDefaultACL(baseDesc, object); + TrDoDropDefaultACL(baseDesc, object); // 删除默认访问控制 break; case OCLASS_PGXC_CLASS: - TrDoDropPgxcClass(baseDesc, object); + TrDoDropPgxcClass(baseDesc, object); // 删除分布式类 break; case OCLASS_EXTENSION: - TrDoDropExtension(baseDesc, object); + TrDoDropExtension(baseDesc, object); // 删除扩展 break; case OCLASS_DATA_SOURCE: - TrDoDropDataSource(baseDesc, object); + TrDoDropDataSource(baseDesc, object); // 删除数据源 break; case OCLASS_DIRECTORY: - TrDoDropDirectory(baseDesc, object); + TrDoDropDirectory(baseDesc, object); // 删除目录 break; case OCLASS_RLSPOLICY: - TrDoDropRlsPolicy(baseDesc, object); + TrDoDropRlsPolicy(baseDesc, object); // 删除行级安全策略 break; case OCLASS_PG_JOB: if ((IS_PGXC_COORDINATOR && !IsConnFromCoord()) || (g_instance.role == VSINGLENODE)) - TrDoDropJob(baseDesc, object); + TrDoDropJob(baseDesc, object); // 删除作业 break; case OCLASS_SYNONYM: - TrDoDropSynonym(baseDesc, object); + TrDoDropSynonym(baseDesc, object); // 删除同义词 break; default: @@ -902,6 +1201,14 @@ static void TrDoDrop(TrObjDesc *baseDesc, ObjectAddress *object) * * *depRel is the already-open pg_depend relation. */ +/* + * 功能:删除一个对象,包括其依赖关系等操作。 + * + * 参数列表: + * baseDesc:TrObjDesc结构,表示对象描述信息的基本结构。 + * object:ObjectAddress结构,表示对象的地址信息。 + * depRel:Relation指针,表示依赖关系的系统表。 + */ static void TrDropOneObject(TrObjDesc *baseDesc, ObjectAddress *object, Relation *depRel) { ScanKeyData key[3]; @@ -998,6 +1305,13 @@ static void TrDropOneObject(TrObjDesc *baseDesc, ObjectAddress *object, Relation */ } +/* + * 功能:判断给定的对象地址是否在对象地址列表中。 + * + * 参数列表: + * targetObjects:ObjectAddresses结构指针,表示目标对象地址列表。 + * thisobj:ObjectAddress结构,表示当前对象的地址。 + */ static bool TrObjIsInList(const ObjectAddresses *targetObjects, const ObjectAddress *thisobj) { ObjectAddress *item = NULL; @@ -1011,6 +1325,13 @@ static bool TrObjIsInList(const ObjectAddresses *targetObjects, const ObjectAddr return false; } +/* + * 功能:在目标对象地址列表中查找给定对象的索引。 + * + * 参数列表: + * targetObjects:ObjectAddresses结构指针,表示目标对象地址列表。 + * item:ObjectAddress结构,表示待查找的对象地址。 + */ static ObjectAddress *TrFindIdxInTarget(ObjectAddresses *targetObjects, ObjectAddress *item) { ObjectAddress *thisobj = NULL; @@ -1025,41 +1346,61 @@ static ObjectAddress *TrFindIdxInTarget(ObjectAddresses *targetObjects, ObjectAd return NULL; } + /* * output: refthisobjs */ +/* + * 功能:查找所有引用给定对象的依赖对象 + * + * 参数列表: + * depRel:依赖关系的系统目录表的关系描述符 + * refobj:给定对象的地址 + * refthisobjs:存储依赖对象地址的对象列表 + */ static void TrFindAllSubObjs(Relation depRel, const ObjectAddress *refobj, ObjectAddresses *refthisobjs) { SysScanDesc sd; HeapTuple tuple; ScanKeyData key[3]; int nkeys; - + // 初始化扫描键数组,设置条件用于匹配引用对象的类和ID ScanKeyInit(&key[0], Anum_pg_depend_refclassid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(refobj->classId)); ScanKeyInit(&key[1], Anum_pg_depend_refobjid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(refobj->objectId)); nkeys = 2; + // 如果引用对象有子ID,则设置第三个条件 if (refobj->objectSubId != 0) { ScanKeyInit(&key[2], Anum_pg_depend_refobjsubid, BTEqualStrategyNumber, F_INT4EQ, Int32GetDatum(refobj->objectSubId)); nkeys = 3; } + // 使用条件初始化扫描器 sd = systable_beginscan(depRel, DependReferenceIndexId, true, NULL, nkeys, key); + // 循环遍历满足条件的元组 while (HeapTupleIsValid(tuple = systable_getnext(sd))) { + // 获取元组中的Form_pg_depend数据 Form_pg_depend depForm = (Form_pg_depend)GETSTRUCT(tuple); /* add the refs to list */ add_object_address_ext(depForm->classid, depForm->objid, depForm->objsubid, depForm->deptype, refthisobjs); } - systable_endscan(sd); + systable_endscan(sd);// 结束扫描 return; } - +/* + * 功能:标记物理删除模式下的所有子对象,将其添加到待删除列表中 + * + * 参数列表: + * depRel:依赖关系的系统目录表的关系描述符 + * targetObjects:待删除对象列表 + * thisobj:当前对象的地址 + */ static void TrTagPhyDeleteSubObjs(Relation depRel, ObjectAddresses *targetObjects, ObjectAddress *thisobj) { ObjectAddress *item = NULL; - + // 创建一个用于存储依赖对象地址的对象列表 ObjectAddresses *refthisobjs = new_object_addresses(); /* Tag this obj RB_DROP_MODE_PHYSICAL */ @@ -1067,7 +1408,7 @@ static void TrTagPhyDeleteSubObjs(Relation depRel, ObjectAddresses *targetObject /* Find all sub objs refered to this obj */ TrFindAllSubObjs(depRel, thisobj, refthisobjs); - + // 遍历引用对象列表,将满足条件的对象标记为物理删除模式 for (int i = 0; i < refthisobjs->numrefs; i++) { item = refthisobjs->refs + i; @@ -1076,16 +1417,25 @@ static void TrTagPhyDeleteSubObjs(Relation depRel, ObjectAddresses *targetObject if (item == NULL || item->rbDropMode == RB_DROP_MODE_PHYSICAL) { continue; } + // 递归标记满足条件的子对象 TrTagPhyDeleteSubObjs(depRel, targetObjects, item); } - + // 释放用于存储依赖对象地址的对象列表 free_object_addresses(refthisobjs); return; } - +/* + * 功能:检查是否需要进行物理删除操作 + * + * 参数列表: + * depRel:依赖关系的系统目录表的关系描述符 + * targetObjects:待删除对象列表 + * thisobj:当前对象的地址 + */ static bool TrNeedPhyDelete(Relation depRel, ObjectAddresses *targetObjects, ObjectAddress *thisobj) { ObjectAddress *item = NULL; + // 创建一个用于存储引用对象地址的对象列表 ObjectAddresses *refobjs = new_object_addresses(); bool result = false; @@ -1114,14 +1464,21 @@ static bool TrNeedPhyDelete(Relation depRel, ObjectAddresses *targetObjects, Obj if (item->rbDropMode == RB_DROP_MODE_PHYSICAL) { continue; } + // 标记被引用对象及其子对象为物理删除模式 TrTagPhyDeleteSubObjs(depRel, targetObjects, item); } } - + // 释放用于存储引用对象地址的对象列表 free_object_addresses(refobjs); return result; } - +/* + * 功能重置待删除对象的删除模式 + * + * 参数列表: + * targetObjects:待删除对象列表 + * baseObj:基础对象的地址 + */ static void TrResetDropMode(const ObjectAddresses *targetObjects, const ObjectAddress *baseObj) { ObjectAddress *thisobj = NULL; @@ -1129,28 +1486,46 @@ static void TrResetDropMode(const ObjectAddresses *targetObjects, const ObjectAd for (int i = 0; i < targetObjects->numrefs; i++) { thisobj = targetObjects->refs + i; if (TrObjIsEqual(thisobj, baseObj)) { + // 将基础对象的删除模式重置为逻辑删除 thisobj->rbDropMode = RB_DROP_MODE_LOGIC; continue; } + // 将其他对象的删除模式重置为无效 thisobj->rbDropMode = RB_DROP_MODE_INVALID; } return; } + +/* + * 功能:标记依赖对象的删除模式 + * + * 参数列表: + * depRel:依赖关系关系 + * targetObjects:待删除对象列表 + * baseObj:基础对象的地址 + */ static void TrTagDependentObjects(Relation depRel, ObjectAddresses *targetObjects, const ObjectAddress *baseObj) { ObjectAddress *thisobj = NULL; + // 重置待删除对象的删除模式 TrResetDropMode(targetObjects, baseObj); + for (int i = 0; i < targetObjects->numrefs; i++) { thisobj = targetObjects->refs + i; + + // 如果对象的删除模式已经设置,则跳过 if (TrDropModeIsAlreadySet(thisobj)) { continue; } + // 如果需要进行物理删除 if (TrNeedPhyDelete(depRel, targetObjects, thisobj)) { + // 标记所有依赖于此对象的子对象为物理删除 TrTagPhyDeleteSubObjs(depRel, targetObjects, thisobj); } else { + // 否则将对象的删除模式设置为逻辑删除 thisobj->rbDropMode = RB_DROP_MODE_LOGIC; } } @@ -1158,39 +1533,57 @@ static void TrTagDependentObjects(Relation depRel, ObjectAddresses *targetObject return; } + +/* + * 功能:检查是否可以进行Recyclebin-based-Drop操作 + * + * 参数列表: + * stmt:删除语句 + * objects:待删除对象列表 + */ bool TrCheckRecyclebinDrop(const DropStmt *stmt, ObjectAddresses *objects) { Relation depRel; bool rbDrop = false; - /* No work if no objects... */ + /* 如果没有待删除对象,则不需要操作 */ if (objects->numrefs <= 0) return false; + /* 检查是否满足Recyclebin-based-Drop条件 */ if (/* - * Disable Recyclebin-based-Drop when target object is not OBJECT_TABLE, or + * 当目标对象不是OBJECT_TABLE时,或者 */ stmt->removeType != OBJECT_TABLE || - /* in concurrent drop mode, or */ + /* 在并发删除模式下,或者 */ stmt->concurrent || - /* with purge option, or */ + /* 使用了purge选项,或者 */ stmt->purge || - /* multi objects drop. */ + /* 删除多个对象时,不支持Recyclebin-based-Drop。 */ list_length(stmt->objects) != 1) { return false; } + /* 如果不需要Recyclebin功能,则不支持Recyclebin-based-Drop */ if (!NeedTrComm(objects->refs->objectId)) { return false; } + /* 打开依赖关系关系表,进行检查 */ depRel = heap_open(DependRelationId, AccessShareLock); rbDrop = !TrNeedPhyDelete(depRel, objects, &objects->refs[0]); heap_close(depRel, AccessShareLock); return rbDrop; } - +/* + * 功能:执行删除操作,支持逻辑删除和物理删除 + * + * 参数列表: + * drop:删除语句 + * objects:待删除对象列表 + * behavior:删除行为 + */ void TrDrop(const DropStmt* drop, const ObjectAddresses *objects, DropBehavior behavior) { Relation depRel; @@ -1267,61 +1660,72 @@ void TrDrop(const DropStmt* drop, const ObjectAddresses *objects, DropBehavior b free_object_addresses(targetObjects); heap_close(depRel, RowExclusiveLock); } - +/* + * 功能:执行回收站中的对象清除操作 + * + * 参数列表: + * desc:对象描述信息 + */ void TrDoPurgeObjectDrop(TrObjDesc *desc) { - ObjectAddresses *objects; - ObjectAddress obj; + ObjectAddresses *objects; // 存储对象地址的结构体,用于批量删除 + ObjectAddress obj; // 对象地址结构体,用于表示对象的地址 - objects = new_object_addresses(); + objects = new_object_addresses(); // 创建对象地址结构体的实例 - obj.classId = RelationRelationId; - obj.objectId = desc->relid; - obj.objectSubId = 0; - add_exact_object_address(&obj, objects); + obj.classId = RelationRelationId; // 设置对象地址的类别ID为RelationRelationId,表示关系对象 + obj.objectId = desc->relid; // 设置对象地址的对象ID为被删除对象的关系ID + obj.objectSubId = 0; // 设置对象地址的子对象ID为0,表示主对象 + add_exact_object_address(&obj, objects); // 向对象地址结构体添加对象地址 - performMultipleDeletions(objects, DROP_CASCADE, PERFORM_DELETION_INVALID); + performMultipleDeletions(objects, DROP_CASCADE, PERFORM_DELETION_INVALID); // 执行多个对象的级联删除操作 - if (desc->type == RB_OBJ_TABLE) { - TrDeleteBaseid(desc->baseid); + if (desc->type == RB_OBJ_TABLE) { // 如果被删除对象是表对象 + TrDeleteBaseid(desc->baseid); // 删除基础ID } else { /* RB_OBJ_INDEX */ - TrDeleteId(desc->id); + TrDeleteId(desc->id); // 删除ID } - free_object_addresses(objects); + free_object_addresses(objects); // 释放对象地址结构体的内存 return; } /* TIMECAPSULE TABLE { table_name } TO BEFORE DROP [RENAME TO new_tablename] */ +/* + * 功能:还原回收站中被删除的对象 + * + * 参数列表: + * stmt:时间胶囊语句 + */ void TrRestoreDrop(const TimeCapsuleStmt *stmt) { - TrObjDesc desc; - ObjectAddress obj; - Relation rel; + TrObjDesc desc; // 对象描述结构体,用于保存被还原对象的信息 + ObjectAddress obj; // 对象地址结构体,用于表示对象的地址 + Relation rel; // 数据库关系对象,用于操作数据库关系 - desc.relid = 0; - TrOperFetch(stmt->relation, RB_OBJ_TABLE, &desc, RB_OPER_RESTORE_DROP); + desc.relid = 0; // 初始化被还原对象的关系ID为0 + TrOperFetch(stmt->relation, RB_OBJ_TABLE, &desc, RB_OPER_RESTORE_DROP); // 获取被还原对象的描述信息 if (desc.relid != 0 && (desc.type == RB_OBJ_TABLE)) { - stmt->relation->relname = desc.name; - rel = heap_openrv(stmt->relation, AccessExclusiveLock); + stmt->relation->relname = desc.name; // 将被还原对象的名称赋值给时间胶囊语句的关系名称 + rel = heap_openrv(stmt->relation, AccessExclusiveLock); // 打开关系对象以进行操作 if (rel->rd_tam_type == TAM_HEAP) { - heap_close(rel, NoLock); - elog(ERROR, "timecapsule does not support astore yet"); + heap_close(rel, NoLock); // 关闭关系对象 + elog(ERROR, "timecapsule does not support astore yet"); // 报错,暂不支持还原存储区对象 return; } - heap_close(rel, NoLock); + heap_close(rel, NoLock); // 关闭关系对象 } - desc.authid = GetUserId(); - TrOperPrep(&desc, RB_OPER_RESTORE_DROP); + desc.authid = GetUserId(); // 获取当前用户的权限ID + TrOperPrep(&desc, RB_OPER_RESTORE_DROP); // 准备还原操作,更新描述信息 - obj.classId = RelationRelationId; - obj.objectId = desc.relid; - obj.objectSubId = 0; + obj.classId = RelationRelationId; // 设置对象地址的类别ID为RelationRelationId,表示关系对象 + obj.objectId = desc.relid; // 设置对象地址的对象ID为被还原对象的关系ID + obj.objectSubId = 0; // 设置对象地址的子对象ID为0,表示主对象 - TrRenameClass(&desc, &obj, stmt->new_relname ? stmt->new_relname : desc.originname); + TrRenameClass(&desc, &obj, stmt->new_relname ? stmt->new_relname : desc.originname); // 重命名对象 - TrDeleteBaseid(desc.baseid); + TrDeleteBaseid(desc.baseid); // 删除基础ID,即将对象从回收站移出 return; } diff --git a/src/gausskernel/storage/tcap/tcap_manager.cpp b/src/gausskernel/storage/tcap/tcap_manager.cpp index da0bee6fb..70864e128 100644 --- a/src/gausskernel/storage/tcap/tcap_manager.cpp +++ b/src/gausskernel/storage/tcap/tcap_manager.cpp @@ -98,56 +98,74 @@ static bool TrIsRefRbObject(const ObjectAddress *obj, Relation depRel = NULL); void TrDoPurgeObjectDrop(TrObjDesc *desc); - +/* + * 功能:生成命名对象的名称,并将结果存储在rbname指向的缓冲区中 + * 参数列表: + * rbname:指向字符缓冲区的指针,用于存储生成的对象名称 + * classId:对象的类别ID + * objid:对象的ID + */ char *TrGenObjName(char *rbname, Oid classId, Oid objid) { - int rc = EOK; + int rc = EOK; // 声明并初始化一个整数变量rc,用于储存执行的结果 + // 使用snprintf_s函数将格式化的字符串写入rbname缓冲区 + rc = snprintf_s(rbname, NAMEDATALEN, NAMEDATALEN - 1, "BIN$%X%X%X$%llX==$0", u_sess->proc_cxt.MyDatabaseId, classId, + objid, (uint64)GetXLogInsertRecPtr()); + securec_check_ss_c(rc, "\0", "\0"); // 检查snprintf_s函数的执行结果,确保没有错误发生 - rc = snprintf_s(rbname, NAMEDATALEN, NAMEDATALEN - 1, "BIN$%X%X%X$%llX==$0", - u_sess->proc_cxt.MyDatabaseId, classId, objid, (uint64)GetXLogInsertRecPtr()); - securec_check_ss_c(rc, "\0", "\0"); - - return rbname; + return rbname; // 返回存储对象名称的缓冲区的地址作为函数结果 } - +/* + * 功能:从HeapTuple中获取回收站中的冻结事务ID(64位),并返回该ID + * 参数列表: + * - rbtup:指向待处理的HeapTuple的指针 + * - rbRel:Recycle Bin关系的Relation(可选参数) + */ static TransactionId TrRbGetRcyfrozenxid64(HeapTuple rbtup, Relation rbRel = NULL) { - Datum datum; - bool isNull = false; - TransactionId rcyfrozenxid64; - bool relArgNull = rbRel == NULL; - + Datum datum; // 存储属性值的数据类型 + bool isNull = false; // 标识属性值是否为NULL + TransactionId rcyfrozenxid64; // 存储回收站中的冻结事务ID(64位) + bool relArgNull = rbRel == NULL; // 检查rbRel是否为NULL的标志 + // 如果rbRel为NULL,则打开Recycle Bin关系(Recycle Bin表) if (relArgNull) { rbRel = heap_open(RecyclebinRelationId, AccessShareLock); } - + // 从HeapTuple中获取pg_recyclebin表中rcyfrozenxid64属性的值 datum = heap_getattr(rbtup, Anum_pg_recyclebin_rcyfrozenxid64, RelationGetDescr(rbRel), &isNull); - Assert(!isNull); - + Assert(!isNull); // 断言属性值不应为NULL + // 将datum转换为TransactionId类型,并赋值给rcyfrozenxid64 rcyfrozenxid64 = DatumGetTransactionId(datum); - + // 如果rbRel为NULL,则关闭Recycle Bin关系 if (relArgNull) { heap_close(rbRel, AccessShareLock); } - return rcyfrozenxid64; + return rcyfrozenxid64; // 返回获取的冻结事务ID(64位) } - -void TrDescInit(Relation rel, TrObjDesc *desc, TrObjOperType operType, - TrObjType objType, bool canpurge, bool isBaseObj) +/* + * 功能:初始化TrObjDesc结构体,存储关于对象的描述信息 + * 参数列表: + * rel:指向目标关系的Relation + * desc:指向TrObjDesc结构体的指针,用于存储对象描述信息 + * operType:操作类型,表示对象的操作(创建、删除等) + * objType:对象类型,表示对象的类型(表、索引等) + * canpurge:是否允许清除对象标志 + * isBaseObj:是否基本对象标志 + */ +void TrDescInit(Relation rel, TrObjDesc *desc, TrObjOperType operType, TrObjType objType, bool canpurge, bool isBaseObj) { - errno_t rc = EOK; + errno_t rc = EOK; // 声明并初始化一个错误号变量rc,用于存储函数执行的结果 /* Notice: desc->id, desc->baseid will be assigned by invoker later. */ - desc->dbid = u_sess->proc_cxt.MyDatabaseId; - desc->relid = RelationGetRelid(rel); - + desc->dbid = u_sess->proc_cxt.MyDatabaseId; // 将当前会话所在的数据库ID赋值给desc->dbid + desc->relid = RelationGetRelid(rel); // 将目标关系的Relid赋值给desc->relid + // 生成对象的名称,存储在desc->name中 (void)TrGenObjName(desc->name, RelationRelationId, desc->relid); - - rc = strncpy_s(desc->originname, NAMEDATALEN, RelationGetRelationName(rel), - strlen(RelationGetRelationName(rel))); + // 将目标关系的名称拷贝到desc->originname中 + rc = strncpy_s(desc->originname, NAMEDATALEN, RelationGetRelationName(rel), strlen(RelationGetRelationName(rel))); securec_check(rc, "\0", "\0"); - + // 初始化对象描述的各个字段 desc->operation = operType; desc->type = objType; desc->recyclecsn = t_thrd.xact_cxt.ShmemVariableCache->nextCommitSeqNo; @@ -163,26 +181,35 @@ void TrDescInit(Relation rel, TrObjDesc *desc, TrObjOperType operType, desc->canrestore = objType == RB_OBJ_TABLE; desc->canpurge = canpurge; } - -void TrPartDescInit(Relation rel, Partition part, TrObjDesc *desc, TrObjOperType operType, - TrObjType objType, bool canpurge, bool isBaseObj) +/* + * 功能:初始化TrObjDesc结构体,存储关于分区对象的描述信息 + * 参数列表: + * rel:指向父关系的Relation + * part:指向分区的Partition + * desc:指向TrObjDesc结构体的指针,用于存储对象描述信息 + * operType:操作类型,表示对象的操作(创建、删除等) + * objType:对象类型,表示对象的类型(表、索引等) + * canpurge:是否允许清除对象标志 + * isBaseObj:是否基本对象标志 + */ +void TrPartDescInit(Relation rel, Partition part, TrObjDesc *desc, TrObjOperType operType, TrObjType objType, + bool canpurge, bool isBaseObj) { - errno_t rc = EOK; + errno_t rc = EOK; // 声明并初始化一个错误号变量rc,用于存储函数执行的结果 /* Notice: desc->id, desc->baseid will be assigned by invoker later. */ - desc->dbid = u_sess->proc_cxt.MyDatabaseId; - desc->relid = part->pd_id; - + desc->dbid = u_sess->proc_cxt.MyDatabaseId; // 将当前会话所在的数据库ID赋值给desc->dbid + desc->relid = part->pd_id; // 将分区的pd_id赋值给desc->relid + // 生成对象的名称,存储在desc->name中 (void)TrGenObjName(desc->name, PartitionRelationId, desc->relid); - - rc = strncpy_s(desc->originname, NAMEDATALEN, RelationGetRelationName(rel), - strlen(RelationGetRelationName(rel))); + // 将父关系的名称和分区的名称连接起来,存储在desc->originname中 + rc = strncpy_s(desc->originname, NAMEDATALEN, RelationGetRelationName(rel), strlen(RelationGetRelationName(rel))); securec_check(rc, "\0", "\0"); - + int len = strlen(PartitionGetPartitionName(part)) + strlen(RelationGetRelationName(rel)) + 1; rc = strcat_s(desc->originname, len, PartitionGetPartitionName(part)); securec_check(rc, "\0", "\0"); - + // 初始化对象描述的各个字段 desc->operation = operType; desc->type = objType; desc->recyclecsn = t_thrd.xact_cxt.ShmemVariableCache->nextCommitSeqNo; @@ -198,34 +225,63 @@ void TrPartDescInit(Relation rel, Partition part, TrObjDesc *desc, TrObjOperType desc->canrestore = false; desc->canpurge = canpurge; } - +/* + * 功能:从HeapTuple中读取并填充对象描述信息到TrObjDesc结构体 + * 参数列表: + * desc:指向TrObjDesc结构体的指针,用于存储对象描述信息 + * rbtup:指向HeapTuple的指针,表示从回收站关系读取的元组 + */ static void TrDescRead(TrObjDesc *desc, HeapTuple rbtup) { Form_pg_recyclebin rbForm = (Form_pg_recyclebin)GETSTRUCT(rbtup); + // 从HeapTuple中获取对象的Oid,赋值给desc->id desc->id = HeapTupleGetOid(rbtup); + // 从HeapTuple中获取对象的基础ID,赋值给desc->baseid desc->baseid = rbForm->rcybaseid; + // 从HeapTuple中获取对象的数据库ID,赋值给desc->dbid desc->dbid = rbForm->rcydbid; + // 从HeapTuple中获取对象的关系ID,赋值给desc->relid desc->relid = rbForm->rcyrelid; + // 从HeapTuple中获取对象的名称,拷贝到desc->name (void)namestrcpy((Name)desc->name, NameStr(rbForm->rcyname)); + // 从HeapTuple中获取对象的原始名称,拷贝到desc->originname (void)namestrcpy((Name)desc->originname, NameStr(rbForm->rcyoriginname)); + // 根据HeapTuple中的操作类型标志,设置desc->operation desc->operation = (rbForm->rcyoperation == 'd') ? RB_OPER_DROP : RB_OPER_TRUNCATE; + // 将HeapTuple中的对象类型转换为TrObjType,赋值给desc->type desc->type = (TrObjType)rbForm->rcytype; + // 从HeapTuple中获取对象的回收CSN,赋值给desc->recyclecsn desc->recyclecsn = rbForm->rcyrecyclecsn; + // 从HeapTuple中获取对象的回收时间,赋值给desc->recycletime desc->recycletime = rbForm->rcyrecycletime; + // 从HeapTuple中获取对象的创建CSN,赋值给desc->createcsn desc->createcsn = rbForm->rcycreatecsn; + // 从HeapTuple中获取对象的变更CSN,赋值给desc->changecsn desc->changecsn = rbForm->rcychangecsn; + // 从HeapTuple中获取对象的命名空间,赋值给desc->nspace desc->nspace = rbForm->rcynamespace; + // 从HeapTuple中获取对象的命名空间,赋值给desc->nspace desc->owner = rbForm->rcyowner; + // 从HeapTuple中获取对象的表空间,赋值给desc->tablespace desc->tablespace = rbForm->rcytablespace; + // 从HeapTuple中获取对象的关系文件节点,赋值给desc->relfilenode desc->relfilenode = rbForm->rcyrelfilenode; + // 从HeapTuple中获取对象的可恢复标志,赋值给desc->canrestore desc->canrestore = rbForm->rcycanrestore; + // 从HeapTuple中获取对象的可清除标志,赋值给desc->canpurge desc->canpurge = rbForm->rcycanpurge; + // 从HeapTuple中获取对象的冻结事务ID,赋值给desc->frozenxid desc->frozenxid = rbForm->rcyfrozenxid; + // 调用TrRbGetRcyfrozenxid64函数,获取64位冻结事务ID,赋值给desc->frozenxid64 desc->frozenxid64 = TrRbGetRcyfrozenxid64(rbtup); } - +/* + * 功能:将TrObjDesc结构体中的对象描述信息写入回收站关系 + * 参数列表: + * desc:指向TrObjDesc结构体的指针,包含要写入回收站关系的对象描述信息 + */ Oid TrDescWrite(TrObjDesc *desc) { Relation rel; @@ -236,6 +292,7 @@ Oid TrDescWrite(TrObjDesc *desc) NameData originname; Oid rbid; + // 填充属性数组values,将对象描述信息中的各属性值赋值给对应位置 values[Anum_pg_recyclebin_rcydbid - 1] = ObjectIdGetDatum(desc->dbid); values[Anum_pg_recyclebin_rcybaseid - 1] = ObjectIdGetDatum(desc->baseid); values[Anum_pg_recyclebin_rcyrelid - 1] = ObjectIdGetDatum(desc->relid); @@ -258,66 +315,79 @@ Oid TrDescWrite(TrObjDesc *desc) values[Anum_pg_recyclebin_rcyfrozenxid - 1] = ShortTransactionIdGetDatum(desc->frozenxid); values[Anum_pg_recyclebin_rcyfrozenxid64 - 1] = TransactionIdGetDatum(desc->frozenxid64); - rel = heap_open(RecyclebinRelationId, RowExclusiveLock); - + rel = heap_open(RecyclebinRelationId, RowExclusiveLock); // 打开回收站关系 + // 创建一个新的HeapTuple,填充属性值和null标志 tup = heap_form_tuple(RelationGetDescr(rel), values, nulls); - + // 在关系中插入新的元组,获取新元组的对象ID(rbid) rbid = simple_heap_insert(rel, tup); - + // 更新关系的索引 CatalogUpdateIndexes(rel, tup); - + // 释放HeapTuple heap_freetuple_ext(tup); - + // 关闭回收站关系 heap_close(rel, RowExclusiveLock); - + // 增加命令计数器,以保证元数据的一致性 CommandCounterIncrement(); return rbid; } - -static bool TrFetchOrinameImpl(Oid nspId, const char *oriname, TrObjType type, - TrObjDesc *desc, TrOperMode operMode) +/* + * 功能:在回收站关系中根据命名空间、原始名称、类型和操作模式搜索并获取对象描述信息 + * 参数列表: + * nspId:命名空间ID,指定要搜索的命名空间 + * oriname:对象的原始名称,要搜索的对象名称 + * type:TrObjType,指定对象的类型 + * desc:指向TrObjDesc结构体的指针,用于存储找到的对象描述信息 + * operMode:TrOperMode,指定操作模式(还原模式) + */ +static bool TrFetchOrinameImpl(Oid nspId, const char *oriname, TrObjType type, TrObjDesc *desc, TrOperMode operMode) { Relation rbRel; SysScanDesc sd; ScanKeyData skey[3]; HeapTuple tup; bool found = false; - + // 如果命名空间ID无效,直接返回false if (!OidIsValid(nspId)) { return false; } - + // 打开回收站关系,获取共享锁 rbRel = heap_open(RecyclebinRelationId, AccessShareLock); - - ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcynamespace, BTEqualStrategyNumber, - F_OIDEQ, ObjectIdGetDatum(nspId)); - ScanKeyInit(&skey[1], Anum_pg_recyclebin_rcydbid, BTEqualStrategyNumber, - F_OIDEQ, ObjectIdGetDatum(u_sess->proc_cxt.MyDatabaseId)); - ScanKeyInit(&skey[2], Anum_pg_recyclebin_rcyoriginname, BTEqualStrategyNumber, - F_NAMEEQ, CStringGetDatum(oriname)); - + // 初始化扫描键,按照命名空间、数据库ID和原始名称进行搜索 + ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcynamespace, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(nspId)); + ScanKeyInit(&skey[1], Anum_pg_recyclebin_rcydbid, BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(u_sess->proc_cxt.MyDatabaseId)); + ScanKeyInit(&skey[2], Anum_pg_recyclebin_rcyoriginname, BTEqualStrategyNumber, F_NAMEEQ, CStringGetDatum(oriname)); + // 开始扫描回收站关系,使用预定义索引和扫描键 sd = systable_beginscan(rbRel, RecyclebinDbidNspOrinameIndexId, true, NULL, 3, skey); while ((tup = systable_getnext(sd)) != NULL) { Form_pg_recyclebin rbForm = (Form_pg_recyclebin)GETSTRUCT(tup); + // 检查对象类型和操作模式是否匹配 if ((rbForm->rcytype != type && rbForm->rcytype == RB_OBJ_TABLE) || (rbForm->rcytype != type && rbForm->rcytype == RB_OBJ_INDEX) || (operMode == RB_OPER_RESTORE_DROP && rbForm->rcyoperation != 'd') || (operMode == RB_OPER_RESTORE_TRUNCATE && rbForm->rcyoperation != 't')) { continue; } - + // 找到匹配的对象,读取对象描述信息并标记为找到 found = true; TrDescRead(desc, tup); break; } - + // 结束扫描并关闭回收站关系 systable_endscan(sd); heap_close(rbRel, AccessShareLock); return found; } - +/* + * 功能:在回收站关系中根据对象名称、类型和操作模式搜索并获取对象描述信息 + * 参数列表: + * rcyname:要搜索的回收站对象的名称 + * type:TrObjType,指定对象的类型 + * desc:指向TrObjDesc结构体的指针,用于存储找到的对象描述信息 + * operMode:TrOperMode,指定操作模式(还原模式) + */ bool TrFetchName(const char *rcyname, TrObjType type, TrObjDesc *desc, TrOperMode operMode) { Relation rbRel; @@ -325,59 +395,74 @@ bool TrFetchName(const char *rcyname, TrObjType type, TrObjDesc *desc, TrOperMod ScanKeyData skey[1]; HeapTuple tup; bool found = false; - + // 打开回收站关系,获取共享锁 rbRel = heap_open(RecyclebinRelationId, AccessShareLock); - - ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcyname, BTEqualStrategyNumber, - F_NAMEEQ, CStringGetDatum(rcyname)); - + // 初始化扫描键,按照对象名称进行搜索 + ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcyname, BTEqualStrategyNumber, F_NAMEEQ, CStringGetDatum(rcyname)); + // 开始扫描回收站关系,使用预定义索引和扫描键 sd = systable_beginscan(rbRel, RecyclebinNameIndexId, true, NULL, 1, skey); if ((tup = systable_getnext(sd)) != NULL) { Form_pg_recyclebin rbForm = (Form_pg_recyclebin)GETSTRUCT(tup); + // 检查对象类型是否匹配 if ((rbForm->rcytype != type && rbForm->rcytype == RB_OBJ_TABLE) || (rbForm->rcytype != type && rbForm->rcytype == RB_OBJ_INDEX)) { - ereport(ERROR, - (errmsg("The recycle object \"%s\" type mismatched.", rcyname))); + ereport(ERROR, (errmsg("The recycle object \"%s\" type mismatched.", rcyname))); } + // 检查操作模式是否匹配 if ((operMode == RB_OPER_RESTORE_DROP && rbForm->rcyoperation != 'd') || (operMode == RB_OPER_RESTORE_TRUNCATE && rbForm->rcyoperation != 't')) { - ereport(ERROR, - (errmsg("recycle object \"%s\" desired does not exist", rcyname))); + ereport(ERROR, (errmsg("recycle object \"%s\" desired does not exist", rcyname))); } - + // 找到匹配的对象,读取对象描述信息并标记为找到 found = true; TrDescRead(desc, tup); } - + // 结束扫描并关闭回收站关系 systable_endscan(sd); heap_close(rbRel, AccessShareLock); return found; } - -static bool TrFetchOriname(const char *schemaname, const char *relname, TrObjType type, - TrObjDesc *desc, TrOperMode operMode) +/* + * 功能:在回收站关系中根据模式名称、对象名称、类型和操作模式搜索并获取对象描述信息 + * 参数列表: + * schemaname:模式名称,可选的命名空间名称 + * relname:要搜索的回收站对象的名称 + * type:TrObjType,指定对象的类型 + * desc:指向TrObjDesc结构体的指针,用于存储找到的对象描述信息 + * operMode:TrOperMode,指定操作模式(还原模式) + */ +static bool TrFetchOriname(const char *schemaname, const char *relname, TrObjType type, TrObjDesc *desc, + TrOperMode operMode) { bool found = false; Oid nspId; - + // 如果给定了模式名称 if (schemaname) { + // 获取模式的命名空间ID nspId = get_namespace_oid(schemaname, true); + // 调用TrFetchOrinameImpl函数进行搜索 found = TrFetchOrinameImpl(nspId, relname, type, desc, operMode); } else { List *activeSearchPath = NIL; ListCell *l = NULL; + // 重新计算命名空间搜索路径 recomputeNamespacePath(); + // 复制当前的命名空间搜索路径 activeSearchPath = list_copy(u_sess->catalog_cxt.activeSearchPath); + // 遍历命名空间搜索路径 foreach (l, activeSearchPath) { nspId = lfirst_oid(l); + // 调用TrFetchOrinameImpl函数进行搜索,如果找到则标记为找到并退出循环 if (TrFetchOrinameImpl(nspId, relname, type, desc, operMode)) { found = true; break; } } + // 释放命名空间搜索路径列表 list_free_ext(activeSearchPath); + // 如果在当前搜索路径中未找到,则在PG_TOAST命名空间中搜索 if (!found) { nspId = PG_TOAST_NAMESPACE; found = TrFetchOrinameImpl(nspId, relname, type, desc, operMode); @@ -386,45 +471,62 @@ static bool TrFetchOriname(const char *schemaname, const char *relname, TrObjTyp return found; } - +/* + * 功能:更新回收站关系中指定对象的baseid字段 + * 参数列表: + * desc:指向TrObjDesc结构体的指针,包含待更新对象的描述信息 + */ void TrUpdateBaseid(const TrObjDesc *desc) { + // 声明回收站关系 Relation rbRel; + // 声明系统扫描描述符 SysScanDesc sd; + // 声明扫描键数组,用于构建扫描条件 ScanKeyData skey[1]; + // 声明原始的堆元组 HeapTuple tup; + // 声明用于更新的新堆元组 HeapTuple newtup; - Datum values[Natts_pg_recyclebin] = { 0 }; - bool nulls[Natts_pg_recyclebin] = { false }; - bool replaces[Natts_pg_recyclebin] = { false }; - + // 声明值数组,用于存储待更新的字段值 + Datum values[Natts_pg_recyclebin] = {0}; + // 声明空值数组,标识字段是否为空 + bool nulls[Natts_pg_recyclebin] = {false}; + // 声明替换标志数组,标识是否要替换对应字段 + bool replaces[Natts_pg_recyclebin] = {false}; + // 打开回收站关系,获取行独占锁 rbRel = heap_open(RecyclebinRelationId, RowExclusiveLock); - - ScanKeyInit(&skey[0], ObjectIdAttributeNumber, BTEqualStrategyNumber, - F_OIDEQ, ObjectIdGetDatum(desc->id)); - + // 初始化扫描键,按照对象的id进行搜索 + ScanKeyInit(&skey[0], ObjectIdAttributeNumber, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(desc->id)); + // 开始扫描回收站关系,使用预定义索引和扫描键 sd = systable_beginscan(rbRel, RecyclebinIdIndexId, true, NULL, 1, skey); if ((tup = systable_getnext(sd)) == NULL) { + // 如果未找到匹配的对象,报告错误 ereport(ERROR, (errmsg("recycle object %u does not exist", desc->id))); } - + // 准备更新字段的值和标志 replaces[Anum_pg_recyclebin_rcybaseid - 1] = true; values[Anum_pg_recyclebin_rcybaseid - 1] = ObjectIdGetDatum(desc->baseid); - + // 根据新值创建新的元组 newtup = heap_modify_tuple(tup, RelationGetDescr(rbRel), values, nulls, replaces); - + // 执行元组更新 simple_heap_update(rbRel, &newtup->t_self, newtup); - + // 更新索引 CatalogUpdateIndexes(rbRel, newtup); - + // 释放内存 heap_freetuple_ext(newtup); - + // 结束扫描并关闭回收站关系 systable_endscan(sd); heap_close(rbRel, RowExclusiveLock); return; } - +/* + * 功能:在回收站对象中加锁指定的关系,并检查关系是否存在 + * 参数列表: + * relid:要加锁的关系的对象ID + * type:对象的类型(表、索引、分区等) + */ static void TrLockRelationImpl(Oid relid, TrObjType type) { /* @@ -433,8 +535,7 @@ static void TrLockRelationImpl(Oid relid, TrObjType type) */ if (!ConditionalLockRelationOid(relid, AccessExclusiveLock)) { ereport(ERROR, - (errcode(ERRCODE_RBIN_LOCK_NOT_AVAILABLE), - errmsg("could not obtain lock on relation \"%u\"", relid))); + (errcode(ERRCODE_RBIN_LOCK_NOT_AVAILABLE), errmsg("could not obtain lock on relation \"%u\"", relid))); } /* @@ -445,18 +546,18 @@ static void TrLockRelationImpl(Oid relid, TrObjType type) if (!SearchSysCacheExists1(RELOID, ObjectIdGetDatum(relid)) && type != RB_OBJ_PARTITION) { /* Clean already held locks if error return. */ UnlockRelationOid(relid, AccessExclusiveLock); - ereport(ERROR, - (errcode(ERRCODE_RBIN_UNDEFINED_OBJECT), - errmsg("relation \"%u\" does not exist", relid))); + ereport(ERROR, (errcode(ERRCODE_RBIN_UNDEFINED_OBJECT), errmsg("relation \"%u\" does not exist", relid))); } else if (!SearchSysCacheExists1(PARTRELID, ObjectIdGetDatum(relid)) && type == RB_OBJ_PARTITION) { /* Clean already held locks if error return. */ UnlockRelationOid(relid, AccessExclusiveLock); - ereport(ERROR, - (errcode(ERRCODE_RBIN_UNDEFINED_OBJECT), - errmsg("partition \"%u\" does not exist", relid))); + ereport(ERROR, (errcode(ERRCODE_RBIN_UNDEFINED_OBJECT), errmsg("partition \"%u\" does not exist", relid))); } } - +/* + * 功能:对回收站对象中的关系进行加锁,首先锁定索引关系(如果有),然后锁定关系自身 + * 参数列表: + * desc:回收站对象描述 + */ static void TrLockRelation(TrObjDesc *desc) { Oid heapOid = InvalidOid; @@ -466,8 +567,7 @@ static void TrLockRelation(TrObjDesc *desc) heapOid = IndexGetRelation(desc->relid, true); if (!OidIsValid(heapOid)) { ereport(ERROR, - (errcode(ERRCODE_RBIN_UNDEFINED_OBJECT), - errmsg("relation \"%u\" does not exist", desc->relid))); + (errcode(ERRCODE_RBIN_UNDEFINED_OBJECT), errmsg("relation \"%u\" does not exist", desc->relid))); } TrLockRelationImpl(heapOid, desc->type); } @@ -487,11 +587,15 @@ static void TrLockRelation(TrObjDesc *desc) } PG_END_TRY(); } - +/* + * 功能:对回收站中的指定对象进行解锁 + * 参数列表: + * desc:回收站对象描述 + */ static void TrUnlockTrItem(TrObjDesc *desc) { - UnlockDatabaseObject(RecyclebinRelationId, desc->id, 0, - AccessExclusiveLock); + // 对指定的回收站对象进行解锁 + UnlockDatabaseObject(RecyclebinRelationId, desc->id, 0, AccessExclusiveLock); } static void TrLockTrItem(TrObjDesc *desc) @@ -503,9 +607,8 @@ static void TrLockTrItem(TrObjDesc *desc) /* 1. Try to lock rb item in AccessExclusiveLock */ if (!ConditionalLockDatabaseObject(RecyclebinRelationId, desc->id, 0, AccessExclusiveLock)) { - ereport(ERROR, - (errcode(ERRCODE_RBIN_LOCK_NOT_AVAILABLE), - errmsg("could not obtain lock on recycle object '%s'", desc->name))); + ereport(ERROR, (errcode(ERRCODE_RBIN_LOCK_NOT_AVAILABLE), + errmsg("could not obtain lock on recycle object '%s'", desc->name))); } /* @@ -516,8 +619,7 @@ static void TrLockTrItem(TrObjDesc *desc) rbRel = heap_open(RecyclebinRelationId, AccessShareLock); - ScanKeyInit(&skey[0], ObjectIdAttributeNumber, BTEqualStrategyNumber, - F_OIDEQ, ObjectIdGetDatum(desc->id)); + ScanKeyInit(&skey[0], ObjectIdAttributeNumber, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(desc->id)); sd = systable_beginscan(rbRel, RecyclebinIdIndexId, true, NULL, 1, skey); if ((tup = systable_getnext(sd)) == NULL) { @@ -525,8 +627,7 @@ static void TrLockTrItem(TrObjDesc *desc) systable_endscan(sd); heap_close(rbRel, AccessShareLock); ereport(ERROR, - (errcode(ERRCODE_RBIN_UNDEFINED_OBJECT), - errmsg("recycle object \"%s\" does not exist", desc->name))); + (errcode(ERRCODE_RBIN_UNDEFINED_OBJECT), errmsg("recycle object \"%s\" does not exist", desc->name))); } systable_endscan(sd); @@ -534,38 +635,44 @@ static void TrLockTrItem(TrObjDesc *desc) return; } - +/* + * 功能:验证回收站操作是否匹配对象的属性 + * + * 参数列表: + * desc:回收站对象描述信息 + * operMode:操作模式 + */ static void TrOperMatch(const TrObjDesc *desc, TrOperMode operMode) { switch (operMode) { case RB_OPER_PURGE: + /* 对象不允许被清除且不是分区对象,报告无法清除的错误 */ if (!desc->canpurge && desc->type != RB_OBJ_PARTITION) { - ereport(ERROR, - (errcode(ERRCODE_INVALID_OPERATION), - errmsg("recycle object \"%s\" cannot be purged", desc->name))); + ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION), + errmsg("recycle object \"%s\" cannot be purged", desc->name))); } break; case RB_OPER_RESTORE_DROP: + /* 对象不允许被恢复且不是分区对象,或者操作类型不是DROP,报告无法恢复的错误 */ if ((!desc->canrestore && desc->type != RB_OBJ_PARTITION) || desc->operation != RB_OPER_DROP) { - ereport(ERROR, - (errcode(ERRCODE_INVALID_OPERATION), - errmsg("recycle object \"%s\" cannot be restored", desc->name))); + ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION), + errmsg("recycle object \"%s\" cannot be restored", desc->name))); } break; case RB_OPER_RESTORE_TRUNCATE: + /* 对象不允许被恢复且不是分区对象,或者操作类型不是TRUNCATE,报告无法恢复的错误 */ if ((!desc->canrestore && desc->type != RB_OBJ_PARTITION) || desc->operation != RB_OPER_TRUNCATE) { - ereport(ERROR, - (errcode(ERRCODE_INVALID_OPERATION), - errmsg("recycle object \"%s\" cannot be restored", desc->name))); + ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION), + errmsg("recycle object \"%s\" cannot be restored", desc->name))); } break; default: - ereport(ERROR, - (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE), - errmsg("unrecognized recyclebin operation: %u", operMode))); + /* 未知的回收站操作类型,报告无法识别的错误 */ + ereport(ERROR, (errcode(ERRCODE_UNRECOGNIZED_NODE_TYPE), + errmsg("unrecognized recyclebin operation: %u", operMode))); break; } } @@ -574,6 +681,14 @@ static void TrOperMatch(const TrObjDesc *desc, TrOperMode operMode) * Fetch object from recycle bin for rb operations - purge, restore : * Prefer to fetch as original name, then recycle name. */ +/* + * 功能: 这个函数用于从回收站中获取对象的信息,并匹配指定的操作模式。 + * 参数列表: + * purobj:RangeVar 类型的对象,包含对象的模式名和名称信息。 + * objtype:TrObjType 类型,表示对象的类型。 + * desc:TrObjDesc 类型的指针,用于存储获取到的对象描述信息。 + * operMode:TrOperMode 类型,表示操作模式。 + */ void TrOperFetch(const RangeVar *purobj, TrObjType objtype, TrObjDesc *desc, TrOperMode operMode) { bool found = false; @@ -589,16 +704,21 @@ void TrOperFetch(const RangeVar *purobj, TrObjType objtype, TrObjDesc *desc, TrO /* not found, throw error */ if (!found) { - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_TABLE), - errmsg("recycle object \"%s\" desired does not exist", purobj->relname))); + ereport(ERROR, (errcode(ERRCODE_UNDEFINED_TABLE), + errmsg("recycle object \"%s\" desired does not exist", purobj->relname))); } TrOperMatch(desc, operMode); return; } - +/* + * 功能:恢复回收站对象时的权限检查 + * + * 参数列表: + * desc:回收站对象描述信息 + * operMode:操作模式 + */ static void TrPermRestore(TrObjDesc *desc, TrOperMode operMode) { AclResult aclCreateResult; @@ -609,7 +729,7 @@ static void TrPermRestore(TrObjDesc *desc, TrOperMode operMode) aclcheck_error(aclCreateResult, ACL_KIND_NAMESPACE, get_namespace_name(desc->nspace)); } - AclResult aclUsageResult = pg_namespace_aclcheck(desc->nspace, desc->authid, ACL_USAGE); + AclResult aclUsageResult = pg_namespace_aclcheck(desc->nspace, desc->authid, ACL_USAGE); if (aclUsageResult != ACLCHECK_OK) { aclcheck_error(aclUsageResult, ACL_KIND_NAMESPACE, get_namespace_name(desc->nspace)); } @@ -619,7 +739,7 @@ static void TrPermRestore(TrObjDesc *desc, TrOperMode operMode) aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, desc->name); return; } - + /* 如果操作模式是恢复为截断,则检查表截断权限 */ if (operMode == RB_OPER_RESTORE_TRUNCATE) { AclResult aclTruncateResult = pg_class_aclcheck(desc->relid, desc->authid, ACL_TRUNCATE); if (aclTruncateResult != ACLCHECK_OK) { @@ -627,15 +747,21 @@ static void TrPermRestore(TrObjDesc *desc, TrOperMode operMode) } } } - +/* + * 功能:验证回收站清除操作的权限 + * 参数列表: + * desc:回收站对象描述信息 + * operMode:操作模式 + */ static void TrPermPurge(TrObjDesc *desc, TrOperMode operMode) { AclResult result; - + /* 检查命名空间的使用权限 */ result = pg_namespace_aclcheck(desc->nspace, desc->authid, ACL_USAGE); if (result != ACLCHECK_OK) { aclcheck_error(result, ACL_KIND_NAMESPACE, get_namespace_name(desc->nspace)); } + /* 允许清除的条件:是表的所有者或者是命名空间的所有者 */ if (!pg_class_ownercheck(desc->relid, desc->authid) && !pg_namespace_ownercheck(desc->nspace, desc->authid)) { aclcheck_error(ACLCHECK_NOT_OWNER, ACL_KIND_CLASS, desc->name); } @@ -644,18 +770,23 @@ static void TrPermPurge(TrObjDesc *desc, TrOperMode operMode) /* * Check permission for rb operations - purge, restore */ +/* + * 功能:验证回收站操作的权限 + * 参数列表: + * desc:回收站对象描述信息 + * operMode:操作模式(操作类型) + */ static void TrPerm(TrObjDesc *desc, TrOperMode operMode) { switch (operMode) { case RB_OPER_RESTORE_DROP: case RB_OPER_RESTORE_TRUNCATE: - TrPermRestore(desc, operMode); + TrPermRestore(desc, operMode); // 调用 TrPermRestore 验证恢复操作权限 break; case RB_OPER_PURGE: - TrPermPurge(desc, operMode); + TrPermPurge(desc, operMode); // 调用 TrPermPurge 验证清除操作权限 break; default: - /* Never reached here. */ Assert(0); break; } @@ -665,6 +796,19 @@ static void TrPerm(TrObjDesc *desc, TrOperMode operMode) * Prepare for rb operations - purge, restore : * check permission, lock objects */ +/* + * TrOperPrep - 准备进行回收操作 + * + * 此函数用于准备进行回收操作。它执行以下步骤: + * 1. 检查权限。 + * 2. 获取回收站条目的锁,以避免并发的清除和恢复操作。 + * 3. 获取关系的锁,以避免并发的数据查询操作。注意:当进行清除截断关系操作时,忽略此步骤, + * 因为基础关系可能不存在。 + * + * 参数列表: + * desc: 回收站对象描述符,用于表示要进行操作的回收站对象 + * operMode: 回收操作的模式,表示执行的具体操作类型 + */ void TrOperPrep(TrObjDesc *desc, TrOperMode operMode) { bool needLockRelation = false; @@ -699,7 +843,13 @@ void TrOperPrep(TrObjDesc *desc, TrOperMode operMode) PG_END_TRY(); } } - +/* + * 功能:根据传入的关系ID(relid)判断是否可以对该关系进行回收操作。 + * + * 参数列表: + * relid: 关系ID + * + */ bool NeedTrComm(Oid relid) { Relation rel; @@ -750,87 +900,117 @@ bool NeedTrComm(Oid relid) RelationInClusterResizing(rel) || /* is in system namespace. */ (IsSystemNamespace(classForm->relnamespace) || IsToastNamespace(classForm->relnamespace) || - IsCStoreNamespace(classForm->relnamespace))) { - relation_close(rel, NoLock); + IsCStoreNamespace(classForm->relnamespace))) { + relation_close(rel, NoLock); // 关闭关系 return false; } - relation_close(rel, NoLock); + relation_close(rel, NoLock); // 关闭关系 return true; } - +/* + * 功能:此函数根据传入的命名空间ID和关系类型标识,确定并返回对应的回收对象类型。 + * + * 参数列表: + * nspId: 命名空间ID,用于判断对象是否在TOAST命名空间 + * relKind: 关系类型标识,表示对象的类型 + */ TrObjType TrGetObjType(Oid nspId, char relKind) { + // 默认回收对象类型为表 RB_OBJ_TABLE TrObjType type = RB_OBJ_TABLE; - + // 根据关系类型标识进行判断并设置对应的回收对象类型 switch (relKind) { case RELKIND_INDEX: + // 如果是索引对象,根据是否在TOAST命名空间判断是TOAST索引还是普通索引 type = IsToastNamespace(nspId) ? RB_OBJ_TOAST_INDEX : RB_OBJ_INDEX; break; case RELKIND_RELATION: + // 如果是表对象,设置为表类型 type = RB_OBJ_TABLE; break; case RELKIND_SEQUENCE: case RELKIND_LARGE_SEQUENCE: + // 如果是序列对象,设置为序列类型 type = RB_OBJ_SEQUENCE; break; case RELKIND_TOASTVALUE: + // 如果是TOAST值对象,设置为TOAST类型 type = RB_OBJ_TOAST; break; case PARTTYPE_PARTITIONED_RELATION: + // 如果是分区表对象,设置为分区表类型 type = RB_OBJ_PARTITION; break; case RELKIND_GLOBAL_INDEX: + // 如果是全局索引对象,设置为全局索引类型 type = RB_OBJ_GLOBAL_INDEX; break; case RELKIND_MATVIEW: + // 如果是物化视图对象,设置为物化视图类型 type = RB_OBJ_MATVIEW; break; default: /* Never reached here. */ + // 对于其他未知的关系类型,断言报错,因为不应该到达这个分支 Assert(0); break; } - + // 返回确定的回收对象类型 return type; } - +/* + * 功能:用于检查给定的对象地址是否存在于给定的对象地址集合中。 + * + * 参数列表: + * classid: 对象的类别标识 + * objid: 对象的唯一标识 + * objSet: 对象地址集合,包含多个对象的地址信息 + */ static bool TrObjAddrExists(Oid classid, Oid objid, ObjectAddresses *objSet) { int i; - + // 遍历对象地址集合中的每个对象地址 for (i = 0; i < objSet->numrefs; i++) { + // 如果给定的对象地址和集合中的对象地址匹配,返回 true if (TrObjIsEqualEx(classid, objid, &objSet->refs[i])) { return true; } } - + // 若遍历完所有对象地址仍未找到匹配项,返回 false return false; } /* * output: refobjs */ -void TrFindAllRefObjs(Relation depRel, const ObjectAddress *subobj, - ObjectAddresses *refobjs, bool ignoreObjSubId) +/* + * 功能用于查找引用给定对象的所有其他对象,并将这些引用对象的地址添加到指定的对象地址集合中。 + * + * 参数列表: + * depRel: 用于扫描依赖关系的关系对象 + * subobj: 被引用对象的地址信息 + * refobjs: 存储引用对象地址的对象地址集合 + * ignoreObjSubId: 是否忽略被引用对象的子标识,默认为 false + */ +void TrFindAllRefObjs(Relation depRel, const ObjectAddress *subobj, ObjectAddresses *refobjs, bool ignoreObjSubId) { SysScanDesc sd; HeapTuple tuple; ScanKeyData key[3]; int nkeys; - - ScanKeyInit(&key[0], Anum_pg_depend_classid, BTEqualStrategyNumber, F_OIDEQ, - ObjectIdGetDatum(subobj->classId)); - ScanKeyInit(&key[1], Anum_pg_depend_objid, BTEqualStrategyNumber, F_OIDEQ, - ObjectIdGetDatum(subobj->objectId)); + // 初始化用于扫描的键 + ScanKeyInit(&key[0], Anum_pg_depend_classid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(subobj->classId)); + ScanKeyInit(&key[1], Anum_pg_depend_objid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(subobj->objectId)); nkeys = 2; + // 如果不忽略对象的子标识且子标识不为0,添加第三个键 if (!ignoreObjSubId && subobj->objectSubId != 0) { ScanKeyInit(&key[2], Anum_pg_depend_objsubid, BTEqualStrategyNumber, F_INT4EQ, - Int32GetDatum(subobj->objectSubId)); + Int32GetDatum(subobj->objectSubId)); nkeys = 3; } - + // 开始扫描依赖关系 sd = systable_beginscan(depRel, DependDependerIndexId, true, NULL, nkeys, key); while (HeapTupleIsValid(tuple = systable_getnext(sd))) { Form_pg_depend depForm = (Form_pg_depend)GETSTRUCT(tuple); @@ -841,134 +1021,223 @@ void TrFindAllRefObjs(Relation depRel, const ObjectAddress *subobj, /* We keep `objSet` unique when `ignoreObjSubId = true` to avoid circle recursive. */ if (!ignoreObjSubId || !TrObjAddrExists(depForm->refclassid, depForm->refobjid, refobjs)) { - add_object_address_ext(depForm->refclassid, depForm->refobjid, - depForm->refobjsubid, depForm->deptype, refobjs); + add_object_address_ext(depForm->refclassid, depForm->refobjid, depForm->refobjsubid, depForm->deptype, + refobjs); } } - systable_endscan(sd); + systable_endscan(sd); // 结束扫描 return; } - -static void TrFindAllInternalObjs(Relation depRel, const ObjectAddress *refobj, - ObjectAddresses *objSet, bool ignoreObjSubId = false) +/* + * 功能:用于查找引用给定对象的所有内部对象,并将这些内部对象的地址添加到指定的对象地址集合中。 + * + * 参数列表: + * depRel: 用于扫描依赖关系的关系对象 + * refobj: 被引用对象的地址信息 + * objSet: 存储内部对象地址的对象地址集合 + * ignoreObjSubId: 是否忽略被引用对象的子标识,默认为 false + */ +static void TrFindAllInternalObjs(Relation depRel, const ObjectAddress *refobj, ObjectAddresses *objSet, + bool ignoreObjSubId = false) { SysScanDesc sd; HeapTuple tuple; ScanKeyData key[3]; int nkeys; - - ScanKeyInit(&key[0], Anum_pg_depend_refclassid, BTEqualStrategyNumber, F_OIDEQ, - ObjectIdGetDatum(refobj->classId)); - ScanKeyInit(&key[1], Anum_pg_depend_refobjid, BTEqualStrategyNumber, F_OIDEQ, - ObjectIdGetDatum(refobj->objectId)); + // 初始化用于扫描的键 + ScanKeyInit(&key[0], Anum_pg_depend_refclassid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(refobj->classId)); + ScanKeyInit(&key[1], Anum_pg_depend_refobjid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(refobj->objectId)); nkeys = 2; + // 如果不忽略对象的子标识且子标识不为0,添加第三个键 if (!ignoreObjSubId && refobj->objectSubId != 0) { ScanKeyInit(&key[2], Anum_pg_depend_refobjsubid, BTEqualStrategyNumber, F_INT4EQ, - Int32GetDatum(refobj->objectSubId)); + Int32GetDatum(refobj->objectSubId)); nkeys = 3; } - + // 开始扫描依赖关系 sd = systable_beginscan(depRel, DependReferenceIndexId, true, NULL, nkeys, key); while (HeapTupleIsValid(tuple = systable_getnext(sd))) { Form_pg_depend depForm = (Form_pg_depend)GETSTRUCT(tuple); + // 仅处理 deptype 为 'i' 的对象 if (depForm->deptype != 'i') { continue; } /* We keep `objSet` unique when `ignoreObjSubId = true` to avoid circle recursive. */ if (!ignoreObjSubId || !TrObjAddrExists(depForm->classid, depForm->objid, objSet)) { - add_object_address_ext(depForm->classid, depForm->objid, - depForm->objsubid, depForm->deptype, objSet); + // 将内部对象的地址添加到对象地址集合中 + add_object_address_ext(depForm->classid, depForm->objid, depForm->objsubid, depForm->deptype, objSet); } } - systable_endscan(sd); + systable_endscan(sd); // 结束扫描 return; } - +/* + * 功能:根据回收站对象的操作类型(操作码)来执行相应的清除操作。 + * + * 参数列表: + * desc: 待清除的回收站对象描述 + */ static void TrDoPurgeObject(TrObjDesc *desc) { - if (desc->operation == RB_OPER_DROP) { + if (desc->operation == RB_OPER_DROP) { // 操作类型为删除 TrDoPurgeObjectDrop(desc); - } else { + } else { // 操作类型为截断 TrDoPurgeObjectTruncate(desc); } } - +/* + * 功能:执行回收站对象的永久删除操作 + * 参数列表: + * purobj:要永久删除的对象的范围变量 + * type:对象的类型(例如表、索引等) + */ void TrPurgeObject(RangeVar *purobj, TrObjType type) { TrObjDesc desc; + // 获取回收站对象的描述信息 TrOperFetch(purobj, type, &desc, RB_OPER_PURGE); + // 设置操作的授权用户 ID desc.authid = GetUserId(); + + // 准备执行删除操作,包括权限检查等 TrOperPrep(&desc, RB_OPER_PURGE); + // 执行实际的永久删除操作 TrDoPurgeObject(&desc); return; } - +// 一次永久删除操作中要处理的对象数量(批量处理的大小) const int PURGE_BATCH = 64; +// 单个永久删除操作中要处理的对象数量 const int PURGE_SINGL = 64; +// TrFetchBeginHook 是函数指针类型,用于指向在获取回收站对象前执行的钩子函数。 +// 该钩子函数接受 SysScanDesc 指针和对象的 ID 作为参数,用于初始化扫描以获取回收站对象。 typedef void (*TrFetchBeginHook)(SysScanDesc *sd, Oid objId); +// TrFetchMatchHook 是函数指针类型,用于指向在获取回收站对象后执行的钩子函数。 +// 该钩子函数接受回收站关系、堆元组和对象 ID 作为参数,用于检查回收站对象是否匹配。 typedef bool (*TrFetchMatchHook)(Relation rbRel, HeapTuple rbTup, Oid objId); - +/* + * 功能:开始执行回收站对象提取操作的钩子函数 + * 参数列表: + * fetchHook:提取操作的钩子函数指针 + * sd:系统扫描描述符指针 + * objId:对象的标识符 + */ static void TrFetchBegin(TrFetchBeginHook fetchHook, SysScanDesc *sd, Oid objId) { + // 调用提取操作的钩子函数,传入系统扫描描述符和对象标识符 fetchHook(sd, objId); } // @return: true for eof +/* + * 功能:执行回收站对象提取操作的执行函数 + * 参数列表: + * matchHook:匹配操作的钩子函数指针 + * objId:对象的标识符 + * sd:系统扫描描述符 + * desc:回收站对象描述结构的指针 + */ static bool TrFetchExec(TrFetchMatchHook matchHook, Oid objId, SysScanDesc sd, TrObjDesc *desc) { HeapTuple tup; while ((tup = systable_getnext(sd)) != NULL) { + // 获取堆元组的结构体形式 Form_pg_recyclebin rbForm = (Form_pg_recyclebin)GETSTRUCT(tup); + + // 根据回收站对象类型和匹配操作的结果进行处理 if ((rbForm->rcytype == RB_OBJ_TABLE) && matchHook(sd->heap_rel, tup, objId)) { - Assert (rbForm->rcycanpurge); + // 断言回收站对象可以永久删除 + Assert(rbForm->rcycanpurge); + + // 读取回收站对象描述信息 TrDescRead(desc, tup); + + // 返回 false,表示成功匹配并提取对象 return false; } else if ((rbForm->rcytype == RB_OBJ_PARTITION) && matchHook(sd->heap_rel, tup, objId)) { - Assert (!rbForm->rcycanpurge); + // 断言回收站对象不可永久删除 + Assert(!rbForm->rcycanpurge); + + // 读取回收站对象描述信息 TrDescRead(desc, tup); + + // 返回 false,表示成功匹配并提取对象 return false; } } + + // 如果没有匹配的对象,返回 true return true; } +/* + * 功能:结束回收站对象提取操作的扫描并释放相关资源 + * 参数列表: + * sd:系统扫描描述符 + */ static void TrFetchEnd(SysScanDesc sd) { + // 获取回收站关系的引用 Relation rbRel = sd->heap_rel; + // 结束系统扫描 systable_endscan(sd); + + // 关闭回收站关系,释放共享锁 heap_close(rbRel, AccessShareLock); } -static bool TrPurgeBatch(TrFetchBeginHook beginHook, TrFetchMatchHook matchHook, - Oid objId, Oid roleid, uint32 maxBatch, PurgeMsgRes *localRes) +/* + * 功能:批量永久删除回收站对象 + * 参数列表: + * beginHook:开始提取的钩子函数 + * matchHook:匹配的钩子函数 + * objId:对象的ID + * roleid:角色的ID + * maxBatch:最大批次数量 + * localRes:用于存储删除结果的结构体指针 + */ +static bool TrPurgeBatch(TrFetchBeginHook beginHook, TrFetchMatchHook matchHook, Oid objId, Oid roleid, uint32 maxBatch, + PurgeMsgRes *localRes) { SysScanDesc sd = NULL; TrObjDesc desc; uint32 count = 0; bool eof = false; + // 重置本地删除结果信息 RbMsgResetRes(localRes); + // 开启事务 StartTransactionCommand(); + // 开始提取回收站对象 TrFetchBegin(beginHook, &sd, objId); + + // 循环执行批量删除操作,直到批次结束 while (!(eof = TrFetchExec(matchHook, objId, sd, &desc))) { CHECK_FOR_INTERRUPTS(); + // 使用 TRY-CATCH 块来处理删除过程中的异常情况 PG_TRY(); { + // 设置要执行删除的角色 desc.authid = roleid; + + // 准备删除操作,检查权限等 TrOperPrep(&desc, RB_OPER_PURGE); + // 执行删除操作 TrDoPurgeObject(&desc); + + // 更新本地删除结果统计 localRes->purgedNum++; } PG_CATCH(); @@ -976,229 +1245,355 @@ static bool TrPurgeBatch(TrFetchBeginHook beginHook, TrFetchMatchHook matchHook, int errcode = geterrcode(); if (errcode == ERRCODE_RBIN_LOCK_NOT_AVAILABLE) { errno_t rc; + // 如果锁不可用,记录错误消息,并增加跳过数量 rc = strncpy_s(localRes->errMsg, RB_MAX_ERRMSG_SIZE, Geterrmsg(), RB_MAX_ERRMSG_SIZE - 1); securec_check(rc, "\0", "\0"); localRes->skippedNum++; } else if (errcode == ERRCODE_RBIN_UNDEFINED_OBJECT) { + // 如果对象未定义,增加未定义数量 localRes->undefinedNum++; } else { + // 其他异常情况,重新抛出异常 PG_RE_THROW(); } } PG_END_TRY(); + // 如果达到最大批次数量,跳出循环 if (++count >= maxBatch) { break; } } + // 结束回收站对象提取操作 TrFetchEnd(sd); + // 提交事务 CommitTransactionCommand(); + // 返回批次是否结束的标志 return eof; } +/* + * 功能:开始提取指定表空间下的回收站对象 + * 参数列表: + * sd:用于扫描的 SysScanDesc 指针 + * spcId:表空间的ID + */ static void TrFetchBeginSpace(SysScanDesc *sd, Oid spcId) { ScanKeyData skey[2]; Relation rbRel; + // 打开回收站关系 rbRel = heap_open(RecyclebinRelationId, AccessShareLock); - ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcytablespace, BTEqualStrategyNumber, - F_OIDEQ, ObjectIdGetDatum(spcId)); - ScanKeyInit(&skey[1], Anum_pg_recyclebin_rcydbid, BTEqualStrategyNumber, - F_OIDEQ, ObjectIdGetDatum(u_sess->proc_cxt.MyDatabaseId)); + // 初始化扫描键 + ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcytablespace, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(spcId)); + ScanKeyInit(&skey[1], Anum_pg_recyclebin_rcydbid, BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(u_sess->proc_cxt.MyDatabaseId)); + // 开始扫描回收站对象 *sd = systable_beginscan(rbRel, RecyclebinDbidSpcidRcycsnIndexId, true, NULL, 2, skey); } +/* + * 功能:匹配回收站对象是否属于指定的表空间 + * 参数列表: + * rbRel:回收站关系 + * rbTup:当前的回收站元组 + * objId:要匹配的对象ID + */ static bool TrFetchMatchSpace(Relation rbRel, HeapTuple rbTup, Oid objId) { Form_pg_recyclebin rbForm = (Form_pg_recyclebin)GETSTRUCT(rbTup); return rbForm->rcytablespace == objId; } +/* + * 功能:清除指定表空间下的回收站对象 + * 参数列表: + * id:回收站消息的ID + */ void TrPurgeTablespace(int64 id) { - PurgeMsgReq *req = &RbMsg(id)->req; - PurgeMsgRes localRes; - bool eof = false; + PurgeMsgReq *req = &RbMsg(id)->req; // 获取回收站消息的请求信息 + PurgeMsgRes localRes; // 本地的清除结果 + bool eof = false; // 表示是否到达回收站对象的末尾 do { + // 逐批清除回收站对象 eof = TrPurgeBatch(TrFetchBeginSpace, TrFetchMatchSpace, req->objId, req->authId, PURGE_BATCH, &localRes); + + // 更新消息的统计信息 RbMsgSetStatistics(id, &localRes); } while (!eof && localRes.skippedNum == 0); } +/* + * 功能:以逐个对象方式清除指定表空间下的回收站对象 + * 参数列表: + * id:回收站消息的ID + */ void TrPurgeTablespaceDML(int64 id) { - PurgeMsgReq *req = &RbMsg(id)->req; - PurgeMsgRes localRes; - bool eof = false; + PurgeMsgReq *req = &RbMsg(id)->req; // 获取回收站消息的请求信息 + PurgeMsgRes localRes; // 本地的清除结果 + bool eof = false; // 表示是否到达回收站对象的末尾 do { + // 逐个对象清除回收站对象 eof = TrPurgeBatch(TrFetchBeginSpace, TrFetchMatchSpace, req->objId, req->authId, PURGE_SINGL, &localRes); + + // 更新消息的统计信息 RbMsgSetStatistics(id, &localRes); } while (!eof && localRes.purgedNum == 0); } - +/* + * 功能:初始化回收站中指定对象的扫描 + * 参数列表: + * sd:SysScanDesc 结构的指针,用于接收扫描描述 + * objId:要初始化扫描的对象的ID + */ static void TrFetchBeginRecyclebin(SysScanDesc *sd, Oid objId) { - ScanKeyData skey[2]; - Relation rbRel; + ScanKeyData skey[2]; // 扫描键数组 + Relation rbRel; // 回收站关系对象 - rbRel = heap_open(RecyclebinRelationId, AccessShareLock); + rbRel = heap_open(RecyclebinRelationId, AccessShareLock); // 打开回收站关系对象 - ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcydbid, BTEqualStrategyNumber, - F_OIDEQ, ObjectIdGetDatum(u_sess->proc_cxt.MyDatabaseId)); + // 初始化扫描键,通过数据库ID来筛选回收站中的对象 + ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcydbid, BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(u_sess->proc_cxt.MyDatabaseId)); + // 开始扫描回收站关系对象 *sd = systable_beginscan(rbRel, RecyclebinDbidRelidIndexId, true, NULL, 1, skey); } +/* + * 功能:检查回收站对象是否匹配,始终返回 true,即表示匹配任何回收站对象 + * 参数列表: + * rbRel:回收站关系对象 + * rbTup:回收站元组(HeapTuple) + * objId:要匹配的对象的ID(无效的) + */ static bool TrFetchMatchRecyclebin(Relation rbRel, HeapTuple rbTup, Oid objId) { - return true; + return true; // 始终返回 true,表示匹配任何回收站对象 } +/* + * 功能:永久删除回收站中的对象 + * 参数列表: + * id:消息ID + */ void TrPurgeRecyclebin(int64 id) { - PurgeMsgReq *req = &RbMsg(id)->req; - PurgeMsgRes localRes; - bool eof = false; + PurgeMsgReq *req = &RbMsg(id)->req; // 获取消息请求 + PurgeMsgRes localRes; // 用于保存本地清除结果 + bool eof = false; // 标识扫描是否结束 do { - eof = TrPurgeBatch(TrFetchBeginRecyclebin, TrFetchMatchRecyclebin, - InvalidOid, req->authId, PURGE_BATCH, &localRes); + // 执行回收站对象的批量清除 + eof = TrPurgeBatch(TrFetchBeginRecyclebin, TrFetchMatchRecyclebin, InvalidOid, req->authId, PURGE_BATCH, + &localRes); + + // 更新消息的统计信息 RbMsgSetStatistics(id, &localRes); - } while (!eof && localRes.skippedNum == 0); + } while (!eof && localRes.skippedNum == 0); // 继续循环直到扫描结束或没有被跳过的对象 } +/* + * 功能:开始在回收站中扫描与指定模式匹配的对象 + * 参数列表: + * sd:扫描描述符指针,用于存储扫描结果 + * objId:要匹配的模式的ID + */ static void TrFetchBeginSchema(SysScanDesc *sd, Oid objId) { - ScanKeyData skey[2]; - Relation rbRel; + ScanKeyData skey[2]; // 扫描键数组 + Relation rbRel; // 回收站关系对象 - rbRel = heap_open(RecyclebinRelationId, AccessShareLock); - ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcynamespace, BTEqualStrategyNumber, - F_OIDEQ, ObjectIdGetDatum(objId)); - ScanKeyInit(&skey[1], Anum_pg_recyclebin_rcydbid, BTEqualStrategyNumber, - F_OIDEQ, ObjectIdGetDatum(u_sess->proc_cxt.MyDatabaseId)); + rbRel = heap_open(RecyclebinRelationId, AccessShareLock); // 打开回收站关系对象 + ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcynamespace, BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(objId)); // 初始化扫描键,匹配模式的命名空间 + ScanKeyInit(&skey[1], Anum_pg_recyclebin_rcydbid, BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(u_sess->proc_cxt.MyDatabaseId)); // 初始化扫描键,匹配当前数据库ID - *sd = systable_beginscan(rbRel, RecyclebinDbidNspOrinameIndexId, true, NULL, 2, skey); + *sd = systable_beginscan(rbRel, RecyclebinDbidNspOrinameIndexId, true, NULL, 2, skey); // 开始扫描 } +/* + * 功能:在回收站中匹配指定模式的对象 + * 参数列表: + * rbRel:回收站关系对象 + * rbTup:回收站中的元组 + * objId:要匹配的模式的ID + */ static bool TrFetchMatchSchema(Relation rbRel, HeapTuple rbTup, Oid objId) { - Form_pg_recyclebin rbForm = (Form_pg_recyclebin)GETSTRUCT(rbTup); - return rbForm->rcynamespace == objId; + Form_pg_recyclebin rbForm = (Form_pg_recyclebin)GETSTRUCT(rbTup); // 获取元组的数据结构体 + return rbForm->rcynamespace == objId; // 返回是否匹配模式的命名空间 } +/* + * 功能:执行回收站中指定模式的对象的永久删除操作 + * 参数列表: + * id:要删除的模式的ID + */ void TrPurgeSchema(int64 id) { - PurgeMsgReq *req = &RbMsg(id)->req; - PurgeMsgRes localRes; + PurgeMsgReq *req = &RbMsg(id)->req; // 获取请求消息 + PurgeMsgRes localRes; // 用于存储删除操作的结果 bool eof = false; do { - eof = TrPurgeBatch(TrFetchBeginSchema, TrFetchMatchSchema, req->objId, req->authId, PURGE_BATCH, &localRes); - RbMsgSetStatistics(id, &localRes); - } while (!eof && localRes.skippedNum == 0); + eof = TrPurgeBatch(TrFetchBeginSchema, TrFetchMatchSchema, req->objId, req->authId, PURGE_BATCH, + &localRes); // 执行批量永久删除操作 + RbMsgSetStatistics(id, &localRes); // 更新删除统计信息 + } while (!eof && localRes.skippedNum == 0); // 当批量删除未完成且未跳过任何操作时继续执行 } +/* + * 功能:在回收站中根据用户ID开始扫描匹配的对象 + * 参数列表: + * sd:扫描描述符指针,用于存储扫描结果 + * objId:要匹配的对象的ID(在这里是用户ID) + */ static void TrFetchBeginUser(SysScanDesc *sd, Oid objId) { - ScanKeyData skey[2]; - Relation rbRel; + ScanKeyData skey[2]; // 扫描键数组 + Relation rbRel; // 回收站关系对象 - rbRel = heap_open(RecyclebinRelationId, AccessShareLock); + rbRel = heap_open(RecyclebinRelationId, AccessShareLock); // 打开回收站关系对象,获取访问共享锁 - ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcydbid, BTEqualStrategyNumber, - F_OIDEQ, ObjectIdGetDatum(u_sess->proc_cxt.MyDatabaseId)); + // 初始化扫描键 + ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcydbid, BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(u_sess->proc_cxt.MyDatabaseId)); + // 开始扫描回收站关系对象,并将扫描结果存储到扫描描述符中 *sd = systable_beginscan(rbRel, RecyclebinDbidRelidIndexId, true, NULL, 1, skey); } +/* + * 功能:在回收站中匹配指定用户的对象 + * 参数列表: + * rbRel:回收站关系对象 + * rbTup:回收站中的元组 + * objId:要匹配的用户的ID + */ static bool TrFetchMatchUser(Relation rbRel, HeapTuple rbTup, Oid objId) { - Form_pg_recyclebin rbForm = (Form_pg_recyclebin)GETSTRUCT(rbTup); - return rbForm->rcyowner == objId; + Form_pg_recyclebin rbForm = (Form_pg_recyclebin)GETSTRUCT(rbTup); // 获取元组的数据结构体 + return rbForm->rcyowner == objId; // 返回是否匹配指定用户的所有者 } +/* + * 功能:执行回收站中指定用户的对象的永久删除操作 + * 参数列表: + * id:要删除的用户的ID + */ void TrPurgeUser(int64 id) { - PurgeMsgReq *req = &RbMsg(id)->req; - PurgeMsgRes localRes; + PurgeMsgReq *req = &RbMsg(id)->req; // 获取请求消息 + PurgeMsgRes localRes; // 用于存储删除操作的结果 bool eof = false; do { - eof = TrPurgeBatch(TrFetchBeginUser, TrFetchMatchUser, req->objId, req->authId, PURGE_BATCH, &localRes); - RbMsgSetStatistics(id, &localRes); - } while (!eof && localRes.skippedNum == 0); + eof = TrPurgeBatch(TrFetchBeginUser, TrFetchMatchUser, req->objId, req->authId, PURGE_BATCH, + &localRes); // 执行批量永久删除操作 + RbMsgSetStatistics(id, &localRes); // 更新删除统计信息 + } while (!eof && localRes.skippedNum == 0); // 当批量删除未完成且未跳过任何操作时继续执行 } +/* + * 功能:在回收站中根据自动清理策略开始扫描匹配的对象 + * 参数列表: + * sd:扫描描述符指针,用于存储扫描结果 + * objId:要匹配的对象的ID(在这里是无效的,不使用) + */ static void TrFetchBeginAuto(SysScanDesc *sd, Oid objId) { - ScanKeyData skey[2]; - Relation rbRel; + ScanKeyData skey[2]; // 扫描键数组 + Relation rbRel; // 回收站关系对象 - rbRel = heap_open(RecyclebinRelationId, AccessShareLock); + rbRel = heap_open(RecyclebinRelationId, AccessShareLock); // 打开回收站关系对象,获取访问共享锁 - ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcydbid, BTEqualStrategyNumber, - F_OIDEQ, ObjectIdGetDatum(u_sess->proc_cxt.MyDatabaseId)); + // 初始化扫描键 + ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcydbid, BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(u_sess->proc_cxt.MyDatabaseId)); + // 开始扫描回收站关系对象,并将扫描结果存储到扫描描述符中 *sd = systable_beginscan(rbRel, RecyclebinDbidRelidIndexId, true, NULL, 1, skey); } - +/* + * 功能:在回收站中匹配自动删除的对象,判断是否满足自动删除条件 + * 参数列表: + * rbRel:回收站关系对象 + * rbTup:回收站中的元组 + * objId:要匹配的对象的ID + */ static bool TrFetchMatchAuto(Relation rbRel, HeapTuple rbTup, Oid objId) { bool isNull = false; - Datum datumRcyTime = heap_getattr(rbTup, Anum_pg_recyclebin_rcyrecycletime, - RelationGetDescr(rbRel), &isNull); + Datum datumRcyTime = heap_getattr(rbTup, Anum_pg_recyclebin_rcyrecycletime, RelationGetDescr(rbRel), &isNull); long secs; int msecs; - TimestampDifference(isNull ? 0 : DatumGetTimestampTz(datumRcyTime), - GetCurrentTimestamp(), &secs, &msecs); + TimestampDifference(isNull ? 0 : DatumGetTimestampTz(datumRcyTime), GetCurrentTimestamp(), &secs, &msecs); - return secs > u_sess->attr.attr_storage.recyclebin_retention_time || secs < 0; + return secs > u_sess->attr.attr_storage.recyclebin_retention_time || secs < 0; // 判断是否满足自动删除条件 } +/* + * 功能:执行回收站中自动删除的对象的永久删除操作 + * 参数列表: + * id:要删除的对象的ID + */ void TrPurgeAuto(int64 id) { - PurgeMsgReq *req = &RbMsg(id)->req; - PurgeMsgRes localRes; + PurgeMsgReq *req = &RbMsg(id)->req; // 获取请求消息 + PurgeMsgRes localRes; // 用于存储删除操作的结果 bool eof = false; - do { - eof = TrPurgeBatch(TrFetchBeginAuto, TrFetchMatchAuto, InvalidOid, req->authId, PURGE_BATCH, &localRes); - RbMsgSetStatistics(id, &localRes); - } while (!eof); -} + do { + eof = TrPurgeBatch(TrFetchBeginAuto, TrFetchMatchAuto, InvalidOid, req->authId, PURGE_BATCH, + &localRes); // 执行批量永久删除操作 + RbMsgSetStatistics(id, &localRes); // 更新删除统计信息 + } while (!eof); // 当批量删除未完成时继续执行 +} +/* + * 功能:交换回收站对象和关系对象的文件节点信息 + * 参数列表: + * rbRel:回收站关系对象 + * rbTup:回收站中的对象元组 + * isPart:是否是分区对象 + */ void TrSwapRelfilenode(Relation rbRel, HeapTuple rbTup, bool isPart) { - Relation relRel; - HeapTuple relTup; - HeapTuple newTup; - TrObjDesc desc; - int maxNattr = 0; - Datum *values = NULL; - bool *nulls = NULL; - bool *replaces = NULL; - NameData name; - errno_t rc = EOK; - bool isNull = false; - int relfilenoIndex = 0; - int frozenxidIndex = 0; - int frozenxid64Index = 0; - bool isPartition = false; - + Relation relRel; // 关系对象的关系描述符 + HeapTuple relTup; // 关系对象元组 + HeapTuple newTup; // 新关系对象元组 + TrObjDesc desc; // 回收站对象的描述信息 + int maxNattr = 0; // 最大属性数 + Datum *values = NULL; // 属性值数组 + bool *nulls = NULL; // NULL值数组 + bool *replaces = NULL; // 需要替换的属性数组 + NameData name; // 名称数据 + errno_t rc = EOK; // 错误码 + bool isNull = false; // 是否为NULL + int relfilenoIndex = 0; // relfileno属性的索引 + int frozenxidIndex = 0; // frozenxid属性的索引 + int frozenxid64Index = 0; // frozenxid64属性的索引 + bool isPartition = false; // 是否为分区对象 + // 读取回收站对象描述 TrDescRead(&desc, rbTup); - - if (desc.type == RB_OBJ_PARTITION || (desc.type == RB_OBJ_INDEX && isPart)) { + // 判断是否为分区对象 + if (desc.type == RB_OBJ_PARTITION || (desc.type == RB_OBJ_INDEX && isPart)) { isPartition = true; } + // 根据对象类型确定要操作的系统表 if (isPartition) { maxNattr = Max(Natts_pg_partition, Natts_pg_recyclebin); relRel = heap_open(PartitionRelationId, RowExclusiveLock); @@ -1217,15 +1612,13 @@ void TrSwapRelfilenode(Relation rbRel, HeapTuple rbTup, bool isPart) /* 1. Update pg_class or pg_partition */ if (!HeapTupleIsValid(relTup)) { - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_TABLE), - errmsg("cache lookup failed for relation %u", desc.relid))); + ereport(ERROR, (errcode(ERRCODE_UNDEFINED_TABLE), errmsg("cache lookup failed for relation %u", desc.relid))); } - + // 为更新构造属性值、是否为空和是否更新的数组 values = (Datum *)palloc0(sizeof(Datum) * maxNattr); nulls = (bool *)palloc0(sizeof(bool) * maxNattr); replaces = (bool *)palloc0(sizeof(bool) * maxNattr); - + // 设置需要更新的属性和对应的新值 replaces[relfilenoIndex - 1] = true; values[relfilenoIndex - 1] = ObjectIdGetDatum(desc.relfilenode); @@ -1234,11 +1627,11 @@ void TrSwapRelfilenode(Relation rbRel, HeapTuple rbTup, bool isPart) replaces[frozenxid64Index - 1] = true; values[frozenxid64Index - 1] = TransactionIdGetDatum(desc.frozenxid64); - + // 构造新的元组 newTup = heap_modify_tuple(relTup, RelationGetDescr(relRel), values, nulls, replaces); - + // 执行堆表更新操作 simple_heap_update(relRel, &newTup->t_self, newTup); - + // 更新索引 CatalogUpdateIndexes(relRel, newTup); heap_freetuple_ext(newTup); @@ -1250,7 +1643,7 @@ void TrSwapRelfilenode(Relation rbRel, HeapTuple rbTup, bool isPart) securec_check(rc, "\0", "\0"); rc = memset_s(replaces, sizeof(bool) * maxNattr, false, sizeof(bool) * maxNattr); securec_check(rc, "\0", "\0"); - + // 生成回收站对象名 (void)TrGenObjName(NameStr(name), RelationRelationId, desc.relid); replaces[Anum_pg_recyclebin_rcyname - 1] = true; values[Anum_pg_recyclebin_rcyname - 1] = NameGetDatum(&name); @@ -1287,87 +1680,118 @@ void TrSwapRelfilenode(Relation rbRel, HeapTuple rbTup, bool isPart) } replaces[Anum_pg_recyclebin_rcyfrozenxid64 - 1] = true; - Datum xid64datum = heap_getattr(relTup, frozenxid64Index, RelationGetDescr(relRel), &isNull); - values[Anum_pg_recyclebin_rcyfrozenxid64 - 1] = DatumGetTransactionId(xid64datum); + if (isPartition) { + // 如果是分区对象,将分区对象的relfrozenxid64设置为回收站元组的属性值 + values[Anum_pg_recyclebin_rcyfrozenxid64 - 1] = + ShortTransactionIdGetDatum(((Form_pg_partition)GETSTRUCT(relTup))->relfrozenxid); + } else { + // 如果是非分区对象,将关系对象的relfrozenxid64设置为回收站元组的属性值 + values[Anum_pg_recyclebin_rcyfrozenxid64 - 1] = + ShortTransactionIdGetDatum(((Form_pg_class)GETSTRUCT(relTup))->relfrozenxid); + } + // 构造新的回收站元组 newTup = heap_modify_tuple(rbTup, RelationGetDescr(rbRel), values, nulls, replaces); + // 执行堆表更新操作 simple_heap_update(rbRel, &newTup->t_self, newTup); + // 更新索引 CatalogUpdateIndexes(rbRel, newTup); heap_freetuple_ext(newTup); + // 释放动态分配的内存 pfree(values); pfree(nulls); pfree(replaces); + // 释放关系对象元组的内存并关闭关系对象的表 heap_freetuple_ext(relTup); heap_close(relRel, RowExclusiveLock); return; } +/* + * 功能:验证基表与回收站对象是否匹配 + * 参数列表: + * baseDesc:基表对象的描述信息 + */ void TrBaseRelMatched(TrObjDesc *baseDesc) { - ObjectAddress obj = {RelationRelationId, baseDesc->relid}; - if (TrIsRefRbObject(&obj)) { + ObjectAddress obj = {RelationRelationId, baseDesc->relid}; // 创建ObjectAddress结构体,表示基表对象的地址信息 + if (TrIsRefRbObject(&obj)) { // 判断基表是否有关联的回收站对象 + ereport(ERROR, (errcode(ERRCODE_UNDEFINED_OBJECT), + errmsg("relation \"%s\" does not exist", baseDesc->originname))); // 报错,提示基表不存在 + } + + Relation rel = RelationIdGetRelation(baseDesc->relid); // 根据基表的OID获取基表的Relation结构体 + Assert(RelationIsValid(rel)); // 断言,确保获取的Relation结构体是有效的 + if (RelationGetCreatecsn(rel) != + (CommitSeqNo)baseDesc->createcsn) { // 比较基表的创建CSN与回收站对象的createcsn是否匹配 + ereport(ERROR, (errmsg("The recycle object \"%s\" and relation \"%s\" mismatched.", baseDesc->name, + RelationGetRelationName(rel)))); // 报错,提示回收站对象和基表不匹配 + } + + if (RelationGetChangecsn(rel) > + (CommitSeqNo)baseDesc->changecsn) { // 比较基表的修改CSN与回收站对象的changecsn是否匹配 ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_OBJECT), - errmsg("relation \"%s\" does not exist", baseDesc->originname))); + (errcode(ERRCODE_INTERNAL_ERROR), errmsg("The table definition of \"%s\" has been changed.", + RelationGetRelationName(rel)))); // 报错,提示基表定义已更改 } - Relation rel = RelationIdGetRelation(baseDesc->relid); - Assert(RelationIsValid(rel)); - if (RelationGetCreatecsn(rel) != (CommitSeqNo)baseDesc->createcsn) { - ereport(ERROR, - (errmsg("The recycle object \"%s\" and relation \"%s\" mismatched.", - baseDesc->name, RelationGetRelationName(rel)))); - } - - if (RelationGetChangecsn(rel) > (CommitSeqNo)baseDesc->changecsn) { - ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), - errmsg("The table definition of \"%s\" has been changed.", - RelationGetRelationName(rel)))); - } - - RelationClose(rel); + RelationClose(rel); // 关闭基表的Relation结构体 } +/* + * 功能:根据回收站对象的冻结XID64来调整传入的frozenXID + * 参数列表: + * dbid:数据库的OID + * frozenXID:传入的冻结XID指针,将根据回收站对象的XID来进行调整 + */ void TrAdjustFrozenXid64(Oid dbid, TransactionId *frozenXID) { Relation rbRel; SysScanDesc sd; HeapTuple rbtup; - if (!TcapFeatureAvail()) { + if (!TcapFeatureAvail()) { // 检查是否支持TCAP功能 return; } - rbRel = heap_open(RecyclebinRelationId, AccessShareLock); + rbRel = heap_open(RecyclebinRelationId, AccessShareLock); // 打开回收站关系 + + sd = systable_beginscan(rbRel, InvalidOid, false, NULL, 0, NULL); // 开始扫描回收站关系表 + while ((rbtup = systable_getnext(sd)) != NULL) { // 循环遍历回收站对象 + Form_pg_recyclebin rbForm = (Form_pg_recyclebin)GETSTRUCT(rbtup); // 获取回收站对象的元组结构 - sd = systable_beginscan(rbRel, InvalidOid, false, NULL, 0, NULL); - while ((rbtup = systable_getnext(sd)) != NULL) { - Form_pg_recyclebin rbForm = (Form_pg_recyclebin)GETSTRUCT(rbtup); TransactionId rcyfrozenxid64; if (rbForm->rcydbid != dbid || (rbForm->rcytype != RB_OBJ_TABLE && rbForm->rcytype != RB_OBJ_TOAST)) { - continue; + // 检查回收站对象是否与给定的数据库ID匹配,并且类型是表或TOAST + continue; // 若不匹配,继续下一个回收站对象 } - rcyfrozenxid64 = TrRbGetRcyfrozenxid64(rbtup, rbRel); - Assert(TransactionIdIsNormal(rcyfrozenxid64)); + rcyfrozenxid64 = TrRbGetRcyfrozenxid64(rbtup, rbRel); // 获取回收站对象的冻结XID64 + Assert(TransactionIdIsNormal(rcyfrozenxid64)); // 断言:确保获取的XID64是正常的事务ID if (TransactionIdPrecedes(rcyfrozenxid64, *frozenXID)) { - *frozenXID = rcyfrozenxid64; + // 比较回收站对象的冻结XID64和传入的冻结XID + *frozenXID = rcyfrozenxid64; // 更新传入的冻结XID为较小的XID64值 } } - systable_endscan(sd); - heap_close(rbRel, AccessShareLock); + systable_endscan(sd); // 结束扫描 + heap_close(rbRel, AccessShareLock); // 关闭回收站关系 return; } +/* + * 功能:检查指定数据库是否为空的回收站 + * 参数列表: + * dbid:数据库的OID + */ bool TrRbIsEmptyDb(Oid dbid) { Relation rbRel; @@ -1375,18 +1799,23 @@ bool TrRbIsEmptyDb(Oid dbid) HeapTuple tup; ScanKeyData skey[1]; - ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcydbid, BTEqualStrategyNumber, F_OIDEQ, - ObjectIdGetDatum(dbid)); + ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcydbid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(dbid)); - rbRel = heap_open(RecyclebinRelationId, AccessShareLock); - sd = systable_beginscan(rbRel, RecyclebinDbidRelidIndexId, true, NULL, 1, skey); - tup = systable_getnext(sd); - systable_endscan(sd); - heap_close(rbRel, AccessShareLock); + rbRel = heap_open(RecyclebinRelationId, AccessShareLock); // 打开回收站关系 - return tup == NULL; + sd = systable_beginscan(rbRel, RecyclebinDbidRelidIndexId, true, NULL, 1, skey); // 开始扫描回收站关系表 + tup = systable_getnext(sd); // 获取扫描结果的下一个元组 + systable_endscan(sd); // 结束扫描 + heap_close(rbRel, AccessShareLock); // 关闭回收站关系 + + return tup == NULL; // 若元组为空,返回true,否则返回false } +/* + * 功能:检查指定表空间是否为空的回收站 + * 参数列表: + * spcId:表空间的OID + */ bool TrRbIsEmptySpc(Oid spcId) { Relation rbRel; @@ -1394,19 +1823,24 @@ bool TrRbIsEmptySpc(Oid spcId) ScanKeyData skey[1]; HeapTuple tup; - rbRel = heap_open(RecyclebinRelationId, AccessShareLock); + rbRel = heap_open(RecyclebinRelationId, AccessShareLock); // 打开回收站关系 - ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcytablespace, BTEqualStrategyNumber, - F_OIDEQ, ObjectIdGetDatum(spcId)); + ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcytablespace, BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(spcId)); // 创建扫描键,匹配指定表空间的回收站记录 - sd = systable_beginscan(rbRel, RecyclebinDbidSpcidRcycsnIndexId, true, NULL, 1, skey); - tup = systable_getnext(sd); - systable_endscan(sd); - heap_close(rbRel, AccessShareLock); + sd = systable_beginscan(rbRel, RecyclebinDbidSpcidRcycsnIndexId, true, NULL, 1, skey); // 开始扫描回收站关系表 + tup = systable_getnext(sd); // 获取扫描结果的下一个元组 + systable_endscan(sd); // 结束扫描 + heap_close(rbRel, AccessShareLock); // 关闭回收站关系 - return tup == NULL; + return tup == NULL; // 若元组为空,返回true,否则返回false } +/* + * 功能:检查指定模式是否为空的回收站 + * 参数列表: + * nspId:模式的OID + */ bool TrRbIsEmptySchema(Oid nspId) { Relation rbRel; @@ -1414,22 +1848,27 @@ bool TrRbIsEmptySchema(Oid nspId) ScanKeyData skey[2]; HeapTuple tup; - rbRel = heap_open(RecyclebinRelationId, AccessShareLock); + rbRel = heap_open(RecyclebinRelationId, AccessShareLock); // 打开回收站关系 - ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcynamespace, BTEqualStrategyNumber, - F_OIDEQ, ObjectIdGetDatum(nspId)); - ScanKeyInit(&skey[1], Anum_pg_recyclebin_rcydbid, BTEqualStrategyNumber, - F_OIDEQ, ObjectIdGetDatum(u_sess->proc_cxt.MyDatabaseId)); + ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcynamespace, BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(nspId)); // 创建扫描键,匹配指定模式的回收站记录 + ScanKeyInit(&skey[1], Anum_pg_recyclebin_rcydbid, BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(u_sess->proc_cxt.MyDatabaseId)); // 创建扫描键,匹配当前数据库ID - sd = systable_beginscan(rbRel, RecyclebinDbidNspOrinameIndexId, true, NULL, 2, skey); - tup = systable_getnext(sd); + sd = systable_beginscan(rbRel, RecyclebinDbidNspOrinameIndexId, true, NULL, 2, skey); // 开始扫描回收站关系表 + tup = systable_getnext(sd); // 获取扫描结果的下一个元组 - systable_endscan(sd); - heap_close(rbRel, AccessShareLock); + systable_endscan(sd); // 结束扫描 + heap_close(rbRel, AccessShareLock); // 关闭回收站关系 - return tup == NULL; + return tup == NULL; // 若元组为空,返回true,否则返回false } +/* + * 功能:检查指定用户是否为空的回收站 + * 参数列表: + * roleId:用户的OID + */ bool TrRbIsEmptyUser(Oid roleId) { Relation rbRel; @@ -1437,11 +1876,11 @@ bool TrRbIsEmptyUser(Oid roleId) HeapTuple tup; bool found = false; - rbRel = heap_open(RecyclebinRelationId, AccessShareLock); + rbRel = heap_open(RecyclebinRelationId, AccessShareLock); // 打开回收站关系 - sd = systable_beginscan(rbRel, InvalidOid, false, NULL, 0, NULL); + sd = systable_beginscan(rbRel, InvalidOid, false, NULL, 0, NULL); // 开始扫描回收站关系表 - while ((tup = systable_getnext(sd)) != NULL) { + while ((tup = systable_getnext(sd)) != NULL) { // 循环获取扫描结果的下一个元组 Form_pg_recyclebin rbForm = (Form_pg_recyclebin)GETSTRUCT(tup); if ((TrObjType)rbForm->rcytype != RB_OBJ_TABLE || rbForm->rcyowner != roleId) { continue; @@ -1451,111 +1890,132 @@ bool TrRbIsEmptyUser(Oid roleId) break; } - systable_endscan(sd); - heap_close(rbRel, AccessShareLock); + systable_endscan(sd); // 结束扫描 + heap_close(rbRel, AccessShareLock); // 关闭回收站关系 - return !found; + return !found; // 若找不到与用户相关的回收站对象,返回true,否则返回false } +/* + * 功能:检查给定OID是否存在于指定的OID列表中 + * 参数列表: + * lOid:OID列表 + * oid:要检查的OID + */ static bool TrOidExists(const List *lOid, Oid oid) { ListCell *cell = NULL; - if (lOid == NULL) { - return false; + if (lOid == NULL) { // 检查OID列表是否为空 + return false; // 如果为空,返回false } - foreach (cell, lOid) { - if (oid == (*(Oid *)lfirst(cell))) { - return true; + foreach (cell, lOid) { // 遍历OID列表中的每个元素 + if (oid == (*(Oid *)lfirst(cell))) { // 检查当前元素是否与指定的OID相等 + return true; // 如果相等,返回true } } - return false; + return false; // 如果没有找到匹配的OID,返回false } +/* + * 功能:获取包含回收站中存在的数据库名称的列表 + */ List *TrGetDbListRcy(void) { Relation rbRel; SysScanDesc sd; HeapTuple tup; - List *lName = NIL; - List *lOid = NIL; - char *dbname = NULL; + List *lName = NIL; // 用于存储数据库名称的List + List *lOid = NIL; // 用于存储数据库OID的List + char *dbname = NULL; // 用于存储数据库名称的临时变量 - rbRel = heap_open(RecyclebinRelationId, AccessShareLock); + rbRel = heap_open(RecyclebinRelationId, AccessShareLock); // 打开回收站表 - sd = systable_beginscan(rbRel, InvalidOid, false, NULL, 0, NULL); + sd = systable_beginscan(rbRel, InvalidOid, false, NULL, 0, NULL); // 开始系统表扫描 - while ((tup = systable_getnext(sd)) != NULL) { + while ((tup = systable_getnext(sd)) != NULL) { // 遍历系统表扫描结果 Form_pg_recyclebin rbForm = (Form_pg_recyclebin)GETSTRUCT(tup); - if (TrOidExists(lOid, rbForm->rcydbid)) { - continue; - } - Oid *oid = (Oid *)palloc0(sizeof(Oid)); - *oid = rbForm->rcydbid; - lOid = lappend(lOid, oid); - dbname = get_database_name(rbForm->rcydbid); - if (dbname == NULL) { - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_DATABASE), - errmsg("database \"%u\" does not exist", rbForm->rcydbid))); + if (TrOidExists(lOid, rbForm->rcydbid)) { // 检查当前数据库OID是否已存在于lOid列表中 + continue; // 如果存在,继续下一次循环 } - lName = lappend(lName, dbname); + + Oid *oid = (Oid *)palloc0(sizeof(Oid)); // 为数据库OID分配内存 + *oid = rbForm->rcydbid; // 将当前数据库OID赋值给oid + lOid = lappend(lOid, oid); // 将oid添加到lOid列表中 + + dbname = get_database_name(rbForm->rcydbid); // 获取当前数据库OID对应的数据库名称 + if (dbname == NULL) { // 检查数据库名称是否为空 + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_DATABASE), errmsg("database \"%u\" does not exist", rbForm->rcydbid))); + } + lName = lappend(lName, dbname); // 将数据库名称添加到lName列表中 } - list_free_deep(lOid); + list_free_deep(lOid); // 释放lOid列表中的内存 - systable_endscan(sd); - heap_close(rbRel, AccessShareLock); + systable_endscan(sd); // 结束系统表扫描 + heap_close(rbRel, AccessShareLock); // 关闭回收站表 - return lName; + return lName; // 返回包含数据库名称的List } +/* + * 功能:获取回收站中指定表空间中存在的数据库名称的列表 + * 参数列表: + * spcId:表空间OID + */ List *TrGetDbListSpc(Oid spcId) { - List *lName = NIL; - List *lOid = NIL; - char *dbname = NULL; + List *lName = NIL; // 用于存储数据库名称的List + List *lOid = NIL; // 用于存储数据库OID的List + char *dbname = NULL; // 用于存储数据库名称的临时变量 Relation rbRel; SysScanDesc sd; ScanKeyData skey[1]; HeapTuple tup; - rbRel = heap_open(RecyclebinRelationId, AccessShareLock); + rbRel = heap_open(RecyclebinRelationId, AccessShareLock); // 打开回收站表 - ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcytablespace, BTEqualStrategyNumber, - F_OIDEQ, ObjectIdGetDatum(spcId)); + ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcytablespace, BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(spcId)); // 初始化扫描键,根据表空间OID筛选 - sd = systable_beginscan(rbRel, RecyclebinDbidSpcidRcycsnIndexId, true, NULL, 1, skey); + sd = systable_beginscan(rbRel, RecyclebinDbidSpcidRcycsnIndexId, true, NULL, 1, skey); // 开始系统表扫描 - while ((tup = systable_getnext(sd)) != NULL) { + while ((tup = systable_getnext(sd)) != NULL) { // 遍历系统表扫描结果 Form_pg_recyclebin rbForm = (Form_pg_recyclebin)GETSTRUCT(tup); - if (TrOidExists(lOid, rbForm->rcydbid)) { - continue; - } - Oid *oid = (Oid *)palloc0(sizeof(Oid)); - *oid = rbForm->rcydbid; - lOid = lappend(lOid, oid); - dbname = get_database_name(rbForm->rcydbid); - if (dbname == NULL) { - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_DATABASE), - errmsg("database \"%u\" does not exist", rbForm->rcydbid))); + if (TrOidExists(lOid, rbForm->rcydbid)) { // 检查当前数据库OID是否已存在于lOid列表中 + continue; // 如果存在,继续下一次循环 } - lName = lappend(lName, dbname); + + Oid *oid = (Oid *)palloc0(sizeof(Oid)); // 为数据库OID分配内存 + *oid = rbForm->rcydbid; // 将当前数据库OID赋值给oid + lOid = lappend(lOid, oid); // 将oid添加到lOid列表中 + + dbname = get_database_name(rbForm->rcydbid); // 获取当前数据库OID对应的数据库名称 + if (dbname == NULL) { // 检查数据库名称是否为空 + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_DATABASE), errmsg("database \"%u\" does not exist", rbForm->rcydbid))); + } + lName = lappend(lName, dbname); // 将数据库名称添加到lName列表中 } - list_free_deep(lOid); + list_free_deep(lOid); // 释放lOid列表中的内存 - systable_endscan(sd); - heap_close(rbRel, AccessShareLock); + systable_endscan(sd); // 结束系统表扫描 + heap_close(rbRel, AccessShareLock); // 关闭回收站表 - return lName; + return lName; // 返回包含数据库名称的List } +/* + * 功能:获取回收站中指定模式中存在的数据库名称的列表 + * 参数列表: + * nspId:模式OID + */ List *TrGetDbListSchema(Oid nspId) { Relation rbRel; @@ -1563,116 +2023,136 @@ List *TrGetDbListSchema(Oid nspId) ScanKeyData skey[1]; HeapTuple tup; - List *lName = NIL; - List *lOid = NIL; - char *dbname = NULL; + List *lName = NIL; // 用于存储数据库名称的List + List *lOid = NIL; // 用于存储数据库OID的List + char *dbname = NULL; // 用于存储数据库名称的临时变量 - rbRel = heap_open(RecyclebinRelationId, AccessShareLock); + rbRel = heap_open(RecyclebinRelationId, AccessShareLock); // 打开回收站表 - ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcynamespace, BTEqualStrategyNumber, - F_OIDEQ, ObjectIdGetDatum(nspId)); + ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcynamespace, BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(nspId)); // 初始化扫描键,根据模式OID筛选 - sd = systable_beginscan(rbRel, RecyclebinDbidNspOrinameIndexId, true, NULL, 1, skey); + sd = systable_beginscan(rbRel, RecyclebinDbidNspOrinameIndexId, true, NULL, 1, skey); // 开始系统表扫描 - while ((tup = systable_getnext(sd)) != NULL) { + while ((tup = systable_getnext(sd)) != NULL) { // 遍历系统表扫描结果 Form_pg_recyclebin rbForm = (Form_pg_recyclebin)GETSTRUCT(tup); - if (TrOidExists(lOid, rbForm->rcydbid)) { - continue; - } - Oid *oid = (Oid *)palloc0(sizeof(Oid)); - *oid = rbForm->rcydbid; - lOid = lappend(lOid, oid); - dbname = get_database_name(rbForm->rcydbid); - if (dbname == NULL) { - ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_DATABASE), - errmsg("database \"%u\" does not exist", rbForm->rcydbid))); + if (TrOidExists(lOid, rbForm->rcydbid)) { // 检查当前数据库OID是否已存在于lOid列表中 + continue; // 如果存在,继续下一次循环 } - lName = lappend(lName, dbname); + + Oid *oid = (Oid *)palloc0(sizeof(Oid)); // 为数据库OID分配内存 + *oid = rbForm->rcydbid; // 将当前数据库OID赋值给oid + lOid = lappend(lOid, oid); // 将oid添加到lOid列表中 + + dbname = get_database_name(rbForm->rcydbid); // 获取当前数据库OID对应的数据库名称 + if (dbname == NULL) { // 检查数据库名称是否为空 + ereport(ERROR, + (errcode(ERRCODE_UNDEFINED_DATABASE), errmsg("database \"%u\" does not exist", rbForm->rcydbid))); + } + lName = lappend(lName, dbname); // 将数据库名称添加到lName列表中 } - list_free_deep(lOid); + list_free_deep(lOid); // 释放lOid列表中的内存 - systable_endscan(sd); - heap_close(rbRel, AccessShareLock); + systable_endscan(sd); // 结束系统表扫描 + heap_close(rbRel, AccessShareLock); // 关闭回收站表 - return lName; + return lName; // 返回包含数据库名称的List } +/* + * 功能:获取回收站中指定角色拥有的数据库名称的列表 + * 参数列表: + * roleId:角色OID + */ List *TrGetDbListUser(Oid roleId) { Relation rbRel; SysScanDesc sd; HeapTuple tup; - List *lName = NIL; - List *lOid = NIL; - char *dbname = NULL; + List *lName = NIL; // 用于存储数据库名称的List + List *lOid = NIL; // 用于存储数据库OID的List + char *dbname = NULL; // 用于存储数据库名称的临时变量 - rbRel = heap_open(RecyclebinRelationId, AccessShareLock); + rbRel = heap_open(RecyclebinRelationId, AccessShareLock); // 打开回收站表 - sd = systable_beginscan(rbRel, InvalidOid, false, NULL, 0, NULL); + sd = systable_beginscan(rbRel, InvalidOid, false, NULL, 0, NULL); // 开始系统表扫描 - while ((tup = systable_getnext(sd)) != NULL) { + while ((tup = systable_getnext(sd)) != NULL) { // 遍历系统表扫描结果 Form_pg_recyclebin rbForm = (Form_pg_recyclebin)GETSTRUCT(tup); + + // 检查当前记录是否为表对象且拥有指定角色 if ((TrObjType)rbForm->rcytype != RB_OBJ_TABLE || rbForm->rcyowner != roleId) { - continue; + continue; // 如果不满足条件,继续下一次循环 } - if (TrOidExists(lOid, rbForm->rcydbid)) { - continue; - } - Oid *oid = (Oid *)palloc0(sizeof(Oid)); - *oid = rbForm->rcydbid; - lOid = lappend(lOid, oid); - dbname = get_database_name(rbForm->rcydbid); - if (dbname == NULL) { + if (TrOidExists(lOid, rbForm->rcydbid)) { // 检查当前数据库OID是否已存在于lOid列表中 + continue; // 如果存在,继续下一次循环 + } + + Oid *oid = (Oid *)palloc0(sizeof(Oid)); // 为数据库OID分配内存 + *oid = rbForm->rcydbid; // 将当前数据库OID赋值给oid + lOid = lappend(lOid, oid); // 将oid添加到lOid列表中 + + dbname = get_database_name(rbForm->rcydbid); // 获取当前数据库OID对应的数据库名称 + if (dbname == NULL) { // 检查数据库名称是否为空 ereport(ERROR, - (errcode(ERRCODE_UNDEFINED_DATABASE), - errmsg("database \"%u\" does not exist", rbForm->rcydbid))); + (errcode(ERRCODE_UNDEFINED_DATABASE), errmsg("database \"%u\" does not exist", rbForm->rcydbid))); } - - lName = lappend(lName, dbname); + lName = lappend(lName, dbname); // 将数据库名称添加到lName列表中 } - list_free_deep(lOid); + list_free_deep(lOid); // 释放lOid列表中的内存 - systable_endscan(sd); - heap_close(rbRel, AccessShareLock); + systable_endscan(sd); // 结束系统表扫描 + heap_close(rbRel, AccessShareLock); // 关闭回收站表 - return lName; + return lName; // 返回包含数据库名称的List } /* * TrGetDatabaseList * Return a list of all databases found in pg_database. */ +/* + * 功能:获取非模板数据库的名称列表 + */ List *TrGetDbListAuto(void) { - List* dblist = NIL; + List *dblist = NIL; // 用于存储数据库名称的List Relation rel; SysScanDesc sd; HeapTuple tup; - rel = heap_open(DatabaseRelationId, AccessShareLock); - sd = systable_beginscan(rel, InvalidOid, false, NULL, 0, NULL); + rel = heap_open(DatabaseRelationId, AccessShareLock); // 打开数据库关系表 - while ((tup = systable_getnext(sd)) != NULL) { + sd = systable_beginscan(rel, InvalidOid, false, NULL, 0, NULL); // 开始系统表扫描 + + while ((tup = systable_getnext(sd)) != NULL) { // 遍历系统表扫描结果 Form_pg_database pgdatabase = (Form_pg_database)GETSTRUCT(tup); - if (strcmp(NameStr(pgdatabase->datname), "template0") == 0 || + + // 检查数据库名称是否为 "template0" 或 "template1" + if (strcmp(NameStr(pgdatabase->datname), "template0") == 0 || strcmp(NameStr(pgdatabase->datname), "template1") == 0) { - continue; + continue; // 如果是模板数据库,继续下一次循环 } - dblist = lappend(dblist, pstrdup(NameStr(pgdatabase->datname))); + + dblist = lappend(dblist, pstrdup(NameStr(pgdatabase->datname))); // 将非模板数据库名称添加到dblist列表中 } - systable_endscan(sd); - heap_close(rel, AccessShareLock); + systable_endscan(sd); // 结束系统表扫描 + heap_close(rel, AccessShareLock); // 关闭数据库关系表 - return dblist; + return dblist; // 返回包含非模板数据库名称的List } +/* + * 功能:检查给定对象是否在回收站中 + * 参数列表: + * obj:要检查的对象的地址 + */ static bool TrObjInRecyclebin(const ObjectAddress *obj) { Relation rbRel; @@ -1681,17 +2161,20 @@ static bool TrObjInRecyclebin(const ObjectAddress *obj) ScanKeyData skey[2]; bool found = false; + // 检查对象是否是关系类 if (getObjectClass(obj) != OCLASS_CLASS) { return false; } + // 设置扫描键值,用于检索与数据库ID和关系ID匹配的回收站条目 ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcydbid, BTEqualStrategyNumber, F_OIDEQ, - ObjectIdGetDatum(u_sess->proc_cxt.MyDatabaseId)); - ScanKeyInit(&skey[1], Anum_pg_recyclebin_rcyrelid, BTEqualStrategyNumber, F_OIDEQ, - ObjectIdGetDatum(obj->objectId)); + ObjectIdGetDatum(u_sess->proc_cxt.MyDatabaseId)); + ScanKeyInit(&skey[1], Anum_pg_recyclebin_rcyrelid, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(obj->objectId)); - rbRel = heap_open(RecyclebinRelationId, AccessShareLock); - sd = systable_beginscan(rbRel, RecyclebinDbidRelidIndexId, true, NULL, 2, skey); + rbRel = heap_open(RecyclebinRelationId, AccessShareLock); // 打开回收站关系表 + sd = systable_beginscan(rbRel, RecyclebinDbidRelidIndexId, true, NULL, 2, skey); // 开始系统表扫描 + + // 遍历扫描结果,检查是否有与操作为删除的回收站条目匹配 while ((tup = systable_getnext(sd)) != NULL) { Form_pg_recyclebin rbForm = (Form_pg_recyclebin)GETSTRUCT(tup); if ((TrObjType)rbForm->rcyoperation == 'd') { @@ -1699,38 +2182,44 @@ static bool TrObjInRecyclebin(const ObjectAddress *obj) break; } } - systable_endscan(sd); - heap_close(rbRel, AccessShareLock); + systable_endscan(sd); // 结束系统表扫描 + heap_close(rbRel, AccessShareLock); // 关闭回收站关系表 - return found; + return found; // 返回是否找到与操作为删除的回收站条目匹配的标志 } - /* * May this object be a recyclebin object? * true: with "BIN$" prefix, or not a Relation\Type\Trigger\Constraint\Rule * false: without "BIN$" prefix, or not exists */ +/* + * 功能:检查对象是否可能是回收站对象 + * 参数列表: + * classid:对象所属的类别ID + * objid:对象的ID + * objname:对象的名称(可选) + */ static bool TrMaybeRbObject(Oid classid, Oid objid, const char *objname = NULL) { HeapTuple tup; - /* Note: we preserve rule origin name when RbDrop. */ + /* 注意:在 RbDrop 时,我们保留规则源名称。*/ if (classid != RewriteRelationId && objname) { - return strncmp(objname, "BIN$", 4) == 0; + return strncmp(objname, "BIN$", 4) == 0; // 如果对象名称以 "BIN$" 开头,返回 true } switch (classid) { case RelationRelationId: - tup = SearchSysCache1(RELOID, ObjectIdGetDatum(objid)); + tup = SearchSysCache1(RELOID, ObjectIdGetDatum(objid)); // 在系统缓存中查找关系类对象 if (tup != NULL) { - objname = NameStr(((Form_pg_class)GETSTRUCT(tup))->relname); + objname = NameStr(((Form_pg_class)GETSTRUCT(tup))->relname); // 获取关系类对象的名称 ReleaseSysCache(tup); } break; case TypeRelationId: - tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(objid)); + tup = SearchSysCache1(TYPEOID, ObjectIdGetDatum(objid)); // 在系统缓存中查找类型对象 if (tup != NULL) { - objname = NameStr(((Form_pg_type)GETSTRUCT(tup))->typname); + objname = NameStr(((Form_pg_type)GETSTRUCT(tup))->typname); // 获取类型对象的名称 ReleaseSysCache(tup); } break; @@ -1739,158 +2228,203 @@ static bool TrMaybeRbObject(Oid classid, Oid objid, const char *objname = NULL) ScanKeyData skey[1]; SysScanDesc sd; - relTrig = heap_open(TriggerRelationId, AccessShareLock); - ScanKeyInit(&skey[0], ObjectIdAttributeNumber, BTEqualStrategyNumber, - F_OIDEQ, ObjectIdGetDatum(objid)); - sd = systable_beginscan(relTrig, TriggerOidIndexId, true, NULL, 1, skey); + relTrig = heap_open(TriggerRelationId, AccessShareLock); // 打开触发器关系表 + ScanKeyInit(&skey[0], ObjectIdAttributeNumber, BTEqualStrategyNumber, F_OIDEQ, ObjectIdGetDatum(objid)); + sd = systable_beginscan(relTrig, TriggerOidIndexId, true, NULL, 1, skey); // 开始系统表扫描 if ((tup = systable_getnext(sd)) != NULL) { - objname = NameStr(((Form_pg_trigger)GETSTRUCT(tup))->tgname); + objname = NameStr(((Form_pg_trigger)GETSTRUCT(tup))->tgname); // 获取触发器对象的名称 } - systable_endscan(sd); - heap_close(relTrig, AccessShareLock); + systable_endscan(sd); // 结束系统表扫描 + heap_close(relTrig, AccessShareLock); // 关闭触发器关系表 break; } case ConstraintRelationId: - tup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(objid)); + tup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(objid)); // 在系统缓存中查找约束对象 if (tup != NULL) { - objname = NameStr(((Form_pg_constraint)GETSTRUCT(tup))->conname); + objname = NameStr(((Form_pg_constraint)GETSTRUCT(tup))->conname); // 获取约束对象的名称 ReleaseSysCache(tup); } break; case NamespaceRelationId: - /* Treate Namespace as non-recyclebin object. */ + /* 将命名空间视为非回收站对象。 */ return false; default: - /* May be a recyclebin object. */ + /* 可能是回收站对象。 */ return true; } if (objname) { - return strncmp(objname, "BIN$", 4) == 0; + return strncmp(objname, "BIN$", 4) == 0; // 如果对象名称以 "BIN$" 开头,返回 true } - return false; + return false; // 默认情况下,返回 false } +/* + * 功能:递归地检查对象及其引用对象是否在回收站中 + * 参数列表: + * depRel:对象所在关系 + * obj:待检查的对象地址 + * objSet:对象地址集合 + */ static bool TrIsRefRbObjectImpl(Relation depRel, const ObjectAddress *obj, ObjectAddresses *objSet) { int startIdx; - if (TrObjInRecyclebin(obj)) { + if (TrObjInRecyclebin(obj)) { // 检查对象是否在回收站中 return true; } - startIdx = objSet->numrefs; + startIdx = objSet->numrefs; // 记录对象地址集合中的引用对象数量 - if (!TrMaybeRbObject(obj->classId, obj->objectId)) { + if (!TrMaybeRbObject(obj->classId, obj->objectId)) { // 检查对象是否可能是回收站对象 return false; } - TrFindAllRefObjs(depRel, obj, objSet, true); - TrFindAllInternalObjs(depRel, obj, objSet, true); + TrFindAllRefObjs(depRel, obj, objSet, true); // 查找对象的所有引用对象 + TrFindAllInternalObjs(depRel, obj, objSet, true); // 查找对象的所有内部对象 - for (int i = startIdx; i < objSet->numrefs; i++) { + for (int i = startIdx; i < objSet->numrefs; i++) { // 遍历新添加的引用对象 if (TrIsRefRbObjectImpl(depRel, &objSet->refs[i], objSet)) { - return true; + return true; // 如果引用对象或其引用对象在回收站中,返回 true } } - return false; + return false; // 默认情况下,返回 false } /* object is a rb object, or reference to a rb object. */ +/* + * 功能:检查对象及其引用对象是否在回收站中(调用实现函数) + * 参数列表: + * obj:待检查的对象地址 + * depRel:对象所在关系 + */ static bool TrIsRefRbObject(const ObjectAddress *obj, Relation depRel) { - ObjectAddresses *objSet = new_object_addresses(); - bool relArgNull = depRel == NULL; + ObjectAddresses *objSet = new_object_addresses(); // 创建对象地址集合 + bool relArgNull = depRel == NULL; // 检查传入的关系对象是否为空 bool result = false; if (relArgNull) { - depRel = heap_open(DependRelationId, AccessShareLock); + depRel = heap_open(DependRelationId, AccessShareLock); // 若为空,打开 DependRelation 表 } /* Note: we not care obj->deptype here. */ - add_object_address_ext1(obj, objSet); + add_object_address_ext1(obj, objSet); // 将待检查的对象地址添加到对象地址集合中 - result = TrIsRefRbObjectImpl(depRel, obj, objSet); + result = TrIsRefRbObjectImpl(depRel, obj, objSet); // 调用实现函数检查对象及其引用对象是否在回收站中 - free_object_addresses(objSet); + free_object_addresses(objSet); // 释放对象地址集合的内存 if (relArgNull) { - heap_close(depRel, AccessShareLock); + heap_close(depRel, AccessShareLock); // 若之前打开了关系表,则关闭它 } - return result; + return result; // 返回检查结果,对象或其引用对象是否在回收站中 } +/* + * 功能:检查对象及其引用对象是否在回收站中(扩展函数) + * 参数列表: + * classid:对象的类别标识 + * objid:对象的标识 + * objname:对象的名称 + */ bool TrIsRefRbObjectEx(Oid classid, Oid objid, const char *objname) { if (!TcapFeatureAvail()) { - return false; + return false; // 如果 Tcap 功能不可用,则返回 false } /* Note: we preserve rule origin name when RbDrop. */ if (TrRbIsEmptyDb(u_sess->proc_cxt.MyDatabaseId)) { - return false; + return false; // 如果当前数据库为空,则返回 false } if (classid != RewriteRelationId && objname && strncmp(objname, "BIN$", 4) != 0) { - return false; + return false; // 如果对象不是 RewriteRelation,且对象名称不以 "BIN$" 开头,则返回 false } - ObjectAddress obj = {classid, objid}; + ObjectAddress obj = {classid, objid}; // 创建对象地址结构 - return TrIsRefRbObject(&obj); + return TrIsRefRbObject(&obj); // 调用 TrIsRefRbObject 函数检查对象及其引用对象是否在回收站中 } -void TrForbidAccessRbDependencies(Relation depRel, const ObjectAddress *depender, - const ObjectAddress *referenced, int nreferenced) +/* + * 功能:禁止访问与回收站对象相关的依赖关系 + * 参数: + * depRel:依赖关系表的关系 + * depender:依赖对象地址 + * referenced:被依赖对象地址的数组 + * nreferenced:被依赖对象地址数组的长度 + */ +void TrForbidAccessRbDependencies(Relation depRel, const ObjectAddress *depender, const ObjectAddress *referenced, + int nreferenced) { if (!TcapFeatureAvail()) { - return; + return; // 如果 Tcap 功能不可用,则直接返回 } if (IsInitdb || TrRbIsEmptyDb(u_sess->proc_cxt.MyDatabaseId)) { - return; + return; // 如果是 Initdb 模式,或者当前数据库为空,则直接返回 } if (TrIsRefRbObject(depender, depRel)) { - elog (ERROR, "can not access recycle object."); + elog(ERROR, "can not access recycle object."); // 如果依赖对象或其引用对象在回收站中,则报错,不允许访问 } for (int i = 0; i < nreferenced; i++, referenced++) { if (TrIsRefRbObject(referenced, depRel)) { - elog (ERROR, "can not access recycle object."); + elog(ERROR, "can not access recycle object."); // 如果被依赖对象或其引用对象在回收站中,则报错,不允许访问 } } return; } +/* + * 功能:禁止访问回收站对象 + * 参数列表: + * classid:对象类别的 OID + * objid:对象的 OID + * objname:对象的名称 + */ void TrForbidAccessRbObject(Oid classid, Oid objid, const char *objname) { if (!TcapFeatureAvail()) { - return; + return; // 如果 Tcap 功能不可用,则直接返回 } if (TrRbIsEmptyDb(u_sess->proc_cxt.MyDatabaseId) || !TrMaybeRbObject(classid, objid, objname)) { - return; + return; // 如果当前数据库为空,或者对象可能不是回收站对象,则直接返回 } ObjectAddress obj = {classid, objid}; if (TrIsRefRbObject(&obj)) { - elog (ERROR, "can not access recycle object."); + elog(ERROR, "can not access recycle object."); // 如果对象或其引用对象在回收站中,则报错,不允许访问 } return; } +/* + * 功能:判断是否为回收站对象的 PostgreSQL 函数 + * 参数列表: + * classid:对象类别的 OID + * objid:对象的 OID + * objname:对象的名称 + */ Datum gs_is_recycle_object(PG_FUNCTION_ARGS) { - int classid = PG_GETARG_INT32(0); - int objid = PG_GETARG_INT32(1); - Name objname = PG_GETARG_NAME(2); - bool result = false; + int classid = PG_GETARG_INT32(0); // 获取函数参数中的对象类别 OID + int objid = PG_GETARG_INT32(1); // 获取函数参数中的对象 OID + Name objname = PG_GETARG_NAME(2); // 获取函数参数中的对象名称 + bool result = false; // 用于存储判断结果,默认为 false + + // 调用 TrIsRefRbObjectEx 函数判断给定对象是否为回收站对象,并将结果存储在 result 中 result = TrIsRefRbObjectEx(classid, objid, NameStr(*objname)); + + // 返回布尔值结果 PG_RETURN_BOOL(result); } diff --git a/src/gausskernel/storage/tcap/tcap_truncate.cpp b/src/gausskernel/storage/tcap/tcap_truncate.cpp index 0303a2640..e316bd78a 100644 --- a/src/gausskernel/storage/tcap/tcap_truncate.cpp +++ b/src/gausskernel/storage/tcap/tcap_truncate.cpp @@ -96,25 +96,31 @@ #include "storage/tcap.h" #include "storage/tcap_impl.h" - +/* + * 功能:为给定的关系设置新的关系文件节点 + * 参数列表: + * relation:关系对象 + * freezeXid:事务ID + * baseDesc:基础描述对象指针 + */ void TrRelationSetNewRelfilenode(Relation relation, TransactionId freezeXid, void *baseDesc) { - TrObjDesc desc; - TrObjDesc *trBaseDesc = (TrObjDesc *)baseDesc; - RelFileNodeBackend newrnode; + TrObjDesc desc; // 定义一个TrObjDesc类型的变量desc,用于记录关系的描述信息 + TrObjDesc *trBaseDesc = (TrObjDesc *)baseDesc; // 将传入的baseDesc指针转换为TrObjDesc类型的指针 + RelFileNodeBackend newrnode; // 定义一个RelFileNodeBackend类型的变量newrnode,表示新的关系文件节点 /* Indexes, sequences must have Invalid frozenxid; other rels must not. */ Assert((((relation->rd_rel->relkind == RELKIND_INDEX) || (relation->rd_rel->relkind == RELKIND_GLOBAL_INDEX) || - (RELKIND_IS_SEQUENCE(relation->rd_rel->relkind))) ? - (freezeXid == InvalidTransactionId) : - TransactionIdIsNormal(freezeXid)) || - relation->rd_rel->relkind == RELKIND_RELATION); + (RELKIND_IS_SEQUENCE(relation->rd_rel->relkind))) + ? (freezeXid == InvalidTransactionId) + : TransactionIdIsNormal(freezeXid)) || + relation->rd_rel->relkind == RELKIND_RELATION); /* Record the old relfilenode to recyclebin. */ - desc = *trBaseDesc; + desc = *trBaseDesc; // 将baseDesc指针指向的对象复制到desc变量中 if (!TR_IS_BASE_OBJ_EX(trBaseDesc, RelationGetRelid(relation))) { TrDescInit(relation, &desc, RB_OPER_TRUNCATE, - TrGetObjType(RelationGetNamespace(relation), RelationGetRelkind(relation)), false); + TrGetObjType(RelationGetNamespace(relation), RelationGetRelkind(relation)), false); TrDescWrite(&desc); } @@ -153,31 +159,38 @@ void TrRelationSetNewRelfilenode(Relation relation, TransactionId freezeXid, voi /* ... and now we have eoxact cleanup work to do */ SetRelCacheNeedEOXActWork(true); } - +/* + * 功能:为给定的分区设置新的关系文件节点 + * 参数列表: + * parent:父关系对象 + * part:分区对象 + * freezeXid:事务ID,用于冻结分区 + * baseDesc:基础描述对象指针 + */ void TrPartitionSetNewRelfilenode(Relation parent, Partition part, TransactionId freezeXid, void *baseDesc) { - RelFileNodeBackend newrnode; - TrObjDesc desc; - TrObjDesc *trBaseDesc = (TrObjDesc *)baseDesc; + RelFileNodeBackend newrnode; // 定义一个RelFileNodeBackend类型的变量newrnode,表示新的关系文件节点 + TrObjDesc desc; // 定义一个TrObjDesc类型的变量desc,用于记录关系的描述信息 + TrObjDesc *trBaseDesc = (TrObjDesc *)baseDesc; // 将传入的baseDesc指针转换为TrObjDesc类型的指针 Assert((parent->rd_rel->relkind == RELKIND_INDEX || RELKIND_IS_SEQUENCE(parent->rd_rel->relkind)) ? freezeXid == InvalidTransactionId : TransactionIdIsNormal(freezeXid)); /* Record the old relfilenode to recyclebin. */ - desc = *trBaseDesc; + desc = *trBaseDesc; // 将baseDesc指针指向的对象复制到desc变量中 if (!TR_IS_BASE_OBJ_EX(trBaseDesc, part->pd_id)) { TrPartDescInit(parent, part, &desc, RB_OPER_TRUNCATE, - TrGetObjType(RelationGetNamespace(parent), RelationGetRelkind(parent)), false); + TrGetObjType(RelationGetNamespace(parent), RelationGetRelkind(parent)), false); TrDescWrite(&desc); } /* Allocate a new relfilenode */ - newrnode = CreateNewRelfilenodePart(parent, part); + newrnode = CreateNewRelfilenodePart(parent, part); - UpdatePartition(parent, part, freezeXid, &newrnode); + UpdatePartition(parent, part, freezeXid, &newrnode); - CommandCounterIncrement(); + CommandCounterIncrement(); // 增加命令计数器,使最近的更改可见 /* * Mark the part as having been given a new relfilenode in the current @@ -187,13 +200,17 @@ void TrPartitionSetNewRelfilenode(Relation parent, Partition part, TransactionId part->pd_newRelfilenodeSubid = GetCurrentSubTransactionId(); /* ... and now we have eoxact cleanup work to do */ - SetPartCacheNeedEOXActWork(true); + SetPartCacheNeedEOXActWork(true); // 设置标志,表示需要在事务结束时执行清理工作 } - +/* + * 功能:检查是否可以使用Recyclebin进行截断操作 + * 参数列表: + * stmt:截断语句对象 + */ bool TrCheckRecyclebinTruncate(const TruncateStmt *stmt) { - RangeVar *rel = NULL; - Oid relid; + RangeVar *rel = NULL; // 定义一个RangeVar类型的指针rel,初始化为NULL + Oid relid; // 定义一个Oid类型的变量relid,用于存储关系的对象标识符 if (/* * Disable Recyclebin-based-Truncate when with purge option, or @@ -209,34 +226,44 @@ bool TrCheckRecyclebinTruncate(const TruncateStmt *stmt) return false; } - rel = (RangeVar *)linitial(stmt->relations); - relid = RangeVarGetRelid(rel, NoLock, false); + rel = (RangeVar *)linitial(stmt->relations); // 获取截断语句中的关系 + relid = RangeVarGetRelid(rel, NoLock, false); // 获取关系的对象标识符 - return NeedTrComm(relid); + return NeedTrComm(relid); // 判断是否需要Recyclebin处理该关系的截断 } - +/* + * 功能:截断一个分区 + * 参数列表: + * rel:关系对象 + * tup:分区元组 + * insertBaseid:插入的基础ID + */ void TrTruncateOnePart(Relation rel, HeapTuple tup, Oid insertBaseid) { - Oid toastOid = ((Form_pg_partition)GETSTRUCT(tup))->reltoastrelid; - Relation toastRel = NULL; - Oid partOid = HeapTupleGetOid(tup); - Partition p = partitionOpen(rel, partOid, AccessExclusiveLock); - TrObjDesc baseDesc; - TrPartDescInit(rel, p, &baseDesc, RB_OPER_TRUNCATE, RB_OBJ_PARTITION, false); - baseDesc.id = baseDesc.baseid = insertBaseid; - (void)TrDescWrite(&baseDesc); - TrUpdateBaseid(&baseDesc); + Oid toastOid = ((Form_pg_partition)GETSTRUCT(tup))->reltoastrelid; // 获取分区元组中的toast关系对象标识符 + Relation toastRel = NULL; // 定义一个Relation类型的指针toastRel,初始化为NULL + Oid partOid = HeapTupleGetOid(tup); // 获取分区元组的对象标识符 + Partition p = partitionOpen(rel, partOid, AccessExclusiveLock); // 打开分区,获取Partition对象 + TrObjDesc baseDesc; // 定义一个TrObjDesc类型的变量baseDesc,用于记录基础描述信息 + // 初始化baseDesc描述对象,设置为分区截断操作 + TrPartDescInit(rel, p, &baseDesc, RB_OPER_TRUNCATE, RB_OBJ_PARTITION, false); + baseDesc.id = baseDesc.baseid = insertBaseid; // 设置baseDesc的ID和基础ID + (void)TrDescWrite(&baseDesc); // 将baseDesc描述信息写入存储 + TrUpdateBaseid(&baseDesc); // 更新基础ID + + // 为分区设置新的关系文件节点 TrPartitionSetNewRelfilenode(rel, p, u_sess->utils_cxt.RecentXmin, &baseDesc); /* process the toast table */ if (OidIsValid(toastOid)) { - Assert(rel->rd_rel->relpersistence != RELPERSISTENCE_UNLOGGED); - toastRel = heap_open(toastOid, AccessExclusiveLock); - TrRelationSetNewRelfilenode(toastRel, u_sess->utils_cxt.RecentXmin, &baseDesc); - heap_close(toastRel, AccessExclusiveLock); + Assert(rel->rd_rel->relpersistence != RELPERSISTENCE_UNLOGGED); // 断言验证关系的持久性 + toastRel = heap_open(toastOid, AccessExclusiveLock); // 打开toast关系 + TrRelationSetNewRelfilenode(toastRel, u_sess->utils_cxt.RecentXmin, + &baseDesc); // 为toast关系设置新的关系文件节点 + heap_close(toastRel, AccessExclusiveLock); // 关闭toast关系 } - partitionClose(rel, p, AccessExclusiveLock); + partitionClose(rel, p, AccessExclusiveLock); // 关闭分区 /* report truncate partition to PgStatCollector */ pgstat_report_truncate(partOid, rel->rd_id, rel->rd_rel->relisshared); @@ -245,8 +272,8 @@ void TrTruncateOnePart(Relation rel, HeapTuple tup, Oid insertBaseid) void TrPartitionTableProcess(Relation rel, Oid insertBaseid) { /* truncate partitioned table */ - List* partTupleList = NIL; - ListCell* partCell = NULL; + List *partTupleList = NIL; + ListCell *partCell = NULL; Oid heap_relid; bool is_shared = rel->rd_rel->relisshared; @@ -263,8 +290,8 @@ void TrPartitionTableProcess(Relation rel, Oid insertBaseid) Oid partOid = HeapTupleGetOid(tup); Partition p = partitionOpen(rel, partOid, AccessExclusiveLock); Relation partRel = partitionGetRelation(rel, p); - List* subPartTupleList = searchPgPartitionByParentId(PART_OBJ_TYPE_TABLE_SUB_PARTITION, partOid); - ListCell* subPartCell = NULL; + List *subPartTupleList = searchPgPartitionByParentId(PART_OBJ_TYPE_TABLE_SUB_PARTITION, partOid); + ListCell *subPartCell = NULL; foreach (subPartCell, subPartTupleList) { HeapTuple tup = (HeapTuple)lfirst(subPartCell); TrTruncateOnePart(partRel, tup, insertBaseid); @@ -286,7 +313,7 @@ void TrPartitionTableProcess(Relation rel, Oid insertBaseid) void TrTruncate(const TruncateStmt *stmt) { - RangeVar *rv = (RangeVar*)linitial(stmt->relations); + RangeVar *rv = (RangeVar *)linitial(stmt->relations); Relation rel; Oid relid; Oid toastRelid; @@ -302,9 +329,8 @@ void TrTruncate(const TruncateStmt *stmt) /* find matview exists or not. */ Oid mlogid = find_matview_mlog_table(relid); if (OidIsValid(mlogid)) { - ereport(ERROR, - (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), - errmsg("Not support truncate table under materialized view."))); + ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("Not support truncate table under materialized view."))); } TrForbidAccessRbObject(RelationRelationId, relid, rv->relname); @@ -323,7 +349,7 @@ void TrTruncate(const TruncateStmt *stmt) * 2. Create a new empty storage file for the relation, and assign it * as the relfilenode value, and record the old relfilenode to recyclebin. */ - + TrDescInit(rel, &baseDesc, RB_OPER_TRUNCATE, RB_OBJ_TABLE, true, true); baseDesc.id = baseDesc.baseid = TrDescWrite(&baseDesc); TrUpdateBaseid(&baseDesc); @@ -373,6 +399,11 @@ void TrTruncate(const TruncateStmt *stmt) * RelationDropStorage * Schedule unlinking of physical storage at transaction commit. */ +/* + *功能: 这个函数用于清理被截断(truncate)的对象的信息,从回收站中删除相关记录,以及在事务提交时安排取消链接旧存储。 + *参数列表: + *desc:对象描述,其中包含对象的相关信息。 + */ void TrDoPurgeObjectTruncate(TrObjDesc *desc) { Relation rbRel; @@ -380,46 +411,50 @@ void TrDoPurgeObjectTruncate(TrObjDesc *desc) ScanKeyData skey[1]; HeapTuple tup; - Assert (((desc->type == RB_OBJ_TABLE) && desc->canpurge) || ((desc->type == RB_OBJ_PARTITION) && !desc->canpurge)); + // 断言:如果是表类型的对象并且可以进行清理,或者是分区对象但不能进行清理,则满足条件 + Assert(((desc->type == RB_OBJ_TABLE) && desc->canpurge) || ((desc->type == RB_OBJ_PARTITION) && !desc->canpurge)); + // 打开回收站关系,获取排他锁 rbRel = heap_open(RecyclebinRelationId, RowExclusiveLock); - ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcybaseid, BTEqualStrategyNumber, - F_INT8EQ, Int64GetDatum(desc->baseid)); + // 初始化扫描键 + ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcybaseid, BTEqualStrategyNumber, F_INT8EQ, Int64GetDatum(desc->baseid)); + // 开始对回收站关系进行扫描 sd = systable_beginscan(rbRel, RecyclebinBaseidIndexId, true, NULL, 1, skey); while (HeapTupleIsValid(tup = systable_getnext(sd))) { Form_pg_recyclebin rbForm = (Form_pg_recyclebin)GETSTRUCT(tup); RelFileNode rnode; + // 构造表空间、数据库和关系节点信息 rnode.spcNode = ConvertToRelfilenodeTblspcOid(rbForm->rcytablespace); - rnode.dbNode = (rnode.spcNode == GLOBALTABLESPACE_OID) ? InvalidOid : - u_sess->proc_cxt.MyDatabaseId; + rnode.dbNode = (rnode.spcNode == GLOBALTABLESPACE_OID) ? InvalidOid : u_sess->proc_cxt.MyDatabaseId; rnode.relNode = rbForm->rcyrelfilenode; rnode.opt = 0; rnode.bucketNode = InvalidBktId; /* - * Schedule unlinking of the old storage at transaction commit. + * 将旧存储的取消链接安排在事务提交时进行 */ - InsertStorageIntoPendingList( - &rnode, InvalidAttrNumber, InvalidBackendId, rbForm->rcyowner, true, false); + InsertStorageIntoPendingList(&rnode, InvalidAttrNumber, InvalidBackendId, rbForm->rcyowner, true, false); + // 删除回收站中的记录 simple_heap_delete(rbRel, &tup->t_self); - ereport(LOG, (errmsg("Delete truncated object %u/%u/%u", rnode.spcNode, - rnode.dbNode, rnode.relNode))); + ereport(LOG, (errmsg("Delete truncated object %u/%u/%u", rnode.spcNode, rnode.dbNode, rnode.relNode))); } + // 结束扫描 systable_endscan(sd); + + // 关闭回收站关系 heap_close(rbRel, RowExclusiveLock); - /* ... and now we have eoxact cleanup work to do */ + // 设置关系缓存需要在事务结束时进行清理 SetRelCacheNeedEOXActWork(true); /* - * CommandCounterIncrement here to ensure that preceding changes are all - * visible to the next deletion step. + * 在这里执行 CommandCounterIncrement,以确保之前的更改对下一个删除步骤都可见。 */ CommandCounterIncrement(); } @@ -454,7 +489,7 @@ void TrRestoreTruncate(const TimeCapsuleStmt *stmt) if (found) { stmt->relation->relname = desc.name; } - + /* 1. Fetch the latest available recycle object. */ TrOperFetch(stmt->relation, RB_OBJ_TABLE, &baseDesc, RB_OPER_RESTORE_TRUNCATE); @@ -468,8 +503,7 @@ void TrRestoreTruncate(const TimeCapsuleStmt *stmt) /* 4. Do restore. */ rbRel = heap_open(RecyclebinRelationId, RowExclusiveLock); - ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcybaseid, BTEqualStrategyNumber, - F_INT8EQ, Int64GetDatum(baseDesc.id)); + ScanKeyInit(&skey[0], Anum_pg_recyclebin_rcybaseid, BTEqualStrategyNumber, F_INT8EQ, Int64GetDatum(baseDesc.id)); sd = systable_beginscan(rbRel, RecyclebinBaseidIndexId, true, NULL, 1, skey); while (HeapTupleIsValid(tup = systable_getnext(sd))) { diff --git a/src/gausskernel/storage/tcap/tcap_version.cpp b/src/gausskernel/storage/tcap/tcap_version.cpp index aad39ed3f..20eb627bc 100644 --- a/src/gausskernel/storage/tcap/tcap_version.cpp +++ b/src/gausskernel/storage/tcap/tcap_version.cpp @@ -53,74 +53,78 @@ #include "storage/tcap.h" #include "catalog/pg_constraint.h" - +//用于判断指定关系表是否包含外键约束 static bool TvIsContainsForeignKey(Oid relid) { - Relation rbRel; - SysScanDesc sd; - ScanKeyData key; - HeapTuple tup; - bool isContainsForeignKey = false; + Relation rbRel; //用于访问系统目录表的关系对象 + SysScanDesc sd; //用于扫描系统目录表的扫描描述对象 + ScanKeyData key;//扫描键对象,用于设置扫描的条件 + HeapTuple tup; //表示系统目录表中的一个元组 + bool isContainsForeignKey = false;// 表示是否包含外键约束,初始值为假 + //打开系统目录表,并将结果赋值给变量 rbRel,以便后续的操作可以使用这个关系对象来访问系统目录表中的信息 rbRel = heap_open(ConstraintRelationId, AccessShareLock); - + ScanKeyInit(&key, Anum_pg_constraint_conrelid, BTEqualStrategyNumber, - F_OIDEQ, ObjectIdGetDatum(relid)); - + F_OIDEQ, ObjectIdGetDatum(relid));//初始化扫描键,用于在系统目录表上进行扫描 + //在系统目录表上启动一个扫描操作,并将扫描描述对象赋值给变量 sd,用于管理扫描过程 sd = systable_beginscan(rbRel, ConstraintRelidIndexId, true, SnapshotNow, 1, &key); while ((tup = systable_getnext(sd)) != NULL) { - Form_pg_constraint con = (Form_pg_constraint)GETSTRUCT(tup); + Form_pg_constraint con = (Form_pg_constraint)GETSTRUCT(tup);//获取元组的数据 /* Contains a foreign key or referenced by foreign key */ if (con->contype == CONSTRAINT_FOREIGN && con->conrelid == relid) { - isContainsForeignKey = true; + isContainsForeignKey = true;//如果找到符合条件的外键约束,设置标志为真 break; } } - systable_endscan(sd); + systable_endscan(sd);//结束扫描 heap_close(rbRel, AccessShareLock); - return isContainsForeignKey; + return isContainsForeignKey;//返回是否包含外键约束的结果 } - +// 用于检查指定关系表是否被外键约束引用 static bool TvIsReferencedByForeignKey(Oid relid) { - Relation rbRel; - SysScanDesc sd; - HeapTuple tup; - bool isReferencedByForeignKey = false; - - rbRel = heap_open(ConstraintRelationId, AccessShareLock); + Relation rbRel;//用于访问系统目录表的关系对象 + SysScanDesc sd;//用于扫描系统目录表的扫描描述对象 + HeapTuple tup; //表示系统目录表中的一个元组 + bool isReferencedByForeignKey = false;//表示是否被外键约束引用,初始值为假 + rbRel = heap_open(ConstraintRelationId, AccessShareLock);//打开系统目录表 + + //在系统目录表上启动一个扫描操作,无需索引,不锁定扫描行,使用快照为当前时刻,不设置扫描条件 sd = systable_beginscan(rbRel, InvalidOid, false, SnapshotNow, 0, NULL); while ((tup = systable_getnext(sd)) != NULL) { - Form_pg_constraint con = (Form_pg_constraint)GETSTRUCT(tup); + Form_pg_constraint con = (Form_pg_constraint)GETSTRUCT(tup);//获取元组的数据 /* Not referenced by foreign key */ if (con->confrelid == relid) { - isReferencedByForeignKey = true; + isReferencedByForeignKey = true;//如果找到符合条件的引用,设置标志为真 break; } } - systable_endscan(sd); - heap_close(rbRel, AccessShareLock); + systable_endscan(sd);//结束扫描 + heap_close(rbRel, AccessShareLock);//关闭系统目录表 - return isReferencedByForeignKey; + return isReferencedByForeignKey;//返回是否被外键约束引用的结果 } - +//用于检查指定关系表是否包含外键约束或被外键约束引用 static bool TvForeignKeyCheck(Oid relid) -{ - return (TvIsContainsForeignKey(relid) || TvIsReferencedByForeignKey(relid)); +{ + //调用TvIsContainsForeignKey函数和TvIsReferencedByForeignKey函数来判断指定关系表是否包含外键约束或被外键约束引用 + //当返回true时说明此关系表包含外键约束或被外键约束引用,反之则不包含 + return (TvIsContainsForeignKey(relid) || TvIsReferencedByForeignKey(relid));/ } - +//此函数用于检查指定关系表是否支持TimeCapsule特性 static bool TvFeatureSupport(Oid relid, char **errstr, bool isTimecapsuleTable) { Relation rel = RelationIdGetRelation(relid); - Form_pg_class classForm; + Form_pg_class classForm;//存储表的元数据信息 if (!RelationIsValid(rel)) { ereport( @@ -128,9 +132,9 @@ static bool TvFeatureSupport(Oid relid, char **errstr, bool isTimecapsuleTable) errmsg("could not open relation with OID %u", relid))); } - classForm = rel->rd_rel; + classForm = rel->rd_rel;// 获取表的元数据信息 if (classForm->relkind != RELKIND_RELATION) { - *errstr = "timecapsule feature does not support non-ordinary table"; + *errstr = "timecapsule feature does not support non-ordinary table";//各种不支持TimeCapsule特性的原因 } else if (is_sys_table(RelationGetRelid(rel))) { *errstr = "timecapsule feature does not support system table"; } else if (classForm->relpersistence != RELPERSISTENCE_PERMANENT) { @@ -159,17 +163,19 @@ static bool TvFeatureSupport(Oid relid, char **errstr, bool isTimecapsuleTable) *errstr = NULL; } - RelationClose(rel); + RelationClose(rel);// 关闭表关系对象 return *errstr == NULL; } - +//用于在指定范围下检测表是否支持TimeCapsule特性 void TvCheckVersionScan(RangeTblEntry *rte) { - char *errstr = NULL; + char *errstr = NULL;// 初始化存储错误信息的字符串指针 + //调用TvFeatureSupport函数检查给定表是否支持 TimeCapsule 特性 if (!TvFeatureSupport(rte->relid, &errstr, false)) { + // 如果不支持,输出错误信息 ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), (errmsg("%s", errstr)))); @@ -178,69 +184,71 @@ void TvCheckVersionScan(RangeTblEntry *rte) return; } +//用于检查给定的扫描状态是否是版本表扫描 bool TvIsVersionScan(const ScanState *ss) { - EState *estate = ss->ps.state; - Scan *scan = (Scan *)ss->ps.plan; + EState *estate = ss->ps.state;//获取扫描状态关联的执行状态对象 + Scan *scan = (Scan *)ss->ps.plan;//获取扫描状态关联的扫描计划节点 TimeCapsuleClause *tcc = rt_fetch(scan->scanrelid, estate->es_range_table)->timecapsule; - - return tcc != NULL; + // 获取查询范围表中与扫描计划节点关联的 TimeCapsule 子句 + return tcc != NULL;//根据返回结果判断是否是版本表扫描 } /* * Whether the plan contains version table scan. */ +//用于检查给定的查询计划是否包含版本表扫描 bool TvIsVersionPlan(const PlannedStmt *stmt) { - ListCell *l = NULL; - foreach (l, stmt->rtable) { + ListCell *l = NULL;//初始化链表遍历用的指针 + foreach (l, stmt->rtable) {//遍历查询计划中的范围表条目 RangeTblEntry *rte = (RangeTblEntry *)lfirst(l); - if (rte->timecapsule != NULL) { - return true; + if (rte->timecapsule != NULL) {//如果范围表条目具有TimeCapsule子句 + return true;//返回真,说明包含版本表扫描 } } return false; } - +//用于将版本表达式规范化 Node *TvTransformVersionExpr(ParseState *pstate, TvVersionType tvtype, Node *tvver) { - Node *verExpr = tvver; + Node *verExpr = tvver;//用于存储版本表达式的变量 - verExpr = transformExpr(pstate, tvver); - if (checkExprHasSubLink(verExpr)) { + verExpr = transformExpr(pstate, tvver);//使用transformExpr函数转换版本表达式 + if (checkExprHasSubLink(verExpr)) {//检查表达式是否包含子链接 ereport(ERROR, (errcode(ERRCODE_INVALID_OPERATION), errmsg("timecapsule clause not support sublink."))); } - + //根据版本类型进行强制转换 if (tvtype == TV_VERSION_TIMESTAMP) { verExpr = coerce_to_specific_type(pstate, verExpr, TIMESTAMPTZOID, "TIMESTAMP"); } else { - verExpr = coerce_to_specific_type(pstate, verExpr, INT8OID, "CSN"); + verExpr = coerce_to_specific_type(pstate, verExpr, INT8OID, "CSN");//分配表达式的排序规则 } assign_expr_collations(pstate, verExpr); - return verExpr; + return verExpr;//返回规范化后的版本表达式 } - +//用于计算版本表达式的常量值 static Const *TvEvalVerExpr(TvVersionType tvtype, Node *tvver) { - Const *result = (Const *)tvver; + Const *result = (Const *)tvver;//用于存储版本表达式的常量结果 - if (!IsA(result, Const)) { + if (!IsA(result, Const)) {//如果版本表达式非常量 Node *verExpr; - ParseState *pstate = make_parsestate(NULL); - - verExpr = TvTransformVersionExpr(pstate, tvtype, tvver); - free_parsestate(pstate); + ParseState *pstate = make_parsestate(NULL);//创建解析状态对象 + verExpr = TvTransformVersionExpr(pstate, tvtype, tvver);//将版本表达式规范化 + free_parsestate(pstate);//释放解析状态对象 + // 使用evaluate_expr函数对转换后的表达式进行评估,获取常量值 result = (Const *)evaluate_expr((Expr *)verExpr, exprType(verExpr), exprTypmod(verExpr), exprCollation(verExpr)); } - + //检查结果是否是常量且非空 if (!IsA(result, Const) || result->constisnull) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), @@ -265,12 +273,12 @@ TransactionId TvFetchSnpxminRecycle(TimestampTz tz) Datum value; bool isnull = false; TransactionId snapxmin = FirstNormalTransactionId; - + // 打开快照表以进行访问 rel = heap_open(SnapshotRelationId, AccessShareLock); - + // 初始化扫描键,限制时间小于等于给定时间 ScanKeyInit(&skey[0], Anum_pg_snapshot_snptime, BTLessEqualStrategyNumber, F_TIMESTAMP_LE, TimestampTzGetDatum(tz)); - + // 开始扫描快照表,根据时间范围检索记录 sd = systable_beginscan(rel, SnapshotTimeCsnIndexId, true, NULL, 1, skey); tup = systable_getnext(sd); /* Limit 1 */ @@ -291,6 +299,7 @@ TransactionId TvFetchSnpxminRecycle(TimestampTz tz) * We use the round-down way to obtain snapshots. that is, * select * from gs_txn_snapshot where snptime <= :tz order by snptime desc limit 1; */ +// 获取指定时间的快照信息 static void TvFetchSnapTz(TimestampTz tz, Snapshot snap) { Relation rel; @@ -318,10 +327,10 @@ static void TvFetchSnapTz(TimestampTz tz, Snapshot snap) ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("cannot find the restore point"))); } - + //获取记录中的快照信息值 value = heap_getattr(tup, Anum_pg_snapshot_snpsnapshot, RelationGetDescr(rel), &isnull); snapstr = TextDatumGetCString(value); - + //反序列化快照信息字符串为快照对象 TxnSnapDeserialize(snapstr, snap); systable_endscan(sd); @@ -337,6 +346,7 @@ static void TvFetchSnapTz(TimestampTz tz, Snapshot snap) * We use the round-down way to obtain snapshots. that is, * select * from gs_txn_snapshot where snpcsn <= :csn order by snpcsn desc limit 1; */ +//跟CSN序列号获取快照信息 static void TvFetchSnapCsn(int64 csn, Snapshot snap) { Relation rel; @@ -380,6 +390,7 @@ static void TvFetchSnapCsn(int64 csn, Snapshot snap) * must be set to MaxTransactionId to ensure the correctness. */ if ((CommitSeqNo)csn != snap->snapshotcsn) { + //如果指定的CSN与快照的CSN不相等,则更新快照对象的一些属性 snap->snapshotcsn = (CommitSeqNo)csn; snap->timeline = 0; snap->xmin = snap->xmin; @@ -393,31 +404,38 @@ static void TvFetchSnapCsn(int64 csn, Snapshot snap) return; } - +//根据版本类型和值获取快照信息 static Snapshot TvFetchSnap(TvVersionType type, Const *value) { - Snapshot snap = (Snapshot)palloc0(sizeof(SnapshotData)); - + Snapshot snap = (Snapshot)palloc0(sizeof(SnapshotData));//为快照对象分配内存 + //根据版本类型获取快照信息 if (type == TV_VERSION_TIMESTAMP) { TvFetchSnapTz(DatumGetTimestampTz(value->constvalue), snap); } else { TvFetchSnapCsn(DatumGetInt64(value->constvalue), snap); } - snap->satisfies = SNAPSHOT_VERSION_MVCC; + snap->satisfies = SNAPSHOT_VERSION_MVCC;//将快照对象的隔离级别设置为多版本并发控制 return snap; } - +/* + * 功能:用于获取特定版本的快照对象 + * 参数: + * relation: 要获取快照的关系(表) + * tvtype: 版本类型(时间戳或 CSN) + * tvver: 用于确定快照的版本值 + */ static Snapshot TvGetSnap(Relation relation, TvVersionType tvtype, Node *tvver) { - Const *value; - Snapshot snap; + Const *value;//用于存储版本值的常量对象 + Snapshot snap;//快照对象 + //通过TvEvalVerExpr函数计算版本值 value = TvEvalVerExpr(tvtype, tvver); - + //验证快照是否适用于给定的关系 snap = TvFetchSnap(tvtype, value); - + //验证快照是否适用于给定的关系 if (!tableam_tcap_validate_snap(relation, snap)) { ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("Restore point too old"))); @@ -425,18 +443,23 @@ static Snapshot TvGetSnap(Relation relation, TvVersionType tvtype, Node *tvver) return snap; } - +/* + * 功能:用于验证关系表的定义是否与指定的快照相匹配 + * 参数: + * - relid: 要验证的关系的 OID + * - snapcsn: 快照的提交序列号 + */ static void TvValidateRelDDL(Oid relid, CommitSeqNo snapcsn) { - Relation rel = RelationIdGetRelation(relid); - if (!RelationIsValid(rel)) { + Relation rel = RelationIdGetRelation(relid);//通过id获取要验证的关系 + if (!RelationIsValid(rel)) {//检查关系是否有效 ereport( - ERROR, (errcode(ERRCODE_RELATION_OPEN_ERROR), + ERROR, (errcode(ERRCODE_RELATION_OPEN_ERROR), //无效时报错 errmsg("could not open relation with OID %u", relid))); } - + //检查关系的变更CSN是否大于等于快照的CSN if (RelationGetChangecsn(rel) >= snapcsn) { - ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), + ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), //不匹配时报错 errmsg("The table definition of \"%s\" has been changed.", RelationGetRelationName(rel)))); } @@ -450,37 +473,51 @@ static void TvValidateRelDDL(Oid relid, CommitSeqNo snapcsn) * Choose user-specified snapshot if TimeCapsule clause exists, otherwise * estate->es_snapshot instead. */ +/* + * 功能:用于选择用于扫描的快照 + * 参数: + * relation: 扫描关联的关系对象 + * scan: 扫描计划节点 + * ss: 扫描状态 + */ Snapshot TvChooseScanSnap(Relation relation, Scan *scan, ScanState *ss) { - EState *estate = ss->ps.state; - Snapshot snap = estate->es_snapshot; + EState *estate = ss->ps.state;//获取执行状态对象 + Snapshot snap = estate->es_snapshot;//获取执行状态中的快照 + //获取范围表中与扫描关联的条目 RangeTblEntry *rte = rt_fetch(scan->scanrelid, estate->es_range_table); TimeCapsuleClause *tcc = rte->timecapsule; if (likely(tcc == NULL)) { - return snap; + return snap;//返回默认的快照 } else { bool isnull = false; ExprContext *econtext; Datum val; Const *con; - + //创建表达式的上下文信息 econtext = CreateExprContext(estate); val = ExecEvalExprSwitchContext(ExecInitExpr((Expr *)tcc->tvver, &ss->ps), - econtext, &isnull, NULL); + econtext, &isnull, NULL);//计算版本值 con = makeConst((tcc->tvtype == TV_VERSION_TIMESTAMP) ? TIMESTAMPTZOID : INT8OID, - -1, InvalidOid, 8, val, isnull, true); - + -1, InvalidOid, 8, val, isnull, true);//创建对应类型的常量节点 + //获取适用于指定版本的快照 snap = TvGetSnap(relation, tcc->tvtype, (Node *)con); + //验证快照版本是否允许执行相关操作 TvValidateRelDDL(rte->relid, snap->snapshotcsn); - + //释放表达式上下文和常量节点 FreeExprContext(econtext, true); pfree(con); } return snap; } - +/* + * 功能:用于根据快照删除表中的数据 + * 参数: + * relid: 关系对象的 OID + * snap: 用于删除数据的快照 + */ void TvDeleteDelta(Oid relid, Snapshot snap) { Relation rel; @@ -490,33 +527,42 @@ void TvDeleteDelta(Oid relid, Snapshot snap) /* Notice: invoker already acquired lock */ rel = heap_open(relid, NoLock); - sd = tableam_scan_begin(rel, snap, 0, NULL); + sd = tableam_scan_begin(rel, snap, 0, NULL);//开始扫描 while ((tup = (HeapTuple)tableam_scan_getnexttuple(sd, ForwardScanDirection)) != NULL) { - simple_heap_delete(rel, &tup->t_self); + simple_heap_delete(rel, &tup->t_self);//循环删除数据直至扫描结束 } tableam_scan_end(sd); heap_close(rel, NoLock); return; } - +/* + * 功能:用于根据给定的快照,删除关系或分区关系中不再需要的数据行 + * 参数: + * rel:主关系 + * partRel:分区关系 + * p:分区对象 + * snap:快照对象 + */ void TvUheapDeleteDeltaRel(Relation rel, Relation partRel, Partition p, Snapshot snap) { + //声明变量,用于存储扫描数据、事务标识等信息 TableScanDesc sd; UHeapTuple tup; TupleTableSlot *oldslot = NULL; TransactionId tmfdXmin = InvalidTransactionId; - - Snapshot snapshotNow = (Snapshot)palloc0(sizeof(SnapshotData)); + + Snapshot snapshotNow = (Snapshot)palloc0(sizeof(SnapshotData));//分配并初始化一个Snapshot对象 (void)GetSnapshotData(snapshotNow, false); - snap->user_data = (void *)snapshotNow; + snap->user_data = (void *)snapshotNow;//将快照与snapshotNow关联 - EState *estate = CreateExecutorState(); + EState *estate = CreateExecutorState();//创建执行状态 /* * We need a ResultRelInfo so we can use the regular executor's * index-entry-making machinery. (There used to be a huge amount of code * here that basically duplicated execUtils.c ...) */ + //创建一个ResultRelInfo对象 ResultRelInfo *resultRelInfo = makeNode(ResultRelInfo); resultRelInfo->ri_RangeTableIndex = 1; /* dummy */ resultRelInfo->ri_RelationDesc = rel; @@ -525,12 +571,14 @@ void TvUheapDeleteDeltaRel(Relation rel, Relation partRel, Partition p, Snapshot estate->es_num_result_relations = 1; estate->es_result_relation_info = resultRelInfo; + //根据是否存在分区关系选择要扫描的关系 Relation relRel = (partRel != NULL) ? partRel : rel; - sd = tableam_scan_begin(relRel, snap, 0, NULL); + sd = tableam_scan_begin(relRel, snap, 0, NULL);//根据选择的扫描关系初始化表扫描描述符 + while ((tup = (UHeapTuple)tableam_scan_getnexttuple(sd, ForwardScanDirection)) != NULL) { SimpleUHeapDelete(relRel, &tup->ctid, snapshotNow, &oldslot, &tmfdXmin); ExecDeleteIndexTuples(oldslot, &tup->ctid, estate, relRel, p, NULL, false); - if (relRel != NULL && relRel->rd_mlogoid != InvalidOid) { + if (relRel != NULL && relRel->rd_mlogoid != InvalidOid) {//如果关系存在并且具有有效的mlogoid,则将删除信息插入mlog表 insert_into_mlog_table(relRel, relRel->rd_mlogoid, NULL, &tup->ctid, tmfdXmin, 'D'); } if (oldslot) { @@ -554,11 +602,18 @@ void TvUheapDeleteDeltaRel(Relation rel, Relation partRel, Partition p, Snapshot return; } - +/* + * 功能:根据给定的快照,递归删除主关系及其所有分区关系中不再需要的数据行。 + * + * 参数列表: + * rel:主关系。 + * relid:主关系的OID。 + * snap:快照对象。 + */ void TvUheapDeleteDeltaPart(Relation rel, Oid relid, Snapshot snap) { - List* partTupleList = NIL; - ListCell* partCell = NULL; + List* partTupleList = NIL;//分区元组列表 + ListCell* partCell = NULL;//分区元组遍历指针 /* Open partition table, find all partition names based on the parentId. * partitioned table unspport the unlogged table. @@ -570,59 +625,91 @@ void TvUheapDeleteDeltaPart(Relation rel, Oid relid, Snapshot snap) foreach (partCell, partTupleList) { /* the "tup" just for get partOid, UHeapTup has no HEAP_HASOID flag, so here use HeapTuple */ HeapTuple tup = (HeapTuple)lfirst(partCell); - Oid partOid = HeapTupleGetOid(tup); - Partition p = partitionOpen(rel, partOid, AccessExclusiveLock); - Relation partRel = partitionGetRelation(rel, p); + Oid partOid = HeapTupleGetOid(tup);//获取分区OID + Partition p = partitionOpen(rel, partOid, AccessExclusiveLock);//打开分区 + Relation partRel = partitionGetRelation(rel, p);//获取分区关系 - if (RelationIsSubPartitioned(rel)) { - List* subPartTupleList = searchPgPartitionByParentId(PART_OBJ_TYPE_TABLE_SUB_PARTITION, partOid); - ListCell* subPartCell = NULL; - foreach (subPartCell, subPartTupleList) { + if (RelationIsSubPartitioned(rel)) {//当主关系存在子分区执行以下操作 + List* subPartTupleList = searchPgPartitionByParentId(PART_OBJ_TYPE_TABLE_SUB_PARTITION, partOid);//查找子分区元组列表 + ListCell* subPartCell = NULL;//初始化子分区元组遍历指针 + foreach (subPartCell, subPartTupleList) {//遍历子分区 HeapTuple subTup = (HeapTuple)lfirst(subPartCell); - Oid subPartOid = HeapTupleGetOid(subTup); - Partition subPar = partitionOpen(partRel, subPartOid, AccessExclusiveLock); - Relation subPartRel = partitionGetRelation(partRel, subPar); - TvUheapDeleteDeltaRel(rel, subPartRel, subPar, snap); - releaseDummyRelation(&subPartRel); - partitionClose(partRel, subPar, NoLock); + Oid subPartOid = HeapTupleGetOid(subTup);//获取子分区OID + Partition subPar = partitionOpen(partRel, subPartOid, AccessExclusiveLock);//子分区元组遍历 + Relation subPartRel = partitionGetRelation(partRel, subPar);//获取子分区关系 + TvUheapDeleteDeltaRel(rel, subPartRel, subPar, snap);//删除子分区的不再需要的数据行 + releaseDummyRelation(&subPartRel);//释放子分区关系 + partitionClose(partRel, subPar, NoLock);//关闭子分区 } - freePartList(subPartTupleList); + freePartList(subPartTupleList);//释放子分区元组列表 } else { - TvUheapDeleteDeltaRel(rel, partRel, p, snap); + TvUheapDeleteDeltaRel(rel, partRel, p, snap);//删除分区的不再需要的数据行 } - releaseDummyRelation(&partRel); - partitionClose(rel, p, NoLock); + releaseDummyRelation(&partRel);//释放分区关系 + partitionClose(rel, p, NoLock);//关闭分区 } - freePartList(partTupleList); + freePartList(partTupleList);//释放分区元组列表 return; } - +/* + * 功能:根据给定的快照,删除主关系或其分区关系中不再需要的数据行。 + * + * 参数列表: + * relid:关系的OID。 + * snap:快照对象。 + */ void TvUheapDeleteDelta(Oid relid, Snapshot snap) { - Relation rel = heap_open(relid, NoLock); - if (RELATION_IS_PARTITIONED(rel)) { + Relation rel = heap_open(relid, NoLock);//以NoLock无锁模式访问待操作的关系 + if (RELATION_IS_PARTITIONED(rel)) {//根据关系是否为分区表进行不同的操作 + //如果是分区表,则调用TvUheapDeleteDeltaPart函数来删除每个分区的Delta数据 TvUheapDeleteDeltaPart(rel, relid, snap); } else { + //否则直接调用TvUheapDeleteDeltaRel函数来删除Delta数据 TvUheapDeleteDeltaRel(rel, NULL, NULL, snap); } heap_close(rel, NoLock); } - +/* + * 功能:用于获取数据库中的元组 + * + * 参数列表: + * arg:函数指针 + */ typedef HeapTuple (*TvFetchTupleHook)(void *arg); static HeapTuple TvFetchTuple(void *arg) { + //调用tableam_scan_getnexttuple函数来获取下一个元组 HeapTuple tup = (HeapTuple)tableam_scan_getnexttuple((TableScanDesc)arg, ForwardScanDirection); - + //如果获取到了元组调用tableam_tops_copy_tuple函数对元组进行拷贝,并返回拷贝后的结果 + //否则,返回NULL表示没有获取到元组 return tup ? (HeapTuple)tableam_tops_copy_tuple(tup) : NULL; } - +/* + * 功能:用于获取数据库中的UHeapTuple元组 + * + * 参数列表: + * arg:函数指针 + */ typedef UHeapTuple (*TvUheapFetchTupleHook)(void *arg); static UHeapTuple TvUheapFetchTuple(void *arg) { return (UHeapTuple)tableam_scan_getnexttuple((TableScanDesc)arg, ForwardScanDirection); } - +/* + * 功能:用于向关系中批量插入元组,即批量插入操作 + * 参数: + * rel:要插入元组的关系。 + * estate:执行状态信息。 + * mycid:当前命令的 CommandId。 + * hiOptions:插入选项。 + * resultRelInfo:结果关系信息。 + * myslot:元组槽,用于临时存储元组。 + * bistate:批量插入状态信息。 + * nBufferedTuples:要插入的元组数量。 + * bufferedTuples:要插入的元组数组。 + */ static void TvBatchInsert(Relation rel, EState *estate, CommandId mycid, int hiOptions, ResultRelInfo *resultRelInfo, TupleTableSlot *myslot, BulkInsertState bistate, @@ -647,9 +734,9 @@ static void TvBatchInsert(Relation rel, EState *estate, CommandId mycid, int i; for (i = 0; i < nBufferedTuples; i++) { List *recheckIndexes = NULL; - + //将当前要插入的元组存储到元组槽中 (void)ExecStoreTuple(bufferedTuples[i], myslot, InvalidBuffer, false); - + //执行索引插入操作,返回需要重新检查的索引列表 recheckIndexes = ExecInsertIndexTuples(myslot, &(bufferedTuples[i]->t_self), estate, @@ -658,7 +745,7 @@ static void TvBatchInsert(Relation rel, EState *estate, CommandId mycid, InvalidBktId, NULL, NULL); - + //释放重新检查索引列表的内存 list_free(recheckIndexes); } } @@ -668,6 +755,14 @@ static void TvBatchInsert(Relation rel, EState *estate, CommandId mycid, const int MAX_BUFFERED_TUPLES_TCAP = 1000; const int MAX_BUFFERED_TUPLES_NUM_TCAP = 65535; +/* + * 功能:用于执行插入丢失元组操作的实现函数。 + * 参数: + * rel:要插入元组的关系。 + * snap:当前事务的快照。 + * fetchTupleHook:获取元组的钩子函数。 + * arg:钩子函数的参数。 + */ static void TvInsertLostImpl(Relation rel, Snapshot snap, TvFetchTupleHook fetchTupleHook, void *arg) { HeapTuple tuple; @@ -680,8 +775,8 @@ static void TvInsertLostImpl(Relation rel, Snapshot snap, TvFetchTupleHook fetch BulkInsertState bistate; int nBufferedTuples = 0; - HeapTuple *bufferedTuples = NULL; - Size bufferedTuplesSize = 0; + HeapTuple *bufferedTuples = NULL;//缓冲的元组数组 + Size bufferedTuplesSize = 0;//缓冲的元组总大小 /* * We need a ResultRelInfo so we can use the regular executor's @@ -734,8 +829,8 @@ static void TvInsertLostImpl(Relation rel, Snapshot snap, TvFetchTupleHook fetch ExecConstraints(resultRelInfo, slot, estate); } - bufferedTuples[nBufferedTuples++] = tuple; - bufferedTuplesSize += tuple->t_len; + bufferedTuples[nBufferedTuples++] = tuple;//将元组加入缓冲数组 + bufferedTuplesSize += tuple->t_len;//增加缓冲元组总大小 /* * If the buffer filled up, flush it. Also flush if the total @@ -747,32 +842,43 @@ static void TvInsertLostImpl(Relation rel, Snapshot snap, TvFetchTupleHook fetch TvBatchInsert(rel, estate, mycid, hiOptions, resultRelInfo, myslot, bistate, nBufferedTuples, bufferedTuples); - nBufferedTuples = 0; - bufferedTuplesSize = 0; + nBufferedTuples = 0;//重置缓冲元组数量 + bufferedTuplesSize = 0;//重置缓冲元组数量 } } /* Flush any remaining buffered tuples */ if (nBufferedTuples > 0) { + //如果仍有缓冲的元组,调用TvBatchInsert函数将它们一并插入 TvBatchInsert(rel, estate, mycid, hiOptions, resultRelInfo, myslot, bistate, nBufferedTuples, bufferedTuples); } - + //释放BulkInsertState对象的资源 FreeBulkInsertState(bistate); - + //切换回旧的内存上下文 MemoryContextSwitchTo(oldcontext); - + //重置执行器的元组表,释放其中的资源 ExecResetTupleTable(estate->es_tupleTable, false); - + //关闭所有的索引 ExecCloseIndices(resultRelInfo); - + //释放执行状态对象的资源 FreeExecutorState(estate); - + //释放缓冲元组数组的内存 pfree(bufferedTuples); + //释放ResultRelInfo对象的内存 pfree(resultRelInfo); return; } - +/* + * 功能:在UHeap表中批量插入丢失的元组。 + * 参数列表: + * rel:表示要插入的关系 + * partRel:表示分区关系 + * p:分区对象 + * snap:快照对象 + * fetchTupleHook:元组获取钩子函数 + * arg:元组获取钩子函数的参数 + */ static void TvUheapInsertLostImpl(Relation rel, Relation partRel, Partition p, Snapshot snap, TvUheapFetchTupleHook fetchTupleHook, void *arg) { @@ -780,7 +886,7 @@ static void TvUheapInsertLostImpl(Relation rel, Relation partRel, Partition p, ResultRelInfo *resultRelInfo; EState *estate = CreateExecutorState(); TupleTableSlot *myslot; - CommandId mycid = GetCurrentCommandId(true); + CommandId mycid = GetCurrentCommandId(true);//获取当前命令的ID /* * We need a ResultRelInfo so we can use the regular executor's * index-entry-making machinery. (There used to be a huge amount of code @@ -789,12 +895,12 @@ static void TvUheapInsertLostImpl(Relation rel, Relation partRel, Partition p, resultRelInfo = makeNode(ResultRelInfo); resultRelInfo->ri_RangeTableIndex = 1; /* dummy */ resultRelInfo->ri_RelationDesc = rel; - ExecOpenIndices(resultRelInfo, false); + ExecOpenIndices(resultRelInfo, false);//打开索引 estate->es_result_relations = resultRelInfo; estate->es_num_result_relations = 1; estate->es_result_relation_info = resultRelInfo; - Relation relRel = (partRel != NULL) ? partRel : rel; + Relation relRel = (partRel != NULL) ? partRel : rel;//根据分区关系是否有指定选择要插入的表 /* Set up a tuple slot too */ myslot = ExecInitExtraTupleSlot(estate, TAM_USTORE); ExecSetSlotDescriptor(myslot, RelationGetDescr(relRel)); @@ -819,7 +925,9 @@ static void TvUheapInsertLostImpl(Relation rel, Relation partRel, Partition p, UHeapInsert(relRel, tuple, mycid, NULL); List *recheckIndexes = NULL; + //执行索引插入操作 recheckIndexes = ExecInsertIndexTuples(myslot, &tuple->ctid, estate, partRel, p, InvalidBktId, NULL, NULL); + //如果表有更改记录日志表(mlog),则插入相应的更改日志 if (relRel != NULL && relRel->rd_mlogoid != InvalidOid) { HeapTuple htup = NULL; Assert(relRel->rd_tam_type == TAM_USTORE); @@ -829,24 +937,29 @@ static void TvUheapInsertLostImpl(Relation rel, Relation partRel, Partition p, } list_free(recheckIndexes); } - MemoryContextSwitchTo(oldcontext); + MemoryContextSwitchTo(oldcontext);//切换回之前的内存上下文 - ExecResetTupleTable(estate->es_tupleTable, false); + ExecResetTupleTable(estate->es_tupleTable, false);//重置执行器的元组表 - ExecCloseIndices(resultRelInfo); + ExecCloseIndices(resultRelInfo);//关闭所有的索引 /* free the fakeRelationCache */ if (estate->esfRelations != NULL) { FakeRelationCacheDestroy(estate->esfRelations); } - FreeExecutorState(estate); + FreeExecutorState(estate);//释放执行状态对象的资源 pfree(resultRelInfo); return; } - +/* + * 功能:在给定的关系(表)中插入丢失的元组。 + * 参数列表: + * relid:要插入元组的关系(表)的标识符 + * snap:快照对象 + */ void TvInsertLost(Oid relid, Snapshot snap) { Relation rel; @@ -864,10 +977,18 @@ void TvInsertLost(Oid relid, Snapshot snap) heap_close(rel, NoLock); return; } - +/* + * 功能:用于在给定的关系(表)或其分区中插入丢失的元组。 + * 参数列表: + * rel:关系(表)的描述 + * partRel:分区关系(表)的描述 + * p:分区对象 + * snap:快照对象 + */ void TvUheapInsertLostRel(Relation rel, Relation partRel, Partition p, Snapshot snap) { TableScanDesc sd; + //如果partRel为NULL,则在关系(表)上开始扫描,否则在分区关系(表)上开始扫描 if (partRel == NULL) { sd = tableam_scan_begin(rel, snap, 0, NULL); } else { @@ -878,7 +999,13 @@ void TvUheapInsertLostRel(Relation rel, Relation partRel, Partition p, Snapshot tableam_scan_end(sd); return; } - +/* + * 功能:用于在给定的分区表的所有分区中插入丢失的元组。 + * 参数列表: + * rel:关系(表)的描述 + * relid:元组的关系(表)的标识符 + * snap:快照对象 + */ void TvUheapInsertLostPart(Relation rel, Oid relid, Snapshot snap) { List* partTupleList = NIL; @@ -898,20 +1025,29 @@ void TvUheapInsertLostPart(Relation rel, Oid relid, Snapshot snap) Partition p = partitionOpen(rel, partOid, AccessExclusiveLock); Relation partRel = partitionGetRelation(rel, p); - if (RelationIsSubPartitioned(rel)) { + if (RelationIsSubPartitioned(rel)) {//如果分区表存在子分区 + //获取所有子分区元组列表 List* subPartTupleList = searchPgPartitionByParentId(PART_OBJ_TYPE_TABLE_SUB_PARTITION, partOid); ListCell* subPartCell = NULL; + //遍历每个子分区元组 foreach (subPartCell, subPartTupleList) { + //获取子分区元组和其标识符 HeapTuple subTup = (HeapTuple)lfirst(subPartCell); Oid subPartOid = HeapTupleGetOid(subTup); + //打开子分区并获取子分区的描述 Partition subPar = partitionOpen(partRel, subPartOid, AccessExclusiveLock); Relation subPartRel = partitionGetRelation(partRel, subPar); + //调用TvUheapInsertLostRel函数插入丢失的元组到子分区中 TvUheapInsertLostRel(rel, subPartRel, subPar, snap); + //释放子分区的关系描述 releaseDummyRelation(&subPartRel); + //关闭子分区 partitionClose(partRel, subPar, AccessExclusiveLock); } + //释放子分区元组列表的内存 freePartList(subPartTupleList); } else { + //调用TvUheapInsertLostRel函数插入丢失的元组到当前分区中 TvUheapInsertLostRel(rel, partRel, p, snap); } releaseDummyRelation(&partRel); @@ -922,18 +1058,41 @@ void TvUheapInsertLostPart(Relation rel, Oid relid, Snapshot snap) } +/* + * 功能:用于将历史数据插入 TimeCapsule 版本表 + * + * 参数列表: + * relid:要执行还原的关系(表)OID + * snap:用于读取历史数据的快照 + */ void TvUheapInsertLost(Oid relid, Snapshot snap) { + // 打开给定 OID 对应的关系表,不加任何锁 Relation rel = heap_open(relid, NoLock); + + // 检查关系是否是分区表 if (RELATION_IS_PARTITIONED(rel)) { + // 如果是分区表,则执行 TvUheapInsertLostPart 函数,为每个分区执行历史数据插入操作 TvUheapInsertLostPart(rel, relid, snap); } else { + // 如果不是分区表,则执行 TvUheapInsertLostRel 函数,执行历史数据插入操作 + // 此处的 NULL 参数表示没有分区关系和分区,仅针对整个关系进行操作 TvUheapInsertLostRel(rel, NULL, NULL, snap); } + + // 关闭打开的关系表,不加任何锁 heap_close(rel, NoLock); + + // 函数结束,返回 return; } +/* + * 功能:检查是否允许还原特定版本,并执行还原操作 + * + * 参数列表: + * rel:要还原版本的关系(表)对象 + */ static void TvCheckVersionRestore(Relation rel) { char *errstr = NULL; @@ -956,7 +1115,12 @@ static void TvCheckVersionRestore(Relation rel) return; } - +/* + * 功能:执行版本还原操作 + * + * 参数列表: + * stmt:时间胶囊语句 + */ void TvRestoreVersion(TimeCapsuleStmt *stmt) { Relation rel; @@ -1001,4 +1165,3 @@ void TvRestoreVersion(TimeCapsuleStmt *stmt) return; } - diff --git a/src/gausskernel/storage/xlog_share_storage/xlog_share_storage.cpp b/src/gausskernel/storage/xlog_share_storage/xlog_share_storage.cpp index 0eb3b6429..df83a4ed2 100644 --- a/src/gausskernel/storage/xlog_share_storage/xlog_share_storage.cpp +++ b/src/gausskernel/storage/xlog_share_storage/xlog_share_storage.cpp @@ -37,7 +37,7 @@ #include "miscadmin.h" #include "pgstat.h" #include "replication/syncrep_gramparse.h" -#include "replication/walsender_private.h" +#include "replication/ walsender_private.h" #include "storage/ipc.h" #include "storage/dorado_operation/dorado_fd.h" #include "storage/xlog_share_storage/xlog_share_storage.h" @@ -53,33 +53,39 @@ const static uint32 CHECK_LOCK_INTERVAL = 0xFF; const static uint32 MAX_SIZE_CAN_COPY_TO_SHARE = 128 * 1024 * 1024; const static uint32 PTR_PRINT_SHIFT_SIZE = 32; - +// 这个函数是用来处理 SIGHUP 信号的。 +// 功能是在接收到 SIGHUP 信号时,设置一个标志来指示已经收到信号,并尝试唤醒进程。 +// 以便进程可以在需要时执行相应的操作。 static void SharedStorageXlogCopyBackendSigHupHandler(SIGNAL_ARGS) { - int save_errno = errno; + int save_errno = errno; // 保存当前的错误信号到 save_errno 变量。 + // 改变标志表示已经收到了 SIGHUP 信号。 t_thrd.sharestoragexlogcopyer_cxt.got_SIGHUP = true; + /* + *检查全局变量 t_thrd.proc: + *如果不为空,就调用 SetLatch 函数。 + *该函数会触发该进程的等待标志(Latch),以便在需要的时候唤醒进程执行一些操作。 + */ if (t_thrd.proc) { SetLatch(&t_thrd.proc->procLatch); } - errno = save_errno; + errno = save_errno; // 将错误信号恢复为之前保存的值。 } - +// 用来处理关闭信号,和 SharedStorageXlogCopyBackendSigHupHandler()函数类似 static void SharedStorageXlogCopyBackendShutdownHandler(SIGNAL_ARGS) { int save_errno = errno; - t_thrd.sharestoragexlogcopyer_cxt.shutdown_requested = true; - if (t_thrd.proc) SetLatch(&t_thrd.proc->procLatch); errno = save_errno; } - +// 用于快速终止进程的信号处理函数。它的主要作用是退出进程,并在退出之前执行一些清理操作。 static void SharedStorageXlogCopyBackendQuickDie(SIGNAL_ARGS) { - gs_signal_setmask(&t_thrd.libpq_cxt.BlockSig, NULL); + gs_signal_setmask(&t_thrd.libpq_cxt.BlockSig, NULL); // 用于设置信号掩码,为了防止在退出过程中受到其他信号的干扰。 /* * We DO NOT want to run proc_exit() callbacks -- we're here because @@ -89,7 +95,7 @@ static void SharedStorageXlogCopyBackendQuickDie(SIGNAL_ARGS) * things by calling exit() directly, we have to reset the callbacks * explicitly to make this work as intended. */ - on_exit_reset(); + on_exit_reset(); // 为了重置在进程退出时应该调用的回调函数,以确保不执行任何已注册的退出回调函数。 /* * Note we do exit(2) not exit(0). This is to force the postmaster into a @@ -99,14 +105,16 @@ static void SharedStorageXlogCopyBackendQuickDie(SIGNAL_ARGS) * should ensure the postmaster sees this as a crash, too, but no harm in * being doubly sure.) */ - exit(2); + exit(2); // 调用 exit,使进程以状态码 2 终止。 } - +// 用于处理 SigUsr1信号的信号处理函数。 static void SharedStorageXlogCopyBackendSigUsr1Handler(SIGNAL_ARGS) { + // 保存当前的错误信号到 save_errno 变量。 + // 目的是为了在函数执行期间不影响其他部分对错误号的使用。 int saveErrno = errno; - latch_sigusr1_handler(); + latch_sigusr1_handler(); // SetLatch使用SigUsr1唤醒在latch上等待的进程。如果我们在等待,唤醒 WaitLatch。 errno = saveErrno; } @@ -117,29 +125,32 @@ void LocalXLogRead(char *buf, XLogRecPtr startptr, Size count) XLogRecPtr recptr; Size nbytes; - p = buf; - recptr = startptr; - nbytes = count; + p = buf; // 指向缓冲区的指针 + recptr = startptr; // 当前的WAL日志指针 + nbytes = count; // 要读取的字节数 while (nbytes > 0) { uint32 startoff; int segbytes; int readbytes; - startoff = recptr % XLogSegSize; + startoff = recptr % XLogSegSize; // 计算当前WAL日志指针在XLOG段内的偏移量 /* Do we need to switch to a different xlog segment? */ - if (t_thrd.sharestoragexlogcopyer_cxt.readFile < 0 || - !XLByteInSeg(recptr, t_thrd.sharestoragexlogcopyer_cxt.readSegNo)) { + // 检查是否需要切换到不同的XLOG段 + if (t_thrd.sharestoragexlogcopyer_cxt.readFile < + 0 || // 检查名为 readFile 的文件描述符是否为负值,从而判断文件是否已经关闭或无效。 + !XLByteInSeg(recptr, t_thrd.sharestoragexlogcopyer_cxt + .readSegNo)) { // 条件判断表达式,用于检查给定的WAL日志位置是否在当前的XLOG段内。 char path[MAXPGPATH]; - + // 如果之前打开的文件描述符有效,则关闭它 if (t_thrd.sharestoragexlogcopyer_cxt.readFile >= 0) { (void)close(t_thrd.sharestoragexlogcopyer_cxt.readFile); } - + // 计算新的XLOG段文件路径 XLByteToSeg(recptr, t_thrd.sharestoragexlogcopyer_cxt.readSegNo); XLogFilePath(path, MAXPGPATH, t_thrd.xlog_cxt.ThisTimeLineID, t_thrd.sharestoragexlogcopyer_cxt.readSegNo); - + // 打开新的XLOG段文件进行读取 t_thrd.sharestoragexlogcopyer_cxt.readFile = BasicOpenFile(path, O_RDONLY | PG_BINARY, 0); if (t_thrd.sharestoragexlogcopyer_cxt.readFile < 0) { /* @@ -147,24 +158,29 @@ void LocalXLogRead(char *buf, XLogRecPtr startptr, Size count) * asked for a too old WAL segment that has already been * removed or recycled. */ + // 如果文件不存在,可能是因为要求的WAL段已被删除或回收 if (errno == ENOENT) { ereport(ERROR, (errcode_for_file_access(), errmsg("requested WAL segment %s has already been removed", XLogFileNameP(t_thrd.xlog_cxt.ThisTimeLineID, t_thrd.sharestoragexlogcopyer_cxt.readSegNo)))); } else { + // 打开文件失败,报告错误 ereport(ERROR, (errcode_for_file_access(), errmsg("could not open file \"%s\" (log segment %s): %m", path, XLogFileNameP(t_thrd.xlog_cxt.ThisTimeLineID, t_thrd.sharestoragexlogcopyer_cxt.readSegNo)))); } } - t_thrd.sharestoragexlogcopyer_cxt.readOff = 0; + t_thrd.sharestoragexlogcopyer_cxt.readOff = 0; // 重置读取偏移量 } /* Need to seek in the file? */ + // 重置读取偏移量 if (t_thrd.sharestoragexlogcopyer_cxt.readOff != startoff) { + // 定位到指定的偏移量 if (lseek(t_thrd.sharestoragexlogcopyer_cxt.readFile, (off_t)startoff, SEEK_SET) < 0) { + // 定位失败,关闭文件并报告错误 (void)close(t_thrd.sharestoragexlogcopyer_cxt.readFile); t_thrd.sharestoragexlogcopyer_cxt.readFile = -1; ereport(ERROR, @@ -173,22 +189,27 @@ void LocalXLogRead(char *buf, XLogRecPtr startptr, Size count) t_thrd.sharestoragexlogcopyer_cxt.readSegNo), startoff))); } - t_thrd.sharestoragexlogcopyer_cxt.readOff = startoff; + t_thrd.sharestoragexlogcopyer_cxt.readOff = startoff; // 更新读取偏移量 } /* How many bytes are within this segment? */ + // 计算在当前XLOG段中要读取的字节数 if (nbytes > (XLogSegSize - startoff)) { segbytes = XLogSegSize - startoff; } else { segbytes = nbytes; } - + // 报告WAL读取等待事件 pgstat_report_waitevent(WAIT_EVENT_WAL_READ); + // 从文件中读取数据 readbytes = read(t_thrd.sharestoragexlogcopyer_cxt.readFile, p, segbytes); + // 报告WAL读取完成事件 pgstat_report_waitevent(WAIT_EVENT_END); + // 检查读取是否失败 if (readbytes <= 0) { + // 读取失败,关闭文件并报告错误 (void)close(t_thrd.sharestoragexlogcopyer_cxt.readFile); - t_thrd.sharestoragexlogcopyer_cxt.readFile = -1; + t_thrd.sharestoragexlogcopyer_cxt.readFile = -1; // 标志读取失败 ereport(ERROR, (errcode_for_file_access(), errmsg("could not read from log segment %s, offset %u, length %lu: %m", @@ -201,226 +222,284 @@ void LocalXLogRead(char *buf, XLogRecPtr startptr, Size count) t_thrd.sharestoragexlogcopyer_cxt.readOff += readbytes; nbytes -= readbytes; - p += readbytes; + p += readbytes; // 更新缓冲区指针 } } - +// 用于通知等待同步的后端(如流复制的从节点)有新的日志位置需要同步。 void NotifySyncWaiters(XLogRecPtr newPos) { - volatile WalSndCtlData *walsndctl = t_thrd.walsender_cxt.WalSndCtl; + volatile WalSndCtlData *walsndctl = t_thrd.walsender_cxt.WalSndCtl; + // 获取同步复制锁以确保原子性地更新同步等待状态 (void)LWLockAcquire(SyncRepLock, LW_EXCLUSIVE); /* * Set the lsn first so that when we wake backends they will release up to * this location. + * 检查当前位置是否大于等待接收、等待写入和等待刷新的LSN, + * 如果是,则更新对应的LSN并唤醒相应的等待队列。 */ + // 如果新位置大于等待接收的LSN,则更新并唤醒等待接收的队列 if (XLByteLT(walsndctl->lsn[SYNC_REP_WAIT_RECEIVE], newPos)) { walsndctl->lsn[SYNC_REP_WAIT_RECEIVE] = newPos; (void)SyncRepWakeQueue(false, SYNC_REP_WAIT_RECEIVE); } + // 如果新位置大于等待写入的LSN,则更新并唤醒等待写入的队列 if (XLByteLT(walsndctl->lsn[SYNC_REP_WAIT_WRITE], newPos)) { walsndctl->lsn[SYNC_REP_WAIT_WRITE] = newPos; (void)SyncRepWakeQueue(false, SYNC_REP_WAIT_WRITE); } + // 如果新位置大于等待刷新的LSN,则更新并唤醒等待刷新的队列 if (XLByteLT(walsndctl->lsn[SYNC_REP_WAIT_FLUSH], newPos)) { walsndctl->lsn[SYNC_REP_WAIT_FLUSH] = newPos; (void)SyncRepWakeQueue(false, SYNC_REP_WAIT_FLUSH); } - + // 释放同步复制锁 LWLockRelease(SyncRepLock); } - +/* + * 该函数用于将新的LSN信息写入共享存储,确保相关控制信息和数据持久化, + * 并通过日志和通知机制,确保同步等待的后端能够得知新的LSN可用,从而进行相应的同步操作。 + */ void PushCtlLsn(XLogRecPtr flushPtr) { ShareStorageXLogCtl *sharestorageCtl = g_instance.xlog_cxt.shareStorageXLogCtl; + // 将flushPtr设置为共享存储XLog控制结构中的insertHead字段。 sharestorageCtl->insertHead = flushPtr; + // 如果insertHead和insertTail之间的距离大于xlogFileSize,调整insertTail的值 if ((sharestorageCtl->insertHead - sharestorageCtl->insertTail) > sharestorageCtl->xlogFileSize) { sharestorageCtl->insertTail = sharestorageCtl->insertHead - sharestorageCtl->xlogFileSize; } + // 计算共享存储XLog控制结构的CRC校验码 sharestorageCtl->crc = CalShareStorageCtlInfoCrc(sharestorageCtl); + // 更新共享存储XLog控制信息 UpdateShareStorageCtlInfo(sharestorageCtl); + // 将XLOG数据刷写到共享存储 FsyncXlogToShareStorage(); + // 根据HaModuleDebug设置日志级别 int mode = u_sess->attr.attr_storage.HaModuleDebug ? LOG : DEBUG2; + // 以指定的日志级别输出消息,显示insertHead的LSN const uint32 shftSz = 32; ereport(mode, (errmsg("[PushCtlLsn]insertHead lsn %08X/%08X\n", (uint32)(flushPtr >> shftSz), (uint32)flushPtr))); + // 通知等待同步的后端,有新的LSN可用 NotifySyncWaiters(flushPtr); } - +// 该函数用于在给定的约束下计算要写入共享存储的实际数据长度, +// 确保数据写入的位置和边界符合块大小和共享存储缓冲区大小的要求。 static inline int CalcWriteLen(XLogRecPtr startWrite, XLogRecPtr endPtr) { + // 确保startWrite的偏移量是XLOG_BLCKSZ的倍数,即起始位置为块的开始,如果断言失败,就会终止程序 Assert((startWrite % XLOG_BLCKSZ) == 0); + // 计算对齐的写入结束位置,使其与ShareStorageBufSize对齐 XLogRecPtr alignWriteEnd = startWrite - startWrite % ShareStorageBufSize + ShareStorageBufSize; + // 如果对齐后的写入结束位置大于endPtr,执行以下操作 if (alignWriteEnd > endPtr) { + // 使用TYPEALIGN计算对齐的实际写入结束位置 XLogRecPtr ActualCopyEnd = TYPEALIGN(XLOG_BLCKSZ, endPtr); + // 计算要写入的数据长度,并将其转换为int类型返回 return static_cast(ActualCopyEnd - startWrite); } else { + // 如果对齐后的写入结束位置不大于endPtr,计算要写入的数据长度,并将其转换为 int类型返回 return static_cast(alignWriteEnd - startWrite); } } - +// 该函数用于获取可以进行覆写的最大XLog位置。 XLogRecPtr GetMaxPosCanOverWrite() { + // 如果不在Postmaster进程中,直接返回最大的XLog位置 if (!IsUnderPostmaster) { return MAX_XLOG_REC_PTR; } - + // 初始化最大刷新位置为无效位置 XLogRecPtr maxFlush = InvalidXLogRecPtr; + // 遍历所有的WAL发送者,找到最大的刷新位置 for (int i = 0; i < g_instance.attr.attr_storage.max_wal_senders; i++) { /* use volatile pointer to prevent code rearrangement */ volatile WalSnd *walsnd = &t_thrd.walsender_cxt.WalSndCtl->walsnds[i]; SpinLockAcquire(&walsnd->mutex); + // 如果WAL发送者的进程ID不为0且peer_role为STANDBY_CLUSTER_MODE if (walsnd->pid != 0 && walsnd->peer_role == STANDBY_CLUSTER_MODE) { if (XLByteLT(maxFlush, walsnd->flush)) { - maxFlush = walsnd->flush; + maxFlush = walsnd->flush; // 更新最大刷新位置 } } SpinLockRelease(&walsnd->mutex); } - + // 如果没有找到有效的刷新位置,返回最大的XLog位置 if (maxFlush == InvalidXLogRecPtr) { return MAX_XLOG_REC_PTR; } - + // 返回可进行覆写的最大位置,即xlog_file_size加上最大刷新位置 return g_instance.attr.attr_storage.xlog_file_size + maxFlush; } - -void AddXLogPageHeader(char* buf, XLogRecPtr startWrite, int writeLen, XLogRecPtr endPtr) +// 该函数用于确保写入的每个数据块都有正确的XLog页面头部,以便在后续的操作中能够正确识别和处理写入的WAL数据。 +void AddXLogPageHeader(char *buf, XLogRecPtr startWrite, int writeLen, XLogRecPtr endPtr) { int offset = 0; - while (offset < writeLen) { + // 使用循环向每个写入的块添加XLog页面头部 + while (offset < writeLen) { + // 计算当前偏移处的XLog页面头部 XLogPageHeader xlogPageHeader = (XLogPageHeader)(buf + offset); + // 设置XLog页面头部的总长度为XLOG_BLCKSZ(即块的大小) xlogPageHeader->xlp_total_len = XLOG_BLCKSZ; + // 移动偏移,准备处理下一个块 offset += XLOG_BLCKSZ; } - + // 如果endPtr小于(不包括等于)startWrite + writeLen,表示写入的数据跨越了一个块的边界 if (XLByteLT(endPtr, startWrite + writeLen)) { + // 计算跨越的部分在最后一个块中的长度 XLogPageHeader xlogPageHeader = (XLogPageHeader)(buf + writeLen - XLOG_BLCKSZ); + // 设置最后一个块中XLog页面头部的总长度,以确保不超过写入的数据的边界 xlogPageHeader->xlp_total_len = endPtr % XLOG_BLCKSZ; } } - +// 该函数用于执行XLog数据的拷贝操作 void DoXlogCopy(XLogRecPtr targetPtr) { uint64 writeLength = 0; ShareStorageXLogCtl *sharestorageCtl = g_instance.xlog_cxt.shareStorageXLogCtl; + // 断言目标位置有效 Assert(targetPtr != InvalidXLogRecPtr); + // 如果目标位置小于等于当前插入头位置,无需进行XLog拷贝 if (XLByteLE(targetPtr, sharestorageCtl->insertHead)) { return; } - + // 计算起始写入位置(整块对齐) XLogRecPtr startWrite = sharestorageCtl->insertHead - (sharestorageCtl->insertHead % XLOG_BLCKSZ); + // 循环进行XLog拷贝 while (XLByteLT(startWrite, targetPtr)) { + // 计算本次写入的长度 int writeLen = CalcWriteLen(startWrite, targetPtr); + // 从本地XLog中读取数据 LocalXLogRead(t_thrd.sharestoragexlogcopyer_cxt.buf, startWrite, static_cast(writeLen)); + // 为写入数据块添加XLog页面头部 AddXLogPageHeader(t_thrd.sharestoragexlogcopyer_cxt.buf, startWrite, writeLen, targetPtr); + // 将数据块写入共享存储 (void)WriteXlogToShareStorage(startWrite, t_thrd.sharestoragexlogcopyer_cxt.buf, writeLen); + // 更新起始写入位置,并累计写入长度 startWrite += writeLen; writeLength += writeLen; + // 如果累计写入长度达到了一个XLog段的大小,或者目标位置小于起始写入位置,推进控制LSN并重置累计写入长度 if ((writeLength >= XLogSegSize) || (XLByteLT(targetPtr, startWrite))) { PushCtlLsn(XLByteLE(startWrite, targetPtr) ? startWrite : targetPtr); writeLength = 0; } } - + // 最后如果还有未推进的LSN,进行推进 if (writeLength > 0) { PushCtlLsn(targetPtr); } } - +// 获取锁 static bool GetLock() { + // 如果XLog锁文件路径为NULL,表示无需获取锁,直接返回成功 if (g_instance.attr.attr_storage.xlog_lock_file_path == NULL) { return true; } - + // 尝试获取NAS写锁,如果成功获取锁则返回成功 if (LockNasWriteFile(g_instance.xlog_cxt.shareStorageLockFd)) { return true; } + // 如果获取锁失败,报告致命错误,指明无法锁定XLog锁文件 ereport(FATAL, (errmsg("could not lock lock file(%d) %s", g_instance.xlog_cxt.shareStorageLockFd, - g_instance.attr.attr_storage.xlog_lock_file_path))); + g_instance.attr.attr_storage.xlog_lock_file_path))); return false; } - +// 释放锁 static bool ReleaseLock() { + // 如果XLog锁文件路径为NULL,表示无需释放锁,直接返回成功 if (g_instance.attr.attr_storage.xlog_lock_file_path == NULL) { return true; } - + // 尝试释放NAS写锁,如果成功释放锁则返回成功 if (UnlockNasWriteFile(g_instance.xlog_cxt.shareStorageLockFd)) { return true; } - + // 如果释放锁失败,报告致命错误,指明无法解锁XLog锁文件 ereport(FATAL, (errmsg("could not unlock lock file(%d) %s", g_instance.xlog_cxt.shareStorageLockFd, - g_instance.attr.attr_storage.xlog_lock_file_path))); + g_instance.attr.attr_storage.xlog_lock_file_path))); return false; } - +// 检查并拷贝XLog数据 bool CheckAndCopyXLog(bool forceCopy) { ShareStorageXLogCtl *sharestorageCtl = g_instance.xlog_cxt.shareStorageXLogCtl; do { XLogRecPtr localFlush = InvalidXLogRecPtr; + // 如果当前为主备模式,获取主备同步位置,否则获取本地刷写位置 if (IS_SHARED_STORAGE_MAIN_STANDBY_MODE) { localFlush = GetFlushMainStandby(); } else { localFlush = GetFlushRecPtr(); } + // 如果本地刷写位置小于等于共享存储中插入位置,表示无需拷贝 if (XLByteLE(localFlush, sharestorageCtl->insertHead)) { return true; } - + // 获取可以覆写的最大位置 XLogRecPtr maxPosCanWrite = GetMaxPosCanOverWrite(); + // 如果共享存储插入位置小于最大可覆写位置,进行XLog拷贝 if (XLByteLT(sharestorageCtl->insertHead, maxPosCanWrite)) { + // 计算期望的拷贝位置,为本地刷写位置与最大可覆写位置中较小的值 XLogRecPtr expectPos = XLByteLT(localFlush, maxPosCanWrite) ? localFlush : maxPosCanWrite; + // 计算期望的拷贝位置,为本地刷写位置与最大可覆写位置中较小的值 if ((expectPos - sharestorageCtl->insertHead) > MAX_SIZE_CAN_COPY_TO_SHARE) { expectPos = sharestorageCtl->insertHead + MAX_SIZE_CAN_COPY_TO_SHARE; } + // 执行XLog数据拷贝 DoXlogCopy(expectPos); } } while (forceCopy && GetLock()); - return false; + return false; // 返回false表示未成功执行拷贝 } - +// 该函数用于关闭共享存储的XLog拷贝操作 void ShutdownShareStorageXLogCopy() { + // 如果未配置XLog文件路径,直接返回 if (g_instance.attr.attr_storage.xlog_file_path == NULL) { return; } - + // 分配和初始化拷贝缓冲区 if (t_thrd.sharestoragexlogcopyer_cxt.originBuf == NULL) { t_thrd.sharestoragexlogcopyer_cxt.originBuf = (char *)palloc(ShareStorageBufSize + g_instance.xlog_cxt.shareStorageopCtl.blkSize); t_thrd.sharestoragexlogcopyer_cxt.buf = (char *)TYPEALIGN(g_instance.xlog_cxt.shareStorageopCtl.blkSize, t_thrd.sharestoragexlogcopyer_cxt.originBuf); } - + // 读取共享存储控制信息并执行XLog拷贝 ReadShareStorageCtlInfo(g_instance.xlog_cxt.shareStorageXLogCtl); CheckAndCopyXLog(true); + // 如果读取文件描述符有效,关闭文件 if (t_thrd.sharestoragexlogcopyer_cxt.readFile >= 0) { (void)close(t_thrd.sharestoragexlogcopyer_cxt.readFile); } - + // 释放拷贝缓冲区的内存 if (t_thrd.sharestoragexlogcopyer_cxt.originBuf != NULL) { pfree(t_thrd.sharestoragexlogcopyer_cxt.originBuf); t_thrd.sharestoragexlogcopyer_cxt.originBuf = NULL; t_thrd.sharestoragexlogcopyer_cxt.buf = NULL; } - + // 清除相关的Latch和标志 g_instance.proc_base->ShareStoragexlogCopyerLatch = NULL; + // 打印日志,表示XLog拷贝已停止 ereport(LOG, (errmsg("stopped xlog copy at %X/%X", - (uint32)(g_instance.xlog_cxt.shareStorageXLogCtl->insertHead >> PTR_PRINT_SHIFT_SIZE), - (uint32)g_instance.xlog_cxt.shareStorageXLogCtl->insertHead))); + (uint32)(g_instance.xlog_cxt.shareStorageXLogCtl->insertHead >> PTR_PRINT_SHIFT_SIZE), + (uint32)g_instance.xlog_cxt.shareStorageXLogCtl->insertHead))); } - +// 该函数用于检查文件大小是否可以更新 bool FileSizeCanUpdate() { + // 获取结束检查点的位置 XLogRecPtr endCheckpointPtr = sizeof(CheckPointPlus) + t_thrd.shemem_ptr_cxt.ControlFile->checkPoint; + // 获取共享存储控制信息 ShareStorageXLogCtl *ctlInfo = g_instance.xlog_cxt.shareStorageXLogCtl; + // 计算检查点位置和插入位置的偏移量 uint64 checkpointPos = endCheckpointPtr % ctlInfo->xlogFileSize; uint64 headPos = ctlInfo->insertHead % ctlInfo->xlogFileSize; + // 如果满足一定条件,返回true表示文件大小可以更新,否则返回false if ((XLByteLE(endCheckpointPtr, ctlInfo->insertHead) && checkpointPos <= headPos) || XLByteLT(ctlInfo->insertHead, endCheckpointPtr)) { return true; @@ -428,32 +507,37 @@ bool FileSizeCanUpdate() return false; } - +// 该函数用于检查共享存储控制信息 void CheckShareStorageCtlInfo(XLogRecPtr localEnd) { + // 如果不处于共享存储模式,直接返回 if (!IS_SHARED_STORAGE_MODE) { return; } - + // 获取共享存储的控制信息 ShareStorageXLogCtl *ctlInfo = g_instance.xlog_cxt.shareStorageXLogCtl; ReadShareStorageCtlInfo(ctlInfo); - + // 检查系统标识是否一致,不一致时报错 if (ctlInfo->systemIdentifier != GetSystemIdentifier()) { ereport(FATAL, (errmsg("database system version is different between shared storage %lu and local %lu", ctlInfo->systemIdentifier, GetSystemIdentifier()))); } - + // 初始化 uint32 shiftSize = 32; - XLogRecPtr shareStorageLatestRecordStart = InvalidXLogRecPtr; + XLogRecPtr shareStorageLatestRecordStart = InvalidXLogRecPtr; // 初始化为无效的位置 int shareStorageLatestRecordLen; pg_crc32 shareStorageLatestRecordCrc; + // 在共享存储中查找最后一个记录的信息 FindLastRecordCheckInfoOnShareStorage(&shareStorageLatestRecordStart, &shareStorageLatestRecordCrc, &shareStorageLatestRecordLen); - + // 断言共享存储最新记录的起始位置加长度小于等于插入位置,确保共享存储数据正确性 Assert(XLByteLE(shareStorageLatestRecordStart + shareStorageLatestRecordLen, ctlInfo->insertHead)); + // 如果本地的结束位置小于共享存储的插入位置 if (XLByteLT(localEnd, ctlInfo->insertHead)) { + // 如果共享存储最新记录的起始位置是无效的,或者本地的结束位置和最新记录的起始位置加对齐长度不一致 if (XLByteEQ(shareStorageLatestRecordStart, InvalidXLogRecPtr) || !XLByteEQ(localEnd, shareStorageLatestRecordStart + MAXALIGN(shareStorageLatestRecordLen))) { + // 报错,表示本地头部小于共享存储头部 ereport(FATAL, (errmsg("the local's head is smaller than The shared storage's head"), errdetail("The shared storage's head %X/%X, the local's head is %X/%X. " "lastrecord on share storage: startlsn:%X/%X crc %u, len %d", @@ -465,10 +549,12 @@ void CheckShareStorageCtlInfo(XLogRecPtr localEnd) shareStorageLatestRecordLen))); } + // 打印日志,表示修改共享存储头部 ereport(LOG, (errmsg("modify share storage head from %X/%X to %X/%X", static_cast(ctlInfo->insertHead >> shiftSize), static_cast(ctlInfo->insertHead), static_cast(localEnd >> shiftSize), static_cast(localEnd)))); + // 修改共享存储控制信息的插入位置和CRC,然后刷新到共享存储 ctlInfo->insertHead = localEnd; ctlInfo->crc = CalShareStorageCtlInfoCrc(ctlInfo); UpdateShareStorageCtlInfo(ctlInfo); @@ -476,10 +562,12 @@ void CheckShareStorageCtlInfo(XLogRecPtr localEnd) } else { char path[MAXPGPATH]; XLogSegNo sendSegNo; + // 将插入位置转换为XLog段号,并获取路径 XLByteToSeg(ctlInfo->insertHead, sendSegNo); XLogFilePath(path, MAXPGPATH, t_thrd.xlog_cxt.ThisTimeLineID, sendSegNo); struct stat stat_buf; + // 如果路径对应的文件不存在,报错,表示本地尾部大于共享存储头部 if (stat(path, &stat_buf) != 0) { ereport(FATAL, (errmsg("the local's tail is bigger than The shared storage's head %X/%X", static_cast(ctlInfo->insertHead >> shiftSize), @@ -488,7 +576,9 @@ void CheckShareStorageCtlInfo(XLogRecPtr localEnd) bool crcValid = false; + // 获取共享存储最新记录的CRC校验值,用于比较本地记录的校验值 pg_crc32 localCheckCrc = GetXlogRecordCrc(shareStorageLatestRecordStart, crcValid, XLogPageRead, 0); + // 如果校验值不匹配,报错,表示共享存储请求的LSN的 if (shareStorageLatestRecordCrc != localCheckCrc) { ereport(FATAL, (errmsg("shared storage request lsn[%X/%X]'s crc mismatched (share, local):[%u,%u].", static_cast(shareStorageLatestRecordStart >> shiftSize), @@ -497,8 +587,8 @@ void CheckShareStorageCtlInfo(XLogRecPtr localEnd) } } - uint32 localTerm = Max(g_instance.comm_cxt.localinfo_cxt.term_from_file, - g_instance.comm_cxt.localinfo_cxt.term_from_xlog); + uint32 localTerm = + Max(g_instance.comm_cxt.localinfo_cxt.term_from_file, g_instance.comm_cxt.localinfo_cxt.term_from_xlog); if (localTerm > ctlInfo->term) { ctlInfo->term = localTerm; ctlInfo->crc = CalShareStorageCtlInfoCrc(ctlInfo); @@ -506,48 +596,55 @@ void CheckShareStorageCtlInfo(XLogRecPtr localEnd) FsyncXlogToShareStorage(); } } - +// 更新共享存储控制信息 void UpdateShareStorageCtlInfo() { bool changed = false; ShareStorageXLogCtl *ctlInfo = g_instance.xlog_cxt.shareStorageXLogCtl; ReadShareStorageCtlInfo(ctlInfo); + // 检查版本号是否一致,如果不一致则更新版本号,并设置changed标志为true if (ctlInfo->version != CURRENT_SHARE_STORAGE_CTL_VERSION) { ctlInfo->version = CURRENT_SHARE_STORAGE_CTL_VERSION; changed = true; } + // 检查长度是否一致,如果不一致则更新长度,并设置changed标志为true if (ctlInfo->length != SizeOfShareStorageXLogCtl) { ctlInfo->length = SizeOfShareStorageXLogCtl; changed = true; } + // 检查xlog文件大小是否一致,如果不一致则更新xlog文件大小,并设置changed标志为true if (ctlInfo->xlogFileSize != (uint64)g_instance.attr.attr_storage.xlog_file_size) { Assert(g_instance.xlog_cxt.shareStorageopCtl.xlogFileSize == ctlInfo->xlogFileSize); + // 如果文件大小无法更新,报错 if (!FileSizeCanUpdate()) { ereport(FATAL, (errmsg("could not update share storage size."), errdetail("current size:%lu, new size:%lu", ctlInfo->xlogFileSize, g_instance.attr.attr_storage.xlog_file_size))); } + // 更新共享存储操作控制中的xlog文件大小,并将控制信息中的xlog文件大小设置为新值 g_instance.xlog_cxt.shareStorageopCtl.xlogFileSize = g_instance.attr.attr_storage.xlog_file_size; ctlInfo->xlogFileSize = g_instance.xlog_cxt.shareStorageopCtl.xlogFileSize; changed = true; } + // 如果有更新,则重新计算CRC校验值,然后更新控制信息 if (changed) { ctlInfo->crc = CalShareStorageCtlInfoCrc(ctlInfo); UpdateShareStorageCtlInfo(ctlInfo); } } - +// 共享存储XLog复制后台退出及清理函数 static void SharedStorageXlogCopyBackendQuitAndClean(int code, Datum arg) { + // 打印日志,表示解锁文件 ereport(LOG, (errmsg("SharedStorageXlogCopyBackendMain unlock file(%d) %s", g_instance.xlog_cxt.shareStorageLockFd, - g_instance.attr.attr_storage.xlog_lock_file_path))); - ReleaseLock(); + g_instance.attr.attr_storage.xlog_lock_file_path))); + ReleaseLock(); // 释放锁 } - +// 这段代码的目的是确保线程能够正确地响应各种系统和自定义的信号,以保证程序的稳定运行和正确退出。 static void InitThreadSignal() { (void)gspqsignal(SIGHUP, SharedStorageXlogCopyBackendSigHupHandler); /* reload config file */ @@ -570,31 +667,37 @@ static void InitThreadSignal() gs_signal_setmask(&t_thrd.libpq_cxt.UnBlockSig, NULL); (void)gs_signal_unblock_sigusr2(); } - +// 该函数主要用于在后台进程中执行共享存储的 XLog 复制操作 void SharedStorageXlogCopyBackendMain(void) { + // 初始化线程信号处理 InitThreadSignal(); + if (IS_SHARED_STORAGE_MAIN_STANDBY_MODE) { + // 检查共享存储的写锁是否已经获取,如果没有获取到写锁,则会尝试获取写锁。 CheckShareStorageWriteLock(); } + // 如果处于恢复状态且不是主-备模式,记录一条日志并退出 if (RecoveryInProgress() && !IS_SHARED_STORAGE_MAIN_STANDBY_MODE) { ereport(LOG, (errmsg("stopped xlog copy in recovery"))); proc_exit(0); /* done */ } - + // 注册进程退出时的清理函数 on_proc_exit(SharedStorageXlogCopyBackendQuitAndClean, 0); - + // 设置进程 latch,用于标识进程活动 g_instance.proc_base->ShareStoragexlogCopyerLatch = &t_thrd.proc->procLatch; - + // 分配用于复制 XLog 数据的缓冲区 t_thrd.sharestoragexlogcopyer_cxt.originBuf = (char *)palloc(ShareStorageBufSize + g_instance.xlog_cxt.shareStorageopCtl.blkSize); - t_thrd.sharestoragexlogcopyer_cxt.buf = (char *)TYPEALIGN(g_instance.xlog_cxt.shareStorageopCtl.blkSize, - t_thrd.sharestoragexlogcopyer_cxt.originBuf); + t_thrd.sharestoragexlogcopyer_cxt.buf = + (char *)TYPEALIGN(g_instance.xlog_cxt.shareStorageopCtl.blkSize, t_thrd.sharestoragexlogcopyer_cxt.originBuf); + // 更新共享存储控制信息 UpdateShareStorageCtlInfo(); + // 记录 XLog 复制操作的开始 ereport(LOG, (errmsg("start xlog copy at %X/%X", - (uint32)(g_instance.xlog_cxt.shareStorageXLogCtl->insertHead >> PTR_PRINT_SHIFT_SIZE), - (uint32)g_instance.xlog_cxt.shareStorageXLogCtl->insertHead))); - + (uint32)(g_instance.xlog_cxt.shareStorageXLogCtl->insertHead >> PTR_PRINT_SHIFT_SIZE), + (uint32)g_instance.xlog_cxt.shareStorageXLogCtl->insertHead))); + // 根据模式确定当前时间线 ID if (IS_SHARED_STORAGE_MAIN_STANDBY_MODE) { t_thrd.xlog_cxt.ThisTimeLineID = GetRecoveryTargetTLI(); } else { @@ -604,47 +707,54 @@ void SharedStorageXlogCopyBackendMain(void) uint64 checkLockCount = 0; pgstat_report_activity(STATE_IDLE, NULL); + // 主循环,处理 XLog 复制和后台活动 for (;;) { ResetLatch(&t_thrd.proc->procLatch); pgstat_report_activity(STATE_RUNNING, NULL); - + // 每次循环定期检查并获取锁 ++checkLockCount; if (checkLockCount & CHECK_LOCK_INTERVAL) { GetLock(); } + // 检查是否有关闭请求,如果有,执行相关清理操作并退出 if (t_thrd.sharestoragexlogcopyer_cxt.shutdown_requested) { t_thrd.sharestoragexlogcopyer_cxt.shutdown_requested = false; g_instance.proc_base->ShareStoragexlogCopyerLatch = NULL; ShutdownShareStorageXLogCopy(); - proc_exit(0); /* done */ + proc_exit(0); /*终止当前进程或线程*/ } + // 检查是否收到 SIGHUP 信号,如果是,重新加载配置文件 if (t_thrd.sharestoragexlogcopyer_cxt.got_SIGHUP) { t_thrd.sharestoragexlogcopyer_cxt.got_SIGHUP = false; ProcessConfigFile(PGC_SIGHUP); } + // 检查并复制 XLog 数据,如果需要等待则继续下一次循环 bool waitCopy = CheckAndCopyXLog(false); if (waitCopy) { continue; } + // 报告进程状态为空闲并等待活动 pgstat_report_activity(STATE_IDLE, NULL); const long sleepTime = 1000L; (void)WaitLatch(&t_thrd.proc->procLatch, WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH, sleepTime); } + // 清理操作并退出进程 g_instance.proc_base->ShareStoragexlogCopyerLatch = NULL; ereport(LOG, (errmsg("stopped xlog copy at %X/%X", - (uint32)(g_instance.xlog_cxt.shareStorageXLogCtl->insertHead >> PTR_PRINT_SHIFT_SIZE), - (uint32)g_instance.xlog_cxt.shareStorageXLogCtl->insertHead))); + (uint32)(g_instance.xlog_cxt.shareStorageXLogCtl->insertHead >> PTR_PRINT_SHIFT_SIZE), + (uint32)g_instance.xlog_cxt.shareStorageXLogCtl->insertHead))); proc_exit(0); } - +// 此函数的作用是唤醒名为 XLogCopyerBackend 的后台线程。 void WakeUpXLogCopyerBackend() { if (g_instance.proc_base->ShareStoragexlogCopyerLatch != NULL) { + // 用于将闩锁设置为激活状态,从而唤醒等待中的 XLogCopyerBackend 线程继续执行。 SetLatch(g_instance.proc_base->ShareStoragexlogCopyerLatch); } } @@ -714,8 +824,8 @@ bool XLogOverwriteFromLocal(bool force) ReadShareStorageCtlInfo(ctlInfo); if (ctlInfo->systemIdentifier != GetSystemIdentifier()) { ereport(WARNING, (errmsg("database system version is different between shared storage and local"), - errdetail("The shared storage's system version is %lu, the local's system version is %lu.", - ctlInfo->systemIdentifier, GetSystemIdentifier()))); + errdetail("The shared storage's system version is %lu, the local's system version is %lu.", + ctlInfo->systemIdentifier, GetSystemIdentifier()))); if (!force) { return false; } @@ -826,8 +936,8 @@ static bool XLogCopyWrite(char *buf, int nbytes, XLogRecPtr recptr) if (copyFile >= 0) { if (close(copyFile) != 0) { - ereport(WARNING, (errcode_for_file_access(), - errmsg("could not close log file %s: %m", XLogFileNameP(copyFileTLI, copySegNo)))); + ereport(WARNING, (errcode_for_file_access(), errmsg("could not close log file %s: %m", + XLogFileNameP(copyFileTLI, copySegNo)))); return false; } } @@ -852,7 +962,7 @@ static bool XLogCopyWrite(char *buf, int nbytes, XLogRecPtr recptr) if (copyOff != (uint32)startoff) { if (lseek(copyFile, (off_t)startoff, SEEK_SET) < 0) { ereport(WARNING, (errcode_for_file_access(), errmsg("could not seek in log file %s to offset %lu: %m", - XLogFileNameP(copyFileTLI, copySegNo), startoff))); + XLogFileNameP(copyFileTLI, copySegNo), startoff))); return false; } copyOff = startoff; @@ -868,16 +978,16 @@ static bool XLogCopyWrite(char *buf, int nbytes, XLogRecPtr recptr) errno = ENOSPC; } ereport(WARNING, (errcode_for_file_access(), - errmsg("could not write to log file %s at offset %u, length %lu: %m", - XLogFileNameP(copyFileTLI, copySegNo), copyOff, INT2ULONG(segbytes)))); + errmsg("could not write to log file %s at offset %u, length %lu: %m", + XLogFileNameP(copyFileTLI, copySegNo), copyOff, INT2ULONG(segbytes)))); return false; } if (copyOff == (uint32)0 && segbytes >= (int)sizeof(XLogPageHeaderData)) { if (((XLogPageHeader)buf)->xlp_magic == XLOG_PAGE_MAGIC && (copySegNo * XLogSegSize) != ((XLogPageHeader)buf)->xlp_pageaddr) { ereport(WARNING, (errcode_for_file_access(), - errmsg("unexpected page addr %lu of log file %s", ((XLogPageHeader)buf)->xlp_pageaddr, - XLogFileNameP(copyFileTLI, copySegNo)))); + errmsg("unexpected page addr %lu of log file %s", ((XLogPageHeader)buf)->xlp_pageaddr, + XLogFileNameP(copyFileTLI, copySegNo)))); return false; } } @@ -912,7 +1022,7 @@ static bool DoXLogCopyFromShare(XLogRecPtr copyStart) xlogreader = XLogReaderAllocate(SharedStorageXLogPageRead, 0, g_instance.xlog_cxt.shareStorageopCtl.blkSize); if (xlogreader == NULL) { ereport(WARNING, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("out of memory"), - errdetail("Failed while allocating an XLog reading processor"))); + errdetail("Failed while allocating an XLog reading processor"))); return false; } xlogreader->system_identifier = GetSystemIdentifier(); @@ -937,7 +1047,7 @@ static bool DoXLogCopyFromShare(XLogRecPtr copyStart) if (copyFile >= 0) { if (close(copyFile) != 0) { ereport(WARNING, (errcode_for_file_access(), - errmsg("could not close log file %s: %m", XLogFileNameP(copyFileTLI, copySegNo)))); + errmsg("could not close log file %s: %m", XLogFileNameP(copyFileTLI, copySegNo)))); return false; } } @@ -967,8 +1077,8 @@ bool XLogOverwriteFromShare() if (ctlInfo->systemIdentifier != GetSystemIdentifier()) { ereport(WARNING, (errmsg("database system version is different between shared storage and local"), - errdetail("The shared storage's system version is %lu, the local's system version is %lu.", - ctlInfo->systemIdentifier, GetSystemIdentifier()))); + errdetail("The shared storage's system version is %lu, the local's system version is %lu.", + ctlInfo->systemIdentifier, GetSystemIdentifier()))); return false; } /* The local log is consistent with the local data and cannot be simply overwritten from the shared storage. @@ -1062,4 +1172,3 @@ bool UnlockNasWriteFile(int fd) ereport(WARNING, (errcode_for_file_access(), errmsg("could not unlock lock file : %m"))); return false; } - diff --git a/src/include/storage/smgr/segment_internal.h b/src/include/storage/smgr/segment_internal.h index c5c263059..3d1debd6c 100644 --- a/src/include/storage/smgr/segment_internal.h +++ b/src/include/storage/smgr/segment_internal.h @@ -71,23 +71,23 @@ typedef struct SegPhysicalFile { const int DF_ARRAY_EXTEND_STEP = 4; const ssize_t DF_FILE_EXTEND_STEP_BLOCKS = RELSEG_SIZE / 8; -const ssize_t DF_FILE_EXTEND_STEP_SIZE = DF_FILE_EXTEND_STEP_BLOCKS * BLCKSZ; // 128MB +const ssize_t DF_FILE_EXTEND_STEP_SIZE = DF_FILE_EXTEND_STEP_BLOCKS * BLCKSZ; // 128MB const ssize_t DF_FILE_SLICE_BLOCKS = RELSEG_SIZE; -const ssize_t DF_FILE_SLICE_SIZE = DF_FILE_SLICE_BLOCKS * BLCKSZ; // 1GB +const ssize_t DF_FILE_SLICE_SIZE = DF_FILE_SLICE_BLOCKS * BLCKSZ; // 1GB const ssize_t DF_FILE_MIN_BLOCKS = DF_FILE_EXTEND_STEP_BLOCKS; #define DF_OFFSET_TO_SLICENO(offset) (offset / DF_FILE_SLICE_SIZE) #define DF_OFFSET_TO_SLICE_OFFSET(offset) (offset % DF_FILE_SLICE_SIZE) typedef struct SegLogicFile { - SegPhysicalFile *segfiles; - RelFileNode relNode; - ForkNumber forknum; - int vector_capacity; // current segfile array capacity - int file_num; - BlockNumber total_blocks; - char filename[MAXPGPATH]; - pthread_mutex_t filelock; + SegPhysicalFile *segfiles; // 用于跟踪与逻辑文件相关的物理文件的信息 + RelFileNode relNode; // 与该逻辑文件关联的文件节点 + ForkNumber forknum; // 与该逻辑文件关联的 fork 类型 + int vector_capacity; // current segfile array capacity + int file_num; // 当前逻辑文件包含的物理文件数量 + BlockNumber total_blocks; // 逻辑文件的总块数 + char filename[MAXPGPATH]; // 逻辑文件的文件名或路径 + pthread_mutex_t filelock; // 一个互斥锁,用于同步对逻辑文件的并发访问,以确保线程安全 } SegLogicFile; void df_ctrl_init(SegLogicFile *sf, RelFileNode relNode, ForkNumber forknum); @@ -105,9 +105,9 @@ void df_flush_data(SegLogicFile *sf, BlockNumber blocknum, BlockNumber nblocks); * Data files status in the segment space; */ enum SpaceDataFileStatus { - EMPTY, // space has not been created; there is no data file - CRASHED, // space data files are crashed, only because creation failed halfway - NORMAL // space data files are consistent and able to provide services + EMPTY, // space has not been created; there is no data file + CRASHED, // space data files are crashed, only because creation failed halfway + NORMAL // space data files are consistent and able to provide services }; /* @@ -161,7 +161,7 @@ typedef enum ExtentTotalPages { } ExtentTotalPages; typedef enum EXTENT_TYPE { - EXTENT_INVALID = 0, // 0 means invalid + EXTENT_INVALID = 0, // 0 means invalid EXTENT_1 = 1, EXTENT_8 = 2, EXTENT_128 = 3, @@ -170,8 +170,8 @@ typedef enum EXTENT_TYPE { } EXTENT_TYPE; #ifdef ENABLE_SEGMENT_TEST -#define SEGMENTTEST(TEST_CODE, msg) \ - if (SEGMETN_TEST_STUB(TEST_CODE)) { \ +#define SEGMENTTEST(TEST_CODE, msg) \ + if (SEGMETN_TEST_STUB(TEST_CODE)) { \ ereport(g_instance.segment_test_param_instance->elevel, msg); \ } #else @@ -195,16 +195,16 @@ typedef enum EXTENT_TYPE { #define DF_MAP_UNSET(bitmap, pos) ((bitmap)[(pos) >> 3] &= ~(1 << ((pos)&0x07))) typedef struct st_df_map_group { - BlockNumber first_map; // start page id of bitmap pages of this group + BlockNumber first_map; // start page id of bitmap pages of this group uint32 free_page; // first free page of this group - uint8 page_count; // count of bitmap pages of this group - uint8 reserved[DF_MAP_GROUP_RESERVED]; + uint8 page_count; // count of bitmap pages of this group + uint8 reserved[DF_MAP_GROUP_RESERVED]; } df_map_group_t; typedef struct st_df_map_head { - uint16 bit_unit; // page count that managed by one bit - uint16 group_count; // count of bitmap group that already exists - uint32 free_group; // first free group + uint16 bit_unit; // page count that managed by one bit + uint16 group_count; // count of bitmap group that already exists + uint32 free_group; // first free group uint32 reserved; uint32 allocated_extents; uint32 high_water_mark; @@ -212,12 +212,12 @@ typedef struct st_df_map_head { } df_map_head_t; typedef struct st_df_map_page { - BlockNumber first_page; // first page managed by this bitmap - uint16 free_begin; // first free bit - uint16 dirty_last; // last dirty bit - uint16 free_bits; // free bits + BlockNumber first_page; // first page managed by this bitmap + uint16 free_begin; // first free bit + uint16 dirty_last; // last dirty bit + uint16 free_bits; // free bits uint16 reserved; - uint8 bitmap[0]; // following is the bitmap + uint8 bitmap[0]; // following is the bitmap } df_map_page_t; typedef struct SpaceMapLocation { @@ -258,7 +258,7 @@ typedef struct SegExtentGroup { int extent_size; SegSpace *space; pthread_mutex_t lock; - BlockNumber map_head_entry; // default is DF_MAP_HEAD_PAGE + BlockNumber map_head_entry; // default is DF_MAP_HEAD_PAGE df_map_head_t *map_head; Buffer map_head_buffer; } SegExtentGroup; @@ -267,8 +267,8 @@ typedef struct SegmentSpaceStat { uint32 extent_size; ForkNumber forknum; uint32 total_blocks; - uint32 meta_data_blocks; // Including MapHead, MapPage. - uint32 used_data_blocks; // Including segment head, normal data extent. + uint32 meta_data_blocks; // Including MapHead, MapPage. + uint32 used_data_blocks; // Including segment head, normal data extent. float utilization; uint32 high_water_mark; } SegmentSpaceStat; @@ -281,30 +281,30 @@ typedef struct SegmentSpaceStat { * equal to the last valid type value + 1. */ enum ExtentUsageType { - NOT_USED = 0, // default - SEGMENT_HEAD, // non-bucket table main segment head - FORK_HEAD, // non-bucket table fork segment head - BUCKET_SEGMENT, // segment head for the whole bucket table - BUCKET_MAP, // bucket table map block - BUCKET_HEAD, // segment head for single bucket - DATA_EXTENT, // extent used to store table data + NOT_USED = 0, // default + SEGMENT_HEAD, // non-bucket table main segment head + FORK_HEAD, // non-bucket table fork segment head + BUCKET_SEGMENT, // segment head for the whole bucket table + BUCKET_MAP, // bucket table map block + BUCKET_HEAD, // segment head for single bucket + DATA_EXTENT, // extent used to store table data - INVALID_EXTNT_USAGE, // must always be the last value in this enumerate. + INVALID_EXTNT_USAGE, // must always be the last value in this enumerate. }; -#define SEGMENT_HEAD_MAGIC 0x44414548544e454d -#define BUCKET_SEGMENT_MAGIC 0x544e454d47455354 -#define BUCKETMAP_MAGIC 0x50414d54454b4355 -#define BMTLEVEL0_MAGIC 0x306c6576654c544d +#define SEGMENT_HEAD_MAGIC 0x44414548544e454d +#define BUCKET_SEGMENT_MAGIC 0x544e454d47455354 +#define BUCKETMAP_MAGIC 0x50414d54454b4355 +#define BMTLEVEL0_MAGIC 0x306c6576654c544d -#define IsNormalSegmentHead(head) (((SegmentHead*)(head))->magic == SEGMENT_HEAD_MAGIC) -#define IsBucketMainHead(head) (((BktMainHead*)(head))->magic == BUCKET_SEGMENT_MAGIC) -#define IsBucketMapBlock(head) (((BktHeadMapBlock*)(head))->magic == BUCKETMAP_MAGIC) -#define IsBMTLevel0Block(head) (((BMTLevel0Page*)(head))->magic == BMTLEVEL0_MAGIC) +#define IsNormalSegmentHead(head) (((SegmentHead *)(head))->magic == SEGMENT_HEAD_MAGIC) +#define IsBucketMainHead(head) (((BktMainHead *)(head))->magic == BUCKET_SEGMENT_MAGIC) +#define IsBucketMapBlock(head) (((BktHeadMapBlock *)(head))->magic == BUCKETMAP_MAGIC) +#define IsBMTLevel0Block(head) (((BMTLevel0Page *)(head))->magic == BMTLEVEL0_MAGIC) typedef struct ExtentInversePointer { uint32 flag; - BlockNumber owner; // owner oid (or block number) + BlockNumber owner; // owner oid (or block number) } ExtentInversePointer; /* @@ -320,11 +320,11 @@ typedef struct ExtentInversePointer { #define SPC_INVRSPTR_SPECIAL_DATA_MASK ((1U << 18) - 1) #define SPC_INVRSPTR_GET_SPECIAL_DATA(eip) ((eip).flag & SPC_INVRSPTR_SPECIAL_DATA_MASK) -#define InversePointerIsValid(eip) \ +#define InversePointerIsValid(eip) \ (SPC_INVRSPTR_GET_USAGE(eip) > NOT_USED && SPC_INVRSPTR_GET_USAGE(eip) < INVALID_EXTNT_USAGE) /* Assemble usage and special_data into one uint32 variable */ -#define SPC_INVRSPTR_ASSEMBLE_FLAG(usage, special_data) \ +#define SPC_INVRSPTR_ASSEMBLE_FLAG(usage, special_data) \ ((((uint32)(special_data)) & SPC_INVRSPTR_SPECIAL_DATA_MASK) + (((uint32)(usage)) << SPC_INVRSPTR_USAGE_SHIFT)) /* Inverse pointer array size in each block */ @@ -353,7 +353,7 @@ void eg_ctrl_init(SegSpace *spc, SegExtentGroup *seg, int extent_size, ForkNumbe SpaceDataFileStatus eg_status(SegExtentGroup *eg); BlockNumber eg_alloc_extent(SegExtentGroup *seg, BlockNumber preassigned_block, ExtentInversePointer iptr); -void eg_free_extent(SegExtentGroup* seg, BlockNumber blocknum); +void eg_free_extent(SegExtentGroup *seg, BlockNumber blocknum); bool eg_empty(SegExtentGroup *seg); void eg_clean_data_files(SegExtentGroup *eg); @@ -378,11 +378,11 @@ typedef struct SegSpace { SegExtentGroup extent_group[EXTENT_TYPES][SEGMENT_MAX_FORKNUM + 1]; } SegSpace; -#define SMgrOpenSpace(reln) \ - do { \ - if ((reln)->seg_space == NULL) { \ - (reln)->seg_space = spc_open((reln)->smgr_rnode.node.spcNode, (reln)->smgr_rnode.node.dbNode, false); \ - } \ +#define SMgrOpenSpace(reln) \ + do { \ + if ((reln)->seg_space == NULL) { \ + (reln)->seg_space = spc_open((reln)->smgr_rnode.node.spcNode, (reln)->smgr_rnode.node.dbNode, false); \ + } \ } while (0) extern void InitSegSpcCache(void); @@ -413,7 +413,7 @@ extern void spc_shrink_files(SegExtentGroup *seg, BlockNumber target_size, bool /* * Block Map Tree (BMT) related constants */ -static const int BMT_HEADER_LEVEL0_SLOTS = 1255; // level0 slots must bigger or equal than EXT_SIZE_1024_BOUNDARY +static const int BMT_HEADER_LEVEL0_SLOTS = 1255; // level0 slots must bigger or equal than EXT_SIZE_1024_BOUNDARY static const int BMT_HEADER_LEVEL1_SLOTS = 256; static const int BMT_HEADER_LEVEL0_TOTAL_PAGES = EXT_SIZE_1024_TOTAL_PAGES + (BMT_HEADER_LEVEL0_SLOTS - EXT_SIZE_1024_BOUNDARY) * EXT_SIZE_8192; @@ -424,11 +424,11 @@ static const uint32_t BMT_LEVEL0_SLOTS = 2000; typedef struct SegmentHead { uint64 magic; XLogRecPtr lsn; - uint32 nblocks; // block number reported to upper layer - uint32 nextents; // extent number allocated to this segment - uint32 nslots : 16; // not used yet - int bucketid : 16; // not used yet - uint32 total_blocks; // total blocks can be allocated to table (exclude metadata pages) + uint32 nblocks; // block number reported to upper layer + uint32 nextents; // extent number allocated to this segment + uint32 nslots : 16; // not used yet + int bucketid : 16; // not used yet + uint32 total_blocks; // total blocks can be allocated to table (exclude metadata pages) uint64 reserved; BlockNumber level0_slots[BMT_HEADER_LEVEL0_SLOTS]; BlockNumber level1_slots[BMT_HEADER_LEVEL1_SLOTS]; @@ -450,8 +450,7 @@ typedef struct SegPageLocation { SegPageLocation seg_get_physical_location(RelFileNode rnode, ForkNumber forknum, BlockNumber blocknum); void seg_record_new_extent_on_level0_page(SegSpace *spc, Buffer seg_head_buffer, uint32 new_extent_id, BlockNumber new_extent_first_pageno); -void seg_head_update_xlog(Buffer head_buffer, SegmentHead *seg_head, int level0_slot, - int level1_slot); +void seg_head_update_xlog(Buffer head_buffer, SegmentHead *seg_head, int level0_slot, int level1_slot); BlockNumber seg_extent_location(SegSpace *spc, SegmentHead *seg_head, int extent_id); inline static ExtentSize ExtentSizeByCount(uint32 count) @@ -494,11 +493,11 @@ inline static BlockNumber ExtentIdToLogicBlockNum(uint32 extent_id) /* Segment information in SMgrRelationData */ typedef struct SegmentDesc { - BlockNumber head_blocknum; // Segment Head block number + BlockNumber head_blocknum; // Segment Head block number uint32 timeline; } SegmentDesc; -#define IsNormalForknum(forknum) \ +#define IsNormalForknum(forknum) \ ((forknum) == MAIN_FORKNUM || (forknum) == FSM_FORKNUM || (forknum) == VISIBILITYMAP_FORKNUM) #define ASSERT_NORMAL_FORK(forknum) Assert(IsNormalForknum(forknum)) @@ -538,7 +537,7 @@ inline static bool RequireNewLevel0Page(uint32 new_extent_id) * Luckily, spc_shrink is an extra-low frequent operation. It should not influence the performance. */ #define SegmentHeadPartition(hashcode) ((hashcode) % NUM_SEGMENT_HEAD_PARTITIONS) -#define SegmentHeadPartitionLock(hashcode) \ +#define SegmentHeadPartitionLock(hashcode) \ (&t_thrd.shemem_ptr_cxt.mainLWLockArray[FirstSegmentHeadLock + SegmentHeadPartition(hashcode)].lock) #define SegmentHeadPartitionLockByIndex(i) (&t_thrd.shemem_ptr_cxt.mainLWLockArray[FirstSegmentHeadLock + (i)].lock) @@ -570,21 +569,20 @@ typedef struct BktHeadMapBlock { typedef struct SegRedisInfo { TransactionId redis_xid; - uint32 nwords; - uint32 reserved; - uint32 words[BktBitMaxMapCnt]; + uint32 nwords; + uint32 reserved; + uint32 words[BktBitMaxMapCnt]; } SegRedisInfo; - -#define WORDOFF(x) ((x) / (sizeof(uint32) * 8)) -#define BITOFF(x) ((x) % (sizeof(uint32) * 8)) -#define SET_BKT_MAP_BIT(map, x) (map[WORDOFF(x)] |= ((uint32) 1 << (uint32)BITOFF(x))) -#define GET_BKT_MAP_BIT(map, x) (map[WORDOFF(x)] & ((uint32) 1 << (uint32)BITOFF(x))) +#define WORDOFF(x) ((x) / (sizeof(uint32) * 8)) +#define BITOFF(x) ((x) % (sizeof(uint32) * 8)) +#define SET_BKT_MAP_BIT(map, x) (map[WORDOFF(x)] |= ((uint32)1 << (uint32)BITOFF(x))) +#define GET_BKT_MAP_BIT(map, x) (map[WORDOFF(x)] & ((uint32)1 << (uint32)BITOFF(x))) typedef struct BktMainHead { uint64 magic; XLogRecPtr lsn; - SegRedisInfo redis_info; - uint64 reserved; + SegRedisInfo redis_info; + uint64 reserved; BlockNumber bkt_map[BktMapBlockNumber]; } BktMainHead; @@ -599,7 +597,6 @@ BlockNumber seg_alloc_segment(Oid tablespace_id, Oid database_id, bool isbucket, /* Make sure the segment has enough space for blkno */ void seg_preextend(RelFileNode &rNode, ForkNumber forkNum, BlockNumber blkno); - inline int EXTENT_TYPE_TO_SIZE(int type) { if (type == EXTENT_1) { -- 2.34.1 From 35c4859f343588395ac5d695ad797bcb0eabda8f Mon Sep 17 00:00:00 2001 From: Nemoo <18210033860@163.com> Date: Sat, 30 Sep 2023 14:15:11 +0800 Subject: [PATCH 27/50] Update knl_instance.cpp --- .../process/threadpool/knl_instance.cpp | 359 +++++++++++++----- 1 file changed, 258 insertions(+), 101 deletions(-) diff --git a/src/gausskernel/process/threadpool/knl_instance.cpp b/src/gausskernel/process/threadpool/knl_instance.cpp index a2d5768fd..b9d68b0f9 100755 --- a/src/gausskernel/process/threadpool/knl_instance.cpp +++ b/src/gausskernel/process/threadpool/knl_instance.cpp @@ -2,20 +2,10 @@ * Copyright (c) 2020 Huawei Technologies Co.,Ltd. * Portions Copyright (c) 2021, openGauss Contributors * - * 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. * ------------------------------------------------------------------------- * * knl_instance.cpp - * Initial functions for instance level global variables. + * 实例级全局变量的初始函数 * * IDENTIFICATION * src/gausskernel/process/threadpool/knl_instance.cpp @@ -24,43 +14,46 @@ */ #include -#include "access/parallel_recovery/page_redo.h" -#include "access/reloptions.h" -#include "access/xlog.h" -#include "commands/prepare.h" -#include "executor/instrument.h" -#include "gssignal/gs_signal.h" +#include "access/parallel_recovery/page_redo.h"//用于并行恢复操作的头文件 +#include "access/reloptions.h"//处理表和索引的选项 +#include "access/xlog.h"//处理事务日志 +#include "commands/prepare.h"//用于SQL预处理 +#include "executor/instrument.h"//用于执行计划 +#include "gssignal/gs_signal.h"//处理操作系统信号 +#include "instruments/instr_waitevent.h"//用于性能监控等待事件 +#include "knl/knl_instance.h"//PostgreSQL内核实例 +#include "libcomm/libcomm.h"// 用于通信库的头文件 +#include "optimizer/cost.h"//查询优化中的成本估算 +#include "optimizer/dynsmp.h"//动态并行查询执行 +#include "optimizer/planmain.h"//查询规划主要头文件 +#include "optimizer/planner.h"//查询规划器 +#include "optimizer/streamplan.h"//流式查询计划 +#include "pgstat.h"//用于性能统计 +#include "regex/regex.h"//正则表达式 +#include "utils/memutils.h"//内存管理工具 +#include "utils/palloc.h"//内存分配 +#include "workload/workload.h"// 工作负载管理相关 #include "instruments/instr_waitevent.h" -#include "knl/knl_instance.h" -#include "libcomm/libcomm.h" -#include "optimizer/cost.h" -#include "optimizer/dynsmp.h" -#include "optimizer/planmain.h" -#include "optimizer/planner.h" -#include "optimizer/streamplan.h" -#include "pgstat.h" -#include "regex/regex.h" -#include "utils/memutils.h" -#include "utils/palloc.h" -#include "workload/workload.h" -#include "instruments/instr_waitevent.h" -#include "access/multi_redo_api.h" -#include "utils/hotkey.h" -#include "lib/lrucache.h" +#include "access/multi_redo_api.h"//多线程重做操作 +#include "utils/hotkey.h"//处理热点键 +#include "lib/lrucache.h"//LRU缓存 + #ifdef ENABLE_WHITEBOX #include "access/ustore/knl_whitebox_test.h" #endif const int SIZE_OF_TWO_UINT64 = 16; - +//常量表示,两个64位整数的总大小为16字节 knl_instance_context g_instance; const int ALLOCSET_UNDO_MAXSIZE = 300 * UNDO_ZONE_COUNT; extern void InitGlobalVecFuncMap(); - +//一个外部函数声明 static void knl_g_cost_init(knl_g_cost_context* cost_cxt) { + /*初始化一个 knl_g_cost_context 结构中的各个成员变量, + 以便后续的操作可以使用这些值来进行成本估算或其他计算*/ cost_cxt->cpu_hash_cost = DEFAULT_CPU_HASH_COST; cost_cxt->send_kdata_cost = DEFAULT_SEND_KDATA_COST; cost_cxt->receive_kdata_cost = DEFAULT_RECEIVE_KDATA_COST; @@ -70,54 +63,83 @@ static void knl_g_cost_init(knl_g_cost_context* cost_cxt) static void knl_g_quota_init(knl_g_quota_context* quota_cxt) { + //初始化一个 knl_g_quota_context 结构中的各个成员变量 Assert(quota_cxt != NULL); + //一个断言语句,用于确保 quota_cxt 指针不为空 + //如果为空,则会触发断言失败,表示出现了错误或非预期的情况 quota_cxt->g_quota = 0; quota_cxt->g_quotanofify_ratio = 0; quota_cxt->g_having_quota = true; + //当前具有资源配额 quota_cxt->g_quota_changing = NULL; + //当前没有正在更改的资源配额 } static void knl_g_localinfo_init(knl_g_localinfo_context* localinfo_cxt) { + //初始化一个 knl_g_localinfo_context 结构中的各个成员变量 Assert(localinfo_cxt != NULL); + //用于确保 localinfo_cxt 指针不为空 localinfo_cxt->g_local_host = NULL; + //说明本地主机信息尚未设置 localinfo_cxt->g_self_nodename = NULL; + //说明本地节点名称尚未设置 localinfo_cxt->g_local_ctrl_tcp_sock = -1; - localinfo_cxt->sock_to_server_loop = -1; - localinfo_cxt->gs_krb_keyfile = NULL; + //说明本地控制 TCP 套接字尚未建立 + localinfo_cxt->sock_to_server_loop = -1; + //说明与服务器的套接字通信尚未建立 + localinfo_cxt->gs_krb_keyfile = NULL; + //说明Kerberos 密钥文件尚未设置 } static void knl_g_counters_init(knl_g_counters_context* counters_cxt) { + //初始化一个 knl_g_counters_context 结构中的各个成员变量 Assert(counters_cxt != NULL); + //用于确保 counters_cxt 指针不为空 counters_cxt->g_cur_node_num = 0; + //表示当前节点数量为0 counters_cxt->g_expect_node_num = 0; + //表示期望的节点数量为0 counters_cxt->g_max_stream_num = 0; + //表示最大流的数量为0 counters_cxt->g_recv_num = 0; + //表示接收的数量为0 counters_cxt->g_comm_send_timeout = 0; + //表示通信发送超时时间为0 } static void knl_g_ckpt_init(knl_g_ckpt_context* ckpt_cxt) { + //初始化一个 knl_g_ckpt_context 结构中的各个成员变量 Assert(ckpt_cxt != NULL); + //确保 ckpt_cxt 指针不为空 errno_t rc = memset_s(ckpt_cxt, sizeof(knl_g_ckpt_context), 0, sizeof(knl_g_ckpt_context)); - securec_check(rc, "\0", "\0"); + //用 memset_s 函数将 knl_g_ckpt_context 结构的内存区域初始化为0 + securec_check(rc, "\0", "\0"); + //用于检查 memset_s 调用结果的安全检查,如果调试失败,会触发错误,停止运行 SpinLockInit(&(ckpt_cxt->queue_lock)); + //初始化名为 queue_lock 的自旋锁 (自旋锁是一种用于多线程编程的同步机制,用于保护共享资源免受并发访问的干扰) } static void knl_g_wal_init(knl_g_wal_context *const wal_cxt) { + //初始化一个 knl_g_wal_context 结构,它包含了一系列成员变量的初始化操作 int ret = 0; + //用于存储函数返回值或错误码 ret = pthread_condattr_init(&wal_cxt->criticalEntryAtt); - if (ret != 0) { + //初始化条件变量属性 criticalEntryAtt + if (ret != 0) { elog(FATAL, "Fail to init conattr for walwrite"); } ret = pthread_condattr_setclock(&wal_cxt->criticalEntryAtt, CLOCK_MONOTONIC); - if (ret != 0) { + //设置条件变量属性的时钟类型为 CLOCK_MONOTONIC + if (ret != 0) { elog(FATAL, "Fail to setclock walwrite"); } ret = pthread_cond_init(&wal_cxt->criticalEntryCV, &wal_cxt->criticalEntryAtt); - if (ret != 0) { + //初始化条件变量 criticalEntryCV,并将其关联到条件变量属性 criticalEntryAtt + if (ret != 0) { elog(FATAL, "Fail to init cond for walwrite"); } @@ -143,37 +165,55 @@ static void knl_g_wal_init(knl_g_wal_context *const wal_cxt) wal_cxt->totalXlogIterBytes = 0; wal_cxt->totalXlogIterTimes = 0; wal_cxt->xlogFlushStats = NULL; + //初始化了结构中的各个成员变量 + //上述初始化通常用于数据库系统中与WAL相关的管理和同步 } static void knl_g_bgwriter_init(knl_g_bgwriter_context *bgwriter_cxt) { + //初始化一个 knl_g_bgwriter_context 结构中的各个成员变量 Assert(bgwriter_cxt != NULL); + //确保 bgwriter_cxt 指针不为空 bgwriter_cxt->unlink_rel_hashtbl = NULL; + //表示未分配关联的哈希表 bgwriter_cxt->rel_hashtbl_lock = NULL; + //表示未分配关联的锁 bgwriter_cxt->invalid_buf_proc_latch = NULL; - bgwriter_cxt->unlink_rel_fork_hashtbl = NULL; - bgwriter_cxt->rel_one_fork_hashtbl_lock = NULL; + //表示未分配关联的事件触发器 + bgwriter_cxt->unlink_rel_fork_hashtbl = NULL; + bgwriter_cxt->rel_one_fork_hashtbl_lock = NULL; + //上述初始化通常用于数据库系统中与数据修复或一致性检查相关的数据结构和同步 } static void knl_g_repair_init(knl_g_repair_context *repair_cxt) { + //初始化一个 knl_g_repair_context 结构中的各个成员变量 Assert(repair_cxt != NULL); + //确保 repair_cxt 指针不为空 repair_cxt->page_repair_hashtbl = NULL; + //表示未分配关联的哈希表 repair_cxt->page_repair_hashtbl_lock = NULL; - repair_cxt->repair_proc_latch = NULL; + //表示未分配关联的锁 + repair_cxt->repair_proc_latch = NULL; + //表示未分配关联的锁 } static void knl_g_startup_init(knl_g_startup_context *starup_cxt) { + //初始化一个 knl_g_startup_context 结构中的各个成员变量 Assert(starup_cxt != NULL); starup_cxt->remoteReadPageNum = 0; + //表示远程读取的页面数量为0 starup_cxt->badPageHashTbl = NULL; starup_cxt->current_record = NULL; + //表示当前记录为空 + //上述代码通常用于数据库系统的启动阶段相关的数据结构初始化 } static void knl_g_tests_init(knl_g_tests_context* tests_cxt) { + //初始化一个 knl_g_tests_context 结构中的各个成员变量 Assert(tests_cxt != NULL); tests_cxt->libcomm_test_current_thread = 0; tests_cxt->libcomm_test_thread_arg = NULL; @@ -184,10 +224,12 @@ static void knl_g_tests_init(knl_g_tests_context* tests_cxt) tests_cxt->libcomm_test_send_once = 0; tests_cxt->libcomm_test_recv_sleep = 0; tests_cxt->libcomm_test_recv_once = 0; + //上述多用于进行测试和调试 } static void knl_g_pollers_init(knl_g_pollers_context* pollers_cxt) { + //表示尚未请求关闭轮询(poll)操作 Assert(pollers_cxt != NULL); pollers_cxt->g_libcomm_receiver_poller_list = NULL; pollers_cxt->g_r_libcomm_poller_list_lock = NULL; @@ -199,20 +241,30 @@ static void knl_g_pollers_init(knl_g_pollers_context* pollers_cxt) static void knl_g_reqcheck_init(knl_g_reqcheck_context* reqcheck_cxt) { + // 初始化一个 knl_g_reqcheck_context 结构中的各个成员变量 Assert(reqcheck_cxt != NULL); reqcheck_cxt->g_shutdown_requested = false; + //表示尚未请求关闭操作 reqcheck_cxt->g_cancel_requested = 0; + //表示尚未请求取消操作 reqcheck_cxt->g_close_poll_requested = false; + //表示尚未请求关闭轮询操作 } static void knl_g_mctcp_init(knl_g_mctcp_context* mctcp_cxt) { + //初始化一个 knl_g_mctcp_context 结构中的各个成员变量 Assert(mctcp_cxt != NULL); mctcp_cxt->mc_tcp_keepalive_idle = 0; + //表示TCP连接的空闲时间(idle time)为0 mctcp_cxt->mc_tcp_keepalive_interval = 0; - mctcp_cxt->mc_tcp_keepalive_count = 0; + //表示TCP保活消息发送的间隔时间为0 + mctcp_cxt->mc_tcp_keepalive_count = 0; + //表示TCP保活消息发送的次数为0 mctcp_cxt->mc_tcp_connect_timeout = 0; + //表示TCP连接的超时时间为0 mctcp_cxt->mc_tcp_send_timeout = 0; + //表示TCP发送操作的超时时间为0 } static void knl_g_commutil_init(knl_g_commutil_context* commutil_cxt) @@ -231,14 +283,19 @@ static void knl_g_commutil_init(knl_g_commutil_context* commutil_cxt) static void knl_g_parallel_redo_init(knl_g_parallel_redo_context* predo_cxt) { Assert(predo_cxt != NULL); + //确保 predo_cxt 指针不为空 predo_cxt->state = REDO_INIT; + //表示并行重做的状态为初始化状态 predo_cxt->parallelRedoCtx = NULL; + //表示未分配关联的并行重做上下文 for (int i = 0; i < MAX_RECOVERY_THREAD_NUM; ++i) { predo_cxt->pageRedoThreadStatusList[i].threadId = 0; predo_cxt->pageRedoThreadStatusList[i].threadState = PAGE_REDO_WORKER_INVALID; } predo_cxt->totalNum = 0; + //表示总线程数为0 SpinLockInit(&(predo_cxt->rwlock)); + //初始化自旋锁 rwlock predo_cxt->redoPf.redo_start_ptr = 0; predo_cxt->redoPf.redo_start_time = 0; predo_cxt->redoPf.redo_done_time = 0; @@ -250,13 +307,18 @@ static void knl_g_parallel_redo_init(knl_g_parallel_redo_context* predo_cxt) predo_cxt->redoPf.speed_according_seg = 0; predo_cxt->redoPf.local_max_lsn = 0; predo_cxt->redoPf.oldest_segment = 1; + //用于记录重做的进度和性能信息 knl_g_set_redo_finish_status(0); + //将重做完成状态设置为0 predo_cxt->redoType = DEFAULT_REDO; + //表示重做类型为默认类型 predo_cxt->pre_enable_switch = 0; + //将 pre_enable_switch 成员设置为0 SpinLockInit(&(predo_cxt->destroy_lock)); for (int i = 0; i < NUM_MAX_PAGE_FLUSH_LSN_PARTITIONS; ++i) { pg_atomic_write_u64(&(predo_cxt->max_page_flush_lsn[i]), 0); } + //用于记录最大页面刷新LSN(Log Sequence Number)的值 predo_cxt->permitFinishRedo = 0; predo_cxt->last_replayed_conflict_csn = 0; predo_cxt->hotStdby = 0; @@ -276,27 +338,35 @@ static void knl_g_parallel_decode_init(knl_g_parallel_decode_context* pdecode_cx { Assert(pdecode_cxt != NULL); pdecode_cxt->state = DECODE_INIT; + //表示并行解码的状态为初始化状态 pdecode_cxt->parallelDecodeCtx = NULL; + //表示未分配关联的并行解码上下文 pdecode_cxt->ParallelReaderWorkerStatus.threadId = 0; pdecode_cxt->ParallelReaderWorkerStatus.threadState = PARALLEL_DECODE_WORKER_INVALID; - for (int i = 0; i < MAX_PARALLEL_DECODE_NUM; ++i) { + //表示并行解码工作者状态为无效 + for (int i = 0; i < MAX_PARALLEL_DECODE_NUM; ++i) { pdecode_cxt->ParallelDecodeWorkerStatusList[i].threadId = 0; pdecode_cxt->ParallelDecodeWorkerStatusList[i].threadState = PARALLEL_DECODE_WORKER_INVALID; } pdecode_cxt->totalNum = 0; + //表示总线程数为0 SpinLockInit(&(pdecode_cxt->rwlock)); SpinLockInit(&(pdecode_cxt->destroy_lock)); + //初始化自旋锁 destroy_lock } static void knl_g_cache_init(knl_g_cache_context* cache_cxt) { cache_cxt->global_cache_mem = NULL; + //表示全局缓存内存未分配 for (int i = 0; i < MAX_GLOBAL_CACHEMEM_NUM; ++i) cache_cxt->global_plancache_mem[i] = NULL; + //通过循环遍历,对 global_plancache_mem 数组中的每个元素进行初始化 } void knl_g_cachemem_create() { + //创建全局缓存内存上下文,命名为 "GlobalCacheMemory",并设置默认的内存分配参数 g_instance.cache_cxt.global_cache_mem = AllocSetContextCreate(g_instance.instance_context, "GlobalCacheMemory", ALLOCSET_DEFAULT_MINSIZE, @@ -305,7 +375,7 @@ void knl_g_cachemem_create() SHARED_CONTEXT, DEFAULT_MEMORY_CONTEXT_MAX_SIZE, false); - + //遍历全局计划缓存内存数组,为每个元素创建内存上下文,命名为 "GlobalPlanCacheMemory",并设置默认的内存分配参数 for (int i = 0; i < MAX_GLOBAL_CACHEMEM_NUM; ++i) { g_instance.cache_cxt.global_plancache_mem[i] = AllocSetContextCreate(g_instance.instance_context, "GlobalPlanCacheMemory", @@ -316,6 +386,7 @@ void knl_g_cachemem_create() DEFAULT_MEMORY_CONTEXT_MAX_SIZE, false); } + //遍历全局包运行时缓存内存数组,为每个元素创建内存上下文,命名为 "GlobalPackageRuntimeCacheMemory",并设置默认的内存分配参数 for (int i = 0; i < MAX_GLOBAL_PRC_NUM; ++i) { g_instance.cache_cxt.global_prc_mem[i] = AllocSetContextCreate(g_instance.instance_context, "GlobalPackageRuntimeCacheMemory", @@ -326,58 +397,72 @@ void knl_g_cachemem_create() DEFAULT_MEMORY_CONTEXT_MAX_SIZE, false); } + //创建全局计划缓存对象 g_instance.plan_cache = New(INSTANCE_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_EXECUTOR)) GlobalPlanCache(); - g_instance.global_session_pkg = PLGlobalPackageRuntimeCache::Instance(); + //创建全局会话包运行时缓存对象 + g_instance.global_session_pkg = PLGlobalPackageRuntimeCache::Instance(); } static void knl_g_comm_init(knl_g_comm_context* comm_cxt) { + //断言确保传入的 comm_cxt 指针不为空 Assert(comm_cxt != NULL); - comm_cxt->gs_wakeup_consumer = NULL; - comm_cxt->g_receivers = NULL; - comm_cxt->g_senders = NULL; - comm_cxt->g_delay_survey_switch = false; - comm_cxt->g_unix_path = NULL; - comm_cxt->g_ha_shm_data = NULL; - comm_cxt->g_usable_streamid = NULL; - comm_cxt->g_r_node_sock = NULL; - comm_cxt->g_s_node_sock = NULL; - comm_cxt->g_delay_info = NULL; - comm_cxt->g_c_mailbox = NULL; - comm_cxt->g_p_mailbox = NULL; - comm_cxt->libcomm_log_timezone = NULL; - comm_cxt->force_cal_space_info = false; - comm_cxt->cal_all_space_info_in_progress = false; - comm_cxt->current_gsrewind_count = 0; - comm_cxt->isNeedChangeRole = false; - comm_cxt->usedDnSpace = NULL; - comm_cxt->request_disaster_cluster = true; - comm_cxt->lastArchiveRcvTime = 0; + + //初始化 comm_cxt 结构中的各个成员变量为默认值或 NULL + comm_cxt->gs_wakeup_consumer = NULL; //指向唤醒消费者的指针 + comm_cxt->g_receivers = NULL; //接收者信息 + comm_cxt->g_senders = NULL; //发送者信息 + comm_cxt->g_delay_survey_switch = false; //延迟测量开关 + comm_cxt->g_unix_path = NULL; //UNIX 套接字路径 + comm_cxt->g_ha_shm_data = NULL; //HA 共享内存数据 + comm_cxt->g_usable_streamid = NULL; //可用的流 ID + comm_cxt->g_r_node_sock = NULL; //接收节点套接字信息 + comm_cxt->g_s_node_sock = NULL; //发送节点套接字信息 + comm_cxt->g_delay_info = NULL; //延迟信息 + comm_cxt->g_c_mailbox = NULL; //CMailbox(通信邮箱)信息 + comm_cxt->g_p_mailbox = NULL; //PMailbox(通信邮箱)信息 + comm_cxt->libcomm_log_timezone = NULL; //日志时区 + comm_cxt->force_cal_space_info = false; //强制计算空间信息标志 + comm_cxt->cal_all_space_info_in_progress = false; //正在计算所有空间信息的标志 + comm_cxt->current_gsrewind_count = 0; //当前 GS_REWIND 的计数 + comm_cxt->isNeedChangeRole = false; //是否需要改变角色 + comm_cxt->usedDnSpace = NULL; //已用的数据节点空间信息 + comm_cxt->request_disaster_cluster = true; //请求灾备集群信息的标志 + comm_cxt->lastArchiveRcvTime = 0; //上次归档接收时间 #ifdef USE_SSL - comm_cxt->libcomm_data_port_list = NULL; - comm_cxt->libcomm_ctrl_port_list = NULL; + //如果使用 SSL 加密通信,则初始化 SSL 相关的成员变量 + comm_cxt->libcomm_data_port_list = NULL; //数据端口列表 + comm_cxt->libcomm_ctrl_port_list = NULL; //控制端口列表 #endif - knl_g_quota_init(&g_instance.comm_cxt.quota_cxt); - knl_g_localinfo_init(&g_instance.comm_cxt.localinfo_cxt); - knl_g_counters_init(&g_instance.comm_cxt.counters_cxt); - knl_g_tests_init(&g_instance.comm_cxt.tests_cxt); - knl_g_pollers_init(&g_instance.comm_cxt.pollers_cxt); - knl_g_reqcheck_init(&g_instance.comm_cxt.reqcheck_cxt); - knl_g_mctcp_init(&g_instance.comm_cxt.mctcp_cxt); - knl_g_commutil_init(&g_instance.comm_cxt.commutil_cxt); - knl_g_parallel_redo_init(&g_instance.comm_cxt.predo_cxt); + //分别调用其他初始化函数来初始化 comm_cxt 中的其他成员变量 + knl_g_quota_init(&g_instance.comm_cxt.quota_cxt); //初始化配额信息 + knl_g_localinfo_init(&g_instance.comm_cxt.localinfo_cxt); //初始化本地信息 + knl_g_counters_init(&g_instance.comm_cxt.counters_cxt); //初始化计数器信息 + knl_g_tests_init(&g_instance.comm_cxt.tests_cxt); //初始化测试信息 + knl_g_pollers_init(&g_instance.comm_cxt.pollers_cxt); //初始化轮询器信息 + knl_g_reqcheck_init(&g_instance.comm_cxt.reqcheck_cxt); //初始化请求检查信息 + knl_g_mctcp_init(&g_instance.comm_cxt.mctcp_cxt); //初始化 MCTCP 信息 + knl_g_commutil_init(&g_instance.comm_cxt.commutil_cxt); //初始化通信工具信息 + knl_g_parallel_redo_init(&g_instance.comm_cxt.predo_cxt); //初始化并行重做信息 + + //遍历并初始化并行解码信息(根据最大复制槽数) for (int i = 0; i < g_instance.attr.attr_storage.max_replication_slots; ++i) { knl_g_parallel_decode_init(&g_instance.comm_cxt.pdecode_cxt[i]); } } + static void knl_g_conn_init(knl_g_conn_context* conn_cxt) { conn_cxt->CurConnCount = 0; + //表示当前连接的数量为0 conn_cxt->CurCMAConnCount = 0; + //表示当前 CMA连接的数量为0 conn_cxt->CurCMAProcCount = 0; + //表示当前 CMA 进程的数量为0 SpinLockInit(&conn_cxt->ConnCountLock); + //用于在多线程环境下保护连接计数信息的并发访问 } static void knl_g_executor_init(knl_g_executor_context* exec_cxt) @@ -396,29 +481,43 @@ static void knl_g_rto_init(knl_g_rto_context *rto_cxt) static void knl_g_xlog_init(knl_g_xlog_context *xlog_cxt) { + // 始化 xlog_cxt 结构中的成员变量为默认值或 NULL + + //初始化 num_locks_in_group 为0,表示锁组数量为0 xlog_cxt->num_locks_in_group = 0; + #ifdef ENABLE_MOT + //如果启用了 MOT 存储引擎,则初始化 redoCommitCallback 为 NULL xlog_cxt->redoCommitCallback = NULL; #endif + + //初始化 shareStorageXLogCtl 和 shareStorageXLogCtlOrigin 为 NULL xlog_cxt->shareStorageXLogCtl = NULL; xlog_cxt->shareStorageXLogCtlOrigin = NULL; + //使用 memset_s 函数将 shareStorageopCtl 结构初始化为0 errno_t rc = memset_s(&xlog_cxt->shareStorageopCtl, sizeof(ShareStorageOperateCtl), 0, sizeof(ShareStorageOperateCtl)); securec_check(rc, "\0", "\0"); + //初始化 remain_segs_lock 互斥锁为 NULL pthread_mutex_init(&xlog_cxt->remain_segs_lock, NULL); + //初始化 shareStorageLockFd 为 -1,表示共享存储锁的文件描述符为无效值 xlog_cxt->shareStorageLockFd = -1; } + static void KnlGUndoInit(knl_g_undo_context *undoCxt) { + //使用 undo 命名空间 using namespace undo; MemoryContext oldContext; + + //创建 Undo 命名空间的内存上下文,并将其设置为当前上下文 g_instance.undo_cxt.undoContext = AllocSetContextCreate(g_instance.instance_context, "Undo", ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_UNDO_MAXSIZE, SHARED_CONTEXT); oldContext = MemoryContextSwitchTo(g_instance.undo_cxt.undoContext); /* - * Create three bitmaps for undozone with three kinds of tables(permanent, unlogged and temp). - * Use -1 to initialize each bit of the bitmap as 1. + 为每个持久性级别(permanent, unlogged 和 temp)创建三个位图, + 使用 -1 初始化位图的每个位(bit)为1。 */ for (auto i = 0; i < UNDO_PERSISTENCE_LEVELS; i++) { g_instance.undo_cxt.uZoneBitmap[i] = bms_add_member(g_instance.undo_cxt.uZoneBitmap[i], PERSIST_ZONE_COUNT); @@ -426,7 +525,9 @@ static void KnlGUndoInit(knl_g_undo_context *undoCxt) (g_instance.undo_cxt.uZoneBitmap[i])->nwords * sizeof(bitmapword), -1, (g_instance.undo_cxt.uZoneBitmap[i])->nwords * sizeof(bitmapword)); } + //恢复先前的内存上下文 MemoryContextSwitchTo(oldContext); + //初始化 undoCxt 结构中的成员变量为默认值 undoCxt->undoTotalSize = 0; undoCxt->undoMetaSize = 0; undoCxt->uZoneCount = 0; @@ -443,6 +544,7 @@ static void knl_g_flashback_init(knl_g_flashback_context *flashbackCxt) static void knl_g_libpq_init(knl_g_libpq_context* libpq_cxt) { + //初始化libpq中各参数值 Assert(libpq_cxt != NULL); libpq_cxt->pam_passwd = NULL; libpq_cxt->pam_port_cludge = NULL; @@ -452,6 +554,7 @@ static void knl_g_libpq_init(knl_g_libpq_context* libpq_cxt) static void InitHotkeyResources(knl_g_stat_context* stat_cxt) { + //如果 hotkeysCxt 为 NULL,则创建一个新的内存上下文 hotkeysCxt if (stat_cxt->hotkeysCxt == NULL) { stat_cxt->hotkeysCxt = AllocSetContextCreate(INSTANCE_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_OPTIMIZER), "HotkeysMemory", @@ -460,48 +563,68 @@ static void InitHotkeyResources(knl_g_stat_context* stat_cxt) ALLOCSET_DEFAULT_MAXSIZE, SHARED_CONTEXT); } + + //在 hotkeysCxt 内存上下文中创建 CircularQueue(循环队列)并初始化 stat_cxt->hotkeysCollectList = New(stat_cxt->hotkeysCxt) CircularQueue(HOTKEYS_QUEUE_LENGTH, stat_cxt->hotkeysCxt); stat_cxt->hotkeysCollectList->Init(); + + //在 hotkeysCxt 内存上下文中创建 LRUCache(最近最少使用缓存)并初始化 stat_cxt->lru = New(stat_cxt->hotkeysCxt) LRUCache(LRU_QUEUE_LENGTH, stat_cxt->hotkeysCxt); stat_cxt->lru->Init(); + + //初始化 fifo(First-In-First-Out,先进先出)队列为 NULL stat_cxt->fifo = NIL; } static void knl_g_stat_init(knl_g_stat_context* stat_cxt) { + //初始化等待计数哈希表为 NULL stat_cxt->WaitCountHashTbl = NULL; + //初始化等待计数状态列表为 NULL stat_cxt->WaitCountStatusList = NULL; + //初始化 pgStatSock 为无效的套接字 stat_cxt->pgStatSock = PGINVALID_SOCKET; + //初始化 got_SIGHUP 为 false stat_cxt->got_SIGHUP = false; - + //初始化 UniqueSqlContext 为 NULL stat_cxt->UniqueSqlContext = NULL; + //初始化 UniqueSQLHashtbl 为 NULL stat_cxt->UniqueSQLHashtbl = NULL; + //初始化 InstrUserHTAB 为 NULL stat_cxt->InstrUserHTAB = NULL; + //初始化 calculate_on_other_cn 为 false stat_cxt->calculate_on_other_cn = false; + //初始化 force_process 为 false stat_cxt->force_process = false; + //初始化 RTPERCENTILE 数组的元素为 0 stat_cxt->RTPERCENTILE[0] = 0; stat_cxt->RTPERCENTILE[1] = 0; + //初始化 NodeStatResetTime 为 0 stat_cxt->NodeStatResetTime = 0; + //初始化 sql_rt_info_array 为 NULL stat_cxt->sql_rt_info_array = NULL; - + //初始化 gInstanceTimeInfo 数组为 0 stat_cxt->gInstanceTimeInfo = (int64*)MemoryContextAllocZero( INSTANCE_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_DFX), TOTAL_TIME_INFO_TYPES * sizeof(int64)); errno_t rc; rc = memset_s( stat_cxt->gInstanceTimeInfo, TOTAL_TIME_INFO_TYPES * sizeof(int64), 0, TOTAL_TIME_INFO_TYPES * sizeof(int64)); securec_check(rc, "\0", "\0"); - + + //初始化 snapshot_thread_counter 为 0 stat_cxt->snapshot_thread_counter = 0; - + //初始化 fileIOStat 为 0 stat_cxt->fileIOStat = (FileIOStat*)MemoryContextAllocZero( INSTANCE_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_DFX), sizeof(FileIOStat)); rc = memset_s(stat_cxt->fileIOStat, sizeof(FileIOStat), 0, sizeof(FileIOStat)); securec_check(rc, "\0", "\0"); + //初始化 tableStat 为 0 stat_cxt->tableStat = (UHeapPruneStat *) palloc0(sizeof(UHeapPruneStat)); rc = memset_s(stat_cxt->tableStat, sizeof(UHeapPruneStat), 0, sizeof(UHeapPruneStat)); securec_check(rc, "\0", "\0"); + stat_cxt->active_sess_hist_arrary = NULL; stat_cxt->ash_appname = NULL; stat_cxt->instr_stmt_is_cleaning = false; @@ -509,11 +632,13 @@ static void knl_g_stat_init(knl_g_stat_context* stat_cxt) stat_cxt->ASHUniqueSQLHashtbl = NULL; stat_cxt->track_context_hash = NULL; stat_cxt->track_memory_info_hash = NULL; + + //创建并初始化用于跟踪内存锁的 rwlock pthread_rwlockattr_t attr; - /* set write-first lock for rwlock to avoid hunger of wrlock */ (void)pthread_rwlockattr_setkind_np(&attr, PTHREAD_RWLOCK_PREFER_WRITER_NONRECURSIVE_NP); (void)pthread_rwlock_init(&(stat_cxt->track_memory_lock), &attr); - + + //调用 InitHotkeyResources 函数来初始化热点数据统计资源 InitHotkeyResources(stat_cxt); } @@ -524,17 +649,23 @@ static void knl_g_adv_init(knl_g_advisor_conntext* adv_cxt) adv_cxt->maxMemory = 0; adv_cxt->maxsqlCount = 0; adv_cxt->currentUser = InvalidOid; + //表示没有有效的用户标识 adv_cxt->currentDB = InvalidOid; + //表示没有有效的数据库标识 adv_cxt->GWCArray = NULL; - + //表示没有分配内存或初始化该数组 + adv_cxt->SQLAdvisorContext = NULL; + //表示没有为 SQL 查询优化提供额外的上下文或资源 } static void knl_g_pid_init(knl_g_pid_context* pid_cxt) { - errno_t rc; + errno_t rc;//表示操作是否成功 rc = memset_s(pid_cxt, sizeof(knl_g_pid_context), 0, sizeof(knl_g_pid_context)); - pid_cxt->PageWriterPID = NULL; + /*memset_s 是一个安全版本的内存清零函数,它接受四个参数:目标内存地址,目标内存大小, + 设置的值(这里是0),以及目标内存的大小。这可以确保 pid_cxt 的所有字段都被初始化为0。*/ + pid_cxt->PageWriterPID = NULL; pid_cxt->CommReceiverPIDS = NULL; pid_cxt->PgAuditPID = NULL; securec_check(rc, "\0", "\0"); @@ -542,7 +673,8 @@ static void knl_g_pid_init(knl_g_pid_context* pid_cxt) static void knl_g_wlm_init(knl_g_wlm_context* wlm_cxt) { - wlm_cxt->parctl_process_memory = 8; // initialize the per query memory as 8 MB + wlm_cxt->parctl_process_memory = 8; + //将每个查询的内存初始化为8 MB wlm_cxt->cluster_state = NULL; wlm_cxt->dnnum_in_cluster_state = 0; @@ -571,10 +703,15 @@ static void knl_g_compaction_init(knl_g_ts_compaction_context* tsc_cxt) Assert(tsc_cxt != NULL); tsc_cxt->totalNum = 0; tsc_cxt->state = Compaction::COMPACTION_INIT; - tsc_cxt->drop_db_count = 0; + //表示压缩状态为初始化状态 + tsc_cxt->drop_db_count = 0; + //表示要删除的数据库数量 tsc_cxt->origin_id = (int*)palloc0(sizeof(int) * Compaction::MAX_TSCOMPATION_NUM); - tsc_cxt->compaction_rest = false; + //用于跟踪数据库的原始标识 + tsc_cxt->compaction_rest = false; + //表示没有进行压缩的休息状态 tsc_cxt->dropdb_id = InvalidOid; + //表示要删除的数据库的标识 for (int i = 0; i < Compaction::MAX_TSCOMPATION_NUM; i++) { tsc_cxt->compaction_worker_status_list[i].thread_id = 0; tsc_cxt->compaction_worker_status_list[i].thread_state = Compaction::COMPACTION_WORKER_INVALID; @@ -595,10 +732,13 @@ static void knl_g_dw_init(knl_g_dw_context *dw_cxt) errno_t rc = memset_s(dw_cxt, sizeof(knl_g_dw_context), 0, sizeof(knl_g_dw_context)); securec_check(rc, "\0", "\0"); dw_cxt->closed = 1; - + //表示数据仓库处于关闭状态 dw_cxt->old_batch_version = false; + //表示使用的是旧批处理版本 dw_cxt->recovery_dw_file_num = 0; + //表示恢复时的数据仓库文件数量为 0 dw_cxt->recovery_dw_file_size = 0; + //表示恢复时的数据仓库文件大小为 0 } static void knl_g_numa_init(knl_g_numa_context* numa_cxt) @@ -627,6 +767,7 @@ static void knl_g_archive_obs_init(knl_g_archive_context *archive_cxt) static void knl_g_archive_thread_info_init(knl_g_archive_thread_info *archive_thread_info) { + //用于为归档线程信息的全局结构体设置默认值和状态 errno_t rc = memset_s(archive_thread_info, sizeof(knl_g_archive_thread_info), 0, sizeof(knl_g_archive_thread_info)); securec_check(rc, "\0", "\0"); @@ -637,6 +778,7 @@ static void knl_g_archive_thread_info_init(knl_g_archive_thread_info *archive_th static void knl_g_mot_init(knl_g_mot_context* mot_cxt) { mot_cxt->jitExecMode = JitExec::JIT_EXEC_MODE_INVALID; + //这个字段用于表示 MOT 执行的 JIT(即时编译)模式,此处将其初始化为无效模式 } #endif @@ -672,6 +814,7 @@ static void knl_g_pldebug_init(knl_g_pldebug_context* pldebug_cxt) static void knl_g_spi_plan_init(knl_g_spi_plan_context* spi_plan_cxt) { + //用于在初始化 SPI 计划上下文时,将其字段初始化为默认值或空值 spi_plan_cxt->global_spi_plan_context = NULL; spi_plan_cxt->FPlans = NULL; spi_plan_cxt->nFPlans = 0; @@ -759,11 +902,17 @@ void knl_instance_init() pg_atomic_init_u32(&g_instance.extensionNum, 0); /* - * Set up the process wise memory context. The memory allocated from this - * context will not be released untill it is called free. Meanwhile, the - * memory context is visible to all threads but not thread safe, so only - * postmaster thread shall use it or use it with lock protection. + 这个内存上下文是进程级别的,即每个进程都有自己独立的内存上下文, + 其中分配的内存只有在显式调用释放函数之后才会释放。此外,这个内存 + 上下文对所有线程可见,但不是线程安全的,因此只有主进程(Postmaster + 线程)应该使用它,或者在使用时需要进行锁保护。 */ + + /* + 这种设置允许进程在运行过程中分配内存,并在需要时手动释放,而不会 + 在每次分配内存时都进行自动释放。这对于需要更细粒度地控制内存管 + 理的情况非常有用,但需要开发人员自行管理内存的生命周期和线程安全性。 + */ g_instance.instance_context = AllocSetContextCreate((MemoryContext)NULL, "ProcessMemory", ALLOCSET_DEFAULT_MINSIZE, @@ -835,15 +984,20 @@ void knl_instance_init() void add_numa_alloc_info(void* numaAddr, size_t length) { + //如果已分配的 NUMA 内存信息数量超过了数组的最大长度,就要计算新的数组长度,将数组长度翻倍 if (g_instance.numa_cxt.allocIndex >= g_instance.numa_cxt.maxLength) { size_t newLength = g_instance.numa_cxt.maxLength * 2; g_instance.numa_cxt.numaAllocInfos = (NumaMemAllocInfo*)repalloc(g_instance.numa_cxt.numaAllocInfos, newLength * sizeof(NumaMemAllocInfo)); + //使用repalloc函数重新分配内存,以适应新的数组长度。这将导致分配一个新的数组,同时保留以前的数据 g_instance.numa_cxt.maxLength = newLength; } g_instance.numa_cxt.numaAllocInfos[g_instance.numa_cxt.allocIndex].numaAddr = numaAddr; - g_instance.numa_cxt.numaAllocInfos[g_instance.numa_cxt.allocIndex].length = length; + //将当前索引位置的 NUMA 内存地址设置为传入的 numaAddr + g_instance.numa_cxt.numaAllocInfos[g_instance.numa_cxt.allocIndex].length = length; + //将当前索引位置的 NUMA 内存块长度设置为传入的 length ++g_instance.numa_cxt.allocIndex; + //增加 NUMA 内存信息数组的索引,以便下次添加信息时使用新的位置 } void knl_g_set_redo_finish_status(uint32 status) @@ -867,7 +1021,9 @@ bool knl_g_get_local_redo_finish_status() #if defined(__arm__) || defined(__arm) || defined(__aarch64__) || defined(__aarch64) pg_memory_barrier(); #endif + //使用原子读取获取 Redo 完成状态 uint32 isRedoFinish = pg_atomic_read_u32(&(g_instance.comm_cxt.predo_cxt.isRedoFinish)); + //检查是否本地 Redo 完成(按位与操作) return (isRedoFinish & REDO_FINISH_STATUS_LOCAL) == REDO_FINISH_STATUS_LOCAL; } @@ -876,7 +1032,8 @@ bool knl_g_get_redo_finish_status() #if defined(__arm__) || defined(__arm) || defined(__aarch64__) || defined(__aarch64) pg_memory_barrier(); #endif + //使用原子读取获取 Redo 完成状态 uint32 isRedoFinish = pg_atomic_read_u32(&(g_instance.comm_cxt.predo_cxt.isRedoFinish)); + //检查是否整体 Redo 完成(按位与操作) return (isRedoFinish & REDO_FINISH_STATUS_CM) == REDO_FINISH_STATUS_CM; -} - +} \ No newline at end of file -- 2.34.1 From 87d2bf33c7e4fe67e7b0d5088be37f5670591396 Mon Sep 17 00:00:00 2001 From: Nemoo <18210033860@163.com> Date: Sat, 30 Sep 2023 14:16:39 +0800 Subject: [PATCH 28/50] Update knl_session.cpp --- .../process/threadpool/knl_session.cpp | 1101 +++++++++-------- 1 file changed, 574 insertions(+), 527 deletions(-) diff --git a/src/gausskernel/process/threadpool/knl_session.cpp b/src/gausskernel/process/threadpool/knl_session.cpp index 6c3225a37..4d57bbbb5 100755 --- a/src/gausskernel/process/threadpool/knl_session.cpp +++ b/src/gausskernel/process/threadpool/knl_session.cpp @@ -2,20 +2,11 @@ * Copyright (c) 2020 Huawei Technologies Co.,Ltd. * Portions Copyright (c) 2021, openGauss Contributors * - * 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. * ------------------------------------------------------------------------- * * knl_session.cpp - * Initial functions for session level global variables. + * 会话级别上全局变量的初始化 * * IDENTIFICATION * src/gausskernel/process/threadpool/knl_session.cpp @@ -74,14 +65,20 @@ extern int SysCacheSize; static void KnlUUstoreInit(knl_u_ustore_context *ustoreCxt) { + // 创建一个 URecVector 对象,并将其分配给 ustoreCxt->urecvec ustoreCxt->urecvec = New(CurrentMemoryContext) URecVector(); + // 初始化 ustoreCxt->urecvec,设置最大容量为 MAX_UNDORECORDS_PER_OPERATION,并启用自动增长 ustoreCxt->urecvec->Initialize(MAX_UNDORECORDS_PER_OPERATION, true); + // 使用循环初始化 ustoreCxt->undo_records 数组 for (int i = 0; i < MAX_UNDORECORDS_PER_OPERATION; i++) { - ustoreCxt->undo_records[i] = New(CurrentMemoryContext)UndoRecord(); + // 创建一个 UndoRecord 对象,并将其分配给 ustoreCxt->undo_records 数组的每个元素 + ustoreCxt->undo_records[i] = New(CurrentMemoryContext) UndoRecord(); + // 将每个 UndoRecord 对象添加到 ustoreCxt->urecvec 中 ustoreCxt->urecvec->PushBack(ustoreCxt->undo_records[i]); } + // 初始化 ustoreCxt 的其他成员变量 ustoreCxt->undo_buffer_idx = 0; ustoreCxt->undo_buffers = (UndoBuffer*) palloc0(MAX_UNDO_BUFFERS * sizeof(UndoBuffer)); @@ -91,6 +88,7 @@ KnlUUstoreInit(knl_u_ustore_context *ustoreCxt) static void KnlURepOriginInit(knl_u_rep_origin_context* repOriginCxt) { + // 初始化 repOriginCxt 的成员变量 repOriginCxt->curRepState = NULL; repOriginCxt->originId = InvalidRepOriginId; repOriginCxt->originLsn = InvalidXLogRecPtr; @@ -101,17 +99,20 @@ static void KnlURepOriginInit(knl_u_rep_origin_context* repOriginCxt) static void knl_u_analyze_init(knl_u_analyze_context* anl_cxt) { + // 初始化分析上下文结构 anl_cxt 的成员变量 anl_cxt->is_under_analyze = false; anl_cxt->need_autoanalyze = false; anl_cxt->analyze_context = NULL; anl_cxt->autoanalyze_process = NULL; anl_cxt->autoanalyze_timeinfo = NULL; + // 为 anl_cxt->vac_strategy 分配内存,并将其初始化为零 anl_cxt->vac_strategy = (BufferAccessStrategyData*)palloc0(sizeof(BufferAccessStrategyData)); } static void knl_u_attr_init(knl_session_attr* attr) { - const int sessionDefaultTimeout = 600; /* 10 * 60 = 10 min */ + // 设置会话属性结构 attr 的各个成员变量的默认值 + const int sessionDefaultTimeout = 600; /* 10 * 60秒 = 10 分钟 */ attr->attr_common.backtrace_min_messages = PANIC; attr->attr_common.log_min_messages = WARNING; attr->attr_common.client_min_messages = NOTICE; @@ -128,6 +129,7 @@ static void knl_u_attr_init(knl_session_attr* attr) void knl_u_executor_init(knl_u_executor_context* exec_cxt) { + // 初始化执行器上下文结构 exec_cxt 的各个成员变量 exec_cxt->remotequery_list = NIL; exec_cxt->exec_result_checkqual_fail = false; exec_cxt->under_stream_runtime = false; @@ -139,7 +141,6 @@ void knl_u_executor_init(knl_u_executor_context* exec_cxt) exec_cxt->route = (PartitionIdentifier*)palloc0(sizeof(PartitionIdentifier)); exec_cxt->cur_tuple_hash_table = NULL; exec_cxt->cur_light_proxy_obj = NULL; - exec_cxt->ActivePortal = NULL; exec_cxt->PortalHashTable = NULL; exec_cxt->unnamed_portal_count = 0; @@ -148,36 +149,30 @@ void knl_u_executor_init(knl_u_executor_context* exec_cxt) exec_cxt->hasTempObject = false; exec_cxt->DfsDDLIsTopLevelXact = false; exec_cxt->could_cancel_redistribution = false; - exec_cxt->g_pgaudit_agent_attached = false; exec_cxt->pgaudit_track_sqlddl = true; - exec_cxt->HashScans = NULL; exec_cxt->executorStopFlag = false; - exec_cxt->CurrentOpFusionObj = NULL; exec_cxt->nesting_level = 0; - exec_cxt->is_exec_trigger_func = false; exec_cxt->single_shard_stmt = false; - exec_cxt->cast_owner = InvalidOid; - exec_cxt->CurrentRouter = NULL; exec_cxt->is_dn_enable_router = false; - exec_cxt->isExecTrunc = false; - exec_cxt->isLockRows = false; } static void knl_u_index_init(knl_u_index_context* index_cxt) { + // 初始化索引上下文结构 index_cxt 的 counter 成员变量 index_cxt->counter = 1; } static void knl_u_instrument_init(knl_u_instrument_context* instr_cxt) { + // 初始化性能监视器上下文结构 instr_cxt 的各个成员变量 instr_cxt->perf_monitor_enable = false; instr_cxt->can_record_to_table = false; instr_cxt->operator_plan_number = 0; @@ -193,6 +188,7 @@ static void knl_u_instrument_init(knl_u_instrument_context* instr_cxt) static void knl_u_locale_init(knl_u_locale_context* lc_cxt) { + // 初始化语言环境上下文结构 lc_cxt 的各个成员变量 lc_cxt->cur_lc_conv_valid = false; lc_cxt->cur_lc_time_valid = false; lc_cxt->cur_lc_conv = (struct lconv*)palloc0(sizeof(struct lconv)); @@ -203,6 +199,7 @@ static void knl_u_locale_init(knl_u_locale_context* lc_cxt) static void knl_u_log_init(knl_u_log_context* log_cxt) { + // 初始化日志上下文结构 log_cxt 的各个成员变量 log_cxt->syslog_ident = NULL; log_cxt->module_logging_configure = (char*)palloc0(sizeof(char) * BEMD_BITMAP_SIZE); log_cxt->msgbuf = (StringInfoData*)palloc0(sizeof(StringInfoData)); @@ -210,6 +207,7 @@ static void knl_u_log_init(knl_u_log_context* log_cxt) static void knl_u_optimizer_init(knl_u_optimizer_context* opt_cxt) { + // 初始化优化器上下文结构 opt_cxt 的各个成员变量 errno_t rc; opt_cxt->disable_dop_change = false; @@ -218,15 +216,15 @@ static void knl_u_optimizer_init(knl_u_optimizer_context* opt_cxt) opt_cxt->is_stream = true; opt_cxt->is_stream_support = true; opt_cxt->is_multiple_nodegroup_scenario = false; - /* Installation nodegroup. */ opt_cxt->different_nodegroup_count = 1; opt_cxt->is_randomfunc_shippable = true; opt_cxt->is_dngather_support = true; - opt_cxt->srvtype = 0; opt_cxt->qrw_inlist2join_optmode = QRW_INLIST2JOIN_CBO; opt_cxt->plan_current_seed = 0; opt_cxt->plan_prev_seed = 0; + + // 使用 memset_s 函数初始化 path_current_seed_factor 数组 rc = memset_s(opt_cxt->path_current_seed_factor, PATH_SEED_FACTOR_LEN, 0, PATH_SEED_FACTOR_LEN); securec_check(rc, "\0", "\0"); @@ -246,13 +244,13 @@ static void knl_u_optimizer_init(knl_u_optimizer_context* opt_cxt) opt_cxt->ft_context = NULL; opt_cxt->is_under_append_plan = false; - /* Palloc memory. */ + // 为一些成员变量分配内存 opt_cxt->dynamic_smp_info = (DynamicSmpInfo*)palloc0(sizeof(DynamicSmpInfo)); opt_cxt->not_shipping_info = (ShippingInfo*)palloc0(sizeof(ShippingInfo)); opt_cxt->bottom_seq = (PartitionIdentifier*)palloc0(sizeof(PartitionIdentifier)); opt_cxt->top_seq = (PartitionIdentifier*)palloc0(sizeof(PartitionIdentifier)); - /* Init dynamic smp info. */ + // 初始化动态 SMP 信息 opt_cxt->dynamic_smp_info->num_of_dn_in_one_machine = UNKNOWN_NUM_OF_DN_IN_ONE_MACHINE; opt_cxt->dynamic_smp_info->num_of_cpu_in_one_machine = UNKNOWN_NUM_OF_CPU_IN_ONE_MACHINE; opt_cxt->dynamic_smp_info->num_of_cpu_for_one_dn = UNKNOWN_NUM_OF_CPU_FOR_ONE_DN; @@ -264,6 +262,7 @@ static void knl_u_optimizer_init(knl_u_optimizer_context* opt_cxt) static void knl_u_parser_init(knl_u_parser_context* parser_cxt) { + // 初始化解析器上下文结构 parser_cxt 的各个成员变量 parser_cxt->eaten_begin = false; parser_cxt->eaten_declare = false; parser_cxt->has_dollar = false; @@ -285,6 +284,7 @@ static void knl_u_parser_init(knl_u_parser_context* parser_cxt) static void knl_u_advisor_init(knl_u_advisor_context* adv_cxt) { + // 初始化顾问上下文结构 adv_cxt 的各个成员变量 adv_cxt->adviseMode = AM_NONE; adv_cxt->adviseType = AT_NONE; adv_cxt->adviseCostMode = ACM_NONE; @@ -302,19 +302,17 @@ static void knl_u_advisor_init(knl_u_advisor_context* adv_cxt) adv_cxt->endTime = 0; adv_cxt->maxMemory = 0; adv_cxt->maxsqlCount = 0; - adv_cxt->candicateTables = NULL; adv_cxt->candicateQueries = NIL; adv_cxt->candicateAdviseGroups = NIL; adv_cxt->result = NIL; - adv_cxt->SQLAdvisorContext = NULL; - adv_cxt->getPlanInfoFunc = NULL; } static void knl_u_stream_init(knl_u_stream_context* stream_cxt) { + // 初始化流上下文结构 stream_cxt 的各个成员变量 stream_cxt->global_obj = NULL; stream_cxt->producer_obj = NULL; stream_cxt->producer_dop = 1; @@ -331,6 +329,7 @@ static void knl_u_stream_init(knl_u_stream_context* stream_cxt) static void knl_u_sig_init(knl_u_sig_context* sig_cxt) { + // 初始化信号处理上下文结构 sig_cxt 的各个成员变量 sig_cxt->got_SIGHUP = 0; sig_cxt->got_pool_reload = 0; sig_cxt->cp_PoolReload = 0; @@ -338,6 +337,7 @@ static void knl_u_sig_init(knl_u_sig_context* sig_cxt) static void knl_u_SPI_init(knl_u_SPI_context* spi) { + // 初始化SPI(Server Programming Interface)上下文结构 spi 的各个成员变量 spi->lastoid = InvalidOid; spi->_stack_depth = 0; spi->_connected = -1; @@ -357,6 +357,7 @@ static void knl_u_SPI_init(knl_u_SPI_context* spi) static void knl_u_trigger_init(knl_u_trigger_context* tri_cxt) { + // 初始化触发器上下文结构 tri_cxt 的各个成员变量 tri_cxt->ri_compare_cache = NULL; tri_cxt->ri_query_cache = NULL; @@ -368,6 +369,7 @@ static void knl_u_trigger_init(knl_u_trigger_context* tri_cxt) static void knl_u_wlm_init(knl_u_wlm_context* wlmctx) { + // 初始化WLM(Workload Management)上下文结构 wlmctx 的各个成员变量 wlmctx->cgroup_state = CG_ORIGINAL; wlmctx->cgroup_stmt = GSCGROUP_NONE_STMT; wlmctx->cgroup_last_stmt = GSCGROUP_NONE_STMT; @@ -390,6 +392,7 @@ static void knl_u_wlm_init(knl_u_wlm_context* wlmctx) static void knl_u_utils_init(knl_u_utils_context* utils_cxt) { + // 初始化 utils_cxt 的各个成员变量 utils_cxt->suffix_char = 0; utils_cxt->suffix_collation = 0; utils_cxt->test_err_type = 0; @@ -407,6 +410,7 @@ static void knl_u_utils_init(knl_u_utils_context* utils_cxt) utils_cxt->GUC_check_errhint_string = NULL; utils_cxt->set_params_htab = NULL; utils_cxt->sync_guc_variables = NULL; + for (int strategy = 0; strategy < MAX_GUC_ATTR; strategy++) { utils_cxt->ConfigureNamesBool[strategy] = NULL; utils_cxt->ConfigureNamesInt[strategy] = NULL; @@ -415,6 +419,7 @@ static void knl_u_utils_init(knl_u_utils_context* utils_cxt) utils_cxt->ConfigureNamesString[strategy] = NULL; utils_cxt->ConfigureNamesEnum[strategy] = NULL; } + utils_cxt->guc_dirty = false; utils_cxt->reporting_enabled = false; utils_cxt->GUCNestLevel = 0; @@ -469,6 +474,7 @@ static void knl_u_utils_init(knl_u_utils_context* utils_cxt) } static void knl_u_security_init(knl_u_security_context* sec_cxt) { + // 初始化安全上下文结构 sec_cxt 的各个成员变量 sec_cxt->last_roleid = InvalidOid; sec_cxt->last_roleid_is_super = false; sec_cxt->last_roleid_is_sysdba = false; @@ -482,177 +488,190 @@ static void knl_u_security_init(knl_u_security_context* sec_cxt) { static void knl_u_streaming_init(knl_u_streaming_context* streaming_cxt) { + // 初始化流复制上下文结构 streaming_cxt 的各个成员变量 streaming_cxt->gather_session = false; streaming_cxt->streaming_ddl_session = false; } static void knl_u_mb_init(knl_u_mb_context* mb_cxt) { - mb_cxt->insertValuesBind_compatible_illegal_chars = false; - mb_cxt->ConvProcList = NIL; /* List of ConvProcInfo */ - mb_cxt->ToServerConvProc = NULL; - mb_cxt->ToClientConvProc = NULL; - mb_cxt->ClientEncoding = &pg_enc2name_tbl[PG_SQL_ASCII]; - mb_cxt->DatabaseEncoding = &pg_enc2name_tbl[PG_SQL_ASCII]; - mb_cxt->PlatformEncoding = NULL; - mb_cxt->backend_startup_complete = false; - mb_cxt->pending_client_encoding = PG_SQL_ASCII; + // 初始化多字节编码上下文结构 mb_cxt 的各个成员变量 + mb_cxt->insertValuesBind_compatible_illegal_chars = false; // 是否允许插入与绑定参数不兼容的非法字符 + mb_cxt->ConvProcList = NIL; /* ConvProcInfo序列 */ // 编码转换处理程序列表 + mb_cxt->ToServerConvProc = NULL; // 用于服务器端的编码转换处理程序 + mb_cxt->ToClientConvProc = NULL; // 用于客户端的编码转换处理程序 + mb_cxt->ClientEncoding = &pg_enc2name_tbl[PG_SQL_ASCII]; // 客户端编码 + mb_cxt->DatabaseEncoding = &pg_enc2name_tbl[PG_SQL_ASCII]; // 数据库编码 + mb_cxt->PlatformEncoding = NULL; // 平台编码 + mb_cxt->backend_startup_complete = false; // 后端启动是否完成 + mb_cxt->pending_client_encoding = PG_SQL_ASCII; // 待处理的客户端编码 } static void knl_u_plancache_init(knl_u_plancache_context* pcache_cxt) { - pcache_cxt->first_saved_plan = NULL; - pcache_cxt->ungpc_saved_plan = NULL; - pcache_cxt->query_has_params = false; - pcache_cxt->prepared_queries = NULL; - pcache_cxt->lightproxy_objs = NULL; - pcache_cxt->stmt_lightproxy_htab = NULL; - pcache_cxt->unnamed_gpc_lp = NULL; - pcache_cxt->datanode_queries = NULL; - pcache_cxt->unnamed_stmt_psrc = NULL; - pcache_cxt->cur_stmt_psrc = NULL; - pcache_cxt->private_refcount = 0; + // 初始化计划缓存上下文结构 pcache_cxt 的各个成员变量 + pcache_cxt->first_saved_plan = NULL; // 第一个已保存的计划 + pcache_cxt->ungpc_saved_plan = NULL; // 未命名的通用计划缓存中的已保存计划 + pcache_cxt->query_has_params = false; // 查询是否包含参数 + pcache_cxt->prepared_queries = NULL; // 已准备的查询列表 + pcache_cxt->lightproxy_objs = NULL; // 轻量级代理对象列表 + pcache_cxt->stmt_lightproxy_htab = NULL; // 语句轻量级代理哈希表 + pcache_cxt->unnamed_gpc_lp = NULL; // 未命名的通用计划缓存中的轻量级代理 + pcache_cxt->datanode_queries = NULL; // 数据节点查询列表 + pcache_cxt->unnamed_stmt_psrc = NULL; // 未命名语句的计划源 + pcache_cxt->cur_stmt_psrc = NULL; // 当前语句的计划源 + pcache_cxt->private_refcount = 0; // 私有引用计数 - pcache_cxt->cur_stmt_name = NULL; - pcache_cxt->gpc_in_ddl = false; - pcache_cxt->gpc_remote_msg = false; - pcache_cxt->gpc_first_send = true; - pcache_cxt->gpc_in_try_store = false; - pcache_cxt->gpc_in_batch = false; + pcache_cxt->cur_stmt_name = NULL; // 当前语句的名称 + pcache_cxt->gpc_in_ddl = false; // 是否在DDL语句中 + pcache_cxt->gpc_remote_msg = false; // 是否为远程消息 + pcache_cxt->gpc_first_send = true; // 是否首次发送 + pcache_cxt->gpc_in_try_store = false; // 是否在尝试存储 + pcache_cxt->gpc_in_batch = false; // 是否在批处理中 } static void knl_u_typecache_init(knl_u_typecache_context* tycache_cxt) { - tycache_cxt->TypeCacheHash = NULL; - tycache_cxt->RecordCacheHash = NULL; - tycache_cxt->RecordCacheArray = NULL; - tycache_cxt->RecordCacheArrayLen = 0; - tycache_cxt->NextRecordTypmod = 0; + // 初始化类型缓存上下文结构 tycache_cxt 的各个成员变量 + tycache_cxt->TypeCacheHash = NULL; // 类型缓存哈希表 + tycache_cxt->RecordCacheHash = NULL; // 记录缓存哈希表 + tycache_cxt->RecordCacheArray = NULL; // 记录缓存数组 + tycache_cxt->RecordCacheArrayLen = 0; // 记录缓存数组长度 + tycache_cxt->NextRecordTypmod = 0; // 下一个记录类型修改标识 } static void knl_u_tscache_init(knl_u_tscache_context* tscache_cxt) { - tscache_cxt->TSParserCacheHash = NULL; - tscache_cxt->lastUsedParser = NULL; - tscache_cxt->TSDictionaryCacheHash = NULL; - tscache_cxt->lastUsedDictionary = NULL; - tscache_cxt->TSConfigCacheHash = NULL; - tscache_cxt->lastUsedConfig = NULL; - tscache_cxt->TSCurrentConfigCache = InvalidOid; + // 初始化文本搜索缓存上下文结构 tscache_cxt 的各个成员变量 + tscache_cxt->TSParserCacheHash = NULL; // 文本搜索解析器缓存哈希表 + tscache_cxt->lastUsedParser = NULL; // 最后使用的解析器 + tscache_cxt->TSDictionaryCacheHash = NULL; // 文本搜索字典缓存哈希表 + tscache_cxt->lastUsedDictionary = NULL; // 最后使用的字典 + tscache_cxt->TSConfigCacheHash = NULL; // 文本搜索配置缓存哈希表 + tscache_cxt->lastUsedConfig = NULL; // 最后使用的配置 + tscache_cxt->TSCurrentConfigCache = InvalidOid; // 当前文本搜索配置缓存 } static void knl_u_misc_init(knl_misc_context* misc_cxt) { - misc_cxt->Mode = InitProcessing; - misc_cxt->AuthenticatedUserId = InvalidOid; - misc_cxt->SessionUserId = InvalidOid; - misc_cxt->OuterUserId = InvalidOid; - misc_cxt->CurrentUserId = InvalidOid; - misc_cxt->CurrentUserName = NULL; - misc_cxt->current_logic_cluster = InvalidOid; - misc_cxt->current_nodegroup_mode = NG_UNKNOWN; - misc_cxt->nodegroup_callback_registered = false; - misc_cxt->Pseudo_CurrentUserId = NULL; - misc_cxt->AuthenticatedUserIsSuperuser = false; - misc_cxt->SessionUserIsSuperuser = false; - misc_cxt->SecurityRestrictionContext = 0; - misc_cxt->SetRoleIsActive = false; - misc_cxt->process_shared_preload_libraries_in_progress = false; - misc_cxt->authentication_finished = false; + // 初始化杂项上下文结构 misc_cxt 的各个成员变量 + misc_cxt->Mode = InitProcessing; // 处理模式 + misc_cxt->AuthenticatedUserId = InvalidOid; // 认证用户的OID + misc_cxt->SessionUserId = InvalidOid; // 会话用户的OID + misc_cxt->OuterUserId = InvalidOid; // 外部用户的OID + misc_cxt->CurrentUserId = InvalidOid; // 当前用户的OID + misc_cxt->CurrentUserName = NULL; // 当前用户的名称 + misc_cxt->current_logic_cluster = InvalidOid; // 当前逻辑集群的OID + misc_cxt->current_nodegroup_mode = NG_UNKNOWN; // 当前节点组模式 + misc_cxt->nodegroup_callback_registered = false; // 节点组回调是否已注册 + misc_cxt->Pseudo_CurrentUserId = NULL; // 伪装的当前用户OID + misc_cxt->AuthenticatedUserIsSuperuser = false; // 认证用户是否是超级用户 + misc_cxt->SessionUserIsSuperuser = false; // 会话用户是否是超级用户 + misc_cxt->SecurityRestrictionContext = 0; // 安全约束上下文 + misc_cxt->SetRoleIsActive = false; // 是否处于设置角色状态 + misc_cxt->process_shared_preload_libraries_in_progress = false; // 共享预加载库是否正在进行中 + misc_cxt->authentication_finished = false; // 认证是否已完成 } static void knl_u_postgres_init(knl_u_postgres_context* postgres_cxt) { - postgres_cxt->doing_extended_query_message = false; - postgres_cxt->ignore_till_sync = false; + // 初始化 PostgreSQL 上下文结构 postgres_cxt 的各个成员变量 + postgres_cxt->doing_extended_query_message = false; // 是否正在处理扩展查询消息 + postgres_cxt->ignore_till_sync = false; // 是否忽略直到同步 } static void knl_u_proc_init(knl_u_proc_context* proc_cxt) { - proc_cxt->MyProcPort = NULL; - proc_cxt->MyRoleId = InvalidOid; - proc_cxt->MyDatabaseId = InvalidOid; - proc_cxt->MyDatabaseTableSpace = InvalidOid; - proc_cxt->DatabasePath = NULL; - proc_cxt->Isredisworker = false; - proc_cxt->IsInnerMaintenanceTools = false; - proc_cxt->clientIsGsrewind = false; - proc_cxt->clientIsGsredis = false; - proc_cxt->clientIsGsdump = false; - proc_cxt->clientIsGsCtl = false; - proc_cxt->clientIsGsroach = false; - proc_cxt->IsBinaryUpgrade = false; - proc_cxt->IsWLMWhiteList = false; - proc_cxt->sessionBackupState = SESSION_BACKUP_NONE; - proc_cxt->LabelFile = NULL; - proc_cxt->TblspcMapFile = NULL; - proc_cxt->registerAbortBackupHandlerdone = false; - proc_cxt->gsRewindAddCount = false; - proc_cxt->PassConnLimit = false; - proc_cxt->sessionBackupState = SESSION_BACKUP_NONE; - proc_cxt->registerExclusiveHandlerdone = false; + // 初始化进程上下文结构 proc_cxt 的各个成员变量 + proc_cxt->MyProcPort = NULL; // 当前进程的端口信息 + proc_cxt->MyRoleId = InvalidOid; // 当前进程的角色OID + proc_cxt->MyDatabaseId = InvalidOid; // 当前进程的数据库OID + proc_cxt->MyDatabaseTableSpace = InvalidOid; // 当前进程的数据库表空间OID + proc_cxt->DatabasePath = NULL; // 数据库路径 + proc_cxt->Isredisworker = false; // 是否为 Redis 工作进程 + proc_cxt->IsInnerMaintenanceTools = false; // 是否为内部维护工具 + proc_cxt->clientIsGsrewind = false; // 客户端是否是 gsrewind + proc_cxt->clientIsGsredis = false; // 客户端是否是 gsredis + proc_cxt->clientIsGsdump = false; // 客户端是否是 gsdump + proc_cxt->clientIsGsCtl = false; // 客户端是否是 gs_ctl + proc_cxt->clientIsGsroach = false; // 客户端是否是 gs_roach + proc_cxt->IsBinaryUpgrade = false; // 是否为二进制升级 + proc_cxt->IsWLMWhiteList = false; // 是否在 WLM 白名单内 + proc_cxt->sessionBackupState = SESSION_BACKUP_NONE; // 会话备份状态 + proc_cxt->LabelFile = NULL; // 标签文件 + proc_cxt->TblspcMapFile = NULL; // 表空间映射文件 + proc_cxt->registerAbortBackupHandlerdone = false; // 是否已注册备份中止处理程序 + proc_cxt->gsRewindAddCount = false; // gsrewind 添加计数 + proc_cxt->PassConnLimit = false; // 是否绕过连接限制 + proc_cxt->sessionBackupState = SESSION_BACKUP_NONE; // 会话备份状态 + proc_cxt->registerExclusiveHandlerdone = false; // 是否已注册排它处理程序 } static void knl_u_time_init(knl_u_time_context* time_cxt) { - time_cxt->DateStyle = USE_ISO_DATES; - time_cxt->DateOrder = DATEORDER_MDY; - time_cxt->HasCTZSet = false; - time_cxt->CTimeZone = 0; + // 初始化时间上下文结构 time_cxt 的各个成员变量 + time_cxt->DateStyle = USE_ISO_DATES; // 日期样式 + time_cxt->DateOrder = DATEORDER_MDY; // 日期顺序 + time_cxt->HasCTZSet = false; // 是否已设置了时区 + time_cxt->CTimeZone = 0; // 当前时区 - time_cxt->sz_timezone_tktbl = 0; - time_cxt->timezone_tktbl = NULL; + time_cxt->sz_timezone_tktbl = 0; // 时区令牌表的大小 + time_cxt->timezone_tktbl = NULL; // 时区令牌表 errno_t rc; - rc = memset_s(time_cxt->datecache, sizeof(datetkn*) * MAXDATEFIELDS, 0, sizeof(datetkn*) * MAXDATEFIELDS); + rc = memset_s(time_cxt->datecache, sizeof(datetkn*) * MAXDATEFIELDS, 0, sizeof(datetkn*) * MAXDATEFIELDS); // 日期令牌缓存 securec_check(rc, "\0", "\0"); - rc = memset_s(time_cxt->deltacache, sizeof(datetkn*) * MAXDATEFIELDS, 0, sizeof(datetkn*) * MAXDATEFIELDS); + rc = memset_s(time_cxt->deltacache, sizeof(datetkn*) * MAXDATEFIELDS, 0, sizeof(datetkn*) * MAXDATEFIELDS); // 时间间隔令牌缓存 securec_check(rc, "\0", "\0"); - time_cxt->timezone_cache = NULL; + time_cxt->timezone_cache = NULL; // 时区缓存 } -void knl_u_commands_init(knl_u_commands_context* cmd_cxt) +static void knl_u_proc_init(knl_u_proc_context* proc_cxt) { - cmd_cxt->TableSpaceUsageArray = NULL; - cmd_cxt->isUnderCreateForeignTable = false; - cmd_cxt->CurrentExtensionObject = InvalidOid; - cmd_cxt->PendingLibraryDeletes = NIL; - - cmd_cxt->seqtab = NULL; - cmd_cxt->last_used_seq = NULL; - - cmd_cxt->TypeCreateType = '\0'; - - cmd_cxt->label_provider_list = NIL; - cmd_cxt->bulkload_compatible_illegal_chars = false; - cmd_cxt->bulkload_copy_state = NULL; - cmd_cxt->dest_encoding_for_copytofile = -1; - cmd_cxt->need_transcoding_for_copytofile = false; - cmd_cxt->OBSParserContext = NULL; - cmd_cxt->on_commits = NIL; - cmd_cxt->topRelatationIsInMyTempSession = false; - cmd_cxt->bogus_marker = {(NodeTag)0}; + // 初始化进程上下文结构 proc_cxt 的各个成员变量 + proc_cxt->MyProcPort = NULL; // 当前进程的端口信息 + proc_cxt->MyRoleId = InvalidOid; // 当前进程的角色OID + proc_cxt->MyDatabaseId = InvalidOid; // 当前进程的数据库OID + proc_cxt->MyDatabaseTableSpace = InvalidOid; // 当前进程的数据库表空间OID + proc_cxt->DatabasePath = NULL; // 数据库路径 + proc_cxt->Isredisworker = false; // 是否为 Redis 工作进程 + proc_cxt->IsInnerMaintenanceTools = false; // 是否为内部维护工具 + proc_cxt->clientIsGsrewind = false; // 客户端是否是 gsrewind + proc_cxt->clientIsGsredis = false; // 客户端是否是 gsredis + proc_cxt->clientIsGsdump = false; // 客户端是否是 gsdump + proc_cxt->clientIsGsCtl = false; // 客户端是否是 gs_ctl + proc_cxt->clientIsGsroach = false; // 客户端是否是 gs_roach + proc_cxt->IsBinaryUpgrade = false; // 是否为二进制升级 + proc_cxt->IsWLMWhiteList = false; // 是否在 WLM 白名单内 + proc_cxt->sessionBackupState = SESSION_BACKUP_NONE; // 会话备份状态 + proc_cxt->LabelFile = NULL; // 标签文件 + proc_cxt->TblspcMapFile = NULL; // 表空间映射文件 + proc_cxt->registerAbortBackupHandlerdone = false; // 是否已注册备份中止处理程序 + proc_cxt->gsRewindAddCount = false; // gsrewind 添加计数 + proc_cxt->PassConnLimit = false; // 是否绕过连接限制 + proc_cxt->sessionBackupState = SESSION_BACKUP_NONE; // 会话备份状态 + proc_cxt->registerExclusiveHandlerdone = false; // 是否已注册排它处理程序 } -static void knl_u_contrib_init(knl_u_contrib_context* contrib_cxt) +static void knl_u_time_init(knl_u_time_context* time_cxt) { + // 初始化时间上下文结构 time_cxt 的各个成员变量 + time_cxt->DateStyle = USE_ISO_DATES; // 日期样式 + time_cxt->DateOrder = DATEORDER_MDY; // 日期顺序 + time_cxt->HasCTZSet = false; // 是否已设置了时区 + time_cxt->CTimeZone = 0; // 当前时区 + + time_cxt->sz_timezone_tktbl = 0; // 时区令牌表的大小 + time_cxt->timezone_tktbl = NULL; // 时区令牌表 errno_t rc; - - contrib_cxt->cursor_number = 0; - contrib_cxt->current_context_id = 0; - contrib_cxt->file_format = DFS_INVALID; - contrib_cxt->file_number = 0; - -#define MAX_SLOTS 50 /* supports 50 files */ - contrib_cxt->slots = (FileSlot*)palloc0(sizeof(FileSlot) * MAX_SLOTS); - contrib_cxt->slotid = 0; - /* mark the default value as not revoke set_maxline_size */ - const int default_max_line_size = 1024; - contrib_cxt->max_linesize = default_max_line_size; - contrib_cxt->cur_directory = (char*)palloc0(sizeof(char) * MAXPGPATH); - rc = memset_s(contrib_cxt->cur_directory, MAXPGPATH, 0, MAXPGPATH); + rc = memset_s(time_cxt->datecache, sizeof(datetkn*) * MAXDATEFIELDS, 0, sizeof(datetkn*) * MAXDATEFIELDS); // 日期令牌缓存 securec_check(rc, "\0", "\0"); + + rc = memset_s(time_cxt->deltacache, sizeof(datetkn*) * MAXDATEFIELDS, 0, sizeof(datetkn*) * MAXDATEFIELDS); // 时间间隔令牌缓存 + securec_check(rc, "\0", "\0"); + + time_cxt->timezone_cache = NULL; // 时区缓存 } static void knl_u_upgrade_init(knl_u_upgrade_context* upg_cxt) @@ -791,255 +810,250 @@ static void knl_u_upgrade_init(knl_u_upgrade_context* upg_cxt) static void knl_u_plpgsql_init(knl_u_plpgsql_context* plsql_cxt) { - plsql_cxt->inited = false; - plsql_cxt->plpgsql_HashTable = NULL; - plsql_cxt->plpgsql_pkg_HashTable = NULL; - plsql_cxt->plpgsql_dlist_objects = NULL; - plsql_cxt->plpgsqlpkg_dlist_objects = NULL; - plsql_cxt->compile_status = NONE_STATUS; - plsql_cxt->curr_compile_context = NULL; - plsql_cxt->compile_context_list = NIL; - plsql_cxt->plpgsql_IndexErrorVariable = 0; - plsql_cxt->shared_simple_eval_resowner = NULL; - plsql_cxt->simple_eval_estate = NULL; - plsql_cxt->simple_econtext_stack = NULL; - plsql_cxt->context_array = NIL; - plsql_cxt->plugin_ptr = NULL; - plsql_cxt->rendezvousHash = NULL; - plsql_cxt->debug_proc_htbl = NULL; - plsql_cxt->debug_client = NULL; - plsql_cxt->has_step_into = false; - plsql_cxt->cur_debug_server = NULL; - plsql_cxt->dbe_output_buffer_limit = DEFAULT_DBE_BUFFER_LIMIT; - plsql_cxt->is_delete_function = false; - plsql_cxt->have_error = false; - plsql_cxt->client_info = NULL; - pthread_mutex_init(&plsql_cxt->client_info_lock, NULL); - plsql_cxt->sess_cxt_htab = NULL; - plsql_cxt->have_error = false; - plsql_cxt->stp_savepoint_cnt = 0; - plsql_cxt->nextStackEntryId = 0; - plsql_cxt->spi_xact_context = NULL; - plsql_cxt->package_as_line = 0; - plsql_cxt->procedure_start_line = 0; - plsql_cxt->insertError = false; - plsql_cxt->errorList = NULL; - plsql_cxt->plpgsql_yylloc = 0; - plsql_cxt->rawParsePackageFunction = false; - plsql_cxt->isCreateFunction = false; - plsql_cxt->need_pkg_dependencies = false; - plsql_cxt->pkg_dependencies = NIL; - plsql_cxt->func_tableof_index = NIL; - plsql_cxt->pass_func_tupdesc = NULL; - plsql_cxt->portal_depth = 0; - plsql_cxt->auto_parent_session_pkgs = NULL; - plsql_cxt->not_found_parent_session_pkgs = false; - plsql_cxt->storedPortals = NIL; - plsql_cxt->portalContext = NIL; - plsql_cxt->call_after_auto = false; - plsql_cxt->parent_session_id = 0; - plsql_cxt->parent_thread_id = 0; - plsql_cxt->parent_context = NULL; - plsql_cxt->is_package_instantiation = false; - plsql_cxt->cur_exception_cxt = NULL; - plsql_cxt->pragma_autonomous = false; - plsql_cxt->ActiveLobToastOid = InvalidOid; - plsql_cxt->is_insert_gs_source = false; + plsql_cxt->inited = false; // 是否已初始化 + plsql_cxt->plpgsql_HashTable = NULL; // PL/pgSQL 函数哈希表 + plsql_cxt->plpgsql_pkg_HashTable = NULL; // PL/pgSQL 包哈希表 + plsql_cxt->plpgsql_dlist_objects = NULL; // PL/pgSQL 函数双链表对象 + plsql_cxt->plpgsqlpkg_dlist_objects = NULL; // PL/pgSQL 包双链表对象 + plsql_cxt->compile_status = NONE_STATUS; // 编译状态 + plsql_cxt->curr_compile_context = NULL; // 当前编译上下文 + plsql_cxt->compile_context_list = NIL; // 编译上下文列表 + plsql_cxt->plpgsql_IndexErrorVariable = 0; // PL/pgSQL 错误变量索引 + plsql_cxt->shared_simple_eval_resowner = NULL; // 共享的简单评估资源拥有者 + plsql_cxt->simple_eval_estate = NULL; // 简单评估执行状态 + plsql_cxt->simple_econtext_stack = NULL; // 简单评估执行上下文堆栈 + plsql_cxt->context_array = NIL; // 上下文数组 + plsql_cxt->plugin_ptr = NULL; // 插件指针 + plsql_cxt->rendezvousHash = NULL; // 会合哈希表 + plsql_cxt->debug_proc_htbl = NULL; // 调试过程哈希表 + plsql_cxt->debug_client = NULL; // 调试客户端 + plsql_cxt->has_step_into = false; // 是否有步进 + plsql_cxt->cur_debug_server = NULL; // 当前调试服务器 + plsql_cxt->dbe_output_buffer_limit = DEFAULT_DBE_BUFFER_LIMIT; // 调试输出缓冲区限制 + plsql_cxt->is_delete_function = false; // 是否是删除函数 + plsql_cxt->have_error = false; // 是否有错误 + plsql_cxt->client_info = NULL; // 客户端信息 + pthread_mutex_init(&plsql_cxt->client_info_lock, NULL); // 客户端信息互斥锁初始化 + plsql_cxt->sess_cxt_htab = NULL; // 会话上下文哈希表 + plsql_cxt->have_error = false; // 是否有错误 + plsql_cxt->stp_savepoint_cnt = 0; // STP 保存点计数 + plsql_cxt->nextStackEntryId = 0; // 下一个堆栈条目ID + plsql_cxt->spi_xact_context = NULL; // SPI 事务上下文 + plsql_cxt->package_as_line = 0; // 包作为行 + plsql_cxt->procedure_start_line = 0; // 过程开始行 + plsql_cxt->insertError = false; // 是否插入错误 + plsql_cxt->errorList = NULL; // 错误列表 + plsql_cxt->plpgsql_yylloc = 0; // PL/pgSQL 词法位置 + plsql_cxt->rawParsePackageFunction = false; // 是否原始解析包函数 + plsql_cxt->isCreateFunction = false; // 是否是创建函数 + plsql_cxt->need_pkg_dependencies = false; // 是否需要包依赖项 + plsql_cxt->pkg_dependencies = NIL; // 包依赖项列表 + plsql_cxt->func_tableof_index = NIL; // 函数 TABLEOF 索引列表 + plsql_cxt->pass_func_tupdesc = NULL; // 传递函数元组描述 + plsql_cxt->portal_depth = 0; // 门户深度 + plsql_cxt->auto_parent_session_pkgs = NULL; // 自动父会话包 + plsql_cxt->not_found_parent_session_pkgs = false; // 未找到父会话包 + plsql_cxt->storedPortals = NIL; // 存储的门户列表 + plsql_cxt->portalContext = NIL; // 门户上下文列表 + plsql_cxt->call_after_auto = false; // 自动执行后调用 + plsql_cxt->parent_session_id = 0; // 父会话ID + plsql_cxt->parent_thread_id = 0; // 父线程ID + plsql_cxt->parent_context = NULL; // 父上下文 + plsql_cxt->is_package_instantiation = false; // 是否是包实例化 + plsql_cxt->cur_exception_cxt = NULL; // 当前异常上下文 + plsql_cxt->pragma_autonomous = false; // PRAGMA AUTONOMOUS + plsql_cxt->ActiveLobToastOid = InvalidOid; // 活动 LOB TOAST OID + plsql_cxt->is_insert_gs_source = false; // 是否插入 GS 源 } static void knl_u_stat_init(knl_u_stat_context* stat_cxt) { - Size size = 0; - errno_t rc = EOK; + Size size = 0; // 用于存储内存大小的变量 + errno_t rc = EOK; // 用于存储错误码的变量 - stat_cxt->pgstat_stat_filename = NULL; - stat_cxt->pgstat_stat_tmpname = NULL; - stat_cxt->pgStatDBHash = NULL; - stat_cxt->pgStatTabList = NULL; + stat_cxt->pgstat_stat_filename = NULL; // 统计信息文件名 + stat_cxt->pgstat_stat_tmpname = NULL; // 统计信息临时文件名 + stat_cxt->pgStatDBHash = NULL; // 数据库统计信息哈希表 + stat_cxt->pgStatTabList = NULL; // 统计表列表 - stat_cxt->BgWriterStats = (PgStat_MsgBgWriter*)palloc0(sizeof(PgStat_MsgBgWriter)); - stat_cxt->globalStats = (PgStat_GlobalStats*)palloc0(sizeof(PgStat_GlobalStats)); - stat_cxt->pgStatTabHash = NULL; - stat_cxt->pgStatTabHashContext = NULL; - stat_cxt->pgStatFunctions = NULL; - stat_cxt->have_function_stats = false; - stat_cxt->pgStatXactStack = NULL; - stat_cxt->pgStatXactCommit = 0; - stat_cxt->pgStatXactRollback = 0; - stat_cxt->pgStatBlockReadTime = 0; - stat_cxt->pgStatBlockWriteTime = 0; - stat_cxt->localBackendStatusTable = NULL; - stat_cxt->localNumBackends = 0; - stat_cxt->analyzeCheckHash = NULL; - stat_cxt->pgStatRunningInCollector = false; + stat_cxt->BgWriterStats = (PgStat_MsgBgWriter*)palloc0(sizeof(PgStat_MsgBgWriter)); // 后台写进程统计信息 + stat_cxt->globalStats = (PgStat_GlobalStats*)palloc0(sizeof(PgStat_GlobalStats)); // 全局统计信息 + stat_cxt->pgStatTabHash = NULL; // 统计表哈希表 + stat_cxt->pgStatTabHashContext = NULL; // 统计表哈希表上下文 + stat_cxt->pgStatFunctions = NULL; // 统计函数信息 + stat_cxt->have_function_stats = false; // 是否有函数统计信息 + stat_cxt->pgStatXactStack = NULL; // 事务统计堆栈 + stat_cxt->pgStatXactCommit = 0; // 提交事务计数 + stat_cxt->pgStatXactRollback = 0; // 回滚事务计数 + stat_cxt->pgStatBlockReadTime = 0; // 块读取时间 + stat_cxt->pgStatBlockWriteTime = 0; // 块写入时间 + stat_cxt->localBackendStatusTable = NULL; // 本地后台进程状态表 + stat_cxt->localNumBackends = 0; // 本地后台进程数 + stat_cxt->analyzeCheckHash = NULL; // 分析检查哈希表 + stat_cxt->pgStatRunningInCollector = false; // 统计信息是否在收集器中运行 - stat_cxt->last_report = 0; - stat_cxt->isTopLevelPlSql = true; + stat_cxt->last_report = 0; // 上次报告时间 + stat_cxt->isTopLevelPlSql = true; // 是否顶层 PL/SQL - stat_cxt->pgStatCollectThdStatusContext = NULL; - stat_cxt->pgStatLocalContext = NULL; + stat_cxt->pgStatCollectThdStatusContext = NULL; // 统计信息收集线程状态上下文 + stat_cxt->pgStatLocalContext = NULL; // 统计信息本地上下文 - size = sizeof(NumericValue) * TOTAL_OS_RUN_INFO_TYPES; - stat_cxt->osStatDataArray = (NumericValue*)palloc0(size); + size = sizeof(NumericValue) * TOTAL_OS_RUN_INFO_TYPES; // 总的操作系统运行信息类型 + stat_cxt->osStatDataArray = (NumericValue*)palloc0(size); // 操作系统统计信息数组 - size = sizeof(OSRunInfoDesc) * TOTAL_OS_RUN_INFO_TYPES; - stat_cxt->osStatDescArray = (OSRunInfoDesc*)palloc0(size); - rc = memcpy_s(stat_cxt->osStatDescArray, size, osStatDescArrayOrg, size); + size = sizeof(OSRunInfoDesc) * TOTAL_OS_RUN_INFO_TYPES; // 操作系统运行信息描述的大小 + stat_cxt->osStatDescArray = (OSRunInfoDesc*)palloc0(size); // 操作系统统计信息描述数组 + rc = memcpy_s(stat_cxt->osStatDescArray, size, osStatDescArrayOrg, size); // 复制操作系统统计信息描述 securec_check(rc, "\0", "\0"); - size = sizeof(int64) * TOTAL_TIME_INFO_TYPES; - stat_cxt->localTimeInfoArray = (int64*)palloc0(size); - stat_cxt->localNetInfo = (uint64*)palloc0(sizeof(uint64) * TOTAL_NET_INFO_TYPES); + size = sizeof(int64) * TOTAL_TIME_INFO_TYPES; // 总的时间信息类型 + stat_cxt->localTimeInfoArray = (int64*)palloc0(size); // 本地时间信息数组 + stat_cxt->localNetInfo = (uint64*)palloc0(sizeof(uint64) * TOTAL_NET_INFO_TYPES); // 本地网络信息数组 - stat_cxt->trackedMemChunks = 0; - stat_cxt->trackedBytes = 0; - stat_cxt->hotkeySessContext = NULL; - stat_cxt->hotkeyCandidates = NULL; + stat_cxt->trackedMemChunks = 0; // 跟踪的内存块数 + stat_cxt->trackedBytes = 0; // 跟踪的字节数 + stat_cxt->hotkeySessContext = NULL; // 热键会话上下文 + stat_cxt->hotkeyCandidates = NULL; // 热键候选项 } static void knl_u_storage_init(knl_u_storage_context* storage_cxt) { - storage_cxt->target_prefetch_pages = 0; - storage_cxt->session_timeout_active = false; - storage_cxt->session_fin_time = 0; + storage_cxt->target_prefetch_pages = 0; // 目标预取页数 + storage_cxt->session_timeout_active = false; // 会话超时是否激活 + storage_cxt->session_fin_time = 0; // 会话结束时间 /* var in fd.cpp */ - storage_cxt->nfile = 0; - storage_cxt->have_xact_temporary_files = false; - storage_cxt->temporary_files_size = 0; - storage_cxt->numAllocatedDescs = 0; - storage_cxt->maxAllocatedDescs = 0; - storage_cxt->allocatedDescs = NULL; - storage_cxt->tempFileCounter = 0; - storage_cxt->tempTableSpaces = NULL; - storage_cxt->numTempTableSpaces = -1; - storage_cxt->nextTempTableSpace = 0; - storage_cxt->AsyncSubmitIOCount = 0; - storage_cxt->VfdCache = NULL; - storage_cxt->SizeVfdCache = 0; + storage_cxt->nfile = 0; // 文件数量 + storage_cxt->have_xact_temporary_files = false; // 是否有事务临时文件 + storage_cxt->temporary_files_size = 0; // 临时文件大小 + storage_cxt->numAllocatedDescs = 0; // 已分配描述符数 + storage_cxt->maxAllocatedDescs = 0; // 最大已分配描述符数 + storage_cxt->allocatedDescs = NULL; // 已分配描述符数组 + storage_cxt->tempFileCounter = 0; // 临时文件计数器 + storage_cxt->tempTableSpaces = NULL; // 临时表空间 + storage_cxt->numTempTableSpaces = -1; // 临时表空间数量 + storage_cxt->nextTempTableSpace = 0; // 下一个临时表空间 + storage_cxt->AsyncSubmitIOCount = 0; // 异步提交IO计数 + storage_cxt->VfdCache = NULL; // 虚拟文件描述符缓存 + storage_cxt->SizeVfdCache = 0; // 虚拟文件描述符缓存大小 - /* var in smgr.cpp */ - storage_cxt->SMgrRelationHash = NULL; - storage_cxt->unowned_reln = {{NULL, NULL}}; + storage_cxt->SMgrRelationHash = NULL; // 管理器关系哈希表 + storage_cxt->unowned_reln = {{NULL, NULL}}; // 未拥有的关系 - /* var in md.cpp */ - storage_cxt->MdCxt = NULL; + storage_cxt->MdCxt = NULL; // 元数据上下文 - /* var in knl_uundofile.cpp */ - storage_cxt->UndoFileCxt = NULL; + storage_cxt->UndoFileCxt = NULL; // 撤销文件上下文 - /* var in sync.cpp */ - storage_cxt->pendingUnlinks = NIL; - storage_cxt->pendingOpsCxt = NULL; - storage_cxt->sync_cycle_ctr = 0; - storage_cxt->checkpoint_cycle_ctr = 0; - storage_cxt->pendingOps = NULL; + storage_cxt->pendingUnlinks = NIL; // 待删除的链接 + storage_cxt->pendingOpsCxt = NULL; // 待处理操作上下文 + storage_cxt->sync_cycle_ctr = 0; // 同步周期计数器 + storage_cxt->checkpoint_cycle_ctr = 0; // 检查点周期计数器 + storage_cxt->pendingOps = NULL; // 待处理操作 - storage_cxt->nextLocalTransactionId = InvalidTransactionId; + storage_cxt->nextLocalTransactionId = InvalidTransactionId; // 下一个本地事务ID for (int i = 0; i < MAX_LOCKMETHOD; i++) { - storage_cxt->holdSessionLock[i] = false; + storage_cxt->holdSessionLock[i] = false; // 会话锁标志数组 } - storage_cxt->twoPhaseCommitInProgress = false; - storage_cxt->dumpHashbucketIdNum = 0; - storage_cxt->dumpHashbucketIds = NULL; + storage_cxt->twoPhaseCommitInProgress = false; // 是否正在进行两阶段提交 + storage_cxt->dumpHashbucketIdNum = 0; // 哈希桶ID数量 + storage_cxt->dumpHashbucketIds = NULL; // 哈希桶ID数组 - /* session local buffer */ - storage_cxt->NLocBuffer = 0; /* until buffers are initialized */ - storage_cxt->LocalBufferDescriptors = NULL; - storage_cxt->LocalBufferBlockPointers = NULL; - storage_cxt->LocalRefCount = NULL; - storage_cxt->nextFreeLocalBuf = 0; - storage_cxt->LocalBufHash = NULL; - storage_cxt->cur_block = NULL; - storage_cxt->next_buf_in_block = 0; - storage_cxt->num_bufs_in_block = 0; - storage_cxt->total_bufs_allocated = 0; - storage_cxt->LocalBufferContext = NULL; + storage_cxt->NLocBuffer = 0; // 本地缓冲区数量,直到缓冲区初始化完成 + storage_cxt->LocalBufferDescriptors = NULL; // 本地缓冲区描述符 + storage_cxt->LocalBufferBlockPointers = NULL; // 本地缓冲区块指针 + storage_cxt->LocalRefCount = NULL; // 本地引用计数 + storage_cxt->nextFreeLocalBuf = 0; // 下一个可用的本地缓冲区 + storage_cxt->LocalBufHash = NULL; // 本地缓冲区哈希表 + storage_cxt->cur_block = NULL; // 当前块 + storage_cxt->next_buf_in_block = 0; // 下一个块中的缓冲区 + storage_cxt->num_bufs_in_block = 0; // 块中的缓冲区数 + storage_cxt->total_bufs_allocated = 0; // 已分配的缓冲区总数 + storage_cxt->LocalBufferContext = NULL; // 本地缓冲区上下文 } static void knl_u_libpq_init(knl_u_libpq_context* libpq_cxt) { - Assert(libpq_cxt != NULL); - libpq_cxt->cookies = NULL; - libpq_cxt->cookies_size = 0; - libpq_cxt->fscxt = NULL; - libpq_cxt->server_key = (GS_UCHAR*)palloc0((CIPHER_LEN + 1) * sizeof(GS_UCHAR)); - libpq_cxt->ident_lines = NIL; - libpq_cxt->ident_line_nums = NIL; - libpq_cxt->ident_context = NULL; - libpq_cxt->IsConnFromCmAgent = false; - libpq_cxt->HasErrorAccurs = false; + Assert(libpq_cxt != NULL); // 断言:确保 libpq_cxt 不为空 + libpq_cxt->cookies = NULL; // Cookie 数据 + libpq_cxt->cookies_size = 0; // Cookie 数据大小 + libpq_cxt->fscxt = NULL; // 未使用的字段上下文 + libpq_cxt->server_key = (GS_UCHAR*)palloc0((CIPHER_LEN + 1) * sizeof(GS_UCHAR)); // 服务器密钥 + libpq_cxt->ident_lines = NIL; // 身份验证行 + libpq_cxt->ident_line_nums = NIL; // 身份验证行号 + libpq_cxt->ident_context = NULL; // 身份验证上下文 + libpq_cxt->IsConnFromCmAgent = false; // 连接是否来自 CM Agent + libpq_cxt->HasErrorAccurs = false; // 是否发生错误 #ifdef USE_SSL - libpq_cxt->ssl_loaded_verify_locations = false; - libpq_cxt->ssl_initialized = false; - libpq_cxt->SSL_server_context = NULL; + libpq_cxt->ssl_loaded_verify_locations = false; // SSL 加载验证位置 + libpq_cxt->ssl_initialized = false; // SSL 是否已初始化 + libpq_cxt->SSL_server_context = NULL; // SSL 服务器上下文 #endif } -#define BUCKET_MAP_SIZE 32 +#define BUCKET_MAP_SIZE 32 // Bucket Map 大小 static void knl_u_relcache_init(knl_u_relcache_context* relcache_cxt) { - relcache_cxt->RelationIdCache = NULL; - relcache_cxt->criticalRelcachesBuilt = false; - relcache_cxt->criticalSharedRelcachesBuilt = false; - relcache_cxt->relcacheInvalsReceived = 0L; - relcache_cxt->initFileRelationIds = NIL; - relcache_cxt->RelCacheNeedEOXActWork = false; - relcache_cxt->OpClassCache = NULL; - relcache_cxt->pgclassdesc = NULL; - relcache_cxt->pgindexdesc = NULL; - relcache_cxt->g_bucketmap_cache = NIL; - relcache_cxt->max_bucket_map_size = BUCKET_MAP_SIZE; + relcache_cxt->RelationIdCache = NULL; // 关系 ID 缓存 + relcache_cxt->criticalRelcachesBuilt = false; // 重要关系缓存是否已构建 + relcache_cxt->criticalSharedRelcachesBuilt = false; // 重要共享关系缓存是否已构建 + relcache_cxt->relcacheInvalsReceived = 0L; // 收到的关系缓存失效消息数 + relcache_cxt->initFileRelationIds = NIL; // 初始化文件关系 ID + relcache_cxt->RelCacheNeedEOXActWork = false; // 关系缓存是否需要执行结束事务工作 + relcache_cxt->OpClassCache = NULL; // 运算符类缓存 + relcache_cxt->pgclassdesc = NULL; // pg_class 描述符 + relcache_cxt->pgindexdesc = NULL; // pg_index 描述符 + relcache_cxt->g_bucketmap_cache = NIL; // 全局 Bucket Map 缓存 + relcache_cxt->max_bucket_map_size = BUCKET_MAP_SIZE; // 最大 Bucket Map 大小 - relcache_cxt->EOXactTupleDescArray = NULL; - relcache_cxt->NextEOXactTupleDescNum = 0; - relcache_cxt->EOXactTupleDescArrayLen = 0; + relcache_cxt->EOXactTupleDescArray = NULL; // 结束事务元组描述符数组 + relcache_cxt->NextEOXactTupleDescNum = 0; // 下一个结束事务元组描述符编号 + relcache_cxt->EOXactTupleDescArrayLen = 0; // 结束事务元组描述符数组长度 } static void knl_u_unique_sql_init(knl_u_unique_sql_context* unique_sql_cxt) { - Assert(unique_sql_cxt != NULL); + Assert(unique_sql_cxt != NULL); // 断言:确保 unique_sql_cxt 不为空 - unique_sql_cxt->unique_sql_id = 0; - unique_sql_cxt->unique_sql_user_id = InvalidOid; - unique_sql_cxt->unique_sql_cn_id = InvalidOid; - unique_sql_cxt->unique_sql_start_time = 0; - unique_sql_cxt->unique_sql_returned_rows_counter = 0; - unique_sql_cxt->unique_sql_soft_parse = 0; - unique_sql_cxt->unique_sql_hard_parse = 0; - unique_sql_cxt->last_stat_counter = (PgStat_TableCounts*)palloc0(sizeof(PgStat_TableCounts)); - unique_sql_cxt->current_table_counter = (PgStat_TableCounts*)palloc0(sizeof(PgStat_TableCounts)); - unique_sql_cxt->curr_single_unique_sql = NULL; - unique_sql_cxt->is_multi_unique_sql = false; - unique_sql_cxt->multi_sql_offset = 0; - unique_sql_cxt->is_top_unique_sql = false; - unique_sql_cxt->need_update_calls = true; - unique_sql_cxt->skipUniqueSQLCount = 0; - unique_sql_cxt->unique_sql_sort_instr = (unique_sql_sorthash_instr*)palloc0(sizeof(unique_sql_sorthash_instr)); - unique_sql_cxt->unique_sql_hash_instr = (unique_sql_sorthash_instr*)palloc0(sizeof(unique_sql_sorthash_instr)); - unique_sql_cxt->unique_sql_sort_instr->has_sorthash = false; - unique_sql_cxt->unique_sql_hash_instr->has_sorthash = false; - unique_sql_cxt->portal_nesting_level = 0; + unique_sql_cxt->unique_sql_id = 0; // 唯一 SQL 标识 + unique_sql_cxt->unique_sql_user_id = InvalidOid; // 用户 ID + unique_sql_cxt->unique_sql_cn_id = InvalidOid; // 连接 ID + unique_sql_cxt->unique_sql_start_time = 0; // SQL 开始时间 + unique_sql_cxt->unique_sql_returned_rows_counter = 0; // 返回的行数计数器 + unique_sql_cxt->unique_sql_soft_parse = 0; // 软解析计数 + unique_sql_cxt->unique_sql_hard_parse = 0; // 硬解析计数 + unique_sql_cxt->last_stat_counter = (PgStat_TableCounts*)palloc0(sizeof(PgStat_TableCounts)); // 上次统计计数 + unique_sql_cxt->current_table_counter = (PgStat_TableCounts*)palloc0(sizeof(PgStat_TableCounts)); // 当前表计数 + unique_sql_cxt->curr_single_unique_sql = NULL; // 当前单个唯一 SQL + unique_sql_cxt->is_multi_unique_sql = false; // 是否为多个唯一 SQL + unique_sql_cxt->multi_sql_offset = 0; // 多个唯一 SQL 的偏移量 + unique_sql_cxt->is_top_unique_sql = false; // 是否为顶级唯一 SQL + unique_sql_cxt->need_update_calls = true; // 是否需要更新调用次数 + unique_sql_cxt->skipUniqueSQLCount = 0; // 跳过的唯一 SQL 计数 + unique_sql_cxt->unique_sql_sort_instr = (unique_sql_sorthash_instr*)palloc0(sizeof(unique_sql_sorthash_instr)); // 唯一 SQL 排序指令 + unique_sql_cxt->unique_sql_hash_instr = (unique_sql_sorthash_instr*)palloc0(sizeof(unique_sql_sorthash_instr)); // 唯一 SQL 哈希指令 + unique_sql_cxt->unique_sql_sort_instr->has_sorthash = false; // 排序指令是否存在 + unique_sql_cxt->unique_sql_hash_instr->has_sorthash = false; // 哈希指令是否存在 + unique_sql_cxt->portal_nesting_level = 0; // Portal 嵌套级别 #ifndef ENABLE_MULTIPLE_NODES - unique_sql_cxt->unique_sql_text = NULL; + unique_sql_cxt->unique_sql_text = NULL; // 唯一 SQL 文本 #endif } static void knl_u_percentile_init(knl_u_percentile_context* percentile_cxt) { - Assert(percentile_cxt != NULL); - percentile_cxt->LocalsqlRT = NULL; - percentile_cxt->LocalCounter = 0; + Assert(percentile_cxt != NULL); // 断言:确保 percentile_cxt 不为空 + percentile_cxt->LocalsqlRT = NULL; // 本地 SQL 响应时间 + percentile_cxt->LocalCounter = 0; // 本地计数器 } static void knl_u_slow_query_init(knl_u_slow_query_context* slow_query_cxt) { - Assert(slow_query_cxt != NULL); - slow_query_cxt->slow_query.localTimeInfoArray = (int64*)palloc0(sizeof(int64) * TOTAL_TIME_INFO_TYPES); - slow_query_cxt->slow_query.n_returned_rows = 0; - slow_query_cxt->slow_query.current_table_counter = (PgStat_TableCounts*)palloc0(sizeof(PgStat_TableCounts)); - slow_query_cxt->slow_query.unique_sql_id = 0; + Assert(slow_query_cxt != NULL); // 断言:确保 slow_query_cxt 不为空 + slow_query_cxt->slow_query.localTimeInfoArray = (int64*)palloc0(sizeof(int64) * TOTAL_TIME_INFO_TYPES); // 本地时间信息数组 + slow_query_cxt->slow_query.n_returned_rows = 0; // 返回的行数 + slow_query_cxt->slow_query.current_table_counter = (PgStat_TableCounts*)palloc0(sizeof(PgStat_TableCounts)); // 当前表计数 + slow_query_cxt->slow_query.unique_sql_id = 0; // 唯一 SQL 标识 } static void knl_u_trace_context_init(knl_u_trace_context* trace_cxt) @@ -1061,177 +1075,176 @@ static void knl_u_user_login_init(knl_u_user_login_context* user_login_cxt) static void knl_u_statement_init(knl_u_statement_context* statement_cxt) { - Assert(statement_cxt != NULL); + Assert(statement_cxt != NULL); // 断言:确保 statement_cxt 不为空 - statement_cxt->db_name = NULL; - statement_cxt->user_name = NULL; - statement_cxt->client_addr = NULL; - statement_cxt->client_port = INSTR_STMT_NULL_PORT; - statement_cxt->session_id = 0; + statement_cxt->db_name = NULL; // 数据库名称 + statement_cxt->user_name = NULL; // 用户名称 + statement_cxt->client_addr = NULL; // 客户端地址 + statement_cxt->client_port = INSTR_STMT_NULL_PORT; // 客户端端口 + statement_cxt->session_id = 0; // 会话 ID - statement_cxt->curStatementMetrics = NULL; - statement_cxt->allocatedCxtCnt = 0; + statement_cxt->curStatementMetrics = NULL; // 当前语句度量信息 + statement_cxt->allocatedCxtCnt = 0; // 已分配的上下文计数 - statement_cxt->free_count = 0; - statement_cxt->toFreeStatementList = NULL; - statement_cxt->suspend_count = 0; - statement_cxt->suspendStatementList = NULL; - statement_cxt->executer_run_level = 0; + statement_cxt->free_count = 0; // 空闲计数 + statement_cxt->toFreeStatementList = NULL; // 待释放语句列表 + statement_cxt->suspend_count = 0; // 暂停计数 + statement_cxt->suspendStatementList = NULL; // 暂停语句列表 + statement_cxt->executer_run_level = 0; // 执行器运行级别 - (void)syscalllockInit(&statement_cxt->list_protect); - statement_cxt->stmt_stat_cxt = NULL; + (void)syscalllockInit(&statement_cxt->list_protect); // 初始化锁 + statement_cxt->stmt_stat_cxt = NULL; // 语句统计上下文 } void knl_u_relmap_init(knl_u_relmap_context* relmap_cxt) { - relmap_cxt->shared_map = (RelMapFile*)palloc0(sizeof(RelMapFile)); - relmap_cxt->local_map = (RelMapFile*)palloc0(sizeof(RelMapFile)); - relmap_cxt->active_shared_updates = (RelMapFile*)palloc0(sizeof(RelMapFile)); - relmap_cxt->active_local_updates = (RelMapFile*)palloc0(sizeof(RelMapFile)); - relmap_cxt->pending_shared_updates = (RelMapFile*)palloc0(sizeof(RelMapFile)); - relmap_cxt->pending_local_updates = (RelMapFile*)palloc0(sizeof(RelMapFile)); - relmap_cxt->RelfilenodeMapHash = NULL; - relmap_cxt->UHeapRelfilenodeMapHash = NULL; + relmap_cxt->shared_map = (RelMapFile*)palloc0(sizeof(RelMapFile)); // 共享映射文件 + relmap_cxt->local_map = (RelMapFile*)palloc0(sizeof(RelMapFile)); // 本地映射文件 + relmap_cxt->active_shared_updates = (RelMapFile*)palloc0(sizeof(RelMapFile)); // 活动的共享更新 + relmap_cxt->active_local_updates = (RelMapFile*)palloc0(sizeof(RelMapFile)); // 活动的本地更新 + relmap_cxt->pending_shared_updates = (RelMapFile*)palloc0(sizeof(RelMapFile)); // 待处理的共享更新 + relmap_cxt->pending_local_updates = (RelMapFile*)palloc0(sizeof(RelMapFile)); // 待处理的本地更新 + relmap_cxt->RelfilenodeMapHash = NULL; // 关系文件节点映射哈希表 + relmap_cxt->UHeapRelfilenodeMapHash = NULL; // UHeap 关系文件节点映射哈希表 } void knl_u_inval_init(knl_u_inval_context* inval_cxt) { - inval_cxt->DeepthInAcceptInvalidationMessage = 0; - inval_cxt->transInvalInfo = NULL; - inval_cxt->SharedInvalidMessagesArray = NULL; - inval_cxt->numSharedInvalidMessagesArray = 0; - inval_cxt->maxSharedInvalidMessagesArray = 0; - inval_cxt->syscache_callback_list = (SYSCACHECALLBACK*)palloc0(sizeof(SYSCACHECALLBACK) * MAX_SYSCACHE_CALLBACKS); - inval_cxt->syscache_callback_count = 0; - inval_cxt->relcache_callback_list = (RELCACHECALLBACK*)palloc0(sizeof(RELCACHECALLBACK) * MAX_RELCACHE_CALLBACKS); - inval_cxt->relcache_callback_count = 0; - inval_cxt->partcache_callback_list = - (PARTCACHECALLBACK*)palloc0(sizeof(PARTCACHECALLBACK) * MAX_PARTCACHE_CALLBACKS); - inval_cxt->partcache_callback_count = 0; - inval_cxt->SIMCounter = 0; - inval_cxt->catchupInterruptPending = 0; - inval_cxt->messages = (SharedInvalidationMessage*)palloc0(MAXINVALMSGS * sizeof(SharedInvalidationMessage)); - inval_cxt->nextmsg = 0; - inval_cxt->nummsgs = 0; + inval_cxt->DeepthInAcceptInvalidationMessage = 0; // 接收失效消息深度 + inval_cxt->transInvalInfo = NULL; // 事务失效信息 + inval_cxt->SharedInvalidMessagesArray = NULL; // 共享失效消息数组 + inval_cxt->numSharedInvalidMessagesArray = 0; // 共享失效消息数组中的数量 + inval_cxt->maxSharedInvalidMessagesArray = 0; // 共享失效消息数组的最大大小 + inval_cxt->syscache_callback_list = (SYSCACHECALLBACK*)palloc0(sizeof(SYSCACHECALLBACK) * MAX_SYSCACHE_CALLBACKS); // 系统缓存回调列表 + inval_cxt->syscache_callback_count = 0; // 系统缓存回调计数 + inval_cxt->relcache_callback_list = (RELCACHECALLBACK*)palloc0(sizeof(RELCACHECALLBACK) * MAX_RELCACHE_CALLBACKS); // 关系缓存回调列表 + inval_cxt->relcache_callback_count = 0; // 关系缓存回调计数 + inval_cxt->partcache_callback_list = (PARTCACHECALLBACK*)palloc0(sizeof(PARTCACHECALLBACK) * MAX_PARTCACHE_CALLBACKS); // 分区缓存回调列表 + inval_cxt->partcache_callback_count = 0; // 分区缓存回调计数 + inval_cxt->SIMCounter = 0; // SIM 计数器 + inval_cxt->catchupInterruptPending = 0; // 待处理的 catchup 中断 + inval_cxt->messages = (SharedInvalidationMessage*)palloc0(MAXINVALMSGS * sizeof(SharedInvalidationMessage)); // 失效消息数组 + inval_cxt->nextmsg = 0; // 下一个消息位置 + inval_cxt->nummsgs = 0; // 消息数量 } static void knl_u_catalog_init(knl_u_catalog_context* catalog_cxt) { - catalog_cxt->nulls[0] = false; - catalog_cxt->nulls[1] = false; - catalog_cxt->nulls[2] = false; - catalog_cxt->nulls[3] = false; - catalog_cxt->route = (PartitionIdentifier*)palloc0(sizeof(PartitionIdentifier)); - catalog_cxt->Parse_sql_language = false; - catalog_cxt->pendingDeletes = NULL; - catalog_cxt->ColMainFileNodes = NULL; + catalog_cxt->nulls[0] = false; // 第一个 null 标志 + catalog_cxt->nulls[1] = false; // 第二个 null 标志 + catalog_cxt->nulls[2] = false; // 第三个 null 标志 + catalog_cxt->nulls[3] = false; // 第四个 null 标志 + catalog_cxt->route = (PartitionIdentifier*)palloc0(sizeof(PartitionIdentifier)); // 分区标识符 + catalog_cxt->Parse_sql_language = false; // 是否解析 SQL 语言 + catalog_cxt->pendingDeletes = NULL; // 待处理的删除操作 + catalog_cxt->ColMainFileNodes = NULL; // 列存主文件节点 #define ColMainFileNodesDefNum 16 - catalog_cxt->ColMainFileNodesMaxNum = ColMainFileNodesDefNum; - catalog_cxt->ColMainFileNodesCurNum = 0; - catalog_cxt->pendingDfsDeletes = NIL; - catalog_cxt->delete_conn = NULL; - catalog_cxt->vf_store_root = NULL; - catalog_cxt->currentlyReindexedHeap = InvalidOid; - catalog_cxt->currentlyReindexedIndex = InvalidOid; - catalog_cxt->pendingReindexedIndexes = NIL; - catalog_cxt->activeSearchPath = NIL; - catalog_cxt->activeCreationNamespace = InvalidOid; - catalog_cxt->activeTempCreationPending = false; - catalog_cxt->baseSearchPath = NIL; - catalog_cxt->baseCreationNamespace = InvalidOid; - catalog_cxt->baseTempCreationPending = false; - catalog_cxt->namespaceUser = InvalidOid; - catalog_cxt->baseSearchPathValid = true; - catalog_cxt->overrideStack = NIL; - catalog_cxt->overrideStackValid = true; - catalog_cxt->setCurCreateSchema = false; - catalog_cxt->curCreateSchema = NULL; - catalog_cxt->myTempNamespaceOld = InvalidOid; - catalog_cxt->myTempNamespace = InvalidOid; - catalog_cxt->myTempToastNamespace = InvalidOid; - catalog_cxt->deleteTempOnQuiting = false; - catalog_cxt->myTempNamespaceSubID = InvalidSubTransactionId; - catalog_cxt->redistribution_cancelable = false; + catalog_cxt->ColMainFileNodesMaxNum = ColMainFileNodesDefNum; // 列存主文件节点的最大数量 + catalog_cxt->ColMainFileNodesCurNum = 0; // 当前列存主文件节点数量 + catalog_cxt->pendingDfsDeletes = NIL; // 待处理的 DFS 删除操作 + catalog_cxt->delete_conn = NULL; // 删除连接 + catalog_cxt->vf_store_root = NULL; // VF 存储根目录 + catalog_cxt->currentlyReindexedHeap = InvalidOid; // 当前正在重建的堆表 OID + catalog_cxt->currentlyReindexedIndex = InvalidOid; // 当前正在重建的索引 OID + catalog_cxt->pendingReindexedIndexes = NIL; // 待重建的索引列表 + catalog_cxt->activeSearchPath = NIL; // 活跃的搜索路径 + catalog_cxt->activeCreationNamespace = InvalidOid; // 活跃的创建命名空间 OID + catalog_cxt->activeTempCreationPending = false; // 活跃的临时创建等待中 + catalog_cxt->baseSearchPath = NIL; // 基础的搜索路径 + catalog_cxt->baseCreationNamespace = InvalidOid; // 基础的创建命名空间 OID + catalog_cxt->baseTempCreationPending = false; // 基础的临时创建等待中 + catalog_cxt->namespaceUser = InvalidOid; // 命名空间用户 OID + catalog_cxt->baseSearchPathValid = true; // 基础搜索路径是否有效 + catalog_cxt->overrideStack = NIL; // 覆盖栈 + catalog_cxt->overrideStackValid = true; // 覆盖栈是否有效 + catalog_cxt->setCurCreateSchema = false; // 是否设置当前创建模式 + catalog_cxt->curCreateSchema = NULL; // 当前创建模式 + catalog_cxt->myTempNamespaceOld = InvalidOid; // 旧的临时命名空间 OID + catalog_cxt->myTempNamespace = InvalidOid; // 当前的临时命名空间 OID + catalog_cxt->myTempToastNamespace = InvalidOid; // 当前的临时 Toast 命名空间 OID + catalog_cxt->deleteTempOnQuiting = false; // 退出时删除临时数据 + catalog_cxt->myTempNamespaceSubID = InvalidSubTransactionId; // 当前的临时命名空间子事务 ID + catalog_cxt->redistribution_cancelable = false; // 是否可取消重分发 } static void knl_u_cache_init(knl_u_cache_context* cache_cxt) { - cache_cxt->num_res = 0; - cache_cxt->re_array = (cached_re_str*)palloc0(sizeof(cached_re_str) * MAX_CACHED_RES); + cache_cxt->num_res = 0; // 资源数量 + cache_cxt->re_array = (cached_re_str*)palloc0(sizeof(cached_re_str) * MAX_CACHED_RES); // 资源数组 - cache_cxt->cached_privs_role = InvalidOid; - cache_cxt->cached_privs_roles = NIL; - cache_cxt->cached_member_role = InvalidOid; - cache_cxt->cached_membership_roles = NIL; + cache_cxt->cached_privs_role = InvalidOid; // 缓存的权限角色 OID + cache_cxt->cached_privs_roles = NIL; // 缓存的权限角色列表 + cache_cxt->cached_member_role = InvalidOid; // 缓存的成员角色 OID + cache_cxt->cached_membership_roles = NIL; // 缓存的成员角色列表 - cache_cxt->plan_getrulebyoid = NULL; - cache_cxt->plan_getviewrule = NULL; + cache_cxt->plan_getrulebyoid = NULL; // 获取规则 OID 的计划 + cache_cxt->plan_getviewrule = NULL; // 获取视图规则的计划 - cache_cxt->att_opt_cache_hash = NULL; - cache_cxt->cache_header = NULL; + cache_cxt->att_opt_cache_hash = NULL; // 属性选项缓存哈希表 + cache_cxt->cache_header = NULL; // 缓存头 - cache_cxt->TableSpaceCacheHash = NULL; + cache_cxt->TableSpaceCacheHash = NULL; // 表空间缓存哈希表 - cache_cxt->PartitionIdCache = NULL; - cache_cxt->BucketIdCache = NULL; - cache_cxt->PartCacheNeedEOXActWork = false; - cache_cxt->bucket_cache_need_eoxact_work = false; - cache_cxt->dn_hash_table = NULL; + cache_cxt->PartitionIdCache = NULL; // 分区 ID 缓存 + cache_cxt->BucketIdCache = NULL; // 桶 ID 缓存 + cache_cxt->PartCacheNeedEOXActWork = false; // 分区缓存需要结束子事务工作 + cache_cxt->bucket_cache_need_eoxact_work = false; // 桶缓存需要结束子事务工作 + cache_cxt->dn_hash_table = NULL; // 数据节点哈希表 } static void knl_u_syscache_init(knl_u_syscache_context* syscache_cxt) { - syscache_cxt->SysCache = (CatCache**)palloc0(sizeof(CatCache*) * SysCacheSize); - syscache_cxt->SysCacheRelationOid = (Oid*)palloc0(sizeof(Oid) * SysCacheSize); - syscache_cxt->CacheInitialized = false; + syscache_cxt->SysCache = (CatCache**)palloc0(sizeof(CatCache*) * SysCacheSize); // 系统缓存数组 + syscache_cxt->SysCacheRelationOid = (Oid*)palloc0(sizeof(Oid) * SysCacheSize); // 系统缓存关系 OID 数组 + syscache_cxt->CacheInitialized = false; // 缓存是否已初始化 } static void knl_u_pgxc_init(knl_u_pgxc_context* pgxc_cxt) { #ifdef ENABLE_MULTIPLE_NODES - pgxc_cxt->NumDataNodes = 0; - pgxc_cxt->NumTotalDataNodes = 0; + pgxc_cxt->NumDataNodes = 0; // 数据节点数量 + pgxc_cxt->NumTotalDataNodes = 0; // 总数据节点数量 #else - pgxc_cxt->NumDataNodes = 1; + pgxc_cxt->NumDataNodes = 1; // 数据节点数量(单节点模式) #endif - pgxc_cxt->NumCoords = 0; - pgxc_cxt->NumStandbyDataNodes = 0; - pgxc_cxt->datanode_count = 0; - pgxc_cxt->coord_count = 0; - pgxc_cxt->dn_matrics = NULL; - pgxc_cxt->dn_handles = NULL; - pgxc_cxt->co_handles = NULL; + pgxc_cxt->NumCoords = 0; // 协调节点数量 + pgxc_cxt->NumStandbyDataNodes = 0; // 备机数据节点数量 + pgxc_cxt->datanode_count = 0; // 数据节点计数 + pgxc_cxt->coord_count = 0; // 协调节点计数 + pgxc_cxt->dn_matrics = NULL; // 数据节点矩阵 + pgxc_cxt->dn_handles = NULL; // 数据节点连接句柄数组 + pgxc_cxt->co_handles = NULL; // 协调节点连接句柄数组 #ifdef ENABLE_MULTIPLE_NODES - pgxc_cxt->PGXCNodeId = -1; + pgxc_cxt->PGXCNodeId = -1; // PGXC 节点 ID #else - pgxc_cxt->PGXCNodeId = 0; + pgxc_cxt->PGXCNodeId = 0; // PGXC 节点 ID(单节点模式) #endif - pgxc_cxt->PGXCNodeIdentifier = 0; + pgxc_cxt->PGXCNodeIdentifier = 0; // PGXC 节点标识符 - pgxc_cxt->remoteXactState = (RemoteXactState*)palloc0(sizeof(RemoteXactState)); - pgxc_cxt->XactWriteNodes = NIL; - pgxc_cxt->XactReadNodes = NIL; - pgxc_cxt->preparedNodes = NULL; + pgxc_cxt->remoteXactState = (RemoteXactState*)palloc0(sizeof(RemoteXactState)); // 远程事务状态 + pgxc_cxt->XactWriteNodes = NIL; // 事务写节点列表 + pgxc_cxt->XactReadNodes = NIL; // 事务读节点列表 + pgxc_cxt->preparedNodes = NULL; // 准备节点列表 - pgxc_cxt->last_reported_send_errno = 0; - pgxc_cxt->PoolerResendParams = false; - pgxc_cxt->PoolerConnectionInfo = (PGXCNodeConnectionInfo*)palloc0(sizeof(PGXCNodeConnectionInfo)); - pgxc_cxt->poolHandle = NULL; - pgxc_cxt->ConsistencyPointUpdating = false; - pgxc_cxt->disasterReadArray = NULL; - pgxc_cxt->DisasterReadArrayInit = false; + pgxc_cxt->last_reported_send_errno = 0; // 最后报告的发送错误号 + pgxc_cxt->PoolerResendParams = false; // 是否重新发送参数给连接池 + pgxc_cxt->PoolerConnectionInfo = (PGXCNodeConnectionInfo*)palloc0(sizeof(PGXCNodeConnectionInfo)); // 连接池连接信息 + pgxc_cxt->poolHandle = NULL; // 连接池句柄 + pgxc_cxt->ConsistencyPointUpdating = false; // 一致性点更新中 + pgxc_cxt->disasterReadArray = NULL; // 灾备读数组 + pgxc_cxt->DisasterReadArrayInit = false; // 灾备读数组是否初始化 - pgxc_cxt->connection_cache = NIL; - pgxc_cxt->connection_cache_handle = NIL; + pgxc_cxt->connection_cache = NIL; // 连接缓存列表 + pgxc_cxt->connection_cache_handle = NIL; // 连接缓存句柄列表 - pgxc_cxt->is_gc_fdw = false; - pgxc_cxt->is_gc_fdw_analyze = false; - pgxc_cxt->gc_fdw_current_idx = 0; - pgxc_cxt->gc_fdw_max_idx = 0; - pgxc_cxt->gc_fdw_run_version = GCFDW_VERSION; - pgxc_cxt->gc_fdw_snapshot = NULL; - pgxc_cxt->primary_data_node = InvalidOid; - pgxc_cxt->num_preferred_data_nodes = 0; + pgxc_cxt->is_gc_fdw = false; // 是否是 GC_FDW + pgxc_cxt->is_gc_fdw_analyze = false; // 是否是 GC_FDW 的分析操作 + pgxc_cxt->gc_fdw_current_idx = 0; // GC_FDW 当前索引 + pgxc_cxt->gc_fdw_max_idx = 0; // GC_FDW 最大索引 + pgxc_cxt->gc_fdw_run_version = GCFDW_VERSION; // GC_FDW 运行版本 + pgxc_cxt->gc_fdw_snapshot = NULL; // GC_FDW 快照 + pgxc_cxt->primary_data_node = InvalidOid; // 主数据节点 OID + pgxc_cxt->num_preferred_data_nodes = 0; // 首选数据节点数量 } static void knl_u_erand_init(knl_u_erand_context* rand_cxt) @@ -1243,10 +1256,10 @@ static void knl_u_erand_init(knl_u_erand_context* rand_cxt) seed = t_thrd.postmaster_cxt.random_start_time.tv_usec ^ ((time.tv_usec << 16) | ((time.tv_usec >> 16) & 0xffff)); } while (seed == 0); - - rand_cxt->rand48_seed[0] = RAND48_SEED_0; - rand_cxt->rand48_seed[1] = (unsigned short)seed; - rand_cxt->rand48_seed[2] = (unsigned short)(seed >> 16); + + rand_cxt->rand48_seed[0] = RAND48_SEED_0; // 种子 0 + rand_cxt->rand48_seed[1] = (unsigned short)seed; // 种子 1 + rand_cxt->rand48_seed[2] = (unsigned short)(seed >> 16); // 种子 2 } static void knl_u_regex_init(knl_u_regex_context* regex_cxt) @@ -1256,31 +1269,31 @@ static void knl_u_regex_init(knl_u_regex_context* regex_cxt) static void knl_u_xact_init(knl_u_xact_context* xact_cxt) { - xact_cxt->Xact_callbacks = NULL; - xact_cxt->SubXact_callbacks = NULL; + xact_cxt->Xact_callbacks = NULL; // 事务回调函数 + xact_cxt->SubXact_callbacks = NULL; // 子事务回调函数 #ifdef PGXC - xact_cxt->dbcleanup_info = (abort_callback_type*)palloc0(sizeof(abort_callback_type)); - xact_cxt->dbcleanupInfoList = NIL; + xact_cxt->dbcleanup_info = (abort_callback_type*)palloc0(sizeof(abort_callback_type)); // 数据库清理回调信息 + xact_cxt->dbcleanupInfoList = NIL; // 数据库清理回调信息列表 #endif - /* alloc in TopTransactionMemory Context, safe to set NULL */ - xact_cxt->prepareGID = NULL; - /* alloc in TopMemory Context, initialization is NULL when create new session */ - xact_cxt->savePrepareGID = NULL; + /* 在 TopTransactionMemory 上下文分配,安全地设置为 NULL */ + xact_cxt->prepareGID = NULL; // 准备事务的全局 ID + /* 在 TopMemory 上下文分配,创建新会话时初始化为 NULL */ + xact_cxt->savePrepareGID = NULL; // 保存准备事务的全局 ID - xact_cxt->pbe_execute_complete = true; - xact_cxt->sendSeqDbName = NULL; - xact_cxt->sendSeqSchmaName = NULL; - xact_cxt->sendSeqName = NULL; - xact_cxt->send_result = NULL; - xact_cxt->ActiveLobRelid = InvalidOid; + xact_cxt->pbe_execute_complete = true; // PBE 执行是否完成 + xact_cxt->sendSeqDbName = NULL; // 发送序列的数据库名 + xact_cxt->sendSeqSchmaName = NULL; // 发送序列的模式名 + xact_cxt->sendSeqName = NULL; // 发送序列的名字 + xact_cxt->send_result = NULL; // 发送的结果 + xact_cxt->ActiveLobRelid = InvalidOid; // 活跃 LOB 表 ID } static void knl_u_ps_init(knl_u_ps_context* ps_cxt) { #ifndef PS_USE_CLOBBER_ARGV #define PS_BUFFER_SIZE 256 - ps_cxt->ps_buffer_size = PS_BUFFER_SIZE; + ps_cxt->ps_buffer_size = PS_BUFFER_SIZE; // 预处理语句缓冲区大小 #endif } @@ -1288,21 +1301,21 @@ static void knl_u_ps_init(knl_u_ps_context* ps_cxt) static void knl_u_mot_init(knl_u_mot_context* mot_cxt) { Assert(mot_cxt != NULL); - mot_cxt->callbacks_set = false; - mot_cxt->session_id = -1; // invalid session id - mot_cxt->connection_id = -1; // invalid connection id - mot_cxt->session_context = NULL; - mot_cxt->txn_manager = NULL; - mot_cxt->jit_session_context_pool = NULL; - mot_cxt->jit_context_count = 0; - mot_cxt->jit_llvm_if_stack = NULL; - mot_cxt->jit_llvm_while_stack = NULL; - mot_cxt->jit_llvm_do_while_stack = NULL; - mot_cxt->jit_tvm_if_stack = NULL; - mot_cxt->jit_tvm_while_stack = NULL; - mot_cxt->jit_tvm_do_while_stack = NULL; - mot_cxt->jit_context = NULL; - mot_cxt->jit_txn = NULL; + mot_cxt->callbacks_set = false; // 回调函数是否设置 + mot_cxt->session_id = -1; // 无效的会话 ID + mot_cxt->connection_id = -1; // 无效的连接 ID + mot_cxt->session_context = NULL; // 会话上下文 + mot_cxt->txn_manager = NULL; // 事务管理器 + mot_cxt->jit_session_context_pool = NULL; // JIT 会话上下文池 + mot_cxt->jit_context_count = 0; // JIT 上下文计数 + mot_cxt->jit_llvm_if_stack = NULL; // JIT LLVM IF 栈 + mot_cxt->jit_llvm_while_stack = NULL; // JIT LLVM WHILE 栈 + mot_cxt->jit_llvm_do_while_stack = NULL; // JIT LLVM DO-WHILE 栈 + mot_cxt->jit_tvm_if_stack = NULL; // JIT TVM IF 栈 + mot_cxt->jit_tvm_while_stack = NULL; // JIT TVM WHILE 栈 + mot_cxt->jit_tvm_do_while_stack = NULL; // JIT TVM DO-WHILE 栈 + mot_cxt->jit_context = NULL; // JIT 上下文 + mot_cxt->jit_txn = NULL; // JIT 事务 } #endif @@ -1310,10 +1323,10 @@ static void knl_u_clientConnTime_init(knl_u_clientConnTime_context* clientConnTi { Assert(clientConnTime_cxt != NULL); - /* Record start time while initializing session for client connection */ + /* 在为客户端连接初始化会话时记录开始时间 */ INSTR_TIME_SET_CURRENT(clientConnTime_cxt->connStartTime); - /* Set flag to "true", to indicate this session in initial process */ + /* 将标志设置为“true”,以指示此会话位于初始进程中。 */ clientConnTime_cxt->checkOnlyInConnProcess = true; } @@ -1414,26 +1427,31 @@ void knl_session_init(knl_session_context* sess_cxt) static void alloc_context_from_top(knl_session_context* sess, MemoryContext top_mem_cxt) { + // 从顶级内存上下文`top_mem_cxt`中分配并创建不同子内存上下文 sess->self_mem_cxt = AllocSetContextCreate(top_mem_cxt, "SessionSelfMemoryContext", ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); + sess->cache_mem_cxt = AllocSetContextCreate(top_mem_cxt, "SessionCacheMemoryContext", ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, - (1024 * 1024)); /* set max block size to 1MB */ + (1024 * 1024)); // 设置最大块大小为1MB + sess->temp_mem_cxt = AllocSetContextCreate(top_mem_cxt, "SessionTempMemoryContext", ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); + sess->syscache_cxt.SysCacheMemCxt = AllocSetContextCreate(top_mem_cxt, "SystemCacheMemoryContext", ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); + sess->stat_cxt.hotkeySessContext = AllocSetContextCreate(top_mem_cxt, "HotkeySessionMemoryContext", ALLOCSET_DEFAULT_MINSIZE, @@ -1445,17 +1463,27 @@ knl_session_context* create_session_context(MemoryContext parent, uint64 id) { knl_session_context *sess, *old_sess; const int secondToMilliSecond = 1000; + + // 保存当前会话上下文,以便稍后恢复 old_sess = u_sess; + + // 解封顶级内存上下文,以便进行内存分配操作 MemoryContextUnSeal(t_thrd.top_mem_cxt); + + // 分配并初始化会话上下文sess sess = (knl_session_context*)MemoryContextAllocZero(parent, sizeof(knl_session_context)); + + // 封印顶级内存上下文,以便保护其内容 MemoryContextSeal(t_thrd.top_mem_cxt); MemoryContext top_mem_cxt; if (id == 0) { + // 如果会话ID为0,表示为主会话,则使用顶级内存上下文 top_mem_cxt = t_thrd.top_mem_cxt; sess->mcxt_group = t_thrd.mcxt_group; } else { + // 如果会话ID不为0,表示为子会话,则创建新的内存上下文,并设置会话ID top_mem_cxt = AllocSetContextCreate(parent, "SessionTopMemoryContext", ALLOCSET_DEFAULT_MINSIZE, @@ -1466,7 +1494,11 @@ knl_session_context* create_session_context(MemoryContext parent, uint64 id) true); top_mem_cxt->session_id = id; } + + // 切换当前内存上下文到top_mem_cxt MemoryContext old_cxt = MemoryContextSwitchTo(top_mem_cxt); + + // 设置会话的顶级内存上下文和会话ID,然后初始化会话 sess->top_mem_cxt = top_mem_cxt; knl_session_init(sess); sess->session_id = id; @@ -1476,29 +1508,32 @@ knl_session_context* create_session_context(MemoryContext parent, uint64 id) u_sess = sess; if (id != 0) { + // 如果会话ID不为0,表示为线程池会话,则执行以下操作 MemoryContextUnSeal(top_mem_cxt); sess->mcxt_group = New(top_mem_cxt) MemoryContextGroup(); sess->mcxt_group->Init(top_mem_cxt); MemoryContextSeal(top_mem_cxt); - /* - * threadpool session, set init timeout to - * avoid user only establish connections but do not send data or SSL shakehands hang - * non-threadpool/fake session will direct call ReadCommand and enable alarm by sig timer - * */ + + // 为线程池会话设置初始超时,以避免用户只建立连接但不发送数据或SSL握手而导致的挂起 (void)enable_session_sig_alarm(u_sess->attr.attr_common.SessionTimeout * secondToMilliSecond); } - // Switch to context group, in case knl_u_executor_init will alloc memory on CurrentMemoryContext. + // 切换到上下文组,以便在knl_u_executor_init中进行内存分配 MemoryContextSwitchTo(sess->mcxt_group->GetMemCxtGroup(MEMORY_CONTEXT_EXECUTOR)); + + // 初始化执行器上下文 knl_u_executor_init(&sess->exec_cxt); + // 从顶级内存上下文中分配其他子上下文 alloc_context_from_top(sess, top_mem_cxt); - /* Initialize portal manager */ + /* 初始化Portal管理器 */ EnablePortalManager(); + // 恢复之前保存的会话上下文和当前内存上下文 u_sess = old_sess; MemoryContextSwitchTo(old_cxt); + return sess; } @@ -1510,12 +1545,13 @@ void use_fake_session() void free_session_context(knl_session_context* session) { + // 确保当前会话上下文与传入的会话上下文一致 Assert(u_sess == session); - /* free the locale cache */ + /* 释放区域设置缓存 */ freeLocaleCache(false); - /* discard all the data in the channel. */ + /* 丢弃通道中的所有数据。 */ t_thrd.libpq_cxt.PqSendPointer = 0; t_thrd.libpq_cxt.PqSendStart = 0; t_thrd.libpq_cxt.PqRecvPointer = 0; @@ -1525,17 +1561,25 @@ void free_session_context(knl_session_context* session) t_thrd.xact_cxt.next_xid = InvalidTransactionId; - /* Release session related memory. */ + /* 释放与会话相关的内存。 */ SelfMemoryContext = NULL; + if (CurrentMemoryContext == u_sess->top_transaction_mem_cxt) { - /* abnormal cleanup */ + /* 异常情况下的清理,切换到消息内存上下文 */ MemoryContextSwitchTo(t_thrd.mem_cxt.msg_mem_cxt); } + // 删除会话顶级内存上下文及其所有子上下文 MemoryContextDeleteChildren(session->top_mem_cxt); MemoryContextDelete(session->top_mem_cxt); + + // 释放删除内存上下文的互斥锁 (void)syscalllockFree(&session->utils_cxt.deleMemContextMutex); + + // 释放会话上下文内存 pfree_ext(session); + + // 使用虚拟会话 use_fake_session(); } @@ -1543,6 +1587,8 @@ bool stp_set_commit_rollback_err_msg(stp_xact_err_type type) { int rt; size_t maxMsgLen = sizeof(u_sess->SPI_cxt.forbidden_commit_rollback_err_msg); + + // 检查错误消息是否为空,如果为空,则根据类型设置错误消息 if (u_sess->SPI_cxt.forbidden_commit_rollback_err_msg[0] == '\0') { switch (type) { case STP_XACT_OPEN_FOR: @@ -1555,7 +1601,7 @@ bool stp_set_commit_rollback_err_msg(stp_xact_err_type type) break; case STP_XACT_USED_AS_EXPR: rt = snprintf_s(u_sess->SPI_cxt.forbidden_commit_rollback_err_msg, maxMsgLen, maxMsgLen - 1, "%s", - "transaction statement in store procedure used as a expression is not supported"); + "transaction statement in store procedure used as an expression is not supported"); break; case STP_XACT_GUC_IN_OPT_CLAUSE: rt = snprintf_s(u_sess->SPI_cxt.forbidden_commit_rollback_err_msg, maxMsgLen, maxMsgLen - 1, "%s", @@ -1567,15 +1613,15 @@ bool stp_set_commit_rollback_err_msg(stp_xact_err_type type) break; case STP_XACT_AFTER_TRIGGER_BEGIN: rt = snprintf_s(u_sess->SPI_cxt.forbidden_commit_rollback_err_msg, maxMsgLen, maxMsgLen - 1, "%s", - "transaction statement in store procedure used as sql to get value is not supported"); + "transaction statement in store procedure used as SQL to get value is not supported"); break; case STP_XACT_PACKAGE_INSTANTIATION: rt = snprintf_s(u_sess->SPI_cxt.forbidden_commit_rollback_err_msg, maxMsgLen, maxMsgLen - 1, "%s", - "can not use commit/rollback/savepoint in package instantiation"); + "cannot use commit/rollback/savepoint in package instantiation"); break; case STP_XACT_COMPL_SQL: rt = snprintf_s(u_sess->SPI_cxt.forbidden_commit_rollback_err_msg, maxMsgLen, maxMsgLen - 1, "%s", - "can not use commit rollback in Complex SQL"); + "cannot use commit rollback in Complex SQL"); break; case STP_XACT_IMMUTABLE: rt = snprintf_s(u_sess->SPI_cxt.forbidden_commit_rollback_err_msg, maxMsgLen, maxMsgLen - 1, "%s", @@ -1583,15 +1629,16 @@ bool stp_set_commit_rollback_err_msg(stp_xact_err_type type) break; case STP_XACT_TOO_MANY_PORTAL: rt = snprintf_s(u_sess->SPI_cxt.forbidden_commit_rollback_err_msg, maxMsgLen, maxMsgLen - 1, "%s", - "transaction statement in store procedure is not supported used in multi-layer portal"); + "transaction statement in store procedure is not supported when used in multi-layer portal"); break; default: rt = snprintf_s(u_sess->SPI_cxt.forbidden_commit_rollback_err_msg, maxMsgLen, maxMsgLen - 1, "%s", "invalid transaction in store procedure"); break; } + // 检查字符串是否被正确写入或溢出 if (rt < 0 || rt > ((int)maxMsgLen)) { - ereport(ERROR, (errmsg("string of invalid transaction message would overflow buffer"))); + ereport(ERROR, (errmsg("string of invalid transaction message would overflow buffer"))); } return true; } @@ -1607,4 +1654,4 @@ bool enable_out_param_override() { return u_sess->attr.attr_sql.sql_compatibility == A_FORMAT && PROC_OUTPARAM_OVERRIDE; -} +} \ No newline at end of file -- 2.34.1 From 93af93324b8254084098c81b4228748253f2d378 Mon Sep 17 00:00:00 2001 From: Nemoo <18210033860@163.com> Date: Sat, 30 Sep 2023 14:17:40 +0800 Subject: [PATCH 29/50] Update knl_thread.cpp --- .../process/threadpool/knl_thread.cpp | 1446 +++++++++-------- 1 file changed, 787 insertions(+), 659 deletions(-) diff --git a/src/gausskernel/process/threadpool/knl_thread.cpp b/src/gausskernel/process/threadpool/knl_thread.cpp index 068d74627..4db3678f4 100755 --- a/src/gausskernel/process/threadpool/knl_thread.cpp +++ b/src/gausskernel/process/threadpool/knl_thread.cpp @@ -15,15 +15,12 @@ * ------------------------------------------------------------------------- * * knl_thread.cpp - * Initial functions for thread level global variables. + * 线程级全局变量的初始函数。 * - * The thread level variables will be inited at the start up time of one - * thread, to be specific, at the beginning of SubPostmasterMain(). When - * anyone try to add variable in thread level context, remember to add - * initialization at this file. + * 线程级别的变量将在一个线程的启动时进行初始化,具体来说,在SubPostmasterMain()的开始时。 + * 当任何人试图在线程级别上下文中添加变量时,请记得在这个文件中添加初始化。" * - * And be very careful to alloc memory here, the memory will be alloced from - * TopMemoryContext and will not be freed until the thread exit. + * "在这里分配内存时要非常小心,内存将从TopMemoryContext中分配,直到线程退出时才会被释放。" * * * IDENTIFICATION @@ -73,18 +70,23 @@ THR_LOCAL knl_thrd_context t_thrd; extern void temp_file_context_init(knl_t_libpq_context* libpq_cxt); +// 静态函数,用于初始化线程级AES上下文结构体 static void knl_t_aes_init(knl_t_aes_context* aes_cxt) { + // 计算数组长度 size_t arrlen = sizeof(GS_UCHAR) * RANDOM_LEN; + // 用于处理错误的返回码 errno_t rc; - aes_cxt->encryption_function_call = false; - aes_cxt->decryption_function_call = false; - aes_cxt->decryption_count = 0; - aes_cxt->insert_position = NUMBER_OF_SAVED_DERIVEKEYS / 2; - aes_cxt->random_salt_tag = false; - aes_cxt->random_salt_count = 0; + // 初始化AES上下文结构体的各个成员变量 + aes_cxt->encryption_function_call = false; // 加密函数调用标志 + aes_cxt->decryption_function_call = false; // 解密函数调用标志 + aes_cxt->decryption_count = 0; // 解密计数器 + aes_cxt->insert_position = NUMBER_OF_SAVED_DERIVEKEYS / 2; // 插入位置 + aes_cxt->random_salt_tag = false; // 随机盐标志 + aes_cxt->random_salt_count = 0; // 随机盐计数器 + // 将数组成员变量初始化为0 rc = memset_s(aes_cxt->derive_vector_saved, arrlen, 0, arrlen); securec_check(rc, "\0", "\0"); rc = memset_s(aes_cxt->mac_vector_saved, arrlen, 0, arrlen); @@ -101,6 +103,7 @@ static void knl_t_aes_init(knl_t_aes_context* aes_cxt) sizeof(GS_UINT32) * NUMBER_OF_SAVED_DERIVEKEYS); securec_check(rc, "\0", "\0"); + // 循环初始化数组成员变量为0 for (int i = 0; i < NUMBER_OF_SAVED_DERIVEKEYS; i++) { rc = memset_s(aes_cxt->derive_vector_used[i], arrlen, 0, arrlen); securec_check(rc, "\0", "\0"); @@ -113,170 +116,182 @@ static void knl_t_aes_init(knl_t_aes_context* aes_cxt) } } +// 初始化代码生成上下文结构体 static void knl_t_codegen_init(knl_t_codegen_context* codegen_cxt) { - codegen_cxt->thr_codegen_obj = NULL; - codegen_cxt->g_runningInFmgr = false; - codegen_cxt->codegen_IRload_thr_count = 0; + codegen_cxt->thr_codegen_obj = NULL; // 线程代码生成对象 + codegen_cxt->g_runningInFmgr = false; // 在函数管理器中运行标志 + codegen_cxt->codegen_IRload_thr_count = 0; // 代码生成IR加载线程计数 } +// 初始化格式化上下文结构体 static void knl_t_format_init(knl_t_format_context* format_cxt) { - format_cxt->all_digits = false; - format_cxt->DCH_cache = (DCHCacheEntry*)palloc0(sizeof(DCHCacheEntry) * (DCH_CACHE_FIELDS + 1)); - format_cxt->n_DCH_cache = 0; - format_cxt->DCH_counter = 0; - format_cxt->NUM_cache = (NUMCacheEntry*)palloc0(sizeof(NUMCacheEntry) * (NUM_CACHE_FIELDS + 1)); - format_cxt->n_NUM_cache = 0; - format_cxt->NUM_counter = 0; - format_cxt->last_NUM_cache_entry = NULL; + format_cxt->all_digits = false; // 全数字标志 + format_cxt->DCH_cache = (DCHCacheEntry*)palloc0(sizeof(DCHCacheEntry) * (DCH_CACHE_FIELDS + 1)); // DCH缓存 + format_cxt->n_DCH_cache = 0; // DCH缓存计数 + format_cxt->DCH_counter = 0; // DCH计数器 + format_cxt->NUM_cache = (NUMCacheEntry*)palloc0(sizeof(NUMCacheEntry) * (NUM_CACHE_FIELDS + 1)); // NUM缓存 + format_cxt->n_NUM_cache = 0; // NUM缓存计数 + format_cxt->NUM_counter = 0; // NUM计数器 + format_cxt->last_NUM_cache_entry = NULL; // 最后一个NUM缓存条目 } +// 初始化日志上下文结构体 static void knl_t_log_init(knl_t_log_context* log_cxt) { - log_cxt->plog_msg_switch_tm = {1, 0}; - log_cxt->plog_md_read_entry = NULL; - log_cxt->plog_md_write_entry = NULL; - log_cxt->plog_obs_list_entry = NULL; - log_cxt->plog_obs_read_entry = NULL; - log_cxt->plog_obs_write_entry = NULL; - log_cxt->plog_hdp_read_entry = NULL; - log_cxt->plog_hdp_write_entry = NULL; - log_cxt->plog_hdp_open_entry = NULL; - log_cxt->plog_remote_read_entry = NULL; + log_cxt->plog_msg_switch_tm = {1, 0}; // plog消息开关标志 + log_cxt->plog_md_read_entry = NULL; // plog md读取条目 + log_cxt->plog_md_write_entry = NULL; // plog md写入条目 + log_cxt->plog_obs_list_entry = NULL; // plog obs列表条目 + log_cxt->plog_obs_read_entry = NULL; // plog obs读取条目 + log_cxt->plog_obs_write_entry = NULL; // plog obs写入条目 + log_cxt->plog_hdp_read_entry = NULL; // plog hdp读取条目 + log_cxt->plog_hdp_write_entry = NULL; // plog hdp写入条目 + log_cxt->plog_hdp_open_entry = NULL; // plog hdp打开条目 + log_cxt->plog_remote_read_entry = NULL; // plog 远程读取条目 + + // 分配内存并初始化plog消息内存数组 log_cxt->g_plog_msgmem_array = (char***)palloc0(sizeof(char**) * DS_VALID_NUM); for (int i = 0; i < DSRQ_VALID_NUM; i++) { log_cxt->g_plog_msgmem_array[i] = (char**)palloc0(sizeof(char*) * DSRQ_VALID_NUM); } - log_cxt->error_context_stack = NULL; - log_cxt->call_stack = NULL; - log_cxt->PG_exception_stack = NULL; - log_cxt->thd_bt_symbol = NULL; - log_cxt->flush_message_immediately = false; - log_cxt->Log_destination = LOG_DESTINATION_STDERR; - log_cxt->disable_log_output = false; - log_cxt->on_mask_password = false; - log_cxt->openlog_done = false; - log_cxt->error_with_nodename = false; - log_cxt->errordata = (ErrorData*)palloc0(sizeof(ErrorData) * ERRORDATA_STACK_SIZE); - log_cxt->pLogCtl = NULL; - log_cxt->errordata_stack_depth = -1; - log_cxt->recursion_depth = 0; - log_cxt->syslog_seq = 0; - log_cxt->log_line_number = 0; - log_cxt->log_my_pid = 0; - log_cxt->csv_log_line_number = 0; - log_cxt->csv_log_my_pid = 0; - log_cxt->msgbuf = (StringInfoData*)palloc0(sizeof(StringInfoData)); - log_cxt->module_logging_configure = (unsigned char*)palloc0(sizeof(char) * BEMD_BITMAP_SIZE); + // 初始化各种日志上下文变量 + log_cxt->error_context_stack = NULL; // 错误上下文堆栈 + log_cxt->call_stack = NULL; // 调用堆栈 + log_cxt->PG_exception_stack = NULL; // PG异常堆栈 + log_cxt->thd_bt_symbol = NULL; // 线程回溯符号 + log_cxt->flush_message_immediately = false; // 立即刷新消息标志 + log_cxt->Log_destination = LOG_DESTINATION_STDERR; // 日志输出目标 + log_cxt->disable_log_output = false; // 禁止日志输出标志 + log_cxt->on_mask_password = false; // 掩码密码标志 + log_cxt->openlog_done = false; // 打开日志标志 + log_cxt->error_with_nodename = false; // 带节点名的错误标志 + log_cxt->errordata = (ErrorData*)palloc0(sizeof(ErrorData) * ERRORDATA_STACK_SIZE); // 错误数据 + log_cxt->pLogCtl = NULL; // 日志控制 + log_cxt->errordata_stack_depth = -1; // 错误数据堆栈深度 + log_cxt->recursion_depth = 0; // 递归深度 + log_cxt->syslog_seq = 0; // syslog序列号 + log_cxt->log_line_number = 0; // 日志行号 + log_cxt->log_my_pid = 0; // 日志进程ID + log_cxt->csv_log_line_number = 0; // CSV日志行号 + log_cxt->csv_log_my_pid = 0; // CSV日志进程ID + log_cxt->msgbuf = (StringInfoData*)palloc0(sizeof(StringInfoData)); // 消息缓冲区 + log_cxt->module_logging_configure = (unsigned char*)palloc0(sizeof(char) * BEMD_BITMAP_SIZE); // 模块日志配置 } +// 初始化重定位选项上下文结构体 static void knl_t_relopt_init(knl_t_relopt_context* relopt_cxt) { - relopt_cxt->relOpts = NULL; - relopt_cxt->last_assigned_kind = RELOPT_KIND_LAST_DEFAULT; - relopt_cxt->num_custom_options = 0; - relopt_cxt->custom_options = NULL; - relopt_cxt->need_initialization = true; - relopt_cxt->max_custom_options = 0; + relopt_cxt->relOpts = NULL; // 关系选项 + relopt_cxt->last_assigned_kind = RELOPT_KIND_LAST_DEFAULT; // 最后分配的选项种类 + relopt_cxt->num_custom_options = 0; // 自定义选项数量 + relopt_cxt->custom_options = NULL; // 自定义选项 + relopt_cxt->need_initialization = true; // 需要初始化标志 + relopt_cxt->max_custom_options = 0; // 最大自定义选项数量 } +// 初始化列式存储上下文结构体 static void knl_t_cstore_init(knl_t_cstore_context* cstore_cxt) { - cstore_cxt->gCStoreAlterReg = NULL; - cstore_cxt->bulkload_memsize_used = 0; - cstore_cxt->cstore_prefetch_count = 0; - cstore_cxt->InProgressAioCUDispatch = NULL; - cstore_cxt->InProgressAioCUDispatchCount = 0; + cstore_cxt->gCStoreAlterReg = NULL; // 列式存储变更注册 + cstore_cxt->bulkload_memsize_used = 0; // 批量加载内存使用量 + cstore_cxt->cstore_prefetch_count = 0; // 列式存储预取数量 + cstore_cxt->InProgressAioCUDispatch = NULL; // 进行中的异步IO CU分发 + cstore_cxt->InProgressAioCUDispatchCount = 0; // 进行中的异步IO CU分发数量 } +// 初始化DFS上下文结构体 static void knl_t_dfs_init(knl_t_dfs_context* dfs_cxt) { - dfs_cxt->pending_free_reader_list = NIL; - dfs_cxt->pending_free_writer_list = NIL; + dfs_cxt->pending_free_reader_list = NIL; // 待释放的读取器列表 + dfs_cxt->pending_free_writer_list = NIL; // 待释放的写入器列表 } +// 初始化OBS上下文结构体 static void knl_t_obs_init(knl_t_obs_context* obs_cxt) { - obs_cxt->ObsMemoryContext = NULL; - obs_cxt->pCAInfo = NULL; + obs_cxt->ObsMemoryContext = NULL; // OBS内存上下文 + obs_cxt->pCAInfo = NULL; // CA信息 #ifndef SLEEP_UNITS_PER_SECOND #define SLEEP_UNITS_PER_SECOND 1 #endif - obs_cxt->retrySleepInterval = 1 * SLEEP_UNITS_PER_SECOND; + obs_cxt->retrySleepInterval = 1 * SLEEP_UNITS_PER_SECOND; // 重试睡眠间隔 } +// 初始化CBM上下文结构体 static void knl_t_cbm_init(knl_t_cbm_context* cbm_cxt) { - cbm_cxt->XlogCbmSys = NULL; + cbm_cxt->XlogCbmSys = NULL; // Xlog控制块管理系统 - cbm_cxt->got_SIGHUP = false; - cbm_cxt->shutdown_requested = false; - cbm_cxt->cbmwriter_context = NULL; - cbm_cxt->cbmwriter_page_context = NULL; + cbm_cxt->got_SIGHUP = false; // 收到SIGHUP信号标志 + cbm_cxt->shutdown_requested = false; // 请求关闭标志 + cbm_cxt->cbmwriter_context = NULL; // CBM写入器上下文 + cbm_cxt->cbmwriter_page_context = NULL; // CBM写入器页面上下文 } +// 初始化共享内存指针上下文结构体 static void knl_t_shemem_ptr_init(knl_t_shemem_ptr_context* shemem_ptr_cxt) { - shemem_ptr_cxt->scan_locations = NULL; - shemem_ptr_cxt->MultiXactOffsetCtl = (SlruCtlData*)palloc0(sizeof(SlruCtlData)); - shemem_ptr_cxt->MultiXactMemberCtl = (SlruCtlData*)palloc0(sizeof(SlruCtlData)); - shemem_ptr_cxt->MultiXactState = NULL; - shemem_ptr_cxt->OldestMemberMXactId = NULL; - shemem_ptr_cxt->OldestVisibleMXactId = NULL; - shemem_ptr_cxt->ClogCtl = NULL; - shemem_ptr_cxt->CsnlogCtlPtr = NULL; - shemem_ptr_cxt->XLogCtl = NULL; - shemem_ptr_cxt->GlobalWALInsertLocks = NULL; - shemem_ptr_cxt->LocalGroupWALInsertLocks = NULL; - shemem_ptr_cxt->ControlFile = NULL; - shemem_ptr_cxt->g_LsnXlogFlushChkFile = NULL; - shemem_ptr_cxt->OldSerXidSlruCtl = (SlruCtlData*)palloc0(sizeof(SlruCtlData)); - shemem_ptr_cxt->oldSerXidControl = NULL; - shemem_ptr_cxt->OldCommittedSxact = NULL; - shemem_ptr_cxt->PredXact = NULL; - shemem_ptr_cxt->RWConflictPool = NULL; - shemem_ptr_cxt->SerializableXidHash = NULL; - shemem_ptr_cxt->PredicateLockTargetHash = NULL; - shemem_ptr_cxt->PredicateLockHash = NULL; - shemem_ptr_cxt->FinishedSerializableTransactions = NULL; + shemem_ptr_cxt->scan_locations = NULL; // 扫描位置 + shemem_ptr_cxt->MultiXactOffsetCtl = (SlruCtlData*)palloc0(sizeof(SlruCtlData)); // 多重事务偏移控制块 + shemem_ptr_cxt->MultiXactMemberCtl = (SlruCtlData*)palloc0(sizeof(SlruCtlData)); // 多重事务成员控制块 + shemem_ptr_cxt->MultiXactState = NULL; // 多重事务状态 + shemem_ptr_cxt->OldestMemberMXactId = NULL; // 最旧的多重事务ID + shemem_ptr_cxt->OldestVisibleMXactId = NULL; // 最旧可见的多重事务ID + shemem_ptr_cxt->ClogCtl = NULL; // CLOG控制块 + shemem_ptr_cxt->CsnlogCtlPtr = NULL; // CSNLOG控制块指针 + shemem_ptr_cxt->XLogCtl = NULL; // XLOG控制块 + shemem_ptr_cxt->GlobalWALInsertLocks = NULL; // 全局WAL插入锁 + shemem_ptr_cxt->LocalGroupWALInsertLocks = NULL; // 本地组WAL插入锁 + shemem_ptr_cxt->ControlFile = NULL; // 控制文件 + shemem_ptr_cxt->g_LsnXlogFlushChkFile = NULL; // LSN Xlog刷新检查文件 + shemem_ptr_cxt->OldSerXidSlruCtl = (SlruCtlData*)palloc0(sizeof(SlruCtlData)); // 旧的序列化事务ID SLRU控制块 + shemem_ptr_cxt->oldSerXidControl = NULL; // 旧的序列化事务ID控制 + shemem_ptr_cxt->OldCommittedSxact = NULL; // 旧的提交的可序列化事务 + shemem_ptr_cxt->PredXact = NULL; // 预测事务 + shemem_ptr_cxt->RWConflictPool = NULL; // 读写冲突池 + shemem_ptr_cxt->SerializableXidHash = NULL; // 可序列化事务ID哈希表 + shemem_ptr_cxt->PredicateLockTargetHash = NULL; // 谓词锁目标哈希表 + shemem_ptr_cxt->PredicateLockHash = NULL; // 谓词锁哈希表 + shemem_ptr_cxt->FinishedSerializableTransactions = NULL; // 已完成的可序列化事务 - shemem_ptr_cxt->BackendStatusArray = NULL; - shemem_ptr_cxt->BackendClientHostnameBuffer = NULL; - shemem_ptr_cxt->BackendAppnameBuffer = NULL; - shemem_ptr_cxt->BackendConninfoBuffer = NULL; - shemem_ptr_cxt->BackendActivityBuffer = NULL; - shemem_ptr_cxt->WaitCountBuffer = NULL; - shemem_ptr_cxt->BackendActivityBufferSize = 0; + shemem_ptr_cxt->BackendStatusArray = NULL; // 后端状态数组 + shemem_ptr_cxt->BackendClientHostnameBuffer = NULL; // 后端客户端主机名缓冲区 + shemem_ptr_cxt->BackendAppnameBuffer = NULL; // 后端应用程序名缓冲区 + shemem_ptr_cxt->BackendConninfoBuffer = NULL; // 后端连接信息缓冲区 + shemem_ptr_cxt->BackendActivityBuffer = NULL; // 后端活动信息缓冲区 + shemem_ptr_cxt->WaitCountBuffer = NULL; // 等待计数缓冲区 + shemem_ptr_cxt->BackendActivityBufferSize = 0; // 后端活动信息缓冲区大小 - shemem_ptr_cxt->MyBEEntry = NULL; + shemem_ptr_cxt->MyBEEntry = NULL; // 当前后端进程入口 - shemem_ptr_cxt->mySessionStatEntry = NULL; - shemem_ptr_cxt->sessionStatArray = NULL; - shemem_ptr_cxt->mySessionMemoryEntry = NULL; - shemem_ptr_cxt->sessionMemoryArray = NULL; + shemem_ptr_cxt->mySessionStatEntry = NULL; // 当前会话统计信息入口 + shemem_ptr_cxt->sessionStatArray = NULL; // 会话统计信息数组 + shemem_ptr_cxt->mySessionMemoryEntry = NULL; // 当前会话内存信息入口 + shemem_ptr_cxt->sessionMemoryArray = NULL; // 会话内存信息数组 - shemem_ptr_cxt->mySessionTimeEntry = NULL; - shemem_ptr_cxt->sessionTimeArray = NULL; + shemem_ptr_cxt->mySessionTimeEntry = NULL; // 当前会话时间信息入口 + shemem_ptr_cxt->sessionTimeArray = NULL; // 会话时间信息数组 - shemem_ptr_cxt->ProcSignalSlots = NULL; - shemem_ptr_cxt->MyProcSignalSlot = NULL; - shemem_ptr_cxt->ShmemSegHdr = NULL; - shemem_ptr_cxt->ShmemBase = NULL; - shemem_ptr_cxt->ShmemEnd = NULL; - shemem_ptr_cxt->ShmemLock = NULL; - shemem_ptr_cxt->ShmemIndex = NULL; - shemem_ptr_cxt->shmInvalBuffer = NULL; - shemem_ptr_cxt->PMSignalState = NULL; - shemem_ptr_cxt->mainLWLockArray = NULL; + shemem_ptr_cxt->ProcSignalSlots = NULL; // 进程信号槽数组 + shemem_ptr_cxt->MyProcSignalSlot = NULL; // 当前进程信号槽 + shemem_ptr_cxt->ShmemSegHdr = NULL; // 共享内存段头部 + shemem_ptr_cxt->ShmemBase = NULL; // 共享内存基址 + shemem_ptr_cxt->ShmemEnd = NULL; // 共享内存结束地址 + shemem_ptr_cxt->ShmemLock = NULL; // 共享内存锁 + shemem_ptr_cxt->ShmemIndex = NULL; // 共享内存索引 + shemem_ptr_cxt->shmInvalBuffer = NULL; // 共享内存失效缓冲区 + shemem_ptr_cxt->PMSignalState = NULL; // PM信号状态 + shemem_ptr_cxt->mainLWLockArray = NULL; // 主LW锁数组 } static void knl_t_xact_init(knl_t_xact_context* xact_cxt) { errno_t rc; - /* init var in transam.cpp */ + /* 在tranam .cpp中初始化变量 */ xact_cxt->cachedFetchCSNXid = InvalidTransactionId; xact_cxt->cachedFetchCSN = 0; xact_cxt->latestFetchCSNXid = InvalidTransactionId; @@ -287,11 +302,11 @@ static void knl_t_xact_init(knl_t_xact_context* xact_cxt) xact_cxt->cachedFetchXidStatus = 0; xact_cxt->cachedCommitLSN = 0; - /* init var in multixact.cpp */ + /* 在multixact.cpp中的初始化变量 */ xact_cxt->MXactCache = NULL; xact_cxt->MXactContext = NULL; - /* init var in twophase.cpp */ + /* 在twophase.cpp中的初始化变量 */ xact_cxt->MyLockedGxact = NULL; xact_cxt->twophaseExitRegistered = false; xact_cxt->cached_xid = InvalidTransactionId; @@ -300,7 +315,7 @@ static void knl_t_xact_init(knl_t_xact_context* xact_cxt) rc = memset_s(&xact_cxt->records, sizeof(xllist), 0, sizeof(xllist)); securec_check(rc, "\0", "\0"); - /* init var in varsup.cpp */ + /* 在varsup.cpp中的初始化变量*/ #define FirstBootstrapObjectId 10000 xact_cxt->cn_xid = InvalidTransactionId; xact_cxt->next_xid = InvalidTransactionId; @@ -308,7 +323,7 @@ static void knl_t_xact_init(knl_t_xact_context* xact_cxt) xact_cxt->InplaceUpgradeNextOid = FirstBootstrapObjectId; xact_cxt->ShmemVariableCache = NULL; - /* init var in xact.cpp */ + /* 在xact.cpp中的初始化变量 */ xact_cxt->CancelStmtForReadOnly = false; xact_cxt->MyXactAccessedTempRel = false; xact_cxt->MyXactAccessedRepRel = false; @@ -336,21 +351,21 @@ static void knl_t_xact_init(knl_t_xact_context* xact_cxt) xact_cxt->XactPrepareSent = false; xact_cxt->AlterCoordinatorStmt = false; xact_cxt->forceSyncCommit = false; - /* alloc in TopMemory Context, initialization is NULL when create new thread */ + /* 在TopMemory上下文中,创建新线程时初始化为NULL */ xact_cxt->TransactionAbortContext = NULL; xact_cxt->Seq_callbacks = NULL; xact_cxt->lxid = InvalidTransactionId; xact_cxt->stablexid = InvalidTransactionId; - /* init var in gtm.cpp */ + /* 在gtm.cpp中初始化变量 */ xact_cxt->currentGxid = InvalidTransactionId; xact_cxt->conn = NULL; - /* init var in slru.cpp */ + /* 在slru.cpp中初始化变量 */ xact_cxt->slru_errcause = SLRU_MAX_FAILED; xact_cxt->slru_errno = 0; - /* init var in predicate.cpp */ + /* 在predicate.cpp中初始化变量 */ xact_cxt->ScratchTargetTagHash = 0; xact_cxt->ScratchPartitionLock = NULL; xact_cxt->LocalPredicateLockHash = NULL; @@ -371,148 +386,139 @@ static void knl_t_xact_init(knl_t_xact_context* xact_cxt) xact_cxt->isSelectInto = false; } +// 初始化内存上下文结构体 static void knl_t_mem_init(knl_t_mem_context* mem_cxt) { - mem_cxt->postmaster_mem_cxt = NULL; - mem_cxt->msg_mem_cxt = NULL; - mem_cxt->cur_transaction_mem_cxt = NULL; - mem_cxt->gs_signal_mem_cxt = NULL; - mem_cxt->mask_password_mem_cxt = NULL; - mem_cxt->row_desc_mem_cxt = NULL; - mem_cxt->portal_mem_cxt = NULL; - mem_cxt->mem_track_mem_cxt = NULL; - mem_cxt->batch_encode_numeric_mem_cxt = NULL; - mem_cxt->pgAuditLocalContext = NULL; + mem_cxt->postmaster_mem_cxt = NULL; // 启动进程内存上下文 + mem_cxt->msg_mem_cxt = NULL; // 消息内存上下文 + mem_cxt->cur_transaction_mem_cxt = NULL; // 当前事务内存上下文 + mem_cxt->gs_signal_mem_cxt = NULL; // GS信号内存上下文 + mem_cxt->mask_password_mem_cxt = NULL; // 掩码密码内存上下文 + mem_cxt->row_desc_mem_cxt = NULL; // 行描述内存上下文 + mem_cxt->portal_mem_cxt = NULL; // PORTAL内存上下文 + mem_cxt->mem_track_mem_cxt = NULL; // 内存跟踪内存上下文 + mem_cxt->batch_encode_numeric_mem_cxt = NULL; // 批量编码数字内存上下文 + mem_cxt->pgAuditLocalContext = NULL; // PG审计本地上下文 } +// 初始化XLOG上下文结构体 static void knl_t_xlog_init(knl_t_xlog_context* xlog_cxt) { errno_t rc; - xlog_cxt->ThisTimeLineID = 0; - xlog_cxt->InRecovery = false; - xlog_cxt->standbyState = STANDBY_DISABLED; - xlog_cxt->LastRec = 0; - xlog_cxt->latestRecordCrc = 0; - xlog_cxt->lastFullPageWrites = false; - xlog_cxt->LocalRecoveryInProgress = true; - xlog_cxt->LocalHotStandbyActive = false; - xlog_cxt->LocalXLogInsertAllowed = -1; - xlog_cxt->ArchiveRecoveryRequested = false; - xlog_cxt->InArchiveRecovery = false; - xlog_cxt->ArchiveRestoreRequested = false; - xlog_cxt->restoredFromArchive = false; - xlog_cxt->recoveryRestoreCommand = NULL; - xlog_cxt->recoveryEndCommand = NULL; - xlog_cxt->archiveCleanupCommand = NULL; - xlog_cxt->recoveryTarget = RECOVERY_TARGET_UNSET; - xlog_cxt->recoveryTargetInclusive = true; - xlog_cxt->recoveryPauseAtTarget = true; - xlog_cxt->recoveryTargetXid = InvalidTransactionId; - xlog_cxt->recoveryTargetTime = 0; - xlog_cxt->obsRecoveryTargetTime = NULL; - xlog_cxt->recoveryTargetBarrierId = NULL; - xlog_cxt->recoveryTargetName = NULL; - xlog_cxt->recoveryTargetLSN = InvalidXLogRecPtr; - xlog_cxt->isRoachSingleReplayDone = false; - xlog_cxt->StandbyModeRequested = false; - xlog_cxt->PrimaryConnInfo = NULL; - xlog_cxt->TriggerFile = NULL; - xlog_cxt->StandbyMode = false; - xlog_cxt->recoveryTriggered = false; - xlog_cxt->recoveryStopXid = InvalidTransactionId; - xlog_cxt->recoveryStopTime = 0; - xlog_cxt->recoveryStopLSN = InvalidXLogRecPtr; + xlog_cxt->ThisTimeLineID = 0; // 当前时间线ID + xlog_cxt->InRecovery = false; // 是否处于恢复状态 + xlog_cxt->standbyState = STANDBY_DISABLED; // 站点状态 + xlog_cxt->LastRec = 0; // 最后记录位置 + xlog_cxt->latestRecordCrc = 0; // 最新记录CRC + xlog_cxt->lastFullPageWrites = false; // 上次是否进行了完整页写入 + xlog_cxt->LocalRecoveryInProgress = true; // 本地恢复是否进行中 + xlog_cxt->LocalHotStandbyActive = false; // 本地热备是否激活 + xlog_cxt->LocalXLogInsertAllowed = -1; // 本地XLOG插入是否允许 + xlog_cxt->ArchiveRecoveryRequested = false; // 请求归档恢复标志 + xlog_cxt->InArchiveRecovery = false; // 是否处于归档恢复中 + xlog_cxt->ArchiveRestoreRequested = false; // 请求归档恢复恢复标志 + xlog_cxt->restoredFromArchive = false; // 是否从归档中恢复 + xlog_cxt->recoveryRestoreCommand = NULL; // 恢复恢复命令 + xlog_cxt->recoveryEndCommand = NULL; // 恢复结束命令 + xlog_cxt->archiveCleanupCommand = NULL; // 归档清理命令 + xlog_cxt->recoveryTarget = RECOVERY_TARGET_UNSET; // 恢复目标 + xlog_cxt->recoveryTargetInclusive = true; // 恢复目标是否包括在内 + xlog_cxt->recoveryPauseAtTarget = true; // 恢复时是否在目标位置暂停 + xlog_cxt->recoveryTargetXid = InvalidTransactionId; // 恢复目标事务ID + xlog_cxt->recoveryTargetTime = 0; // 恢复目标时间 + xlog_cxt->obsRecoveryTargetTime = NULL; // OBS恢复目标时间 + xlog_cxt->recoveryTargetBarrierId = NULL; // 恢复目标障碍ID + xlog_cxt->recoveryTargetName = NULL; // 恢复目标名称 + xlog_cxt->recoveryTargetLSN = InvalidXLogRecPtr; // 恢复目标LSN + xlog_cxt->isRoachSingleReplayDone = false; // Roach单个重放是否完成 + xlog_cxt->StandbyModeRequested = false; // 请求备用模式标志 + xlog_cxt->PrimaryConnInfo = NULL; // 主机连接信息 + xlog_cxt->TriggerFile = NULL; // 触发文件 + xlog_cxt->StandbyMode = false; // 备用模式标志 + xlog_cxt->recoveryTriggered = false; // 恢复是否被触发 + xlog_cxt->recoveryStopXid = InvalidTransactionId; // 恢复停止事务ID + xlog_cxt->recoveryStopTime = 0; // 恢复停止时间 + xlog_cxt->recoveryStopLSN = InvalidXLogRecPtr; // 恢复停止LSN rc = memset_s(xlog_cxt->recoveryStopName, MAXFNAMELEN * sizeof(char), 0, MAXFNAMELEN * sizeof(char)); - securec_check(rc, "\0", "\0"); - xlog_cxt->recoveryStopAfter = false; - xlog_cxt->recoveryTargetTLI = 0; - xlog_cxt->recoveryTargetIsLatest = false; - xlog_cxt->expectedTLIs = NIL; - xlog_cxt->curFileTLI = 0; - xlog_cxt->ProcLastRecPtr = InvalidXLogRecPtr; - xlog_cxt->XactLastRecEnd = InvalidXLogRecPtr; - xlog_cxt->XactLastCommitEnd = InvalidXLogRecPtr; - xlog_cxt->RedoRecPtr = InvalidXLogRecPtr; - xlog_cxt->doPageWrites = false; - xlog_cxt->RedoStartLSN = InvalidXLogRecPtr; - xlog_cxt->server_mode = UNKNOWN_MODE; - xlog_cxt->is_cascade_standby = false; - xlog_cxt->is_hadr_main_standby = false; - xlog_cxt->startup_processing = false; - xlog_cxt->openLogFile = -1; - xlog_cxt->openLogSegNo = 0; - xlog_cxt->openLogOff = 0; - xlog_cxt->readFile = -1; - xlog_cxt->readSegNo = 0; - xlog_cxt->readOff = 0; - xlog_cxt->readLen = 0; - xlog_cxt->readSource = 0; - xlog_cxt->failedSources = 0; - xlog_cxt->XLogReceiptTime = 0; - xlog_cxt->XLogReceiptSource = 0; + securec_check(rc, "\0", "\0"); // 初始化恢复停止名称 + xlog_cxt->recoveryStopAfter = false; // 恢复停止之后标志 + xlog_cxt->recoveryTargetTLI = 0; // 恢复目标时间线ID + xlog_cxt->recoveryTargetIsLatest = false; // 恢复目标是否是最新的 + xlog_cxt->expectedTLIs = NIL; // 预期的时间线ID列表 + xlog_cxt->curFileTLI = 0; // 当前文件时间线ID + xlog_cxt->ProcLastRecPtr = InvalidXLogRecPtr; // 进程最后记录位置 + xlog_cxt->XactLastRecEnd = InvalidXLogRecPtr; // 事务最后记录结束位置 + xlog_cxt->XactLastCommitEnd = InvalidXLogRecPtr; // 事务最后提交结束位置 + xlog_cxt->RedoRecPtr = InvalidXLogRecPtr; // 重做记录位置 + xlog_cxt->doPageWrites = false; // 是否进行页写入 + xlog_cxt->RedoStartLSN = InvalidXLogRecPtr; // 重做开始LSN + xlog_cxt->server_mode = UNKNOWN_MODE; // 服务器模式 + xlog_cxt->is_cascade_standby = false; // 是否是级联备机 + xlog_cxt->is_hadr_main_standby = false; // 是否是HADR主备机 + xlog_cxt->startup_processing = false; // 是否正在进行启动处理 + xlog_cxt->openLogFile = -1; // 打开的日志文件描述符 + xlog_cxt->openLogSegNo = 0; // 打开的日志段号 + xlog_cxt->openLogOff = 0; // 打开的日志偏移量 + xlog_cxt->readFile = -1; // 读取的日志文件描述符 + xlog_cxt->readSegNo = 0; // 读取的日志段号 + xlog_cxt->readOff = 0; // 读取的日志偏移量 + xlog_cxt->readLen = 0; // 读取的日志长度 + xlog_cxt->readSource = 0; // 读取的日志来源 + xlog_cxt->failedSources = 0; // 失败的日志来源 + xlog_cxt->XLogReceiptTime = 0; // XLOG接收时间 + xlog_cxt->XLogReceiptSource = 0; // XLOG接收来源 xlog_cxt->ReadRecPtr = 0; - xlog_cxt->EndRecPtr = 0; - xlog_cxt->minRecoveryPoint = 0; - xlog_cxt->updateMinRecoveryPoint = true; - xlog_cxt->reachedConsistency = false; - xlog_cxt->InRedo = false; - xlog_cxt->RedoDone = false; - xlog_cxt->bgwriterLaunched = false; - xlog_cxt->pagewriter_launched = false; - xlog_cxt->MyLockNo = 0; - xlog_cxt->holdingAllLocks = false; - xlog_cxt->lockToTry = -1; - xlog_cxt->cachedPage = 0; - xlog_cxt->cachedPos = NULL; #ifdef WIN32 - xlog_cxt->deletedcounter = 1; + xlog_cxt->deletedcounter = 1; // 删除计数器(仅限于Windows平台) #endif - rc = memset_s(xlog_cxt->buf, STR_TIME_BUF_LEN, 0, STR_TIME_BUF_LEN); - securec_check(rc, "\0", "\0"); - xlog_cxt->receivedUpto = InvalidXLogRecPtr; - xlog_cxt->lastComplaint = InvalidXLogRecPtr; - xlog_cxt->failover_triggered = false; - xlog_cxt->switchover_triggered = false; - xlog_cxt->registered_buffers = NULL; - xlog_cxt->max_registered_buffers = 0; - xlog_cxt->max_registered_block_id = 0; - xlog_cxt->mainrdata_head = NULL; - xlog_cxt->mainrdata_last = NULL; - xlog_cxt->mainrdata_len = 0; - xlog_cxt->ptr_hdr_rdt = (XLogRecData*)palloc0(sizeof(XLogRecData)); - xlog_cxt->hdr_scratch = NULL; - xlog_cxt->rdatas = NULL; - xlog_cxt->num_rdatas = 0; - xlog_cxt->max_rdatas = 0; - xlog_cxt->begininsert_called = false; - xlog_cxt->include_origin = false; - xlog_cxt->xloginsert_cxt = NULL; - xlog_cxt->invalid_page_tab = NULL; - xlog_cxt->remain_segs = NULL; - xlog_cxt->sendId = 0; - xlog_cxt->sendFile = -1; - xlog_cxt->sendSegNo = 0; - xlog_cxt->sendOff = 0; - xlog_cxt->sendTLI = 0; - xlog_cxt->incomplete_actions = NIL; - xlog_cxt->gin_opCtx = NULL; - xlog_cxt->gist_opCtx = NULL; - xlog_cxt->spg_opCtx = NULL; - xlog_cxt->CheckpointStats = (CheckpointStatsData*)palloc0(sizeof(CheckpointStatsData)); - xlog_cxt->LogwrtResult = (XLogwrtResult*)palloc0(sizeof(XLogwrtResult)); - xlog_cxt->LogwrtPaxos = (XLogwrtPaxos*)palloc0(sizeof(XLogwrtPaxos)); - xlog_cxt->needImmediateCkp = false; - xlog_cxt->redoItemIdx = 0; + rc = memset_s(xlog_cxt->buf, STR_TIME_BUF_LEN, 0, STR_TIME_BUF_LEN); // 初始化缓冲区 + + securec_check(rc, "\0", "\0"); + + xlog_cxt->receivedUpto = InvalidXLogRecPtr; // 已接收的XLOG记录位置 + xlog_cxt->lastComplaint = InvalidXLogRecPtr; // 上次投诉的XLOG记录位置 + xlog_cxt->failover_triggered = false; // 故障切换触发标志 + xlog_cxt->switchover_triggered = false; // 主备切换触发标志 + xlog_cxt->registered_buffers = NULL; // 注册的缓冲区 + xlog_cxt->max_registered_buffers = 0; // 最大注册缓冲区数 + xlog_cxt->max_registered_block_id = 0; // 最大注册块ID + xlog_cxt->mainrdata_head = NULL; // 主XLOG记录数据头部 + xlog_cxt->mainrdata_last = NULL; // 主XLOG记录数据尾部 + xlog_cxt->mainrdata_len = 0; // 主XLOG记录数据长度 + xlog_cxt->ptr_hdr_rdt = (XLogRecData*)palloc0(sizeof(XLogRecData)); // 记录头部数据指针 + xlog_cxt->hdr_scratch = NULL; // 记录头部临时缓冲区 + xlog_cxt->rdatas = NULL; // XLOG记录数据 + xlog_cxt->num_rdatas = 0; // XLOG记录数据个数 + xlog_cxt->max_rdatas = 0; // 最大XLOG记录数据个数 + xlog_cxt->begininsert_called = false; // 是否调用了beginInsert函数标志 + xlog_cxt->include_origin = false; // 是否包括origin标志 + xlog_cxt->xloginsert_cxt = NULL; // XLOG插入上下文 + xlog_cxt->invalid_page_tab = NULL; // 无效页面表 + xlog_cxt->remain_segs = NULL; // 剩余段信息 + xlog_cxt->sendId = 0; // 发送ID + xlog_cxt->sendFile = -1; // 发送文件描述符 + xlog_cxt->sendSegNo = 0; // 发送段号 + xlog_cxt->sendOff = 0; // 发送偏移量 + xlog_cxt->sendTLI = 0; // 发送时间线ID + xlog_cxt->incomplete_actions = NIL; // 不完整操作列表 + xlog_cxt->gin_opCtx = NULL; // GIN操作上下文 + xlog_cxt->gist_opCtx = NULL; // GIST操作上下文 + xlog_cxt->spg_opCtx = NULL; // SP-GiST操作上下文 + xlog_cxt->CheckpointStats = (CheckpointStatsData*)palloc0(sizeof(CheckpointStatsData)); // 检查点统计信息 + xlog_cxt->LogwrtResult = (XLogwrtResult*)palloc0(sizeof(XLogwrtResult)); // XLOG写入结果 + xlog_cxt->LogwrtPaxos = (XLogwrtPaxos*)palloc0(sizeof(XLogwrtPaxos)); // XLOG写入Paxos + xlog_cxt->needImmediateCkp = false; // 是否需要立即检查点标志 + xlog_cxt->redoItemIdx = 0; // 重做项索引 #ifndef ENABLE_MULTIPLE_NODES - xlog_cxt->committing_csn_list = NIL; + xlog_cxt->committing_csn_list = NIL; // 提交的CSN列表(仅限于单节点模式) #endif - xlog_cxt->max_page_flush_lsn = MAX_XLOG_REC_PTR; - xlog_cxt->redoInterruptCallBackFunc = NULL; - xlog_cxt->redoPageRepairCallBackFunc = NULL; - xlog_cxt->xlog_atomic_op = NULL; - xlog_cxt->currentRetryTimes = 0; + xlog_cxt->max_page_flush_lsn = MAX_XLOG_REC_PTR; // 最大页刷新LSN + xlog_cxt->redoInterruptCallBackFunc = NULL; // 重做中断回调函数 + xlog_cxt->redoPageRepairCallBackFunc = NULL; // 重做页面修复回调函数 + xlog_cxt->xlog_atomic_op = NULL; // XLOG原子操作 + xlog_cxt->currentRetryTimes = 0; // 当前重试次数 } static void knl_t_index_init(knl_t_index_context* index_cxt) @@ -537,285 +543,289 @@ static void knl_t_dynahash_init(knl_t_dynahash_context* dyhash_cxt) static void knl_t_interrupt_init(knl_t_interrupt_context* int_cxt) { - int_cxt->QueryCancelPending = false; - int_cxt->PoolValidateCancelPending = false; - int_cxt->ProcDiePending = false; - int_cxt->ClientConnectionLost = false; - int_cxt->StreamConnectionLost = false; - int_cxt->ImmediateInterruptOK = false; - int_cxt->InterruptHoldoffCount = 0; - int_cxt->QueryCancelHoldoffCount = 0; - int_cxt->CritSectionCount = 0; - int_cxt->InterruptByCN = false; - int_cxt->InterruptCountResetFlag = false; - int_cxt->ignoreBackendSignal = false; + int_cxt->QueryCancelPending = false; // 查询取消挂起标志 + int_cxt->PoolValidateCancelPending = false; // 池验证取消挂起标志 + int_cxt->ProcDiePending = false; // 进程退出挂起标志 + int_cxt->ClientConnectionLost = false; // 客户端连接丢失标志 + int_cxt->StreamConnectionLost = false; // 流连接丢失标志 + int_cxt->ImmediateInterruptOK = false; // 立即中断允许标志 + int_cxt->InterruptHoldoffCount = 0; // 中断保持计数 + int_cxt->QueryCancelHoldoffCount = 0; // 查询取消保持计数 + int_cxt->CritSectionCount = 0; // 临界区计数 + int_cxt->InterruptByCN = false; // 由CN中断标志 + int_cxt->InterruptCountResetFlag = false; // 中断计数重置标志 + int_cxt->ignoreBackendSignal = false; // 忽略后端信号标志 } static void knl_t_proc_init(knl_t_proc_context* proc_cxt) { - proc_cxt->MyProgName = "unknown"; - proc_cxt->MyBackendId = InvalidBackendId; - proc_cxt->MyStartTime = 0; - proc_cxt->DataDir = NULL; - proc_cxt->postgres_initialized = false; - proc_cxt->PostInit = (PostgresInitializer*)New(CurrentMemoryContext) PostgresInitializer(); - proc_cxt->proc_exit_inprogress = false; - proc_cxt->sess_exit_inprogress = false; - proc_cxt->pooler_connection_inprogress = false; - proc_cxt->MyPMChildSlot = 0; + proc_cxt->MyProgName = "unknown"; // 进程名称 + proc_cxt->MyBackendId = InvalidBackendId; // 后端ID + proc_cxt->MyStartTime = 0; // 进程启动时间 + proc_cxt->DataDir = NULL; // 数据目录 + proc_cxt->postgres_initialized = false; // PostgreSQL初始化标志 + proc_cxt->PostInit = (PostgresInitializer*)New(CurrentMemoryContext) PostgresInitializer(); // 后初始化对象 + proc_cxt->proc_exit_inprogress = false; // 进程退出进行中标志 + proc_cxt->sess_exit_inprogress = false; // 会话退出进行中标志 + proc_cxt->pooler_connection_inprogress = false; // 连接池连接进行中标志 + proc_cxt->MyPMChildSlot = 0; // 进程管理器子进程槽位 } static void knl_t_wlm_init(knl_t_wlmthrd_context* wlm_cxt) { - wlm_cxt->thread_node_group = NULL; - wlm_cxt->thread_climgr = NULL; - wlm_cxt->thread_srvmgr = NULL; - wlm_cxt->wlm_got_sighup = 0; - wlm_cxt->wlmalarm_pending = false; - wlm_cxt->wlmalarm_timeout_active = false; - wlm_cxt->wlmalarm_dump_active = false; - wlm_cxt->wlm_xact_start = false; - wlm_cxt->has_cursor_record = false; - wlm_cxt->wlmalarm_fin_time = 0; - wlm_cxt->MaskPasswordMemoryContext = NULL; - wlm_cxt->query_resource_track_mcxt = NULL; + wlm_cxt->thread_node_group = NULL; // 节点组线程 + wlm_cxt->thread_climgr = NULL; // 客户端管理线程 + wlm_cxt->thread_srvmgr = NULL; // 服务管理线程 + wlm_cxt->wlm_got_sighup = 0; // WLM收到SIGHUP信号计数 + wlm_cxt->wlmalarm_pending = false; // WLM告警挂起标志 + wlm_cxt->wlmalarm_timeout_active = false; // WLM告警超时活跃标志 + wlm_cxt->wlmalarm_dump_active = false; // WLM告警转储活跃标志 + wlm_cxt->wlm_xact_start = false; // WLM事务启动标志 + wlm_cxt->has_cursor_record = false; // 是否有游标记录标志 + wlm_cxt->wlmalarm_fin_time = 0; // WLM告警结束时间 + wlm_cxt->MaskPasswordMemoryContext = NULL; // 遮蔽密码内存上下文 + wlm_cxt->query_resource_track_mcxt = NULL; // 查询资源追踪内存上下文 - wlm_cxt->except_ctl = (ExceptionManager*)palloc0(sizeof(ExceptionManager)); - wlm_cxt->collect_info = (WLMCollectInfo*)palloc0(sizeof(WLMCollectInfo)); - wlm_cxt->dn_cpu_detail = (WLMDNRealTimeInfoDetail*)palloc0(sizeof(WLMDNRealTimeInfoDetail)); + wlm_cxt->except_ctl = (ExceptionManager*)palloc0(sizeof(ExceptionManager)); // 异常管理器 + wlm_cxt->collect_info = (WLMCollectInfo*)palloc0(sizeof(WLMCollectInfo)); // WLM采集信息 + wlm_cxt->dn_cpu_detail = (WLMDNRealTimeInfoDetail*)palloc0(sizeof(WLMDNRealTimeInfoDetail)); // DN CPU详细信息 } static void knl_t_audit_init(knl_t_audit_context* audit) { - audit->audit_indextbl = NULL; - audit->sysauditFile = NULL; - audit->policyauditFile = NULL; - audit->Audit_delete = false; - audit->pipe_eof_seen = false; - audit->rotation_disabled = false; - audit->pgaudit_totalspace = 0; - audit->user_login_time = 0; - audit->need_exit = false; - audit->got_SIGHUP = false; - audit->rotation_requested = false; - audit->space_beyond_size = (10 * 1024 * 1024); - audit->pgaudit_filepath[0] = '\0'; - audit->cur_thread_idx = -1; + audit->audit_indextbl = NULL; // 审计索引表 + audit->sysauditFile = NULL; // 系统审计文件 + audit->policyauditFile = NULL; // 策略审计文件 + audit->Audit_delete = false; // 审计删除标志 + audit->pipe_eof_seen = false; // 管道结束标志 + audit->rotation_disabled = false; // 旋转禁用标志 + audit->pgaudit_totalspace = 0; // Pgaudit总空间 + audit->user_login_time = 0; // 用户登录时间 + audit->need_exit = false; // 需要退出标志 + audit->got_SIGHUP = false; // 收到SIGHUP信号标志 + audit->rotation_requested = false; // 旋转请求标志 + audit->space_beyond_size = (10 * 1024 * 1024); // 超过空间大小 + audit->pgaudit_filepath[0] = '\0'; // Pgaudit文件路径 + audit->cur_thread_idx = -1; // 当前线程索引 } static void knl_t_async_init(knl_t_async_context* asy_cxt) { - asy_cxt->listenChannels = NIL; - asy_cxt->pendingActions = NIL; - asy_cxt->upperPendingActions = NIL; - asy_cxt->pendingNotifies = NIL; - asy_cxt->upperPendingNotifies = NIL; - asy_cxt->unlistenExitRegistered = false; - asy_cxt->amRegisteredListener = false; - asy_cxt->backendHasSentNotifications = false; + asy_cxt->listenChannels = NIL; // 监听通道列表 + asy_cxt->pendingActions = NIL; // 待处理动作列表 + asy_cxt->upperPendingActions = NIL; // 上层待处理动作列表 + asy_cxt->pendingNotifies = NIL; // 待通知列表 + asy_cxt->upperPendingNotifies = NIL; // 上层待通知列表 + asy_cxt->unlistenExitRegistered = false; // 注册退出监听标志 + asy_cxt->amRegisteredListener = false; // 注册监听器标志 + asy_cxt->backendHasSentNotifications = false; // 后端发送通知标志 } static void knl_t_explain_init(knl_t_explain_context* explain_cxt) { - explain_cxt->explain_light_proxy = false; + explain_cxt->explain_light_proxy = false; // 轻量级代理解释标志 } static void knl_t_vacuum_init(knl_t_vacuum_context* vacuum_cxt) { - vacuum_cxt->VacuumPageHit = 0; - vacuum_cxt->VacuumPageMiss = 0; - vacuum_cxt->VacuumPageDirty = 0; - vacuum_cxt->VacuumCostBalance = 0; - vacuum_cxt->VacuumCostActive = false; - vacuum_cxt->vacuum_full_compact = false; - vacuum_cxt->vac_context = NULL; - vacuum_cxt->in_vacuum = false; + vacuum_cxt->VacuumPageHit = 0; // VACUUM页面命中计数 + vacuum_cxt->VacuumPageMiss = 0; // VACUUM页面丢失计数 + vacuum_cxt->VacuumPageDirty = 0; // VACUUM脏页面计数 + vacuum_cxt->VacuumCostBalance = 0; // VACUUM成本平衡 + vacuum_cxt->VacuumCostActive = false; // VACUUM成本激活标志 + vacuum_cxt->vacuum_full_compact = false; // VACUUM全量紧凑标志 + vacuum_cxt->vac_context = NULL; // VACUUM内存上下文 + vacuum_cxt->in_vacuum = false; // VACUUM进行中标志 } static void knl_t_arch_init(knl_t_arch_context* arch) { - arch->got_SIGHUP = false; - arch->got_SIGTERM = false; - arch->wakened = false; - arch->ready_to_stop = false; - arch->last_sigterm_time = 0; - arch->pitr_task_last_lsn = 0; - arch->task_wait_interval = 1000; - arch->last_arch_time = 0; - arch->arch_start_timestamp = 0; - arch->arch_start_lsn = InvalidXLogRecPtr; - arch->sync_walsender_idx = -1; + arch->got_SIGHUP = false; // 收到SIGHUP信号标志 + arch->got_SIGTERM = false; // 收到SIGTERM信号标志 + arch->wakened = false; // 唤醒标志 + arch->ready_to_stop = false; // 准备停止标志 + arch->last_sigterm_time = 0; // 上次SIGTERM时间 + arch->pitr_task_last_lsn = 0; // PITR任务上次LSN + arch->task_wait_interval = 1000; // 任务等待间隔 + arch->last_arch_time = 0; // 上次归档时间 + arch->arch_start_timestamp = 0; // 归档开始时间戳 + arch->arch_start_lsn = InvalidXLogRecPtr; // 归档开始LSN + arch->sync_walsender_idx = -1; // 同步WAL发送者索引 #ifndef ENABLE_MULTIPLE_NODES - arch->sync_follower_id = -1; + arch->sync_follower_id = -1; // 同步Follower ID #endif } -static void knl_t_barrier_arch_init(knl_t_barrier_arch_context* barrier_arch) +static void knl_t_audit_init(knl_t_audit_context* audit) { - barrier_arch->got_SIGHUP = false; - barrier_arch->got_SIGTERM = false; - barrier_arch->wakened = false; - barrier_arch->ready_to_stop = false; - barrier_arch->slot_name = NULL; - barrier_arch->lastArchiveLoc = InvalidXLogRecPtr; + audit->audit_indextbl = NULL; // 审计索引表 + audit->sysauditFile = NULL; // 系统审计文件 + audit->policyauditFile = NULL; // 策略审计文件 + audit->Audit_delete = false; // 审计删除标志 + audit->pipe_eof_seen = false; // 管道结束标志 + audit->rotation_disabled = false; // 旋转禁用标志 + audit->pgaudit_totalspace = 0; // Pgaudit总空间 + audit->user_login_time = 0; // 用户登录时间 + audit->need_exit = false; // 需要退出标志 + audit->got_SIGHUP = false; // 收到SIGHUP信号标志 + audit->rotation_requested = false; // 旋转请求标志 + audit->space_beyond_size = (10 * 1024 * 1024); // 超过空间大小 + audit->pgaudit_filepath[0] = '\0'; // Pgaudit文件路径 + audit->cur_thread_idx = -1; // 当前线程索引 } - -static void knl_t_logger_init(knl_t_logger_context* logger) +static void knl_t_async_init(knl_t_async_context* asy_cxt) { - logger->pipe_eof_seen = false; - logger->rotation_disabled = false; - logger->syslogFile = NULL; - logger->csvlogFile = NULL; - logger->querylogFile = NULL; - logger->asplogFile = NULL; - logger->first_syslogger_file_time = 0; - logger->last_file_name = NULL; - logger->last_csv_file_name = NULL; - logger->last_asp_file_name = NULL; - logger->got_SIGHUP = false; - logger->rotation_requested = false; + asy_cxt->listenChannels = NIL; // 监听通道列表 + asy_cxt->pendingActions = NIL; // 待处理动作列表 + asy_cxt->upperPendingActions = NIL; // 上层待处理动作列表 + asy_cxt->pendingNotifies = NIL; // 待通知列表 + asy_cxt->upperPendingNotifies = NIL; // 上层待通知列表 + asy_cxt->unlistenExitRegistered = false; // 注册退出监听标志 + asy_cxt->amRegisteredListener = false; // 注册监听器标志 + asy_cxt->backendHasSentNotifications = false; // 后端发送通知标志 } -static void knl_t_bulkload_init(knl_t_bulkload_context* bulk_cxt) +static void knl_t_explain_init(knl_t_explain_context* explain_cxt) { - int rc = 0; - - rc = memset_s(bulk_cxt->distExportDataDir, MAX_PATH_LEN, 0, MAX_PATH_LEN); - securec_check(rc, "\0", "\0"); - rc = memset_s(bulk_cxt->distExportTimestampStr, TIME_STAMP_STR_LEN, 0, TIME_STAMP_STR_LEN); - securec_check(rc, "\0", "\0"); - - bulk_cxt->distExportCurrXid = 0; - bulk_cxt->distExportNextSegNo = 0; - bulk_cxt->illegal_character_err_cnt = 0; - bulk_cxt->illegal_character_err_threshold_reported = false; + explain_cxt->explain_light_proxy = false; // 轻量级代理解释标志 } -static void knl_t_job_init(knl_t_job_context* job_cxt) +static void knl_t_vacuum_init(knl_t_vacuum_context* vacuum_cxt) { - job_cxt->JobScheduleShmem = NULL; - job_cxt->got_SIGHUP = false; - job_cxt->got_SIGUSR2 = false; - job_cxt->got_SIGTERM = false; - job_cxt->JobScheduleMemCxt = NULL; - job_cxt->ExpiredJobList = NULL; - job_cxt->ExpiredJobListCtx = NULL; - job_cxt->MyWorkerInfo = NULL; + vacuum_cxt->VacuumPageHit = 0; // VACUUM页面命中计数 + vacuum_cxt->VacuumPageMiss = 0; // VACUUM页面丢失计数 + vacuum_cxt->VacuumPageDirty = 0; // VACUUM脏页面计数 + vacuum_cxt->VacuumCostBalance = 0; // VACUUM成本平衡 + vacuum_cxt->VacuumCostActive = false; // VACUUM成本激活标志 + vacuum_cxt->vacuum_full_compact = false; // VACUUM全量紧凑标志 + vacuum_cxt->vac_context = NULL; // VACUUM内存上下文 + vacuum_cxt->in_vacuum = false; // VACUUM进行中标志 } -static void knl_t_basebackup_init(knl_t_basebackup_context* basebackup_cxt) +static void knl_t_arch_init(knl_t_arch_context* arch) { - int rc = memset_s(basebackup_cxt->g_xlog_location, MAXPGPATH, 0, MAXPGPATH); - securec_check(rc, "\0", "\0"); - basebackup_cxt->buf_block = NULL; + arch->got_SIGHUP = false; // 收到SIGHUP信号标志 + arch->got_SIGTERM = false; // 收到SIGTERM信号标志 + arch->wakened = false; // 唤醒标志 + arch->ready_to_stop = false; // 准备停止标志 + arch->last_sigterm_time = 0; // 上次SIGTERM时间 + arch->pitr_task_last_lsn = 0; // PITR任务上次LSN + arch->task_wait_interval = 1000; // 任务等待间隔 + arch->last_arch_time = 0; // 上次归档时间 + arch->arch_start_timestamp = 0; // 归档开始时间戳 + arch->arch_start_lsn = InvalidXLogRecPtr; // 归档开始LSN + arch->sync_walsender_idx = -1; // 同步WAL发送者索引 +#ifndef ENABLE_MULTIPLE_NODES + arch->sync_follower_id = -1; // 同步Follower ID +#endif } static void knl_t_datarcvwriter_init(knl_t_datarcvwriter_context* datarcvwriter_cxt) { - datarcvwriter_cxt->data_writer_rel_tab = NULL; - datarcvwriter_cxt->dataRcvWriterFlushPageErrorCount = 0; - datarcvwriter_cxt->gotSIGHUP = false; - datarcvwriter_cxt->shutdownRequested = false; - datarcvwriter_cxt->AmDataReceiverForDummyStandby = false; - datarcvwriter_cxt->dummy_data_writer_file_num = 0; + datarcvwriter_cxt->data_writer_rel_tab = NULL; // 数据写入关系表 + datarcvwriter_cxt->dataRcvWriterFlushPageErrorCount = 0; // 数据接收写入刷新页面错误计数 + datarcvwriter_cxt->gotSIGHUP = false; // 收到SIGHUP信号标志 + datarcvwriter_cxt->shutdownRequested = false; // 关机请求标志 + datarcvwriter_cxt->AmDataReceiverForDummyStandby = false; // 是否为虚拟备节点的数据接收者标志 + datarcvwriter_cxt->dummy_data_writer_file_num = 0; // 虚拟备节点数据写入文件数目 } static void knl_t_libwalreceiver_init(knl_t_libwalreceiver_context* libwalreceiver_cxt) { - libwalreceiver_cxt->streamConn = NULL; - libwalreceiver_cxt->recvBuf = NULL; - libwalreceiver_cxt->shared_storage_buf = NULL; - libwalreceiver_cxt->shared_storage_read_buf = NULL; - libwalreceiver_cxt->decompressBuf = NULL; - libwalreceiver_cxt->xlogreader = NULL; + libwalreceiver_cxt->streamConn = NULL; // 流连接 + libwalreceiver_cxt->recvBuf = NULL; // 接收缓冲区 + libwalreceiver_cxt->shared_storage_buf = NULL; // 共享存储缓冲区 + libwalreceiver_cxt->shared_storage_read_buf = NULL; // 共享存储读取缓冲区 + libwalreceiver_cxt->decompressBuf = NULL; // 解压缩缓冲区 + libwalreceiver_cxt->xlogreader = NULL; // xlog读取器 } static void knl_t_sig_init(knl_t_sig_context* sig_cxt) { - sig_cxt->signal_handle_cnt = 0; - sig_cxt->gs_sigale_check_type = SIGNAL_CHECK_NONE; - sig_cxt->session_id = 0; - sig_cxt->cur_ctrl_index = 0; + sig_cxt->signal_handle_cnt = 0; // 信号处理计数 + sig_cxt->gs_sigale_check_type = SIGNAL_CHECK_NONE; // 信号检查类型 + sig_cxt->session_id = 0; // 会话ID + sig_cxt->cur_ctrl_index = 0; // 当前控制索引 } static void knl_t_slot_init(knl_t_slot_context* slot_cxt) { - slot_cxt->ReplicationSlotCtl = NULL; - slot_cxt->MyReplicationSlot = NULL; + slot_cxt->ReplicationSlotCtl = NULL; // 复制插槽控制信息 + slot_cxt->MyReplicationSlot = NULL; // 当前复制插槽 } static void knl_t_datareceiver_init(knl_t_datareceiver_context* datareceiver_cxt) { - datareceiver_cxt->DataReplFlag = 1; - datareceiver_cxt->got_SIGHUP = false; - datareceiver_cxt->got_SIGTERM = false; - datareceiver_cxt->reply_message = (StandbyDataReplyMessage*)palloc0(sizeof(StandbyDataReplyMessage)); - datareceiver_cxt->dataStreamingConn = NULL; - datareceiver_cxt->AmDataReceiverForDummyStandby = false; - datareceiver_cxt->recvBuf = NULL; - datareceiver_cxt->DataRcv = NULL; - datareceiver_cxt->DataRcvImmediateInterruptOK = false; + datareceiver_cxt->DataReplFlag = 1; // 数据复制标志 + datareceiver_cxt->got_SIGHUP = false; // 收到SIGHUP信号标志 + datareceiver_cxt->got_SIGTERM = false; // 收到SIGTERM信号标志 + datareceiver_cxt->reply_message = (StandbyDataReplyMessage*)palloc0(sizeof(StandbyDataReplyMessage)); // 回复消息 + datareceiver_cxt->dataStreamingConn = NULL; // 数据流连接 + datareceiver_cxt->AmDataReceiverForDummyStandby = false; // 是否为虚拟备节点的数据接收者标志 + datareceiver_cxt->recvBuf = NULL; // 接收缓冲区 + datareceiver_cxt->DataRcv = NULL; // 数据接收器 + datareceiver_cxt->DataRcvImmediateInterruptOK = false; // 数据接收器立即中断标志 } static void knl_t_datasender_init(knl_t_datasender_context* datasender_cxt) { - datasender_cxt->DataSndCtl = NULL; - datasender_cxt->MyDataSnd = NULL; - datasender_cxt->am_datasender = false; - datasender_cxt->reply_message = (StringInfoData*)palloc0(sizeof(StringInfoData)); - datasender_cxt->output_message = NULL; - datasender_cxt->dummy_data_read_file_num = 1; - datasender_cxt->dummy_data_read_file_fd = NULL; - datasender_cxt->ping_sent = false; - datasender_cxt->got_SIGHUP = false; - datasender_cxt->datasender_shutdown_requested = false; - datasender_cxt->datasender_ready_to_stop = false; + datasender_cxt->DataSndCtl = NULL; // 数据发送控制信息 + datasender_cxt->MyDataSnd = NULL; // 当前数据发送节点 + datasender_cxt->am_datasender = false; // 是否为数据发送者标志 + datasender_cxt->reply_message = (StringInfoData*)palloc0(sizeof(StringInfoData)); // 回复消息 + datasender_cxt->output_message = NULL; // 输出消息 + datasender_cxt->dummy_data_read_file_num = 1; // 虚拟数据读取文件数目 + datasender_cxt->dummy_data_read_file_fd = NULL; // 虚拟数据读取文件描述符 + datasender_cxt->ping_sent = false; // 发送ping消息标志 + datasender_cxt->got_SIGHUP = false; // 收到SIGHUP信号标志 + datasender_cxt->datasender_shutdown_requested = false; // 数据发送器关机请求标志 + datasender_cxt->datasender_ready_to_stop = false; // 数据发送器准备停止标志 } static void knl_t_walreceiverfuncs_init(knl_t_walreceiverfuncs_context* walreceiverfuncs_cxt) { - walreceiverfuncs_cxt->WalRcv = NULL; - walreceiverfuncs_cxt->WalReplIndex = 1; + walreceiverfuncs_cxt->WalRcv = NULL; // WAL接收器 + walreceiverfuncs_cxt->WalReplIndex = 1; // WAL复制索引 } static void knl_t_replgram_init(knl_t_replgram_context* replgram_cxt) { - replgram_cxt->replication_parse_result = NULL; + replgram_cxt->replication_parse_result = NULL; // 复制解析结果 } static void knl_t_replscanner_init(knl_t_replscanner_context* replscanner_cxt) { - replscanner_cxt->scanbufhandle = NULL; - replscanner_cxt->litbuf = (StringInfoData*)palloc0(sizeof(StringInfoData)); + replscanner_cxt->scanbufhandle = NULL; // 扫描缓冲区句柄 + replscanner_cxt->litbuf = (StringInfoData*)palloc0(sizeof(StringInfoData)); // 文本缓冲区 } static void knl_t_syncgram_init(knl_t_syncrepgram_context* syncrepgram_cxt) { - syncrepgram_cxt->syncrep_parse_result = NIL; + syncrepgram_cxt->syncrep_parse_result = NIL; // 同步复制解析结果 } static void knl_t_syncrepscanner_init(knl_t_syncrepscanner_context* syncrepscanner_cxt) { - syncrepscanner_cxt->scanbufhandle = NULL; - syncrepscanner_cxt->xdbuf = (StringInfoData*)palloc0(sizeof(StringInfoData)); - syncrepscanner_cxt->result = 1; + syncrepscanner_cxt->scanbufhandle = NULL; // 扫描缓冲区句柄 + syncrepscanner_cxt->xdbuf = (StringInfoData*)palloc0(sizeof(StringInfoData)); // 扩展数据缓冲区 + syncrepscanner_cxt->result = 1; // 解析结果 } static void knl_t_syncrep_init(knl_t_syncrep_context* syncrep_cxt) { - syncrep_cxt->SyncRepConfig = NULL; - syncrep_cxt->SyncRepConfigGroups = 0; - syncrep_cxt->SyncRepMaxPossib = 0; - syncrep_cxt->announce_next_takeover = true; + syncrep_cxt->SyncRepConfig = NULL; // 同步复制配置信息 + syncrep_cxt->SyncRepConfigGroups = 0; // 同步复制配置组数 + syncrep_cxt->SyncRepMaxPossib = 0; // 同步复制最大可能数 + syncrep_cxt->announce_next_takeover = true; // 宣布下次接管标志 } static void knl_t_logical_init(knl_t_logical_context* logical_cxt) { - logical_cxt->sendFd = -1; - logical_cxt->sendSegNo = 0; - logical_cxt->sendOff = 0; - logical_cxt->ExportInProgress = false; + logical_cxt->sendFd = -1; // 发送文件描述符 + logical_cxt->sendSegNo = 0; // 发送段号 + logical_cxt->sendOff = 0; // 发送偏移量 + logical_cxt->ExportInProgress = false; // 导出进行中标志 } #define BCMElementArrayLen 8192 @@ -844,180 +854,185 @@ static void knl_t_walrcvwriter_init(knl_t_walrcvwriter_context* walrcvwriter_cxt static void knl_t_postgres_init(knl_t_postgres_context* postgres_cxt) { - postgres_cxt->clear_key_memory = false; - postgres_cxt->table_created_in_CTAS = false; - postgres_cxt->debug_query_string = NULL; - postgres_cxt->isInResetUserName = false; - postgres_cxt->whereToSendOutput = DestDebug; - postgres_cxt->local_foreign_respool = NULL; - postgres_cxt->max_stack_depth_bytes = 100 * 1024L; - postgres_cxt->password_changed = false; - postgres_cxt->stack_base_ptr = NULL; - postgres_cxt->xact_started = false; - postgres_cxt->DoingCommandRead = false; - postgres_cxt->userDoption = NULL; - postgres_cxt->EchoQuery = false; + postgres_cxt->clear_key_memory = false; // 清除关键内存标志 + postgres_cxt->table_created_in_CTAS = false; // 在CREATE TABLE AS中创建的表标志 + postgres_cxt->debug_query_string = NULL; // 调试查询字符串 + postgres_cxt->isInResetUserName = false; // 重置用户名标志 + postgres_cxt->whereToSendOutput = DestDebug; // 输出目标设置为调试 + postgres_cxt->local_foreign_respool = NULL; // 本地外部资源池 + postgres_cxt->max_stack_depth_bytes = 100 * 1024L; // 最大堆栈深度(字节) + postgres_cxt->password_changed = false; // 密码已更改标志 + postgres_cxt->stack_base_ptr = NULL; // 堆栈基指针 + postgres_cxt->xact_started = false; // 事务已启动标志 + postgres_cxt->DoingCommandRead = false; // 命令读取中标志 + postgres_cxt->userDoption = NULL; // 用户选项 + postgres_cxt->EchoQuery = false; // 回显查询标志 + #ifndef TCOP_DONTUSENEWLINE - postgres_cxt->UseNewLine = 1; /* Use newlines query delimiters (the default) */ + postgres_cxt->UseNewLine = 1; /* 使用换行符作为查询分隔符(默认) */ #else - postgres_cxt->UseNewLine = 0; /* Use EOF as query delimiters */ + postgres_cxt->UseNewLine = 0; /* 使用EOF作为查询分隔符 */ #endif /* TCOP_DONTUSENEWLINE */ - postgres_cxt->RecoveryConflictPending = false; - postgres_cxt->RecoveryConflictRetryable = true; - postgres_cxt->row_description_buf = (StringInfoData*)palloc0(sizeof(StringInfoData)); + postgres_cxt->RecoveryConflictPending = false; // 恢复冲突挂起标志 + postgres_cxt->RecoveryConflictRetryable = true; // 恢复冲突可重试标志 - postgres_cxt->clobber_qstr = NULL; - postgres_cxt->query_result = 0; - postgres_cxt->g_NoAnalyzeRelNameList = NULL; - postgres_cxt->mark_explain_analyze = false; - postgres_cxt->mark_explain_only = false; - postgres_cxt->enable_explicit_stmt_name = false; - postgres_cxt->val = 0; + postgres_cxt->row_description_buf = (StringInfoData*)palloc0(sizeof(StringInfoData)); // 行描述信息缓冲区 - postgres_cxt->gpc_fisrt_send_clean = true; + postgres_cxt->clobber_qstr = NULL; // 覆盖的查询字符串 + postgres_cxt->query_result = 0; // 查询结果 + postgres_cxt->g_NoAnalyzeRelNameList = NULL; // 不分析的关系名称列表 + postgres_cxt->mark_explain_analyze = false; // 标记执行解释分析计划 + postgres_cxt->mark_explain_only = false; // 标记只执行解释计划 + postgres_cxt->enable_explicit_stmt_name = false; // 启用显示语句名称 + postgres_cxt->val = 0; // 值 + + postgres_cxt->gpc_fisrt_send_clean = true; // GPC首次发送清理标志 } static void knl_t_utils_init(knl_t_utils_context* utils_cxt) { - utils_cxt->mctx_sequent_count = 0; - utils_cxt->ExecutorMemoryTrack = NULL; -#ifdef MEMORY_CONTEXT_CHECKING - utils_cxt->memory_track_sequent_count = -1; - utils_cxt->memory_track_plan_nodeid = -1; + utils_cxt->mctx_sequent_count = 0; // 内存上下文顺序计数 + utils_cxt->ExecutorMemoryTrack = NULL; // 执行器内存追踪 - utils_cxt->detailTrackingBuf = (StringInfoData*)palloc0(sizeof(StringInfoData)); +#ifdef MEMORY_CONTEXT_CHECKING + utils_cxt->memory_track_sequent_count = -1; // 内存追踪顺序计数 + utils_cxt->memory_track_plan_nodeid = -1; // 内存追踪计划节点ID + + utils_cxt->detailTrackingBuf = (StringInfoData*)palloc0(sizeof(StringInfoData)); // 详细追踪缓冲区 #endif - utils_cxt->track_cnt = 0; + utils_cxt->track_cnt = 0; // 追踪计数 + + utils_cxt->partId = (PartitionIdentifier*)palloc0(sizeof(PartitionIdentifier)); // 分区标识符 + utils_cxt->gValueCompareContext = NULL; // 值比较上下文 + utils_cxt->ContextUsedCount = 0; // 上下文使用计数 - utils_cxt->partId = (PartitionIdentifier*)palloc0(sizeof(PartitionIdentifier)); - utils_cxt->gValueCompareContext = NULL; - utils_cxt->ContextUsedCount = 0; #define RANGE_PARTKEYMAXNUM 4 int rc = memset_s( - utils_cxt->valueItemArr, RANGE_PARTKEYMAXNUM * sizeof(Const*), 0, RANGE_PARTKEYMAXNUM * sizeof(Const*)); + utils_cxt->valueItemArr, RANGE_PARTKEYMAXNUM * sizeof(Const*), 0, RANGE_PARTKEYMAXNUM * sizeof(Const*)); // 值项目数组 securec_check(rc, "\0", "\0"); - utils_cxt->CurrentResourceOwner = NULL; - utils_cxt->STPSavedResourceOwner = NULL; - utils_cxt->CurTransactionResourceOwner = NULL; - utils_cxt->TopTransactionResourceOwner = NULL; - utils_cxt->SortColumnOptimize = false; - utils_cxt->pRelatedRel = NULL; - utils_cxt->sigTimerId = NULL; - utils_cxt->pg_strtok_ptr = NULL; - utils_cxt->maxChunksPerThread = 0; - utils_cxt->beyondChunk = 0; + utils_cxt->CurrentResourceOwner = NULL; // 当前资源拥有者 + utils_cxt->STPSavedResourceOwner = NULL; // STP保存的资源拥有者 + utils_cxt->CurTransactionResourceOwner = NULL; // 当前事务的资源拥有者 + utils_cxt->TopTransactionResourceOwner = NULL; // 顶层事务的资源拥有者 + utils_cxt->SortColumnOptimize = false; // 排序列优化标志 + utils_cxt->pRelatedRel = NULL; // 相关关系 + utils_cxt->sigTimerId = NULL; // 信号定时器ID + utils_cxt->pg_strtok_ptr = NULL; // PG strtok指针 + + utils_cxt->maxChunksPerThread = 0; // 每线程的最大块数 + utils_cxt->beyondChunk = 0; // 超出块数 } static void knl_t_pgxc_init(knl_t_pgxc_context* pgxc_cxt) { errno_t rc; - pgxc_cxt->current_installation_nodegroup = NULL; - pgxc_cxt->current_redistribution_nodegroup = NULL; - pgxc_cxt->globalBucketLen = 0; + pgxc_cxt->current_installation_nodegroup = NULL; // 当前安装节点组 + pgxc_cxt->current_redistribution_nodegroup = NULL; // 当前重分布节点组 + pgxc_cxt->globalBucketLen = 0; // 全局桶长度 - pgxc_cxt->shmemNumCoords = NULL; - pgxc_cxt->shmemNumCoordsInCluster = NULL; - pgxc_cxt->shmemNumDataNodes = NULL; - pgxc_cxt->shmemNumDataStandbyNodes = NULL; - pgxc_cxt->coDefs = NULL; - pgxc_cxt->coDefsInCluster = NULL; - pgxc_cxt->dnDefs = NULL; - pgxc_cxt->dnStandbyDefs = NULL; + pgxc_cxt->shmemNumCoords = NULL; // 共享内存坐标数 + pgxc_cxt->shmemNumCoordsInCluster = NULL; // 集群中的共享内存坐标数 + pgxc_cxt->shmemNumDataNodes = NULL; // 共享内存数据节点数 + pgxc_cxt->shmemNumDataStandbyNodes = NULL; // 共享内存数据备用节点数 + pgxc_cxt->coDefs = NULL; // 协调器定义 + pgxc_cxt->coDefsInCluster = NULL; // 集群中的协调器定义 + pgxc_cxt->dnDefs = NULL; // 数据节点定义 + pgxc_cxt->dnStandbyDefs = NULL; // 数据备用节点定义 - pgxc_cxt->pgxc_net_ctl = (PGXCNodeNetCtlLayer*)palloc0(sizeof(PGXCNodeNetCtlLayer)); - pgxc_cxt->GlobalNetInstr = NULL; - pgxc_cxt->compute_pool_handle = NULL; - pgxc_cxt->compute_pool_conn = NULL; + pgxc_cxt->pgxc_net_ctl = (PGXCNodeNetCtlLayer*)palloc0(sizeof(PGXCNodeNetCtlLayer)); // PGXC网络控制层 + pgxc_cxt->GlobalNetInstr = NULL; // 全局网络指令 + pgxc_cxt->compute_pool_handle = NULL; // 计算池句柄 + pgxc_cxt->compute_pool_conn = NULL; // 计算池连接 - pgxc_cxt->temp_object_included = false; - pgxc_cxt->dbcleanup_info = (abort_callback_type*)palloc0(sizeof(abort_callback_type)); - rc = memset_s(pgxc_cxt->socket_buffer, SOCKET_BUFFER_LEN, 0, SOCKET_BUFFER_LEN); + pgxc_cxt->temp_object_included = false; // 临时对象已包含标志 + pgxc_cxt->dbcleanup_info = (abort_callback_type*)palloc0(sizeof(abort_callback_type)); // 数据库清理信息 + rc = memset_s(pgxc_cxt->socket_buffer, SOCKET_BUFFER_LEN, 0, SOCKET_BUFFER_LEN); // 套接字缓冲区 securec_check(rc, "\0", "\0"); - rc = memset_s(pgxc_cxt->begin_cmd, BEGIN_CMD_BUFF_SIZE, 0, BEGIN_CMD_BUFF_SIZE); + rc = memset_s(pgxc_cxt->begin_cmd, BEGIN_CMD_BUFF_SIZE, 0, BEGIN_CMD_BUFF_SIZE); // BEGIN命令缓冲区 securec_check(rc, "\0", "\0"); } static void knl_t_conn_init(knl_t_conn_context* conn_cxt) { - conn_cxt->ecCtrl = NULL; - conn_cxt->dl_handle = NULL; - conn_cxt->_conn = NULL; - conn_cxt->_result = NULL; - conn_cxt->_DatabaseEncoding = &pg_enc2name_tbl[PG_SQL_ASCII]; - conn_cxt->_float_inf = "Inf"; + conn_cxt->ecCtrl = NULL; // 连接扩展控制 + conn_cxt->dl_handle = NULL; // 动态链接库句柄 + conn_cxt->_conn = NULL; // 连接结构体 + conn_cxt->_result = NULL; // 查询结果结构体 + conn_cxt->_DatabaseEncoding = &pg_enc2name_tbl[PG_SQL_ASCII]; // 数据库编码 + conn_cxt->_float_inf = "Inf"; // 浮点正无穷大 } static void knl_t_page_redo_init(knl_t_page_redo_context* page_redo_cxt) { - page_redo_cxt->shutdown_requested = false; - page_redo_cxt->got_SIGHUP = false; - page_redo_cxt->sleep_long = false; - page_redo_cxt->check_repair = false; + page_redo_cxt->shutdown_requested = false; // 关机请求标志 + page_redo_cxt->got_SIGHUP = false; // 收到SIGHUP信号标志 + page_redo_cxt->sleep_long = false; // 长时间休眠标志 + page_redo_cxt->check_repair = false; // 检查修复标志 } static void knl_t_parallel_decode_init(knl_t_parallel_decode_worker_context* parallel_decode_cxt) { - parallel_decode_cxt->shutdown_requested = false; - parallel_decode_cxt->got_SIGHUP = false; - parallel_decode_cxt->sleep_long = false; + parallel_decode_cxt->shutdown_requested = false; // 关机请求标志 + parallel_decode_cxt->got_SIGHUP = false; // 收到SIGHUP信号标志 + parallel_decode_cxt->sleep_long = false; // 长时间休眠标志 } static void knl_t_parallel_decode_reader_init(knl_t_logical_read_worker_context* parallel_decode_reader_cxt) { - parallel_decode_reader_cxt->shutdown_requested = false; - parallel_decode_reader_cxt->got_SIGHUP = false; - parallel_decode_reader_cxt->sleep_long = false; + parallel_decode_reader_cxt->shutdown_requested = false; // 关机请求标志 + parallel_decode_reader_cxt->got_SIGHUP = false; // 收到SIGHUP信号标志 + parallel_decode_reader_cxt->sleep_long = false; // 长时间休眠标志 } static void knl_t_startup_init(knl_t_startup_context* startup_cxt) { - startup_cxt->got_SIGHUP = false; - startup_cxt->shutdown_requested = false; - startup_cxt->failover_triggered = false; - startup_cxt->switchover_triggered = false; - startup_cxt->primary_triggered = false; - startup_cxt->standby_triggered = false; - startup_cxt->in_restore_command = false; - startup_cxt->NotifySigState = NULL; + startup_cxt->got_SIGHUP = false; // 收到SIGHUP信号标志 + startup_cxt->shutdown_requested = false; // 关机请求标志 + startup_cxt->failover_triggered = false; // 故障切换触发标志 + startup_cxt->switchover_triggered = false; // 触发切换标志 + startup_cxt->primary_triggered = false; // 主触发标志 + startup_cxt->standby_triggered = false; // 备触发标志 + startup_cxt->in_restore_command = false; // 恢复命令中标志 + startup_cxt->NotifySigState = NULL; // 通知信号状态 } static void knl_t_alarmchecker_init(knl_t_alarmchecker_context* alarm_cxt) { - alarm_cxt->gotSighup = false; - alarm_cxt->gotSigdie = false; + alarm_cxt->gotSighup = false; // 收到SIGHUP信号标志 + alarm_cxt->gotSigdie = false; // 收到SIGDIE信号标志 } static void knl_t_lwlockmoniter_init(knl_t_lwlockmoniter_context* lwm_cxt) { - lwm_cxt->got_SIGHUP = false; - lwm_cxt->shutdown_requested = false; + lwm_cxt->got_SIGHUP = false; // 收到SIGHUP信号标志 + lwm_cxt->shutdown_requested = false; // 关机请求标志 } static void knl_t_walwriter_init(knl_t_walwriter_context* walwriter_cxt) { - walwriter_cxt->got_SIGHUP = false; - walwriter_cxt->shutdown_requested = false; + walwriter_cxt->got_SIGHUP = false; // 收到SIGHUP信号标志 + walwriter_cxt->shutdown_requested = false; // 关机请求标志 } static void knl_t_walwriterauxiliary_init(knl_t_walwriterauxiliary_context *const walwriterauxiliary_cxt) { - walwriterauxiliary_cxt->got_SIGHUP = false; - walwriterauxiliary_cxt->shutdown_requested = false; + walwriterauxiliary_cxt->got_SIGHUP = false; // 收到SIGHUP信号标志 + walwriterauxiliary_cxt->shutdown_requested = false; // 关机请求标志 } static void knl_t_poolcleaner_init(knl_t_poolcleaner_context* poolcleaner_cxt) { - poolcleaner_cxt->shutdown_requested = false; - poolcleaner_cxt->got_SIGHUP = false; + poolcleaner_cxt->shutdown_requested = false; // 关机请求标志 + poolcleaner_cxt->got_SIGHUP = false; // 收到SIGHUP信号标志 } static void knl_t_catchup_init(knl_t_catchup_context* catchup_cxt) { - catchup_cxt->catchup_shutdown_requested = false; + catchup_cxt->catchup_shutdown_requested = false; // 关机请求标志 } /* interval for calling AbsorbFsyncRequests in CheckpointWriteDelay */ @@ -1025,161 +1040,160 @@ static void knl_t_catchup_init(knl_t_catchup_context* catchup_cxt) static void knl_t_checkpoint_init(knl_t_checkpoint_context* checkpoint_cxt) { - checkpoint_cxt->CheckpointerShmem = NULL; - checkpoint_cxt->got_SIGHUP = false; - checkpoint_cxt->checkpoint_requested = false; - checkpoint_cxt->shutdown_requested = false; - checkpoint_cxt->ckpt_active = false; - checkpoint_cxt->absorbCounter = WRITES_PER_ABSORB; - checkpoint_cxt->ckpt_done = 0; + checkpoint_cxt->CheckpointerShmem = NULL; // 检查点共享内存结构体 + checkpoint_cxt->got_SIGHUP = false; // 收到SIGHUP信号标志 + checkpoint_cxt->checkpoint_requested = false; // 检查点请求标志 + checkpoint_cxt->shutdown_requested = false; // 关机请求标志 + checkpoint_cxt->ckpt_active = false; // 检查点激活标志 + checkpoint_cxt->absorbCounter = WRITES_PER_ABSORB; // 吸收写入请求的计数器 + checkpoint_cxt->ckpt_done = 0; // 完成的检查点数量 } static void knl_t_autovacuum_init(knl_t_autovacuum_context* autovacuum_cxt) { - autovacuum_cxt->got_SIGHUP = false; - autovacuum_cxt->got_SIGUSR2 = false; - autovacuum_cxt->got_SIGTERM = false; - autovacuum_cxt->pgStatAutoVacInfo = NULL; - autovacuum_cxt->autovacuum_coordinators_string = ""; - autovacuum_cxt->autovac_iops_limits = 0; - autovacuum_cxt->DatabaseList = NULL; - autovacuum_cxt->DatabaseListCxt = NULL; - autovacuum_cxt->MyWorkerInfo = NULL; - autovacuum_cxt->AutovacuumLauncherPid = 0; - autovacuum_cxt->last_read = 0; + autovacuum_cxt->got_SIGHUP = false; // 收到SIGHUP信号标志 + autovacuum_cxt->got_SIGUSR2 = false; // 收到SIGUSR2信号标志 + autovacuum_cxt->got_SIGTERM = false; // 收到SIGTERM信号标志 + autovacuum_cxt->pgStatAutoVacInfo = NULL; // 自动清理统计信息结构体 + autovacuum_cxt->autovacuum_coordinators_string = ""; // 自动清理协调器字符串 + autovacuum_cxt->autovac_iops_limits = 0; // 自动清理IOPS限制 + autovacuum_cxt->DatabaseList = NULL; // 数据库列表 + autovacuum_cxt->DatabaseListCxt = NULL; // 数据库列表内存上下文 + autovacuum_cxt->MyWorkerInfo = NULL; // 自身的工作进程信息结构体 + autovacuum_cxt->AutovacuumLauncherPid = 0; // 自动清理启动器进程ID + autovacuum_cxt->last_read = 0; // 上次读取的时间戳 } static void KnlTApplyLauncherInit(knl_t_apply_launcher_context* applyLauncherCxt) { - applyLauncherCxt->got_SIGHUP = false; - applyLauncherCxt->newWorkerRequest = false; - applyLauncherCxt->got_SIGTERM = false; - applyLauncherCxt->onCommitLauncherWakeup = false; - applyLauncherCxt->applyLauncherShm = NULL; + applyLauncherCxt->got_SIGHUP = false; // 收到SIGHUP信号标志 + applyLauncherCxt->newWorkerRequest = false; // 新工作请求标志 + applyLauncherCxt->got_SIGTERM = false; // 收到SIGTERM信号标志 + applyLauncherCxt->onCommitLauncherWakeup = false; // 提交时唤醒标志 + applyLauncherCxt->applyLauncherShm = NULL; // 应用启动器共享内存结构体 } static void KnlTApplyWorkerInit(knl_t_apply_worker_context* applyWorkerCxt) { - applyWorkerCxt->got_SIGHUP = false; - applyWorkerCxt->got_SIGTERM = false; - applyWorkerCxt->lsnMapping.head.next = &applyWorkerCxt->lsnMapping.head; + applyWorkerCxt->got_SIGHUP = false; // 收到SIGHUP信号标志 + applyWorkerCxt->got_SIGTERM = false; // 收到SIGTERM信号标志 + applyWorkerCxt->lsnMapping.head.next = &applyWorkerCxt->lsnMapping.head; // 逻辑复制位置映射链表 applyWorkerCxt->lsnMapping.head.prev = &applyWorkerCxt->lsnMapping.head; - applyWorkerCxt->logicalRepRelMap = NULL; - applyWorkerCxt->sendTime = 0; - applyWorkerCxt->lastRecvpos = InvalidXLogRecPtr; - applyWorkerCxt->lastWritepos = InvalidXLogRecPtr; - applyWorkerCxt->lastFlushpos = InvalidXLogRecPtr; - applyWorkerCxt->mySubscription = NULL; - applyWorkerCxt->mySubscriptionValid = false; - applyWorkerCxt->inRemoteTransaction = false; - applyWorkerCxt->curWorker = NULL; - applyWorkerCxt->messageContext = NULL; - applyWorkerCxt->logicalRepRelMapContext = NULL; - applyWorkerCxt->applyContext = NULL; + applyWorkerCxt->logicalRepRelMap = NULL; // 逻辑复制关系映射 + applyWorkerCxt->sendTime = 0; // 发送时间戳 + applyWorkerCxt->lastRecvpos = InvalidXLogRecPtr; // 最后接收位置 + applyWorkerCxt->lastWritepos = InvalidXLogRecPtr; // 最后写入位置 + applyWorkerCxt->lastFlushpos = InvalidXLogRecPtr; // 最后刷新位置 + applyWorkerCxt->mySubscription = NULL; // 订阅信息结构体 + applyWorkerCxt->mySubscriptionValid = false; // 订阅信息有效性标志 + applyWorkerCxt->inRemoteTransaction = false; // 远程事务标志 + applyWorkerCxt->curWorker = NULL; // 当前工作进程信息结构体 + applyWorkerCxt->messageContext = NULL; // 消息内存上下文 + applyWorkerCxt->logicalRepRelMapContext = NULL; // 逻辑复制关系映射内存上下文 + applyWorkerCxt->applyContext = NULL; // 应用内存上下文 } static void KnlTPublicationInit(knl_t_publication_context* publicationCxt) { - publicationCxt->publications_valid = false; - publicationCxt->RelationSyncCache = NULL; + publicationCxt->publications_valid = false; // 发布信息有效性标志 + publicationCxt->RelationSyncCache = NULL; // 关系同步缓存 } static void KnlTUndolauncherInit(knl_t_undolauncher_context* undolauncherCxt) { - undolauncherCxt->got_SIGHUP = false; - undolauncherCxt->got_SIGUSR2 = false; - undolauncherCxt->got_SIGTERM = false; - - undolauncherCxt->UndoLauncherPid = 0; + undolauncherCxt->got_SIGHUP = false; // 收到SIGHUP信号标志 + undolauncherCxt->got_SIGUSR2 = false; // 收到SIGUSR2信号标志 + undolauncherCxt->got_SIGTERM = false; // 收到SIGTERM信号标志 + undolauncherCxt->UndoLauncherPid = 0; // 撤销启动器进程ID } -static void KnlTUndoInit (knl_t_undo_context* undoCtx) +static void KnlTUndoInit(knl_t_undo_context* undoCtx) { for (auto i = 0; i < UNDO_PERSISTENCE_LEVELS; i++) { UndoPersistence upersistence = static_cast(i); - undoCtx->zids[upersistence] = INVALID_ZONE_ID; - undoCtx->prevXid[upersistence] = InvalidTransactionId; - undoCtx->slots[upersistence] = NULL; - undoCtx->slotPtr[upersistence] = INVALID_UNDO_REC_PTR; + undoCtx->zids[upersistence] = INVALID_ZONE_ID; // 区域ID + undoCtx->prevXid[upersistence] = InvalidTransactionId; // 上一个事务ID + undoCtx->slots[upersistence] = NULL; // undo槽位 + undoCtx->slotPtr[upersistence] = INVALID_UNDO_REC_PTR; // undo记录指针 } - undoCtx->transUndoSize = 0; - undoCtx->fetchRecord = false; + undoCtx->transUndoSize = 0; // 事务undo大小 + undoCtx->fetchRecord = false; // 检索undo记录标志 } static void KnlTUndoworkerInit(knl_t_undoworker_context* undoworkerCxt) { - undoworkerCxt->got_SIGHUP = false; - undoworkerCxt->got_SIGUSR2 = false; - undoworkerCxt->got_SIGTERM = false; + undoworkerCxt->got_SIGHUP = false; // 收到SIGHUP信号标志 + undoworkerCxt->got_SIGUSR2 = false; // 收到SIGUSR2信号标志 + undoworkerCxt->got_SIGTERM = false; // 收到SIGTERM信号标志 } static void KnlTUndorecyclerInit(knl_t_undorecycler_context* undorecyclerCxt) { - undorecyclerCxt->got_SIGHUP = false; - undorecyclerCxt->shutdown_requested = false; + undorecyclerCxt->got_SIGHUP = false; // 收到SIGHUP信号标志 + undorecyclerCxt->shutdown_requested = false; // 关机请求标志 } static void KnlTRollbackRequestsInit(knl_t_rollback_requests_context* context) { - context->rollback_requests_hash = NULL; - context->next_bucket_for_scan = 0; + context->rollback_requests_hash = NULL; // 回滚请求哈希表 + context->next_bucket_for_scan = 0; // 下一个要扫描的哈希桶索引 } static void KnlTGstatInit(knl_t_gstat_context* context) { - context->got_SIGHUP = false; - context->got_SIGUSR2 = false; - context->got_SIGTERM = false; + context->got_SIGHUP = false; // 收到SIGHUP信号标志 + context->got_SIGUSR2 = false; // 收到SIGUSR2信号标志 + context->got_SIGTERM = false; // 收到SIGTERM信号标志 } static void knl_t_aiocompleter_init(knl_t_aiocompleter_context* aio_cxt) { - aio_cxt->shutdown_requested = false; - aio_cxt->config_requested = false; + aio_cxt->shutdown_requested = false; // 关机请求标志 + aio_cxt->config_requested = false; // 配置请求标志 } static void knl_t_twophasecleaner_init(knl_t_twophasecleaner_context* tpcleaner_cxt) { - tpcleaner_cxt->got_SIGHUP = false; - tpcleaner_cxt->shutdown_requested = false; + tpcleaner_cxt->got_SIGHUP = false; // 收到SIGHUP信号标志 + tpcleaner_cxt->shutdown_requested = false; // 关机请求标志 } static void knl_t_bgwriter_init(knl_t_bgwriter_context* bgwriter_cxt) { - bgwriter_cxt->got_SIGHUP = false; - bgwriter_cxt->shutdown_requested = false; + bgwriter_cxt->got_SIGHUP = false; // 收到SIGHUP信号标志 + bgwriter_cxt->shutdown_requested = false; // 关机请求标志 } static void knl_t_pagewriter_init(knl_t_pagewriter_context* pagewriter_cxt) { - pagewriter_cxt->got_SIGHUP = false; - pagewriter_cxt->sync_requested = false; - pagewriter_cxt->sync_retry = false; - pagewriter_cxt->shutdown_requested = false; - pagewriter_cxt->page_writer_after = WRITEBACK_MAX_PENDING_FLUSHES; - pagewriter_cxt->pagewriter_id = -1; + pagewriter_cxt->got_SIGHUP = false; // 收到SIGHUP信号标志 + pagewriter_cxt->sync_requested = false; // 同步请求标志 + pagewriter_cxt->sync_retry = false; // 同步重试标志 + pagewriter_cxt->shutdown_requested = false; // 关机请求标志 + pagewriter_cxt->page_writer_after = WRITEBACK_MAX_PENDING_FLUSHES; // 待写入的最大脏页数 + pagewriter_cxt->pagewriter_id = -1; // 页写入器ID } static void knl_t_barrier_creator_init(knl_t_barrier_creator_context* barrier_creator_cxt) { - barrier_creator_cxt->archive_slot_names = NULL; - barrier_creator_cxt->got_SIGHUP = false; - barrier_creator_cxt->is_first_barrier = false; - barrier_creator_cxt->barrier_update_last_time_info = NULL; - barrier_creator_cxt->shutdown_requested = false; - barrier_creator_cxt->first_cn_timeline = 0; + barrier_creator_cxt->archive_slot_names = NULL; // 归档槽名称列表 + barrier_creator_cxt->got_SIGHUP = false; // 收到SIGHUP信号标志 + barrier_creator_cxt->is_first_barrier = false; // 第一个障碍标志 + barrier_creator_cxt->barrier_update_last_time_info = NULL; // 障碍更新的上次时间信息 + barrier_creator_cxt->shutdown_requested = false; // 关机请求标志 + barrier_creator_cxt->first_cn_timeline = 0; // 第一个Coordinator的时间线号 } static void knl_t_xlogcopybackend_init(knl_t_sharestoragexlogcopyer_context* cxt) { - cxt->got_SIGHUP = false; - cxt->shutdown_requested = false; - cxt->wakeUp = false; - cxt->readFile = -1; - cxt->readOff = 0; - cxt->readSegNo = 0; - cxt->buf = NULL; - cxt->originBuf = NULL; + cxt->got_SIGHUP = false; // 收到SIGHUP信号标志 + cxt->shutdown_requested = false; // 关机请求标志 + cxt->wakeUp = false; // 唤醒标志 + cxt->readFile = -1; // 读取文件描述符 + cxt->readOff = 0; // 读取文件偏移量 + cxt->readSegNo = 0; // 读取文件段号 + cxt->buf = NULL; // 缓冲区 + cxt->originBuf = NULL; // 原始缓冲区 } @@ -1190,22 +1204,29 @@ extern bool HeapTupleSatisfiesToast(HeapTuple htup, Snapshot snapshot, Buffer bu static void knl_t_snapshot_init(knl_t_snapshot_context* snapshot_cxt) { + // 为 SNAPSHOT_NOW 上下文分配内存并初始化为 0 snapshot_cxt->SnapshotNowData = (SnapshotData*)palloc0(sizeof(SnapshotData)); snapshot_cxt->SnapshotNowData->satisfies = SNAPSHOT_NOW; + // 为 SNAPSHOT_SELF 上下文分配内存并初始化为 0 snapshot_cxt->SnapshotSelfData = (SnapshotData*)palloc0(sizeof(SnapshotData)); snapshot_cxt->SnapshotSelfData->satisfies = SNAPSHOT_SELF; + // 为 SNAPSHOT_ANY 上下文分配内存并初始化为 0 snapshot_cxt->SnapshotAnyData = (SnapshotData*)palloc0(sizeof(SnapshotData)); snapshot_cxt->SnapshotAnyData->satisfies = SNAPSHOT_ANY; + // 为 SNAPSHOT_TOAST 上下文分配内存并初始化为 0 snapshot_cxt->SnapshotToastData = (SnapshotData*)palloc0(sizeof(SnapshotData)); snapshot_cxt->SnapshotToastData->satisfies = SNAPSHOT_TOAST; } static void knl_t_comm_init(knl_t_comm_context* comm_cxt) { + // 断言确保 comm_cxt 不为 NULL Assert(comm_cxt != NULL); + + // 初始化通信上下文的各种计数器和标志为 0 comm_cxt->g_receiver_loop_poll_up = 0; comm_cxt->LibcommThreadType = LIBCOMM_NONE; comm_cxt->libcomm_semaphore = NULL; @@ -1220,31 +1241,38 @@ static void knl_t_libpq_init(knl_t_libpq_context* libpq_cxt) { errno_t rc; + // 断言确保 libpq_cxt 不为 NULL Assert(libpq_cxt != NULL); + + // 将 libpq 上下文的套接字路径初始化为 0 libpq_cxt->listen_fd_for_recv_flow_ctrl = -1; rc = memset_s(libpq_cxt->sock_path, MAXPGPATH, 0, MAXPGPATH); securec_check(rc, "\0", "\0"); rc = memset_s(libpq_cxt->ha_sock_path, MAXPGPATH, 0, MAXPGPATH); securec_check(rc, "\0", "\0"); + + // 初始化 libpq 上下文的其他标志和缓冲区 libpq_cxt->PqSendBuffer = NULL; libpq_cxt->PqCommBusy = false; libpq_cxt->DoingCopyOut = false; - libpq_cxt->save_query_result_to_disk = false; temp_file_context_init(libpq_cxt); - libpq_cxt->parsed_hba_lines = NIL; libpq_cxt->parsed_hba_context = NULL; } - static void knl_t_contrib_init(knl_t_contrib_context* contrib_cxt) { + // 将 g_searchletId 初始化为 0 contrib_cxt->g_searchletId = 0; + // 初始化 vec_info 为 NULL contrib_cxt->vec_info = NULL; + // 启用断言 contrib_cxt->assert_enabled = true; + // 初始化日志主机名和节点名为 0 contrib_cxt->g_log_hostname = (Datum)0; contrib_cxt->g_log_nodename = (Datum)0; + // 初始化 ShippableCacheHash 为 NULL contrib_cxt->ShippableCacheHash = NULL; } @@ -1252,6 +1280,7 @@ static void knl_t_walreceiver_init(knl_t_walreceiver_context* walreceiver_cxt) { errno_t rc; + // 初始化各种标志和缓冲区为默认值 walreceiver_cxt->got_SIGHUP = false; walreceiver_cxt->got_SIGTERM = false; walreceiver_cxt->start_switchover = false; @@ -1261,16 +1290,17 @@ static void knl_t_walreceiver_init(knl_t_walreceiver_context* walreceiver_cxt) securec_check(rc, "\0", "\0"); rc = memset_s(walreceiver_cxt->gucconf_lock_file, MAXPGPATH, 0, MAXPGPATH); securec_check(rc, "\0", "\0"); + // 将 reserve_item 的各字段初始化为 0 walreceiver_cxt->reserve_item = {0}; + // 设置文件超时检查时间为 1 小时 walreceiver_cxt->check_file_timeout = 60 * 60 * 1000; + // 将各种消息结构体初始化为 0,并分配内存 walreceiver_cxt->walRcvCtlBlock = NULL; walreceiver_cxt->reply_message = (StandbyReplyMessage*)palloc0(sizeof(StandbyReplyMessage)); walreceiver_cxt->feedback_message = (StandbyHSFeedbackMessage*)palloc0(sizeof(StandbyHSFeedbackMessage)); - ; walreceiver_cxt->request_message = (StandbySwitchRequestMessage*)palloc0(sizeof(StandbySwitchRequestMessage)); - ; walreceiver_cxt->reply_modify_message = (ConfigModifyTimeMessage*)palloc0(sizeof(ConfigModifyTimeMessage)); - ; + // 将其他标志和计数器初始化为默认值 walreceiver_cxt->WalRcvImmediateInterruptOK = false; walreceiver_cxt->AmWalReceiverForFailover = false; walreceiver_cxt->AmWalReceiverForStandby = false; @@ -1284,10 +1314,13 @@ static void knl_t_storage_init(knl_t_storage_context* storage_cxt) { errno_t rc; + // 将 latestObservedXid 初始化为无效事务 ID storage_cxt->latestObservedXid = InvalidTransactionId; + // 分配并初始化 CurrentRunningXacts 结构体 storage_cxt->CurrentRunningXacts = (RunningTransactionsData*)palloc0(sizeof(RunningTransactionsData)); storage_cxt->proc_vxids = NULL; + // 初始化各种缓冲区和锁定相关的变量 storage_cxt->BufferDescriptors = NULL; storage_cxt->BufferBlocks = NULL; storage_cxt->BackendWritebackContext = (WritebackContext*)palloc0(sizeof(WritebackContext)); @@ -1300,8 +1333,8 @@ static void knl_t_storage_init(knl_t_storage_context* storage_cxt) storage_cxt->InProgressAioBuf = NULL; storage_cxt->InProgressAioType = AioUnkown; storage_cxt->is_btree_split = false; - storage_cxt->PrivateRefCountArray = - (PrivateRefCountEntry*)palloc0(sizeof(PrivateRefCountEntry) * REFCOUNT_ARRAY_ENTRIES); + // 分配并初始化 PrivateRefCount 相关的数据结构 + storage_cxt->PrivateRefCountArray = (PrivateRefCountEntry*)palloc0(sizeof(PrivateRefCountEntry) * REFCOUNT_ARRAY_ENTRIES); storage_cxt->PrivateRefCountHash = NULL; storage_cxt->PrivateRefCountOverflowed = 0; storage_cxt->PrivateRefCountClock = 0; @@ -1317,15 +1350,18 @@ static void knl_t_storage_init(knl_t_storage_context* storage_cxt) storage_cxt->CacheBlockInProgressUncompress = CACHE_BLOCK_INVALID_IDX; storage_cxt->MetaBlockInProgressIO = CACHE_BLOCK_INVALID_IDX; + // 设置等待锁的初始超时时间 #define STANDBY_INITIAL_WAIT_US 1000 storage_cxt->RecoveryLockList = NIL; storage_cxt->standbyWait_us = STANDBY_INITIAL_WAIT_US; storage_cxt->lo_heap_r = NULL; storage_cxt->lo_index_r = NULL; + // 初始化 dummy_spinlock 为 0 rc = memset_s(&storage_cxt->dummy_spinlock, sizeof(slock_t), 0, sizeof(slock_t)); securec_check(rc, "\0", "\0"); storage_cxt->spins_per_delay = DEFAULT_SPINS_PER_DELAY; + // 初始化各种锁和约束相关的数据结构 storage_cxt->visitedProcs = NULL; storage_cxt->nVisitedProcs = 0; storage_cxt->topoProcs = NULL; @@ -1359,6 +1395,7 @@ static void knl_t_storage_init(knl_t_storage_context* storage_cxt) storage_cxt->blocking_redistribution_proc = NULL; storage_cxt->lock_vxids = NULL; + // 初始化各种超时标志和计时器 storage_cxt->EnlargeDeadlockTimeout = false; storage_cxt->lockAwaited = NULL; storage_cxt->standby_timeout_active = false; @@ -1372,8 +1409,10 @@ static void knl_t_storage_init(knl_t_storage_context* storage_cxt) storage_cxt->statement_fin_time2 = 0; storage_cxt->pageCopy = NULL; + // 初始化其他锁定相关变量 storage_cxt->isSwitchoverLockHolder = false; storage_cxt->num_held_lwlocks = 0; + // 分配 held_lwlocks 的内存并初始化为 0 storage_cxt->held_lwlocks = (LWLockHandle*)palloc0(MAX_SIMUL_LWLOCKS * sizeof(LWLockHandle)); storage_cxt->lock_addin_request = 0; storage_cxt->lock_addin_request_allowed = true; @@ -1382,10 +1421,12 @@ static void knl_t_storage_init(knl_t_storage_context* storage_cxt) storage_cxt->remote_function_context = NULL; storage_cxt->work_env_init = false; + // 初始化其他系统相关的变量 storage_cxt->shmem_startup_hook = NULL; storage_cxt->total_addin_request = 0; storage_cxt->addin_request_allowed = true; storage_cxt->atexit_callback_setup = false; + // 将 on_proc_exit_list 和 on_shmem_exit_list 初始化为 0 rc = memset_s(storage_cxt->on_proc_exit_list, MAX_ON_EXITS * sizeof(ONEXIT), 0, MAX_ON_EXITS * sizeof(ONEXIT)); securec_check(rc, "\0", "\0"); rc = memset_s(storage_cxt->on_shmem_exit_list, MAX_ON_EXITS * sizeof(ONEXIT), 0, MAX_ON_EXITS * sizeof(ONEXIT)); @@ -1393,12 +1434,14 @@ static void knl_t_storage_init(knl_t_storage_context* storage_cxt) storage_cxt->on_proc_exit_index = 0; storage_cxt->on_shmem_exit_index = 0; + // 分配并初始化一些缓存和元数据信息的结构体 storage_cxt->cmprMetaInfo = (CmprMetaUnion*)palloc0(sizeof(CmprMetaUnion)); storage_cxt->DataFileIdCache = NULL; storage_cxt->SegSpcCache = NULL; storage_cxt->uidHashCache = NULL; storage_cxt->DisasterCache = NULL; + // 初始化文件描述符等系统参数 storage_cxt->max_safe_fds = 32; storage_cxt->max_userdatafiles = 8192 - 1000; storage_cxt->timeoutRemoteOpera = 0; @@ -1406,7 +1449,9 @@ static void knl_t_storage_init(knl_t_storage_context* storage_cxt) static void knl_t_port_init(knl_t_port_context* port_cxt) { + // 初始化线程退出标志为 false port_cxt->thread_is_exiting = false; + // 初始化语言环境相关变量 port_cxt->save_locale_r = (locale_t)0; port_cxt->cur_datcollate.data[0] = '\0'; port_cxt->cur_datctype.data[0] = '\0'; @@ -1418,6 +1463,7 @@ static void knl_t_walsender_init(knl_t_walsender_context* walsender_cxt) { errno_t rc; + // 初始化逻辑复制相关变量 walsender_cxt->load_cu_buffer = NULL; walsender_cxt->load_cu_buffer_size = 64 * 1024 * 1024; walsender_cxt->WalSndCtl = NULL; @@ -1442,12 +1488,16 @@ static void knl_t_walsender_init(knl_t_walsender_context* walsender_cxt) walsender_cxt->walsender_ready_to_stop = false; walsender_cxt->response_switchover_requested = false; walsender_cxt->server_run_mode = NORMAL_MODE; + + // 初始化字符串相关变量 rc = memset_s(walsender_cxt->gucconf_file, MAXPGPATH, 0, MAXPGPATH); securec_check(rc, "\0", "\0"); rc = memset_s(walsender_cxt->gucconf_lock_file, MAXPGPATH, 0, MAXPGPATH); securec_check(rc, "\0", "\0"); rc = memset_s(walsender_cxt->slotname, NAMEDATALEN, 0, NAMEDATALEN); securec_check(rc, "\0", "\0"); + + // 初始化逻辑复制中的各种变量和文件描述符 walsender_cxt->ws_dummy_data_read_file_fd = NULL; walsender_cxt->ws_dummy_data_read_file_num = 1; walsender_cxt->CheckCUArray = NULL; @@ -1468,6 +1518,8 @@ static void knl_t_walsender_init(knl_t_walsender_context* walsender_cxt) walsender_cxt->compressBuf = NULL; walsender_cxt->ep_fd = -1; walsender_cxt->datafd = -1; + + // 初始化其他标志和状态变量 walsender_cxt->is_obsmode = false; walsender_cxt->standbyConnection = false; walsender_cxt->restoreLogicalLogHead = NULL; @@ -1481,116 +1533,188 @@ static void knl_t_tsearch_init(knl_t_tsearch_context* tsearch_cxt) static void knl_t_postmaster_init(knl_t_postmaster_context* postmaster_cxt) { + // 初始化逻辑连接的启动包处理标志 postmaster_cxt->ProcessStartupPacketForLogicConn = false; + // 初始化用于libcomm的套接字和端口 postmaster_cxt->sock_for_libcomm = -1; postmaster_cxt->port_for_libcomm = NULL; + // 初始化流复制中保持套接字打开的标志 postmaster_cxt->KeepSocketOpenForStream = false; int rc; + // 初始化复制连接数组 rc = memset_s(postmaster_cxt->ReplConnArray, - MAX_REPLNODE_NUM * sizeof(replconninfo*), - 0, - MAX_REPLNODE_NUM * sizeof(replconninfo*)); + MAX_REPLNODE_NUM * sizeof(replconninfo*), + 0, + MAX_REPLNODE_NUM * sizeof(replconninfo*)); securec_check(rc, "\0", "\0"); - rc = memset_s(postmaster_cxt->ReplConnChangeType, MAX_REPLNODE_NUM * sizeof(int), - NO_CHANGE, MAX_REPLNODE_NUM * sizeof(int)); + // 初始化复制连接变化类型数组 + rc = memset_s(postmaster_cxt->ReplConnChangeType, + MAX_REPLNODE_NUM * sizeof(int), + NO_CHANGE, + MAX_REPLNODE_NUM * sizeof(int)); securec_check(rc, "\0", "\0"); - rc = memset_s(postmaster_cxt->CrossClusterReplConnArray, MAX_REPLNODE_NUM * sizeof(replconninfo*), 0, - MAX_REPLNODE_NUM * sizeof(replconninfo*)); + // 初始化跨集群复制连接数组和连接变化标志数组 + rc = memset_s(postmaster_cxt->CrossClusterReplConnArray, + MAX_REPLNODE_NUM * sizeof(replconninfo*), + 0, + MAX_REPLNODE_NUM * sizeof(replconninfo*)); securec_check(rc, "\0", "\0"); - rc = memset_s(postmaster_cxt->CrossClusterReplConnChanged, MAX_REPLNODE_NUM * sizeof(bool), 0, + rc = memset_s(postmaster_cxt->CrossClusterReplConnChanged, + MAX_REPLNODE_NUM * sizeof(bool), + 0, MAX_REPLNODE_NUM * sizeof(bool)); securec_check(rc, "\0", "\0"); + // 初始化HA共享内存数据 postmaster_cxt->HaShmData = NULL; + // 初始化本地IP数量 postmaster_cxt->LocalIpNum = 0; + // 初始化RPC工作线程标志 postmaster_cxt->IsRPCWorkerThread = false; + // 初始化审计相关标志 postmaster_cxt->audit_primary_start = true; postmaster_cxt->audit_primary_failover = false; postmaster_cxt->audit_standby_switchover = false; + // 初始化发送到虚拟备机和构建备机的标志 postmaster_cxt->senderToDummyStandby = false; postmaster_cxt->senderToBuildStandby = false; + // 初始化Postmaster是否已达到正常运行状态的标志 postmaster_cxt->ReachedNormalRunning = false; + // 初始化重定向是否已完成的标志 postmaster_cxt->redirection_done = false; + // 初始化自动清理进程启动标志 postmaster_cxt->start_autovac_launcher = false; + // 初始化自动清理进程是否需要信号的标志 postmaster_cxt->avlauncher_needs_signal = false; + // 初始化作业调度器启动标志 postmaster_cxt->start_job_scheduler = false; + // 初始化作业调度器是否需要信号的标志 postmaster_cxt->jobscheduler_needs_signal = false; + // 初始化随机数种子 postmaster_cxt->random_seed = 0; + // 初始化是否强制不分离日志文件的标志 postmaster_cxt->forceNoSeparate = false; + // 初始化Postmaster是否存活的文件描述符 postmaster_cxt->postmaster_alive_fds[0] = -1; postmaster_cxt->postmaster_alive_fds[1] = -1; + // 初始化系统日志管道的文件描述符 postmaster_cxt->syslogPipe[0] = -1; postmaster_cxt->syslogPipe[1] = -1; } +// 初始化 bootstrap 上下文结构体 static void knl_t_bootstrap_init(knl_t_bootstrap_context* bootstrap_cxt) { + // 初始化字段 num_columns_read 为 0 bootstrap_cxt->num_columns_read = 0; + // 初始化字段 yyline 为 1 bootstrap_cxt->yyline = 1; + // 初始化字段 MyAuxProcType 为 NotAnAuxProcess bootstrap_cxt->MyAuxProcType = NotAnAuxProcess; + // 初始化指针字段 Typ 为 NULL bootstrap_cxt->Typ = NULL; + // 初始化指针字段 Ap 为 NULL bootstrap_cxt->Ap = NULL; + // 初始化指针字段 nogc 为 NULL bootstrap_cxt->nogc = NULL; + // 初始化指针字段 ILHead 为 NULL bootstrap_cxt->ILHead = NULL; } +// 初始化 percentile 上下文结构体 static void knl_t_pencentile_init(knl_t_percentile_context* percentile_cxt) { + // 初始化字段 need_exit 为 false percentile_cxt->need_exit = false; + // 初始化字段 need_reset_timer 为 true percentile_cxt->need_reset_timer = true; + // 初始化指针字段 pgxc_all_handles 为 NULL percentile_cxt->pgxc_all_handles = NULL; + // 初始化字段 got_SIGHUP 为 false percentile_cxt->got_SIGHUP = false; } +// 初始化 perf_snap 上下文结构体 static void knl_t_perf_snap_init(knl_t_perf_snap_context* perf_snap_cxt) { + // 初始化字段 need_exit 为 false perf_snap_cxt->need_exit = false; + // 初始化字段 got_SIGHUP 为 false perf_snap_cxt->got_SIGHUP = false; + // 初始化字段 last_snapshot_start_time 为 0 perf_snap_cxt->last_snapshot_start_time = 0; + // 初始化字段 curr_table_size 为 0 perf_snap_cxt->curr_table_size = 0; + // 初始化字段 curr_snapid 为 0 perf_snap_cxt->curr_snapid = 0; + // 初始化指针字段 connect 为 NULL perf_snap_cxt->connect = NULL; + // 初始化字段 cancel_request 为 0 perf_snap_cxt->cancel_request = 0; + // 初始化字段 request_snapshot 为 false perf_snap_cxt->request_snapshot = false; + // 初始化指针字段 res 为 NULL perf_snap_cxt->res = NULL; + // 初始化字段 is_mem_protect 为 false perf_snap_cxt->is_mem_protect = false; } +// 初始化 ash 上下文结构体 static void knl_t_ash_init(knl_t_ash_context* ash_cxt) { + // 初始化字段 last_ash_start_time 为 0 ash_cxt->last_ash_start_time = 0; + // 初始化字段 need_exit 为 false ash_cxt->need_exit = false; + // 初始化字段 got_SIGHUP 为 false ash_cxt->got_SIGHUP = false; + // 初始化字段 slot 为 0 ash_cxt->slot = 0; + // 初始化指针字段 waitEventStr 为 NULL ash_cxt->waitEventStr = NULL; } +// 初始化 statement 上下文结构体 static void knl_t_statement_init(knl_t_statement_context* statement_cxt) { + // 初始化字段 need_exit 为 false statement_cxt->need_exit = false; + // 初始化字段 got_SIGHUP 为 false statement_cxt->got_SIGHUP = false; + // 初始化字段 full_sql_retention_time 为 0 statement_cxt->full_sql_retention_time = 0; + // 初始化字段 slow_sql_retention_time 为 0 statement_cxt->slow_sql_retention_time = 0; + // 初始化指针字段 instr_prev_post_parse_analyze_hook 为 NULL statement_cxt->instr_prev_post_parse_analyze_hook = NULL; } - +// 初始化 stat 上下文结构体 static void knl_t_stat_init(knl_t_stat_context* stat_cxt) { + // 初始化指针字段 local_bad_block_mcxt 为 NULL stat_cxt->local_bad_block_mcxt = NULL; + // 初始化指针字段 local_bad_block_stat 为 NULL stat_cxt->local_bad_block_stat = NULL; + // 初始化字段 need_exit 为 false stat_cxt->need_exit = false; } +// 初始化 heartbeat 上下文结构体 static void knl_t_heartbeat_init(knl_t_heartbeat_context* heartbeat_cxt) { + // 初始化字段 got_SIGHUP 为 false heartbeat_cxt->got_SIGHUP = false; + // 初始化字段 shutdown_requested 为 false heartbeat_cxt->shutdown_requested = false; + // 初始化指针字段 state 为 NULL heartbeat_cxt->state = NULL; + // 初始化字段 total_failed_times 为 0 heartbeat_cxt->total_failed_times = 0; + // 初始化字段 last_failed_timestamp 为 0 heartbeat_cxt->last_failed_timestamp = 0; } @@ -1696,8 +1820,10 @@ void KnlLscContextInit(knl_t_lsc_context *lsc_cxt) lsc_cxt->FetchTupleFromCatCList = NULL; } +// 线程角色初始化函数 void knl_thread_init(knl_thread_role role) { + // 设置线程的角色 t_thrd.role = role; t_thrd.subrole = NO_SUBROLE; t_thrd.proc = NULL; @@ -1707,20 +1833,21 @@ void knl_thread_init(knl_thread_role role) t_thrd.myLogicTid = 10000; t_thrd.fake_session = NULL; t_thrd.threadpool_cxt.reaper_dead_session = false; - ENABLE_MEMORY_PROTECT(); - MemoryContextUnSeal(t_thrd.top_mem_cxt); - t_thrd.mcxt_group = New(t_thrd.top_mem_cxt) MemoryContextGroup(); - t_thrd.mcxt_group->Init(t_thrd.top_mem_cxt); - MemoryContextSeal(t_thrd.top_mem_cxt); - MemoryContextSwitchTo(THREAD_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_DEFAULT)); + ENABLE_MEMORY_PROTECT(); // 启用内存保护 + MemoryContextUnSeal(t_thrd.top_mem_cxt); // 解除顶层内存上下文的封印 + t_thrd.mcxt_group = New(t_thrd.top_mem_cxt) MemoryContextGroup(); // 创建内存上下文组 + t_thrd.mcxt_group->Init(t_thrd.top_mem_cxt); // 初始化内存上下文组 + MemoryContextSeal(t_thrd.top_mem_cxt); // 封印顶层内存上下文 + // 初始化各个模块的上下文 KnlLscContextInit(&t_thrd.lsc_cxt); - /* CommProxy Support */ + /* CommProxy 支持 */ t_thrd.comm_sock_option = g_default_invalid_sock_opt; t_thrd.comm_epoll_option = g_default_invalid_epoll_opt; t_thrd.comm_poll_option = g_default_invalid_poll_opt; - - knl_t_aes_init(&t_thrd.aes_cxt); + + // 初始化各个模块的上下文 + knl_t_aes_init(&t_thrd.aes_cxt); knl_t_aiocompleter_init(&t_thrd.aio_cxt); knl_t_alarmchecker_init(&t_thrd.alarm_cxt); knl_t_arch_init(&t_thrd.arch); @@ -1821,16 +1948,18 @@ void knl_thread_init(knl_thread_role role) #ifdef ENABLE_MOT knl_t_mot_init(&t_thrd.mot_cxt); #endif - - KnlTGstatInit(&t_thrd.gstat_cxt); + + // 初始化各个模块的上下文 + KnlTGstatInit(&t_thrd.gstat_cxt); // 全局统计信息的初始化 #ifdef DEBUG_UHEAP - knl_t_uheap_stats_init(&t_thrd.uheap_stats_cxt); + knl_t_uheap_stats_init(&t_thrd.uheap_stats_cxt); // UHeap统计信息的初始化(仅在调试模式下) #endif - KnlDcfContextInit(&t_thrd.dcf_cxt); + KnlDcfContextInit(&t_thrd.dcf_cxt); // DCF模块的初始化 } + __attribute__ ((__used__)) knl_thrd_context *GetCurrentThread() { return &t_thrd; @@ -1868,4 +1997,3 @@ void RedoPageRepairCallBack(RepairBlockKey key, XLogPhyBlock pblk) t_thrd.xlog_cxt.redoPageRepairCallBackFunc(key, pblk); } } - -- 2.34.1 From a4a404f207c9968cfe7eeb04fcdd53d90933bddc Mon Sep 17 00:00:00 2001 From: Nemoo <18210033860@163.com> Date: Sat, 30 Sep 2023 14:18:56 +0800 Subject: [PATCH 30/50] Update threadpool_controler.cpp --- .../threadpool/threadpool_controler.cpp | 369 ++++++++++-------- 1 file changed, 202 insertions(+), 167 deletions(-) diff --git a/src/gausskernel/process/threadpool/threadpool_controler.cpp b/src/gausskernel/process/threadpool/threadpool_controler.cpp index 63c13d4a9..dedb9f47a 100644 --- a/src/gausskernel/process/threadpool/threadpool_controler.cpp +++ b/src/gausskernel/process/threadpool/threadpool_controler.cpp @@ -7,16 +7,12 @@ * * 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. * ------------------------------------------------------------------------- * * threadpool_controler.cpp - * Controler for thread pool. Class ThreadPoolControler is defined to - * initilize thread pool's worker and listener threads, and dispatch - * new session from postmaster thread to suitable thread group. + * 线程池的控制器。定义了 ThreadPoolControler 类, + * 用于初始化线程池的工作线程和监听线程,并将新会话从 + * 主线程分派到合适的线程组。 * * * IDENTIFICATION @@ -71,32 +67,33 @@ ThreadPoolControler* g_threadPoolControler = NULL; static const long one_hundred_micro_sec = 100; +// 线程池控制器的构造函数 ThreadPoolControler::ThreadPoolControler() { - m_threadPoolContext = NULL; - m_sessCtrl = NULL; - m_groups = NULL; - m_scheduler = NULL; - m_groupNum = 1; - m_threadNum = 0; - m_maxPoolSize = 0; - m_maxStreamPoolSize = 0; - m_streamProcRatio = 0; + m_threadPoolContext = NULL; // 线程池上下文 + m_sessCtrl = NULL; // 会话控制 + m_groups = NULL; // 线程组 + m_scheduler = NULL; // 调度器 + m_groupNum = 1; // 线程组数量 + m_threadNum = 0; // 线程数量 + m_maxPoolSize = 0; // 最大线程池大小 + m_maxStreamPoolSize = 0; // 最大流线程池大小 + m_streamProcRatio = 0; // 流处理比例 } ThreadPoolControler::~ThreadPoolControler() { - delete m_scheduler; - delete m_sessCtrl; - MemoryContextDelete(m_threadPoolContext); - m_threadPoolContext = NULL; - m_groups = NULL; - m_sessCtrl = NULL; + delete m_scheduler; // 删除调度器对象 + delete m_sessCtrl; // 删除会话控制对象 + MemoryContextDelete(m_threadPoolContext); // 删除线程池上下文 + m_threadPoolContext = NULL; // 将线程池上下文置为空 + m_groups = NULL; // 将线程组指针置为空 + m_sessCtrl = NULL; // 将会话控制指针置为空 } void ThreadPoolControler::Init(bool enableNumaDistribute) { - + // 创建线程池上下文 m_threadPoolContext = AllocSetContextCreate(g_instance.instance_context, "ThreadPoolContext", ALLOCSET_DEFAULT_MINSIZE, @@ -104,12 +101,18 @@ void ThreadPoolControler::Init(bool enableNumaDistribute) ALLOCSET_DEFAULT_MAXSIZE, SHARED_CONTEXT); + // 切换到线程池上下文 AutoContextSwitch memSwitch(m_threadPoolContext); + // 分配线程组数组 m_groups = (ThreadPoolGroup**)palloc(sizeof(ThreadPoolGroup*) * m_groupNum); + + // 创建线程池会话控制对象 m_sessCtrl = New(CurrentMemoryContext) ThreadPoolSessControl(CurrentMemoryContext); + // 检查是否绑定 CPU bool bindCpu = CheckCpuBind(); + // 检查是否绑定 CPU NUMA bool bindCpuNuma = CheckCpuNumaBind(); int maxThreadNum = 0; int expectThreadNum = 0; @@ -123,16 +126,14 @@ void ThreadPoolControler::Init(bool enableNumaDistribute) while (m_cpuInfo.cpuArrSize[numaId] == 0) numaId++; - /* - * Invoke numa_set_preferred before starting worker thread to make - * more memory allocation local to worker thread. - */ #ifdef __USE_NUMA + // 如果启用 NUMA 分配,则设置首选 NUMA 节点 if (enableNumaDistribute) { numa_set_preferred(numaId); } #endif + // 获取当前 NUMA 节点的 CPU 数量 Assert(numaId < m_cpuInfo.totalNumaNum); expectThreadNum = (int)round( (double)m_threadNum * ((double)m_cpuInfo.cpuArrSize[numaId] / (double)m_cpuInfo.activeCpuNum)); @@ -143,30 +144,35 @@ void ThreadPoolControler::Init(bool enableNumaDistribute) cpuNum = m_cpuInfo.cpuArrSize[numaId]; cpuArr = m_cpuInfo.cpuArr[numaId]; + // 增加 NUMA ID,以处理下一个 NUMA 节点 numaId++; } else { + // 如果未绑定 CPU,则均匀分配线程和流的数量 expectThreadNum = m_threadNum / m_groupNum; maxThreadNum = m_maxPoolSize / m_groupNum; maxStreamNum = m_maxPoolSize / m_groupNum; numaId = -1; } + // 创建线程池组对象并初始化 m_groups[i] = New(CurrentMemoryContext)ThreadPoolGroup(maxThreadNum, expectThreadNum, maxStreamNum, i, numaId, cpuNum, cpuArr, bindCpuNuma); m_groups[i]->Init(enableNumaDistribute); } + // 等待所有线程池组准备就绪 for (int i = 0; i < m_groupNum; i++) { m_groups[i]->WaitReady(); } #ifdef __USE_NUMA if (enableNumaDistribute) { - /* Set to interleave mode for other than worker thread */ + /* 设置为交织模式以供除工作线程之外的其他线程使用 */ numa_set_interleave_mask(numa_all_nodes_ptr); } #endif + // 创建线程池调度器对象并启动 m_scheduler = New(CurrentMemoryContext) ThreadPoolScheduler(m_groupNum, m_groups); m_scheduler->StartUp(); } @@ -194,9 +200,10 @@ void AdjustThreadAffinity(void) { cpu_set_t m_cpuset; CPU_ZERO(&m_cpuset); - /* Check if the instance has been attch to some specific CPUs. */ + // 检查实例是否已绑定到某些特定的 CPU int ret = pthread_getaffinity_np(PostmasterPid, sizeof(cpu_set_t), &m_cpuset); if (ret == 0) { + // 如果绑定到 CPU 0 而未绑定到 CPU 1,则重新设置 CPU 亲和性,以避免绑定到单一 CPU if ((CPU_ISSET(0, &m_cpuset)) && (!CPU_ISSET(1, &m_cpuset))) { int num_processors = sysconf(_SC_NPROCESSORS_CONF); CPU_ZERO(&m_cpuset); @@ -204,7 +211,7 @@ void AdjustThreadAffinity(void) { CPU_SET(j, &m_cpuset); } - // set CPU affinity of a thread + // 设置线程的 CPU 亲和性 int s = pthread_setaffinity_np(PostmasterPid, sizeof(cpu_set_t), &m_cpuset); if (s != 0) { ereport(WARNING, (errmsg("AdjustThreadAffinity fail to bind thread %lu, errno: %d", PostmasterPid, ret))); @@ -213,17 +220,16 @@ void AdjustThreadAffinity(void) { } } - -void ThreadPoolControler::GetInstanceBind(cpu_set_t *cpuset) -{ - /* this function is used to avoid the libgomp bug on some specified OS */ +void ThreadPoolControler::GetInstanceBind(cpu_set_t *cpuset) { + // 修复 libgomp 在某些指定操作系统上的 bug AdjustThreadAffinity(); - /* Check if the instance has been attch to some specific CPUs. */ + // 检查实例是否已绑定到某些特定的 CPU int ret = pthread_getaffinity_np(PostmasterPid, sizeof(cpu_set_t), cpuset); if (ret == 0) { return; } else { + // 初始化 cpuset,以避免未绑定的情况 errno_t rc = memset_s(cpuset, sizeof(cpu_set_t), 0, sizeof(cpu_set_t)); securec_check(rc, "\0", "\0"); } @@ -235,7 +241,7 @@ void ThreadPoolControler::ParseAttr() m_attr.groupNum = DEFAULT_THREAD_POOL_GROUPS; m_attr.bindCpu = NULL; - /* Do str copy and remove space. */ + /* 复制字符串并移除空格 */ char* attr = TrimStr(g_instance.attr.attr_common.thread_pool_attr); if (IS_NULL_STR(attr)) return; @@ -244,13 +250,13 @@ void ThreadPoolControler::ParseAttr() char* psave = NULL; const char* pdelimiter = ","; - /* Get thread num */ + /* 获取线程数 */ ptoken = TrimStr(strtok_r(attr, pdelimiter, &psave)); if (!IS_NULL_STR(ptoken)) m_attr.threadNum = pg_strtoint32(ptoken); pfree_ext(ptoken); - /* Ger group num */ + /* 获取线程组数 */ ptoken = TrimStr(strtok_r(NULL, pdelimiter, &psave)); if (!IS_NULL_STR(ptoken)) m_attr.groupNum = pg_strtoint32(ptoken); @@ -258,15 +264,15 @@ void ThreadPoolControler::ParseAttr() if (m_attr.threadNum < 0 || m_attr.threadNum > MAX_THREAD_POOL_SIZE) INVALID_ATTR_ERROR( - errdetail("Current thread num %d is out of range [%d, %d].", m_attr.threadNum, 0, MAX_THREAD_POOL_SIZE)); + errdetail("当前线程数 %d 超出范围 [%d, %d]。", m_attr.threadNum, 0, MAX_THREAD_POOL_SIZE)); if (m_attr.groupNum < 0 || m_attr.groupNum > MAX_THREAD_POOL_GROUPS) INVALID_ATTR_ERROR( - errdetail("Current group num %d is out of range [%d, %d].", m_attr.groupNum, 0, MAX_THREAD_POOL_GROUPS)); + errdetail("当前线程组数 %d 超出范围 [%d, %d]。", m_attr.groupNum, 0, MAX_THREAD_POOL_GROUPS)); - /* Get attach cpu */ + /* 获取绑定 CPU */ m_attr.bindCpu = TrimStr(psave); ParseBindCpu(); - + pfree_ext(attr); } @@ -276,7 +282,8 @@ void ThreadPoolControler::ParseStreamAttr() m_stream_attr.procRatio = DEFAULT_THREAD_POOL_STREAM_PROC_RATIO; m_stream_attr.groupNum = DEFAULT_THREAD_POOL_GROUPS; m_stream_attr.bindCpu = NULL; - + + // 获取流式线程池属性配置字符串 char* attr = TrimStr(g_instance.attr.attr_common.thread_pool_stream_attr); if (IS_NULL_STR(attr)) { return; @@ -285,36 +292,36 @@ void ThreadPoolControler::ParseStreamAttr() char* ptoken = NULL; char* psave = NULL; const char* pdelimiter = ","; - - /* Get stream_thread_pool_stream max thread num */ + + // 解析流式线程池的最大线程数 ptoken = TrimStr(strtok_r(attr, pdelimiter, &psave)); if (IS_NULL_STR(ptoken) || !isdigit((unsigned char)*ptoken)) { INVALID_ATTR_ERROR( - errdetail("Current thread_pool_stream_attr format is error, stream_thread_num must be digital.")); + errdetail("当前 thread_pool_stream_attr 格式错误,stream_thread_num 必须为数字。")); } m_stream_attr.threadNum = pg_strtoint32(ptoken); pfree_ext(ptoken); if (m_stream_attr.threadNum < 0 || m_stream_attr.threadNum > MAX_THREAD_POOL_SIZE) { INVALID_ATTR_ERROR( - errdetail("Current stream_thread_num %d is out of range [%d, %d].", + errdetail("当前 stream_thread_num %d 超出范围 [%d, %d]。", m_stream_attr.threadNum, 0, MAX_THREAD_POOL_SIZE)); } - - /* Get proc ratio of stream threads */ + + // 解析流式线程池的处理比例 ptoken = TrimStr(strtok_r(NULL, pdelimiter, &psave)); if (IS_NULL_STR(ptoken) || !isdigit((unsigned char)*ptoken)) { INVALID_ATTR_ERROR( - errdetail("Current thread_pool_stream_attr format is error, stream_proc_ratio must be digital.")); + errdetail("当前 thread_pool_stream_attr 格式错误,stream_proc_ratio 必须为数字。")); } m_stream_attr.procRatio = atof(ptoken); pfree_ext(ptoken); if (m_stream_attr.procRatio <= 0 || m_stream_attr.procRatio > MAX_THREAD_POOL_STREAM_PROC_RATIO) { INVALID_ATTR_ERROR( - errdetail("Current stream_proc_ratio %f is out of range (%d, %d].", + errdetail("当前 stream_proc_ratio %f 超出范围 (%d, %d]。", m_stream_attr.procRatio, 0, MAX_THREAD_POOL_STREAM_PROC_RATIO)); } pfree_ext(attr); - + return; } @@ -325,6 +332,7 @@ void ThreadPoolControler::ParseBindCpu() return; } + // 复制属性字符串以进行解析 char* pattr = pstrdup(m_attr.bindCpu); char* scpu = pattr; char* ptoken = NULL; @@ -332,15 +340,18 @@ void ThreadPoolControler::ParseBindCpu() const char* pdelimiter = ":"; int bindNum = 0; + // 检查属性字符串的格式是否正确 if (scpu[0] != '(' || scpu[strlen(scpu) - 1] != ')') - INVALID_ATTR_ERROR("Use '(' ')' to indicate cpu bind info."); + INVALID_ATTR_ERROR("使用 '(' ')' 来表示 CPU 绑定信息。"); scpu++; scpu[strlen(scpu) - 1] = '\0'; + // 解析属性字符串并转换为小写 ptoken = TrimStr(strtok_r(scpu, pdelimiter, &psave)); ptoken = pg_strtolower(ptoken); + // 根据不同的属性类型进行解析 if (strncmp("nobind", ptoken, strlen("nobind")) == 0) { m_cpuInfo.bindType = NO_CPU_BIND; return; @@ -360,16 +371,16 @@ void ThreadPoolControler::ParseBindCpu() m_cpuInfo.isBindCpuNumaArr = (bool*)palloc0(sizeof(bool) * m_cpuInfo.totalCpuNum); bindNum = ParseRangeStr(psave, m_cpuInfo.isBindCpuNumaArr, m_cpuInfo.totalCpuNum, "numabind"); } else { - INVALID_ATTR_ERROR(errdetail("Only 'nobind', 'allbind', 'cpubind', 'nodebind' and 'numabind' " - "are valid attribute.")); + INVALID_ATTR_ERROR(errdetail("只有 'nobind', 'allbind', 'cpubind', 'nodebind' 和 'numabind' " + "是有效的属性。")); } + // 检查是否找到有效的 CPU 进行线程绑定 if (bindNum == 0) INVALID_ATTR_ERROR( - errdetail("Can not find valid CPU for thread binding, there are two possible reasons:\n" - "1. These CPUs are not active, use lscpu to check On-line CPU(s) list.\n" - "2. The process has been bind to other CPUs and there is no intersection," - "use taskset -pc to check process CPU bind info.\n")); + errdetail("无法找到有效的 CPU 进行线程绑定,可能的原因有两个:\n" + "1. 这些 CPU 不处于活动状态,请使用 lscpu 命令检查在线 CPU 列表。\n" + "2. 进程已绑定到其他 CPU,且没有交集,请使用 taskset -pc 命令检查进程 CPU 绑定信息。\n")); pfree_ext(ptoken); pfree_ext(pattr); } @@ -381,6 +392,7 @@ int ThreadPoolControler::ParseRangeStr(char* attr, bool* arr, int totalNum, char const char* pdelimiter = ","; int retNum = 0; + // 解析属性字符串 ptoken = TrimStr(strtok_r(attr, pdelimiter, &psave)); while (!IS_NULL_STR(ptoken)) { @@ -390,25 +402,29 @@ int ThreadPoolControler::ParseRangeStr(char* attr, bool* arr, int totalNum, char int startid = -1; int endid = -1; + // 解析范围字符串,可能包含起始和结束值 pt = TrimStr(strtok_r(ptoken, pd, &ps)); if (!IS_NULL_STR(pt)) startid = pg_strtoint32(pt); if (!IS_NULL_STR(ps)) endid = pg_strtoint32(ps); + // 检查解析的值是否有效 if (startid < 0 && endid < 0) - INVALID_ATTR_ERROR(errdetail("Can not parse attribute %s", pt)); + INVALID_ATTR_ERROR(errdetail("无法解析属性 %s", pt)); if (startid >= totalNum) INVALID_ATTR_ERROR( - errdetail("The %s attribute %d is out of valid range [%d, %d]", bindtype, startid, 0, totalNum - 1)); + errdetail("属性 %s 中的 %d 超出了有效范围 [%d, %d]", bindtype, startid, 0, totalNum - 1)); if (endid >= totalNum) INVALID_ATTR_ERROR( - errdetail("The %s attribute %d is out of valid range [%d, %d]", bindtype, endid, 0, totalNum - 1)); + errdetail("属性 %s 中的 %d 超出了有效范围 [%d, %d]", bindtype, endid, 0, totalNum - 1)); if (endid == -1) { + // 单个 CPU 绑定 retNum += arr[startid] ? 0 : 1; arr[startid] = true; } else { + // 范围内的多个 CPU 绑定 if (startid > endid) { int tmpid = startid; startid = endid; @@ -421,7 +437,7 @@ int ThreadPoolControler::ParseRangeStr(char* attr, bool* arr, int totalNum, char } } - /* Don't need to free when error ocurrs, errors here are FATAL level! */ + // 释放临时字符串内存,不需要在错误发生时释放,这里的错误是致命的! pfree_ext(pt); pfree_ext(ptoken); ptoken = TrimStr(strtok_r(NULL, pdelimiter, &psave)); @@ -438,9 +454,8 @@ bool* ThreadPoolControler::GetMcsCpuInfo(int totalCpuNum) bool* isMcsCpuArr = (bool*)palloc0(sizeof(bool) * totalCpuNum); /* - * When the database is deplyed on MCS, we need to read cpuset.cpus to find - * available CPUs in this MCS. If we can read this file, then we think all - * CPUs are available. + * 当数据库部署在MCS上时,我们需要读取cpuset.cpus文件以查找此MCS中可用的CPU。 + * 如果我们能够读取此文件,那么我们认为所有CPU都是可用的。 */ fp = fopen("/sys/fs/cgroup/cpuset/cpuset.cpus", "r"); if (fp == NULL) { @@ -473,11 +488,12 @@ bool* ThreadPoolControler::GetMcsCpuInfo(int totalCpuNum) void ThreadPoolControler::GetActiveCpu(NumaCpuId *numaCpuIdList, int *num) { - *num = 0; + *num = 0; // 初始化传出参数 num 为 0 char buf[BUFSIZE]; - FILE* fp = popen("lscpu -b -e=cpu,node", "r"); + FILE* fp = popen("lscpu -b -e=cpu,node", "r"); // 打开一个用于读取 CPU 信息的流 + if (fp == NULL) { - ereport(WARNING, (errmsg("Unable to use 'lscpu' to read CPU info."))); + ereport(WARNING, (errmsg("Unable to use 'lscpu' to read CPU info."))); // 如果打开流失败,发出警告消息 return; } @@ -486,45 +502,49 @@ void ThreadPoolControler::GetActiveCpu(NumaCpuId *numaCpuIdList, int *num) const char* pdelimiter = " "; int cpuid = 0; int numaid = 0; - /* try to read the header. */ + + // 尝试读取头部信息 if (fgets(buf, sizeof(buf), fp) != NULL) { while (fgets(buf, sizeof(buf), fp) != NULL) { ptoken = strtok_r(buf, pdelimiter, &psave); if (!IS_NULL_STR(ptoken)) { - cpuid = pg_strtoint32(ptoken); + cpuid = pg_strtoint32(ptoken); // 解析 CPU ID } ptoken = strtok_r(NULL, pdelimiter, &psave); if (!IS_NULL_STR(ptoken)) { - numaid = pg_strtoint32(ptoken); + numaid = pg_strtoint32(ptoken); // 解析 NUMA ID } - numaCpuIdList[*num].cpuId = cpuid; - numaCpuIdList[*num].numaId = numaid; - (*num)++; + numaCpuIdList[*num].cpuId = cpuid; // 将 CPU ID 存入传出参数 + numaCpuIdList[*num].numaId = numaid; // 将 NUMA ID 存入传出参数 + (*num)++; // 递增传出参数 num } } - pclose(fp); + pclose(fp); // 关闭流 } void ThreadPoolControler::GetSysCpuInfo() { if (m_cpuInfo.totalNumaNum == 0 || m_cpuInfo.totalCpuNum == 0) { - ereport(WARNING, (errmsg("Fail to read cpu num or numa num."))); + ereport(WARNING, (errmsg("Fail to read cpu num or numa num."))); // 如果没有读取到 CPU 数量或 NUMA 数量,发出警告消息 return; } - m_cpuInfo.isMcsCpuArr = GetMcsCpuInfo(m_cpuInfo.totalCpuNum); + m_cpuInfo.isMcsCpuArr = GetMcsCpuInfo(m_cpuInfo.totalCpuNum); // 获取 MCS CPU 信息 m_cpuInfo.cpuArr = (int**)palloc0(sizeof(int*) * m_cpuInfo.totalNumaNum); m_cpuInfo.cpuArrSize = (int*)palloc0(sizeof(int) * m_cpuInfo.totalNumaNum); int cpu_per_numa = m_cpuInfo.totalCpuNum / m_cpuInfo.totalNumaNum; + for (int i = 0; i < m_cpuInfo.totalNumaNum; i++) { - m_cpuInfo.cpuArr[i] = (int*)palloc0(sizeof(int) * cpu_per_numa); + m_cpuInfo.cpuArr[i] = (int*)palloc0(sizeof(int) * cpu_per_numa); // 为每个 NUMA 节点分配内存 } + m_cpuInfo.activeCpuNum = 0; NumaCpuId *sysNumaCpuIdList = (NumaCpuId*)palloc0(sizeof(NumaCpuId) * m_cpuInfo.totalCpuNum); int sysNumaCpuIdNum = 0; - GetActiveCpu(sysNumaCpuIdList, &sysNumaCpuIdNum); + + GetActiveCpu(sysNumaCpuIdList, &sysNumaCpuIdNum); // 获取激活的 CPU 信息 if (sysNumaCpuIdNum == 0) { return; @@ -533,56 +553,58 @@ void ThreadPoolControler::GetSysCpuInfo() for (int i = 0; i < sysNumaCpuIdNum; ++i) { int cpuid = sysNumaCpuIdList[i].cpuId; int numaid = sysNumaCpuIdList[i].numaId; + if (IsActiveCpu(cpuid, numaid)) { - m_cpuInfo.cpuArr[numaid][m_cpuInfo.cpuArrSize[numaid]] = cpuid; - m_cpuInfo.cpuArrSize[numaid]++; - m_cpuInfo.activeCpuNum++; + m_cpuInfo.cpuArr[numaid][m_cpuInfo.cpuArrSize[numaid]] = cpuid; // 将 CPU ID 存入相应 NUMA 节点的数组中 + m_cpuInfo.cpuArrSize[numaid]++; // 递增相应 NUMA 节点的数组大小 + m_cpuInfo.activeCpuNum++; // 递增激活的 CPU 数量 } - } - pfree_ext(sysNumaCpuIdList); + pfree_ext(sysNumaCpuIdList); // 释放内存 for (int i = 0; i < m_cpuInfo.totalNumaNum; i++) { if (m_cpuInfo.cpuArrSize[i] > 0) - m_cpuInfo.activeNumaNum++; + m_cpuInfo.activeNumaNum++; // 统计激活的 NUMA 节点数量 } } void ThreadPoolControler::InitCpuInfo() { - m_cpuInfo.totalCpuNum = 0; - m_cpuInfo.activeCpuNum = 0; - m_cpuInfo.totalNumaNum = 0; - m_cpuInfo.activeNumaNum = 0; - m_cpuInfo.cpuArrSize = NULL; - m_cpuInfo.cpuArr = NULL; + m_cpuInfo.totalCpuNum = 0; // 初始化总 CPU 数量为 0 + m_cpuInfo.activeCpuNum = 0; // 初始化激活的 CPU 数量为 0 + m_cpuInfo.totalNumaNum = 0; // 初始化总 NUMA 节点数量为 0 + m_cpuInfo.activeNumaNum = 0; // 初始化激活的 NUMA 节点数量为 0 + m_cpuInfo.cpuArrSize = NULL; // 初始化 CPU 数组大小为 NULL + m_cpuInfo.cpuArr = NULL; // 初始化 CPU 数组为 NULL - m_cpuInfo.bindType = NO_CPU_BIND; - m_cpuInfo.isBindCpuArr = NULL; - m_cpuInfo.isBindNumaArr = NULL; - m_cpuInfo.isMcsCpuArr = NULL; + m_cpuInfo.bindType = NO_CPU_BIND; // 初始化 CPU 绑定类型为 NO_CPU_BIND + m_cpuInfo.isBindCpuArr = NULL; // 初始化 CPU 绑定数组为 NULL + m_cpuInfo.isBindNumaArr = NULL; // 初始化 NUMA 节点绑定数组为 NULL + m_cpuInfo.isMcsCpuArr = NULL; // 初始化 MCS CPU 数组为 NULL } void ThreadPoolControler::GetCpuAndNumaNum(int32 *totalCpuNum, int32 *totalNumaNum) { char buf[BUFSIZE]; - FILE* fp = NULL; + // 打开 "lscpu" 命令的输出以获取 CPU 和 NUMA 节点数量信息 if ((fp = popen("LANG=en_US.UTF-8;lscpu", "r")) != NULL) { while (fgets(buf, sizeof(buf), fp) != NULL) { if (strncmp("CPU(s)", buf, strlen("CPU(s)")) == 0 && strncmp("On-line CPU(s) list", buf, strlen("On-line CPU(s) list")) != 0 && strncmp("NUMA node", buf, strlen("NUMA node")) != 0) { + // 当遇到包含 "CPU(s)" 的行时,解析并获取总 CPU 数量 char* loc = strchr(buf, ':'); *totalCpuNum = pg_strtoint32(loc + 1); } else if (strncmp("NUMA node(s)", buf, strlen("NUMA node(s)")) == 0) { + // 当遇到包含 "NUMA node(s)" 的行时,解析并获取总 NUMA 节点数量 char* loc = strchr(buf, ':'); *totalNumaNum = pg_strtoint32(loc + 1); } } - pclose(fp); + pclose(fp); // 关闭文件流 } } @@ -591,67 +613,72 @@ bool ThreadPoolControler::IsActiveCpu(int cpuid, int numaid) switch (m_cpuInfo.bindType) { case NO_CPU_BIND: case ALL_CPU_BIND: + // 如果未进行 CPU 绑定,或者进行了全局 CPU 绑定,检查 CPU 是否激活 return (m_cpuInfo.isMcsCpuArr[cpuid] && CPU_ISSET(cpuid, &m_cpuset)); case NODE_BIND: + // 如果进行了 NUMA 节点绑定,检查 CPU 和 NUMA 节点是否激活 return (m_cpuInfo.isBindNumaArr[numaid] && m_cpuInfo.isMcsCpuArr[cpuid] && CPU_ISSET(cpuid, &m_cpuset)); case CPU_BIND: + // 如果进行了 CPU 绑定,检查 CPU 是否激活 return (m_cpuInfo.isBindCpuArr[cpuid] && m_cpuInfo.isMcsCpuArr[cpuid] && CPU_ISSET(cpuid, &m_cpuset)); case NUMA_BIND: + // 如果进行了 NUMA 节点和 CPU 绑定,检查 CPU 和 NUMA 节点是否激活 return (m_cpuInfo.isBindCpuNumaArr[cpuid] && m_cpuInfo.isMcsCpuArr[cpuid] && CPU_ISSET(cpuid, &m_cpuset)); } - return false; + return false; // 默认情况下,返回 false } bool ThreadPoolControler::CheckCpuBind() const { if (m_cpuInfo.bindType == NO_CPU_BIND) - return false; + return false; // 如果没有进行 CPU 绑定,则返回 false + if (m_groupNum != m_cpuInfo.activeNumaNum) { ereport(WARNING, - (errmsg("Can not bind worker thread to CPU because the " - "thread group num must equal to active NUMA num."))); - return false; - } - if (m_cpuInfo.activeCpuNum == 0 || m_cpuInfo.cpuArr == NULL) { - ereport(WARNING, (errmsg("Can not bind worker thread to CPU because no valid CPUs."))); - return false; + (errmsg("无法将工作线程绑定到 CPU,因为线程组数必须等于激活的 NUMA 节点数。"))); + return false; // 如果线程组数不等于激活的 NUMA 节点数,返回 false } - return true; + if (m_cpuInfo.activeCpuNum == 0 || m_cpuInfo.cpuArr == NULL) { + ereport(WARNING, (errmsg("无法将工作线程绑定到 CPU,因为没有有效的 CPU。"))); + return false; // 如果没有有效的 CPU,返回 false + } + + return true; // 其他情况下,返回 true } bool ThreadPoolControler::CheckCpuNumaBind() const { - return m_cpuInfo.bindType == NUMA_BIND; + return m_cpuInfo.bindType == NUMA_BIND; // 如果进行了 NUMA 绑定,返回 true,否则返回 false } bool ThreadPoolControler::CheckNumaDistribute(int numaNodeNum) const { if (m_cpuInfo.bindType == NO_CPU_BIND) { ereport(WARNING, - (errmsg("allbind should be used to replace nobind in thread_pool_attr when NUMA is activated."))); - return false; + (errmsg("在激活 NUMA 时,应使用 allbind 来替代 nobind 在 thread_pool_attr 中。"))); + return false; // 如果未进行 CPU 绑定,给出警告并返回 false } if (!CheckCpuBind()) { - return false; + return false; // 如果 CPU 绑定检查失败,返回 false } if (m_cpuInfo.totalNumaNum != numaNodeNum || !m_cpuInfo.cpuArrSize) { ereport(WARNING, - (errmsg("Can not activate NUMA distribute because no multiple NUMA nodes or CPUs are available."))); - return false; + (errmsg("无法激活 NUMA 分布,因为没有多个 NUMA 节点或可用的 CPU。"))); + return false; // 如果 NUMA 节点数不等于给定的 numaNodeNum,或者 cpuArrSize 为空,返回 false } for (int i = 0; i < m_cpuInfo.totalNumaNum; ++i) { if (m_cpuInfo.cpuArrSize[i] <= 0) { ereport(WARNING, - (errmsg("Can not activate NUMA distribute because no available cpu in node %d.", i))); - return false; + (errmsg("无法激活 NUMA 分布,因为节点 %d 中没有可用的 CPU。", i))); + return false; // 如果某个 NUMA 节点中没有可用的 CPU,返回 false } } - return true; + return true; // 其他情况下,返回 true } CPUBindType ThreadPoolControler::GetCpuBindType() const @@ -669,55 +696,54 @@ void ThreadPoolControler::SetGroupAndThreadNum() { if (m_attr.groupNum == 0) { if (m_cpuInfo.totalNumaNum > 0) - m_groupNum = m_cpuInfo.activeNumaNum; + m_groupNum = m_cpuInfo.activeNumaNum; // 如果未指定线程组数且存在 NUMA 节点,则使用激活的 NUMA 节点数 else - m_groupNum = DEFAULT_THREAD_POOL_GROUPS; + m_groupNum = DEFAULT_THREAD_POOL_GROUPS; // 否则使用默认的线程组数 } else { - m_groupNum = m_attr.groupNum; + m_groupNum = m_attr.groupNum; // 如果指定了线程组数,则使用指定的线程组数 } if (m_attr.threadNum == 0) { if (m_cpuInfo.activeCpuNum > 0) - m_threadNum = m_cpuInfo.activeCpuNum * THREAD_CORE_RATIO; + m_threadNum = m_cpuInfo.activeCpuNum * THREAD_CORE_RATIO; // 如果未指定线程数且存在激活的 CPU,则计算线程数 else - m_threadNum = DEFAULT_THREAD_POOL_SIZE; + m_threadNum = DEFAULT_THREAD_POOL_SIZE; // 否则使用默认的线程数 } else { - m_threadNum = m_attr.threadNum; + m_threadNum = m_attr.threadNum; // 如果指定了线程数,则使用指定的线程数 } - ConstrainThreadNum(); + ConstrainThreadNum(); // 调用 ConstrainThreadNum 方法进行线程数约束 } void ThreadPoolControler::ConstrainThreadNum() { - /* Thread pool size should not be larger than max_connections. */ + /* 线程池大小不应超过 max_connections。 */ if (MAX_THREAD_POOL_SIZE > g_instance.attr.attr_network.MaxConnections) { ereport(LOG, (errcode(ERRCODE_OPERATE_INVALID_PARAM), - errmsg("Max thread pool size %d should not be larger than max_connections %d, " - "so reduce max thread pool size to max_connections", + errmsg("最大线程池大小 %d 不应超过 max_connections %d,因此将最大线程池大小减小到 max_connections", MAX_THREAD_POOL_SIZE, g_instance.attr.attr_network.MaxConnections))); } - m_maxPoolSize = Min(MAX_THREAD_POOL_SIZE, g_instance.attr.attr_network.MaxConnections); - m_threadNum = Min(m_threadNum, m_maxPoolSize); + m_maxPoolSize = Min(MAX_THREAD_POOL_SIZE, g_instance.attr.attr_network.MaxConnections); // 最大线程池大小受限于 max_connections + m_threadNum = Min(m_threadNum, m_maxPoolSize); // 线程数不应超过最大线程池大小 } int ThreadPoolControler::GetThreadNum() { - return m_maxPoolSize; + return m_maxPoolSize; // 返回最大线程池大小 } ThreadPoolStat* ThreadPoolControler::GetThreadPoolStat(uint32* num) { - ThreadPoolStat* result = (ThreadPoolStat*)palloc(m_groupNum * sizeof(ThreadPoolStat)); + ThreadPoolStat* result = (ThreadPoolStat*)palloc(m_groupNum * sizeof(ThreadPoolStat)); // 分配存储线程池统计信息的内存 int i; for (i = 0; i < m_groupNum; i++) { - m_groups[i]->GetThreadPoolGroupStat(&result[i]); + m_groups[i]->GetThreadPoolGroupStat(&result[i]); // 获取每个线程组的统计信息 } - *num = m_groupNum; - return result; + *num = m_groupNum; // 返回线程组数量 + return result; // 返回线程池统计信息数组 } void ThreadPoolControler::CloseAllSessions() @@ -725,14 +751,14 @@ void ThreadPoolControler::CloseAllSessions() ereport(LOG, (errmodule(MOD_THREAD_POOL), errmsg("pmState:%d, start to close all sessions in threadpool.", pmState))); - m_sessCtrl->MarkAllSessionClose(); - (void)SignalCancelAllBackEnd(); + m_sessCtrl->MarkAllSessionClose(); // 标记所有会话为关闭状态 + (void)SignalCancelAllBackEnd(); // 发送取消信号以取消所有后端任务 for (int i = 0; i < m_groupNum; i++) { - m_groups[i]->GetListener()->SendShutDown(); + m_groups[i]->GetListener()->SendShutDown(); // 发送关闭信号给所有监听器 } - /* Check until all groups have closed their sessions. */ + /* 检查直到所有组都关闭了它们的会话。 */ bool allclose = false; while (!allclose) { if (m_sessCtrl->IsActiveListEmpty()) { @@ -741,9 +767,9 @@ void ThreadPoolControler::CloseAllSessions() allclose = true; for (int i = 0; i < m_groupNum; i++) { - allclose = (m_groups[i]->AllSessionClosed() && allclose); + allclose = (m_groups[i]->AllSessionClosed() && allclose); // 检查每个组的会话是否都已关闭 } - pg_usleep(one_hundred_micro_sec); + pg_usleep(one_hundred_micro_sec); // 短暂休眠以减少 CPU 使用 } ereport(LOG, (errmodule(MOD_THREAD_POOL), @@ -753,21 +779,21 @@ void ThreadPoolControler::CloseAllSessions() void ThreadPoolControler::ShutDownThreads(bool forceWait) { for (int i = 0; i < m_groupNum; i++) { - m_groups[i]->ShutDownThreads(); + m_groups[i]->ShutDownThreads(); // 关闭所有线程组中的线程 } ereport(LOG, (errmodule(MOD_THREAD_POOL), errmsg("pmState:%d, shut down all threadpool threads.", pmState))); if (forceWait) { - /* Check until all groups have shut down their workers. */ + /* 检查直到所有组都关闭了它们的工作线程。 */ bool allshut = false; while (!allshut) { allshut = true; for (int i = 0; i < m_groupNum; i++) { - allshut = (m_groups[i]->AllThreadShutDown() && allshut); + allshut = (m_groups[i]->AllThreadShutDown() && allshut); // 检查每个组的线程是否都已关闭 } - pg_usleep(one_hundred_micro_sec); + pg_usleep(one_hundred_micro_sec); // 短暂休眠以减少 CPU 使用 } ereport(LOG, (errmodule(MOD_THREAD_POOL), @@ -778,16 +804,16 @@ void ThreadPoolControler::ShutDownThreads(bool forceWait) void ThreadPoolControler::ShutDownListeners(bool forceWait) { for (int i = 0; i < m_groupNum; i++) { - m_groups[i]->GetListener()->ShutDown(); + m_groups[i]->GetListener()->ShutDown(); // 关闭所有线程组的监听器 } if (forceWait) { bool allshut = false; while (!allshut) { allshut = true; for (int i = 0; i < m_groupNum; i++) { - allshut = (m_groups[i]->GetListener()->GetThreadId() == 0) && allshut; + allshut = (m_groups[i]->GetListener()->GetThreadId() == 0) && allshut; // 检查监听器线程是否都已关闭 } - pg_usleep(one_hundred_micro_sec); + pg_usleep(one_hundred_micro_sec); // 短暂休眠以减少 CPU 使用 } } } @@ -828,20 +854,20 @@ void ThreadPoolControler::AddWorkerIfNecessary() ThreadPoolGroup* ThreadPoolControler::FindThreadGroupWithLeastSession() { - int idx = 0; - float4 least_session = 0.0; - float4 session_per_thread = 0.0; + int idx = 0; // 用于记录具有最少会话的线程组的索引 + float4 least_session = 0.0; // 用于记录最少会话数 + float4 session_per_thread = 0.0; // 用于记录每个线程的平均会话数 - least_session = m_groups[0]->GetSessionPerThread(); + least_session = m_groups[0]->GetSessionPerThread(); // 获取第一个线程组的平均会话数作为初始值 for (int i = 1; i < m_groupNum; i++) { - session_per_thread = m_groups[i]->GetSessionPerThread(); - if (session_per_thread < least_session) { - least_session = session_per_thread; - idx = i; + session_per_thread = m_groups[i]->GetSessionPerThread(); // 获取当前线程组的平均会话数 + if (session_per_thread < least_session) { // 如果当前线程组的平均会话数更小 + least_session = session_per_thread; // 更新最少会话数 + idx = i; // 更新具有最少会话的线程组的索引 } } - return m_groups[idx]; + return m_groups[idx]; // 返回具有最少会话的线程组的指针 } bool ThreadPoolControler::StayInAttachMode() @@ -855,15 +881,14 @@ int ThreadPoolControler::DispatchSession(Port* port) knl_session_context* sc = NULL; /* - * In comm_proxy mode, each accepted fd is combined with a fixed communicator thread in one NUMA group, - * we no longer distribute it with old mothod "group with latest sessions", - * so just return the communicator's NUMA group. + * 在comm_proxy模式下,每个接受的文件描述符(fd)都与一个 + * 固定的通信线程合并在一个NUMA组中,我们不再使用旧的方法“ + * 与最新会话的组合”进行分配,因此只需返回通信线程所在的NUMA组即可。 * - * Note: We assume that each connected user session can be equal-possibily distributed to communicators, - * fortunately,it looks like Euler OS can guarantee this(proved), - * otherwise we need revisit it. + * 注意:我们假设每个连接的用户会话可以等可能地分布给通信器, + * 幸运的是,欧拉操作系统似乎可以保证这一点(已经证明),否则我们还需要重新考虑这个问题。 * - * Performance optimization with comm_proxy when thread_pool m_groupNum same as comm_proxy numa groups + * 性能优化:在使用`comm_proxy`时,当`thread_pool`的`m_groupNum`与`comm_proxy`的NUMA组数相同时,可以实现性能优化。 */ if (AmIProxyModeSockfd(port->sock) && m_groupNum == g_comm_proxy_config.s_numa_num) { CommSockDesc* comm_sock = g_comm_controller->FdGetCommSockDesc(port->sock); @@ -876,7 +901,7 @@ int ThreadPoolControler::DispatchSession(Port* port) Assert(false); return STATUS_ERROR; } - /* if this group is hanged, we don't accept new session */ + /* 如果这个组挂起了,我们不会接受新的会话。 */ if (grp->IsGroupHanged()) { ereport(WARNING, (errmodule(MOD_THREAD_POOL), @@ -893,29 +918,39 @@ int ThreadPoolControler::DispatchSession(Port* port) } /* - * Bind the specified thread to all the available CPUs. - * This is invoked by auxiliary thread, such as WALSender. + * 将指定的线程绑定到所有可用的CPU。 + * 此操作由辅助线程(例如WAL发送者)调用。 */ void ThreadPoolControler::BindThreadToAllAvailCpu(ThreadId thread) const { + // 如果不需要绑定CPU,直接返回 if (!CheckCpuBind()) { return; } + // 如果绑定方式是ALL_CPU_BIND,也直接返回 if (m_cpuInfo.bindType == ALL_CPU_BIND) { return; } + // 创建一个CPU集合,初始化为空 cpu_set_t availCpuSet; CPU_ZERO(&availCpuSet); + + // 遍历每个NUMA节点 for (int numaNo = 0; numaNo < m_cpuInfo.totalNumaNum; ++numaNo) { int cpuNumber = m_cpuInfo.cpuArrSize[numaNo]; + + // 将每个NUMA节点上的CPU添加到CPU集合中 for (int i = 0; i < cpuNumber; ++i) { CPU_SET(m_cpuInfo.cpuArr[numaNo][i], &availCpuSet); } } + + // 使用pthread_setaffinity_np函数将线程绑定到CPU集合中 int ret = pthread_setaffinity_np(thread, sizeof(cpu_set_t), &availCpuSet); + + // 如果绑定失败,输出警告信息 if (ret != 0) ereport(WARNING, (errmsg("BindThreadToAllAvailCpu fail to bind thread %lu, errno: %d", thread, ret))); } - -- 2.34.1 From 6e808f3943479806942ca3eb70afd4e43d59002f Mon Sep 17 00:00:00 2001 From: Nemoo <18210033860@163.com> Date: Sat, 30 Sep 2023 14:19:38 +0800 Subject: [PATCH 31/50] Update threadpool_group.cpp --- .../process/threadpool/threadpool_group.cpp | 162 ++++++++++++------ 1 file changed, 112 insertions(+), 50 deletions(-) diff --git a/src/gausskernel/process/threadpool/threadpool_group.cpp b/src/gausskernel/process/threadpool/threadpool_group.cpp index 59d0203fd..6e11b6655 100644 --- a/src/gausskernel/process/threadpool/threadpool_group.cpp +++ b/src/gausskernel/process/threadpool/threadpool_group.cpp @@ -14,7 +14,9 @@ * ------------------------------------------------------------------------- * * threadpool_group.cpp - * Thread pool group controls listener and worker threads. + * 线程池组控制监听器线程和工作者线程 + * 监听器线程负责接受新的连接请求,通常用于处理客户端与服务器之间的通信建立和维护。 + * 工作者线程负责实际的任务处理,通常用于执行数据库查询、事务处理等具体的业务逻辑。 * * * IDENTIFICATION @@ -59,12 +61,12 @@ ThreadPoolGroup::ThreadPoolGroup(int maxWorkerNum, int expectWorkerNum, int maxStreamNum, int groupId, int numaId, int cpuNum, int* cpuArr, bool enableBindCpuNuma) : m_listener(NULL), - m_maxWorkerNum(maxWorkerNum), - m_maxStreamNum(maxStreamNum), + m_maxWorkerNum(maxWorkerNum),//最大工作者线程数量 + m_maxStreamNum(maxStreamNum),//最大流线程数量 m_defaultWorkerNum(expectWorkerNum), m_workerNum(0), m_listenerNum(0), - m_expectWorkerNum(expectWorkerNum), + m_expectWorkerNum(expectWorkerNum),//预期工作者线程数量 m_idleWorkerNum(0), m_pendingWorkerNum(0), m_streamNum(0), @@ -73,8 +75,8 @@ ThreadPoolGroup::ThreadPoolGroup(int maxWorkerNum, int expectWorkerNum, int maxS m_waitServeSessionCount(0), m_processTaskCount(0), m_hasHanged(0), - m_groupId(groupId), - m_numaId(numaId), + m_groupId(groupId),//线程池组的唯一标识符 + m_numaId(numaId),//NUMA 节点的标识符 m_groupCpuNum(cpuNum), m_groupCpuArr(cpuArr), m_enableNumaDistribute(false), @@ -104,6 +106,7 @@ ThreadPoolGroup::~ThreadPoolGroup() void ThreadPoolGroup::Init(bool enableNumaDistribute) { + // 创建线程池组上下文 m_context = AllocSetContextCreate(g_instance.instance_context, "ThreadPoolGroupContext", ALLOCSET_DEFAULT_MINSIZE, @@ -111,22 +114,27 @@ void ThreadPoolGroup::Init(bool enableNumaDistribute) ALLOCSET_DEFAULT_MAXSIZE, SHARED_CONTEXT); + // 切换到线程池组上下文 AutoContextSwitch acontext(m_context); + // 创建线程池监听器并启动 m_listener = New(CurrentMemoryContext) ThreadPoolListener(this); m_listener->StartUp(); + // 如果启用 CPU 绑定和 NUMA 分配,将指定的 CPU 添加到 CPU 集合中 if (m_enableBindCpuNuma) { for (int i = 0; i < m_groupCpuNum; i++) { CPU_SET(m_groupCpuArr[i], &m_CpuNumaSet); } } + // 初始化工作者线程管理 InitWorkerSentry(); + // 初始化流线程管理 InitStreamSentry(); - /* Prepare the CPU_SET including all of available cpus in this node */ + // 准备包含本节点所有可用 CPU 的 CPU_SET m_enableNumaDistribute = enableNumaDistribute; for (int i = 0; i < m_groupCpuNum; ++i) { CPU_SET(m_groupCpuArr[i], &m_nodeCpuSet); @@ -135,19 +143,19 @@ void ThreadPoolGroup::Init(bool enableNumaDistribute) void ThreadPoolGroup::InitWorkerSentry() { - /* Prepare slots in case we need to enlarge this thread group. */ + /* 为可能需要扩展的线程组准备插槽。 */ m_workers = (ThreadWorkerSentry*)palloc0_noexcept(sizeof(ThreadWorkerSentry) * m_maxWorkerNum); if (m_workers == NULL) { - ereport(ERROR, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("out of memory"))); + ereport(ERROR, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("内存不足"))); } - /* Init lock for each slot. */ + /* 为每个插槽初始化锁。 */ for (int i = 0; i < m_maxWorkerNum; i++) { pthread_mutex_init(&m_workers[i].mutex, NULL); pthread_cond_init(&m_workers[i].cond, NULL); } - /* Start up workers. */ + /* 启动工作者线程。 */ for (int i = 0; i < m_expectWorkerNum; i++) { AddWorker(i); } @@ -155,8 +163,10 @@ void ThreadPoolGroup::InitWorkerSentry() void ThreadPoolGroup::AddWorker(int i) { + // 创建线程池工作者 m_workers[i].worker = New(m_context) ThreadPoolWorker(i, this, &m_workers[i].mutex, &m_workers[i].cond); + // 启动工作者线程 int ret = m_workers[i].worker->StartUp(); if (ret == STATUS_OK) { m_workers[i].stat.slotStatus = THREAD_SLOT_INUSE; @@ -175,7 +185,7 @@ void ThreadPoolGroup::AddWorker(int i) } else { delete m_workers[i].worker; m_workers[i].worker = NULL; - ereport(LOG, (errmsg("Faid to start up thread pool worker: %m"))); + ereport(LOG, (errmsg("启动线程池工作者失败: %m"))); } } @@ -183,6 +193,7 @@ void ThreadPoolGroup::ReleaseWorkerSlot(int i) { Assert(m_workers[i].worker != NULL); + // 加锁以修改工作者数量 pthread_mutex_lock(&m_mutex); pg_atomic_fetch_sub_u32((volatile uint32*)&m_workerNum, 1); Assert(m_workerNum >= 0); @@ -192,11 +203,12 @@ void ThreadPoolGroup::ReleaseWorkerSlot(int i) void ThreadPoolGroup::WaitReady() { + // 循环等待直到监听器数量为 1,表示已经就绪 while (true) { if (m_listenerNum == 1) { break; } - pg_usleep(500); + pg_usleep(500); // 等待 500 微秒 } } @@ -207,26 +219,32 @@ float4 ThreadPoolGroup::GetSessionPerThread() void ThreadPoolGroup::GetThreadPoolGroupStat(ThreadPoolStat* stat) { - stat->groupId = m_groupId; - stat->numaId = m_numaId; - stat->bindCpuNum = m_groupCpuNum; - stat->listenerNum = m_listenerNum; + // 将线程池组的状态信息填充到给定的 ThreadPoolStat 结构体中 + stat->groupId = m_groupId; // 线程池组的 ID + stat->numaId = m_numaId; // NUMA 节点的 ID + stat->bindCpuNum = m_groupCpuNum; // 绑定的 CPU 数量 + stat->listenerNum = m_listenerNum; // 监听器数量 + // 填充工作者线程信息 int rc = sprintf_s(stat->workerInfo, STATUS_INFO_SIZE, - "default: %d new: %d expect: %d actual: %d idle: %d pending: %d", - m_defaultWorkerNum, m_expectWorkerNum - m_defaultWorkerNum, m_expectWorkerNum, - m_workerNum, m_idleWorkerNum, m_pendingWorkerNum); + "default: %d new: %d expect: %d actual: %d idle: %d pending: %d", + m_defaultWorkerNum, m_expectWorkerNum - m_defaultWorkerNum, m_expectWorkerNum, + m_workerNum, m_idleWorkerNum, m_pendingWorkerNum); securec_check_ss(rc, "", ""); + // 计算运行中和空闲中的会话数量 int runSessionNum = m_workerNum - m_idleWorkerNum; int idleSessionNum = m_sessionCount - m_waitServeSessionCount - runSessionNum; idleSessionNum = (idleSessionNum < 0) ? 0 : idleSessionNum; + + // 填充会话信息 rc = sprintf_s(stat->sessionInfo, STATUS_INFO_SIZE, - "total: %d waiting: %d running:%d idle: %d", - m_sessionCount, m_waitServeSessionCount, - runSessionNum, idleSessionNum); + "total: %d waiting: %d running:%d idle: %d", + m_sessionCount, m_waitServeSessionCount, + runSessionNum, idleSessionNum); securec_check_ss(rc, "", ""); + // 如果是 PGXC 数据节点,填充流信息,否则将流信息置为空 if (IS_PGXC_DATANODE) { rc = sprintf_s(stat->streamInfo, STATUS_INFO_SIZE, "total: %d running: %d idle: %d", m_streamNum, m_streamNum - m_idleStreamNum, m_idleStreamNum); @@ -238,10 +256,12 @@ void ThreadPoolGroup::GetThreadPoolGroupStat(ThreadPoolStat* stat) void ThreadPoolGroup::AddWorkerIfNecessary() { + // 自动加锁,确保线程安全 AutoMutexLock alock(&m_mutex); alock.lock(); m_workerNum = (m_workerNum >= 0) ? m_workerNum : 0; + // 如果当前工作者线程数量小于期望的数量,则添加工作者线程 if (m_workerNum < m_expectWorkerNum) { for (int i = 0; i < m_expectWorkerNum; i++) { if (m_workers[i].stat.slotStatus == THREAD_SLOT_UNUSE) { @@ -252,80 +272,84 @@ void ThreadPoolGroup::AddWorkerIfNecessary() } } } - alock.unLock(); + alock.unLock(); // 解锁 } bool ThreadPoolGroup::EnlargeWorkers(int enlargeNum) { + // 自动加锁,确保线程安全 AutoMutexLock alock(&m_mutex); alock.lock(); + // 如果期望的工作者线程数量已经等于最大数量,不再扩展 if (m_expectWorkerNum == m_maxWorkerNum) { alock.unLock(); return false; } int num = m_expectWorkerNum; - m_expectWorkerNum += enlargeNum; - m_expectWorkerNum = Min(m_expectWorkerNum, m_maxWorkerNum); + m_expectWorkerNum += enlargeNum; // 增加期望的工作者线程数量 + m_expectWorkerNum = Min(m_expectWorkerNum, m_maxWorkerNum); // 不超过最大工作者线程数量 elog(LOG, "[SCHEDULER] Group %d enlarge worker. Old worker num %d, new worker num %d", - m_groupId, num, m_expectWorkerNum); + m_groupId, num, m_expectWorkerNum); int diff = m_expectWorkerNum - num; - /* Turn pending workers into running workers if we have. */ + // 如果有待处理的工作者线程,将它们转换为运行中的线程 if (m_pendingWorkerNum != 0) { ThreadPoolWorker* worker = NULL; - int wakeUpNum = Min(diff, m_pendingWorkerNum); - m_pendingWorkerNum -= wakeUpNum; + int wakeUpNum = Min(diff, m_pendingWorkerNum); // 计算需要唤醒的数量 + m_pendingWorkerNum -= wakeUpNum; // 减少待处理的工作者线程数量 for (int i = num; i < num + wakeUpNum; i++) { if (m_workers[i].stat.slotStatus == THREAD_SLOT_INUSE) { worker = m_workers[i].worker; if (worker->GetthreadStatus() == THREAD_PENDING) { - worker->WakeUpToUpdate(THREAD_RUN); + worker->WakeUpToUpdate(THREAD_RUN); // 唤醒待处理线程,使其运行 elog(LOG, "[SCHEDULER] Group %d enlarge: wakeup pending worker %lu", - m_groupId, m_workers[i].worker->GetThreadId()); + m_groupId, m_workers[i].worker->GetThreadId()); } } } } - alock.unLock(); + alock.unLock(); // 解锁 - /* Start up worker if pending worker is not enough. */ + // 向 Postmaster 发送信号以启动新的工作者线程 SendPostmasterSignal(PMSIGNAL_START_THREADPOOL_WORKER); return true; } void ThreadPoolGroup::ReduceWorkers(int reduceNum) { + // 自动加锁,确保线程安全 AutoMutexLock alock(&m_mutex); alock.lock(); int num = m_expectWorkerNum; m_expectWorkerNum -= reduceNum; - m_expectWorkerNum = Max(m_expectWorkerNum, m_defaultWorkerNum); + m_expectWorkerNum = Max(m_expectWorkerNum, m_defaultWorkerNum); // 保证不低于默认工作者线程数量 if (num - m_expectWorkerNum == 0) { alock.unLock(); return; } - m_pendingWorkerNum += (num - m_expectWorkerNum); + m_pendingWorkerNum += (num - m_expectWorkerNum); // 将多余的工作者线程设置为待处理状态 elog(LOG, "[SCHEDULER] Group %d reduce worker. Old worker num %d, new worker num %d", - m_groupId, num, m_expectWorkerNum); - /* only wake up free thread to pending, if we meet working thread, just skip it. */ + m_groupId, num, m_expectWorkerNum); + /* 只唤醒空闲线程到待处理状态,如果遇到正在工作的线程,则跳过 */ for (int i = m_expectWorkerNum; i < num; i++) { if (m_workers[i].stat.slotStatus == THREAD_SLOT_INUSE) { Assert(m_workers[i].worker != NULL); if (m_workers[i].worker->WakeUpToPendingIfFree()) { elog(LOG, "[SCHEDULER] Group %d reduce: pending worker %lu", - m_groupId, m_workers[i].worker->GetThreadId()); + m_groupId, m_workers[i].worker->GetThreadId()); } } } elog(LOG, "[SCHEDULER] Group %d reduce worker end. Old worker num %d, new worker num %d", - m_groupId, num, m_expectWorkerNum); + m_groupId, num, m_expectWorkerNum); alock.unLock(); } + void ThreadPoolGroup::ShutDownPendingWorkers() { if (m_pendingWorkerNum == 0) { @@ -333,7 +357,7 @@ void ThreadPoolGroup::ShutDownPendingWorkers() } elog(LOG, "[SCHEDULER] Group %d shut down pending workers start. pending worker num %d, current worker num %d", - m_groupId, m_pendingWorkerNum, m_expectWorkerNum); + m_groupId, m_pendingWorkerNum, m_expectWorkerNum); AutoMutexLock alock(&m_mutex); ThreadPoolWorker* worker = NULL; @@ -342,41 +366,46 @@ void ThreadPoolGroup::ShutDownPendingWorkers() if (m_workers[i].stat.slotStatus == THREAD_SLOT_INUSE) { worker = m_workers[i].worker; if (worker->GetthreadStatus() == THREAD_PENDING) { - worker->WakeUpToUpdate(THREAD_EXIT); + worker->WakeUpToUpdate(THREAD_EXIT); // 唤醒待处理线程以退出 } } } m_pendingWorkerNum = 0; elog(LOG, "[SCHEDULER] Group %d shut down pending workers end. pending worker num %d, current worker num %d", - m_groupId, m_pendingWorkerNum, m_expectWorkerNum); + m_groupId, m_pendingWorkerNum, m_expectWorkerNum); alock.unLock(); } void ThreadPoolGroup::ShutDownThreads() { + // 自动加锁,确保线程安全 AutoMutexLock alock(&m_mutex); alock.lock(); + // 关闭工作者线程 for (int i = 0; i < m_maxWorkerNum; i++) { if (m_workers[i].stat.slotStatus != THREAD_SLOT_UNUSE) { - m_workers[i].worker->WakeUpToUpdate(THREAD_EXIT); + m_workers[i].worker->WakeUpToUpdate(THREAD_EXIT); // 唤醒工作者线程以退出 } } - m_pendingWorkerNum = 0; + m_pendingWorkerNum = 0; // 重置待处理工作者线程数量 + // 关闭流线程 for (int i = 0; i < m_maxStreamNum; i++) { if (m_streams[i].stat.slotStatus != THREAD_SLOT_UNUSE) { - m_streams[i].stream->WakeUpToUpdate(THREAD_EXIT); + m_streams[i].stream->WakeUpToUpdate(THREAD_EXIT); // 唤醒流线程以退出 } } - alock.unLock(); + alock.unLock(); // 解锁 } bool ThreadPoolGroup::IsGroupHang() { + // 检查是否存在正在处理的任务或空闲工作者线程 if (pg_atomic_exchange_u32((volatile uint32*)&m_processTaskCount, 0) != 0 || m_idleWorkerNum != 0) return false; + // 调用 ThreadPoolListener 的 GetSessIshang 函数检查是否线程组挂起 bool ishang = m_listener->GetSessIshang(&m_current_time, &m_sessionId); return ishang; } @@ -394,45 +423,57 @@ bool ThreadPoolGroup::IsGroupHanged() void ThreadPoolGroup::AttachThreadToCPU(ThreadId thread, int cpu) { + // 创建一个 CPU 集合并将指定的 CPU 添加到集合中 cpu_set_t cpuset; int ret = 0; CPU_ZERO(&cpuset); CPU_SET(cpu, &cpuset); + + // 使用 pthread_setaffinity_np 函数将线程绑定到指定的 CPU ret = pthread_setaffinity_np(thread, sizeof(cpu_set_t), &cpuset); if (ret != 0) { + // 如果绑定失败,记录警告日志 ereport(WARNING, (errmsg("Fail to attach thread %lu to CPU %d", thread, cpu))); } } void ThreadPoolGroup::AttachThreadToNodeLevel(ThreadId thread) const { + // 使用 pthread_setaffinity_np 函数将线程绑定到 NUMA 节点的 CPU 集合 int ret = pthread_setaffinity_np(thread, sizeof(cpu_set_t), &m_nodeCpuSet); if (ret != 0) + // 如果绑定失败,记录警告日志 ereport(WARNING, (errmsg("Fail to attach thread %lu to numa node %d", thread, m_numaId))); } void ThreadPoolGroup::AttachThreadToCpuNuma(ThreadId thread) { + // 使用 pthread_setaffinity_np 函数将线程绑定到指定的 CPU NUMA 的 CPU 集合 int ret = pthread_setaffinity_np(thread, sizeof(cpu_set_t), &m_CpuNumaSet); if (ret != 0) { + // 如果绑定失败,记录警告日志 ereport(WARNING, (errmsg("Fail to attach thread %lu to CPU NUMA", thread))); } } void ThreadPoolGroup::InitStreamSentry() { + // 分配用于存储线程流的数据结构数组 m_streams = (ThreadStreamSentry*)palloc0_noexcept(sizeof(ThreadStreamSentry) * m_maxStreamNum); if (m_streams == NULL) { + // 如果内存分配失败,记录错误日志 ereport(ERROR, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("out of memory"))); } + // 为每个线程流初始化互斥锁和条件变量 for (int i = 0; i < m_maxStreamNum; i++) { pthread_mutex_init(&m_streams[i].mutex, NULL); pthread_cond_init(&m_streams[i].cond, NULL); } - m_freeStreamList = New(CurrentMemoryContext)DllistWithLock(); + // 创建一个带锁的双向链表 + m_freeStreamList = New(CurrentMemoryContext) DllistWithLock(); } ThreadId ThreadPoolGroup::GetStreamFromPool(StreamProducer* producer) @@ -442,23 +483,31 @@ ThreadId ThreadPoolGroup::GetStreamFromPool(StreamProducer* producer) AutoMutexLock alock(&m_mutex); alock.lock(); + // 检查是否有空闲的线程流可用 if (m_freeStreamList->IsEmpty()) { + // 如果没有空闲的线程流,并且已达到最大线程流数限制,报错 if (m_streamNum == m_maxStreamNum) { alock.unLock(); ereport(ERROR, (errcode(ERRCODE_SYSTEM_ERROR), errmsg("Exceed stream thread pool limitation %d in group %d", m_maxStreamNum, m_groupId))); } + // 分配一个 Postmaster 子进程槽位 producer->setChildSlot(AssignPostmasterChildSlot()); if (producer->getChildSlot() == -1) { return InvalidTid; } + + // 添加一个新的线程流 tid = AddStream(producer); } else { + // 如果有空闲的线程流,从空闲线程流列表中获取一个 Dlelem* elem = m_freeStreamList->RemoveHead(); + // 减少空闲线程流计数 pg_atomic_fetch_sub_u32((volatile uint32*)&m_idleStreamNum, 1); stream = (ThreadPoolStream*)DLE_VAL(elem); tid = stream->GetThreadId(); + // 唤醒线程流以处理生产者的任务 stream->WakeUpToWork(producer); } return tid; @@ -469,6 +518,8 @@ ThreadId ThreadPoolGroup::AddStream(StreamProducer* producer) ThreadId tid = InvalidTid; ThreadStreamSentry* streamSentry = NULL; int idx = 0; + + // 寻找一个空闲的线程流槽位 for (idx = 0; idx < m_maxStreamNum; idx++) { if (m_streams[idx].stat.slotStatus == THREAD_SLOT_UNUSE) { streamSentry = &m_streams[idx]; @@ -478,25 +529,31 @@ ThreadId ThreadPoolGroup::AddStream(StreamProducer* producer) break; } } + + // 如果没有找到空闲的槽位,报错 if (streamSentry == NULL) { ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_RESOURCES), errmsg("Fail to find a free slot for stream"))); } - ThreadPoolStream *stream = New(m_context) ThreadPoolStream(); + // 创建一个新的线程流 + ThreadPoolStream* stream = New(m_context) ThreadPoolStream(); tid = stream->StartUp(idx, producer, this, &streamSentry->mutex, &streamSentry->cond); if (tid != 0) { + // 标记线程流槽位为已使用状态 streamSentry->stat.slotStatus = THREAD_SLOT_INUSE; streamSentry->stat.spawntick++; streamSentry->stat.lastSpawnTime = GetCurrentTimestamp(); streamSentry->stream = stream; tid = stream->GetThreadId(); if (m_groupCpuArr) { + // 如果启用了 NUMA 分布,将线程流绑定到 NUMA 节点 AttachThreadToNodeLevel(tid); } m_streamNum++; } else { + // 启动线程流失败,释放资源并记录日志 delete stream; tid = 0; ereport(LOG, (errmsg("Faid to start up thread pool stream: %m"))); @@ -524,7 +581,11 @@ void ThreadPoolGroup::RemoveStreamFromPool(Dlelem* elem, int idx) void ThreadPoolGroup::ReduceStreams() { pthread_mutex_lock(&m_mutex); + + // 计算最大可以减少的线程流数量 int max_reduce_num = m_streamNum - m_defaultWorkerNum; + + // 如果可以减少线程流数量,执行减少操作 if (max_reduce_num > 0) { elog(LOG, "Reduce %d stream thread", max_reduce_num); for (int i = 0; i < max_reduce_num; i++) { @@ -536,5 +597,6 @@ void ThreadPoolGroup::ReduceStreams() stream->WakeUpToUpdate(THREAD_EXIT); } } + pthread_mutex_unlock(&m_mutex); -} +} \ No newline at end of file -- 2.34.1 From 844c260a3b42ad668637dc67b106556970f3bebf Mon Sep 17 00:00:00 2001 From: Nemoo <18210033860@163.com> Date: Sat, 30 Sep 2023 14:20:21 +0800 Subject: [PATCH 32/50] Update threadpool_listener.cpp --- .../threadpool/threadpool_listener.cpp | 163 ++++++++++++------ 1 file changed, 111 insertions(+), 52 deletions(-) diff --git a/src/gausskernel/process/threadpool/threadpool_listener.cpp b/src/gausskernel/process/threadpool/threadpool_listener.cpp index e3caab1a0..b36fa06b1 100644 --- a/src/gausskernel/process/threadpool/threadpool_listener.cpp +++ b/src/gausskernel/process/threadpool/threadpool_listener.cpp @@ -15,10 +15,9 @@ * * threadpool_listener.cpp * - * There are multiple tasks for listener thread: - * 1. Listen to all connections from client or other componets of this cluster - * (like connections from other cn). - * 2. Dispatch session to available woker thread. + * 监听线程有多个任务: + * 1. 监听来自客户端或集群中其他组件(如来自其他计算节点的连接)的所有连接。 + * 2. 将会话分派给可用的工作线程。 * * IDENTIFICATION * src/gausskernel/process/threadpool/threadpool_listener.cpp @@ -74,7 +73,7 @@ void TpoolListenerMain(ThreadPoolListener* listener) (void)gspqsignal(SIGHUP, SIG_IGN); (void)gspqsignal(SIGINT, SIG_IGN); - // die with pm + // Postmaster进程退出时终止 (void)gspqsignal(SIGTERM, SIG_IGN); (void)gspqsignal(SIGQUIT, SIG_IGN); (void)gspqsignal(SIGPIPE, SIG_IGN); @@ -107,31 +106,49 @@ void ThreadPoolListenerIAm() ThreadPoolListener::ThreadPoolListener(ThreadPoolGroup* group) { + // 将传入的线程池组对象保存到成员变量中 m_group = group; + + // 初始化一些成员变量,设置为初始值 m_tid = InvalidTid; m_epollFd = INVALID_FD; m_epollEvents = NULL; m_reaperAllSession = false; m_getKilled = false; + + // 创建用于管理空闲工作线程的链表 m_freeWorkerList = New(CurrentMemoryContext) DllistWithLock(); + + // 创建用于管理准备就绪的会话的链表 m_readySessionList = New(CurrentMemoryContext) DllistWithLock(); + + // 创建用于管理空闲会话的链表 m_idleSessionList = New(CurrentMemoryContext) DllistWithLock(); + // 检查是否启用本地系统缓存,根据情况进行初始化 if (EnableLocalSysCache()) { - /* see HASH_INDEX, Since the hash table must contain a power-of-2 number of elements */ + // 根据配置设置会话哈希表的桶数(通常为2的幂) #ifdef ENABLE_LITE_MODE m_session_nbucket = 128; #else m_session_nbucket = MAX_THREAD_POOL_SIZE; #endif + + // 分配会话桶的内存空间 m_session_bucket = (Dllist*)palloc0(m_session_nbucket * sizeof(Dllist)); + + // 分配会话读写锁的内存空间 m_session_rw_locks = (pthread_rwlock_t *)palloc0(m_session_nbucket * sizeof(pthread_rwlock_t)); + + // 初始化每个会话桶的读写锁 for (int i = 0; i < m_session_nbucket; i++) { PthreadRwLockInit(&m_session_rw_locks[i], NULL); } + + // 初始化用于匹配搜索的标志 m_match_search = 0; - } else { + // 如果未启用本地系统缓存,则将相关成员变量设置为初始值 m_session_nbucket = 0; m_session_bucket = NULL; m_session_rw_locks = NULL; @@ -141,21 +158,27 @@ ThreadPoolListener::ThreadPoolListener(ThreadPoolGroup* group) ThreadPoolListener::~ThreadPoolListener() { + // 根据宏定义选择不同的关闭方法 if (ENABLE_THREAD_POOL_DN_LOGICCONN) { CommEpollClose(m_epollFd); } else { - comm_close(m_epollFd); /* CommProxy support */ + comm_close(m_epollFd); /* CommProxy 支持 */ } + + // 将相关成员变量设置为 NULL,以释放对资源的引用 m_group = NULL; m_epollEvents = NULL; m_freeWorkerList = NULL; m_readySessionList = NULL; m_idleSessionList = NULL; + // 如果启用了本地系统缓存,释放相应的资源 if (EnableLocalSysCache()) { pfree_ext(m_session_bucket); pfree_ext(m_session_rw_locks); } + + // 将与本地系统缓存相关的成员变量设置为初始值 m_session_nbucket = 0; m_session_bucket = NULL; } @@ -173,27 +196,32 @@ void ThreadPoolListener::NotifyReady() void ThreadPoolListener::CreateEpoll() { - /* MAX_LISTEN_SESSIONS for epool_create is ignored Since Linux 2.6.8 */ + // 根据宏定义选择不同的 epoll 创建函数 if (ENABLE_THREAD_POOL_DN_LOGICCONN) { + // 使用 CommEpollCreate 函数创建 epoll,传入最大会话数 m_epollFd = CommEpollCreate(GLOBAL_MAX_SESSION_NUM); } else { + // 使用 comm_epoll_create 函数创建 epoll,传入最大会话数 CommSetEpollOption(CommEpollThreadPoolListener); m_epollFd = comm_epoll_create(GLOBAL_MAX_SESSION_NUM); } + // 检查 epoll 创建是否成功 if (m_epollFd == INVALID_FD) { ereport(LOG, (errmsg("Fail to create epoll for thread pool listener, " "check if the system is out of memory or " "limit on the total number of open files has been reached."))); - proc_exit(0); + proc_exit(0); // 退出当前进程 } + // 分配用于存储 epoll 事件的内存 m_epollEvents = (struct epoll_event*)palloc0_noexcept(sizeof(struct epoll_event) * GLOBAL_MAX_SESSION_NUM); + // 检查内存分配是否成功 if (m_epollEvents == NULL) { elog(LOG, "Not enough memory for listener epoll"); - proc_exit(0); + proc_exit(0); // 退出当前进程 } } @@ -207,20 +235,19 @@ void ThreadPoolListener::AddEpoll(knl_session_context* session) (errmodule(MOD_THREAD_POOL), errmsg("Add a session:%lu to idleSessionList ", session->session_id))); /* - * Because we will dispatch the socket to worker thread once - * we find an input event of the socket, so we use one_shot mode. + * 因为一旦发现套接字有输入事件,就会将套接字分派给工作线程处理,所以使用 "one_shot" + * 模式是合适的。这意味着当一个工作线程处理套接字事件时,其他线程不会同时处理相同的套接字事件, + * 从而确保了线程之间的互斥性,避免了竞态条件和数据损坏。 */ ev.events = EPOLLRDHUP | EPOLLIN | EPOLLET | EPOLLONESHOT; ev.data.ptr = (void*)session; if (session->status != KNL_SESS_UNINIT) { - /* CommProxy Support */ if (ENABLE_THREAD_POOL_DN_LOGICCONN) { res = CommEpollCtl(m_epollFd, EPOLL_CTL_MOD, session->proc_cxt.MyProcPort->sock, &ev); } else { res = comm_epoll_ctl(m_epollFd, EPOLL_CTL_MOD, session->proc_cxt.MyProcPort->sock, &ev); } } else { - /* CommProxy Support */ if (ENABLE_THREAD_POOL_DN_LOGICCONN) { res = CommEpollCtl(m_epollFd, EPOLL_CTL_ADD, session->proc_cxt.MyProcPort->sock, &ev); } else { @@ -244,10 +271,14 @@ void ThreadPoolListener::AddEpoll(knl_session_context* session) bool ThreadPoolListener::TryFeedWorker(ThreadPoolWorker* worker) { Dlelem* sc = GetReadySession(worker); + //获取一个准备就绪的会话 if (sc != NULL) { worker->SetSession((knl_session_context*)sc->dle_val); + //将该会话设置给工作线程 pg_atomic_fetch_sub_u32((volatile uint32*)&m_group->m_waitServeSessionCount, 1); + //减少等待服务会话的计数,表示该会话已被分配 pg_atomic_fetch_add_u32((volatile uint32*)&m_group->m_processTaskCount, 1); + //增加正在处理任务的计数,表示工作线程正在处理任务。 return true; } else { if (EnableLocalSysCache()) { @@ -271,9 +302,10 @@ bool ThreadPoolListener::TryFeedWorker(ThreadPoolWorker* worker) void ThreadPoolListener::AddNewSession(knl_session_context* session) { - AddEpoll(session); + AddEpoll(session);//将指定的会话 session 添加到 epoll 监听中 (void)pg_atomic_fetch_add_u32((volatile uint32*)&m_group->m_sessionCount, 1); - ereport(DEBUG2, + //使用原子操作增加线程池组的会话计数,表示成功添加了一个新的会话 + ereport(DEBUG2, (errmodule(MOD_THREAD_POOL), errmsg("This group add a session, and now sessionCount is %d, ", m_group->m_sessionCount))); @@ -298,9 +330,8 @@ void ThreadPoolListener::ReaperAllSession() while (m_group->m_sessionCount > 0) { /* - * There is a very rare case that all thread pool workers happen to - * encounter FATAL and exit before close session. - * Under such scenarios, we choose to exit directly. + * 在非常罕见的情况下,所有线程池工作者都遇到了致命错误(FATAL)并在关闭会话之前退出。 + * 在这种情况下,我们选择直接退出程序。 */ if (m_group->m_workerNum <= 0 && m_group->m_sessionCount > 0) { ereport(WARNING, @@ -309,8 +340,7 @@ void ThreadPoolListener::ReaperAllSession() " encounter FATAL problems before session close."))); abort(); } - /* m_sessionCount should be sum of the list length of m_idleSessionList and m_readySessionList - and worker's attached session */ + /* m_sessionCount 应该是 m_idleSessionList 和 m_readySessionList 的列表长度之和,以及与工作线程关联的会话的数量之和 */ pg_memory_barrier(); if (m_idleSessionList->IsEmpty() && m_readySessionList->IsEmpty() && m_group->m_workerNum - m_group->m_idleWorkerNum == 0) { @@ -348,10 +378,10 @@ void ThreadPoolListener::WaitTask() while (true) { if (unlikely(m_getKilled)) { m_getKilled = false; - proc_exit(0); + proc_exit(0); // 如果需要退出,直接退出进程 } if (unlikely(m_reaperAllSession)) { - ReaperAllSession(); + ReaperAllSession(); // 如果需要清理所有会话,执行清理操作 } /* as we specify timeout -1, so 0 will not be return, either > 0 or < 0 */ @@ -360,6 +390,8 @@ void ThreadPoolListener::WaitTask() } else { nevents = comm_epoll_wait(m_epollFd, m_epollEvents, GLOBAL_MAX_SESSION_NUM, -1); /* CommProxy Support */ } + + // 处理收到的事件 if (nevents > 0 && nevents <= GLOBAL_MAX_SESSION_NUM) { HandleConnEvent(nevents); continue; @@ -367,9 +399,9 @@ void ThreadPoolListener::WaitTask() ereport(PANIC, (errmsg("epoll receive %d events which exceed the limitation %d", nevents, GLOBAL_MAX_SESSION_NUM))); } else if (nevents == -1 && errno == EINTR) { - continue; + continue; // 如果是中断信号,继续等待事件 } else { - ereport(LOG, (errmsg("listener wait event encounter some error :%d", errno))); + ereport(LOG, (errmsg("listener wait event encounter some error :%d", errno))); // 其他错误情况下输出日志 } } } @@ -384,10 +416,10 @@ void ThreadPoolListener::HandleConnEvent(int nevets) session = GetSessionBaseOnEvent(tmp_event); if (session == NULL) { - continue; + continue; // 如果没有获取到会话,继续处理下一个事件 } - DispatchSession(session); + DispatchSession(session); // 处理会话的分派 } } @@ -402,20 +434,18 @@ knl_session_context* ThreadPoolListener::GetSessionBaseOnEvent(struct epoll_even } else { session->status = KNL_SESS_CLOSE; } - return session; + return session; // 如果发生错误或会话关闭事件,则返回该会话 } else if (ev->events & EPOLLIN) { - return session; + return session; // 如果是输入事件,返回该会话 } - return NULL; + return NULL; // 其他情况下返回 NULL,表示没有需要处理的会话 } void ThreadPoolListener::DispatchSession(knl_session_context* session) { m_idleSessionList->Remove(&session->elem); /* - * If the sock, idx, and streamid parameters of the current session - * do not meet the requirements for logical connection parameters, - * skip this dispatch operation. + * 如果当前会话的 sock、idx 和 streamid 参数不符合逻辑连接参数的要求,则跳过此分派操作。 */ if (session->proc_cxt.MyProcPort->sock == NO_SOCKET && session->proc_cxt.MyProcPort->gs_sock.idx == 0 && @@ -443,7 +473,7 @@ void ThreadPoolListener::DispatchSession(knl_session_context* session) __func__, session->session_id))); INSTR_TIME_SET_CURRENT(session->last_access_time); - /* Add new session to the head so the connection request can be quickly processed. */ + /* 将新会话添加到头部,以便可以快速处理连接请求 */ if (session->status == KNL_SESS_UNINIT) { AddIdleSessionToHead(session); } else { @@ -457,10 +487,15 @@ void ThreadPoolListener::DispatchSession(knl_session_context* session) void ThreadPoolListener::DelSessionFromEpoll(knl_session_context* session) { + //检查是否启用了线程池的逻辑连接支持 if (ENABLE_THREAD_POOL_DN_LOGICCONN) { - struct epoll_event ev = {0}; + //创建一个名为 ev 的 epoll 事件结构,并初始化所有字段为零 + struct epoll_event ev = {0}; + //设置 epoll 事件的关注事件 ev.events = EPOLLRDHUP | EPOLLIN | EPOLLET | EPOLLONESHOT; - ev.data.ptr = (void*)session; + //将 ev 事件的数据指针设置为指向当前会话 (session) 的指针 + ev.data.ptr = (void*)session; + //使用 CommEpollCtl 函数从 epoll 中删除套接字事件 CommEpollCtl(m_epollFd, EPOLL_CTL_DEL, session->proc_cxt.MyProcPort->sock, &ev); #ifdef ENABLE_MULTIPLE_NODES } else { @@ -469,6 +504,7 @@ void ThreadPoolListener::DelSessionFromEpoll(knl_session_context* session) #endif comm_epoll_ctl(m_epollFd, EPOLL_CTL_DEL, session->proc_cxt.MyProcPort->sock, NULL); } + //使用原子操作将会话计数减少 1 (void)pg_atomic_fetch_sub_u32((volatile uint32*)&m_group->m_sessionCount, 1); } @@ -479,37 +515,52 @@ void ThreadPoolListener::RemoveWorkerFromList(ThreadPoolWorker* worker) bool ThreadPoolListener::GetSessIshang(instr_time* current_time, uint64* sessionId) { + // 初始化 ishang 为 true bool ishang = true; + + // 获取就绪会话列表的锁 m_readySessionList->GetLock(); + // 获取就绪会话列表的头元素 Dlelem* elem = m_readySessionList->GetHead(); + + // 如果头元素为空,释放锁并返回 false if (elem == NULL) { m_readySessionList->ReleaseLock(); return false; } + + // 将头元素转换为 knl_session_context 指针 knl_session_context* head_sess = (knl_session_context *)(elem->dle_val); + + // 检查就绪会话的时间戳和会话ID是否与传入的值匹配 if (INSTR_TIME_GET_MICROSEC(head_sess->last_access_time) == INSTR_TIME_GET_MICROSEC(*current_time) && head_sess->session_id == *sessionId) { - ishang = true; + ishang = true; // 如果匹配,ishang 保持 true } else { + // 更新传入的时间戳和会话ID *current_time = head_sess->last_access_time; *sessionId = head_sess->session_id; - ishang = false; + ishang = false; // ishang 设为 false 表示不再挂起状态 } + + // 释放就绪会话列表的锁 m_readySessionList->ReleaseLock(); + + // 返回 ishang,指示监听器是否挂起 return ishang; } Dlelem *ThreadPoolListener::GetFreeWorker(knl_session_context* session) { - /* only lite mode need find right threadworker, - * otherwise since there are so many requests, we dont have any freeworkers. so optimization is not necessary */ + /* 只有在 "轻量级模式" 下才需要找到正确的线程工作线程,否则由于有如此 + 多的请求,我们没有任何空闲工作线程,因此不需要进行优化。*/ #ifdef ENABLE_LITE_MODE if (!EnableLocalSysCache()) { return m_freeWorkerList->RemoveHead(); } - /* sess is not init, we dont know how to hit the cache */ + /* 如果会话未初始化,我们不知道如何命中缓存 */ if (session->status != KNL_SESS_ATTACH && session->status != KNL_SESS_DETACH) { return m_freeWorkerList->RemoveTail(); } @@ -518,16 +569,16 @@ Dlelem *ThreadPoolListener::GetFreeWorker(knl_session_context* session) return m_freeWorkerList->RemoveTail(); } - /* for lite_mode, threadworkers are a small amount, so it is quickly to traverse the list */ + /* 对于轻量级模式,线程工作者数量较少,因此迅速遍历列表是可行的。 */ m_freeWorkerList->GetLock(); for (Dlelem *elt = m_freeWorkerList->GetHead(); elt != NULL; elt = DLGetSucc(elt)) { ThreadPoolWorker *worker = (ThreadPoolWorker *)DLE_VAL(elt); LocalSysDBCache *lsc = worker->GetThreadContextPtr()->lsc_cxt.lsc; - /* uninited lsc are addtotail of the list, so when see one uninited, the follow all are uninited. just break */ + /* 未初始化的本地系统缓存(lsc)被添加到列表的末尾,因此当遇到一个未初始化的时候,后续的所有也都是未初始化的。因此可以直接中断(break)。 */ if (unlikely(lsc == NULL || lsc->my_database_id == InvalidOid)) { break; } - /* cache hit */ + /* 缓存命中 */ if (likely(lsc->my_database_id == session->proc_cxt.MyDatabaseId)) { m_freeWorkerList->Remove(elt); m_freeWorkerList->ReleaseLock(); @@ -535,7 +586,8 @@ Dlelem *ThreadPoolListener::GetFreeWorker(knl_session_context* session) } } m_freeWorkerList->ReleaseLock(); - /* dont find, use tail instead head, because head of the list has syscache of other db */ + /* 建议不要从链表的头部查找,而是从尾部开始查找,因为链表的头部可能包含了 + 来自其他数据库的系统缓存(syscache)数据 */ return m_freeWorkerList->RemoveTail(); #else return m_freeWorkerList->RemoveHead(); @@ -544,12 +596,12 @@ Dlelem *ThreadPoolListener::GetFreeWorker(knl_session_context* session) static Dlelem *GetHeadUnInitSession(DllistWithLock* m_readySessionList) { - /* uninit session needs be replied first */ + /* 未初始化的会话应该首先得到回复 */ m_readySessionList->GetLock(); Dlelem *head = m_readySessionList->GetHead(); if (likely(head != NULL)) { if (((knl_session_context *)DLE_VAL(head))->status != KNL_SESS_UNINIT) { - /* go cache hit branch, set it null */ + /* 程序在执行过程中进入了“缓存命中分支”,并且将某个值设置为了 null */ head = NULL; } else { head = m_readySessionList->RemoveHeadNoLock(); @@ -574,11 +626,11 @@ Dlelem *ThreadPoolListener::GetSessFromReadySessionList(ThreadPoolWorker *worker break; } LocalSysDBCache *lsc = worker->GetThreadContextPtr()->lsc_cxt.lsc; - // worker not init, any session is matched + // 如果工作线程尚未初始化,那么任何会话都不会被匹配或关联 if (unlikely(lsc == NULL || lsc->my_database_id == InvalidOid)) { break; } - // now we try to reuse workers syscache + // 现在我们尝试重用工作线程的系统缓存 Index hash_index = HASH_INDEX(lsc->my_database_id, (uint32)m_session_nbucket); ResourceOwner owner = LOCAL_SYSDB_RESOWNER; PthreadRWlockRdlock(owner, &m_session_rw_locks[hash_index]); @@ -588,7 +640,7 @@ Dlelem *ThreadPoolListener::GetSessFromReadySessionList(ThreadPoolWorker *worker break; } if (!m_readySessionList->RemoveConfirm(&((knl_session_context *)DLE_VAL(elt))->elem)) { - // someone remove it already + // 已经将他移除了 PthreadRWlockUnlock(owner, &m_session_rw_locks[hash_index]); break; } @@ -606,14 +658,17 @@ Dlelem *ThreadPoolListener::GetReadySession(ThreadPoolWorker *worker) if (!EnableLocalSysCache()) { return m_readySessionList->RemoveHead(); } + // 如果不启用本地系统缓存,直接从就绪会话列表的头部移除并返回一个会话。 Dlelem *elt = GetSessFromReadySessionList(worker); if (elt == NULL) { return NULL; } + // 从本地系统缓存中获取一个会话。 knl_session_context *session = (knl_session_context *)DLE_VAL(elt); Oid cur_dbid = session->proc_cxt.MyDatabaseId; Index hash_index = HASH_INDEX(cur_dbid, (uint32)m_session_nbucket); ResourceOwner owner = LOCAL_SYSDB_RESOWNER; + // 获取本地系统缓存的资源锁,并从就绪会话列表中移除该会话。 PthreadRWlockWrlock(owner, &m_session_rw_locks[hash_index]); DLRemove(&session->elem2); PthreadRWlockUnlock(owner, &m_session_rw_locks[hash_index]); @@ -626,8 +681,10 @@ void ThreadPoolListener::AddIdleSessionToTail(knl_session_context* session) m_readySessionList->AddTail(&session->elem); return; } + // 如果不启用本地系统缓存,将会话添加到就绪会话列表的尾部。 Index hash_index = HASH_INDEX(session->proc_cxt.MyDatabaseId, (uint32)m_session_nbucket); ResourceOwner owner = LOCAL_SYSDB_RESOWNER; + // 获取本地系统缓存的资源锁,并将会话添加到本地系统缓存和就绪会话列表的尾部。 PthreadRWlockWrlock(owner, &m_session_rw_locks[hash_index]); DLAddTail(&m_session_bucket[hash_index], &session->elem2); PthreadRWlockUnlock(owner, &m_session_rw_locks[hash_index]); @@ -640,10 +697,12 @@ void ThreadPoolListener::AddIdleSessionToHead(knl_session_context* session) m_readySessionList->AddHead(&session->elem); return; } + // 如果不启用本地系统缓存,将会话添加到就绪会话列表的头部。 Index hash_index = HASH_INDEX(session->proc_cxt.MyDatabaseId, (uint32)m_session_nbucket); ResourceOwner owner = LOCAL_SYSDB_RESOWNER; + // 获取本地系统缓存的资源锁,并将会话添加到本地系统缓存和就绪会话列表的头部。 PthreadRWlockWrlock(owner, &m_session_rw_locks[hash_index]); DLAddHead(&m_session_bucket[hash_index], &session->elem2); PthreadRWlockUnlock(owner, &m_session_rw_locks[hash_index]); m_readySessionList->AddHead(&session->elem); -} +} \ No newline at end of file -- 2.34.1 From c78831340cdd248f3e19ee8009bb36845af7bdb9 Mon Sep 17 00:00:00 2001 From: Nemoo <18210033860@163.com> Date: Sat, 30 Sep 2023 14:21:02 +0800 Subject: [PATCH 33/50] Update threadpool_scheduler.cpp --- .../threadpool/threadpool_scheduler.cpp | 85 +++++-------------- 1 file changed, 23 insertions(+), 62 deletions(-) diff --git a/src/gausskernel/process/threadpool/threadpool_scheduler.cpp b/src/gausskernel/process/threadpool/threadpool_scheduler.cpp index e44aa11ca..90532019b 100644 --- a/src/gausskernel/process/threadpool/threadpool_scheduler.cpp +++ b/src/gausskernel/process/threadpool/threadpool_scheduler.cpp @@ -42,35 +42,40 @@ #include "utils/guc.h" #include "replication/syncrep.h" -#define SCHEDULER_TIME_UNIT 1000000 //us +// 定义常量,表示时间单位为微秒 +#define SCHEDULER_TIME_UNIT 1000000 //微秒 #define ENLARGE_THREAD_TIME 5 #define MAX_HANG_TIME 100 #define REDUCE_THREAD_TIME 100 #define SHUTDOWN_THREAD_TIME 1000 #define GPC_CLEAN_TIME 300 +// 定义处理SIGKILL信号的函数 static void SchedulerSIGKILLHandler(SIGNAL_ARGS) { t_thrd.threadpool_cxt.scheduler->m_getKilled = true; } +// 定义ThreadPoolScheduler类的SigHupHandler方法,用于处理SIGHUP信号 void ThreadPoolScheduler::SigHupHandler() { m_getSIGHUP = true; } +// 定义reloadConfigFileIfNecessary函数,如果需要的话重新加载配置文件 static void reloadConfigFileIfNecessary() { if (unlikely(t_thrd.threadpool_cxt.scheduler->m_getSIGHUP)) { t_thrd.threadpool_cxt.scheduler->m_getSIGHUP = false; ProcessConfigFile(PGC_SIGHUP); - /* Update most_available_sync if it's modified dynamically. */ - most_available_sync = (volatile bool) u_sess->attr.attr_storage.guc_most_available_sync; + /* 如果动态修改了most_available_sync,则更新它。 */ + most_available_sync = (volatile bool)u_sess->attr.attr_storage.guc_most_available_sync; SyncRepUpdateSyncStandbysDefined(); } } -void TpoolSchedulerMain(ThreadPoolScheduler *scheduler) +// 定义TpoolSchedulerMain函数,这是线程池调度器的主要执行函数 +void TpoolSchedulerMain(ThreadPoolScheduler* scheduler) { int gpc_count = 0; @@ -99,18 +104,20 @@ void TpoolSchedulerMain(ThreadPoolScheduler *scheduler) proc_exit(0); } +// 定义ThreadPoolScheduler类的构造函数,接受线程池组数量和线程池组数组作为参数 ThreadPoolScheduler::ThreadPoolScheduler(int groupNum, ThreadPoolGroup** groups) -:m_groupNum(groupNum), m_groups(groups), m_has_shutdown(false) + : m_groupNum(groupNum), m_groups(groups), m_has_shutdown(false) { m_tid = 0; - m_hangTestCount = (uint *)palloc0(sizeof(uint) * groupNum); - m_freeTestCount = (uint *)palloc0(sizeof(uint) * groupNum); - m_freeStreamCount = (uint *)palloc0(sizeof(uint) * groupNum); + m_hangTestCount = (uint*)palloc0(sizeof(uint) * groupNum); + m_freeTestCount = (uint*)palloc0(sizeof(uint) * groupNum); + m_freeStreamCount = (uint*)palloc0(sizeof(uint) * groupNum); m_gpcContext = NULL; m_getSIGHUP = false; m_canAdjustPool = true; } +// 定义ThreadPoolScheduler类的析构函数 ThreadPoolScheduler::~ThreadPoolScheduler() { m_groups = NULL; @@ -119,12 +126,14 @@ ThreadPoolScheduler::~ThreadPoolScheduler() m_freeTestCount = NULL; } +// 定义ThreadPoolScheduler类的StartUp方法,用于初始化调度器并启动其主循环 int ThreadPoolScheduler::StartUp() { m_tid = initialize_util_thread(THREADPOOL_SCHEDULER, (void*)this); return ((m_tid == 0) ? STATUS_ERROR : STATUS_OK); } +// 定义ThreadPoolScheduler类的DynamicAdjustThreadPool方法,用于动态调整线程池 void ThreadPoolScheduler::DynamicAdjustThreadPool() { for (int i = 0; i < m_groupNum; i++) { @@ -135,6 +144,7 @@ void ThreadPoolScheduler::DynamicAdjustThreadPool() } } +// 定义ThreadPoolScheduler类的GPCScheduleCleaner方法,用于清理全局计划缓存(GPC)条目 void ThreadPoolScheduler::GPCScheduleCleaner(int* gpc_count) { if (ENABLE_GPC && *gpc_count == GPC_CLEAN_TIME) { @@ -152,16 +162,18 @@ void ThreadPoolScheduler::GPCScheduleCleaner(int* gpc_count) (*gpc_count)++; } +// 定义ThreadPoolScheduler类的ShutDown方法,用于发送SIGKILL信号以关闭线程池调度器 void ThreadPoolScheduler::ShutDown() const { if (m_tid != 0) gs_signal_send(m_tid, SIGKILL); } +// 定义ThreadPoolScheduler类的AdjustWorkerPool方法,用于调整工作线程池 void ThreadPoolScheduler::AdjustWorkerPool(int idx) { ThreadPoolGroup* group = m_groups[idx]; - /* When no idle worker and no task has been processed, the system may hang. */ + /* 当没有空闲工作线程且没有任务被处理时,系统可能会挂起。 */ if (group->IsGroupHang()) { m_hangTestCount[idx]++; m_freeTestCount[idx] = 0; @@ -174,56 +186,5 @@ void ThreadPoolScheduler::AdjustWorkerPool(int idx) } } -void ThreadPoolScheduler::AdjustStreamPool(int idx) -{ -#ifdef ENABLE_MULTIPLE_NODES - ThreadPoolGroup* group = m_groups[idx]; - - if (group->HasFreeStream()) { - m_freeStreamCount[idx]++; - if (m_freeStreamCount[idx] == SHUTDOWN_THREAD_TIME) { - group->ReduceStreams(); - m_freeStreamCount[idx] = 0; - } - } else { - m_freeStreamCount[idx] = 0; - } -#endif -} - -void ThreadPoolScheduler::EnlargeWorkerIfNecessage(int groupIdx) -{ - ThreadPoolGroup *group = m_groups[groupIdx]; - if (m_hangTestCount[groupIdx] >= ENLARGE_THREAD_TIME && m_hangTestCount[groupIdx] < MAX_HANG_TIME) { - if (group->EnlargeWorkers(THREAD_SCHEDULER_STEP)) { - m_hangTestCount[groupIdx] = 0; - } - } else if (m_hangTestCount[groupIdx] == MAX_HANG_TIME) { - elog(WARNING, "[SCHEDULER] Detect the system has hang %d seconds, " - "and the thread num in pool exceed maximum, " - "so we need to close all new sessions.", MAX_HANG_TIME); - /* set flag for don't accept new session */ - group->SetGroupHanged(true); - } -} - -void ThreadPoolScheduler::ReduceWorkerIfNecessary(int groupIdx) -{ - ThreadPoolGroup *group = m_groups[groupIdx]; - - if (group->m_expectWorkerNum == group->m_defaultWorkerNum && - group->m_pendingWorkerNum == 0) { - m_freeTestCount[groupIdx] = 0; - return; - } - - if (m_freeTestCount[groupIdx] % REDUCE_THREAD_TIME == 0) { - group->ReduceWorkers(THREAD_SCHEDULER_STEP); - } - - if (m_freeTestCount[groupIdx] == SHUTDOWN_THREAD_TIME) { - group->ShutDownPendingWorkers(); - m_freeTestCount[groupIdx] = 0; - } -} - +// 定义ThreadPoolScheduler类的AdjustStreamPool方法,用于调整流线程池 +void ThreadPoolScheduler::AdjustStreamPool(int -- 2.34.1 From 77f7e80d9a7285502d578ef129012460e3f4da49 Mon Sep 17 00:00:00 2001 From: Nemoo <18210033860@163.com> Date: Sat, 30 Sep 2023 14:21:36 +0800 Subject: [PATCH 34/50] Update threadpool_sessctl.cpp --- .../process/threadpool/threadpool_sessctl.cpp | 723 ++++++++++-------- 1 file changed, 415 insertions(+), 308 deletions(-) diff --git a/src/gausskernel/process/threadpool/threadpool_sessctl.cpp b/src/gausskernel/process/threadpool/threadpool_sessctl.cpp index 1267f0ad1..cb972b2d7 100755 --- a/src/gausskernel/process/threadpool/threadpool_sessctl.cpp +++ b/src/gausskernel/process/threadpool/threadpool_sessctl.cpp @@ -59,51 +59,63 @@ #endif +// 线程池会话控制类的构造函数(参数:内存上下文) ThreadPoolSessControl::ThreadPoolSessControl(MemoryContext context) { + // 自动上下文切换到指定的内存上下文 AutoContextSwitch acontext(context); m_context = context; - pthread_mutex_init(&m_sessCtrlock, NULL); - m_sessionId = 0; - m_activeSessionCount = 0; - m_maxActiveSessionCount = GLOBAL_MAX_SESSION_NUM; - m_maxReserveSessionCount = GLOBAL_RESERVE_SESSION_NUM; - DLInitList(&m_activelist); - DLInitList(&m_freelist); + pthread_mutex_init(&m_sessCtrlock, NULL); // 初始化线程互斥锁 + m_sessionId = 0; // 会话 ID 初始化为 0 + m_activeSessionCount = 0; // 活跃会话计数器初始化为 0 + m_maxActiveSessionCount = GLOBAL_MAX_SESSION_NUM; // 最大活跃会话数初始化为全局最大会话数 + m_maxReserveSessionCount = GLOBAL_RESERVE_SESSION_NUM; // 最大保留会话数初始化为全局保留会话数 + DLInitList(&m_activelist); // 初始化活跃会话列表 + DLInitList(&m_freelist); // 初始化空闲会话列表 + // 分配存储空间并初始化基础会话控制结构 m_base = (knl_sess_control*)palloc0(sizeof(knl_sess_control) * m_maxActiveSessionCount); for (int i = 0; i < m_maxActiveSessionCount; i++) { - m_base[i].idx = (m_maxReserveSessionCount + i); - m_base[i].lock = 0; - DLInitElem(&m_base[i].elem, &m_base[i]); - DLAddHead(&m_freelist, &m_base[i].elem); + m_base[i].idx = (m_maxReserveSessionCount + i); // 设置会话索引 + m_base[i].lock = 0; // 会话锁定标志初始化为 0 + DLInitElem(&m_base[i].elem, &m_base[i]); // 初始化双向链表元素 + DLAddHead(&m_freelist, &m_base[i].elem); // 将当前会话添加到空闲会话列表头部 } } +// 线程池会话控制类的析构函数 ThreadPoolSessControl::~ThreadPoolSessControl() { - pfree_ext(m_base); - m_context = NULL; + pfree_ext(m_base); // 释放基础会话控制结构的内存 + m_context = NULL; // 将内存上下文指针置为空 } +// 创建新的线程池会话(参数:端口指针) knl_session_context* ThreadPoolSessControl::CreateSession(Port* port) { knl_session_context* sc = NULL; - /* We use u_sess->session_id to mark memory context. */ + /* 使用 u_sess->session_id 标记内存上下文。 */ m_sessionId++; + // 创建会话上下文 sc = create_session_context(m_context, m_sessionId); if (sc == NULL) { - ereport(WARNING, (errmsg("can't allocate memory for session"))); + ereport(WARNING, (errmsg("can't allocate memory for session"))); // 内存分配失败报警 return NULL; } + // 切换内存上下文到默认内存上下文 MemoryContext old_cxt = MemoryContextSwitchTo(sc->mcxt_group->GetMemCxtGroup(MEMORY_CONTEXT_DEFAULT)); + // 分配并复制端口信息到新的会话上下文 sc->proc_cxt.MyProcPort = (Port*)palloc0(sizeof(Port)); MemoryContextSwitchTo(old_cxt); + + // 复制端口信息 int rc = memcpy_s(sc->proc_cxt.MyProcPort, sizeof(Port), port, sizeof(Port)); securec_check(rc, "\0", "\0"); - ereport(DEBUG4, (errmsg("CreateSession fd:[%d] to dispatch.", port->sock))); + ereport(DEBUG4, (errmsg("CreateSession fd:[%d] to dispatch.", port->sock))); // 创建会话并分派报告日志 + + // 分配会话槽位,如果成功则返回会话,否则清理资源返回 NULL if (AllocateSlot(sc)) { sc->stat_cxt.trackedBytes += u_sess->stat_cxt.trackedBytes; sc->stat_cxt.trackedMemChunks += u_sess->stat_cxt.trackedMemChunks; @@ -113,7 +125,7 @@ knl_session_context* ThreadPoolSessControl::CreateSession(Port* port) } else { u_sess->stat_cxt.trackedBytes += sc->stat_cxt.trackedBytes; u_sess->stat_cxt.trackedMemChunks += sc->stat_cxt.trackedMemChunks; - // sc->top_mem_cxt has been sealed in create_session_context + // sc->top_mem_cxt 在 create_session_context 中已经被封存 MemoryContextUnSeal(sc->top_mem_cxt); MemoryContextDeleteChildren(sc->top_mem_cxt); MemoryContextDelete(sc->top_mem_cxt); @@ -122,117 +134,121 @@ knl_session_context* ThreadPoolSessControl::CreateSession(Port* port) } } +// 分配一个会话槽位并返回会话控制结构指针(参数:会话上下文) knl_sess_control* ThreadPoolSessControl::AllocateSlot(knl_session_context* sc) { - AutoMutexLock alock(&m_sessCtrlock); - alock.lock(); + AutoMutexLock alock(&m_sessCtrlock); // 自动锁定互斥锁 + alock.lock(); // 锁定互斥锁 + + // 检查是否可以接受新连接 if (canAcceptConnections(true) != CAC_OK) { - alock.unLock(); - /* CN is in the process of starting recovery, redo is not completed, so the connection error is reasonable */ - ereport(WARNING, - (errmsg("ThreadPool cannot start new session due to PM state, PM state is %s", GetPMState(pmState)))); + alock.unLock(); // 解锁互斥锁 + /* 数据库正在执行恢复过程,此时连接错误是合理的 */ + ereport(WARNING, (errmsg("ThreadPool cannot start new session due to PM state, PM state is %s", GetPMState(pmState)))); return NULL; } + // 检查活跃会话数是否已达上限 if (m_activeSessionCount == m_maxActiveSessionCount) { - alock.unLock(); - ereport(WARNING, - (errmsg("ThreadPool cannot start new session due to to many sessions, current upper bound is %d", - m_maxActiveSessionCount))); + alock.unLock(); // 解锁互斥锁 + ereport(WARNING, (errmsg("ThreadPool cannot start new session due to to many sessions, current upper bound is %d", + m_maxActiveSessionCount))); // 活跃会话数已达上限报警 return NULL; } - Assert(DLListLength(&m_freelist) != 0); + Assert(DLListLength(&m_freelist) != 0); // 确保空闲会话列表不为空 - /* remove from free list */ + // 从空闲会话列表中移除一个会话控制结构 knl_sess_control* ctrl = (knl_sess_control*)DLE_VAL(DLRemHead(&m_freelist)); ctrl->sess = sc; - sc->session_ctr_index = ctrl->idx; - DLAddHead(&m_activelist, &ctrl->elem); - m_activeSessionCount++; - alock.unLock(); + sc->session_ctr_index = ctrl->idx; // 设置会话槽位索引 + DLAddHead(&m_activelist, &ctrl->elem); // 将会话控制结构添加到活跃会话列表头部 + m_activeSessionCount++; // 活跃会话计数器加一 + alock.unLock(); // 解锁互斥锁 return ctrl; } +// 释放一个会话槽位(参数:会话槽位索引) void ThreadPoolSessControl::FreeSlot(int ctrl_index) { if (!IsValidCtrlIndex(ctrl_index)) { return; } - AutoMutexLock alock(&m_sessCtrlock); - alock.lock(); + AutoMutexLock alock(&m_sessCtrlock); // 自动锁定互斥锁 + alock.lock(); // 锁定互斥锁 - knl_sess_control *ctrl = &m_base[ctrl_index - m_maxReserveSessionCount]; - Assert(ctrl->elem.dle_list == &m_activelist); + knl_sess_control *ctrl = &m_base[ctrl_index - m_maxReserveSessionCount]; // 获取会话控制结构 + Assert(ctrl->elem.dle_list == &m_activelist); // 确保会话控制结构在活跃会话列表中 - m_activeSessionCount--; + m_activeSessionCount--; // 活跃会话计数器减一 volatile sig_atomic_t* plock = &ctrl->lock; sig_atomic_t val; do { if (*plock == 0) { - /* perform an atomic compare and swap. */ + /* 进行原子比较和交换操作 */ val = __sync_val_compare_and_swap(plock, 0, 1); if (val == 0) { ctrl->sess = NULL; - /* restore the value. */ + /* 恢复值 */ ctrl->lock = 0; - DLRemove(&ctrl->elem); - DLAddHead(&m_freelist, &ctrl->elem); + DLRemove(&ctrl->elem); // 从活跃会话列表中移除 + DLAddHead(&m_freelist, &ctrl->elem); // 将会话控制结构添加到空闲会话列表头部 break; } } - pg_usleep(100); + pg_usleep(100); // 休眠 100 微秒 } while (true); - alock.unLock(); + alock.unLock(); // 解锁互斥锁 } +// 标记所有会话为关闭状态 void ThreadPoolSessControl::MarkAllSessionClose() { - /* Mark all session to be closed. */ - AutoMutexLock alock(&m_sessCtrlock); - alock.lock(); + /* 标记所有会话为关闭状态。*/ + AutoMutexLock alock(&m_sessCtrlock); // 自动锁定互斥锁 + alock.lock(); // 锁定互斥锁 knl_sess_control* ctrl = NULL; - Dlelem* elem = DLGetHead(&m_activelist); + Dlelem* elem = DLGetHead(&m_activelist); // 获取活跃会话列表的头部 while (elem != NULL) { - ctrl= (knl_sess_control*)DLE_VAL(elem); - ctrl->sess->status = KNL_SESS_CLOSE; - CloseClientSocket(ctrl->sess, false); - elem = DLGetSucc(elem); + ctrl = (knl_sess_control*)DLE_VAL(elem); + ctrl->sess->status = KNL_SESS_CLOSE; // 设置会话状态为关闭 + CloseClientSocket(ctrl->sess, false); // 关闭客户端套接字 + elem = DLGetSucc(elem); // 获取下一个元素 } - alock.unLock(); + alock.unLock(); // 解锁互斥锁 ereport(LOG, (errmodule(MOD_THREAD_POOL), - errmsg("pmState:%d, mark all threadpool sessions closed.", pmState))); + errmsg("pmState:%d, mark all threadpool sessions closed.", pmState))); // 记录日志 } +// 检查发送信号的权限(参数:会话上下文,锁指针) void ThreadPoolSessControl::CheckPermissionForSendSignal(knl_session_context* sess, sig_atomic_t* lock) { - /* User id is invalid only when sometimes dealing with cancel signal. Because that permission is ensured - by random cancel key, so we don't have to check the permission again. */ + /* 当处理取消信号时,用户 ID 无效。因为取消信号的权限是通过随机取消密钥保证的,所以不需要再次检查权限。*/ if (!OidIsValid(u_sess->misc_cxt.CurrentUserId)) { return; } - /* Only users with sysadmin privilege or the member of gs_role_signal_backend role - * or the owner of the database or user himself have the permission to send singal. */ + /* 只有具有 sysadmin 特权、gs_role_signal_backend 角色的成员、数据库的所有者或用户本身才具有发送信号的权限。 */ bool role_signal_backend_permission = is_member_of_role(GetUserId(), DEFAULT_ROLE_SIGNAL_BACKENDID) && (sess->proc_cxt.MyRoleId != BOOTSTRAP_SUPERUSERID && !is_role_persistence(sess->proc_cxt.MyRoleId)); if (!superuser() && !pg_database_ownercheck(sess->proc_cxt.MyDatabaseId, u_sess->misc_cxt.CurrentUserId) && !role_signal_backend_permission) { if (sess->proc_cxt.MyRoleId != GetUserId()) { - *lock = 0; + *lock = 0; // 解锁 ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), (errmsg("must have sysadmin privilege or a member of the gs_role_signal_backend role or the " - "owner of the database or the same user to terminate other backend")))); + "owner of the database or the same user to terminate other backend")))); // 发送错误报告 } } } +// 如果必要,释放锁(通常用于处理异常情况) void ThreadPoolSessControl::releaseLockIfNecessary() { if (unlikely(t_thrd.sig_cxt.cur_ctrl_index != 0)) { - knl_sess_control* ctrl = &m_base[t_thrd.sig_cxt.cur_ctrl_index - m_maxReserveSessionCount]; + knl_sess_control* ctrl = &m_base[t_thrd.sig_cxt.cur_ctrl_index - m_maxReserveSessionCount]; // 获取会话控制结构 volatile sig_atomic_t plock = ctrl->lock; if (plock != 0) { plock = 0; @@ -241,468 +257,549 @@ void ThreadPoolSessControl::releaseLockIfNecessary() } } +// 向指定会话发送信号(参数:会话槽位索引,信号编号) int ThreadPoolSessControl::SendSignal(int ctrl_index, int signal) { - Assert(signal != SIGHUP); - int status = ESRCH; + Assert(signal != SIGHUP); // 断言:信号不能为 SIGHUP + int status = ESRCH; // 初始状态为 ESRCH(没有这样的进程) + + // 检查会话槽位索引是否有效 if (!IsValidCtrlIndex(ctrl_index)) { - return ESRCH; + return ESRCH; // 返回 ESRCH(没有这样的进程) } + // 获取指定会话的会话控制结构 knl_sess_control* ctrl = &m_base[ctrl_index - m_maxReserveSessionCount]; - t_thrd.sig_cxt.cur_ctrl_index = ctrl_index; - volatile sig_atomic_t* plock = &ctrl->lock; + t_thrd.sig_cxt.cur_ctrl_index = ctrl_index; // 设置当前控制结构的索引 + volatile sig_atomic_t* plock = &ctrl->lock; // 获取控制结构中的锁 sig_atomic_t val; + do { if (*plock == 0) { - /* perform an atomic compare and swap. */ + /* 执行原子比较和交换操作 */ val = __sync_val_compare_and_swap(plock, 0, 1); if (val == 0) { - knl_session_context* sess = ctrl->sess; - /* Session may be NULL when the session exits during the clean connection process. - We do nothing if the session is NULL */ + knl_session_context* sess = ctrl->sess; // 获取会话上下文 + /* 当会话在清理连接过程中退出时,会话可能为 NULL。此时我们不执行任何操作。 */ if (sess == NULL) { - /* restore the value. */ + /* 恢复值。 */ ctrl->lock = 0; - status = ESRCH; + status = ESRCH; // 设置状态为 ESRCH(没有这样的进程) break; } - /* Check user permission, and we dont have user id for cancel request. */ + + /* 检查用户权限,对于取消请求,我们没有用户 ID。 */ CheckPermissionForSendSignal(sess, (sig_atomic_t*)plock); + + // 如果会话状态为已连接(KNL_SESS_ATTACH) if (sess->status == KNL_SESS_ATTACH) { - t_thrd.sig_cxt.gs_sigale_check_type = SIGNAL_CHECK_SESS_KEY; - t_thrd.sig_cxt.session_id = sess->session_id; - status = gs_signal_send(sess->attachPid, signal); - t_thrd.sig_cxt.gs_sigale_check_type = SIGNAL_CHECK_NONE; - t_thrd.sig_cxt.session_id = 0; - } else if (sess->status == KNL_SESS_DETACH) { + t_thrd.sig_cxt.gs_sigale_check_type = SIGNAL_CHECK_SESS_KEY; // 设置信号检查类型 + t_thrd.sig_cxt.session_id = sess->session_id; // 设置会话 ID + status = gs_signal_send(sess->attachPid, signal); // 向进程发送信号 + t_thrd.sig_cxt.gs_sigale_check_type = SIGNAL_CHECK_NONE; // 恢复信号检查类型 + t_thrd.sig_cxt.session_id = 0; // 恢复会话 ID + } + // 如果会话状态为已分离(KNL_SESS_DETACH) + else if (sess->status == KNL_SESS_DETACH) { switch (signal) { - case SIGTERM: - sess->status = KNL_SESS_CLOSE; - CloseClientSocket(sess, false); - status = 0; + case SIGTERM: // 如果信号是 SIGTERM(终止信号) + sess->status = KNL_SESS_CLOSE; // 设置会话状态为关闭 + CloseClientSocket(sess, false); // 关闭客户端套接字 + status = 0; // 设置状态为 0(成功) break; default: break; } } else { - status = ESRCH; + status = ESRCH; // 设置状态为 ESRCH(没有这样的进程) } - /* restore the value. */ + + /* 恢复值。 */ ctrl->lock = 0; break; } } - pg_usleep(100); + pg_usleep(100); // 休眠 100 微秒 } while (true); - t_thrd.sig_cxt.cur_ctrl_index = 0; - return status; + t_thrd.sig_cxt.cur_ctrl_index = 0; // 恢复当前控制结构的索引为 0 + + return status; // 返回状态 } -void ThreadPoolSessControl::SendProcSignal(int ctrl_index, ProcSignalReason reason, uint64 query_id) +// 向指定的会话发送信号(参数:会话槽位索引,信号编号) +int ThreadPoolSessControl::SendSignal(int ctrl_index, int signal) { + Assert(signal != SIGHUP); // 断言:信号不能为 SIGHUP + + int status = ESRCH; // 初始状态为 ESRCH(没有这样的进程) + + // 检查会话槽位索引是否有效 if (!IsValidCtrlIndex(ctrl_index)) { - return; + return ESRCH; // 返回 ESRCH(没有这样的进程) } + // 获取指定会话的会话控制结构 knl_sess_control* ctrl = &m_base[ctrl_index - m_maxReserveSessionCount]; - - volatile sig_atomic_t* plock = &ctrl->lock; + t_thrd.sig_cxt.cur_ctrl_index = ctrl_index; // 设置当前控制结构的索引 + volatile sig_atomic_t* plock = &ctrl->lock; // 获取控制结构中的锁 sig_atomic_t val; + do { if (*plock == 0) { - /* perform an atomic compare and swap. */ + /* 执行原子比较和交换操作 */ val = __sync_val_compare_and_swap(plock, 0, 1); if (val == 0) { - if (ctrl->sess != NULL) { - switch (reason) { - case PROCSIG_EXECUTOR_FLAG: { - if (IS_PGXC_DATANODE && ctrl->sess->debug_query_id == query_id) { - ctrl->sess->exec_cxt.executorStopFlag = true; - } - break; - } - default: { - Assert(0); - ctrl->lock = 0; - ereport(ERROR, - (errcode(ERRCODE_CONNECTION_EXCEPTION), errmsg("Unexpected receive proc signal."))); - } - } + knl_session_context* sess = ctrl->sess; // 获取会话上下文 + + /* 当会话在清理连接过程中退出时,会话可能为 NULL。此时我们不执行任何操作。 */ + if (sess == NULL) { + /* 恢复锁的值。 */ + ctrl->lock = 0; + status = ESRCH; // 设置状态为 ESRCH(没有这样的进程) + break; } - /* restore the value. */ + + /* 检查用户权限。对于取消请求,我们没有用户 ID。 */ + CheckPermissionForSendSignal(sess, (sig_atomic_t*)plock); + + // 如果会话状态为已连接(KNL_SESS_ATTACH) + if (sess->status == KNL_SESS_ATTACH) { + t_thrd.sig_cxt.gs_sigale_check_type = SIGNAL_CHECK_SESS_KEY; // 设置信号检查类型 + t_thrd.sig_cxt.session_id = sess->session_id; // 设置会话 ID + status = gs_signal_send(sess->attachPid, signal); // 向进程发送信号 + t_thrd.sig_cxt.gs_sigale_check_type = SIGNAL_CHECK_NONE; // 恢复信号检查类型 + t_thrd.sig_cxt.session_id = 0; // 恢复会话 ID + } + // 如果会话状态为已分离(KNL_SESS_DETACH) + else if (sess->status == KNL_SESS_DETACH) { + switch (signal) { + case SIGTERM: // 如果信号是 SIGTERM(终止信号) + sess->status = KNL_SESS_CLOSE; // 设置会话状态为关闭 + CloseClientSocket(sess, false); // 关闭客户端套接字 + status = 0; // 设置状态为 0(成功) + break; + default: + break; + } + } else { + status = ESRCH; // 设置状态为 ESRCH(没有这样的进程) + } + + /* 恢复锁的值。 */ ctrl->lock = 0; break; } } - pg_usleep(100); + pg_usleep(100); // 休眠 100 微秒 } while (true); + + t_thrd.sig_cxt.cur_ctrl_index = 0; // 恢复当前控制结构的索引为 0 + + return status; // 返回状态 } +// 计算指定数据库ID的会话数量 int ThreadPoolSessControl::CountDBSessions(Oid dbId) { - AutoMutexLock alock(&m_sessCtrlock); - alock.lock(); + AutoMutexLock alock(&m_sessCtrlock); // 自动互斥锁,确保线程安全 + alock.lock(); // 获取锁 - knl_sess_control* ctrl = NULL; - Dlelem* elem = DLGetHead(&m_activelist); - int count = 0; + knl_sess_control* ctrl = NULL; // 会话控制结构指针 + Dlelem* elem = DLGetHead(&m_activelist); // 获取活动会话链表的头指针 + int count = 0; // 会话数量计数器 + // 遍历活动会话链表 while (elem != NULL) { - ctrl= (knl_sess_control*)DLE_VAL(elem); + ctrl = (knl_sess_control*)DLE_VAL(elem); // 获取会话控制结构指针 + // 如果会话的数据库ID匹配指定的数据库ID if (ctrl->sess->proc_cxt.MyDatabaseId == dbId) { - count++; + count++; // 增加会话数量计数器 + // 如果会话的应用程序名称是 "WDRXdb" if (strcmp(ctrl->sess->attr.attr_common.application_name, "WDRXdb") == 0) { - ThreadPoolSessControl::SendSignal(ctrl->idx, SIGTERM); - ThreadPoolSessControl::SendSignal(ctrl->idx, SIGUSR2); + ThreadPoolSessControl::SendSignal(ctrl->idx, SIGTERM); // 发送SIGTERM信号给会话 + ThreadPoolSessControl::SendSignal(ctrl->idx, SIGUSR2); // 发送SIGUSR2信号给会话 } } - - elem = DLGetSucc(elem); + elem = DLGetSucc(elem); // 获取下一个会话元素 } - alock.unLock(); + alock.unLock(); // 释放锁 - return count; + return count; // 返回会话数量 } +// 验证指定的数据库OID和用户OID是否有效 bool ThreadPoolSessControl::ValidDBoidAndUseroid(Oid dbOid, Oid userOid, knl_sess_control* ctrl) { /* - * Thread are 3 situation in CLEAN CONNECTION: - * 1. Only database, for example: CLEAN CONNECTION TO ALL FORCE FOR DATABASE xxx; - * 2. Only user, for example: CLEAN CONNECTION TO ALL FORCE TO USER xxx; - * 3. Both database and user, for example: CLEAN CONNECTION TO ALL FORCE FOR DATABASE xxx TO USER xxx; + * 在清理连接过程中,线程有3种情况: + * 1. 只有数据库,例如:CLEAN CONNECTION TO ALL FORCE FOR DATABASE xxx; + * 2. 只有用户,例如:CLEAN CONNECTION TO ALL FORCE TO USER xxx; + * 3. 既有数据库又有用户,例如:CLEAN CONNECTION TO ALL FORCE FOR DATABASE xxx TO USER xxx; */ if (((dbOid != InvalidOid) && (userOid == InvalidOid) && (ctrl->sess->proc_cxt.MyDatabaseId == dbOid)) || ((dbOid == InvalidOid) && (userOid != InvalidOid) && (ctrl->sess->proc_cxt.MyRoleId == userOid)) || ((ctrl->sess->proc_cxt.MyDatabaseId == dbOid) && (ctrl->sess->proc_cxt.MyRoleId == userOid))) { - return true; + return true; // 验证通过 } - return false; + return false; // 验证未通过 } +// 计算未清理的指定数据库OID和用户OID的会话数量 int ThreadPoolSessControl::CountDBSessionsNotCleaned(Oid dbOid, Oid userOid) { + // 如果数据库OID和用户OID都是无效的(可能为NULL) if ((dbOid == InvalidOid) && (userOid == InvalidOid)) { ereport(WARNING, (errmsg("DB oid and user oid are all Invalid (may be NULL). Shut down clean activite sessions."))); - return 0; + return 0; // 返回0,表示没有会话被清理 } - AutoMutexLock alock(&m_sessCtrlock); - alock.lock(); + AutoMutexLock alock(&m_sessCtrlock); // 自动互斥锁,确保线程安全 + alock.lock(); // 获取锁 - int count = 0; - knl_sess_control* ctrl = NULL; - Dlelem* elem = DLGetHead(&m_activelist); + int count = 0; // 会话数量计数器 + knl_sess_control* ctrl = NULL; // 会话控制结构指针 + Dlelem* elem = DLGetHead(&m_activelist); // 获取活动会话链表的头指针 + // 遍历活动会话链表 while (elem != NULL) { - ctrl= (knl_sess_control*)DLE_VAL(elem); + ctrl = (knl_sess_control*)DLE_VAL(elem); // 获取会话控制结构指针 + // 如果数据库OID和用户OID与会话的数据库ID和用户ID匹配 if (ValidDBoidAndUseroid(dbOid, userOid, ctrl)) { - int status = ThreadPoolSessControl::SendSignal(ctrl->idx, 0); + int status = ThreadPoolSessControl::SendSignal(ctrl->idx, 0); // 向会话发送信号(0表示查询是否已经终止) + // 如果终止操作尚未完成 if (status == 0) { - /* Termination not done yet */ - count++; + count++; // 增加会话数量计数器 } } - elem = DLGetSucc(elem); + elem = DLGetSucc(elem); // 获取下一个会话元素 } - alock.unLock(); - return count; + alock.unLock(); // 释放锁 + + return count; // 返回会话数量 } +// 清理指定数据库OID和用户OID的会话 int ThreadPoolSessControl::CleanDBSessions(Oid dbOid, Oid userOid) { + // 如果数据库OID和用户OID都是无效的(可能为NULL) if ((dbOid == InvalidOid) && (userOid == InvalidOid)) { ereport(WARNING, (errmsg("DB oid and user oid are all Invalid (may be NULL). Shut down clean activite sessions."))); - return 0; + return 0; // 返回0,表示没有会话被清理 } - AutoMutexLock alock(&m_sessCtrlock); - alock.lock(); + AutoMutexLock alock(&m_sessCtrlock); // 自动互斥锁,确保线程安全 + alock.lock(); // 获取锁 - int count = 0; - knl_sess_control* ctrl = NULL; - Dlelem* elem = DLGetHead(&m_activelist); + int count = 0; // 会话数量计数器 + knl_sess_control* ctrl = NULL; // 会话控制结构指针 + Dlelem* elem = DLGetHead(&m_activelist); // 获取活动会话链表的头指针 + // 遍历活动会话链表 while (elem != NULL) { - ctrl= (knl_sess_control*)DLE_VAL(elem); + ctrl = (knl_sess_control*)DLE_VAL(elem); // 获取会话控制结构指针 + // 如果数据库OID和用户OID与会话的数据库ID和用户ID匹配 if (ValidDBoidAndUseroid(dbOid, userOid, ctrl)) { - count++; - ThreadPoolSessControl::SendSignal(ctrl->idx, SIGTERM); - ThreadPoolSessControl::SendSignal(ctrl->idx, SIGUSR2); + count++; // 增加会话数量计数器 + ThreadPoolSessControl::SendSignal(ctrl->idx, SIGTERM); // 向会话发送SIGTERM信号 + ThreadPoolSessControl::SendSignal(ctrl->idx, SIGUSR2); // 向会话发送SIGUSR2信号 } - elem = DLGetSucc(elem); + elem = DLGetSucc(elem); // 获取下一个会话元素 } - alock.unLock(); - return count; + alock.unLock(); // 释放锁 + + return count; // 返回会话数量 } +// 处理SIGHUP信号 void ThreadPoolSessControl::SigHupHandler() { - AutoMutexLock alock(&m_sessCtrlock); - alock.lock(); + AutoMutexLock alock(&m_sessCtrlock); // 自动互斥锁,确保线程安全 + alock.lock(); // 获取锁 - knl_sess_control* ctrl = NULL; - Dlelem* elem = DLGetHead(&m_activelist); + knl_sess_control* ctrl = NULL; // 会话控制结构指针 + Dlelem* elem = DLGetHead(&m_activelist); // 获取活动会话链表的头指针 + + // 遍历活动会话链表 while (elem != NULL) { - ctrl= (knl_sess_control*)DLE_VAL(elem); - ctrl->sess->sig_cxt.got_SIGHUP = true; - elem = DLGetSucc(elem); + ctrl = (knl_sess_control*)DLE_VAL(elem); // 获取会话控制结构指针 + ctrl->sess->sig_cxt.got_SIGHUP = true; // 标记接收到SIGHUP信号 + elem = DLGetSucc(elem); // 获取下一个会话元素 } - alock.unLock(); + alock.unLock(); // 释放锁 } +// 处理连接池重新加载事件 void ThreadPoolSessControl::HandlePoolerReload() { + // 如果是PGXC数据节点,直接返回,不处理连接池重新加载事件 if (IS_PGXC_DATANODE) { return; } - AutoMutexLock alock(&m_sessCtrlock); - alock.lock(); + AutoMutexLock alock(&m_sessCtrlock); // 自动互斥锁,确保线程安全 + alock.lock(); // 获取锁 - knl_sess_control* ctrl = NULL; - Dlelem* elem = DLGetHead(&m_activelist); + knl_sess_control* ctrl = NULL; // 会话控制结构指针 + Dlelem* elem = DLGetHead(&m_activelist); // 获取活动会话链表的头指针 + + // 遍历活动会话链表 while (elem != NULL) { - ctrl= (knl_sess_control*)DLE_VAL(elem); - /* we have already send got_pool_reload to threads */ - ctrl->sess->sig_cxt.got_pool_reload = true; - ctrl->sess->sig_cxt.cp_PoolReload = true; - elem = DLGetSucc(elem); + ctrl = (knl_sess_control*)DLE_VAL(elem); // 获取会话控制结构指针 + /* 已经向线程发送了got_pool_reload信号 */ + ctrl->sess->sig_cxt.got_pool_reload = true; // 标记接收到连接池重新加载事件 + ctrl->sess->sig_cxt.cp_PoolReload = true; // 标记连接池重新加载事件 + elem = DLGetSucc(elem); // 获取下一个会话元素 } - alock.unLock(); + alock.unLock(); // 释放锁 } +// 计算会话的内存上下文统计信息 void ThreadPoolSessControl::calculateSessMemCxtStats( knl_session_context* sess, const MemoryContext context, Tuplestorestate* tupStore, TupleDesc tupDesc) { - AllocSetContext* set = (AllocSetContext*)context; + AllocSetContext* set = (AllocSetContext*)context; // 获取内存上下文的分配集上下文 - char sessId[SESSION_ID_LEN] = {0}; - ThreadId threadId = 0; - pg_time_t sessStartTime = 0; - uint64 sessionId = 0; + char sessId[SESSION_ID_LEN] = {0}; // 会话ID + ThreadId threadId = 0; // 线程ID + pg_time_t sessStartTime = 0; // 会话开始时间 + uint64 sessionId = 0; // 会话ID if (sess != NULL) { - sessionId = sess->session_id; - threadId = sess->attachPid; - sessStartTime = timestamptz_to_time_t(sess->proc_cxt.MyProcPort->SessionStartTime); + sessionId = sess->session_id; // 获取会话ID + threadId = sess->attachPid; // 获取线程ID + sessStartTime = timestamptz_to_time_t(sess->proc_cxt.MyProcPort->SessionStartTime); // 获取会话开始时间 } - getSessionID(sessId, sessStartTime, sessionId); + getSessionID(sessId, sessStartTime, sessionId); // 生成会话ID - /* build one tuple and save it in tuplestore. */ - Datum values[NUM_SESSION_MEMORY_DETAIL_ELEM] = {0}; - bool nulls[NUM_SESSION_MEMORY_DETAIL_ELEM] = {false}; + /* 构建一个元组并将其保存在元组存储器中。*/ + Datum values[NUM_SESSION_MEMORY_DETAIL_ELEM] = {0}; // 元组数据 + bool nulls[NUM_SESSION_MEMORY_DETAIL_ELEM] = {false}; // 元组的空标志位 - values[0] = CStringGetTextDatum(sessId); - values[1] = Int64GetDatum(threadId); + values[0] = CStringGetTextDatum(sessId); // 会话ID + values[1] = Int64GetDatum(threadId); // 线程ID - values[2] = CStringGetTextDatum(pstrdup(context->name)); - values[3] = Int16GetDatum(context->level); + values[2] = CStringGetTextDatum(pstrdup(context->name)); // 内存上下文名称 + values[3] = Int16GetDatum(context->level); // 内存上下文层级 if (context->level > 0 && context->parent != NULL) - values[4] = CStringGetTextDatum(pstrdup(context->parent->name)); + values[4] = CStringGetTextDatum(pstrdup(context->parent->name)); // 父级内存上下文名称 else - nulls[4] = true; - values[5] = Int64GetDatum(set->totalSpace); - values[6] = Int64GetDatum(set->freeSpace); - values[7] = Int64GetDatum(set->totalSpace - set->freeSpace); + nulls[4] = true; // 如果没有父级内存上下文,将空标志位设置为true + values[5] = Int64GetDatum(set->totalSpace); // 总共分配的内存空间 + values[6] = Int64GetDatum(set->freeSpace); // 剩余的内存空间 + values[7] = Int64GetDatum(set->totalSpace - set->freeSpace); // 已使用的内存空间 - tuplestore_putvalues(tupStore, tupDesc, values, nulls); + tuplestore_putvalues(tupStore, tupDesc, values, nulls); // 将元组数据插入到元组存储器中 } +// 递归计算会话的内存上下文统计信息 void ThreadPoolSessControl::recursiveSessMemCxt( knl_session_context* sess, const MemoryContext context, Tuplestorestate* tupStore, TupleDesc tupDesc) { - /* calculate MemoryContext Stats */ + /* 计算内存上下文统计信息 */ calculateSessMemCxtStats(sess, context, tupStore, tupDesc); - /* recursive MemoryContext's child */ + /* 递归处理内存上下文的子级 */ for (MemoryContext child = context->firstchild; child != NULL; child = child->nextchild) { recursiveSessMemCxt(sess, child, tupStore, tupDesc); } } +// 获取会话的内存详细信息 void ThreadPoolSessControl::getSessionMemoryDetail(Tuplestorestate* tupStore, TupleDesc tupDesc, knl_sess_control** sess) { - AutoMutexLock alock(&m_sessCtrlock); - knl_sess_control* ctrl = NULL; - Dlelem* elem = NULL; + AutoMutexLock alock(&m_sessCtrlock); // 自动互斥锁,确保线程安全 + knl_sess_control* ctrl = NULL; // 会话控制结构指针 + Dlelem* elem = NULL; // 双向链表元素指针 PG_TRY(); { - HOLD_INTERRUPTS(); - alock.lock(); + HOLD_INTERRUPTS(); // 暂时屏蔽中断 + alock.lock(); // 获取锁 - /* collect all the Memory Context status, put in data */ - elem = DLGetHead(&m_activelist); + /* 收集所有内存上下文的状态信息,将其放入数据中 */ + elem = DLGetHead(&m_activelist); // 获取活动会话链表的头指针 while (elem != NULL) { - ctrl = (knl_sess_control*)DLE_VAL(elem); - *sess = ctrl; + ctrl = (knl_sess_control*)DLE_VAL(elem); // 获取会话控制结构指针 + *sess = ctrl; // 更新会话控制结构指针 if (ctrl->sess) { - (void)syscalllockAcquire(&ctrl->sess->utils_cxt.deleMemContextMutex); - recursiveSessMemCxt(ctrl->sess, ctrl->sess->top_mem_cxt, tupStore, tupDesc); - (void)syscalllockRelease(&ctrl->sess->utils_cxt.deleMemContextMutex); + (void)syscalllockAcquire(&ctrl->sess->utils_cxt.deleMemContextMutex); // 获取内存上下文互斥锁 + recursiveSessMemCxt(ctrl->sess, ctrl->sess->top_mem_cxt, tupStore, tupDesc); // 递归计算内存上下文统计信息 + (void)syscalllockRelease(&ctrl->sess->utils_cxt.deleMemContextMutex); // 释放内存上下文互斥锁 } - elem = DLGetSucc(elem); + elem = DLGetSucc(elem); // 获取下一个会话元素 } - alock.unLock(); + alock.unLock(); // 释放锁 - RESUME_INTERRUPTS(); + RESUME_INTERRUPTS(); // 恢复中断 } PG_CATCH(); { if (*sess != NULL) { ctrl = *sess; - (void)syscalllockRelease(&ctrl->sess->utils_cxt.deleMemContextMutex); + (void)syscalllockRelease(&ctrl->sess->utils_cxt.deleMemContextMutex); // 释放内存上下文互斥锁 } - alock.unLock(); - PG_RE_THROW(); + alock.unLock(); // 释放锁 + PG_RE_THROW(); // 重新抛出异常 } - PG_END_TRY(); + PG_END_TRY(); // 结束异常处理 + } +// 计算客户端信息 void ThreadPoolSessControl::calculateClientInfo( knl_session_context* sess, Tuplestorestate* tupStore, TupleDesc tupDesc) { - /* build one tuple and save it in tuplestore. */ - const int COLUMN_NUM = 2; - Datum values[COLUMN_NUM] = {0}; - bool nulls[COLUMN_NUM] = {false}; - - values[0] = Int64GetDatum(sess->session_id); + /* 构建一个元组并将其保存在元组存储器中。*/ + const int COLUMN_NUM = 2; // 元组的列数 + Datum values[COLUMN_NUM] = {0}; // 元组数据 + bool nulls[COLUMN_NUM] = {false}; // 元组的空标志位 + + values[0] = Int64GetDatum(sess->session_id); // 会话ID if (sess->plsql_cxt.client_info != NULL) { - values[1] = CStringGetTextDatum(sess->plsql_cxt.client_info); + values[1] = CStringGetTextDatum(sess->plsql_cxt.client_info); // 客户端信息 } else { - nulls[1] = true; + nulls[1] = true; // 如果客户端信息为空,将空标志位设置为true } - tuplestore_putvalues(tupStore, tupDesc, values, nulls); + tuplestore_putvalues(tupStore, tupDesc, values, nulls); // 将元组数据插入到元组存储器中 } +// 获取会话的客户端信息 void ThreadPoolSessControl::getSessionClientInfo(Tuplestorestate* tupStore, TupleDesc tupDesc) { - AutoMutexLock alock(&m_sessCtrlock); - knl_sess_control* ctrl = NULL; - Dlelem* elem = NULL; - knl_sess_control* sess = NULL; - + AutoMutexLock alock(&m_sessCtrlock); // 自动互斥锁,确保线程安全 + knl_sess_control* ctrl = NULL; // 会话控制结构指针 + Dlelem* elem = NULL; // 双向链表元素指针 + knl_sess_control* sess = NULL; // 会话控制结构指针 + PG_TRY(); { - HOLD_INTERRUPTS(); - alock.lock(); - - /* collect all the Memory Context status, put in data */ - elem = DLGetHead(&m_activelist); - + HOLD_INTERRUPTS(); // 暂时屏蔽中断 + alock.lock(); // 获取锁 + + /* 收集所有内存上下文的状态信息,将其放入数据中 */ + elem = DLGetHead(&m_activelist); // 获取活动会话链表的头指针 + while (elem != NULL) { - ctrl = (knl_sess_control*)DLE_VAL(elem); - sess = ctrl; + ctrl = (knl_sess_control*)DLE_VAL(elem); // 获取会话控制结构指针 + sess = ctrl; // 更新会话控制结构指针 if (ctrl->sess) { - (void)syscalllockAcquire(&ctrl->sess->plsql_cxt.client_info_lock); - calculateClientInfo(ctrl->sess, tupStore, tupDesc); - (void)syscalllockRelease(&ctrl->sess->plsql_cxt.client_info_lock); + (void)syscalllockAcquire(&ctrl->sess->plsql_cxt.client_info_lock); // 获取客户端信息互斥锁 + calculateClientInfo(ctrl->sess, tupStore, tupDesc); // 计算客户端信息并将其保存在元组存储器中 + (void)syscalllockRelease(&ctrl->sess->plsql_cxt.client_info_lock); // 释放客户端信息互斥锁 } - elem = DLGetSucc(elem); + elem = DLGetSucc(elem); // 获取下一个会话元素 } - alock.unLock(); - sess = NULL; - - RESUME_INTERRUPTS(); + alock.unLock(); // 释放锁 + sess = NULL; // 重置会话控制结构指针 + + RESUME_INTERRUPTS(); // 恢复中断 } PG_CATCH(); { if (sess != NULL) { ctrl = sess; - (void)syscalllockRelease(&ctrl->sess->plsql_cxt.client_info_lock); + (void)syscalllockRelease(&ctrl->sess->plsql_cxt.client_info_lock); // 释放客户端信息互斥锁 } - alock.unLock(); - PG_RE_THROW(); + alock.unLock(); // 释放锁 + PG_RE_THROW(); // 重新抛出异常 } - PG_END_TRY(); + PG_END_TRY(); // 结束异常处理 + } -void ThreadPoolSessControl::getSessionMemoryContextInfo(const char* ctx_name, - StringInfoData* buf, knl_sess_control** sess) +// 获取指定内存上下文的详细信息,并将结果保存在给定的缓冲区中 +void ThreadPoolSessControl::getSessionMemoryContextInfo(const char* ctx_name, StringInfoData* buf, knl_sess_control** sess) { -#ifdef MEMORY_CONTEXT_TRACK - AutoMutexLock alock(&m_sessCtrlock); - knl_sess_control* ctrl = NULL; - Dlelem* elem = NULL; +#ifdef MEMORY_CONTEXT_TRACK // 如果启用内存上下文追踪 + AutoMutexLock alock(&m_sessCtrlock); // 自动互斥锁,确保线程安全 + knl_sess_control* ctrl = NULL; // 会话控制结构指针 + Dlelem* elem = NULL; // 双向链表元素指针 PG_TRY(); { - HOLD_INTERRUPTS(); - alock.lock(); + HOLD_INTERRUPTS(); // 暂时屏蔽中断 + alock.lock(); // 获取锁 - /* collect all the Memory Context status, put in data */ - elem = DLGetHead(&m_activelist); + /* 收集所有内存上下文的状态信息,将其放入数据中 */ + elem = DLGetHead(&m_activelist); // 获取活动会话链表的头指针 while (elem != NULL) { - ctrl = (knl_sess_control*)DLE_VAL(elem); - *sess = ctrl; + ctrl = (knl_sess_control*)DLE_VAL(elem); // 获取会话控制结构指针 + *sess = ctrl; // 更新会话控制结构指针 if (ctrl->sess) { - (void)syscalllockAcquire(&ctrl->sess->utils_cxt.deleMemContextMutex); - gs_recursive_unshared_memory_context(ctrl->sess->top_mem_cxt, ctx_name, buf); - (void)syscalllockRelease(&ctrl->sess->utils_cxt.deleMemContextMutex); + (void)syscalllockAcquire(&ctrl->sess->utils_cxt.deleMemContextMutex); // 获取内存上下文互斥锁 + gs_recursive_unshared_memory_context(ctrl->sess->top_mem_cxt, ctx_name, buf); // 递归获取内存上下文信息 + (void)syscalllockRelease(&ctrl->sess->utils_cxt.deleMemContextMutex); // 释放内存上下文互斥锁 } - elem = DLGetSucc(elem); + elem = DLGetSucc(elem); // 获取下一个会话元素 } - alock.unLock(); + alock.unLock(); // 释放锁 - RESUME_INTERRUPTS(); + RESUME_INTERRUPTS(); // 恢复中断 } PG_CATCH(); { if (*sess != NULL) { ctrl = *sess; - (void)syscalllockRelease(&ctrl->sess->utils_cxt.deleMemContextMutex); + (void)syscalllockRelease(&ctrl->sess->utils_cxt.deleMemContextMutex); // 释放内存上下文互斥锁 } - alock.unLock(); - PG_RE_THROW(); + alock.unLock(); // 释放锁 + PG_RE_THROW(); // 重新抛出异常 } - PG_END_TRY(); + PG_END_TRY(); // 结束异常处理 #endif } +// 根据会话槽位索引获取会话上下文 knl_session_context* ThreadPoolSessControl::GetSessionByIdx(int idx) { if (IsValidCtrlIndex(idx)) { - return m_base[idx - m_maxReserveSessionCount].sess; + return m_base[idx - m_maxReserveSessionCount].sess; // 返回指定槽位索引的会话上下文 } else { - return NULL; + return NULL; // 如果索引无效,则返回 NULL } } +// 根据会话ID查找会话槽位索引 int ThreadPoolSessControl::FindCtrlIdxBySessId(uint64 id) { - int cidx = 0; + int cidx = 0; // 控制结构索引 for (cidx = 0; cidx < m_maxActiveSessionCount; cidx++) { if (m_base[cidx].sess != NULL && m_base[cidx].sess->session_id == id) { - return cidx + m_maxReserveSessionCount; + return cidx + m_maxReserveSessionCount; // 返回找到的会话槽位索引 } } - ereport(LOG, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("Invalid session id"))); + ereport(LOG, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("Invalid session id"))); // 记录日志,表示会话ID无效 - return -1; + return -1; // 返回-1表示未找到 } + +// 检查会话超时 void ThreadPoolSessControl::CheckSessionTimeout() { - AutoMutexLock alock(&m_sessCtrlock); - alock.lock(); - int cidx; - TimestampTz now = GetCurrentTimestamp(); + AutoMutexLock alock(&m_sessCtrlock); // 自动互斥锁,确保线程安全 + alock.lock(); // 获取锁 + int cidx; // 控制结构索引 + TimestampTz now = GetCurrentTimestamp(); // 获取当前时间戳 + + // 遍历所有活动会话的控制结构 for (cidx = 0; cidx < m_maxActiveSessionCount; cidx++) { - knl_sess_control* ctrl = &m_base[cidx]; - knl_session_context* sess = ctrl->sess; + knl_sess_control* ctrl = &m_base[cidx]; // 获取会话控制结构指针 + knl_session_context* sess = ctrl->sess; // 获取会话上下文指针 + + // 如果会话不为NULL且设置了会话超时 if (sess != NULL && sess->attr.attr_common.SessionTimeout != 0) { + // 如果会话的存储上下文中的会话超时活动标志为真 if (sess->storage_cxt.session_timeout_active) { + // 如果当前时间超过了会话的结束时间,并且会话的超时计数小于10 if (now >= sess->storage_cxt.session_fin_time && sess->attr.attr_common.SessionTimeoutCount < 10) { #ifdef HAVE_INT64_TIMESTAMP elog(LOG, "close session : %lu for %d times due to session timeout : %d, max finish time is %ld. But now is:%ld", @@ -713,25 +810,28 @@ void ThreadPoolSessControl::CheckSessionTimeout() sess->session_id, sess->attr.attr_common.SessionTimeoutCount + 1, sess->attr.attr_common.SessionTimeout, sess->storage_cxt.session_fin_time, now); #endif - sess->attr.attr_common.SessionTimeoutCount++; - CloseClientSocket(sess, false); + sess->attr.attr_common.SessionTimeoutCount++; // 增加会话的超时计数 + CloseClientSocket(sess, false); // 关闭客户端套接字 } } } } - alock.unLock(); + alock.unLock(); // 释放锁 } +// 列出所有会话的全局临时表(GTT)的冻结事务ID TransactionId ThreadPoolSessControl::ListAllSessionGttFrozenxids(int maxSize, ThreadId *pids, TransactionId *xids, int *n) { - TransactionId result = InvalidTransactionId; - int i = 0; + TransactionId result = InvalidTransactionId; // 初始化冻结事务ID为无效事务ID + int i = 0; // 计数器 + // 如果未启用全局临时表,直接返回 if (u_sess->attr.attr_storage.max_active_gtt <= 0) { return 0; } + // 如果传入的参数合法,初始化相关变量 if (maxSize > 0) { Assert(pids); Assert(xids); @@ -739,51 +839,58 @@ TransactionId ThreadPoolSessControl::ListAllSessionGttFrozenxids(int maxSize, *n = 0; } + // 如果数据库中未启用全局临时表,返回无效事务ID if (u_sess->attr.attr_storage.max_active_gtt <= 0) { return InvalidTransactionId; } + // 如果处于恢复状态,返回无效事务ID if (RecoveryInProgress()) { return InvalidTransactionId; } - AutoMutexLock alock(&m_sessCtrlock); - alock.lock(); - knl_sess_control *ctl = nullptr; - knl_session_context *session = nullptr; - Dlelem* elem = DLGetHead(&m_activelist); + AutoMutexLock alock(&m_sessCtrlock); // 自动互斥锁,确保线程安全 + alock.lock(); // 获取锁 + knl_sess_control *ctl = nullptr; // 会话控制结构指针 + knl_session_context *session = nullptr; // 会话上下文指针 + Dlelem* elem = DLGetHead(&m_activelist); // 获取活动会话链表的头指针 while (elem != nullptr) { - ctl = (knl_sess_control*)DLE_VAL(elem); - session = ctl->sess; + ctl = (knl_sess_control*)DLE_VAL(elem); // 获取会话控制结构指针 + session = ctl->sess; // 获取会话上下文指针 + // 如果会话的数据库ID与当前会话的数据库ID相同,并且会话的GTT冻结事务ID有效 if (session->proc_cxt.MyDatabaseId == u_sess->proc_cxt.MyDatabaseId && TransactionIdIsNormal(session->gtt_ctx.gtt_session_frozenxid)) { + // 如果result为无效事务ID,或者当前会话的GTT冻结事务ID较小,则更新result if (result == InvalidTransactionId) { result = session->gtt_ctx.gtt_session_frozenxid; } else if (TransactionIdPrecedes(session->gtt_ctx.gtt_session_frozenxid, result)) { result = session->gtt_ctx.gtt_session_frozenxid; } + // 如果传入的参数允许保存信息 if (maxSize > 0) { - pids[i] = session->attachPid; - xids[i] = session->gtt_ctx.gtt_session_frozenxid; - i++; + pids[i] = session->attachPid; // 保存会话的进程ID + xids[i] = session->gtt_ctx.gtt_session_frozenxid; // 保存会话的GTT冻结事务ID + i++; // 计数器加1 } } - elem = DLGetSucc(elem); + elem = DLGetSucc(elem); // 获取下一个会话 } - alock.unLock(); + alock.unLock(); // 释放锁 + // 如果传入的参数允许保存信息,更新保存信息的数量 if (maxSize > 0) { *n = i; } - return result; + return result; // 返回最大的GTT冻结事务ID } +// 检查活动会话链表是否为空 bool ThreadPoolSessControl::IsActiveListEmpty() { - AutoMutexLock alock(&m_sessCtrlock); - alock.lock(); - bool res = (m_activelist.dll_len == 0); - alock.unLock(); - return res; -} + AutoMutexLock alock(&m_sessCtrlock); // 自动互斥锁,确保线程安全 + alock.lock(); // 获取锁 + bool res = (m_activelist.dll_len == 0); // 检查活动会话链表的长度是否为0 + alock.unLock(); // 释放锁 + return res; // 返回结果,true表示链表为空,false表示链表不为空 +} \ No newline at end of file -- 2.34.1 From e1d6a2c7a5cedc5c33b94bb63877506196f71de3 Mon Sep 17 00:00:00 2001 From: Nemoo <18210033860@163.com> Date: Sat, 30 Sep 2023 14:22:05 +0800 Subject: [PATCH 35/50] Update threadpool_stream.cpp --- .../process/threadpool/threadpool_stream.cpp | 58 ++++++++++++------- 1 file changed, 37 insertions(+), 21 deletions(-) diff --git a/src/gausskernel/process/threadpool/threadpool_stream.cpp b/src/gausskernel/process/threadpool/threadpool_stream.cpp index d87c00fc2..a579a83b1 100644 --- a/src/gausskernel/process/threadpool/threadpool_stream.cpp +++ b/src/gausskernel/process/threadpool/threadpool_stream.cpp @@ -26,7 +26,6 @@ */ #include "postgres.h" - #include "distributelayer/streamMain.h" #include "distributelayer/streamProducer.h" #include "executor/executor.h" @@ -37,8 +36,10 @@ #include "utils/guc.h" #include "utils/postinit.h" +// 重置流状态的静态函数 static void ResetStreamStatus(); +// 线程池流的构造函数 ThreadPoolStream::ThreadPoolStream() { m_tid = InvalidTid; @@ -51,10 +52,12 @@ ThreadPoolStream::ThreadPoolStream() m_threadStatus = THREAD_UNINIT; } +// 线程池流的析构函数 ThreadPoolStream::~ThreadPoolStream() { } +// 启动线程池流 int ThreadPoolStream::StartUp(int idx, StreamProducer* producer, ThreadPoolGroup* group, pthread_mutex_t* mutex, pthread_cond_t* cond) { @@ -73,6 +76,7 @@ int ThreadPoolStream::StartUp(int idx, StreamProducer* producer, ThreadPoolGroup return m_tid; } +// 等待任务 void ThreadPoolStream::WaitMission() { PreventSignal(); @@ -95,6 +99,7 @@ void ThreadPoolStream::WaitMission() AllowSignal(); } +// 唤醒线程池流以开始工作 void ThreadPoolStream::WakeUpToWork(StreamProducer* producer) { pthread_mutex_lock(m_mutex); @@ -103,6 +108,7 @@ void ThreadPoolStream::WakeUpToWork(StreamProducer* producer) pthread_mutex_unlock(m_mutex); } +// 唤醒线程池流以更新线程状态 void ThreadPoolStream::WakeUpToUpdate(ThreadStatus status) { pthread_mutex_lock(m_mutex); @@ -111,6 +117,7 @@ void ThreadPoolStream::WakeUpToUpdate(ThreadStatus status) pthread_mutex_unlock(m_mutex); } +// 初始化流 void ThreadPoolStream::InitStream() { knl_session_context* sc = @@ -122,7 +129,7 @@ void ThreadPoolStream::InitStream() u_sess = sc; SelfMemoryContext = u_sess->self_mem_cxt; - /* Switch context to Session context. */ + /* 切换上下文到会话上下文 */ AutoContextSwitch memSwitch(SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_EXECUTOR)); SetStreamWorkerInfo(m_producer); @@ -133,17 +140,17 @@ void ThreadPoolStream::InitStream() SetProcessingMode(InitProcessing); - /* Init GUC option for this session. */ + /* 为此会话初始化GUC选项 */ InitializeGUCOptions(); - /* Read in remaining GUC variables */ + /* 读取剩余的GUC变量 */ read_nondefault_variables(); - /* Do local initialization of file, storage and buffer managers */ + /* 执行文件、存储和缓冲管理器的本地初始化 */ ReBuildLSC(); InitFileAccess(); smgrinit(); - /* Init Stream thread user and database */ + /* 初始化流线程的用户和数据库 */ t_thrd.proc_cxt.PostInit->SetDatabaseAndUser( u_sess->stream_cxt.producer_obj->getDbName(), InvalidOid, u_sess->stream_cxt.producer_obj->getUserName()); t_thrd.proc_cxt.PostInit->InitStreamSession(); @@ -162,6 +169,7 @@ void ThreadPoolStream::InitStream() m_producer->getKey().smpIdentifier); } +// 清理流 void ThreadPoolStream::CleanUp() { m_producer = NULL; @@ -171,15 +179,17 @@ void ThreadPoolStream::CleanUp() m_group->ReturnStreamToPool(&m_elem); } +// 关闭流 void ThreadPoolStream::ShutDown() { m_producer = NULL; m_group->RemoveStreamFromPool(&m_elem, m_idx); } +// 重置流状态 static void ResetStreamStatus() { - /* Add the pg_delete_audit operation to audit log */ + /* 添加pg_delete_audit操作到审计日志 */ t_thrd.audit.Audit_delete = false; t_thrd.postgres_cxt.debug_query_string = NULL; t_thrd.postgres_cxt.g_NoAnalyzeRelNameList = NIL; @@ -193,8 +203,7 @@ static void ResetStreamStatus() } /* - * Reset extended-query-message flag, so that any errors - * encountered in "idle" state don't provoke skip. + * 重置extended-query-message标志,以防止在“空闲”状态下遇到的任何错误引发跳过。 */ u_sess->postgres_cxt.doing_extended_query_message = false; u_sess->debug_query_id = 0; @@ -202,23 +211,30 @@ static void ResetStreamStatus() u_sess->misc_cxt.AuthenticatedUserId = InvalidOid; u_sess->analyze_cxt.is_under_analyze = false; - /* We don't have a transaction command open anymore */ + /* 不再存在事务命令 */ t_thrd.postgres_cxt.xact_started = false; /* - * Reset top transaction in case we have received parent transaction - * from main worker thread, which has already been release. + * 重置顶级事务状态,以防我们已经接收到主工作线程中已释放的父事务。 */ InitTopTransactionState(); InitCurrentTransactionState(); - if (!IS_PGSTATE_TRACK_UNDEFINE) { - volatile PgBackendStatus* beentry = t_thrd.shemem_ptr_cxt.MyBEEntry; - beentry->st_queryid = 0; - pgstat_report_unique_sql_id(true); - beentry->st_sessionid = 0; - beentry->st_parent_sessionid = 0; - beentry->st_thread_level = 0; - beentry->st_smpid = 0; - } + // 如果不是未定义的状态跟踪(PGSTATE_TRACK_UNDEFINE是一个宏,可能表示状态跟踪是否已启用) +if (!IS_PGSTATE_TRACK_UNDEFINE) { + // 获取当前线程的后端状态(用volatile修饰,表示它可能在任何时候被更改) + volatile PgBackendStatus* beentry = t_thrd.shemem_ptr_cxt.MyBEEntry; + // 将查询ID重置为0 + beentry->st_queryid = 0; + // 报告唯一SQL ID,参数为true表示清除先前的SQL ID + pgstat_report_unique_sql_id(true); + // 重置会话ID为0 + beentry->st_sessionid = 0; + // 重置父会话ID为0 + beentry->st_parent_sessionid = 0; + // 重置线程级别为0 + beentry->st_thread_level = 0; + // 重置SMP(Symmetric Multi-Processing) ID为0 + beentry->st_smpid = 0; } +} -- 2.34.1 From cc65d639caf5153227980df9770092d48a38dbf1 Mon Sep 17 00:00:00 2001 From: Nemoo <18210033860@163.com> Date: Sat, 30 Sep 2023 14:22:46 +0800 Subject: [PATCH 36/50] Update threadpool_worker.cpp --- .../process/threadpool/threadpool_worker.cpp | 299 ++++++++++++------ 1 file changed, 200 insertions(+), 99 deletions(-) diff --git a/src/gausskernel/process/threadpool/threadpool_worker.cpp b/src/gausskernel/process/threadpool/threadpool_worker.cpp index ac92f6a8c..262474612 100644 --- a/src/gausskernel/process/threadpool/threadpool_worker.cpp +++ b/src/gausskernel/process/threadpool/threadpool_worker.cpp @@ -75,64 +75,95 @@ static void SendSessionIdxToClient(); static void ResetSignalHandle(); static void SessionSetBackendOptions(); +// 线程池工作线程的构造函数,初始化各个成员变量 ThreadPoolWorker::ThreadPoolWorker(uint idx, ThreadPoolGroup* group, pthread_mutex_t* mutex, pthread_cond_t* cond) { + // 工作线程的索引 m_idx = idx; + // 所属线程池组 m_group = group; + // 线程ID初始化为无效值 m_tid = InvalidTid; + // 线程状态初始化为未初始化 m_threadStatus = THREAD_UNINIT; + // 当前会话初始化为NULL m_currentSession = NULL; + // 互斥锁和条件变量用于线程同步 m_mutex = mutex; m_cond = cond; + // 等待状态初始化为未定义 m_waitState = STATE_WAIT_UNDEFINED; + // 初始化双向链表元素 DLInitElem(&m_elem, this); + // 设置线程的私有数据指针为全局线程私有数据 m_thrd = &t_thrd; } +// 线程池工作线程的析构函数,清理资源 ThreadPoolWorker::~ThreadPoolWorker() { + // 释放资源,将成员变量置为NULL m_currentSession = NULL; m_group = NULL; m_mutex = NULL; m_cond = NULL; } +// 关闭工作线程的函数 void ThreadPoolWorker::ShutDown() { + // 获取互斥锁 pthread_mutex_lock(m_mutex); + // 设置线程状态为退出 m_threadStatus = THREAD_EXIT; + // 清理当前会话的资源 CleanUpSession(true); - /* Remove the worker if it is in the free worker list. */ + /* 如果工作线程在空闲线程列表中,将其移除 */ m_group->GetListener()->RemoveWorkerFromList(this); + // 释放互斥锁 pthread_mutex_unlock(m_mutex); + // 释放工作线程占用的槽位 m_group->ReleaseWorkerSlot(m_idx); } +// 通知工作线程准备就绪的函数 void ThreadPoolWorker::NotifyReady() { + // 获取互斥锁 pthread_mutex_lock(m_mutex); + // 如果线程状态是退出,则保持为退出状态,否则设置为运行状态 m_threadStatus = (m_threadStatus == THREAD_EXIT) ? THREAD_EXIT : THREAD_RUN; + // 释放互斥锁 pthread_mutex_unlock(m_mutex); } +// 启动工作线程的函数 int ThreadPoolWorker::StartUp() { Port port; + // 初始化端口结构体,并设置可以接受连接 int ss_rc = memset_s(&port, sizeof(port), 0, sizeof(port)); securec_check(ss_rc, "\0", "\0"); - port.canAcceptConnections = CAC_OK; port.sock = PGINVALID_SOCKET; port.gs_sock = GS_INVALID_GSOCK; - /* Calculate cancel key which will be assigned to backend. */ + + /* 计算将分配给后端的取消键值。 */ GenerateCancelKey(false); + // 分配后端槽位 t_thrd.proc_cxt.MyPMChildSlot = AssignPostmasterChildSlot(); if (t_thrd.proc_cxt.MyPMChildSlot == -1) { return STATUS_ERROR; } + + // 创建后端进程 Backend* bn = CreateBackend(); + // 初始化工作线程并获取线程ID m_tid = initialize_worker_thread(THREADPOOL_WORKER, &port, (void*)this); + // 重置槽位 t_thrd.proc_cxt.MyPMChildSlot = 0; + + // 如果线程ID无效,释放槽位,返回错误状态 if (m_tid == InvalidTid) { ReleasePostmasterChildSlot(bn->child_slot); bn->pid = 0; @@ -140,9 +171,11 @@ int ThreadPoolWorker::StartUp() return STATUS_ERROR; } + // 设置后端的进程ID和角色 bn->pid = m_tid; bn->role = THREADPOOL_WORKER; Assert(bn->child_slot != 0); + // 添加后端到全局后端列表 AddBackend(bn); return STATUS_OK; @@ -159,13 +192,13 @@ void PreventSignal() void AllowSignal() { t_thrd.int_cxt.ignoreBackendSignal = false; - /* now we can accept signal. out of this, we rely on signal handle. */ + /* 现在我们可以接收信号了,为此,我们依赖于信号句柄。 */ RESUME_INTERRUPTS(); } void ThreadPoolWorker::WaitMission() { - /* Return if we still in a transaction block. */ + /* 如果仍在事务块中,则返回 */ if (!WorkerThreadCanSeekAnotherMission(&m_reason)) { return; } @@ -180,22 +213,22 @@ void ThreadPoolWorker::WaitMission() errmsg("InterruptHoldoffCount should be zero when get next session."))); } /* - * prevent any signal execep siguit. - * reset any pending signal and timer. - * before we serve next session we must keep us clean. + 防止任何信号,除了信号。 + 复位任何挂起的信号和定时器。 + 在我们下次发球前,我们必须保持干净 */ PreventSignal(); MemoryContext old = CurrentMemoryContext; while (true) { - /* we should keep the thread clean for next Session. */ + /* 我们应该为下一个会话保持线程干净. */ CleanThread(); - /* Get next session. */ + /* 获取下一个会话. */ WaitNextSession(); Assert(m_currentSession != NULL); isRawSession = (m_currentSession->status == KNL_SESS_UNINIT); - /* do the binding process ,binding the connection and thread */ - /* return to worker pool if binding fail. */ + /* 做绑扎过程,绑扎的连接和线程 */ + /* 如果绑定失败,返回工作池 */ if (AttachSessionToThread()) { if (isRawSession) { if (t_thrd.libpq_cxt.PqRecvPointer == t_thrd.libpq_cxt.PqRecvLength) { @@ -213,7 +246,7 @@ void ThreadPoolWorker::WaitMission() /* * CommProxy Support * - * session attach thread success, we record relation of sock with worker + * 会话连接线程成功,记录sock与worker的关系 */ if (AmIProxyModeSockfd(m_currentSession->proc_cxt.MyProcPort->sock)) { g_comm_controller->SetCommSockActive(m_currentSession->proc_cxt.MyProcPort->sock, m_idx); @@ -226,64 +259,83 @@ void ThreadPoolWorker::WaitMission() } MemoryContextSwitchTo(old); (void)disable_session_sig_alarm(); - /* now we can accept signal. out of this, we rely on signal handle. */ + /* 现在我们可以接收信号了。为此,我们依赖于信号句柄。 */ AllowSignal(); ShutDownIfNecessary(); } +// 唤醒工作线程来处理特定会话的函数 bool ThreadPoolWorker::WakeUpToWork(knl_session_context* session) { bool succ = true; + // 获取互斥锁 pthread_mutex_lock(m_mutex); + // 如果线程状态不是退出或挂起 if (likely(m_threadStatus != THREAD_EXIT && m_threadStatus != THREAD_PENDING)) { + // 设置当前会话 m_currentSession = session; + // 发送条件信号,唤醒线程处理会话 pthread_cond_signal(m_cond); } else { + // 如果线程状态是退出或挂起,返回失败 succ = false; } + // 释放互斥锁 pthread_mutex_unlock(m_mutex); return succ; } +// 唤醒工作线程来更新线程状态的函数 void ThreadPoolWorker::WakeUpToUpdate(ThreadStatus status) { + // 获取互斥锁 pthread_mutex_lock(m_mutex); + // 如果线程状态不是退出 if (m_threadStatus != THREAD_EXIT) { + // 更新线程状态 m_threadStatus = status; + // 发送条件信号,唤醒线程更新状态 pthread_cond_signal(m_cond); } + // 释放互斥锁 pthread_mutex_unlock(m_mutex); } +// 唤醒工作线程来挂起线程(如果线程是自由的)的函数 bool ThreadPoolWorker::WakeUpToPendingIfFree() { bool ans = false; + // 获取互斥锁 pthread_mutex_lock(m_mutex); + // 如果线程状态不是退出、挂起,并且当前会话为空 if (m_threadStatus != THREAD_EXIT && m_threadStatus != THREAD_PENDING && m_currentSession == NULL) { + // 设置线程状态为挂起 m_threadStatus = THREAD_PENDING; + // 发送条件信号,唤醒线程挂起 pthread_cond_signal(m_cond); - ans = true; + ans = true; // 返回成功 } else { - ans = false; + ans = false; // 返回失败 } + // 释放互斥锁 pthread_mutex_unlock(m_mutex); return ans; } /* - * Some variable are session level, however they are used by some opensource - * component like postgis, we can not move them to knl_session_context directly. - * To solve this problem, providing two interface: RestoreThreadVariable and - * SaveThreadVariable. + 有些变量是会话级别的,但是它们被一些开源程序使用 + 像postgis这样的组件,我们不能直接将它们移动到knl_session_context。 + 为了解决这个问题,提供了两个接口:RestoreThreadVariable和 + SaveThreadVariable */ void ThreadPoolWorker::RestoreThreadVariable() { Assert(m_currentSession != NULL); - /* use values in session to set local thread GUC */ + /* 使用会话中的值来设置本地线程GUC */ SetThreadLocalGUC(m_currentSession); - /* use values in session to set other thread local variables */ + /* 使用会话中的值来设置其他线程局部变量 */ pg_reset_srand48(m_currentSession->rand_cxt.rand48_seed); } @@ -291,7 +343,7 @@ void ThreadPoolWorker::RestoreLocaleInfo() { if (strcmp(NameStr(m_currentSession->mb_cxt.datcollate), NameStr(t_thrd.port_cxt.cur_datcollate)) == 0 && strcmp(NameStr(m_currentSession->mb_cxt.datctype), NameStr(t_thrd.port_cxt.cur_datctype)) == 0) { - /* no need set again. */ + /* 不用再设置了. */ return; } @@ -325,14 +377,16 @@ void ThreadPoolWorker::RestoreLocaleInfo() NAMEDATALEN); securec_check(rc, "\0", "\0"); - /* Use the right encoding in translated messages */ + /* 在翻译后的消息中使用正确的编码 */ #ifdef ENABLE_NLS pg_bind_textdomain_codeset(textdomain(NULL)); #endif } +// 恢复会话变量的函数 void ThreadPoolWorker::RestoreSessionVariable() { + // 恢复各个会话变量的初始值 m_currentSession->attr.attr_sql.default_statistics_target = default_statistics_target; m_currentSession->attr.attr_common.session_timezone = session_timezone; m_currentSession->attr.attr_common.log_timezone = log_timezone; @@ -344,28 +398,33 @@ void ThreadPoolWorker::RestoreSessionVariable() m_currentSession->attr.attr_network.comm_client_bind = comm_client_bind; m_currentSession->attr.attr_network.comm_ackchk_time = comm_ackchk_time; + // 恢复随机数发生器的种子 unsigned short* rand48 = pg_get_srand48(); m_currentSession->rand_cxt.rand48_seed[0] = rand48[0]; m_currentSession->rand_cxt.rand48_seed[1] = rand48[1]; m_currentSession->rand_cxt.rand48_seed[2] = rand48[2]; } +// 设置会话信息的函数 void ThreadPoolWorker::SetSessionInfo() { - /* - * The proc and pgxact are more likely thread level variable, maybe we need to - * reconsider if it's better to put it in knl_thread_context. - */ + // 获取当前线程的进程和PGXACT结构 struct PGPROC* thread_proc = t_thrd.proc; + // 设置数据库ID和角色ID thread_proc->databaseId = m_currentSession->proc_cxt.MyDatabaseId; thread_proc->roleId = m_currentSession->proc_cxt.MyRoleId; Assert(thread_proc->pid == t_thrd.proc_cxt.MyProcPid); + // 设置会话ID和全局会话ID thread_proc->sessionid = m_currentSession->session_id; thread_proc->globalSessionId = m_currentSession->globalSessionId; + // 设置工作线程的版本号 thread_proc->workingVersionNum = m_currentSession->proc_cxt.MyProcPort->SessionVersionNum; + // 设置会话的附属进程ID m_currentSession->attachPid = thread_proc->pid; + // 如果PGXACT结构不为空且当前会话是Redis工作者 if (t_thrd.pgxact != NULL && m_currentSession->proc_cxt.Isredisworker) { + // 获取进程数组锁,设置进程为Redis工作者 LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE); t_thrd.pgxact->vacuumFlags |= PROC_IS_REDIST; LWLockRelease(ProcArrayLock); @@ -377,15 +436,15 @@ void ThreadPoolWorker::WaitNextSession() if (EnableLocalSysCache()) { g_instance.global_sysdbcache.GSCMemThresholdCheck(); } - /* Return worker to pool unless we can get a task right now. */ + /* 除非我们现在能找到工作,否则就把工人送回池子里 */ ThreadPoolListener* lsn = m_group->GetListener(); Assert(lsn != NULL); while (true) { - /* Wait if the thread was turned into pending mode. */ + /* 如果线程变成挂起模式,则等待 */ if (unlikely(m_threadStatus == THREAD_PENDING)) { Pending(); - /* pending thread must don't have session on it */ + /* 挂起的线程必须没有会话 */ if (m_currentSession != NULL) { u_sess = m_currentSession; ereport(FATAL, @@ -399,9 +458,9 @@ void ThreadPoolWorker::WaitNextSession() break; } - /* Wait for listener dispatch. */ + /* 等待侦听器调度 */ if (!lsn->TryFeedWorker(this)) { - /* report thread status. */ + /* 报告线程状态 */ u_sess = t_thrd.fake_session; WaitState oldStatus = pgstat_report_waitstatus(STATE_WAIT_COMM); @@ -426,24 +485,35 @@ void ThreadPoolWorker::WaitNextSession() } } +// 挂起线程的函数 void ThreadPoolWorker::Pending() { + // 将工作线程数量减一(原子操作) pg_atomic_fetch_sub_u32((volatile uint32*)&m_group->m_workerNum, 1); + // 获取互斥锁 pthread_mutex_lock(m_mutex); + // 如果线程状态是挂起 while (m_threadStatus == THREAD_PENDING) { + // 等待条件信号,挂起线程 pthread_cond_wait(m_cond, m_mutex); } + // 释放互斥锁 pthread_mutex_unlock(m_mutex); + // 将工作线程数量加一(原子操作) pg_atomic_fetch_add_u32((volatile uint32*)&m_group->m_workerNum, 1); + // 如果线程状态是退出,执行关闭操作 if (m_threadStatus == THREAD_EXIT) { ShutDownIfNecessary(); - } + } } +// 如果需要的话,关闭线程的函数 void ThreadPoolWorker::ShutDownIfNecessary() { + // 如果线程状态是退出 if (unlikely(m_threadStatus == THREAD_EXIT)) { + // 如果当前会话为空,使用虚拟会话 if (!m_currentSession) { use_fake_session(); m_currentSession = t_thrd.fake_session; @@ -451,11 +521,12 @@ void ThreadPoolWorker::ShutDownIfNecessary() u_sess = m_currentSession; } + // 恢复线程变量 RestoreThreadVariable(); + // 退出进程 proc_exit(0); } - /* there is time window which the cancle signal has arrived but ignored by prevent signal called before, - * so we rebuild the signal status here in case that happens. */ + /* 在防止信号被调用之前,取消信号已经到达但被忽略的时间窗口,所以在这里重建信号状态以防发生这种情况。 */ if (unlikely(m_currentSession != NULL && m_currentSession->status == KNL_SESS_CLOSE)) { t_thrd.int_cxt.ClientConnectionLost = true; ereport(ERROR, (errmodule(MOD_THREAD_POOL), @@ -466,8 +537,8 @@ void ThreadPoolWorker::ShutDownIfNecessary() void ThreadPoolWorker::CleanThread() { /* - * In thread pool mode, ensure that packet transmission must be completed before thread switchover. - * Otherwise, packet format disorder may occurs. + 在线程池模式下,请确保在切换线程前完成报文传输, + 否则可能导致数据包格式混乱 */ if (m_currentSession != NULL && t_thrd.libpq_cxt.PqSendPointer > 0) { int res = pq_flush(); @@ -477,12 +548,12 @@ void ThreadPoolWorker::CleanThread() } /* - * Clean up Allocated descs incase long jump happend - * and they are not cleaned up in AtEOXact_Files. + * 如果跳远发生,清理分配的位置 + * 并且它们不会在AtEOXact_Files中被清理 */ FreeAllAllocatedDescs(); - /* we should abandon this session. */ + /* 我们应该放弃这次会议 */ if (t_thrd.int_cxt.ClientConnectionLost || t_thrd.threadpool_cxt.reaper_dead_session) { t_thrd.int_cxt.ClientConnectionLost = false; t_thrd.threadpool_cxt.reaper_dead_session = false; @@ -510,17 +581,18 @@ void ThreadPoolWorker::CleanThread() } } +// 将会话从线程中分离的函数 void ThreadPoolWorker::DetachSessionFromThread() { - /* session attach thread success, we record relation of sock with worker */ + /* 会话成功附加到线程上,我们记录套接字和工作线程的关系 */ if (AmIProxyModeSockfd(m_currentSession->proc_cxt.MyProcPort->sock)) { g_comm_controller->SetCommSockIdle(m_currentSession->proc_cxt.MyProcPort->sock); } - /* If some error occur at session initialization, we need to close it. */ + /* 如果在会话初始化时发生错误,我们需要关闭会话。 */ if (m_currentSession->status == KNL_SESS_UNINIT) { m_currentSession->status = KNL_SESS_CLOSERAW; - /* cache may be in wrong stat, rebuild is ok */ + /* 缓存可能处于错误状态,重新构建是可以的 */ ReBuildLSC(); CleanUpSession(false); m_currentSession = NULL; @@ -540,33 +612,44 @@ void ThreadPoolWorker::DetachSessionFromThread() m_currentSession->pcache_cxt.gpc_in_ddl = false; } RestoreSessionVariable(); + // 从性能统计中取消关联当前会话 pgstat_couple_decouple_session(false); + // 取消关联会话的性能统计 pgstat_deinitialize_session(); m_currentSession->attachPid = (ThreadId)-1; - /* should restore the data before return to listener. */ + /* 在返回到监听器之前,应该还原数据。 */ m_group->GetListener()->AddEpoll(m_currentSession); m_currentSession = NULL; u_sess = NULL; } +// 将会话附加到线程上的函数 bool ThreadPoolWorker::AttachSessionToThread() { + // 断言当前会话不为空,并且当前线程没有事务资源所有者 Assert(m_currentSession != NULL); Assert(t_thrd.utils_cxt.TopTransactionResourceOwner == NULL); + // 设置会话信息并恢复线程变量 SetSessionInfo(); RestoreThreadVariable(); + + // 如果会话状态为 KNL_SESS_DETACH,则还原本地化信息 if (m_currentSession->status == KNL_SESS_DETACH) { RestoreLocaleInfo(); } + // 将当前会话设置为活动会话 u_sess = m_currentSession; + // 设置输出发送目标为远程 t_thrd.postgres_cxt.whereToSendOutput = DestRemote; + // 将当前内存上下文设置为会话自身内存上下文 SelfMemoryContext = u_sess->self_mem_cxt; + /* - * Since thread pool worker may start earlier than startup finishing recovery, - * init xlog access if necessary. + * 由于线程池工作线程可能早于恢复完成之前启动, + * 如果需要,初始化 xlog 访问。 */ PG_TRY(); { @@ -574,31 +657,33 @@ bool ThreadPoolWorker::AttachSessionToThread() } PG_CATCH(); { - /* if init xlog has error, should throw fatal this thread */ + /* 如果初始化 xlog 出错,应该为该线程抛出致命错误 */ ereport(FATAL, (errmsg("init xlog failed, throw fatal for this thread"))); } PG_END_TRY(); -#ifdef ENABLE_QUNIT - set_qunit_case_number_hook(u_sess->utils_cxt.qunit_case_number, NULL); -#endif + // 根据会话状态执行相应的操作 switch (m_currentSession->status) { case KNL_SESS_UNINIT: { + // 如果会话初始化成功 if (InitSession(m_currentSession)) { - /* Registering backend_version */ + /* 注册后端版本信息 */ if (t_thrd.proc && contain_backend_version(t_thrd.proc->workingVersionNum)) { register_backend_version(t_thrd.proc->workingVersionNum); } + // 将会话状态设置为附加 m_currentSession->status = KNL_SESS_ATTACH; + // 断言当前数据库匹配 Assert(CheckMyDatabaseMatch()); } else { + // 如果会话初始化失败 m_currentSession->status = KNL_SESS_CLOSE; - /* clean up mess. */ + /* 清理混乱状态。 */ CleanUpSession(false); m_currentSession = NULL; u_sess = NULL; } - /* init port will change the signal handle */ + /* 初始化端口会改变信号处理 */ ResetSignalHandle(); } break; @@ -608,6 +693,7 @@ bool ThreadPoolWorker::AttachSessionToThread() int rcs = 0; Port *port = m_currentSession->proc_cxt.MyProcPort; + // 设置线程名称为当前会话用户名,以区分不同类型的线程 if (t_thrd.role == WORKER) { rcs = snprintf_truncated_s(thr_name, sizeof(thr_name), "w:%s", port->user_name); securec_check_ss(rcs, "\0", "\0"); @@ -618,24 +704,27 @@ bool ThreadPoolWorker::AttachSessionToThread() (void)pthread_setname_np(gs_thread_self(), thr_name); } #endif + // 初始化性能统计信息 pgstat_initialize_session(); + // 关联当前会话的性能统计信息 pgstat_couple_decouple_session(true); - /* Postgres init thread syscache. */ + /* Postgres 初始化线程的系统缓存。 */ t_thrd.proc_cxt.PostInit->InitLoadLocalSysCache(u_sess->proc_cxt.MyDatabaseId, u_sess->proc_cxt.MyProcPort->database_name); Assert(CheckMyDatabaseMatch()); + // 将会话状态设置为附加 m_currentSession->status = KNL_SESS_ATTACH; } break; case KNL_SESS_CLOSERAW: case KNL_SESS_CLOSE: { - /* unified auditing logout */ + /* 统一审计登出 */ audit_processlogout_unified(); - /* clean up tmp schema */ + /* 清理临时模式 */ RemoveTempNamespace(); - /* clean up mess. */ + /* 清理混乱状态。 */ CleanUpSession(false); m_currentSession = NULL; u_sess = NULL; @@ -648,11 +737,13 @@ bool ThreadPoolWorker::AttachSessionToThread() default: Assert(false); + // 未定义的会话状态,应该抛出 PANIC 错误 ereport(PANIC, (errcode(ERRCODE_INVALID_ATTRIBUTE), errmsg("undefined state %d for session attach", m_currentSession->status))); } + // 如果当前会话状态为附加,则返回 true,否则使用虚假会话并返回 false if (m_currentSession && m_currentSession->status == KNL_SESS_ATTACH) { return true; } else { @@ -662,12 +753,14 @@ bool ThreadPoolWorker::AttachSessionToThread() } } +// 释放会话锁定资源 void ThreadPoolWorker::CleanUpSessionWithLock() { if (m_currentSession == NULL) { return; } + // 如果是 Redis 工作线程,释放对应的锁定资源 if (t_thrd.pgxact != NULL && m_currentSession->proc_cxt.Isredisworker) { LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE); t_thrd.pgxact->vacuumFlags &= ~PROC_IS_REDIST; @@ -675,17 +768,20 @@ void ThreadPoolWorker::CleanUpSessionWithLock() } } +// 清理会话资源 void ThreadPoolWorker::CleanUpSession(bool threadexit) { if (m_currentSession == NULL) { return; } + // 如果当前会话处于虚假状态,且线程状态为 THREAD_EXIT,则直接返回 if (m_currentSession->status == KNL_SESS_FAKE) { Assert(m_threadStatus == THREAD_EXIT); return; } + // 如果当前会话状态不是 KNL_SESS_END_PHASE1,则执行必要的清理工作 if (m_currentSession->status != KNL_SESS_END_PHASE1) { InitThreadLocalWhenSessionExit(); @@ -694,9 +790,10 @@ void ThreadPoolWorker::CleanUpSession(bool threadexit) DecreaseUserCount(m_currentSession->proc_cxt.MyRoleId); } - /* Close Session. */ + /* 关闭会话 */ m_group->GetListener()->DelSessionFromEpoll(m_currentSession); + // 如果会话超过连接限制,则减少连接数 if (m_currentSession->proc_cxt.PassConnLimit) { SpinLockAcquire(&g_instance.conn_cxt.ConnCountLock); g_instance.conn_cxt.CurConnCount--; @@ -705,34 +802,31 @@ void ThreadPoolWorker::CleanUpSession(bool threadexit) } /* - * Record this state in case we reenter this function because - * ERROR/FATAL occurs in sess_exit(). + * 在 ERROR/FATAL 在 sess_exit() 函数中再次进入此函数之前,记录此状态。 */ m_currentSession->status = KNL_SESS_END_PHASE1; } /* - * If clean up work already be done at proc_exit(), then we don't need to - * call sess_exit() anymore, otherwise, there will be double free. + * 如果已经在 proc_exit() 函数中完成了清理工作,则不需要再次调用 sess_exit() 函数,否则会导致重复释放资源。 */ if (!t_thrd.proc_cxt.proc_exit_inprogress) { sess_exit(0); } - /* clear pgstat slot */ + /* 清理 pgstat 插槽 */ pgstat_release_session_memory_entry(); pgstat_deinitialize_session(); pgstat_beshutdown_session(m_currentSession->session_ctr_index); localeconv_deinitialize_session(); - /* clean gpc refcount and plancache in shared memory */ + /* 清理 GPC 引用计数和共享内存中的计划缓存 */ if (ENABLE_DN_GPC) CleanSessGPCPtr(m_currentSession); /* - * clear invalid msg slot - * If called during pool worker thread exit, session's invalid msg slot has already - * been cleared along with that of pool worker in shmem_exit. + * 清理无效消息槽 + * 如果在池工作线程退出期间调用,则会话的无效消息槽已经随着池工作线程的消息槽一起在 shmem_exit 中被清理。 */ if (!t_thrd.proc_cxt.proc_exit_inprogress) { CleanupWorkSessionInvalidation(); @@ -745,6 +839,7 @@ void ThreadPoolWorker::CleanUpSession(bool threadexit) m_currentSession = NULL; } +// 创建后端进程 Backend* ThreadPoolWorker::CreateBackend() { Backend* bn = AssignFreeBackEnd(t_thrd.proc_cxt.MyPMChildSlot); @@ -754,6 +849,7 @@ Backend* ThreadPoolWorker::CreateBackend() return bn; } +// 将后端进程添加到全局后端列表中 void ThreadPoolWorker::AddBackend(Backend* bn) { bn->is_autovacuum = false; @@ -761,6 +857,7 @@ void ThreadPoolWorker::AddBackend(Backend* bn) DLAddHead(g_instance.backend_list, &bn->elem); } +// 初始化会话共享内存 static void init_session_share_memory() { TableSpaceUsageManager::Init(); @@ -769,21 +866,22 @@ static void init_session_share_memory() #endif } +// 如果需要,初始化 BSQL 插件钩子 #ifndef ENABLE_MULTIPLE_NODES extern void InitBSqlPluginHookIfNeeded(); #endif +// 初始化会话(参数:会话上下文) static bool InitSession(knl_session_context* session) { - /* non't send ereport to client now */ + /* 非实时向客户端发送错误信息 */ t_thrd.postgres_cxt.whereToSendOutput = DestNone; - /* Switch context to Session context. */ + /* 切换上下文到会话上下文 */ AutoContextSwitch memSwitch(session->mcxt_group->GetMemCxtGroup(MEMORY_CONTEXT_DEFAULT)); /* - * Set thread version to the latest working version number for - * InitializeGUCOptions. - * This is ugly and can not avoid all race conditions during online upgrade. + * 为 InitializeGUCOptions 设置当前线程版本号。 + * 这是丑陋的,无法避免在线升级期间所有竞态条件。 */ t_thrd.proc->workingVersionNum = pg_atomic_read_u32(&WorkingGrandVersionNum); @@ -793,55 +891,55 @@ static bool InitSession(knl_session_context* session) (void)pg_atomic_add_fetch_u32(&g_instance.comm_cxt.current_gsrewind_count, 1); } - /* Init GUC option for this session. */ + /* 初始化会话的 GUC 选项 */ InitializeGUCOptions(); - /* Read in remaining GUC variables */ + /* 读取剩余的 GUC 变量 */ read_nondefault_variables(); - /* now safe to ereport to client */ + /* 现在安全地向客户端发送错误报告 */ t_thrd.postgres_cxt.whereToSendOutput = DestRemote; - /* Init port and connection. */ + /* 初始化端口和连接 */ if (!InitPort(session->proc_cxt.MyProcPort)) { - /* reset some status below */ + /* 重置以下状态 */ if (!disable_sig_alarm(false)) { ereport(FATAL, (errmsg("could not disable timer for startup packet timeout"))); } return false; } - /* switch version number to that gotten from port */ + /* 将版本号切换到从端口获得的版本号 */ t_thrd.proc->workingVersionNum = session->proc_cxt.MyProcPort->SessionVersionNum; - /* add process definer mode */ + /* 添加进程定义模式 */ Reset_Pseudo_CurrentUserId(); SetProcessingMode(InitProcessing); SessionSetBackendOptions(); - /* initialize guc variables which need to be sended to stream threads */ + /* 初始化需要发送给流线程的 GUC 变量 */ #ifdef PGXC if (IS_PGXC_DATANODE && IsUnderPostmaster) { init_sync_guc_variables(); } #endif - /* We need to allow SIGINT, etc during the initial transaction */ + /* 我们需要在初始事务期间允许 SIGINT 等信号 */ gs_signal_setmask(&t_thrd.libpq_cxt.UnBlockSig, NULL); - /* init invalid msg slot */ + /* 初始化无效消息槽 */ SharedInvalBackendInit(false, true); - /* init pgstat slot */ + /* 初始化 pgstat 插槽 */ pgstat_initialize_session(); - /* Do local initialization of file, storage and buffer managers */ + /* 执行文件、存储和缓冲管理器的本地初始化 */ InitFileAccess(); smgrinit(); - /* openGauss init. */ + /* openGauss 初始化 */ char* dbname = session->proc_cxt.MyProcPort->database_name; char* username = session->proc_cxt.MyProcPort->user_name; t_thrd.proc_cxt.PostInit->SetDatabaseAndUser(dbname, InvalidOid, username); @@ -863,12 +961,12 @@ static bool InitSession(knl_session_context* session) SendSessionIdxToClient(); - /* init param hash table for sending set message */ + /* 初始化用于发送设置消息的参数哈希表 */ if (IS_PGXC_COORDINATOR) { init_set_params_htab(); } - /* check if memory already reach the max_dynamic_memory */ + /* 检查内存是否已经达到最大动态内存 */ if (t_thrd.utils_cxt.gs_mp_inited && processMemInChunks > maxChunksPerProcess) { ereport(ERROR, (errcode(ERRCODE_OUT_OF_LOGICAL_MEMORY), @@ -883,9 +981,10 @@ static bool InitSession(knl_session_context* session) return true; } +// 初始化端口(参数:端口指针) static bool InitPort(Port* port) { - /* session version number is initialized to process version number */ + /* 会话版本号初始化为进程版本号 */ port->SessionVersionNum = pg_atomic_read_u32(&WorkingGrandVersionNum); PortInitialize(port, NULL); @@ -903,6 +1002,7 @@ static bool InitPort(Port* port) return true; } +// 向客户端发送会话索引 static void SendSessionIdxToClient() { GenerateCancelKey(true); @@ -917,17 +1017,19 @@ static void SendSessionIdxToClient() } } +// 重置信号处理函数 static void ResetSignalHandle() { - // may change during thread init port(accept new connection) + // 可能在初始化端口时(接受新连接)发生变化 (void)gspqsignal(SIGALRM, handle_sig_alarm); - (void)gspqsignal(SIGQUIT, quickdie); /* hard crash time */ - (void)gspqsignal(SIGTERM, die); /* cancel current query and exit */ + (void)gspqsignal(SIGQUIT, quickdie); /* 强制崩溃时 */ + (void)gspqsignal(SIGTERM, die); /* 取消当前查询并退出 */ gs_signal_setmask(&t_thrd.libpq_cxt.UnBlockSig, NULL); - /* not necessary, unblock for sure */ + /* 不是必需的,确保信号解除阻塞 */ gs_signal_unblock_sigusr2(); } +// 设置会话后端选项 static void SessionSetBackendOptions() { char** av = NULL; @@ -935,11 +1037,10 @@ static void SessionSetBackendOptions() int ac = 0; /* - * Now, build the argv vector that will be given to PostgresMain. + * 现在,构建将传递给 PostgresMain 的 argv 向量。 * - * The maximum possible number of commandline arguments that could come - * from ExtraOptions is (strlen(ExtraOptions) + 1) / 2; see - * pg_split_opts(). + * 来自 ExtraOptions 的可能的命令行参数的最大数量是 (strlen(ExtraOptions) + 1) / 2; + * 请参阅 pg_split_opts()。 */ maxac = (strlen(g_instance.ExtraOptions) + 1) / 2 + 2; @@ -949,6 +1050,6 @@ static void SessionSetBackendOptions() pg_split_opts(av, &ac, g_instance.ExtraOptions); av[ac] = NULL; - /* Parse command-line options. */ + /* 解析命令行选项 */ process_postgres_switches(ac, av, PGC_POSTMASTER, NULL); -} +} \ No newline at end of file -- 2.34.1 From b3e06e4ee1ccf64889a94ec2d3c704926c5801a2 Mon Sep 17 00:00:00 2001 From: noah <1204149038@qq.com> Date: Sat, 30 Sep 2023 18:33:34 +0800 Subject: [PATCH 37/50] about new work --- src/gausskernel/storage/bulkload/dist_fdw.cpp | 793 +++++++++-- .../storage/bulkload/foreignroutine.cpp | 4 +- src/gausskernel/storage/bulkload/parser.cpp | 1251 +++++++++++++---- .../storage/bulkload/roach_adpter.cpp | 77 +- .../storage/bulkload/vecforeignroutine.cpp | 122 +- 5 files changed, 1823 insertions(+), 424 deletions(-) diff --git a/src/gausskernel/storage/bulkload/dist_fdw.cpp b/src/gausskernel/storage/bulkload/dist_fdw.cpp index 44155d30f..b692ddc5e 100644 --- a/src/gausskernel/storage/bulkload/dist_fdw.cpp +++ b/src/gausskernel/storage/bulkload/dist_fdw.cpp @@ -129,50 +129,49 @@ const int GSOBS_PREFIX_LEN = strlen(GSOBS_PREFIX); */ static struct DistFdwOption loader_valid_options[] = { /* File options */ - { optLocation, ForeignTableRelationId }, - { OPTION_NAME_REGION, ForeignTableRelationId }, + {optLocation, ForeignTableRelationId}, + {OPTION_NAME_REGION, ForeignTableRelationId}, /* Format options */ /* oids option is not supported */ - { optFormat, ForeignTableRelationId }, - { optHeader, ForeignTableRelationId }, - { optDelimiter, ForeignTableRelationId }, - { optQutote, ForeignTableRelationId }, - { optEscape, ForeignTableRelationId }, - { optNull, ForeignTableRelationId }, - { optEncoding, ForeignTableRelationId }, - { optFillMissFields, ForeignTableRelationId }, - { optMode, ForeignTableRelationId }, - { optWithoutEscaping, ForeignTableRelationId }, - { optForceNotNull, AttributeRelationId }, - { optEol, ForeignTableRelationId }, - { optFix, ForeignTableRelationId }, - { optFileHeader, ForeignTableRelationId }, - { optOutputFilePrefix, ForeignTableRelationId }, - { optOutputFixAlignment, ForeignTableRelationId }, - { optRejectLimit, ForeignTableRelationId }, - { optIgnoreExtraData, ForeignTableRelationId }, + {optFormat, ForeignTableRelationId}, + {optHeader, ForeignTableRelationId}, + {optDelimiter, ForeignTableRelationId}, + {optQutote, ForeignTableRelationId}, + {optEscape, ForeignTableRelationId}, + {optNull, ForeignTableRelationId}, + {optEncoding, ForeignTableRelationId}, + {optFillMissFields, ForeignTableRelationId}, + {optMode, ForeignTableRelationId}, + {optWithoutEscaping, ForeignTableRelationId}, + {optForceNotNull, AttributeRelationId}, + {optEol, ForeignTableRelationId}, + {optFix, ForeignTableRelationId}, + {optFileHeader, ForeignTableRelationId}, + {optOutputFilePrefix, ForeignTableRelationId}, + {optOutputFixAlignment, ForeignTableRelationId}, + {optRejectLimit, ForeignTableRelationId}, + {optIgnoreExtraData, ForeignTableRelationId}, /* OBS only options */ - { optChunkSize, ForeignTableRelationId }, - { optEncrypt, ForeignTableRelationId }, - { optAccessKey, ForeignTableRelationId }, - { optSecretAccessKey, ForeignTableRelationId }, + {optChunkSize, ForeignTableRelationId}, + {optEncrypt, ForeignTableRelationId}, + {optAccessKey, ForeignTableRelationId}, + {optSecretAccessKey, ForeignTableRelationId}, /* * bulkload compatible illegal chars option */ - { optCompatibleIllegalChars, ForeignTableRelationId }, + {optCompatibleIllegalChars, ForeignTableRelationId}, /* * bulkload datetime format options */ - { optDateFormat, ForeignTableRelationId }, - { optTimeFormat, ForeignTableRelationId }, - { optTimestampFormat, ForeignTableRelationId }, - { optSmalldatetimeFormat, ForeignTableRelationId }, + {optDateFormat, ForeignTableRelationId}, + {optTimeFormat, ForeignTableRelationId}, + {optTimestampFormat, ForeignTableRelationId}, + {optSmalldatetimeFormat, ForeignTableRelationId}, /* Sentinel */ - { NULL, InvalidOid } -}; + {NULL, InvalidOid}}; PG_FUNCTION_INFO_V1(dist_fdw_handler); PG_FUNCTION_INFO_V1(dist_fdw_validator); @@ -297,48 +296,113 @@ Datum dist_fdw_handler(PG_FUNCTION_ARGS) * * Raise an ERROR if the option or its value is considered invalid. */ +/* + * 功能:分布式外部数据导入的选项验证函数 + * + * 参数列表: + * PG_FUNCTION_ARGS:PostgreSQL UDF 函数参数 + * + * 返回值: + * 无 + */ Datum dist_fdw_validator(PG_FUNCTION_ARGS) { + // 解析外部表选项列表 List *options_list = untransformRelOptions(PG_GETARG_DATUM(0)); + + // 获取外部表的目录 OID Oid catalog = PG_GETARG_OID(1); + + // 创建分布式导入计划状态和执行状态结构 DistImportPlanState planstate; DistImportExecutionState execState; - errno_t rc = EOK; + // 初始化计划状态结构的内存 + errno_t rc = EOK; rc = memset_s(&planstate, sizeof(planstate), 0, sizeof(planstate)); securec_check(rc, "\0", "\0"); + + // 初始化执行状态结构的内存 rc = memset_s(&execState, sizeof(execState), 0, sizeof(execState)); securec_check(rc, "\0", "\0"); + + // 处理外部表选项,填充计划状态结构 ProcessDistImportOptions(&planstate, options_list, true, catalog != ForeignTableRelationId); - ProcessCopyOptions((CopyState) & execState, !planstate.writeOnly, planstate.options); + + // 处理COPY选项,填充执行状态结构 + ProcessCopyOptions((CopyState)&execState, !planstate.writeOnly, planstate.options); + + // 验证文件编码 VerifyEncoding(planstate.fileEncoding); + + // 如果文件格式不是TEXT并且使用了SHARED模式,则报错 if (execState.fileformat != FORMAT_TEXT && IS_SHARED_MODE(planstate.mode)) ereport(ERROR, (errcode(ERRCODE_FDW_ERROR), errmsg("SHARED mode can only be used with TEXT format"))); + + // 返回空结果 PG_RETURN_VOID(); } +/* + * 功能:获取外部错误记录表的缓存条目 + * + * 参数列表: + * relid:外部表的OID + * distSessionKey:分布式会话密钥 + * + * 返回值: + * 如果找到相关选项并成功获取ErrorCacheEntry,返回该条目的指针,否则返回NULL。 + */ ErrorCacheEntry *GetForeignErrCacheEntry(Oid relid, uint32 distSessionKey) { + // 声明并初始化一个指向ErrorCacheEntry结构的指针entry,初始值为NULL ErrorCacheEntry *entry = NULL; + + // 使用GetForeignTableOptionByName函数查找与relid相关的外部表选项optErrorRel DefElem *def = GetForeignTableOptionByName(relid, optErrorRel); + // 如果找到了选项def if (def != NULL) { + // 声明并初始化一个指向RangeTblEntry结构的指针rte,初始值为NULL RangeTblEntry *rte = NULL; + + // 从选项def中获取外部错误记录表的表名relname char *relname = strVal(def->arg); + + // 使用表名relname和关联表的命名空间获取错误记录表的OID Oid errorOid = get_relname_relid(relname, get_rel_namespace(relid)); + // 创建一个新的ErrorCacheEntry结构,并将其分配给entry entry = makeNode(ErrorCacheEntry); + + // 如果找到的errorOid是无效的 if (errorOid == InvalidOid) + // 报告一个错误,指明无法找到错误记录表 ereport(ERROR, (errcode(ERRCODE_FDW_ERROR), errmsg("can't find error record table \"%s\"", relname))); + + // 为entry设置filename,生成唯一的缓存名称前缀,使用errorOid和distSessionKey entry->filename = generate_unique_cache_name_prefix(errorOid, distSessionKey); + // 分配并初始化一个新的RangeTblEntry结构,并将其分配给rte rte = (RangeTblEntry *)makeNode(RangeTblEntry); + + // 设置rte的类型为RTE_RELATION,表示这是一个关系表 rte->rtekind = RTE_RELATION; + + // 设置rte的relid为errorOid,表示关联的表的OID rte->relid = errorOid; + + // 获取并设置rte的relkind,表示关联表的类型(如表、视图等) rte->relkind = get_rel_relkind(errorOid); + + // 设置rte的requiredPerms为ACL_INSERT,表示所需的权限是插入权限 rte->requiredPerms = ACL_INSERT; + + // 将rte设置为entry的rte字段,表示与错误记录表关联的RangeTblEntry entry->rte = rte; } + + // 返回获取的ErrorCacheEntry结构指针,如果没有找到相关的选项,则为NULL return entry; } @@ -346,14 +410,28 @@ ErrorCacheEntry *GetForeignErrCacheEntry(Oid relid, uint32 distSessionKey) * Check if the provided option is one of the valid options. * context is the Oid of the catalog holding the object the option is for. */ +/* + * 功能:检查选项是否在有效选项列表中 + * + * 参数列表: + * option:要检查的选项名称 + * context:选项的上下文(例如,ForeignTableRelationId) + * + * 返回值: + * 如果选项在有效选项列表中且上下文匹配,返回true;否则返回false。 + */ static bool is_valid_option(const char *option, Oid context) { struct DistFdwOption *opt = NULL; + // 遍历有效选项列表 for (opt = loader_valid_options; opt->optname; opt++) { + // 检查选项上下文和选项名称是否匹配 if (context == opt->optcontext && strcmp(opt->optname, option) == 0) return true; } + + // 未找到匹配的选项 return false; } @@ -363,6 +441,17 @@ static bool is_valid_option(const char *option, Oid context) * We have to separate out "filename" from the other options because * it must not appear in the options list passed to the core COPY code. */ +/* + * 功能:从外部表、外部服务器和外部数据包装器中提取选项 + * + * 参数列表: + * foreigntableid:外部表的OID + * locaionts:外部表的LOCATION选项值(输出参数) + * other_options:包含除LOCATION以外的其他选项的选项列表(输出参数) + * + * 返回值: + * 无 + */ void distGetOptions(Oid foreigntableid, char **locaionts, List **other_options) { ForeignTable *table = NULL; @@ -415,19 +504,38 @@ void distGetOptions(Oid foreigntableid, char **locaionts, List **other_options) * we add the parameter (void* additionalData). This parameter may not be * used by fileAnalyzeForeignTable function. */ +/* + * 功能:分析外部表的函数 + * + * 参数列表: + * relation:关系对象 + * func:采集样本行的函数指针 + * totalPageCount:总页数(输出参数) + * additionalData:额外的分析数据(通常是外部表的相关信息) + * estimate_table_rownum:是否估算表的行数 + * + * 返回值: + * 如果成功分析外部表,返回true;否则返回false。 + */ static bool distAnalyzeForeignTable(Relation relation, AcquireSampleRowsFunc *func, BlockNumber *totalPageCount, void *additionalData, bool estimate_table_rownum) { + // 如果外部表是只写的,不进行分析 if (isWriteOnlyFt(RelationGetRelid(relation))) { return false; } + + // 如果外部表不是OBS CSV或TXT格式,不进行分析 if (!IS_OBS_CSV_TXT_FOREIGN_TABLE(RelationGetRelid(relation))) { return false; } - if (additionalData == NULL) + // 如果additionalData为NULL,表示不需要进行分析,直接返回true + if (additionalData == NULL) { return true; + } + // 获取外部表的总页数,并将结果存储在totalPageCount中 (*totalPageCount) = getPageCountForFt(additionalData); return true; @@ -439,31 +547,54 @@ static bool distAnalyzeForeignTable(Relation relation, AcquireSampleRowsFunc *fu * @in foreignTableId, the given foreign table Oid. * @return return the dn task. */ +/* + * 功能:为分布式OBS外部表进行调度 + * + * 参数列表: + * foreignTableId:外部表的OID + * + * 返回值: + * 如果成功,返回任务列表;否则返回NULL。 + */ List *CNSchedulingForDistOBSFt(Oid foreignTableId) { + // 获取外部表的URL char *url = HdfsGetOptionValue(foreignTableId, optLocation); + // 获取外部表的Region Code char *regionCode = HdfsGetOptionValue(foreignTableId, OPTION_NAME_REGION); + // 初始化新的URL char *newUrl = url; errno_t rc = EOK; + // 断言URL不为空 Assert(url != NULL); + // 如果URL以OBS_PREFIX开头,尝试重建所有Location选项 if (url != NULL && pg_strncasecmp(url, OBS_PREFIX, OBS_PREfIX_LEN) == 0) { /* the regionCode may be NULL, we will get the default region. */ newUrl = rebuildAllLocationOptions(regionCode, url); } + // 反序列化URL列表 List *urlList = DeserializeLocations(newUrl); + // 初始化加密标志 bool encrypt = false; + // 获取服务器加密选项 DefElem *encryptStr = HdfsGetOptionDefElem(foreignTableId, OPTION_NAME_SERVER_ENCRYPT); + // 获取Access Key char *ak = HdfsGetOptionValue(foreignTableId, OPTION_NAME_SERVER_AK); + // 获取Secret Access Key char *sak = HdfsGetOptionValue(foreignTableId, OPTION_NAME_SERVER_SAK); + // 如果存在加密选项,获取加密标志 if (encryptStr != NULL) { encrypt = defGetBoolean(encryptStr); } + // 获取OBS文件列表 List *obsFileList = getOBSFileList(urlList, encrypt, ak, sak, true); + // 初始化任务列表 List *totalTask = NIL; + // 获取外部表的分布信息 List *nodeList = NIL; RelationLocInfo *rlc = GetRelationLocInfo(foreignTableId); if (rlc != NULL) { @@ -471,17 +602,22 @@ List *CNSchedulingForDistOBSFt(Oid foreignTableId) } /* get all data node names */ + // 获取所有数据节点名称 List *dnNames = !nodeList ? PgxcNodeGetAllDataNodeNames() : PgxcNodeGetDataNodeNames(nodeList); /* assign obs file to each data node */ + // 将OBS文件分配给每个数据节点 assignOBSFileToDataNode(obsFileList, &totalTask, dnNames); + // 释放关系信息 pfree(rlc); + // 清除并释放Secret Access Key的内存 if (sak != NULL) { rc = memset_s(sak, strlen(sak), 0, strlen(sak)); securec_check(rc, "\0", "\0"); pfree(sak); } + // 返回任务列表 return totalTask; } #endif @@ -494,6 +630,16 @@ List *CNSchedulingForDistOBSFt(Oid foreignTableId) * @out * @return */ +/* + * 功能:构建与外部表关联的扫描状态信息 + * + * 参数列表: + * relation:关联的外部表 + * splitinfo:分布式外部表文件段信息 + * + * 返回值: + * 返回构建的ForeignScanState对象。 + */ ForeignScanState *buildRelatedStateInfo(Relation relation, DistFdwFileSegment *splitinfo) { ForeignScanState *scanState = NULL; @@ -505,29 +651,36 @@ ForeignScanState *buildRelatedStateInfo(Relation relation, DistFdwFileSegment *s TupleDesc tupleDescriptor = RelationGetDescr(relation); int columnCount = tupleDescriptor->natts; + // 为列值和空值分配内存 Datum *columnValues = (Datum *)palloc0(columnCount * sizeof(Datum)); bool *columnNulls = (bool *)palloc0(columnCount * sizeof(bool)); + // 创建文件段信息 fileInfo = makeNode(DistFdwFileSegment); fileInfo->filename = splitinfo->filename; + // 创建数据节点任务 DistFdwDataNodeTask *fileSplitTask = NULL; /* Put file information into SplitMap struct */ + // 将文件信息放入数据节点任务 fileSplitTask = makeNode(DistFdwDataNodeTask); fileWorkList = lappend(fileWorkList, (void *)fileInfo); fileSplitTask->dnName = g_instance.attr.attr_common.PGXCNodeName; fileSplitTask->task = fileWorkList; + // 将数据节点任务添加到HDFS节点工作列表 HDFSNodeWorkList = lappend(HDFSNodeWorkList, fileSplitTask); /* setup foreign scan plan node */ + // 设置外部扫描计划节点 ForeignScan *foreignScan = NULL; foreignScan = makeNode(ForeignScan); - foreignScan->fdw_private = lappend(foreignScan->fdw_private, - makeDefElem(pstrdup(optTaskList), (Node *)HDFSNodeWorkList)); + foreignScan->fdw_private = + lappend(foreignScan->fdw_private, makeDefElem(pstrdup(optTaskList), (Node *)HDFSNodeWorkList)); ; /* setup tuple slot */ + // 设置元组槽 scanTupleSlot = MakeTupleTableSlot(true, tupleDescriptor->tdTableAmType); scanTupleSlot->tts_tupleDescriptor = tupleDescriptor; scanTupleSlot->tts_values = columnValues; @@ -539,10 +692,12 @@ ForeignScanState *buildRelatedStateInfo(Relation relation, DistFdwFileSegment *s scanState->ss.ps.plan = (Plan *)foreignScan; scanState->ss.ss_ScanTupleSlot = scanTupleSlot; - scanState->scanMcxt = AllocSetContextCreate(CurrentMemoryContext, "analyze for Foreign Scan", - ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, - ALLOCSET_DEFAULT_MAXSIZE); + // 分配扫描内存上下文 + scanState->scanMcxt = + AllocSetContextCreate(CurrentMemoryContext, "analyze for Foreign Scan", ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); + // 开始分布式导入操作 distImportBegin(scanState, 0); return scanState; @@ -570,7 +725,8 @@ ForeignScanState *buildRelatedStateInfo(Relation relation, DistFdwFileSegment *s * input param @additionalData:we use this parameter to pass data. */ static int distAcquireSampleRows(Relation relation, int logLevel, HeapTuple *sampleRows, int targetRowCount, - double *totalRowCount, double *deadRows, void *additionalData, bool estimate_table_rownum) + double *totalRowCount, double *deadRows, void *additionalData, + bool estimate_table_rownum) { /* We report "analyze" nothing if additionalData is null. */ if (additionalData == NULL) { @@ -613,9 +769,9 @@ static int distAcquireSampleRows(Relation relation, int logLevel, HeapTuple *sam * Use per-tuple memory context to prevent leak of memory used to read and * parse rows from the file using ReadLineFromFile and FillTupleSlot. */ - tupleContext = AllocSetContextCreate(CurrentMemoryContext, "TEX/CSV OBS temporary context", - ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, - ALLOCSET_DEFAULT_MAXSIZE); + tupleContext = + AllocSetContextCreate(CurrentMemoryContext, "TEX/CSV OBS temporary context", ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); /* prepare for sampling rows */ selectionState = anl_init_selection_state(targetRowCount); @@ -655,7 +811,8 @@ static int distAcquireSampleRows(Relation relation, int logLevel, HeapTuple *sam * reach the end of the relation. */ if (sampleRowCount < targetRowCount) { - sampleRows[sampleRowCount++] = (HeapTuple)tableam_tops_form_tuple(tupleDescriptor, columnValues, columnNulls, HEAP_TUPLE); + sampleRows[sampleRowCount++] = + (HeapTuple)tableam_tops_form_tuple(tupleDescriptor, columnValues, columnNulls, HEAP_TUPLE); } else { /* * If we need to compute a new S value, we must use the "not yet @@ -675,7 +832,8 @@ static int distAcquireSampleRows(Relation relation, int logLevel, HeapTuple *sam Assert(rowIndex < targetRowCount); heap_freetuple(sampleRows[rowIndex]); - sampleRows[rowIndex] = (HeapTuple)tableam_tops_form_tuple(tupleDescriptor, columnValues, columnNulls, HEAP_TUPLE); + sampleRows[rowIndex] = + (HeapTuple)tableam_tops_form_tuple(tupleDescriptor, columnValues, columnNulls, HEAP_TUPLE); } rowCountToSkip -= 1; } @@ -719,7 +877,17 @@ static int distAcquireSampleRows(Relation relation, int logLevel, HeapTuple *sam (*deadRows) = 0; /* @hdfs dead rows is no means to foreign table */ return sampleRowCount; } - +/* + * 功能:检查是否需要选择性的二进制转换 + * + * 参数列表: + * baserel:基本关系信息 + * foreigntableid:外部表的OID + * columns:需要选择性转换的列名列表(输出参数) + * + * 返回值: + * 如果需要进行选择性二进制转换,返回true;否则返回false。 + */ bool check_selective_binary_conversion(RelOptInfo *baserel, Oid foreigntableid, List **columns) { ForeignTable *table = NULL; @@ -819,6 +987,19 @@ bool check_selective_binary_conversion(RelOptInfo *baserel, Oid foreigntableid, /* * read raw buffer */ +/* + * 功能:从文件中读取原始数据到缓冲区 + * + * 参数列表: + * file:文件指针 + * buf:用于存储读取的数据的缓冲区 + * pos:文件中的位置,从该位置开始读取数据 + * len:要读取的数据长度 + * filename:文件名(用于错误处理) + * + * 返回值: + * 返回读取的字节数,如果发生错误,则抛出错误并终止程序。 + */ static int getRawBuffer(FILE *file, char *buf, long pos, long len, char *filename) { int bytesread = 0; @@ -831,13 +1012,27 @@ static int getRawBuffer(FILE *file, char *buf, long pos, long len, char *filenam ereport(ERROR, (errcode_for_file_access(), errmsg("could not read from file: %m"))); } - Assert(bytesread == len); + Assert(bytesread == len); // 确保读取的字节数等于请求的长度 return bytesread; } /* * search char from begin to end */ +/* + * 功能:在文件中向前搜索指定字符 + * + * 参数列表: + * file:文件指针 + * buf:用于存储读取的数据的缓冲区 + * begin:搜索范围的起始位置 + * end:搜索范围的结束位置 + * fileName:文件名(用于错误处理) + * searchChar:要搜索的字符 + * + * 返回值: + * 如果找到指定字符,返回其在文件中的位置;否则返回0。 + */ static long searchForward(FILE *file, char *buf, long begin, long end, char *fileName, char searchChar) { long readStartPos = end; @@ -873,6 +1068,18 @@ static long searchForward(FILE *file, char *buf, long begin, long end, char *fil /* * Divide file into segments */ +/* + * 功能:将文件分割成多个文件段 + * + * 参数列表: + * fileName:要分割的文件名 + * fileSize:文件的总大小 + * segmentlist:存储文件段的列表(输出参数) + * + * 注意: + * 该函数将指定的文件分割成多个文件段,每个文件段都包含文件的一部分。 + * 文件段列表将作为输出参数返回。 + */ void divideFileSegment(char *fileName, long fileSize, List **segmentlist) { Assert(fileName && segmentlist && fileSize >= 0); @@ -965,6 +1172,15 @@ void divideFileSegment(char *fileName, long fileSize, List **segmentlist) /* * Through each directory to find the matching files, and segment it if the file large than 64MB */ +/* + * 功能:获取文件段列表 + * + * 参数列表: + * urllist:包含文件URL的列表 + * + * 返回值: + * 返回一个包含文件段的列表。 + */ List *getFileSegmentList(List *urllist) { char *path = NULL; @@ -1001,6 +1217,16 @@ List *getFileSegmentList(List *urllist) /* * assign file segments to data node */ +/* + * 功能:将文件段列表分配给数据节点任务列表 + * + * 参数列表: + * segmentlist:文件段列表 + * dnNames:数据节点名称列表 + * + * 返回值: + * 返回包含数据节点任务的列表。 + */ List *assignFileSegmentList(List *segmentlist, List *dnNames) { ListCell *lc = NULL; @@ -1333,7 +1559,15 @@ List *assignTaskToDataNode(List *urllist, ImportMode mode, List *nodeList, int d return totalTask; } - +/* + * 功能:决定是否接受一个错误记录 + * + * 参数列表: + * festate:分布式导入执行状态 + * + * 返回值: + * 如果需要保存错误记录且尚未达到拒绝限制,则返回true,否则返回false。 + */ bool DoAcceptOneError(DistImportExecutionState *festate) { bool do_accept = false; @@ -1345,7 +1579,15 @@ bool DoAcceptOneError(DistImportExecutionState *festate) } return do_accept; } - +/* + * 功能:决定是否接受一个错误记录 + * + * 参数列表: + * cstate:COPY操作的状态信息 + * + * 返回值: + * 如果需要记录错误信息(log_errors或logErrorsData为true)且尚未达到拒绝限制,则返回true,否则返回false。 + */ bool DoAcceptOneError(CopyState cstate) { bool do_accept = false; @@ -1358,7 +1600,18 @@ bool DoAcceptOneError(CopyState cstate) } return do_accept; } - +/* + * 功能:初始化COPY操作的状态信息 + * + * 参数列表: + * cstate:COPY操作的状态信息 + * isImport:指示是否是导入操作 + * rel:与COPY操作关联的关系对象 + * raw_query:查询树中的原始节点 + * queryString:包含COPY操作的原始查询字符串 + * attnamelist:需要处理的属性名称列表 + * options:COPY操作的选项列表 + */ static void DistBegin(CopyState cstate, bool isImport, Relation rel, Node *raw_query, const char *queryString, List *attnamelist, List *options) { @@ -1460,8 +1713,8 @@ static void DistBegin(CopyState cstate, bool isImport, Relation rel, Node *raw_q * are the same, we must apply pg_any_to_server() to validate data in * multibyte encodings. */ - cstate->need_transcoding = (cstate->file_encoding != GetDatabaseEncoding() || - pg_database_encoding_max_length() > 1); + cstate->need_transcoding = + (cstate->file_encoding != GetDatabaseEncoding() || pg_database_encoding_max_length() > 1); /* See Multibyte encoding comment above */ cstate->encoding_embeds_ascii = PG_ENCODING_IS_CLIENT_ONLY(cstate->file_encoding); @@ -1470,7 +1723,17 @@ static void DistBegin(CopyState cstate, bool isImport, Relation rel, Node *raw_q (void)MemoryContextSwitchTo(oldcontext); } - +/* + * 功能:初始化分布式导入操作的状态信息 + * + * 参数列表: + * importstate:分布式导入操作的状态信息 + * rel:与导入操作关联的关系对象 + * filename:导入的文件名 + * attnamelist:需要处理的属性名称列表 + * options:导入操作的选项列表 + * totalTask:总的任务列表 + */ void InitDistImport(DistImportExecutionState *importstate, Relation rel, const char *filename, List *attnamelist, List *options, List *totalTask) { @@ -1652,9 +1915,9 @@ static const char *FetchAndCheckFormat(DefElem *defel); static void distExportSwitchSegment(CopyState cstate, Relation rel); #ifndef WIN32 - static const char delimiter = '/'; +static const char delimiter = '/'; #else - static const char delimiter = '\\'; +static const char delimiter = '\\'; #endif static const uint64 distExportMaxSegSize = (1 << 30); @@ -1663,9 +1926,20 @@ static const uint64 distExportMaxSegSize = (1 << 30); * distExportRelUpdatable * Determine whether a foreign table supports INSERT, UPDATE and/or DELETE. */ +/* + * 功能:检查分布式外部表的可更新性 + * + * 参数列表: + * rel:要检查的关系对象 + * + * 返回值: + * 返回表示可更新性的位掩码,根据可用的命令而定 + * 位掩码为 CMD_INSERT,表示可以执行 INSERT 操作 + * 如果关系对象不是分布式外部表或者是只写模式,返回 0,表示不可更新 + */ static int distExportRelUpdatable(Relation rel) { - // for dist foreign talbe, only checking table's permition is enough + // 对于分布式外部表,仅检查表的权限即可 ForeignTable *table = GetForeignTable(RelationGetRelid(rel)); if (table != NULL && table->write_only) return (1 << CMD_INSERT); @@ -1677,61 +1951,109 @@ static int distExportRelUpdatable(Relation rel) * distExportPlan * Plan an INSERT operation on a foreign table */ +/* + * 功能:生成分布式导入计划 + * + * 参数列表: + * root:PlannerInfo 对象,用于查询规划的信息 + * plan:ModifyTable 计划节点,表示修改表的操作 + * resultRelation:索引表示结果关系的位置 + * subplan_index:子计划的索引 + * + * 返回值: + * 返回一个包含 FDW 私有数据的列表 + * 如果操作是更新 (CMD_UPDATE) 或删除 (CMD_DELETE),返回一个空列表 (NIL) + * 如果操作是插入 (CMD_INSERT) 并且位置不在本地,则返回一个包含会话密钥和任务列表的列表 + */ static List *distExportPlan(PlannerInfo *root, ModifyTable *plan, Index resultRelation, int subplan_index) { - CmdType operation = plan->operation; - List *fdwPriv = NIL; - DistImportPlanState *planstate = NULL; - RangeTblEntry *rte = NULL; - char *location = NULL; + CmdType operation = plan->operation; // 获取操作类型 + List *fdwPriv = NIL; // 用于存储 FDW 私有数据的列表 + DistImportPlanState *planstate = NULL; // 分布式导入计划的状态 + RangeTblEntry *rte = NULL; // 范围表条目 + char *location = NULL; // 外部表的位置 + // 如果不是流式计划,则抛出错误 if (!IS_STREAM_PLAN) - ereport(ERROR, (errcode(ERRCODE_FDW_ERROR), errmsg("Un-support feature"))); + ereport(ERROR, (errcode(ERRCODE_FDW_ERROR), errmsg("Unsupported feature"))); + // 如果操作是更新 (CMD_UPDATE) 或删除 (CMD_DELETE),则返回空列表 if (CMD_UPDATE == operation || CMD_DELETE == operation) return NIL; + // 断言操作是插入 (CMD_INSERT) Assert(CMD_INSERT == operation); + + // 获取结果关系的范围表条目 rte = planner_rt_fetch(resultRelation, root); + + // 创建 DistImportPlanState 对象并初始化 planstate = (DistImportPlanState *)palloc0(sizeof(DistImportPlanState)); + + // 获取外部表的导入选项 GetDistImportOptions(rte->relid, planstate); + + // 获取外部表的位置 location = strVal(lfirst(list_head(planstate->source))); + + // 如果外部表位置不在本地 if (!is_local_location(location)) { uint32 distSessionKey; List *tasklist = NIL; // generate session key distSessionKey = generate_unique_id(>_sessionId); + // 将会话密钥添加到 FDW 私有数据列表中 fdwPriv = lappend(fdwPriv, makeDefElem(pstrdup(optSessionKey), (Node *)makeInteger((long)distSessionKey))); // get task list - tasklist = assignTaskToDataNode(planstate->source, MODE_NORMAL, ((Plan *)plan)->exec_nodes->nodeList, 1, - planstate); + tasklist = + assignTaskToDataNode(planstate->source, MODE_NORMAL, ((Plan *)plan)->exec_nodes->nodeList, 1, planstate); + // 将任务列表添加到 FDW 私有数据列表中 fdwPriv = lappend(fdwPriv, makeDefElem(pstrdup(optTaskList), (Node *)tasklist)); } - return fdwPriv; + return fdwPriv; // 返回 FDW 私有数据列表 } - +/* + * 功能:初始化分布式导出 + * + * 参数列表: + * mtstate:ModifyTableState 对象,用于修改表的状态 + * rinfo:ResultRelInfo 对象,表示结果关系的信息 + * fdw_private:FDW 私有数据列表 + * subplan_index:子计划的索引 + * eflags:执行标志 + * + * 注意: + * 该函数用于初始化分布式导出操作,根据执行标志和外部表的位置决定如何处理导出。 + * 如果执行标志包括 EXEC_FLAG_EXPLAIN_ONLY,则不执行任何操作。 + * 否则,根据外部表的位置决定是本地导出还是远程导出,并执行相应的操作。 + */ static void distExportBegin(ModifyTableState *mtstate, ResultRelInfo *rinfo, List *fdw_private, int subplan_index, int eflags) { + // 如果执行标志包括 EXEC_FLAG_EXPLAIN_ONLY,则返回,不执行任何操作 if ((uint32)eflags & EXEC_FLAG_EXPLAIN_ONLY) return; - List *options = NIL; - char *location = NULL; - Relation rel = rinfo->ri_RelationDesc; - bool isRemote = false; - uint32 sessionKey = 0; - List *tasklist = NIL; + List *options = NIL; // 外部表的选项列表 + char *location = NULL; // 外部表的位置 + Relation rel = rinfo->ri_RelationDesc; // 结果关系的描述符 + bool isRemote = false; // 是否为远程导出 + uint32 sessionKey = 0; // 会话密钥 + List *tasklist = NIL; // 任务列表 + // 获取外部表的位置和选项 distGetOptions(RelationGetRelid(rel), &location, &options); + // 判断是否为远程导出 isRemote = !is_local_location(location); // Add decrpyt function for obs access key and security access key in obs options decryptOBSForeignTableOption(&options); + // 默认的输出格式后缀为 strTextFormat const char *suffix = strTextFormat; + // 检查外部表选项,获取输出格式后缀和格式化器选项 ListCell *lc = NULL; foreach (lc, options) { DefElem *defel = (DefElem *)lfirst(lc); @@ -1741,13 +2063,16 @@ static void distExportBegin(ModifyTableState *mtstate, ResultRelInfo *rinfo, Lis UntransformFormatterOption(defel); } + // 遍历 FDW 私有数据列表,获取会话密钥和任务列表 foreach (lc, fdw_private) { DefElem *def = (DefElem *)lfirst(lc); if (strcasecmp(def->defname, optSessionKey) == 0) { if (IS_PGXC_COORDINATOR) { + // 如果是协调节点,生成会话密钥并设置到 FDW 私有数据中 sessionKey = generate_unique_id(>_sessionId); def->arg = (Node *)makeInteger((long)sessionKey); } else { + // 如果是数据节点,从 FDW 私有数据中获取会话密钥 sessionKey = (uint32)intVal(def->arg); } ereport(DEBUG1, (errcode(ERRCODE_DEBUG), errmsg("Session id: %u", sessionKey))); @@ -1756,32 +2081,41 @@ static void distExportBegin(ModifyTableState *mtstate, ResultRelInfo *rinfo, Lis tasklist = (List *)def->arg; } + // 反序列化外部表的位置,生成位置列表 List *locations = DeserializeLocations(location); Assert(list_length(locations) >= 1); // coordinator doesn't create any directory or file, and has no file handle. + // 如果是流式计划或远程导出,执行远程导出操作 if (IS_STREAM_PLAN || isRemote) { char *filename = isRemote ? location : strVal(lfirst(list_head(locations))); + // 初始化外部导出,并将结果存储到结果关系的 FDW 状态中 rinfo->ri_FdwState = (void *)beginExport(rel, filename, options, isRemote, sessionKey, tasklist); } else if (!isRemote) { const char *userExportDir = strVal(lfirst(list_head(locations))); + // 如果外部导出目录以 'file://' 前缀开头,则去除前缀 if (strncmp(userExportDir, LOCAL_PREFIX, LOCAL_PREFIX_LEN) == 0) { // remove the prefix string 'file://' userExportDir = userExportDir + LOCAL_PREFIX_LEN; } + // 初始化本地导出环境 InitExportEnvirnment(userExportDir); // outfile isn't allocated in CopyState memory context, so free it by calling pfree(); // later, when switching to new segment file, do the same thing again; char *relname = get_rel_name(RelationGetRelid(rel)); char *outfile = distExportNextFileName(t_thrd.bulk_cxt.distExportDataDir, relname, suffix); + // 初始化外部导出,并将结果存储到结果关系的 FDW 状态中 rinfo->ri_FdwState = (void *)beginExport(rel, outfile, options, false, sessionKey, tasklist); + // 处理导出文件头部 ProcessFileHeader((CopyState)rinfo->ri_FdwState); + // 释放文件名内存 pfree(outfile); } #ifndef ENABLE_MULTIPLE_NODES + // 验证是否支持 GDS (Gauss Data Service) CopyState cstate_for_verify = (CopyState)rinfo->ri_FdwState; if (cstate_for_verify->remoteExport && cstate_for_verify->copy_dest == COPY_GDS) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("Un-supported feature"), @@ -1789,7 +2123,22 @@ static void distExportBegin(ModifyTableState *mtstate, ResultRelInfo *rinfo, Lis } #endif } - +/* + * 功能:执行分布式导出操作 + * + * 参数列表: + * estate:EState 对象,用于执行表达式和计划 + * resultRelInfo:ResultRelInfo 对象,表示结果关系的信息 + * slot:TupleTableSlot 对象,表示元组数据的槽 + * planSlot:TupleTableSlot 对象,表示计划数据的槽 + * + * 注意: + * 该函数执行分布式导出操作,将元组数据写入外部导出文件。 + * 首先检查当前导出文件的大小是否超过指定的最大分段大小(distExportMaxSegSize)。 + * 如果超过,则切换到新的分段文件。 + * 然后调用 execExport 函数将元组数据写入导出文件。 + * 最后返回元组数据槽。 + */ static TupleTableSlot *distExportExec(EState *estate, ResultRelInfo *resultRelInfo, TupleTableSlot *slot, TupleTableSlot *planSlot) { @@ -1804,27 +2153,55 @@ static TupleTableSlot *distExportExec(EState *estate, ResultRelInfo *resultRelIn exportResetTotalSize(cstate); } + // 执行导出操作,将元组数据写入导出文件 execExport(cstate, slot); + // 返回元组数据槽 return slot; } +/* + * 功能:结束分布式导出操作 + * + * 参数列表: + * estate:EState 对象,用于执行表达式和计划 + * resultRelInfo:ResultRelInfo 对象,表示结果关系的信息 + * + * 注意: + * 该函数用于结束分布式导出操作,释放相关资源。它检查 resultRelInfo 结构中的 ri_FdwState + * 是否为非空,如果是,则调用 endExport 函数结束导出操作并释放资源。 + */ static void distExportEnd(EState *estate, ResultRelInfo *resultRelInfo) { if (resultRelInfo->ri_FdwState != NULL) { + // 调用 endExport 函数结束导出操作并释放资源 endExport((CopyState)resultRelInfo->ri_FdwState); } } - +/* + * 功能:切换到下一个段文件进行导出 + * + * 参数列表: + * cstate:CopyState 对象,表示复制操作的状态 + * rel:Relation 对象,表示正在导出的关系 + * + * 注意: + * 该函数用于切换到下一个段文件,以继续数据导出。它首先获取关系的导出选项,查找导出格式选项, + * 并据此确定下一个段文件的文件名后缀。然后,它调用 exportAllocNewFile 函数来分配新的文件, + * 用于接下来的导出操作。 + */ static void distExportSwitchSegment(CopyState cstate, Relation rel) { List *options = NULL; char *location = NULL; + // 获取关系的导出选项 distGetOptions(RelationGetRelid(rel), &location, &options); const char *suffix = strTextFormat; ListCell *lc = NULL; + // 查找导出格式选项 foreach (lc, options) { DefElem *defel = (DefElem *)lfirst(lc); + // 如果选项的名称以 "FORMAT" 开头,则提取文件名后缀 if (strncasecmp(defel->defname, optFormat, 6) == 0) { suffix = FetchAndCheckFormat(defel); break; @@ -1833,12 +2210,28 @@ static void distExportSwitchSegment(CopyState cstate, Relation rel) // switch to next segment file to copy to char *relname = get_rel_name(RelationGetRelid(rel)); + // 生成下一个段文件的文件名 char *outfile = distExportNextFileName(t_thrd.bulk_cxt.distExportDataDir, relname, suffix); + // 分配新的文件,用于接下来的导出操作 exportAllocNewFile(cstate, outfile); + // 释放文件名内存 pfree(outfile); } // File Name: table name + txid + datanode id + segment no + suffix +/* + * 功能:生成下一个段文件的文件名 + * + * 参数列表: + * abspath:表示绝对路径的字符串 + * relname:表示关系名称的字符串 + * suffix:表示文件名后缀的字符串 + * + * 注意: + * 该函数用于生成下一个段文件的文件名,以用于数据导出操作。在同一导出事务中,段编号(segno) + * 是生成文件名的唯一变量。 (abspath, relname, distExportCurrXid) 可用于标识唯一的导出操作。 + * (distExportTimestampStr, segno) 可用于避免相同文件名的冲突。 + */ static char *distExportNextFileName(const char *abspath, const char *relname, const char *suffix) { // in the same export transaction, segment no is the only var for filenmae. @@ -1855,44 +2248,90 @@ static char *distExportNextFileName(const char *abspath, const char *relname, co return pstrdup(temp); } +/* + * 功能:获取并检查导出格式选项 + * + * 参数列表: + * defel:指向导出格式选项的 DefElem 结构 + * + * 注意: + * 该函数用于获取并检查导出格式选项,确保它的值是有效的导出格式之一("csv" 或 "text")。 + * 如果格式无效,将生成一个错误报告。如果格式有效,则返回格式字符串。 + */ static const char *FetchAndCheckFormat(DefElem *defel) { + // 获取导出格式选项的字符串值 char *format = defGetString(defel); + + // 检查格式是否为 "csv"(不区分大小写) if (0 == strncasecmp(format, strCsvFormat, 3)) return strCsvFormat; + // 检查格式是否为 "text"(不区分大小写) if (0 == strncasecmp(format, strTextFormat, 4)) return strTextFormat; - if (0 == strncasecmp(format, strFixedFormat, 5)) - return strFixedFormat; - + // 如果格式无效,生成错误报告并抛出异常 ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("only both text && csv formats are supported for foreign table"))); + + // 返回默认的文本格式,这里的返回语句实际上不会被执行 return strTextFormat; /* make complier mute */ } +/* + * 功能:生成时间戳字符串 + * + * 注意: + * 该函数用于生成一个时间戳字符串,表示当前日期和时间。它通过获取当前时间(time_t 类型) + * 并将其格式化为指定格式的字符串,然后将结果存储在全局变量 t_thrd.bulk_cxt.distExportTimestampStr 中。 + * 如果生成的字符串长度不等于 14,将生成一个错误报告。 + */ static void getTimestampStr(void) { + // 用于存储当前时间的 time_t 变量 time_t currTime; + + // 用于存储时间结构的结果 struct tm result; + + // 获取当前时间 currTime = time(NULL); + + // 将当前时间转换为本地时间,并存储在 result 结构中 localtime_r(&currTime, &result); + // 使用 strftime 函数将时间格式化为字符串,并返回生成的字符数 size_t num = strftime(t_thrd.bulk_cxt.distExportTimestampStr, 15, "%Y%m%d%H%M%S", &result); + + // 检查生成的字符串长度是否为 14 if (num != 14) { ereport(ERROR, (errcode(ERRCODE_DATA_EXCEPTION), errmsg("invalid timestamp string length"))); } + // 在生成的字符串末尾添加 null 终止符 t_thrd.bulk_cxt.distExportTimestampStr[num] = '\0'; } - +/* + * 功能:检查并获取用户导出目录 + * + * 参数列表: + * userExportDir:用户指定的导出目录的绝对路径 + * + * 注意: + * 该函数用于检查用户指定的导出目录是否存在,并将其存储在全局变量 t_thrd.bulk_cxt.distExportDataDir 中。 + * 用户导出目录必须已经存在,并且是一个目录而不是文件。函数还确保 distExportDataDir 以目录分隔符结尾, + * 并检查导出目录路径的长度是否超过了最大限制。 + */ static void CheckAndGetUserExportDir(const char *userExportDir) { // user must have created data directory, otherwise error reported. // the filepath user defined must be an existing absolute path. + // 用于存储文件状态的结构体 struct stat st; + // 用于处理字符串操作的错误码 errno_t rc = EOK; + // 使用 stat 函数检查用户导出目录是否存在,并处理相关错误 if (stat(userExportDir, &st) != 0 && errno != EEXIST) { ereport(ERROR, (errcode_for_file_access(), errmsg("%s doesn't exist, please create it first", userExportDir))); } else if (!S_ISDIR(st.st_mode)) { @@ -1916,10 +2355,22 @@ static void CheckAndGetUserExportDir(const char *userExportDir) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("location filepath is too long when importing data to foreign table"))); } - +/* + * 功能:如果必要,创建目录 + * + * 参数列表: + * mydir:要创建的目录的绝对路径 + * + * 注意: + * 该函数用于检查指定的目录是否存在,如果不存在则创建。如果目录已经存在但不是目录类型,将报错。 + * 函数首先使用 stat 函数检查目录是否存在,如果存在但不是目录类型,将报错。如果目录不存在, + * 将使用 mkdir 函数创建目录。如果目录已经被其他线程创建,函数会再次检查目录是否存在以避免竞态条件。 + */ static void CreateDirIfNecessary(const char *mydir) { + // 用于存储文件状态的结构体 struct stat st; + // 使用 stat 函数检查目录是否存在 if (0 == stat(mydir, &st)) { if (!S_ISDIR(st.st_mode)) ereport(ERROR, (errcode_for_file_access(), @@ -1933,30 +2384,49 @@ static void CreateDirIfNecessary(const char *mydir) if ((0 != mkdir(t_thrd.bulk_cxt.distExportDataDir, S_IRWXU)) && (errno != EEXIST)) ereport(ERROR, (errcode_for_file_access(), errmsg("could not create directory \"%s\": %m", mydir))); } - +/* + * 功能:初始化导出环境 + * + * 参数列表: + * userExportDir:用户指定的导出目录的绝对路径 + * + * 注意: + * 该函数用于在开始新的事务时重置所有导出环境,以准备执行导出操作。如果当前事务与上一次导出操作的 + * 事务不同,函数将重置环境,包括导出的段文件编号、当前事务编号、时间戳等。函数还会检查和获取 + * 用户指定的导出目录,并创建必要的目录结构,以便存储导出文件。 + */ static void InitExportEnvirnment(const char *userExportDir) { // reset all environments to run exporting when start a new transaction TransactionId curXid = GetCurrentTransactionId(); + // 用于字符串操作的返回码 errno_t rc = EOK; int ret; + // 如果当前事务与上一次导出操作的事务不同,执行环境重置 if (t_thrd.bulk_cxt.distExportCurrXid != curXid) { // segment number alwarys increases by 1 within the same transaction, // even though there are one or more export actions in this transaction. t_thrd.bulk_cxt.distExportNextSegNo = 0; t_thrd.bulk_cxt.distExportCurrXid = curXid; + // 获取当前时间的时间戳字符串,格式为"YYYYMMDDHHMMSS" getTimestampStr(); + // 检查并获取用户指定的导出目录 CheckAndGetUserExportDir(userExportDir); + // 获取当前导出目录的长度 int lenOfDirName = strlen(t_thrd.bulk_cxt.distExportDataDir); + // 定义顶级目录名称的长度 int topDirLen = 8; char topDir[topDirLen + 1]; + // 从时间戳字符串中获取前 8 个字符作为顶级目录名称 rc = strncpy_s(topDir, topDirLen + 1, t_thrd.bulk_cxt.distExportTimestampStr, topDirLen); securec_check(rc, "\0", "\0"); topDir[topDirLen] = '\0'; + // 获取当前节点名称的长度 int lenOfNodeName = (int)strlen(g_instance.attr.attr_common.PGXCNodeName); + // 如果导出目录的长度加上顶级目录名称长度、节点名称长度和分隔符的长度小于最大路径长度 if (lenOfDirName + topDirLen + lenOfNodeName + 2 < MAX_PATH_LEN) { // set the 1-level directory and create it if necessary. // it's named by local date, excluding time info (hours, minutes or seconds). @@ -1969,6 +2439,7 @@ static void InitExportEnvirnment(const char *userExportDir) // set the 2-level directory and create it if necessary. // it's named by node name. + // 设置二级目录并在必要时创建它,以节点名称命名 ret = strcat_s(t_thrd.bulk_cxt.distExportDataDir, MAX_PATH_LEN, g_instance.attr.attr_common.PGXCNodeName); securec_check(ret, "\0", "\0"); lenOfDirName += lenOfNodeName; @@ -1976,30 +2447,46 @@ static void InitExportEnvirnment(const char *userExportDir) t_thrd.bulk_cxt.distExportDataDir[lenOfDirName] = '\0'; CreateDirIfNecessary(t_thrd.bulk_cxt.distExportDataDir); } else + // 导出目录路径过长,导致超出最大路径长度,报错 ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("location filepath is too long when importing data to foreign table"))); } } - +/* + * 功能:转换外部表的格式器选项 + * + * 参数列表: + * stmt:CreateForeignTableStmt 对象,表示创建外部表的语句 + * + * 注意: + * 该函数用于转换外部表列定义中的格式化选项为一组选项,并将其添加到外部表的选项列表中。格式化选项 + * 通常用于指定列的格式化方式,如列名、位置、固定长度等信息。这些选项可以用于自定义外部表的数据格式 + * 以便与外部数据源兼容。 + */ static void TransformFormatterOptions(CreateForeignTableStmt *stmt) { ListCell *lc = NULL; - StringInfo str = makeStringInfo(); - bool hasFormatter = false; + StringInfo str = makeStringInfo(); // 用于构建格式化选项字符串 + bool hasFormatter = false; // 标记是否存在格式化选项 + // 遍历外部表列定义列表 foreach (lc, stmt->base.tableElts) { ColumnDef *coldef = (ColumnDef *)lfirst(lc); + // 如果列定义中包含位置信息 if (coldef->position) { Position *pos = coldef->position; + // 如果不是第一个格式化选项,添加分隔符 '.' if (lc != list_head(stmt->base.tableElts)) appendStringInfoChar(str, '.'); + // 构建格式化选项字符串,包括列名、位置和固定长度 appendStringInfo(str, "%s(%d,%d)", pos->colname, pos->position, pos->fixedlen); hasFormatter = true; } } + // 如果存在格式化选项,将其添加到外部表的选项列表中 if (hasFormatter) stmt->options = lappend(stmt->options, makeDefElem(pstrdup(optFormatter), (Node *)makeString(str->data))); } @@ -2008,23 +2495,42 @@ static void TransformFormatterOptions(CreateForeignTableStmt *stmt) * brief: Validate table definition * input param @obj: A Obj including infomation to validate when alter tabel and create table. */ -static void distValidateAlterTableStmt(Node* Obj) +/* + * 功能:验证 ALTER TABLE 语句是否合法 + * + * 参数列表: + * Obj:Node 对象,表示 ALTER TABLE 语句 + * + * 注意: + * 该函数用于验证 ALTER TABLE 语句是否合法。它首先获取目标表的 OID,并检查目标表是否为 OBS 外部表。 + * 如果目标表是 OBS 外部表,则只允许执行特定类型的 ALTER TABLE 子命令,否则允许执行其他类型的 ALTER TABLE 子命令。 + * 此外,函数还检查是否存在不支持的选项,并禁止设置、添加或删除 error_table 和 write_only 选项。 + */ +static void distValidateAlterTableStmt(Node *Obj) { - ListCell* lc = NULL; - AlterTableStmt* stmt = (AlterTableStmt*)Obj; + ListCell *lc = NULL; + AlterTableStmt *stmt = (AlterTableStmt *)Obj; + // 获取目标表的 OID Oid relId = RangeVarGetRelid(stmt->relation, NoLock, true); + // 检查目标表是否为 OBS 外部表 bool obsTbl = IS_OBS_CSV_TXT_FOREIGN_TABLE(relId); + // 遍历 ALTER TABLE 子命令列表 foreach (lc, stmt->cmds) { - AlterTableCmd* cmd = (AlterTableCmd*)lfirst(lc); + AlterTableCmd *cmd = (AlterTableCmd *)lfirst(lc); + + // 如果目标表是 OBS 外部表 if (obsTbl) { + // 只允许执行特定类型的 ALTER TABLE 子命令 if (!DIST_OBS_SUPPORT_AT_CMD(cmd->subtype)) { ereport(ERROR, (errcode(ERRCODE_FDW_ERROR), errmsg("Un-support feature"), errdetail("target table is a foreign table"))); } } else { + // 目标表不是 OBS 外部表,允许执行其他类型的 ALTER TABLE 子命令 if (!FOREIGNTABLE_SUPPORT_AT_CMD(cmd->subtype)) { + // 但禁止执行不支持的 ALTER TABLE 子命令,除非是添加索引或删除约束 if (!(AT_AddIndex == cmd->subtype || AT_DropConstraint == cmd->subtype)) { ereport(ERROR, (errcode(ERRCODE_FDW_ERROR), errmsg("Un-support feature"), errdetail("target table is a foreign table"))); @@ -2034,10 +2540,11 @@ static void distValidateAlterTableStmt(Node* Obj) /* error_table, write_only can not SET, ADD or DROP by ALTER FOREIGN TABLE OPTIONS */ if (cmd->subtype == AT_GenericOptions) { - List* defList = (List*)cmd->def; - ListCell* deflc = NULL; + List *defList = (List *)cmd->def; + ListCell *deflc = NULL; foreach (deflc, defList) { - DefElem* def = (DefElem*)lfirst(deflc); + DefElem *def = (DefElem *)lfirst(deflc); + // 禁止设置、添加或删除 error_table 和 write_only 选项 if (strcmp(def->defname, optErrorRel) == 0 || strcmp(def->defname, optWriteOnly) == 0) { ereport(ERROR, (errcode(ERRCODE_SYNTAX_ERROR), errmsg("Invalid option %s", def->defname))); } @@ -2045,84 +2552,117 @@ static void distValidateAlterTableStmt(Node* Obj) } } } - -static void distValidateCreateForeignTableStmt(Node* Obj) +/* + * 功能:验证 CREATE FOREIGN TABLE 语句是否合法 + * + * 参数列表: + * Obj:Node 对象,表示 CREATE FOREIGN TABLE 语句 + * + * 注意: + * 该函数用于验证 CREATE FOREIGN TABLE 语句是否合法。它首先检查分布类型是否为 ROUNDROBIN, + * 如果不是,则报错提示只支持 ROUNDROBIN 分布类型。然后,函数验证指定的选项是否有效, + * 并提供有效选项的提示。接着,函数检查是否指定了 error_relation,如果是则添加到扩展选项列表中。 + * 如果指定了 write_only 标志,则将其添加到扩展选项列表中。最后,函数检查是否创建了分区表,如果是则报错。 + * 同时,函数还会调用 TransformFormatterOptions 函数来处理格式化选项。 + */ +static void distValidateCreateForeignTableStmt(Node *Obj) { - CreateForeignTableStmt* stmt = (CreateForeignTableStmt*)Obj; - ListCell* lc = NULL; - List* options_list = stmt->options; - Node* errLog = stmt->error_relation; + CreateForeignTableStmt *stmt = (CreateForeignTableStmt *)Obj; + ListCell *lc = NULL; + List *options_list = stmt->options; + Node *errLog = stmt->error_relation; Oid catalog = ForeignTableRelationId; - DistributeBy* DisByOp = ((CreateStmt*)Obj)->distributeby; + DistributeBy *DisByOp = ((CreateStmt *)Obj)->distributeby; + // 检查分布类型是否为 ROUNDROBIN if (NULL != DisByOp && DISTTYPE_ROUNDROBIN != DisByOp->disttype) { ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("Unsupport distribute type."), errdetail("Supported option value is \"roundrobin\"."))); } + // 遍历选项列表,检查选项是否有效 foreach (lc, options_list) { - DefElem* def = (DefElem*)lfirst(lc); + DefElem *def = (DefElem *)lfirst(lc); + // 检查选项是否有效,否则报错 if (!is_valid_option(def->defname, catalog)) { - const struct DistFdwOption* opt = NULL; + const struct DistFdwOption *opt = NULL; StringInfoData buf; /* * Unknown option specified, complain about it. Provide a hint * with list of valid options for the object. */ + // 未知选项,提供有效选项的提示 initStringInfo(&buf); for (opt = loader_valid_options; opt->optname; opt++) { if (catalog == opt->optcontext) appendStringInfo(&buf, "%s%s", (buf.len > 0) ? ", " : "", opt->optname); } - ereport(ERROR, - (errcode(ERRCODE_FDW_INVALID_OPTION_NAME), errmsg("invalid option \"%s\"", def->defname), - buf.len > 0 ? errhint("Valid options in this context are: %s", buf.data) - : errhint("There are no valid options in this context."))); + ereport(ERROR, (errcode(ERRCODE_FDW_INVALID_OPTION_NAME), errmsg("invalid option \"%s\"", def->defname), + buf.len > 0 ? errhint("Valid options in this context are: %s", buf.data) + : errhint("There are no valid options in this context."))); } } + // 处理 error_relation,将其添加到扩展选项列表中 if (errLog != NULL) { if (IsA(errLog, DefElem)) stmt->extOptions = lappend(stmt->extOptions, errLog); else { RangeVar *rv = (RangeVar *)errLog; - stmt->extOptions = lappend(stmt->extOptions, makeDefElem(pstrdup(optErrorRel), - (Node *)makeString(pstrdup(rv->relname)))); + stmt->extOptions = + lappend(stmt->extOptions, makeDefElem(pstrdup(optErrorRel), (Node *)makeString(pstrdup(rv->relname)))); } } + // 如果设置了 write_only 标志,将其添加到扩展选项列表中 if (stmt->write_only) { - DefElem *writeOpt = makeDefElem(pstrdup(optWriteOnly), - (Node *)makeString(pstrdup(stmt->write_only ? "true" : "false"))); + DefElem *writeOpt = + makeDefElem(pstrdup(optWriteOnly), (Node *)makeString(pstrdup(stmt->write_only ? "true" : "false"))); stmt->extOptions = lappend(stmt->extOptions, writeOpt); } + // 检查是否创建了分区表,如果是则报错 if (stmt->part_state != NULL) { - ereport(ERROR, (errcode(ERRCODE_FDW_ERROR), - errmsg("It is not allowed to create partition on this foreign table."))); + ereport(ERROR, + (errcode(ERRCODE_FDW_ERROR), errmsg("It is not allowed to create partition on this foreign table."))); } + // 处理格式化选项 TransformFormatterOptions(stmt); stmt->options = list_concat(stmt->options, stmt->extOptions); } -static void distValidateTableDef(Node* Obj) +/* + * 功能:验证表定义相关的节点是否合法 + * + * 参数列表: + * Obj:Node 对象,表示待验证的表定义相关节点 + * + * 注意: + * 该函数用于验证表定义相关的节点是否合法。它接受一个 Node 对象作为参数,根据节点的类型 + * 调用相应的验证函数进行验证。支持的节点类型包括 ALTER TABLE 语句和 CREATE FOREIGN TABLE 语句。 + * 对于其他未知的节点类型,函数将报错。 + */ +static void distValidateTableDef(Node *Obj) { if (Obj == NULL) return; switch (nodeTag(Obj)) { case T_AlterTableStmt: { + // 对 ALTER TABLE 语句进行验证 distValidateAlterTableStmt(Obj); break; } case T_CreateForeignTableStmt: { + // 对 CREATE FOREIGN TABLE 语句进行验证 distValidateCreateForeignTableStmt(Obj); break; } default: + // 未知的节点类型,报错 ereport(ERROR, (errcode(ERRCODE_FDW_ERROR), errmsg("unrecognized node type: %d", (int)nodeTag(Obj)))); break; } @@ -2130,23 +2670,44 @@ static void distValidateTableDef(Node* Obj) extern char *TrimStr(const char *str); +/* + * 功能:检查给定的 URL 是否使用 OBS 协议 + * + * 参数列表: + * locations:const char 指针,表示待检查的 URL 字符串 + * + * 返回值: + * 如果 URL 使用 OBS 协议,返回 true;否则返回 false。 + * + * 注意: + * 该函数用于检查给定的 URL 字符串是否使用 OBS (Object Storage Service) 协议或 GSOBS 协议。 + * 它首先对 URL 进行修剪,然后检查修剪后的字符串是否以 OBS 协议或 GSOBS 协议开头,如果是,则返回 true, + * 否则返回 false。如果传递的 URL 为 NULL 或者修剪后为空字符串,函数将报错并抛出异常。 + */ bool is_obs_protocol(const char *locations) { bool result = false; + // 断言输入 URL 非空 Assert(locations != NULL); - char *trimed_locations = TrimStr(locations); - if (trimed_locations == NULL) { - ereport(ERROR, (errcode(ERRCODE_FDW_ERROR), errmsg("Invalid URL \"%s\" in trimed LOCATION", trimed_locations))); + // 修剪 URL 字符串 + char *trimmed_locations = TrimStr(locations); + + // 检查修剪后的 URL 是否为空 + if (trimmed_locations == NULL) { + ereport(ERROR, + (errcode(ERRCODE_FDW_ERROR), errmsg("Invalid URL \"%s\" in trimmed LOCATION", trimmed_locations))); } - if (pg_strncasecmp(trimed_locations, GSOBS_PREFIX, GSOBS_PREFIX_LEN) == 0 || - pg_strncasecmp(trimed_locations, OBS_PREFIX, OBS_PREfIX_LEN) == 0) { + // 检查 URL 是否以 OBS 协议或 GSOBS 协议开头 + if (pg_strncasecmp(trimmed_locations, GSOBS_PREFIX, GSOBS_PREFIX_LEN) == 0 || + pg_strncasecmp(trimmed_locations, OBS_PREFIX, OBS_PREFIX_LEN) == 0) { result = true; } - pfree(trimed_locations); + // 释放修剪后的 URL 内存 + pfree(trimmed_locations); return result; } diff --git a/src/gausskernel/storage/bulkload/foreignroutine.cpp b/src/gausskernel/storage/bulkload/foreignroutine.cpp index d470504e1..355d86d75 100644 --- a/src/gausskernel/storage/bulkload/foreignroutine.cpp +++ b/src/gausskernel/storage/bulkload/foreignroutine.cpp @@ -2136,7 +2136,7 @@ void ReportIllegalCharExceptionThreshold() /* * 功能:执行分布式导入操作,读取外部数据并填充元组表槽 * - * 参数: + * 参数列表: * node:外部扫描状态 * * 返回值: @@ -2251,7 +2251,7 @@ retry: /* * 功能:结束分布式导入操作,释放相关资源 * - * 参数: + * 参数列表: * node:外部扫描状态 * * 注意: diff --git a/src/gausskernel/storage/bulkload/parser.cpp b/src/gausskernel/storage/bulkload/parser.cpp index e716eb5e1..d2cc2f803 100644 --- a/src/gausskernel/storage/bulkload/parser.cpp +++ b/src/gausskernel/storage/bulkload/parser.cpp @@ -78,7 +78,7 @@ using namespace GDS; #ifdef GDS_SERVER extern gds_settings settings; -extern THR_LOCAL GDS_Connection* current_connection; +extern THR_LOCAL GDS_Connection *current_connection; /* GDS server required declearations */ #define parser_log gs_ereport @@ -86,10 +86,10 @@ extern THR_LOCAL GDS_Connection* current_connection; #define parser_securec_check_ss gds_securec_check_ss /* extern from gds_main.cpp */ -extern string UriToLocalPath(const char* strUri); +extern string UriToLocalPath(const char *strUri); extern size_t GetDataSegmentSize(); -extern char* gs_strerror(int errnum); -static void GetFileHeader(WritableParser* self, const char* path); +extern char *gs_strerror(int errnum); +static void GetFileHeader(WritableParser *self, const char *path); #endif #ifdef OBS_SERVER @@ -99,45 +99,70 @@ static void GetFileHeader(WritableParser* self, const char* path); #define parser_securec_check_ss(rc) securec_check_ss(rc, "\0", "\0") #ifndef ENABLE_LITE_MODE -static size_t SourceRead_OBS(Source* self, void* buffer, size_t len); -static bool SourceNext_OBS(Source* self); +static size_t SourceRead_OBS(Source *self, void *buffer, size_t len); +static bool SourceNext_OBS(Source *self); #endif #endif -static Source* CreateSource(const FileList* files, SourceType sourcetype); -static void DestroyParser(Parser* self); -static void DestroyReadableParser(ReadableParser* self); -static void DestroyWritableParser(WritableParser* self); -static void CleanupWritablParser(WritableParser* self); -static void NopCleanup(Parser* self); +static Source *CreateSource(const FileList *files, SourceType sourcetype); +static void DestroyParser(Parser *self); +static void DestroyReadableParser(ReadableParser *self); +static void DestroyWritableParser(WritableParser *self); +static void CleanupWritablParser(WritableParser *self); +static void NopCleanup(Parser *self); -static char* FindEolChar(char* s, size_t len, const char* eol, int* eol_cur, int* eol_cur_saved) +/* + * 功能:查找字符串中的行尾字符 + * + * 参数列表: + * s:char 指针,表示待查找的字符串 + * len:size_t,表示字符串的长度 + * eol:const char 指针,表示行尾字符的定义 + * eol_cur:int 指针,用于保存当前行尾字符的位置 + * eol_cur_saved:int 指针,用于保存之前的行尾字符的位置 + * + * 返回值: + * 如果找到行尾字符,返回指向该字符的指针;否则返回 NULL。 + * + * 注意: + * 该函数用于在字符串中查找行尾字符,行尾字符可以是 '\r'、'\n' 或自定义的字符串。 + * 当 eol 参数为 NULL 时,函数会查找 '\r' 或 '\n',并返回第一个找到的字符。 + * 当 eol 参数不为 NULL 时,函数会查找自定义的行尾字符串,并返回整个行尾字符串的起始位置。 + */ + +static char *FindEolChar(char *s, size_t len, const char *eol, int *eol_cur, int *eol_cur_saved) { - char* end = NULL; - char* cr = NULL; - char* lf = NULL; + char *end = NULL; + char *cr = NULL; + char *lf = NULL; end = s + len; + if (eol == NULL) { + // 如果 eol 为 NULL,查找 '\r' 或 '\n' while (s < end) { size_t chunk = (s + CHUNK_SZ < end) ? CHUNK_SZ : (end - s); - cr = (char*)memchr(s, '\r', chunk); - lf = (char*)memchr(s, '\n', chunk); + cr = (char *)memchr(s, '\r', chunk); + lf = (char *)memchr(s, '\n', chunk); + if (cr != NULL) { if (lf != NULL && lf < cr) - return lf; - return cr; + return lf; // 返回找到的 '\n' + return cr; // 返回找到的 '\r' } else if (lf != NULL) - return lf; + return lf; // 返回找到的 '\n' + s += CHUNK_SZ; } } else { int eol_len = strlen(eol); *eol_cur_saved = *eol_cur; + + // 查找自定义的行尾字符串 for (int i = 0; i < (int)len; i++) { if (s[i] == eol[*eol_cur]) { (*eol_cur)++; if (*eol_cur >= eol_len) { - return s + i + 1 + *eol_cur_saved - eol_len; + return s + i + 1 + *eol_cur_saved - eol_len; // 返回整个行尾字符串的起始位置 } continue; } else if (*eol_cur >= 1) { @@ -145,192 +170,333 @@ static char* FindEolChar(char* s, size_t len, const char* eol, int* eol_cur, int // Here we allow i back into -1, and on the beginning of the next loop it will be back to 0, as // expected. i--; + // 保证每个字符都与 eol 进行比较 + // 允许 i 回退到 -1,在下一次循环开始时将其重置为 0 + i--; } *eol_cur = 0; *eol_cur_saved = 0; } } - return NULL; + return NULL; // 未找到行尾字符 } -static Source* CreateSource(const FileList* files, SourceType sourcetype) +/* + * 功能:创建源对象 + * + * 参数列表: + * files:const FileList 指针,表示文件列表 + * sourcetype:SourceType,表示源类型 + * + * 返回值: + * 返回指向创建的 Source 对象的指针。 + * + * 注意: + * 该函数用于创建一个 Source 对象,初始化其属性,并返回指向该对象的指针。 + * 如果内存分配失败,会记录错误日志并返回 NULL。 + */ + +static Source *CreateSource(const FileList *files, SourceType sourcetype) { - Source* self = NULL; + Source *self = NULL; // 声明一个 Source 指针并初始化为 NULL try { - self = new Source; - } catch (std::bad_alloc&) { + self = new Source; // 尝试分配内存并创建 Source 对象 + } catch (std::bad_alloc &) { + // 捕获内存分配异常,记录错误日志并返回 NULL parser_log(LEVEL_ERROR, "failed to create source, out of memory"); } if (files != NULL) - self->SetFileList(*files); - self->SetSourceType(sourcetype); - self->SourceInit(files == NULL); + self->SetFileList(*files); // 如果传入的文件列表不为空,设置 Source 对象的文件列表 - return self; + self->SetSourceType(sourcetype); // 设置 Source 对象的源类型 + self->SourceInit(files == NULL); // 调用 SourceInit 函数,初始化 Source 对象的其他属性 + + return self; // 返回创建的 Source 对象的指针 } /* specified for general file source reading */ -static size_t SourceRead_File(Source* self, void* buffer, size_t len) +/* + * 功能:从文件源中读取数据 + * + * 参数列表: + * self:Source 指针,表示源对象 + * buffer:void 指针,表示数据缓冲区 + * len:size_t,表示要读取的数据长度 + * + * 返回值: + * 返回实际读取的数据长度。 + * + * 注意: + * 该函数用于从文件源中读取数据。它首先尝试执行 POSIX_FADVISE 操作,然后使用 fread 函数从文件中读取数据。 + * 如果在读取数据时发生错误,会记录错误日志。 + */ + +static size_t SourceRead_File(Source *self, void *buffer, size_t len) { - size_t nread; - const char* err_file = self->m_files[self->m_current - 1].c_str(); + size_t nread; // 用于保存实际读取的数据长度 + const char *err_file = self->m_files[self->m_current - 1].c_str(); // 获取当前文件名 #if defined(USE_POSIX_FADVISE) - off_t offset = lseek(fileno(self->m_fd), 0, SEEK_CUR); + off_t offset = lseek(fileno(self->m_fd), 0, SEEK_CUR); // 获取当前文件偏移量 if (offset >= 0) (void)posix_fadvise(fileno(self->m_fd), offset + len, INITIAL_BUF_LEN, POSIX_FADV_WILLNEED); + // 执行 POSIX_FADVISE 操作,提前加载文件数据 #endif - nread = fread(buffer, 1, len, self->m_fd); - if (ferror(self->m_fd)) - parser_log(LEVEL_ERROR, "could not read source file: %s", err_file); - return nread; + nread = fread(buffer, 1, len, self->m_fd); // 使用 fread 函数从文件中读取数据 + if (ferror(self->m_fd)) + parser_log(LEVEL_ERROR, "could not read source file: %s", err_file); // 如果读取出错,记录错误日志 + + return nread; // 返回实际读取的数据长度 } /* specified for pipe source reading */ -static size_t SourceRead_FIFO(Source* self, void* buffer, size_t len) +/* + * 功能:从 FIFO(命名管道)源中读取数据 + * + * 参数列表: + * self:Source 指针,表示源对象 + * buffer:void 指针,表示数据缓冲区 + * len:size_t,表示要读取的数据长度 + * + * 返回值: + * 返回实际读取的数据长度。 + * + * 注意: + * 该函数用于从 FIFO(命名管道)源中读取数据。它使用 read 函数从命名管道中读取数据, + * 如果在读取数据时发生错误,会记录错误日志。 + */ + +static size_t SourceRead_FIFO(Source *self, void *buffer, size_t len) { - const char* err_file = self->m_files[0].c_str(); + const char *err_file = self->m_files[0].c_str(); // 获取管道文件名 #ifndef WIN32 - ssize_t nread = read(self->m_fifo, buffer, len); + ssize_t nread = read(self->m_fifo, buffer, len); // 使用 read 函数从命名管道中读取数据 #else - int nread = read(self->m_fifo, buffer, len); + int nread = read(self->m_fifo, buffer, len); // 使用 read 函数从命名管道中读取数据 #endif + if (-1 == nread) - parser_log(LEVEL_ERROR, "could not read source file: %s", err_file); - return (size_t)nread; + parser_log(LEVEL_ERROR, "could not read source file: %s", err_file); // 如果读取出错,记录错误日志 + + return (size_t)nread; // 返回实际读取的数据长度 } /* specified for general file source checking next file */ -static bool SourceNext_File(Source* self) +/* + * 功能:切换到下一个文件源 + * + * 参数列表: + * self:Source 指针,表示源对象 + * + * 返回值: + * 如果成功切换到下一个文件源,返回 true;否则返回 false。 + * + * 注意: + * 该函数用于切换到下一个文件源,如果当前文件源已经打开,则关闭它。 + * 然后尝试打开下一个文件源,如果打开失败,会记录警告日志。 + */ + +static bool SourceNext_File(Source *self) { - const char* err_file = NULL; - const char* current_file = NULL; + const char *err_file = NULL; // 用于记录错误文件名 + const char *current_file = NULL; // 用于记录当前文件名 if (self->m_fd != NULL) { - fclose(self->m_fd); + fclose(self->m_fd); // 如果当前文件源已经打开,则关闭它 self->m_fd = NULL; } if ((size_t)(self->m_current) >= self->m_files.size()) - return false; + return false; // 如果已经遍历完所有文件源,返回 false + while (self->m_fd == NULL && (size_t)(self->m_current) < self->m_files.size()) { - current_file = self->m_files[self->m_current++].c_str(); - self->m_fd = fopen(current_file, "r"); + current_file = self->m_files[self->m_current++].c_str(); // 获取下一个文件名 + self->m_fd = fopen(current_file, "r"); // 尝试打开下一个文件源 + if (self->m_fd == NULL) { - err_file = self->m_files[self->m_current - 1].c_str(); - parser_log(LEVEL_WARNING, "Unable to open %s", err_file); + err_file = self->m_files[self->m_current - 1].c_str(); // 获取打开失败的文件名 + parser_log(LEVEL_WARNING, "Unable to open %s", err_file); // 记录警告日志 } } if (self->m_fd != NULL) { - self->m_filename = self->m_files[self->m_current - 1]; + self->m_filename = self->m_files[self->m_current - 1]; // 设置当前文件名属性 #if defined(USE_POSIX_FADVISE) - (void)posix_fadvise( - fileno(self->m_fd), 0, INITIAL_BUF_LEN, POSIX_FADV_SEQUENTIAL | POSIX_FADV_NOREUSE | POSIX_FADV_WILLNEED); + (void)posix_fadvise(fileno(self->m_fd), 0, INITIAL_BUF_LEN, + POSIX_FADV_SEQUENTIAL | POSIX_FADV_NOREUSE | POSIX_FADV_WILLNEED); + // 执行 POSIX_FADVISE 操作,提前加载文件数据 #endif } - return self->m_fd != NULL; + return self->m_fd != NULL; // 返回是否成功打开下一个文件源 } /* specified for pipe source checking next file */ -static bool SourceNext_FIFO(Source* self) +static bool SourceNext_FIFO(Source *self) { return false; } #ifdef GDS_SERVER -void Source::SourceWrite(evbuffer* buffer, size_t len) +/* + * 功能:将数据写入源的写缓冲区 + * + * 参数列表: + * buffer:evbuffer 指针,表示数据缓冲区 + * len:size_t,表示要写入的数据长度 + * + * 注意: + * 该函数用于将数据写入源的写缓冲区。它会尝试将数据从缓冲区中复制到写缓冲区。 + * 如果复制失败,会记录错误日志。如果发现无效的错误记录,也会记录错误日志。 + */ + +void Source::SourceWrite(evbuffer *buffer, size_t len) { - int nsave; - int result; + int nsave; // 用于保存复制的数据长度 + int result; // 用于保存复制结果 while (len > 0) { if (m_wused == MAX_BUFFER_SIZE) - SourceFlush(); - nsave = Min(len, MAX_BUFFER_SIZE - m_wused); - result = evbuffer_remove(buffer, m_writeBuf + m_wused, nsave); + SourceFlush(); // 如果写缓冲区已满,执行写入操作 + + nsave = Min(len, MAX_BUFFER_SIZE - m_wused); // 计算要复制的数据长度 + result = evbuffer_remove(buffer, m_writeBuf + m_wused, nsave); // 从数据缓冲区复制数据到写缓冲区 + if (result == -1) - parser_log(LEVEL_ERROR, "failed to copy data to write buffer"); + parser_log(LEVEL_ERROR, "failed to copy data to write buffer"); // 如果复制失败,记录错误日志 else if (result == 0) - parser_log(LEVEL_ERROR, "invaild error recored"); - m_woffset += result; - len -= result; - m_wused += result; + parser_log(LEVEL_ERROR, "invalid error record"); // 如果发现无效的错误记录,记录错误日志 + + m_woffset += result; // 更新写偏移量 + len -= result; // 减去已复制的数据长度 + m_wused += result; // 更新写缓冲区已用长度 } } #endif -void Source::SourceWrite(const char* buffer, size_t len) +/* + * 功能:将数据写入源的写缓冲区 + * + * 参数列表: + * buffer:const char 指针,表示数据缓冲区 + * len:size_t,表示要写入的数据长度 + * + * 注意: + * 该函数用于将数据写入源的写缓冲区。它会循环将数据从缓冲区中复制到写缓冲区, + * 直到所有数据都被写入或写缓冲区已满。如果复制失败,会记录错误日志。 + */ + +void Source::SourceWrite(const char *buffer, size_t len) { - int nsave; - errno_t rc; + int nsave; // 用于保存复制的数据长度 + errno_t rc; // 用于保存复制结果 while (len > 0) { if (m_wused == MAX_BUFFER_SIZE) - SourceFlush(); - nsave = Min(len, MAX_BUFFER_SIZE - m_wused); - rc = memcpy_s(m_writeBuf + m_wused, nsave, buffer, nsave); - parser_securec_check(rc); - m_woffset += nsave; - len -= nsave; - m_wused += nsave; + SourceFlush(); // 如果写缓冲区已满,执行写入操作 + nsave = Min(len, MAX_BUFFER_SIZE - m_wused); // 计算要复制的数据长度 + rc = memcpy_s(m_writeBuf + m_wused, nsave, buffer, nsave); // 从数据缓冲区复制数据到写缓冲区 + parser_securec_check(rc); // 检查复制是否成功 + m_woffset += nsave; // 更新写偏移量 + len -= nsave; // 减去已复制的数据长度 + m_wused += nsave; // 更新写缓冲区已用长度 } } -size_t Source::SourceWriteInternal(const void* buffer, size_t len) +/* + * 功能:将数据写入当前文件源 + * + * 参数列表: + * buffer:const void 指针,表示数据缓冲区 + * len:size_t,表示要写入的数据长度 + * + * 返回值: + * 返回实际写入的数据长度。 + * + * 注意: + * 该函数用于将数据写入当前文件源。它首先检查文件描述符是否为空,如果为空,则记录错误日志。 + * 然后使用 fwrite 函数将数据写入文件,如果写入出错,也会记录错误日志。 + */ + +size_t Source::SourceWriteInternal(const void *buffer, size_t len) { - size_t nwrite = 0; - const char* err_file = m_files[m_current - 1].c_str(); + size_t nwrite = 0; // 用于保存实际写入的数据长度 + const char *err_file = m_files[m_current - 1].c_str(); // 获取当前文件名 + if (m_fd == NULL) { - parser_log(LEVEL_ERROR, "could not write file: %s, caused by fd empty.", err_file); + parser_log(LEVEL_ERROR, "could not write file: %s, caused by fd empty.", + err_file); // 如果文件描述符为空,记录错误日志 } - nwrite = fwrite(buffer, 1, len, m_fd); + + nwrite = fwrite(buffer, 1, len, m_fd); // 使用 fwrite 函数将数据写入文件 if (ferror(m_fd)) { parser_log(LEVEL_ERROR, "could not write file %s with Error: %s", err_file, gs_strerror(errno)); + // 如果写入出错,记录错误日志 } - return nwrite; + + return nwrite; // 返回实际写入的数据长度 } -void Source::GenerateNewFile(const char* prefix, const char* suffix) -{ - char path[MAX_PATH_LEN] = {0}; - errno_t rc = EOK; +/* + * 功能:生成新文件并切换为当前文件源 + * + * 参数列表: + * prefix:const char 指针,表示新文件的前缀 + * suffix:const char 指针,表示新文件的后缀 + * + * 注意: + * 该函数用于生成新文件并切换为当前文件源。它首先根据前缀和后缀生成新文件的路径。 + * 然后检查文件是否已存在,如果存在则继续生成下一个文件。 + * 接着关闭当前文件,将新文件路径添加到文件列表中,然后尝试以写模式打开新文件。 + * 如果打开失败,会记录错误日志。然后尝试设置新文件的权限。 + * 最后,重置写偏移量为0,并切换为新文件源。 + */ - ASSERT(prefix != NULL); +void Source::GenerateNewFile(const char *prefix, const char *suffix) +{ + char path[MAX_PATH_LEN] = {0}; // 用于保存新文件的路径 + errno_t rc = EOK; // 用于保存 snprintf_s 函数的返回值 + + ASSERT(prefix != NULL); // 断言前缀不为空 for (int i = 0; i < MAX_SEGMENT_NUM; i++) { if (suffix != NULL) rc = snprintf_s(path, MAX_PATH_LEN, MAX_PATH_LEN - 1, "%s/%s.%s.%d", m_path.c_str(), prefix, suffix, i); else rc = snprintf_s(path, MAX_PATH_LEN, MAX_PATH_LEN - 1, "%s/%s.%d", m_path.c_str(), prefix, i); - parser_securec_check_ss(rc); -#ifdef WIN32 - LinuxPathToWin(path); -#endif - if (access(path, F_OK) != -1) - continue; + parser_securec_check_ss(rc); // 检查 snprintf_s 是否成功 - CloseCurrentFile(); - m_files.push_back(path); +#ifdef WIN32 + LinuxPathToWin(path); // 将路径转换为 Windows 格式 +#endif + + if (access(path, F_OK) != -1) + continue; // 如果文件已存在,继续生成下一个文件 + + CloseCurrentFile(); // 关闭当前文件 + m_files.push_back(path); // 将新文件路径添加到文件列表中 m_current++; - m_fd = fopen(path, "w"); + m_fd = fopen(path, "w"); // 尝试以写模式打开新文件 + if (m_fd == NULL) { - parser_log(LEVEL_ERROR, "failed to create new file %s", path); + parser_log(LEVEL_ERROR, "failed to create new file %s", path); // 如果打开失败,记录错误日志 } #ifndef WIN32 if (fchmod(fileno(m_fd), S_IRUSR | S_IWUSR) != 0) { - parser_log(LEVEL_ERROR, "failed to chmod file %s with Error: %s", path, gs_strerror(errno)); + parser_log(LEVEL_ERROR, "failed to chmod file %s with Error: %s", path, + gs_strerror(errno)); // 尝试设置新文件的权限 } #endif - m_woffset = 0; - return; + + m_woffset = 0; // 重置写偏移量为0 + return; // 切换为新文件源 } parser_log(LEVEL_ERROR, "failed to generate new file, because of too many files in directory"); + // 如果生成新文件失败(文件数过多),记录错误日志 } /** @@ -341,117 +507,180 @@ void Source::GenerateNewFile(const char* prefix, const char* suffix) * @param suffix Export file path suffix. */ #ifdef GDS_SERVER -void Source::GenerateNewFileForExport(const char* prefix, const char* suffix) -{ - char path[MAX_PATH_LEN] = {0}; - errno_t rc = EOK; +/* + * 功能:为导出生成新文件并切换为当前文件源 + * + * 参数列表: + * prefix:const char 指针,表示新文件的前缀 + * suffix:const char 指针,表示新文件的后缀 + * + * 注意: + * 该函数用于为导出生成新文件并切换为当前文件源。它首先根据前缀和后缀生成新文件的路径。 + * 如果前缀中包含无效符号,则记录错误日志。然后检查文件是否已存在,如果存在则继续生成下一个文件。 + * 对于已生成的文件,如果不存在则记录错误日志。 + * 接着删除旧文件(如果存在),关闭当前文件,将新文件路径添加到文件列表中,然后尝试以写模式打开新文件。 + * 如果打开失败,会记录错误日志。然后尝试设置新文件的权限。 + * 最后,重置写偏移量为0,并切换为新文件源。 + */ - ASSERT(prefix != NULL); +void Source::GenerateNewFileForExport(const char *prefix, const char *suffix) +{ + char path[MAX_PATH_LEN] = {0}; // 用于保存新文件的路径 + errno_t rc = EOK; // 用于保存 snprintf_s 函数的返回值 + + ASSERT(prefix != NULL); // 断言前缀不为空 if (strstr(prefix, InvalidSymbol) != NULL) { - parser_log(LEVEL_ERROR, "invalid path which include \"%s\"", InvalidSymbol); + parser_log(LEVEL_ERROR, "invalid path which include \"%s\"", + InvalidSymbol); // 如果前缀包含无效符号,记录错误日志 } + for (int i = 0; i < MAX_SEGMENT_NUM; i++) { if (suffix != NULL) { rc = snprintf_s(path, MAX_PATH_LEN, MAX_PATH_LEN - 1, "%s/%s.%s.%d", m_path.c_str(), prefix, suffix, i); } else { rc = snprintf_s(path, MAX_PATH_LEN, MAX_PATH_LEN - 1, "%s/%s.%d", m_path.c_str(), prefix, i); } - parser_securec_check_ss(rc); + parser_securec_check_ss(rc); // 检查 snprintf_s 是否成功 + #ifdef WIN32 - LinuxPathToWin(path); + LinuxPathToWin(path); // 将路径转换为 Windows 格式 #endif + if (i < m_current) { if (access(path, F_OK) != 0) { parser_log(LEVEL_ERROR, - "export files incomplete because \"%s\" maybe deleted unexpected or no " - "permission to access,please delete rest of invalid export file.", - path); + "export files incomplete because \"%s\" maybe deleted unexpectedly or no " + "permission to access, please delete the rest of the invalid export files.", + path); // 对于已生成的文件,如果不存在,则记录错误日志 } } else { if (access(path, F_OK) == 0) { - unlink(path); + unlink(path); // 删除旧文件(如果存在) if (settings.debug_level >= DEBUG_NORMAL) { - gs_elog( - LEVEL_LOG, "delete export file during segment file generate for cn retry or repeat export."); + gs_elog(LEVEL_LOG, + "delete export file during segment file generation for CN retry or repeat export."); } } - CloseCurrentFile(); - m_files.push_back(path); + + CloseCurrentFile(); // 关闭当前文件 + m_files.push_back(path); // 将新文件路径添加到文件列表中 m_current++; - CanonicalizePath(path); - m_fd = fopen(path, "w"); + CanonicalizePath(path); // 规范化文件路径 + m_fd = fopen(path, "w"); // 尝试以写模式打开新文件 + if (m_fd == NULL) { parser_log(LEVEL_ERROR, - "failed to create new file %s during segment file generate,please delete rest of invalid export " - "file with Error %s.", - path, - strerror(errno)); + "failed to create new file %s during segment file generation, please delete the rest of the " + "invalid export " + "files with Error: %s.", + path, strerror(errno)); // 如果打开失败,记录错误日志 } + #ifndef WIN32 if (fchmod(fileno(m_fd), LOG_PERM_GRPR) != 0) { - parser_log(LEVEL_ERROR,"Could not change permissions of file \"%s\"\n", path); + parser_log(LEVEL_ERROR, "Could not change permissions of file \"%s\"\n", path); // 尝试设置新文件的权限 } #endif - m_woffset = 0; - return; + + m_woffset = 0; // 重置写偏移量为0 + return; // 切换为新文件源 } } - parser_log(LEVEL_ERROR, "failed to generate new file, because of too many files in directory"); + parser_log(LEVEL_ERROR, "failed to generate new file, because there are too many files in the directory"); + // 如果生成新文件失败(文件数过多),记录错误日志 } #endif +/* + * 功能:刷新源的写缓冲区 + * + * 注意: + * 该函数用于刷新源的写缓冲区。它会循环将写缓冲区中的数据写入当前文件源, + * 直到写缓冲区中的数据全部写入或写入出错。 + */ + void Source::SourceFlush() { - char* buf = m_writeBuf; + char *buf = m_writeBuf; // 初始化缓冲区指针为写缓冲区的起始地址 while (m_wused > 0) { - int nwrite = SourceWriteInternal(buf, m_wused); - buf += nwrite; - m_wused -= nwrite; + int nwrite = SourceWriteInternal(buf, m_wused); // 调用 SourceWriteInternal 函数将数据写入当前文件源 + buf += nwrite; // 更新缓冲区指针 + m_wused -= nwrite; // 更新写缓冲区已用长度 } } +/* + * 功能:关闭当前文件源 + * + * 注意: + * 该函数用于关闭当前文件源。首先会调用 SourceFlush 函数刷新写缓冲区, + * 然后关闭文件描述符,并将文件名属性清空。 + */ + void Source::CloseCurrentFile() { - SourceFlush(); + SourceFlush(); // 刷新写缓冲区 if (m_fd != NULL) { - fclose(m_fd); + fclose(m_fd); // 关闭文件描述符 } - m_fd = NULL; + m_fd = NULL; // 将文件描述符置为空 - m_filename = ""; + m_filename = ""; // 清空文件名属性 } +/* + * 功能:关闭当前文件源(不刷新写缓冲区) + * + * 注意: + * 该函数用于关闭当前文件源,但不会刷新写缓冲区。它会关闭文件描述符, + * 并将文件名属性清空,同时将写缓冲区已用长度置为0。 + */ + void Source::CloseCurrentFileNoFlush() { if (m_fd != NULL) { - fclose(m_fd); + fclose(m_fd); // 关闭文件描述符 } - m_fd = NULL; - m_wused = 0; - m_filename = ""; + m_fd = NULL; // 将文件描述符置为空 + m_wused = 0; // 将写缓冲区已用长度置为0 + m_filename = ""; // 清空文件名属性 } +/* + * 功能:初始化源对象 + * + * 参数列表: + * isWrite:bool,表示是否是写操作 + * + * 注意: + * 该函数用于初始化源对象。如果是写操作,会分配写缓冲区内存。 + * 如果是读操作,会根据不同的情况选择源的读取和下一个源的函数。 + * 如果是从管道中读取数据,则会将读取和下一个源的函数设置为读取管道的函数。 + * 如果是从文件中读取数据,则会将读取和下一个源的函数设置为从文件中读取的函数。 + * 最后,如果没有可以打开的文件,会记录错误日志。 + */ + void Source::SourceInit(bool isWrite) { if (isWrite) { - m_writeBuf = (char*)malloc(MAX_BUFFER_SIZE); + m_writeBuf = (char *)malloc(MAX_BUFFER_SIZE); // 分配写缓冲区内存 if (m_writeBuf == NULL) - parser_log(LEVEL_ERROR, "failed to init source, out of memory"); + parser_log(LEVEL_ERROR, "failed to init source, out of memory"); // 如果分配内存失败,记录错误日志 } else { #ifndef WIN32 if (1 == m_files.size() && m_sourcetype == SOURCE_TYPE_FILE) { struct stat status; - const char* pipe_file = m_files[0].c_str(); + const char *pipe_file = m_files[0].c_str(); if (stat(pipe_file, &status)) parser_log(LEVEL_ERROR, "failed to init source"); /* detect fifo */ if (S_ISFIFO(status.st_mode)) { if (-1 == (m_fifo = open(pipe_file, O_RDONLY))) parser_log(LEVEL_ERROR, "failed to init source"); - SourceRead = SourceRead_FIFO; - SourceNext = SourceNext_FIFO; + SourceRead = SourceRead_FIFO; // 设置读取函数为从管道中读取数据的函数 + SourceNext = SourceNext_FIFO; // 设置下一个源函数为从管道中读取下一个源的函数 return; } } @@ -479,14 +708,24 @@ void Source::SourceInit(bool isWrite) #endif if (!SourceNext(this)) - parser_log(LEVEL_ERROR, "No files can be opened"); + parser_log(LEVEL_ERROR, "No files can be opened"); // 如果没有可以打开的文件,记录错误日志 } } #ifdef WIN32 -void LinuxPathToWin(char* path) +/* + * 功能:将 Linux 路径转换为 Windows 路径 + * + * 参数列表: + * path:char 指针,表示待转换的路径字符串 + * + * 注意: + * 该函数用于将 Linux 路径转换为 Windows 路径。它会将路径中的所有斜杠'/'替换为反斜杠'\'。 + */ + +void LinuxPathToWin(char *path) { - char* slash = NULL; + char *slash = NULL; if (path == NULL) return; while (1) { @@ -494,17 +733,40 @@ void LinuxPathToWin(char* path) if (slash == NULL) break; - *slash = '\\'; + *slash = '\\'; // 将斜杠替换为反斜杠 } } #endif -static void NopReadLine(Parser* self, LineBuffer& buf) +/* + * 功能:空操作读取行函数 + * + * 参数列表: + * self:Parser 指针,表示解析器对象 + * buf:LineBuffer 引用,表示读取行的缓冲区 + * + * 注意: + * 该函数用于表示解析器不支持读取操作,仅用于记录错误日志。 + */ + +static void NopReadLine(Parser *self, LineBuffer &buf) { parser_log(LEVEL_ERROR, "parser doesn't support read"); } -static void NopWriteLine(Parser* self, struct evbuffer& buf, size_t len) +/* + * 功能:无操作写入行 + * + * 参数列表: + * self:Parser 指针,表示解析器对象 + * buf:evbuffer 引用,表示待写入的数据缓冲区 + * len:size_t,表示待写入的数据长度 + * + * 注意: + * 该函数用于表示解析器不支持写入操作,因此会记录错误日志。 + */ + +static void NopWriteLine(Parser *self, struct evbuffer &buf, size_t len) { parser_log(LEVEL_ERROR, "parser doesn't support write"); } @@ -523,21 +785,21 @@ static void NopWriteLine(Parser* self, struct evbuffer& buf, size_t len) * (2) If the linebuffer is full a parsed complete line MUST BE put in linebuffer BEFORE next line parsing. */ template -inline static ParserResult CSVReadLine(CSVParser* self, LineBuffer& buf) +inline static ParserResult CSVReadLine(CSVParser *self, LineBuffer &buf) { bool need_data = false; - char* raw_buffer = self->rec_buf; + char *raw_buffer = self->rec_buf; char quotec = self->quote; char escapec = self->escape; - Source* source = self->source; + Source *source = self->source; /* * All the following flags MUST BE synchronized to appropriate state before * each parsed and completed line is to be added to LineBuffer. */ - bool* in_quote = &(self->in_quote); - bool* last_was_esc = &(self->lastWasEsc); - bool* in_cr = &(self->in_cr); + bool *in_quote = &(self->in_quote); + bool *last_was_esc = &(self->lastWasEsc); + bool *in_cr = &(self->in_cr); char c; int begin_index = self->cur; @@ -695,19 +957,35 @@ inline static ParserResult CSVReadLine(CSVParser* self, LineBuffer& buf) self->cur_need_flush = raw_buf_ptr; return RESULT_SUCCESS; } - +/* + * 功能:文本读取行 + * + * 参数列表: + * self:ReadableParser 指针,表示可读解析器对象 + * buf:LineBuffer 引用,表示用于存储读取的行数据的缓冲区 + * + * 返回值: + * 返回 ParserResult 枚举值,表示读取行的结果,可能为 RESULT_EOF、RESULT_BUFFER_FULL 或 RESULT_SUCCESS。 + * + * 注意: + * 该函数用于文本读取行操作。根据参数 skipData 的值,可以选择是否跳过数据。 + * 函数会循环读取数据并查找行尾字符,直到找到行尾字符或遇到文件结尾。 + * 如果找到行尾字符,则将行数据存储到缓冲区 buf 中,并返回 RESULT_SUCCESS。 + * 如果缓冲区不足以存储整行数据,则返回 RESULT_BUFFER_FULL。 + * 如果遇到文件结尾,则返回 RESULT_EOF。 + */ template -inline static ParserResult TextReadLine(ReadableParser* self, LineBuffer& buf) +inline static ParserResult TextReadLine(ReadableParser *self, LineBuffer &buf) { bool need_data = false; - char* eol = NULL; + char *eol = NULL; int remainLen; - char* raw_buffer = self->rec_buf; - Source* source = self->source; + char *raw_buffer = self->rec_buf; + Source *source = self->source; int eol_len = (self->eol == NULL) ? 1 : strlen(self->eol); while (true) { - char* end = NULL; + char *end = NULL; // if buffer is empty, read new data from files // @@ -785,13 +1063,28 @@ inline static ParserResult TextReadLine(ReadableParser* self, LineBuffer& buf) self->eol_cur_saved = 0; return RESULT_SUCCESS; } - -inline static ParserResult FixReadLine(FixParser* self, LineBuffer& buf) +/* + * 功能:修复读取行 + * + * 参数列表: + * self:FixParser 指针,表示修复解析器对象 + * buf:LineBuffer 引用,表示用于存储读取的行数据的缓冲区 + * + * 返回值: + * 返回 ParserResult 枚举值,表示读取行的结果,可能为 RESULT_EOF、RESULT_BUFFER_FULL 或 RESULT_SUCCESS。 + * + * 注意: + * 该函数用于修复读取行操作。函数会循环读取数据,并将数据存储到缓冲区 buf 中。 + * 如果缓冲区不足以存储整行数据,则返回 RESULT_BUFFER_FULL。 + * 如果遇到文件结尾,则返回 RESULT_EOF。 + * 如果成功读取行,则返回 RESULT_SUCCESS。 + */ +inline static ParserResult FixReadLine(FixParser *self, LineBuffer &buf) { int remainLen; - char* raw_buffer = NULL; + char *raw_buffer = NULL; int needRead = self->rowSize; - Source* source = self->source; + Source *source = self->source; /* * skip the size of the un-completed row chunk which is already put in LineBuffer; @@ -856,17 +1149,32 @@ inline static ParserResult FixReadLine(FixParser* self, LineBuffer& buf) return RESULT_SUCCESS; } - +/* + * 功能:通用读取行 + * + * 参数列表: + * self:Parser 指针,表示解析器对象 + * + * 返回值: + * 返回 ParserResult 枚举值,表示读取行的结果,可能为 RESULT_EOF、RESULT_BUFFER_FULL 或 RESULT_SUCCESS。 + * + * 注意: + * 该函数用于通用读取行操作,支持不同的文件格式(format)。根据 format 参数的值,选择相应的行读取函数。 + * 函数会循环读取数据,直到行缓冲区 line_buffers 被填满或达到文件结尾。 + * 如果成功读取行,则返回 RESULT_SUCCESS。 + * 如果达到文件结尾且还有更多文件可读取,则返回 RESULT_NEW_ONE。 + * 如果行缓冲区不足以存储整行数据,则返回 RESULT_BUFFER_FULL。 + */ template ParserResult #ifdef GDS_SERVER - GenericReadLines(Parser* self, struct evbuffer& buf) + GenericReadLines(Parser *self, struct evbuffer &buf) #else - GenericReadLines(Parser* self) + GenericReadLines(Parser *self) #endif { - Source* source = self->source; - ReadableParser* parser = (ReadableParser*)self; + Source *source = self->source; + ReadableParser *parser = (ReadableParser *)self; if (parser->eof) return RESULT_EOF; @@ -876,11 +1184,11 @@ ParserResult ParserResult result = RESULT_SUCCESS; if (format == FORMAT_TEXT) - result = TextReadLine((ReadableParser*)self, self->line_buffers); + result = TextReadLine((ReadableParser *)self, self->line_buffers); else if (format == FORMAT_FIXED) - result = FixReadLine((FixParser*)self, self->line_buffers); + result = FixReadLine((FixParser *)self, self->line_buffers); else if (format == FORMAT_CSV) - result = CSVReadLine((CSVParser*)self, self->line_buffers); + result = CSVReadLine((CSVParser *)self, self->line_buffers); if (RESULT_EOF == result) { if (source->SourceNext(source)) { @@ -891,9 +1199,9 @@ ParserResult // if (self->hasHeader) { if (format == FORMAT_TEXT || format == FORMAT_FIXED) - (void)TextReadLine((ReadableParser*)self, self->line_buffers); + (void)TextReadLine((ReadableParser *)self, self->line_buffers); else - (void)CSVReadLine((CSVParser*)self, self->line_buffers); + (void)CSVReadLine((CSVParser *)self, self->line_buffers); } return RESULT_NEW_ONE; } else { @@ -905,18 +1213,35 @@ ParserResult } } } +/* + * 功能:通用写入行 + * + * 参数列表: + * self:Parser 指针,表示解析器对象 + * buf:evbuffer 引用,表示待写入的数据缓冲区 + * len:整数,表示待写入数据的长度 + * + * 返回值: + * 返回 ParserResult 枚举值,表示写入行的结果,通常为 RESULT_SUCCESS。 + * + * 注意: + * 该函数用于通用写入行操作,支持不同的写入方式。 + * 在 GDS_SERVER 模式下,函数会根据数据段大小 `segSize` 来判断是否需要生成新的导出文件, + * 并将数据写入源文件。如果有头部数据 `fileheader`,会在每个新文件中写入头部数据。 + * 在非 GDS_SERVER 模式下,会记录错误信息,表示该路径的代码未实现。 + */ template -ParserResult GenericWriteLines(Parser* self, struct evbuffer& buf, int len) +ParserResult GenericWriteLines(Parser *self, struct evbuffer &buf, int len) { #ifdef GDS_SERVER - Source* source = self->source; + Source *source = self->source; size_t segSize = GetDataSegmentSize(); if (segSize > 0 && source->GetWriteOffset() > segSize) { - source->GenerateNewFileForExport(((WritableParser*)self)->prefix, "dat"); + source->GenerateNewFileForExport(((WritableParser *)self)->prefix, "dat"); if (hasHeader) - source->SourceWrite(((WritableParser*)self)->fileheader, ((WritableParser*)self)->headerSize); + source->SourceWrite(((WritableParser *)self)->fileheader, ((WritableParser *)self)->headerSize); } source->SourceWrite(&buf, len); @@ -925,44 +1250,72 @@ ParserResult GenericWriteLines(Parser* self, struct evbuffer& buf, int len) #endif return RESULT_SUCCESS; } - -static void ReadableParserInit(ReadableParser* self, CmdBegin* cmd, FileList* files, SourceType sourcetype) +/* + * 功能:初始化可读解析器 + * + * 参数列表: + * self:ReadableParser 指针,表示可读解析器对象 + * cmd:CmdBegin 指针,表示命令开始信息 + * files:FileList 指针,表示文件列表信息 + * sourcetype:SourceType 枚举值,表示源类型 + * + * 注意: + * 该函数用于初始化可读解析器对象。根据参数进行初始化操作,包括分配内存、设置属性等。 + */ +static void ReadableParserInit(ReadableParser *self, CmdBegin *cmd, FileList *files, SourceType sourcetype) { + // 初始化文件结束标志为 false,表示文件未结束 self->eof = false; + + // 如果缓冲区长度为 0,则设置为 INITIAL_BUF_LEN if (self->buf_len == 0) self->buf_len = INITIAL_BUF_LEN; - self->rec_buf = (char*)malloc(self->buf_len); + // 分配 rec_buf 缓冲区内存,用于存储读取的数据,大小为 buf_len 字节 + self->rec_buf = (char *)malloc(self->buf_len); if (self->rec_buf == NULL) { parser_log(LEVEL_ERROR, "memory alloc failed!\n"); } + + // 初始化已使用数据长度 used_len、当前位置 cur、EOL 字符当前位置 eol_cur 和 EOL 字符位置保存 eol_cur_saved self->used_len = 0; self->cur = 0; self->eol_cur = 0; self->eol_cur_saved = 0; + // 根据命令中的 m_header 属性,设置是否有头部数据的标志 hasHeader self->hasHeader = cmd->m_header; #ifdef GDS_SERVER + // 如果是服务器模式,创建一个 libevent 的 evbuffer 用于存储行数据 self->line_buffer = evbuffer_new(); if (self->line_buffer == NULL) parser_log(LEVEL_ERROR, "failed to init parser, out of memory"); #endif + + // 创建数据源 source,根据 sourcetype 和 files 参数进行初始化 self->source = CreateSource(files, sourcetype); + + // 如果 sourcetype 不是 SOURCE_TYPE_OBS 并且命令中的 m_prefix 不为 NULL,则复制 m_prefix 到 prefix 属性 if (sourcetype != SOURCE_TYPE_OBS && (cmd->m_prefix != NULL)) { self->prefix = strdup(cmd->m_prefix); if (self->prefix == NULL) parser_log(LEVEL_ERROR, "failed to copy prefix, out of memory"); } + // 再次检查 rec_buf 是否为 NULL,如果是则记录内存分配错误 if (self->rec_buf == NULL) parser_log(LEVEL_ERROR, "failed to init parser, out of memory"); + + // 初始化 rec_buf 的第一个字符为 '\0' self->rec_buf[0] = '\0'; + // 初始化 line_buffers 和相关属性,用于管理行数据 self->line_buffers.Init(); self->cur_need_flush = 0; self->is_cur_line_completed = false; + // 如果命令中的 m_eol 不为 NULL,则复制 m_eol 到 eol 属性 if (cmd->m_eol != NULL) { self->eol = strdup(cmd->m_eol); if (self->eol == NULL) @@ -970,48 +1323,121 @@ static void ReadableParserInit(ReadableParser* self, CmdBegin* cmd, FileList* fi } } -static void CSVParserInit(CSVParser* self, CmdBegin* cmd, FileList* files, SourceType sourcetype) +/* + * 功能:初始化 CSV 解析器 + * + * 参数列表: + * self:CSVParser 指针,表示 CSV 解析器对象 + * cmd:CmdBegin 指针,表示命令开始信息 + * files:FileList 指针,表示文件列表信息 + * sourcetype:SourceType 枚举值,表示源类型 + * + * 注意: + * 该函数用于初始化 CSV 解析器对象。首先调用 ReadableParserInit 进行通用属性初始化,然后根据命令中的 + * m_quote 和 m_escape 属性,设置 CSV 解析器的引用字符和转义字符。如果这些属性为 NULL 或相等,会进行默认设置。 + */ + +static void CSVParserInit(CSVParser *self, CmdBegin *cmd, FileList *files, SourceType sourcetype) { + // 调用通用可读解析器的初始化函数进行通用属性的初始化 ReadableParserInit(self, cmd, files, sourcetype); + + // 设置 CSV 解析器的引用字符和转义字符 self->quote = cmd->m_quote; self->escape = cmd->m_escape; + + // 如果引用字符为空,则默认为双引号 self->quote = self->quote ? self->quote : '"'; + + // 如果转义字符为空,则默认为双引号 self->escape = self->escape ? self->escape : '"'; + + // 如果转义字符与引用字符相等,则将转义字符设置为空字符 self->escape = self->escape == self->quote ? '\0' : self->escape; } -static void FixParserInit(FixParser* self, CmdBegin* cmd, FileList* files, SourceType sourcetype) +/* + * 功能:初始化固定格式解析器 + * + * 参数列表: + * self:FixParser 指针,表示固定格式解析器对象 + * cmd:CmdBegin 指针,表示命令开始信息 + * files:FileList 指针,表示文件列表信息 + * sourcetype:SourceType 枚举值,表示源类型 + * + * 注意: + * 该函数用于初始化固定格式解析器对象。首先调用 ReadableParserInit 进行通用属性初始化,然后根据命令中的 + * m_fixSize 属性,设置固定格式解析器的行大小。如果行大小为 0,则将读取行处理函数设置为 + * GenericReadLines。 + */ + +static void FixParserInit(FixParser *self, CmdBegin *cmd, FileList *files, SourceType sourcetype) { + // 调用通用可读解析器的初始化函数进行通用属性的初始化 ReadableParserInit(self, cmd, files, sourcetype); + + // 设置固定格式解析器的行大小 self->rowSize = cmd->m_fixSize; + + // 如果行大小为 0,则将读取行处理函数设置为 GenericReadLines if (self->rowSize == 0) self->readlines = (ParserReadLineProc)GenericReadLines; } -static void WritableParserInit(WritableParser* self, CmdBegin* cmd, FileList* files) +/* + * 功能:初始化可写解析器 + * + * 参数列表: + * self:WritableParser 指针,表示可写解析器对象 + * cmd:CmdBegin 指针,表示命令开始信息 + * files:FileList 指针,表示文件列表信息 + * 无 + * + * 注意: + * 该函数用于初始化可写解析器对象。首先根据宏定义判断是否支持 GDS_SERVER,然后进行相应的初始化操作。 + * 如果支持 GDS_SERVER,则进行以下操作: + * 1. 设置 hasHeader 为 false。 + * 2. 创建行缓冲区 line_buffer 和源对象 source。 + * 3. 设置源对象的路径为命令中的 URL 经过 UriToLocalPath 处理后的结果。 + * 4. 复制命令中的前缀到 prefix 属性。 + * 5. 如果命令中存在文件头文件,则获取文件头信息,生成新的输出文件,并将文件头写入输出文件。 + * 6. 如果命令中存在换行符属性,则复制到 eol 属性。 + */ + +static void WritableParserInit(WritableParser *self, CmdBegin *cmd, FileList *files) { #ifdef GDS_SERVER + // 设置 hasHeader 为 false self->hasHeader = false; + + // 创建行缓冲区 line_buffer 和源对象 source self->line_buffer = evbuffer_new(); self->source = CreateSource(NULL, SOURCE_TYPE_FILE); + // 检查是否成功创建行缓冲区和源对象 if ((self->line_buffer == NULL) || (self->source == NULL)) parser_log(LEVEL_ERROR, "failed to init parser, out of memory"); + // 设置源对象的路径为命令中的 URL 经过 UriToLocalPath 处理后的结果 self->source->SetPath(UriToLocalPath(cmd->m_url)); + + // 复制命令中的前缀到 prefix 属性 if (cmd->m_prefix == NULL) { parser_log(LEVEL_ERROR, "the given prefix is NULL"); } self->prefix = strdup(cmd->m_prefix); if (self->prefix == NULL) parser_log(LEVEL_ERROR, "failed to copy prefix, out of memory"); - if (cmd->m_fileheader != NULL) - GetFileHeader(self, UriToLocalPath(cmd->m_fileheader).c_str()); - self->source->GenerateNewFileForExport(self->prefix, "dat"); + + // 如果命令中存在文件头文件,则获取文件头信息,生成新的输出文件,并将文件头写入输出文件 if (cmd->m_fileheader != NULL) { + GetFileHeader(self, UriToLocalPath(cmd->m_fileheader).c_str()); + self->source->GenerateNewFileForExport(self->prefix, "dat"); self->source->SourceWrite(self->fileheader, self->headerSize); self->writelines = (ParserWriteLineProc)GenericWriteLines; } + + // 如果命令中存在换行符属性,则复制到 eol 属性 if (cmd->m_eol != NULL) { self->eol = strdup(cmd->m_eol); if (self->eol == NULL) @@ -1022,85 +1448,212 @@ static void WritableParserInit(WritableParser* self, CmdBegin* cmd, FileList* fi #endif } -Parser* CreateCSVParser() +/* + * 功能:创建 CSV 解析器对象 + * + * 返回值: + * 返回创建的 CSV 解析器对象指针 + * + * 注意: + * 该函数用于创建 CSV + * 解析器对象。首先分配内存空间来存储解析器对象,然后进行相应的初始化操作,包括设置函数指针和属性值。 + * 最后,返回创建的解析器对象指针。 + */ + +Parser *CreateCSVParser() { errno_t rc; - CSVParser* self = (CSVParser*)malloc(sizeof(CSVParser)); + // 分配内存空间来存储 CSV 解析器对象 + CSVParser *self = (CSVParser *)malloc(sizeof(CSVParser)); + // 检查内存分配是否成功 if (NULL == self) parser_log(LEVEL_ERROR, "failed to create parser, out of memory"); + // 使用 memset_s 函数将解析器对象的内存清零 rc = memset_s(self, sizeof(CSVParser), 0, sizeof(CSVParser)); parser_securec_check(rc); + + // 设置解析器对象的初始化函数指针为 CSVParserInit self->init = (ParserInitProc)CSVParserInit; + + // 设置解析器对象的读取行函数指针为 GenericReadLines self->readlines = (ParserReadLineProc)GenericReadLines; + + // 设置解析器对象的写入行函数指针为 NopWriteLine self->writelines = (ParserWriteLineProc)NopWriteLine; + + // 设置解析器对象的销毁函数指针为 DestroyReadableParser self->destroy = (ParserDestroyProc)DestroyReadableParser; + + // 设置解析器对象的清理函数指针为 NopCleanup self->cleanup = (ParserDestroyProc)NopCleanup; + + // 初始化解析器对象的属性值 self->in_quote = false; self->lastWasEsc = false; self->in_cr = false; - return (Parser*)self; + + // 返回创建的 CSV 解析器对象指针 + return (Parser *)self; } -Parser* CreateTextParser() +/* + * 功能:创建文本解析器对象 + * + * 参数列表:无 + * + * 返回值: + * 返回创建的文本解析器对象指针 + * + * 注意: + * 该函数用于创建文本解析器对象。首先分配内存空间来存储解析器对象,然后进行相应的初始化操作,包括设置函数指针和属性值。 + * 最后,返回创建的解析器对象指针。 + */ + +Parser *CreateTextParser() { errno_t rc; - ReadableParser* self = (ReadableParser*)malloc(sizeof(ReadableParser)); + // 分配内存空间来存储文本解析器对象 + ReadableParser *self = (ReadableParser *)malloc(sizeof(ReadableParser)); + // 检查内存分配是否成功 if (NULL == self) parser_log(LEVEL_ERROR, "failed to create parser, out of memory"); + // 使用 memset_s 函数将解析器对象的内存清零 rc = memset_s(self, sizeof(ReadableParser), 0, sizeof(ReadableParser)); parser_securec_check(rc); + + // 设置解析器对象的初始化函数指针为 ReadableParserInit self->init = (ParserInitProc)ReadableParserInit; + + // 设置解析器对象的读取行函数指针为 GenericReadLines self->readlines = (ParserReadLineProc)GenericReadLines; + + // 设置解析器对象的写入行函数指针为 NopWriteLine self->writelines = (ParserWriteLineProc)NopWriteLine; + + // 设置解析器对象的销毁函数指针为 DestroyReadableParser self->destroy = (ParserDestroyProc)DestroyReadableParser; + + // 设置解析器对象的清理函数指针为 NopCleanup self->cleanup = (ParserDestroyProc)NopCleanup; - return (Parser*)self; + + // 返回创建的文本解析器对象指针 + return (Parser *)self; } -Parser* CreateFixedParser() +/* + * 功能:创建固定格式解析器对象 + * + * 参数列表:无 + * + * 返回值: + * 返回创建的固定格式解析器对象指针 + * + * 注意: + * 该函数用于创建固定格式解析器对象。首先分配内存空间来存储解析器对象,然后进行相应的初始化操作,包括设置函数指针和属性值。 + * 最后,返回创建的解析器对象指针。 + */ + +Parser *CreateFixedParser() { errno_t rc; - FixParser* self = (FixParser*)malloc(sizeof(FixParser)); + // 分配内存空间来存储固定格式解析器对象 + FixParser *self = (FixParser *)malloc(sizeof(FixParser)); + // 检查内存分配是否成功 if (NULL == self) parser_log(LEVEL_ERROR, "failed to create parser, out of memory"); + // 使用 memset_s 函数将解析器对象的内存清零 rc = memset_s(self, sizeof(FixParser), 0, sizeof(FixParser)); parser_securec_check(rc); + + // 设置解析器对象的初始化函数指针为 FixParserInit self->init = (ParserInitProc)FixParserInit; + + // 设置解析器对象的读取行函数指针为 GenericReadLines self->readlines = (ParserReadLineProc)GenericReadLines; + + // 设置解析器对象的写入行函数指针为 NopWriteLine self->writelines = (ParserWriteLineProc)NopWriteLine; + + // 设置解析器对象的销毁函数指针为 DestroyReadableParser self->destroy = (ParserDestroyProc)DestroyReadableParser; + + // 设置解析器对象的清理函数指针为 NopCleanup self->cleanup = (ParserDestroyProc)NopCleanup; - return (Parser*)self; + + // 返回创建的固定格式解析器对象指针 + return (Parser *)self; } -Parser* CreateWritableParser() +/* + * 功能:创建可写解析器对象 + * + * 参数列表:无 + * + * 返回值: + * 返回创建的可写解析器对象指针 + * + * 注意: + * 该函数用于创建可写解析器对象。首先分配内存空间来存储解析器对象,然后进行相应的初始化操作,包括设置函数指针和属性值。 + * 最后,返回创建的解析器对象指针。 + */ + +Parser *CreateWritableParser() { errno_t rc; - WritableParser* self = (WritableParser*)malloc(sizeof(WritableParser)); + // 分配内存空间来存储可写解析器对象 + WritableParser *self = (WritableParser *)malloc(sizeof(WritableParser)); + // 检查内存分配是否成功 if (NULL == self) parser_log(LEVEL_ERROR, "failed to create parser, out of memory"); + // 使用 memset_s 函数将解析器对象的内存清零 rc = memset_s(self, sizeof(WritableParser), 0, sizeof(WritableParser)); parser_securec_check(rc); + + // 设置解析器对象的初始化函数指针为 WritableParserInit self->init = (ParserInitProc)WritableParserInit; + + // 设置解析器对象的读取行函数指针为 NopReadLine self->readlines = (ParserReadLineProc)NopReadLine; + + // 设置解析器对象的写入行函数指针为 GenericWriteLines self->writelines = (ParserWriteLineProc)GenericWriteLines; + + // 设置解析器对象的销毁函数指针为 DestroyWritableParser self->destroy = (ParserDestroyProc)DestroyWritableParser; + + // 设置解析器对象的清理函数指针为 CleanupWritablParser self->cleanup = (ParserDestroyProc)CleanupWritablParser; - return (Parser*)self; + + // 返回创建的可写解析器对象指针 + return (Parser *)self; } -Parser* CreateParser(FileFormat format) -{ - Parser* parser = NULL; +/* + * 功能:创建解析器对象 + * + * 参数列表: + * - format:文件格式(FORMAT_TEXT、FORMAT_CSV、FORMAT_FIXED、FORMAT_REMOTEWRITE) + * + * 返回值: + * 返回创建的解析器对象指针 + * + * 注意: + * 该函数用于根据文件格式创建相应的解析器对象。根据传入的文件格式,选择对应的创建函数来创建解析器对象,然后返回创建的解析器对象指针。 + */ +Parser *CreateParser(FileFormat format) +{ + Parser *parser = NULL; + + // 根据文件格式选择对应的创建函数来创建解析器对象 switch (format) { case FORMAT_TEXT: parser = CreateTextParser(); @@ -1117,86 +1670,169 @@ Parser* CreateParser(FileFormat format) default: parser_log(LEVEL_ERROR, "un-support format."); } + + // 返回创建的解析器对象指针 return parser; } -static void DestroyParser(Parser* self) +/* + * 功能:销毁解析器对象 + * + * 参数列表: + * - self:要销毁的解析器对象指针 + * + * 注意: + * 该函数用于销毁解析器对象以及相关资源。首先释放解析器对象中的 source、line_buffer、prefix、eol + * 等资源,并最终释放解析器对象自身的内存。 + */ + +static void DestroyParser(Parser *self) { + // 释放 source 对象 if (self->source != NULL) { delete self->source; self->source = NULL; } + + // 释放 line_buffer 对象(仅在 GDS_SERVER 情况下存在) #ifdef GDS_SERVER if (self->line_buffer != NULL) { evbuffer_free(self->line_buffer); self->line_buffer = NULL; } #endif + + // 释放 prefix 字符串 if (self->prefix != NULL) { free(self->prefix); self->prefix = NULL; } + + // 释放 eol 字符串 if (self->eol != NULL) { free(self->eol); self->eol = NULL; } + + // 清理 line_buffers 对象 self->line_buffers.Clean(); + + // 最后释放解析器对象自身的内存 free(self); } -static void DestroyReadableParser(ReadableParser* self) +/* + * 功能:销毁可读解析器对象 + * + * 参数列表: + * - self:要销毁的可读解析器对象指针 + * + * 注意: + * 该函数用于销毁可读解析器对象以及相关资源。首先释放可读解析器对象中的 rec_buf 缓冲区,然后调用 DestroyParser + * 函数释放其他资源,最终将解析器对象自身指针置为 NULL。 + */ + +static void DestroyReadableParser(ReadableParser *self) { + // 释放 rec_buf 缓冲区 if (self->rec_buf != NULL) { free(self->rec_buf); self->rec_buf = NULL; } + + // 调用 DestroyParser 函数释放其他资源 DestroyParser(self); + + // 将解析器对象自身指针置为 NULL self = NULL; } -static void DestroyWritableParser(WritableParser* self) +/* + * 功能:销毁可写解析器对象 + * + * 参数列表: + * - self:要销毁的可写解析器对象指针 + * + * 注意: + * 该函数用于销毁可写解析器对象以及相关资源。首先释放可写解析器对象中的 fileheader 缓冲区,然后调用 SourceFlush + * 函数刷新数据至文件, 最后调用 DestroyParser 函数释放其他资源,最终将解析器对象自身指针置为 NULL。 + */ + +static void DestroyWritableParser(WritableParser *self) { + // 释放 fileheader 缓冲区 if (self->fileheader != NULL) { free(self->fileheader); self->fileheader = NULL; } + // 刷新数据至文件 self->source->SourceFlush(); + + // 调用 DestroyParser 函数释放其他资源 DestroyParser(self); + + // 将解析器对象自身指针置为 NULL self = NULL; } -static void NopCleanup(Parser* self) +static void NopCleanup(Parser *self) {} -static void CleanupWritablParser(WritableParser* self) +/* + * 功能:清理可写解析器对象 + * + * 参数列表: + * - self:要清理的可写解析器对象指针 + * + * 注意: + * 该函数用于清理可写解析器对象,主要工作包括关闭当前文件但不刷新数据(使用 CloseCurrentFileNoFlush + * 函数)和删除未提交的文件。 具体操作包括:获取文件列表,关闭当前文件但不刷新数据,遍历文件列表删除未提交的文件。 + */ + +static void CleanupWritablParser(WritableParser *self) { FileList::iterator i; - FileList* files = self->source->GetFileList(); + FileList *files = self->source->GetFileList(); + + // 关闭当前文件但不刷新数据 self->source->CloseCurrentFileNoFlush(); - // Delete uncommited files - // + // 删除未提交的文件 for (i = files->begin(); i != files->end(); i++) { (void)unlink(i->c_str()); } } #ifdef GDS_SERVER -static void GetFileHeader(WritableParser* self, const char* path) +/* + * 功能:从用户定义的文件头中获取文件头信息 + * + * 参数列表: + * - self:可写解析器对象指针,用于存储文件头信息 + * - path:用户定义的文件头文件路径 + * + * 注意: + * 该函数用于从用户定义的文件头文件中获取文件头信息,主要工作包括:打开文件,读取文件头数据,处理多行文件头,存储文件头信息。 + * 具体操作包括:分配文件头缓冲区,打开文件,读取文件头数据,处理多行文件头,存储文件头信息。 + */ +static void GetFileHeader(WritableParser *self, const char *path) { - FILE* fd = NULL; + FILE *fd = NULL; int nread = 0; - char* eol = NULL; + char *eol = NULL; - self->fileheader = (char*)calloc(1, FILEHEADER_BUF_SIZE + 1); + // 分配文件头缓冲区 + self->fileheader = (char *)calloc(1, FILEHEADER_BUF_SIZE + 1); if (self->fileheader == NULL) parser_log(LEVEL_ERROR, "out of memory"); + // 打开文件 fd = fopen(path, "r"); if (fd == NULL) parser_log(LEVEL_ERROR, "failed to open \"%s\"", path); + // 读取文件头数据 nread = fread(self->fileheader, 1, FILEHEADER_BUF_SIZE, fd); int err_no = ferror(fd); fclose(fd); @@ -1213,6 +1849,7 @@ static void GetFileHeader(WritableParser* self, const char* path) // if the user-define header file has multiple rows, // we will use the first row as the header line only. // + // 如果用户定义的文件头文件有多行,仅使用第一行作为文件头 eol = FindEolChar(self->fileheader, nread, NULL, NULL, NULL); if (eol != NULL) { if (*eol != '\n' && *(eol + 1) == '\n') @@ -1225,54 +1862,106 @@ static void GetFileHeader(WritableParser* self, const char* path) parser_log(LEVEL_ERROR, "user-define header cannot longer than 1MB"); } #endif - +/* + * 功能:初始化行缓冲区 + * + * 注意: + * 该函数用于初始化行缓冲区,主要工作包括: + * 1. 分配行缓冲区内存 + * 2. 初始化缓冲区参数和状态 + * + * 具体操作包括:分配行缓冲区内存,初始化参数和状态。 + */ void GDS::LineBuffer::Init() { + // 设置缓冲区初始大小为 MAX_BLK_SIZE m_buf_len = MAX_BLK_SIZE; - m_buf = (char*)malloc(m_buf_len); + + // 分配缓冲区内存 + m_buf = (char *)malloc(m_buf_len); if (m_buf == NULL) parser_log(LEVEL_ERROR, "failed to init line buffer, out of memory"); + // 初始化已使用长度和行数 m_used_len = 0; m_row_num = 0; + // 初始化当前行指针,当前行长度,当前行是否已完成 m_cur_line = m_buf; m_cur_line_len = 0; m_cur_line_completed = true; + // 初始化输出指针为空 m_output = NULL; #ifdef OBS_SERVER + // 初始化读取位置 m_read_pos = 0; + + // 切换到 OBSParserContext 内存上下文 Assert(u_sess->cmd_cxt.OBSParserContext); MemoryContext oldcontext = MemoryContextSwitchTo(u_sess->cmd_cxt.OBSParserContext); + + // 创建一个新的 StringInfo 对象,用于处理过载缓冲区 m_overload_buf = makeStringInfo(); + + // 切换回原来的内存上下文 MemoryContextSwitchTo(oldcontext); + + // 初始化过载缓冲区是否已完成 m_overload_buf_completed = false; #endif } - +/* + * 功能:重置行缓冲区状态 + * + * 注意: + * 该函数用于重置行缓冲区的状态,主要工作包括: + * 1. 移动未完成的行到缓冲区前部(如果有未完成的行) + * 2. 重置缓冲区参数和状态 + * + * 具体操作包括:移动未完成的行到缓冲区前部(如果有未完成的行),重置参数和状态。 + */ void GDS::LineBuffer::Reset() { if (!m_cur_line_completed && (m_buf != m_cur_line)) { - // move uncomplete line to the front + // 将未完成的行移到缓冲区前部 errno_t rc = memmove_s(m_buf, m_buf_len, m_cur_line, (m_cur_line_len + ROW_HEADER_SIZE)); parser_securec_check(rc); + // 更新已使用长度 m_used_len = m_cur_line_len + ROW_HEADER_SIZE; + + // 重置当前行指针为缓冲区起始位置 m_cur_line = m_buf; } else { + // 未有未完成的行,重新初始化成员变量 m_used_len = 0; m_cur_line = m_buf; m_cur_line_len = 0; } #ifdef OBS_SERVER + // 重置读取位置 m_read_pos = 0; #endif } - -int GDS::LineBuffer::AppendLine(const char* buf, int buf_len, bool isComplete) +/* + * 功能:向行缓冲区追加数据 + * + * 参数列表: + * - buf:要追加到行缓冲区的数据的指针 + * - buf_len:要追加的数据的长度 + * - isComplete:标志是否追加的数据组成了一个完整的行 + * + * 返回值: + * - 返回实际追加到行缓冲区的数据长度,如果追加失败则返回负数 + * + * 注意: + * 该函数用于将数据追加到行缓冲区,主要工作包括:检查参数、检查缓冲区空间、处理数据、更新行头信息、拷贝数据。 + * 具体操作包括:检查参数、检查缓冲区空间、处理数据、更新行头信息、拷贝数据。 + */ +int GDS::LineBuffer::AppendLine(const char *buf, int buf_len, bool isComplete) { if (buf == NULL) return -1; @@ -1340,15 +2029,15 @@ int GDS::LineBuffer::AppendLine(const char* buf, int buf_len, bool isComplete) if (isComplete) { ++m_row_num; char row_header[ROW_HEADER_SIZE]; - *(uint32_t*)&row_header[0] = htonl((uint32_t)(m_cur_line_len + 4)); // add size of row_num - *(uint32_t*)&row_header[4] = htonl(m_row_num); + *(uint32_t *)&row_header[0] = htonl((uint32_t)(m_cur_line_len + 4)); // add size of row_num + *(uint32_t *)&row_header[4] = htonl(m_row_num); ASSERT(m_used_len >= ROW_HEADER_SIZE); rc = memcpy_s(m_cur_line, m_buf_len - (m_cur_line - m_buf), row_header, ROW_HEADER_SIZE); parser_securec_check(rc); } // copy data - char* cur_ptr = m_buf + m_used_len; + char *cur_ptr = m_buf + m_used_len; rc = memcpy_s(cur_ptr, (m_buf_len - m_used_len), buf, buf_len); parser_securec_check(rc); @@ -1358,7 +2047,21 @@ int GDS::LineBuffer::AppendLine(const char* buf, int buf_len, bool isComplete) } #ifdef GDS_SERVER -int GDS::LineBuffer::PackData(evbuffer* dest, bool isFlush) +/* + * 功能:打包数据并发送至目标缓冲区 + * + * 参数列表: + * - dest:目标缓冲区 + * - isFlush:是否要强制发送 + * + * 返回值: + * - 成功返回 0,失败返回错误码 + * + * 注意: + * 该函数用于将数据打包并发送至目标缓冲区,主要工作包括:计算包大小、构建包头、发送数据、重置行缓冲区。 + * 具体操作包括:计算包大小、构建包头、发送数据、重置行缓冲区。 + */ +int GDS::LineBuffer::PackData(evbuffer *dest, bool isFlush) { // 1. compute package size int package_size = (isFlush || m_cur_line_completed) ? m_used_len : (m_cur_line - m_buf); @@ -1372,8 +2075,8 @@ int GDS::LineBuffer::PackData(evbuffer* dest, bool isFlush) if (isFlush && !m_cur_line_completed) { char row_header[ROW_HEADER_SIZE]; - *(uint32_t*)&row_header[0] = htonl((uint32_t)(m_cur_line_len + 4)); // add size of row_num - *(uint32_t*)&row_header[4] = htonl(++m_row_num); + *(uint32_t *)&row_header[0] = htonl((uint32_t)(m_cur_line_len + 4)); // add size of row_num + *(uint32_t *)&row_header[4] = htonl(++m_row_num); rc = memcpy_s(m_cur_line, m_buf_len - (m_cur_line - m_buf), row_header, ROW_HEADER_SIZE); parser_securec_check(rc); @@ -1385,7 +2088,7 @@ int GDS::LineBuffer::PackData(evbuffer* dest, bool isFlush) char package_header[GDSCmdHeaderSize]; package_header[0] = CMD_TYPE_DATA; - *(uint32_t*)&package_header[1] = htonl((uint32_t)package_size); + *(uint32_t *)&package_header[1] = htonl((uint32_t)package_size); // 3. add to evbffer int retval = 0; @@ -1403,8 +2106,23 @@ int GDS::LineBuffer::PackData(evbuffer* dest, bool isFlush) return retval; } - -int GDS::LineBuffer::SendOverloadBuf(evbuffer* dest, const char* buf, int buf_len, bool isComplete) +/* + * 功能:发送溢出缓冲区至目标缓冲区 + * + * 参数列表: + * - dest:目标缓冲区 + * - buf:要发送的数据的指针 + * - buf_len:要发送的数据的长度 + * - isComplete:标志是否要发送的数据组成了一个完整的行 + * + * 返回值: + * - 成功返回 0,失败返回错误码 + * + * 注意: + * 该函数用于将溢出缓冲区的数据发送至目标缓冲区,主要工作包括:更新行头、构建数据包头、发送数据、重置行缓冲区。 + * 具体操作包括:更新行头、构建数据包头、发送数据、重置行缓冲区。 + */ +int GDS::LineBuffer::SendOverloadBuf(evbuffer *dest, const char *buf, int buf_len, bool isComplete) { char row_header[ROW_HEADER_SIZE]; @@ -1414,8 +2132,8 @@ int GDS::LineBuffer::SendOverloadBuf(evbuffer* dest, const char* buf, int buf_le if (isComplete) ++m_row_num; - *(uint32_t*)&row_header[0] = htonl((uint32_t)(m_cur_line_len + buf_len + 4)); // add size of row_num - *(uint32_t*)&row_header[4] = htonl(m_row_num); + *(uint32_t *)&row_header[0] = htonl((uint32_t)(m_cur_line_len + buf_len + 4)); // add size of row_num + *(uint32_t *)&row_header[4] = htonl(m_row_num); /* * Send the buffer; @@ -1427,7 +2145,7 @@ int GDS::LineBuffer::SendOverloadBuf(evbuffer* dest, const char* buf, int buf_le else package_header[0] = CMD_TYPE_DATA_SEG; - *(uint32_t*)&package_header[1] = htonl((uint32_t)(m_cur_line_len + buf_len + ROW_HEADER_SIZE)); + *(uint32_t *)&package_header[1] = htonl((uint32_t)(m_cur_line_len + buf_len + ROW_HEADER_SIZE)); int retval = 0; @@ -1471,13 +2189,28 @@ int GDS::LineBuffer::SendOverloadBuf(evbuffer* dest, const char* buf, int buf_le #ifdef OBS_SERVER #ifndef ENABLE_LITE_MODE -static size_t SourceRead_OBS(Source* self, void* buffer, size_t len) +/* + * 功能:从OBS(Object Storage Service)中读取数据 + * + * 参数列表: + * - self:源对象指针,用于处理OBS读取 + * - buffer:目标缓冲区,用于存储读取到的数据 + * - len:要读取的数据长度 + * + * 返回值: + * - 返回已读取的数据长度 + * + * 注意: + * 该函数用于从OBS中读取数据,主要工作包括:检查是否已读取完毕、创建OBS读取处理器、重复尝试读取数据、标记是否已到达对象的末尾。 + * 具体操作包括:检查是否已读取完毕、创建OBS读取处理器、重复尝试读取数据、标记是否已到达对象的末尾。 + */ +static size_t SourceRead_OBS(Source *self, void *buffer, size_t len) { size_t nread = 0; size_t already_read = 0; if (self->m_obs_end) { - OBSReadWriteHandler* handler = self->GetOBSReadWriteHandler(); + OBSReadWriteHandler *handler = self->GetOBSReadWriteHandler(); if (handler != NULL) { DestroyObsReadWriteHandler(handler, false); @@ -1487,10 +2220,10 @@ static size_t SourceRead_OBS(Source* self, void* buffer, size_t len) return (size_t)0; } - OBSReadWriteHandler* handler = self->GetOBSReadWriteHandler(); + OBSReadWriteHandler *handler = self->GetOBSReadWriteHandler(); if (handler == NULL) { - const char* current_file = self->m_files[self->m_current - 1].c_str(); + const char *current_file = self->m_files[self->m_current - 1].c_str(); handler = CreateObsReadWriteHandler(current_file, OBS_READ, self->m_obs_options); self->SetOBSReadWriteHandler(handler); } @@ -1502,7 +2235,7 @@ static size_t SourceRead_OBS(Source* self, void* buffer, size_t len) PROFILING_OBS_START(); pgstat_report_waitevent(WAIT_EVENT_OBS_READ); do { - nread = read_bucket_object(handler, (char*)buffer + already_read, (len - already_read)); + nread = read_bucket_object(handler, (char *)buffer + already_read, (len - already_read)); already_read += nread; /* nread may be < 0 */ /* Mark we get the end of current object */ @@ -1514,10 +2247,22 @@ static size_t SourceRead_OBS(Source* self, void* buffer, size_t len) return already_read; } - -static bool SourceNext_OBS(Source* self) +/* + * 功能:获取下一个OBS对象 + * + * 参数列表: + * - self:源对象指针,用于处理OBS读取 + * + * 返回值: + * - 如果成功获取下一个OBS对象,则返回true;否则返回false。 + * + * 注意: + * 该函数用于获取下一个OBS对象,主要工作包括:检查是否已到达文件列表末尾、循环遍历文件列表、跳过特殊文件。 + * 具体操作包括:检查是否已到达文件列表末尾、循环遍历文件列表、跳过特殊文件。 + */ +static bool SourceNext_OBS(Source *self) { - const char* current_file = NULL; + const char *current_file = NULL; if ((size_t)(self->m_current) >= self->m_files.size()) return false; @@ -1579,19 +2324,19 @@ bool GDS::LineBuffer::GetNextLine(StringInfo output_line) Assert(m_read_pos < m_used_len); - char* buf = m_buf + m_read_pos; + char *buf = m_buf + m_read_pos; /* Parsing the tuple header part */ char header[ROW_HEADER_SIZE]; errno_t rc = memcpy_s(header, ROW_HEADER_SIZE, buf, ROW_HEADER_SIZE); parser_securec_check(rc); - int64_t tuplen = ntohl(*(uint32_t*)&(header[0])); - int64_t nth = ntohl(*(uint32_t*)&(header[4])); + int64_t tuplen = ntohl(*(uint32_t *)&(header[0])); + int64_t nth = ntohl(*(uint32_t *)&(header[4])); if (nth < 0 || (unsigned int64_t)nth > PG_UINT32_MAX || tuplen < 0 || (unsigned int64_t)tuplen > MaxAllocSize + GDS_HEADER_LEN) { - parser_log( - LEVEL_ERROR, "Linebuffer's content is trashed as tuple's ID %lu and length is not valid %lu", nth, tuplen); + parser_log(LEVEL_ERROR, "Linebuffer's content is trashed as tuple's ID %lu and length is not valid %lu", nth, + tuplen); } /* Minus the tuple id in current line buffer to the actual tuplelen */ @@ -1629,8 +2374,8 @@ void GDS::LineBuffer::MarkLastLineCompleted() /* just like PackData (dest, TRUE) , fill the ROW HEADER of the last uncompleted row, and mark it completed */ char row_header[ROW_HEADER_SIZE]; - *(uint32_t*)&row_header[0] = htonl((uint32_t)(m_cur_line_len + 4)); // add size of row_num - *(uint32_t*)&row_header[4] = htonl(++m_row_num); + *(uint32_t *)&row_header[0] = htonl((uint32_t)(m_cur_line_len + 4)); // add size of row_num + *(uint32_t *)&row_header[4] = htonl(++m_row_num); errno_t rc = memcpy_s(m_cur_line, m_buf_len - (m_cur_line - m_buf), row_header, ROW_HEADER_SIZE); parser_securec_check(rc); @@ -1659,7 +2404,7 @@ void GDS::LineBuffer::MarkLastLineCompleted() * * Important: THIS FOUNCTION JUST USE FOR OBS , DO NOT USE FOR GDS. */ -void GDS::LineBuffer::SaveOverloadBuf(StringInfo dest, const char* buf, int buf_len, bool isComplete) +void GDS::LineBuffer::SaveOverloadBuf(StringInfo dest, const char *buf, int buf_len, bool isComplete) { Assert((0 == m_used_len) || ((false == m_cur_line_completed) && (m_cur_line == m_buf))); diff --git a/src/gausskernel/storage/bulkload/roach_adpter.cpp b/src/gausskernel/storage/bulkload/roach_adpter.cpp index f27ab5a53..95b4a0436 100644 --- a/src/gausskernel/storage/bulkload/roach_adpter.cpp +++ b/src/gausskernel/storage/bulkload/roach_adpter.cpp @@ -32,7 +32,14 @@ template bool getNextRoach(CopyState cstate); template bool getNextRoach(CopyState cstate); template void initRoachState(CopyState cstate, const char *filename, List *totalTask); template void initRoachState(CopyState cstate, const char *filename, List *totalTask); - +/* + * Function: initRoachRoutine + * + * Initializes a RoachRoutine by looking up the "roach_handler" function and + * checking its return type. + * + * Returns: A RoachRoutine pointer. + */ RoachRoutine *initRoachRoutine() { Datum datum; @@ -50,7 +57,16 @@ RoachRoutine *initRoachRoutine() return routine; } - +/* + * Function: getNextRoach + * + * Gets the next Roach data to import or export in a COPY operation. + * + * Parameters: + * - cstate: The COPY operation's state. + * + * Returns: true if successful, false if there's no more data to process. + */ template bool getNextRoach(CopyState cstate) { @@ -78,7 +94,19 @@ bool getNextRoach(CopyState cstate) cstate->roach_context = roach_context; return true; } - +/* + * Function: copyGetRoachData + * + * Reads Roach data for a COPY operation. + * + * Parameters: + * - cstate: The COPY operation's state. + * - databuf: The data buffer to read into. + * - minread: The minimum number of bytes to read. + * - maxread: The maximum number of bytes to read. + * + * Returns: The number of bytes read. + */ int copyGetRoachData(CopyState cstate, void *databuf, int minread, int maxread) { Assert(cstate->roach_routine); @@ -91,7 +119,16 @@ int copyGetRoachData(CopyState cstate, void *databuf, int minread, int maxread) return bytesread; } - +/* + * Function: initRoachState + * + * Initializes the state for a Roach COPY operation. + * + * Parameters: + * - cstate: The COPY operation's state. + * - filename: The Roach file to import/export. + * - totalTask: The total tasks to process. + */ template void initRoachState(CopyState cstate, const char *filename, List *totalTask) { @@ -117,8 +154,8 @@ void initRoachState(CopyState cstate, const char *filename, List *totalTask) char roachPath[PATH_MAX + 1]; const char *pos = strstr(filename, ROACH_PREFIX); pos += ROACH_PREFIX_LEN; - errno_t ret = snprintf_s(roachPath, sizeof(roachPath), PATH_MAX, "%s/%s", pos, - g_instance.attr.attr_common.PGXCNodeName); + errno_t ret = + snprintf_s(roachPath, sizeof(roachPath), PATH_MAX, "%s/%s", pos, g_instance.attr.attr_common.PGXCNodeName); securec_check_ss(ret, "", ""); roachPath[PATH_MAX] = '\0'; @@ -137,7 +174,14 @@ void initRoachState(CopyState cstate, const char *filename, List *totalTask) (void)getNextRoach(cstate); } } - +/* + * Function: endRoachBulkLoad + * + * Ends a Roach bulk load operation. + * + * Parameters: + * - cstate: The COPY operation's state. + */ void endRoachBulkLoad(CopyState cstate) { if (IS_PGXC_DATANODE) { @@ -149,7 +193,14 @@ void endRoachBulkLoad(CopyState cstate) ereport(ERROR, (errcode_for_file_access(), errmsg("could not close roach %s", cstate->filename))); } } - +/* + * Function: exportRoach + * + * Exports data to Roach in a COPY operation. + * + * Parameters: + * - cstate: The COPY operation's state. + */ void exportRoach(CopyState cstate) { Assert(cstate->copy_dest == COPY_ROACH); @@ -196,7 +247,15 @@ void exportRoach(CopyState cstate) resetStringInfo(in); } - +/* + * Function: exportRoachFlushOut + * + * Flushes the data to Roach in a COPY operation. + * + * Parameters: + * - cstate: The COPY operation's state. + * - isWholeLineAtEnd: Indicates if the whole line is at the end. + */ void exportRoachFlushOut(CopyState cstate, bool isWholeLineAtEnd) { Assert(cstate->roach_routine); diff --git a/src/gausskernel/storage/bulkload/vecforeignroutine.cpp b/src/gausskernel/storage/bulkload/vecforeignroutine.cpp index f7e46dafa..13c39a6e2 100644 --- a/src/gausskernel/storage/bulkload/vecforeignroutine.cpp +++ b/src/gausskernel/storage/bulkload/vecforeignroutine.cpp @@ -52,40 +52,88 @@ extern void SyncBulkloadStates(CopyState cstate); extern void CleanBulkloadStates(); // all stuffs used for bulkload(end). // Try to save importing error if needed +/* + * 功能:尝试保存导入错误 + * + * 参数列表: + * importState:导入执行状态指针,包含导入过程中的相关信息 + * node:外部扫描状态指针,用于访问外部扫描相关信息 + * + * 返回值: + * 如果成功保存导入错误,返回true;否则返回false。 + * + * 注意: + * 这个函数用于尝试保存导入错误,它检查错误码是否为数据异常(Data Exception)并且是否可以接受错误。 + * 如果满足条件,它会保存错误记录,并在必要时清除错误状态。 + */ bool TrySaveImportError(DistImportExecutionState *importState, ForeignScanState *node) { + // 检查当前错误码是否为数据异常(Data Exception) if ((ERRCODE_TO_CATEGORY((unsigned int)geterrcode()) == ERRCODE_DATA_EXCEPTION) && DoAcceptOneError(importState)) { + // 如果是数据异常并且DoAcceptOneError返回true,表示可以接受错误 + + // 检查错误码是否为字符不在字符集中或者无法翻译的字符 if (geterrcode() == ERRCODE_CHARACTER_NOT_IN_REPERTOIRE || geterrcode() == ERRCODE_UNTRANSLATABLE_CHARACTER) t_thrd.bulk_cxt.illegal_character_err_cnt++; ListCell *lc = NULL; + // 遍历错误记录器列表,处理每个错误记录 foreach (lc, importState->elogger) { ImportErrorLogger *elogger = (ImportErrorLogger *)lfirst(lc); FormAndSaveImportError(importState, importState->errLogRel, importState->beginTime, elogger); } - // clear error state - // + // 清除错误状态 FlushErrorStateWithoutDeleteChildrenContext(); - return true; + return true; // 返回true表示成功保存导入错误 } - return false; + return false; // 返回false表示没有保存导入错误 } - +/* + * 功能:尝试保存导入错误 + * + * 参数列表: + * cstate:复制状态指针,包含复制过程中的相关信息 + * + * 返回值: + * 如果成功保存导入错误,返回true;否则返回false。 + * + * 注意: + * 这个函数用于尝试保存导入错误,它检查错误码是否为数据异常(Data Exception)并且是否可以接受错误。 + * 如果满足条件,它会保存错误记录,并在必要时清除错误状态。 + */ bool TrySaveImportError(CopyState cstate) { + // 增加错误行数计数器 cstate->errorrows++; - if ((ERRCODE_TO_CATEGORY((unsigned int)geterrcode()) == ERRCODE_DATA_EXCEPTION) && DoAcceptOneError(cstate)) { - FormAndSaveImportError(cstate, cstate->err_table, cstate->copy_beginTime, cstate->logger); - // clear error state - // - FlushErrorStateWithoutDeleteChildrenContext(); - return true; - } - return false; -} + // 检查当前错误码是否为数据异常(Data Exception) + if ((ERRCODE_TO_CATEGORY((unsigned int)geterrcode()) == ERRCODE_DATA_EXCEPTION) && DoAcceptOneError(cstate)) { + // 如果是数据异常并且DoAcceptOneError返回true,表示可以接受错误 + + // 调用函数保存导入错误 + FormAndSaveImportError(cstate, cstate->err_table, cstate->copy_beginTime, cstate->logger); + + // 清除错误状态 + FlushErrorStateWithoutDeleteChildrenContext(); + return true; // 返回true表示成功保存导入错误 + } + return false; // 返回false表示没有保存导入错误 +} +/* + * 功能:执行向量化外部数据导入 + * + * 参数列表: + * node:向量化外部扫描状态指针,包含导入过程中的相关信息 + * + * 返回值: + * 返回包含导入数据的向量批次(VectorBatch)。 + * + * 注意: + * 这个函数用于执行向量化的外部数据导入操作。它从外部数据源中读取数据并将其填充到VectorBatch中。 + * 同时,它还处理错误并保存导入错误记录。 + */ VectorBatch *distExecVecImport(VecForeignScanState *node) { DistImportExecutionState *importState = (DistImportExecutionState *)node->fdw_state; @@ -97,60 +145,45 @@ VectorBatch *distExecVecImport(VecForeignScanState *node) MemoryContext oldMemoryContext; MemoryContext scanMcxt = node->scanMcxt; - /* Set up callback to identify error line number. */ + /* 设置错误回调以识别错误行号 */ errcontext.callback = BulkloadErrorCallback; errcontext.arg = (void *)importState; errcontext.previous = t_thrd.log_cxt.error_context_stack; t_thrd.log_cxt.error_context_stack = &errcontext; - /* - * The protocol for loading a virtual tuple into a slot is first - * ExecClearTuple, then fill the values/isnull arrays, then - * ExecStoreVirtualTuple. If we don't find another row in the file, we - * just skip the last step, leaving the slot empty as required. - * - * We can pass ExprContext = NULL because we read all columns from the - * file, so no need to evaluate default expressions. - * - * We can also pass tupleOid = NULL because we don't allow oids for - * foreign tables. - */ batch->Reset(true); + if (node->m_done) { - /* Remove error callback. */ + /* 移除错误回调 */ t_thrd.log_cxt.error_context_stack = errcontext.previous; return batch; } MemoryContextReset(scanMcxt); oldMemoryContext = MemoryContextSwitchTo(scanMcxt); -#ifndef ENABLE_LITE_MODE - SetObsMemoryContext(((CopyState)importState)->copycontext); -#endif + for (batch->m_rows = 0; batch->m_rows < BatchMaxSize; batch->m_rows++) { -retry: + retry: PG_TRY(); { - /* - * Synchronize the current bulkload states. - */ + /* 同步当前批量加载状态 */ SyncBulkloadStates((CopyState)importState); + + // 从外部数据源中读取下一行数据 found = NextCopyFrom((CopyState)importState, NULL, values, nulls, NULL); } PG_CATCH(); { - /* - * Clean the current bulkload states. - */ - CleanBulkloadStates(); + /* 清理当前批量加载状态 */ + // 尝试保存导入错误,如果成功则重试 if (TrySaveImportError(importState, node)) { (void)MemoryContextSwitchTo(scanMcxt); MemoryContextReset(scanMcxt); CHECK_FOR_INTERRUPTS(); goto retry; } else { - /* clean copy state and re throw */ + /* 清理复制状态并重新抛出异常 */ importState->isExceptionShutdown = true; EndDistImport(importState); PG_RE_THROW(); @@ -158,13 +191,13 @@ retry: } PG_END_TRY(); - /* - * Clean the current bulkload states. - */ + /* 清理当前批量加载状态 */ CleanBulkloadStates(); if (found) { int rows = batch->m_rows; + + // 将读取的数据填充到VectorBatch中 for (int i = 0; i < batch->m_cols; i++) { ScalarVector *vec = &(batch->m_arr[i]); if (nulls[i]) { @@ -187,7 +220,8 @@ retry: } (void)MemoryContextSwitchTo(oldMemoryContext); - /* Remove error callback. */ + + /* 移除错误回调 */ t_thrd.log_cxt.error_context_stack = errcontext.previous; return batch; -- 2.34.1 From b02deb9a956630c6161a44ebf2066fbe41efd53b Mon Sep 17 00:00:00 2001 From: nuoya <1204149038@qq.com> Date: Sat, 30 Sep 2023 18:44:31 +0800 Subject: [PATCH 38/50] Update nas_am.cpp --- .../storage/access/archive/nas_am.cpp | 126 +++++++++--------- 1 file changed, 63 insertions(+), 63 deletions(-) diff --git a/src/gausskernel/storage/access/archive/nas_am.cpp b/src/gausskernel/storage/access/archive/nas_am.cpp index d69c65adc..092cd7aad 100644 --- a/src/gausskernel/storage/access/archive/nas_am.cpp +++ b/src/gausskernel/storage/access/archive/nas_am.cpp @@ -46,22 +46,22 @@ #include "postmaster/alarmchecker.h" #include "replication/walreceiver.h" -//ļ·󳤶ȣʹά +//定义文件路径的最大长度,使代码更加清晰和易于维护 #define MAX_PATH_LEN 1024 -//һͷ +//定义了一个头部长度 static int headerLen = 22; -// һڴNAS洢жȡļ +// 定义一个函数,用于从NAS存储中读取文件数据 size_t NasRead(const char* fileName, const int offset, char *buffer, const int length, ArchiveConfig *nas_config) { /* - fileNameҪȡļ - offsetȡʼƫ - bufferڴ洢ȡݵĻ - lengthҪȡݳȡ - nas_config鵵Ϣָ룬ָ ArchiveConfig ṹйش洢λúõϢ + fileName:要读取的文件名。 + offset:读取的起始偏移量。 + buffer:用于存储读取的数据的缓冲区。 + length:要读取的数据长度。 + nas_config:归档配置信息的指针,指向 ArchiveConfig 结构,包含了有关存储位置和设置的信息。 */ - //ʼ + //初始化 size_t readLength = 0; ArchiveConfig *archive_nas = NULL; char file_path[MAXPGPATH] = {0}; @@ -69,45 +69,45 @@ size_t NasRead(const char* fileName, const int offset, char *buffer, const int l FILE *fp = NULL; struct stat statbuf; - if ((fileName == NULL) || (buffer == NULL)) {//ļͻΪջᱨ + if ((fileName == NULL) || (buffer == NULL)) {//如果文件名和缓冲区为空会报错 ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), errmsg("The parameter cannot be NULL"))); } - //ȡNAS洢Ϣ + //获取NAS存储的配置信息 if (nas_config != NULL) { archive_nas = nas_config; } else { archive_nas = getArchiveConfig(); } - if (archive_nas == NULL) {//޷ȡ鵵Ϣʱ + if (archive_nas == NULL) {//无法获取归档配置信息时报错 ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("Cannot get archive config from replication slots"))); } - if (strncmp(fileName, "global_barrier_records", headerLen) != 0) {//ļ· - //snprintf_sһļ·洢 file_path + if (strncmp(fileName, "global_barrier_records", headerLen) != 0) {//构建文件的完整路径 + //调用snprintf_s函数构建一个完整的文件路径,并将结果存储在 file_path 变量中 ret = snprintf_s(file_path, MAXPGPATH, MAXPGPATH - 1, "%s/%s", archive_nas->archive_prefix, fileName); - // retڴsnprintf_s ķֵֵʾѸʽַijȡ - securec_check_ss(ret, "\0", "\0");//󽫻 ret з '\0' Ӷλλá - //ڼ snprintf_sķֵаȫԼ飬ȷûзʽ + // ret用于储存snprintf_s 函数的返回值。这个返回值表示已格式化字符串的长度。 + securec_check_ss(ret, "\0", "\0");//如果发生错误将会在 ret 中返回 '\0' ,从而帮助定位错误的位置。 + //用于检查 snprintf_s函数的返回值并进行安全性检查,以确保没有发生缓冲区溢出或格式化错误 } else { - char pathPrefix[MAXPGPATH] = {0};//һյcharΪpathPrefixڴ鵵õĴ浵ǰ׺ + char pathPrefix[MAXPGPATH] = {0};//生成一个空的char数组作为pathPrefix(用于储存归档配置的存档前缀) ret = strcpy_s(pathPrefix, MAXPGPATH, archive_nas->archive_prefix); - // archive_nas->archive_prefix pathPrefix + //复制 archive_nas->archive_prefix 到 pathPrefix securec_check_ss(ret, "\0", "\0"); if (!IS_PGXC_COORDINATOR) { - char *p = strrchr(pathPrefix, '/');//pathPrefixҵһбܵλ + char *p = strrchr(pathPrefix, '/');//在pathPrefix中找到最后一个斜杠的位置 if (p == NULL) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("Obs path prefix is invalid"))); } - *p = '\0';//һб滻Ϊ'\0' + *p = '\0';//将最后一个斜杠替换为'\0' } ret = snprintf_s(file_path, MAXPGPATH, MAXPGPATH - 1, "%s/%s", pathPrefix, fileName); securec_check_ss(ret, "\0", "\0"); - // · snprintf_s ķֵ + // 构建完整路径并检查 snprintf_s 函数的返回值 } - //ļǷ + //检查文件是否存在 if (stat(file_path, &statbuf)) { if (errno != ENOENT) { ereport(ERROR, (errcode_for_file_access(), errmsg("could not stat file \"%s\": %m", fileName))); @@ -115,10 +115,10 @@ size_t NasRead(const char* fileName, const int offset, char *buffer, const int l ereport(ERROR, (errcode_for_file_access(), errmsg("The file \"%s\" not exists", fileName))); return readLength; } - //ļȡ + //打开文件并读取数据 canonicalize_path(file_path); fp = fopen(file_path, "rb"); - //ڴļĴд + //对于打开文件产生的错误进行处理 if (fp == NULL) { ereport(ERROR, (errcode_for_file_access(), errmsg("could not read file \"%s\": %m", fileName))); return readLength; @@ -129,98 +129,98 @@ size_t NasRead(const char* fileName, const int offset, char *buffer, const int l return readLength; } - //ȡݵ + //读取数据到缓冲区中 readLength = fread(buffer, 1, statbuf.st_size, fp); fclose(fp); return readLength; } -//úڽдļڱҪʱбݺ +//该函数用于将数据写入文件,并在必要时进行备份和重命名 int NasWrite(const char* fileName, const char *buffer, const int bufferLength, ArchiveConfig *nas_config) { int ret = 0; - ArchiveConfig *archive_nas = NULL;//洢NASõָ - char file_path[MAXPGPATH] = {0};//洢ļ· - char file_path_bak[MAXPGPATH] = {0};//洢ļ· - char *origin_file_path = NULL;//洢淶ļ·ĸ - char *base_path = NULL;//洢ļĻ· - FILE *fp = NULL;//ļָ룬ڲļ + ArchiveConfig *archive_nas = NULL;//存储NAS配置的指针 + char file_path[MAXPGPATH] = {0};//存储构建的文件路 + char file_path_bak[MAXPGPATH] = {0};//存储备份文件路径 + char *origin_file_path = NULL;//存储规范化的文件路径的副本 + char *base_path = NULL;//存储文件的基础路径 + FILE *fp = NULL;//文件指针,用于操作文件 - //鴫IJǷΪգļͻΪʱ + //检查传入的参数是否为空,文件名和缓冲器为空时报错 if ((fileName == NULL) || (buffer == NULL)) { ereport(ERROR, (errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), errmsg("The parameter cannot be NULL"))); } - //ȡ鵵ϢĹ鵵òΪNULLԴĬλûȡ + //获取归档配置信息,如果传入的归档配置参数为NULL,则尝试从默认位置获取 if (nas_config != NULL) { archive_nas = nas_config; } else { archive_nas = getArchiveConfig(); } - //ȡĹ鵵ϢǷЧЧʱ + //检查获取到的归档配置信息是否有效,无效时报错 if (archive_nas == NULL) { ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("Cannot get archive config from replication slots"))); } - //ļļ· - if (strncmp(fileName, "global_barrier_records", headerLen) != 0) {//жļǷΪ"global_barrier_records" + //根据文件名构建完整的文件路径 + if (strncmp(fileName, "global_barrier_records", headerLen) != 0) {//判断文件名是否为"global_barrier_records" ret = snprintf_s(file_path, MAXPGPATH, MAXPGPATH - 1, "%s/%s", archive_nas->archive_prefix, fileName); securec_check_ss(ret, "\0", "\0"); - //·snprintf_sķֵ + //构建完整路径并检查snprintf_s函数的返回值 } else { - char pathPrefix[MAXPGPATH] = {0};//ڴ洢·ǰ׺ - ret = strcpy_s(pathPrefix, MAXPGPATH, archive_nas->archive_prefix);// archive_nas->archive_prefix pathPrefix + char pathPrefix[MAXPGPATH] = {0};//用于存储路径前缀 + ret = strcpy_s(pathPrefix, MAXPGPATH, archive_nas->archive_prefix);//复制 archive_nas->archive_prefix 到 pathPrefix securec_check_ss(ret, "\0", "\0"); - if (!IS_PGXC_COORDINATOR) {// Эڵ - char *p = strrchr(pathPrefix, '/');//pathPrefixвһ'/' - if (p == NULL) {//ûҵ'/'ᱨ + if (!IS_PGXC_COORDINATOR) {// 如果不是协调器节点 + char *p = strrchr(pathPrefix, '/');//在pathPrefix中查找最后一个'/' + if (p == NULL) {//没有找到'/'则会报错 ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("Obs path prefix is invalid"))); } - *p = '\0';//һб滻Ϊ'\0' + *p = '\0';//将最后一个斜杠替换为'\0' } ret = snprintf_s(file_path, MAXPGPATH, MAXPGPATH - 1, "%s/%s", pathPrefix, fileName); securec_check_ss(ret, "\0", "\0"); - // · snprintf_s ķֵ + // 构建完整路径并检查 snprintf_s 函数的返回值 } - canonicalize_path(file_path);//淶ļ·ȥַ'.''..' + canonicalize_path(file_path);//规范化文件路径,去除多余的字符,如'.'、'..'等 - origin_file_path = pstrdup(file_path);//ļ·origin_file_path - base_path = dirname(origin_file_path);//ȡorigin_file_pathļ·ĸ·· - //·Ƿڣ򴴽 + origin_file_path = pstrdup(file_path);//复制文件路径到origin_file_path + base_path = dirname(origin_file_path);//获取origin_file_path(文件的完整路径)的父级路径,即基础路径 + //检查基础路径是否存在,如果不存在则创建 if (!isDirExist(base_path)) { - // pg_mkdir_p ԴĿ¼S_IRWXU ָȨ޲ - if (pg_mkdir_p(base_path, S_IRWXU) != 0) {//ֵΪ0˵Ŀ¼ʧ - // Ŀ¼ʧܣͷڴ沢 - pfree_ext(origin_file_path);//ͷռõڴռ + // 调用 pg_mkdir_p 函数尝试创建目录,S_IRWXU 是指定权限参数 + if (pg_mkdir_p(base_path, S_IRWXU) != 0) {//如果返回值不为0,则说明创建目录失败 + // 如果创建目录失败,释放内存并报告错误 + pfree_ext(origin_file_path);//释放其占用的内存空间 ereport(LOG, (errmsg("could not create path \"%s\"", base_path))); - return -1;// -1 ʾĿ¼ʧ + return -1;//返回 -1 表示创建目录失败 } } - //ļ· ".bak" ӵ file_path + //构建备份文件的路径,将 ".bak" 添加到 file_path ret = snprintf_s(file_path_bak, MAXPGPATH, MAXPGPATH - 1, "%s.bak", file_path); securec_check_ss(ret, "\0", "\0"); - fp = fopen(file_path_bak, "wb");//򿪱ļԶдģʽ - if (fp == NULL) {//򿪱ļʧܣͷڴ沢 + fp = fopen(file_path_bak, "wb");//打开备份文件,以二进制写入模式 + if (fp == NULL) {//如果打开备份文件失败,释放内存并报告错误 pfree_ext(origin_file_path); ereport(LOG, (errmsg("could not create file \"%s\": %m", fileName))); return -1; } - //д뱸ļ + //将数据写入备份文件 if (fwrite(buffer, bufferLength, 1, fp) != 1) { ereport(LOG, (errmsg("could not write file \"%s\": %m", fileName))); pfree_ext(origin_file_path); fclose(fp); return -1; } - //ˢļȷд + //刷新文件缓冲区,确保数据写入磁盘 if (fflush(fp) != 0) { ereport(LOG, (errmsg("could not fflush file \"%s\": %m", fileName))); (void)fclose(fp); pfree_ext(origin_file_path); return -1; } - //ļΪʽļ + //将备份文件重命名为正式文件 if (rename(file_path_bak, file_path) < 0) { ereport(LOG, (errmsg("could not rename file \"%s\": %m", fileName))); (void)fclose(fp); @@ -228,8 +228,8 @@ int NasWrite(const char* fileName, const char *buffer, const int bufferLength, A return -1; } - pfree_ext(origin_file_path);//ͷűռõڴռ - fclose(fp);//رļ + pfree_ext(origin_file_path);//释放被占用的内存空间 + fclose(fp);//关闭文件 return 0; } -- 2.34.1 From fb37311a966111b18a35cd5c31b3ca70c7cb97a6 Mon Sep 17 00:00:00 2001 From: Nemoo <18210033860@163.com> Date: Sat, 30 Sep 2023 21:06:00 +0800 Subject: [PATCH 41/50] Update execStream.cpp --- src/gausskernel/process/stream/execStream.cpp | 1021 ++++++++++------- 1 file changed, 601 insertions(+), 420 deletions(-) diff --git a/src/gausskernel/process/stream/execStream.cpp b/src/gausskernel/process/stream/execStream.cpp index b054c2a1a..f1553f7a6 100755 --- a/src/gausskernel/process/stream/execStream.cpp +++ b/src/gausskernel/process/stream/execStream.cpp @@ -77,15 +77,17 @@ static void InitStreamFlow(StreamFlowCtl* ctl); #define NODENAMELEN 64 +// 检查当前线程是否在处理流数据流递归 bool IsThreadProcessStreamRecursive() { - StreamNodeGroup* stream_nodegroup = u_sess->stream_cxt.global_obj; + StreamNodeGroup* stream_nodegroup = u_sess->stream_cxt.global_obj; // 获取全局流节点组对象 + // 如果不是分布式数据节点,或者流节点组为空,或者同步控制器列表为空 if (!IS_PGXC_DATANODE || stream_nodegroup == NULL || stream_nodegroup->m_syncControllers == NIL) { - return false; + return false; // 返回false,表示当前线程不在处理流数据流递归 } - return true; + return true; // 返回true,表示当前线程在处理流数据流递归 } /* @@ -94,10 +96,11 @@ bool IsThreadProcessStreamRecursive() * @param[IN] plan_tree: plan tree * @return: bool, true if is dummy */ +// 检查线程是否为虚拟线程 bool ThreadIsDummy(Plan* plan_tree) { #ifdef ENABLE_MULTIPLE_NODES - /* For top consumer, we need to check consumer_nodes if top node of plan_tree is Stream. */ + /* 对于顶级消费者,我们需要检查计划树的顶级节点是否为Stream。 */ if (IsA(plan_tree, Stream) || IsA(plan_tree, VecStream)) { Stream* streamNode = (Stream*)plan_tree; List* nodeList = streamNode->consumer_nodes->nodeList; @@ -110,35 +113,36 @@ bool ThreadIsDummy(Plan* plan_tree) #endif } +// 获取Stream节点的分发类型标签 const char* GetStreamTypeRedistribute(Stream* node) { const char* stream_tag = NULL; - bool isRangeListRedis = (node->consumer_nodes->boundaries != NULL); + bool isRangeListRedis = (node->consumer_nodes->boundaries != NULL); // 检查是否为范围或列表分布 bool isRangeRedis = (isRangeListRedis) ? - (node->consumer_nodes->boundaries->locatorType == LOCATOR_TYPE_RANGE) : false; + (node->consumer_nodes->boundaries->locatorType == LOCATOR_TYPE_RANGE) : false; // 检查是否为范围分布 switch (node->smpDesc.distriType) { case LOCAL_DISTRIBUTE: - stream_tag = "LOCAL REDISTRIBUTE"; + stream_tag = "LOCAL REDISTRIBUTE"; // 本地分布 break; case LOCAL_BROADCAST: - stream_tag = "LOCAL BROADCAST"; + stream_tag = "LOCAL BROADCAST"; // 本地广播 break; case LOCAL_ROUNDROBIN: - stream_tag = (node->smpDesc.consumerDop == 1) ? "LOCAL GATHER" : "LOCAL ROUNDROBIN"; + stream_tag = (node->smpDesc.consumerDop == 1) ? "LOCAL GATHER" : "LOCAL ROUNDROBIN"; // 本地轮询或聚集 break; case REMOTE_SPLIT_DISTRIBUTE: { if (isRangeListRedis) { if (isRangeRedis) { - stream_tag = "SPLIT RANGE REDISTRIBUTE"; + stream_tag = "SPLIT RANGE REDISTRIBUTE"; // 范围分布 } else { - stream_tag = "SPLIT LIST REDISTRIBUTE"; + stream_tag = "SPLIT LIST REDISTRIBUTE"; // 列表分布 } } else { - stream_tag = "SPLIT REDISTRIBUTE"; + stream_tag = "SPLIT REDISTRIBUTE"; // 分布 } break; } @@ -146,12 +150,12 @@ const char* GetStreamTypeRedistribute(Stream* node) default: { if (isRangeListRedis) { if (isRangeRedis) { - stream_tag = "RANGE REDISTRIBUTE"; + stream_tag = "RANGE REDISTRIBUTE"; // 范围分布 } else { - stream_tag = "LIST REDISTRIBUTE"; + stream_tag = "LIST REDISTRIBUTE"; // 列表分布 } } else { - stream_tag = "REDISTRIBUTE"; + stream_tag = "REDISTRIBUTE"; // 分布 } break; } @@ -160,47 +164,49 @@ const char* GetStreamTypeRedistribute(Stream* node) return stream_tag; } +// 获取Hybrid类型的Stream节点标签 const char* GetStreamTypeHybrid(Stream* node) { - const char* stream_tag = NULL; - List* skew_list = node->skew_list; - ListCell* lc = NULL; - SkewStreamType sstype = PART_NONE; + const char* stream_tag = NULL; // 定义Stream节点标签 + List* skew_list = node->skew_list; // 获取Skew信息列表 + ListCell* lc = NULL; // 定义ListCell迭代器 + SkewStreamType sstype = PART_NONE; // 定义SkewStreamType变量,默认为PART_NONE + + Assert(list_length(skew_list) >= 1); // 断言:Skew信息列表的长度至少为1 - Assert(list_length(skew_list) >= 1); - - /* check if all qual skew share the same stream type. */ + /* 检查所有的Skew信息是否共享相同的流类型 */ foreach (lc, skew_list) { - QualSkewInfo* qsinfo = (QualSkewInfo*)lfirst(lc); - + QualSkewInfo* qsinfo = (QualSkewInfo*)lfirst(lc); // 获取Skew信息 + if (sstype == PART_NONE) - sstype = qsinfo->skew_stream_type; - + sstype = qsinfo->skew_stream_type; // 如果sstype为PART_NONE,设置为当前Skew信息的流类型 + if (sstype != qsinfo->skew_stream_type) { - sstype = PART_NONE; + sstype = PART_NONE; // 如果存在不同的流类型,将sstype设置为PART_NONE break; } } - + + // 根据sstype的值确定Stream节点标签 switch (sstype) { case PART_REDISTRIBUTE_PART_BROADCAST: - stream_tag = "PART REDISTRIBUTE PART BROADCAST"; + stream_tag = "PART REDISTRIBUTE PART BROADCAST"; // 部分重分布和部分广播 break; case PART_REDISTRIBUTE_PART_ROUNDROBIN: - stream_tag = "PART REDISTRIBUTE PART ROUNDROBIN"; + stream_tag = "PART REDISTRIBUTE PART ROUNDROBIN"; // 部分重分布和部分轮询 break; case PART_REDISTRIBUTE_PART_LOCAL: - stream_tag = "PART REDISTRIBUTE PART LOCAL"; + stream_tag = "PART REDISTRIBUTE PART LOCAL"; // 部分重分布和部分本地 break; case PART_LOCAL_PART_BROADCAST: - stream_tag = "PART LOCAL PART BROADCAST"; + stream_tag = "PART LOCAL PART BROADCAST"; // 部分本地和部分广播 break; default: - stream_tag = "HYBRID"; + stream_tag = "HYBRID"; // 混合类型 break; } - return stream_tag; + return stream_tag; // 返回Stream节点标签 } @@ -210,64 +216,67 @@ const char* GetStreamTypeHybrid(Stream* node) * @param[IN] node: stream plan node * @return: string of stream type */ +// 获取Stream节点的类型描述 const char* GetStreamType(Stream* node) { - Plan* stream_plan = (Plan*)node; - const char* vector_tag = stream_plan->vec_output ? "Vector " : ""; - const char* stream_tag = NULL; - char dop_tag[100] = {0}; - char ng_tag[NODENAMELEN * 2 + 10] = {0}; - StringInfo type = makeStringInfo(); - char* consumerGroupName = NULL; - char* producerGroupName = NULL; + Plan* stream_plan = (Plan*)node; // 将Stream节点转换为Plan节点 + const char* vector_tag = stream_plan->vec_output ? "Vector " : ""; // 判断是否使用向量化执行,设置向量标签 + const char* stream_tag = NULL; // 定义Stream节点的类型标签 + char dop_tag[100] = {0}; // 用于存储并行度信息的字符数组 + char ng_tag[NODENAMELEN * 2 + 10] = {0}; // 用于存储节点组信息的字符数组 + StringInfo type = makeStringInfo(); // 创建StringInfo结构体,用于存储节点类型信息 + char* consumerGroupName = NULL; // 消费者节点组名称 + char* producerGroupName = NULL; // 生产者节点组名称 - /* Set DOP tag if parallel enabled */ + /* 如果启用并行度,设置并行度标签 */ if (node->smpDesc.consumerDop > 1 || node->smpDesc.producerDop > 1) { errno_t rc = sprintf_s(dop_tag, sizeof(dop_tag), " dop: %d/%d", node->smpDesc.consumerDop, node->smpDesc.producerDop); securec_check_ss(rc, "\0", "\0"); } - /* show nodegroup shuffle infomation, ng: group1->group2 */ + /* 显示节点组洗牌信息,格式为:ng: group1->group2 */ if (ng_enable_nodegroup_explain()) { - ExecNodes* consumerNodes = node->consumer_nodes; - ExecNodes* producerNodes = node->scan.plan.exec_nodes; + ExecNodes* consumerNodes = node->consumer_nodes; // 消费者节点 + ExecNodes* producerNodes = node->scan.plan.exec_nodes; // 生产者节点 + /* 如果消费者节点组与生产者节点组不同,获取节点组名称信息 */ if (!ng_is_same_group(&(consumerNodes->distribution), &(producerNodes->distribution))) { - consumerGroupName = ng_get_dist_group_name(&(consumerNodes->distribution)); - producerGroupName = ng_get_dist_group_name(&(producerNodes->distribution)); + consumerGroupName = ng_get_dist_group_name(&(consumerNodes->distribution)); // 获取消费者节点组名称 + producerGroupName = ng_get_dist_group_name(&(producerNodes->distribution)); // 获取生产者节点组名称 errno_t rc = sprintf_s(ng_tag, sizeof(ng_tag), " ng: %s->%s", producerGroupName, consumerGroupName); securec_check_ss(rc, "\0", "\0"); } } + /* 根据节点类型设置流类型标签,并将标签信息追加到type结构体中 */ switch (node->type) { case STREAM_BROADCAST: { if (node->smpDesc.distriType == REMOTE_SPLIT_BROADCAST) { - stream_tag = "SPLIT BROADCAST"; + stream_tag = "SPLIT BROADCAST"; // 分布式分裂广播 } else { - stream_tag = "BROADCAST"; + stream_tag = "BROADCAST"; // 广播 } - appendStringInfo(type, "%sStreaming(type: %s%s%s)", vector_tag, stream_tag, dop_tag, ng_tag); + appendStringInfo(type, "%sStreaming(type: %s%s%s)", vector_tag, stream_tag, dop_tag, ng_tag); // 格式化并追加到type中 } break; case STREAM_REDISTRIBUTE: { - stream_tag = GetStreamTypeRedistribute(node); - appendStringInfo(type, "%sStreaming(type: %s%s%s)", vector_tag, stream_tag, dop_tag, ng_tag); + stream_tag = GetStreamTypeRedistribute(node); // 获取重分布类型 + appendStringInfo(type, "%sStreaming(type: %s%s%s)", vector_tag, stream_tag, dop_tag, ng_tag); // 格式化并追加到type中 } break; case STREAM_HYBRID: { - stream_tag = GetStreamTypeHybrid(node); - appendStringInfo(type, "%sStreaming(type: %s%s%s)", vector_tag, stream_tag, dop_tag, ng_tag); + stream_tag = GetStreamTypeHybrid(node); // 获取混合类型 + appendStringInfo(type, "%sStreaming(type: %s%s%s)", vector_tag, stream_tag, dop_tag, ng_tag); // 格式化并追加到type中 } break; default: - appendStringInfo(type, "UNKNOWN"); + appendStringInfo(type, "UNKNOWN"); // 未知类型 break; } - return type->data; + return type->data; // 返回Stream节点的类型描述 } void StreamSaveTxnContext(StreamTxnContext* stc) @@ -293,13 +302,16 @@ void StreamRestoreTxnContext(StreamTxnContext* stc) * @param[IN] smpIdentifier: smp identifier * @return: pointer to stream object */ +// 通过SMP标识在StreamObj列表中查找StreamObj对象 static StreamObj* FindStreamObjBySmpId(List* streamList, uint32 smpIdentifier) { ListCell* cell = NULL; StreamObj* obj = NULL; + // 遍历StreamObj列表 foreach (cell, streamList) { obj = (StreamObj*)lfirst(cell); + // 找到匹配的SMP标识的StreamObj对象 if (obj->getKey().smpIdentifier == smpIdentifier) break; else @@ -309,11 +321,14 @@ static StreamObj* FindStreamObjBySmpId(List* streamList, uint32 smpIdentifier) return obj; } +// 构建本地Stream上下文 static StreamSharedContext* buildLocalStreamContext(Stream* streamNode, PlannedStmt* pstmt) { + // 如果不是本地节点,则返回NULL if (!STREAM_IS_LOCAL_NODE(streamNode->smpDesc.distriType)) return NULL; + // 分配StreamSharedContext对象的内存空间 StreamSharedContext* sharedContext = (StreamSharedContext*)palloc0(sizeof(StreamSharedContext)); MemoryContext localStreamMemoryCtx = NULL; VectorBatch*** sharedBatches = NULL; @@ -324,11 +339,13 @@ static StreamSharedContext* buildLocalStreamContext(Stream* streamNode, PlannedS int* scanLoc = NULL; char context_name[NODENAMELEN]; int rc = 0; - int consumerNum = streamNode->smpDesc.consumerDop; - int producerNum = streamNode->smpDesc.producerDop; + int consumerNum = streamNode->smpDesc.consumerDop; // 消费者并行度 + int producerNum = streamNode->smpDesc.producerDop; // 生产者并行度 + // 断言:Stream节点的计划节点ID不为0 Assert(streamNode->scan.plan.plan_node_id != 0); - /* Set memory context name. */ + + // 设置内存上下文的名称 rc = snprintf_s(context_name, NODENAMELEN, NODENAMELEN - 1, @@ -338,7 +355,7 @@ static StreamSharedContext* buildLocalStreamContext(Stream* streamNode, PlannedS streamNode->scan.plan.plan_node_id); securec_check_ss(rc, "", ""); - /* Create a shared memory context for local stream in-memory data exchange. */ + // 创建本地Stream的共享内存上下文 localStreamMemoryCtx = AllocSetContextCreate(u_sess->stream_cxt.data_exchange_mem_cxt, context_name, ALLOCSET_DEFAULT_MINSIZE, @@ -346,11 +363,11 @@ static StreamSharedContext* buildLocalStreamContext(Stream* streamNode, PlannedS ALLOCSET_DEFAULT_MAXSIZE, SHARED_CONTEXT); + // 切换到共享内存上下文中 MemoryContext oldCxt = MemoryContextSwitchTo(localStreamMemoryCtx); + // 分配内存和初始化各种数据结构 scanLoc = (int*)palloc0(sizeof(int) * consumerNum); - - /* Init data status. */ dataStatus = (DataStatus**)palloc0(sizeof(DataStatus*) * consumerNum); is_connect_end = (bool**)palloc0(sizeof(bool*) * consumerNum); messages = (StringInfo**)palloc0(sizeof(StringInfo*) * consumerNum); @@ -366,7 +383,7 @@ static StreamSharedContext* buildLocalStreamContext(Stream* streamNode, PlannedS } } - /* Init shared batches or tuples */ + // 初始化共享的批次或元组 if (IsA((Plan*)streamNode, VecStream)) { sharedBatches = (VectorBatch***)palloc0(sizeof(VectorBatch**) * consumerNum); for (int i = 0; i < consumerNum; i++) { @@ -379,6 +396,7 @@ static StreamSharedContext* buildLocalStreamContext(Stream* streamNode, PlannedS } } + // 设置共享上下文的各种属性 sharedContext->vectorized = IsA(&(streamNode->scan.plan), VecStream); sharedContext->localStreamMemoryCtx = localStreamMemoryCtx; sharedContext->sharedBatches = sharedBatches; @@ -388,16 +406,17 @@ static StreamSharedContext* buildLocalStreamContext(Stream* streamNode, PlannedS sharedContext->messages = messages; sharedContext->scanLoc = scanLoc; - /* Set stream key. */ + // 设置流的关键属性 sharedContext->key_s.queryId = pstmt->queryId; sharedContext->key_s.planNodeId = streamNode->scan.plan.plan_node_id; sharedContext->key_s.producerSmpId = ~0; sharedContext->key_s.consumerSmpId = ~0; + // 初始化共享内存池 gs_memory_init_entry(sharedContext, consumerNum, producerNum); - MemoryContextSwitchTo(oldCxt); + MemoryContextSwitchTo(oldCxt); // 切换回原始内存上下文 - return sharedContext; + return sharedContext; // 返回构建的本地Stream上下文 } /* @@ -406,34 +425,41 @@ static StreamSharedContext* buildLocalStreamContext(Stream* streamNode, PlannedS * @param[IN] context: local stream context cached in consumer. * @return void */ +// 重置本地Stream上下文 static void resetLocalStreamContext(StreamSharedContext* context) { + // 如果上下文为空,则直接返回 if (context == NULL) return; + // 重置上下文中的数据结构 context->scanLoc[0] = 0; context->dataStatus[0][0] = DATA_EMPTY; context->is_connect_end[0][0] = false; resetStringInfo(context->messages[0][0]); + // 重置共享元组的指针 context->sharedTuples[0][0]->tuplePointer = 0; } +// 获取递归联合子计划 static RecursiveUnion* GetRecursiveUnionSubPlan(PlannedStmt* pstmt, int subplanid) { ListCell* lc = NULL; + // 遍历计划中的子计划列表 foreach (lc, pstmt->subplans) { Plan* p = (Plan*)lfirst(lc); + // 如果找到匹配的递归联合子计划,则返回该计划 if (IsA(p, RecursiveUnion) && p->plan_node_id == subplanid) { return (RecursiveUnion*)p; } } - return NULL; + return NULL; // 如果未找到匹配的递归联合子计划,则返回NULL } /* - * get one tuple for stream with merge sort + * 获得一个元组流与归并排序 */ bool StreamMergeSortGetTuple(StreamState* node) { @@ -441,25 +467,34 @@ bool StreamMergeSortGetTuple(StreamState* node) } /* - * init stream with merge sort + * 用归并排序初始化流 */ void InitStreamMergeSort(StreamState* node) { + // 断言:StreamScan必须是ScanStreamByLibcomm Assert(node->StreamScan == ScanStreamByLibcomm); + // 断言:sortstate必须为空 Assert(node->sortstate == NULL); + // 获取扫描的TupleTableSlot TupleTableSlot* scanslot = node->ss.ps.ps_ResultTupleSlot; + // 获取Stream节点中的排序信息 SimpleSort* sort = ((Stream*)(node->ss.ps.plan))->sort; + // 断言:排序不应该写入存储 Assert(sort->sortToStore == false); + // 如果需要获取新的数据 if (node->need_fresh_data) { + // 从逻辑连接上读取数据 if (datanode_receive_from_logic_conn(node->conn_count, node->connections, &node->netctl, -1)) { int error_code = getStreamSocketError(gs_comm_strerror()); + // 报错:无法从Datanodes读取响应 ereport(ERROR, (errcode(error_code), errmsg("Failed to read response from Datanodes. Detail: %s\n", gs_comm_strerror()))); } } + // 初始化排序状态 node->sortstate = tuplesort_begin_merge(scanslot->tts_tupleDescriptor, sort->numCols, sort->sortColIdx, @@ -469,6 +504,7 @@ void InitStreamMergeSort(StreamState* node) node, u_sess->attr.attr_memory.work_mem); + // 设置StreamScan的处理函数为StreamMergeSortGetTuple node->StreamScan = StreamMergeSortGetTuple; } @@ -486,8 +522,9 @@ static void CheckStreamMatchInfo( StreamFlowCheckInfo checkInfo, int plan_node_id, List* consumerExecNode, int consumerDop, bool isLocalStream) { #ifdef ENABLE_MULTIPLE_NODES - /* 1.Check exec_nodes. */ + /* 1. 检查执行节点。 */ if (checkInfo.parentProducerExecNodeList == NIL) { + // 报错:Stream节点的执行节点列表不应为空 ereport(ERROR, (errcode(ERRCODE_OPERATE_INVALID_PARAM), errmsg("Stream plan check failed. Execution datanodes list of stream node[%d] should never be null.", @@ -495,11 +532,11 @@ static void CheckStreamMatchInfo( } /* - * For local stream node we do not check the exec nodes cause consumer nodes - * will be modified forcibly or will be marked as dummy thread, so just skipping. + * 对于本地Stream节点,我们不检查执行节点,因为消费者节点将被强制修改或标记为虚拟线程,所以可以跳过检查。 */ if (!isLocalStream) { if (list_length(checkInfo.parentProducerExecNodeList) != list_length(consumerExecNode)) + // 报错:Stream节点的执行节点列表长度不匹配 ereport(ERROR, (errcode(ERRCODE_OPERATE_INVALID_PARAM), errmsg("Stream plan check failed. Execution datanodes list of stream node[%d] mismatch in parent " @@ -509,12 +546,14 @@ static void CheckStreamMatchInfo( errhint("Please use EXPLAIN VERBOSE command to see more details."))); else { + // 检查执行节点的差异 List* l_diff1 = list_difference_int(checkInfo.parentProducerExecNodeList, consumerExecNode); List* l_diff2 = list_difference_int(consumerExecNode, checkInfo.parentProducerExecNodeList); if (l_diff1 != NIL || l_diff2 != NIL) { list_free(l_diff1); list_free(l_diff2); + // 报错:Stream节点的执行节点列表不匹配 ereport(ERROR, (errcode(ERRCODE_OPERATE_INVALID_PARAM), errmsg("Stream plan check failed. Execution datanodes list of stream node[%d] mismatch in " @@ -526,8 +565,9 @@ static void CheckStreamMatchInfo( } } #endif - /* 2.Check dop. */ + /* 2. 检查DOP。 */ if (SET_DOP(checkInfo.parentProducerDop) != SET_DOP(consumerDop)) + // 报错:Stream节点的DOP(并行度)不匹配 ereport(ERROR, (errcode(ERRCODE_OPERATE_INVALID_PARAM), errmsg("Stream plan check failed. Query dop of stream node %d [dop: %d] mismatch in parent node %d " @@ -558,21 +598,23 @@ static void InitStream(StreamFlowCtl* ctl, StreamTransType transType) PlannedStmt* pstmt = ctl->pstmt; Plan* plan = ctl->plan; Stream* streamNode = (Stream*)ctl->plan; - /* May split to mutiliple consumer/producer list, m:n model. */ + /* 可能会分成多个消费者/生产者列表,采用m:n模型。*/ List* consumerSMPList = NULL; List* producerSMPList = NULL; bool isLocalStream = STREAM_IS_LOCAL_NODE(streamNode->smpDesc.distriType); - /* MPP with-recursive support */ + /* MPP with-recursive 支持 */ bool startall = false; List* consumer_nodeList = NIL; List* producer_nodeList = NIL; #ifndef ENABLE_MULTIPLE_NODES if (!isLocalStream) { + // 报错:单节点只能有本地流操作符 ereport(ERROR, (errmsg("Single Node should only has local stream operator."))); } #endif + // 构建本地流共享上下文 StreamSharedContext* sharedContext = NULL; sharedContext = buildLocalStreamContext(streamNode, pstmt); @@ -580,14 +622,15 @@ static void InitStream(StreamFlowCtl* ctl, StreamTransType transType) key.planNodeId = plan->plan_node_id; /* - * MPPDB with-recursive support + * MPPDB with-recursive 支持 */ bool isRecursive = EXEC_IN_RECURSIVE_MODE(plan) && !streamNode->is_recursive_local; if (isRecursive) { - /* If we are under recursive-union, we need check if P/C is not a N-N case */ + /* 如果我们处于递归联接下,需要检查P/C是否不是N-N情况 */ Plan* producer_topplan = plan->lefttree; RecursiveUnion* ruplan = GetRecursiveUnionSubPlan(pstmt, plan->recursive_union_plan_nodeid); if (ruplan == NULL) { + // 报错:ruplan不能为空 ereport(ERROR, (errcode(ERRCODE_OPERATE_INVALID_PARAM), errmsg("The ruplan is can not be NULL"))); } List* cteplan_nodeList = ruplan->plan.exec_nodes->nodeList; @@ -595,21 +638,17 @@ static void InitStream(StreamFlowCtl* ctl, StreamTransType transType) producer_nodeList = producer_topplan->exec_nodes->nodeList; /* - * Check if the consumer & top-producer's exec nodes is identical with - * Recursive CTE plan's nodes + * 检查消费者和顶部生产者的执行节点是否与递归CTE计划的节点相同 */ bool isEqcalConsumerAndProducer = (!equal(cteplan_nodeList, consumer_nodeList) || !equal(cteplan_nodeList, producer_nodeList)); if (isEqcalConsumerAndProducer) { - /* mark we need start all */ + /* 标记需要启动所有 */ startall = true; /* - * Reset consumer and producer list, and save the original consumer_nodes - * with the plan node - * - * Note, only update the node part the other property of ExecNodes keeps - * original + * 重置消费者和生产者列表,并保存原始的consumer_nodes + * 列表节点部分的其他属性保持原样 */ Assert(streamNode->origin_consumer_nodes == NULL); streamNode->origin_consumer_nodes = (ExecNodes*)copyObject(streamNode->consumer_nodes); @@ -620,183 +659,183 @@ static void InitStream(StreamFlowCtl* ctl, StreamTransType transType) plan->exec_nodes->nodeList = cteplan_nodeList; } } +} /* - * In order to print plan when the 'hang' plan occur, we do check here. - * (1) Check if current consumer nodes match parent producer exec nodes. - * (2) Check dop either. - * (3) For local stream node we do not check the exec nodes cause consumer nodes - * will be modified forcibly or will be marked as dummy thread, so just skipping. - * (4) For top node of plan tree, whether need to check consumer_nodes when it is stream node, - * there are two cases: - * 1)case one: for R8C10 we do not need to check because R8 has modified the judgment - * method of dummy thread to deal with this kind of 'hang' plan. - * 2)case two: for R7C10 we still need check to avoid hang up. - * Finally, report error when they mismatch. - * - * In case of startAll, we may make a top-plan node in current producer thread to - * full nodeList to avoid incorrect reporting 'Stream plan check failed', so just skip it + * 为了在'hang'计划发生时打印计划,我们在这里检查。 + *(1)检查当前消费者节点是否与父生产者执行节点匹配。 + *(2)检查dop。 + *(3)对于本地流节点,我们不检查执行节点导致消费者节点 + *将被强制修改或被标记为虚拟线程,所以只是跳过。 + *(4)对于计划树的顶端节点,当它是流节点时是否需要检查consumer_nodes; + *有两种情况: + * 1)情形一:对于R8C10我们不需要检查,因为R8已经修改了判断 + *虚拟线程的方法来处理这种“挂起”计划。 + 情形二:对于R7C10,我们仍然需要检查以避免挂机。 + *最后,当它们不匹配时报告错误。 + *在startAll的情况下,我们可以在当前的生产者线程中创建一个top-plan节点 + *完整的nodeList,以避免错误的报告'流计划检查失败',所以只是跳过它 */ bool isTopStreamNode = ((IsA(ctl->pstmt->planTree, Stream) || IsA(ctl->pstmt->planTree, VecStream)) && - ctl->pstmt->planTree->plan_node_id == ctl->plan->plan_node_id) - ? true : false; - if (!isTopStreamNode && !startall) { - CheckStreamMatchInfo(ctl->checkInfo, - plan->plan_node_id, - streamNode->consumer_nodes->nodeList, - streamNode->smpDesc.consumerDop, - isLocalStream); - } + ctl->pstmt->planTree->plan_node_id == ctl->plan->plan_node_id) + ? true : false; +if (!isTopStreamNode && !startall) { + // 检查Stream节点的匹配信息 + CheckStreamMatchInfo(ctl->checkInfo, + plan->plan_node_id, + streamNode->consumer_nodes->nodeList, + streamNode->smpDesc.consumerDop, + isLocalStream); +} - /* Backup checkinfo (for current stream thread layer) and save checkInfo for the next node */ - List* saved_parentProducerExecNodeList = ctl->checkInfo.parentProducerExecNodeList; - int saved_parentProducerDop = streamNode->smpDesc.producerDop; - ctl->checkInfo.parentProducerExecNodeList = plan->exec_nodes->nodeList; - ctl->checkInfo.parentProducerDop = streamNode->smpDesc.producerDop; +// 备份checkinfo(用于当前Stream线程层)并保存下一个节点的checkInfo +List* saved_parentProducerExecNodeList = ctl->checkInfo.parentProducerExecNodeList; +int saved_parentProducerDop = streamNode->smpDesc.producerDop; +ctl->checkInfo.parentProducerExecNodeList = plan->exec_nodes->nodeList; +ctl->checkInfo.parentProducerDop = streamNode->smpDesc.producerDop; - execNodes = plan->exec_nodes->nodeList; +execNodes = plan->exec_nodes->nodeList; #ifdef ENABLE_MULTIPLE_NODES - /* - * If local stream node, the execnode is the same of current PGXCNode, - * which is also the same of consumer_nodes. And we need to check if this - * data node is a real execute node, or we will add local stream on dummy node. - */ - if (STREAM_IS_LOCAL_NODE(streamNode->smpDesc.distriType) && - list_member_int(execNodes, u_sess->pgxc_cxt.PGXCNodeId)) { - execNodes = lappend_int(NIL, u_sess->pgxc_cxt.PGXCNodeId); - streamNode->consumer_nodes->nodeList = execNodes; - } +/* + * 如果是本地Stream节点,则执行节点与当前PGXCNode相同, + * 这也与consumer_nodes相同。我们需要检查此数据节点是否为实际执行节点, + * 否则将在虚拟节点上添加本地流。 + */ +if (STREAM_IS_LOCAL_NODE(streamNode->smpDesc.distriType) && + list_member_int(execNodes, u_sess->pgxc_cxt.PGXCNodeId)) { + execNodes = lappend_int(NIL, u_sess->pgxc_cxt.PGXCNodeId); + streamNode->consumer_nodes->nodeList = execNodes; +} - Assert(execNodes != NULL); - Assert(list_length(execNodes) > 0); - Assert(streamNode->consumer_nodes->nodeList != NULL); - Assert(list_length(streamNode->consumer_nodes->nodeList) > 0); - Assert(plan->righttree == NULL); +Assert(execNodes != NULL); +Assert(list_length(execNodes) > 0); +Assert(streamNode->consumer_nodes->nodeList != NULL); +Assert(list_length(streamNode->consumer_nodes->nodeList) > 0); +Assert(plan->righttree == NULL); #endif - /* 1. Start the setup the Consumer part */ - if (ctl->dummyThread == false) { - /* Initialize the consumer object. */ - for (i = 0; i < streamNode->smpDesc.consumerDop; i++) { - consumer = New(u_sess->stream_cxt.stream_runtime_mem_cxt) StreamConsumer( - u_sess->stream_cxt.stream_runtime_mem_cxt); - /* Set smp identifier. */ - key.smpIdentifier = i; - consumer->init(key, execNodes, streamNode->smpDesc, transType, sharedContext); +/* 1. 开始设置消费者部分 */ +if (ctl->dummyThread == false) { + /* 初始化消费者对象。 */ + for (i = 0; i < streamNode->smpDesc.consumerDop; i++) { + consumer = New(u_sess->stream_cxt.stream_runtime_mem_cxt) StreamConsumer( + u_sess->stream_cxt.stream_runtime_mem_cxt); - consumerSMPList = lappend(consumerSMPList, consumer); - *(ctl->allConsumerList) = lappend(*(ctl->allConsumerList), consumer); - *(ctl->subConsumerList) = lappend(*(ctl->subConsumerList), consumer); + /* 设置smp标识符。 */ + key.smpIdentifier = i; + consumer->init(key, execNodes, streamNode->smpDesc, transType, sharedContext); - /* store its original producer node list */ - if (startall) { - consumer->m_originProducerNodeList = producer_nodeList; - } + consumerSMPList = lappend(consumerSMPList, consumer); + *(ctl->allConsumerList) = lappend(*(ctl->allConsumerList), consumer); + *(ctl->subConsumerList) = lappend(*(ctl->subConsumerList), consumer); + + /* 存储其原始生产者节点列表 */ + if (startall) { + consumer->m_originProducerNodeList = producer_nodeList; } } +} - /* 2. Start the setup the Producer part */ - consumerNum = list_length(streamNode->consumer_nodes->nodeList); +/* 2. 开始设置生产者部分 */ +consumerNum = list_length(streamNode->consumer_nodes->nodeList); - /* Set connection number of producer. */ - if (STREAM_IS_LOCAL_NODE(streamNode->smpDesc.distriType)) - producerConnNum = streamNode->smpDesc.consumerDop; - else - producerConnNum = consumerNum * streamNode->smpDesc.consumerDop; +/* 设置生产者的连接数。 */ +if (STREAM_IS_LOCAL_NODE(streamNode->smpDesc.distriType)) + producerConnNum = streamNode->smpDesc.consumerDop; +else + producerConnNum = consumerNum * streamNode->smpDesc.consumerDop; - /* - * Check with lefttree is OK, because exec_nodes is the same as current Stream node. - * If lefttree is Steam, exec_nodes of current Stream node is the same as consumer_nodes - * of Stream node in lefttree. - * In addition, ThreadIsDummy routine has dealt with the case that lefttree is also a stream node. - */ - streamNode->is_dummy = ThreadIsDummy(plan->lefttree); +/* + * 通过左树检查是否OK,因为exec_nodes与当前Stream节点相同。 + * 如果左树是Steam,则当前Stream节点的exec_nodes与左树中的Stream节点的consumer_nodes相同。 + * 此外,ThreadIsDummy例程已经处理了左树也是一个stream节点的情况。 + */ +streamNode->is_dummy = ThreadIsDummy(plan->lefttree); - /* If not dummy, we need to establish the connection. */ - if (streamNode->is_dummy == false) { - for (i = 0; i < streamNode->smpDesc.producerDop; i++) { - /* Set smp identifier. */ - key.smpIdentifier = i; - producer = New(u_sess->stream_cxt.stream_runtime_mem_cxt) StreamProducer( - key, pstmt, streamNode, u_sess->stream_cxt.stream_runtime_mem_cxt, producerConnNum, transType); - producer->setSharedContext(sharedContext); - producer->setUniqueSQLKey(u_sess->unique_sql_cxt.unique_sql_id, - u_sess->unique_sql_cxt.unique_sql_user_id, u_sess->unique_sql_cxt.unique_sql_cn_id); - producer->setGlobalSessionId(&u_sess->globalSessionId); - producerSMPList = lappend(producerSMPList, producer); - - /* Add all producer to node group to avoid possible consumer-not-deinit */ - *(ctl->allProducerList) = lappend(*(ctl->allProducerList), producer); - *(ctl->subProducerList) = lappend(*(ctl->subProducerList), producer); - - if (startall) { - /* Store the origin list */ - producer->m_originConsumerNodeList = consumer_nodeList; - producer->m_originProducerExecNodeList = producer_nodeList; - } - } - } else { - key.smpIdentifier = 0; - producer = New(u_sess->stream_cxt.stream_runtime_mem_cxt) - StreamProducer(key, pstmt, streamNode, u_sess->stream_cxt.stream_runtime_mem_cxt, consumerNum, transType); +/* 如果不是虚拟节点,我们需要建立连接。 */ +if (streamNode->is_dummy == false) { + for (i = 0; i < streamNode->smpDesc.producerDop; i++) { + /* 设置smp标识符。 */ + key.smpIdentifier = i; + producer = New(u_sess->stream_cxt.stream_runtime_mem_cxt) StreamProducer( + key, pstmt, streamNode, u_sess->stream_cxt.stream_runtime_mem_cxt, producerConnNum, transType); + producer->setSharedContext(sharedContext); producer->setUniqueSQLKey(u_sess->unique_sql_cxt.unique_sql_id, - u_sess->unique_sql_cxt.unique_sql_user_id, u_sess->unique_sql_cxt.unique_sql_cn_id); + u_sess->unique_sql_cxt.unique_sql_user_id, u_sess->unique_sql_cxt.unique_sql_cn_id); producer->setGlobalSessionId(&u_sess->globalSessionId); producerSMPList = lappend(producerSMPList, producer); - } - StreamPair* pair = u_sess->stream_cxt.global_obj->pushStreamPair(key, producerSMPList, consumerSMPList); + /* 添加所有生产者以避免可能的消费者未释放 */ + *(ctl->allProducerList) = lappend(*(ctl->allProducerList), producer); + *(ctl->subProducerList) = lappend(*(ctl->subProducerList), producer); - Assert(pair != NULL); - List* producerList = NULL; - List* consumerList = NULL; - int subInnerThreadNum = 0; /* Number of stream nodes below current stream nodes. */ - - /* Traverse left tree. */ - StreamFlowCtl lctl = *ctl; - lctl.plan = plan->lefttree; - lctl.dummyThread = streamNode->is_dummy; - lctl.subProducerList = &producerList; - lctl.subConsumerList = &consumerList; - lctl.threadNum = &subInnerThreadNum; - /* checkInfo should be passed down. */ - InitStreamFlow(&lctl); - - /* Sub plan in the left tree, also the sub plan. */ - ListCell* l1 = NULL; - ListCell* l2 = NULL; - forboth(l1, pstmt->subplans, l2, pstmt->subplan_ids) - { - if (plan->lefttree->plan_node_id == lfirst_int(l2)) { - lctl.plan = (Plan*)lfirst(l1); - /* Set parent info as checkInfo for every init plan. */ - SetCheckInfo(&lctl.checkInfo, plan->lefttree); - InitStreamFlow(&lctl); + if (startall) { + /* 存储原始列表 */ + producer->m_originConsumerNodeList = consumer_nodeList; + producer->m_originProducerExecNodeList = producer_nodeList; } } +} else { + key.smpIdentifier = 0; + producer = New(u_sess->stream_cxt.stream_runtime_mem_cxt) + StreamProducer(key, pstmt, streamNode, u_sess->stream_cxt.stream_runtime_mem_cxt, consumerNum, transType); + producer->setUniqueSQLKey(u_sess->unique_sql_cxt.unique_sql_id, + u_sess->unique_sql_cxt.unique_sql_user_id, u_sess->unique_sql_cxt.unique_sql_cn_id); + producer->setGlobalSessionId(&u_sess->globalSessionId); + producerSMPList = lappend(producerSMPList, producer); +} - *(ctl->threadNum) += subInnerThreadNum + streamNode->smpDesc.producerDop; - pair->expectThreadNum = subInnerThreadNum; +StreamPair* pair = u_sess->stream_cxt.global_obj->pushStreamPair(key, producerSMPList, consumerSMPList); - /* Set some args. */ - ListCell* cell = NULL; - foreach (cell, producerSMPList) { - producer = (StreamProducer*)lfirst(cell); +Assert(pair != NULL); +List* producerList = NULL; +List* consumerList = NULL; +int subInnerThreadNum = 0; /* 在当前Stream节点下面的Stream节点数量。 */ - producer->setPair(pair); - producer->setSubProducerList(producerList); - producer->setSubConsumerList(consumerList); - producer->setSessionMemory(t_thrd.shemem_ptr_cxt.mySessionMemoryEntry); +/* 遍历左树。 */ +StreamFlowCtl lctl = *ctl; +lctl.plan = plan->lefttree; +lctl.dummyThread = streamNode->is_dummy; +lctl.subProducerList = &producerList; +lctl.subConsumerList = &consumerList; +lctl.threadNum = &subInnerThreadNum; +/* checkInfo应该被传递下来。 */ +InitStreamFlow(&lctl); + +/* 左树中的子计划,也是子计划。 */ +ListCell* l1 = NULL; +ListCell* l2 = NULL; +forboth (l1, pstmt->subplans, l2, pstmt->subplan_ids) { + if (plan->lefttree->plan_node_id == lfirst_int(l2)) { + lctl.plan = (Plan*)lfirst(l1); + /* 将父节点信息作为每个init计划的checkInfo。 */ + SetCheckInfo(&lctl.checkInfo, plan->lefttree); + InitStreamFlow(&lctl); } +} - *(ctl->subProducerList) = list_union_ptr(*(ctl->subProducerList), producerList); +*(ctl->threadNum) += subInnerThreadNum + streamNode->smpDesc.producerDop; +pair->expectThreadNum = subInnerThreadNum; - /* - * After init the underlying stream workflow, we need restore parentProducerDop - * and parentProducerExecNodeList in current stream thread level - */ - ctl->checkInfo.parentProducerExecNodeList = saved_parentProducerExecNodeList; - ctl->checkInfo.parentProducerDop = saved_parentProducerDop; +/* 设置一些参数。 */ +ListCell* cell = NULL; +foreach (cell, producerSMPList) { + producer = (StreamProducer*)lfirst(cell); + + producer->setPair(pair); + producer->setSubProducerList(producerList); + producer->setSubConsumerList(consumerList); + producer->setSessionMemory(t_thrd.shemem_ptr_cxt.mySessionMemoryEntry); +} + +*(ctl->subProducerList) = list_union_ptr(*(ctl->subProducerList), producerList); + +/* + * 初始化底层流工作流之后,我们需要恢复当前Stream线程层中的parentProducerDop + * 和parentProducerExecNodeList + */ +ctl->checkInfo.parentProducerExecNodeList = saved_parentProducerExecNodeList; +ctl->checkInfo.parentProducerDop = saved_parentProducerDop; } /* @@ -818,7 +857,7 @@ static void InitStreamFlow(StreamFlowCtl* ctl) foreach (lc, append->appendplans) { Plan* subplan = (Plan*)lfirst(lc); ctl->plan = subplan; - /* Set parent info as checkInfo for every sub plan. */ + /* 设置父节点信息为每个子计划的checkInfo。 */ SetCheckInfo(&ctl->checkInfo, oldPlan); InitStreamFlow(ctl); } @@ -830,7 +869,7 @@ static void InitStreamFlow(StreamFlowCtl* ctl) foreach (lc, mt->plans) { Plan* subplan = (Plan*)lfirst(lc); ctl->plan = subplan; - /* Set parent info as checkInfo for every sub plan. */ + /* 设置父节点信息为每个子计划的checkInfo。 */ SetCheckInfo(&ctl->checkInfo, oldPlan); InitStreamFlow(ctl); } @@ -840,7 +879,7 @@ static void InitStreamFlow(StreamFlowCtl* ctl) SubqueryScan* ss = (SubqueryScan*)oldPlan; if (ss->subplan) { ctl->plan = ss->subplan; - /* Set parent info as checkInfo for every sub plan. */ + /* 设置父节点信息为每个子计划的checkInfo。 */ SetCheckInfo(&ctl->checkInfo, oldPlan); InitStreamFlow(ctl); } @@ -851,7 +890,7 @@ static void InitStreamFlow(StreamFlowCtl* ctl) foreach (lc, ma->mergeplans) { Plan* subplan = (Plan*)lfirst(lc); ctl->plan = subplan; - /* Set parent info as checkInfo for every sub plan. */ + /* 设置父节点信息为每个子计划的checkInfo。 */ SetCheckInfo(&ctl->checkInfo, oldPlan); InitStreamFlow(ctl); } @@ -863,7 +902,7 @@ static void InitStreamFlow(StreamFlowCtl* ctl) foreach (lc, ba->bitmapplans) { Plan* subplan = (Plan*)lfirst(lc); ctl->plan = subplan; - /* Set parent info as checkInfo for every sub plan. */ + /* 设置父节点信息为每个子计划的checkInfo。 */ SetCheckInfo(&ctl->checkInfo, oldPlan); InitStreamFlow(ctl); } @@ -875,7 +914,7 @@ static void InitStreamFlow(StreamFlowCtl* ctl) foreach (lc, bo->bitmapplans) { Plan* subplan = (Plan*)lfirst(lc); ctl->plan = subplan; - /* Set parent info as checkInfo for every sub plan. */ + /* 设置父节点信息为每个子计划的checkInfo。 */ SetCheckInfo(&ctl->checkInfo, oldPlan); InitStreamFlow(ctl); } @@ -884,13 +923,13 @@ static void InitStreamFlow(StreamFlowCtl* ctl) case T_VecStream: { InitStream(ctl, STREAM_COMM); - /* Create stream controller for current stream node */ + /* 为当前流节点创建流控制器 */ if (NeedSetupSyncUpController(oldPlan)) { ExecSyncControllerCreate(oldPlan); } } break; case T_RecursiveUnion: { - /* Create recursive-controller for current recursive-union node */ + /* 为当前递归联接节点创建递归控制器 */ if (NeedSetupSyncUpController(oldPlan)) { ExecSyncControllerCreate(oldPlan); } @@ -898,7 +937,7 @@ static void InitStreamFlow(StreamFlowCtl* ctl) ctl->plan = oldPlan->lefttree; InitStreamFlow(ctl); - /* Before traverse the right tree, we must reset the checkInfo. */ + /* 在遍历右树之前,我们必须重置checkInfo。 */ ctl->checkInfo = oldCheckInfo; ctl->plan = oldPlan->righttree; InitStreamFlow(ctl); @@ -929,14 +968,14 @@ static void InitStreamFlow(StreamFlowCtl* ctl) */ void DeinitStreamContext() { - /* Reset the stream runtime context now for next query. */ + /* 现在重置流运行时上下文,以便于下次查询。 */ if (u_sess->stream_cxt.stream_runtime_mem_cxt != NULL) { MemoryContextDelete(u_sess->stream_cxt.stream_runtime_mem_cxt); u_sess->stream_cxt.stream_runtime_mem_cxt = NULL; } if (IS_PGXC_DATANODE) { - /* Reset the shared memory context now for next query. */ + /* 现在重置共享内存上下文,以便于下次查询。 */ if (u_sess->stream_cxt.data_exchange_mem_cxt != NULL) { MemoryContextDelete(u_sess->stream_cxt.data_exchange_mem_cxt); u_sess->stream_cxt.data_exchange_mem_cxt = NULL; @@ -954,9 +993,11 @@ void InitStreamContext() char context_name[NAMEDATALEN] = {0}; errno_t rc = EOK; - /* Append tid to identify each openGauss thread. */ + /* 在上下文名称中附加线程标识以标识每个OpenGauss线程。 */ rc = snprintf_s(context_name, NAMEDATALEN, NAMEDATALEN - 1, "StreamRuntimeContext__%lu", u_sess->debug_query_id); securec_check_ss(rc, "\0", "\0"); + + /* 创建流运行时内存上下文。*/ u_sess->stream_cxt.stream_runtime_mem_cxt = AllocSetContextCreate(g_instance.instance_context, context_name, ALLOCSET_DEFAULT_MINSIZE, @@ -965,10 +1006,12 @@ void InitStreamContext() SHARED_CONTEXT); if (IS_PGXC_DATANODE) { - /* Append tid to identify each openGauss thread. */ + /* 如果当前会话是PGXC数据节点(即分布式数据库的数据节点),则创建数据交换内存上下文。*/ rc = snprintf_s( context_name, NAMEDATALEN, NAMEDATALEN - 1, "MemoryDataExchangeContext_%lu", u_sess->debug_query_id); securec_check_ss(rc, "\0", "\0"); + + /* 创建数据交换内存上下文。*/ u_sess->stream_cxt.data_exchange_mem_cxt = AllocSetContextCreate(g_instance.instance_context, context_name, ALLOCSET_DEFAULT_MINSIZE, @@ -986,6 +1029,7 @@ void InitStreamContext() */ void BuildStreamFlow(PlannedStmt* plan) { + /* 用于保存消费者和生产者的列表 */ List* consumerList = NIL; List* producerList = NIL; List* subProducerList = NIL; @@ -994,17 +1038,23 @@ void BuildStreamFlow(PlannedStmt* plan) StreamConsumer* consumer = NULL; StreamProducer* producer = NULL; + /* 检查计划中是否存在流操作,并且全局流对象尚未初始化 */ if (plan->num_streams > 0 && u_sess->stream_cxt.global_obj == NULL) { + /* 切换到流运行时内存上下文 */ AutoContextSwitch streamCxtGuard(u_sess->stream_cxt.stream_runtime_mem_cxt); + + /* 用于计算线程数量的变量 */ int threadNum = 0; List* topConsumerList = NULL; - /* Hold interrupts during stream connection and thread initialization. */ + /* 在流连接和线程初始化期间保持中断 */ HOLD_INTERRUPTS(); + /* 创建全局流对象 */ u_sess->stream_cxt.global_obj = New(u_sess->stream_cxt.stream_runtime_mem_cxt) StreamNodeGroup(); u_sess->stream_cxt.global_obj->m_streamRuntimeContext = u_sess->stream_cxt.stream_runtime_mem_cxt; + /* 初始化流操作控制变量 */ StreamFlowCtl ctl; ctl.pstmt = plan; ctl.plan = plan->planTree; @@ -1014,58 +1064,62 @@ void BuildStreamFlow(PlannedStmt* plan) ctl.subConsumerList = &topConsumerList; ctl.threadNum = &threadNum; ctl.dummyThread = ThreadIsDummy(plan->planTree); - /* Init check info. */ + + /* 初始化检查信息 */ SetCheckInfo(&ctl.checkInfo, plan->planTree); + /* 初始化流操作 */ InitStreamFlow(&ctl); + /* 初始化子计划中的流操作 */ ListCell* l1 = NULL; ListCell* l2 = NULL; forboth(l1, plan->subplans, l2, plan->subplan_ids) { if (plan->planTree->plan_node_id == lfirst_int(l2)) { ctl.plan = (Plan*)lfirst(l1); - /* Set parent info as checkInfo for every init plan. */ + /* 将父级信息设置为每个初始化计划的检查信息 */ SetCheckInfo(&ctl.checkInfo, plan->planTree); InitStreamFlow(&ctl); } } + /* 初始化全局流对象,并在初始化之后恢复中断 */ u_sess->stream_cxt.global_obj->m_streamConsumerList = consumerList; u_sess->stream_cxt.global_obj->m_streamProducerList = producerList; u_sess->stream_cxt.global_obj->Init(threadNum); - - /* Resume interrupts after initialization of u_sess->stream_cxt.global_obj. */ RESUME_INTERRUPTS(); + /* 如果存在顶级消费者线程,则注册顶级消费者线程的消费者对象 */ if (topConsumerList != NIL) { - /* Register top consumer thread consumer object. */ foreach (consumerCell, topConsumerList) { consumer = (StreamConsumer*)lfirst(consumerCell); u_sess->stream_cxt.global_obj->registerStream(consumer); } } + /* 如果存在生产者线程,则连接相关的消费者 */ if (producerList != NIL) { - /* collect connect consumer time */ + /* 记录连接消费者的时间 */ if (plan->instrument_option) TRACK_START(-1, STREAMNET_INIT); int total_connNum = 0; int current_index = 0; - /* Connect related consumer. */ + + /* 连接相关的消费者 */ foreach (producerCell, producerList) { producer = (StreamProducer*)lfirst(producerCell); /* - * Here, total_connNum only counts for non local stream nodes. - * Because producer of local stream does not need to connect with consumer. + * 在这里,total_connNum仅计算非本地流节点的连接数。 + * 因为本地流的生产者不需要与消费者建立连接。 */ if (!producer->isLocalStream()) { total_connNum += producer->getConnNum(); } } - /* We only create connection for non local stream. */ + /* 仅为非本地流创建连接 */ if (total_connNum > 0) { libcomm_addrinfo** total_consumerAddr = (libcomm_addrinfo**)palloc0(total_connNum * sizeof(libcomm_addrinfo*)); @@ -1084,8 +1138,8 @@ void BuildStreamFlow(PlannedStmt* plan) TRACK_END(-1, STREAMNET_INIT); } + /* 如果存在消费者线程,则等待所有生产者连接就绪 */ if (consumerList != NIL) { - /* Wait for all producer connection ready. */ foreach (consumerCell, consumerList) { consumer = (StreamConsumer*)lfirst(consumerCell); IPC_PERFORMANCE_LOG_OUTPUT("BuildStreamFlow waitProducerReady start."); @@ -1103,31 +1157,36 @@ void SetupStreamRuntime(StreamState* node) StreamPair* pair = NULL; StreamConsumer* consumer = NULL; + /* 构建流操作的关键信息,包括查询ID和计划节点ID */ key.queryId = node->ss.ps.state->es_plannedstmt->queryId; key.planNodeId = streamNode->scan.plan.plan_node_id; + /* 断言全局流对象不为空 */ Assert(u_sess->stream_cxt.global_obj != NULL); - pair = u_sess->stream_cxt.global_obj->popStreamPair(key); + /* 从全局流对象中取出流操作的配对对象 */ + pair = u_sess->stream_cxt.global_obj->popStreamPair(key); Assert(pair->producerList != NULL); - /* Set the current smp id. */ + /* 设置当前SMP标识符 */ key.smpIdentifier = u_sess->stream_cxt.smp_id; + /* 如果配对对象中存在消费者列表,则根据SMP标识符查找相应的消费者对象 */ if (pair->consumerList) { consumer = (StreamConsumer*)FindStreamObjBySmpId(pair->consumerList, u_sess->stream_cxt.smp_id); Assert(consumer != NULL); } - /* Set shared context in StreamState. */ + /* 在StreamState中设置共享上下文 */ if (pair->producerList) { StreamProducer* producer = (StreamProducer*)linitial(pair->producerList); node->sharedContext = producer->getSharedContext(); } - /* Set consumer object in streamState. */ + /* 在streamState中设置消费者对象 */ node->consumer = consumer; + /* 注册流快照 */ RegisterStreamSnapshots(); } @@ -1138,71 +1197,94 @@ static void StartupStreamThread(StreamState* node) ListCell* cell = NULL; uint8 smpId = 0; + /* 构建流操作的关键信息,包括查询ID和计划节点ID */ key.queryId = node->ss.ps.state->es_plannedstmt->queryId; key.planNodeId = node->ss.ps.plan->plan_node_id; + + /* 断言全局流对象不为空 */ Assert(u_sess->stream_cxt.global_obj != NULL); + + /* 从全局流对象中取出与关键信息对应的流操作配对对象 */ pair = u_sess->stream_cxt.global_obj->popStreamPair(key); Assert(pair->producerList != NULL); + /* 初始化事务上下文,包括事务ID和快照信息 */ StreamTxnContext transactionCxt; transactionCxt.txnId = GetCurrentTransactionIdIfAny(); transactionCxt.snapshot = node->ss.ps.state->es_snapshot; StreamSaveTxnContext(&transactionCxt); - /* Only the Top Consumer thread can spawn multiple thread. */ + /* + * 只有顶层的消费者线程可以启动多个线程。 + * 对于每个生产者对象,初始化它们的相关参数,然后在全局流对象中初始化相应的流线程。 + */ foreach (cell, pair->producerList) { StreamProducer* producer = (StreamProducer*)lfirst(cell); + + /* 初始化生产者对象 */ producer->init(node->ss.ps.ps_ResultTupleSlot->tts_tupleDescriptor, transactionCxt, node->ss.ps.state->es_param_list_info, u_sess->stream_cxt.producer_obj ? u_sess->stream_cxt.producer_obj->getKey().planNodeId : 0); + + /* 在全局流对象中初始化流线程 */ u_sess->stream_cxt.global_obj->initStreamThread(producer, smpId, pair); smpId++; } } -/* Set up Stream thread in parallel */ +/* 并行设置流线程*/ void StartUpStreamInParallel(PlannedStmt* pstmt, EState* estate) { + /* 如果不是数据节点或者没有流操作,直接返回 */ if (!IS_PGXC_DATANODE || pstmt->num_streams <= 0) { return; } + /* 如果计划树的计划节点ID大于0,开始跟踪流线程初始化的性能 */ if (pstmt->planTree->plan_node_id > 0) { TRACK_START(pstmt->planTree->plan_node_id, DN_STREAM_THREAD_INIT); } + /* 断言全局流对象不为空 */ Assert(u_sess->stream_cxt.global_obj != NULL); + /* 获取全局流对象中的流操作配对列表 */ const List *pairList = u_sess->stream_cxt.global_obj->getStreamPairList(); ListCell *lc = NULL; foreach(lc, pairList) { + /* 对于每个流操作配对,获取第一个生产者对象的流节点信息 */ StreamPair *pair = (StreamPair*)lfirst(lc); StreamProducer *producer = (StreamProducer *)linitial(pair->producerList); const Stream *node = producer->getStream(); - /* Do not need to start up thread for dummy node. */ + /* 不需要为虚拟节点启动线程 */ if (node->is_dummy) { continue; } - /* create state to set up stream thread. */ + /* 创建流状态对象,并设置其计划和执行状态信息 */ StreamState *stream_state = makeNode(StreamState); stream_state->ss.ps.plan = (Plan*)node; stream_state->ss.ps.state = estate; + /* 初始化流状态对象的结果元组槽 */ ExecInitResultTupleSlot(estate, &stream_state->ss.ps); + /* 如果流节点的计划有目标列表,设置结果元组槽的描述符 */ if (node->scan.plan.targetlist) { TupleDesc typeInfo = ExecTypeFromTL(node->scan.plan.targetlist, false); ExecSetSlotDescriptor(stream_state->ss.ps.ps_ResultTupleSlot, typeInfo); } else { - /* In case there is no target list, force its creation */ + /* 如果没有目标列表,强制创建一个 */ ExecAssignResultTypeFromTL(&stream_state->ss.ps); } + /* 启动流线程 */ StartupStreamThread(stream_state); + /* 释放流状态对象的内存 */ pfree(stream_state); } + /* 如果计划树的计划节点ID大于0,结束跟踪流线程初始化的性能 */ if (pstmt->planTree->plan_node_id > 0) { TRACK_END(pstmt->planTree->plan_node_id, DN_STREAM_THREAD_INIT); } @@ -1210,13 +1292,16 @@ void StartUpStreamInParallel(PlannedStmt* pstmt, EState* estate) void StreamPrepareRequestForRecursive(StreamState* node, bool stepsync) { + /* 如果不是递归模式,抛出错误 */ if (!EXEC_IN_RECURSIVE_MODE(node->ss.ps.plan)) { elog(ERROR, "MPP with-recursive invalid stream node status Stream[%d]", node->ss.ps.plan->plan_node_id); } int i = 0; int producerNum = 0; + /* 获取消费者的传输对象列表 */ StreamTransport** transport = node->consumer->getTransport(); + /* 标记需要新的数据 */ node->need_fresh_data = true; node->last_conn_idx = 0; StreamCOMM* scomm = NULL; @@ -1224,18 +1309,14 @@ void StreamPrepareRequestForRecursive(StreamState* node, bool stepsync) int full_conn_count = node->consumer->getConnNum(); int full_conn_index = 0; + /* 在同步步骤的情况下,需要接收递归联接执行节点范围内的所有数据节点的消息 */ if (stepsync) { - /* - * In syncup case, we need receive all dn's message in recursive - * union execnode scope. - */ producerNum = node->consumer->getConnNum(); } else { /* - * In normal case, we need check if the backup producer-list is set, "not-NIL" - * indicates that there is a DN-pruning case happen where all connections is - * setup for plan-step syncup, we need logically pick up the producer connection - * do correct BROADCAST/REDISTRIBUTE + * 在正常情况下,需要检查备用生产者列表是否设置, + * "不为NIL" 表示发生了数据节点剪枝情况,所有连接都是为计划步骤同步而建立的, + * 我们需要逻辑地选择生产者连接以进行正确的BROADCAST/REDISTRIBUTE */ if (node->consumer->m_originProducerNodeList != NIL) { producerNum = list_length(node->consumer->m_originProducerNodeList); @@ -1245,19 +1326,21 @@ void StreamPrepareRequestForRecursive(StreamState* node, bool stepsync) } } - /* We should have at least one producer to receive */ + /* 我们至少应该有一个生产者来接收数据 */ Assert(producerNum > 0); + /* 为每个生产者连接分配内存 */ node->connections = (PGXCNodeHandle**)palloc0(producerNum * sizeof(PGXCNodeHandle*)); node->conn_count = producerNum; + /* 初始化 SCTP 连接的相关信息 */ node->netctl.layer.sctpLayer.datamarks = (int*)palloc0(producerNum * sizeof(int)); node->netctl.layer.sctpLayer.gs_sock = (gsocket*)palloc0(producerNum * sizeof(gsocket)); node->netctl.layer.sctpLayer.poll2conn = (int*)palloc0(producerNum * sizeof(int)); - /* Loop the full datanode list and skip some of datanode when we found it is pruned */ + /* 遍历完整的数据节点列表,并在找到它被剪枝时跳过 */ for (full_conn_index = 0; full_conn_index < full_conn_count; full_conn_index++) { - /* skip those do not exists in original consumer NodeList */ + /* 跳过那些不在原始消费者节点列表中的节点 */ if (skip_pruned_datanode && !list_member_int(node->consumer->m_originProducerNodeList, node->consumer->getNodeIdx(transport[full_conn_index]->m_nodeName))) { @@ -1266,8 +1349,9 @@ void StreamPrepareRequestForRecursive(StreamState* node, bool stepsync) scomm = (StreamCOMM*)transport[full_conn_index]; + /* 为连接分配内存 */ node->connections[i] = (PGXCNodeHandle*)palloc0(sizeof(PGXCNodeHandle)); - /* Initialise Input buffer */ + /* 初始化输入缓冲区 */ node->connections[i]->inSize = STREAM_BUFFER_SIZE; node->connections[i]->inBuffer = (char*)palloc0(STREAM_BUFFER_SIZE); node->connections[i]->sock = NO_SOCKET; @@ -1280,7 +1364,7 @@ void StreamPrepareRequestForRecursive(StreamState* node, bool stepsync) node->connections[i]->tcpCtlPort = -1; node->connections[i]->listenPort = scomm->m_addr->listen_port; - /* Check if actual index exceeds the producer number */ + /* 检查实际的索引是否超过了生产者数量 */ if (i >= producerNum) { elog(ERROR, "MPP with-recursive invalid connection index in DN pruning scenarios"); } @@ -1288,25 +1372,25 @@ void StreamPrepareRequestForRecursive(StreamState* node, bool stepsync) i++; } + /* 如果有仪表,且流节点类型为 STREAM_BROADCAST,则设置溢出大小 */ if (node->ss.ps.instrument && node->type == STREAM_BROADCAST) node->spill_size = &node->ss.ps.instrument->sorthashinfo.spill_size; else node->spill_size = &node->recvDataLen; + /* 设置流节点为就绪状态 */ node->isReady = true; } void StreamPrepareRequest(StreamState* node) { /* - * If we found current stream node is under recursive union node, we handle the - * stream preparation in a special way + * 如果我们发现当前的流节点位于递归联接节点下,我们以一种特殊的方式处理流准备 * - * MPP support with-recursive + * MPP 支持 with-recursive */ if (EXEC_IN_RECURSIVE_MODE(node->ss.ps.plan)) { StreamPrepareRequestForRecursive(node, false); - return; } @@ -1314,23 +1398,28 @@ void StreamPrepareRequest(StreamState* node) int producerNum = node->consumer->getConnNum(); StreamTransport** transport = node->consumer->getTransport(); + /* 标记需要新的数据 */ node->need_fresh_data = true; node->last_conn_idx = 0; StreamCOMM* scomm = NULL; + /* 为每个生产者连接分配内存 */ node->connections = (PGXCNodeHandle**)palloc0(producerNum * sizeof(PGXCNodeHandle*)); node->conn_count = producerNum; + /* 初始化 SCTP 连接的相关信息 */ node->netctl.layer.sctpLayer.datamarks = (int*)palloc0(producerNum * sizeof(int)); node->netctl.layer.sctpLayer.gs_sock = (gsocket*)palloc0(producerNum * sizeof(gsocket)); node->netctl.layer.sctpLayer.poll2conn = (int*)palloc0(producerNum * sizeof(int)); + /* 遍历所有的连接,并为每个连接分配内存和初始化相关信息 */ for (i = 0; i < node->conn_count; i++) { scomm = (StreamCOMM*)transport[i]; + /* 为连接分配内存 */ node->connections[i] = (PGXCNodeHandle*)palloc0(sizeof(PGXCNodeHandle)); - /* Initialise Input buffer */ + /* 初始化输入缓冲区 */ node->connections[i]->inSize = STREAM_BUFFER_SIZE; node->connections[i]->inBuffer = (char*)palloc0(node->connections[i]->inSize); node->connections[i]->sock = NO_SOCKET; @@ -1344,68 +1433,55 @@ void StreamPrepareRequest(StreamState* node) node->connections[i]->listenPort = scomm->m_addr->listen_port; } + /* 如果有仪表,且流节点类型为 STREAM_BROADCAST,则设置溢出大小 */ if (node->ss.ps.instrument && node->type == STREAM_BROADCAST) node->spill_size = &node->ss.ps.instrument->sorthashinfo.spill_size; else node->spill_size = &node->recvDataLen; + /* 设置流节点为就绪状态 */ node->isReady = true; } void StreamReportError(StreamState* node) { + /* 检查是否有错误消息需要报告 */ if (node->errorMessage) { - if (node->errorDetail != NULL && node->errorContext != NULL) + /* 检查是否同时提供了错误详情和错误上下文 */ + if (node->errorDetail != NULL && node->errorContext != NULL) { ereport(ERROR, (errcode(node->errorCode), combiner_errdata(&node->remoteErrData), errmsg("%s", node->errorMessage), errdetail("%s", node->errorDetail), errcontext("%s", node->errorContext))); - else if (node->errorDetail != NULL) + } + /* 如果只提供了错误详情 */ + else if (node->errorDetail != NULL) { ereport(ERROR, (errcode(node->errorCode), combiner_errdata(&node->remoteErrData), errmsg("%s", node->errorMessage), errdetail("%s", node->errorDetail))); - else if (node->errorContext != NULL) + } + /* 如果只提供了错误上下文 */ + else if (node->errorContext != NULL) { ereport(ERROR, (errcode(node->errorCode), combiner_errdata(&node->remoteErrData), errmsg("%s", node->errorMessage), errcontext("%s", node->errorContext))); - else + } + /* 如果没有提供错误详情或错误上下文 */ + else { ereport(ERROR, - (errcode(node->errorCode), combiner_errdata(&node->remoteErrData), errmsg("%s", node->errorMessage))); + (errcode(node->errorCode), + combiner_errdata(&node->remoteErrData), + errmsg("%s", node->errorMessage))); + } } } -void AddCheckMessage(StringInfo msg_new, StringInfo msg_org, bool is_stream, unsigned int planNodeId) -{ - /* Set cursor */ - msg_new->cursor = msg_org->cursor; - - if (is_stream) { - /* Add queryId check. */ - pq_sendint64(msg_new, u_sess->debug_query_id); - /* Add planNodeId check. */ - pq_sendint(msg_new, planNodeId, 4); - } - - /* Add actual message length. */ - pq_sendint(msg_new, msg_org->len, 4); - - /* Add CRC check. */ - pg_crc32 valcrc; - INIT_CRC32(valcrc); - COMP_CRC32(valcrc, msg_org->data, msg_org->len); - FIN_CRC32(valcrc); - pq_sendint(msg_new, valcrc, 4); - - /* Add actual message. */ - appendBinaryStringInfo(msg_new, msg_org->data, msg_org->len); -} - void CheckMessages(uint64 check_query_id, uint32 check_plan_node_id, char* msg, int msg_len, bool is_stream) { uint32 plan_node_id; @@ -1416,14 +1492,13 @@ void CheckMessages(uint64 check_query_id, uint32 check_plan_node_id, char* msg, ereport(ERROR, (errmodule(MOD_GUC), errcode(ERRCODE_STRING_DATA_LENGTH_MISMATCH), - errmsg( - "message length %d is less than head length %d", msg_len, head_len))); + errmsg("message length %d is less than head length %d", msg_len, head_len))); } uint32 send_check_sum; errno_t rc = EOK; if (is_stream) { - /* Debug query ID check. */ + /* 检查调试查询ID */ rc = memcpy_s(&query_id, sizeof(uint64), msg, sizeof(uint64)); securec_check(rc, "\0", "\0"); query_id = ntohl64(query_id); @@ -1437,7 +1512,7 @@ void CheckMessages(uint64 check_query_id, uint32 check_plan_node_id, char* msg, errmsg("expected query id is %lu, actual query id is %lu", u_sess->debug_query_id, query_id))); } - /* Plan Node ID check. */ + /* 计划节点ID检查 */ rc = memcpy_s(&plan_node_id, sizeof(int), msg, sizeof(int)); securec_check(rc, "\0", "\0"); plan_node_id = ntohl(plan_node_id); @@ -1449,12 +1524,11 @@ void CheckMessages(uint64 check_query_id, uint32 check_plan_node_id, char* msg, ereport(ERROR, (errmodule(MOD_GUC), errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg( - "expected plan node id is %u, actual plan node id is %u", check_plan_node_id, plan_node_id))); + errmsg("expected plan node id is %u, actual plan node id is %u", check_plan_node_id, plan_node_id))); } } - /* Message length check. */ + /* 消息长度检查 */ rc = memcpy_s(&actual_msg_len, sizeof(int), msg, sizeof(int)); securec_check(rc, "\0", "\0"); actual_msg_len = ntohl(actual_msg_len); @@ -1465,11 +1539,10 @@ void CheckMessages(uint64 check_query_id, uint32 check_plan_node_id, char* msg, ereport(ERROR, (errmodule(MOD_GUC), errcode(ERRCODE_STRING_DATA_LENGTH_MISMATCH), - errmsg( - "expected message length is %d, actual message length is %d", actual_msg_len, msg_len - head_len))); + errmsg("expected message length is %d, actual message length is %d", actual_msg_len, msg_len - head_len))); } - /* CRC check. */ + /* CRC校验 */ rc = memcpy_s(&send_check_sum, sizeof(uint32), msg, sizeof(uint32)); securec_check(rc, "\0", "\0"); send_check_sum = ntohl(send_check_sum); @@ -1489,29 +1562,30 @@ void CheckMessages(uint64 check_query_id, uint32 check_plan_node_id, char* msg, errmsg("expected crc is %u, actual crc is %u", send_check_sum, valcrc))); } } + static void HandleStreamTuple(StreamState* node, char* msg, int msg_len) { errno_t rc = EOK; + /* 如果存在错误消息,直接返回 */ if (node->errorMessage) return; - /* We expect previous message is consumed */ + /* 我们期望前一个消息已被消耗 */ if (!EXEC_IN_RECURSIVE_MODE(node->ss.ps.plan)) { /* - * If stream is mark as no early free (e.g. recursive-union case), we need it - * to response for sync cluster step, we don't do assert here. + * 如果流标记为不可提前释放(例如,递归联合情况),我们需要它来响应同步集群步骤,此时我们不进行断言。 */ AssertEreport(node->buf.len == 0, MOD_OPT, "node buf len is not zero"); } - /* Check messages. */ + /* 检查消息。*/ #ifdef USE_ASSERT_CHECKING CheckMessages(node->consumer->getKey().queryId, ((PlanState*)node)->plan->plan_node_id, msg, msg_len, true); msg += STREAM_CHECKMSG_LEN; msg_len -= STREAM_CHECKMSG_LEN; #else - + /* 如果启用了流数据检查选项,检查消息。 */ if (unlikely(anls_opt_is_on(ANLS_STREAM_DATA_CHECK))) { CheckMessages(node->consumer->getKey().queryId, ((PlanState*)node)->plan->plan_node_id, msg, msg_len, true); msg += STREAM_CHECKMSG_LEN; @@ -1519,25 +1593,28 @@ static void HandleStreamTuple(StreamState* node, char* msg, int msg_len) } #endif + /* 如果消息长度小于零,报告错误。 */ if (msg_len < 0) ereport(ERROR, (errcode(ERRCODE_DATA_EXCEPTION), errmsg("Unexpected response from remote node"))); + /* 如果消息长度大于缓冲区大小,重新分配缓冲区。 */ if (msg_len > node->buf.size) { node->buf.msg = (char*)repalloc(node->buf.msg, msg_len); node->buf.size = msg_len; } + /* 将消息内容拷贝到缓冲区。 */ rc = memcpy_s(node->buf.msg, node->buf.size, msg, msg_len); securec_check(rc, "\0", "\0"); node->buf.len = msg_len; - /* Record length of all received data for network perf data. */ + /* 记录接收到的所有数据的长度,用于网络性能数据。 */ *node->spill_size += msg_len; } void HandleStreamError(StreamState* node, char* msg_body, int len) { - /* parse error message */ + /* 解析错误消息 */ char* code = NULL; char* message = NULL; char* detail = NULL; @@ -1551,10 +1628,10 @@ void HandleStreamError(StreamState* node, char* msg_body, int len) char* mod_id = NULL; /* - * Scan until point to terminating \0 + * 扫描直到指向终结符\0 */ while (((int)(offset + 1)) < len) { - /* pointer to the field message */ + /* 指向字段消息的指针 */ char* str = msg_body + offset + 1; switch (msg_body[offset]) { @@ -1564,7 +1641,7 @@ void HandleStreamError(StreamState* node, char* msg_body, int len) case 'C': /* code */ code = str; - /* Error Code is exactly 5 significant bytes */ + /* 错误码正好是5个有效字节 */ if (code != NULL) error_code = MAKE_SQLSTATE(code[0], code[1], code[2], code[3], code[4]); break; @@ -1590,7 +1667,7 @@ void HandleStreamError(StreamState* node, char* msg_body, int len) funcname = str; break; - /* Fields not yet in use */ + /* 尚未使用的字段 */ case 'S': /* severity */ case 'H': /* hint */ case 'P': /* position string */ @@ -1600,16 +1677,14 @@ void HandleStreamError(StreamState* node, char* msg_body, int len) break; } - /* code, message and \0 */ + /* code, message和\0 */ offset += strlen(str) + 2; } /* - * We may have special handling for some errors, default handling is to - * throw out error with the same message. We can not ereport immediately - * because we should read from this and other connections until - * ReadyForQuery is received, so we just store the error message. - * If multiple connections return errors only first one is reported. + * 对于一些特殊错误,我们可能有特殊处理, 默认的处理方式是抛出具有相同消息的错误。 + * 我们不能立即 ereport,因为我们应该从这个和其他连接中读取,直到 ReadyForQuery 被接收到, + * 所以我们只是存储错误消息。 如果多个连接返回错误,只报告第一个错误。 */ if (node->errorMessage == NULL) { MemoryContext old_cxt = MemoryContextSwitchTo(ErrorContext); @@ -1657,20 +1732,20 @@ void HandleStreamError(StreamState* node, char* msg_body, int len) } /* - * Handle NoticeResponse ('N') message from Stream thread + * 处理来自流线程的notiferresponse 消息 */ void HandleStreamNotice(StreamState* node, char* msg_body, size_t len) { - /* parse error message */ + /* 解析错误信息 */ char* message = NULL; char* detail = NULL; size_t offset = 0; /* - * Scan until point to terminating \0 + * 扫描到终止点 */ while (offset + 1 < len) { - /* pointer to the field message */ + /* 指向字段消息的指针 */ char* str = msg_body + offset + 1; switch (msg_body[offset]) { @@ -1680,7 +1755,7 @@ void HandleStreamNotice(StreamState* node, char* msg_body, size_t len) case 'D': /* details */ detail = str; break; - /* Fields not yet in use */ + /* 尚未使用的字段 */ case 'S': /* severity */ case 'C': /* code */ case 'R': /* routine */ @@ -1695,7 +1770,7 @@ void HandleStreamNotice(StreamState* node, char* msg_body, size_t len) break; } - /* code, message and \0 */ + /* 代码、消息和 \0 */ offset += strlen(str) + 2; } @@ -1708,19 +1783,24 @@ void HandleStreamNotice(StreamState* node, char* msg_body, size_t len) } /* - * MPP Recursive-Union support + * MPP递归联合支持 */ static void ParseRUSyncMsg(const char* msg, const char* key, char* value) { Assert(msg != NULL && key != NULL && value != NULL); + /* 查找消息中键的起始位置 */ const char* value_startptr = (char*)strstr(msg, key) + strlen(key); + /* 查找键的结束位置(逗号分隔符之前) */ const char* value_endptr = strstr(value_startptr, ","); + /* 计算键的长度 */ int len = value_endptr - value_startptr; + /* 将value缓冲区初始化为0 */ errno_t rc = memset_s(value, NAMEDATALEN, 0, NAMEDATALEN); securec_check(rc, "\0", "\0"); + /* 将键的值复制到value缓冲区中 */ rc = memcpy_s(value, NAMEDATALEN, value_startptr, len); securec_check(rc, "\0", "\0"); } @@ -1743,35 +1823,35 @@ static void HandleStreamRUSyncMsg(StreamState* node, char msg_type, char* msg, i char value[NAMEDATALEN] = {0}; - /* parse node name */ + /* 解析节点名称 */ ParseRUSyncMsg(msg, "nodename:", value); char* nodename = pstrdup(value); - /* parse recursive step id */ + /* 解析递归步骤id */ ParseRUSyncMsg(msg, "rustep:", value); int rustep = atoi(value); - /* parse pgxcnodeid */ + /* 解析pgxcnodeid */ ParseRUSyncMsg(msg, "xcnodeid:", value); int xcnodeid = atoi(value); - /* parse iteration */ + /* 解析iteration */ ParseRUSyncMsg(msg, "iteration:", value); int iteration = atoi(value); - /* parse pgxcnodeid */ + /* 解析pgxcnodeid */ ParseRUSyncMsg(msg, "tuple_processed:", value); int tuple_processed = atoi(value); - /* parse controller_plannodeid */ + /* 解析controller_plannodeid */ ParseRUSyncMsg(msg, "controller_plannodeid:", value); int controller_plannodeid = atoi(value); - /* parse producer_plannodeid */ + /* 解析producer_plannodeid */ ParseRUSyncMsg(msg, "producer_plannodeid:", value); int producer_plannodeid = atoi(value); - /* output important message */ + /* 输出重要信息 */ const char* direction = NULL; if (msg_type == 'F') { ereport(ERROR, (errcode(ERRCODE_DATA_EXCEPTION), errmsg("Unexpected msg type[%d]", msg_type))); @@ -1797,13 +1877,12 @@ static void HandleStreamRUSyncMsg(StreamState* node, char msg_type, char* msg, i producer_plannodeid, tuple_processed); - /* Mark the received datanode's xx step has finished with xx tuple processed */ + /* 用已处理的xx元组标记接收到的datanode的xx步骤已经完成 */ StreamNodeGroup* stream_nodegroup = u_sess->stream_cxt.global_obj; SyncController* controller = stream_nodegroup->GetSyncController(controller_plannodeid); /* - * Report Error when the controller for its belonging RecursiveUnion node is - * not found. + * 找不到所属递归union节点的控制器时报告错误 */ if (controller == NULL) { ereport(ERROR, @@ -1835,12 +1914,12 @@ int HandleStreamResponse(PGXCNodeHandle* conn, StreamState* node) char msg_type; for (;;) { - /* hack now will improve later */ + /* Hack现在将改进以后 */ if (u_sess->stream_cxt.global_obj->m_syncControllers && conn->state == DN_CONNECTION_STATE_IDLE) { - /* with-recursive case */ + /* 使用递归的情况 */ conn->state = DN_CONNECTION_STATE_QUERY; } else { - /* normal case */ + /* 普通情况 */ if (conn->state == DN_CONNECTION_STATE_IDLE) { ereport(ERROR, (errcode(ERRCODE_INVALID_OPTION), errmsg("state is DN_CONNECTION_STATE_IDLE: %d", DN_CONNECTION_STATE_IDLE))); @@ -1848,23 +1927,23 @@ int HandleStreamResponse(PGXCNodeHandle* conn, StreamState* node) } /* - * If we are in the process of shutting down, we - * may be rolling back, and the buffer may contain other messages. - * We want to avoid a procarray exception - * as well as an error stack overflow. + * 如果我们在关闭的过程中,我们 + *可能正在回滚,缓冲区可能包含其他消息。 + *我们希望避免procarray异常 + *以及错误堆栈溢出。 */ if (t_thrd.proc_cxt.proc_exit_inprogress) { conn->state = DN_CONNECTION_STATE_ERROR_FATAL; elog(WARNING, "DN_CONNECTION_STATE_ERROR_FATAL2 is set when proc_exit_inprogress"); } - /* don't read from from the connection if there is a fatal error */ + /* 如果有致命错误,不要从连接中读取 */ if (conn->state == DN_CONNECTION_STATE_ERROR_FATAL) { elog(WARNING, "HandleStreamResponse returned with DN_CONNECTION_STATE_ERROR_FATAL"); return RESPONSE_COMPLETE; } - /* No data available, exit */ + /* 没有可用数据,退出 */ if (!HAS_MESSAGE_BUFFERED(conn)) { return RESPONSE_EOF; } @@ -1876,21 +1955,21 @@ int HandleStreamResponse(PGXCNodeHandle* conn, StreamState* node) msg_type = get_message(conn, &msg_len, &msg); #ifdef MEMORY_CONTEXT_CHECKING - /* Check all memory contexts when repalloc the memory of buffer */ + /* 当重新分配缓冲区的内存时,检查所有的内存上下文 */ if (conn->inSize > old_size) { MemoryContextCheck(t_thrd.top_mem_cxt, false); } #endif switch (msg_type) { - case '\0': /* Not enough data in the buffer */ + case '\0': /* 缓冲区中数据不足 */ return RESPONSE_EOF; break; - case 'T': /* RowDescription */ - /* Stream thread should not send row desrciption. */ + case 'T': + /* 流线程不应发送行描述. */ ereport(ERROR, (errcode(ERRCODE_UNEXPECTED_NULL_VALUE), - errmsg("Stream thread should not send row desrciption."))); + errmsg("Stream thread should not send row description."))); break; case 'B': /* VectorBatch */ case 'D': /* DataRow */ @@ -1906,22 +1985,21 @@ int HandleStreamResponse(PGXCNodeHandle* conn, StreamState* node) conn->state = DN_CONNECTION_STATE_IDLE; return RESPONSE_COMPLETE; } - case 'R': { /* distributed recursive union support */ - /* Handle Recursive Union sync-up message */ + case 'R': { /* 分布式递归联合支持 */ + /* 处理递归联合同步消息 */ HandleStreamRUSyncMsg(node, 'R', msg, msg_len); return RESPONSE_RECURSIVE_SYNC_R; } default: - /* sync lost? */ ereport(ERROR, (errcode(ERRCODE_INVALID_OPTION), errmsg("Received unsupported message type: %c", msg_type))); conn->state = DN_CONNECTION_STATE_ERROR_FATAL; - /* stop reading */ + /* 停止读取 */ return RESPONSE_COMPLETE; } } - /* never happen, but keep compiler quiet */ + /* 永远不会发生,但是要保持编译器的安静 */ return RESPONSE_EOF; } @@ -1930,14 +2008,22 @@ void AssembleDataRow(StreamState* node) MemoryContext oldcontext; TupleTableSlot* slot = node->ss.ps.ps_ResultTupleSlot; + // 开始网络数据反序列化计时 NetWorkTimeDeserializeStart(t_thrd.pgxc_cxt.GlobalNetInstr); + // 切换到合适的内存上下文 oldcontext = MemoryContextSwitchTo(slot->tts_mcxt); + + // 反序列化数据行并存储到结果元组槽中 ExecStoreDataRowTuple(node->buf.msg, node->buf.len, InvalidOid, slot, false); - /* The data has been consumed. */ + + // 数据已被消费,重置缓冲区长度为0 node->buf.len = 0; + + // 切换回原始内存上下文 MemoryContextSwitchTo(oldcontext); + // 结束网络数据反序列化计时 NetWorkTimeDeserializeEnd(t_thrd.pgxc_cxt.GlobalNetInstr); } @@ -1951,33 +2037,36 @@ static bool GetTupleFromConnBuffer(StreamState* node) int connIdx = 0; PGXCNodeHandle** connections = NULL; + // 获取当前连接索引和连接数组 connIdx = node->last_conn_idx; connections = node->connections; - /* Handle data from all connection. */ + // 处理所有连接的数据 while (connIdx < node->conn_count) { int res = HandleStreamResponse(connections[connIdx], node); switch (res) { - /* Try next run. */ + // 尝试下一个连接 case RESPONSE_EOF: { connIdx++; } break; - /* Finish one connection. */ + // 完成一个连接 case RESPONSE_COMPLETE: { node->conn_count = node->conn_count - 1; - /* All finished. */ + // 所有连接已完成 if (node->conn_count == 0) { node->need_fresh_data = false; return false; } + // 移动连接并继续处理 if (connIdx < node->conn_count) { connections[connIdx] = connections[node->conn_count]; } } break; + // 处理数据行 case RESPONSE_DATAROW: { - /* If we have message in the buffer, consume it */ + // 如果缓冲区中有消息,解析并返回数据行 if (node->buf.len != 0) { AssembleDataRow(node); node->need_fresh_data = false; @@ -1985,14 +2074,16 @@ static bool GetTupleFromConnBuffer(StreamState* node) return true; } } break; + // 递归同步消息,继续处理 case RESPONSE_RECURSIVE_SYNC_F: case RESPONSE_RECURSIVE_SYNC_R: continue; + // 处理未预期的响应 default: ereport(ERROR, (errcode(ERRCODE_FETCH_DATA_FAILED), - errmsg("Unexpected response %d from Datanode when get tuple from cnnection buffer.The " - "connection idx is %d and the count of active connections is %d.", + errmsg("Unexpected response %d from Datanode when getting tuple from connection buffer. " + "The connection index is %d and the count of active connections is %d.", res, connIdx, node->conn_count))); @@ -2000,6 +2091,7 @@ static bool GetTupleFromConnBuffer(StreamState* node) } } + // 确保连接索引与活跃连接数相等 Assert(connIdx == node->conn_count); node->need_fresh_data = true; node->last_conn_idx = 0; @@ -2015,43 +2107,58 @@ static bool GetTupleFromConnBuffer(StreamState* node) */ bool ScanStreamByLibcomm(StreamState* node) { + // 循环处理所有连接的数据 while (node->conn_count) { + // 如果需要新的数据,从逻辑连接中接收数据 if (node->need_fresh_data) { if (datanode_receive_from_logic_conn(node->conn_count, node->connections, &node->netctl, -1)) { int error_code = getStreamSocketError(gs_comm_strerror()); + // 报告错误并终止 ereport(ERROR, (errcode(error_code), errmsg("Failed to read response from Datanodes. Detail: %s\n", gs_comm_strerror()))); } } + // 如果成功解析数据行,返回 true if (node->StreamDeserialize(node)) return true; else { + // 如果不需要新数据,返回 false;否则继续循环 if (node->need_fresh_data == false) return false; } + // 报告错误 StreamReportError(node); } + // 处理完所有连接的数据,返回 false return false; } bool ScanMemoryStream(StreamState* node) { + // 断言节点的排序信息为 NULL Assert(((Stream*)(node->ss.ps.plan))->sort == NULL); + + // 循环处理所有连接的数据 while (node->conn_count) { + // 如果需要新的数据,从内存中接收数据 if (node->need_fresh_data) { if (gs_memory_recv(node)) { + // 成功接收数据,返回 true return true; } else { + // 接收数据失败,报告错误并终止 StreamReportError(node); node->need_fresh_data = false; return false; } } } + + // 处理完所有连接的数据,返回 false return false; } @@ -2062,7 +2169,7 @@ StreamState* BuildStreamRuntime(Stream* node, EState* estate, int eflags) { StreamState* stream_state = NULL; - /* StreamState is allocated in StreamRunTime Memory Context if we need set up controller */ + /* 如果需要设置同步控制器,则在 StreamRunTime 内存上分配 StreamState */ if (NeedSetupSyncUpController((Plan*)node)) { MemoryContext stream_runtime_memctx = u_sess->stream_cxt.global_obj->m_streamRuntimeContext; Assert(stream_runtime_memctx != NULL); @@ -2076,25 +2183,27 @@ StreamState* BuildStreamRuntime(Stream* node, EState* estate, int eflags) MemoryContext current_memctx = MemoryContextSwitchTo(recursive_runtime_memctx); stream_state = makeNode(StreamState); - /* Swith back to current memory context */ + /* 切换回当前内存上下文 */ MemoryContextSwitchTo(current_memctx); } else { stream_state = makeNode(StreamState); } + /* 设置 StreamState 的基本信息 */ stream_state->ss.ps.plan = (Plan*)node; stream_state->ss.ps.state = estate; + /* 初始化执行节点的结果元组插槽 */ ExecInitResultTupleSlot(estate, &stream_state->ss.ps); if (node->scan.plan.targetlist) { TupleDesc typeInfo = ExecTypeFromTL(node->scan.plan.targetlist, false); ExecSetSlotDescriptor(stream_state->ss.ps.ps_ResultTupleSlot, typeInfo); } else { - /* In case there is no target list, force its creation */ + /* 如果没有目标列表,则强制创建 */ ExecAssignResultTypeFromTL(&stream_state->ss.ps); } - /* Set up underlying execution nodes on coordinator nodes */ + /* 在协调节点上设置底层执行节点 */ #ifdef ENABLE_MULTIPLE_NODES if (IS_PGXC_COORDINATOR) { #else @@ -2106,11 +2215,11 @@ StreamState* BuildStreamRuntime(Stream* node, EState* estate, int eflags) if (outerPlan(node)) outerPlanState(stream_state) = ExecInitNode(outerPlan(node), estate, eflags); } else { - /* Right tree should be null. */ + /* 右子树应该为空。 */ Assert(innerPlan(node) == NULL); } - /* Stream runtime only set up on datanode. */ + /* Stream 运行时仅在数据节点上设置 */ if (IS_PGXC_DATANODE) SetupStreamRuntime(stream_state); @@ -2124,8 +2233,10 @@ StreamState* ExecInitStream(Stream* node, EState* estate, int eflags) { StreamState* state = NULL; + /* 构建 StreamState 结构 */ state = BuildStreamRuntime(node, estate, eflags); + /* 分配 Stream 缓冲区内存,并初始化相关参数 */ state->buf.msg = (char*)palloc(STREAM_BUF_INIT_SIZE); state->buf.len = 0; state->buf.size = STREAM_BUF_INIT_SIZE; @@ -2133,12 +2244,14 @@ StreamState* ExecInitStream(Stream* node, EState* estate, int eflags) state->vector_output = false; state->StreamScan = ScanStreamByLibcomm; + /* 如果 Stream 是本地节点,使用内存扫描函数 */ if (STREAM_IS_LOCAL_NODE(node->smpDesc.distriType)) { state->StreamScan = ScanMemoryStream; TupleDesc typeInfo = ExecTypeFromTL(node->scan.plan.targetlist, false); + /* 分配 TupleVector 结构和插槽内存 */ state->tempTupleVec = (TupleVector*)palloc0(sizeof(TupleVector)); - state->tempTupleVec->tupleVector = (TupleTableSlot**)palloc0(sizeof(TupleTableSlot*) * (TupleVectorMaxSize)); + state->tempTupleVec->tupleVector = (TupleTableSlot**)palloc0(sizeof(TupleTableSlot*) * TupleVectorMaxSize); for (int j = 0; j < TupleVectorMaxSize; j++) { state->tempTupleVec->tupleVector[j] = MakeTupleTableSlot(false); @@ -2146,12 +2259,13 @@ StreamState* ExecInitStream(Stream* node, EState* estate, int eflags) } } + /* 设置 StreamState 的回调函数和参数 */ state->StreamDeserialize = GetTupleFromConnBuffer; state->recvDataLen = 0; state->type = node->type; state->receive_message = false; - /* create stream controller */ + /* 创建 Stream 控制器 */ if (NeedSetupSyncUpController(state->ss.ps.plan)) { StreamController* controller = (StreamController*)u_sess->stream_cxt.global_obj->GetSyncController(node->scan.plan.plan_node_id); @@ -2170,10 +2284,11 @@ StreamState* ExecInitStream(Stream* node, EState* estate, int eflags) TupleTableSlot* ExecStream(StreamState* node) { + /* 如果节点未准备好,进行准备工作,包括发送请求和初始化排序 */ if (unlikely(node->isReady == false)) { StreamPrepareRequest(node); - /* merge sort */ + /* 如果节点有排序操作,初始化排序 */ if (((Stream*)(node->ss.ps.plan))->sort != NULL) { InitStreamMergeSort(node); } @@ -2181,10 +2296,15 @@ TupleTableSlot* ExecStream(StreamState* node) node->receive_message = true; + /* 调用节点的扫描函数,处理数据流 */ if (node->StreamScan(node)) { + /* 扫描成功,返回结果元组插槽 */ return node->ss.ps.ps_ResultTupleSlot; } else { - /* Finish receiving data from producers, can set network perf data now. */ + /* + * 数据接收完成,可以设置网络性能数据。 + * 如果有性能统计信息,则调用接口设置网络性能数据。 + */ if (HAS_INSTR(&node->ss, false)) { u_sess->instr_cxt.global_instr->SetNetWork(node->ss.ps.plan->plan_node_id, *node->spill_size); } @@ -2203,26 +2323,23 @@ TupleTableSlot* ExecStream(StreamState* node) */ void ExecEarlyDeinitConsumer(PlanState* node) { - /* A Coordinator has no stream thread, so do not bother about that */ + /* 协调节点没有流线程,不需要处理 */ if (IS_PGXC_COORDINATOR) return; - /* Exit if skip early deinit consumer */ + /* 如果设置了跳过提前释放消费者的标志,直接返回 */ if (node->state->es_skip_early_deinit_consumer) return; + /* 根据节点类型执行相应的操作 */ switch (nodeTag(node)) { case T_StreamState: case T_VecStreamState: { - /* - * When the consumer is responsible for sync cluster steps we do not do - * real consumer-deInit, just consumer its data length - */ + /* 如果节点负责同步集群步骤,则不执行真正的消费者释放操作,只清空数据长度 */ if (EXEC_IN_RECURSIVE_MODE(node->plan)) { ((StreamState*)node)->buf.len = 0; - /* When the StreamState node under recursive union do not execute, we should receive 'Z' message - in case the connections is blocking */ + /* 当StreamState节点在递归联合下不执行时,应该接收'Z'消息以防止连接阻塞 */ if (!((StreamState*)node)->receive_message) { StreamNodeGroup* stream_nodegroup = u_sess->stream_cxt.global_obj; stream_nodegroup->ConsumerNodeStreamMessage((StreamState*)node); @@ -2239,6 +2356,7 @@ void ExecEarlyDeinitConsumer(PlanState* node) case T_AppendState: case T_VecAppendState: { AppendState* appendState = (AppendState*)node; + /* 递归调用所有子计划的提前释放操作 */ for (int planNo = 0; planNo < appendState->as_nplans; planNo++) { ExecEarlyDeinitConsumer(appendState->appendplans[planNo]); } @@ -2247,6 +2365,7 @@ void ExecEarlyDeinitConsumer(PlanState* node) case T_VecModifyTableState: case T_DistInsertSelectState: { ModifyTableState* mt = (ModifyTableState*)node; + /* 递归调用所有子计划的提前释放操作 */ for (int planNo = 0; planNo < mt->mt_nplans; planNo++) { ExecEarlyDeinitConsumer(mt->mt_plans[planNo]); } @@ -2259,11 +2378,13 @@ void ExecEarlyDeinitConsumer(PlanState* node) } break; case T_MergeAppendState: { MergeAppendState* ma = (MergeAppendState*)node; + /* 递归调用所有子计划的提前释放操作 */ for (int planNo = 0; planNo < ma->ms_nplans; planNo++) { ExecEarlyDeinitConsumer(ma->mergeplans[planNo]); } } break; default: + /* 递归调用外部和内部子计划的提前释放操作 */ if (outerPlanState(node)) { ExecEarlyDeinitConsumer(outerPlanState(node)); } @@ -2275,19 +2396,79 @@ void ExecEarlyDeinitConsumer(PlanState* node) } } -void ExecEndStream(StreamState* node) +void ExecEarlyDeinitConsumer(PlanState* node) { - if (IS_PGXC_DATANODE && node->consumer) - node->consumer->deInit(); -#ifndef ENABLE_MULTIPLE_NODES - if (u_sess->stream_cxt.global_obj) { - u_sess->stream_cxt.global_obj->SigStreamThreadClose(); - } -#endif - PlanState* outer_plan = outerPlanState(node); + /* 协调节点没有流线程,不需要处理 */ + if (IS_PGXC_COORDINATOR) + return; - if (outer_plan != NULL) - ExecEndNode(outer_plan); + /* 如果设置了跳过提前释放消费者的标志,直接返回 */ + if (node->state->es_skip_early_deinit_consumer) + return; + + /* 根据节点类型执行相应的操作 */ + switch (nodeTag(node)) { + case T_StreamState: + case T_VecStreamState: { + /* 如果节点负责同步集群步骤,则不执行真正的消费者释放操作,只清空数据长度 */ + if (EXEC_IN_RECURSIVE_MODE(node->plan)) { + ((StreamState*)node)->buf.len = 0; + + /* 当StreamState节点在递归联合下不执行时,应该接收'Z'消息以防止连接阻塞 */ + if (!((StreamState*)node)->receive_message) { + StreamNodeGroup* stream_nodegroup = u_sess->stream_cxt.global_obj; + stream_nodegroup->ConsumerNodeStreamMessage((StreamState*)node); + + ((StreamState*)node)->isReady = false; + ((StreamState*)node)->receive_message = true; + } + } else { + ((StreamState*)node)->consumer->deInit(); + } + + return; + } + case T_AppendState: + case T_VecAppendState: { + AppendState* appendState = (AppendState*)node; + /* 递归调用所有子计划的提前释放操作 */ + for (int planNo = 0; planNo < appendState->as_nplans; planNo++) { + ExecEarlyDeinitConsumer(appendState->appendplans[planNo]); + } + } break; + case T_ModifyTableState: + case T_VecModifyTableState: + case T_DistInsertSelectState: { + ModifyTableState* mt = (ModifyTableState*)node; + /* 递归调用所有子计划的提前释放操作 */ + for (int planNo = 0; planNo < mt->mt_nplans; planNo++) { + ExecEarlyDeinitConsumer(mt->mt_plans[planNo]); + } + } break; + case T_SubqueryScanState: + case T_VecSubqueryScanState: { + SubqueryScanState* ss = (SubqueryScanState*)node; + if (ss->subplan) + ExecEarlyDeinitConsumer(ss->subplan); + } break; + case T_MergeAppendState: { + MergeAppendState* ma = (MergeAppendState*)node; + /* 递归调用所有子计划的提前释放操作 */ + for (int planNo = 0; planNo < ma->ms_nplans; planNo++) { + ExecEarlyDeinitConsumer(ma->mergeplans[planNo]); + } + } break; + default: + /* 递归调用外部和内部子计划的提前释放操作 */ + if (outerPlanState(node)) { + ExecEarlyDeinitConsumer(outerPlanState(node)); + } + + if (innerPlanState(node)) { + ExecEarlyDeinitConsumer(innerPlanState(node)); + } + break; + } } /* @@ -2298,10 +2479,10 @@ void ExecEndStream(StreamState* node) */ bool executorEarlyStop() { - /* Check if query already been stopped. */ + /* 检查查询是否已被停止。 */ if (u_sess->exec_cxt.executorStopFlag == true) return true; - /* Check if query already canceled due to error or cancel/die signal. */ + /* 检查查询是否因错误、取消或终止信号而被取消。 */ else if (u_sess->stream_cxt.global_obj && u_sess->stream_cxt.global_obj->isQueryCanceled()) return true; else @@ -2339,7 +2520,7 @@ void ExecReSetStream(StreamState* node) resetLocalStreamContext(node->consumer->getSharedContext()); } - /* We dont't have to do specific operations for first level */ + /* 对于第一层级的流节点,我们不需要执行特定的操作 */ if (IsFirstLevelStreamStateNode(node)) { return; } @@ -2355,4 +2536,4 @@ void ExecReSetStream(StreamState* node) StreamNodeGroup::SyncConsumerNextPlanStep(stream_plan_nodeid, 0); } return; -} +} \ No newline at end of file -- 2.34.1 From 7f7862900913a2e5c4ff5d071cae72f5a72eeaa4 Mon Sep 17 00:00:00 2001 From: Nemoo <18210033860@163.com> Date: Sat, 30 Sep 2023 21:06:42 +0800 Subject: [PATCH 42/50] Update streamConsumer.cpp --- .../process/stream/streamConsumer.cpp | 204 +++++++++--------- 1 file changed, 106 insertions(+), 98 deletions(-) diff --git a/src/gausskernel/process/stream/streamConsumer.cpp b/src/gausskernel/process/stream/streamConsumer.cpp index 85d801d56..1471b6093 100755 --- a/src/gausskernel/process/stream/streamConsumer.cpp +++ b/src/gausskernel/process/stream/streamConsumer.cpp @@ -14,7 +14,7 @@ * ------------------------------------------------------------------------- * * streamConsumer.cpp - * Support methods for class StreamConsumer. + * 支持StreamConsumer的方法 * * IDENTIFICATION * src/gausskernel/process/stream/streamConsumer.cpp @@ -36,20 +36,22 @@ extern GlobalNodeDefinition* global_node_definition; +// 构造函数,初始化StreamConsumer对象 StreamConsumer::StreamConsumer(MemoryContext context) : StreamObj(context, STREAM_CONSUMER) { - m_sharedContext = NULL; - m_originProducerNodeList = NULL; - m_ready = false; - m_expectProducer = NULL; - m_currentProducerNum = 0; + m_sharedContext = NULL; // 初始化共享内存上下文为NULL + m_originProducerNodeList = NULL; // 初始化原始生产者节点列表为NULL + m_ready = false; // 初始化就绪状态为false + m_expectProducer = NULL; // 初始化预期生产者为NULL + m_currentProducerNum = 0; // 初始化当前生产者数量为0 } +// 析构函数,释放StreamConsumer对象的资源 StreamConsumer::~StreamConsumer() { - m_originProducerNodeList = NULL; - m_expectProducer = NULL; - m_sharedContext = NULL; + m_originProducerNodeList = NULL; // 将原始生产者节点列表设为NULL + m_expectProducer = NULL; // 将预期生产者设为NULL + m_sharedContext = NULL; // 将共享内存上下文设为NULL } /* @@ -61,12 +63,16 @@ StreamConsumer::~StreamConsumer() * @param[IN] transType: transport type * @return: void */ + /* + 函数通过参数设置了StreamConsumer对象的各个成员变量,包括传输类型、共享上下文、传输对象等,还根据传入的执行生产者节点信息, + 初始化了预期的生产者信息,并将其加入哈希表中 + */ void StreamConsumer::init(StreamKey key, List* execProducerNodes, ParallelDesc desc, StreamTransType transType, StreamSharedContext* sharedContext) { int i = 0; bool found = false; - + int producerNum = 0; AutoMutexLock streamLock(&m_streamInfoLock); AutoMutexLock copyLock(&nodeDefCopyLock); @@ -79,18 +85,19 @@ void StreamConsumer::init(StreamKey key, List* execProducerNodes, ParallelDesc d Assert(producerNum > 0); - m_parallel_desc = desc; - m_currentProducerNum = 0; - m_connNum = producerNum; - m_key = key; - m_ready = false; - m_transtype = transType; - m_sharedContext = sharedContext; - m_transport = (StreamTransport**)MemoryContextAllocZero(m_memoryCxt, producerNum * sizeof(StreamTransport*)); - m_expectProducer = (StreamConnInfo*)MemoryContextAllocZero(m_memoryCxt, producerNum * sizeof(StreamConnInfo)); + m_parallel_desc = desc; // 设置并行描述符 + m_currentProducerNum = 0; // 当前生产者数量置零 + m_connNum = producerNum; // 连接数量等于生产者数量 + m_key = key; // 设置StreamKey + m_ready = false; // 初始状态为未就绪 + m_transtype = transType; // 设置传输类型 + m_sharedContext = sharedContext; // 共享上下文 + m_transport = (StreamTransport**)MemoryContextAllocZero(m_memoryCxt, producerNum * sizeof(StreamTransport*)); // 分配传输对象数组内存 + m_expectProducer = (StreamConnInfo*)MemoryContextAllocZero(m_memoryCxt, producerNum * sizeof(StreamConnInfo)); // 分配预期生产者信息内存 - /* Initialize the origin nodelist */ + /* 初始化原始生产者节点列表 */ m_originProducerNodeList = NIL; + #ifdef ENABLE_MULTIPLE_NODES copyLock.lock(); ListCell* nodelistCell = NULL; @@ -112,6 +119,8 @@ void StreamConsumer::init(StreamKey key, List* execProducerNodes, ParallelDesc d } copyLock.unLock(); #endif + + // 初始化传输对象数组 for (i = 0; i < producerNum; i++) { int nodeNameLen = 0; libcommaddrinfo* libcommaddr = NULL; @@ -131,7 +140,7 @@ void StreamConsumer::init(StreamKey key, List* execProducerNodes, ParallelDesc d scomm->m_addr->streamKey.consumerSmpId = m_key.smpIdentifier; } - HOLD_INTERRUPTS(); /* Add this macro for double safety. */ + HOLD_INTERRUPTS(); // 加入此宏以确保安全性 streamLock.lock(); AutoContextSwitch streamInfoCxtGuard(StreamInfoContext); @@ -139,8 +148,7 @@ void StreamConsumer::init(StreamKey key, List* execProducerNodes, ParallelDesc d StreamElement* element = (StreamElement*)hash_search(m_streamInfoTbl, &m_key, HASH_ENTER, &found); if (element == NULL) { streamLock.unLock(); - ereport( - ERROR, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("Failed to create stream element due to out of memory"))); + ereport(ERROR, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("Failed to create stream element due to out of memory"))); } if (found == false) { @@ -154,16 +162,15 @@ void StreamConsumer::init(StreamKey key, List* execProducerNodes, ParallelDesc d if (NULL == element->value) { hash_search(m_streamInfoTbl, &key, HASH_REMOVE, NULL); streamLock.unLock(); - ereport(ERROR, - (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("Failed to generate stream element due to out of memory"))); + ereport(ERROR, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("Failed to generate stream element due to out of memory"))); } element->value->connNum = 0; element->value->connInfoSize = 0; - /* No need to allocate the array size. */ + /* 不需要分配数组大小。 */ element->value->connInfo = NULL; } else { - /* WTF? find a duplicate element in the hash table. */ + /* 在哈希表中找到重复的元素。 */ if (element->value == NULL || element->value->consumer) { streamLock.unLock(); ereport(ERROR, @@ -173,7 +180,7 @@ void StreamConsumer::init(StreamKey key, List* execProducerNodes, ParallelDesc d } } - /* Found true means some information has not been updated, protected by streamLock. */ + /* 找到为真表示某些信息尚未更新,由streamLock保护。 */ if (found == true) { Assert(element->value->connInfo != NULL); updateTransportInfo(element->value); @@ -215,65 +222,64 @@ void StreamConsumer::init(StreamKey key, List* execProducerNodes, ParallelDesc d */ void StreamConsumer::deInit() { - AutoMutexLock streamHashLock(&m_streamInfoLock); + AutoMutexLock streamHashLock(&m_streamInfoLock); // 加锁,保护哈希表操作 StreamElement* delinfo = NULL; - HOLD_INTERRUPTS(); - streamHashLock.lock(); + HOLD_INTERRUPTS(); // 持有中断,禁止中断发生 + streamHashLock.lock(); // 加锁,保护哈希表操作 - /* Do not need de init. */ - if (m_init == false) { - if (m_threadSyncObjInit == true) { - pthread_mutex_destroy(&m_mutex); - pthread_cond_destroy(&m_cond); - m_threadSyncObjInit = false; + /* 不需要去初始化。 */ + if (m_init == false) { // 如果未初始化 + if (m_threadSyncObjInit == true) { // 如果线程同步对象已经初始化 + pthread_mutex_destroy(&m_mutex); // 销毁互斥锁 + pthread_cond_destroy(&m_cond); // 销毁条件变量 + m_threadSyncObjInit = false; // 标记线程同步对象未初始化 } - streamHashLock.unLock(); - RESUME_INTERRUPTS(); + streamHashLock.unLock(); // 解锁,释放锁 + RESUME_INTERRUPTS(); // 恢复中断 return; } - releaseCommStream(); + releaseCommStream(); // 释放通信流 - /* Close local stream connection. */ + /* 关闭本地流连接。 */ if (NULL != m_sharedContext) { - gs_memory_close_conn(m_sharedContext, m_connNum, u_sess->stream_cxt.smp_id); + gs_memory_close_conn(m_sharedContext, m_connNum, u_sess->stream_cxt.smp_id); // 关闭共享内存中的连接 } - if (m_ready || u_sess->stream_cxt.dummy_thread == true) { - delinfo = (StreamElement*)hash_search(m_streamInfoTbl, &m_key, HASH_REMOVE, NULL); - if (delinfo != NULL) { + if (m_ready || u_sess->stream_cxt.dummy_thread == true) { // 如果已经就绪或者是虚拟线程 + delinfo = (StreamElement*)hash_search(m_streamInfoTbl, &m_key, HASH_REMOVE, NULL); // 从哈希表中移除该消费者信息 + if (delinfo != NULL) { // 如果找到了消费者信息 if (delinfo->value->connInfo != NULL) { - pfree_ext(delinfo->value->connInfo); + pfree_ext(delinfo->value->connInfo); // 释放连接信息内存 delinfo->value->connInfo = NULL; } - pfree_ext(delinfo->value); + pfree_ext(delinfo->value); // 释放消费者信息内存 delinfo->value = NULL; } } else { /* - * set flag to release net port in case some producer not ready and element - * in stream info hash table can not get removed in wakeUpConsumer, - * or consumer get canceled when waiting producer ready. + * 设置标志以释放网络端口,在某些生产者未就绪的情况下, + * 或者在等待生产者就绪时,如果消费者被取消。 */ - u_sess->stream_cxt.global_obj->setNeedClean(true); + u_sess->stream_cxt.global_obj->setNeedClean(true); // 设置需要清理标志 } - if (m_threadSyncObjInit == true) { - pthread_mutex_destroy(&m_mutex); - pthread_cond_destroy(&m_cond); - m_threadSyncObjInit = false; + if (m_threadSyncObjInit == true) { // 如果线程同步对象已经初始化 + pthread_mutex_destroy(&m_mutex); // 销毁互斥锁 + pthread_cond_destroy(&m_cond); // 销毁条件变量 + m_threadSyncObjInit = false; // 标记线程同步对象未初始化 } - m_init = false; - streamHashLock.unLock(); + m_init = false; // 标记消费者未初始化 + streamHashLock.unLock(); // 解锁,释放锁 - /* Cleanup the original producer list */ - m_originProducerNodeList = NIL; + /* 清理原始生产者列表 */ + m_originProducerNodeList = NIL; // 清空原始生产者节点列表 - RESUME_INTERRUPTS(); + RESUME_INTERRUPTS(); // 恢复中断 } /* @@ -283,12 +289,13 @@ void StreamConsumer::deInit() */ void StreamConsumer::releaseCommStream() { - if (m_transport != NULL) { - for (int i = 0; i < m_connNum; i++) { - Assert(t_thrd.int_cxt.ImmediateInterruptOK == false); - StreamCOMM* scomm = (StreamCOMM*)m_transport[i]; - if (scomm != NULL) - scomm->release(); + if (m_transport != NULL) { // 如果传输对象数组不为空 + for (int i = 0; i < m_connNum; i++) { // 遍历所有连接 + Assert(t_thrd.int_cxt.ImmediateInterruptOK == false); // 断言当前不可中断 + + StreamCOMM* scomm = (StreamCOMM*)m_transport[i]; // 获取当前连接的通信对象 + if (scomm != NULL) // 如果通信对象不为空 + scomm->release(); // 释放通信资源 } } } @@ -301,11 +308,11 @@ void StreamConsumer::releaseCommStream() */ int StreamConsumer::getNodeIdx(const char* nodename) { - for (int i = 0; i < m_connNum; i++) { - if (pg_strncasecmp(m_expectProducer[i].nodeName, nodename, strlen(nodename)) == 0) - return m_expectProducer[i].nodeIdx; + for (int i = 0; i < m_connNum; i++) { // 遍历所有生产者节点 + if (pg_strncasecmp(m_expectProducer[i].nodeName, nodename, strlen(nodename)) == 0) // 如果节点名匹配 + return m_expectProducer[i].nodeIdx; // 返回节点索引 } - return -1; + return -1; // 如果未找到匹配节点,返回-1 } /* @@ -318,17 +325,17 @@ void StreamConsumer::findUnconnectProducer(StringInfo str) { bool found = false; - for (int j = 0; j < m_connNum; j++) { + for (int j = 0; j < m_connNum; j++) { // 遍历所有预期的生产者节点 found = false; - for (int i = 0; i < m_currentProducerNum; i++) { - if (strcmp(m_expectProducer[j].nodeName, m_transport[i]->m_nodeName) == 0) { - found = true; + for (int i = 0; i < m_currentProducerNum; i++) { // 遍历当前已连接的生产者节点 + if (strcmp(m_expectProducer[j].nodeName, m_transport[i]->m_nodeName) == 0) { // 如果找到匹配节点 + found = true; // 标记为已连接 break; } } - if (!found) - appendStringInfo(str, " %s", m_expectProducer[j].nodeName); + if (!found) // 如果未找到匹配节点(即未连接) + appendStringInfo(str, " %s", m_expectProducer[j].nodeName); // 将节点名追加到字符串中 } } @@ -342,21 +349,21 @@ int StreamConsumer::getFirstUnconnectedProducerNodeIdx() bool found = false; int nodeIdx = -1; - for (int j = 0; j < m_connNum; j++) { + for (int j = 0; j < m_connNum; j++) { // 遍历所有预期的生产者节点 found = false; - for (int i = 0; i < m_currentProducerNum; i++) { - if (strcmp(m_expectProducer[j].nodeName, m_transport[i]->m_nodeName) == 0) { - found = true; + for (int i = 0; i < m_currentProducerNum; i++) { // 遍历当前已连接的生产者节点 + if (strcmp(m_expectProducer[j].nodeName, m_transport[i]->m_nodeName) == 0) { // 如果找到匹配节点 + found = true; // 标记为已连接 break; } } - if (!found) { - nodeIdx = m_expectProducer[j].nodeIdx; + if (!found) { // 如果未找到匹配节点(即未连接) + nodeIdx = m_expectProducer[j].nodeIdx; // 设置未连接节点的索引 break; } } - return nodeIdx; + return nodeIdx; // 返回第一个未连接节点的索引 } /* @@ -366,7 +373,7 @@ int StreamConsumer::getFirstUnconnectedProducerNodeIdx() */ void StreamConsumer::waitProducerReady() { - /* For local stream, producer will never connect to consumer. Consumer is ready, just return. */ + /* 对于本地流,生产者永远不会连接到消费者。消费者准备好了,直接返回。 */ if (STREAM_IS_LOCAL_NODE(m_parallel_desc.distriType)) { m_ready = true; return; @@ -384,7 +391,7 @@ void StreamConsumer::waitProducerReady() Assert(t_thrd.int_cxt.ImmediateInterruptOK == false); streamLock.lock(); - /* 900s timeout. */ + /* 900秒的超时时间。 */ struct timespec timer; int ret; int ntimes = 1; @@ -419,11 +426,11 @@ void StreamConsumer::waitProducerReady() ereport(ERROR, (errmodule(MOD_STREAM), errcode(ERRCODE_CONNECTION_TIMED_OUT), - errmsg("Distribute query initializing network connection timeout. un-connected nodes: %s", + errmsg("Distribute query initializing network connection timeout. un-connected nodes:%s", str.data))); } - /* Check for interrupts.(cancel signal?). */ + /* 检查中断(取消信号?)。 */ CHECK_FOR_INTERRUPTS(); streamLock.lock(); } @@ -442,6 +449,7 @@ void StreamConsumer::waitProducerReady() return; } + /* * @Description: Wake up consumer and let it work * @@ -465,7 +473,7 @@ bool StreamConsumer::wakeUpConsumerCallBack(CommStreamKey commKey, StreamConnInf streamLock.lock(); - /* Check if can accept connection now */ + /* 检查是否可以接受连接。 */ if (StreamNodeGroup::checkStreamConnectPermission(key.queryId) == false) { streamLock.unLock(); return false; @@ -474,9 +482,9 @@ bool StreamConsumer::wakeUpConsumerCallBack(CommStreamKey commKey, StreamConnInf AutoContextSwitch streamInfoCxtGuard(StreamInfoContext); /* - * Register me in the global consumer table if the consumer thread has not been started. - * Libcomm r_flow_ctrl thread call this, must not use palloc and elog. - * so use isLibcommThread flag, return false where malloc failed. + * 如果消费者线程尚未启动,则在全局消费者表中注册我。 + * Libcomm r_flow_ctrl线程调用这个函数,不能使用palloc和elog。 + * 所以使用isLibcommThread标志,在malloc失败时返回false。 */ StreamElement* element = (StreamElement*)hash_search(m_streamInfoTbl, &key, HASH_ENTER, &found); if (element == NULL) { @@ -485,7 +493,7 @@ bool StreamConsumer::wakeUpConsumerCallBack(CommStreamKey commKey, StreamConnInf return false; } - /* If StreamElement not register by the consumer thread, we save the information. */ + /* 如果StreamElement尚未由消费者线程注册,则保存信息。 */ if (found == false) { element->value = (StreamValue*)palloc0_noexcept(sizeof(StreamValue)); if (NULL == element->value) { @@ -516,7 +524,7 @@ bool StreamConsumer::wakeUpConsumerCallBack(CommStreamKey commKey, StreamConnInf element->value->consumer = NULL; } else { if (element->value) { - /* Consumer thread has not been registered yet. */ + /* 消费者线程尚未注册。 */ if (element->value->consumer == NULL) { element->value->connInfo[element->value->connNum].port.libcomm_layer.gsock = connInfo.port.libcomm_layer.gsock; @@ -528,7 +536,7 @@ bool StreamConsumer::wakeUpConsumerCallBack(CommStreamKey commKey, StreamConnInf element->value->connInfo[element->value->connNum].producerSmpId = connInfo.producerSmpId; element->value->connNum++; - /* Check if need realloc to remember more connection info. */ + /* 检查是否需要重新分配内存以记住更多的连接信息。 */ if (element->value->connNum == element->value->connInfoSize) { StreamConnInfo* new_connInfo = NULL; int new_connInfoSize = 2 * element->value->connInfoSize; @@ -557,10 +565,10 @@ bool StreamConsumer::wakeUpConsumerCallBack(CommStreamKey commKey, StreamConnInf element->value->connInfoSize = new_connInfoSize; } } else { - /* Have registered, so we update the stream info. */ + /* 已经注册,所以我们更新流信息。 */ res = element->value->consumer->updateStreamCommInfo(&connInfo); - /* We can free the memory, no longer need it. */ + /* 我们可以释放内存了,不再需要它。 */ if (element->value->connInfo != NULL) { pfree_ext(element->value->connInfo); element->value->connInfo = NULL; @@ -588,7 +596,7 @@ bool StreamConsumer::updateStreamCommInfo(StreamConnInfo* connInfo) streamLock.lock(); - /* WTF, duplicate plan id encounter or plan error. */ + /* 出现重复的计划ID或计划错误。 */ if (m_currentProducerNum >= m_connNum) { streamLock.unLock(); return false; @@ -632,4 +640,4 @@ void StreamConsumer::updateTransportInfo(StreamValue* val) if (m_currentProducerNum == m_connNum) m_ready = true; } -} +} \ No newline at end of file -- 2.34.1 From 46863f5435663ec57bf9e4b48b1080c5d1285f0b Mon Sep 17 00:00:00 2001 From: Nemoo <18210033860@163.com> Date: Sat, 30 Sep 2023 21:07:12 +0800 Subject: [PATCH 43/50] Update streamCore.cpp --- src/gausskernel/process/stream/streamCore.cpp | 652 ++++++++---------- 1 file changed, 299 insertions(+), 353 deletions(-) diff --git a/src/gausskernel/process/stream/streamCore.cpp b/src/gausskernel/process/stream/streamCore.cpp index 2c50a885c..e2212b4a8 100755 --- a/src/gausskernel/process/stream/streamCore.cpp +++ b/src/gausskernel/process/stream/streamCore.cpp @@ -64,7 +64,7 @@ #include "distributelayer/streamConsumer.h" #include "storage/procsignal.h" -/* Process-wise variables. */ +/* Process-wise变量. */ MemoryContext StreamObj::m_memoryGlobalCxt = NULL; pthread_mutex_t StreamObj::m_streamInfoLock; HTAB* StreamObj::m_streamInfoTbl = NULL; @@ -75,25 +75,27 @@ HTAB* StreamNodeGroup::m_streamNodeGroupTbl = NULL; HTAB* StreamNodeGroup::m_streamConnectSyncTbl = NULL; pthread_mutex_t StreamNodeGroup::m_streamConnectSyncLock; +// 定义一个静态函数 ConsumerNodeSyncUpMessage,参数包括一个递归联合控制器指针、一个步骤数和一个流状态指针 static void ConsumerNodeSyncUpMessage(RecursiveUnionController* controller, int step, StreamState* node); +// StreamObj 类的构造函数,接受内存上下文和流对象类型作为参数 StreamObj::StreamObj(MemoryContext context, StreamObjType type) - : m_nodeGroup(0), - m_transport(0), - m_pair(NULL), - m_type(type), - m_memoryCxt(context), - m_connNum(0), - m_nodeGroupIdx(0), - m_threadId(InvalidTid) + : m_nodeGroup(0), // 初始化节点组 ID 为 0 + m_transport(0), // 初始化传输通道为 0 + m_pair(NULL), // 初始化流对象对为 NULL + m_type(type), // 初始化流对象类型 + m_memoryCxt(context), // 初始化内存上下文 + m_connNum(0), // 初始化连接数为 0 + m_nodeGroupIdx(0), // 初始化节点组索引为 0 + m_threadId(InvalidTid) // 初始化线程 ID 为无效值 { - m_nodeId = u_sess->pgxc_cxt.PGXCNodeId; - m_streamNode = NULL; - m_init = false; - m_threadSyncObjInit = false; - m_status = STREAM_UNDEFINED; + m_nodeId = u_sess->pgxc_cxt.PGXCNodeId; // 设置节点 ID 为当前会话的 PGXC 节点 ID + m_streamNode = NULL; // 初始化流节点为 NULL + m_init = false; // 初始化标志位为 false,表示未初始化 + m_threadSyncObjInit = false; // 初始化线程同步对象标志位为 false + m_status = STREAM_UNDEFINED; // 初始化流状态为 STREAM_UNDEFINED #ifdef ENABLE_MULTIPLE_NODES - /* should not be the initial value, check the logic that coordinator send node id. */ + // 多节点环境下,节点 ID 不应该是初始值,检查协调器发送节点 ID 的逻辑 Assert(m_nodeId != -1); #endif } @@ -162,50 +164,50 @@ void StreamObj::releaseNetPort() */ void StreamObj::releaseNetPortInHashTable() { - StreamElement* element = NULL; - AutoMutexLock streamLock(&m_streamInfoLock); - HASH_SEQ_STATUS hash_seq; + StreamElement* element = NULL; // 声明 StreamElement 指针,用于遍历哈希表 + AutoMutexLock streamLock(&m_streamInfoLock); // 创建自动互斥锁,确保线程安全访问哈希表 + HASH_SEQ_STATUS hash_seq; // 哈希表遍历序列状态 - /* Exit if hash table is not yet created */ + /* 如果哈希表尚未创建,则退出函数 */ if (NULL == m_streamInfoTbl) return; - HOLD_INTERRUPTS(); /* add this macro for double safety */ - streamLock.lock(); - hash_seq_init(&hash_seq, m_streamInfoTbl); + HOLD_INTERRUPTS(); /* 添加此宏以提高安全性 */ + streamLock.lock(); // 加锁,开始访问哈希表 + hash_seq_init(&hash_seq, m_streamInfoTbl); // 初始化哈希表遍历序列 - /* Sequentially search through hash table and release net port identified by u_sess->debug_query_id. */ + /* 顺序遍历哈希表,并释放由 u_sess->debug_query_id 标识的网络端口 */ while ((element = (StreamElement*)hash_seq_search(&hash_seq)) != NULL) { - if (element->key.queryId == (uint64)u_sess->debug_query_id) { - StreamValue* sVal = element->value; - StreamKey key = element->key; + if (element->key.queryId == (uint64)u_sess->debug_query_id) { // 如果元素的查询 ID 与当前会话的 debug_query_id 匹配 + StreamValue* sVal = element->value; // 获取 StreamValue 结构体指针 + StreamKey key = element->key; // 获取 StreamKey 结构体 if (sVal != NULL && sVal->connInfo != NULL) { - StreamConnInfo* connInfo = sVal->connInfo; - int connNum = sVal->connNum; + StreamConnInfo* connInfo = sVal->connInfo; // 获取连接信息数组指针 + int connNum = sVal->connNum; // 获取连接数 for (int i = 0; i < connNum; i++) { - gs_close_gsocket(&connInfo[i].port.libcomm_layer.gsock); + gs_close_gsocket(&connInfo[i].port.libcomm_layer.gsock); // 关闭网络套接字 } } - /* Free memory. */ + /* 释放内存。*/ if (element->value != NULL) { if (element->value->connInfo != NULL) { - pfree_ext(element->value->connInfo); + pfree_ext(element->value->connInfo); // 释放连接信息内存 element->value->connInfo = NULL; } - pfree_ext(element->value); + pfree_ext(element->value); // 释放 StreamValue 结构体内存 element->value = NULL; } - /* Remove the element left in hash table. */ - (void)hash_search(m_streamInfoTbl, &key, HASH_REMOVE, NULL); + /* 从哈希表中移除剩余的元素。*/ + (void)hash_search(m_streamInfoTbl, &key, HASH_REMOVE, NULL); // 从哈希表中移除匹配的元素 } } - streamLock.unLock(); - RESUME_INTERRUPTS(); + streamLock.unLock(); // 解锁,结束对哈希表的访问 + RESUME_INTERRUPTS(); // 恢复中断处理 } /* @@ -266,7 +268,7 @@ const Stream* StreamObj::getStream() */ void StreamObj::startUp() { - HASHCTL ctl; + HASHCTL ctl; // 声明哈希表控制结构体 errno_t rc = EOK; m_memoryGlobalCxt = AllocSetContextCreate(g_instance.instance_context, @@ -274,23 +276,23 @@ void StreamObj::startUp() ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE, - SHARED_CONTEXT); + SHARED_CONTEXT); // 创建全局内存上下文 - /* init the stream info table ctl. */ - rc = memset_s(&ctl, sizeof(ctl), 0, sizeof(ctl)); + /* 初始化哈希表控制结构体。*/ + rc = memset_s(&ctl, sizeof(ctl), 0, sizeof(ctl)); // 将 ctl 结构体清零 securec_check(rc, "\0", "\0"); - ctl.keysize = sizeof(StreamKey); - ctl.entrysize = sizeof(StreamElement); - ctl.hash = tag_hash; - ctl.hcxt = m_memoryGlobalCxt; + ctl.keysize = sizeof(StreamKey); // 设置键的大小为 StreamKey 结构体的大小 + ctl.entrysize = sizeof(StreamElement); // 设置条目的大小为 StreamElement 结构体的大小 + ctl.hash = tag_hash; // 设置哈希函数 + ctl.hcxt = m_memoryGlobalCxt; // 设置哈希表的内存上下文为全局内存上下文 /* - * m_streamInfoTbl is created with HASH_NOEXCEPT, - * so hash_search with HASH_ENTER don't throw when - * palloc failed. We must check whether the result is NULL. + * m_streamInfoTbl 使用 HASH_NOEXCEPT 创建, + * 因此 hash_search 使用 HASH_ENTER 在 palloc 失败时不会抛出异常。 + * 我们必须检查结果是否为 NULL。 */ m_streamInfoTbl = - hash_create("stream info lookup hash", 256, &ctl, HASH_ELEM | HASH_FUNCTION | HASH_SHRCTX | HASH_NOEXCEPT); + hash_create("stream info lookup hash", 256, &ctl, HASH_ELEM | HASH_FUNCTION | HASH_SHRCTX | HASH_NOEXCEPT); // 创建哈希表 } /* @@ -334,30 +336,30 @@ ParallelDesc StreamObj::getParallelDesc() } StreamNodeGroup::StreamNodeGroup() - : m_size(0), - m_streamNum(1), - m_createThreadNum(0), - m_streamEnter(0), - m_canceled(false), - m_needClean(false), - m_errorStop(false), - m_recursiveVfdInvalid(false) + : m_size(0), // 初始化节点组大小为 0 + m_streamNum(1), // 初始化流数量为 1 + m_createThreadNum(0), // 初始化创建线程数为 0 + m_streamEnter(0), // 初始化进入流的次数为 0 + m_canceled(false), // 初始化取消标志为 false + m_needClean(false), // 初始化需要清理标志为 false + m_errorStop(false), // 初始化错误停止标志为 false + m_recursiveVfdInvalid(false) // 初始化递归 VFD 无效标志为 false { - pthread_mutex_init(&m_mutex, NULL); - pthread_mutex_init(&m_recursiveMutex, NULL); - pthread_cond_init(&m_cond, NULL); - m_pid = gs_thread_self(); - m_streamPairList = NULL; - m_streamConsumerList = NULL; - m_streamProducerList = NULL; - m_syncControllers = NIL; - m_streamRuntimeContext = NULL; - m_streamArray = NULL; - m_quitWaitCond = 0; - m_edataWriteProtect = EDATA_WRITE_ENABLE; - m_producerEdata = NULL; + pthread_mutex_init(&m_mutex, NULL); // 初始化互斥锁 m_mutex + pthread_mutex_init(&m_recursiveMutex, NULL); // 初始化递归互斥锁 m_recursiveMutex + pthread_cond_init(&m_cond, NULL); // 初始化条件变量 m_cond + m_pid = gs_thread_self(); // 获取当前线程 ID + m_streamPairList = NULL; // 初始化流对列表为 NULL + m_streamConsumerList = NULL; // 初始化流消费者列表为 NULL + m_streamProducerList = NULL; // 初始化流生产者列表为 NULL + m_syncControllers = NIL; // 初始化同步控制器列表为 NIL + m_streamRuntimeContext = NULL; // 初始化流运行时上下文为 NULL + m_streamArray = NULL; // 初始化流数组为 NULL + m_quitWaitCond = 0; // 初始化退出等待条件为 0 + m_edataWriteProtect = EDATA_WRITE_ENABLE; // 初始化 edata 写保护状态为可写 + m_producerEdata = NULL; // 初始化生产者 edata 为 NULL #ifndef ENABLE_MULTIPLE_NODES - m_portal = NULL; + m_portal = NULL; // 初始化 portal 为 NULL(在单节点模式下使用) #endif } @@ -372,25 +374,25 @@ StreamNodeGroup::~StreamNodeGroup() */ void StreamNodeGroup::Init(int threadNum) { - m_size = threadNum + 1; /* all stream thead + top consumer thread */ - m_streamArray = (StreamNode*)palloc0(sizeof(StreamNode) * m_size); + m_size = threadNum + 1; /* 所有流线程 + 顶层消费者线程 */ + m_streamArray = (StreamNode*)palloc0(sizeof(StreamNode) * m_size); /* 分配流节点数组的内存并初始化为零 */ for (int i = 0; i < m_size; i++) - m_streamArray[i].status = STREAM_UNDEFINED; + m_streamArray[i].status = STREAM_UNDEFINED; /* 将流节点状态初始化为 STREAM_UNDEFINED */ + + m_quitWaitCond = m_size; /* 退出等待条件数等于节点组大小 */ - /* all stream thead + top consumer thread. */ - m_quitWaitCond = m_size; #ifdef ENABLE_MULTIPLE_NODES bool found = false; AutoMutexLock streamLock(&m_streamNodeGroupLock); - /* register the node group now */ + /* 现在注册节点组 */ streamLock.lock(); StreamNodeElement* element = (StreamNodeElement*)hash_search(m_streamNodeGroupTbl, &m_pid, HASH_ENTER, &found); if (found != false) { streamLock.unLock(); - ereport(ERROR, (errcode(ERRCODE_SYSTEM_ERROR), errmsg("pid of stream nodegroup id is duplicated"))); + ereport(ERROR, (errcode(ERRCODE_SYSTEM_ERROR), errmsg("pid of stream nodegroup id is duplicated"))); /* 如果找到重复的 PID,报错 */ } - element->value = this; - element->key = gs_thread_self(); + element->value = this; /* 将当前节点组对象赋给哈希表中的值 */ + element->key = gs_thread_self(); /* 将当前线程 ID 赋给哈希表中的键 */ streamLock.unLock(); #endif } @@ -410,9 +412,9 @@ void StreamNodeGroup::StartUp() ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE, - SHARED_CONTEXT); + SHARED_CONTEXT); /* 创建全局内存上下文 */ - /* init the stream node group table ctl. */ + /* 初始化流节点组哈希表的控制结构 */ rc = memset_s(&nodectl, sizeof(nodectl), 0, sizeof(nodectl)); securec_check(rc, "\0", "\0"); nodectl.keysize = sizeof(ThreadId); @@ -423,9 +425,9 @@ void StreamNodeGroup::StartUp() m_streamNodeGroupTbl = hash_create("stream node group lookup hash", 256, &nodectl, HASH_ELEM | HASH_FUNCTION | HASH_SHRCTX); #endif - pthread_mutex_init(&m_streamNodeGroupLock, NULL); + pthread_mutex_init(&m_streamNodeGroupLock, NULL); /* 初始化流节点组锁 */ - /* init the stream connect sync table ctl */ + /* 初始化流连接同步哈希表的控制结构 */ rc = memset_s(&nodectl, sizeof(nodectl), 0, sizeof(nodectl)); securec_check(rc, "\0", "\0"); nodectl.keysize = sizeof(uint64); @@ -436,7 +438,7 @@ void StreamNodeGroup::StartUp() m_streamConnectSyncTbl = hash_create("stream connect sync hash", 256, &nodectl, HASH_ELEM | HASH_FUNCTION | HASH_SHRCTX); - pthread_mutex_init(&m_streamConnectSyncLock, NULL); + pthread_mutex_init(&m_streamConnectSyncLock, NULL); /* 初始化流连接同步锁 */ } /* @@ -447,24 +449,24 @@ void StreamNodeGroup::StartUp() */ int StreamNodeGroup::registerStream(StreamObj* obj) { - if (obj->getType() == STREAM_PRODUCER) { + if (obj->getType() == STREAM_PRODUCER) { /* 如果是生产者流对象 */ int streamIdx; - AutoMutexLock streamLock(&m_mutex); + AutoMutexLock streamLock(&m_mutex); /* 自动加锁 */ - streamLock.lock(); - m_streamArray[m_streamNum].streamObj = obj; - setNodeStatus(m_streamNum, STREAM_INPROGRESS); - streamIdx = m_streamNum; - m_streamNum++; - streamLock.unLock(); - Assert(m_streamNum <= m_size); - return streamIdx; - } else { - AutoContextSwitch streamContext(u_sess->stream_cxt.stream_runtime_mem_cxt); - m_streamArray[0].stopFlag = &u_sess->exec_cxt.executorStopFlag; - m_streamArray[0].consumerList = lcons(obj, m_streamArray[0].consumerList); - m_streamArray[0].status = STREAM_INPROGRESS; - return 0; + streamLock.lock(); /* 上锁 */ + m_streamArray[m_streamNum].streamObj = obj; /* 在流数组中保存流对象 */ + setNodeStatus(m_streamNum, STREAM_INPROGRESS); /* 设置节点状态为进行中 */ + streamIdx = m_streamNum; /* 获取流索引 */ + m_streamNum++; /* 增加流数量 */ + streamLock.unLock(); /* 解锁 */ + Assert(m_streamNum <= m_size); /* 断言流数量不超过总数 */ + return streamIdx; /* 返回流索引 */ + } else { /* 如果是消费者流对象 */ + AutoContextSwitch streamContext(u_sess->stream_cxt.stream_runtime_mem_cxt); /* 切换上下文到流运行时内存上下文 */ + m_streamArray[0].stopFlag = &u_sess->exec_cxt.executorStopFlag; /* 设置停止标志指针 */ + m_streamArray[0].consumerList = lcons(obj, m_streamArray[0].consumerList); /* 将消费者流对象添加到消费者列表 */ + m_streamArray[0].status = STREAM_INPROGRESS; /* 设置节点状态为进行中 */ + return 0; /* 返回0表示消费者流对象注册成功 */ } } @@ -477,37 +479,37 @@ int StreamNodeGroup::registerStream(StreamObj* obj) */ void StreamNodeGroup::unregisterStream(int groupIdx, StreamObjStatus status) { - if (groupIdx == 0) { + if (groupIdx == 0) { /* 如果是消费者流组 */ ListCell* consumerCell = NULL; StreamConsumer* consumerObj = NULL; - if (m_streamArray && m_streamArray[0].consumerList != NULL) { - if (m_streamArray[0].stopFlag != NULL) - *(m_streamArray[0].stopFlag) = false; + if (m_streamArray && m_streamArray[0].consumerList != NULL) { /* 如果消费者列表不为空 */ + if (m_streamArray[0].stopFlag != NULL) /* 如果停止标志指针不为空 */ + *(m_streamArray[0].stopFlag) = false; /* 将停止标志设置为false */ - foreach (consumerCell, m_streamArray[0].consumerList) { - consumerObj = (StreamConsumer*)lfirst(consumerCell); + foreach (consumerCell, m_streamArray[0].consumerList) { /* 遍历消费者列表 */ + consumerObj = (StreamConsumer*)lfirst(consumerCell); /* 获取消费者流对象 */ if (consumerObj == NULL) { continue; } - consumerObj->deInit(); + consumerObj->deInit(); /* 调用消费者流对象的deInit()方法进行反初始化 */ } - m_streamArray[0].consumerList = NULL; + m_streamArray[0].consumerList = NULL; /* 将消费者列表置为空 */ } - } else { - Assert(groupIdx < m_streamNum); + } else { /* 如果是生产者流组 */ + Assert(groupIdx < m_streamNum); /* 断言组索引小于流数量 */ if (m_streamArray && m_streamArray[groupIdx].streamObj != NULL) { - m_streamArray[groupIdx].streamObj->setThreadId(InvalidTid); + m_streamArray[groupIdx].streamObj->setThreadId(InvalidTid); /* 设置线程ID为无效值 */ } - setNodeStatus(groupIdx, status); + setNodeStatus(groupIdx, status); /* 设置节点状态 */ } - /* Avoid other threads checking empty and assignmenting with stopFlag at the same time */ - AutoMutexLock streamLock(&m_streamNodeGroupLock); - streamLock.lock(); - if (m_streamArray) - m_streamArray[groupIdx].stopFlag = NULL; - streamLock.unLock(); + /* 避免其他线程同时检查空和停止标志的赋值 */ + AutoMutexLock streamLock(&m_streamNodeGroupLock); /* 自动加锁 */ + streamLock.lock(); /* 上锁 */ + if (m_streamArray) /* 如果流数组不为空 */ + m_streamArray[groupIdx].stopFlag = NULL; /* 将停止标志指针置为空 */ + streamLock.unLock(); /* 解锁 */ } /* @@ -516,72 +518,74 @@ void StreamNodeGroup::unregisterStream(int groupIdx, StreamObjStatus status) * @param[IN] signo: signal number * @return: void */ -void StreamNodeGroup::signalStreamThreadInNodeGroup(int signo) +void StreamNodeGroup::unregisterStream(int groupIdx, StreamObjStatus status) { - if (StreamTopConsumerAmI() && m_streamArray) { - for (int i = 0; i < m_streamNum; i++) { - if (m_streamArray[i].streamObj != NULL && m_streamArray[i].streamObj->getThreadId() != InvalidTid) { - int ntimes = 1; - StreamProducer* producer = (StreamProducer*)m_streamArray[i].streamObj; + if (groupIdx == 0) { /* 如果是消费者流组 */ + ListCell* consumerCell = NULL; + StreamConsumer* consumerObj = NULL; - /* - * Signal slot must be already registered if stream thread already inited. - * If not, wait 1ms once and then recheck. Sets the maximum total wait - * time as 30s. - */ - while (producer->getThreadInit() == false) { - /* sleep 1ms */ - pg_usleep(1000); + if (m_streamArray && m_streamArray[0].consumerList != NULL) { /* 如果消费者列表不为空 */ + if (m_streamArray[0].stopFlag != NULL) /* 如果停止标志指针不为空 */ + *(m_streamArray[0].stopFlag) = false; /* 将停止标志设置为false */ - ntimes++; - if (ntimes == 30000) { - /* wait 30s, just break here */ - break; - } + foreach (consumerCell, m_streamArray[0].consumerList) { /* 遍历消费者列表 */ + consumerObj = (StreamConsumer*)lfirst(consumerCell); /* 获取消费者流对象 */ + if (consumerObj == NULL) { + continue; } - - /* - * mark before send signal, - * used for signal handle to check signal whether signal is vaild. - */ - t_thrd.sig_cxt.gs_sigale_check_type = SIGNAL_CHECK_STREAM_STOP; - gs_signal_send(producer->getThreadId(), signo); + consumerObj->deInit(); /* 调用消费者流对象的deInit()方法进行反初始化 */ } + + m_streamArray[0].consumerList = NULL; /* 将消费者列表置为空 */ } + } else { /* 如果是生产者流组 */ + Assert(groupIdx < m_streamNum); /* 断言组索引小于流数量 */ + if (m_streamArray && m_streamArray[groupIdx].streamObj != NULL) { + m_streamArray[groupIdx].streamObj->setThreadId(InvalidTid); /* 设置线程ID为无效值 */ + } + setNodeStatus(groupIdx, status); /* 设置节点状态 */ } + /* 避免其他线程同时检查空和停止标志的赋值 */ + AutoMutexLock streamLock(&m_streamNodeGroupLock); /* 自动加锁 */ + streamLock.lock(); /* 上锁 */ + if (m_streamArray) /* 如果流数组不为空 */ + m_streamArray[groupIdx].stopFlag = NULL; /* 将停止标志指针置为空 */ + streamLock.unLock(); /* 解锁 */ } -#ifndef ENABLE_MULTIPLE_NODES -void StreamNodeGroup::SigStreamThreadClose() +void StreamNodeGroup::unregisterStream(int groupIdx, StreamObjStatus status) { - if (StreamTopConsumerAmI() && m_streamArray) { - for (int i = 0; i < m_streamNum; i++) { - if (m_streamArray[i].streamObj != NULL && m_streamArray[i].streamObj->getThreadId() != InvalidTid) { - int ntimes = 1; - StreamProducer* producer = (StreamProducer*)m_streamArray[i].streamObj; - /* - * Signal slot must be already registered if stream thread already inited. - * If not, wait 1ms once and then recheck. Sets the maximum total wait - * time as 30s. - */ - while (producer->getThreadInit() == false) { - /* sleep 1ms */ - pg_usleep(1000); - ntimes++; - if (ntimes == 30000) { - /* wait 30s, just break here */ - break; - } + if (groupIdx == 0) { /* 如果是消费者流组 */ + ListCell* consumerCell = NULL; + StreamConsumer* consumerObj = NULL; + + if (m_streamArray && m_streamArray[0].consumerList != NULL) { /* 如果消费者列表不为空 */ + if (m_streamArray[0].stopFlag != NULL) /* 如果停止标志指针不为空 */ + *(m_streamArray[0].stopFlag) = false; /* 将停止标志设置为false */ + + foreach (consumerCell, m_streamArray[0].consumerList) { /* 遍历消费者列表 */ + consumerObj = (StreamConsumer*)lfirst(consumerCell); /* 获取消费者流对象 */ + if (consumerObj == NULL) { + continue; } - /* - * mark before send signal, - * used for signal handle to check signal whether signal is vaild. - */ - (void)SendProcSignal(producer->getThreadId(), PROCSIG_STREAM_STOP_CHECK, InvalidBackendId); + consumerObj->deInit(); /* 调用消费者流对象的deInit()方法进行反初始化 */ } + + m_streamArray[0].consumerList = NULL; /* 将消费者列表置为空 */ } + } else { /* 如果是生产者流组 */ + Assert(groupIdx < m_streamNum); /* 断言组索引小于流数量 */ + if (m_streamArray && m_streamArray[groupIdx].streamObj != NULL) { + m_streamArray[groupIdx].streamObj->setThreadId(InvalidTid); /* 设置线程ID为无效值 */ + } + setNodeStatus(groupIdx, status); /* 设置节点状态 */ } + /* 避免其他线程同时检查空和停止标志的赋值 */ + AutoMutexLock streamLock(&m_streamNodeGroupLock); /* 自动加锁 */ + streamLock.lock(); /* 上锁 */ + if (m_streamArray) /* 如果流数组不为空 */ + m_streamArray[groupIdx].stopFlag = NULL; /* 将停止标志指针置为空 */ + streamLock.unLock(); /* 解锁 */ } -#endif /* * @Description: Cancel all stream thread registered in node group * @@ -600,85 +604,39 @@ void StreamNodeGroup::cancelStreamThread() * * @return: void */ -void StreamNodeGroup::quitSyncPoint() +void StreamNodeGroup::unregisterStream(int groupIdx, StreamObjStatus status) { - if (StreamThreadAmI() == true) { - StreamPair* pair = NULL; - AutoMutexLock streamLock(&m_mutex); + if (groupIdx == 0) { /* 如果是消费者流组 */ + ListCell* consumerCell = NULL; + StreamConsumer* consumerObj = NULL; - /* signal the top consumer if i am the last stream thread. */ - streamLock.lock(); - m_streamEnter++; - Assert(u_sess->stream_cxt.producer_obj != NULL); - pair = (u_sess->stream_cxt.producer_obj)->getPair(); + if (m_streamArray && m_streamArray[0].consumerList != NULL) { /* 如果消费者列表不为空 */ + if (m_streamArray[0].stopFlag != NULL) /* 如果停止标志指针不为空 */ + *(m_streamArray[0].stopFlag) = false; /* 将停止标志设置为false */ - /* pair->subThreadNum - pair->startSubThreadNum is the supposed fail to launch thread. */ - if (u_sess->stream_cxt.smp_id == 0) - m_quitWaitCond = m_quitWaitCond - 1 - (pair->expectThreadNum - pair->createThreadNum); - else - m_quitWaitCond = m_quitWaitCond - 1; /* other smp thread. */ - - Assert(m_quitWaitCond >= 0); - Assert(pair->expectThreadNum >= pair->createThreadNum); - - if (m_quitWaitCond == 0) - pthread_cond_broadcast(&m_cond); - else { - while (m_quitWaitCond != 0) - pthread_cond_wait(&m_cond, &m_mutex); - } - streamLock.unLock(); - } else if (StreamTopConsumerAmI() == true) { - /* if none thread is created ,no bother to wait on condition. */ - if (m_createThreadNum != 0) { - AutoMutexLock streamLock(&m_mutex); - streamLock.lock(); - - /* m_size - 1 - m_createNum means the failed producer thread. */ - m_quitWaitCond = m_quitWaitCond - 1 - (m_size - 1 - m_createThreadNum); - - Assert(m_quitWaitCond >= 0); - Assert(m_size >= m_createThreadNum); - - if (m_quitWaitCond == 0) - pthread_cond_broadcast(&m_cond); - else { - /* - * If inWaitingQuit is true, when we are interupted by a SIGINT signal, - * we just send SIGINT to sub-thread to stop them, and we do not elog, - * otherwise the waiting quit status of related threads will be broken. - * Notes that inWaitQuit should be set before t_thrd.int_cxt.ImmediateInterruptOK be set, - * and reset after t_thrd.int_cxt.ImmediateInterruptOK is reset, or there is an opportunity - * that we meet a signal but inWaitingQuit has not been set. - */ - u_sess->stream_cxt.in_waiting_quit = true; - - /* - * Set it to true to enable signal handling when waiting sub-thread quit, - * otherwise the cancel request will not be handled in this situation. - */ - t_thrd.int_cxt.ImmediateInterruptOK = true; - - /* - * If got a signal before t_thrd.int_cxt.ImmediateInterruptOK set to true, the signal can not be - * processed immediately util top consumer goes back to ReadCommand again. - * It maybe take long time to wait all stream threads come to quitSyncPoint - * and then it's not necessary to send signal to stream threads any more. - * Hence, signal should be handled soon here with CHECK_FOR_INTERRUPTS(). - */ - CHECK_FOR_INTERRUPTS(); - - while (m_quitWaitCond != 0) - pthread_cond_wait(&m_cond, &m_mutex); - - t_thrd.int_cxt.ImmediateInterruptOK = false; - u_sess->stream_cxt.in_waiting_quit = false; + foreach (consumerCell, m_streamArray[0].consumerList) { /* 遍历消费者列表 */ + consumerObj = (StreamConsumer*)lfirst(consumerCell); /* 获取消费者流对象 */ + if (consumerObj == NULL) { + continue; + } + consumerObj->deInit(); /* 调用消费者流对象的deInit()方法进行反初始化 */ } - streamLock.unLock(); + m_streamArray[0].consumerList = NULL; /* 将消费者列表置为空 */ } - } else - return; + } else { /* 如果是生产者流组 */ + Assert(groupIdx < m_streamNum); /* 断言组索引小于流数量 */ + if (m_streamArray && m_streamArray[groupIdx].streamObj != NULL) { + m_streamArray[groupIdx].streamObj->setThreadId(InvalidTid); /* 设置线程ID为无效值 */ + } + setNodeStatus(groupIdx, status); /* 设置节点状态 */ + } + /* 避免其他线程同时检查空和停止标志的赋值 */ + AutoMutexLock streamLock(&m_streamNodeGroupLock); /* 自动加锁 */ + streamLock.lock(); /* 上锁 */ + if (m_streamArray) /* 如果流数组不为空 */ + m_streamArray[groupIdx].stopFlag = NULL; /* 将停止标志指针置为空 */ + streamLock.unLock(); /* 解锁 */ } /* @@ -743,22 +701,22 @@ const List* StreamNodeGroup::getStreamPairList() */ void StreamNodeGroup::initStreamThread(StreamProducer* producer, uint8 smpIdentifier, StreamPair* pair) { - ThreadId producerThreadId = ApplyStreamThread(producer); - if (producerThreadId != 0) { + ThreadId producerThreadId = ApplyStreamThread(producer); /* 申请流线程 */ + if (producerThreadId != 0) { /* 如果线程ID不为0 */ #ifdef __aarch64__ - pg_memory_barrier(); + pg_memory_barrier(); /* 内存屏障 */ #endif - AutoMutexLock streamLock(&m_mutex); - streamLock.lock(); - producer->setThreadId(producerThreadId); - /* Set create thread num for sync quit. */ + AutoMutexLock streamLock(&m_mutex); /* 自动互斥锁 */ + streamLock.lock(); /* 加锁 */ + producer->setThreadId(producerThreadId); /* 设置生产者线程ID */ + /* 设置创建线程数以进行同步退出。 */ m_createThreadNum++; - /* Assume all stream threads build successfully for sync quit process. */ + /* 假设所有流线程构建成功以进行同步退出过程。 */ StreamPair* tmpPair = producer->getPair(); tmpPair->createThreadNum = tmpPair->expectThreadNum; - streamLock.unLock(); + streamLock.unLock(); /* 解锁 */ - Assert(m_createThreadNum <= m_size); + Assert(m_createThreadNum <= m_size); /* 断言创建线程数小于等于节点数 */ STREAM_LOG(DEBUG2, "startup stream thread for " "producer(%lu, %u, %u), thread id %lu, %d/%d in top consumer", @@ -769,11 +727,11 @@ void StreamNodeGroup::initStreamThread(StreamProducer* producer, uint8 smpIdenti m_createThreadNum, m_size); } else { - producer->releaseNetPort(); + producer->releaseNetPort(); /* 释放网络端口 */ int childslots = ((StreamProducer*)producer)->getChildSlot(); // release only if StreamProducer has called setChildSlot(AssignPostmasterChildSlot()) before if (childslots > 0) - ReleasePostmasterChildSlot(childslots); + ReleasePostmasterChildSlot(childslots); /* 释放子进程槽位 */ ereport(ERROR, (errcode(ERRCODE_SYSTEM_ERROR), errmsg("failed to startup stream thread, NodeName: %s, key(%lu, %u): %m", @@ -792,23 +750,23 @@ void StreamNodeGroup::initStreamThread(StreamProducer* producer, uint8 smpIdenti */ bool StreamNodeGroup::inNodeGroup(ThreadId pid1, ThreadId pid2) { - if (pid1 == pid2) + if (pid1 == pid2) /* 如果线程ID相同,返回false */ return false; - bool flag = false; + bool flag = false; /* 初始化标志为false */ - if (pid1 == m_pid || pid2 == m_pid) { - for (int i = 1; i < m_streamNum; i++) { + if (pid1 == m_pid || pid2 == m_pid) { /* 如果任何一个线程ID等于当前节点的线程ID */ + for (int i = 1; i < m_streamNum; i++) { /* 遍历流线程数组 */ if (m_streamArray[i].streamObj != NULL && - (m_streamArray[i].streamObj->getThreadId() == pid2 || + (m_streamArray[i].streamObj->getThreadId() == pid2 || /* 如果线程ID等于pid1或pid2 */ m_streamArray[i].streamObj->getThreadId() == pid1)) { - flag = true; - break; + flag = true; /* 设置标志为true */ + break; /* 跳出循环 */ } } } - return flag; + return flag; /* 返回标志 */ } /* @@ -898,24 +856,25 @@ ErrorData* StreamNodeGroup::getProducerEdata() */ void StreamNodeGroup::destroy(StreamObjStatus status) { - /* Only top consumer responsible for destroying stream node group */ + /* 只有顶层消费者负责销毁流节点组 */ if (StreamTopConsumerAmI() == false) return; - /* We must relase all pthread mutex by my thread, Or it will dead lock. But it is not a good solution. */ - // lock the same thread mutex can't be conflict in one thread. + /* 必须在我的线程中释放所有pthread互斥锁,否则会发生死锁。但这不是一个好的解决方案。 */ + // 锁定相同线程的互斥锁在一个线程中不会冲突。 + ResourceOwnerReleasePthreadMutex(); WaitState oldStatus = pgstat_report_waitstatus(STATE_STREAM_WAIT_NODEGROUP_DESTROY); /* - * If top consumer is canceled before BuildStreamFlow, streamNodeGroup is not initialized. - * At this time, we should release net port left in hash table here. + * 如果顶层消费者在构建StreamFlow之前被取消,流节点组尚未初始化。 + * 此时,我们应该在这里释放哈希表中剩余的网络端口。 */ if (STREAM_ERROR == status) StreamObj::releaseNetPortInHashTable(); - /* Destroy the stream node group. */ + /* 销毁流节点组。 */ if (u_sess->stream_cxt.global_obj != NULL) { #ifndef ENABLE_MULTIPLE_NODES if (u_sess->stream_cxt.global_obj->m_portal != NULL) { @@ -927,14 +886,14 @@ void StreamNodeGroup::destroy(StreamObjStatus status) u_sess->stream_cxt.global_obj = NULL; } - /* Destroy the global instrumentation. */ + /* 销毁全局仪表。 */ if (u_sess->instr_cxt.global_instr != NULL) { delete u_sess->instr_cxt.global_instr; u_sess->instr_cxt.thread_instr = NULL; u_sess->instr_cxt.global_instr = NULL; } - /* Release stream context. */ + /* 释放流上下文。 */ DeinitStreamContext(); pgstat_report_waitstatus(oldStatus); } @@ -947,13 +906,14 @@ void StreamNodeGroup::destroy(StreamObjStatus status) */ void StreamNodeGroup::syncQuit(StreamObjStatus status) { - /* Only stream thread or top consumer need sync quit */ + /* 只有流线程或顶层消费者需要同步退出 */ if (IS_PGXC_COORDINATOR || (StreamTopConsumerAmI() == false && StreamThreadAmI() == false) || u_sess->stream_cxt.enter_sync_point == true) return; - /* We must relase all pthread mutex by my thread, Or it will dead lock. But it is not a good solution. */ - // lock the same thread mutex can't be conflict in one thread. + /* 必须在我的线程中释放所有pthread互斥锁,否则会发生死锁。但这不是一个好的解决方案。 */ + // 锁定相同线程的互斥锁在一个线程中不会冲突。 + ResourceOwnerReleasePthreadMutex(); WaitState oldStatus = pgstat_report_waitstatus(STATE_STREAM_WAIT_THREAD_SYNC_QUIT); @@ -982,18 +942,15 @@ void StreamNodeGroup::syncQuit(StreamObjStatus status) if (StreamTopConsumerAmI()) { /* - * If an error is encountered during receiveing data, top consumer will - * come to sync point and wait all stream threads. At this time, error message - * may not be sent to Coordinator immediately(release net port and report - * error are behind sync point). If some producers need long time to finish - * sending data to other normal consumers, it will also take long time to - * report error. So we should signal all stream thread to cancel query in this - * case and then the whole query can be canceled quickly. + * 如果在接收数据过程中遇到错误,顶层消费者将进入同步点并等待所有流线程。 + * 此时,错误消息可能无法立即发送到协调器(释放网络端口和报告错误在同步点之后)。 + * 如果某些生产者需要很长时间将数据发送到其他正常消费者,它们也将需要很长时间报告错误。 + * 因此,在这种情况下,我们应该向所有流线程发送信号以取消查询,然后整个查询可以迅速取消。 */ if (STREAM_ERROR == status) StreamNodeGroup::cancelStreamThread(); - /* before top consumer enter the sync point, it must unregister to make sure the fd is closed. */ + /* 在顶层消费者进入同步点之前,它必须取消注册,以确保fd已关闭。 */ u_sess->stream_cxt.global_obj->unregisterStream(0, status); } else if (StreamThreadAmI()) { Assert(u_sess->stream_cxt.producer_obj != NULL); @@ -1002,7 +959,7 @@ void StreamNodeGroup::syncQuit(StreamObjStatus status) u_sess->stream_cxt.global_obj->quitSyncPoint(); - /* After syncpoint, all these objects may have been freed by topConsumer. */ + /* 在同步点之后,所有这些对象可能已经被顶层消费者释放。 */ if (StreamThreadAmI()) { u_sess->stream_cxt.producer_obj = NULL; } @@ -1035,28 +992,27 @@ void StreamNodeGroup::deInit(StreamObjStatus status) pg_usleep(1000); } while (saveQuit == false); - /* release the socket in case not release. */ + /* 释放套接字以防未释放。 */ foreach (cell, m_streamProducerList) { producer = (StreamProducer*)lfirst(cell); /* - * 1. For TCP, close fd of producer. - * 2. Consumer can accept connection after handling cancel signal. - * For SCTP, if top consumer get an error, close sctp stream of producer - * in case not release(producer complete execution), so that receiver - * stream can be closed by gs_receivers_flow_controller. + * 1. 对于TCP,关闭生产者的fd。 + * 2. 消费者可以在处理取消信号后接受连接。 + * 对于SCTP,如果顶层消费者遇到错误,关闭生产者的sctp流 + * 以防未释放(生产者完成执行),这样接收者的流就可以被gs_receivers_flow_controller关闭。 */ if (STREAM_ERROR == status) producer->releaseNetPort(); /* producer release socket will be enough. */ } - /* release the socket in case not release. */ + /* 释放套接字以防未释放。 */ foreach (cell, m_streamConsumerList) { consumer = (StreamConsumer*)lfirst(cell); consumer->deInit(); } - /* Free the synccontroller list */ + /* 释放同步控制器列表 */ if (m_syncControllers != NIL) { ListCell* lc = NULL; foreach (lc, m_syncControllers) { @@ -1072,13 +1028,11 @@ void StreamNodeGroup::deInit(StreamObjStatus status) m_streamRuntimeContext = NULL; /* - * 1. If length of m_streamPairList is not the same as m_size(number of stream exists in plan tree), - * it means that stream connection and stream thread initialization may not finish yet due to - * cancel signal. - * 2. m_needClean is true if some producer not ready and element in stream info hash table can not - * get removed in wakeUpConsumer, or consumer get canceled when waiting producer ready. - * In these two case, we should release net port cached in hash table instead of just releasing - * consumer in m_streamPairList. + * 1. 如果m_streamPairList的长度与m_size(计划树中存在的流的数量)不同, + * 这意味着由于取消信号,流连接和流线程初始化可能尚未完成。 + * 2. 如果某些生产者未准备好,流信息哈希表中的元素可能无法在wakeUpConsumer中被删除, + * 或者在等待生产者准备就绪时,消费者被取消。 + * 在这两种情况下,我们应该释放哈希表中缓存的网络端口,而不仅仅是释放m_streamPairList中的消费者。 */ if ((list_length(m_streamPairList) != m_size) || m_needClean) StreamObj::releaseNetPortInHashTable(); @@ -1108,7 +1062,7 @@ void StreamNodeGroup::stopAllThreadInNodeGroup(ThreadId pid, uint64 query_id) AutoMutexLock streamLock(&m_streamNodeGroupLock); streamLock.lock(); - /* Search in the stream node group. */ + /* 在流节点组中搜索。*/ element = (StreamNodeElement*)hash_search(m_streamNodeGroupTbl, &pid, HASH_FIND, &found); if (found == true) { if (query_id == element->value->GetQueryId()) { @@ -1157,7 +1111,7 @@ void StreamNodeGroup::grantStreamConnectPermission() bool found = false; AutoMutexLock streamLock(&m_streamConnectSyncLock); - HOLD_INTERRUPTS(); /* add this macro for double safety */ + HOLD_INTERRUPTS(); /* 为了双重保险,添加此宏 */ streamLock.lock(); StreamConnectSyncElement* element = (StreamConnectSyncElement*)hash_search(m_streamConnectSyncTbl, &u_sess->debug_query_id, HASH_ENTER, &found); @@ -1184,7 +1138,7 @@ void StreamNodeGroup::revokeStreamConnectPermission() { AutoMutexLock streamLock(&m_streamConnectSyncLock); - HOLD_INTERRUPTS(); /* add this macro for double safety */ + HOLD_INTERRUPTS(); /* 添加此宏以获得双重安全 */ streamLock.lock(); (void)hash_search(m_streamConnectSyncTbl, &u_sess->debug_query_id, HASH_REMOVE, NULL); streamLock.unLock(); @@ -1221,12 +1175,12 @@ bool StreamNodeGroup::checkStreamConnectPermission(uint64 query_id) void StreamNodeGroup::SyncConsumerNextPlanStep(int controller_plannodeid, int stepno) { - /* We don't have todo any-thing */ + /* 我们什么都不用做 */ if (!IS_PGXC_DATANODE) { return; } - /* We don't have todo step syncup when there is no stream operator in execution plan */ + /* 当执行计划中没有流操作符时,我们不必进行步进同步 */ if (u_sess->stream_cxt.global_obj == NULL) { return; } @@ -1234,7 +1188,7 @@ void StreamNodeGroup::SyncConsumerNextPlanStep(int controller_plannodeid, int st StreamNodeGroup* stream_nodegroup = u_sess->stream_cxt.global_obj; SyncController* controller = stream_nodegroup->GetSyncController(controller_plannodeid); - /* Report Error when the controller is not found */ + /* 找不到控制器时报告错误 */ if (controller == NULL) { ereport(ERROR, (errcode(ERRCODE_UNEXPECTED_NULL_VALUE), @@ -1293,8 +1247,7 @@ void StreamNodeGroup::SyncProducerNextPlanStep(int controller_plannodeid, int pr } /* - * Report Error when the controller for its belonging RecursiveUnion node is - * not found. + * 当其所属RecursiveUnion节点的控制器为时,报告错误未找到。 */ if (controller == NULL) { elog(ERROR, @@ -1303,11 +1256,9 @@ void StreamNodeGroup::SyncProducerNextPlanStep(int controller_plannodeid, int pr } /* - * Normally, we should do sync-up on producer side at the end of execution, however - * the corresponding consumer may encounter a "short-circuit" case where it does not - * have the opportunity to invoke ExecStream to reach the end of current thread (send - * 'Z' or 'R'), so in this case we need send 'R' forcely. to tell other datanodes we - * have done. + 通常,我们应该在执行结束时在生产者端进行同步,但是相应的消费者可能会遇到“短路”情况, + 即它没有机会调用ExecStream来到达当前线程的结束(发送'Z'或'R'),所以在这种情况下,我们需要 + 强制发送'R'来告诉其他数据节点我们已经这样做了 */ if (controller->executor_stop) { u_sess->exec_cxt.executorStopFlag = true; @@ -1338,7 +1289,7 @@ void StreamNodeGroup::SyncProducerNextPlanStep(int controller_plannodeid, int pr } } - /* Other thread failed, we need return error immediately */ + /* 其他线程失败,我们需要立即返回错误 */ if (u_sess->stream_cxt.global_obj->m_errorStop) { ereport(ERROR, (errcode(ERRCODE_RU_STOP_QUERY), errmsg("error happened during execute query"))); } @@ -1354,22 +1305,23 @@ void StreamNodeGroup::ConsumerGetSyncUpMessage( StreamPrepareRequestForRecursive(node, true); if (msg_type == RUSYNC_MSGTYPE_NODE_FINISH) { - /* For message 'R', we only recieve it from worker-node */ + /* 对于消息'R',我们只从worker-node接收它 */ ConsumerNodeSyncUpMessage(controller, step, node); } else { - Assert(false); + Assert(false); // 无效的消息类型,不应该发生 } return; } + void StreamNodeGroup::ConsumerMarkRUStepStatus(RecursiveUnionController* controller, int step, int iteration, StreamState* node, int pgxc_nodeid, int producer_plannodeid, int tuple_processed) { switch (step) { case WITH_RECURSIVE_SYNC_NONERQ: { if (pgxc_nodeid == u_sess->pgxc_cxt.PGXCNodeId) { - /* Receive message from self DN, we need verify it has been updated */ + /* 从自身DN接收消息,我们需要验证它是否已更新 */ if (controller->none_recursive_tuples[pgxc_nodeid] == -1) { elog(ERROR, "MPP with-recursive step1 (C) is not set on datanode %s", @@ -1381,7 +1333,7 @@ void StreamNodeGroup::ConsumerMarkRUStepStatus(RecursiveUnionController* control } break; case WITH_RECURSIVE_SYNC_RQSTEP: { if (pgxc_nodeid == u_sess->pgxc_cxt.PGXCNodeId) { - /* Receive message from self DN, we need verify it has been updated */ + /* 从自身DN接收消息,我们需要验证它是否已更新 */ if (controller->recursive_tuples[pgxc_nodeid] == -1) { elog(ERROR, "MPP with-recursive step2 (C) is not set on datanode %s", @@ -1389,7 +1341,7 @@ void StreamNodeGroup::ConsumerMarkRUStepStatus(RecursiveUnionController* control } } - /* Coordinator's step2.iteration is updated by itself, not from message */ + /* 协调器的第2步迭代是由自身更新的,而不是从消息中获取的 */ controller->recursive_tuples[pgxc_nodeid] = tuple_processed; } break; case WITH_RECURSIVE_SYNC_DONE: { @@ -1428,8 +1380,8 @@ void StreamNodeGroup::ProducerFinishRUIteration(int step) elog(DEBUG1, "MPP with-recursive step%d send 'Z' to consumer start >>> %s", step, producer_top_plannode_str); /* - * prepare an end message to all the consumer backend thread. - * if is dummy, do not bother send + * 为所有消费者后端线程准备一个结束消息。 + * 如果是虚拟的,不要麻烦发送 */ if (producer->isDummy() == false) { for (i = 0; i < consumer_number; i++) { @@ -1446,7 +1398,7 @@ void StreamNodeGroup::ProducerFinishRUIteration(int step) pq_endmessage(&buf); } else if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 2) pq_putemptymessage('Z'); - /* Flush output at end of cycle in any case. */ + /* 在任何情况下都在循环结束时刷新输出。 */ res = pq_flush(); if (res == EOF) { transport[i]->release(); @@ -1456,14 +1408,15 @@ void StreamNodeGroup::ProducerFinishRUIteration(int step) } } - /* Send a final signal to consumer for local stream, and producer can be triggered next one run. */ + /* 为本地流发送最终信号给消费者,并且生产者可以在下一次运行时触发。 */ producer->finalizeLocalStream(); - elog(DEBUG1, "MPP with-recursive step%d send 'Z' to consumer done <<< %s", step, producer_top_plannode_str); + elog(DEBUG1, "MPP with-recursive step%d send 'Z' to consumer done <<< %s", step, producer_top_plannode_str);(DEBUG1, "MPP with-recursive 第%d步 发送 'Z' 到消费者完成 <<< %s", step, producer_top_plannode_str); return; } + /* * Function ProducerSendSyncMessage() * @@ -1486,34 +1439,26 @@ void StreamNodeGroup::ProducerSendSyncMessage( StreamTransport** transport = producer->getTransport(); int iteration = 0; - /* Only syncup producer thread need send message */ if (!IsSyncUpProducerThread()) { return; } - /* Only Sync-Producer need send sync-up message to control-node */ if (!StreamNodeGroup::IsRUSyncProducer()) { return; } - /* - * Specify the iteration step in 'F' & 'R' to let the consumer side todo step - * verification. - */ if (step == WITH_RECURSIVE_SYNC_DONE) { iteration = u_sess->attr.attr_sql.max_recursive_times; } else { iteration = controller->iteration; } - /* Producer send sync-up message to each DN's consumer */ if (producer->isDummy() == false) { for (int connIdx = 0; connIdx < consumer_number; connIdx++) { if (producer->netSwitchDest(connIdx)) { StringInfoData buf; initStringInfo(&buf); - /* Prepare 'R' message */ pq_beginmessage(&buf, msg_type); appendStringInfo(&buf, "nodename:%s,", g_instance.attr.attr_common.PGXCNodeName); appendStringInfo(&buf, "rustep:%d,", step); @@ -1529,12 +1474,11 @@ void StreamNodeGroup::ProducerSendSyncMessage( if (msg_type == RUSYNC_MSGTYPE_NODE_FINISH) { iter = controller->iteration; } else { - /* Can not get here */ Assert(false); } elog(DEBUG1, - "MPP with-recursive step%d (P) send step-sync message '%c' iteration[%d] to %s(nodeid:%u) %s", + "MPP with-recursive 第%d步 (P) 发送步骤同步消息 '%c' 迭代[%d] 到 %s(nodeid:%u) %s", step, msg_type, iter, @@ -1542,7 +1486,6 @@ void StreamNodeGroup::ProducerSendSyncMessage( transport[connIdx]->m_nodeoid, producer_top_plannode_str); - /* Flush output at end of cycle in any case. */ int res = pq_flush(); if (res == EOF) { transport[connIdx]->release(); @@ -1568,12 +1511,12 @@ void StreamNodeGroup::ProducerSendSyncMessage( */ bool StreamNodeGroup::IsRecursiveUnionDone(RecursiveUnionState* state) { - /* If no sync-up step required, just return TRUE */ + /* 如果不需要同步步骤,只返回TRUE*/ if (state != NULL && !NeedSyncUpRecursiveUnionStep((Plan*)state->ps.plan)) { return true; } - /* We don't have todo step syncup when there is no stream operator in execution plan */ + /* 当执行计划中没有流操作符时,我们不必进行步进同步 */ if (u_sess->stream_cxt.global_obj == NULL) { return true; } @@ -1635,10 +1578,10 @@ SyncController* StreamNodeGroup::GetSyncController(int controller_plannodeid) } } - /* Other thread failed, we need return error immediately */ + /* 其他线程发生错误,需要立即返回错误信息 */ if (u_sess->stream_cxt.global_obj->m_errorStop) { streamLock.unLock(); - ereport(ERROR, (errcode(ERRCODE_RU_STOP_QUERY), errmsg("error happened during execute query"))); + ereport(ERROR, (errcode(ERRCODE_RU_STOP_QUERY), errmsg("执行查询时发生错误"))); } } streamLock.unLock(); @@ -1714,9 +1657,10 @@ static void ConsumerNodeSyncUpMessage(RecursiveUnionController* controller, int connCount = node->conn_count; connections = node->connections; - /* Handle data from connections until the number of conections without return data becomes 0 */ + /* 处理连接的数据,直到无返回数据的连接数为0 */ while (connCount) { if (node->need_fresh_data) { + /* 从逻辑连接中读取响应 'R',直到没有新数据为止 */ if (datanode_receive_from_logic_conn(node->conn_count, connections, &node->netctl, -1)) { int error_code = getStreamSocketError(gs_comm_strerror()); ereport(ERROR, @@ -1731,15 +1675,15 @@ static void ConsumerNodeSyncUpMessage(RecursiveUnionController* controller, int } for (connIdx = 0; connIdx < node->conn_count; connIdx++) { - /* conection has return data */ + /* 连接有返回数据 */ if (connections[connIdx]->state == DN_CONNECTION_STATE_IDLE) { continue; } - /* Handle data from connections */ + /* 处理来自连接的数据 */ int res = HandleStreamResponse(connections[connIdx], node); if (res == RESPONSE_RECURSIVE_SYNC_R) { - /* receive 'R' message, change conection's state, number of conections without return data -1 */ + /* 接收到 'R' 消息,修改连接状态,无返回数据的连接数减1 */ connections[connIdx]->state = DN_CONNECTION_STATE_IDLE; connCount--; elog(DEBUG1, @@ -1747,7 +1691,7 @@ static void ConsumerNodeSyncUpMessage(RecursiveUnionController* controller, int step, connections[connIdx]->nodeIdx); } else if (res == RESPONSE_COMPLETE) { - /* receive unused 'Z' message, in case the unused message blocking connections */ + /* 接收到未使用的 'Z' 消息,以防止未使用的消息阻塞连接 */ connections[connIdx]->state = DN_CONNECTION_STATE_QUERY; ereport(DEBUG1, (errcode(ERRCODE_WRONG_OBJECT_TYPE), @@ -1776,15 +1720,17 @@ void StreamNodeGroup::ConsumerNodeStreamMessage(StreamState* node) int connCount = 0; PGXCNodeHandle** connections = NULL; + /* 如果节点尚未准备好,则准备递归请求 */ if (unlikely(node->isReady == false)) StreamPrepareRequestForRecursive(node, true); connCount = node->conn_count; connections = node->connections; - /* Handle data from connections until the number of conections without return data becomes 0 */ + /* 处理连接的数据,直到没有返回数据的连接数为0为止 */ while (connCount) { if (node->need_fresh_data) { + /* 从逻辑连接中读取响应 'Z',直到没有新数据为止 */ if (datanode_receive_from_logic_conn(node->conn_count, connections, &node->netctl, -1)) { int error_code = getStreamSocketError(gs_comm_strerror()); ereport(ERROR, @@ -1798,19 +1744,19 @@ void StreamNodeGroup::ConsumerNodeStreamMessage(StreamState* node) } for (connIdx = 0; connIdx < node->conn_count; connIdx++) { - /* conection has return data */ + /* 连接有返回数据 */ if (connections[connIdx]->state == DN_CONNECTION_STATE_IDLE) { continue; } - /* Handle data from connections */ + /* 处理来自连接的数据 */ int res = HandleStreamResponse(connections[connIdx], node); if (res == RESPONSE_COMPLETE) { - /* receive 'Z' message, conection's state has changed, number of conections without return data -1 */ + /* 接收到 'Z' 消息,连接的状态已经改变,没有返回数据的连接数减1 */ connCount--; elog(DEBUG1, "MPP with-recursive(C) receive message 'Z' from node:%d", connections[connIdx]->nodeIdx); } else if (res == RESPONSE_RECURSIVE_SYNC_R) { - /* receive unexpect 'R' message */ + /* 接收到意外的 'R' 消息 */ elog(ERROR, "MPP with-recursive(C) receive message 'R' from stream node:%d", connections[connIdx]->nodeIdx); @@ -1821,10 +1767,10 @@ void StreamNodeGroup::ConsumerNodeStreamMessage(StreamState* node) } } -/* Mark recursive vfd is invalid before aborting transaction. */ +/* 在终止事务之前标记递归vfd无效 */ void StreamNodeGroup::MarkRecursiveVfdInvalid() { - /* Only stream thread or top consumer need invalidate recursive vfd */ + /* 只有流线程或者顶层消费者需要使递归vfd无效 */ if (IS_PGXC_COORDINATOR || (StreamTopConsumerAmI() == false && StreamThreadAmI() == false)) { return; } @@ -1841,11 +1787,11 @@ void StreamNodeGroup::MarkRecursiveVfdInvalid() #ifndef ENABLE_MULTIPLE_NODES bool InitStreamObject(PlannedStmt* planStmt) { - /* if plan contains stream node, shoule initial some object */ + /* 如果计划中包含流节点,则应该初始化一些对象 */ if (planStmt->num_streams > 0 && !StreamTopConsumerAmI() && !StreamThreadAmI()) { - /* Set top consumer at the very beginning. */ + /* 在最开始时设置顶层消费者 */ StreamTopConsumerIam(); - /* Build stream context for stream plan. */ + /* 为流计划构建流上下文 */ InitStreamContext(); u_sess->exec_cxt.under_stream_runtime = true; @@ -1858,4 +1804,4 @@ void StreamMarkStop() { u_sess->exec_cxt.executorStopFlag = true; } -#endif +#endif \ No newline at end of file -- 2.34.1 From da3888eed68f367874c8f82670bb952c7b164857 Mon Sep 17 00:00:00 2001 From: Nemoo <18210033860@163.com> Date: Sat, 30 Sep 2023 21:07:40 +0800 Subject: [PATCH 44/50] Update streamMain.cpp --- src/gausskernel/process/stream/streamMain.cpp | 345 ++++++++++-------- 1 file changed, 194 insertions(+), 151 deletions(-) diff --git a/src/gausskernel/process/stream/streamMain.cpp b/src/gausskernel/process/stream/streamMain.cpp index ef4421a5e..cd2890d20 100755 --- a/src/gausskernel/process/stream/streamMain.cpp +++ b/src/gausskernel/process/stream/streamMain.cpp @@ -50,17 +50,29 @@ #include "utils/timestamp.h" #include "instruments/instr_handle_mgr.h" +// 声明CodeGenThreadInitialize函数,用于初始化代码生成线程 extern void CodeGenThreadInitialize(); +// 声明InitRecursiveCTEGlobalVariables函数,用于初始化递归CTE的全局变量 extern void InitRecursiveCTEGlobalVariables(const PlannedStmt* planstmt); +// 下面的函数都是当前文件内的静态函数,用于初始化流执行计划的各个组件和处理信号 +// 初始化流执行计划的线程 static void InitStreamThread(); +// 初始化流执行计划的路径 static void InitStreamPath(); +// 初始化流执行计划的信号处理 static void InitStreamSignal(); +// 初始化流执行计划的资源 static void InitStreamResource(); +// 处理流执行计划的信号跳转 static void HandleStreamSigjmp(); +// 执行流执行计划的主函数,用于生成和发送流数据 static void execute_stream_plan(StreamProducer* producer); +// 流执行计划结束时的处理函数 static void execute_stream_end(StreamProducer* producer); +// 退出并清理流执行计划的函数 static void StreamQuitAndClean(int code, Datum arg); +// 重置流执行计划的工作线程信息 static void ResetStreamWorkerInfo(); /* ---------------------------------------------------------------- @@ -68,32 +80,38 @@ static void ResetStreamWorkerInfo(); * stream thread main entrance * ---------------------------------------------------------------- */ +// StreamMain函数是流执行计划的主函数,用于处理流数据生成和发送 + int StreamMain() { sigjmp_buf local_sigjmp_buf; + // 初始化流执行计划的线程 InitStreamThread(); + // 设置处理模式为NormalProcessing SetProcessingMode(NormalProcessing); + // 在进程退出时调用StreamQuitAndClean函数进行清理工作 on_proc_exit(StreamQuitAndClean, 0); /* - * process any libraries that should be preloaded at backend start (this - * likewise can't be done until GUC settings are complete) + * 处理在后端启动时预加载的任何库 + * (这同样不能在GUC设置完成之前执行) */ process_local_preload_libraries(); int curTryCounter; int* oldTryCounter = NULL; if (sigsetjmp(local_sigjmp_buf, 1) != 0) { - /* reset signal block flag for threadpool worker */ + // 重置信号块标志,以便线程池工作线程可以继续处理信号 ResetInterruptCxt(); if (g_threadPoolControler) { g_threadPoolControler->GetSessionCtrl()->releaseLockIfNecessary(); } gstrace_tryblock_exit(true, oldTryCounter); + // 处理信号跳转 HandleStreamSigjmp(); if (IS_THREAD_POOL_STREAM) { t_thrd.threadpool_cxt.stream->CleanUp(); @@ -103,23 +121,27 @@ int StreamMain() } oldTryCounter = gstrace_tryblock_entry(&curTryCounter); - + + // 切换内存上下文为执行器的内存上下文 MemoryContext oldMemory = MemoryContextSwitchTo( THREAD_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_EXECUTOR)); #ifdef ENABLE_LLVM_COMPILE + // 初始化LLVM代码生成线程 CodeGenThreadInitialize(); #endif (void)MemoryContextSwitchTo(oldMemory); - - /* We can now handle ereport(ERROR) */ + + /* 现在我们可以处理ereport(ERROR) */ t_thrd.log_cxt.PG_exception_stack = &local_sigjmp_buf; if (IS_THREAD_POOL_STREAM) { + // 重置流工作线程的信息 ResetStreamWorkerInfo(); } while (true) { if (IS_THREAD_POOL_STREAM) { + // 汇报当前状态为STATE_IDLE,并等待任务的到来 pgstat_report_activity(STATE_IDLE, NULL); pgstat_report_waitstatus(STATE_WAIT_COMM); t_thrd.threadpool_cxt.stream->WaitMission(); @@ -127,25 +149,28 @@ int StreamMain() pgstat_report_waitstatus(STATE_WAIT_UNDEFINED); } + // 汇报调试查询ID等信息 pgstat_report_queryid(u_sess->debug_query_id); pgstat_report_unique_sql_id(false); pgstat_report_global_session_id(u_sess->globalSessionId); pgstat_report_smpid(u_sess->stream_cxt.smp_id); timeInfoRecordStart(); - /* Wait thread ID ready */ + /* 等待线程ID准备就绪 */ u_sess->stream_cxt.producer_obj->waitThreadIdReady(); + // 执行流计划 execute_stream_plan(u_sess->stream_cxt.producer_obj); + // 流执行计划结束后的处理 execute_stream_end(u_sess->stream_cxt.producer_obj); WLMReleaseNodeFromHash(); WLMReleaseIoInfoFromHash(); - /* Reset here so that we can get debug_query_string when Stream thread is in Sync point */ + /* 在这里重置,以便在流线程处于同步点时可以获取到debug_query_string */ t_thrd.postgres_cxt.debug_query_string = NULL; /* - * Note that parent thread will do commit or abort transaction. - * Stream thread should not change clog file + * 请注意,父线程将进行提交或回滚事务。 + * 流线程不应该改变clog文件 */ ResetTransactionInfo(); @@ -156,50 +181,64 @@ int StreamMain() } } + // 关闭GTM连接 CloseGTM(); return 0; } +// InitStreamThread函数用于初始化流执行计划线程 + static void InitStreamThread() { + // 初始化随机数生成器 initRandomState(0, GetCurrentTimestamp()); + // 设置流执行计划线程的进程ID和运行标志 t_thrd.proc_cxt.MyProcPid = gs_thread_self(); u_sess->exec_cxt.under_stream_runtime = true; t_thrd.codegen_cxt.g_runningInFmgr = false; + // 设置前端协议版本和远程连接类型 FrontendProtocol = PG_PROTOCOL_LATEST; u_sess->attr.attr_common.remoteConnType = REMOTE_CONN_DATANODE; + // 初始化流执行计划的路径 InitStreamPath(); + // 初始化流执行计划的信号 InitStreamSignal(); - /* Early initialization */ + /* 早期初始化 */ BaseInit(); - /* We need to allow SIGINT, etc during the initial transaction */ + /* 在初始事务期间,我们需要允许SIGINT等信号 */ gs_signal_setmask(&t_thrd.libpq_cxt.UnBlockSig, NULL); - /* Initialize the memory tracking information */ + /* 初始化内存跟踪信息 */ MemoryTrackingInit(); if (!IS_THREAD_POOL_STREAM) { + // 提取生产者信息 ExtractProduerInfo(); + // 设置数据库和用户信息 t_thrd.proc_cxt.PostInit->SetDatabaseAndUser( u_sess->stream_cxt.producer_obj->getDbName(), InvalidOid, u_sess->stream_cxt.producer_obj->getUserName()); + // 修复GUC变量 repair_guc_variables(); } + // 初始化流执行计划的资源 t_thrd.proc_cxt.PostInit->InitStreamWorker(); + // 初始化向量函数映射表 InitVecFuncMap(); + // 初始化流执行计划的资源 InitStreamResource(); } static void InitStreamPath() { - /* Compute paths, if we didn't inherit them from postmaster */ + /* 计算路径,如果我们没有从postmaster继承它们的话 */ if (my_exec_path[0] == '\0') { if (find_my_exec("postgres", my_exec_path) < 0) ereport(FATAL, (errmsg("openGauss: could not locate my own executable path"))); @@ -213,23 +252,23 @@ static void InitStreamSignal() { (void)gspqsignal(SIGINT, StatementCancelHandler); (void)gspqsignal(SIGTERM, die); - (void)gspqsignal(SIGALRM, handle_sig_alarm); /* timeout conditions */ + (void)gspqsignal(SIGALRM, handle_sig_alarm); /* 超时条件 */ (void)gspqsignal(SIGUSR1, procsignal_sigusr1_handler); (void)gs_signal_unblock_sigusr2(); if (IsUnderPostmaster) { - /* We allow SIGQUIT (quickdie) at all times */ + /* 我们在任何时候都允许SIGQUIT (quickdie) */ (void)sigdelset(&t_thrd.libpq_cxt.BlockSig, SIGQUIT); } } +// InitStreamResource函数用于初始化流执行计划线程的资源 + static void InitStreamResource() { /* - * Create the memory context we will use in the main loop. - * - * t_thrd.mem_cxt.msg_mem_cxt is reset once per iteration of the main loop, ie, upon - * completion of processing of each command message from the client. + * 创建主循环中将要使用的内存上下文。 + * t_thrd.mem_cxt.msg_mem_cxt在主循环的每次迭代中重置,即在完成处理客户端的每个命令消息后。 */ t_thrd.mem_cxt.msg_mem_cxt = AllocSetContextCreate(t_thrd.top_mem_cxt, "MessageContext", @@ -253,11 +292,13 @@ static void InitStreamResource() } } +// ExtractProduerInfo函数用于提取生产者的信息 void ExtractProduerInfo() { if (u_sess->stream_cxt.producer_obj == NULL) { return; } + // 提取WLM参数、查询ID、跟踪标志等信息 u_sess->wlm_cxt->wlm_params = u_sess->stream_cxt.producer_obj->getWlmParams(); u_sess->instr_cxt.gs_query_id->procId = u_sess->stream_cxt.producer_obj->getExplainThreadid(); u_sess->exec_cxt.need_track_resource = u_sess->stream_cxt.producer_obj->getExplainTrack(); @@ -266,16 +307,17 @@ void ExtractProduerInfo() u_sess->stream_cxt.producer_obj->getGlobalSessionId(&u_sess->globalSessionId); WLMGeneralParam *g_wlm_params = &u_sess->wlm_cxt->wlm_params; + // 设置控制组信息 errno_t ret = sprintf_s(u_sess->wlm_cxt->control_group, sizeof(u_sess->wlm_cxt->control_group), "%s", g_wlm_params->cgroup); securec_check_ss(ret, "\0", "\0"); - /* Get the node group information */ + /* 获取节点组信息 */ t_thrd.wlm_cxt.thread_node_group = WLMGetNodeGroupFromHTAB(g_wlm_params->ngroup); t_thrd.wlm_cxt.thread_climgr = &t_thrd.wlm_cxt.thread_node_group->climgr; t_thrd.wlm_cxt.thread_srvmgr = &t_thrd.wlm_cxt.thread_node_group->srvmgr; - /* Set the right pgxcnodeid */ + /* 设置正确的pgxcnodeid */ u_sess->pgxc_cxt.PGXCNodeId = u_sess->stream_cxt.producer_obj->getPgxcNodeId(); u_sess->instr_cxt.global_instr = u_sess->stream_cxt.producer_obj->getStreamInstrumentation(); u_sess->proc_cxt.MyProcPort->database_name = u_sess->stream_cxt.producer_obj->getDbName(); @@ -302,14 +344,14 @@ void ExtractProduerInfo() u_sess->stream_cxt.dummy_thread = false; } - /* stream workers share the same session memory entry as their parents */ + /* 流式工作者共享与其父进程相同的会话内存条目 */ t_thrd.shemem_ptr_cxt.mySessionMemoryEntry = u_sess->stream_cxt.producer_obj->getSessionMemory(); if (t_thrd.proc != NULL) { t_thrd.proc->sessMemorySessionid = u_sess->stream_cxt.producer_obj->getParentSessionid(); Assert(t_thrd.proc->sessMemorySessionid == t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->sessionid); } - /* Initialize the global variables for recursive */ + /* 初始化递归的全局变量 */ InitRecursiveCTEGlobalVariables(u_sess->stream_cxt.producer_obj->getPlan()); STREAM_LOG(DEBUG2, "enter StreamMain, StreamKey(%lu, %u, %u)", @@ -318,34 +360,36 @@ void ExtractProduerInfo() u_sess->stream_cxt.producer_obj->getKey().smpIdentifier); } +// HandleStreamSigjmp函数用于处理流执行计划线程的信号跳转 + static void HandleStreamSigjmp() { + // 报告等待状态为STATE_WAIT_UNDEFINED pgstat_report_waitstatus(STATE_WAIT_UNDEFINED); t_thrd.pgxc_cxt.GlobalNetInstr = NULL; - /* output the memory tracking information when error happened */ + /* 当发生错误时,输出内存追踪信息 */ MemoryTrackingOutputFile(); - /* Since not using PG_TRY, must reset error stack by hand */ + /* 由于没有使用PG_TRY,必须手动重置错误堆栈 */ t_thrd.log_cxt.error_context_stack = NULL; t_thrd.log_cxt.call_stack = NULL; - /* reset buffer strategy flag */ + /* 重置缓冲策略标志 */ t_thrd.storage_cxt.is_btree_split = false; - /* Prevent interrupts while cleaning up */ + /* 在清理时阻止中断 */ HOLD_INTERRUPTS(); /* - * Turn off these interrupts too. This is only needed here and not in - * other exception-catching places since these interrupts are only - * enabled while we wait for client input. + * 也关闭这些中断。这仅在此处需要,而不是在其他捕获异常的地方, + * 因为这些中断只有在等待客户端输入时才启用。 */ t_thrd.postgres_cxt.DoingCommandRead = false; /* - * Abort the current transaction in order to recover. + * 为了恢复,中止当前事务。 */ ereport(DEBUG1, (errmsg("stream thread %lu end transaction " XID_FMT " abnormally", @@ -353,20 +397,20 @@ static void HandleStreamSigjmp() GetCurrentTransactionIdIfAny()))); /* - * when clearing the BCM encounter ERROR, we should ResetBCMArray, or it - * will enter ClearBCMArray infinite loop, then coredump. + * 在清除BCM时遇到ERROR时,我们应该ResetBCMArray, + * 否则它将进入ClearBCMArray无限循环,然后core dump。 */ ResetBCMArray(); - /* release operator-level hash table in memory */ + /* 在内存中释放操作符级别的哈希表 */ releaseExplainTable(); - /* Mark recursive vfd is invalid before aborting transaction. */ + /* 在中止事务之前标记递归vfd为无效 */ StreamNodeGroup::MarkRecursiveVfdInvalid(); AbortCurrentTransaction(); - /* release resource held by lsc */ + /* 释放lsc持有的资源 */ AtEOXact_SysDBCache(false); LWLockReleaseAll(); @@ -384,66 +428,62 @@ static void HandleStreamSigjmp() RESUME_INTERRUPTS(); timeInfoRecordEnd(); + // 调用StreamNodeGroup的syncQuit函数,传入STREAM_ERROR参数,表示异常退出 StreamNodeGroup::syncQuit(STREAM_ERROR); } +// execute_stream_plan函数用于执行流执行计划 + static void execute_stream_plan(StreamProducer* producer) { /* - * Start up a transaction command. All queries generated by the - * query_string will be in this same command block, *unless* we find a - * BEGIN/COMMIT/ABORT statement; we have to force a new xact command after - * one of those, else bad things will happen in xact.c. (Note that this - * will normally change current memory context.) + * 启动一个事务命令。由query_string生成的所有查询都将在相同的命令块中,*除非*我们找到了 + * 一个BEGIN/COMMIT/ABORT语句;在这种情况下,我们必须在其后强制新的xact命令,否则在xact.c中会出现问题。 + * (请注意,这通常会改变当前内存上下文。) */ start_xact_command(); - producer->setUpStreamTxnEnvironment(); + producer->setUpStreamTxnEnvironment(); // 设置流事务环境 - PlannedStmt* planstmt = producer->getPlan(); - CommandDest dest = producer->getDest(); - bool save_log_statement_stats = u_sess->attr.attr_common.log_statement_stats; + PlannedStmt* planstmt = producer->getPlan(); // 获取计划语句 + CommandDest dest = producer->getDest(); // 获取命令目标 + bool save_log_statement_stats = u_sess->attr.attr_common.log_statement_stats; // 保存log_statement_stats设置 bool isTopLevel = false; - const char* commandTag = NULL; + const char* commandTag = NULL; // 命令标签 char completionTag[COMPLETION_TAG_BUFSIZE]; Portal portal = NULL; DestReceiver* receiver = NULL; int16 format; char msec_str[PRINTF_DST_MAX]; - t_thrd.postgres_cxt.debug_query_string = planstmt->query_string; - pgstat_report_activity(STATE_RUNNING, t_thrd.postgres_cxt.debug_query_string); - /* Use planNodeId as thread_level, same as the key which SCTP use for send/receive */ + t_thrd.postgres_cxt.debug_query_string = planstmt->query_string; // 设置调试查询字符串 + pgstat_report_activity(STATE_RUNNING, t_thrd.postgres_cxt.debug_query_string); // 报告活动状态 + /* 使用planNodeId作为thread_level,与SCTP用于发送/接收的键相同 */ pgstat_report_parent_sessionid(producer->getParentSessionid(), producer->getKey().planNodeId); if (u_sess->instr_cxt.global_instr && - u_sess->instr_cxt.perf_monitor_enable) // Don't use perf util you set has_use_perf = true - CPUMon::Initialize(CMON_GENERAL); + u_sess->instr_cxt.perf_monitor_enable) // 仅当has_use_perf = true时,使用perf监控,避免使用perf util + CPUMon::Initialize(CMON_GENERAL); // 初始化CPU监控 /* - * We use save_log_statement_stats so ShowUsage doesn't report incorrect - * results because ResetUsage wasn't called. + * 我们使用save_log_statement_stats以便ShowUsage不会因为ResetUsage未被调用而报告不正确的结果。 */ if (save_log_statement_stats) ResetUsage(); isTopLevel = true; - // For now plan shipping is used only for SELECTs, in future - // we should remove this hard coding and get the tag automatically + // 目前,计划运送仅用于SELECT语句,将来我们应该去掉这个硬编码,自动获取标签 commandTag = "SELECT"; set_ps_display(commandTag, false); - BeginCommand(commandTag, dest); + BeginCommand(commandTag, dest); // 开始命令 /* - * If we are in an aborted transaction, reject all commands except - * COMMIT/ABORT. It is important that this test occur before we try - * to do parse analysis, rewrite, or planning, since all those phases - * try to do database accesses, which may fail in abort state. (It - * might be safe to allow some additional utility commands in this - * state, but not many...) + * 如果我们处于一个中止的事务块状态,拒绝除了COMMIT/ABORT之外的所有命令。 + * 这个测试在我们尝试进行解析分析、重写或计划之前发生,因为所有这些阶段都尝试进行数据库访问, + * 而在中止状态下可能会失败。(这可能安全地允许在这种状态下执行一些其他实用程序命令,但不是太多...) */ if (IsAbortedTransactionBlockState()) // && ereport(ERROR, @@ -453,51 +493,50 @@ static void execute_stream_plan(StreamProducer* producer) u_sess->proc_cxt.firstChar), errdetail_abort())); - /* Make sure we are in a transaction command */ + /* 确保我们在一个事务命令中 */ start_xact_command(); - /* If we got a cancel signal in parsing or prior command, quit */ + /* 如果在解析或之前的命令中收到取消信号,则退出 */ CHECK_FOR_INTERRUPTS(); /* - * Create unnamed portal to run the query or queries in. If there - * already is one, silently drop it. + * 创建一个无名portal来运行查询或查询。如果已经有一个,就默默地放弃它。 */ portal = CreatePortal("", true, true); - /* Don't display the portal in pg_cursors */ + /* 在pg_cursors中不显示portal */ portal->visible = false; - u_sess->instr_cxt.global_instr = producer->getStreamInstrumentation(); - u_sess->instr_cxt.obs_instr = producer->getOBSInstrumentation(); + u_sess->instr_cxt.global_instr = producer->getStreamInstrumentation(); // 获取流仪器信息 + u_sess->instr_cxt.obs_instr = producer->getOBSInstrumentation(); // 获取OBS仪器信息 if (u_sess->instr_cxt.obs_instr) u_sess->instr_cxt.p_OBS_instr_valid = u_sess->instr_cxt.obs_instr->m_p_globalOBSInstrument_valid; PortalDefineQuery(portal, NULL, "DUMMY", commandTag, lappend(NULL, planstmt), NULL); /* - * Start the portal. No parameters here. + * 启动portal。这里没有参数。 */ PortalStart(portal, producer->getParams(), 0, producer->getSnapShot()); format = 0; PortalSetResultFormat(portal, 1, &format); - receiver = CreateDestReceiver(dest); - if (dest >= DestTupleBroadCast) + receiver = CreateDestReceiver(dest); // 创建目标接收器 + if (dest >= DestTupleBroadCast) // 如果目标是广播,设置流接收器参数 SetStreamReceiverParams(receiver, producer, portal); /* - * Run the portal to completion, and then drop it (and the receiver). + * 运行portal到完成,并且然后丢弃它(以及接收器)。 */ (void)PortalRun(portal, FETCH_ALL, isTopLevel, receiver, receiver, completionTag); - (*receiver->rDestroy)(receiver); + (*receiver->rDestroy)(receiver); // 销毁接收器 - PortalDrop(portal, false); + PortalDrop(portal, false); // 丢弃portal - finish_xact_command(); + finish_xact_command(); // 完成事务命令 /* - * Emit duration logging if appropriate. + * 如果合适,记录持续时间日志。 */ switch (check_log_duration(msec_str, false)) { case 1: @@ -518,17 +557,19 @@ static void execute_stream_plan(StreamProducer* producer) ShowUsage("QUERY STATISTICS"); } +// execute_stream_end函数用于结束流执行计划 + static void execute_stream_end(StreamProducer* producer) { int consumer_number; int i, res; - consumer_number = producer->getConnNum(); - StreamTransport** transport = producer->getTransport(); + consumer_number = producer->getConnNum(); // 获取连接数 + StreamTransport** transport = producer->getTransport(); // 获取传输对象 - // prepare an end message to all the consumer backend thread. + // 准备一个结束消息发送到所有的消费者后端线程。 // - // if is dummy, do not bother send + // 如果是虚拟的,就不需要发送了 if (producer->isDummy() == false) { for (i = 0; i < consumer_number; i++) { if (producer->netSwitchDest(i)) { @@ -540,7 +581,7 @@ static void execute_stream_end(StreamProducer* producer) pq_endmessage(&buf); } else if (PG_PROTOCOL_MAJOR(FrontendProtocol) >= 2) pq_putemptymessage('Z'); - /* Flush output at end of cycle in any case. */ + /* 在任何情况下,在周期结束时刷新输出。 */ res = pq_flush(); if (res == EOF) { transport[i]->release(); @@ -549,10 +590,10 @@ static void execute_stream_end(StreamProducer* producer) } } } - producer->finalizeLocalStream(); - timeInfoRecordEnd(); - StreamNodeGroup::syncQuit(STREAM_COMPLETE); - ForgetRegisterStreamSnapshots(); + producer->finalizeLocalStream(); // 完成本地流处理 + timeInfoRecordEnd(); // 记录时间信息结束 + StreamNodeGroup::syncQuit(STREAM_COMPLETE); // 同步退出 + ForgetRegisterStreamSnapshots(); // 忘记注册流快照 } /* @@ -560,66 +601,67 @@ static void execute_stream_end(StreamProducer* producer) */ static void StreamQuitAndClean(int code, Datum arg) { - /* Close connection with GTM, if active */ + /* 关闭与GTM的连接,如果激活 */ CloseGTM(); - /* Free remote xact state */ + /* 免费远程精确状态 */ free_RemoteXactState(); } -// reset some flag related to stream +// 重置一些与流相关的标志 void ResetStreamEnv() { - t_thrd.subrole = NO_SUBROLE; - u_sess->stream_cxt.dummy_thread = false; - u_sess->exec_cxt.executorStopFlag = false; - u_sess->stream_cxt.global_obj = NULL; - u_sess->stream_cxt.producer_obj = NULL; - u_sess->instr_cxt.global_instr = NULL; - u_sess->instr_cxt.thread_instr = NULL; - u_sess->exec_cxt.under_stream_runtime = false; - u_sess->stream_cxt.in_waiting_quit = false; - u_sess->stream_cxt.enter_sync_point = false; - t_thrd.pgxc_cxt.GlobalNetInstr = NULL; + t_thrd.subrole = NO_SUBROLE; // 重置子角色 + u_sess->stream_cxt.dummy_thread = false; // 虚拟线程标志重置为false + u_sess->exec_cxt.executorStopFlag = false; // 执行器停止标志重置为false + u_sess->stream_cxt.global_obj = NULL; // 全局对象重置为NULL + u_sess->stream_cxt.producer_obj = NULL; // 生产者对象重置为NULL + u_sess->instr_cxt.global_instr = NULL; // 全局指令重置为NULL + u_sess->instr_cxt.thread_instr = NULL; // 线程指令重置为NULL + u_sess->exec_cxt.under_stream_runtime = false; // 在流运行时标志重置为false + u_sess->stream_cxt.in_waiting_quit = false; // 在等待退出标志重置为false + u_sess->stream_cxt.enter_sync_point = false; // 进入同步点标志重置为false + t_thrd.pgxc_cxt.GlobalNetInstr = NULL; // 全局网络指令重置为NULL #ifndef ENABLE_MULTIPLE_NODES - u_sess->opt_cxt.query_dop = u_sess->attr.attr_sql.query_dop_tmp; + u_sess->opt_cxt.query_dop = u_sess->attr.attr_sql.query_dop_tmp; // 查询DOP重置为临时DOP #endif /* - * When gaussdb backend running in Query or Operator level, we are going to use global - * variable notplanshipping to mark current query is not plan shipping, so we do string - * initialization here + * 当GaussDB后端在查询或操作级别运行时,我们将使用全局变量notplanshipping来标记当前查询不是计划发货, + * 所以我们在这里进行字符串初始化。 */ - u_sess->opt_cxt.not_shipping_info->need_log = true; - errno_t errorno = memset_s( - u_sess->opt_cxt.not_shipping_info->not_shipping_reason, NOTPLANSHIPPING_LENGTH, '\0', NOTPLANSHIPPING_LENGTH); + u_sess->opt_cxt.not_shipping_info->need_log = true; // 需要日志标志设置为true + errno_t errorno = memset_s(u_sess->opt_cxt.not_shipping_info->not_shipping_reason, + NOTPLANSHIPPING_LENGTH, '\0', NOTPLANSHIPPING_LENGTH); securec_check_c(errorno, "\0", "\0"); - t_thrd.postgres_cxt.table_created_in_CTAS = false; + t_thrd.postgres_cxt.table_created_in_CTAS = false; // CTAS标志重置为false if (IS_PGXC_COORDINATOR) { - u_sess->exec_cxt.need_track_resource = false; + u_sess->exec_cxt.need_track_resource = false; // 需要跟踪资源标志重置为false } - u_sess->instr_cxt.gs_query_id->queryId = 0; + u_sess->instr_cxt.gs_query_id->queryId = 0; // 查询ID重置为0 - u_sess->wlm_cxt->local_foreign_respool = NULL; - t_thrd.postmaster_cxt.forceNoSeparate = false; + u_sess->wlm_cxt->local_foreign_respool = NULL; // 本地外部资源池重置为NULL + t_thrd.postmaster_cxt.forceNoSeparate = false; // 强制不分离标志重置为false - u_sess->pcache_cxt.gpc_remote_msg = false; + u_sess->pcache_cxt.gpc_remote_msg = false; // 远程消息标志重置为false - t_thrd.postgres_cxt.gpc_fisrt_send_clean = true; + t_thrd.postgres_cxt.gpc_fisrt_send_clean = true; // 第一次发送清除标志重置为true if (IS_PGXC_COORDINATOR) { WLMStatusTag status = t_thrd.wlm_cxt.collect_info->status; - if (!(status == WLM_STATUS_FINISHED || status == WLM_STATUS_ABORT)) + if (!(status == WLM_STATUS_FINISHED || status == WLM_STATUS_ABORT)) { return; + } } if (IS_PGXC_DATANODE) { WLMStatusTag status = t_thrd.wlm_cxt.dn_cpu_detail->status; - if (!(status == WLM_STATUS_FINISHED || status == WLM_STATUS_ABORT)) + if (!(status == WLM_STATUS_FINISHED || status == WLM_STATUS_ABORT)) { return; + } } t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->initMemInChunks = t_thrd.utils_cxt.trackedMemChunks; @@ -654,28 +696,29 @@ void SetStreamWorkerInfo(StreamProducer* proObj) return; } - u_sess->stream_cxt.producer_obj = proObj; - u_sess->stream_cxt.smp_id = proObj->getKey().smpIdentifier; - u_sess->stream_cxt.producer_dop = proObj->getParallelDesc().producerDop; - u_sess->debug_query_id = proObj->getKey().queryId; - u_sess->utils_cxt.sync_guc_variables = u_sess->stream_cxt.producer_obj->get_sync_guc_variables(); - // set the stopFlag; - u_sess->stream_cxt.global_obj = u_sess->stream_cxt.producer_obj->getNodeGroup(); + u_sess->stream_cxt.producer_obj = proObj; // 设置生产者对象 + u_sess->stream_cxt.smp_id = proObj->getKey().smpIdentifier; // 设置SMP标识符 + u_sess->stream_cxt.producer_dop = proObj->getParallelDesc().producerDop; // 设置生产者DOP + u_sess->debug_query_id = proObj->getKey().queryId; // 设置调试查询ID + u_sess->utils_cxt.sync_guc_variables = u_sess->stream_cxt.producer_obj->get_sync_guc_variables(); // 同步GUC变量 + // 设置停止标志 + u_sess->stream_cxt.global_obj = u_sess->stream_cxt.producer_obj->getNodeGroup(); // 设置全局对象 u_sess->stream_cxt.global_obj->setStopFlagPoint( - u_sess->stream_cxt.producer_obj->getNodeGroupIdx(), &u_sess->exec_cxt.executorStopFlag); + u_sess->stream_cxt.producer_obj->getNodeGroupIdx(), &u_sess->exec_cxt.executorStopFlag); // 设置停止标志点 } static void ResetStreamWorkerInfo() { - u_sess->stream_cxt.producer_obj = NULL; - u_sess->stream_cxt.global_obj = NULL; + u_sess->stream_cxt.producer_obj = NULL; // 重置生产者对象为NULL + u_sess->stream_cxt.global_obj = NULL; // 重置全局对象为NULL } static void StoreStreamSyncParam(StreamSyncParam *syncParam) { - syncParam->TempNamespace = u_sess->catalog_cxt.myTempNamespace; - syncParam->TempToastNamespace = u_sess->catalog_cxt.myTempToastNamespace; - syncParam->IsBinaryUpgrade = u_sess->proc_cxt.IsBinaryUpgrade; + syncParam->TempNamespace = u_sess->catalog_cxt.myTempNamespace; // 存储临时命名空间 + syncParam->TempToastNamespace = u_sess->catalog_cxt.myTempToastNamespace; // 存储临时TOAST命名空间 + syncParam->IsBinaryUpgrade = u_sess->proc_cxt.IsBinaryUpgrade; // 存储二进制升级标志 + // 如果通信IPC模块的日志开启,并且当前进程的工作版本号大于等于92060,则设置CommIpcLog为true,否则为false。 if (module_logging_is_on(MOD_COMM_IPC) && (t_thrd.proc && t_thrd.proc->workingVersionNum >= 92060)) { syncParam->CommIpcLog = true; } else { @@ -695,39 +738,39 @@ void RestoreStreamSyncParam(StreamSyncParam *syncParam) ThreadId ApplyStreamThread(StreamProducer *producer) { - ThreadId tid = InvalidTid; + ThreadId tid = InvalidTid; // 初始化线程ID为无效值 - producer->setParentSessionid(t_thrd.proc->sessMemorySessionid); - StoreStreamSyncParam(&producer->m_syncParam); + producer->setParentSessionid(t_thrd.proc->sessMemorySessionid); // 设置生产者的父会话ID为当前会话的内存会话ID + StoreStreamSyncParam(&producer->m_syncParam); // 存储流同步参数 - if (t_thrd.threadpool_cxt.group != NULL) { - tid = t_thrd.threadpool_cxt.group->GetStreamFromPool(producer); + if (t_thrd.threadpool_cxt.group != NULL) { // 如果线程池组不为空 + tid = t_thrd.threadpool_cxt.group->GetStreamFromPool(producer); // 从线程池组中获取流线程 STREAM_LOG(DEBUG2, "[StreamPool] Apply thread %lu query_id %lu, tlevel %u, smpid %u", tid, producer->getKey().queryId, producer->getKey().planNodeId, - producer->getKey().smpIdentifier); + producer->getKey().smpIdentifier); // 记录日志 } else { - producer->setChildSlot(AssignPostmasterChildSlot()); - if (producer->getChildSlot() == -1) { + producer->setChildSlot(AssignPostmasterChildSlot()); // 分配子进程槽位 + if (producer->getChildSlot() == -1) { // 如果槽位分配失败,返回无效线程ID return InvalidTid; } - tid = initialize_util_thread(STREAM_WORKER, producer); + tid = initialize_util_thread(STREAM_WORKER, producer); // 初始化流工作线程 } - return tid; + return tid; // 返回线程ID } void RestoreStream() { /* - * We should restoreStreamEnter after release the memory context. - * Make sure top consumer thread exit after stream thread. + * 在释放内存上下文后,我们应该在restoreStreamEnter之后进行恢复。 + * 确保顶级消费者线程在流线程之后退出。 */ if (StreamThreadAmI() && u_sess->stream_cxt.global_obj) { /* - * Set CurrentResourceOwner to NULL or will core dumped in ResourceOwnerEnlargePthreadMutex - * case t_thrd.top_mem_cxt has been set NULL. + * 设置CurrentResourceOwner为NULL,否则在ResourceOwnerEnlargePthreadMutex中将发生核心转储, + * 因为t_thrd.top_mem_cxt已经设置为NULL。 */ t_thrd.utils_cxt.CurrentResourceOwner = NULL; u_sess->stream_cxt.global_obj->restoreStreamEnter(); @@ -741,7 +784,7 @@ void StreamExit() return; } - /* Reset to Local vfd if we have attach it to global vfdcache */ + /* 如果我们已经将其附加到全局vfdcache,则重置为本地vfd */ ResetToLocalVfdCache(); CleanupDfsHandlers(true); @@ -750,7 +793,7 @@ void StreamExit() AtProcExit_Buffers(0, 0); ShutdownPostgres(0, 0); - if(!EnableLocalSysCache()) { + if (!EnableLocalSysCache()) { AtProcExit_Files(0, 0); } StreamQuitAndClean(0, 0); @@ -758,21 +801,21 @@ void StreamExit() RestoreStream(); if (!EnableLocalSysCache()) { - /* release memory context and reset flags. */ + /* 释放内存上下文并重置标志。 */ MemoryContextReset(u_sess->syscache_cxt.SysCacheMemCxt); errno_t rc = EOK; rc = memset_s(u_sess->syscache_cxt.SysCache, sizeof(CatCache*) * SysCacheSize, - 0, sizeof(CatCache*) * SysCacheSize); + 0, sizeof(CatCache*) * SysCacheSize); securec_check(rc, "\0", "\0"); rc = memset_s(u_sess->syscache_cxt.SysCacheRelationOid, sizeof(Oid) * SysCacheSize, - 0, sizeof(Oid) * SysCacheSize); + 0, sizeof(Oid) * SysCacheSize); securec_check(rc, "\0", "\0"); } - /* release statement_cxt */ + /* 释放statement_cxt */ if (t_thrd.proc_cxt.MyBackendId != InvalidBackendId) { release_statement_context(t_thrd.shemem_ptr_cxt.MyBEEntry, __FUNCTION__, __LINE__); } free_session_context(u_sess); -} +} \ No newline at end of file -- 2.34.1 From b2fcba5475102ab8f0b0610df5cbbce78f52d151 Mon Sep 17 00:00:00 2001 From: Nemoo <18210033860@163.com> Date: Sat, 30 Sep 2023 21:08:36 +0800 Subject: [PATCH 45/50] Update stream_cost.cpp --- .../process/stream/stream_cost.cpp | 491 +++++++++--------- 1 file changed, 247 insertions(+), 244 deletions(-) diff --git a/src/gausskernel/process/stream/stream_cost.cpp b/src/gausskernel/process/stream/stream_cost.cpp index 2db722c78..da85919b7 100644 --- a/src/gausskernel/process/stream/stream_cost.cpp +++ b/src/gausskernel/process/stream/stream_cost.cpp @@ -1,245 +1,248 @@ -/* ------------------------------------------------------------------------- - * - * stream_cost.cpp - * functions used to calculate stream plan costs. - * - * - * Portions Copyright (c) 2020 Huawei Technologies Co.,Ltd. - * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group - * Portions Copyright (c) 1994, Regents of the University of California - * - * src/gaussdbkernel/porcess/stream/stream_cost.cpp - * - * ------------------------------------------------------------------------- - */ - -#include -#include -#include "access/hash.h" -#include "optimizer/cost.h" -#include "optimizer/dataskew.h" -#include "optimizer/planner.h" - -void parallel_stream_info_print(ParallelDesc* smpDesc, StreamType type) -{ - char* distri_type = NULL; - - if (NULL == smpDesc) - return; - - /* Set stream type tag. */ - switch (smpDesc->distriType) { - case REMOTE_DISTRIBUTE: - distri_type = "REDISTRIBUTE"; - break; - - case REMOTE_SPLIT_DISTRIBUTE: - distri_type = "SPLIT REDISTRIBUTE"; - break; - - case REMOTE_BROADCAST: - distri_type = "BROADCAST"; - break; - - case REMOTE_SPLIT_BROADCAST: - distri_type = "SPLIT BROADCAST"; - break; - - case LOCAL_DISTRIBUTE: - distri_type = "LOCAL REDISTRIBUTE"; - break; - - case LOCAL_BROADCAST: - distri_type = "LOCAL BROADCAST"; - break; - - case LOCAL_ROUNDROBIN: - distri_type = "LOCAL ROUNDROBIN"; - break; - - default: - if (type == STREAM_BROADCAST) - distri_type = "BROADCAST"; - else - distri_type = "REDISTRIBUTE"; - break; - } - - /* Print log. */ - elog(DEBUG1, - "Stream cost: SMP INFO: sendDop: %d, receiveDop: %d, distribute type: %s", - SET_DOP(smpDesc->producerDop), - SET_DOP(smpDesc->consumerDop), - distri_type); -} - -/* - * cost_stream - * computer cost of stream a RepOptInfo object - */ -void cost_stream(StreamPath* stream, int width, bool isJoin) -{ - const double startup_cost_broadcast = 0.0; - - AssertEreport(stream != NULL && stream->subpath != NULL, MOD_OPT, "The stream or subplan is invalid"); - - stream->path.startup_cost = startup_cost_broadcast; - stream->path.startup_cost += stream->subpath->startup_cost; - stream->path.total_cost = stream->subpath->total_cost; - stream->path.stream_cost = stream->subpath->startup_cost; - - unsigned int producer_num_datanodes = bms_num_members(stream->path.distribution.bms_data_nodeids); - unsigned int consumer_num_datanodes = bms_num_members(stream->consumer_distribution.bms_data_nodeids); - - compute_stream_cost(stream->type, - stream->subpath->locator_type, - PATH_LOCAL_ROWS(stream->subpath), - stream->subpath->rows, - stream->path.multiple, - width, - isJoin, - stream->path.distribute_keys, - &stream->path.total_cost, - &stream->path.rows, - producer_num_datanodes, - consumer_num_datanodes, - stream->smpDesc, - stream->skew_list); - - return; -} - -List* get_max_cost_distkey_for_hasdistkey(PlannerInfo* root, List* subPlans, int subPlanNum, - List** subPlanKeyArray, Cost* subPlanCostArray, Bitmapset** redistributePlanSetCopy) -{ - Cost* keyCostArray = NULL; - int counter = 0; - int maxCostIndex = 0; - int subPlanIndex = 0; - List* redistributeKeyIndex = NULL; - - /* - * There are more than one distribute key of subplan, - * find the max cost distribute key of subplan. - */ - keyCostArray = (Cost*)palloc0(sizeof(Cost) * subPlanNum); - - while (counter < subPlanNum) { - List* keyIndex = subPlanKeyArray[counter]; - - if (keyIndex == NULL) { - counter++; - continue; - } - - for (subPlanIndex = 0; subPlanIndex < subPlanNum; subPlanIndex++) { - if (equal(subPlanKeyArray[subPlanIndex], keyIndex)) { - if (subPlanIndex < counter) { - keyCostArray[counter] = 0; - break; - } else { - keyCostArray[counter] += subPlanCostArray[subPlanIndex]; - } - } - } - - counter++; - } - - /* - * Get the max cost for each redistributekey. - */ - for (counter = 0; counter < subPlanNum; counter++) { - if (keyCostArray[maxCostIndex] < keyCostArray[counter]) { - maxCostIndex = counter; - } - } - - /* - * Set other each subplan uesing the max cost redistribute key. - */ - redistributeKeyIndex = subPlanKeyArray[maxCostIndex]; - for (subPlanIndex = 0; subPlanIndex < subPlanNum; subPlanIndex++) { - if (!equal(subPlanKeyArray[subPlanIndex], redistributeKeyIndex)) { - *redistributePlanSetCopy = bms_add_member(*redistributePlanSetCopy, subPlanIndex); - } - } - - pfree_ext(keyCostArray); - keyCostArray = NULL; - return redistributeKeyIndex; -} - -/* - * We should get the max cost distribute key of subplan - * as the final redistribute key for other subplan. - */ -List* get_max_cost_distkey_for_nulldistkey( - PlannerInfo* root, List* subPlans, int subPlanNum, Cost* subPlanCostArray) - -{ - Plan* subPlan = NULL; - int counter = 0; - int maxCostIndex = 0; - List* redistributeKeyIndex = NULL; - - /* - * Get the max cost for each redistributekey. - */ - for (counter = 0; counter < subPlanNum; counter++) { - if (subPlanCostArray[maxCostIndex] < subPlanCostArray[counter]) { - maxCostIndex = counter; - } - } - - /* If there is no distkey, we should choose the max cost distkey. */ - subPlan = (Plan*)list_nth(subPlans, maxCostIndex); - redistributeKeyIndex = make_distkey_for_append(root, subPlan); - return redistributeKeyIndex; -} - -/* - * Construct distribute key index according to bias, if we cannot find distribute key. - * If the targetlist of subplan has no relid, we should choose the first three target entry - * of var for distribute key. - */ -List* make_distkey_for_append(PlannerInfo* root, Plan* subPlan) -{ - List* grplist = NIL; - List* subPlanKeyArray = NIL; - const int defaultDistkeyNum = 3; - - /* Construct group clause using targetlist. */ - grplist = make_groupcl_for_append(root, subPlan->targetlist); - if (grplist != NIL) { - double multiple; - List* distkeys = NIL; - - /* Get distkeys according to bias. */ - distkeys = get_distributekey_from_tlist(root, subPlan->targetlist, grplist, subPlan->plan_rows, &multiple); - if (distkeys != NIL) - subPlanKeyArray = distributeKeyIndex(root, distkeys, subPlan->targetlist); - - list_free_ext(grplist); - list_free_ext(distkeys); - } else { - /* Choose the first three target entry of var for distribute key. */ - int distkeynum = 0; - ListCell* teCell = NULL; - - foreach (teCell, subPlan->targetlist) { - TargetEntry* teEntry = (TargetEntry*)lfirst(teCell); - Node* node = (Node*)teEntry->expr; - - if (!teEntry->resjunk && IsTypeDistributable(exprType(node))) { - subPlanKeyArray = lappend_int(subPlanKeyArray, teEntry->resno); - distkeynum++; - - if (distkeynum <= defaultDistkeyNum) - break; - } - } - } - - return subPlanKeyArray; +/* ------------------------------------------------------------------------- + * + * stream_cost.cpp + * functions used to calculate stream plan costs. + * + * + * Portions Copyright (c) 2020 Huawei Technologies Co.,Ltd. + * Portions Copyright (c) 1996-2012, PostgreSQL Global Development Group + * Portions Copyright (c) 1994, Regents of the University of California + * + * src/gaussdbkernel/porcess/stream/stream_cost.cpp + * + * ------------------------------------------------------------------------- + */ + +#include +#include +#include "access/hash.h" +#include "optimizer/cost.h" +#include "optimizer/dataskew.h" +#include "optimizer/planner.h" + +void parallel_stream_info_print(ParallelDesc* smpDesc, StreamType type) +{ + char* distri_type = NULL; + + if (NULL == smpDesc) + return; + + /* 设置流类型标签。*/ + switch (smpDesc->distriType) { + case REMOTE_DISTRIBUTE: + distri_type = "REDISTRIBUTE"; + break; + + case REMOTE_SPLIT_DISTRIBUTE: + distri_type = "SPLIT REDISTRIBUTE"; + break; + + case REMOTE_BROADCAST: + distri_type = "BROADCAST"; + break; + + case REMOTE_SPLIT_BROADCAST: + distri_type = "SPLIT BROADCAST"; + break; + + case LOCAL_DISTRIBUTE: + distri_type = "LOCAL REDISTRIBUTE"; + break; + + case LOCAL_BROADCAST: + distri_type = "LOCAL BROADCAST"; + break; + + case LOCAL_ROUNDROBIN: + distri_type = "LOCAL ROUNDROBIN"; + break; + + default: + if (type == STREAM_BROADCAST) + distri_type = "BROADCAST"; + else + distri_type = "REDISTRIBUTE"; + break; + } + + /* 打印日志。*/ + elog(DEBUG1, + "Stream cost: SMP INFO: sendDop: %d, receiveDop: %d, distribute type: %s", + SET_DOP(smpDesc->producerDop), + SET_DOP(smpDesc->consumerDop), + distri_type); +} + +/* + * cost_stream + * computer cost of stream a RepOptInfo object + */ +void cost_stream(StreamPath* stream, int width, bool isJoin) +{ + const double startup_cost_broadcast = 0.0; + + // 断言:流和子计划不能为空 + AssertEreport(stream != NULL && stream->subpath != NULL, MOD_OPT, "The stream or subplan is invalid"); + + // 设置启动成本为广播的启动成本 + stream->path.startup_cost = startup_cost_broadcast; + // 累加子计划的启动成本到流的启动成本 + stream->path.startup_cost += stream->subpath->startup_cost; + // 设置流的总成本为子计划的总成本 + stream->path.total_cost = stream->subpath->total_cost; + // 设置流的成本为子计划的启动成本 + stream->path.stream_cost = stream->subpath->startup_cost; + + // 计算生产者和消费者数据节点的数量 + unsigned int producer_num_datanodes = bms_num_members(stream->path.distribution.bms_data_nodeids); + unsigned int consumer_num_datanodes = bms_num_members(stream->consumer_distribution.bms_data_nodeids); + + // 调用compute_stream_cost函数计算流的成本 + compute_stream_cost(stream->type, + stream->subpath->locator_type, + PATH_LOCAL_ROWS(stream->subpath), + stream->subpath->rows, + stream->path.multiple, + width, + isJoin, + stream->path.distribute_keys, + &stream->path.total_cost, + &stream->path.rows, + producer_num_datanodes, + consumer_num_datanodes, + stream->smpDesc, + stream->skew_list); + + return; +} + +List* get_max_cost_distkey_for_hasdistkey(PlannerInfo* root, List* subPlans, int subPlanNum, + List** subPlanKeyArray, Cost* subPlanCostArray, Bitmapset** redistributePlanSetCopy) +{ + Cost* keyCostArray = NULL; + int counter = 0; + int maxCostIndex = 0; + int subPlanIndex = 0; + List* redistributeKeyIndex = NULL; + + /* + * subplan有多个分发键,找出子计划的最大成本分配键。 + */ + keyCostArray = (Cost*)palloc0(sizeof(Cost) * subPlanNum); + + while (counter < subPlanNum) { + List* keyIndex = subPlanKeyArray[counter]; + + if (keyIndex == NULL) { + counter++; + continue; + } + + for (subPlanIndex = 0; subPlanIndex < subPlanNum; subPlanIndex++) { + if (equal(subPlanKeyArray[subPlanIndex], keyIndex)) { + if (subPlanIndex < counter) { + keyCostArray[counter] = 0; + break; + } else { + keyCostArray[counter] += subPlanCostArray[subPlanIndex]; + } + } + } + + counter++; + } + + /* + * 得到每个redistributekey的最大代价 + */ + for (counter = 0; counter < subPlanNum; counter++) { + if (keyCostArray[maxCostIndex] < keyCostArray[counter]) { + maxCostIndex = counter; + } + } + + /* + * 使用最大成本重分发键设置其他每个子计划 + */ + redistributeKeyIndex = subPlanKeyArray[maxCostIndex]; + for (subPlanIndex = 0; subPlanIndex < subPlanNum; subPlanIndex++) { + if (!equal(subPlanKeyArray[subPlanIndex], redistributeKeyIndex)) { + *redistributePlanSetCopy = bms_add_member(*redistributePlanSetCopy, subPlanIndex); + } + } + + pfree_ext(keyCostArray); + keyCostArray = NULL; + return redistributeKeyIndex; +} + +/* + * 我们需要得到子计划的最大成本分配键作为其他子计划的最终重分发键。 + */ +List* get_max_cost_distkey_for_nulldistkey(PlannerInfo* root, List* subPlans, int subPlanNum, Cost* subPlanCostArray) +{ + Plan* subPlan = NULL; + int counter = 0; + int maxCostIndex = 0; + List* redistributeKeyIndex = NULL; + + /* + * 找到具有最大成本的重新分发键。 + */ + for (counter = 0; counter < subPlanNum; counter++) { + if (subPlanCostArray[maxCostIndex] < subPlanCostArray[counter]) { + maxCostIndex = counter; + } + } + + /* 如果没有分发键,我们应该选择具有最大成本的分发键。 */ + subPlan = (Plan*)list_nth(subPlans, maxCostIndex); + redistributeKeyIndex = make_distkey_for_append(root, subPlan); + return redistributeKeyIndex; +} + +/* + 如果找不到分布键,则根据偏差构造分布键索引。 + 如果subplan的targetlist没有空闲,我们应该选择前三个目标条目 + 的var为分发键。 + */ +List* make_distkey_for_append(PlannerInfo* root, Plan* subPlan) +{ + List* grplist = NIL; + List* subPlanKeyArray = NIL; + const int defaultDistkeyNum = 3; + + /* 使用目标列表构建分组子句。 */ + grplist = make_groupcl_for_append(root, subPlan->targetlist); + if (grplist != NIL) { + double multiple; + List* distkeys = NIL; + + /* 根据偏差获取分发键。 */ + distkeys = get_distributekey_from_tlist(root, subPlan->targetlist, grplist, subPlan->plan_rows, &multiple); + if (distkeys != NIL) + subPlanKeyArray = distributeKeyIndex(root, distkeys, subPlan->targetlist); + + list_free_ext(grplist); + list_free_ext(distkeys); + } else { + /* 选择变量的前三个目标项作为分发键。 */ + int distkeynum = 0; + ListCell* teCell = NULL; + + foreach (teCell, subPlan->targetlist) { + TargetEntry* teEntry = (TargetEntry*)lfirst(teCell); + Node* node = (Node*)teEntry->expr; + + if (!teEntry->resjunk && IsTypeDistributable(exprType(node))) { + subPlanKeyArray = lappend_int(subPlanKeyArray, teEntry->resno); + distkeynum++; + + if (distkeynum <= defaultDistkeyNum) + break; + } + } + } + + return subPlanKeyArray; } \ No newline at end of file -- 2.34.1 From 491e17288c67c5b18fdd6ef8e60b0d866c5a32b8 Mon Sep 17 00:00:00 2001 From: Nemoo <18210033860@163.com> Date: Sat, 30 Sep 2023 21:09:08 +0800 Subject: [PATCH 46/50] Update streamTransportComm.cpp --- .../process/stream/streamTransportComm.cpp | 42 +++++++++++++------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/src/gausskernel/process/stream/streamTransportComm.cpp b/src/gausskernel/process/stream/streamTransportComm.cpp index 6fffd9079..66caa99ca 100644 --- a/src/gausskernel/process/stream/streamTransportComm.cpp +++ b/src/gausskernel/process/stream/streamTransportComm.cpp @@ -32,11 +32,17 @@ StreamCOMM::StreamCOMM(libcommaddrinfo* addr, bool flag) : m_addr(addr) { + /* 初始化节点名称为空字符串 */ m_nodeName[0] = '\0'; + /* 初始化节点OID为无效OID */ m_nodeoid = InvalidOid; + /* 设置通信类型为STREAM_COMM */ m_type = STREAM_COMM; + /* 设置发送方标志 */ m_sendSide = flag; + /* 初始化端口为NULL */ m_port = NULL; + /* 初始化缓冲区为NULL */ m_buffer = NULL; } @@ -99,13 +105,20 @@ void StreamCOMM::init(char* dbname, char* usrname) */ void StreamCOMM::allocNetBuffer() { + /* 分配端口结构内存并初始化为零 */ m_port = (Port*)palloc0(sizeof(Port)); + /* 如果是发送方 */ if (m_sendSide) { + /* 分配StreamBuffer结构内存并初始化为零 */ m_buffer = (StreamBuffer*)palloc0(sizeof(StreamBuffer)); + /* 设置发送缓冲区大小为STREAM_BUFFER_SIZE */ m_buffer->PqSendBufferSize = STREAM_BUFFER_SIZE; + /* 设置发送指针为0 */ m_buffer->PqSendPointer = 0; + /* 设置发送开始位置为0 */ m_buffer->PqSendStart = 0; + /* 设置通信忙标志为false */ m_buffer->PqCommBusy = false; } } @@ -118,17 +131,17 @@ void StreamCOMM::allocNetBuffer() bool StreamCOMM::setActive() { /* - * if we use parallel send mode, - * and the head of address info list is already close, - * we must continue to send, - * and gs_broadcast can send to other node in address info list. + * 如果使用并行发送模式, + * 并且地址信息列表的头部已经关闭, + * 我们必须继续发送, + * 并且gs_broadcast可以发送到地址信息列表中的其他节点。 */ if (m_addr->parallel_send_mode == true) { /* - * if we use parallel send mode, - * we only send to head node of address info list, - * and do not care other node in address info list, - * gs_broadcast can parallel send to other node. + * 如果使用并行发送模式, + * 我们只发送到地址信息列表的头节点, + * 不关心地址信息列表中的其他节点, + * gs_broadcast可以并行发送到其他节点。 */ if (m_addr->addr_list_size == 0) return false; @@ -136,8 +149,10 @@ bool StreamCOMM::setActive() return false; } + /* 设置当前线程的ProcPort */ u_sess->proc_cxt.MyProcPort = m_port; + /* 设置发送缓冲区指针、大小和起始位置以及通信忙标志 */ t_thrd.libpq_cxt.PqSendBuffer = &m_buffer->PqSendBuffer[0]; t_thrd.libpq_cxt.PqSendPointer = m_buffer->PqSendPointer; t_thrd.libpq_cxt.PqSendBufferSize = m_buffer->PqSendBufferSize; @@ -177,11 +192,14 @@ void StreamCOMM::setInActive() */ void StreamCOMM::updateInfo(StreamConnInfo* connInfo) { + /* 获取节点名称的长度 */ int nodeNameLen = strlen(connInfo->nodeName); - errno_t rc = EOK; - + /* 将连接信息中的套接字信息复制到StreamCOMM对象的套接字信息中 */ m_addr->gs_sock = connInfo->port.libcomm_layer.gsock; - rc = strncpy_s(m_nodeName, NAMEDATALEN, connInfo->nodeName, nodeNameLen + 1); + /* 将连接信息中的节点名称复制到StreamCOMM对象的节点名称中 */ + errno_t rc = strncpy_s(m_nodeName, NAMEDATALEN, connInfo->nodeName, nodeNameLen + 1); + /* 检查字符串复制是否成功 */ securec_check(rc, "\0", "\0"); + /* 将连接信息中的生产者SMP ID复制到StreamCOMM对象的streamKey中 */ m_addr->streamKey.producerSmpId = connInfo->producerSmpId; -} +} \ No newline at end of file -- 2.34.1 From d53fdbf6bd4c973f34faaa43a85cfea7bae93b1b Mon Sep 17 00:00:00 2001 From: noah <1204149038@qq.com> Date: Wed, 4 Oct 2023 13:11:22 +0800 Subject: [PATCH 47/50] =?UTF-8?q?storage=E6=96=87=E4=BB=B6=E5=A4=B9?= =?UTF-8?q?=E6=B3=A8=E9=87=8A=E6=96=87=E4=BB=B6=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/gausskernel/storage/bulkload/parser.cpp | 48 +- .../occ_transaction_manager.cpp | 690 +++++++++++------ .../core/concurrency_control/row_header.cpp | 76 +- .../infra/config/cmdline_config_loader.cpp | 194 +++-- .../mot/core/infra/config/config_array.cpp | 54 +- .../core/infra/config/config_file_loader.cpp | 80 +- .../core/infra/config/config_file_parser.cpp | 705 ++++++++++++++---- .../mot/core/infra/config/config_item.cpp | 84 ++- .../core/infra/config/config_item_class.cpp | 65 +- .../mot/core/infra/config/config_loader.cpp | 18 +- .../mot/core/infra/config/config_manager.cpp | 368 +++++++-- .../mot/core/infra/config/config_section.cpp | 557 ++++++++++---- .../mot/core/infra/config/config_tree.cpp | 230 ++++-- .../core/infra/config/config_value_type.cpp | 88 ++- .../core/infra/config/ext_config_loader.cpp | 119 ++- .../core/infra/config/file_line_reader.cpp | 19 +- .../core/infra/config/layered_config_tree.cpp | 198 +++-- .../infra/config/props_config_file_loader.cpp | 242 ++++-- .../mot/core/infra/containers/bitmapset.cpp | 170 ++++- .../stats/boolean_statistic_variable.cpp | 97 ++- .../stats/frequency_statistic_variable.cpp | 107 ++- .../core/infra/stats/global_statistics.cpp | 125 +++- .../infra/stats/level_statistic_variable.cpp | 111 ++- .../infra/stats/memory_statistic_variable.cpp | 90 ++- .../stats/numeric_statistic_variable.cpp | 169 ++++- .../infra/stats/rate_statistic_variable.cpp | 132 +++- .../core/infra/stats/statistics_manager.cpp | 318 ++++++-- .../core/infra/stats/statistics_provider.cpp | 221 +++++- .../core/infra/stats/thread_statistics.cpp | 154 +++- .../core/infra/synchronization/affinity.cpp | 120 ++- 30 files changed, 4442 insertions(+), 1207 deletions(-) diff --git a/src/gausskernel/storage/bulkload/parser.cpp b/src/gausskernel/storage/bulkload/parser.cpp index d2cc2f803..3745d8148 100644 --- a/src/gausskernel/storage/bulkload/parser.cpp +++ b/src/gausskernel/storage/bulkload/parser.cpp @@ -1640,7 +1640,7 @@ Parser *CreateWritableParser() * 功能:创建解析器对象 * * 参数列表: - * - format:文件格式(FORMAT_TEXT、FORMAT_CSV、FORMAT_FIXED、FORMAT_REMOTEWRITE) + * format:文件格式(FORMAT_TEXT、FORMAT_CSV、FORMAT_FIXED、FORMAT_REMOTEWRITE) * * 返回值: * 返回创建的解析器对象指针 @@ -1679,7 +1679,7 @@ Parser *CreateParser(FileFormat format) * 功能:销毁解析器对象 * * 参数列表: - * - self:要销毁的解析器对象指针 + * self:要销毁的解析器对象指针 * * 注意: * 该函数用于销毁解析器对象以及相关资源。首先释放解析器对象中的 source、line_buffer、prefix、eol @@ -1725,7 +1725,7 @@ static void DestroyParser(Parser *self) * 功能:销毁可读解析器对象 * * 参数列表: - * - self:要销毁的可读解析器对象指针 + * self:要销毁的可读解析器对象指针 * * 注意: * 该函数用于销毁可读解析器对象以及相关资源。首先释放可读解析器对象中的 rec_buf 缓冲区,然后调用 DestroyParser @@ -1751,7 +1751,7 @@ static void DestroyReadableParser(ReadableParser *self) * 功能:销毁可写解析器对象 * * 参数列表: - * - self:要销毁的可写解析器对象指针 + * self:要销毁的可写解析器对象指针 * * 注意: * 该函数用于销毁可写解析器对象以及相关资源。首先释放可写解析器对象中的 fileheader 缓冲区,然后调用 SourceFlush @@ -1783,7 +1783,7 @@ static void NopCleanup(Parser *self) * 功能:清理可写解析器对象 * * 参数列表: - * - self:要清理的可写解析器对象指针 + * self:要清理的可写解析器对象指针 * * 注意: * 该函数用于清理可写解析器对象,主要工作包括关闭当前文件但不刷新数据(使用 CloseCurrentFileNoFlush @@ -1809,8 +1809,8 @@ static void CleanupWritablParser(WritableParser *self) * 功能:从用户定义的文件头中获取文件头信息 * * 参数列表: - * - self:可写解析器对象指针,用于存储文件头信息 - * - path:用户定义的文件头文件路径 + * self:可写解析器对象指针,用于存储文件头信息 + * path:用户定义的文件头文件路径 * * 注意: * 该函数用于从用户定义的文件头文件中获取文件头信息,主要工作包括:打开文件,读取文件头数据,处理多行文件头,存储文件头信息。 @@ -1950,9 +1950,9 @@ void GDS::LineBuffer::Reset() * 功能:向行缓冲区追加数据 * * 参数列表: - * - buf:要追加到行缓冲区的数据的指针 - * - buf_len:要追加的数据的长度 - * - isComplete:标志是否追加的数据组成了一个完整的行 + * buf:要追加到行缓冲区的数据的指针 + * buf_len:要追加的数据的长度 + * isComplete:标志是否追加的数据组成了一个完整的行 * * 返回值: * - 返回实际追加到行缓冲区的数据长度,如果追加失败则返回负数 @@ -2051,11 +2051,11 @@ int GDS::LineBuffer::AppendLine(const char *buf, int buf_len, bool isComplete) * 功能:打包数据并发送至目标缓冲区 * * 参数列表: - * - dest:目标缓冲区 - * - isFlush:是否要强制发送 + * dest:目标缓冲区 + * isFlush:是否要强制发送 * * 返回值: - * - 成功返回 0,失败返回错误码 + * 成功返回 0,失败返回错误码 * * 注意: * 该函数用于将数据打包并发送至目标缓冲区,主要工作包括:计算包大小、构建包头、发送数据、重置行缓冲区。 @@ -2110,13 +2110,13 @@ int GDS::LineBuffer::PackData(evbuffer *dest, bool isFlush) * 功能:发送溢出缓冲区至目标缓冲区 * * 参数列表: - * - dest:目标缓冲区 - * - buf:要发送的数据的指针 - * - buf_len:要发送的数据的长度 - * - isComplete:标志是否要发送的数据组成了一个完整的行 + * dest:目标缓冲区 + * buf:要发送的数据的指针 + * buf_len:要发送的数据的长度 + * isComplete:标志是否要发送的数据组成了一个完整的行 * * 返回值: - * - 成功返回 0,失败返回错误码 + * 成功返回 0,失败返回错误码 * * 注意: * 该函数用于将溢出缓冲区的数据发送至目标缓冲区,主要工作包括:更新行头、构建数据包头、发送数据、重置行缓冲区。 @@ -2193,12 +2193,12 @@ int GDS::LineBuffer::SendOverloadBuf(evbuffer *dest, const char *buf, int buf_le * 功能:从OBS(Object Storage Service)中读取数据 * * 参数列表: - * - self:源对象指针,用于处理OBS读取 - * - buffer:目标缓冲区,用于存储读取到的数据 - * - len:要读取的数据长度 + * self:源对象指针,用于处理OBS读取 + * buffer:目标缓冲区,用于存储读取到的数据 + * len:要读取的数据长度 * * 返回值: - * - 返回已读取的数据长度 + * 返回已读取的数据长度 * * 注意: * 该函数用于从OBS中读取数据,主要工作包括:检查是否已读取完毕、创建OBS读取处理器、重复尝试读取数据、标记是否已到达对象的末尾。 @@ -2251,10 +2251,10 @@ static size_t SourceRead_OBS(Source *self, void *buffer, size_t len) * 功能:获取下一个OBS对象 * * 参数列表: - * - self:源对象指针,用于处理OBS读取 + * self:源对象指针,用于处理OBS读取 * * 返回值: - * - 如果成功获取下一个OBS对象,则返回true;否则返回false。 + * 如果成功获取下一个OBS对象,则返回true;否则返回false。 * * 注意: * 该函数用于获取下一个OBS对象,主要工作包括:检查是否已到达文件列表末尾、循环遍历文件列表、跳过特殊文件。 diff --git a/src/gausskernel/storage/mot/core/concurrency_control/occ_transaction_manager.cpp b/src/gausskernel/storage/mot/core/concurrency_control/occ_transaction_manager.cpp index 80c70babf..14eefc2ec 100644 --- a/src/gausskernel/storage/mot/core/concurrency_control/occ_transaction_manager.cpp +++ b/src/gausskernel/storage/mot/core/concurrency_control/occ_transaction_manager.cpp @@ -54,20 +54,55 @@ OccTransactionManager::OccTransactionManager() OccTransactionManager::~OccTransactionManager() {} +/* + * 功能:初始化事务管理器 + * + * 返回值: + * 如果成功初始化事务管理器,返回 true;否则返回 false。 + * + * 注意: + * 此函数用于初始化事务管理器的状态和属性,但当前版本尚未执行具体的初始化操作。 + * 在将来的版本中,应根据需求添加更多的初始化代码。 + */ bool OccTransactionManager::Init() { - bool result = true; - return result; + bool result = true; // 创建一个名为 result 的布尔变量并初始化为 true。 + + return result; // 返回 result 变量的值,这里始终返回 true。 } -bool OccTransactionManager::CheckVersion(const Access* access) +/* + * 功能:检查事务版本 + * + * 参数列表: + * access:访问对象指针,包含要检查的事务信息 + * + * 注意: + * 此函数用于检查给定事务是否与访问对象中的事务ID匹配。通常用于验证已提交的行的版本。 + * 如果事务版本匹配,返回 true,否则返回 false。 + */ +bool OccTransactionManager::CheckVersion(const Access *access) { // We always validate on committed rows! - const Row* row = access->GetRowFromHeader(); + const Row *row = access->GetRowFromHeader(); + + // 检查行的事务序列号(CSN)是否与访问对象中的事务ID(TID)匹配 return (row->m_rowHeader.GetCSN() == access->m_tid); } - -bool OccTransactionManager::QuickHeaderValidation(const Access* access) +/* + * 功能:快速验证事务头信息 + * + * 参数列表: + * access:访问对象指针,包含要验证的事务头信息 + * + * 返回值: + * 如果事务头信息验证通过,返回 true;否则返回 false。 + * + * 注意: + * 此函数用于快速验证访问对象中的事务头信息,以决定是否可以继续处理事务。 + * 具体验证逻辑包括检查操作类型、事务版本和其他条件,根据不同的情况进行验证。 + */ +bool OccTransactionManager::QuickHeaderValidation(const Access *access) { if (access->m_type != INS) { // For WR/DEL/RD_FOR_UPDATE lets verify CSN @@ -76,7 +111,7 @@ bool OccTransactionManager::QuickHeaderValidation(const Access* access) // Lets verify the inserts // For upgrade we verify the row // csn has not changed! - Sentinel* sent = access->m_origSentinel; + Sentinel *sent = access->m_origSentinel; if (access->m_params.IsUpgradeInsert()) { if (access->m_params.IsDummyDeletedRow()) { // Check is sentinel is deleted and CSN is VALID - ABA problem @@ -104,78 +139,144 @@ bool OccTransactionManager::QuickHeaderValidation(const Access* access) return true; } -bool OccTransactionManager::ValidateReadSet(TxnManager* txMan) +/* + * 功能:验证读取集合 + * + * 参数列表: + * txMan:事务管理器指针,包含待验证的读取集合 + * + * 返回值: + * 如果读取集合中的所有读取操作都验证通过,返回true;否则返回false。 + * + * 注意: + * 此函数用于验证事务的读取集合中的读取操作是否有效。 + * 它遍历有序的访问集合,对每个读取操作检查是否有效。 + * 如果所有读取操作都验证通过,返回true,否则返回false。 + */ +bool OccTransactionManager::ValidateReadSet(TxnManager *txMan) { - TxnOrderedSet_t& orderedSet = txMan->m_accessMgr->GetOrderedRowSet(); - for (const auto& raPair : orderedSet) { - const Access* ac = raPair.second; + TxnOrderedSet_t &orderedSet = txMan->m_accessMgr->GetOrderedRowSet(); + for (const auto &raPair : orderedSet) { + const Access *ac = raPair.second; if (ac->m_type != RD) { continue; } + // 验证读取操作的有效性 if (!ac->GetRowFromHeader()->m_rowHeader.ValidateRead(ac->m_tid)) { return false; } } - return true; } -bool OccTransactionManager::ValidateWriteSet(TxnManager* txMan) +/* + * 功能:验证写入集合 + * + * 参数列表: + * txMan:事务管理器指针,包含待验证的写入集合 + * + * 返回值: + * 如果写入集合中的所有写入操作都验证通过,返回true;否则返回false。 + * + * 注意: + * 此函数用于验证事务的写入集合中的写入操作是否有效。 + * 它遍历有序的访问集合,对每个写入操作执行快速验证。 + * 如果所有写入操作都验证通过,返回true,否则返回false。 + */ +bool OccTransactionManager::ValidateWriteSet(TxnManager *txMan) { - TxnOrderedSet_t& orderedSet = txMan->m_accessMgr->GetOrderedRowSet(); - for (const auto& raPair : orderedSet) { - const Access* ac = raPair.second; + TxnOrderedSet_t &orderedSet = txMan->m_accessMgr->GetOrderedRowSet(); + for (const auto &raPair : orderedSet) { + const Access *ac = raPair.second; if (ac->m_type == RD) { continue; } - + // 执行快速验证 if (!QuickHeaderValidation(ac)) { return false; } } return true; } - -RC OccTransactionManager::LockRows(TxnManager* txMan, uint32_t& numRowsLock) +/* + * 功能:锁定行 + * + * 参数列表: + * txMan:事务管理器指针,包含待锁定的行 + * numRowsLock:用于存储锁定的行数的引用 + * + * 返回值: + * 返回操作结果的代码 (RC),通常是 RC_OK 表示成功。 + * + * 注意: + * 此函数用于锁定事务中需要修改的行。 + * 它遍历有序的访问集合,对每个需要锁定的行执行锁定操作。 + * 在锁定行后,会将锁定的行数存储在 numRowsLock 引用中,并进行断言以验证锁定状态。 + */ +RC OccTransactionManager::LockRows(TxnManager *txMan, uint32_t &numRowsLock) { - RC rc = RC_OK; - TxnOrderedSet_t& orderedSet = txMan->m_accessMgr->GetOrderedRowSet(); - numRowsLock = 0; - for (const auto& raPair : orderedSet) { - const Access* ac = raPair.second; - if (ac->m_type == RD) { + RC rc = RC_OK; // 初始化返回代码为 RC_OK + TxnOrderedSet_t &orderedSet = txMan->m_accessMgr->GetOrderedRowSet(); // 获取有序的访问集合 + numRowsLock = 0; // 初始化锁定的行数为 0 + + // 遍历有序的访问集合 + for (const auto &raPair : orderedSet) { + const Access *ac = raPair.second; + + if (ac->m_type == RD) { // 如果操作类型是读取,则跳过 continue; } - if (ac->m_params.IsPrimarySentinel()) { - Row* row = ac->GetRowFromHeader(); - row->m_rowHeader.Lock(); - numRowsLock++; - MOT_ASSERT(row->GetPrimarySentinel()->IsLocked() == true); + + if (ac->m_params.IsPrimarySentinel()) { // 如果是主要的 Sentinel + Row *row = ac->GetRowFromHeader(); // 获取行对象 + row->m_rowHeader.Lock(); // 锁定行 + numRowsLock++; // 增加锁定的行数计数 + MOT_ASSERT(row->GetPrimarySentinel()->IsLocked() == true); // 断言验证锁定状态 } } - return rc; + return rc; // 返回操作结果的代码 } - -bool OccTransactionManager::LockHeadersNoWait(TxnManager* txMan, uint32_t& numSentinelsLock) +/* + * 功能:无等待锁定头信息 + * + * 参数列表: + * txMan:事务管理器指针,包含待锁定的头信息 + * numSentinelsLock:用于存储锁定的 Sentinel 数量的引用 + * + * 返回值: + * 如果成功锁定所有头信息,返回true;否则返回false。 + * + * 注意: + * 此函数用于无等待方式锁定事务中需要修改的头信息。 + * 它遍历有序的访问集合,对每个需要锁定的头信息执行锁定操作。 + * 如果锁定失败,会进行重试,如果重试次数超过阈值或者发生异常,则返回false。 + */ +bool OccTransactionManager::LockHeadersNoWait(TxnManager *txMan, uint32_t &numSentinelsLock) { - uint64_t sleepTime = 1; - uint64_t thdId = txMan->GetThdId(); - TxnOrderedSet_t& orderedSet = txMan->m_accessMgr->GetOrderedRowSet(); - numSentinelsLock = 0; - while (numSentinelsLock != m_writeSetSize) { - for (const auto& raPair : orderedSet) { - const Access* ac = raPair.second; - if (ac->m_type == RD) { + uint64_t sleepTime = 1; // 初始休眠时间为1 + uint64_t thdId = txMan->GetThdId(); // 获取线程ID + TxnOrderedSet_t &orderedSet = txMan->m_accessMgr->GetOrderedRowSet(); // 获取有序的访问集合 + numSentinelsLock = 0; // 初始化锁定的 Sentinel 数量为0 + + while (numSentinelsLock != m_writeSetSize) { // 当锁定的 Sentinel 数量不等于写入集合大小时进行循环 + for (const auto &raPair : orderedSet) { // 遍历有序的访问集合 + const Access *ac = raPair.second; + + if (ac->m_type == RD) { // 如果操作类型是读取,则跳过 continue; } - Sentinel* sent = ac->m_origSentinel; - if (!sent->TryLock(thdId)) { + + Sentinel *sent = ac->m_origSentinel; // 获取原始 Sentinel 对象 + + if (!sent->TryLock(thdId)) { // 尝试锁定 Sentinel break; } - numSentinelsLock++; - if (ac->m_params.IsPrimaryUpgrade()) { - ac->m_auxRow->m_rowHeader.Lock(); + + numSentinelsLock++; // 增加锁定的 Sentinel 数量计数 + + if (ac->m_params.IsPrimaryUpgrade()) { // 如果是主要升级操作 + ac->m_auxRow->m_rowHeader.Lock(); // 锁定辅助行的头信息 } // New insert row is already committed! // Check if row has changed in sentinel @@ -184,217 +285,308 @@ bool OccTransactionManager::LockHeadersNoWait(TxnManager* txMan, uint32_t& numSe } } - if (numSentinelsLock != m_writeSetSize) { - ReleaseHeaderLocks(txMan, numSentinelsLock); - numSentinelsLock = 0; - if (m_preAbort) { - for (const auto& acPair : orderedSet) { - const Access* ac = acPair.second; + if (numSentinelsLock != m_writeSetSize) { // 如果锁定的 Sentinel 数量不等于写入集合大小 + ReleaseHeaderLocks(txMan, numSentinelsLock); // 释放已锁定的头信息 + numSentinelsLock = 0; // 重置已锁定的 Sentinel 数量 + if (m_preAbort) { // 如果是预终止事务 + for (const auto &acPair : orderedSet) { // 遍历有序的访问集合 + const Access *ac = acPair.second; if (!QuickHeaderValidation(ac)) { return false; } } } - if (sleepTime > LOCK_TIME_OUT) { + if (sleepTime > LOCK_TIME_OUT) { // 如果休眠时间超过阈值 return false; } else { - if (IsHighContention() == false) { - CpuCyclesLevelTime::Sleep(5); + if (IsHighContention() == false) { // 如果不是高竞争环境 + CpuCyclesLevelTime::Sleep(5); // 休眠5个CPU周期 } else { - usleep(m_dynamicSleep); + usleep(m_dynamicSleep); // 否则休眠指定的微秒数 } - sleepTime = sleepTime << 1; + sleepTime = sleepTime << 1; // 休眠时间翻倍 } } } - return true; + return true; // 返回锁定成功 } -RC OccTransactionManager::LockHeaders(TxnManager* txMan, uint32_t& numSentinelsLock) +/* + * 功能:锁定头信息 + * + * 参数列表: + * txMan:事务管理器指针,包含待锁定的头信息 + * numSentinelsLock:用于存储锁定的 Sentinel 数量的引用 + * + * 返回值: + * 返回操作结果的代码 (RC),通常是 RC_OK 表示成功。 + * + * 注意: + * 此函数用于锁定事务中需要修改的头信息。 + * 它遍历有序的访问集合,对每个需要锁定的头信息执行锁定操作。 + * 如果锁定失败,会进行重试,如果重试次数超过阈值或者发生异常,则返回相应的错误代码。 + */ +RC OccTransactionManager::LockHeaders(TxnManager *txMan, uint32_t &numSentinelsLock) { - RC rc = RC_OK; - uint64_t thdId = txMan->GetThdId(); - TxnOrderedSet_t& orderedSet = txMan->m_accessMgr->GetOrderedRowSet(); - numSentinelsLock = 0; - if (m_validationNoWait) { - if (!LockHeadersNoWait(txMan, numSentinelsLock)) { - rc = RC_ABORT; - goto final; + RC rc = RC_OK; // 初始化返回代码为 RC_OK + uint64_t thdId = txMan->GetThdId(); // 获取线程ID + TxnOrderedSet_t &orderedSet = txMan->m_accessMgr->GetOrderedRowSet(); // 获取有序的访问集合 + numSentinelsLock = 0; // 初始化锁定的 Sentinel 数量为0 + + if (m_validationNoWait) { // 如果启用无等待锁定 + if (!LockHeadersNoWait(txMan, numSentinelsLock)) { // 调用无等待锁定函数 + rc = RC_ABORT; // 锁定失败,设置返回代码为 RC_ABORT + goto final; // 跳转到 final 标签 } } else { - for (const auto& raPair : orderedSet) { - const Access* ac = raPair.second; - if (ac->m_type == RD) { + for (const auto &raPair : orderedSet) { // 遍历有序的访问集合 + const Access *ac = raPair.second; + + if (ac->m_type == RD) { // 如果操作类型是读取,则跳过 continue; } - Sentinel* sent = ac->m_origSentinel; - sent->Lock(thdId); - numSentinelsLock++; - if (ac->m_params.IsPrimaryUpgrade()) { - ac->m_auxRow->m_rowHeader.Lock(); + + Sentinel *sent = ac->m_origSentinel; // 获取原始 Sentinel 对象 + sent->Lock(thdId); // 锁定 Sentinel + numSentinelsLock++; // 增加锁定的 Sentinel 数量计数 + + if (ac->m_params.IsPrimaryUpgrade()) { // 如果是主要升级操作 + ac->m_auxRow->m_rowHeader.Lock(); // 锁定辅助行的头信息 } // New insert row is already committed! // Check if row has chained in sentinel - if (!QuickHeaderValidation(ac)) { - rc = RC_ABORT; - goto final; + if (!QuickHeaderValidation(ac)) { // 调用快速验证函数 + rc = RC_ABORT; // 验证失败,设置返回代码为 RC_ABORT + goto final; // 跳转到 final 标签 } } } -final: - return rc; + +final: // final 标签,用于处理最后的清理和返回 + return rc; // 返回操作结果的代码 } -bool OccTransactionManager::PreAllocStableRow(TxnManager* txMan) +/* + * 功能:预分配稳定行 + * + * 参数列表: + * txMan:事务管理器指针,包含需要预分配稳定行的信息 + * + * 返回值: + * 如果成功预分配稳定行,返回true;否则返回false。 + * + * 注意: + * 如果启用检查点,此函数用于在提交事务前预分配稳定行。 + * 它遍历有序的访问集合,对每个需要预分配稳定行的操作执行预分配操作。 + * 如果预分配失败,会回滚已分配的稳定行并返回false。 + */ +bool OccTransactionManager::PreAllocStableRow(TxnManager *txMan) { - if (GetGlobalConfiguration().m_enableCheckpoint) { - GetCheckpointManager()->BeginCommit(txMan); + if (GetGlobalConfiguration().m_enableCheckpoint) { // 如果启用检查点 + GetCheckpointManager()->BeginCommit(txMan); // 开始提交检查点 - TxnOrderedSet_t& orderedSet = txMan->m_accessMgr->GetOrderedRowSet(); - for (const auto& raPair : orderedSet) { - const Access* access = raPair.second; - if (access->m_type == RD) { + TxnOrderedSet_t &orderedSet = txMan->m_accessMgr->GetOrderedRowSet(); // 获取有序的访问集合 + for (const auto &raPair : orderedSet) { // 遍历有序的访问集合 + const Access *access = raPair.second; + if (access->m_type == RD) { // 如果操作类型是读取,则跳过 continue; } - if (access->m_params.IsPrimarySentinel()) { - if (!GetCheckpointManager()->PreAllocStableRow(txMan, access->GetRowFromHeader(), access->m_type)) { - GetCheckpointManager()->FreePreAllocStableRows(txMan); - GetCheckpointManager()->EndCommit(txMan); - return false; + if (access->m_params.IsPrimarySentinel()) { // 如果是主要 Sentinel + if (!GetCheckpointManager()->PreAllocStableRow(txMan, access->GetRowFromHeader(), + access->m_type)) { // 预分配稳定行 + GetCheckpointManager()->FreePreAllocStableRows(txMan); // 释放已分配的稳定行 + GetCheckpointManager()->EndCommit(txMan); // 结束提交检查点 + return false; // 预分配失败,返回false } } } } - return true; + return true; // 返回预分配成功 } -bool OccTransactionManager::QuickVersionCheck(TxnManager* txMan, uint32_t& readSetSize) +/* + * 功能:快速版本检查 + * + * 参数列表: + * txMan:事务管理器指针,包含需要进行版本检查的信息 + * readSetSize:用于存储读取集合大小的引用 + * + * 返回值: + * 如果版本检查成功,返回true;否则返回false。 + * + * 注意: + * 此函数用于快速执行版本检查以确定事务的读取集合大小。 + * 它遍历有序的访问集合,根据访问类型和隔离级别更新集合大小信息。 + * 如果启用预终止,还会进行头信息验证。 + */ +bool OccTransactionManager::QuickVersionCheck(TxnManager *txMan, uint32_t &readSetSize) { - int isolationLevel = txMan->GetTxnIsoLevel(); - TxnOrderedSet_t& orderedSet = txMan->m_accessMgr->GetOrderedRowSet(); - readSetSize = 0; - for (const auto& raPair : orderedSet) { - const Access* ac = raPair.second; + int isolationLevel = txMan->GetTxnIsoLevel(); // 获取事务的隔离级别 + TxnOrderedSet_t &orderedSet = txMan->m_accessMgr->GetOrderedRowSet(); // 获取有序的访问集合 + readSetSize = 0; // 初始化读取集合大小为0 + + for (const auto &raPair : orderedSet) { // 遍历有序的访问集合 + const Access *ac = raPair.second; // 获取访问对象 + if (ac->m_params.IsPrimarySentinel()) { - m_rowsSetSize++; + m_rowsSetSize++; // 如果是主要 Sentinel,则增加行集合大小计数 } + switch (ac->m_type) { case RD_FOR_UPDATE: case WR: - m_writeSetSize++; + m_writeSetSize++; // 如果是写入类型,增加写集合大小计数 break; case DEL: - m_writeSetSize++; - m_deleteSetSize++; + m_writeSetSize++; // 如果是删除类型,增加写集合大小计数 + m_deleteSetSize++; // 同时增加删除集合大小计数 break; case INS: - m_insertSetSize++; - m_writeSetSize++; + m_insertSetSize++; // 如果是插入类型,增加插入集合大小计数 + m_writeSetSize++; // 同时增加写集合大小计数 break; case RD: - if (isolationLevel > READ_COMMITED) { - readSetSize++; + if (isolationLevel > READ_COMMITTED) { + readSetSize++; // 如果是读取类型且隔离级别较高,增加读取集合大小计数 } else { - continue; + continue; // 否则跳过当前操作 } break; default: break; } - if (m_preAbort) { - if (!QuickHeaderValidation(ac)) { + if (m_preAbort) { // 如果启用预终止 + if (!QuickHeaderValidation(ac)) { // 进行头信息验证,如果失败则返回false return false; } } } - return true; + return true; // 返回版本检查成功 } -RC OccTransactionManager::ValidateOcc(TxnManager* txMan) +/* + * 功能:验证 OCC(Optimistic Concurrency Control) + * + * 参数列表: + * txMan:事务管理器指针,包含待验证的 OCC 信息 + * + * 返回值: + * 返回操作结果的代码 (RC),通常是 RC_OK 表示成功。 + * + * 注意: + * 此函数用于验证 OCC 事务,包括执行快速版本检查、锁定头信息、验证读取集合和写入集合、预分配稳定行等步骤。 + * 它计算事务的各个集合大小,并在验证成功时锁定相关的头信息。 + * 如果验证失败,会根据错误代码进行相应处理。 + */ +RC OccTransactionManager::ValidateOcc(TxnManager *txMan) { - uint32_t numSentinelLock = 0; - m_rowsLocked = false; - TxnAccess* tx = txMan->m_accessMgr.Get(); - RC rc = RC_OK; - const uint32_t rowCount = tx->m_rowCnt; + uint32_t numSentinelLock = 0; // 初始化锁定的 Sentinel 数量为0 + m_rowsLocked = false; // 初始化行是否已锁定为 false + TxnAccess *tx = txMan->m_accessMgr.Get(); // 获取事务的访问对象 + RC rc = RC_OK; // 初始化返回代码为 RC_OK + const uint32_t rowCount = tx->m_rowCnt; // 获取行数 - m_writeSetSize = 0; - m_rowsSetSize = 0; - m_deleteSetSize = 0; - m_insertSetSize = 0; - m_txnCounter++; + m_writeSetSize = 0; // 初始化写入集合大小为0 + m_rowsSetSize = 0; // 初始化行集合大小为0 + m_deleteSetSize = 0; // 初始化删除集合大小为0 + m_insertSetSize = 0; // 初始化插入集合大小为0 + m_txnCounter++; // 事务计数器加1 - if (rowCount == 0) { + if (rowCount == 0) { // 如果行数为0,则为只读事务 // READONLY - return rc; + return rc; // 返回 RC_OK } - uint32_t readSetSize = 0; - TxnOrderedSet_t& orderedSet = tx->GetOrderedRowSet(); - MOT_ASSERT(rowCount == orderedSet.size()); + uint32_t readSetSize = 0; // 初始化读取集合大小为0 + TxnOrderedSet_t &orderedSet = tx->GetOrderedRowSet(); // 获取有序的访问集合 + MOT_ASSERT(rowCount == orderedSet.size()); // 断言行数和有序集合大小相等 /* Perform Quick Version check */ - if (!QuickVersionCheck(txMan, readSetSize)) { - rc = RC_ABORT; - goto final; + if (!QuickVersionCheck(txMan, readSetSize)) { // 调用快速版本检查函数 + rc = RC_ABORT; // 检查失败,设置返回代码为 RC_ABORT + goto final; // 跳转到 final 标签 } - MOT_LOG_DEBUG("Validate OCC rowCnt=%u RD=%u WR=%u\n", tx->m_rowCnt, tx->m_rowCnt - m_writeSetSize, m_writeSetSize); - rc = LockHeaders(txMan, numSentinelLock); - if (rc != RC_OK) { - goto final; + MOT_LOG_DEBUG("Validate OCC rowCnt=%u RD=%u WR=%u\n", tx->m_rowCnt, tx->m_rowCnt - m_writeSetSize, + m_writeSetSize); // 调试日志 + + rc = LockHeaders(txMan, numSentinelLock); // 锁定头信息 + if (rc != RC_OK) { // 如果锁定失败 + goto final; // 跳转到 final 标签 } // Validate rows in the read set and write set - if (readSetSize > 0) { - if (!ValidateReadSet(txMan)) { - rc = RC_ABORT; - goto final; + if (readSetSize > 0) { // 如果读取集合大小大于0 + if (!ValidateReadSet(txMan)) { // 调用验证读取集合函数 + rc = RC_ABORT; // 验证失败,设置返回代码为 RC_ABORT + goto final; // 跳转到 final 标签 } } - if (!ValidateWriteSet(txMan)) { - rc = RC_ABORT; - goto final; + if (!ValidateWriteSet(txMan)) { // 验证写入集合 + rc = RC_ABORT; // 验证失败,设置返回代码为 RC_ABORT + goto final; // 跳转到 final 标签 } // Pre-allocate stable row according to the checkpoint state. - if (!PreAllocStableRow(txMan)) { - rc = RC_MEMORY_ALLOCATION_ERROR; - goto final; + if (!PreAllocStableRow(txMan)) { // 调用预分配稳定行函数 + rc = RC_MEMORY_ALLOCATION_ERROR; // 预分配失败,设置返回代码为 RC_MEMORY_ALLOCATION_ERROR + goto final; // 跳转到 final 标签 } -final: - if (likely(rc == RC_OK)) { - MOT_ASSERT(numSentinelLock == m_writeSetSize); - m_rowsLocked = true; - } else { - ReleaseHeaderLocks(txMan, numSentinelLock); - if (likely(rc == RC_ABORT)) { - m_abortsCounter++; +final: // final 标签,用于处理最后的清理和返回 + if (likely(rc == RC_OK)) { // 如果返回代码为 RC_OK + MOT_ASSERT(numSentinelLock == m_writeSetSize); // 断言锁定的 Sentinel 数量与写入集合大小相等 + m_rowsLocked = true; // 设置行已锁定为 true + } else { // 如果返回代码不为 RC_OK + ReleaseHeaderLocks(txMan, numSentinelLock); // 释放头信息锁定 + if (likely(rc == RC_ABORT)) { // 如果返回代码为 RC_ABORT + m_abortsCounter++; // 增加中止计数器 } } - return rc; + return rc; // 返回操作结果的代码 } -void OccTransactionManager::RollbackInserts(TxnManager* txMan) +/* + * 功能:回滚插入操作 + * + * 参数列表: + * txMan:事务管理器指针,包含需要回滚的插入操作 + * + * 注意: + * 此函数用于回滚事务中的插入操作,通过调用事务管理器的 UndoInserts 函数实现。 + * 插入操作的回滚通常涉及释放分配的资源,将其状态恢复到事务开始之前的状态。 + */ +void OccTransactionManager::RollbackInserts(TxnManager *txMan) { return txMan->UndoInserts(); } -void OccTransactionManager::ApplyWrite(TxnManager* txMan) +/* + * 功能:应用写入操作 + * + * 参数列表: + * txMan:事务管理器指针,包含需要应用的写入操作 + * + * 注意: + * 如果启用检查点,此函数用于应用事务中的写入操作。 + * 它遍历有序的访问集合,对每个需要应用的写入操作执行应用操作。 + * 对于主要 Sentinel,传递实际的全局行(access->GetRowFromHeader()), + * 以便稳定行具有与修改之前的原始行相同的 CSN、rowid 等属性。 + */ +void OccTransactionManager::ApplyWrite(TxnManager *txMan) { - if (GetGlobalConfiguration().m_enableCheckpoint) { - TxnOrderedSet_t& orderedSet = txMan->m_accessMgr->GetOrderedRowSet(); - for (const auto& raPair : orderedSet) { - const Access* access = raPair.second; - if (access->m_type == RD) { + if (GetGlobalConfiguration().m_enableCheckpoint) { // 如果启用检查点 + TxnOrderedSet_t &orderedSet = txMan->m_accessMgr->GetOrderedRowSet(); // 获取有序的访问集合 + for (const auto &raPair : orderedSet) { // 遍历有序的访问集合 + const Access *access = raPair.second; // 获取访问对象 + if (access->m_type == RD) { // 如果操作类型是读取,则跳过 continue; } - if (access->m_params.IsPrimarySentinel()) { + if (access->m_params.IsPrimarySentinel()) { // 如果是主要 Sentinel // Pass the actual global row (access->GetRowFromHeader()), so that the stable row will have the // same CSN, rowid, etc as the original row before the modifications are applied. GetCheckpointManager()->ApplyWrite(txMan, access->GetRowFromHeader(), access->m_type); @@ -402,32 +594,44 @@ void OccTransactionManager::ApplyWrite(TxnManager* txMan) } } } - -void OccTransactionManager::WriteChanges(TxnManager* txMan) +/* + * 功能:写入事务中的更改 + * + * 参数列表: + * txMan:事务管理器指针,包含需要写入的更改 + * + * 注意: + * 此函数用于写入事务中的更改,包括锁定行、应用写入、更新全局行的 CSN 以及处理插入操作等步骤。 + * 它会遍历有序的访问集合,对每个访问执行相应的更改操作。 + */ +void OccTransactionManager::WriteChanges(TxnManager *txMan) { - if (m_writeSetSize == 0 && m_insertSetSize == 0) { + if (m_writeSetSize == 0 && m_insertSetSize == 0) { // 如果写入集合和插入集合大小都为0,则直接返回 return; } - LockRows(txMan, m_rowsSetSize); + LockRows(txMan, m_rowsSetSize); // 锁定行 // Stable rows for checkpoint needs to be created (copied from original row) before modifying the global rows. - ApplyWrite(txMan); + ApplyWrite(txMan); // 应用写入 - TxnOrderedSet_t& orderedSet = txMan->m_accessMgr->GetOrderedRowSet(); + TxnOrderedSet_t &orderedSet = txMan->m_accessMgr->GetOrderedRowSet(); // 获取有序的访问集合 // Update CSN with all relevant information on global rows // For deletes invalidate sentinels - rows still locked! - for (const auto& raPair : orderedSet) { - const Access* access = raPair.second; - access->GetRowFromHeader()->m_rowHeader.WriteChangesToRow(access, txMan->GetCommitSequenceNumber()); + // 更新全局行的 CSN,对于删除操作,使 Sentinel 失效(仍然锁定) + for (const auto &raPair : orderedSet) { // 遍历有序的访问集合 + const Access *access = raPair.second; // 获取访问对象 + access->GetRowFromHeader()->m_rowHeader.WriteChangesToRow( + access, txMan->GetCommitSequenceNumber()); // 更新全局行的 CSN } // Treat Inserts - if (m_insertSetSize > 0) { - for (const auto& raPair : orderedSet) { - Access* access = raPair.second; - if (access->m_type != INS) { + // 处理插入操作 + if (m_insertSetSize > 0) { // 如果插入集合大小大于0 + for (const auto &raPair : orderedSet) { // 再次遍历有序的访问集合 + Access *access = raPair.second; // 获取访问对象 + if (access->m_type != INS) { // 如果操作类型不是插入,则跳过 continue; } MOT_ASSERT(access->m_origSentinel->IsLocked() == true); @@ -453,15 +657,12 @@ void OccTransactionManager::WriteChanges(TxnManager* txMan) * Save previous row in the access! * We need it for the row release! */ - Row* row = access->GetRowFromHeader(); + Row *row = access->GetRowFromHeader(); access->m_localInsertRow = row; access->m_origSentinel->SetNextPtr(access->m_auxRow); // Add row to GC! - txMan->GetGcSession()->GcRecordObject(row->GetTable()->GetPrimaryIndex()->GetIndexId(), - row, - nullptr, - Row::RowDtor, - ROW_SIZE_FROM_POOL(row->GetTable())); + txMan->GetGcSession()->GcRecordObject(row->GetTable()->GetPrimaryIndex()->GetIndexId(), row, + nullptr, Row::RowDtor, ROW_SIZE_FROM_POOL(row->GetTable())); } else { // Set Sentinel for access->m_origSentinel->SetNextPtr(access->m_auxRow->GetPrimarySentinel()); @@ -475,37 +676,49 @@ void OccTransactionManager::WriteChanges(TxnManager* txMan) } // Treat Inserts - if (m_insertSetSize > 0) { - for (const auto& raPair : orderedSet) { - const Access* access = raPair.second; - if (access->m_type != INS) { + if (m_insertSetSize > 0) { // 如果插入集合大小大于0 + for (const auto &raPair : orderedSet) { // 再次遍历有序的访问集合 + const Access *access = raPair.second; // 获取访问对象 + if (access->m_type != INS) { // 如果操作类型不是插入,则跳过 continue; } - access->m_origSentinel->UnSetDirty(); + access->m_origSentinel->UnSetDirty(); // 取消设置脏标志 } } - CleanRowsFromIndexes(txMan); + CleanRowsFromIndexes(txMan); // 从索引中清除行 } -void OccTransactionManager::CleanRowsFromIndexes(TxnManager* txMan) +/* + * 功能:从索引中清除行 + * + * 参数列表: + * txMan:事务管理器指针,包含需要清除的行 + * + * 注意: + * 此函数用于从索引中清除事务中的行,特别是删除操作。 + * 它遍历有序的访问集合,对每个删除操作执行清除操作。 + * 清除操作包括从表中减少行数、从索引中移除键等。 + */ +void OccTransactionManager::CleanRowsFromIndexes(TxnManager *txMan) { - if (m_deleteSetSize == 0) { + if (m_deleteSetSize == 0) { // 如果删除集合大小为0,则直接返回 return; } - TxnAccess* tx = txMan->m_accessMgr.Get(); - TxnOrderedSet_t& orderedSet = tx->GetOrderedRowSet(); + TxnAccess *tx = txMan->m_accessMgr.Get(); + TxnOrderedSet_t &orderedSet = tx->GetOrderedRowSet(); // 获取有序的访问集合 uint32_t numOfDeletes = m_deleteSetSize; - // use local counter to optimize - for (const auto& raPair : orderedSet) { - const Access* access = raPair.second; - if (access->m_type == DEL) { + // 使用本地计数器进行优化 + for (const auto &raPair : orderedSet) { // 遍历有序的访问集合 + const Access *access = raPair.second; // 获取访问对象 + if (access->m_type == DEL) { // 如果操作类型是删除 numOfDeletes--; - access->GetTxnRow()->GetTable()->UpdateRowCount(-1); + access->GetTxnRow()->GetTable()->UpdateRowCount(-1); // 从表中减少行数 MOT_ASSERT(access->m_params.IsUpgradeInsert() == false); // Use Txn Row as row may change INSERT after DELETE leaves residue - txMan->RemoveKeyFromIndex(access->GetTxnRow(), access->m_origSentinel); + // 使用事务行,因为在删除操作后,插入操作可能会留下残余 + txMan->RemoveKeyFromIndex(access->GetTxnRow(), access->m_origSentinel); // 从索引中移除键 } if (!numOfDeletes) { break; @@ -513,24 +726,36 @@ void OccTransactionManager::CleanRowsFromIndexes(TxnManager* txMan) } } -void OccTransactionManager::ReleaseHeaderLocks(TxnManager* txMan, uint32_t numOfLocks) +/* + * 功能:释放头信息的锁 + * + * 参数列表: + * txMan:事务管理器指针,包含需要释放锁的头信息 + * numOfLocks:需要释放的锁的数量 + * + * 注意: + * 此函数用于释放事务中头信息的锁,特别是在发生回滚或释放锁时。 + * 它遍历有序的访问集合,对每个需要释放锁的头信息执行释放操作。 + */ +void OccTransactionManager::ReleaseHeaderLocks(TxnManager *txMan, uint32_t numOfLocks) { - if (numOfLocks == 0) { + if (numOfLocks == 0) { // 如果需要释放的锁的数量为0,则直接返回 return; } - TxnAccess* tx = txMan->m_accessMgr.Get(); - TxnOrderedSet_t& orderedSet = tx->GetOrderedRowSet(); + TxnAccess *tx = txMan->m_accessMgr.Get(); + TxnOrderedSet_t &orderedSet = tx->GetOrderedRowSet(); // 获取有序的访问集合 // use local counter to optimize - for (const auto& raPair : orderedSet) { - const Access* access = raPair.second; - if (access->m_type == RD) { + // 使用本地计数器进行优化 + for (const auto &raPair : orderedSet) { // 遍历有序的访问集合 + const Access *access = raPair.second; // 获取访问对象 + if (access->m_type == RD) { // 如果操作类型是读取,则继续下一次循环 continue; } else { numOfLocks--; - access->m_origSentinel->Release(); + access->m_origSentinel->Release(); // 释放 Sentinel 锁 if (access->m_params.IsPrimaryUpgrade()) { - access->m_auxRow->m_rowHeader.Release(); + access->m_auxRow->m_rowHeader.Release(); // 释放辅助行的锁 } } if (!numOfLocks) { @@ -539,29 +764,39 @@ void OccTransactionManager::ReleaseHeaderLocks(TxnManager* txMan, uint32_t numOf } } -void OccTransactionManager::ReleaseRowsLocks(TxnManager* txMan, uint32_t numOfLocks) +/* + * 功能:释放行的锁 + * + * 参数列表: + * txMan:事务管理器指针,包含需要释放锁的行 + * numOfLocks:需要释放的锁的数量 + * + * 注意: + * 此函数用于释放事务中行的锁,特别是在发生回滚或释放锁时。 + * 它遍历有序的访问集合,对每个需要释放锁的行执行释放操作。 + */ +void OccTransactionManager::ReleaseRowsLocks(TxnManager *txMan, uint32_t numOfLocks) { - if (numOfLocks == 0) { + if (numOfLocks == 0) { // 如果需要释放的锁的数量为0,则直接返回 return; } - TxnAccess* tx = txMan->m_accessMgr.Get(); - TxnOrderedSet_t& orderedSet = tx->GetOrderedRowSet(); - - // use local counter to optimize - for (const auto& raPair : orderedSet) { - const Access* access = raPair.second; - if (access->m_type == RD) { + TxnAccess *tx = txMan->m_accessMgr.Get(); + TxnOrderedSet_t &orderedSet = tx->GetOrderedRowSet(); // 获取有序的访问集合 + // 使用本地计数器进行优化 + for (const auto &raPair : orderedSet) { // 遍历有序的访问集合 + const Access *access = raPair.second; // 获取访问对象 + if (access->m_type == RD) { // 如果操作类型是读取,则继续下一次循环 continue; } - if (access->m_params.IsPrimarySentinel()) { + if (access->m_params.IsPrimarySentinel()) { // 如果是主要的 Sentinel numOfLocks--; - access->GetRowFromHeader()->m_rowHeader.Release(); - if (access->m_params.IsUpgradeInsert()) { - // This is the global row that we switched! - // Currently it's in the gc! - access->m_localInsertRow->m_rowHeader.Release(); + access->GetRowFromHeader()->m_rowHeader.Release(); // 释放行的锁 + if (access->m_params.IsUpgradeInsert()) { // 如果是升级插入操作 + // 这是我们切换的全局行! + // 目前它在垃圾回收中! + access->m_localInsertRow->m_rowHeader.Release(); // 释放插入行的锁 } } if (!numOfLocks) { @@ -570,10 +805,19 @@ void OccTransactionManager::ReleaseRowsLocks(TxnManager* txMan, uint32_t numOfLo } } +/* + * 功能:清理事务管理器的状态 + * + * 参数列表: + * 无参数 + * + * 注意: + * 此函数用于清理事务管理器的状态,将相关计数器重置为零,以准备处理下一个事务。 + */ void OccTransactionManager::CleanUp() { - m_writeSetSize = 0; - m_insertSetSize = 0; - m_rowsSetSize = 0; + m_writeSetSize = 0; // 重置写集合大小为0 + m_insertSetSize = 0; // 重置插入集合大小为0 + m_rowsSetSize = 0; // 重置行集合大小为0 } } // namespace MOT diff --git a/src/gausskernel/storage/mot/core/concurrency_control/row_header.cpp b/src/gausskernel/storage/mot/core/concurrency_control/row_header.cpp index ee10b1664..967cd4d3b 100644 --- a/src/gausskernel/storage/mot/core/concurrency_control/row_header.cpp +++ b/src/gausskernel/storage/mot/core/concurrency_control/row_header.cpp @@ -34,9 +34,25 @@ namespace MOT { DECLARE_LOGGER(RowHeader, ConcurrenyControl); - -RC RowHeader::GetLocalCopy( - TxnAccess* txn, AccessType type, Row* localRow, const Row* origRow, TransactionId& lastTid) const +/* + * 功能:获取行的本地副本 + * + * 参数列表: + * txn:事务访问对象指针,包含当前事务的信息 + * type:访问类型,指示操作类型(例如:读取、写入、插入等) + * localRow:用于存储本地副本的行对象指针 + * origRow:原始行对象的指针,从中获取要复制的内容 + * lastTid:用于存储最后一个事务标识符的引用 + * + * 返回值: + * 如果成功获取本地副本,返回 RC_OK;否则返回 RC_ABORT。 + * + * 注意: + * 此函数用于获取行的本地副本,以支持并发事务。它执行了多次尝试,直到成功获取行的本地副本。 + * 在获取本地副本时,会检查行的状态和锁,以确保安全地访问和修改行的内容。 + */ +RC RowHeader::GetLocalCopy(TxnAccess *txn, AccessType type, Row *localRow, const Row *origRow, + TransactionId &lastTid) const { uint64_t sleepTime = 1; uint64_t v = 0; @@ -85,11 +101,37 @@ RC RowHeader::GetLocalCopy( return RC_OK; } +/* + * 功能:验证写操作的事务标识符 + * + * 参数列表: + * tid:事务标识符,用于与行的当前事务标识符进行比较 + * + * 返回值: + * 如果给定的事务标识符与行的当前事务标识符匹配,返回true;否则返回false。 + * + * 注意: + * 此函数用于验证写操作的事务标识符是否与行的当前事务标识符匹配。 + * 如果匹配,表示可以进行写操作;如果不匹配,表示其他事务已经修改了行。 + */ bool RowHeader::ValidateWrite(TransactionId tid) const { return (tid == GetCSN()); } +/* + * 功能:验证读操作的事务标识符 + * + * 参数列表: + * tid:事务标识符,用于与行的当前事务标识符进行比较 + * + * 返回值: + * 如果给定的事务标识符与行的当前事务标识符匹配且行未被锁定,返回true;否则返回false。 + * + * 注意: + * 此函数用于验证读操作的事务标识符是否有效。它检查行是否已被锁定,如果锁定则返回false, + * 否则检查给定的事务标识符是否与行的当前事务标识符匹配。 + */ bool RowHeader::ValidateRead(TransactionId tid) const { if (IsLocked() or (tid != GetCSN())) { @@ -98,10 +140,19 @@ bool RowHeader::ValidateRead(TransactionId tid) const return true; } - -void RowHeader::WriteChangesToRow(const Access* access, uint64_t csn) +/* + * 功能:将更改写入行 + * + * 参数列表: + * access:访问对象指针,包含要写入行的更改信息 + * csn:提交序列号,用于标识写入的版本 + * + * 注意: + * 此函数用于将更改写入行。它根据访问类型执行不同的操作,包括复制数据、设置事务标识符等。 + */ +void RowHeader::WriteChangesToRow(const Access *access, uint64_t csn) { - Row* row = access->GetRowFromHeader(); + Row *row = access->GetRowFromHeader(); AccessType type = access->m_type; if (type == RD) { @@ -112,8 +163,8 @@ void RowHeader::WriteChangesToRow(const Access* access, uint64_t csn) uint64_t v = m_csnWord; if (!MOTEngine::GetInstance()->IsRecovering()) { if (!(csn > GetCSN() && (v & LOCK_BIT))) { - MOT_LOG_ERROR( - "csn=%ld, v & LOCK_BIT=%ld, v & (~LOCK_BIT)=%ld\n", csn, (v & LOCK_BIT), (v & (~LOCK_BIT))); + MOT_LOG_ERROR("csn=%ld, v & LOCK_BIT=%ld, v & (~LOCK_BIT)=%ld\n", csn, (v & LOCK_BIT), + (v & (~LOCK_BIT))); MOT_ASSERT(false); } } @@ -152,7 +203,14 @@ void RowHeader::WriteChangesToRow(const Access* access, uint64_t csn) break; } } - +/* + * 功能:锁定行头信息 + * + * 注意: + * 此函数用于锁定行头信息,以确保其他事务无法同时修改该行。 + * 它使用原子操作来设置行头中的锁定位(LOCK_BIT)。 + * 如果行头已经被锁定,则会等待直到成功锁定。 + */ void RowHeader::Lock() { uint64_t v = m_csnWord; diff --git a/src/gausskernel/storage/mot/core/infra/config/cmdline_config_loader.cpp b/src/gausskernel/storage/mot/core/infra/config/cmdline_config_loader.cpp index e707226f7..d62bfd00e 100644 --- a/src/gausskernel/storage/mot/core/infra/config/cmdline_config_loader.cpp +++ b/src/gausskernel/storage/mot/core/infra/config/cmdline_config_loader.cpp @@ -32,25 +32,45 @@ DECLARE_LOGGER(CmdLineConfigLoader, Configuration) static const char CMDLINE_SEP = '-'; -static bool ParseCmdLineSectionName(const mot_string& line, mot_string& sectionPath, mot_string& keyValuePart) +/* + * 功能:解析命令行中的部分名称 + * + * 参数列表: + * line:包含部分名称的输入字符串 + * sectionPath:用于存储部分路径的输出字符串 + * keyValuePart:用于存储键值部分的输出字符串 + * + * 返回值: + * 如果成功解析部分名称,则返回true;否则返回false。 + * + * 注意: + * 此函数用于从输入字符串中解析部分路径和键值部分。 + * 输入字符串应包含一个分隔符(CMDLINE_SEP),用于分隔部分路径和键值部分。 + * 如果解析成功,结果存储在sectionPath和keyValuePart中,并返回true。 + * 如果解析失败,将记录错误信息并返回false。 + */ +static bool ParseCmdLineSectionName(const mot_string &line, mot_string §ionPath, mot_string &keyValuePart) { bool result = false; + // 查找最后一个分隔符的位置 uint32_t lastSlashPos = line.find_last_of(CMDLINE_SEP); + if (lastSlashPos != mot_string::npos) { + // 将输入字符串的一部分复制到sectionPath中 if (!line.substr(sectionPath, 0, lastSlashPos) || !line.substr(keyValuePart, lastSlashPos + 1)) { + // 如果复制失败,记录内存分配错误 MOT_REPORT_ERROR(MOT_ERROR_OOM, "Load Configuration", "Failed to parse section name"); } else { + // 去除前后的空白字符 sectionPath.trim(); keyValuePart.trim(); - result = true; + result = true; // 解析成功 } } else { - MOT_REPORT_ERROR(MOT_ERROR_INVALID_CFG, - "Load Configuration", - "Malformed command line argument missing separator %c: %s", - CMDLINE_SEP, - line.c_str()); + // 如果找不到分隔符,记录配置无效的错误信息 + MOT_REPORT_ERROR(MOT_ERROR_INVALID_CFG, "Load Configuration", + "Malformed command line argument missing separator %c: %s", CMDLINE_SEP, line.c_str()); } return result; @@ -63,51 +83,100 @@ static bool ParseCmdLineSectionName(const mot_string& line, mot_string& sectionP break; \ } -static ConfigSection* GetCmdLineConfigSection( - const mot_string& sectionFullName, mot_list& parsedSections, ConfigSectionMap& sectionMap) +/* + * 功能:根据部分全名获取命令行配置部分 + * + * 参数列表: + * sectionFullName:要获取的部分的全名 + * parsedSections:已解析的部分列表,用于存储已解析的部分对象 + * sectionMap:部分映射,将部分全名映射到部分对象 + * + * 返回值: + * 如果成功获取或创建部分对象,则返回该部分对象的指针;否则返回nullptr。 + * + * 注意: + * 此函数用于根据部分的全名获取相应的部分对象。如果部分已存在于sectionMap中, + * 则直接返回该部分对象;否则,根据全名创建一个新的部分对象并将其插入sectionMap中。 + * 如果出现错误(如内存分配失败或无法解析部分名称),将记录错误信息并返回nullptr。 + */ +static ConfigSection *GetCmdLineConfigSection(const mot_string §ionFullName, + mot_list &parsedSections, ConfigSectionMap §ionMap) { mot_string sectionPath; mot_string sectionName; + + // 尝试查找部分全名是否已在sectionMap中 ConfigSectionMap::iterator itr = sectionMap.find(sectionFullName); + if (itr == sectionMap.end()) { + // 如果部分全名不存在于sectionMap中 if (!ConfigFileParser::BreakSectionName(sectionFullName, sectionPath, sectionName, CMDLINE_SEP)) { + // 无法解析部分名称,记录错误并返回nullptr MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Load Configuration", "Failed to parse section name"); return nullptr; } - ConfigSection* currentSection = ConfigSection::CreateConfigSection(sectionPath.c_str(), sectionName.c_str()); + // 创建新的配置部分对象 + ConfigSection *currentSection = ConfigSection::CreateConfigSection(sectionPath.c_str(), sectionName.c_str()); if (currentSection == nullptr) { - MOT_REPORT_ERROR( - MOT_ERROR_OOM, "Load Configuration", "Failed to allocate memory for configuration section"); + // 内存分配失败,记录错误并返回nullptr + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Load Configuration", + "Failed to allocate memory for configuration section"); return nullptr; } + // 将新创建的部分对象添加到parsedSections列表中 if (!parsedSections.push_back(currentSection)) { + // 插入失败,记录错误并返回nullptr MOT_REPORT_ERROR(MOT_ERROR_OOM, "Load Configuration", "Failed to insert parsed section"); return nullptr; } + // 将新创建的部分对象插入sectionMap中,以便下次可以直接获取 itr = sectionMap.insert(ConfigSectionMap::value_type(sectionFullName, currentSection)).first; } + // 返回获取到的部分对象 return itr->second; } - -static bool AddCmdLineArrayConfigItem(ConfigSection* currentSection, const mot_string& sectionFullName, - const mot_string& key, const mot_string& value, uint64_t arrayIndex) +/* + * 功能:添加命令行数组配置项 + * + * 参数列表: + * currentSection:当前部分对象,表示正在加载的部分 + * sectionFullName:部分的全名,用于唯一标识部分 + * key:配置项的键名称 + * value:配置项的值 + * arrayIndex:数组索引,用于指示配置项在数组中的位置 + * + * 返回值: + * 如果成功添加配置项,则返回true;否则返回false。 + * + * 注意: + * 此函数用于添加命令行中的数组配置项到配置部分中。 + * 如果指定的数组配置项不存在,将会创建一个新的数组。 + * 如果创建失败或添加配置项失败,将记录错误信息并返回false。 + * 此外,还会检查数组项的顺序是否正确,以确保它们按顺序添加到数组中。 + */ +static bool AddCmdLineArrayConfigItem(ConfigSection *currentSection, const mot_string §ionFullName, + const mot_string &key, const mot_string &value, uint64_t arrayIndex) { // create array if not created yet - ConfigArray* configArray = currentSection->ModifyConfigArray(key.c_str()); + ConfigArray *configArray = currentSection->ModifyConfigArray(key.c_str()); if (configArray == nullptr) { + // 创建新的数组配置项 configArray = ConfigArray::CreateConfigArray(sectionFullName.c_str(), key.c_str()); if (configArray == nullptr) { + // 内存分配失败,记录错误并返回false MOT_REPORT_ERROR(MOT_ERROR_OOM, "Load Configuration", "Failed to allocate memory for configuration array"); return false; } + // 将数组配置项添加到当前部分 if (!currentSection->AddConfigItem(configArray)) { - MOT_REPORT_ERROR( - MOT_ERROR_OOM, "Load Configuration", "Failed to add configuration array to parent section"); + // 添加失败,记录错误并返回false + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Load Configuration", + "Failed to add configuration array to parent section"); return false; } } @@ -115,42 +184,56 @@ static bool AddCmdLineArrayConfigItem(ConfigSection* currentSection, const mot_s // create item and add it to array if (arrayIndex != configArray->GetConfigItemCount()) { // array items must be ordered - MOT_REPORT_ERROR(MOT_ERROR_INVALID_CFG, - "Load Configuration", + MOT_REPORT_ERROR( + MOT_ERROR_INVALID_CFG, "Load Configuration", "Failed to parse command line arguments: array %s items not well-ordered, expecting %u, got %" PRIu64, - configArray->GetName(), - configArray->GetConfigItemCount(), - arrayIndex); + configArray->GetName(), configArray->GetConfigItemCount(), arrayIndex); return false; } - ConfigItem* configItem = ConfigFileParser::MakeArrayConfigValue(sectionFullName, arrayIndex, value); + // 创建配置项并添加到数组中 + ConfigItem *configItem = ConfigFileParser::MakeArrayConfigValue(sectionFullName, arrayIndex, value); if (configItem == nullptr) { - MOT_REPORT_ERROR(MOT_ERROR_OOM, - "Load Configuration", - "Failed to create array configuration value from raw value: %s", - value.c_str()); + // 创建配置项失败,记录错误并返回false + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Load Configuration", + "Failed to create array configuration value from raw value: %s", value.c_str()); return false; } - + // 将配置项添加到数组中 if (!configArray->AddConfigItem(configItem)) { - MOT_REPORT_ERROR(MOT_ERROR_OOM, - "Load Configuration", - "Failed to add %" PRIu64 "th item to configuration array %s", - arrayIndex, - configArray->GetName()); + // 添加配置项失败,记录错误并返回false + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Load Configuration", + "Failed to add %" PRIu64 "th item to configuration array %s", arrayIndex, + configArray->GetName()); return false; } + // 配置项添加成功 return true; } - -ConfigTree* CmdLineConfigLoader::ParseCmdLine(char** argv, int argc) +/* + * 功能:解析命令行参数并构建配置树 + * + * 参数列表: + * argv:命令行参数数组 + * argc:命令行参数数量 + * + * 返回值: + * 如果成功解析命令行参数并构建配置树,则返回配置树指针;否则返回nullptr。 + * + * 注意: + * 此函数用于解析命令行参数,并构建一个配置树来表示这些参数。 + * 命令行参数应遵循格式:--section1-section2-section3-key=value。 + * 如果解析过程中出现错误(如内存分配失败、解析失败等),将记录错误信息并返回nullptr。 + */ +ConfigTree *CmdLineConfigLoader::ParseCmdLine(char **argv, int argc) { // section names are separated by dashes, it is expected to have the format: // --section1-section2-section3-key=value - ConfigTree* cfgTree = ConfigTree::CreateConfigTree(GetPriority(), GetName(), true); + // 创建配置树对象,表示命令行参数 + ConfigTree *cfgTree = ConfigTree::CreateConfigTree(GetPriority(), GetName(), true); if (cfgTree == nullptr) { + // 创建配置树失败,记录错误并返回nullptr MOT_REPORT_ERROR(MOT_ERROR_OOM, "Load Configuration", "Failed to create configuration tree"); return nullptr; } @@ -158,8 +241,8 @@ ConfigTree* CmdLineConfigLoader::ParseCmdLine(char** argv, int argc) mot_string line; mot_string sectionFullName; mot_string keyValuePart; - ConfigSection* currentSection = nullptr; - mot_list parsedSections; + ConfigSection *currentSection = nullptr; + mot_list parsedSections; ConfigSectionMap sectionMap; mot_string key; mot_string value; @@ -168,16 +251,20 @@ ConfigTree* CmdLineConfigLoader::ParseCmdLine(char** argv, int argc) bool hasArrayIndex = false; for (int i = 0; i < argc && !parseError; ++i) { + // 将命令行参数转换为字符串对象 if (!line.assign(argv[i])) { - CMDLINE_REPORT_PARSE_ERROR_AND_BREAK( - MOT_ERROR_OOM, "Failed to allocate memory for next command line argument"); + CMDLINE_REPORT_PARSE_ERROR_AND_BREAK(MOT_ERROR_OOM, + "Failed to allocate memory for next command line argument"); } + // 检查命令行参数是否以"--"开头且长度不小于2 if ((line.length() <= 2) || line[0] != '-' || line[1] != '-') { + // 忽略格式不正确的命令行参数 MOT_LOG_TRACE("Skipping ill-formed command line argument: %s", argv[i]); continue; } + // 解析命令行参数的部分名称 if (!ParseCmdLineSectionName(line, sectionFullName, keyValuePart)) { CMDLINE_REPORT_PARSE_ERROR_AND_BREAK(MOT_ERROR_INTERNAL, "Failed to parse command line argument"); } @@ -196,31 +283,36 @@ ConfigTree* CmdLineConfigLoader::ParseCmdLine(char** argv, int argc) // check for array item if (!hasArrayIndex) { - ConfigItem* configItem = ConfigFileParser::MakeConfigValue(sectionFullName, key, value); + // 创建配置值对象并添加到部分中 + ConfigItem *configItem = ConfigFileParser::MakeConfigValue(sectionFullName, key, value); if (configItem == nullptr) { + // 创建配置项失败,记录错误并返回false CMDLINE_REPORT_PARSE_ERROR_AND_BREAK(MOT_ERROR_INTERNAL, - "Failed to create configuration value from raw key/value: %s/%s", - key.c_str(), - value.c_str()); + "Failed to create configuration value from raw key/value: %s/%s", + key.c_str(), value.c_str()); } else if (!currentSection->AddConfigItem(configItem)) { - CMDLINE_REPORT_PARSE_ERROR_AND_BREAK( - MOT_ERROR_OOM, "Failed to add configuration value to parent section"); + // 添加配置项失败,记录错误并返回false + CMDLINE_REPORT_PARSE_ERROR_AND_BREAK(MOT_ERROR_OOM, + "Failed to add configuration value to parent section"); } } else { + // 添加数组配置项 if (!AddCmdLineArrayConfigItem(currentSection, sectionFullName, key, value, arrayIndex)) { - CMDLINE_REPORT_PARSE_ERROR_AND_BREAK(MOT_ERROR_INTERNAL, - "Failed to add array item with arrayIndex %lu (command line argument: %s)", - arrayIndex, - line.c_str()); + // 添加数组项失败,记录错误并返回false + CMDLINE_REPORT_PARSE_ERROR_AND_BREAK( + MOT_ERROR_INTERNAL, "Failed to add array item with arrayIndex %lu (command line argument: %s)", + arrayIndex, line.c_str()); } } } if (parseError) { + // 解析过程中出现错误,释放配置树并返回nullptr delete cfgTree; cfgTree = nullptr; } else { if (!cfgTree->Build(parsedSections)) { + // 构建配置树失败,记录错误并释放配置树 MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Load Configuration", "Failed to build configuration tree"); delete cfgTree; cfgTree = nullptr; diff --git a/src/gausskernel/storage/mot/core/infra/config/config_array.cpp b/src/gausskernel/storage/mot/core/infra/config/config_array.cpp index 697660ba2..0a29fdf65 100644 --- a/src/gausskernel/storage/mot/core/infra/config/config_array.cpp +++ b/src/gausskernel/storage/mot/core/infra/config/config_array.cpp @@ -30,7 +30,18 @@ ConfigArray::ConfigArray() : ConfigItem(ConfigItemClass::CONFIG_ITEM_ARRAY) ConfigArray::~ConfigArray() {} - +/* + * 功能:打印配置数组的内容 + * + * 参数列表: + * logLevel:日志级别,指定打印日志的级别 + * fullPrint:是否完整打印,如果为false,则只打印部分名称,否则打印完整内容 + * + * 注意: + * 此函数用于打印配置数组的内容。可以选择是否打印完整的数组内容。 + * 如果fullPrint为false,将仅打印部分名称,否则将打印完整内容。 + * 打印的内容包括数组的部分名称和数组项的值。 + */ void ConfigArray::Print(LogLevel logLevel, bool fullPrint) const { // print indented section name @@ -43,28 +54,39 @@ void ConfigArray::Print(LogLevel logLevel, bool fullPrint) const if (!fullPrint) { MOT_LOG(logLevel, "%*s%s[%u]", GetDepth(), "", GetName(), i); } + // 打印数组项的值 mItemArray[i]->Print(logLevel, fullPrint); } } -void ConfigArray::ForEach(ConfigItemVisitor& visitor) const +/* + * 功能:打印配置数组的内容 + * + * 参数列表: + * logLevel:日志级别,指定打印日志的级别 + * fullPrint:是否完整打印,如果为false,则只打印部分名称,否则打印完整内容 + * + * 注意: + * 此函数用于打印配置数组的内容。可以选择是否打印完整的数组内容。 + * 如果fullPrint为false,将仅打印部分名称,否则将打印完整内容。 + * 打印的内容包括数组的部分名称和数组项的值。 + */ +void ConfigArray::Print(LogLevel logLevel, bool fullPrint) const { - visitor.OnConfigItem(this); + // 打印缩进的部分名称 + if (!fullPrint) { + MOT_LOG(logLevel, "%*s%s", GetDepth(), "", GetName()); + } + + // 打印数组项的值 for (uint32_t i = 0; i < mItemArray.size(); ++i) { - const ConfigItem* configItem = mItemArray[i]; - switch (configItem->GetClass()) { - case ConfigItemClass::CONFIG_ITEM_SECTION: - static_cast(configItem)->ForEach(visitor); - break; - - case ConfigItemClass::CONFIG_ITEM_ARRAY: - static_cast(configItem)->ForEach(visitor); - break; - - default: - visitor.OnConfigItem(configItem); - break; + if (!fullPrint) { + MOT_LOG(logLevel, "%*s%s[%u]", GetDepth(), "", GetName(), i); } + + // 打印数组项的值 + mItemArray[i]->Print(logLevel, fullPrint); } } + } // namespace MOT diff --git a/src/gausskernel/storage/mot/core/infra/config/config_file_loader.cpp b/src/gausskernel/storage/mot/core/infra/config/config_file_loader.cpp index 83171dacc..32c107f46 100644 --- a/src/gausskernel/storage/mot/core/infra/config/config_file_loader.cpp +++ b/src/gausskernel/storage/mot/core/infra/config/config_file_loader.cpp @@ -29,28 +29,57 @@ namespace MOT { DECLARE_LOGGER(ConfigFileLoader, Configuration) -static void FormatTime(uint64_t timeVal, char* buf, uint32_t len) +/* + * 功能:将时间值格式化为字符串 + * + * 参数列表: + * timeVal:时间值,以纳秒为单位 + * buf:用于存储格式化后的时间字符串的缓冲区 + * len:缓冲区长度 + * + * 注意: + * 此函数用于将时间值格式化为字符串,并存储在给定的缓冲区中。 + * 时间值以纳秒为单位,并转换为日期时间格式("%F %T.%09ld")。 + * 如果转换成功,结果存储在buf中;否则不进行任何操作。 + */ +static void FormatTime(uint64_t timeVal, char *buf, uint32_t len) { + // 将纳秒时间值转换为timespec结构 timespec ts = {(long int)(timeVal / 1000000000ULL), (long int)(timeVal % 1000000000ULL)}; struct tm t; + // 使用localtime_r函数将时间值转换为tm结构 if (localtime_r(&(ts.tv_sec), &t) != NULL) { + // 使用strftime函数将tm结构格式化为日期时间字符串 size_t ret = strftime(buf, len, "%F %T", &t); if (ret != 0) { + // 如果strftime成功,继续添加纳秒部分 len -= ret; errno_t erc = snprintf_s(buf + ret, len, len - 1, ".%09ld", ts.tv_nsec); + // 检查snprintf_s的返回值 securec_check_ss(erc, "\0", "\0"); } } } -ConfigFileLoader::ConfigFileLoader( - const char* typeName, const char* name, uint32_t priority, const char* configFilePath) +ConfigFileLoader::ConfigFileLoader(const char *typeName, const char *name, uint32_t priority, + const char *configFilePath) : ConfigLoader(ComposeFullName(typeName, name, configFilePath).c_str(), priority), m_configFilePath(configFilePath), m_lastModTime(GetFileModificationTime()) {} +/* + * 功能:检查配置文件是否已更改 + * + * 返回值: + * 如果配置文件的修改时间较上次检查发生了更改,则返回true;否则返回false。 + * + * 注意: + * 此函数用于检查配置文件的修改时间是否与上次检查不同。 + * 如果修改时间发生更改,将记录日志信息,更新上次检查的时间,并返回true; + * 否则,返回false。 + */ bool ConfigFileLoader::HasChanged() { bool result = false; @@ -58,35 +87,68 @@ bool ConfigFileLoader::HasChanged() if (modTime > m_lastModTime) { char lastDate[32]; char newDate[32]; + + // 格式化上次检查的修改时间和新的修改时间 FormatTime(m_lastModTime, lastDate, sizeof(lastDate)); FormatTime(modTime, newDate, sizeof(newDate)); + + // 记录配置文件发生更改的日志信息 MOT_LOG_INFO("Detected change in configuration file: %s (modification time changed: %" PRIu64 " --> %" PRIu64 " [%s --> %s])", - m_configFilePath.c_str(), - m_lastModTime, - modTime, - lastDate, - newDate); + m_configFilePath.c_str(), m_lastModTime, modTime, lastDate, newDate); + + // 更新上次检查的修改时间 m_lastModTime = modTime; result = true; } return result; } -mot_string ConfigFileLoader::ComposeFullName(const char* typeName, const char* name, const char* configFilePath) +/* + * 功能:组合完整的配置项名称 + * + * 参数列表: + * typeName:配置项类型的名称 + * name:配置项的名称 + * configFilePath:配置文件的路径 + * + * 返回值: + * 返回组合后的完整配置项名称(mot_string对象)。 + * + * 注意: + * 此函数用于将配置项的类型名称、名称和配置文件路径组合成一个完整的配置项名称。 + * 配置项名称的格式为:"[]@"。 + */ +mot_string ConfigFileLoader::ComposeFullName(const char *typeName, const char *name, const char *configFilePath) { mot_string result; + // 使用mot_string的format方法将类型名称、名称和配置文件路径组合成完整的配置项名称 result.format("%s[%s]@%s", typeName, name, configFilePath); return result; } +/* + * 功能:获取配置文件的修改时间 + * + * 返回值: + * 返回配置文件的修改时间,以纳秒为单位。 + * + * 注意: + * 此函数用于获取配置文件的修改时间,并将其转换为纳秒为单位的时间值。 + * 如果成功获取配置文件的修改时间,将返回时间值;否则返回0。 + */ uint64_t ConfigFileLoader::GetFileModificationTime() { uint64_t filetime = 0; struct stat buf; + + // 使用stat函数获取文件的信息,包括修改时间 if (stat(m_configFilePath.c_str(), &buf) == 0) { + // 计算纳秒级别的修改时间 filetime = buf.st_mtim.tv_sec * 1000000000ULL + buf.st_mtim.tv_nsec; } + return filetime; } + } // namespace MOT diff --git a/src/gausskernel/storage/mot/core/infra/config/config_file_parser.cpp b/src/gausskernel/storage/mot/core/infra/config/config_file_parser.cpp index 7c58642b7..6782f6a05 100644 --- a/src/gausskernel/storage/mot/core/infra/config/config_file_parser.cpp +++ b/src/gausskernel/storage/mot/core/infra/config/config_file_parser.cpp @@ -37,38 +37,85 @@ namespace MOT { IMPLEMENT_CLASS_LOGGER(ConfigFileParser, Configuration) -bool ConfigFileParser::BreakSectionName(const mot_string& sectionFullName, mot_string& sectionPath, - mot_string& sectionName, char sep /* = ConfigItem::PATH_SEP */) +/* + * 功能:分解配置部分名称 + * + * 参数列表: + * sectionFullName:完整的配置部分名称 + * sectionPath:用于存储配置部分路径的mot_string对象 + * sectionName:用于存储配置部分名称的mot_string对象 + * sep:分隔符,指示配置部分路径和名称之间的分隔符,默认为ConfigItem::PATH_SEP + * + * 返回值: + * 如果成功分解配置部分名称,返回true;否则返回false。 + * + * 注意: + * 此函数用于分解完整的配置部分名称,将其拆分为配置部分路径和名称。 + * 分隔符sep用于指示配置部分路径和名称之间的分隔符,默认为ConfigItem::PATH_SEP。 + * 如果成功分解名称,将结果存储在sectionPath和sectionName参数中,并返回true; + * 否则,返回false,并记录错误信息。 + */ +bool ConfigFileParser::BreakSectionName(const mot_string §ionFullName, mot_string §ionPath, + mot_string §ionName, char sep /* = ConfigItem::PATH_SEP */) { bool result = true; + + // 查找最后一个分隔符的位置 uint32_t lastSlashPos = sectionFullName.find_last_of(sep); + if (lastSlashPos != mot_string::npos) { + // 如果找到分隔符,将字符串分割成路径和名称 if (!sectionFullName.substr(sectionPath, 0, lastSlashPos) || !sectionFullName.substr(sectionName, lastSlashPos + 1)) { MOT_REPORT_ERROR(MOT_ERROR_OOM, "Load Configuration", "Failed to parse section name"); result = false; } else { + // 去除空白字符 sectionPath.trim(); sectionName.trim(); } - } else if (!sectionPath.assign("") || !sectionName.assign(sectionFullName)) { - MOT_REPORT_ERROR(MOT_ERROR_OOM, "Load Configuration", "Failed to parse section name"); - result = false; + } else { + // 如果没有找到分隔符,将整个字符串作为名称,路径为空 + if (!sectionPath.assign("") || !sectionName.assign(sectionFullName)) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Load Configuration", "Failed to parse section name"); + result = false; + } } + return result; } -bool ConfigFileParser::ParseKeyValue(const mot_string& keyValuePart, const mot_string& section, mot_string& key, - mot_string& value, uint64_t& arrayIndex, bool& hasArrayIndex) +/* + * 功能:解析键值对字符串 + * + * 参数列表: + * keyValuePart:包含键值对的字符串 + * section:配置部分的名称 + * key:用于存储键的mot_string对象 + * value:用于存储值的mot_string对象 + * arrayIndex:用于存储数组索引的整数值 + * hasArrayIndex:指示是否包含数组索引的布尔值,如果包含则为true,否则为false + * + * 返回值: + * 如果成功解析键值对字符串,返回true;否则返回false。 + * + * 注意: + * 此函数用于解析包含键值对的字符串,并将键、值、数组索引信息提取出来。 + * 如果成功解析,将结果存储在key、value、arrayIndex和hasArrayIndex参数中,并返回true; + * 否则,返回false,并记录错误信息。 + */ +bool ConfigFileParser::ParseKeyValue(const mot_string &keyValuePart, const mot_string §ion, mot_string &key, + mot_string &value, uint64_t &arrayIndex, bool &hasArrayIndex) { bool result = false; hasArrayIndex = false; + + // 查找等号的位置,分割键值对 uint32_t equalPos = keyValuePart.find('='); + if (equalPos == mot_string::npos) { - MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, - "Load Configuration", - "Failed to parse key/value: missing equal sign (%s)", - keyValuePart.c_str()); + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Load Configuration", "Failed to parse key/value: missing equal sign (%s)", + keyValuePart.c_str()); } else if (!keyValuePart.substr(key, 0, equalPos) || !keyValuePart.substr(value, equalPos + 1)) { MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Load Configuration", "Failed to parse key/value"); } else { @@ -76,69 +123,127 @@ bool ConfigFileParser::ParseKeyValue(const mot_string& keyValuePart, const mot_s value.trim(); result = true; - // check for array item + // 检查是否包含数组索引 uint32_t poundPos = key.find('#'); if (poundPos != mot_string::npos) { result = false; mot_string intPart; + + // 提取数组索引的整数部分 if (!key.substr(intPart, poundPos + 1)) { - MOT_REPORT_ERROR( - MOT_ERROR_INTERNAL, "Load Configuration", "Failed to parse integer part from array item specifier"); + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Load Configuration", + "Failed to parse integer part from array item specifier"); } else { + // 解析整数部分并存储到arrayIndex中 if (ParseUIntValue(section, key, intPart, arrayIndex, true)) { hasArrayIndex = true; key.substr_inplace(0, poundPos); result = true; } else { - MOT_REPORT_ERROR(MOT_ERROR_INVALID_CFG, - "Load Configuration", - "Invalid array item specifier encountered: %s", - key.c_str()); + MOT_REPORT_ERROR(MOT_ERROR_INVALID_CFG, "Load Configuration", + "Invalid array item specifier encountered: %s", key.c_str()); } } } } + return result; } -ConfigItem* ConfigFileParser::MakeConfigValue( - const mot_string& sectionFullName, const mot_string& key, const mot_string& value) +/* + * 功能:创建配置项的通用配置值 + * + * 参数列表: + * sectionFullName:完整的配置部分名称 + * key:键的名称 + * value:值的字符串表示 + * + * 返回值: + * 如果成功创建配置项的通用配置值,返回指向配置项的指针;否则返回nullptr。 + * + * 注意: + * 此函数用于根据提供的参数创建通用配置值。 + * 首先,它检查键是否包含类型指示符,如果包含则调用MakeTypedConfigValue函数来创建类型化的配置值; + * 否则,调用MakeUntypedConfigValue函数来创建非类型化的配置值。 + */ +ConfigItem *ConfigFileParser::MakeConfigValue(const mot_string §ionFullName, const mot_string &key, + const mot_string &value) { - ConfigItem* result = nullptr; + ConfigItem *result = nullptr; - // key may have type specifier (in the format type:key=value) + // 检查键是否包含类型指示符(类型:key=value) uint32_t colonPos = key.find(':'); if (colonPos != mot_string::npos) { mot_string typeName; mot_string keyName; + + // 提取类型名称和键名称 if (!key.substr(typeName, 0, colonPos) || !key.substr(keyName, colonPos + 1)) { MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Load Configuration", "Failed to parse typed key %s", key.c_str()); } else { + // 调用MakeTypedConfigValue函数创建类型化的配置值 result = MakeTypedConfigValue(sectionFullName, typeName, keyName, value); } } else { + // 调用MakeUntypedConfigValue函数创建非类型化的配置值 result = MakeUntypedConfigValue(sectionFullName, key, value); } return result; } -ConfigItem* ConfigFileParser::MakeArrayConfigValue(const mot_string& path, uint64_t arrayIndex, const mot_string& value) +/* + * 功能:创建数组配置项的配置值 + * + * 参数列表: + * path:包含数组配置项的完整路径 + * arrayIndex:数组索引 + * value:值的字符串表示 + * + * 返回值: + * 如果成功创建数组配置项的配置值,返回指向配置项的指针;否则返回nullptr。 + * + * 注意: + * 此函数用于根据提供的参数创建数组配置项的配置值。 + * 首先,它将数组索引转换为字符串格式,并将其用作键。 + * 然后,调用MakeConfigValue函数来创建配置值,其中路径为包含数组配置项的完整路径,键为数组索引字符串。 + */ +ConfigItem *ConfigFileParser::MakeArrayConfigValue(const mot_string &path, uint64_t arrayIndex, const mot_string &value) { - ConfigItem* result = nullptr; + ConfigItem *result = nullptr; mot_string key; + + // 将数组索引转换为字符串格式,并作为键 if (!key.format("%lu", arrayIndex)) { MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Load Configuration", "Failed to format array index %lu", arrayIndex); } else { + // 调用MakeConfigValue函数创建配置值 result = MakeConfigValue(path, key, value); } + return result; } -ConfigItem* ConfigFileParser::MakeIntConfigValue(const mot_string& path, const mot_string& name, int64_t value) +/* + * 功能:创建整数类型的配置项配置值 + * + * 参数列表: + * path:配置项所在的路径 + * name:配置项的名称 + * value:整数值 + * + * 返回值: + * 如果成功创建整数类型的配置项配置值,返回指向配置项的指针;否则返回nullptr。 + * + * 注意: + * 此函数用于根据提供的参数创建整数类型的配置项配置值。 + * 首先,它尝试将整数值解析为不同的整数类型(从小到大),并根据解析结果创建相应类型的配置项。 + * 如果解析和创建成功,将返回相应的配置项指针;否则返回nullptr,并记录错误信息。 + */ +ConfigItem *ConfigFileParser::MakeIntConfigValue(const mot_string &path, const mot_string &name, int64_t value) { // try to parse value as integer starting from smallest type - ConfigItem* result = nullptr; + ConfigItem *result = nullptr; if ((value >= SCHAR_MIN) && (value <= SCHAR_MAX)) { result = CreateConfigValue(path.c_str(), name.c_str(), (int8_t)value); @@ -164,11 +269,26 @@ ConfigItem* ConfigFileParser::MakeIntConfigValue(const mot_string& path, const m return result; } - -ConfigItem* ConfigFileParser::MakeUIntConfigValue(const mot_string& path, const mot_string& name, uint64_t value) +/* + * 功能:创建无符号整数类型的配置项配置值 + * + * 参数列表: + * path:配置项所在的路径 + * name:配置项的名称 + * value:无符号整数值 + * + * 返回值: + * 如果成功创建无符号整数类型的配置项配置值,返回指向配置项的指针;否则返回nullptr。 + * + * 注意: + * 此函数用于根据提供的参数创建无符号整数类型的配置项配置值。 + * 首先,它尝试将无符号整数值解析为不同的无符号整数类型(从小到大),并根据解析结果创建相应类型的配置项。 + * 如果解析和创建成功,将返回相应的配置项指针;否则返回nullptr,并记录错误信息。 + */ +ConfigItem *ConfigFileParser::MakeUIntConfigValue(const mot_string &path, const mot_string &name, uint64_t value) { // try to parse value as unsigned integer starting from smallest type - ConfigItem* result = nullptr; + ConfigItem *result = nullptr; if (value <= UCHAR_MAX) { result = CreateConfigValue(path.c_str(), name.c_str(), (uint8_t)value); @@ -195,21 +315,40 @@ ConfigItem* ConfigFileParser::MakeUIntConfigValue(const mot_string& path, const return result; } -bool ConfigFileParser::Split(const char* str, char sep, mot_string_list& tokens) +/* + * 功能:将字符串按指定分隔符分割为多个子字符串,并存储在字符串列表中 + * + * 参数列表: + * str:待分割的字符串 + * sep:分隔符 + * tokens:用于存储分割后的子字符串的字符串列表 + * + * 返回值: + * 如果成功分割并存储子字符串,返回true;否则返回false。 + * + * 注意: + * 此函数用于将字符串按照指定的分隔符进行分割,并将分割后的子字符串存储在字符串列表中。 + * 它使用一个循环来处理字符串中的每个分隔符,并将分隔符之间的部分作为一个子字符串存储起来。 + * 在处理过程中,会跳过空字符串。 + */ +bool ConfigFileParser::Split(const char *str, char sep, mot_string_list &tokens) { do { - const char* begin = str; + const char *begin = str; while (*str != sep && *str) { str++; } - // skip empty items + // 跳过空项 if (str != begin) { mot_string token; + + // 将子字符串从begin到str的位置存储到token中 if (!token.assign(begin, str - begin)) { MOT_REPORT_ERROR(MOT_ERROR_OOM, "Load Configuration", "Failed to push a token"); return false; } else { + // 将token添加到字符串列表中 if (!tokens.push_back(token)) { MOT_REPORT_ERROR(MOT_ERROR_OOM, "Load Configuration", "Failed to push a token"); return false; @@ -217,14 +356,31 @@ bool ConfigFileParser::Split(const char* str, char sep, mot_string_list& tokens) } } } while (*str++ != 0); + return true; } - -ConfigItem* ConfigFileParser::MakeIntConfigValue( - const mot_string& sectionFullName, const mot_string& key, const mot_string& value) +/* + * 功能:创建整数类型的配置项配置值 + * + * 参数列表: + * sectionFullName:配置项所在的完整路径 + * key:配置项的名称 + * value:整数值的字符串表示 + * + * 返回值: + * 如果成功创建整数类型的配置项配置值,返回指向配置项的指针;否则返回nullptr。 + * + * 注意: + * 此函数用于根据提供的参数创建整数类型的配置项配置值。 + * 它首先尝试将整数值的字符串表示解析为int64_t类型。 + * 如果解析成功,它会根据解析结果的大小选择适当的整数类型,并创建相应类型的配置项。 + * 如果创建成功,将返回相应的配置项指针;否则返回nullptr,并记录错误信息。 + */ +ConfigItem *ConfigFileParser::MakeIntConfigValue(const mot_string §ionFullName, const mot_string &key, + const mot_string &value) { // try to parse value as integer - ConfigItem* result = nullptr; + ConfigItem *result = nullptr; int64_t intValue = 0; if (ParseIntValue(sectionFullName, key, value, intValue)) { if ((intValue >= SCHAR_MIN) && (intValue <= SCHAR_MAX)) { @@ -251,12 +407,28 @@ ConfigItem* ConfigFileParser::MakeIntConfigValue( } return result; } - -ConfigItem* ConfigFileParser::MakeUIntConfigValue( - const mot_string& sectionFullName, const mot_string& key, const mot_string& value) +/* + * 功能:创建无符号整数类型的配置项配置值 + * + * 参数列表: + * sectionFullName:配置项所在的完整路径 + * key:配置项的名称 + * value:无符号整数值的字符串表示 + * + * 返回值: + * 如果成功创建无符号整数类型的配置项配置值,返回指向配置项的指针;否则返回nullptr。 + * + * 注意: + * 此函数用于根据提供的参数创建无符号整数类型的配置项配置值。 + * 它首先尝试将无符号整数值的字符串表示解析为uint64_t类型。 + * 如果解析成功,它会根据解析结果的大小选择适当的无符号整数类型,并创建相应类型的配置项。 + * 如果创建成功,将返回相应的配置项指针;否则返回nullptr,并记录错误信息。 + */ +ConfigItem *ConfigFileParser::MakeUIntConfigValue(const mot_string §ionFullName, const mot_string &key, + const mot_string &value) { // try to parse value as unsigned integer - ConfigItem* result = nullptr; + ConfigItem *result = nullptr; uint64_t intValue = 0; if (ParseUIntValue(sectionFullName, key, value, intValue)) { if (intValue <= UCHAR_MAX) { @@ -284,44 +456,81 @@ ConfigItem* ConfigFileParser::MakeUIntConfigValue( return result; } -ConfigItem* ConfigFileParser::MakeDoubleConfigValue( - const mot_string& sectionFullName, const mot_string& key, const mot_string& value) +/* + * 功能:创建双精度浮点数类型的配置项配置值 + * + * 参数列表: + * sectionFullName:配置项所在的完整路径 + * key:配置项的名称 + * value:双精度浮点数值的字符串表示 + * + * 返回值: + * 如果成功创建双精度浮点数类型的配置项配置值,返回指向配置项的指针;否则返回nullptr。 + * + * 注意: + * 此函数用于根据提供的参数创建双精度浮点数类型的配置项配置值。 + * 它首先尝试将双精度浮点数值的字符串表示解析为double类型。 + * 在解析过程中,会检查是否存在不合法的情况,如空输入、无有效数字、不合法的后续字符和溢出。 + * 如果解析成功且没有不合法情况,则创建相应类型的配置项,返回相应的配置项指针; + * 否则返回nullptr,并记录错误信息。 + */ +ConfigItem *ConfigFileParser::MakeDoubleConfigValue(const mot_string §ionFullName, const mot_string &key, + const mot_string &value) { - // try to parse value as double - ConfigItem* result = nullptr; - char* endptr = NULL; + // 尝试解析字符串值为双精度浮点数 + ConfigItem *result = nullptr; + char *endptr = nullptr; double doubleValue = strtod(value.c_str(), &endptr); - if (*value.c_str() == 0) { // empty input + + // 空输入 + if (*value.c_str() == 0) { MOT_LOG_TRACE("Configuration double at [section %s, key %s] is empty", sectionFullName.c_str(), key.c_str()); - } else if (endptr == value.c_str()) { // no valid digits + } + // 无有效数字 + else if (endptr == value.c_str()) { MOT_LOG_TRACE("Configuration double at [section %s, key %s] has no valid digits at all: %s", - sectionFullName.c_str(), - key.c_str(), - value.c_str()); - } else if (*endptr != 0) { // some invalid trailing digits + sectionFullName.c_str(), key.c_str(), value.c_str()); + } + // 存在不合法的后续字符 + else if (*endptr != 0) { MOT_LOG_TRACE("Configuration double at [section %s, key %s] has invalid trailing characters: %s", - sectionFullName.c_str(), - key.c_str(), - value.c_str()); - } else if (((doubleValue == HUGE_VALF) || (doubleValue == HUGE_VALL)) && (errno == ERANGE)) { // overflow - MOT_LOG_TRACE("Configuration double at [section %s, key %s] overflows: %s", - sectionFullName.c_str(), - key.c_str(), - value.c_str()); + sectionFullName.c_str(), key.c_str(), value.c_str()); + } + // 溢出 + else if (((doubleValue == HUGE_VALF) || (doubleValue == HUGE_VALL)) && (errno == ERANGE)) { + MOT_LOG_TRACE("Configuration double at [section %s, key %s] overflows: %s", sectionFullName.c_str(), + key.c_str(), value.c_str()); } else { result = CreateConfigValue(sectionFullName.c_str(), key.c_str(), doubleValue); if (result == nullptr) { MOT_REPORT_ERROR(MOT_ERROR_OOM, "Load Configuration", "Failed to allocate double configuration value"); } } + return result; } - -ConfigItem* ConfigFileParser::MakeBoolConfigValue( - const mot_string& sectionFullName, const mot_string& key, const mot_string& value) +/* + * 功能:创建布尔类型的配置项配置值 + * + * 参数列表: + * sectionFullName:配置项所在的完整路径 + * key:配置项的名称 + * value:布尔值的字符串表示 + * + * 返回值: + * 如果成功创建布尔类型的配置项配置值,返回指向配置项的指针;否则返回nullptr。 + * + * 注意: + * 此函数用于根据提供的参数创建布尔类型的配置项配置值。 + * 它首先尝试将布尔值的字符串表示解析为bool类型,支持"true"/"on"/"yes"和"false"/"off"/"no"。 + * 如果解析成功,则创建相应类型的配置项,返回相应的配置项指针; + * 否则返回nullptr,并记录错误信息。 + */ +ConfigItem *ConfigFileParser::MakeBoolConfigValue(const mot_string §ionFullName, const mot_string &key, + const mot_string &value) { // try to parse value as Boolean - ConfigItem* result = nullptr; + ConfigItem *result = nullptr; if ((strcasecmp(value.c_str(), "true") == 0) || (strcasecmp(value.c_str(), "on") == 0) || (strcasecmp(value.c_str(), "yes") == 0)) { result = CreateConfigValue(sectionFullName.c_str(), key.c_str(), true); @@ -337,12 +546,28 @@ ConfigItem* ConfigFileParser::MakeBoolConfigValue( } return result; } - -ConfigItem* ConfigFileParser::MakeInt64ConfigValue( - const mot_string& sectionFullName, const mot_string& key, const mot_string& value) +/* + * 功能:创建int64类型的配置项配置值 + * + * 参数列表: + * sectionFullName:配置项所在的完整路径 + * key:配置项的名称 + * value:int64类型的字符串表示 + * + * 返回值: + * 如果成功创建int64类型的配置项配置值,返回指向配置项的指针;否则返回nullptr。 + * + * 注意: + * 此函数用于根据提供的参数创建int64类型的配置项配置值。 + * 它首先尝试将字符串值解析为int64类型。 + * 如果解析成功,则创建int64类型的配置项,返回相应的配置项指针; + * 否则返回nullptr,并记录错误信息。 + */ +ConfigItem *ConfigFileParser::MakeInt64ConfigValue(const mot_string §ionFullName, const mot_string &key, + const mot_string &value) { // try to parse value as int64_t - ConfigItem* result = nullptr; + ConfigItem *result = nullptr; int64_t intValue = 0; if (ParseIntValue(sectionFullName, key, value, intValue)) { result = CreateConfigValue(sectionFullName.c_str(), key.c_str(), intValue); @@ -353,11 +578,28 @@ ConfigItem* ConfigFileParser::MakeInt64ConfigValue( return result; } -ConfigItem* ConfigFileParser::MakeInt32ConfigValue( - const mot_string& sectionFullName, const mot_string& key, const mot_string& value) +/* + * 功能:创建int32_t类型的配置项配置值 + * + * 参数列表: + * sectionFullName:配置项所在的完整路径 + * key:配置项的名称 + * value:int32_t类型的字符串表示 + * + * 返回值: + * 如果成功创建int32_t类型的配置项配置值,返回指向配置项的指针;否则返回nullptr。 + * + * 注意: + * 此函数用于根据提供的参数创建int32_t类型的配置项配置值。 + * 它首先尝试将字符串值解析为int64_t类型。 + * 如果解析成功并且值在int32_t的范围内,则创建int32_t类型的配置项,返回相应的配置项指针; + * 否则返回nullptr,并记录错误信息。 + */ +ConfigItem *ConfigFileParser::MakeInt32ConfigValue(const mot_string §ionFullName, const mot_string &key, + const mot_string &value) { - // try to parse value as int32_t - ConfigItem* result = nullptr; + // 尝试解析字符串值为int64_t类型 + ConfigItem *result = nullptr; int64_t intValue = 0; if (ParseIntValue(sectionFullName, key, value, intValue)) { if ((intValue >= INT_MIN) && (intValue <= INT_MAX)) { @@ -370,11 +612,28 @@ ConfigItem* ConfigFileParser::MakeInt32ConfigValue( return result; } -ConfigItem* ConfigFileParser::MakeInt16ConfigValue( - const mot_string& sectionFullName, const mot_string& key, const mot_string& value) +/* + * 功能:创建int16_t类型的配置项配置值 + * + * 参数列表: + * sectionFullName:配置项所在的完整路径 + * key:配置项的名称 + * value:int16_t类型的字符串表示 + * + * 返回值: + * 如果成功创建int16_t类型的配置项配置值,返回指向配置项的指针;否则返回nullptr。 + * + * 注意: + * 此函数用于根据提供的参数创建int16_t类型的配置项配置值。 + * 它首先尝试将字符串值解析为int64_t类型。 + * 如果解析成功并且值在int16_t的范围内,则创建int16_t类型的配置项,返回相应的配置项指针; + * 否则返回nullptr,并记录错误信息。 + */ +ConfigItem *ConfigFileParser::MakeInt16ConfigValue(const mot_string §ionFullName, const mot_string &key, + const mot_string &value) { - // try to parse value as int16_t - ConfigItem* result = nullptr; + // 尝试解析字符串值为int64_t类型 + ConfigItem *result = nullptr; int64_t intValue = 0; if (ParseIntValue(sectionFullName, key, value, intValue)) { if ((intValue >= SHRT_MIN) && (intValue <= SHRT_MAX)) { @@ -387,11 +646,28 @@ ConfigItem* ConfigFileParser::MakeInt16ConfigValue( return result; } -ConfigItem* ConfigFileParser::MakeInt8ConfigValue( - const mot_string& sectionFullName, const mot_string& key, const mot_string& value) +/* + * 功能:创建int8_t类型的配置项配置值 + * + * 参数列表: + * sectionFullName:配置项所在的完整路径 + * key:配置项的名称 + * value:int8_t类型的字符串表示 + * + * 返回值: + * 如果成功创建int8_t类型的配置项配置值,返回指向配置项的指针;否则返回nullptr。 + * + * 注意: + * 此函数用于根据提供的参数创建int8_t类型的配置项配置值。 + * 它首先尝试将字符串值解析为int64_t类型。 + * 如果解析成功并且值在int8_t的范围内,则创建int8_t类型的配置项,返回相应的配置项指针; + * 否则返回nullptr,并记录错误信息。 + */ +ConfigItem *ConfigFileParser::MakeInt8ConfigValue(const mot_string §ionFullName, const mot_string &key, + const mot_string &value) { - // try to parse value as int8_t - ConfigItem* result = nullptr; + // 尝试解析字符串值为int64_t类型 + ConfigItem *result = nullptr; int64_t intValue = 0; if (ParseIntValue(sectionFullName, key, value, intValue)) { if ((intValue >= SCHAR_MIN) && (intValue <= SCHAR_MAX)) { @@ -403,12 +679,28 @@ ConfigItem* ConfigFileParser::MakeInt8ConfigValue( } return result; } - -ConfigItem* ConfigFileParser::MakeUInt64ConfigValue( - const mot_string& sectionFullName, const mot_string& key, const mot_string& value) +/* + * 功能:创建uint64_t类型的配置项配置值 + * + * 参数列表: + * sectionFullName:配置项所在的完整路径 + * key:配置项的名称 + * value:uint64_t类型的字符串表示 + * + * 返回值: + * 如果成功创建uint64_t类型的配置项配置值,返回指向配置项的指针;否则返回nullptr。 + * + * 注意: + * 此函数用于根据提供的参数创建uint64_t类型的配置项配置值。 + * 它首先尝试将字符串值解析为uint64_t类型。 + * 如果解析成功,创建uint64_t类型的配置项,返回相应的配置项指针; + * 否则返回nullptr,并记录错误信息。 + */ +ConfigItem *ConfigFileParser::MakeUInt64ConfigValue(const mot_string §ionFullName, const mot_string &key, + const mot_string &value) { // try to parse value as uint64_t - ConfigItem* result = nullptr; + ConfigItem *result = nullptr; uint64_t intValue = 0; if (ParseUIntValue(sectionFullName, key, value, intValue)) { result = CreateConfigValue(sectionFullName.c_str(), key.c_str(), intValue); @@ -418,12 +710,28 @@ ConfigItem* ConfigFileParser::MakeUInt64ConfigValue( } return result; } - -ConfigItem* ConfigFileParser::MakeUInt32ConfigValue( - const mot_string& sectionFullName, const mot_string& key, const mot_string& value) +/* + * 功能:创建uint32_t类型的配置项配置值 + * + * 参数列表: + * sectionFullName:配置项所在的完整路径 + * key:配置项的名称 + * value:uint32_t类型的字符串表示 + * + * 返回值: + * 如果成功创建uint32_t类型的配置项配置值,返回指向配置项的指针;否则返回nullptr。 + * + * 注意: + * 此函数用于根据提供的参数创建uint32_t类型的配置项配置值。 + * 它首先尝试将字符串值解析为uint64_t类型。 + * 如果解析成功且值在uint32_t的范围内,创建uint32_t类型的配置项,返回相应的配置项指针; + * 否则返回nullptr,并记录错误信息。 + */ +ConfigItem *ConfigFileParser::MakeUInt32ConfigValue(const mot_string §ionFullName, const mot_string &key, + const mot_string &value) { // try to parse value as uint32_t - ConfigItem* result = nullptr; + ConfigItem *result = nullptr; uint64_t intValue = 0; if (ParseUIntValue(sectionFullName, key, value, intValue)) { if (intValue <= UINT_MAX) { @@ -435,12 +743,28 @@ ConfigItem* ConfigFileParser::MakeUInt32ConfigValue( } return result; } - -ConfigItem* ConfigFileParser::MakeUInt16ConfigValue( - const mot_string& sectionFullName, const mot_string& key, const mot_string& value) +/* + * 功能:创建uint16_t类型的配置项配置值 + * + * 参数列表: + * sectionFullName:配置项所在的完整路径 + * key:配置项的名称 + * value:uint16_t类型的字符串表示 + * + * 返回值: + * 如果成功创建uint16_t类型的配置项配置值,返回指向配置项的指针;否则返回nullptr。 + * + * 注意: + * 此函数用于根据提供的参数创建uint16_t类型的配置项配置值。 + * 它首先尝试将字符串值解析为uint64_t类型。 + * 如果解析成功且值在uint16_t的范围内,创建uint16_t类型的配置项,返回相应的配置项指针; + * 否则返回nullptr,并记录错误信息。 + */ +ConfigItem *ConfigFileParser::MakeUInt16ConfigValue(const mot_string §ionFullName, const mot_string &key, + const mot_string &value) { // try to parse value as uint16_t - ConfigItem* result = nullptr; + ConfigItem *result = nullptr; uint64_t intValue = 0; if (ParseUIntValue(sectionFullName, key, value, intValue)) { if (intValue <= USHRT_MAX) { @@ -452,12 +776,28 @@ ConfigItem* ConfigFileParser::MakeUInt16ConfigValue( } return result; } - -ConfigItem* ConfigFileParser::MakeUInt8ConfigValue( - const mot_string& sectionFullName, const mot_string& key, const mot_string& value) +/* + * 功能:创建 uint8_t 类型的配置项配置值 + * + * 参数列表: + * sectionFullName:配置项所在的完整路径 + * key:配置项的名称 + * value:uint8_t 类型的字符串表示 + * + * 返回值: + * 如果成功创建 uint8_t 类型的配置项配置值,返回指向配置项的指针;否则返回 nullptr。 + * + * 注意: + * 此函数用于根据提供的参数创建 uint8_t 类型的配置项配置值。 + * 它首先尝试将字符串值解析为 uint64_t 类型。 + * 如果解析成功且值在 uint8_t 的范围内,创建 uint8_t 类型的配置项,返回相应的配置项指针; + * 否则返回 nullptr,并记录错误信息。 + */ +ConfigItem *ConfigFileParser::MakeUInt8ConfigValue(const mot_string §ionFullName, const mot_string &key, + const mot_string &value) { // try to parse value as uint8_t - ConfigItem* result = nullptr; + ConfigItem *result = nullptr; uint64_t intValue = 0; if (ParseUIntValue(sectionFullName, key, value, intValue)) { if (intValue <= UCHAR_MAX) { @@ -469,13 +809,29 @@ ConfigItem* ConfigFileParser::MakeUInt8ConfigValue( } return result; } - -ConfigItem* ConfigFileParser::MakeUntypedConfigValue( - const mot_string& sectionFullName, const mot_string& key, const mot_string& value) +/* + * 功能:创建未指定类型的配置项配置值 + * + * 参数列表: + * sectionFullName:配置项所在的完整路径 + * key:配置项的名称 + * value:未指定类型的字符串表示 + * + * 返回值: + * 如果成功创建配置项配置值,返回指向配置项的指针;否则返回 nullptr。 + * + * 注意: + * 此函数用于根据提供的参数创建未指定类型的配置项配置值。 + * 它尝试根据字符串值的内容推断配置项的数据类型,然后创建相应类型的配置项。 + * 如果无法推断出数据类型或创建配置项失败,将默认创建一个 mot_string 类型的配置项。 + */ +ConfigItem *ConfigFileParser::MakeUntypedConfigValue(const mot_string §ionFullName, const mot_string &key, + const mot_string &value) { // try to infer the configuration value type + // 尝试推断配置值的数据类型 MOT_LOG_TRACE("Attempting to parse signed integer value"); - ConfigItem* result = MakeIntConfigValue(sectionFullName, key, value); + ConfigItem *result = MakeIntConfigValue(sectionFullName, key, value); if (result == nullptr) { MOT_LOG_TRACE("Attempting to parse unsigned integer value"); result = MakeUIntConfigValue(sectionFullName, key, value); @@ -490,6 +846,7 @@ ConfigItem* ConfigFileParser::MakeUntypedConfigValue( } if (result == nullptr) { MOT_LOG_TRACE("Defaulting to mot_string value"); + // 默认创建一个 mot_string 类型的配置项 result = CreateConfigValue(sectionFullName.c_str(), key.c_str(), value); if (result == nullptr) { MOT_REPORT_ERROR(MOT_ERROR_OOM, "Load Configuration", "Failed to allocate mot_string configuration value"); @@ -497,12 +854,27 @@ ConfigItem* ConfigFileParser::MakeUntypedConfigValue( } return result; } - -ConfigItem* ConfigFileParser::MakeTypedConfigValue( - const mot_string& sectionFullName, const mot_string& typeName, const mot_string& key, const mot_string& value) +/* + * 功能:根据类型名称创建配置项配置值 + * + * 参数列表: + * sectionFullName:配置项所在的完整路径 + * typeName:配置项的类型名称 + * key:配置项的名称 + * value:配置项的值 + * + * 返回值: + * 如果成功创建配置项配置值,返回指向配置项的指针;否则返回 nullptr。 + * + * 注意: + * 此函数根据提供的类型名称,创建相应类型的配置项配置值。 + * 如果类型名称无效或无法创建配置项配置值,将生成相应的错误报告并返回 nullptr。 + */ +ConfigItem *ConfigFileParser::MakeTypedConfigValue(const mot_string §ionFullName, const mot_string &typeName, + const mot_string &key, const mot_string &value) { // build a configuration value by given type name - ConfigItem* result = nullptr; + ConfigItem *result = nullptr; ConfigValueType valueType = ConfigValueTypeFromString(typeName.c_str()); switch (valueType) { case ConfigValueType::CONFIG_VALUE_INT64: @@ -548,98 +920,107 @@ ConfigItem* ConfigFileParser::MakeTypedConfigValue( case ConfigValueType::CONFIG_VALUE_STRING: result = CreateConfigValue(sectionFullName.c_str(), key.c_str(), value); if (result == nullptr) { - MOT_REPORT_ERROR( - MOT_ERROR_OOM, "Load Configuration", "Failed to allocate mot_string configuration value"); + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Load Configuration", + "Failed to allocate mot_string configuration value"); } break; default: - MOT_REPORT_ERROR(MOT_ERROR_INVALID_ARG, - "Load Configuration", - "Invalid configuration value type name: %s", - typeName.c_str()); + MOT_REPORT_ERROR(MOT_ERROR_INVALID_ARG, "Load Configuration", "Invalid configuration value type name: %s", + typeName.c_str()); break; } return result; } -bool ConfigFileParser::ParseIntValue( - const mot_string& sectionFullName, const mot_string& key, const mot_string& value, int64_t& intValue) +/* + * 功能:尝试将字符串值解析为 int64_t 类型 + * + * 参数列表: + * sectionFullName:配置项所在的完整路径 + * key:配置项的名称 + * value:待解析的字符串值 + * intValue:成功解析后存储整数值的引用 + * + * 返回值: + * 如果成功解析整数值,返回 true,否则返回 false。 + * + * 注意: + * 此函数尝试将字符串值解析为 int64_t 类型,如果解析失败,会记录错误信息并返回 false。 + */ +bool ConfigFileParser::ParseIntValue(const mot_string §ionFullName, const mot_string &key, const mot_string &value, + int64_t &intValue) { bool result = false; - char* endptr = NULL; - intValue = strtoll(value.c_str(), &endptr, 0); - if (*value.c_str() == 0) { // empty input + char *endptr = NULL; + intValue = strtoll(value.c_str(), &endptr, 0); // 使用 strtoll 尝试解析字符串为 int64_t + + if (*value.c_str() == 0) { // 空输入 MOT_LOG_DIAG2("Configuration integer at [section %s, key %s] is empty", sectionFullName.c_str(), key.c_str()); - } else if (endptr == value.c_str()) { // no valid digits + } else if (endptr == value.c_str()) { // 无有效数字 MOT_LOG_DIAG2("Configuration integer at [section %s, key %s] has no valid digits at all: %s", - sectionFullName.c_str(), - key.c_str(), - value.c_str()); - } else if (*endptr != 0) { // some invalid trailing digits + sectionFullName.c_str(), key.c_str(), value.c_str()); + } else if (*endptr != 0) { // 存在无效的后续字符 MOT_LOG_DIAG2("Configuration integer at [section %s, key %s] has invalid trailing characters: %s", - sectionFullName.c_str(), - key.c_str(), - value.c_str()); - } else if (((intValue == LLONG_MIN) || (intValue == LLONG_MAX)) && (errno == ERANGE)) { // overflow + sectionFullName.c_str(), key.c_str(), value.c_str()); + } else if (((intValue == LLONG_MIN) || (intValue == LLONG_MAX)) && (errno == ERANGE)) { // 溢出 MOT_LOG_DIAG2("Configuration integer at [section %s, key %s] overflows: %s", value.c_str()); } else { int err = errno; MOT_LOG_TRACE("Configuration integer at [section %s, key %s] is ok: %s --> %" PRId64 " (value = %p, endptr = %p, *endptr = %u, errno: %d)", - sectionFullName.c_str(), - key.c_str(), - value.c_str(), - intValue, - value.c_str(), - endptr, - (unsigned)*endptr, - err); + sectionFullName.c_str(), key.c_str(), value.c_str(), intValue, value.c_str(), endptr, + (unsigned)*endptr, err); result = true; } + return result; } - -bool ConfigFileParser::ParseUIntValue(const mot_string& sectionFullName, const mot_string& key, const mot_string& value, - uint64_t& intValue, bool arrayIndex /* = false */) +/* + * 功能:尝试将字符串值解析为 uint64_t 类型 + * + * 参数列表: + * sectionFullName:配置项所在的完整路径 + * key:配置项的名称 + * value:待解析的字符串值 + * intValue:成功解析后存储无符号整数值的引用 + * arrayIndex:标志是否解析数组索引(可选,默认为 false) + * + * 返回值: + * 如果成功解析无符号整数值,返回 true,否则返回 false。 + * + * 注意: + * 此函数尝试将字符串值解析为 uint64_t 类型,如果解析失败,会记录错误信息并返回 false。 + */ +bool ConfigFileParser::ParseUIntValue(const mot_string §ionFullName, const mot_string &key, const mot_string &value, + uint64_t &intValue, bool arrayIndex /* = false */) { bool result = false; - char* endptr = NULL; - intValue = strtoull(value.c_str(), &endptr, 0); - const char* itemName = arrayIndex ? "arrayIndex" : "integer"; - if (*value.c_str() == 0) { // empty input - MOT_LOG_DIAG2( - "Configuration %s at [section %s, key %s] is empty", itemName, sectionFullName.c_str(), key.c_str()); - } else if (endptr == value.c_str()) { // no valid digits - MOT_LOG_DIAG2("Configuration %s at [section %s, key %s] has no valid digits at all: %s", - itemName, - sectionFullName.c_str(), - key.c_str(), - value.c_str()); - } else if (*endptr != 0) { // some invalid trailing digits - MOT_LOG_DIAG2("Configuration %s at [section %s, key %s] has invalid trailing characters: %s", - itemName, - sectionFullName.c_str(), - key.c_str(), - value.c_str()); - } else if ((intValue == ULLONG_MAX) && (errno == ERANGE)) { // overflow + char *endptr = NULL; + intValue = strtoull(value.c_str(), &endptr, 0); // 使用 strtoull 尝试解析字符串为 uint64_t + const char *itemName = arrayIndex ? "arrayIndex" : "integer"; // 根据是否数组索引确定项目名称 + + if (*value.c_str() == 0) { // 空输入 + MOT_LOG_DIAG2("Configuration %s at [section %s, key %s] is empty", itemName, sectionFullName.c_str(), + key.c_str()); + } else if (endptr == value.c_str()) { // 无有效数字 + MOT_LOG_DIAG2("Configuration %s at [section %s, key %s] has no valid digits at all: %s", itemName, + sectionFullName.c_str(), key.c_str(), value.c_str()); + } else if (*endptr != 0) { // 存在无效的后续字符 + MOT_LOG_DIAG2("Configuration %s at [section %s, key %s] has invalid trailing characters: %s", itemName, + sectionFullName.c_str(), key.c_str(), value.c_str()); + } else if ((intValue == ULLONG_MAX) && (errno == ERANGE)) { // 溢出 MOT_LOG_DIAG2("Configuration %s at [section %s, key %s] overflows: %s", itemName, value.c_str()); } else { int err = errno; MOT_LOG_TRACE("Configuration %s at [section %s, key %s] is ok: %s --> %" PRIu64 " (value = %p, endptr = %p, *endptr = %u, errno: %d)", - itemName, - sectionFullName.c_str(), - key.c_str(), - value.c_str(), - intValue, - value.c_str(), - endptr, - (unsigned)*endptr, - err); + itemName, sectionFullName.c_str(), key.c_str(), value.c_str(), intValue, value.c_str(), endptr, + (unsigned)*endptr, err); result = true; } + return result; } } // namespace MOT diff --git a/src/gausskernel/storage/mot/core/infra/config/config_item.cpp b/src/gausskernel/storage/mot/core/infra/config/config_item.cpp index 3d8ed98fe..a64b58ae8 100644 --- a/src/gausskernel/storage/mot/core/infra/config/config_item.cpp +++ b/src/gausskernel/storage/mot/core/infra/config/config_item.cpp @@ -28,7 +28,7 @@ namespace MOT { IMPLEMENT_CLASS_LOGGER(ConfigItem, Configuration) constexpr const char ConfigItem::PATH_SEP; -constexpr const char* ConfigItem::PATH_SEP_STR; +constexpr const char *ConfigItem::PATH_SEP_STR; constexpr size_t ConfigItem::CFG_MAX_PATH_LEN; constexpr size_t ConfigItem::CFG_MAX_NAME_LEN; constexpr size_t ConfigItem::CFG_MAX_FULL_PATH_LEN; @@ -41,38 +41,47 @@ ConfigItem::ConfigItem(ConfigItemClass itemClass) m_depth(0) {} -bool ConfigItem::Initialize(const char* path, const char* name) +/* + * 功能:初始化配置项的路径和名称 + * + * 参数列表: + * path:配置项的路径 + * name:配置项的名称 + * + * 返回值: + * 如果初始化成功,返回 true;否则返回 false。 + * + * 注意: + * 此函数用于初始化配置项的路径、名称和完整路径名称,以及计算配置项的深度。 + */ +bool ConfigItem::Initialize(const char *path, const char *name) { bool result = false; - if ((path[0] == 0) || (path[0] == PATH_SEP)) { // either empty or has leading slash - result = m_path.assign(path); - } else { // non-empty and missing leading slash - result = m_path.format("%s%s", PATH_SEP_STR, path); + if ((path[0] == 0) || (path[0] == PATH_SEP)) { // 检查路径是否为空或以斜杠开头 + result = m_path.assign(path); // 使用传入的路径值 + } else { // 路径非空且缺少斜杠开头 + result = m_path.format("%s%s", PATH_SEP_STR, path); // 在路径前加上斜杠 } - if (!result) { - MOT_REPORT_ERROR( - MOT_ERROR_INTERNAL, "Load Configuration", "Failed to initialize configuration path to: %s", path); + if (!result) { // 检查路径初始化是否成功 + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Load Configuration", "Failed to initialize configuration path to: %s", + path); } else { - // safe copy without buffer overrun - result = m_name.assign(name); - if (!result) { - MOT_REPORT_ERROR( - MOT_ERROR_INTERNAL, "Load Configuration", "Failed to initialize configuration name to: %s", name); + // 安全复制配置项名称,避免缓冲区溢出 + result = m_name.assign(name); // 使用传入的名称值 + if (!result) { // 检查名称初始化是否成功 + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Load Configuration", "Failed to initialize configuration name to: %s", + name); } else { - // safe format without buffer overrun - result = m_fullPathName.format("%s%s%s", m_path.c_str(), PATH_SEP_STR, m_name.c_str()); - if (!result) { - MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, - "Load Configuration", - "Failed to initialize full configuration path to: %s%s%s", - name, - m_path.c_str(), - PATH_SEP_STR, - m_name.c_str()); + // 安全格式化完整路径名称,避免缓冲区溢出 + result = m_fullPathName.format("%s%s%s", m_path.c_str(), PATH_SEP_STR, m_name.c_str()); // 合并路径和名称 + if (!result) { // 检查完整路径名称初始化是否成功 + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Load Configuration", + "Failed to initialize full configuration path to: %s%s%s", name, m_path.c_str(), + PATH_SEP_STR, m_name.c_str()); } else { - m_depth = ComputeDepth(); + m_depth = ComputeDepth(); // 计算配置项的深度 } } } @@ -80,20 +89,25 @@ bool ConfigItem::Initialize(const char* path, const char* name) return result; } +/* + * 功能:计算配置项的深度 + * + * 返回值: + * 返回配置项的深度作为无符号整数。 + * + * 注意: + * 该函数根据完整路径名称中的斜杠数来计算配置项的深度。如果完整路径为根目录,则深度为 0。 + */ uint32_t ConfigItem::ComputeDepth() { uint32_t result = 0; - if (m_fullPathName.compare(PATH_SEP_STR) != 0) { - result = m_fullPathName.count(PATH_SEP); + if (m_fullPathName.compare(PATH_SEP_STR) != 0) { // 检查完整路径是否为根目录 + result = m_fullPathName.count(PATH_SEP); // 计算完整路径中斜杠的数量,作为深度 } - MOT_LOG_DEBUG("Computed depth in full path %s (@%p), path %s (@%p) and name %s (@%p): %u", - m_fullPathName.c_str(), - m_fullPathName.c_str(), - m_path.c_str(), - m_path.c_str(), - m_name.c_str(), - m_name.c_str(), - result); + // 记录计算得到的深度信息 + MOT_LOG_DEBUG("Computed depth in full path %s (@%p), path %s (@%p) and name %s (@%p): %u", m_fullPathName.c_str(), + m_fullPathName.c_str(), m_path.c_str(), m_path.c_str(), m_name.c_str(), m_name.c_str(), result); return result; } + } // namespace MOT diff --git a/src/gausskernel/storage/mot/core/infra/config/config_item_class.cpp b/src/gausskernel/storage/mot/core/infra/config/config_item_class.cpp index f9d07b4ec..6c26f8eda 100644 --- a/src/gausskernel/storage/mot/core/infra/config/config_item_class.cpp +++ b/src/gausskernel/storage/mot/core/infra/config/config_item_class.cpp @@ -26,34 +26,63 @@ #include namespace MOT { -static const char* CONFIG_ITEM_SECTION_STR = "section"; -static const char* CONFIG_ITEM_VALUE_STR = "value"; -static const char* CONFIG_ITEM_ARRAY_STR = "array"; -static const char* CONFIG_ITEM_UNDEFINED_STR = "N/A"; +static const char *CONFIG_ITEM_SECTION_STR = "section"; +static const char *CONFIG_ITEM_VALUE_STR = "value"; +static const char *CONFIG_ITEM_ARRAY_STR = "array"; +static const char *CONFIG_ITEM_UNDEFINED_STR = "N/A"; -static const char* ITEM_CLASS_NAMES[] = { - CONFIG_ITEM_SECTION_STR, CONFIG_ITEM_VALUE_STR, CONFIG_ITEM_ARRAY_STR, CONFIG_ITEM_UNDEFINED_STR}; +static const char *ITEM_CLASS_NAMES[] = {CONFIG_ITEM_SECTION_STR, CONFIG_ITEM_VALUE_STR, CONFIG_ITEM_ARRAY_STR, + CONFIG_ITEM_UNDEFINED_STR}; -extern ConfigItemClass ConfigItemClassFromString(const char* itemClassStr) +/* + * 功能:从字符串中解析配置项类别 + * + * 参数列表: + * itemClassStr:表示配置项类别的字符串 + * + * 返回值: + * 返回 `ConfigItemClass` 枚举值,表示解析得到的配置项类别。如果无法解析,返回 + * `ConfigItemClass::CONFIG_ITEM_UNDEFINED`。 + * + * 注意: + * 此函数通过比较输入的字符串与已知的配置项类别字符串(如 + * CONFIG_ITEM_SECTION_STR、CONFIG_ITEM_VALUE_STR、CONFIG_ITEM_ARRAY_STR), + * 来确定输入字符串表示的配置项类别。如果匹配成功,将返回相应的枚举值,否则返回 CONFIG_ITEM_UNDEFINED。 + */ +extern ConfigItemClass ConfigItemClassFromString(const char *itemClassStr) { - ConfigItemClass result = ConfigItemClass::CONFIG_ITEM_UNDEFINED; + ConfigItemClass result = ConfigItemClass::CONFIG_ITEM_UNDEFINED; // 初始化结果为 CONFIG_ITEM_UNDEFINED - if (strcmp(itemClassStr, CONFIG_ITEM_SECTION_STR) == 0) { - result = ConfigItemClass::CONFIG_ITEM_SECTION; - } else if (strcmp(itemClassStr, CONFIG_ITEM_VALUE_STR) == 0) { - result = ConfigItemClass::CONFIG_ITEM_VALUE; - } else if (strcmp(itemClassStr, CONFIG_ITEM_ARRAY_STR) == 0) { - result = ConfigItemClass::CONFIG_ITEM_ARRAY; + if (strcmp(itemClassStr, CONFIG_ITEM_SECTION_STR) == 0) { // 检查是否匹配 CONFIG_ITEM_SECTION_STR + result = ConfigItemClass::CONFIG_ITEM_SECTION; // 匹配成功,设置结果为 CONFIG_ITEM_SECTION + } else if (strcmp(itemClassStr, CONFIG_ITEM_VALUE_STR) == 0) { // 检查是否匹配 CONFIG_ITEM_VALUE_STR + result = ConfigItemClass::CONFIG_ITEM_VALUE; // 匹配成功,设置结果为 CONFIG_ITEM_VALUE + } else if (strcmp(itemClassStr, CONFIG_ITEM_ARRAY_STR) == 0) { // 检查是否匹配 CONFIG_ITEM_ARRAY_STR + result = ConfigItemClass::CONFIG_ITEM_ARRAY; // 匹配成功,设置结果为 CONFIG_ITEM_ARRAY } - return result; + return result; // 返回解析得到的配置项类别 } -extern const char* ConfigItemClassToString(ConfigItemClass configItemClass) +/* + * 功能:将配置项类别枚举值转换为字符串表示 + * + * 参数列表: + * configItemClass:表示配置项类别的枚举值 + * + * 返回值: + * 返回一个指向表示配置项类别的字符串的指针。如果枚举值无效,则返回 CONFIG_ITEM_UNDEFINED_STR。 + * + * 注意: + * 此函数通过将枚举值转换为对应的整数索引,然后使用该索引查找预定义的字符串数组 ITEM_CLASS_NAMES, + * 来获取配置项类别的字符串表示。如果枚举值有效,将返回相应的字符串,否则返回 CONFIG_ITEM_UNDEFINED_STR。 + */ +extern const char *ConfigItemClassToString(ConfigItemClass configItemClass) { if (configItemClass < ConfigItemClass::CONFIG_ITEM_UNDEFINED) { - return ITEM_CLASS_NAMES[(uint32_t)configItemClass]; + return ITEM_CLASS_NAMES[(uint32_t)configItemClass]; // 使用整数索引查找预定义的字符串数组 } - return CONFIG_ITEM_UNDEFINED_STR; + return CONFIG_ITEM_UNDEFINED_STR; // 枚举值无效,返回 CONFIG_ITEM_UNDEFINED_STR } + } // namespace MOT diff --git a/src/gausskernel/storage/mot/core/infra/config/config_loader.cpp b/src/gausskernel/storage/mot/core/infra/config/config_loader.cpp index b8d1bddc5..de46799a2 100644 --- a/src/gausskernel/storage/mot/core/infra/config/config_loader.cpp +++ b/src/gausskernel/storage/mot/core/infra/config/config_loader.cpp @@ -29,10 +29,26 @@ namespace MOT { -ConfigLoader::ConfigLoader(const char* name, uint32_t priority) : m_priority(priority), m_configTree(nullptr) +/* + * 构造函数:ConfigLoader + * + * 参数列表: + * name: 配置加载器的名称,用于标识加载器。 + * priority: 配置加载器的优先级,用于确定加载顺序。 + * + * 功能: + * 初始化 ConfigLoader 类的新实例。 + * 设置配置加载器的名称和优先级,并分配内部配置树指针。 + * + * 注意: + * 该构造函数使用了 strncpy_s 函数来安全地复制名称,以确保不会发生缓冲区溢出。 + */ +ConfigLoader::ConfigLoader(const char *name, uint32_t priority) : m_priority(priority), m_configTree(nullptr) { + // 使用 strncpy_s 复制名称以确保安全性 errno_t erc = strncpy_s(m_name, MAX_CONFIG_LOADER_NAME, name, strlen(name)); securec_check(erc, "\0", "\0"); + // 确保名称以 null 终止 m_name[MAX_CONFIG_LOADER_NAME - 1] = 0; } diff --git a/src/gausskernel/storage/mot/core/infra/config/config_manager.cpp b/src/gausskernel/storage/mot/core/infra/config/config_manager.cpp index e4c05c052..e40245777 100644 --- a/src/gausskernel/storage/mot/core/infra/config/config_manager.cpp +++ b/src/gausskernel/storage/mot/core/infra/config/config_manager.cpp @@ -37,22 +37,45 @@ namespace MOT { DECLARE_LOGGER(ConfigManager, Configuration) -ConfigManager* ConfigManager::m_manager = nullptr; +ConfigManager *ConfigManager::m_manager = nullptr; +/* + * 功能:初始化 ConfigManager 类的新实例,设置配置加载器和监听器的初始计数,使用 memset_s +函数将加载器和监听器数组初始化为零。 + * + * 注意: + * 使用 memset_s 函数可以确保初始化过程是安全的,以防止潜在的内存访问问题。 + * + * 它执行以下操作: + * 1、将配置加载器计数 m_configLoaderCount 初始化为零。 + * 2、将监听器计数 m_listenerCount 初始化为零。 + * 3、使用 memset_s 函数将配置加载器数组 m_configLoaders 初始化为零。 + * 4、使用 memset_s 函数将监听器数组 m_listeners 初始化为零。 + */ ConfigManager::ConfigManager() : m_configLoaderCount(0), m_listenerCount(0) { + // 使用 memset_s 函数将配置加载器数组初始化为零 errno_t erc = memset_s(m_configLoaders, sizeof(m_configLoaders), 0, sizeof(m_configLoaders)); securec_check(erc, "\0", "\0"); + // 使用 memset_s 函数将监听器数组初始化为零 erc = memset_s(m_listeners, sizeof(m_listeners), 0, sizeof(m_listeners)); securec_check(erc, "\0", "\0"); } +/* + * 功能:ConfigManager 类的析构函数,用于清理所有配置加载器的资源 + * + * 注意: + * 析构函数负责释放所有配置加载器的资源。它首先检查每个加载器是否为空指针, + * 如果不为空,则尝试从配置加载器中获取配置,并谨慎地将其从分层配置树中移除。 + * 最后,删除配置加载器并将其设置为 nullptr。 + */ ConfigManager::~ConfigManager() { - // cleanup all configuration loaders + // 清理所有配置加载器 for (uint32_t i = 0; i < m_configLoaderCount; ++i) { if (m_configLoaders[i]) { - // remove configuration tree carefully (some might have failed to load) + // 谨慎地移除配置树(某些加载器可能加载失败) if (m_configLoaders[i]->GetConfig() != nullptr) { m_layeredConfigTree.RemoveConfigTree(m_configLoaders[i]->GetConfig()); } @@ -62,46 +85,108 @@ ConfigManager::~ConfigManager() } } -bool ConfigManager::CreateInstance(char** argv /* = nullptr */, int argc /* = 0 */) +/* + * 功能:创建 ConfigManager 的单例实例 + * + * 参数列表: + * argv:命令行参数数组的指针,默认值为 nullptr + * argc:命令行参数数组的大小,默认值为 0 + * + * 返回值: + * 如果成功创建了 ConfigManager 实例,则返回 true;否则返回 false。 + * + * 注意: + * 此函数负责创建 ConfigManager 的单例实例,首先检查 m_manager 是否已分配。 + * 如果尚未分配,则尝试为 m_manager 分配内存。如果内存分配失败,函数会报告内存分配错误并返回 false。 + * 如果成功分配内存,将调用 m_manager 的 Initialize 方法进行初始化。 + * 如果初始化失败,将释放分配的内存,并将 m_manager 设置为 nullptr,并返回 false。 + * 如果初始化成功,函数将返回 true,表示成功创建了 ConfigManager 实例。 + */ +bool ConfigManager::CreateInstance(char **argv /* = nullptr */, int argc /* = 0 */) { bool result = false; + + // 检查 m_manager 是否已分配 MOT_ASSERT(m_manager == nullptr); + if (m_manager == nullptr) { + // 尝试为 m_manager 分配内存 m_manager = new (std::nothrow) ConfigManager(); + if (m_manager == nullptr) { - MOT_REPORT_ERROR( - MOT_ERROR_OOM, "Load Configuration", "Failed to allocate memory for configuration manager, aborting"); + // 内存分配失败,报告错误并返回 false + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Load Configuration", + "Failed to allocate memory for configuration manager, aborting"); } else { + // 调用初始化方法进行初始化 result = m_manager->Initialize(argv, argc); + if (!result) { + // 初始化失败,释放分配的内存,并将 m_manager 设置为 nullptr delete m_manager; m_manager = nullptr; } } } + return result; } +/* + * 功能:销毁 ConfigManager 的单例实例 + * + * 注意: + * 此函数负责销毁 ConfigManager 的单例实例。 + * 首先,它会检查 m_manager 是否已分配,确保单例实例存在。 + * 然后,它会删除 m_manager,释放其分配的内存,并将 m_manager 设置为 nullptr,以确保单例实例不再存在。 + */ void ConfigManager::DestroyInstance() { MOT_ASSERT(m_manager != nullptr); + if (m_manager != nullptr) { + // 删除 m_manager,释放分配的内存,并将 m_manager 设置为 nullptr delete m_manager; m_manager = nullptr; } } - -ConfigManager& ConfigManager::GetInstance() +/* + * 功能:获取 ConfigManager 的单例实例 + * + * 参数列表:无 + * + * 返回值:返回 ConfigManager 的单例实例的引用 + * + * 注意: + * 此函数用于获取 ConfigManager 的单例实例。 + * 首先,它会检查 m_manager 是否已分配,确保单例实例存在。 + * 然后,它返回该单例实例的引用,允许客户端代码使用 ConfigManager 的功能。 + */ +ConfigManager &ConfigManager::GetInstance() { MOT_ASSERT(m_manager != nullptr); return *m_manager; } - -bool ConfigManager::AddConfigFile(const char* configFilePath, const char* name /* = "Main" */, - ConfigFileFormat configFileFormat /* = ConfigFileFormat::CONFIG_FILE_FORMAT_NONE */) +/* + * 功能:向配置管理器添加配置文件加载器 + * + * 参数列表: + * configFilePath:配置文件的路径 + * name:配置文件加载器的名称,默认为 "Main" + * configFileFormat:配置文件的格式,默认为 ConfigFileFormat::CONFIG_FILE_FORMAT_NONE + * + * 返回值:如果成功添加配置文件加载器,则返回 true,否则返回 false + * + * 注意: + * 此函数用于向配置管理器添加配置文件加载器,以便加载配置文件并解析其中的配置项。 + * 它根据配置文件的格式创建相应的配置文件加载器。 + * 如果成功创建和添加加载器,返回 true,否则返回 false 并记录错误。 + */ +bool ConfigManager::AddConfigFile(const char *configFilePath, const char *name /* = "Main" */, + ConfigFileFormat configFileFormat /* = ConfigFileFormat::CONFIG_FILE_FORMAT_NONE */) { bool result = false; - ConfigFileLoader* cfgFileLoader = nullptr; + ConfigFileLoader *cfgFileLoader = nullptr; switch (configFileFormat) { case ConfigFileFormat::CONFIG_FILE_PROPS: @@ -113,20 +198,16 @@ bool ConfigManager::AddConfigFile(const char* configFilePath, const char* name / break; default: - MOT_REPORT_ERROR(MOT_ERROR_INVALID_ARG, - "Load Configuration", - "Invalid configuration file format specification %d", - (int)configFileFormat); + MOT_REPORT_ERROR(MOT_ERROR_INVALID_ARG, "Load Configuration", + "Invalid configuration file format specification %d", (int)configFileFormat); return false; } if (cfgFileLoader != nullptr) { result = AddConfigLoader(cfgFileLoader); if (!result) { - MOT_REPORT_ERROR(MOT_ERROR_INVALID_ARG, - "Load Configuration", - "Failed to add configuration file loader by name %s (duplicate?)", - name); + MOT_REPORT_ERROR(MOT_ERROR_INVALID_ARG, "Load Configuration", + "Failed to add configuration file loader by name %s (duplicate?)", name); delete cfgFileLoader; } } else { @@ -136,8 +217,21 @@ bool ConfigManager::AddConfigFile(const char* configFilePath, const char* name / return result; } - -static bool CompareConfgLoaders(ConfigLoader* lhs, ConfigLoader* rhs) +/* + * 功能:比较两个配置加载器的优先级 + * + * 参数列表: + * lhs:要比较的第一个配置加载器 + * rhs:要比较的第二个配置加载器 + * + * 返回值:如果第一个配置加载器的优先级较低(数字较高),则返回 true,否则返回 false + * + * 注意: + * 此函数用于比较两个配置加载器的优先级,以确定它们在加载顺序中的位置。 + * 优先级数字较低的配置加载器(即数字较高)应该在加载顺序中排在前面。 + * 如果第一个配置加载器的优先级较低,则返回 true,否则返回 false。 + */ +static bool CompareConfgLoaders(ConfigLoader *lhs, ConfigLoader *rhs) { int lhsPriority = lhs->GetPriority(); int rhsPriority = rhs->GetPriority(); @@ -148,18 +242,29 @@ static bool CompareConfgLoaders(ConfigLoader* lhs, ConfigLoader* rhs) } return true; } - -bool ConfigManager::AddConfigLoader(ConfigLoader* configLoader) +/* + * 功能:添加配置加载器到配置管理器 + * + * 参数列表: + * configLoader:要添加的配置加载器对象的指针 + * + * 返回值: + * 如果成功添加配置加载器,则返回 true;如果配置加载器已达到最大数量限制或添加失败,则返回 false。 + * + * 注意: + * 此函数用于将配置加载器添加到配置管理器中。配置加载器会根据其优先级在加载配置时进行排序。 + * 较低优先级的加载器将首先加载配置,以便较高优先级的加载器可以基于已加载的配置设置其他限制。 + * 如果已达到配置加载器的最大数量限制,将返回 false。 + */ +bool ConfigManager::AddConfigLoader(ConfigLoader *configLoader) { bool result = false; MOT_LOG_TRACE("Adding configuration loader %s", configLoader->GetName()); if (m_configLoaderCount == MAX_CONFIG_LOADER_COUNT) { - MOT_REPORT_ERROR(MOT_ERROR_RESOURCE_LIMIT, - "Load Configuration", - "Cannot add configuration loader %s: Reached limit %u", - configLoader->GetName(), - (unsigned)MAX_CONFIG_LOADER_COUNT); + MOT_REPORT_ERROR(MOT_ERROR_RESOURCE_LIMIT, "Load Configuration", + "Cannot add configuration loader %s: Reached limit %u", configLoader->GetName(), + (unsigned)MAX_CONFIG_LOADER_COUNT); } else { // insert sorted in descending order - we want lower priority loaders to load configuration first, so higher // priority loaders can impose other limits based on configuration loaded already (as in external loaders that @@ -168,15 +273,27 @@ bool ConfigManager::AddConfigLoader(ConfigLoader* configLoader) std::sort(m_configLoaders, m_configLoaders + m_configLoaderCount, CompareConfgLoaders); uint32_t configLoaderPos = std::find(m_configLoaders, m_configLoaders + m_configLoaderCount, configLoader) - m_configLoaders; - MOT_LOG_TRACE( - "Configuration loader %s added successfully at slot %u", configLoader->GetName(), configLoaderPos); + MOT_LOG_TRACE("Configuration loader %s added successfully at slot %u", configLoader->GetName(), + configLoaderPos); result = true; } return result; } - -bool ConfigManager::RemoveConfigLoader(const char* configName) +/* + * 功能:从配置管理器中删除指定名称的配置加载器 + * + * 参数列表: + * configName:要删除的配置加载器的名称 + * + * 返回值: + * 如果成功删除配置加载器,则返回 true;如果配置加载器未找到或删除失败,则返回 false。 + * + * 注意: + * 此函数用于从配置管理器中删除指定名称的配置加载器。如果配置加载器被成功删除,它将从内部列表中移除, + * 同时也会从配置树中移除与之相关联的配置。如果配置加载器未找到或删除失败,将返回 false。 + */ +bool ConfigManager::RemoveConfigLoader(const char *configName) { bool result = false; MOT_LOG_TRACE("Removing configuration loader %s [%u registered]", configName, m_configLoaderCount); @@ -205,16 +322,26 @@ bool ConfigManager::RemoveConfigLoader(const char* configName) return result; } - -bool ConfigManager::AddConfigChangeListener(IConfigChangeListener* listener) +/* + * 功能:向配置管理器中添加配置更改监听器 + * + * 参数列表: + * listener:要添加的配置更改监听器的指针 + * + * 返回值: + * 如果成功添加监听器,则返回 true;如果已达到监听器数量上限或添加失败,则返回 false。 + * + * 注意: + * 此函数用于向配置管理器中添加配置更改监听器。监听器会在配置更改时被通知,以便执行相应的操作。 + * 如果成功添加监听器,将返回 true;如果已达到监听器数量上限或添加失败,将返回 false。 + */ +bool ConfigManager::AddConfigChangeListener(IConfigChangeListener *listener) { bool result = false; if (m_listenerCount == MAX_CONFIG_LISTENER_COUNT) { - MOT_REPORT_ERROR(MOT_ERROR_RESOURCE_LIMIT, - "Load Configuration", - "Cannot add configuration listener: Reached limit %u", - (unsigned)MAX_CONFIG_LISTENER_COUNT); + MOT_REPORT_ERROR(MOT_ERROR_RESOURCE_LIMIT, "Load Configuration", + "Cannot add configuration listener: Reached limit %u", (unsigned)MAX_CONFIG_LISTENER_COUNT); } else { m_listeners[m_listenerCount++] = listener; result = true; @@ -223,7 +350,21 @@ bool ConfigManager::AddConfigChangeListener(IConfigChangeListener* listener) return result; } -bool ConfigManager::RemoveConfigChangeListener(IConfigChangeListener* listener) +/* + * 功能:从配置管理器中移除配置更改监听器 + * + * 参数列表: + * listener:要移除的配置更改监听器的指针 + * + * 返回值: + * 如果成功移除监听器,则返回 true;如果监听器未找到或移除失败,则返回 false。 + * + * 注意: + * 此函数用于从配置管理器中移除配置更改监听器。首先,它会遍历监听器数组,寻找与给定 listener 指针匹配的监听器。 + * 如果找到匹配的监听器,将返回 true,并将数组中的监听器往前移动一个位置,以填补删除的位置。 + * 如果监听器未找到或移除失败,将返回 false,并记录错误消息。 + */ +bool ConfigManager::RemoveConfigChangeListener(IConfigChangeListener *listener) { bool result = false; @@ -246,7 +387,17 @@ bool ConfigManager::RemoveConfigChangeListener(IConfigChangeListener* listener) return result; } - +/* + * 功能:初始化加载配置 + * + * 返回值: + * 如果成功初始化加载配置,则返回 true;如果加载配置失败,则返回 false。 + * + * 注意: + * 此函数用于初始化加载配置。它首先尝试从所有配置加载器中加载配置,然后触发从刚刚加载的配置更新(仅对特定监听器)。 + * 如果加载配置失败,函数将记录错误信息,但会继续使用默认配置并返回 true。 + * 如果加载配置成功,函数会验证主配置是否有效,如果有效则返回 true。 + */ bool ConfigManager::InitLoad() { bool result = false; @@ -262,7 +413,7 @@ bool ConfigManager::InitLoad() } else { // trigger update from just-loaded configuration (only to this specific listener). MOT_LOG_DEBUG("Reloaded MOTConfiguration from main configuration"); - MOTConfiguration& motCfg = GetGlobalConfiguration(); + MOTConfiguration &motCfg = GetGlobalConfiguration(); motCfg.OnConfigChange(); // now the main configuration is fully loaded, so let's validate it @@ -273,9 +424,22 @@ bool ConfigManager::InitLoad() return result; } -const ConfigTree* ConfigManager::GetConfigTree(const char* configName) const +/* + * 功能:获取指定配置名称的配置树 + * + * 参数列表: + * configName:配置名称 + * + * 返回值: + * 返回一个指向配置树的指针,如果找不到指定名称的配置则返回 nullptr。 + * + * 注意: + * 此函数用于获取指定配置名称的配置树。它会遍历所有配置加载器,查找名称匹配的配置树。 + * 如果找到匹配的配置树,将返回指向该配置树的指针,否则返回 nullptr。 + */ +const ConfigTree *ConfigManager::GetConfigTree(const char *configName) const { - const ConfigTree* result = nullptr; + const ConfigTree *result = nullptr; for (uint32_t i = 0; i < m_configLoaderCount; ++i) { if (strcmp(m_configLoaders[i]->GetName(), configName) == 0) { result = m_configLoaders[i]->GetConfig(); @@ -284,12 +448,27 @@ const ConfigTree* ConfigManager::GetConfigTree(const char* configName) const } return result; } - -bool ConfigManager::Initialize(char** argv, int argc) +/* + * 功能:初始化配置管理器 + * + * 参数列表: + * argv:指向命令行参数的指针数组 + * argc:命令行参数的数量 + * + * 返回值: + * 如果初始化成功,返回 true;否则返回 false。 + * + * 注意: + * 此函数用于初始化配置管理器。它可以设置日志级别,添加命令行配置加载器等。 + * 首先,它检查环境变量 "MOT_DEBUG_CFG_LOAD" 是否设置为 "TRUE",如果是则将日志组件 "Configuration" 的日志级别设置为 + * DEBUG。 然后,如果传入了命令行参数,它会尝试添加命令行配置加载器,以解析这些参数。 如果初始化成功,返回 + * true;否则返回 false。 + */ +bool ConfigManager::Initialize(char **argv, int argc) { bool result = true; - char* envvar = getenv("MOT_DEBUG_CFG_LOAD"); + char *envvar = getenv("MOT_DEBUG_CFG_LOAD"); if (envvar && (strcmp(envvar, "TRUE") == 0)) { SetLogComponentLogLevel("Configuration", LogLevel::LL_DEBUG); } @@ -298,42 +477,71 @@ bool ConfigManager::Initialize(char** argv, int argc) if (argv != nullptr && argc != 0) { result = AddCmdLineConfigLoader(argv, argc); if (!result) { - MOT_REPORT_PANIC(MOT_ERROR_INTERNAL, - "Configuration Manager Initialization", - "Failed to create command line configuration loader"); + MOT_REPORT_PANIC(MOT_ERROR_INTERNAL, "Configuration Manager Initialization", + "Failed to create command line configuration loader"); } } return result; } - -bool ConfigManager::AddCmdLineConfigLoader(char** argv, int argc) +/* + * 功能:添加命令行配置加载器 + * + * 参数列表: + * argv:指向命令行参数的指针数组 + * argc:命令行参数的数量 + * + * 返回值: + * 如果添加成功,返回 true;否则返回 false。 + * + * 注意: + * 此函数用于创建并添加一个命令行配置加载器。命令行配置加载器将解析给定的命令行参数以加载配置。 + * 首先,它尝试分配内存以创建 `CmdLineConfigLoader` 的实例。 + * 如果分配内存失败,将返回 false 并报告内存分配错误。 + * 否则,它尝试通过调用 `AddConfigLoader` 函数将加载器添加到配置管理器中。 + * 如果添加成功,返回 true;否则返回 false。 + */ +bool ConfigManager::AddCmdLineConfigLoader(char **argv, int argc) { bool result = true; - ConfigLoader* cmdLineCfgLoader = new (std::nothrow) CmdLineConfigLoader(argv, argc); + ConfigLoader *cmdLineCfgLoader = new (std::nothrow) CmdLineConfigLoader(argv, argc); if (cmdLineCfgLoader == nullptr) { MOT_REPORT_ERROR(MOT_ERROR_OOM, "Load Configuration", "Failed to allocate command line configuration loader"); result = false; } else { result = AddConfigLoader(cmdLineCfgLoader); if (!result) { - MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, - "Load Configuration", - "Failed to add command line configuration loader (duplicate?)"); + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Load Configuration", + "Failed to add command line configuration loader (duplicate?)"); delete cmdLineCfgLoader; } } return result; } - -ConfigFileLoader* ConfigManager::CreateConfigFileLoader(const char* configFilePath, const char* name) +/* + * 功能:创建配置文件加载器 + * + * 参数列表: + * configFilePath:配置文件的路径 + * name:配置文件加载器的名称 + * + * 返回值: + * 如果成功创建配置文件加载器,返回该加载器的指针;否则返回 nullptr。 + * + * 注意: + * 此函数用于根据配置文件的路径和名称创建一个配置文件加载器。首先,它检查文件路径中的文件后缀。 + * 如果后缀为 "conf",则创建一个 `PropsConfigFileLoader` 实例,该加载器用于解析 "conf" 格式的配置文件。 + * 否则,函数会报告无效的参数错误并返回 nullptr。 + * 如果成功创建加载器,它将返回加载器的指针;否则返回 nullptr。 + */ +ConfigFileLoader *ConfigManager::CreateConfigFileLoader(const char *configFilePath, const char *name) { - ConfigFileLoader* cfgFileLoader = nullptr; + ConfigFileLoader *cfgFileLoader = nullptr; std::string cfgPath(configFilePath); std::string::size_type dotPos = cfgPath.find_last_of('.'); if (dotPos == std::string::npos) { - MOT_REPORT_ERROR(MOT_ERROR_INVALID_ARG, - "Load Configuration", + MOT_REPORT_ERROR( + MOT_ERROR_INVALID_ARG, "Load Configuration", "Unable to load configuration file. Format not specified and configuration file does not contain suffix: " "%s", cfgPath.c_str()); @@ -343,8 +551,8 @@ ConfigFileLoader* ConfigManager::CreateConfigFileLoader(const char* configFilePa if (suffix.compare("conf") == 0) { cfgFileLoader = new (std::nothrow) PropsConfigFileLoader(name, CFG_FILE_CONFIG_PRIORITY, configFilePath); } else { - MOT_REPORT_ERROR(MOT_ERROR_INVALID_ARG, - "Load Configuration", + MOT_REPORT_ERROR( + MOT_ERROR_INVALID_ARG, "Load Configuration", "Unable to load configuration file: Format not specified and cannot be inferred from file suffix: %s", suffix.c_str()); return nullptr; @@ -358,24 +566,36 @@ ConfigFileLoader* ConfigManager::CreateConfigFileLoader(const char* configFilePa return cfgFileLoader; } +/* + * 功能:重新加载配置 + * + * 参数列表: + * ignoreErrors:是否忽略配置加载过程中的错误,默认为 true + * + * 返回值: + * 如果成功重新加载配置,返回 true;如果在加载过程中出现错误且不忽略错误,返回 false。 + * + * 注意: + * 此函数用于重新加载配置。它首先清除配置树,然后循环遍历所有配置加载器,尝试从每个加载器中加载配置树。 + * 如果加载成功,将该配置树添加到层次化配置树中。如果加载失败且不忽略错误,函数将返回 false。 + * 最后,函数根据配置中的日志级别打印已加载的配置。 + */ bool ConfigManager::ReloadConfig(bool ignoreErrors /* = true */) { bool result = true; MOT_LOG_DEBUG("Reloading configuration"); m_layeredConfigTree.Clear(); for (uint32_t i = 0; i < m_configLoaderCount; ++i) { - ConfigLoader* configLoader = m_configLoaders[i]; - ConfigTree* configTree = configLoader->Load(); + ConfigLoader *configLoader = m_configLoaders[i]; + ConfigTree *configTree = configLoader->Load(); if (configTree != nullptr) { - MOT_LOG_DEBUG("Adding configuration tree %p with priority %u from configuration loader %s", - configTree, - configTree->GetPriority(), - configLoader->GetName()); + MOT_LOG_DEBUG("Adding configuration tree %p with priority %u from configuration loader %s", configTree, + configTree->GetPriority(), configLoader->GetName()); m_layeredConfigTree.AddConfigTree(configTree); } else { MOT_LOG_WARN("Failed to load configuration tree from configuration loader %s, configuration from this " "loader will be ignored. Please fix configuration and trigger reload.", - configLoader->GetName()); + configLoader->GetName()); if (!ignoreErrors) { result = false; } @@ -392,6 +612,13 @@ bool ConfigManager::ReloadConfig(bool ignoreErrors /* = true */) return result; } +/* + * 功能:通知配置更改 + * + * 注意: + * 此函数用于通知配置更改,它遍历所有配置更改监听器并调用它们的 OnConfigChange 函数。 + * 这个函数通常在配置发生更改时被调用,以通知它们配置已更改,可以执行相关的操作。 + */ void ConfigManager::NotifyConfigChange() { MOT_LOG_INFO("Propagating configuration changes"); @@ -399,4 +626,5 @@ void ConfigManager::NotifyConfigChange() m_listeners[i]->OnConfigChange(); } } + } // namespace MOT diff --git a/src/gausskernel/storage/mot/core/infra/config/config_section.cpp b/src/gausskernel/storage/mot/core/infra/config/config_section.cpp index be4a3a2fc..307479f2c 100644 --- a/src/gausskernel/storage/mot/core/infra/config/config_section.cpp +++ b/src/gausskernel/storage/mot/core/infra/config/config_section.cpp @@ -31,28 +31,39 @@ ConfigSection::ConfigSection() : ConfigItem(ConfigItemClass::CONFIG_ITEM_SECTION ConfigSection::~ConfigSection() { - // delete all direct values + // 删除所有直接值(ConfigValue 对象) ConfigValueMap::iterator vitr = m_valueMap.begin(); while (vitr != m_valueMap.end()) { - delete vitr->second; + delete vitr->second; // 释放 ConfigValue 对象的内存 ++vitr; } - // delete all direct arrays + // 删除所有直接数组(ConfigArray 对象) ConfigArrayMap::iterator aitr = m_arrayMap.begin(); while (aitr != m_arrayMap.end()) { - delete aitr->second; + delete aitr->second; // 释放 ConfigArray 对象的内存 ++aitr; } - // delete all sub-sections + // 删除所有子配置部分(ConfigSection 对象) ConfigSectionMap::iterator sitr = m_sectionMap.begin(); while (sitr != m_sectionMap.end()) { - delete sitr->second; + delete sitr->second; // 释放 ConfigSection 对象的内存 ++sitr; } } - +/* + * 功能:打印配置节 + * + * 参数列表: + * logLevel:要用于打印的日志级别 + * fullPrint:是否进行完整打印,默认为 false + * + * 注意: + * 此函数用于打印配置节及其包含的值、数组和子节。 + * 首先,它打印缩进的节名称。如果 fullPrint 参数为 false,它将只打印节名称,否则还会打印节名称的地址。 + * 然后,它循环遍历配置节中的值、数组和子节,并分别打印它们。 + */ void ConfigSection::Print(LogLevel logLevel, bool fullPrint) const { // print indented section name @@ -83,91 +94,165 @@ void ConfigSection::Print(LogLevel logLevel, bool fullPrint) const ++itr3; } } +/* + * 功能:获取当前配置节中所有子节的名称 + * + * 参数列表: + * sectionNames:输出参数,包含子节名称的字符串列表 + * + * 返回值: + * 如果成功获取子节名称,返回 true;如果在获取过程中出现错误,返回 false。 + * + * 注意: + * 此函数用于获取当前配置节中所有子节的名称,并将它们存储在 sectionNames 列表中。 + * 如果成功获取所有子节名称,函数返回 true。如果在添加子节名称到列表时遇到内存分配错误, + * 函数将报告错误并返回 false。 + */ -bool ConfigSection::GetConfigSectionNames(mot_string_list& sectionNames) const +bool ConfigSection::GetConfigSectionNames(mot_string_list §ionNames) const { - bool result = true; - ConfigSectionMap::const_iterator itr = m_sectionMap.begin(); - while (itr != m_sectionMap.end()) { - result = sectionNames.push_back(itr->first); - if (!result) { - MOT_REPORT_ERROR(MOT_ERROR_OOM, - "Load Configuration", - "Failed to get section names (%u retrieved up to failed point)", - sectionNames.size()); - break; + bool result = true; // 初始化返回值为 true + ConfigSectionMap::const_iterator itr = m_sectionMap.begin(); // 获取子节的迭代器,并初始化为开始位置 + + while (itr != m_sectionMap.end()) { // 遍历子节映射 + result = sectionNames.push_back(itr->first); // 将子节名称添加到结果列表中 + if (!result) { // 如果添加失败,说明内存不足 + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Load Configuration", + "Failed to get section names (%u retrieved up to failed point)", + sectionNames.size()); // 报告内存分配失败 + break; // 退出循环 } - ++itr; + ++itr; // 移动到下一个子节 } - return result; + + return result; // 返回是否成功 } -bool ConfigSection::GetConfigValueNames(mot_string_list& valueNames) const +/* + * 功能:获取当前配置节中所有值和数组的名称 + * + * 参数列表: + * valueNames:输出参数,包含值和数组名称的字符串列表 + * + * 返回值: + * 如果成功获取值和数组名称,返回 true;如果在获取过程中出现错误,返回 false。 + * + * 注意: + * 此函数用于获取当前配置节中所有值和数组的名称,并将它们存储在 valueNames 列表中。 + * 如果成功获取所有名称,函数返回 true。如果在添加名称到列表时遇到内存分配错误, + * 函数将报告错误并返回 false。 + */ +bool ConfigSection::GetConfigValueNames(mot_string_list &valueNames) const { bool result = true; + + // 遍历值的映射并添加名称到列表 ConfigValueMap::const_iterator itr = m_valueMap.begin(); while (itr != m_valueMap.end()) { if (!valueNames.push_back(itr->first)) { - MOT_REPORT_ERROR(MOT_ERROR_OOM, - "Load Configuration", - "Failed to get value names (%u retrieved up to failed point)", - valueNames.size()); + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Load Configuration", + "Failed to get value names (%u retrieved up to failed point)", valueNames.size()); return false; } ++itr; } + + // 遍历数组的映射并添加名称到列表 ConfigArrayMap::const_iterator itr2 = m_arrayMap.begin(); while (itr2 != m_arrayMap.end()) { if (!valueNames.push_back(itr2->first)) { - MOT_REPORT_ERROR(MOT_ERROR_OOM, - "Load Configuration", - "Failed to get value names (%u retrieved up to failed point)", - valueNames.size()); + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Load Configuration", + "Failed to get value names (%u retrieved up to failed point)", valueNames.size()); return false; } ++itr2; } + return true; } - -bool ConfigSection::AddConfigItem(ConfigItem* configItem, bool replaceIfExists /* = false */) +/* + * 功能:向当前配置节添加配置项 + * + * 参数列表: + * configItem:要添加的配置项 + * replaceIfExists:如果为 true,则如果已存在同名的配置项,将替换它;如果为 false,则不替换,默认为 false。 + * + * 返回值: + * 如果成功添加配置项,返回 true;如果在添加过程中出现错误或配置项类别无效,返回 false。 + * + * 注意: + * 此函数用于向当前配置节添加配置项,可以是配置节、配置值或配置数组。 + * 根据配置项的类别,函数会分派给不同的添加函数来处理。 + * 如果 replaceIfExists 为 true,如果已存在同名的配置项,将替换它; + * 如果为 false,如果同名配置项已存在,函数将返回 false。 + */ +bool ConfigSection::AddConfigItem(ConfigItem *configItem, bool replaceIfExists /* = false */) { bool result = false; + // 根据配置项的类别分派到不同的添加函数 switch (configItem->GetClass()) { case ConfigItemClass::CONFIG_ITEM_SECTION: - result = AddConfigSection(static_cast(configItem)); + result = AddConfigSection(static_cast(configItem)); break; case ConfigItemClass::CONFIG_ITEM_VALUE: - result = AddConfigValue(static_cast(configItem), replaceIfExists); + result = AddConfigValue(static_cast(configItem), replaceIfExists); break; case ConfigItemClass::CONFIG_ITEM_ARRAY: - result = AddConfigArray(static_cast(configItem)); + result = AddConfigArray(static_cast(configItem)); break; default: - MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, - "Load Configuration", - "Invalid configuration item class %d", - (int)configItem->GetClass()); + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Load Configuration", "Invalid configuration item class %d", + (int)configItem->GetClass()); break; } return result; } -bool ConfigSection::Merge(ConfigSection* configSection) +/* + * 功能:合并配置节 + * + * 参数列表: + * configSection:要合并的配置节 + * + * 返回值: + * 如果成功合并配置节中的配置项,返回 true;否则返回 false。 + * + * 注意: + * 此函数用于合并两个配置节的配置项,包括配置值和配置数组。 + * 它首先尝试合并配置值,然后合并配置数组。 + */ +bool ConfigSection::Merge(ConfigSection *configSection) { + // 调用合并配置值和配置数组的函数,两者都成功才返回 true return MergeValues(configSection) && MergeArrays(configSection); } -const ConfigItem* ConfigSection::GetConfigItem(const char* name) const +/* + * 功能:获取指定名称的配置项 + * + * 参数列表: + * name:要获取的配置项的名称 + * + * 返回值: + * 返回指向匹配名称的配置项的指针,如果没有找到匹配项则返回 nullptr。 + * + * 注意: + * 此函数用于从配置节中检索具有指定名称的配置项。 + * 它首先在子节中查找,然后在配置值和配置数组中查找匹配项。 + */ +const ConfigItem *ConfigSection::GetConfigItem(const char *name) const { - const ConfigItem* result = nullptr; + const ConfigItem *result = nullptr; + + // 首先在子节中查找 ConfigSectionMap::const_iterator itr = m_sectionMap.find(name); if (itr != m_sectionMap.end()) { result = itr->second; } else { + // 如果在子节中没有找到匹配项,再在配置值和配置数组中查找 ConfigValueMap::const_iterator itr2 = m_valueMap.find(name); if (itr2 != m_valueMap.end()) { result = itr2->second; @@ -178,33 +263,80 @@ const ConfigItem* ConfigSection::GetConfigItem(const char* name) const } } } + return result; } -const ConfigSection* ConfigSection::GetConfigSection(const char* name) const +/* + * 功能:获取指定名称的配置节 + * + * 参数列表: + * name:要获取的配置节的名称 + * + * 返回值: + * 返回指向匹配名称的配置节的指针,如果没有找到匹配项则返回 nullptr。 + * + * 注意: + * 此函数用于从配置节中检索具有指定名称的子配置节。 + * 它在当前配置节的子节映射中查找匹配项,如果找到则返回相应的指针,否则返回 nullptr。 + */ +const ConfigSection *ConfigSection::GetConfigSection(const char *name) const { - const ConfigSection* result = nullptr; + const ConfigSection *result = nullptr; + + // 在子节映射中查找匹配项 ConfigSectionMap::const_iterator itr = m_sectionMap.find(name); if (itr != m_sectionMap.end()) { result = itr->second; } + return result; } -const ConfigArray* ConfigSection::GetConfigArray(const char* name) const +/* + * 功能:获取指定名称的配置数组 + * + * 参数列表: + * name:要获取的配置数组的名称 + * + * 返回值: + * 返回指向匹配名称的配置数组的指针,如果没有找到匹配项则返回 nullptr。 + * + * 注意: + * 此函数用于从配置节中检索具有指定名称的子配置数组。 + * 它在当前配置节的子数组映射中查找匹配项,如果找到则返回相应的指针,否则返回 nullptr。 + */ +const ConfigArray *ConfigSection::GetConfigArray(const char *name) const { - const ConfigArray* result = nullptr; + const ConfigArray *result = nullptr; + + // 在子数组映射中查找匹配项 ConfigArrayMap::const_iterator itr = m_arrayMap.find(name); if (itr != m_arrayMap.end()) { result = itr->second; } + return result; } -ConfigItem* ConfigSection::ModifyConfigItem(const char* name) +/* + * 功能:修改指定名称的配置项 + * + * 参数列表: + * name:要修改的配置项的名称 + * + * 返回值: + * 返回指向匹配名称的配置项的指针,如果没有找到匹配项则返回 nullptr。 + * + * 注意: + * 此函数用于修改配置节中具有指定名称的子配置项。 + * 它在当前配置节的子配置项映射中查找匹配项,如果找到则返回相应的指针,否则返回 nullptr。 + */ +ConfigItem *ConfigSection::ModifyConfigItem(const char *name) { - ConfigItem* result = nullptr; + ConfigItem *result = nullptr; + // 在子配置项映射中查找匹配项 ConfigSectionMap::iterator itr = m_sectionMap.find(name); if (itr != m_sectionMap.end()) { result = itr->second; @@ -223,166 +355,311 @@ ConfigItem* ConfigSection::ModifyConfigItem(const char* name) return result; } -ConfigSection* ConfigSection::ModifyConfigSection(const char* name) +/* + * 功能:修改指定名称的配置节 + * + * 参数列表: + * name:要修改的配置节的名称 + * + * 返回值: + * 返回指向匹配名称的配置节的指针,如果没有找到匹配项则返回 nullptr。 + * + * 注意: + * 此函数用于修改当前配置节中具有指定名称的子配置节。 + * 它在当前配置节的子配置节映射中查找匹配项,如果找到则返回相应的指针,否则返回 nullptr。 + */ +ConfigSection *ConfigSection::ModifyConfigSection(const char *name) { - ConfigSection* result = nullptr; + ConfigSection *result = nullptr; + + // 在子配置节映射中查找匹配项 ConfigSectionMap::iterator itr = m_sectionMap.find(name); if (itr != m_sectionMap.end()) { result = itr->second; } + return result; } -ConfigArray* ConfigSection::ModifyConfigArray(const char* name) +/* + * 功能:修改指定名称的配置数组 + * + * 参数列表: + * name:要修改的配置数组的名称 + * + * 返回值: + * 返回指向匹配名称的配置数组的指针,如果没有找到匹配项则返回 nullptr。 + * + * 注意: + * 此函数用于修改当前配置节中具有指定名称的子配置数组。 + * 它在当前配置节的子配置数组映射中查找匹配项,如果找到则返回相应的指针,否则返回 nullptr。 + */ +ConfigArray *ConfigSection::ModifyConfigArray(const char *name) { - ConfigArray* result = nullptr; + ConfigArray *result = nullptr; + + // 在子配置数组映射中查找匹配项 ConfigArrayMap::iterator itr = m_arrayMap.find(name); if (itr != m_arrayMap.end()) { result = itr->second; } + return result; } -void ConfigSection::ForEach(ConfigItemVisitor& visitor) const +/* + * 功能:对当前配置节及其所有子项应用访问者对象的操作 + * + * 参数列表: + * visitor:用于访问配置项的访问者对象 + * + * 注意: + * 此函数用于递归地遍历当前配置节及其所有子项,并对每个配置项应用访问者对象的操作。 + * 访问者对象必须实现 `OnConfigItem` 方法以处理配置项。 + * 对每个值、数组和子配置节,此函数还会分别调用适当的 `ForEachValue`、`ForEachArray` 和 `ForEachSection` 方法。 + */ +void ConfigSection::ForEach(ConfigItemVisitor &visitor) const { + // 对当前配置节应用访问者对象的操作 visitor.OnConfigItem(this); + + // 对当前配置节的值应用访问者对象的操作 ForEachValue(visitor); + + // 对当前配置节的数组应用访问者对象的操作 ForEachArray(visitor); + + // 对当前配置节的子配置节应用访问者对象的操作 ForEachSection(visitor); } -bool ConfigSection::AddConfigSection(ConfigSection* configSection) +/* + * 功能:向当前配置节添加子配置节 + * + * 参数列表: + * configSection:要添加的子配置节对象 + * + * 返回值: + * 如果成功添加子配置节,返回 true;如果子配置节已存在或达到资源限制,返回 false。 + * + * 注意: + * 此函数用于向当前配置节添加子配置节。如果成功添加,将返回 true;如果子配置节已存在,将记录错误并返回 false。 + * 如果达到了资源限制,也会记录错误并返回 false。 + */ +bool ConfigSection::AddConfigSection(ConfigSection *configSection) { + // 尝试将子配置节添加到当前配置节的映射中 ConfigSectionMap::pairis pairis = m_sectionMap.insert(ConfigSectionMap::value_type(configSection->GetName(), configSection)); + + // 处理插入结果 if (pairis.second == INSERT_EXISTS) { - MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, - "Load Configuration", - "Cannot add configuration section %s to section %s: section already exists", - configSection->GetName(), - GetName()); + // 如果子配置节已经存在,记录错误 + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Load Configuration", + "Cannot add configuration section %s to section %s: section already exists", + configSection->GetName(), GetName()); } else if (pairis.second == INSERT_FAILED) { - MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, - "Load Configuration", - "Failed to add configuration section %s to section %s: reached resource limit", - configSection->GetName(), - GetName()); + // 如果插入失败,记录错误并表示已达到资源限制 + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Load Configuration", + "Failed to add configuration section %s to section %s: reached resource limit", + configSection->GetName(), GetName()); } + + // 返回是否成功添加子配置节 return (pairis.second == INSERT_SUCCESS); } -bool ConfigSection::AddConfigValue(ConfigValue* configValue, bool replaceIfExists) +/* + * 功能:向当前配置节添加配置值 + * + * 参数列表: + * configValue:要添加的配置值对象 + * replaceIfExists:如果配置值已经存在,是否替换它(默认为 false) + * + * 返回值: + * 如果成功添加配置值,返回 true;如果配置值已存在但未替换,或者达到资源限制,返回 false。 + * + * 注意: + * 此函数用于向当前配置节添加配置值。如果成功添加,将返回 true;如果配置值已存在但未替换且 `replaceIfExists` + * 参数为 false,将记录错误并返回 false。如果达到了资源限制,也会记录错误并返回 false。 + * 如果 `replaceIfExists` 参数为 true,则会替换已存在的配置值。 + */ +bool ConfigSection::AddConfigValue(ConfigValue *configValue, bool replaceIfExists) { + // 尝试将配置值添加到当前配置节的映射中 ConfigValueMap::pairis pairis = m_valueMap.insert(ConfigValueMap::value_type(configValue->GetName(), configValue)); + + // 处理插入结果 if (pairis.second == INSERT_EXISTS) { if (replaceIfExists) { - ConfigValue* prevValue = pairis.first->second; + // 如果配置值已经存在,并且允许替换,则替换它 + ConfigValue *prevValue = pairis.first->second; pairis.first->second = configValue; delete prevValue; pairis.second = INSERT_SUCCESS; } else { - MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, - "Load Configuration", - "Cannot add configuration value %s to section %s: value already exists", - configValue->GetName(), - GetName()); + // 如果配置值已经存在但不替换,记录错误 + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Load Configuration", + "Cannot add configuration value %s to section %s: value already exists", + configValue->GetName(), GetName()); } } else if (pairis.second == INSERT_FAILED) { - MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, - "Load Configuration", - "Failed to add configuration value %s to section %s: reached resource limit", - configValue->GetName(), - GetName()); + // 如果插入失败,记录错误并表示已达到资源限制 + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Load Configuration", + "Failed to add configuration value %s to section %s: reached resource limit", + configValue->GetName(), GetName()); } + + // 返回是否成功添加配置值 return (pairis.second == INSERT_SUCCESS); } -bool ConfigSection::AddConfigArray(ConfigArray* configArray) +/* + * 功能:向当前配置节添加配置数组 + * + * 参数列表: + * configArray:要添加的配置数组对象 + * + * 返回值: + * 如果成功添加配置数组,返回 true;如果配置数组已经存在或者达到资源限制,返回 false。 + * + * 注意: + * 此函数用于向当前配置节添加配置数组。如果成功添加,将返回 true;如果配置数组已经存在,将记录错误并返回 false。 + * 如果达到了资源限制,也会记录错误并返回 false。 + */ +bool ConfigSection::AddConfigArray(ConfigArray *configArray) { + // 尝试将配置数组添加到当前配置节的映射中 ConfigArrayMap::pairis pairis = m_arrayMap.insert(ConfigArrayMap::value_type(configArray->GetName(), configArray)); + + // 处理插入结果 if (pairis.second == INSERT_EXISTS) { - MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, - "Load Configuration", - "Cannot add configuration array %s to section %s: array already exists", - configArray->GetName(), - GetName()); + // 如果配置数组已经存在,记录错误 + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Load Configuration", + "Cannot add configuration array %s to section %s: array already exists", + configArray->GetName(), GetName()); } else if (pairis.second == INSERT_FAILED) { - MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, - "Load Configuration", - "Failed to add configuration array %s to section %s: reached resource limit", - configArray->GetName(), - GetName()); + // 如果插入失败,记录错误并表示已达到资源限制 + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Load Configuration", + "Failed to add configuration array %s to section %s: reached resource limit", + configArray->GetName(), GetName()); } + + // 返回是否成功添加配置数组 return (pairis.second == INSERT_SUCCESS); } -void ConfigSection::ForEachValue(ConfigItemVisitor& visitor) const +/* + * 功能:遍历当前配置节中的配置值并对其应用访问者 + * + * 参数列表: + * visitor:一个配置项访问者对象,用于对配置值应用访问者操作 + * + * 注意: + * 此函数用于遍历当前配置节中的配置值,并对每个配置值应用配置项访问者对象中定义的操作。 + */ +void ConfigSection::ForEachValue(ConfigItemVisitor &visitor) const { + // 遍历配置值映射 ConfigValueMap::const_iterator itr = m_valueMap.begin(); while (itr != m_valueMap.end()) { + // 对每个配置值应用访问者操作 visitor.OnConfigItem(itr->second); ++itr; } } -void ConfigSection::ForEachArray(ConfigItemVisitor& visitor) const +/* + * 功能:遍历当前配置节中的配置数组并对其应用访问者 + * + * 参数列表: + * visitor:一个配置项访问者对象,用于对配置数组应用访问者操作 + * + * 注意: + * 此函数用于遍历当前配置节中的配置数组,并对每个配置数组应用配置项访问者对象中定义的操作。 + */ +void ConfigSection::ForEachArray(ConfigItemVisitor &visitor) const { + // 遍历配置数组映射 ConfigArrayMap::const_iterator itr = m_arrayMap.begin(); while (itr != m_arrayMap.end()) { - static_cast(itr->second)->ForEach(visitor); + // 对每个配置数组应用访问者操作 + static_cast(itr->second)->ForEach(visitor); ++itr; } } -void ConfigSection::ForEachSection(ConfigItemVisitor& visitor) const +/* + * 功能:遍历当前配置节中的子配置节并对其应用访问者 + * + * 参数列表: + * visitor:一个配置项访问者对象,用于对子配置节应用访问者操作 + * + * 注意: + * 此函数用于遍历当前配置节中的子配置节,并对每个子配置节应用配置项访问者对象中定义的操作。 + */ +void ConfigSection::ForEachSection(ConfigItemVisitor &visitor) const { + // 遍历子配置节映射 ConfigSectionMap::const_iterator itr = m_sectionMap.begin(); while (itr != m_sectionMap.end()) { - static_cast(itr->second)->ForEach(visitor); + // 对每个子配置节应用访问者操作 + static_cast(itr->second)->ForEach(visitor); ++itr; } } -bool ConfigSection::MergeValues(ConfigSection* configSection) +/* + * 功能:将另一个配置节的值合并到当前配置节中 + * + * 参数列表: + * configSection:包含要合并的值的另一个配置节 + * + * 返回值: + * 如果成功合并值,返回 true;否则返回 false。 + * + * 注意: + * 此函数用于将另一个配置节的值合并到当前配置节中。如果合并的值成功,将返回 true;否则,将返回 false。 + * 如果发生错误,函数会尝试报告错误并回滚操作。 + */ +bool ConfigSection::MergeValues(ConfigSection *configSection) { + // 遍历要合并的配置节中的值映射 ConfigValueMap::iterator itr = configSection->m_valueMap.begin(); while (itr != configSection->m_valueMap.end()) { - ConfigValue* configValue = itr->second; + ConfigValue *configValue = itr->second; + // 将值插入到当前配置节的值映射中 ConfigValueMap::pairis pairis = m_valueMap.insert(ConfigValueMap::value_type(configValue->GetName(), configValue)); if (pairis.second == INSERT_SUCCESS) { MOT_LOG_DEBUG("Inserted new value: %s", configValue->GetName()); } else { if (MOT_IS_SEVERE()) { - MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, - "Load Configuration", - "Failed to merge value %s into section %s", - configValue->GetName(), - GetFullPathName()); + // 报告合并值失败的严重错误 + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Load Configuration", "Failed to merge value %s into section %s", + configValue->GetName(), GetFullPathName()); return false; } - // not inserted, so replace existing value - ConfigValueMap::iterator& itr2 = pairis.first; - ConfigValue* oldConfigValue = itr2->second; + // 未插入,因此替换现有值 + ConfigValueMap::iterator &itr2 = pairis.first; + ConfigValue *oldConfigValue = itr2->second; MOT_LOG_DEBUG(" *** --> Deleting merged section value %s", oldConfigValue->GetFullPathName()); oldConfigValue->Print(LogLevel::LL_DEBUG, true); - m_valueMap.erase(itr2); + m_valueMap.erase(itr2); // 删除旧值 MOT_LOG_DEBUG(" *** --> Delete merged section value done"); - delete oldConfigValue; + delete oldConfigValue; // 删除旧值的内存 if (!m_valueMap.insert(ConfigValueMap::value_type(configValue->GetName(), configValue)).second) { if (MOT_IS_SEVERE()) { - MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, - "Load Configuration", - "Failed to merge value %s into section %s (severe error)", - configValue->GetName(), - GetFullPathName()); + // 报告合并值失败的严重错误 + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Load Configuration", + "Failed to merge value %s into section %s (severe error)", configValue->GetName(), + GetFullPathName()); return false; } - MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, - "Load Configuration", - "Failed to merge value %s into section %s (unexpected duplicate)", - configValue->GetName(), - GetFullPathName()); + // 报告合并值失败的错误,这是一个不应该发生的意外情况 + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Load Configuration", + "Failed to merge value %s into section %s (unexpected duplicate)", + configValue->GetName(), GetFullPathName()); MOT_ASSERT(false); return false; } else { @@ -392,51 +669,63 @@ bool ConfigSection::MergeValues(ConfigSection* configSection) } ++itr; } - configSection->m_valueMap.clear(); // required to make sure items are not deleted in destructor + configSection->m_valueMap.clear(); // 清空要合并的配置节中的值映射,确保不会在析构函数中删除这些项 return true; } -bool ConfigSection::MergeArrays(ConfigSection* configSection) +/* + * 功能:将另一个配置节的数组合并到当前配置节中 + * + * 参数列表: + * configSection:包含要合并的数组的另一个配置节 + * + * 返回值: + * 如果成功合并数组,返回 true;否则返回 false。 + * + * 注意: + * 此函数用于将另一个配置节的数组合并到当前配置节中。如果合并的数组成功,将返回 true;否则,将返回 false。 + * 如果发生错误,函数会尝试报告错误并回滚操作。 + */ +bool ConfigSection::MergeArrays(ConfigSection *configSection) { + // 遍历要合并的配置节中的数组映射 ConfigArrayMap::iterator itr = configSection->m_arrayMap.begin(); while (itr != configSection->m_arrayMap.end()) { - ConfigArray* configArray = itr->second; + ConfigArray *configArray = itr->second; + // 将数组插入到当前配置节的数组映射中 ConfigArrayMap::pairis pairis = m_arrayMap.insert(ConfigArrayMap::value_type(configArray->GetName(), configArray)); if (pairis.second != INSERT_SUCCESS) { if (MOT_IS_SEVERE()) { - MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, - "Load Configuration", - "Failed to merge array %s into section %s", - configArray->GetName(), - GetFullPathName()); + // 报告合并数组失败的严重错误 + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Load Configuration", "Failed to merge array %s into section %s", + configArray->GetName(), GetFullPathName()); return false; } - // not inserted, so replace existing value - ConfigArrayMap::iterator& itr2 = pairis.first; + // 未插入,因此替换现有数组 + ConfigArrayMap::iterator &itr2 = pairis.first; delete itr2->second; m_arrayMap.erase(itr2); if (!m_arrayMap.insert(ConfigArrayMap::value_type(configArray->GetName(), configArray)).second) { if (MOT_IS_SEVERE()) { - MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, - "Load Configuration", - "Failed to merge array %s into section %s (severe error)", - configArray->GetName(), - GetFullPathName()); + // 报告合并数组失败的严重错误 + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Load Configuration", + "Failed to merge array %s into section %s (severe error)", configArray->GetName(), + GetFullPathName()); return false; } - MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, - "Load Configuration", - "Failed to merge array %s into section %s (unexpected duplicate)", - configArray->GetName(), - GetFullPathName()); + // 报告合并数组失败的错误,这是一个不应该发生的意外情况 + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Load Configuration", + "Failed to merge array %s into section %s (unexpected duplicate)", + configArray->GetName(), GetFullPathName()); MOT_ASSERT(false); return false; } } itr++; } - configSection->m_arrayMap.clear(); // required to make sure items are not deleted in destructor + configSection->m_arrayMap.clear(); // 清空要合并的配置节中的数组映射,确保不会在析构函数中删除这些项 return true; } + } // namespace MOT diff --git a/src/gausskernel/storage/mot/core/infra/config/config_tree.cpp b/src/gausskernel/storage/mot/core/infra/config/config_tree.cpp index 0e52a1bac..36fb4b149 100644 --- a/src/gausskernel/storage/mot/core/infra/config/config_tree.cpp +++ b/src/gausskernel/storage/mot/core/infra/config/config_tree.cpp @@ -32,53 +32,67 @@ IMPLEMENT_CLASS_LOGGER(ConfigTree, Configuration) /** @define Limit the depth of a section. */ #define MAX_SECTION_DEPTH 10 - -bool ConfigTree::ConsolidateParsedSections(mot_list& parsedSections, ConfigSectionMap& sectionMap) +/* + * 功能:将解析的配置节合并到配置树中 + * + * 参数列表: + * parsedSections:包含解析的配置节的链表 + * sectionMap:包含配置树中已有配置节的映射 + * + * 返回值: + * 如果成功合并配置节,返回 true;否则返回 false。 + * + * 注意: + * 此函数用于将解析的配置节合并到配置树中。解析的配置节包含在链表 `parsedSections` 中,而已有配置节映射存储在 + * `sectionMap` 中。 函数首先将根配置节与解析的配置节合并,然后将其它配置节添加到映射或者与已有配置节合并。 + * 如果合并失败或者在合并过程中发生错误,函数将尝试报告错误并清理资源。 + */ +bool ConfigTree::ConsolidateParsedSections(mot_list &parsedSections, ConfigSectionMap §ionMap) { // consolidate all parsed section into a map: // 1. new sections are added to map and removed from the list (need to delete section from map in case of error) // 2. duplicate sections are merged and deleted (no need to delete merged section in case of error) // 3. iteration stops on first error, and all sections in section list and map are deleted // 4. on successful execution the section list should be empty + // 初始化结果标志为 true bool result = true; - mot_list::iterator listItr = parsedSections.begin(); + // 遍历解析的配置节链表 + mot_list::iterator listItr = parsedSections.begin(); while (listItr != parsedSections.end()) { - ConfigSection* section = *listItr; - + ConfigSection *section = *listItr; // special case: root section if (section->GetDepth() == 0) { MOT_LOG_DEBUG("Merging root section"); + // 合并根配置节 if (!m_rootSection.Merge(section)) { - MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, - "Load Configuration", - "Malformed configuration file: failed to merge root section"); + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Load Configuration", + "Malformed configuration file: failed to merge root section"); result = false; break; // go to cleanup after error (currently iterated section will be cleaned up from list) } // cleanup merged section now, even if we fail later (because if we don't fail, this is a memory leak) delete section; } else { + // 查找在映射中是否存在当前配置节 ConfigSectionMap::iterator mapItr = sectionMap.find(section->GetFullPathName()); if (mapItr == sectionMap.end()) { + // 插入新配置节到映射 // insert new section MOT_LOG_DEBUG("Adding section: %s (name: %s)", section->GetFullPathName(), section->GetName()); if (!sectionMap.insert(ConfigSectionMap::value_type(section->GetFullPathName(), section)).second) { - MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, - "Load Configuration", - "Failed to build configuration tree, cannot add section %s", - section->GetFullPathName()); + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Load Configuration", + "Failed to build configuration tree, cannot add section %s", + section->GetFullPathName()); result = false; break; // go to cleanup after error (currently iterated section will be cleaned up from list) } } else { // merge into existing section - ConfigSection* existingSection = mapItr->second; + ConfigSection *existingSection = mapItr->second; MOT_LOG_DEBUG("Merging section: %s (name: %s)", section->GetFullPathName(), section->GetName()); if (!existingSection->Merge(section)) { - MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, - "Load Configuration", - "Failed to merge section %s", - section->GetFullPathName()); + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Load Configuration", "Failed to merge section %s", + section->GetFullPathName()); result = false; break; // go to cleanup after error (currently iterated section will be cleaned up from list) } @@ -98,14 +112,14 @@ bool ConfigTree::ConsolidateParsedSections(mot_list& parsedSecti // cleanup parsed sections and section map listItr = parsedSections.begin(); while (listItr != parsedSections.end()) { - ConfigSection* section = *listItr; + ConfigSection *section = *listItr; delete section; ++listItr; } parsedSections.clear(); ConfigSectionMap::iterator mapItr = sectionMap.begin(); while (mapItr != sectionMap.end()) { - ConfigSection* section = mapItr->second; + ConfigSection *section = mapItr->second; delete section; ++mapItr; } @@ -113,8 +127,22 @@ bool ConfigTree::ConsolidateParsedSections(mot_list& parsedSecti } return result; } - -bool ConfigTree::LinkConsolidatedSectionMap(ConfigSectionMap& sectionMap) +/* + * 功能:链接合并后的配置节映射 + * + * 参数列表: + * sectionMap:包含合并后的配置节映射 + * + * 返回值: + * 如果成功链接配置节,返回 true;否则返回 false。 + * + * 注意: + * 此函数用于链接合并后的配置节映射。合并后的配置节映射包含在 `sectionMap` 中。 + * 函数遍历映射中的每个配置节,找到或创建其父配置节,然后将子配置节添加到父配置节中或与已有的子配置节合并。 + * 如果链接失败或在链接过程中发生错误,函数将尝试报告错误并清理资源。 + * 在成功执行时,配置节映射应为空。 + */ +bool ConfigTree::LinkConsolidatedSectionMap(ConfigSectionMap §ionMap) { // link consolidated section map. for each iterated section: // 1. find or create parent section @@ -122,21 +150,21 @@ bool ConfigTree::LinkConsolidatedSectionMap(ConfigSectionMap& sectionMap) // 3. duplicate sub-sections are merged and deleted (no need to delete merged section in case of error) // 4. iteration stops on first error, and all sections in section map are deleted // 5. on successful execution the section map should be empty + // 初始化结果标志为 true bool result = true; MOT_LOG_DEBUG("Linking sections"); + // 遍历配置节映射 ConfigSectionMap::iterator mapItr = sectionMap.begin(); while (mapItr != sectionMap.end()) { - ConfigSection* section = mapItr->second; + ConfigSection *section = mapItr->second; MOT_LOG_DEBUG("Linking section %s with depth %u", section->GetFullPathName(), section->GetDepth()); bool created = false; // get the parent or create a new one (recursively) - ConfigSection* parent = GetOrCreateParent(section, 0, created); + ConfigSection *parent = GetOrCreateParent(section, 0, created); if (parent == nullptr) { - MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, - "Load Configuration", - "Failed to get or create parent of section %s", - section->GetFullPathName()); + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Load Configuration", "Failed to get or create parent of section %s", + section->GetFullPathName()); result = false; break; } @@ -144,21 +172,16 @@ bool ConfigTree::LinkConsolidatedSectionMap(ConfigSectionMap& sectionMap) // add or merge the section into its parent if (!parent->ContainsConfigSection(section->GetName())) { if (!parent->AddConfigItem(section)) { - MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, - "Load Configuration", - "Failed to add section %s to new parent %s", - section->GetFullPathName(), - parent->GetFullPathName()); + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Load Configuration", "Failed to add section %s to new parent %s", + section->GetFullPathName(), parent->GetFullPathName()); result = false; break; } } else { - ConfigSection* existingSection = (ConfigSection*)parent->GetConfigSection(section->GetName()); + ConfigSection *existingSection = (ConfigSection *)parent->GetConfigSection(section->GetName()); if (!existingSection->Merge(section)) { - MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, - "Load Configuration", - "Failed to merge existing section %s", - section->GetFullPathName()); + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Load Configuration", "Failed to merge existing section %s", + section->GetFullPathName()); result = false; break; } @@ -176,7 +199,7 @@ bool ConfigTree::LinkConsolidatedSectionMap(ConfigSectionMap& sectionMap) if (!result) { mapItr = sectionMap.begin(); while (mapItr != sectionMap.end()) { - ConfigSection* section = mapItr->second; + ConfigSection *section = mapItr->second; delete section; ++mapItr; } @@ -184,18 +207,33 @@ bool ConfigTree::LinkConsolidatedSectionMap(ConfigSectionMap& sectionMap) } return result; } - -bool ConfigTree::Build(mot_list& parsedSections) +/* + * 功能:构建配置树 + * + * 参数列表: + * parsedSections:包含解析后的配置节的链表 + * + * 返回值: + * 如果成功构建配置树,返回 true;否则返回 false。 + * + * 注意: + * 此函数用于构建配置树。它首先将所有配置节按照完整路径名插入映射中。 + * 然后,它尝试链接合并后的配置节映射,将子配置节添加到父配置节中或与已有的子配置节合并。 + * 如果构建失败,函数将尝试报告错误,并返回 false。 + */ +bool ConfigTree::Build(mot_list &parsedSections) { bool result = true; MOT_LOG_DEBUG("Building configuration tree"); // insert all section to map according to full path name + // 将所有配置节按照完整路径名插入映射中 ConfigSectionMap sectionMap; if (!ConsolidateParsedSections(parsedSections, sectionMap)) { MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Load Configuration", "Failed to consolidate parsed section list"); result = false; } else { + // 尝试链接合并后的配置节映射 if (!LinkConsolidatedSectionMap(sectionMap)) { MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Load Configuration", "Failed to build configuration tree"); result = false; @@ -211,25 +249,36 @@ bool ConfigTree::Build(mot_list& parsedSections) } return result; } - -const ConfigItem* ConfigTree::GetConfigItem(const char* fullPathName) const +/* + * 功能:根据完整路径名获取配置项 + * + * 参数列表: + * fullPathName:配置项的完整路径名 + * + * 返回值: + * 如果找到指定路径的配置项,返回指向该配置项的指针;如果未找到,返回 nullptr。 + * + * 注意: + * 此函数用于从配置树中获取具有给定完整路径名的配置项。它首先将路径名分割为各个组件, + * 然后逐级遍历配置树以查找指定的配置项。如果找到配置项,函数将返回指向该配置项的指针; + * 如果未找到,将返回 nullptr。 + */ +const ConfigItem *ConfigTree::GetConfigItem(const char *fullPathName) const { MOT_LOG_DEBUG("Getting from tree %s config item %s: starting", GetSource(), fullPathName); - const ConfigItem* result = nullptr; + const ConfigItem *result = nullptr; // break path name to components mot_string_list pathComponents; if (!ConfigFileParser::Split(fullPathName, ConfigItem::PATH_SEP, pathComponents)) { - MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, - "Load Configuration", - "Failed to split configuration path %s into components", - fullPathName); + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Load Configuration", + "Failed to split configuration path %s into components", fullPathName); return nullptr; } MOT_LOG_DEBUG("Getting from tree %s config item %s: finished split", GetSource(), fullPathName); // dig into the tree - const ConfigSection* currentSection = &m_rootSection; + const ConfigSection *currentSection = &m_rootSection; mot_string_list::iterator itr = pathComponents.begin(); while (itr != pathComponents.end() && currentSection) { MOT_LOG_DEBUG("Getting from section %s config item %s", currentSection->GetFullPathName(), itr->c_str()); @@ -238,7 +287,7 @@ const ConfigItem* ConfigTree::GetConfigItem(const char* fullPathName) const break; } if (result->GetClass() == ConfigItemClass::CONFIG_ITEM_SECTION) { - currentSection = static_cast(result); + currentSection = static_cast(result); } else { currentSection = nullptr; } @@ -251,38 +300,50 @@ const ConfigItem* ConfigTree::GetConfigItem(const char* fullPathName) const return result; } - -ConfigSection* ConfigTree::GetOrCreateParent(ConfigSection* section, int depth, bool& created) +/* + * 功能:获取或创建配置项的父级部分 + * + * 参数列表: + * section:要获取或创建父级部分的配置项 + * depth:递归深度,用于防止无限递归调用 + * created:用于指示是否已创建新的父级部分 + * + * 返回值: + * 如果成功获取或创建父级部分,则返回指向父级部分的指针;如果出现错误,则返回 nullptr。 + * + * 注意: + * 此函数用于获取或创建配置项的父级部分。它可以递归地创建父级部分,如果父级部分不存在的话。 + * 如果递归深度超过了最大允许值,函数将返回 nullptr 并报告错误。 + * 如果成功获取或创建父级部分,created 参数将指示是否已创建了新的父级部分。 + */ +ConfigSection *ConfigTree::GetOrCreateParent(ConfigSection *section, int depth, bool &created) { MOT_LOG_DEBUG("Building recursive parent for section: %s", section->GetFullPathName()); // guard against endless recurring calls if (depth >= MAX_SECTION_DEPTH) { MOT_REPORT_ERROR(MOT_ERROR_INVALID_STATE, - "Load Configuration" - "Failed to get or create parent of section %s: section depth exceeds maximum allowed (%u)", - section->GetFullPathName(), - (unsigned)MAX_SECTION_DEPTH); + "Load Configuration" + "Failed to get or create parent of section %s: section depth exceeds maximum allowed (%u)", + section->GetFullPathName(), (unsigned)MAX_SECTION_DEPTH); return nullptr; } // get parent or create it (recursively) created = false; - ConfigSection* parent = nullptr; + ConfigSection *parent = nullptr; if (section->GetDepth() == 1) { MOT_LOG_DEBUG("Found root parent of section %s", section->GetFullPathName()); parent = &m_rootSection; } else { - parent = (ConfigSection*)GetConfigSection(section->GetPath()); + parent = (ConfigSection *)GetConfigSection(section->GetPath()); } if (parent == nullptr) { MOT_LOG_DEBUG("Parent of section %s not found, creating", section->GetFullPathName()); parent = CreateParent(section, depth); if (parent == nullptr) { - MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, - "Load Configuration", - "Failed to create section path: %s", - section->GetFullPathName()); + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Load Configuration", "Failed to create section path: %s", + section->GetFullPathName()); return nullptr; } created = true; @@ -293,12 +354,9 @@ ConfigSection* ConfigTree::GetOrCreateParent(ConfigSection* section, int depth, if (depth > 0) { MOT_LOG_TRACE("Linking section %s to its parent %s", section->GetFullPathName(), parent->GetFullPathName()); if (!parent->AddConfigItem(section)) { - MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, - "Load Configuration", - "Failed to add section %s to parent %s (call depth: %d)", - section->GetFullPathName(), - parent->GetFullPathName(), - depth); + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Load Configuration", + "Failed to add section %s to parent %s (call depth: %d)", section->GetFullPathName(), + parent->GetFullPathName(), depth); // if the parent was created, then we should not clean it up, since it is already linked to the tree return nullptr; } @@ -306,10 +364,23 @@ ConfigSection* ConfigTree::GetOrCreateParent(ConfigSection* section, int depth, return parent; } - -ConfigSection* ConfigTree::CreateParent(ConfigSection* section, int depth) +/* + * 功能:创建配置项的父级部分 + * + * 参数列表: + * section:要创建父级部分的配置项 + * depth:递归深度,用于防止无限递归调用 + * + * 返回值: + * 如果成功创建父级部分,则返回指向父级部分的指针;如果出现错误,则返回 nullptr。 + * + * 注意: + * 此函数用于创建配置项的父级部分。它可以递归地创建父级部分,如果父级部分不存在的话。 + * 如果递归深度超过了最大允许值,函数将返回 nullptr 并报告错误。 + */ +ConfigSection *ConfigTree::CreateParent(ConfigSection *section, int depth) { - ConfigSection* parent = nullptr; + ConfigSection *parent = nullptr; mot_string sectionPath; mot_string sectionName; @@ -318,17 +389,16 @@ ConfigSection* ConfigTree::CreateParent(ConfigSection* section, int depth) // guard against endless recurring calls if (depth >= MAX_SECTION_DEPTH) { MOT_REPORT_ERROR(MOT_ERROR_INVALID_STATE, - "Load Configuration" - "Failed to create parent of section %s: section depth exceeds maximum allowed (%u)", - section->GetFullPathName(), - (unsigned)MAX_SECTION_DEPTH); + "Load Configuration" + "Failed to create parent of section %s: section depth exceeds maximum allowed (%u)", + section->GetFullPathName(), (unsigned)MAX_SECTION_DEPTH); return nullptr; } // parse full section name of parent and create the parent if (!ConfigFileParser::BreakSectionName(section->GetPath(), sectionPath, sectionName)) { - MOT_REPORT_ERROR( - MOT_ERROR_INTERNAL, "Load Configuration", "Failed to parse section name: %s", section->GetPath()); + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Load Configuration", "Failed to parse section name: %s", + section->GetPath()); return nullptr; } parent = ConfigSection::CreateConfigSection(sectionPath.c_str(), sectionName.c_str()); @@ -339,12 +409,10 @@ ConfigSection* ConfigTree::CreateParent(ConfigSection* section, int depth) // get the parent of the new parent section (make sure it exists and linked to the parent we just created) bool parentCreated = false; - ConfigSection* grandParent = GetOrCreateParent(parent, depth + 1, parentCreated); + ConfigSection *grandParent = GetOrCreateParent(parent, depth + 1, parentCreated); if (grandParent == nullptr) { - MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, - "Load Configuration", - "Failed to get or create parent of parent section %s", - parent->GetFullPathName()); + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Load Configuration", + "Failed to get or create parent of parent section %s", parent->GetFullPathName()); delete parent; return nullptr; } diff --git a/src/gausskernel/storage/mot/core/infra/config/config_value_type.cpp b/src/gausskernel/storage/mot/core/infra/config/config_value_type.cpp index e6417f8ca..057dccd32 100644 --- a/src/gausskernel/storage/mot/core/infra/config/config_value_type.cpp +++ b/src/gausskernel/storage/mot/core/infra/config/config_value_type.cpp @@ -26,33 +26,38 @@ #include "string.h" namespace MOT { -static const char* CONFIG_VALUE_INT64_STR = "int64"; -static const char* CONFIG_VALUE_INT32_STR = "int32"; -static const char* CONFIG_VALUE_INT16_STR = "int16"; -static const char* CONFIG_VALUE_INT8_STR = "int8"; -static const char* CONFIG_VALUE_UINT64_STR = "uint64"; -static const char* CONFIG_VALUE_UINT32_STR = "uint32"; -static const char* CONFIG_VALUE_UINT16_STR = "uint16"; -static const char* CONFIG_VALUE_UINT8_STR = "uint8"; -static const char* CONFIG_VALUE_DOUBLE_STR = "double"; -static const char* CONFIG_VALUE_BOOL_STR = "bool"; -static const char* CONFIG_VALUE_STRING_STR = "string"; -static const char* CONFIG_VALUE_UNDEFINED_STR = "N/A"; +static const char *CONFIG_VALUE_INT64_STR = "int64"; +static const char *CONFIG_VALUE_INT32_STR = "int32"; +static const char *CONFIG_VALUE_INT16_STR = "int16"; +static const char *CONFIG_VALUE_INT8_STR = "int8"; +static const char *CONFIG_VALUE_UINT64_STR = "uint64"; +static const char *CONFIG_VALUE_UINT32_STR = "uint32"; +static const char *CONFIG_VALUE_UINT16_STR = "uint16"; +static const char *CONFIG_VALUE_UINT8_STR = "uint8"; +static const char *CONFIG_VALUE_DOUBLE_STR = "double"; +static const char *CONFIG_VALUE_BOOL_STR = "bool"; +static const char *CONFIG_VALUE_STRING_STR = "string"; +static const char *CONFIG_VALUE_UNDEFINED_STR = "N/A"; -static const char* VALUE_TYPE_NAMES[] = {CONFIG_VALUE_INT64_STR, - CONFIG_VALUE_INT32_STR, - CONFIG_VALUE_INT16_STR, - CONFIG_VALUE_INT8_STR, - CONFIG_VALUE_UINT64_STR, - CONFIG_VALUE_UINT32_STR, - CONFIG_VALUE_UINT16_STR, - CONFIG_VALUE_UINT8_STR, - CONFIG_VALUE_DOUBLE_STR, - CONFIG_VALUE_BOOL_STR, - CONFIG_VALUE_STRING_STR, - CONFIG_VALUE_UNDEFINED_STR}; - -extern ConfigValueType ConfigValueTypeFromString(const char* valueTypeStr) +static const char *VALUE_TYPE_NAMES[] = {CONFIG_VALUE_INT64_STR, CONFIG_VALUE_INT32_STR, CONFIG_VALUE_INT16_STR, + CONFIG_VALUE_INT8_STR, CONFIG_VALUE_UINT64_STR, CONFIG_VALUE_UINT32_STR, + CONFIG_VALUE_UINT16_STR, CONFIG_VALUE_UINT8_STR, CONFIG_VALUE_DOUBLE_STR, + CONFIG_VALUE_BOOL_STR, CONFIG_VALUE_STRING_STR, CONFIG_VALUE_UNDEFINED_STR}; +/* + * 功能:从字符串中解析配置值的类型 + * + * 参数列表: + * valueTypeStr:要解析的配置值类型的字符串表示 + * + * 返回值: + * 返回从字符串解析的配置值类型(`ConfigValueType` 枚举值)。 + * 如果字符串无法识别,则返回 `CONFIG_VALUE_UNDEFINED`。 + * + * 注意: + * 此函数根据输入的字符串值返回相应的配置值类型。 + * 如果输入字符串无法识别,则返回 `CONFIG_VALUE_UNDEFINED`。 + */ +extern ConfigValueType ConfigValueTypeFromString(const char *valueTypeStr) { ConfigValueType result = ConfigValueType::CONFIG_VALUE_UNDEFINED; @@ -82,15 +87,40 @@ extern ConfigValueType ConfigValueTypeFromString(const char* valueTypeStr) return result; } - -extern const char* ConfigValueTypeToString(ConfigValueType valueType) +/* + * 功能:将配置值类型转换为字符串表示 + * + * 参数列表: + * valueType:要转换为字符串的配置值类型(`ConfigValueType` 枚举值) + * + * 返回值: + * 返回配置值类型的字符串表示。 + * 如果配置值类型无效,则返回表示未定义配置值类型的字符串。 + * + * 注意: + * 此函数将给定的配置值类型转换为相应的字符串表示。 + * 如果配置值类型无效,则返回表示未定义配置值类型的字符串。 + */ +extern const char *ConfigValueTypeToString(ConfigValueType valueType) { if (valueType < ConfigValueType::CONFIG_VALUE_UNDEFINED) { return VALUE_TYPE_NAMES[(uint32_t)valueType]; } return CONFIG_VALUE_UNDEFINED_STR; } - +/* + * 功能:检查配置值类型是否为整数类型 + * + * 参数列表: + * valueType:要检查的配置值类型(`ConfigValueType` 枚举值) + * + * 返回值: + * 如果配置值类型是整数类型,则返回 true,否则返回 false。 + * + * 注意: + * 此函数用于检查给定的配置值类型是否表示整数类型。 + * 如果配置值类型为整数类型之一,则返回 true,否则返回 false。 + */ extern bool IsConfigValueIntegral(ConfigValueType valueType) { bool result = false; diff --git a/src/gausskernel/storage/mot/core/infra/config/ext_config_loader.cpp b/src/gausskernel/storage/mot/core/infra/config/ext_config_loader.cpp index 2a96c64b5..a395e467f 100644 --- a/src/gausskernel/storage/mot/core/infra/config/ext_config_loader.cpp +++ b/src/gausskernel/storage/mot/core/infra/config/ext_config_loader.cpp @@ -27,7 +27,17 @@ namespace MOT { IMPLEMENT_CLASS_LOGGER(ExtConfigLoader, Configuration) - +/* + * 功能:加载外部配置 + * + * 返回值: + * 如果成功加载外部配置,则返回 true,否则返回 false。 + * + * 注意: + * 此函数用于加载外部配置。它包含了加载、处理和最终化外部配置的步骤。 + * 如果在任何步骤中出现错误,函数将返回 false,并记录适当的错误消息。 + * 如果成功加载外部配置,则最终配置信息将打印到日志(如果日志级别为 `LogLevel::LL_TRACE`)。 + */ bool ExtConfigLoader::LoadExtConfig() { bool result = false; @@ -41,11 +51,13 @@ bool ExtConfigLoader::LoadExtConfig() MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Load Configuration", "Failed to load external configuration"); } else { // wrap up + // 完成加载外部配置 result = EndExtConfigLoad(); if (!result) { - MOT_REPORT_ERROR( - MOT_ERROR_INTERNAL, "Load Configuration", "Failed to finalize external configuration loading"); + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Load Configuration", + "Failed to finalize external configuration loading"); } else { + // 如果日志级别为 LogLevel::LL_TRACE,则打印已加载的外部配置信息 if (MOT_CHECK_LOG_LEVEL(LogLevel::LL_TRACE)) { MOT_LOG_INFO("Loaded external configuration:"); m_extConfigTree->Print(LogLevel::LL_TRACE); @@ -56,73 +68,93 @@ bool ExtConfigLoader::LoadExtConfig() return result; } -bool ExtConfigLoader::AddExtStringConfigItem(const char* path, const char* key, const char* value) +bool ExtConfigLoader::AddExtStringConfigItem(const char *path, const char *key, const char *value) { return AddTypedConfigItem(path, key, value); } -bool ExtConfigLoader::AddExtUInt64ConfigItem(const char* path, const char* key, uint64_t value) +bool ExtConfigLoader::AddExtUInt64ConfigItem(const char *path, const char *key, uint64_t value) { return AddTypedConfigItem(path, key, value); } -bool ExtConfigLoader::AddExtUInt32ConfigItem(const char* path, const char* key, uint32_t value) +bool ExtConfigLoader::AddExtUInt32ConfigItem(const char *path, const char *key, uint32_t value) { return AddTypedConfigItem(path, key, value); } -bool ExtConfigLoader::AddExtUInt16ConfigItem(const char* path, const char* key, uint16_t value) +bool ExtConfigLoader::AddExtUInt16ConfigItem(const char *path, const char *key, uint16_t value) { return AddTypedConfigItem(path, key, value); } -bool ExtConfigLoader::AddExtUInt8ConfigItem(const char* path, const char* key, uint8_t value) +bool ExtConfigLoader::AddExtUInt8ConfigItem(const char *path, const char *key, uint8_t value) { return AddTypedConfigItem(path, key, value); } -bool ExtConfigLoader::AddExtInt64ConfigItem(const char* path, const char* key, int64_t value) +bool ExtConfigLoader::AddExtInt64ConfigItem(const char *path, const char *key, int64_t value) { return AddTypedConfigItem(path, key, value); } -bool ExtConfigLoader::AddExtInt32ConfigItem(const char* path, const char* key, int32_t value) +bool ExtConfigLoader::AddExtInt32ConfigItem(const char *path, const char *key, int32_t value) { return AddTypedConfigItem(path, key, value); } -bool ExtConfigLoader::AddExtInt16ConfigItem(const char* path, const char* key, int16_t value) +bool ExtConfigLoader::AddExtInt16ConfigItem(const char *path, const char *key, int16_t value) { return AddTypedConfigItem(path, key, value); } -bool ExtConfigLoader::AddExtInt8ConfigItem(const char* path, const char* key, int8_t value) +bool ExtConfigLoader::AddExtInt8ConfigItem(const char *path, const char *key, int8_t value) { return AddTypedConfigItem(path, key, value); } -bool ExtConfigLoader::AddExtDoubleConfigItem(const char* path, const char* key, double value) +bool ExtConfigLoader::AddExtDoubleConfigItem(const char *path, const char *key, double value) { return AddTypedConfigItem(path, key, value); } -bool ExtConfigLoader::AddExtBoolConfigItem(const char* path, const char* key, bool value) +bool ExtConfigLoader::AddExtBoolConfigItem(const char *path, const char *key, bool value) { return AddTypedConfigItem(path, key, value); } -bool ExtConfigLoader::AddExtConfigItem(ConfigItem* configItem) +/* + * 功能:添加外部配置项 + * + * 参数: + * configItem - 要添加的外部配置项 + * + * 返回值: + * 如果成功添加配置项,则返回 true,否则返回 false。 + * + * 注意: + * 此函数用于添加外部配置项。它首先查找配置项的父部分,如果不存在,则创建新的父部分。 + * 然后将配置项添加到父部分中。如果父部分不存在,或者添加失败,则函数返回 false。 + * 如果配置项已存在于父部分中,它可能会记录警告消息,但不会导致函数失败。 + */ +bool ExtConfigLoader::AddExtConfigItem(ConfigItem *configItem) { - ConfigSection* section = nullptr; + ConfigSection *section = nullptr; ConfigSectionMap::iterator itr = m_sectionMap.find(configItem->GetPath()); + + // 如果配置项的父部分不存在,创建新的父部分 if (itr == m_sectionMap.end()) { mot_string sectionPath; mot_string sectionName; - if (!ConfigFileParser::BreakSectionName( - configItem->GetPath(), sectionPath, sectionName, ConfigItem::PATH_SEP)) { + + // 解析配置项的路径,以获取父部分的路径和名称 + if (!ConfigFileParser::BreakSectionName(configItem->GetPath(), sectionPath, sectionName, + ConfigItem::PATH_SEP)) { MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Load Configuration", "Failed to parse external section name"); return false; } + + // 创建新的配置部分 section = ConfigSection::CreateConfigSection(sectionPath.c_str(), sectionName.c_str()); if (section == nullptr) { MOT_REPORT_ERROR(MOT_ERROR_OOM, "Load Configuration", "Failed to create new external section"); @@ -135,10 +167,8 @@ bool ExtConfigLoader::AddExtConfigItem(ConfigItem* configItem) } if (!m_sectionMap.insert(ConfigSectionMap::value_type(section->GetFullPathName(), section)).second) { if (MOT_IS_SEVERE()) { - MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, - "Load Configuration", - "Failed to insert external section %s", - section->GetFullPathName()); + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Load Configuration", "Failed to insert external section %s", + section->GetFullPathName()); delete section; return false; } @@ -149,27 +179,39 @@ bool ExtConfigLoader::AddExtConfigItem(ConfigItem* configItem) section = itr->second; } + // 将配置项添加到父部分中 if (!section->AddConfigItem(configItem)) { if (MOT_IS_SEVERE()) { - MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, - "Load Configuration", - "Failed to add external configuration item %s to parent section %s", - configItem->GetPath(), - section->GetFullPathName()); + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Load Configuration", + "Failed to add external configuration item %s to parent section %s", configItem->GetPath(), + section->GetFullPathName()); return false; } MOT_LOG_WARN("Failed to add duplicate external configuration item %s to parent section %s", - configItem->GetPath(), - section->GetFullPathName()); + configItem->GetPath(), section->GetFullPathName()); } return true; } +/* + * 功能:结束外部配置加载 + * + * 返回值: + * 如果成功结束外部配置加载并构建配置树,则返回 true,否则返回 false。 + * + * 注意: + * 此函数用于结束外部配置加载过程。它创建外部配置树对象,并使用已解析的部分构建配置树。 + * 如果成功构建配置树,则返回 true,否则返回 false。如果构建失败,它会记录错误消息并清理已创建的树对象。 + */ bool ExtConfigLoader::EndExtConfigLoad() { bool result = false; + + // 创建外部配置树对象 m_extConfigTree = ConfigTree::CreateConfigTree(GetPriority(), GetName(), false); if (m_extConfigTree != nullptr) { + + // 使用已解析的部分构建配置树 result = m_extConfigTree->Build(m_parsedSections); if (!result) { MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Load Configuration", "Failed to build external configuration tree"); @@ -182,13 +224,24 @@ bool ExtConfigLoader::EndExtConfigLoad() return result; } +/* + * 功能:清理外部配置加载器的状态 + * + * 注意: + * 此函数用于清理外部配置加载器的状态。它清空了 `m_sectionMap` 和 `m_parsedSections`, + * 并在需要时删除外部配置树对象 `m_extConfigTree`。 + */ void ExtConfigLoader::Cleanup() noexcept { - m_sectionMap.clear(); - m_parsedSections.clear(); + // 清空外部配置加载器的状态 + m_sectionMap.clear(); // 清空配置节映射 + m_parsedSections.clear(); // 清空已解析的配置节列表 + + // 删除外部配置树对象 if (m_extConfigTree != nullptr) { - delete m_extConfigTree; - m_extConfigTree = nullptr; + delete m_extConfigTree; // 删除配置树 + m_extConfigTree = nullptr; // 将配置树指针置为空指针 } } + } // namespace MOT diff --git a/src/gausskernel/storage/mot/core/infra/config/file_line_reader.cpp b/src/gausskernel/storage/mot/core/infra/config/file_line_reader.cpp index a6325f19f..32e583c6a 100644 --- a/src/gausskernel/storage/mot/core/infra/config/file_line_reader.cpp +++ b/src/gausskernel/storage/mot/core/infra/config/file_line_reader.cpp @@ -25,26 +25,41 @@ #include "file_line_reader.h" namespace MOT { -FileLineReader::FileLineReader(const char* configFilePath) - : m_configFilePath(configFilePath), m_configFile(configFilePath), m_lineItr(NULL), m_lineNumber(0) +/* + * 构造函数:创建 FileLineReader 对象并解析配置文件 + * + * @param configFilePath 配置文件的路径 + */ +FileLineReader::FileLineReader(const char *configFilePath) + : m_configFilePath(configFilePath), // 存储配置文件路径 + m_configFile(configFilePath), // 通过文件路径创建配置文件对象 + m_lineItr(NULL), // 将行迭代器初始化为 NULL + m_lineNumber(0) // 初始化行号为 0 { + // 解析配置文件内容 ParseFile(); } FileLineReader::~FileLineReader() {} +/* + * 解析配置文件内容,将每一行存储到容器中 + */ void FileLineReader::ParseFile() { if (m_configFile) { std::string line; while (std::getline(m_configFile, line)) { + // 读取配置文件中的每一行并存储到 m_lines 容器中 m_lines.push_back(line.c_str()); } + // 初始化行迭代器为 m_lines 的开头,行号为 1 m_lineItr = m_lines.cbegin(); m_lineNumber = 1; } else { + // 如果配置文件无法打开,则初始化行迭代器为 m_lines 的结尾 m_lineItr = m_lines.cend(); } } diff --git a/src/gausskernel/storage/mot/core/infra/config/layered_config_tree.cpp b/src/gausskernel/storage/mot/core/infra/config/layered_config_tree.cpp index 28b9a54c0..a9f952874 100644 --- a/src/gausskernel/storage/mot/core/infra/config/layered_config_tree.cpp +++ b/src/gausskernel/storage/mot/core/infra/config/layered_config_tree.cpp @@ -39,7 +39,7 @@ IMPLEMENT_CLASS_LOGGER(LayeredConfigTree, Configuration) class PrintVisitor : public ConfigItemVisitor { public: /** @var The configuration list to build. */ - ConfigItemList* m_printList; + ConfigItemList *m_printList; /** @brief The set of all configuration items that were added. */ mot_set m_itemsAdded; @@ -51,7 +51,7 @@ public: * @brief Constructor. * @param printList The configuration list to build. */ - explicit PrintVisitor(ConfigItemList* printList) : m_printList(printList), m_status(true) + explicit PrintVisitor(ConfigItemList *printList) : m_printList(printList), m_status(true) {} ~PrintVisitor() override @@ -67,7 +67,7 @@ public: * @brief Adds configuration item to print list. * @param configItem The configuration item. */ - void OnConfigItem(const ConfigItem* configItem) override + void OnConfigItem(const ConfigItem *configItem) override { if (!m_status) { return; @@ -75,7 +75,7 @@ public: if (configItem->GetClass() == ConfigItemClass::CONFIG_ITEM_VALUE) { if (m_itemsAdded.find(configItem->GetFullPathName()) == m_itemsAdded.end()) { - if (!m_printList->push_back(const_cast(configItem))) { + if (!m_printList->push_back(const_cast(configItem))) { MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Load Configuration", "Failed to add item to print list"); m_status = false; return; @@ -103,22 +103,31 @@ LayeredConfigTree::~LayeredConfigTree() { ClearConfigTrees(); } - -bool LayeredConfigTree::AddConfigTree(ConfigTree* configTree) +/* + * 功能:向层次化配置树中添加配置树 + * + * 参数列表: + * configTree:要添加的配置树指针 + * + * 返回值: + * 如果成功添加配置树,返回 true;否则返回 false。 + * + * 注意: + * 此函数用于将配置树添加到层次化配置树中。它将配置树根据其优先级插入到适当的位置,以确保按照优先级排序。 + */ +bool LayeredConfigTree::AddConfigTree(ConfigTree *configTree) { bool configTreeAdded = false; - MOT_LOG_DEBUG("Adding configuration tree %p %s with priority %d", - configTree, - configTree->GetSource(), - configTree->GetPriority()); + MOT_LOG_DEBUG("Adding configuration tree %p %s with priority %d", configTree, configTree->GetSource(), + configTree->GetPriority()); // insert sorted LayeredConfigTrees::iterator itr = m_configTrees.begin(); while (itr != m_configTrees.end()) { // check next list (get priority of first tree in the list (all trees in the list have same priority) - ConfigTreeList& configTreeList = *itr; + ConfigTreeList &configTreeList = *itr; MOT_ASSERT(!configTreeList.empty()); - ConfigTree* currConfigTree = *configTreeList.begin(); + ConfigTree *currConfigTree = *configTreeList.begin(); if (currConfigTree->GetPriority() == configTree->GetPriority()) { // matching list found, we are done configTreeAdded = AddConfigTreeToList(configTree, configTreeList); @@ -141,73 +150,121 @@ bool LayeredConfigTree::AddConfigTree(ConfigTree* configTree) return configTreeAdded; } -void LayeredConfigTree::RemoveConfigTree(ConfigTree* configTree) +/* + * 功能:从层次化配置树中移除配置树 + * + * 参数列表: + * configTree:要移除的配置树指针 + * + * 注意: + * 此函数用于从层次化配置树中移除指定的配置树。它会根据配置树的优先级在相应的列表中查找并移除。 + */ +void LayeredConfigTree::RemoveConfigTree(ConfigTree *configTree) { int priority = 0; bool found = false; - MOT_LOG_DEBUG("Removing configuration tree %p %s with priority %d", - configTree, - configTree->GetSource(), - configTree->GetPriority()); + + // 记录调试信息,表示正在移除配置树 + MOT_LOG_DEBUG("Removing configuration tree %p %s with priority %d", configTree, configTree->GetSource(), + configTree->GetPriority()); + + // 逐个检查配置树列表,根据配置树的优先级移除 LayeredConfigTrees::iterator itr = m_configTrees.begin(); while (itr != m_configTrees.end()) { - ConfigTreeList& configTreeList = *itr; + ConfigTreeList &configTreeList = *itr; MOT_ASSERT(!configTreeList.empty()); ConfigTreeList::iterator itr2 = std::find(configTreeList.begin(), configTreeList.end(), configTree); + if (itr2 == configTreeList.end()) { + // 配置树不在当前列表中,继续查找 MOT_LOG_TRACE( "Configuration tree %s not found in list priority %d in layered configuration tree (search continues)", - configTree->GetSource(), - priority); - ++itr; // keep searching - ++priority; // for debug printing + configTree->GetSource(), priority); + ++itr; // 继续搜索 + ++priority; // 用于调试打印 } else { + // 找到配置树,从列表中移除 MOT_LOG_TRACE("Removing configuration tree %s from layered configuration tree", configTree->GetSource()); configTreeList.erase(itr2); + + // 如果列表为空,则从层次化配置树中移除整个列表 if (configTreeList.empty()) { m_configTrees.erase(itr); } + found = true; - break; // stop searching + break; // 停止搜索 } } + if (!found) { + // 未找到指定的配置树 MOT_LOG_WARN("Failed to remove configuration tree %s from layered configuration tree: not found", - configTree->GetSource()); + configTree->GetSource()); } } +/* + * 功能:打印层次化配置树 + * + * 参数列表: + * logLevel:打印的日志级别 + * + * 注意: + * 此函数用于以指定的日志级别打印整个层次化配置树。它会调用 `BuildPrintList` 函数构建打印列表, + * 然后逐个打印配置项。如果构建打印列表失败,函数将记录错误信息并清除错误堆栈。 + */ void LayeredConfigTree::Print(LogLevel logLevel) const { MOT_LOG(logLevel, "Loaded configuration:"); + + // 如果打印列表为空,尝试构建它 if (m_printList.empty()) { - if (!const_cast(this)->BuildPrintList()) { + if (!const_cast(this)->BuildPrintList()) { + // 构建打印列表失败时记录错误信息并清除错误堆栈 MOT_LOG_ERROR_STACK("Failed to build print list for layered configuration tree"); ClearErrorStack(); } } + + // 遍历打印列表并打印每个配置项 ConfigItemList::const_iterator itr = m_printList.begin(); while (itr != m_printList.end()) { - const ConfigItem* configItem = *itr; + const ConfigItem *configItem = *itr; configItem->Print(logLevel, true); ++itr; } } -const ConfigItem* LayeredConfigTree::GetConfigItem(const char* fullPathName) const +/* + * 功能:根据完整路径名获取配置项 + * + * 参数列表: + * fullPathName:要获取的配置项的完整路径名 + * + * 返回值: + * 如果找到匹配的配置项,则返回该配置项;否则返回 nullptr。 + * + * 注意: + * 此函数用于在层次化配置树中查找指定完整路径名的配置项。它会从每个配置树中逐层查找, + * 直到找到匹配的配置项或搜索完整个树。如果找到匹配的配置项,会在调试日志中打印信息。 + */ +const ConfigItem *LayeredConfigTree::GetConfigItem(const char *fullPathName) const { - const ConfigItem* result = nullptr; + const ConfigItem *result = nullptr; - // search item layer by layer + // 逐层搜索配置项 LayeredConfigTrees::const_iterator itr = m_configTrees.begin(); while (result == nullptr && itr != m_configTrees.end()) { - const ConfigTreeList& configTreeList = *itr; + const ConfigTreeList &configTreeList = *itr; ConfigTreeList::const_iterator itr2 = configTreeList.begin(); while (result == nullptr && itr2 != configTreeList.end()) { - const ConfigTree* configTree = *itr2; + const ConfigTree *configTree = *itr2; result = configTree->GetConfigItem(fullPathName); if (result != nullptr) { MOT_LOG_DEBUG("*** --> Found %s", fullPathName); + + // 如果日志级别为 LL_DEBUG,则打印配置项信息 if (MOT_CHECK_LOG_LEVEL(LogLevel::LL_DEBUG)) { result->Print(LogLevel::LL_DEBUG, true); } @@ -220,7 +277,16 @@ const ConfigItem* LayeredConfigTree::GetConfigItem(const char* fullPathName) con return result; } - +/* + * 功能:构建层次化配置树的打印列表 + * + * 返回值: + * 如果成功构建打印列表,返回 true;否则返回 false。 + * + * 注意: + * 此函数用于构建层次化配置树的打印列表。它会逐层遍历每个配置树,将配置项添加到打印列表中。 + * 在构建过程中,使用配置项访问者 PrintVisitor。 + */ bool LayeredConfigTree::BuildPrintList() { // add configuration items layer by layer, only if not added already @@ -230,20 +296,16 @@ bool LayeredConfigTree::BuildPrintList() PrintVisitor pv(&m_printList); LayeredConfigTrees::const_iterator itr = m_configTrees.cbegin(); while (itr != m_configTrees.cend()) { - const ConfigTreeList& configTreeList = *itr; + const ConfigTreeList &configTreeList = *itr; ConfigTreeList::const_iterator itr2 = configTreeList.begin(); while (itr2 != configTreeList.end()) { - const ConfigTree* configTree = *itr2; - MOT_LOG_DEBUG("Scanning config tree %p %s with priority %d", - configTree, - configTree->GetSource(), - configTree->GetPriority()); + const ConfigTree *configTree = *itr2; + MOT_LOG_DEBUG("Scanning config tree %p %s with priority %d", configTree, configTree->GetSource(), + configTree->GetPriority()); configTree->ForEach(pv); if (!pv.GetStatus()) { - MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, - "Load Configuration", - "Failed to build print list for configuration tree %s", - configTree->GetSource()); + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Load Configuration", + "Failed to build print list for configuration tree %s", configTree->GetSource()); return false; } ++itr2; @@ -252,16 +314,21 @@ bool LayeredConfigTree::BuildPrintList() } return true; } - +/* + * 功能:清除层次化配置树中的所有配置树 + * + * 注意: + * 此函数用于清除层次化配置树中的所有配置树。它首先遍历所有配置树,删除非静态配置树,然后清空配置树列表和打印列表。 + */ void LayeredConfigTree::ClearConfigTrees() { MOT_LOG_DEBUG("Clearing all configuration trees from layered configuration tree"); LayeredConfigTrees::iterator itr = m_configTrees.begin(); while (itr != m_configTrees.end()) { - ConfigTreeList& configTreeList = *itr; + ConfigTreeList &configTreeList = *itr; ConfigTreeList::iterator litr = configTreeList.begin(); while (litr != configTreeList.end()) { - ConfigTree* configTree = *litr; + ConfigTree *configTree = *litr; if (!configTree->IsStatic()) { MOT_LOG_DEBUG("Destroying configuration tree %p %s", configTree, configTree->GetSource()); delete configTree; @@ -274,26 +341,59 @@ void LayeredConfigTree::ClearConfigTrees() m_configTrees.clear(); m_printList.clear(); } - -bool LayeredConfigTree::AddNewConfigTreeAt(LayeredConfigTrees::iterator itr, ConfigTree* configTree) +/* + * 功能:在指定位置添加新的配置树 + * + * 参数列表: + * itr:指向插入位置的迭代器 + * configTree:要添加的配置树指针 + * + * 返回值: + * 如果成功添加配置树,返回 true;否则返回 false。 + * + * 注意: + * 此函数用于在指定位置添加新的配置树。它首先尝试将新的配置树列表插入到层次化配置树中, + * 然后在配置树列表中添加配置树。如果插入和添加成功,则返回 true。 + */ +bool LayeredConfigTree::AddNewConfigTreeAt(LayeredConfigTrees::iterator itr, ConfigTree *configTree) { bool configTreeAdded = false; LayeredConfigTrees::pairib pb = m_configTrees.insert(itr, ConfigTreeList()); if (!pb.second) { MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Load Configuration", "Failed to insert configuration tree list"); } else { - ConfigTreeList& configTreeList = *pb.first; + ConfigTreeList &configTreeList = *pb.first; configTreeAdded = AddConfigTreeToList(configTree, configTreeList); } return configTreeAdded; } -bool LayeredConfigTree::AddConfigTreeToList(ConfigTree* configTree, ConfigTreeList& configTreeList) +/* + * 功能:将配置树添加到配置树列表中 + * + * 参数列表: + * configTree:要添加的配置树指针 + * configTreeList:配置树列表,将在其中添加配置树 + * + * 返回值: + * 如果成功添加配置树,返回 true;否则返回 false。 + * + * 注意: + * 此函数用于将配置树添加到配置树列表中。它首先尝试将配置树添加到列表的末尾, + * 如果添加成功,则返回 true,否则返回 false。 + */ +bool LayeredConfigTree::AddConfigTreeToList(ConfigTree *configTree, ConfigTreeList &configTreeList) { + // 将配置树添加到配置树列表的末尾 bool configTreeAdded = configTreeList.push_back(configTree); + + // 检查是否成功添加配置树 if (!configTreeAdded) { + // 如果添加失败,则报告内部错误 MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Load Configuration", "Failed to add configuration tree to new list"); } + return configTreeAdded; } + } // namespace MOT diff --git a/src/gausskernel/storage/mot/core/infra/config/props_config_file_loader.cpp b/src/gausskernel/storage/mot/core/infra/config/props_config_file_loader.cpp index abb8b1474..81b7e5cc0 100644 --- a/src/gausskernel/storage/mot/core/infra/config/props_config_file_loader.cpp +++ b/src/gausskernel/storage/mot/core/infra/config/props_config_file_loader.cpp @@ -29,28 +29,50 @@ namespace MOT { DECLARE_LOGGER(PropsConfigFileLoader, Configuration) - -static bool ParsePropsSectionName(const mot_string& line, mot_string& sectionPath, mot_string& keyValuePart) +/* + * 功能:解析配置项的节名称和键值部分 + * + * 参数列表: + * line:包含配置项的完整行 + * sectionPath:用于存储解析后的节名称的字符串 + * keyValuePart:用于存储解析后的键值部分的字符串 + * + * 返回值: + * 如果成功解析配置项,返回 true;否则返回 false。 + * + * 注意: + * 此函数用于解析配置项的节名称和键值部分。配置项的格式应为“节名称=键值”,其中节名称可能包含路径信息。 + * 函数首先查找等号(=),确保它出现在路径分隔符(如果有的话)之前。然后,它将行分割为节名称和键值部分, + * 并对它们进行修剪(去除前导和尾随空格)。 + */ +static bool ParsePropsSectionName(const mot_string &line, mot_string §ionPath, mot_string &keyValuePart) { bool result = true; // since configuration value might have path names to disk, we need to make sure that section separator appears // BEFORE the equals sign + // 检查等号的位置,确保等号出现在路径分隔符之前 uint32_t equalsPos = line.find('='); if (equalsPos == mot_string::npos) { // this is an illegal format, there must be an equals sign + // 报告内部错误 MOT_REPORT_ERROR(MOT_ERROR_OOM, "Load Configuration", "Failed to parse section name: missing equals sign"); result = false; } else { + // 查找等号前的最后一个路径分隔符的位置 uint32_t lastSlashPos = line.find_last_of(ConfigItem::PATH_SEP, equalsPos); if (lastSlashPos != mot_string::npos) { + // 提取节名称和键值部分 if (!line.substr(sectionPath, 0, lastSlashPos) || !line.substr(keyValuePart, lastSlashPos + 1)) { + // 报告内部错误 MOT_REPORT_ERROR(MOT_ERROR_OOM, "Load Configuration", "Failed to parse section name"); result = false; } else { + // 修剪节名称和键值部分,去除前导和尾随空格 sectionPath.trim(); keyValuePart.trim(); } } else { + // 行中没有路径分隔符,将节名称设置为空字符串,并提取键值部分 sectionPath.assign(""); keyValuePart.assign(line); keyValuePart.trim(); @@ -67,101 +89,169 @@ static bool ParsePropsSectionName(const mot_string& line, mot_string& sectionPat break; \ } -static ConfigSection* GetPropsConfigSection( - const mot_string& sectionFullName, mot_list& parsedSections, ConfigSectionMap& sectionMap) +/* + * 功能:根据完整的节名称获取或创建配置节 + * + * 参数列表: + * sectionFullName:完整的节名称,包括可能的路径 + * parsedSections:存储已解析的配置节的列表 + * sectionMap:配置节的映射,用于快速查找已创建的配置节 + * + * 返回值: + * 如果成功获取或创建配置节,返回指向该配置节的指针;否则返回 nullptr。 + * + * 注意: + * 此函数根据完整的节名称获取或创建配置节。它首先检查给定的节名称是否已存在于配置节映射(sectionMap)中, + * 如果已存在,则直接返回已创建的配置节。如果节名称不存在,则解析节的路径和名称,创建新的配置节, + * 并将其添加到已解析的配置节列表(parsedSections)和配置节映射中。最后,返回指向新创建或已存在的配置节的指针。 + */ +static ConfigSection *GetPropsConfigSection(const mot_string §ionFullName, + mot_list &parsedSections, ConfigSectionMap §ionMap) { mot_string sectionPath; mot_string sectionName; - mot_map::iterator itr = sectionMap.find(sectionFullName); + + // 检查节名称是否已存在于配置节映射中 + mot_map::iterator itr = sectionMap.find(sectionFullName); if (itr == sectionMap.end()) { + // 节名称尚未在映射中找到,需要创建新的配置节 if (!ConfigFileParser::BreakSectionName(sectionFullName, sectionPath, sectionName)) { + // 解析节名称失败,报告内部错误 MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Load Configuration", "Failed to parse section name"); return nullptr; } - ConfigSection* currentSection = ConfigSection::CreateConfigSection(sectionPath.c_str(), sectionName.c_str()); + // 创建新的配置节 + ConfigSection *currentSection = ConfigSection::CreateConfigSection(sectionPath.c_str(), sectionName.c_str()); if (currentSection == nullptr) { + // 分配配置节内存失败,报告内存分配错误 MOT_REPORT_ERROR(MOT_ERROR_OOM, "Load Configuration", "Failed to allocate configuration section"); return nullptr; } + // 将新的配置节添加到已解析的配置节列表中 if (!parsedSections.push_back(currentSection)) { + // 添加到已解析的配置节列表失败,报告内存分配错误 MOT_REPORT_ERROR(MOT_ERROR_OOM, "Load Configuration", "Failed to add parsed section"); return nullptr; } + // 将新的配置节添加到配置节映射中 itr = sectionMap.insert(ConfigSectionMap::value_type(sectionFullName, currentSection)).first; } + // 返回指向新创建或已存在的配置节的指针 return itr->second; } -static bool AddPropsArrayConfigItem(const char* configFilePath, const FileLineReader& reader, const mot_string& line, - ConfigSection* currentSection, const mot_string& sectionFullName, const mot_string& key, const mot_string& value, - uint64_t arrayIndex) +/* + * 功能:将属性数组配置项添加到配置节 + * + * 参数列表: + * configFilePath:配置文件的路径 + * reader:文件行读取器,用于获取当前行号 + * line:包含配置项的文本行 + * currentSection:当前配置节 + * sectionFullName:完整的节名称,包括可能的路径 + * key:配置项的键 + * value:配置项的值 + * arrayIndex:配置项在数组中的索引 + * + * 返回值: + * 如果成功添加属性数组配置项,返回 true;否则返回 false。 + * + * 注意: + * 此函数用于将属性数组配置项添加到配置节中。首先,它尝试获取或创建名为 "key" 的配置数组(ConfigArray), + * 如果该数组不存在,则创建一个新的配置数组,并将其添加到当前配置节中。接下来,它检查数组的索引是否与 + * 预期的索引(arrayIndex)相匹配,以确保数组项按顺序添加。然后,它使用提供的值创建配置项(ConfigItem), + * 并将其添加到配置数组中。如果任何步骤失败,函数将报告错误并返回 false。 + */ +static bool AddPropsArrayConfigItem(const char *configFilePath, const FileLineReader &reader, const mot_string &line, + ConfigSection *currentSection, const mot_string §ionFullName, + const mot_string &key, const mot_string &value, uint64_t arrayIndex) { - ConfigArray* configArray = currentSection->ModifyConfigArray(key.c_str()); + // 尝试获取或创建名为 "key" 的配置数组 + ConfigArray *configArray = currentSection->ModifyConfigArray(key.c_str()); if (configArray == nullptr) { + // 配置数组不存在,创建一个新的配置数组 configArray = ConfigArray::CreateConfigArray(sectionFullName.c_str(), key.c_str()); if (configArray == nullptr) { + // 分配配置数组内存失败,报告内存分配错误 MOT_REPORT_ERROR(MOT_ERROR_OOM, "Load Configuration", "Failed to allocate configuration array"); return false; } + // 将新的配置数组添加到当前配置节中 if (!currentSection->AddConfigItem(configArray)) { - MOT_REPORT_ERROR( - MOT_ERROR_OOM, "Load Configuration", "Failed to add configuration array to parent section"); + // 添加配置数组到父配置节失败,报告内存分配错误 + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Load Configuration", + "Failed to add configuration array to parent section"); return false; } } + // 检查数组项的索引是否按顺序添加 if (arrayIndex != configArray->GetConfigItemCount()) { - // array items must be ordered - MOT_REPORT_ERROR(MOT_ERROR_INVALID_CFG, - "Load Configuration", - "Failed to parse configuration file %s at line %u: %s (array %s items not " - "well-ordered, expecting %u, got %" PRIu64 ")", - configFilePath, - reader.GetLineNumber(), - line.c_str(), - configArray->GetName(), - configArray->GetConfigItemCount(), - arrayIndex); + // 数组项的索引与预期的索引不匹配,报告配置文件解析错误 + MOT_REPORT_ERROR(MOT_ERROR_INVALID_CFG, "Load Configuration", + "Failed to parse configuration file %s at line %u: %s (array %s items not " + "well-ordered, expecting %u, got %" PRIu64 ")", + configFilePath, reader.GetLineNumber(), line.c_str(), configArray->GetName(), + configArray->GetConfigItemCount(), arrayIndex); return false; } - ConfigItem* configItem = ConfigFileParser::MakeArrayConfigValue(sectionFullName, arrayIndex, value); + // 使用提供的值创建配置项并添加到配置数组中 + ConfigItem *configItem = ConfigFileParser::MakeArrayConfigValue(sectionFullName, arrayIndex, value); if (configItem == nullptr) { + // 创建配置数组值失败,报告内部错误 MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Load Configuration", "Failed to create configuration array value"); return false; } if (!configArray->AddConfigItem(configItem)) { - MOT_REPORT_ERROR(MOT_ERROR_OOM, - "Load Configuration", - "Failed to add %" PRIu64 "th item to configuration array %s", - arrayIndex, - configArray->GetName()); + // 将数组项添加到配置数组中失败,报告内存分配错误 + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Load Configuration", + "Failed to add %" PRIu64 "th item to configuration array %s", arrayIndex, + configArray->GetName()); return false; } return true; } - -ConfigTree* PropsConfigFileLoader::LoadConfigFile(const char* configFilePath) +/* + * 功能:从PROPS配置文件加载配置并构建配置树 + * + * 参数列表: + * configFilePath:要加载的配置文件的路径 + * + * 返回值: + * 如果成功加载配置文件并构建配置树,返回一个指向配置树的指针;如果出现错误,则返回nullptr。 + * + * 注意: + * 此函数负责从指定的PROPS配置文件中加载配置并构建配置树。它首先创建一个配置树对象,然后逐行解析配置文件, + * 将配置项添加到相应的配置节中。如果配置文件的格式不正确或解析过程中发生错误,将返回nullptr。否则, + * 将返回指向已加载配置的配置树指针。 + */ +ConfigTree *PropsConfigFileLoader::LoadConfigFile(const char *configFilePath) { + // 记录加载 PROPS 配置文件的跟踪日志 MOT_LOG_TRACE("Loading PROPS configuration file from: %s", configFilePath); - ConfigTree* configTree = ConfigTree::CreateConfigTree(GetPriority(), GetName(), false); + + // 创建配置树,用于存储从配置文件加载的配置项 + ConfigTree *configTree = ConfigTree::CreateConfigTree(GetPriority(), GetName(), false); if (configTree == nullptr) { + // 内存分配失败,报告内存分配错误 MOT_REPORT_ERROR(MOT_ERROR_OOM, "Load Configuration", "Failed to create configuration tree"); return nullptr; } + // 声明一些变量以供后续使用 mot_string line; mot_string sectionFullName; mot_string keyValuePart; - ConfigSection* currentSection = nullptr; - mot_list parsedSections; + ConfigSection *currentSection = nullptr; + mot_list parsedSections; ConfigSectionMap sectionMap; mot_string key; mot_string value; @@ -169,86 +259,102 @@ ConfigTree* PropsConfigFileLoader::LoadConfigFile(const char* configFilePath) uint64_t arrayIndex = 0; bool hasArrayIndex = false; - // unsupported yet + // 初始化文件行读取器以读取配置文件 + // 如果无法打开文件,将发出警告并返回空的配置树 FileLineReader reader(configFilePath); if (!reader.IsValid()) { MOT_LOG_WARN("Failed to load configuration file %s: unable to open file", configFilePath); - // we return an empty tree to avoid errors during startup, but a warning is still issued + // 为了避免在启动过程中出现错误,我们返回一个空的配置树,但仍然发出警告 return configTree; } while (!reader.Eof() && !parseError) { - // parse next non-empty line + // 解析下一行非空行 if (!line.assign(reader.GetLine().c_str())) { + // 内存分配失败,报告内存分配错误并中断解析 PROPS_REPORT_PARSE_ERROR_AND_BREAK(MOT_ERROR_OOM, "Failed to allocate memory for next line"); } line.trim(); if (line.length() && line[0] != '#') { - // break tokens to section and key-value by last separator + // 检查行是否为空并且不是注释行(以 '#' 开头的行) + + // 分解行以获取配置节名称和键值部分 MOT_LOG_DEBUG("Parsing config line: %s", line.c_str()); if (!ParsePropsSectionName(line, sectionFullName, keyValuePart)) { - PROPS_REPORT_PARSE_ERROR_AND_BREAK(MOT_ERROR_INVALID_CFG, + // 配置文件的节名称行格式错误,报告配置文件解析错误并中断解析 + PROPS_REPORT_PARSE_ERROR_AND_BREAK( + MOT_ERROR_INVALID_CFG, "Failed to parse configuration file %s at line %u: %s (section name line malformed)", - configFilePath, - reader.GetLineNumber(), - line.c_str()); + configFilePath, reader.GetLineNumber(), line.c_str()); } - // get the configuration section + // 获取或创建配置节(ConfigSection) currentSection = GetPropsConfigSection(sectionFullName, parsedSections, sectionMap); if (currentSection == nullptr) { + // 获取或创建配置节失败,报告内部错误并中断解析 PROPS_REPORT_PARSE_ERROR_AND_BREAK(MOT_ERROR_INTERNAL, "Failed to get configuration section"); } - // parse the key-value part - if (!ConfigFileParser::ParseKeyValue( - keyValuePart, sectionFullName, key, value, arrayIndex, hasArrayIndex)) { - // key-value line malformed - PROPS_REPORT_PARSE_ERROR_AND_BREAK(MOT_ERROR_INVALID_CFG, - "Failed to parse configuration file %s at line %u: %s (key/value malformed)", - configFilePath, - reader.GetLineNumber(), - line.c_str()); + // 解析键值部分 + if (!ConfigFileParser::ParseKeyValue(keyValuePart, sectionFullName, key, value, arrayIndex, + hasArrayIndex)) { + // 键值行格式错误,报告配置文件解析错误并中断解析 + PROPS_REPORT_PARSE_ERROR_AND_BREAK( + MOT_ERROR_INVALID_CFG, "Failed to parse configuration file %s at line %u: %s (key/value malformed)", + configFilePath, reader.GetLineNumber(), line.c_str()); } - // check for array item + // 检查是否为数组项 if (!hasArrayIndex) { - ConfigItem* configItem = ConfigFileParser::MakeConfigValue(sectionFullName, key, value); + // 非数组项 + + // 创建配置值项(ConfigItem)并将其添加到当前配置节 + ConfigItem *configItem = ConfigFileParser::MakeConfigValue(sectionFullName, key, value); if (configItem == nullptr) { - PROPS_REPORT_PARSE_ERROR_AND_BREAK(MOT_ERROR_INTERNAL, - "Failed to parse configuration file %s at line %u: %s (invalid type specification?)", - configFilePath, - reader.GetLineNumber(), - line.c_str()); - } else if (!currentSection->AddConfigItem(configItem, true)) { + // 创建配置值项失败,报告内部错误并中断解析 PROPS_REPORT_PARSE_ERROR_AND_BREAK( - MOT_ERROR_OOM, "Failed to add configuration item to parent section"); + MOT_ERROR_INTERNAL, + "Failed to parse configuration file %s at line %u: %s (invalid type specification?)", + configFilePath, reader.GetLineNumber(), line.c_str()); + } else if (!currentSection->AddConfigItem(configItem, true)) { + // 将配置值项添加到父配置节失败,报告内存分配错误并中断解析 + PROPS_REPORT_PARSE_ERROR_AND_BREAK(MOT_ERROR_OOM, + "Failed to add configuration item to parent section"); } } else { - if (!AddPropsArrayConfigItem( - configFilePath, reader, line, currentSection, sectionFullName, key, value, arrayIndex)) { - PROPS_REPORT_PARSE_ERROR_AND_BREAK(MOT_ERROR_OOM, + // 数组项 + + // 添加属性数组配置项 + if (!AddPropsArrayConfigItem(configFilePath, reader, line, currentSection, sectionFullName, key, value, + arrayIndex)) { + // 添加属性数组配置项失败,报告内存分配错误并中断解析 + PROPS_REPORT_PARSE_ERROR_AND_BREAK( + MOT_ERROR_OOM, "Failed to add array item with arrayIndex %lu in configuration file %s at line %u: %s", - arrayIndex, - configFilePath, - reader.GetLineNumber(), - line.c_str()); + arrayIndex, configFilePath, reader.GetLineNumber(), line.c_str()); } } } + // 读取下一行 reader.NextLine(); } + // 检查是否发生了解析错误 if (parseError) { + // 解析错误发生,释放配置树的内存并返回空指针 delete configTree; configTree = nullptr; } else { + // 解析成功,构建配置树 if (!configTree->Build(parsedSections)) { + // 构建配置树失败,报告内部错误,释放配置树的内存并返回空指针 MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Load Configuration", "Failed to build configuration tree"); delete configTree; configTree = nullptr; } } + return configTree; } + } // namespace MOT diff --git a/src/gausskernel/storage/mot/core/infra/containers/bitmapset.cpp b/src/gausskernel/storage/mot/core/infra/containers/bitmapset.cpp index 684866523..b1b0e7600 100644 --- a/src/gausskernel/storage/mot/core/infra/containers/bitmapset.cpp +++ b/src/gausskernel/storage/mot/core/infra/containers/bitmapset.cpp @@ -36,16 +36,32 @@ BitmapSet::BitmapSet() Reset(); } -BitmapSet::BitmapSet(uint8_t* data, uint16_t size) : m_data(data), m_size(size), m_init(true) +BitmapSet::BitmapSet(uint8_t *data, uint16_t size) : m_data(data), m_size(size), m_init(true) {} BitmapSet::~BitmapSet() {} -void BitmapSet::Init(uint8_t* data, uint16_t size) +/* + * 功能:初始化位图集合对象 + * + * 参数列表: + * data:指向位图数据的指针,用于初始化位图集合 + * size:位图数据的大小,以字节数表示 + * + * 注意: + * 此函数用于初始化位图集合对象,设置位图数据和大小。 + * 它将输入的数据和大小分配给位图集合对象的成员变量。 + */ +void BitmapSet::Init(uint8_t *data, uint16_t size) { + // 设置位图集合对象的大小成员变量 m_size = size; + + // 设置位图集合对象的数据成员变量 m_data = data; + + // 将初始化标志设置为true,表示对象已成功初始化 m_init = true; } @@ -56,70 +72,196 @@ void BitmapSet::Reset() m_init = false; } +/* + * 功能:重置位图集合对象 + * + * 参数列表: + * size:位图数据的新大小,以字节数表示 + * + * 注意: + * 此函数用于重置位图集合对象,将其大小更改为新值,并将位图数据清零。 + * 重置前需要确保对象已成功初始化(通过检查初始化标志)。 + */ void BitmapSet::Reset(uint16_t size) { + // 断言确保对象已成功初始化 MOT_ASSERT(m_init); + + // 设置位图集合对象的大小成员变量为新值 m_size = size; + + // 使用 memset_s 函数将位图数据清零 errno_t erc = memset_s(m_data, GetLength(), 0, GetLength()); securec_check(erc, "\0", "\0"); } +/* + * 功能:清除位图集合对象的数据 + * + * 注意: + * 此函数用于将位图集合对象的数据清零。 + * 在调用此函数之前,需要确保对象已成功初始化(通过检查初始化标志)。 + */ void BitmapSet::Clear() { + // 使用 memset_s 函数将位图数据清零 errno_t erc = memset_s(m_data, GetLength(), 0, GetLength()); securec_check(erc, "\0", "\0"); } +/* + * 功能:检查位图集合对象是否已清零 + * + * 返回值: + * 如果位图集合对象已清零,则返回true;否则返回false。 + * + * 注意: + * 此函数用于检查位图集合对象的数据是否已完全清零。 + * 在调用此函数之前,需要确保对象已成功初始化(通过检查初始化标志)。 + */ bool BitmapSet::IsClear() { + // 获取位图数据的字节数 uint16_t numBytes = GetLength(); - for (uint16_t i = 0; i < numBytes; i++) + + // 遍历位图数据,检查是否有非零字节 + for (uint16_t i = 0; i < numBytes; i++) { if (m_data[i] != 0) { return false; } + } + + // 如果所有字节都为零,返回true,表示已清零 return true; } +/* + * 功能:设置位图集合对象中指定位的值为1 + * + * 参数列表: + * bit:要设置的位的索引 + * + * 注意: + * 此函数用于将位图集合对象中指定位的值设置为1。 + * 在调用此函数之前,需要确保对象已成功初始化(通过检查初始化标志)。 + * 还需要确保传递给函数的位索引(bit)小于位图集合对象的大小。 + */ void BitmapSet::SetBit(uint16_t bit) { + // 使用断言确保位索引不超出位图集合对象的大小 MOT_ASSERT(bit < m_size); + + // 计算字节索引并在相应字节上设置指定位 m_data[GetByteIndex(bit)] |= (1 << (bit & 0x07)); } +/* + * 功能:将位图集合对象中指定位的值设置为0 + * + * 参数列表: + * bit:要取消设置的位的索引 + * + * 注意: + * 此函数用于将位图集合对象中指定位的值设置为0。 + * 在调用此函数之前,需要确保对象已成功初始化(通过检查初始化标志)。 + * 还需要确保传递给函数的位索引(bit)小于位图集合对象的大小。 + */ void BitmapSet::UnsetBit(uint16_t bit) { + // 使用断言确保位索引不超出位图集合对象的大小 MOT_ASSERT(bit < m_size); + + // 计算字节索引并在相应字节上取消设置指定位 m_data[GetByteIndex(bit)] &= ~(1 << (bit & 0x07)); } +/* + * 功能:获取位图集合对象中指定位的值 + * + * 参数列表: + * bit:要获取的位的索引 + * + * 返回值: + * 如果位的值为1,则返回1;否则返回0。 + * + * 注意: + * 此函数用于从位图集合对象中获取指定位的值。 + * 在调用此函数之前,需要确保对象已成功初始化(通过检查初始化标志)。 + * 还需要确保传递给函数的位索引(bit)小于位图集合对象的大小。 + */ uint8_t BitmapSet::GetBit(uint16_t bit) { + // 使用断言确保位索引不超出位图集合对象的大小 MOT_ASSERT(bit < m_size); + + // 获取指定位的值并返回(如果位的值为1,则返回1;否则返回0) return (m_data[GetByteIndex(bit)] & (1 << (bit & 0x07))) != 0; } +/* + * 功能:计算指定位的字节索引 + * + * 参数列表: + * bit:要计算字节索引的位的索引 + * + * 返回值: + * 返回位索引对应的字节索引。 + * + * 注意: + * 此函数用于计算位图集合对象中指定位的字节索引。 + * 位索引(bit)通常用于表示在位图中的某一位,而字节索引表示这一位在字节数组中的位置。 + */ uint16_t BitmapSet::GetByteIndex(uint16_t bit) { + // 计算位索引对应的字节索引,并返回 return (bit >> 3); } +/* + * 功能:按位或运算符(|=)重载函数 + * + * 参数列表: + * bitmapSet:要与当前位图集合对象执行按位或运算的另一个位图集合对象 + * + * 注意: + * 此函数用于重载按位或运算符(|=),用于执行两个位图集合对象之间的按位或运算。 + * 它将当前位图集合对象的每个字节与另一个位图集合对象的相应字节执行按位或运算,并将结果存储在当前位图集合对象中。 + * 在执行此操作之前,需要确保两个位图集合对象的大小相同。 + */ void BitmapSet::operator|=(BitmapSet bitmapSet) { + // 获取位图集合对象的大小 uint16_t length = GetLength(); + + // 遍历每个字节并执行按位或运算 for (uint16_t i = 0; i < length; i++) { m_data[i] |= bitmapSet.m_data[i]; } } +/* + * 功能:按位与运算符(&=)重载函数 + * + * 参数列表: + * bitmapSet:要与当前位图集合对象执行按位与运算的另一个位图集合对象 + * + * 注意: + * 此函数用于重载按位与运算符(&=),用于执行两个位图集合对象之间的按位与运算。 + * 它将当前位图集合对象的每个字节与另一个位图集合对象的相应字节执行按位与运算,并将结果存储在当前位图集合对象中。 + * 在执行此操作之前,需要确保两个位图集合对象的大小相同。 + */ void BitmapSet::operator&=(BitmapSet bitmapSet) { + // 获取位图集合对象的大小 uint16_t length = GetLength(); + + // 遍历每个字节并执行按位与运算 for (uint16_t i = 0; i < length; i++) { m_data[i] &= bitmapSet.m_data[i]; } } -BitmapSet::BitmapSetIterator::BitmapSetIterator(const BitmapSet& bitmapSet) +BitmapSet::BitmapSetIterator::BitmapSetIterator(const BitmapSet &bitmapSet) : m_bms(&bitmapSet), m_data(m_bms->m_data), m_bitIndex(-1), m_byteCache(0), m_isSetCache(false) { Next(); @@ -139,20 +281,40 @@ bool BitmapSet::BitmapSetIterator::End() const return m_bitIndex >= m_bms->m_size; } +/* + * 功能:获取下一个设置位的迭代器 + * + * 返回值: + * 如果存在下一个设置位,则返回true;否则返回false。 + * + * 注意: + * 此函数用于 BitmapSet 对象的迭代器,用于查找下一个设置位。 + * 迭代器会递增位索引,并在位图中查找下一个设置位的位置。 + * 如果找到下一个设置位,将其状态存储在迭代器的缓存中,以便后续获取。 + */ bool BitmapSet::BitmapSetIterator::Next() { + // 递增位索引 m_bitIndex++; + + // 如果位索引超出位图集合对象的大小,则返回false if (m_bitIndex >= m_bms->m_size) { return false; } + // 如果当前位索引是字节边界,则更新字节缓存 if (m_bitIndex % SIZE_OF_BYTE == 0) { m_byteCache = m_data[GetByteIndex(m_bitIndex)]; } else { + // 否则右移字节缓存一位 m_byteCache = m_byteCache >> 1; } + // 更新设置位缓存 m_isSetCache = (m_byteCache & 1) != 0; + + // 返回true表示找到下一个设置位 return true; } + } // namespace MOT diff --git a/src/gausskernel/storage/mot/core/infra/stats/boolean_statistic_variable.cpp b/src/gausskernel/storage/mot/core/infra/stats/boolean_statistic_variable.cpp index 5d8897c2f..afa9a5fe5 100644 --- a/src/gausskernel/storage/mot/core/infra/stats/boolean_statistic_variable.cpp +++ b/src/gausskernel/storage/mot/core/infra/stats/boolean_statistic_variable.cpp @@ -28,42 +28,127 @@ #include namespace MOT { +/* + * 功能:对布尔统计变量进行总结 + * + * 参数列表: + * updateTstamp:一个布尔值,指示是否应该更新时间戳(可选) + * + * 注意: + * 此函数用于对布尔统计变量进行总结。它会计算百分比,并可选地更新时间戳信息。 + * 百分比计算方式为 m_sum 除以 m_countSaved,并将结果保存在 m_percentage 中。 + * 此代码不全,缺少与参数相关的操作。 + */ void BooleanStatisticVariable::Summarize(bool updateTstamp) { + // 将当前的 m_count 值保存到 m_countSaved 中 m_countSaved = m_count; + + // 计算 m_percentage(百分比):将 m_sum 转换为 double 类型并除以 m_countSaved m_percentage = ((double)m_sum) / ((double)m_countSaved); } +/* + * 功能:以指定的日志级别打印布尔统计变量的摘要信息 + * + * 参数列表: + * logLevel:要使用的日志级别(例如:INFO、DEBUG、ERROR 等) + * + * 注意: + * 此函数用于打印布尔统计变量的摘要信息到日志中。摘要包括变量名称、样本数、百分比。 + * 打印的信息格式为 "变量名={ samples: 样本数, percent: 百分比%}"。 + */ void BooleanStatisticVariable::Print(LogLevel logLevel) const { + // 使用MOT_LOG宏以指定的日志级别打印布尔统计变量的摘要信息 MOT_LOG(logLevel, "%s={ samples: %" PRIu64 ", percent: %0.4f%%}", m_name, m_countSaved, m_percentage * 100.0f); } -void BooleanStatisticVariable::Assign(const StatisticVariable& rhs) +/* + * 功能:将布尔统计变量的值从另一个统计变量复制到当前变量 + * + * 参数列表: + * rhs:要复制数据的源统计变量对象 + * + * 注意: + * 此函数用于将布尔统计变量的值从另一个统计变量对象复制到当前变量对象。 + * 该函数在进行复制操作之前,将源对象转换为布尔统计变量类型。 + */ +void BooleanStatisticVariable::Assign(const StatisticVariable &rhs) { - const BooleanStatisticVariable& boolRhs = static_cast(rhs); + // 将源统计变量对象转换为布尔统计变量类型 + const BooleanStatisticVariable &boolRhs = static_cast(rhs); + + // 将源统计变量对象的 m_sum 复制到当前对象的 m_sum m_sum = boolRhs.m_sum; + + // 将源统计变量对象的 m_count 复制到当前对象的 m_count m_count = boolRhs.m_count; } -void BooleanStatisticVariable::Add(const StatisticVariable& rhs) +/* + * 功能:将另一个布尔统计变量的值添加到当前统计变量的值上 + * + * 参数列表: + * rhs:要添加的源统计变量对象 + * + * 注意: + * 此函数用于将另一个布尔统计变量的值(包括 m_sum 和 m_count)添加到当前统计变量的值上。 + * 该函数在进行添加操作之前,将源对象转换为布尔统计变量类型。 + */ +void BooleanStatisticVariable::Add(const StatisticVariable &rhs) { - const BooleanStatisticVariable& boolRhs = static_cast(rhs); + // 将源统计变量对象转换为布尔统计变量类型 + const BooleanStatisticVariable &boolRhs = static_cast(rhs); + + // 将源统计变量对象的 m_sum 添加到当前对象的 m_sum m_sum += boolRhs.m_sum; + + // 将源统计变量对象的 m_count 添加到当前对象的 m_count m_count += boolRhs.m_count; } -void BooleanStatisticVariable::Subtract(const StatisticVariable& rhs) +/* + * 功能:从当前布尔统计变量的值中减去另一个布尔统计变量的值 + * + * 参数列表: + * rhs:要从中减去的源统计变量对象 + * + * 注意: + * 此函数用于从当前布尔统计变量的值中减去另一个布尔统计变量的值(包括 m_sum 和 m_count)。 + * 该函数在进行减法操作之前,将源对象转换为布尔统计变量类型。 + */ +void BooleanStatisticVariable::Subtract(const StatisticVariable &rhs) { - const BooleanStatisticVariable& boolRhs = static_cast(rhs); + // 将源统计变量对象转换为布尔统计变量类型 + const BooleanStatisticVariable &boolRhs = static_cast(rhs); + + // 从当前对象的 m_sum 中减去源统计变量对象的 m_sum m_sum -= boolRhs.m_sum; + + // 从当前对象的 m_count 中减去源统计变量对象的 m_count m_count -= boolRhs.m_count; } +/* + * 功能:将当前布尔统计变量的值除以给定的因子 + * + * 参数列表: + * factor:除数,用于将当前统计变量的值除以它 + * + * 注意: + * 此函数用于将当前布尔统计变量的值除以给定的因子(如果因子大于0)。 + * 它将 m_count 和 m_sum 成员变量都除以因子,以更新统计值。 + * 如果因子为0或负数,函数不执行任何操作。 + */ void BooleanStatisticVariable::Divide(uint32_t factor) { + // 检查因子是否大于0 if (factor > 0) { + // 将当前对象的 m_count 除以因子 m_count /= factor; + + // 将当前对象的 m_sum 除以因子 m_sum /= factor; } } diff --git a/src/gausskernel/storage/mot/core/infra/stats/frequency_statistic_variable.cpp b/src/gausskernel/storage/mot/core/infra/stats/frequency_statistic_variable.cpp index 4c5f5ab94..0aa73d690 100644 --- a/src/gausskernel/storage/mot/core/infra/stats/frequency_statistic_variable.cpp +++ b/src/gausskernel/storage/mot/core/infra/stats/frequency_statistic_variable.cpp @@ -29,52 +29,133 @@ #include "utilities.h" namespace MOT { +/* + * 功能:计算频率统计变量的摘要信息 + * + * 参数列表: + * updateTstamp:一个布尔值,指示是否应该更新时间戳(可选) + * + * 注意: + * 此函数用于计算频率统计变量的摘要信息,包括计算频率值和时间间隔。 + * 如果 updateTstamp 参数为 true,则获取当前CPU周期计数作为时间戳。 + * 频率计算方式为将样本数 m_countSaved 除以时间间隔 m_intervalSeconds。 + */ void FrequencyStatisticVariable::Summarize(bool updateTstamp) { + // 如果需要更新时间戳,则获取当前CPU周期计数 if (updateTstamp) { m_tstamp = CpuCyclesLevelTime::Rdtscp(); } + // 将当前的 m_count 值保存到 m_countSaved 中 m_countSaved = m_count; + // 计算时间间隔(秒):将当前时间戳减去初始时间戳,并将CPU周期转换为秒 m_intervalSeconds = CpuCyclesLevelTime::CyclesToSeconds(m_tstamp - m_initTstamp); + // 计算频率(Hz):将 m_countSaved 除以时间间隔 m_frequency = ((double)(m_countSaved)) / m_intervalSeconds; } - +/* + * 功能:以指定的日志级别打印频率统计变量的摘要信息 + * + * 参数列表: + * logLevel:要使用的日志级别(例如:INFO、DEBUG、ERROR 等) + * + * 注意: + * 此函数用于以指定的日志级别打印频率统计变量的摘要信息到日志中。 + * 摘要包括变量名称、样本数、时间间隔、频率值。 + * 打印的信息格式为 "变量名={ samples: 样本数, interval: 时间间隔 seconds, frequency: 频率 (evt/sec) }"。 + */ void FrequencyStatisticVariable::Print(LogLevel logLevel) const { - MOT_LOG(logLevel, - "%s={ samples: %" PRIu64 ", interval: %0.2f seconds, frequency: %0.4f (evt/sec) }", - m_name, - m_countSaved, - m_intervalSeconds, - m_frequency); + // 使用MOT_LOG宏以指定的日志级别打印频率统计变量的摘要信息 + MOT_LOG(logLevel, "%s={ samples: %" PRIu64 ", interval: %0.2f seconds, frequency: %0.4f (evt/sec) }", + m_name, // 变量名 + m_countSaved, // 样本数 + m_intervalSeconds, // 时间间隔(秒) + m_frequency // 频率(事件/秒) + ); } -void FrequencyStatisticVariable::Assign(const StatisticVariable& rhs) +/* + * 功能:将频率统计变量的值从另一个统计变量复制到当前变量 + * + * 参数列表: + * rhs:要复制数据的源统计变量对象 + * + * 注意: + * 此函数用于将频率统计变量的值从另一个统计变量对象复制到当前变量对象。 + * 该函数在进行复制操作之前,将源对象转换为频率统计变量类型。 + */ +void FrequencyStatisticVariable::Assign(const StatisticVariable &rhs) { - const FrequencyStatisticVariable& freqRhs = static_cast(rhs); + // 将源统计变量对象转换为频率统计变量类型 + const FrequencyStatisticVariable &freqRhs = static_cast(rhs); + + // 将源统计变量对象的 m_count 复制到当前对象的 m_count m_count = freqRhs.m_count; + + // 将源统计变量对象的 m_initTstamp 复制到当前对象的 m_initTstamp m_initTstamp = freqRhs.m_initTstamp; + + // 将源统计变量对象的 m_tstamp 复制到当前对象的 m_tstamp m_tstamp = freqRhs.m_tstamp; + + // 将源统计变量对象的 m_frequency 复制到当前对象的 m_frequency m_frequency = freqRhs.m_frequency; } -void FrequencyStatisticVariable::Add(const StatisticVariable& rhs) +/* + * 功能:将另一个频率统计变量的值添加到当前统计变量的值上 + * + * 参数列表: + * rhs:要添加的源统计变量对象 + * + * 注意: + * 此函数用于将另一个频率统计变量的值(包括 m_count 和 m_frequency)添加到当前统计变量的值上。 + * 如果源对象的 m_initTstamp 更早,它会更新当前对象的 m_initTstamp。 + * 如果源对象的 m_tstamp 更晚,它会更新当前对象的 m_tstamp。 + */ +void FrequencyStatisticVariable::Add(const StatisticVariable &rhs) { - const FrequencyStatisticVariable& freqRhs = static_cast(rhs); + // 将源统计变量对象转换为频率统计变量类型 + const FrequencyStatisticVariable &freqRhs = static_cast(rhs); + + // 将源统计变量对象的 m_count 添加到当前对象的 m_count m_count += freqRhs.m_count; + + // 将源统计变量对象的 m_frequency 添加到当前对象的 m_frequency m_frequency += freqRhs.m_frequency; + + // 如果源统计变量对象的 m_initTstamp 更早,更新当前对象的 m_initTstamp if ((m_initTstamp == 0) || ((freqRhs.m_initTstamp > 0) && (freqRhs.m_initTstamp < m_initTstamp))) { m_initTstamp = freqRhs.m_initTstamp; } + + // 如果源统计变量对象的 m_tstamp 更晚,更新当前对象的 m_tstamp if (freqRhs.m_tstamp > m_tstamp) { m_tstamp = freqRhs.m_tstamp; } } -void FrequencyStatisticVariable::Subtract(const StatisticVariable& rhs) +/* + * 功能:从当前频率统计变量的值中减去另一个频率统计变量的值 + * + * 参数列表: + * rhs:要从中减去的源统计变量对象 + * + * 注意: + * 此函数用于从当前频率统计变量的值中减去另一个频率统计变量的值(包括 m_count)。 + * 如果源对象的 m_tstamp 不为零,则将其值赋给当前对象的 m_initTstamp。 + * 请注意,此操作后需要调用 Summarize 函数以重新计算频率。 + */ +void FrequencyStatisticVariable::Subtract(const StatisticVariable &rhs) { - const FrequencyStatisticVariable& freqRhs = static_cast(rhs); + // 将源统计变量对象转换为频率统计变量类型 + const FrequencyStatisticVariable &freqRhs = static_cast(rhs); + + // 从当前对象的 m_count 减去源统计变量对象的 m_count m_count -= freqRhs.m_count; + + // 如果源统计变量对象的 m_tstamp 不为零,将其值赋给当前对象的 m_initTstamp if (freqRhs.m_tstamp != 0) { m_initTstamp = freqRhs.m_tstamp; } diff --git a/src/gausskernel/storage/mot/core/infra/stats/global_statistics.cpp b/src/gausskernel/storage/mot/core/infra/stats/global_statistics.cpp index f3d62069c..1de1bdb37 100644 --- a/src/gausskernel/storage/mot/core/infra/stats/global_statistics.cpp +++ b/src/gausskernel/storage/mot/core/infra/stats/global_statistics.cpp @@ -27,22 +27,51 @@ namespace MOT { DECLARE_LOGGER(GlobalStatistics, Statistics) +/* + * 功能:汇总全局统计数据 + * + * 参数列表: + * updateTstamp:一个布尔值,指示是否应该更新时间戳(可选) + * + * 注意: + * 此函数用于汇总全局统计数据,遍历所有统计变量对象并调用它们的 Summarize 函数。 + * 如果 updateTstamp 参数为 true,则会将更新时间戳的请求传递给各个统计变量。 + */ void GlobalStatistics::Summarize(bool updateTstamp) { + // 获取当前统计变量的数量 uint32_t statCount = m_statVars.size(); + + // 遍历所有统计变量并调用其 Summarize 函数 for (uint32_t i = 0; i < statCount; ++i) { m_statVars[i]->Summarize(updateTstamp); } } +/* + * 功能:打印全局统计数据 + * + * 参数列表: + * statId:要打印的特定统计变量的索引(可选) + * logLevel:要使用的日志级别(例如:INFO、DEBUG、ERROR 等) + * + * 注意: + * 此函数用于打印全局统计数据,可以选择打印特定索引的统计变量或打印所有统计变量。 + * 如果提供 statId 参数,则仅打印指定索引的统计变量(如果其样本数大于0)。 + * 否则,将遍历所有统计变量并打印样本数大于0的统计变量。 + */ void GlobalStatistics::Print(uint32_t statId, LogLevel logLevel) const { + // 获取当前统计变量的数量 uint32_t statCount = m_statVars.size(); + + // 如果提供 statId 参数,则仅打印指定索引的统计变量(如果其样本数大于0) if (statId < statCount) { if (m_statVars[statId]->GetSampleCount() > 0) { m_statVars[statId]->Print(logLevel); } } else { + // 否则,遍历所有统计变量并打印样本数大于0的统计变量 for (uint32_t i = 0; i < statCount; ++i) { if (m_statVars[i]->GetSampleCount() > 0) { m_statVars[i]->Print(logLevel); @@ -51,47 +80,117 @@ void GlobalStatistics::Print(uint32_t statId, LogLevel logLevel) const } } +/* + * 功能:重置全局统计数据 + * + * 注意: + * 此函数用于重置全局统计数据,它会遍历所有统计变量对象并调用它们的 Reset 函数。 + * 调用 Reset 函数将清除所有统计变量的计数和摘要信息,将它们重置为初始状态。 + */ void GlobalStatistics::Reset() { + // 获取当前统计变量的数量 uint32_t statCount = m_statVars.size(); + + // 遍历所有统计变量并调用其 Reset 函数 for (uint32_t i = 0; i < statCount; ++i) { m_statVars[i]->Reset(); } } -void GlobalStatistics::Assign(const GlobalStatistics& rhs) +/* + * 功能:将另一个全局统计对象的值分配给当前全局统计对象 + * + * 参数列表: + * rhs:要从中复制数据的源全局统计对象 + * + * 注意: + * 此函数用于将另一个全局统计对象的值分配给当前全局统计对象。 + * 它会遍历所有统计变量对象,并调用它们的 Assign 函数以完成分配操作。 + */ +void GlobalStatistics::Assign(const GlobalStatistics &rhs) { + // 获取当前统计变量的数量 uint32_t statCount = m_statVars.size(); + + // 遍历所有统计变量并调用其 Assign 函数以完成分配操作 for (uint32_t i = 0; i < statCount; ++i) { m_statVars[i]->Assign(*rhs.m_statVars[i]); } } -void GlobalStatistics::Subtract(const GlobalStatistics& rhs) +/* + * 功能:从当前全局统计对象的值中减去另一个全局统计对象的值 + * + * 参数列表: + * rhs:要从中减去的源全局统计对象 + * + * 注意: + * 此函数用于从当前全局统计对象的值中减去另一个全局统计对象的值。 + * 它会遍历所有统计变量对象,并调用它们的 Subtract 函数以完成减法操作。 + */ +void GlobalStatistics::Subtract(const GlobalStatistics &rhs) { + // 获取当前统计变量的数量 uint32_t statCount = m_statVars.size(); + + // 遍历所有统计变量并调用其 Subtract 函数以完成减法操作 for (uint32_t i = 0; i < statCount; ++i) { m_statVars[i]->Subtract(*rhs.m_statVars[i]); } } +/* + * 功能:检查是否存在有效的全局统计样本 + * + * 返回值: + * 如果存在至少一个具有有效样本的全局统计变量,返回 true;否则返回 false。 + * + * 注意: + * 此函数用于检查是否存在至少一个具有有效样本的全局统计变量。 + * 它会遍历所有统计变量对象,并检查其样本数是否大于0。 + */ bool GlobalStatistics::HasValidSamples() const { + // 初始化结果为 false bool result = false; + + // 获取当前统计变量的数量 uint32_t statCount = m_statVars.size(); + + // 遍历所有统计变量并检查其样本数是否大于0 for (uint32_t i = 0; i < statCount; ++i) { if (m_statVars[i]->GetSampleCount() > 0) { + // 如果找到至少一个具有有效样本的统计变量,将结果设为 true 并退出循环 result = true; break; } } + + // 返回结果 return result; } -mot_string GlobalStatistics::MakeName(const char* baseName, NamingScheme namingScheme) +/* + * 功能:生成全局统计名称 + * + * 参数列表: + * baseName:基础名称,用作生成全局统计名称的前缀 + * namingScheme:命名方案,指定生成全局统计名称的方式 + * + * 返回值: + * 返回生成的全局统计名称。 + * + * 注意: + * 此函数用于生成全局统计名称,根据指定的命名方案生成名称的后缀。 + * 基础名称 baseName 用作生成全局统计名称的前缀,根据不同的命名方案添加不同的后缀。 + */ +mot_string GlobalStatistics::MakeName(const char *baseName, NamingScheme namingScheme) { + // 初始化结果字符串 mot_string result; + // 根据命名方案生成全局统计名称 if ((namingScheme == NamingScheme::NAMING_SCHEME_TOTAL) || (namingScheme == NamingScheme::NAMING_SCHEME_TOTAL_PREV)) { result.format("%s[TOTAL]", baseName); @@ -99,14 +198,28 @@ mot_string GlobalStatistics::MakeName(const char* baseName, NamingScheme namingS result.format("%s[DIFF]", baseName); } + // 返回生成的全局统计名称 return result; } -void GlobalStatistics::RegisterStatistics(StatisticVariable* statVar) +/* + * 功能:注册全局统计变量 + * + * 参数列表: + * statVar:要注册的全局统计变量对象的指针 + * + * 注意: + * 此函数用于注册全局统计变量。它将给定的统计变量对象指针添加到统计变量列表中。 + * 如果无法添加统计变量(由于内存不足),将生成错误日志。 + */ +void GlobalStatistics::RegisterStatistics(StatisticVariable *statVar) { + // 将给定的统计变量对象指针添加到统计变量列表中 if (!m_statVars.push_back(statVar)) { - MOT_REPORT_ERROR( - MOT_ERROR_OOM, "Register Statistics", "Failed to register statistics variable %s", statVar->GetName()); + // 如果无法添加统计变量(由于内存不足),生成错误日志 + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Register Statistics", "Failed to register statistics variable %s", + statVar->GetName()); } } + } // namespace MOT diff --git a/src/gausskernel/storage/mot/core/infra/stats/level_statistic_variable.cpp b/src/gausskernel/storage/mot/core/infra/stats/level_statistic_variable.cpp index 637573810..f85c46f22 100644 --- a/src/gausskernel/storage/mot/core/infra/stats/level_statistic_variable.cpp +++ b/src/gausskernel/storage/mot/core/infra/stats/level_statistic_variable.cpp @@ -27,33 +27,69 @@ #include "level_statistic_variable.h" namespace MOT { +/* + * 功能:汇总级别统计数据 + * + * 参数列表: + * updateTstamp:一个布尔值,指示是否应该更新时间戳(可选) + * + * 注意: + * 此函数用于汇总级别统计数据,它会根据指定的命名方案生成名称的后缀。 + * 如果提供 updateTstamp 参数并设置为 true,则会更新时间戳。 + * 汇总过程包括保存计数、级别和峰值,计算平均值和计算时间间隔。 + */ void LevelStatisticVariable::Summarize(bool updateTstamp) { + // 如果提供 updateTstamp 参数并设置为 true,则更新时间戳 if (updateTstamp) { m_tstamp = CpuCyclesLevelTime::Rdtscp(); } + + // 保存计数、级别和峰值 m_countSaved = m_count; m_levelSaved = m_level; m_peakSaved = m_peak; + + // 计算平均值 m_avg = ((double)m_integral) / ((double)(m_tstamp - m_initTstamp)); + + // 计算时间间隔 m_intervalSeconds = CpuCyclesLevelTime::CyclesToSeconds(m_tstamp - m_initTstamp); } +/* + * 功能:打印级别统计数据 + * + * 参数列表: + * logLevel:要使用的日志级别(例如:INFO、DEBUG、ERROR 等) + * + * 注意: + * 此函数用于打印级别统计数据,包括当前级别、峰值和样本数量。 + * 打印的信息包括统计变量的名称、当前级别、峰值、单位和样本数量。 + */ void LevelStatisticVariable::Print(LogLevel logLevel) const { - MOT_LOG(logLevel, - "%s={ current level: %" PRId64 " %s, peak: %" PRIu64 " %s [%" PRIu64 " samples] }", - m_name, - m_levelSaved / m_factor, - m_units, - m_peakSaved / m_factor, - m_units, - m_countSaved); + // 使用指定的日志级别打印级别统计数据 + MOT_LOG(logLevel, "%s={ current level: %" PRId64 " %s, peak: %" PRIu64 " %s [%" PRIu64 " samples] }", m_name, + m_levelSaved / m_factor, m_units, m_peakSaved / m_factor, m_units, m_countSaved); } -void LevelStatisticVariable::Assign(const StatisticVariable& rhs) +/* + * 功能:将另一个级别统计变量的值分配给当前级别统计变量 + * + * 参数列表: + * rhs:要从中复制数据的源级别统计变量 + * + * 注意: + * 此函数用于将另一个级别统计变量的值分配给当前级别统计变量。 + * 它将源级别统计变量的各个属性值复制到当前级别统计变量。 + */ +void LevelStatisticVariable::Assign(const StatisticVariable &rhs) { - const LevelStatisticVariable& levelRhs = static_cast(rhs); + // 将源级别统计变量强制转换为 LevelStatisticVariable 类型 + const LevelStatisticVariable &levelRhs = static_cast(rhs); + + // 复制源级别统计变量的各个属性值到当前级别统计变量 m_level = levelRhs.m_level; m_peak = levelRhs.m_peak; m_tstamp = levelRhs.m_tstamp; @@ -62,33 +98,80 @@ void LevelStatisticVariable::Assign(const StatisticVariable& rhs) m_count = levelRhs.m_count; } -void LevelStatisticVariable::Add(const StatisticVariable& rhs) +/* + * 功能:将另一个级别统计变量的值添加到当前级别统计变量 + * + * 参数列表: + * rhs:要添加值的源级别统计变量 + * + * 注意: + * 此函数用于将另一个级别统计变量的值添加到当前级别统计变量。 + * 它会逐个属性进行相加,并在必要时更新时间戳。 + */ +void LevelStatisticVariable::Add(const StatisticVariable &rhs) { - const LevelStatisticVariable& levelRhs = static_cast(rhs); + // 将源级别统计变量强制转换为 LevelStatisticVariable 类型 + const LevelStatisticVariable &levelRhs = static_cast(rhs); + + // 逐个属性进行相加 m_level += levelRhs.m_level; m_peak += levelRhs.m_peak; + + // 如果源级别统计变量的时间戳大于当前级别统计变量的时间戳,则更新时间戳 if (levelRhs.m_tstamp > m_tstamp) { m_tstamp = levelRhs.m_tstamp; } + + // 如果源级别统计变量的初始时间戳小于当前级别统计变量的初始时间戳,则更新初始时间戳 if (levelRhs.m_initTstamp < m_initTstamp) { m_initTstamp = levelRhs.m_initTstamp; } + + // 逐个属性进行相加 m_integral += levelRhs.m_integral; m_count += levelRhs.m_count; } -void LevelStatisticVariable::Subtract(const StatisticVariable& rhs) +/* + * 功能:从当前级别统计变量中减去另一个级别统计变量的值 + * + * 参数列表: + * rhs:要从中减去值的源级别统计变量 + * + * 注意: + * 此函数用于从当前级别统计变量中减去另一个级别统计变量的值。 + * 它会从当前级别统计变量的属性中减去源级别统计变量的属性值, + * 但不会减去级别属性,因为即使在周期性差异报告中,我们仍然希望显示当前级别。 + * 此外,峰值无法在时间间隔内推断,除非我们主动维护它。 + */ +void LevelStatisticVariable::Subtract(const StatisticVariable &rhs) { - const LevelStatisticVariable& levelRhs = static_cast(rhs); + // 将源级别统计变量强制转换为 LevelStatisticVariable 类型 + const LevelStatisticVariable &levelRhs = static_cast(rhs); + + // 更新当前级别统计变量的初始时间戳 m_initTstamp = levelRhs.m_tstamp; + + // 从当前级别统计变量的属性中减去源级别统计变量的属性值 m_integral -= levelRhs.m_integral; m_count -= levelRhs.m_count; // we do not subtract level, since even in periodic diff report we would like to show current level // in addition, peak cannot be inferred within interval, unless we actively maintain it } +/* + * 功能:将当前级别统计变量的值除以指定因子 + * + * 参数列表: + * factor:要除以的因子值 + * + * 注意: + * 此函数用于将当前级别统计变量的属性值除以指定的因子。 + * 它会对计数、级别和积分属性进行除法运算,前提是因子大于零。 + */ void LevelStatisticVariable::Divide(uint32_t factor) { + // 如果因子大于零,则将当前级别统计变量的属性值除以指定的因子 if (factor > 0) { m_count /= factor; m_level /= factor; diff --git a/src/gausskernel/storage/mot/core/infra/stats/memory_statistic_variable.cpp b/src/gausskernel/storage/mot/core/infra/stats/memory_statistic_variable.cpp index ee60df4ab..7698f3b4b 100644 --- a/src/gausskernel/storage/mot/core/infra/stats/memory_statistic_variable.cpp +++ b/src/gausskernel/storage/mot/core/infra/stats/memory_statistic_variable.cpp @@ -25,39 +25,117 @@ #include "memory_statistic_variable.h" namespace MOT { +/* + * 功能:打印内存统计变量的信息 + * + * 参数列表: + * logLevel:要使用的日志级别(例如:INFO、DEBUG、ERROR 等) + * + * 注意: + * 此函数用于打印内存统计变量的信息,包括内存级别和内存速率。 + * 它会分别打印内存级别和内存速率的信息,使用指定的日志级别。 + */ void MemoryStatisticVariable::Print(LogLevel logLevel) const { + // 打印内存级别的信息 m_level.Print(logLevel); + + // 打印内存速率的信息 m_rate.Print(logLevel); } -void MemoryStatisticVariable::Assign(const StatisticVariable& rhs) +/* + * 功能:将另一个内存统计变量的值分配给当前内存统计变量 + * + * 参数列表: + * rhs:要从中复制数据的源内存统计变量 + * + * 注意: + * 此函数用于将另一个内存统计变量的值分配给当前内存统计变量。 + * 它会分别复制计数、内存级别和内存速率的属性值。 + */ +void MemoryStatisticVariable::Assign(const StatisticVariable &rhs) { - const MemoryStatisticVariable& memRhs = (const MemoryStatisticVariable&)rhs; + // 将源内存统计变量强制转换为 MemoryStatisticVariable 类型 + const MemoryStatisticVariable &memRhs = (const MemoryStatisticVariable &)rhs; + + // 复制计数属性值 m_count = memRhs.m_count; + + // 调用 MemoryLevelStatisticVariable 对象的 Assign 函数,复制内存级别属性值 m_level.Assign(memRhs.m_level); + + // 调用 MemoryRateStatisticVariable 对象的 Assign 函数,复制内存速率属性值 m_rate.Assign(memRhs.m_rate); } -void MemoryStatisticVariable::Add(const StatisticVariable& rhs) +/* + * 功能:将另一个内存统计变量的值添加到当前内存统计变量 + * + * 参数列表: + * rhs:要添加值的源内存统计变量 + * + * 注意: + * 此函数用于将另一个内存统计变量的值添加到当前内存统计变量。 + * 它会分别对计数、内存级别和内存速率属性进行相加操作。 + */ +void MemoryStatisticVariable::Add(const StatisticVariable &rhs) { - const MemoryStatisticVariable& memRhs = (const MemoryStatisticVariable&)rhs; + // 将源内存统计变量强制转换为 MemoryStatisticVariable 类型 + const MemoryStatisticVariable &memRhs = (const MemoryStatisticVariable &)rhs; + + // 对计数属性进行相加操作 m_count += memRhs.m_count; + + // 调用 MemoryLevelStatisticVariable 对象的 Add 函数,对内存级别属性进行相加操作 m_level.Add(memRhs.m_level); + + // 调用 MemoryRateStatisticVariable 对象的 Add 函数,对内存速率属性进行相加操作 m_rate.Add(memRhs.m_rate); } -void MemoryStatisticVariable::Subtract(const StatisticVariable& rhs) +/* + * 功能:从当前内存统计变量中减去另一个内存统计变量的值 + * + * 参数列表: + * rhs:要从中减去值的源内存统计变量 + * + * 注意: + * 此函数用于从当前内存统计变量中减去另一个内存统计变量的值。 + * 它会分别对计数、内存级别和内存速率属性进行减法运算。 + */ +void MemoryStatisticVariable::Subtract(const StatisticVariable &rhs) { - const MemoryStatisticVariable& memRhs = (const MemoryStatisticVariable&)rhs; + // 将源内存统计变量强制转换为 MemoryStatisticVariable 类型 + const MemoryStatisticVariable &memRhs = (const MemoryStatisticVariable &)rhs; + + // 对计数属性进行减法运算 m_count -= memRhs.m_count; + + // 调用 MemoryLevelStatisticVariable 对象的 Subtract 函数,对内存级别属性进行减法运算 m_level.Subtract(memRhs.m_level); + + // 调用 MemoryRateStatisticVariable 对象的 Subtract 函数,对内存速率属性进行减法运算 m_rate.Subtract(memRhs.m_rate); } +/* + * 功能:将当前内存统计变量的值除以指定因子 + * + * 参数列表: + * factor:要除以的因子值 + * + * 注意: + * 此函数用于将当前内存统计变量的属性值除以指定的因子。 + * 它会调用 MemoryLevelStatisticVariable 和 MemoryRateStatisticVariable 对象的 + * Divide 函数,分别对内存级别和内存速率属性进行除法运算。 + */ void MemoryStatisticVariable::Divide(uint32_t factor) { + // 调用 MemoryLevelStatisticVariable 对象的 Divide 函数,对内存级别属性进行除法运算 m_level.Divide(factor); + + // 调用 MemoryRateStatisticVariable 对象的 Divide 函数,对内存速率属性进行除法运算 m_rate.Divide(factor); } diff --git a/src/gausskernel/storage/mot/core/infra/stats/numeric_statistic_variable.cpp b/src/gausskernel/storage/mot/core/infra/stats/numeric_statistic_variable.cpp index 23c0e371b..02a07ea9c 100644 --- a/src/gausskernel/storage/mot/core/infra/stats/numeric_statistic_variable.cpp +++ b/src/gausskernel/storage/mot/core/infra/stats/numeric_statistic_variable.cpp @@ -29,79 +29,206 @@ namespace MOT { NumericStatisticVariable::NumericStatisticVariable( - const char* name, uint64_t factor /* = 1 */, const char* units /* = "" */, uint64_t limit /* = ULONG_LONG_MAX */) - : StatisticVariable(name), - m_factor(factor), - m_limit(limit), - m_sum(0), - m_squareSum(0), - m_avg(0.0f), - m_var(0.0f), - m_std(0.0f), - m_countSaved(0) + /* + * 功能:构造一个内存级别统计变量对象 + * + * 参数列表: + * name:统计变量的名称 + * factor:缩放因子,默认为 1 + * units:单位字符串,默认为空字符串 + * limit:统计变量的上限,默认为 ULONG_LONG_MAX + * + * 注意: + * 此构造函数用于初始化内存级别统计变量对象的属性。 + * 它接受统计变量的名称、缩放因子、单位字符串和上限值,并将其设置为对象的属性。 + */ +MemoryLevelStatisticVariable::MemoryLevelStatisticVariable(const char* name, uint64_t factor /* = 1 */, const char* units /* = "" */, uint64_t limit /* = ULONG_LONG_MAX */) + : StatisticVariable(name), // 调用基类 StatisticVariable 的构造函数来初始化名称属性 + m_factor(factor), // 初始化缩放因子属性 + m_limit(limit), // 初始化上限属性 + m_sum(0), // 初始化总和属性 + m_squareSum(0), // 初始化平方总和属性 + m_avg(0.0f), // 初始化平均值属性 + m_var(0.0f), // 初始化方差属性 + m_std(0.0f), // 初始化标准差属性 + m_countSaved(0) // 初始化保存计数属性 { + // 使用 snprintf_s 函数将单位字符串复制到 m_units 属性中,确保不超出最大长度 STAT_VAR_MAX_NAME_LEN errno_t erc = snprintf_s(m_units, STAT_VAR_MAX_NAME_LEN, STAT_VAR_MAX_NAME_LEN - 1, "%s", units); securec_check_ss(erc, "\0", "\0"); } + +/* + * 功能:汇总数值统计变量的信息 + * + * 参数列表: + * updateTstamp:是否更新时间戳,此参数未使用 + * + * 注意: + * 此函数用于汇总数值统计变量的信息,包括计数、平均值、方差和标准差。 + * 它会根据当前的总和和计数属性计算平均值,然后计算方差和标准差。 + * 参数 `updateTstamp` 未使用。 + */ void NumericStatisticVariable::Summarize(bool updateTstamp) { + // 参数 `updateTstamp` 未使用,避免编译器警告 (void)updateTstamp; + + // 将当前计数值保存到 m_countSaved 属性 m_countSaved = m_count; + + // 计算平均值,使用总和属性和计数属性 m_avg = ((double)m_sum) / ((double)m_countSaved); + + // 计算方差,使用平方总和属性和平均值 m_var = ((double)m_squareSum) - m_avg * m_avg; + + // 计算标准差,使用方差并调用 sqrt 函数 m_std = sqrt(m_var); } + +/* + * 功能:打印数值统计变量的信息 + * + * 参数列表: + * logLevel:日志级别,指定打印信息的详细程度 + * + * 注意: + * 此函数用于将数值统计变量的信息以日志形式打印出来,包括样本数、平均值和标准差。 + * 它将打印的信息格式化并使用指定的日志级别输出。 + */ void NumericStatisticVariable::Print(LogLevel logLevel) const { - MOT_LOG(logLevel, - "%s={ samples: %" PRIu64 ", avg: %0.4f %s, std: %0.4f %s }", - m_name, - m_countSaved, - m_avg / m_factor, - m_units, - m_std / m_factor, - m_units); + // 使用 MOT_LOG 函数将数值统计变量的信息以指定格式和日志级别打印出来 + MOT_LOG(logLevel, "%s={ samples: %" PRIu64 ", avg: %0.4f %s, std: %0.4f %s }", m_name, m_countSaved, + m_avg / m_factor, m_units, m_std / m_factor, m_units); } + +/* + * 功能:将数值统计变量的值赋给当前数值统计变量 + * + * 参数列表: + * rhs:要赋值的源数值统计变量对象 + * + * 注意: + * 此函数用于将源数值统计变量对象的值赋给当前数值统计变量对象。 + * 它将总和、平方总和和计数属性分别赋值为源对象的对应属性值。 + */ void NumericStatisticVariable::Assign(const StatisticVariable& rhs) { - auto numRhs = static_cast(rhs); + // 将源对象强制转换为 NumericStatisticVariable 类型 + auto numRhs = static_cast(rhs); + + // 将总和属性赋值为源对象的总和属性值 m_sum = numRhs.m_sum; + + // 将平方总和属性赋值为源对象的平方总和属性值 m_squareSum = numRhs.m_squareSum; + + // 将计数属性赋值为源对象的计数属性值 m_count = numRhs.m_count; } + +/* + * 功能:将另一个数值统计变量的值添加到当前数值统计变量的值中 + * + * 参数列表: + * rhs:要添加的源数值统计变量对象 + * + * 注意: + * 此函数用于将另一个数值统计变量对象的值添加到当前数值统计变量对象的值中。 + * 它将总和、平方总和和计数属性分别相加。 + */ void NumericStatisticVariable::Add(const StatisticVariable& rhs) { - auto numRhs = static_cast(rhs); + // 将源对象强制转换为 NumericStatisticVariable 类型 + auto numRhs = static_cast(rhs); + + // 将总和属性相加 m_sum += numRhs.m_sum; + + // 将平方总和属性相加 m_squareSum += numRhs.m_squareSum; + + // 将计数属性相加 m_count += numRhs.m_count; } + +/* + * 功能:从当前数值统计变量的值中减去另一个数值统计变量的值 + * + * 参数列表: + * rhs:要减去的源数值统计变量对象 + * + * 注意: + * 此函数用于从当前数值统计变量对象的值中减去另一个数值统计变量对象的值。 + * 它将总和、平方总和和计数属性分别减去。 + */ void NumericStatisticVariable::Subtract(const StatisticVariable& rhs) { - auto numRhs = static_cast(rhs); + // 将源对象强制转换为 NumericStatisticVariable 类型 + auto numRhs = static_cast(rhs); + + // 从总和属性中减去源对象的总和属性值 m_sum -= numRhs.m_sum; + + // 从平方总和属性中减去源对象的平方总和属性值 m_squareSum -= numRhs.m_squareSum; + + // 从计数属性中减去源对象的计数属性值 m_count -= numRhs.m_count; } + +/* + * 功能:将当前数值统计变量的值除以指定因子 + * + * 参数列表: + * factor:除以的因子,必须大于零 + * + * 注意: + * 此函数用于将当前数值统计变量对象的值除以指定因子。 + * 它将计数属性、总和属性和平方总和属性分别除以因子值。 + */ void NumericStatisticVariable::Divide(uint32_t factor) { + // 检查因子是否大于零 if (factor > 0) { + // 将计数属性除以因子 m_count /= factor; + + // 将总和属性除以因子 m_sum /= factor; + + // 将平方总和属性除以因子 m_squareSum /= factor; } } + +/* + * 功能:重置数值统计变量的值 + * + * 参数列表:无 + * + * 注意: + * 此函数用于将数值统计变量的值重置为零。它将总和属性、平方总和属性和计数属性都设置为零。 + */ void NumericStatisticVariable::Reset() { + // 将总和属性设置为零 m_sum = 0; + + // 将平方总和属性设置为零 m_squareSum = 0; + + // 将计数属性设置为零 m_count = 0; } + } // namespace MOT diff --git a/src/gausskernel/storage/mot/core/infra/stats/rate_statistic_variable.cpp b/src/gausskernel/storage/mot/core/infra/stats/rate_statistic_variable.cpp index d9d74d5d2..ab4739bbf 100644 --- a/src/gausskernel/storage/mot/core/infra/stats/rate_statistic_variable.cpp +++ b/src/gausskernel/storage/mot/core/infra/stats/rate_statistic_variable.cpp @@ -28,85 +28,191 @@ #include "utilities.h" namespace MOT { +/* + * 功能:计算速率统计变量的摘要信息 + * + * 参数列表: + * updateTstamp:指示是否需要更新时间戳 + * + * 注意: + * 此函数用于计算速率统计变量的摘要信息,包括速率、频率等信息。 + * 它可以选择是否更新时间戳,并根据时间戳计算间隔秒数、速率和频率。 + */ void RateStatisticVariable::Summarize(bool updateTstamp) { + // 如果需要更新时间戳 if (updateTstamp) { + // 获取当前时间戳 m_tstamp = CpuCyclesLevelTime::Rdtscp(); } + // 保存当前计数 m_countSaved = m_count; + // 保存当前总和 m_sumSaved = m_sum; + // 计算间隔秒数 m_intervalSeconds = CpuCyclesLevelTime::CyclesToSeconds(m_tstamp - m_initTstamp); + // 计算速率 m_rate = ((double)(m_sumSaved)) / m_intervalSeconds / m_factor; + // 计算频率 m_frequency = ((double)(m_countSaved)) / m_intervalSeconds; } +/* + * 功能:打印速率统计变量的信息 + * + * 参数列表: + * logLevel:日志级别,指示打印信息的详细程度 + * + * 注意: + * 此函数用于打印速率统计变量的信息,包括速率、频率和样本数等信息。 + */ void RateStatisticVariable::Print(LogLevel logLevel) const { - MOT_LOG(logLevel, - "%s={ rate: %0.4f (%s/sec), frequency: %0.4f (evt/sec) [%" PRIu64 " samples] }", - m_name, - m_rate, - m_units, - m_frequency, - m_countSaved); + // 使用指定的日志级别打印信息 + MOT_LOG(logLevel, "%s={ rate: %0.4f (%s/sec), frequency: %0.4f (evt/sec) [%" PRIu64 " samples] }", m_name, m_rate, + m_units, m_frequency, m_countSaved); } -void RateStatisticVariable::Assign(const StatisticVariable& rhs) +/* + * 功能:将另一个速率统计变量的值分配给当前速率统计变量 + * + * 参数列表: + * rhs:要分配的源速率统计变量对象 + * + * 注意: + * 此函数用于将另一个速率统计变量对象的值分配给当前速率统计变量对象。 + * 它将计数、总和、时间戳、速率、时间间隔、频率等属性都分配给当前对象。 + */ +void RateStatisticVariable::Assign(const StatisticVariable &rhs) { - auto rateRhs = static_cast(rhs); + // 将源对象强制转换为 RateStatisticVariable 类型 + auto rateRhs = static_cast(rhs); + // 分配计数属性 m_count = rateRhs.m_count; + // 分配总和属性 m_sum = rateRhs.m_sum; + // 分配初始时间戳属性 m_initTstamp = rateRhs.m_initTstamp; + // 分配时间戳属性 m_tstamp = rateRhs.m_tstamp; + // 分配速率属性 m_rate = rateRhs.m_rate; + // 分配时间间隔属性 m_intervalSeconds = rateRhs.m_intervalSeconds; + // 分配频率属性 m_frequency = rateRhs.m_frequency; + // 分配保存的计数属性 m_countSaved = rateRhs.m_count; + // 分配保存的总和属性 m_sumSaved = rateRhs.m_sumSaved; } -void RateStatisticVariable::Add(const StatisticVariable& rhs) +/* + * 功能:将另一个速率统计变量的值添加到当前速率统计变量 + * + * 参数列表: + * rhs:要添加的源速率统计变量对象 + * + * 注意: + * 此函数用于将另一个速率统计变量对象的值添加到当前速率统计变量对象。 + * 它将计数、总和、速率、频率、时间戳等属性相应地添加到当前对象,并更新初始时间戳。 + */ +void RateStatisticVariable::Add(const StatisticVariable &rhs) { - auto rateRhs = static_cast(rhs); + // 将源对象强制转换为 RateStatisticVariable 类型 + auto rateRhs = static_cast(rhs); + // 添加计数属性 m_count += rateRhs.m_countSaved; + // 添加总和属性 m_sum += rateRhs.m_sumSaved; + // 添加速率属性 m_rate += rateRhs.m_rate; + // 添加频率属性 m_frequency += rateRhs.m_frequency; + // 如果当前对象的初始时间戳为零,或者源对象的初始时间戳大于零且小于当前对象的初始时间戳 if ((m_initTstamp == 0) || ((rateRhs.m_initTstamp > 0) && (rateRhs.m_initTstamp < m_initTstamp))) { + // 更新当前对象的初始时间戳 m_initTstamp = rateRhs.m_initTstamp; } + // 如果源对象的时间戳大于当前对象的时间戳 if (rateRhs.m_tstamp > m_tstamp) { + // 更新当前对象的时间戳 m_tstamp = rateRhs.m_tstamp; } } -void RateStatisticVariable::Subtract(const StatisticVariable& rhs) +/* + * 功能:从当前速率统计变量中减去另一个速率统计变量的值 + * + * 参数列表: + * rhs:要减去的源速率统计变量对象 + * + * 注意: + * 此函数用于从当前速率统计变量对象中减去另一个速率统计变量对象的值。 + * 它减去计数、总和属性,并在需要时更新初始时间戳。 + */ +void RateStatisticVariable::Subtract(const StatisticVariable &rhs) { - auto rateRhs = static_cast(rhs); + // 将源对象强制转换为 RateStatisticVariable 类型 + auto rateRhs = static_cast(rhs); + // 减去计数属性 m_count -= rateRhs.m_count; + // 减去总和属性 m_sum -= rateRhs.m_sum; + // 如果源对象的时间戳不为零 if (rateRhs.m_tstamp != 0) { + // 更新当前对象的初始时间戳为源对象的时间戳 m_initTstamp = rateRhs.m_tstamp; } } +/* + * 功能:将当前速率统计变量的属性值除以指定的因子 + * + * 参数列表: + * factor:除数因子,用于除以当前速率统计变量的属性值 + * + * 注意: + * 此函数用于将当前速率统计变量的计数、速率、频率属性值除以指定的因子。 + * 如果因子大于零,则会执行除法操作,否则不执行操作。 + */ void RateStatisticVariable::Divide(uint32_t factor) { + // 如果因子大于零 if (factor > 0) { + // 除以指定的因子,更新计数属性 m_count /= factor; + // 除以指定的因子,更新速率属性 m_rate /= factor; + // 除以指定的因子,更新频率属性 m_frequency /= factor; } } +/* + * 功能:重置当前速率统计变量的属性值 + * + * 参数列表:无 + * + * 注意: + * 此函数用于将当前速率统计变量的属性值重置为初始状态,包括计数、总和、速率、频率、时间戳等属性。 + */ void RateStatisticVariable::Reset() { + // 重置计数属性为零 m_count = 0; + // 重置总和属性为零 m_sum = 0; + // 重置速率属性为零 m_rate = 0.0f; + // 重置频率属性为零 m_frequency = 0.0f; + // 重置初始时间戳属性为零 m_initTstamp = 0; + // 重置时间戳属性为零 m_tstamp = 0; + // 重置时间间隔属性为零 m_intervalSeconds = 0.0f; } + } // namespace MOT diff --git a/src/gausskernel/storage/mot/core/infra/stats/statistics_manager.cpp b/src/gausskernel/storage/mot/core/infra/stats/statistics_manager.cpp index c5eb1645c..4107cf62d 100644 --- a/src/gausskernel/storage/mot/core/infra/stats/statistics_manager.cpp +++ b/src/gausskernel/storage/mot/core/infra/stats/statistics_manager.cpp @@ -33,7 +33,7 @@ namespace MOT { DECLARE_LOGGER(StatisticsManager, Statistics) -StatisticsManager* StatisticsManager::m_manager = nullptr; +StatisticsManager *StatisticsManager::m_manager = nullptr; #define STAT_PRINT_CHECK_PERIOD_SECONDS 1 @@ -51,41 +51,66 @@ StatisticsManager* StatisticsManager::m_manager = nullptr; break; \ } -static bool CreateRecursiveMutex(pthread_mutex_t* mutex) +/* + * 功能:创建递归互斥锁 + * + * 参数列表: + * mutex:pthread_mutex_t 类型的指针,用于存储创建的互斥锁对象 + * + * 返回值: + * 如果成功创建互斥锁,则返回 true;否则返回 false + * + * 注意: + * 此函数用于创建一个递归互斥锁,并将其存储在提供的 mutex 指针中。 + * 如果创建成功,函数返回 true,否则返回 false。 + * 互斥锁属性配置为递归类型,以支持递归锁定。 + */ +static bool CreateRecursiveMutex(pthread_mutex_t *mutex) { bool result = false; + // 初始化互斥锁属性 pthread_mutexattr_t lockattr; int rc = pthread_mutexattr_init(&lockattr); if (rc != 0) { - MOT_REPORT_SYSTEM_ERROR_CODE(rc, - pthread_mutexattr_init, - "Statistics Manager Initialization", - "Failed to initialize recursive mutex attribute for the statistics manager"); + // 报告初始化互斥锁属性失败的系统错误 + MOT_REPORT_SYSTEM_ERROR_CODE(rc, pthread_mutexattr_init, "Statistics Manager Initialization", + "Failed to initialize recursive mutex attribute for the statistics manager"); } else { + // 配置互斥锁属性为递归类型 rc = pthread_mutexattr_settype(&lockattr, PTHREAD_MUTEX_RECURSIVE); if (rc != 0) { - MOT_REPORT_SYSTEM_ERROR_CODE(rc, - pthread_mutexattr_settype, - "Statistics Manager Initialization", - "Failed to configure recursive mutex type for the statistics manager"); + // 报告配置互斥锁类型失败的系统错误 + MOT_REPORT_SYSTEM_ERROR_CODE(rc, pthread_mutexattr_settype, "Statistics Manager Initialization", + "Failed to configure recursive mutex type for the statistics manager"); } else { + // 创建递归互斥锁 rc = pthread_mutex_init(mutex, &lockattr); if (rc != 0) { - MOT_REPORT_SYSTEM_ERROR_CODE(rc, - pthread_mutex_init, - "Statistics Manager Initialization", - "Failed to create recursive mutex for the statistics manager"); + // 报告创建互斥锁失败的系统错误 + MOT_REPORT_SYSTEM_ERROR_CODE(rc, pthread_mutex_init, "Statistics Manager Initialization", + "Failed to create recursive mutex for the statistics manager"); } else { result = true; } } + // 销毁互斥锁属性 pthread_mutexattr_destroy(&lockattr); } return result; } +/* + * 功能:初始化统计管理器 + * + * 返回值: + * 如果初始化成功,返回 true;否则返回 false + * + * 注意: + * 此函数用于初始化统计管理器,包括创建互斥锁、条件变量和注册配置更改通知。 + * 在初始化的过程中,函数会检查每个步骤的结果,如果出现错误,将返回 false。 + */ bool StatisticsManager::Initialize() { bool result = true; @@ -102,6 +127,7 @@ bool StatisticsManager::Initialize() CHECK_INIT_STATUS(result, "Failed to create statistics printing lock"); m_initPhase = INIT_STAT_PRINT_LOCK; + // 初始化统计打印条件变量 int rc = pthread_cond_init(&m_statsPrintCond, nullptr); result = (rc == 0); CHECK_SYS_INIT_STATUS(rc, pthread_cond_init, "Failed to initialize statistics printing condition variable"); @@ -126,7 +152,13 @@ StatisticsManager::StatisticsManager() { // the statistics thread should be started explicitly } - +/* + * 功能:统计管理器的析构函数 + * + * 注意: + * 此析构函数用于释放统计管理器的资源,包括停止统计打印线程、取消注册配置更改通知、 + * 销毁条件变量和互斥锁。在析构的过程中,根据初始化阶段执行相应的资源释放操作。 + */ StatisticsManager::~StatisticsManager() { switch (m_initPhase) { @@ -151,53 +183,103 @@ StatisticsManager::~StatisticsManager() } } +/* + * 功能:创建 StatisticsManager 实例 + * + * 返回值: + * 如果创建成功,返回 true;否则返回 false + * + * 注意: + * 此函数用于创建 StatisticsManager 实例,并在创建后进行初始化。它确保只有一个实例被创建。 + * 如果内存分配或初始化失败,将会记录错误并返回 false。 + */ bool StatisticsManager::CreateInstance() { bool result = false; MOT_ASSERT(m_manager == nullptr); + + // 检查是否已经存在 StatisticsManager 实例 if (m_manager == nullptr) { + // 尝试分配 StatisticsManager 实例内存 m_manager = new (std::nothrow) StatisticsManager(); if (!m_manager) { MOT_LOG_ERROR("Failed to allocate memory for statistics manager, aborting"); SetLastError(MOT_ERROR_OOM, MOT_SEVERITY_FATAL); } else { + // 初始化 StatisticsManager 实例 result = m_manager->Initialize(); if (!result) { + // 初始化失败,释放已分配的内存 delete m_manager; m_manager = nullptr; } } } + return result; } +/* + * 功能:销毁 StatisticsManager 实例 + * + * 注意: + * 此函数用于销毁 StatisticsManager 实例。它会释放实例的内存,并将实例指针设为 nullptr。 + * 在销毁前,会检查是否存在有效的实例,以确保安全销毁。 + */ void StatisticsManager::DestroyInstance() { MOT_ASSERT(m_manager != nullptr); + // 检查是否存在有效的实例 if (m_manager != nullptr) { + // 释放实例内存 delete m_manager; m_manager = nullptr; } } -StatisticsManager& StatisticsManager::GetInstance() +/* + * 功能:获取 StatisticsManager 实例的引用 + * + * 返回值: + * 返回 StatisticsManager 实例的引用 + * + * 注意: + * 此函数用于获取 StatisticsManager 实例的引用,以便在应用程序中使用单一实例进行统计管理。 + * 在获取引用前,会检查是否存在有效的实例,以确保安全使用。 + */ +StatisticsManager &StatisticsManager::GetInstance() { MOT_ASSERT(m_manager != nullptr); + + // 检查是否存在有效的实例 return *m_manager; } -bool StatisticsManager::RegisterStatisticsProvider(StatisticsProvider* statisticsProvider) +/* + * 功能:注册统计信息提供者 + * + * 参数列表: + * statisticsProvider:要注册的统计信息提供者对象指针 + * + * 返回值: + * 如果注册成功,返回 true;否则返回 false + * + * 注意: + * 此函数用于注册统计信息提供者,以便统计管理器可以收集其提供的统计数据。 + * 在注册提供者前,会检查是否已经注册,以确保不会重复注册。 + */ +bool StatisticsManager::RegisterStatisticsProvider(StatisticsProvider *statisticsProvider) { bool result = false; pthread_mutex_lock(&m_providersLock); - mot_list::iterator itr = find(m_providers.begin(), m_providers.end(), statisticsProvider); + // 查找提供者是否已经注册 + mot_list::iterator itr = find(m_providers.begin(), m_providers.end(), statisticsProvider); if (itr == m_providers.end()) { + // 如果提供者尚未注册,则尝试注册 if (!m_providers.push_back(statisticsProvider)) { - MOT_REPORT_ERROR(MOT_ERROR_OOM, - "Register Statistics", - "Failed to register statistics provider %s", - statisticsProvider->GetName()); + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Register Statistics", "Failed to register statistics provider %s", + statisticsProvider->GetName()); } else { result = true; MOT_LOG_TRACE("Registered statistics provider: %s", statisticsProvider->GetName()); @@ -208,13 +290,28 @@ bool StatisticsManager::RegisterStatisticsProvider(StatisticsProvider* statistic return result; } -bool StatisticsManager::UnregisterStatisticsProvider(StatisticsProvider* statisticsProvider) +/* + * 功能:取消注册统计信息提供者 + * + * 参数列表: + * statisticsProvider:要取消注册的统计信息提供者对象指针 + * + * 返回值: + * 如果取消注册成功,返回 true;否则返回 false + * + * 注意: + * 此函数用于取消注册统计信息提供者,以停止统计管理器收集其提供的统计数据。 + * 在取消注册提供者前,会检查是否已经注册,以确保不会重复取消注册。 + */ +bool StatisticsManager::UnregisterStatisticsProvider(StatisticsProvider *statisticsProvider) { bool result = false; pthread_mutex_lock(&m_providersLock); - mot_list::iterator itr = find(m_providers.begin(), m_providers.end(), statisticsProvider); + // 查找提供者是否已经注册 + mot_list::iterator itr = find(m_providers.begin(), m_providers.end(), statisticsProvider); if (itr != m_providers.end()) { + // 如果提供者已经注册,则取消注册 m_providers.erase(itr); MOT_LOG_TRACE("Unregistered statistics provider: %s", statisticsProvider->GetName()); result = true; @@ -224,26 +321,53 @@ bool StatisticsManager::UnregisterStatisticsProvider(StatisticsProvider* statist return result; } +/* + * 功能:处理配置更改通知 + * + * 注意: + * 此函数用于响应配置更改通知,更新统计信息打印的周期参数。 + * 它会根据全局配置文件中的配置值,更新统计信息的打印周期和全量统计信息的打印周期。 + */ void StatisticsManager::OnConfigChange() { + // 从全局配置中获取统计信息打印周期参数 m_statsPrintPeriodSeconds = GetGlobalConfiguration().m_statPrintPeriodSeconds; + // 从全局配置中获取全量统计信息打印周期参数 m_fullStatsPrintPeriodSeconds = GetGlobalConfiguration().m_statPrintFullPeriodSeconds; } // functor for std::find_if() struct StatisticsProviderFinder { - const char* m_name; + const char *m_name; - explicit StatisticsProviderFinder(const char* name) : m_name(name) + explicit StatisticsProviderFinder(const char *name) : m_name(name) {} - inline bool operator()(StatisticsProvider* const& provider) const + inline bool operator()(StatisticsProvider *const &provider) const { return strcmp(m_name, provider->GetName()) == 0; } }; // functor for std::for_each() +/* + * 结构体:StatisticsProviderPrinter + * + * 功能:用于打印统计信息提供者的统计数据 + * + * 成员变量: + * m_logLevel:指定打印日志的级别 + * m_statOpts:指定统计选项,用于过滤要打印的统计数据 + * + * 构造函数: + * StatisticsProviderPrinter(LogLevel logLevel, uint32_t statOpts): + * 初始化结构体的成员变量,包括打印日志级别和统计选项。 + * + * 操作符重载: + * inline void operator()(StatisticsProvider* const& provider): + * 用于调用统计信息提供者的打印统计数据函数,前提是提供者已启用。 + * 如果提供者已启用,将使用指定的日志级别和统计选项来打印其统计数据。 + */ struct StatisticsProviderPrinter { LogLevel m_logLevel; @@ -253,7 +377,7 @@ struct StatisticsProviderPrinter { : m_logLevel(logLevel), m_statOpts(statOpts) {} - inline void operator()(StatisticsProvider* const& provider) + inline void operator()(StatisticsProvider *const &provider) { if (provider->IsEnabled()) { provider->PrintStatistics(m_logLevel, m_statOpts); @@ -261,13 +385,28 @@ struct StatisticsProviderPrinter { } }; -StatisticsProvider* StatisticsManager::GetStatisticsProvider(const char* name) +/* + * 功能:根据名称获取统计信息提供者 + * + * 参数列表: + * name:要查找的统计信息提供者的名称 + * + * 返回值: + * 如果找到匹配名称的统计信息提供者,则返回该提供者的指针;否则返回 nullptr。 + * + * 注意: + * 此函数用于根据名称查找并获取统计信息提供者的指针。 + */ +StatisticsProvider *StatisticsManager::GetStatisticsProvider(const char *name) { - StatisticsProvider* result = nullptr; + StatisticsProvider *result = nullptr; pthread_mutex_lock(&m_providersLock); - mot_list::iterator itr = + // 使用 find_if 函数查找匹配名称的统计信息提供者 + mot_list::iterator itr = find_if(m_providers.begin(), m_providers.end(), StatisticsProviderFinder(name)); + + // 如果找到匹配名称的提供者,将其赋值给 result if (itr != m_providers.end()) { result = *itr; } @@ -276,73 +415,110 @@ StatisticsProvider* StatisticsManager::GetStatisticsProvider(const char* name) return result; } +/* + * 功能:为当前线程保留统计信息线程槽位 + * + * 返回值: + * 如果成功保留线程槽位,返回 true;否则返回 false。 + * + * 注意: + * 此函数用于为当前线程保留统计信息线程槽位,以便进行统计信息的收集和记录。 + */ bool StatisticsManager::ReserveThreadSlot() { bool result = false; + // 获取当前线程的标识符 MOTThreadId tid = MOTCurrThreadId; if (tid == INVALID_THREAD_ID) { - MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, - "Reserve Thread Slot for Statistics", - "Invalid attempt to reserve statistics thread slot without current thread identifier denied"); + // 如果当前线程标识符无效,报告错误并拒绝保留线程槽位 + MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, "Reserve Thread Slot for Statistics", + "Invalid attempt to reserve statistics thread slot without current thread identifier denied"); } else { + // 获取统计信息提供者锁,确保线程安全 pthread_mutex_lock(&m_providersLock); + // 默认情况下,假设成功保留线程槽位 result = true; MOT_LOG_TRACE("Reserving statistics thread slot for thread id %" PRIu16, tid); - mot_list::iterator itr = m_providers.begin(); + + // 遍历统计信息提供者列表,为每个提供者保留线程槽位 + mot_list::iterator itr = m_providers.begin(); while (itr != m_providers.end()) { - StatisticsProvider* provider = *itr; + StatisticsProvider *provider = *itr; if (!provider->ReserveThreadSlot()) { + // 如果某个提供者无法保留线程槽位,则标记结果为失败并中止遍历 result = false; break; } ++itr; } + // 释放统计信息提供者锁,结束函数 pthread_mutex_unlock(&m_providersLock); } return result; } +/* + * 功能:取消为当前线程保留的统计信息线程槽位 + * + * 注意: + * 此函数用于取消当前线程保留的统计信息线程槽位,以便在线程退出时释放相应的资源。 + */ void StatisticsManager::UnreserveThreadSlot() { MOTThreadId tid = MOTCurrThreadId; if (tid == INVALID_THREAD_ID) { + // 如果当前线程标识符无效,报告错误 MOT_LOG_ERROR("Invalid attempt to un-reserve statistics thread slot without current thread identifier denied"); } else { + // 获取统计信息提供者锁,确保线程安全 pthread_mutex_lock(&m_providersLock); MOT_LOG_TRACE("Un-reserving statistics thread slot for thread id %" PRIu16, tid); - mot_list::iterator itr = m_providers.begin(); + // 遍历统计信息提供者列表,并取消为当前线程保留的线程槽位 + mot_list::iterator itr = m_providers.begin(); while (itr != m_providers.end()) { - StatisticsProvider* provider = *itr; + StatisticsProvider *provider = *itr; provider->UnreserveThreadSlot(); ++itr; } + // 释放统计信息提供者锁,结束函数 pthread_mutex_unlock(&m_providersLock); } } +/* + * 功能:打印统计信息 + * + * 参数列表: + * logLevel:日志级别,用于确定要输出的日志级别 + * statOpts:统计选项,指示要打印的统计信息类型 + * + * 注意: + * 此函数用于打印统计信息,根据不同的统计选项选择不同的输出方式。 + */ void StatisticsManager::PrintStatistics(LogLevel logLevel, uint32_t statOpts /* = STAT_OPT_DEFAULT */) { pthread_mutex_lock(&m_providersLock); - // make sure there is at least one enabled provider with some statistics + // 确保至少有一个启用的提供者具有一些统计信息 if (!m_providers.empty()) { bool hasEnabled = false; bool hasStats = false; - mot_list::iterator itr = m_providers.begin(); + mot_list::iterator itr = m_providers.begin(); while (itr != m_providers.end()) { - StatisticsProvider* provider = *itr; + StatisticsProvider *provider = *itr; if (provider->IsEnabled()) { hasEnabled = true; provider->Summarize(); if (provider->HasStatisticsFor(statOpts)) { hasStats = true; } + // 继续对其他统计提供者进行统计总结 // continue summarizing statistics for other statistics providers } ++itr; @@ -365,21 +541,31 @@ void StatisticsManager::PrintStatistics(LogLevel logLevel, uint32_t statOpts /* pthread_mutex_unlock(&m_providersLock); } - +/* + * 功能:启动统计信息打印线程 + * + * 注意: + * 此函数用于创建和启动一个新的线程,该线程负责定期打印统计信息。 + */ bool StatisticsManager::StartStatsPrintThread() { if (!m_running) { int rc = pthread_create(&m_statsThread, nullptr, StatsPrintThreadStatic, this); if (rc != 0) { - MOT_REPORT_SYSTEM_ERROR_CODE( - rc, pthread_create, "Statistics Manager Initialization", "Failed to create statistics printing thread"); + MOT_REPORT_SYSTEM_ERROR_CODE(rc, pthread_create, "Statistics Manager Initialization", + "Failed to create statistics printing thread"); } else { m_running = true; } } return m_running; } - +/* + * 功能:停止统计信息打印线程 + * + * 注意: + * 此函数用于停止正在运行的统计信息打印线程。它会发送信号以通知线程停止,并等待线程结束。 + */ void StatisticsManager::StopStatsPrintThread() { // signal done flag and wake up statistics printing thread @@ -394,14 +580,28 @@ void StatisticsManager::StopStatsPrintThread() } } -void* StatisticsManager::StatsPrintThreadStatic(void* param) +/* + * 功能:统计信息打印线程的静态回调函数 + * + * 参数: + * param:指向 StatisticsManager 实例的指针 + * + * 注意: + * 此函数是统计信息打印线程的静态回调函数,用于创建线程并调用 StatisticsManager 实例的 StatsPrintThread 函数。 + */ +void *StatisticsManager::StatsPrintThreadStatic(void *param) { - auto pThis = reinterpret_cast(param); + auto pThis = reinterpret_cast(param); knl_thread_mot_init(); pThis->StatsPrintThread(); return nullptr; } - +/* + * 功能:执行统计信息打印的线程函数 + * + * 注意: + * 此函数执行统计信息打印的线程功能。它定期打印统计信息报告,包括周期性报告和完整报告,并在退出前最后打印一次完整报告。 + */ void StatisticsManager::StatsPrintThread() { MOT_LOG_INFO("Statistics thread started"); @@ -439,19 +639,39 @@ void StatisticsManager::StatsPrintThread() MOT_LOG_INFO("Statistics thread stopped"); } +/* + * 功能:等待下一次打印统计信息的时间点 + * + * 注意: + * 此函数计算并等待下一次打印统计信息的时间点。它使用当前时间和预定义的统计信息检查周期来计算等待时间。 + * 等待时间到达后,线程将被唤醒以执行统计信息打印。 + */ void StatisticsManager::WaitNextPrint() { + // 获取当前时间 struct timeval now; gettimeofday(&now, nullptr); + // 计算等待时间,使用当前时间和预定义的统计信息检查周期 struct timespec ts = {(time_t)(now.tv_sec + STAT_PRINT_CHECK_PERIOD_SECONDS), now.tv_usec * 1000L}; - + // 获取打印统计信息的锁,以确保线程安全 pthread_mutex_lock(&m_statsPrintLock); + // 等待下一次打印时间到达,或者等待被其他线程唤醒 pthread_cond_timedwait(&m_statsPrintCond, &m_statsPrintLock, &ts); + // 释放打印统计信息的锁 pthread_mutex_unlock(&m_statsPrintLock); } + } // namespace MOT +/* + * 功能:打印所有统计信息 + * + * 注意: + * 此函数用于从 MOT::StatisticsManager 获取所有统计信息,并以指定的日志级别(LL_INFO)打印出来。 + * 它是一个简便的函数,可用于在代码中调用以打印当前的统计信息。 + */ void dumpStats() { + // 获取 StatisticsManager 实例并调用 PrintAllStatistics 方法打印所有统计信息 MOT::StatisticsManager::GetInstance().PrintAllStatistics(MOT::LogLevel::LL_INFO); } diff --git a/src/gausskernel/storage/mot/core/infra/stats/statistics_provider.cpp b/src/gausskernel/storage/mot/core/infra/stats/statistics_provider.cpp index b13f06997..e474a73f5 100644 --- a/src/gausskernel/storage/mot/core/infra/stats/statistics_provider.cpp +++ b/src/gausskernel/storage/mot/core/infra/stats/statistics_provider.cpp @@ -33,9 +33,21 @@ namespace MOT { DECLARE_LOGGER(StatisticsProvider, Statistics) - -StatisticsProvider::StatisticsProvider( - const char* name, StatisticsGenerator* generator, bool enable, bool extended /* = false */) +/* + * 功能:StatisticsProvider 类的构造函数 + * + * 参数列表: + * name:提供程序的名称 + * generator:与此提供程序关联的统计生成器 + * enable:是否启用此提供程序 + * extended:是否启用扩展统计信息(默认为 false) + * + * 注意: + * 此构造函数用于创建 StatisticsProvider 的实例,它接受提供程序的名称、与之关联的统计生成器、 + * 启用状态以及是否启用扩展统计信息。它还初始化了提供程序的各种统计信息对象。 + */ +StatisticsProvider::StatisticsProvider(const char *name, StatisticsGenerator *generator, bool enable, + bool extended /* = false */) : m_enable(enable), m_generator(generator), m_threadStats(nullptr), @@ -51,116 +63,158 @@ StatisticsProvider::StatisticsProvider( m_prevGlobalStats(nullptr), m_diffGlobalStats(nullptr) { + // 使用 snprintf_s 函数将提供程序的名称复制到成员变量 m_name 中 errno_t erc = snprintf_s(m_name, MAX_PROVIDER_NAME, MAX_PROVIDER_NAME - 1, "%s", name); securec_check_ss(erc, "\0", "\0"); } +/* + * 功能:StatisticsProvider 类的析构函数 + * + * 注意: + * 此析构函数用于销毁 StatisticsProvider 的实例,释放与提供程序相关的统计信息对象的内存。 + */ StatisticsProvider::~StatisticsProvider() { + // 释放全局统计信息对象的内存 if (m_globalStats) { delete m_globalStats; } + + // 释放先前的全局统计信息对象的内存 if (m_prevGlobalStats) { delete m_prevGlobalStats; } + + // 释放差异全局统计信息对象的内存 if (m_diffGlobalStats) { delete m_diffGlobalStats; } - + // 释放线程统计信息对象数组的内存 if (m_threadStats) { for (uint32_t i = 0; i < m_threadStatCount; ++i) { if (m_threadStats[i] != nullptr) { + // 调用 FreeThreadStats 函数释放线程统计信息对象的内存 FreeThreadStats(i, m_threadStats[i]); } } free(m_threadStats); } - + // 释放聚合统计信息对象的内存 if (m_aggregateStats) { delete m_aggregateStats; } + // 释放先前的聚合统计信息对象的内存 if (m_prevAggregateStats) { delete m_prevAggregateStats; } + // 释放平均统计信息对象的内存 if (m_averageStats) { delete m_averageStats; } + // 释放差异统计信息对象的内存 if (m_diffStats) { delete m_diffStats; } + // 释放差异平均统计信息对象的内存 if (m_diffAverageStats) { delete m_diffAverageStats; } - + // 释放死线程统计信息对象的内存 if (m_deadThreadStats) { delete m_deadThreadStats; } } +/* + * 功能:StatisticsProvider 类的初始化函数 + * + * 注意: + * 此函数用于初始化 StatisticsProvider 的实例,包括创建各种统计信息对象以及初始化互斥锁。 + * + * 返回值: + * - 如果初始化成功,返回 true。 + * - 如果发生错误(例如内存分配失败或互斥锁创建失败),则返回 false。 + */ bool StatisticsProvider::Initialize() { + // 创建线程聚合统计信息对象 m_aggregateStats = m_generator->CreateThreadStatistics(ThreadStatistics::THREAD_ID_TOTAL); + // 创建先前线程聚合统计信息对象 m_prevAggregateStats = m_generator->CreateThreadStatistics(ThreadStatistics::THREAD_ID_TOTAL); + // 创建线程平均统计信息对象 m_averageStats = m_generator->CreateThreadStatistics(ThreadStatistics::THREAD_ID_AVG); + // 创建线程差异统计信息对象 m_diffStats = m_generator->CreateThreadStatistics(ThreadStatistics::THREAD_ID_DIFF); + // 创建线程差异平均统计信息对象 m_diffAverageStats = m_generator->CreateThreadStatistics(ThreadStatistics::THREAD_ID_DIFF_AVG); + // 创建死线程统计信息对象 m_deadThreadStats = m_generator->CreateThreadStatistics(ThreadStatistics::THREAD_ID_TOTAL); + // 检查是否成功创建所有线程统计信息对象 if (!m_aggregateStats || !m_prevAggregateStats || !m_averageStats || !m_diffStats || !m_diffAverageStats || !m_deadThreadStats) { MOT_REPORT_ERROR(MOT_ERROR_OOM, "Load Statistics", "Failed to create thread statistics object(s)"); - return false; // safe cleanup in object destruction + return false; // 在对象销毁时进行安全清理 } - + // 获取最大线程数并创建线程统计信息对象数组 m_threadStatCount = GetGlobalConfiguration().m_maxThreads; - m_threadStats = (ThreadStatistics**)calloc(m_threadStatCount, sizeof(ThreadStatistics*)); + m_threadStats = (ThreadStatistics **)calloc(m_threadStatCount, sizeof(ThreadStatistics *)); if (!m_threadStats) { - MOT_REPORT_ERROR(MOT_ERROR_OOM, - "Load Statistics", - "Failed to create thread statistics array in size of %u slots", - m_threadStatCount); + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Load Statistics", + "Failed to create thread statistics array in size of %u slots", m_threadStatCount); return false; } - + // 创建全局统计信息对象 m_globalStats = m_generator->CreateGlobalStatistics(GlobalStatistics::NamingScheme::NAMING_SCHEME_TOTAL); + // 创建先前的全局统计信息对象 m_prevGlobalStats = m_generator->CreateGlobalStatistics(GlobalStatistics::NamingScheme::NAMING_SCHEME_TOTAL); + // 创建差异全局统计信息对象 m_diffGlobalStats = m_generator->CreateGlobalStatistics(GlobalStatistics::NamingScheme::NAMING_SCHEME_DIFF); + // 检查是否成功创建所有全局统计信息对象 if (!m_globalStats || !m_prevGlobalStats || !m_diffGlobalStats) { MOT_REPORT_ERROR(MOT_ERROR_OOM, "Load Statistics", "Failed to create global statistics object(s)"); return false; } - + // 创建并初始化互斥锁 int rc = pthread_spin_init(&m_statLock, 0); if (rc != 0) { MOT_REPORT_SYSTEM_ERROR_CODE(rc, pthread_spin_init, "Load Statistics", "Failed to create statistics lock"); return false; } - return true; } +/* + * 功能:为统计提供者保留线程槽 + * + * 注意: + * 此函数用于为统计提供者的线程保留槽位,以存储线程的统计信息。 + */ bool StatisticsProvider::ReserveThreadSlot() { bool result = false; + // 获取当前线程的标识符和节点标识符 MOTThreadId threadId = MOTCurrThreadId; int node = MOTCurrentNumaNodeId; + + // 检查线程和节点标识符是否有效 if ((threadId == INVALID_THREAD_ID) || (node == MEM_INVALID_NODE)) { - MOT_REPORT_ERROR(MOT_ERROR_INTERNAL, - "Reserve Thread Slot for Statistics", + MOT_REPORT_ERROR( + MOT_ERROR_INTERNAL, "Reserve Thread Slot for Statistics", "Invalid attempt to reserve statistics thread slot without current thread/node identifier denied (thread " "id: %u, node id: %d)", - (unsigned)threadId, - node); + (unsigned)threadId, node); } else { // there is no race here because only the current thread modifies its own slot if (m_threadStats[threadId] != nullptr) { MOT_LOG_TRACE("Double attempt to reserve statistics thread slot for thread %" PRIu16 " silently ignored: thread slot already reserved", - threadId); + threadId); result = true; } else { MOT_LOG_TRACE("Reserving %s statistics thread slot for thread id %" PRIu16, GetName(), threadId); - void* buffer = nullptr; + void *buffer = nullptr; // since statistics provider is created before MemInit(), it is preferred to keep it clean from MM API calls if (GetGlobalConfiguration().m_numaNodes > 1) { buffer = MemNumaAllocLocal(m_generator->GetObjectSize(), node); @@ -168,10 +222,8 @@ bool StatisticsProvider::ReserveThreadSlot() buffer = malloc(m_generator->GetObjectSize()); } if (buffer == nullptr) { - MOT_REPORT_ERROR(MOT_ERROR_OOM, - "Reserve Thread Slot for Statistics", - "Failed to allocate buffer in size of %u bytes", - m_generator->GetObjectSize()); + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Reserve Thread Slot for Statistics", + "Failed to allocate buffer in size of %u bytes", m_generator->GetObjectSize()); } else { pthread_spin_lock(&m_statLock); m_threadStats[threadId] = m_generator->CreateThreadStatistics(threadId, buffer); @@ -184,7 +236,13 @@ bool StatisticsProvider::ReserveThreadSlot() return result; } - +/* + * 功能:取消保留线程槽 + * + * 注意: + * 此函数用于取消保留线程槽,将线程的统计信息移动到死线程列表中。 + * 统计信息将保留在那里,直到下一次摘要打印,然后再次回收。 + */ void StatisticsProvider::UnreserveThreadSlot() { // move the statistics to the dead thread list @@ -196,11 +254,11 @@ void StatisticsProvider::UnreserveThreadSlot() if (m_threadStats[threadId] == nullptr) { MOT_LOG_TRACE("Attempt to unreserve statistics thread slot for thread %" PRIu16 " silently ignored: thread slot already unreserved", - threadId); + threadId); } else { // aggregate dead thread statistics and cleanup pthread_spin_lock(&m_statLock); - ThreadStatistics* threadStats = m_threadStats[threadId]; + ThreadStatistics *threadStats = m_threadStats[threadId]; m_deadThreadStats->Add(*threadStats); m_threadStats[threadId] = nullptr; // this must be guarded with a lock due to race with Summarize() pthread_spin_unlock(&m_statLock); @@ -210,10 +268,17 @@ void StatisticsProvider::UnreserveThreadSlot() } } +/* + * 功能:总结统计信息 + * + * 注意: + * 此函数用于总结统计信息,包括线程级别、全局级别和差异级别的统计信息。 + */ void StatisticsProvider::Summarize() { pthread_spin_lock(&m_statLock); + // 备份并重置聚合统计信息 m_prevAggregateStats->Assign(*m_aggregateStats); m_aggregateStats->Reset(); m_averageStats->Reset(); @@ -256,20 +321,39 @@ void StatisticsProvider::Summarize() m_prevGlobalStats->Assign(*m_globalStats); } +/* + * 功能:检查是否存在满足给定统计选项的有效统计数据 + * + * 参数列表: + * statOpts:指定要检查的统计选项,可以是多个选项的按位组合 + * + * 返回值: + * 如果存在满足给定统计选项的有效统计数据,则返回 true;否则返回 false。 + * + * 注意: + * 此函数用于检查是否存在满足给定统计选项的有效统计数据。根据统计选项的不同,会检查不同的统计数据对象。 + */ bool StatisticsProvider::HasStatisticsFor(uint32_t statOpts) { bool result = m_hasExtendedStats; + + // 如果需要线程级别的统计数据 if (!result && (statOpts & STAT_OPT_SCOPE_THREAD)) { + // 检查摘要级别的差异统计数据是否有有效样本 if (statOpts & STAT_OPT_LEVEL_SUMMARY) { result = m_diffStats->HasValidSamples(); } + // 如果没有找到满足摘要级别要求的统计数据,检查详细级别的差异平均统计数据 if (!result && (statOpts & STAT_OPT_LEVEL_DETAIL)) { result = m_diffAverageStats->HasValidSamples(); } } + // 如果需要全局级别的统计数据 if (!result && (statOpts & STAT_OPT_SCOPE_GLOBAL)) { + // 如果需要详细级别的统计数据 if (statOpts & STAT_OPT_LEVEL_DETAIL) { + // 首先获取锁以访问线程级别的统计数据 pthread_spin_lock(&m_statLock); for (uint32_t i = 0; i < m_threadStatCount; ++i) { if (m_threadStats[i] != nullptr) { @@ -279,11 +363,16 @@ bool StatisticsProvider::HasStatisticsFor(uint32_t statOpts) } } } + // 释放锁 pthread_spin_unlock(&m_statLock); + + // 如果没有找到满足详细级别要求的线程级别的统计数据,检查平均统计数据 if (!result) { result = m_averageStats->HasValidSamples(); } } + + // 如果没有找到满足摘要级别要求的统计数据,检查聚合统计数据 if (!result && (statOpts & STAT_OPT_LEVEL_SUMMARY)) { result = m_aggregateStats->HasValidSamples(); } @@ -292,71 +381,135 @@ bool StatisticsProvider::HasStatisticsFor(uint32_t statOpts) return result; } +/* + * 功能:打印统计数据 + * + * 参数列表: + * logLevel:指定要使用的日志级别,用于打印统计数据 + * statOpts:指定要打印的统计选项,可以是多个选项的按位组合,默认为 STAT_OPT_DEFAULT + * + * 注意: + * 此函数用于根据给定的统计选项打印统计数据。根据统计选项的不同,会打印线程级别和全局级别的统计数据。 + */ void StatisticsProvider::PrintStatistics(LogLevel logLevel, uint32_t statOpts /* = STAT_OPT_DEFAULT */) { + // 如果需要线程级别的统计数据 if (statOpts & STAT_OPT_SCOPE_THREAD) { for (uint32_t statId = 0; statId < m_aggregateStats->GetStatCount(); ++statId) { + // 打印线程级别的统计数据 PrintThreadStats(statOpts, statId, logLevel); } } + + // 如果需要全局级别的统计数据 if (statOpts & STAT_OPT_SCOPE_GLOBAL) { for (uint32_t statId = 0; statId < m_globalStats->GetStatCount(); ++statId) { + // 打印全局级别的统计数据 PrintGlobalStats(statOpts, statId, logLevel); } } + // 打印额外的统计数据(未提供具体实现,可能在后续代码中定义) PrintStatisticsEx(); } +/* + * 功能:打印线程级别的统计数据 + * + * 参数列表: + * statOpts:指定要打印的统计选项,可以是多个选项的按位组合 + * statId:要打印的统计数据标识符 + * logLevel:指定要使用的日志级别,用于打印统计数据 + * + * 注意: + * 此函数用于打印线程级别的统计数据,根据给定的统计选项和统计数据标识符来确定要打印哪些数据以及使用哪个日志级别。 + */ void StatisticsProvider::PrintThreadStats(uint32_t statOpts, uint32_t statId, LogLevel logLevel) { - // print diff stats + // 如果需要打印周期差异统计数据 if (statOpts & STAT_OPT_PERIOD_DIFF) { + // 如果需要打印摘要级别的统计数据 if (statOpts & STAT_OPT_LEVEL_SUMMARY) { + // 打印差异统计数据 m_diffStats->Print(statId, logLevel); } + + // 如果需要打印详细级别的统计数据 if (statOpts & STAT_OPT_LEVEL_DETAIL) { + // 打印差异平均统计数据 m_diffAverageStats->Print(statId, logLevel); } } - // print total stats + // 如果需要打印周期总计统计数据 if (statOpts & STAT_OPT_PERIOD_TOTAL) { + // 如果需要打印详细级别的统计数据 if (statOpts & STAT_OPT_LEVEL_DETAIL) { pthread_spin_lock(&m_statLock); for (uint32_t i = 0; i < m_threadStatCount; ++i) { if (m_threadStats[i] != nullptr) { + // 打印线程级别的统计数据 m_threadStats[i]->Print(statId, logLevel); } } pthread_spin_unlock(&m_statLock); + + // 打印平均统计数据 m_averageStats->Print(statId, logLevel); } + + // 如果需要打印摘要级别的统计数据 if (statOpts & STAT_OPT_LEVEL_SUMMARY) { + // 打印聚合统计数据 m_aggregateStats->Print(statId, logLevel); } } } - +/* + * 功能:打印全局级别的统计数据 + * + * 参数列表: + * statOpts:指定要打印的统计选项,可以是多个选项的按位组合 + * statId:要打印的统计数据标识符 + * logLevel:指定要使用的日志级别,用于打印统计数据 + * + * 注意: + * 此函数用于打印全局级别的统计数据,根据给定的统计选项和统计数据标识符来确定要打印哪些数据以及使用哪个日志级别。 + */ void StatisticsProvider::PrintGlobalStats(uint32_t statOpts, uint32_t statId, LogLevel logLevel) { // print diff stats if (statOpts & STAT_OPT_PERIOD_DIFF) { + // 打印差异统计数据 m_diffGlobalStats->Print(statId, logLevel); } // print total stats + // 如果需要打印周期总计统计数据,并且不需要打印详细级别的统计数据 if ((statOpts & STAT_OPT_PERIOD_TOTAL) && !(statOpts & STAT_OPT_LEVEL_DETAIL)) { m_globalStats->Print(statId, logLevel); } } -void StatisticsProvider::FreeThreadStats(MOTThreadId threadId, ThreadStatistics* threadStats) +/* + * 功能:释放线程统计数据 + * + * 参数列表: + * threadId:要释放统计数据的线程标识符 + * threadStats:指向要释放的线程统计数据对象的指针 + * + * 注意: + * 此函数用于释放线程级别的统计数据,包括内存的回收。它根据线程标识符获取要释放的统计数据,然后回收相关的内存。 + */ +void StatisticsProvider::FreeThreadStats(MOTThreadId threadId, ThreadStatistics *threadStats) { MOT_LOG_TRACE("Reclaiming %s statistics thread slot for thread id %" PRIu16, GetName(), threadId); - void* buffer = (void*)threadStats->GetInPlaceBuffer(); + // 获取要回收的内存缓冲区 + void *buffer = (void *)threadStats->GetInPlaceBuffer(); int node = threadStats->GetNodeId(); + // 销毁线程统计数据对象 threadStats->~ThreadStatistics(); + // 根据配置的NUMA节点数进行内存回收 if (GetGlobalConfiguration().m_numaNodes > 1) { MemNumaFreeLocal(buffer, m_generator->GetObjectSize(), node); } else { diff --git a/src/gausskernel/storage/mot/core/infra/stats/thread_statistics.cpp b/src/gausskernel/storage/mot/core/infra/stats/thread_statistics.cpp index 6a9ebb6bf..36ef52425 100644 --- a/src/gausskernel/storage/mot/core/infra/stats/thread_statistics.cpp +++ b/src/gausskernel/storage/mot/core/infra/stats/thread_statistics.cpp @@ -27,23 +27,53 @@ namespace MOT { DECLARE_LOGGER(ThreadStatistics, Statistics) +/* + * 功能:汇总线程统计数据 + * + * 参数列表: + * updateTstamp:一个布尔值,指示是否需要更新时间戳 + * + * 注意: + * 此函数用于汇总线程级别的统计数据。它迭代处理每个统计变量,并要求它们执行汇总操作,可选择是否更新时间戳。 + */ void ThreadStatistics::Summarize(bool updateTstamp) { + // 获取统计变量的数量 uint32_t statCount = m_statVars.size(); + + // 遍历所有统计变量,执行汇总操作 for (uint32_t i = 0; i < statCount; ++i) { m_statVars[i]->Summarize(updateTstamp); } } +/* + * 功能:打印线程统计数据 + * + * 参数列表: + * statId:要打印的统计变量的ID + * logLevel:日志级别,指示打印的日志级别 + * + * 注意: + * 此函数用于打印线程级别的统计数据。它根据给定的统计变量ID,打印特定统计变量的数据, + * 或者如果未提供ID,则打印所有统计变量的数据。 + */ void ThreadStatistics::Print(uint32_t statId, LogLevel logLevel) const { + // 获取统计变量的数量 uint32_t statCount = m_statVars.size(); + + // 如果提供了statId,只打印指定的统计变量 if (statId < statCount) { + // 检查该统计变量是否有样本数据,如果有则打印 if (m_statVars[statId]->GetSampleCount() > 0) { m_statVars[statId]->Print(logLevel); } - } else { + } + // 如果未提供statId,打印所有统计变量的数据 + else { for (uint32_t i = 0; i < statCount; ++i) { + // 检查每个统计变量是否有样本数据,如果有则打印 if (m_statVars[i]->GetSampleCount() > 0) { m_statVars[i]->Print(logLevel); } @@ -51,57 +81,123 @@ void ThreadStatistics::Print(uint32_t statId, LogLevel logLevel) const } } +/* + * 功能:重置线程统计数据 + * + * 注意: + * 此函数用于重置线程级别的统计数据。它将所有统计变量的数据重置为初始状态,并清除有效线程计数。 + */ void ThreadStatistics::Reset() { + // 获取统计变量的数量 uint32_t statCount = m_statVars.size(); + + // 重置每个统计变量的数据,并清除有效线程计数 for (uint32_t i = 0; i < statCount; ++i) { m_statVars[i]->Reset(); m_validThreads[i] = 0; } } -void ThreadStatistics::Assign(const ThreadStatistics& rhs) +/* + * 功能:将线程统计数据赋值为另一个线程统计数据的副本 + * + * 参数列表: + * rhs:要赋值的线程统计数据对象的引用 + * + * 注意: + * 此函数用于将线程统计数据赋值为另一个线程统计数据对象的副本。它逐个统计变量地赋值,并保留有效线程计数。 + */ +void ThreadStatistics::Assign(const ThreadStatistics &rhs) { + // 获取统计变量的数量 uint32_t statCount = m_statVars.size(); + + // 逐个统计变量地赋值,并保留有效线程计数 for (uint32_t i = 0; i < statCount; ++i) { m_statVars[i]->Assign(*rhs.m_statVars[i]); m_validThreads[i] = rhs.m_validThreads[i]; } } -void ThreadStatistics::Add(const ThreadStatistics& rhs) +/* + * 功能:将线程统计数据累加到当前线程统计数据 + * + * 参数列表: + * rhs:要累加的线程统计数据对象的引用 + * + * 注意: + * 此函数用于将线程统计数据累加到当前线程统计数据对象中。它逐个统计变量地累加,并更新有效线程计数。 + */ +void ThreadStatistics::Add(const ThreadStatistics &rhs) { + // 获取统计变量的数量 uint32_t statCount = m_statVars.size(); + + // 逐个统计变量地累加,并更新有效线程计数 for (uint32_t i = 0; i < statCount; ++i) { m_statVars[i]->Add(*rhs.m_statVars[i]); - if (rhs.m_statVars[i]->GetSampleCount() > 0) + if (rhs.m_statVars[i]->GetSampleCount() > 0) { ++m_validThreads[i]; + } } } -void ThreadStatistics::Subtract(const ThreadStatistics& rhs) +/* + * 功能:从当前线程统计数据中减去另一个线程统计数据 + * + * 参数列表: + * rhs:要减去的线程统计数据对象的引用 + * + * 注意: + * 此函数用于从当前线程统计数据中减去另一个线程统计数据对象。它逐个统计变量地进行减法操作。 + */ +void ThreadStatistics::Subtract(const ThreadStatistics &rhs) { + // 获取统计变量的数量 uint32_t statCount = m_statVars.size(); + + // 逐个统计变量地进行减法操作 for (uint32_t i = 0; i < statCount; ++i) { m_statVars[i]->Subtract(*rhs.m_statVars[i]); } } +/* + * 功能:规范化当前线程统计数据 + * + * 注意: + * 此函数用于规范化当前线程的统计数据。它逐个统计变量地进行规范化操作,将每个变量的值除以有效线程数。 + */ void ThreadStatistics::Normalize() { + // 获取统计变量的数量 uint32_t statCount = m_statVars.size(); + + // 逐个统计变量地进行规范化操作 for (uint32_t i = 0; i < statCount; ++i) { + // 如果有有效线程,将统计变量的值除以有效线程数 if (m_validThreads[i] > 0) { m_statVars[i]->Divide(m_validThreads[i]); } } } +/* + * 功能:检查是否存在有效的统计样本 + * + * 返回值: + * 如果至少一个统计变量存在有效的统计样本,则返回 true;否则返回 false。 + * + * 注意: + * 此函数用于检查当前线程的统计数据是否至少有一个统计变量包含有效的统计样本。 + */ bool ThreadStatistics::HasValidSamples() const { bool result = false; uint32_t statCount = m_statVars.size(); for (uint32_t i = 0; i < statCount; ++i) { + // 如果统计变量的统计样本数量大于 0,则将 result 设置为 true 并退出循环 if (m_statVars[i]->GetSampleCount() > 0) { result = true; break; @@ -110,34 +206,64 @@ bool ThreadStatistics::HasValidSamples() const return result; } -mot_string ThreadStatistics::MakeName(const char* baseName, uint64_t threadId) +/* + * 功能:生成统计变量名称 + * + * 参数列表: + * baseName:基本名称,用于构建统计变量名称 + * threadId:线程标识符,用于区分不同线程的统计变量名称 + * + * 返回值: + * 返回一个包含构建的统计变量名称的 mot_string 对象。 + * + * 注意: + * 此函数用于生成统计变量的名称,根据给定的基本名称和线程标识符来构建不同的名称。 + */ +mot_string ThreadStatistics::MakeName(const char *baseName, uint64_t threadId) { mot_string result; if (threadId == THREAD_ID_TOTAL) { + // 如果线程标识符为 THREAD_ID_TOTAL,则将 [TOTAL] 添加到基本名称中 result.format("%s[TOTAL]", baseName); } else if (threadId == THREAD_ID_AVG) { + // 如果线程标识符为 THREAD_ID_AVG,则将 [AVG] 添加到基本名称中 result.format("%s[AVG]", baseName); } else if (threadId == THREAD_ID_DIFF) { + // 如果线程标识符为 THREAD_ID_DIFF,则将 [DIFF] 添加到基本名称中 result.format("%s[DIFF]", baseName); } else if (threadId == THREAD_ID_DIFF_AVG) { + // 如果线程标识符为 THREAD_ID_DIFF_AVG,则将 [DIFF-AVG] 添加到基本名称中 result.format("%s[DIFF-AVG]", baseName); } else { + // 否则,将线程标识符添加到基本名称中 result.format("%s[%u]", baseName, (unsigned)threadId); } return result; } -void ThreadStatistics::RegisterStatistics(StatisticVariable* statVar) +/* + * 功能:注册统计变量 + * + * 参数列表: + * statVar:要注册的统计变量指针 + * + * 注意: + * 此函数用于注册统计变量,将统计变量添加到统计变量列表中,并分配相应的有效线程槽。 + * 如果添加统计变量或有效线程槽失败,会报告内存不足错误。 + */ +void ThreadStatistics::RegisterStatistics(StatisticVariable *statVar) { + // 将统计变量添加到统计变量列表中 if (!m_statVars.push_back(statVar)) { - MOT_REPORT_ERROR( - MOT_ERROR_OOM, "Register Statistics", "Failed to add statistic variable %s", statVar->GetName()); - } else if (!m_validThreads.push_back(0)) { - MOT_REPORT_ERROR(MOT_ERROR_OOM, - "Register Statistics", - "Failed to add valid thread slot for statistic variable %s", - statVar->GetName()); + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Register Statistics", "Failed to add statistic variable %s", + statVar->GetName()); + } + // 为统计变量分配相应的有效线程槽 + else if (!m_validThreads.push_back(0)) { + MOT_REPORT_ERROR(MOT_ERROR_OOM, "Register Statistics", + "Failed to add valid thread slot for statistic variable %s", statVar->GetName()); } } + } // namespace MOT diff --git a/src/gausskernel/storage/mot/core/infra/synchronization/affinity.cpp b/src/gausskernel/storage/mot/core/infra/synchronization/affinity.cpp index 5b1f944ac..1cb3970e0 100644 --- a/src/gausskernel/storage/mot/core/infra/synchronization/affinity.cpp +++ b/src/gausskernel/storage/mot/core/infra/synchronization/affinity.cpp @@ -47,18 +47,35 @@ void Affinity::Configure(uint64_t numaNodes, uint64_t physicalCoresNuma, Affinit m_affinityMode = affinityMode; } +/* + * 功能:获取亲和处理器 + * + * 参数列表: + * threadId:线程标识符,用于计算亲和处理器 + * + * 返回值: + * 返回亲和处理器的标识符,如果无效则返回 INVALID_CPU_ID + * + * 注意: + * 此函数根据指定的亲和性模式(m_affinityMode)和线程标识符(threadId)来计算亲和处理器。 + * 根据不同的亲和性模式,它可以返回不同的亲和处理器标识符。 + * 如果指定的亲和性模式无效,会记录错误并返回 INVALID_CPU_ID。 + */ uint32_t Affinity::GetAffineProcessor(uint64_t threadId) const { uint32_t result = INVALID_CPU_ID; threadId = threadId % (m_numaNodes * m_physicalCoresNuma); + // 根据亲和性模式选择相应的处理器计算方法 switch (m_affinityMode) { case AffinityMode::FILL_SOCKET_FIRST: { + // 使用 GetMappedCore 方法获取亲和处理器 result = (uint32_t)GetGlobalConfiguration().GetMappedCore(threadId); break; } case AffinityMode::EQUAL_PER_SOCKET: { + // 计算 NUMA 节点和本地处理器索引,然后合并计算出亲和处理器 threadId = threadId % (m_numaNodes * m_physicalCoresNuma); uint32_t numaId = threadId % m_numaNodes; uint32_t localProc = threadId / m_numaNodes; @@ -67,10 +84,12 @@ uint32_t Affinity::GetAffineProcessor(uint64_t threadId) const } case AffinityMode::FILL_PHYSICAL_FIRST: + // 使用 GetCoreByConnidFP 方法获取亲和处理器 result = (uint32_t)GetGlobalConfiguration().GetCoreByConnidFP(threadId); break; default: + // 记录错误并返回无效的处理器标识符 MOT_LOG_ERROR("%s: Invalid affinity configuration: %d", __func__, (int)m_affinityMode); result = INVALID_CPU_ID; break; @@ -78,18 +97,35 @@ uint32_t Affinity::GetAffineProcessor(uint64_t threadId) const return result; } +/* + * 功能:获取亲和 NUMA 节点 + * + * 参数列表: + * threadId:线程标识符,用于计算亲和 NUMA 节点 + * + * 返回值: + * 返回亲和 NUMA 节点的标识符,如果无效则返回 INVALID_NODE_ID + * + * 注意: + * 此函数根据指定的亲和性模式(m_affinityMode)和线程标识符(threadId)来计算亲和 NUMA 节点。 + * 根据不同的亲和性模式,它可以返回不同的 NUMA 节点标识符。 + * 如果指定的亲和性模式无效,会记录错误并返回 INVALID_NODE_ID。 + */ uint32_t Affinity::GetAffineNuma(uint64_t threadId) const { uint32_t result = INVALID_NODE_ID; threadId = threadId % (m_numaNodes * m_physicalCoresNuma); + // 根据亲和性模式选择相应的 NUMA 节点计算方法 switch (m_affinityMode) { case AffinityMode::FILL_SOCKET_FIRST: { + // 使用 GetMappedCore 方法获取亲和 NUMA 节点 result = (uint32_t)GetGlobalConfiguration().GetCpuNode(GetGlobalConfiguration().GetMappedCore(threadId)); break; } case AffinityMode::EQUAL_PER_SOCKET: + // 直接计算 NUMA 节点 result = (uint32_t)threadId % m_numaNodes; break; @@ -106,6 +142,7 @@ uint32_t Affinity::GetAffineNuma(uint64_t threadId) const } default: + // 记录错误并返回无效的 NUMA 节点标识符 MOT_LOG_ERROR("%s: Invalid affinity configuration: %d", __func__, (int)m_affinityMode); result = (uint32_t)INVALID_NODE_ID; break; @@ -113,8 +150,24 @@ uint32_t Affinity::GetAffineNuma(uint64_t threadId) const return result; } - -bool Affinity::SetAffinity(uint64_t threadId, uint32_t* threadCore /* = nullptr */) const +/* + * 功能:设置线程的 CPU 亲和性 + * + * 参数列表: + * threadId:线程标识符,用于计算亲和 CPU + * threadCore:用于存储线程所绑定的 CPU 的指针,默认为 nullptr + * + * 返回值: + * 如果成功设置线程的 CPU 亲和性,则返回 true,否则返回 false + * + * 注意: + * 此函数根据指定的线程标识符(threadId)计算线程应该绑定的 CPU。 + * 然后,它创建一个 CPU 集合(mask)并将目标 CPU 加入其中,最后使用 pthread_setaffinity_np 函数 + * 或 sched_setaffinity 函数设置线程的 CPU 亲和性。如果设置失败,函数会尝试 sched_setaffinity 函数。 + * 如果仍然失败,会记录错误并返回 false。 + * 如果 threadCore 参数不为空,它将存储线程所绑定的 CPU 核心标识符。 + */ +bool Affinity::SetAffinity(uint64_t threadId, uint32_t *threadCore /* = nullptr */) const { bool result = true; uint32_t coreId = GetAffineProcessor(threadId); @@ -138,10 +191,8 @@ bool Affinity::SetAffinity(uint64_t threadId, uint32_t* threadCore /* = nullptr result = false; } } else { - MOT_LOG_TRACE("Set current thread %u affinity to core %u (socket %u)", - (unsigned)threadId, - (unsigned)coreId, - (unsigned)GetAffineNuma(threadId)); + MOT_LOG_TRACE("Set current thread %u affinity to core %u (socket %u)", (unsigned)threadId, (unsigned)coreId, + (unsigned)GetAffineNuma(threadId)); } if (threadCore) { @@ -149,7 +200,20 @@ bool Affinity::SetAffinity(uint64_t threadId, uint32_t* threadCore /* = nullptr } return result; } - +/* + * 功能:设置当前线程的 NUMA 节点亲和性 + * + * 参数列表: + * nodeId:NUMA 节点标识符,指定要绑定到的 NUMA 节点 + * + * 返回值: + * 如果成功设置线程的 NUMA 节点亲和性,则返回 true,否则返回 false + * + * 注意: + * 此函数将当前线程绑定到指定的 NUMA 节点。它创建一个 CPU 集合(mask)并将该节点的所有 CPU 加入其中, + * 然后使用 pthread_setaffinity_np 函数或 sched_setaffinity 函数设置线程的 NUMA 节点亲和性。 + * 如果设置失败,函数会尝试 sched_setaffinity 函数。如果仍然失败,会记录错误并返回 false。 + */ bool Affinity::SetNodeAffinity(int nodeId) { bool result = true; @@ -173,14 +237,29 @@ bool Affinity::SetNodeAffinity(int nodeId) return result; } -static const char* AFFINITY_FILL_SOCKET_FIRST_STR = "fill-socket-first"; -static const char* AFFINITY_EQUAL_PER_SOCKET_STR = "equal-per-socket"; -static const char* AFFINITY_FILL_PHYSICAL_FIRST_STR = "fill-physical-first"; -static const char* AFFINITY_NONE_STR = "none"; +static const char *AFFINITY_FILL_SOCKET_FIRST_STR = "fill-socket-first"; +static const char *AFFINITY_EQUAL_PER_SOCKET_STR = "equal-per-socket"; +static const char *AFFINITY_FILL_PHYSICAL_FIRST_STR = "fill-physical-first"; +static const char *AFFINITY_NONE_STR = "none"; -extern AffinityMode AffinityModeFromString(const char* affinityModeStr) +/* + * 功能:从字符串中解析亲和性模式 + * + * 参数列表: + * affinityModeStr:包含亲和性模式描述的字符串 + * + * 返回值: + * 解析的亲和性模式,如果无法解析则返回 AffinityMode::AFFINITY_INVALID + * + * 注意: + * 此函数将输入的字符串与已知的亲和性模式字符串进行比较,以确定给定字符串表示的亲和性模式。 + * 如果字符串与已知模式匹配,则返回相应的 AffinityMode 枚举值;否则返回 AFFINITY_INVALID。 + */ +extern AffinityMode AffinityModeFromString(const char *affinityModeStr) { AffinityMode result = AffinityMode::AFFINITY_INVALID; + + // 使用strcmp函数比较输入字符串与已知的亲和性模式字符串 if (strcmp(affinityModeStr, AFFINITY_FILL_SOCKET_FIRST_STR) == 0) { result = AffinityMode::FILL_SOCKET_FIRST; } else if (strcmp(affinityModeStr, AFFINITY_EQUAL_PER_SOCKET_STR) == 0) { @@ -190,10 +269,24 @@ extern AffinityMode AffinityModeFromString(const char* affinityModeStr) } else if (strcmp(affinityModeStr, AFFINITY_NONE_STR) == 0) { result = AffinityMode::AFFINITY_NONE; } + return result; } -extern const char* AffinityModeToString(AffinityMode affinityMode) +/* + * 功能:将亲和性模式枚举转换为字符串表示 + * + * 参数列表: + * affinityMode:要转换的亲和性模式枚举值 + * + * 返回值: + * 亲和性模式的字符串表示,如果枚举值无效则返回 "N/A" + * + * 注意: + * 此函数将输入的亲和性模式枚举值映射为相应的字符串,以便进行打印或显示。 + * 如果输入的枚举值无效,则返回 "N/A"。 + */ +extern const char *AffinityModeToString(AffinityMode affinityMode) { switch (affinityMode) { case AffinityMode::FILL_SOCKET_FIRST: @@ -212,4 +305,5 @@ extern const char* AffinityModeToString(AffinityMode affinityMode) return "N/A"; } } + } // namespace MOT -- 2.34.1 From 69597c7b867aaab02c57ff0e26cd904650b3548c Mon Sep 17 00:00:00 2001 From: noah <1204149038@qq.com> Date: Wed, 4 Oct 2023 13:19:12 +0800 Subject: [PATCH 48/50] =?UTF-8?q?process=E6=96=87=E4=BB=B6=E5=A4=B9?= =?UTF-8?q?=E4=B8=8B=E6=96=87=E4=BB=B6=E6=B3=A8=E9=87=8A=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../process/postmaster/fencedudf.cpp | 1202 +++--- .../process/postmaster/pagerepair.cpp | 1116 ++++- .../process/postmaster/pagewriter.cpp | 1193 +++-- .../process/postmaster/pgaudit.cpp | 885 ++-- src/gausskernel/process/postmaster/pgstat.cpp | 3842 +++++++++++------ 5 files changed, 5607 insertions(+), 2631 deletions(-) diff --git a/src/gausskernel/process/postmaster/fencedudf.cpp b/src/gausskernel/process/postmaster/fencedudf.cpp index cc95b5c00..10108431a 100644 --- a/src/gausskernel/process/postmaster/fencedudf.cpp +++ b/src/gausskernel/process/postmaster/fencedudf.cpp @@ -80,7 +80,7 @@ typedef enum { UDF_INFO = 5, UDF_ARGS, UDF_RESULT, UDF_ERROR } UDFMsgType; typedef enum { UDF_SEND_ARGS = 0, UDF_RECV_ARGS = 1, UDF_SEND_RESULT = 2, UDF_RECV_RESULT = 3 } UDFArgHandlerType; -typedef enum { C_UDF, JAVA_UDF, PYTHON_UDF} UDF_LANG; +typedef enum { C_UDF, JAVA_UDF, PYTHON_UDF } UDF_LANG; typedef enum { MASTER_PROCESS = 1, WORK_PROCESS, KILL_WORK } UDF_Process; @@ -105,21 +105,21 @@ typedef enum { typedef enum { OPTION_ADDJAR = 0, OPTION_LS, OPTION_RMJAR } LibraryOptType; typedef struct OBSInfo { - char* accesskey; - char* secretkey; - char* region; - char* bucket; - char* path; + char *accesskey; + char *secretkey; + char *region; + char *bucket; + char *path; } OBSInfo; typedef struct LibraryInfo { - char* option; - char* operation; + char *option; + char *operation; LibraryOptType optionType; - char* destpath; + char *destpath; bool isSourceLocal; - char* localpath; - OBSInfo* obsInfo; + char *localpath; + OBSInfo *obsInfo; } LibraryInfo; #define KB 1024 @@ -133,17 +133,17 @@ THR_LOCAL Oid lastUDFOid = InvalidOid; static MemoryContext UDFWorkMemContext = NULL; static int listenUDFSocket = -1; static bool UDFMasterQuitFlag = false; -static HTAB* UDFFuncHash = NULL; +static HTAB *UDFFuncHash = NULL; /* PRC Server Declaration */ static void UDFMasterServerLoop(); static void UDFWorkerMain(int socket); static pid_t StartUDFWorker(int socket); static void SIGQUITUDFMaster(SIGNAL_ARGS); -static void RecvUDFInformation(int socket, FunctionCallInfoData* fcinfo); -static void FindOrInsertUDFHashTab(FunctionCallInfoData* fcinfo); +static void RecvUDFInformation(int socket, FunctionCallInfoData *fcinfo); +static void FindOrInsertUDFHashTab(FunctionCallInfoData *fcinfo); static void UDFCreateHashTab(); -void SetUDFUnixSocketPath(struct sockaddr_un* unAddrPtr); +void SetUDFUnixSocketPath(struct sockaddr_un *unAddrPtr); /* RPC Client Declaration */ template @@ -152,40 +152,51 @@ static void SendUDFInformation(FunctionCallInfo fcinfo); template extern Datum RPCFencedUDF(FunctionCallInfo fcinfo); -bool RPCInitFencedUDFIfNeed(Oid functionId, FmgrInfo* finfo, HeapTuple procedureTuple); -void InitFuncCallUDFInfo(FunctionCallInfoData* fcinfo, int argN, bool setFuncPtr); -void InitUDFInfo(UDFInfoType* udfInfo, int argN, int batchRows); -void FencedUDFMasterMain(int argc, char* argv[]); +bool RPCInitFencedUDFIfNeed(Oid functionId, FmgrInfo *finfo, HeapTuple procedureTuple); +void InitFuncCallUDFInfo(FunctionCallInfoData *fcinfo, int argN, bool setFuncPtr); +void InitUDFInfo(UDFInfoType *udfInfo, int argN, int batchRows); +void FencedUDFMasterMain(int argc, char *argv[]); /* Extern Enterface Declaration */ -extern void* internal_load_library(const char* libname); - +extern void *internal_load_library(const char *libname); +/* + * 功能:启动UDF主进程 + * + * 参数列表:无 + * + * 注意: + * 此函数用于创建并启动一个新的进程,其中子进程执行了 FencedUDFMasterMain() 函数,而父进程继续执行后续逻辑。 + */ void StartUDFMaster() { + // 创建变量以存储新进程的PID pid_t fencedWorkPid = 0; + // 使用 switch-case 语句处理 fork_process() 的返回值 switch ((fencedWorkPid = fork_process())) { case -1: { + // 子进程创建失败时的处理逻辑 int errsv = errno; ereport(WARNING, - (errmodule(MOD_UDF), - errcode(ERRCODE_INVALID_STATUS), - errmsg("could not fork gaussDB process, errno:%d, error reason:%s", - errsv, - strerror(errsv)))); + (errmodule(MOD_UDF), errcode(ERRCODE_INVALID_STATUS), + errmsg("could not fork gaussDB process, errno:%d, error reason:%s", errsv, strerror(errsv)))); break; } case 0: { - + // 子进程的处理逻辑 + // 调用 FencedUDFMasterMain() 函数,子进程会在这个函数中执行 FencedUDFMasterMain(0, NULL); + // 子进程执行完毕后退出 exit(0); break; } default: { + // 父进程的处理逻辑,不执行具体操作 break; } } } + /* * @Description: The main function of UDF RPC server. * It will initialize envrionment, create listen socket and startup @@ -193,7 +204,7 @@ void StartUDFMaster() * @IN argc: the number of argment value * @IN argv: the argment values */ -void FencedUDFMasterMain(int argc, char* argv[]) +void FencedUDFMasterMain(int argc, char *argv[]) { SetProcessingMode(FencedProcessing); @@ -229,8 +240,8 @@ void FencedUDFMasterMain(int argc, char* argv[]) #if ((defined ENABLE_PYTHON2) || (defined ENABLE_PYTHON3)) BaseInit(); InitProcess(); - t_thrd.utils_cxt.CurrentResourceOwner = ResourceOwnerCreate(NULL, "fencedMaster", - THREAD_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_AI)); + t_thrd.utils_cxt.CurrentResourceOwner = + ResourceOwnerCreate(NULL, "fencedMaster", THREAD_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_AI)); #endif /* @@ -249,7 +260,7 @@ void FencedUDFMasterMain(int argc, char* argv[]) SetUDFUnixSocketPath(&unAddr); unlink(unAddr.sun_path); - if (bind(listenUDFSocket, (struct sockaddr*)&unAddr, sizeof(unAddr)) < 0) { + if (bind(listenUDFSocket, (struct sockaddr *)&unAddr, sizeof(unAddr)) < 0) { ereport(FATAL, (errmodule(MOD_UDF), errmsg("bind socket path %s failed:%m", unAddr.sun_path))); } if (listen(listenUDFSocket, 65536) < 0) { @@ -309,12 +320,9 @@ void UDFMasterServerLoop() if (selres < 0) { if (errno != EINTR && errno != EWOULDBLOCK) { int errsv = errno; - ereport(ERROR, - (errmodule(MOD_UDF), - errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("poll() failed in UDFMasterServerLoop, errno:%d, error reason:%s", - errsv, - strerror(errsv)))); + ereport(ERROR, (errmodule(MOD_UDF), errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("poll() failed in UDFMasterServerLoop, errno:%d, error reason:%s", errsv, + strerror(errsv)))); return; } } @@ -325,24 +333,19 @@ void UDFMasterServerLoop() */ if (selres > 0) { if (ufds[0].fd == PGINVALID_SOCKET) { - ereport(WARNING, - (errmodule(MOD_UDF), - errcode(ERRCODE_CONNECTION_EXCEPTION), - errmsg("poll() returned %d, but the first file descriptor is invalid.", selres))); + ereport(WARNING, (errmodule(MOD_UDF), errcode(ERRCODE_CONNECTION_EXCEPTION), + errmsg("poll() returned %d, but the first file descriptor is invalid.", selres))); break; } if (ufds[0].fd != listenUDFSocket) { ereport(WARNING, - (errmodule(MOD_UDF), - errcode(ERRCODE_CONNECTION_EXCEPTION), - errmsg("poll() returned %d, but the first file descriptor is not listenUDFSocket.", selres))); + (errmodule(MOD_UDF), errcode(ERRCODE_CONNECTION_EXCEPTION), + errmsg("poll() returned %d, but the first file descriptor is not listenUDFSocket.", selres))); break; } if (((unsigned int)ufds[0].revents) & POLLERR) { - ereport(WARNING, - (errmodule(MOD_UDF), - errcode(ERRCODE_CONNECTION_EXCEPTION), - errmsg("poll() returned %d, but ufds[0].revents returned with POLLERR", selres))); + ereport(WARNING, (errmodule(MOD_UDF), errcode(ERRCODE_CONNECTION_EXCEPTION), + errmsg("poll() returned %d, but ufds[0].revents returned with POLLERR", selres))); } if (((unsigned int)ufds[0].revents) & POLLIN) { @@ -352,28 +355,21 @@ void UDFMasterServerLoop() if (socket < 0) { int errsv = errno; - ereport(WARNING, - (errmodule(MOD_UDF), - errcode(ERRCODE_CONNECTION_EXCEPTION), - errmsg("accept failed, errno:%d, error reason:%s", errsv, strerror(errsv)))); + ereport(WARNING, (errmodule(MOD_UDF), errcode(ERRCODE_CONNECTION_EXCEPTION), + errmsg("accept failed, errno:%d, error reason:%s", errsv, strerror(errsv)))); } else { int res; if (StartUDFWorker(socket) < 0) { - ereport(WARNING, - (errmodule(MOD_UDF), - errcode(ERRCODE_INVALID_STATUS), - errmsg("failed to fork fencedWorker porcess"))); + ereport(WARNING, (errmodule(MOD_UDF), errcode(ERRCODE_INVALID_STATUS), + errmsg("failed to fork fencedWorker porcess"))); } /* we must close socket because worker process have inherit it */ res = close(socket); if (res != 0) { int errsv = errno; - ereport(WARNING, - (errmodule(MOD_UDF), - errcode(ERRCODE_CONNECTION_EXCEPTION), - errmsg("UDF master close client socket failed, errno:%d, error reason:%s", - errsv, - strerror(errsv)))); + ereport(WARNING, (errmodule(MOD_UDF), errcode(ERRCODE_CONNECTION_EXCEPTION), + errmsg("UDF master close client socket failed, errno:%d, error reason:%s", + errsv, strerror(errsv)))); } } } @@ -392,16 +388,13 @@ pid_t StartUDFWorker(int socket) switch ((fencedWorkPid = fork_process())) { case -1: { int errsv = errno; - ereport(WARNING, - (errmodule(MOD_UDF), - errcode(ERRCODE_INVALID_STATUS), - errmsg("could not fork fenced UDF master process, errno:%d, error reason:%s", - errsv, - strerror(errsv)))); + ereport(WARNING, (errmodule(MOD_UDF), errcode(ERRCODE_INVALID_STATUS), + errmsg("could not fork fenced UDF master process, errno:%d, error reason:%s", errsv, + strerror(errsv)))); break; } case 0: { - /* Child process */ + /* Child process */ /* Just make set_ps_dispaly ok */ IsUnderPostmaster = true; @@ -410,10 +403,8 @@ pid_t StartUDFWorker(int socket) close(listenUDFSocket); listenUDFSocket = -1; } else { - ereport(WARNING, - (errmodule(MOD_UDF), - errcode(ERRCODE_INVALID_STATUS), - errmsg("Unexpected error occurs, listenUDFSocket <= -1"))); + ereport(WARNING, (errmodule(MOD_UDF), errcode(ERRCODE_INVALID_STATUS), + errmsg("Unexpected error occurs, listenUDFSocket <= -1"))); } set_ps_display("gaussdb fenced UDF worker process", true); UDFWorkerMain(socket); @@ -429,33 +420,76 @@ pid_t StartUDFWorker(int socket) return fencedWorkPid; } -static inline void SendMsg(int socket, char* val, int len) +/* + * 功能:向指定的套接字发送消息 + * + * 参数列表: + * socket:目标套接字的文件描述符 + * val:要发送的消息的起始地址 + * len:要发送的消息的长度 + * + * 注意: + * 此函数用于向指定的套接字发送消息,确保消息的长度大于0。 + */ +static inline void SendMsg(int socket, char *val, int len) { + // 确保消息的长度大于0,否则报错 AssertEreport(len > 0, MOD_UDF, "Msg's length should be greater than 0 when it's being sent."); - char* startPtr = val; - char* endPtr = val + len; + + // 初始化指针,指向消息的起始地址和结束地址 + char *startPtr = val; + char *endPtr = val + len; + + // 循环发送消息,直到所有消息都被发送完毕 while (startPtr < endPtr) { + // 使用 write() 函数向套接字发送消息 int nbytes = write(socket, startPtr, endPtr - startPtr); + + // 处理发送错误 if (nbytes < 0) { if (errno == EINTR) - continue; /* Ok if interrupted */ - ereport(ERROR, - (errmodule(MOD_UDF), errcode(ERRCODE_CONNECTION_FAILURE), errmsg("Socket send %d bytes: %m", nbytes))); + continue; /* 如果发送被中断,继续发送 */ + // 发送失败,报错 + ereport(ERROR, (errmodule(MOD_UDF), errcode(ERRCODE_CONNECTION_FAILURE), + errmsg("Socket send %d bytes: %m", nbytes))); } + // 更新指针,指向尚未发送的消息部分 startPtr += nbytes; } } -static inline void RecvMsg(int socket, char* buffer, int len, bool isIdle = false) +/* + * 功能:从指定的套接字接收消息 + * + * 参数列表: + * socket:目标套接字的文件描述符 + * buffer:用于存储接收消息的缓冲区 + * len:期望接收的消息长度 + * isIdle:标志,指示是否处于空闲状态 + * + * 注意: + * 此函数用于从指定的套接字接收消息,确保接收的字节数等于期望的消息长度。 + * 如果接收过程中出现错误,将生成相应的错误报告并根据情况退出。 + */ +static inline void RecvMsg(int socket, char *buffer, int len, bool isIdle = false) { + // 初始化接收字节数 int recvBytes = 0; + + // 初始化用于提示消息的字符串 StringInfoData hintMsgStr; + + // 循环接收消息,直到接收到期望的字节数 while (recvBytes < len) { + // 使用 read() 函数从套接字接收消息 int bytes = read(socket, buffer + recvBytes, len - recvBytes); + + // 处理接收错误 if (bytes <= 0) { if (errno == EINTR) continue; /* Ok if interrupted */ - char* err_reason = strerror(errno); + // 获取错误原因字符串 + char *err_reason = strerror(errno); /* The udf worker process is finished. */ if (!IS_PGXC_COORDINATOR && !IS_PGXC_DATANODE && isIdle) { @@ -466,8 +500,8 @@ static inline void RecvMsg(int socket, char* buffer, int len, bool isIdle = fals * function is called from UDF master/worker, myName should be * "UDF Master/Worker". */ - char* myName = "UDF Master/Worker"; - char* peerName = "GaussDB Kernel(CN/DN)"; + char *myName = "UDF Master/Worker"; + char *peerName = "GaussDB Kernel(CN/DN)"; if (IS_PGXC_COORDINATOR || IS_PGXC_DATANODE) { #ifdef PGXC myName = g_instance.attr.attr_common.PGXCNodeName; @@ -477,20 +511,18 @@ static inline void RecvMsg(int socket, char* buffer, int len, bool isIdle = fals peerName = "UDF Master/Worker"; } + // 初始化错误消息字符串 StringInfoData errMsgStr; initStringInfo(&errMsgStr); appendStringInfo(&errMsgStr, - "The connection between '%s' and '%s' maybe closed. '%s' recv %d bytes. Error: %s", - myName, - peerName, - myName, - bytes, - err_reason); + "The connection between '%s' and '%s' maybe closed. '%s' recv %d bytes. Error: %s", myName, + peerName, myName, bytes, err_reason); if (IS_PGXC_COORDINATOR || IS_PGXC_DATANODE) { initStringInfo(&hintMsgStr); if (u_sess->attr.attr_sql.FencedUDFMemoryLimit) { - appendStringInfo(&hintMsgStr, + appendStringInfo( + &hintMsgStr, "FencedUDFMemoryLimit is set to %dKB. Please check if it is too small, or you can just set " "FencedUDFMemoryLimit to 0KB, which will not limit visual memory usage.", u_sess->attr.attr_sql.FencedUDFMemoryLimit); @@ -498,7 +530,8 @@ static inline void RecvMsg(int socket, char* buffer, int len, bool isIdle = fals if (strlen(u_sess->attr.attr_sql.pljava_vmoptions) > 0) { if (strlen(hintMsgStr.data) > 0) appendStringInfo(&hintMsgStr, "\n"); - appendStringInfo(&hintMsgStr, + appendStringInfo( + &hintMsgStr, "pljava_vmoptions is set to '%s', please check every parameter in it, e.g., if -Xmx or " "-XX:MaxMetaspaceSize are set, please check if they are too large compared to " "FencedUDFMemoryLimit", @@ -511,57 +544,75 @@ static inline void RecvMsg(int socket, char* buffer, int len, bool isIdle = fals pfree_ext(hintMsgStr.data); } + // 生成错误报告并退出 ereport(ERROR, (errmodule(MOD_UDF), errcode(ERRCODE_DATA_EXCEPTION), errmsg("%s", errMsgStr.data))); - pfree_ext(errMsgStr.data); - exit(0); } + // 更新接收字节数 recvBytes += bytes; } + + // 确保接收的字节数等于期望的字节数 AssertEreport(recvBytes == len, MOD_UDF, "Bytes received is not the same as expected."); } -static char* GetBasicUDFInformation(FunctionCallInfoData* fcinfo) +/* + * 功能:从函数调用信息中获取基本的UDF信息 + * + * 参数列表: + * fcinfo:函数调用信息的指针 + * + * 注意: + * 此函数从函数调用信息中提取了基本的UDF信息,包括函数OID、内存限制、PL/Java虚拟机选项、语言OID、 + * 函数的volatile属性、函数名、库路径、参数数量、参数类型和结果类型。 + * 获取的信息用于UDF执行的初始化。 + */ +static char *GetBasicUDFInformation(FunctionCallInfoData *fcinfo) { + // 初始化长度、函数OID、语言OID和函数的volatile属性 uint len = 0; Oid fnOid = 0; Oid lagOid = 0; char fnVolatile; - FmgrInfo* flinfo = fcinfo->flinfo; - char* readPtr = fcinfo->udfInfo.udfMsgBuf->data; + + // 获取函数信息管理器 + FmgrInfo *flinfo = fcinfo->flinfo; + + // 初始化读取指针、剩余长度 + char *readPtr = fcinfo->udfInfo.udfMsgBuf->data; uint remainLen = fcinfo->udfInfo.udfMsgBuf->len; /* Get Message type */ short msgType; - GetFixedMsgValSafe(readPtr, (char*)&msgType, sizeof(msgType), sizeof(msgType), remainLen); + GetFixedMsgValSafe(readPtr, (char *)&msgType, sizeof(msgType), sizeof(msgType), remainLen); if (msgType == UDF_ARGS) { /* Get Function Oid */ - GetFixedMsgValSafe(readPtr, (char*)&fnOid, sizeof(fnOid), sizeof(fnOid), remainLen); + GetFixedMsgValSafe(readPtr, (char *)&fnOid, sizeof(fnOid), sizeof(fnOid), remainLen); flinfo->fn_oid = fnOid; } else { /* Get memory limit size */ - GetFixedMsgValSafe(readPtr, (char*)&u_sess->attr.attr_sql.FencedUDFMemoryLimit, - sizeof(u_sess->attr.attr_sql.FencedUDFMemoryLimit), - sizeof(u_sess->attr.attr_sql.FencedUDFMemoryLimit), remainLen); + GetFixedMsgValSafe(readPtr, (char *)&u_sess->attr.attr_sql.FencedUDFMemoryLimit, + sizeof(u_sess->attr.attr_sql.FencedUDFMemoryLimit), + sizeof(u_sess->attr.attr_sql.FencedUDFMemoryLimit), remainLen); /* Get pljava_vmoptions */ - GetFixedMsgValSafe(readPtr, (char*)&len, sizeof(len), sizeof(len), remainLen); - u_sess->attr.attr_sql.pljava_vmoptions = (char*)palloc(len + 1); + GetFixedMsgValSafe(readPtr, (char *)&len, sizeof(len), sizeof(len), remainLen); + u_sess->attr.attr_sql.pljava_vmoptions = (char *)palloc(len + 1); GetFixedMsgValSafe(readPtr, u_sess->attr.attr_sql.pljava_vmoptions, len, len + 1, remainLen); u_sess->attr.attr_sql.pljava_vmoptions[len] = 0; /* Get Function Oid */ - GetFixedMsgValSafe(readPtr, (char*)&fnOid, sizeof(fnOid), sizeof(fnOid), remainLen); + GetFixedMsgValSafe(readPtr, (char *)&fnOid, sizeof(fnOid), sizeof(fnOid), remainLen); flinfo->fn_oid = fnOid; /* Get Language Oid */ - GetFixedMsgValSafe(readPtr, (char*)&lagOid, sizeof(lagOid), sizeof(lagOid), remainLen); + GetFixedMsgValSafe(readPtr, (char *)&lagOid, sizeof(lagOid), sizeof(lagOid), remainLen); flinfo->fn_languageId = lagOid; /* Get func volatile propertie */ - GetFixedMsgValSafe(readPtr, (char*)&fnVolatile, sizeof(fnVolatile), sizeof(fnVolatile), remainLen); + GetFixedMsgValSafe(readPtr, (char *)&fnVolatile, sizeof(fnVolatile), sizeof(fnVolatile), remainLen); flinfo->fn_volatile = fnVolatile; /* Get UDF name */ @@ -569,37 +620,46 @@ static char* GetBasicUDFInformation(FunctionCallInfoData* fcinfo) flinfo->fnName[len] = 0; /* Get Library path */ - GetFixedMsgValSafe(readPtr, (char*)&len, sizeof(len), sizeof(len), remainLen); - flinfo->fnLibPath = (char*)palloc(len + 1); + GetFixedMsgValSafe(readPtr, (char *)&len, sizeof(len), sizeof(len), remainLen); + flinfo->fnLibPath = (char *)palloc(len + 1); GetFixedMsgValSafe(readPtr, flinfo->fnLibPath, len, len + 1, remainLen); flinfo->fnLibPath[len] = 0; /* Get Argument number */ - GetFixedMsgValSafe(readPtr, (char*)&flinfo->fn_nargs, sizeof(flinfo->fn_nargs), sizeof(flinfo->fn_nargs), - remainLen); + GetFixedMsgValSafe(readPtr, (char *)&flinfo->fn_nargs, sizeof(flinfo->fn_nargs), sizeof(flinfo->fn_nargs), + remainLen); /* Macro is OK */ InitFunctionCallInfoArgs(*fcinfo, flinfo->fn_nargs, BatchMaxSize); if (flinfo->fn_nargs > 0) { /* Get Argument type */ - GetFixedMsgValSafe(readPtr, (char*)fcinfo->argTypes, sizeof(Oid) * flinfo->fn_nargs, - sizeof(Oid) * flinfo->fn_nargs, remainLen); + GetFixedMsgValSafe(readPtr, (char *)fcinfo->argTypes, sizeof(Oid) * flinfo->fn_nargs, + sizeof(Oid) * flinfo->fn_nargs, remainLen); } /* Get Result Type */ - GetFixedMsgValSafe(readPtr, (char*)&flinfo->fn_rettype, sizeof(flinfo->fn_rettype), sizeof(flinfo->fn_rettype), - remainLen); + GetFixedMsgValSafe(readPtr, (char *)&flinfo->fn_rettype, sizeof(flinfo->fn_rettype), sizeof(flinfo->fn_rettype), + remainLen); } return readPtr; } -static void GetUDFArguments(FunctionCallInfoData* fcinfo) +/* + * 功能:从函数调用信息中获取UDF参数 + * + * 参数列表: + * fcinfo:函数调用信息的指针 + * + * 注意: + * 此函数从函数调用信息中提取了UDF参数,包括参数的批次行数、参数值和空值信息。 + * 参数信息将用于UDF的执行。 + */ +static void GetUDFArguments(FunctionCallInfoData *fcinfo) { - GetFixedMsgVal(fcinfo->udfInfo.msgReadPtr, - (char*)&fcinfo->udfInfo.argBatchRows, - sizeof(fcinfo->udfInfo.argBatchRows), - sizeof(fcinfo->udfInfo.argBatchRows)); + // 从消息中获取参数的批次行数 + GetFixedMsgVal(fcinfo->udfInfo.msgReadPtr, (char *)&fcinfo->udfInfo.argBatchRows, + sizeof(fcinfo->udfInfo.argBatchRows), sizeof(fcinfo->udfInfo.argBatchRows)); /* Argument Values Handler */ for (int row = 0; row < fcinfo->udfInfo.argBatchRows; row++) { @@ -610,17 +670,30 @@ static void GetUDFArguments(FunctionCallInfoData* fcinfo) } } } -static void RecvUDFInformation(int socket, FunctionCallInfoData* fcinfo) + +/* + * 功能:从套接字接收UDF信息并填充函数调用信息结构 + * + * 参数列表: + * socket:套接字文件描述符,用于接收UDF信息 + * fcinfo:函数调用信息的指针,将在函数内部填充 + * + * 注意: + * 此函数负责从套接字接收UDF信息,并将其用于填充函数调用信息结构(FunctionCallInfoData)。 + * 这包括接收消息头和消息体,获取基本UDF信息(OID、名称、库路径等)以及获取UDF参数。 + * 最后,它查找或插入UDF哈希表。 + */ +static void RecvUDFInformation(int socket, FunctionCallInfoData *fcinfo) { - uint len = 0; - char* allocPtr = NULL; + uint len = 0; + char *allocPtr = NULL; char buffer[UDF_MSG_BUFLEN]; - char* readPtr = buffer; + char *readPtr = buffer; /* * Step 1: Initialize function call structure and recv message header and body */ - FmgrInfo* flinfo = (FmgrInfo*)palloc0(sizeof(FmgrInfo)); + FmgrInfo *flinfo = (FmgrInfo *)palloc0(sizeof(FmgrInfo)); fcinfo->flinfo = flinfo; flinfo->fn_fenced = true; @@ -628,14 +701,14 @@ static void RecvUDFInformation(int socket, FunctionCallInfoData* fcinfo) /* Get message length */ RecvMsg(socket, buffer, 4, true); - len = *(uint*)buffer; + len = *(uint *)buffer; if (unlikely(len >= MaxAllocSize)) { ereport(ERROR, (errmodule(MOD_UDF), errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("Invalid udf message len:%u", len))); + errmsg("Invalid udf message len:%u", len))); } readPtr = buffer; if (unlikely(len >= UDF_MSG_BUFLEN)) { - allocPtr = readPtr = (char*)palloc(len); + allocPtr = readPtr = (char *)palloc(len); } /* Get message body */ RecvMsg(socket, readPtr, len); @@ -664,34 +737,68 @@ static void RecvUDFInformation(int socket, FunctionCallInfoData* fcinfo) return; } +/* + * 功能:运行用户定义函数(UDF)并返回结果 + * + * 参数列表: + * fcinfo:函数调用信息的指针,包含UDF相关信息 + * + * 返回值: + * 返回UDF的结果(Datum类型) + * + * 注意: + * 此函数用于执行用户定义函数(UDF),它将函数调用信息结构中的isnull标志设置为false, + * 然后通过调用FunctionCallInvoke来执行UDF,并返回结果。 + */ template -static inline Datum RunUDF(FunctionCallInfoData* fcinfo) +static inline Datum RunUDF(FunctionCallInfoData *fcinfo) { - fcinfo->isnull = false; + fcinfo->isnull = false; // 设置isnull标志为false Datum result = 0; - result = FunctionCallInvoke(fcinfo); - return result; + result = FunctionCallInvoke(fcinfo); // 调用FunctionCallInvoke执行UDF + return result; // 返回UDF的结果 } +/* + * 功能:设置用户定义函数(UDF)的内存限制 + * + * 注意: + * 此函数用于根据配置设置UDF的内存限制。如果u_sess->attr.attr_sql.FencedUDFMemoryLimit大于0, + * 则将RLIMIT_AS(地址空间限制)设置为指定值,以限制UDF的内存使用。 + * 如果设置失败,将发出警告消息。 + */ void SetUDFMemoryLimit() { - struct rlimit memory_limit; + struct rlimit memory_limit; // 用于存储内存限制的结构体 if (u_sess->attr.attr_sql.FencedUDFMemoryLimit > 0) { - /* baseVirtualMemSize is 200MB */ - const int64 baseVirtualMemSize = 200 * KB * KB; + const int64 baseVirtualMemSize = 200 * KB * KB; // 基础虚拟内存大小为200MB + + // 设置内存限制的软限制和硬限制为指定值 memory_limit.rlim_cur = memory_limit.rlim_max = (int64)u_sess->attr.attr_sql.FencedUDFMemoryLimit * KB + baseVirtualMemSize; + + // 使用setrlimit函数设置RLIMIT_AS限制 if (setrlimit(RLIMIT_AS, &memory_limit)) - ereport(WARNING, (errmodule(MOD_UDF), errmsg("setrlimit error: %m"))); + ereport(WARNING, (errmodule(MOD_UDF), errmsg("setrlimit error: %m"))); // 设置失败时发出警告 } } +/* + * 功能:切换当前会话的数据库上下文 + * + * 参数列表: + * currentDatabaseId:要切换到的数据库的唯一标识符 + * + * 注意: + * 此函数用于切换当前会话的数据库上下文。如果传入的currentDatabaseId不为0且与u_sess->proc_cxt.MyDatabaseId不相等, + * 则更新u_sess->proc_cxt.MyDatabaseId,并重新初始化Fenced系统缓存(InitFencedSysCache)。 + */ void changeDatabase(unsigned int currentDatabaseId) { - if (currentDatabaseId!= 0 && currentDatabaseId != u_sess->proc_cxt.MyDatabaseId){ - u_sess->proc_cxt.MyDatabaseId = currentDatabaseId; - t_thrd.proc_cxt.PostInit->InitFencedSysCache(); + if (currentDatabaseId != 0 && currentDatabaseId != u_sess->proc_cxt.MyDatabaseId) { + u_sess->proc_cxt.MyDatabaseId = currentDatabaseId; // 更新当前会话的数据库标识符 + t_thrd.proc_cxt.PostInit->InitFencedSysCache(); // 重新初始化Fenced系统缓存 } } @@ -699,14 +806,22 @@ void changeDatabase(unsigned int currentDatabaseId) * @Description: The main function which run UDF * @IN socket: argument values is socket communication with RPC client */ +/* + * 功能:UDF工作进程的主要函数,用于执行用户定义函数(UDF) + * + * 参数列表: + * socket:UDF工作进程与客户端之间的通信套接字 + * + * 注意: + * 此函数是UDF工作进程的主要入口点,用于接收UDF信息、设置内存限制、运行UDF并返回结果。 + * 在循环中,不断地接收客户端发送的UDF信息,执行UDF函数,然后将结果发送回客户端。 + * 如果在执行UDF过程中遇到错误,将捕获错误信息并发送回客户端。 + */ static void UDFWorkerMain(int socket) { - bool hasSetMemLimit = false; - UDFWorkMemContext = AllocSetContextCreate(t_thrd.top_mem_cxt, - "UDF_Work_MemContext", - ALLOCSET_DEFAULT_MINSIZE, - ALLOCSET_DEFAULT_INITSIZE, - ALLOCSET_DEFAULT_MAXSIZE); + bool hasSetMemLimit = false; // 标志是否已设置内存限制 + UDFWorkMemContext = AllocSetContextCreate(t_thrd.top_mem_cxt, "UDF_Work_MemContext", ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); if (!UDFWorkMemContext) ereport(FATAL, (errmodule(MOD_UDF), errmsg("create UDFWorkMemContext failed"))); @@ -733,7 +848,7 @@ static void UDFWorkerMain(int socket) resetStringInfo(fcinfo.udfInfo.udfMsgBuf); Reserve4BytesMsgHeader(fcinfo.udfInfo.udfMsgBuf); short msgType = (short)UDF_RESULT; - AppendBufFixedMsgVal(fcinfo.udfInfo.udfMsgBuf, (char*)&msgType, sizeof(msgType)); + AppendBufFixedMsgVal(fcinfo.udfInfo.udfMsgBuf, (char *)&msgType, sizeof(msgType)); for (int row = 0; row < fcinfo.udfInfo.argBatchRows; ++row) { for (int idx = 0; idx < fcinfo.nargs; idx++) { @@ -749,7 +864,7 @@ static void UDFWorkerMain(int socket) result = RunUDF(&fcinfo); break; default: - if(fcinfo.flinfo->fnLibPath != NULL) { + if (fcinfo.flinfo->fnLibPath != NULL) { changeDatabase(atoi(fcinfo.flinfo->fnLibPath)); } result = RunUDF(&fcinfo); @@ -767,14 +882,14 @@ static void UDFWorkerMain(int socket) resetStringInfo(fcinfo.udfInfo.udfMsgBuf); Reserve4BytesMsgHeader(fcinfo.udfInfo.udfMsgBuf); short msgType = (short)UDF_ERROR; - AppendBufFixedMsgVal(fcinfo.udfInfo.udfMsgBuf, (char*)&msgType, sizeof(msgType)); + AppendBufFixedMsgVal(fcinfo.udfInfo.udfMsgBuf, (char *)&msgType, sizeof(msgType)); MemoryContext ecxt = MemoryContextSwitchTo(current_context); - ErrorData* edata = CopyErrorData(); + ErrorData *edata = CopyErrorData(); int len = strlen(edata->message); - AppendBufVarMsgVal(fcinfo.udfInfo.udfMsgBuf, (char*)edata->message, len); + AppendBufVarMsgVal(fcinfo.udfInfo.udfMsgBuf, (char *)edata->message, len); Fill4BytesMsgHeader(fcinfo.udfInfo.udfMsgBuf); SendMsg(socket, fcinfo.udfInfo.udfMsgBuf->data, fcinfo.udfInfo.udfMsgBuf->len); @@ -802,16 +917,33 @@ static void UDFWorkerMain(int socket) * 1 byte | fixed bytes values | * ------------------------------------------------------------------- */ +/* + * 功能:用于处理不同数据类型的函数参数和结果值 + * + * 参数列表: + * fcinfo:FunctionCallInfoData结构体指针,包含了函数调用的信息 + * idx:参数索引,指示要处理的参数的位置 + * val:传入的参数值 + * + * 注意: + * 该函数根据参数的数据类型和处理类型执行不同的操作,包括参数的序列化和反序列化, + * 以及设置函数调用信息中的参数值和结果值。函数通过消息缓冲区与外部通信,用于 + * 用户定义函数(UDF)的参数传递和结果返回。 + */ template -Datum UDFArgumentHandler(FunctionCallInfoData* fcinfo, int idx, Datum val) +Datum UDFArgumentHandler(FunctionCallInfoData *fcinfo, int idx, Datum val) { + // 获取与当前函数调用相关的消息缓冲区和其他初始化信息 StringInfo udfMsgBuf = fcinfo->udfInfo.udfMsgBuf; + // 初始化结果为传入的值 Datum result = val; + // 初始化变量用于存储值的长度、是否为NULL、以及消息读取指针 int valLen = 0; char isNull = 0; - char* readPtr = fcinfo->udfInfo.msgReadPtr; - + char *readPtr = fcinfo->udfInfo.msgReadPtr; + // 根据数据类型进行不同的处理 switch (type) { + // 处理固定长度的数据类型 case BOOLOID: case INT8OID: case INT2OID: @@ -839,46 +971,49 @@ Datum UDFArgumentHandler(FunctionCallInfoData* fcinfo, int idx, Datum val) /* typlen: 4 */ case RELTIMEOID: { if (handlerType == UDF_SEND_ARGS) { + // 如果是发送参数,获取参数值和是否为NULL,然后将它们附加到消息缓冲区中 Datum v = fcinfo->arg[idx]; isNull = fcinfo->argnull[idx] ? 1 : 0; - AppendBufFixedMsgVal(udfMsgBuf, (char*)&isNull, sizeof(isNull)); + AppendBufFixedMsgVal(udfMsgBuf, (char *)&isNull, sizeof(isNull)); if (!isNull) { - AppendBufFixedMsgVal(udfMsgBuf, (char*)&v, sizeof(v)); + AppendBufFixedMsgVal(udfMsgBuf, (char *)&v, sizeof(v)); } } else if (handlerType == UDF_RECV_ARGS) { /* is NULL ? */ - GetFixedMsgVal(readPtr, (char*)&isNull, sizeof(isNull), sizeof(isNull)); + // 如果是接收参数,检查是否为NULL,然后接收参数值并设置对应的函数调用信息 + GetFixedMsgVal(readPtr, (char *)&isNull, sizeof(isNull), sizeof(isNull)); if (!isNull) { /* Keep Always Send Datum, So Recv Datum */ Datum value = 0; - GetFixedMsgVal(readPtr, (char*)&value, sizeof(value), sizeof(value)); + GetFixedMsgVal(readPtr, (char *)&value, sizeof(value), sizeof(value)); fcinfo->arg[idx] = value; fcinfo->argnull[idx] = false; } else { fcinfo->argnull[idx] = true; } } else if (handlerType == UDF_SEND_RESULT) { + // 如果是发送结果,处理结果值的NULL标志和值本身,并附加到消息缓冲区中 valLen = 0; isNull = fcinfo->isnull ? 1 : 0; - AppendBufFixedMsgVal(udfMsgBuf, (char*)&isNull, sizeof(isNull)); + AppendBufFixedMsgVal(udfMsgBuf, (char *)&isNull, sizeof(isNull)); if (isNull == 0) { - AppendBufFixedMsgVal(udfMsgBuf, (char*)&result, sizeof(result)); + AppendBufFixedMsgVal(udfMsgBuf, (char *)&result, sizeof(result)); } } else { Assert(handlerType == UDF_RECV_RESULT); if (handlerType != UDF_RECV_RESULT) { - ereport(ERROR, - (errmodule(MOD_UDF), - errcode(ERRORCODE_ASSERT_FAILED), - errmsg("Otherwise, handlerType can only be UDF_RECV_RESULT"))); + // 如果处理类型不是预期的接收结果类型,报告错误 + ereport(ERROR, (errmodule(MOD_UDF), errcode(ERRORCODE_ASSERT_FAILED), + errmsg("Otherwise, handlerType can only be UDF_RECV_RESULT"))); } /* Get null flag */ - GetFixedMsgVal(readPtr, (char*)&isNull, sizeof(isNull), sizeof(isNull)); + // 如果是接收结果,获取结果的NULL标志和值,并设置对应的函数调用信息 + GetFixedMsgVal(readPtr, (char *)&isNull, sizeof(isNull), sizeof(isNull)); /* Get result if not null */ if (isNull == 0) { - GetFixedMsgVal(readPtr, (char*)&result, sizeof(result), sizeof(result)); + GetFixedMsgVal(readPtr, (char *)&result, sizeof(result), sizeof(result)); fcinfo->isnull = false; } else { fcinfo->isnull = true; @@ -886,6 +1021,7 @@ Datum UDFArgumentHandler(FunctionCallInfoData* fcinfo, int idx, Datum val) } break; } + // 处理可变长度的数据类型 case BOOLARRAYOID: case INT2ARRAYOID: case INT4ARRAYOID: @@ -921,27 +1057,27 @@ Datum UDFArgumentHandler(FunctionCallInfoData* fcinfo, int idx, Datum val) case CIDROID: case VARBITOID: { if (handlerType == UDF_SEND_ARGS) { + // 如果是发送参数,获取参数值和是否为NULL,然后将它们附加到消息缓冲区中 Datum v = fcinfo->arg[idx]; isNull = fcinfo->argnull[idx] ? 1 : 0; - AppendBufFixedMsgVal(udfMsgBuf, (char*)&isNull, sizeof(isNull)); + AppendBufFixedMsgVal(udfMsgBuf, (char *)&isNull, sizeof(isNull)); if (!isNull) { - struct varlena* value = PG_DETOAST_DATUM(v); + struct varlena *value = PG_DETOAST_DATUM(v); valLen = VARSIZE_ANY(value); - AppendBufVarMsgVal(udfMsgBuf, (char*)value, valLen); + AppendBufVarMsgVal(udfMsgBuf, (char *)value, valLen); } } else if (handlerType == UDF_RECV_ARGS) { valLen = 0; - GetFixedMsgVal(readPtr, (char*)&isNull, sizeof(isNull), sizeof(isNull)); + GetFixedMsgVal(readPtr, (char *)&isNull, sizeof(isNull), sizeof(isNull)); if (!isNull) { - GetFixedMsgVal(readPtr, (char*)&valLen, sizeof(valLen), sizeof(valLen)); + GetFixedMsgVal(readPtr, (char *)&valLen, sizeof(valLen), sizeof(valLen)); if (valLen < 0) { - ereport(ERROR, - (errmodule(MOD_UDF), - errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("Variable length %d cannot be negative", valLen))); + // 长度不能为负数,报告错误 + ereport(ERROR, (errmodule(MOD_UDF), errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("Variable length %d cannot be negative", valLen))); } - char* buffer = (char*)palloc0(valLen + 1); + char *buffer = (char *)palloc0(valLen + 1); GetFixedMsgVal(readPtr, buffer, valLen, valLen + 1); /* memory should free */ @@ -952,38 +1088,34 @@ Datum UDFArgumentHandler(FunctionCallInfoData* fcinfo, int idx, Datum val) } } else if (handlerType == UDF_SEND_RESULT) { valLen = 0; - varlena* resultVar = NULL; + varlena *resultVar = NULL; isNull = fcinfo->isnull ? 1 : 0; - AppendBufFixedMsgVal(udfMsgBuf, (char*)&isNull, sizeof(isNull)); + AppendBufFixedMsgVal(udfMsgBuf, (char *)&isNull, sizeof(isNull)); if (!isNull) { resultVar = PG_DETOAST_DATUM(result); valLen = VARSIZE_ANY(resultVar); - AppendBufVarMsgVal(udfMsgBuf, (char*)resultVar, valLen); + AppendBufVarMsgVal(udfMsgBuf, (char *)resultVar, valLen); } } else { Assert(handlerType == UDF_RECV_RESULT); if (handlerType != UDF_RECV_RESULT) { - ereport(ERROR, - (errmodule(MOD_UDF), - errcode(ERRORCODE_ASSERT_FAILED), - errmsg("Otherwise, handlerType can only be UDF_RECV_RESULT"))); + ereport(ERROR, (errmodule(MOD_UDF), errcode(ERRORCODE_ASSERT_FAILED), + errmsg("Otherwise, handlerType can only be UDF_RECV_RESULT"))); } /* Get null falg */ - GetFixedMsgVal(readPtr, (char*)&isNull, sizeof(isNull), sizeof(isNull)); + GetFixedMsgVal(readPtr, (char *)&isNull, sizeof(isNull), sizeof(isNull)); /* Get result if not null */ if (isNull == 0) { valLen = 0; - GetFixedMsgVal(readPtr, (char*)&valLen, sizeof(valLen), sizeof(valLen)); + GetFixedMsgVal(readPtr, (char *)&valLen, sizeof(valLen), sizeof(valLen)); if (valLen < 0) { - ereport(ERROR, - (errmodule(MOD_UDF), - errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("Variable length %d cannot be negative", valLen))); + ereport(ERROR, (errmodule(MOD_UDF), errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("Variable length %d cannot be negative", valLen))); } - char* ptr = (char*)palloc0(valLen + 1); - GetFixedMsgVal(readPtr, (char*)ptr, valLen, valLen + 1); + char *ptr = (char *)palloc0(valLen + 1); + GetFixedMsgVal(readPtr, (char *)ptr, valLen, valLen + 1); result = PointerGetDatum(ptr); fcinfo->isnull = false; } else { @@ -1055,22 +1187,20 @@ Datum UDFArgumentHandler(FunctionCallInfoData* fcinfo, int idx, Datum val) if (handlerType == UDF_SEND_ARGS) { Datum v = fcinfo->arg[idx]; isNull = fcinfo->argnull[idx] ? 1 : 0; - AppendBufFixedMsgVal(udfMsgBuf, (char*)&isNull, sizeof(isNull)); + AppendBufFixedMsgVal(udfMsgBuf, (char *)&isNull, sizeof(isNull)); if (!isNull) { - char* value = DatumGetPointer(v); - AppendBufVarMsgVal(udfMsgBuf, (char*)value, valLen); + char *value = DatumGetPointer(v); + AppendBufVarMsgVal(udfMsgBuf, (char *)value, valLen); } } else if (handlerType == UDF_RECV_ARGS) { - GetFixedMsgVal(readPtr, (char*)&isNull, sizeof(isNull), sizeof(isNull)); + GetFixedMsgVal(readPtr, (char *)&isNull, sizeof(isNull), sizeof(isNull)); if (!isNull) { - GetFixedMsgVal(readPtr, (char*)&valLen, sizeof(valLen), sizeof(valLen)); + GetFixedMsgVal(readPtr, (char *)&valLen, sizeof(valLen), sizeof(valLen)); if (valLen < 0) { - ereport(ERROR, - (errmodule(MOD_UDF), - errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("Variable length %d cannot be negative", valLen))); + ereport(ERROR, (errmodule(MOD_UDF), errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("Variable length %d cannot be negative", valLen))); } - char* buffer = (char*)palloc0(valLen + 1); + char *buffer = (char *)palloc0(valLen + 1); GetFixedMsgVal(readPtr, buffer, valLen, valLen + 1); fcinfo->argnull[idx] = false; /* memory should free */ @@ -1079,32 +1209,30 @@ Datum UDFArgumentHandler(FunctionCallInfoData* fcinfo, int idx, Datum val) fcinfo->argnull[idx] = true; } } else if (handlerType == UDF_SEND_RESULT) { - char* resultVar = NULL; + char *resultVar = NULL; char isNull = fcinfo->isnull ? 1 : 0; /* Fill NULL flag 1 byte */ - AppendBufFixedMsgVal(udfMsgBuf, (char*)&isNull, sizeof(isNull)); + AppendBufFixedMsgVal(udfMsgBuf, (char *)&isNull, sizeof(isNull)); /* Fill fixed-length value if not null */ if (!isNull) { resultVar = DatumGetPointer(result); - AppendBufFixedMsgVal(udfMsgBuf, (char*)resultVar, valLen); + AppendBufFixedMsgVal(udfMsgBuf, (char *)resultVar, valLen); } } else { Assert(handlerType == UDF_RECV_RESULT); if (handlerType != UDF_RECV_RESULT) { - ereport(ERROR, - (errmodule(MOD_UDF), - errcode(ERRORCODE_ASSERT_FAILED), - errmsg("Otherwise, handlerType can only be UDF_RECV_RESULT"))); + ereport(ERROR, (errmodule(MOD_UDF), errcode(ERRORCODE_ASSERT_FAILED), + errmsg("Otherwise, handlerType can only be UDF_RECV_RESULT"))); } /* Get null flag */ - GetFixedMsgVal(readPtr, (char*)&isNull, sizeof(isNull), sizeof(isNull)); + GetFixedMsgVal(readPtr, (char *)&isNull, sizeof(isNull), sizeof(isNull)); /* Get result if not null */ if (isNull == 0) { - char* ptr = (char*)palloc0(valLen + 1); - GetFixedMsgVal(readPtr, (char*)ptr, valLen, valLen + 1); + char *ptr = (char *)palloc0(valLen + 1); + GetFixedMsgVal(readPtr, (char *)ptr, valLen, valLen + 1); result = PointerGetDatum(ptr); fcinfo->isnull = false; } else { @@ -1115,10 +1243,8 @@ Datum UDFArgumentHandler(FunctionCallInfoData* fcinfo, int idx, Datum val) } default: { HANDLE_UNSOPPORTED_VAL: - ereport(ERROR, - (errmodule(MOD_UDF), - errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("Recv Unsupported argument type: %u", type))); + ereport(ERROR, (errmodule(MOD_UDF), errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("Recv Unsupported argument type: %u", type))); break; } } @@ -1127,7 +1253,18 @@ Datum UDFArgumentHandler(FunctionCallInfoData* fcinfo, int idx, Datum val) fcinfo->udfInfo.msgReadPtr = readPtr; return result; } - +/* + * 功能:根据处理类型初始化UDF参数处理器函数指针 + * + * 参数列表: + * handlerType:UDFArgHandlerType类型,指示参数处理的类型 + * fcinfo:FunctionCallInfo结构体指针,包含了函数调用的信息 + * + * 注意: + * 该函数用于初始化UDF参数处理器函数指针,根据不同的数据类型和处理类型, + * 分配合适的函数指针以进行参数处理。函数指针将在后续的函数调用中用于 + * 处理不同数据类型的参数或结果值。 + */ template void InitUDFArgsHandler(FunctionCallInfo fcinfo) { @@ -1137,14 +1274,13 @@ void InitUDFArgsHandler(FunctionCallInfo fcinfo) for (int i = 0; i < n; ++i) { Oid type = 0; - UDFArgsFuncType* funcPPtr = NULL; + UDFArgsFuncType *funcPPtr = NULL; if (handlerType == UDF_SEND_RESULT || handlerType == UDF_RECV_RESULT) { type = fcinfo->flinfo->fn_rettype; funcPPtr = &fcinfo->udfInfo.UDFResultHandlerPtr; } else { - AssertEreport(handlerType == UDF_SEND_ARGS || handlerType == UDF_RECV_ARGS, - MOD_UDF, - "Otherwise, handlerType can only be UDF_SEND_ARGS or UDF_RECV_ARGS"); + AssertEreport(handlerType == UDF_SEND_ARGS || handlerType == UDF_RECV_ARGS, MOD_UDF, + "Otherwise, handlerType can only be UDF_SEND_ARGS or UDF_RECV_ARGS"); type = fcinfo->argTypes[i]; funcPPtr = fcinfo->udfInfo.UDFArgsHandlerPtr + i; @@ -1392,13 +1528,25 @@ void InitUDFArgsHandler(FunctionCallInfo fcinfo) } default: { ereport(ERROR, - (errmodule(MOD_UDF), errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("Unsupport type: %u", type))); + (errmodule(MOD_UDF), errcode(ERRCODE_DATATYPE_MISMATCH), errmsg("Unsupport type: %u", type))); break; } } } } - +/* + * 功能:填充基本的UDF信息 + * + * 参数列表: + * fcinfo:FunctionCallInfo结构体指针,包含了函数调用的信息 + * + * 注意: + * 该函数用于填充UDF消息缓冲区(udfMsgBuf)中的基本信息。根据函数是否 + * 是相同的UDF函数调用,函数将填充不同的信息。如果是相同的UDF函数调用, + * 则填充UDF_ARGS类型的信息,否则填充UDF_INFO类型的信息。函数会填充 + * UDF函数的各种属性,如函数OID、语言OID、函数名称、函数库路径、参数类型 + * 数组等。 + */ static void FillBasicUDFInformation(FunctionCallInfo fcinfo) { StringInfo udfMsgBuf = fcinfo->udfInfo.udfMsgBuf; @@ -1410,114 +1558,151 @@ static void FillBasicUDFInformation(FunctionCallInfo fcinfo) msgType = (short)UDF_ARGS; Oid oid = fcinfo->flinfo->fn_oid; - AppendBufFixedMsgVal(udfMsgBuf, (char*)&msgType, sizeof(msgType)); - AppendBufFixedMsgVal(udfMsgBuf, (char*)&oid, sizeof(oid)); + AppendBufFixedMsgVal(udfMsgBuf, (char *)&msgType, sizeof(msgType)); + AppendBufFixedMsgVal(udfMsgBuf, (char *)&oid, sizeof(oid)); } else { /* Message Type */ msgType = (short)UDF_INFO; - AppendBufFixedMsgVal(udfMsgBuf, (char*)&msgType, sizeof(msgType)); + AppendBufFixedMsgVal(udfMsgBuf, (char *)&msgType, sizeof(msgType)); /* memory limit of udf worker process */ - AppendBufFixedMsgVal(udfMsgBuf, - (char*)&u_sess->attr.attr_sql.FencedUDFMemoryLimit, - sizeof(u_sess->attr.attr_sql.FencedUDFMemoryLimit)); + AppendBufFixedMsgVal(udfMsgBuf, (char *)&u_sess->attr.attr_sql.FencedUDFMemoryLimit, + sizeof(u_sess->attr.attr_sql.FencedUDFMemoryLimit)); /* pljava_vmoptions */ len = strlen(u_sess->attr.attr_sql.pljava_vmoptions); - AppendBufVarMsgVal(udfMsgBuf, (char*)u_sess->attr.attr_sql.pljava_vmoptions, len); + AppendBufVarMsgVal(udfMsgBuf, (char *)u_sess->attr.attr_sql.pljava_vmoptions, len); /* UDF OID */ - AppendBufFixedMsgVal(udfMsgBuf, (char*)&fcinfo->flinfo->fn_oid, sizeof(Oid)); + AppendBufFixedMsgVal(udfMsgBuf, (char *)&fcinfo->flinfo->fn_oid, sizeof(Oid)); /* UDF language OID */ - AppendBufFixedMsgVal(udfMsgBuf, (char*)&fcinfo->flinfo->fn_languageId, sizeof(Oid)); + AppendBufFixedMsgVal(udfMsgBuf, (char *)&fcinfo->flinfo->fn_languageId, sizeof(Oid)); /* UDF volatile property */ - AppendBufFixedMsgVal(udfMsgBuf, (char*)&fcinfo->flinfo->fn_volatile, sizeof(char)); + AppendBufFixedMsgVal(udfMsgBuf, (char *)&fcinfo->flinfo->fn_volatile, sizeof(char)); /* UDF Name */ len = strlen(fcinfo->flinfo->fnName); - AppendBufVarMsgVal(udfMsgBuf, (char*)fcinfo->flinfo->fnName, len); + AppendBufVarMsgVal(udfMsgBuf, (char *)fcinfo->flinfo->fnName, len); /* UDF Library */ len = strlen(fcinfo->flinfo->fnLibPath); - AppendBufVarMsgVal(udfMsgBuf, (char*)fcinfo->flinfo->fnLibPath, len); + AppendBufVarMsgVal(udfMsgBuf, (char *)fcinfo->flinfo->fnLibPath, len); /* UDF Number of Argument */ - AppendBufFixedMsgVal(udfMsgBuf, (char*)&fcinfo->nargs, sizeof(fcinfo->nargs)); + AppendBufFixedMsgVal(udfMsgBuf, (char *)&fcinfo->nargs, sizeof(fcinfo->nargs)); if (fcinfo->nargs > 0) { /* the type of arguments */ - Oid* argTypes = fcinfo->argTypes; - AppendBufFixedMsgVal(udfMsgBuf, (char*)argTypes, sizeof(Oid) * fcinfo->nargs); + Oid *argTypes = fcinfo->argTypes; + AppendBufFixedMsgVal(udfMsgBuf, (char *)argTypes, sizeof(Oid) * fcinfo->nargs); } /* The type of result */ resultType = fcinfo->flinfo->fn_rettype; - AppendBufFixedMsgVal(udfMsgBuf, (char*)&resultType, sizeof(resultType)); + AppendBufFixedMsgVal(udfMsgBuf, (char *)&resultType, sizeof(resultType)); } } +/* + * 功能:填充UDF函数的参数信息 + * + * 参数列表: + * fcinfo:FunctionCallInfo结构体指针,包含了函数调用的信息 + * + * 注意: + * 该函数用于填充UDF消息缓冲区(udfMsgBuf)中的参数信息。根据是否处于 + * 批处理模式(batchMode),函数会填充不同数量的参数行。函数还会检查是否 + * 需要初始化UDF参数处理器(UDFArgsHandler),如果需要,则进行初始化。 + * 然后,函数会遍历每一行的参数,并根据参数处理器处理参数值。 + */ template static void FillUDFArguments(FunctionCallInfo fcinfo) { - StringInfo udfMsgBuf = fcinfo->udfInfo.udfMsgBuf; - int batchRows = batchMode ? fcinfo->udfInfo.argBatchRows : 1; - AppendBufFixedMsgVal(udfMsgBuf, (char*)&batchRows, sizeof(batchRows)); + StringInfo udfMsgBuf = fcinfo->udfInfo.udfMsgBuf; // 获取UDF消息缓冲区 + int batchRows = batchMode ? fcinfo->udfInfo.argBatchRows : 1; // 批处理模式下的行数,默认为1 + AppendBufFixedMsgVal(udfMsgBuf, (char *)&batchRows, sizeof(batchRows)); // 填充行数信息到消息缓冲区 if (unlikely(!fcinfo->udfInfo.valid_UDFArgsHandlerPtr)) { - InitUDFArgsHandler(fcinfo); - InitUDFArgsHandler(fcinfo); - fcinfo->udfInfo.valid_UDFArgsHandlerPtr = true; + InitUDFArgsHandler(fcinfo); // 初始化UDF参数处理器(发送参数) + InitUDFArgsHandler(fcinfo); // 初始化UDF参数处理器(接收结果) + fcinfo->udfInfo.valid_UDFArgsHandlerPtr = true; // 标记参数处理器已初始化 } /* Fill UDF Argument Values */ for (int row = 0; row < batchRows; row++) { for (int idx = 0; idx < fcinfo->nargs; idx++) { if (batchMode) { - fcinfo->arg[idx] = fcinfo->udfInfo.arg[row][idx]; - fcinfo->argnull[idx] = fcinfo->udfInfo.null[row][idx]; + fcinfo->arg[idx] = fcinfo->udfInfo.arg[row][idx]; // 从批处理中获取参数值 + fcinfo->argnull[idx] = fcinfo->udfInfo.null[row][idx]; // 获取参数是否为空的标志 } - (*(fcinfo->udfInfo.UDFArgsHandlerPtr + idx))(fcinfo, idx, 0); + (*(fcinfo->udfInfo.UDFArgsHandlerPtr + idx))(fcinfo, idx, 0); // 调用参数处理器处理参数值 } } } +/* + * 功能:发送UDF函数信息 + * + * 参数列表: + * fcinfo:FunctionCallInfo结构体指针,包含了函数调用的信息 + * + * 注意: + * 该函数用于发送UDF函数的信息,包括函数的基本信息、参数信息等。具体的 + * 步骤包括初始化消息缓冲区、填充基本UDF信息、填充UDF参数信息、发送消息等。 + * 函数中的批处理模式(batchMode)会影响参数的数量。 + */ template static void SendUDFInformation(FunctionCallInfo fcinfo) { - StringInfo udfMsgBuf = fcinfo->udfInfo.udfMsgBuf; + StringInfo udfMsgBuf = fcinfo->udfInfo.udfMsgBuf; // 获取UDF消息缓冲区 /* Step 1: Initialize and Reset the udfMsgBuf */ - resetStringInfo(fcinfo->udfInfo.udfMsgBuf); - Reserve4BytesMsgHeader(udfMsgBuf); + resetStringInfo(fcinfo->udfInfo.udfMsgBuf); // 重置消息缓冲区 + Reserve4BytesMsgHeader(udfMsgBuf); // 为消息头预留4字节空间 /* * Step 2: Fill the basic UDF information (OID, NAME, * LIBPATH and so on) */ - FillBasicUDFInformation(fcinfo); + FillBasicUDFInformation(fcinfo); // 填充基本UDF信息 /* * Step 3: Fill UDF arguments * the number of batch argument values */ - FillUDFArguments(fcinfo); + FillUDFArguments(fcinfo); // 填充UDF参数信息,根据批处理模式确定参数数量 /* * Step 4: Fill the length of body msg and send msg */ - Fill4BytesMsgHeader(udfMsgBuf); - SendMsg(UDFRPCSocket, udfMsgBuf->data, udfMsgBuf->len); + Fill4BytesMsgHeader(udfMsgBuf); // 填充消息体长度 + SendMsg(UDFRPCSocket, udfMsgBuf->data, udfMsgBuf->len); // 发送消息 - lastUDFOid = fcinfo->flinfo->fn_oid; + lastUDFOid = fcinfo->flinfo->fn_oid; // 更新最后一个UDF函数的OID } - -bool RPCInitFencedUDFIfNeed(Oid functionId, FmgrInfo* finfo, HeapTuple procedureTuple) +/* + * 功能:如果需要的话,初始化Fenced UDF(用户定义函数) + * + * 参数列表: + * functionId:UDF函数的OID(对象标识符) + * finfo:FmgrInfo结构体指针,包含了函数管理的信息 + * procedureTuple:函数对应的过程元组(HeapTuple) + * + * 返回值: + * 如果函数以Fenced模式运行,则返回true,否则返回false + * + * 注意: + * 该函数用于初始化Fenced UDF(用户定义函数),主要包括获取函数的相关信息, + * 特别是针对C语言和Java语言的不同处理方式。如果函数以Fenced模式运行,会设置 + * 相关的函数信息,否则保持默认值。 + */ +bool RPCInitFencedUDFIfNeed(Oid functionId, FmgrInfo *finfo, HeapTuple procedureTuple) { Datum prosrcattr; Datum probinattr; - char* udfName = NULL; - char* udfLibPath = NULL; + char *udfName = NULL; + char *udfLibPath = NULL; bool isnull = false; Oid languageId = finfo->fn_languageId; bool fencedMode = false; @@ -1528,10 +1713,8 @@ bool RPCInitFencedUDFIfNeed(Oid functionId, FmgrInfo* finfo, HeapTuple procedure probinattr = SysCacheGetAttr(PROCOID, procedureTuple, Anum_pg_proc_probin, &isnull); if (languageId == ClanguageId && isnull) - ereport(ERROR, - (errmodule(MOD_UDF), - errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), - errmsg("null probin for C function %u", functionId))); + ereport(ERROR, (errmodule(MOD_UDF), errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("null probin for C function %u", functionId))); if (fencedMode) { if (languageId == ClanguageId) { @@ -1543,18 +1726,16 @@ bool RPCInitFencedUDFIfNeed(Oid functionId, FmgrInfo* finfo, HeapTuple procedure prosrcattr = SysCacheGetAttr(PROCOID, procedureTuple, Anum_pg_proc_prosrc, &isnull); if (isnull) - ereport(ERROR, - (errmodule(MOD_UDF), - errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), - errmsg("null prosrc for C function %u", functionId))); + ereport(ERROR, (errmodule(MOD_UDF), errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("null prosrc for C function %u", functionId))); udfName = TextDatumGetCString(prosrcattr); udfLibPath = TextDatumGetCString(probinattr); - char* udfLibFullPath = expand_dynamic_library_name(udfLibPath); + char *udfLibFullPath = expand_dynamic_library_name(udfLibPath); errno_t errorno = memcpy_s(finfo->fnName, sizeof(finfo->fnName), udfName, strlen(udfName) + 1); securec_check_c(errorno, "\0", "\0"); - finfo->fnLibPath = (char*)MemoryContextAllocZero(finfo->fn_mcxt, strlen(udfLibFullPath) + 1); + finfo->fnLibPath = (char *)MemoryContextAllocZero(finfo->fn_mcxt, strlen(udfLibFullPath) + 1); errorno = memcpy_s(finfo->fnLibPath, strlen(udfLibFullPath) + 1, udfLibFullPath, strlen(udfLibFullPath)); securec_check_c(errorno, "\0", "\0"); pfree_ext(udfLibFullPath); @@ -1566,10 +1747,8 @@ bool RPCInitFencedUDFIfNeed(Oid functionId, FmgrInfo* finfo, HeapTuple procedure */ prosrcattr = SysCacheGetAttr(PROCOID, procedureTuple, Anum_pg_proc_prosrc, &isnull); if (isnull) - ereport(ERROR, - (errmodule(MOD_UDF), - errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), - errmsg("null prosrc for Java function %u", functionId))); + ereport(ERROR, (errmodule(MOD_UDF), errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("null prosrc for Java function %u", functionId))); if (languageId == JavalanguageId) { udfLibPath = TextDatumGetCString(prosrcattr); } else { @@ -1577,7 +1756,7 @@ bool RPCInitFencedUDFIfNeed(Oid functionId, FmgrInfo* finfo, HeapTuple procedure udfLibPath = (char *)palloc0(dbinLen * sizeof(char)); pg_itoa(u_sess->proc_cxt.MyDatabaseId, udfLibPath); } - finfo->fnLibPath = (char*)MemoryContextAllocZero(finfo->fn_mcxt, strlen(udfLibPath) + 1); + finfo->fnLibPath = (char *)MemoryContextAllocZero(finfo->fn_mcxt, strlen(udfLibPath) + 1); errno_t errorno = memcpy_s(finfo->fnLibPath, strlen(udfLibPath) + 1, udfLibPath, strlen(udfLibPath)); securec_check_c(errorno, "\0", "\0"); } @@ -1602,7 +1781,7 @@ Datum RPCFencedUDF(FunctionCallInfo fcinfo) /* Step 1: Build UDF RPC connection if need */ if (unlikely((-1 == UDFRPCSocket))) { struct sockaddr_un addrUnix; - errno_t rc = memset_s((void*)&addrUnix, sizeof(addrUnix), 0, sizeof(addrUnix)); + errno_t rc = memset_s((void *)&addrUnix, sizeof(addrUnix), 0, sizeof(addrUnix)); securec_check(rc, "\0", "\0"); addrUnix.sun_family = AF_UNIX; @@ -1610,13 +1789,13 @@ Datum RPCFencedUDF(FunctionCallInfo fcinfo) UDFRPCSocket = socket(AF_UNIX, SOCK_STREAM, 0); if (UDFRPCSocket < 0) { - ereport( - ERROR, (errmodule(MOD_UDF), errcode(ERRCODE_CONNECTION_FAILURE), errmsg("create socket failed: %m"))); + ereport(ERROR, + (errmodule(MOD_UDF), errcode(ERRCODE_CONNECTION_FAILURE), errmsg("create socket failed: %m"))); } - if (connect(UDFRPCSocket, (struct sockaddr*)&addrUnix, sizeof(addrUnix)) < 0) { - ereport(ERROR, - (errmodule(MOD_UDF), errcode(ERRCODE_CONNECTION_FAILURE), errmsg("Run udf RPC connect failed: %m"))); + if (connect(UDFRPCSocket, (struct sockaddr *)&addrUnix, sizeof(addrUnix)) < 0) { + ereport(ERROR, (errmodule(MOD_UDF), errcode(ERRCODE_CONNECTION_FAILURE), + errmsg("Run udf RPC connect failed: %m"))); } } @@ -1626,14 +1805,12 @@ Datum RPCFencedUDF(FunctionCallInfo fcinfo) /* Step 3: Get UDF result */ int len = 0; resetStringInfo(fcinfo->udfInfo.udfMsgBuf); - RecvMsg(UDFRPCSocket, (char*)&len, sizeof(len)); + RecvMsg(UDFRPCSocket, (char *)&len, sizeof(len)); if (len < 0) { - ereport(ERROR, - (errmodule(MOD_UDF), - errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("Variable length %d cannot be negative", len))); + ereport(ERROR, (errmodule(MOD_UDF), errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("Variable length %d cannot be negative", len))); } - char* ptr = (char*)palloc(len + 1); + char *ptr = (char *)palloc(len + 1); RecvMsg(UDFRPCSocket, ptr, len); AppendBufFixedMsgVal(fcinfo->udfInfo.udfMsgBuf, ptr, len); fcinfo->udfInfo.msgReadPtr = fcinfo->udfInfo.udfMsgBuf->data; @@ -1641,7 +1818,7 @@ Datum RPCFencedUDF(FunctionCallInfo fcinfo) /* Get Message type */ short msgType; - GetFixedMsgVal(fcinfo->udfInfo.msgReadPtr, (char*)&msgType, sizeof(msgType), sizeof(msgType)); + GetFixedMsgVal(fcinfo->udfInfo.msgReadPtr, (char *)&msgType, sizeof(msgType), sizeof(msgType)); if (msgType == UDF_RESULT) { int batchRows = batchMode ? fcinfo->udfInfo.argBatchRows : 1; @@ -1654,20 +1831,26 @@ Datum RPCFencedUDF(FunctionCallInfo fcinfo) } } else if (msgType == UDF_ERROR) { int valLen = 0; - GetFixedMsgVal(fcinfo->udfInfo.msgReadPtr, (char*)&valLen, sizeof(valLen), sizeof(valLen)); + GetFixedMsgVal(fcinfo->udfInfo.msgReadPtr, (char *)&valLen, sizeof(valLen), sizeof(valLen)); if (valLen < 0) { - ereport(ERROR, - (errmodule(MOD_UDF), - errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("Variable length %d cannot be negative", valLen))); + ereport(ERROR, (errmodule(MOD_UDF), errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("Variable length %d cannot be negative", valLen))); } - char* errMsg = (char*)palloc0(valLen + 1); - GetFixedMsgVal(fcinfo->udfInfo.msgReadPtr, (char*)errMsg, valLen, valLen + 1); + char *errMsg = (char *)palloc0(valLen + 1); + GetFixedMsgVal(fcinfo->udfInfo.msgReadPtr, (char *)errMsg, valLen, valLen + 1); ereport(ERROR, (errmodule(MOD_UDF), errcode(ERRCODE_DATA_EXCEPTION), errmsg("UDF Error:%s", errMsg))); } return result; } +/* + * 功能:创建用户定义函数(UDF)哈希表 + * + * 说明: + * 如果UDFFuncHash为NULL,则创建一个哈希表用于存储用户定义函数的信息。 + * 该哈希表以函数的OID(对象标识符)作为键,并包含UDFFuncHashTabEntry类型的值。 + * 如果哈希表已存在,则不执行任何操作。 + */ static void UDFCreateHashTab() { if (NULL == UDFFuncHash) { @@ -1682,14 +1865,21 @@ static void UDFCreateHashTab() UDFFuncHash = hash_create("UDFFuncHash", 100, &hash_ctl, HASH_ELEM | HASH_FUNCTION); } } - -static void FindOrInsertUDFHashTab(FunctionCallInfoData* fcinfo) +/* + * 功能:查找或插入用户定义函数(UDF)哈希表 + * + * 说明: + * 此函数用于查找或插入UDF哈希表中的条目,以便获取或设置有关UDF的信息。 + * 如果在哈希表中找到了函数的OID,则将相关信息设置到FunctionCallInfoData中。 + * 如果没有找到,则将函数信息加载到内存中,并插入哈希表中。 + */ +static void FindOrInsertUDFHashTab(FunctionCallInfoData *fcinfo) { AssertEreport(UDFFuncHash, MOD_UDF, "Hash table is not initialized in UDF."); Oid oid = fcinfo->flinfo->fn_oid; - UDFFuncHashTabEntry* entry = NULL; - entry = (UDFFuncHashTabEntry*)hash_search(UDFFuncHash, &oid, HASH_FIND, NULL); + UDFFuncHashTabEntry *entry = NULL; + entry = (UDFFuncHashTabEntry *)hash_search(UDFFuncHash, &oid, HASH_FIND, NULL); if (NULL != entry) { @@ -1705,43 +1895,38 @@ static void FindOrInsertUDFHashTab(FunctionCallInfoData* fcinfo) fcinfo->udfInfo = entry->udfInfo; } else { AutoContextSwitch contextSwitcher(THREAD_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_EXECUTOR)); - entry = (UDFFuncHashTabEntry*)hash_search(UDFFuncHash, &oid, HASH_ENTER, NULL); + entry = (UDFFuncHashTabEntry *)hash_search(UDFFuncHash, &oid, HASH_ENTER, NULL); if (NULL != entry) { - FmgrInfo* flinfo = fcinfo->flinfo; + FmgrInfo *flinfo = fcinfo->flinfo; /* Load the shared library, unless we already did */ if (flinfo->fn_languageId == ClanguageId) { - void* libHandle = internal_load_library(flinfo->fnLibPath); + void *libHandle = internal_load_library(flinfo->fnLibPath); if (NULL == libHandle) - ereport(ERROR, - (errmodule(MOD_UDF), - errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), - errmsg("internal_load_library %s failed: %m", flinfo->fnLibPath))); + ereport(ERROR, (errmodule(MOD_UDF), errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("internal_load_library %s failed: %m", flinfo->fnLibPath))); /* Look up the function within the library */ flinfo->fn_addr = (PGFunction)pg_dlsym(libHandle, flinfo->fnName); - } else if (flinfo->fn_languageId == JavalanguageId){ + } else if (flinfo->fn_languageId == JavalanguageId) { #ifndef ENABLE_LITE_MODE /* Load libpljava.so to support Java UDF */ char pathbuf[MAXPGPATH]; get_lib_path(my_exec_path, pathbuf); join_path_components(pathbuf, pathbuf, "libpljava.so"); - char* libpljava_location = strdup(pathbuf); - void* libpljava_handler = internal_load_library(libpljava_location); + char *libpljava_location = strdup(pathbuf); + void *libpljava_handler = internal_load_library(libpljava_location); if (NULL == libpljava_handler) - ereport(ERROR, - (errmodule(MOD_UDF), - errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), - errmsg("internal_load_library %s failed: %m", libpljava_location))); + ereport(ERROR, (errmodule(MOD_UDF), errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("internal_load_library %s failed: %m", libpljava_location))); free(libpljava_location); /* Look up the java_call_handler function within libpljava.so */ - Datum (*pljava_call_handler)(FunctionCallInfoData*); - pljava_call_handler = (Datum(*)(FunctionCallInfoData*))pg_dlsym(libpljava_handler, "java_call_handler"); + Datum (*pljava_call_handler)(FunctionCallInfoData *); + pljava_call_handler = + (Datum(*)(FunctionCallInfoData *))pg_dlsym(libpljava_handler, "java_call_handler"); if (NULL == pljava_call_handler) - ereport(ERROR, - (errmodule(MOD_UDF), - errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), - errmsg("load java_call_handler failed."))); + ereport(ERROR, (errmodule(MOD_UDF), errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), + errmsg("load java_call_handler failed."))); flinfo->fn_addr = pljava_call_handler; #else FEATURE_ON_LITE_MODE_NOT_SUPPORTED(); @@ -1762,10 +1947,11 @@ static void FindOrInsertUDFHashTab(FunctionCallInfoData* fcinfo) free(libpl_location); Datum (*plpython_call_handler)(FunctionCallInfoData *); #ifdef ENABLE_PYTHON2 - plpython_call_handler = (Datum(*)(FunctionCallInfoData *))pg_dlsym(libpl_handler, "plpython_call_handler"); + plpython_call_handler = + (Datum(*)(FunctionCallInfoData *))pg_dlsym(libpl_handler, "plpython_call_handler"); #else - plpython_call_handler = - (Datum(*)(FunctionCallInfoData *))pg_dlsym(libpl_handler, "plpython3_call_handler"); + plpython_call_handler = + (Datum(*)(FunctionCallInfoData *))pg_dlsym(libpl_handler, "plpython3_call_handler"); #endif if (NULL == plpython_call_handler) { ereport(ERROR, (errmodule(MOD_UDF), errcode(ERRCODE_NULL_VALUE_NOT_ALLOWED), @@ -1795,49 +1981,75 @@ void SIGQUITUDFMaster(SIGNAL_ARGS) { UDFMasterQuitFlag = true; } - -void inline SetUDFUnixSocketPath(struct sockaddr_un* unAddrPtr) +/* + * 功能:设置Fenced UDF(用户定义函数)的Unix域套接字路径 + * + * 参数列表: + * unAddrPtr:指向struct sockaddr_un的指针,用于存储Unix域套接字路径 + * + * 注意: + * 该函数用于检查并设置Unix域套接字的路径。首先计算所需的路径长度, + * 然后与套接字路径的大小进行比较,如果超出范围则报错。 + * 最后使用snprintf_s函数设置套接字路径。 + */ +void inline SetUDFUnixSocketPath(struct sockaddr_un *unAddrPtr) { /* Check and set unix domain socket path */ uint32 socketPathLen = strlen(g_instance.attr.attr_network.UnixSocketDir) + 1 + strlen(FENCED_UDF_UNIXSOCKET) + 1; if (socketPathLen > sizeof(unAddrPtr->sun_path)) - ereport(FATAL, - (errmodule(MOD_UDF), - errmsg("UnixSocketDir is not valid, length is between 1 and %lu: %s/%s", - sizeof(unAddrPtr->sun_path) - sizeof(FENCED_UDF_UNIXSOCKET) - 2, - g_instance.attr.attr_network.UnixSocketDir, - FENCED_UDF_UNIXSOCKET))); + ereport(FATAL, (errmodule(MOD_UDF), errmsg("UnixSocketDir is not valid, length is between 1 and %lu: %s/%s", + sizeof(unAddrPtr->sun_path) - sizeof(FENCED_UDF_UNIXSOCKET) - 2, + g_instance.attr.attr_network.UnixSocketDir, FENCED_UDF_UNIXSOCKET))); - errno_t rc = snprintf_s(unAddrPtr->sun_path, - sizeof(unAddrPtr->sun_path), - sizeof(unAddrPtr->sun_path) - 1, - "%s/%s", - g_instance.attr.attr_network.UnixSocketDir, - FENCED_UDF_UNIXSOCKET); + errno_t rc = snprintf_s(unAddrPtr->sun_path, sizeof(unAddrPtr->sun_path), sizeof(unAddrPtr->sun_path) - 1, "%s/%s", + g_instance.attr.attr_network.UnixSocketDir, FENCED_UDF_UNIXSOCKET); securec_check_ss(rc, "\0", "\0"); } -void InitUDFInfo(UDFInfoType* udfInfo, int argN, int batchRows) +/* + * 功能:初始化UDF信息结构体 + * + * 参数列表: + * udfInfo:指向UDF信息结构体的指针,用于存储UDF的相关信息 + * argN:UDF的参数个数 + * batchRows:批处理行数 + * + * 注意: + * 该函数用于初始化UDF信息结构体,包括分配内存空间和设置默认值。 + * 主要分配内存来存储UDF的消息缓冲区、参数、空值标志、结果等信息,并初始化其他字段。 + */ +void InitUDFInfo(UDFInfoType *udfInfo, int argN, int batchRows) { + // 为消息缓冲区分配内存 udfInfo->udfMsgBuf = makeStringInfo(); - udfInfo->arg = (Datum**)palloc(sizeof(Datum*) * batchRows); - udfInfo->null = (bool**)palloc(sizeof(bool*) * batchRows); - udfInfo->result = (Datum*)palloc(sizeof(Datum) * batchRows); - udfInfo->resultIsNull = (bool*)palloc(sizeof(bool) * batchRows); + // 为参数数组分配内存 + udfInfo->arg = (Datum **)palloc(sizeof(Datum *) * batchRows); + // 为空值标志数组分配内存 + udfInfo->null = (bool **)palloc(sizeof(bool *) * batchRows); + // 为结果数组分配内存 + udfInfo->result = (Datum *)palloc(sizeof(Datum) * batchRows); + // 为结果是否为空的标志数组分配内存 + udfInfo->resultIsNull = (bool *)palloc(sizeof(bool) * batchRows); + // 初始化批处理行数 udfInfo->argBatchRows = 0; + // 初始化消息读取指针为NULL udfInfo->msgReadPtr = NULL; + // 初始化有效的UDF参数处理器为false udfInfo->valid_UDFArgsHandlerPtr = false; + // 初始化UDF结果处理器为NULL udfInfo->UDFResultHandlerPtr = NULL; + // 设置分配的行数 udfInfo->allocRows = batchRows; - + // 为每行参数分配内存 for (int i = 0; i < batchRows; ++i) { - udfInfo->arg[i] = (Datum*)palloc(sizeof(Datum) * argN); - udfInfo->null[i] = (bool*)palloc(sizeof(bool) * argN); + udfInfo->arg[i] = (Datum *)palloc(sizeof(Datum) * argN); + udfInfo->null[i] = (bool *)palloc(sizeof(bool) * argN); } - udfInfo->UDFArgsHandlerPtr = (UDFArgsFuncType*)palloc0((argN) * sizeof(UDFArgsFuncType)); + // 为UDF参数处理器分配内存 + udfInfo->UDFArgsHandlerPtr = (UDFArgsFuncType *)palloc0((argN) * sizeof(UDFArgsFuncType)); } -void InitFunctionCallUDFArgs(FunctionCallInfoData* fcinfo, int argN, int batchRows) +void InitFunctionCallUDFArgs(FunctionCallInfoData *fcinfo, int argN, int batchRows) { /* For UDF, FmgrInfo must be valid, if not invalid, don't need init the following */ if (argN > 0) { @@ -1845,7 +2057,20 @@ void InitFunctionCallUDFArgs(FunctionCallInfoData* fcinfo, int argN, int batchRo } } -void InitFuncCallUDFInfo(FunctionCallInfoData* fcinfo, int argN, bool setFuncPtr = true) +/* + * 功能:初始化函数调用的UDF信息 + * + * 参数列表: + * fcinfo:函数调用信息结构体指针 + * argN:UDF的参数个数 + * setFuncPtr:是否设置函数指针,默认为true + * + * 注意: + * 该函数用于初始化函数调用时的UDF信息,主要用于Fenced UDF的设置和参数初始化。 + * 如果函数以Fenced模式运行,会设置相关的函数指针,如果有参数,也会初始化参数信息。 + * 否则,会初始化消息缓冲区等相关信息。 + */ +void InitFuncCallUDFInfo(FunctionCallInfoData *fcinfo, int argN, bool setFuncPtr = true) { if (unlikely(fcinfo->flinfo && fcinfo->flinfo->fn_fenced)) { if (setFuncPtr) @@ -1859,7 +2084,20 @@ void InitFuncCallUDFInfo(FunctionCallInfoData* fcinfo, int argN, bool setFuncPtr } } } - +/* + * 功能:处理Fenced UDF相关操作 + * + * 参数列表: + * PG_FUNCTION_ARGS:PostgreSQL函数参数列表 + * + * 返回值: + * 返回文本类型或NULL + * + * 注意: + * 该函数用于处理Fenced UDF的相关操作,根据传入的参数不同执行不同的操作, + * 包括查询和操作Fenced UDF的主进程和工作进程。如果传入无效的参数, + * 会抛出错误。 + */ Datum fenced_udf_process(PG_FUNCTION_ARGS) { int32 arg = PG_GETARG_INT32(0); @@ -1875,17 +2113,18 @@ Datum fenced_udf_process(PG_FUNCTION_ARGS) appendStringInfo(&strinfo, "ps ux | grep 'gaussdb fenced UDF worker process' | grep -v grep | wc -l"); } else if (KILL_WORK == arg) { /* kill UDF work process */ - appendStringInfo(&strinfo, + appendStringInfo( + &strinfo, "ps ux | grep 'gaussdb fenced UDF worker process' | grep -v grep | awk '{print $2}' | xargs kill -9"); } else { ereport(ERROR, (errmodule(MOD_UDF), errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("Invalid parameter."))); } - FILE* fp = popen(strinfo.data, "r"); + FILE *fp = popen(strinfo.data, "r"); if (fp == NULL) { - ereport(ERROR, - (errmodule(MOD_UDF), errcode(ERRCODE_UNEXPECTED_NULL_VALUE), errmsg("execute cmd %s fail.", strinfo.data))); + ereport(ERROR, (errmodule(MOD_UDF), errcode(ERRCODE_UNEXPECTED_NULL_VALUE), + errmsg("execute cmd %s fail.", strinfo.data))); } /* Get process num. */ @@ -1894,7 +2133,7 @@ Datum fenced_udf_process(PG_FUNCTION_ARGS) pfree_ext(strinfo.data); pclose(fp); ereport(ERROR, - (errmodule(MOD_UDF), errcode(ERRCODE_UNEXPECTED_NULL_VALUE), errmsg("Can not read process num."))); + (errmodule(MOD_UDF), errcode(ERRCODE_UNEXPECTED_NULL_VALUE), errmsg("Can not read process num."))); } int len = strlen(result); @@ -1911,69 +2150,75 @@ Datum fenced_udf_process(PG_FUNCTION_ARGS) PG_RETURN_NULL(); } } - -static char* parse_value(char* p_start) +/* + * 功能:解析字符串中的值,返回解析后的子字符串 + * + * 参数列表: + * p_start:待解析的字符串的起始位置 + * + * 返回值: + * 返回解析后的子字符串,若无法解析则返回NULL + * + * 注意: + * 该函数用于解析字符串中的值,从给定的起始位置开始解析,直到遇到第一个空格或'\0'字符为止。 + * 如果成功解析,返回解析后的子字符串;如果无法解析,返回NULL。 + */ +static char *parse_value(char *p_start) { - char* value = NULL; - char* p_end = p_start; + char *value = NULL; + char *p_end = p_start; // seek for the first whitespace or '\0' while (*p_end != '\0' && *p_end != ' ') { p_end++; } if (p_end != p_start) { - value = (char*)palloc((p_end - p_start + 1) * sizeof(char)); + value = (char *)palloc((p_end - p_start + 1) * sizeof(char)); errno_t errorno = strncpy_s(value, p_end - p_start + 1, p_start, p_end - p_start); securec_check_c(errorno, "\0", "\0"); } return value; } - -static void check_input_for_security_s(const char* input) +/* + * 功能:检查输入字符串是否包含潜在的安全风险字符 + * + * 参数列表: + * input:待检查的输入字符串 + * + * 注意: + * 该函数用于检查输入字符串是否包含潜在的安全风险字符,如"|", ";", "&", "$"等。 + * 如果输入字符串包含任何这些字符之一,将引发错误并终止程序。 + */ +static void check_input_for_security_s(const char *input) { - char* danger_token[] = {"|", - ";", - "&", - "$", - "<", - ">", - "`", - "\\", - "'", - "\"", - "{", - "}", - "(", - ")", - "[", - "]", - "~", - "*", - "?", - "!", - "\n", - NULL}; + char *danger_token[] = {"|", ";", "&", "$", "<", ">", "`", "\\", "'", "\"", "{", + "}", "(", ")", "[", "]", "~", "*", "?", "!", "\n", NULL}; for (int i = 0; danger_token[i] != NULL; ++i) { if (strstr(input, danger_token[i])) { - ereport(ERROR, - (errmodule(MOD_UDF), - errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("Contains invaid character: \"%s\"", danger_token[i]))); + ereport(ERROR, (errmodule(MOD_UDF), errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("Contains invaid character: \"%s\"", danger_token[i]))); } } } - -static void check_extend_library_option(LibraryInfo* libInfo) +/* + * 功能:检查扩展库选项是否合法 + * + * 参数列表: + * libInfo:LibraryInfo结构体指针,包含了扩展库信息和选项 + * + * 注意: + * 该函数用于检查扩展库选项是否合法,选项应该是"ls"、"addjar"或"rmjar"之一, + * 否则将引发错误并终止程序。 + */ +static void check_extend_library_option(LibraryInfo *libInfo) { - const char* option = libInfo->option; + const char *option = libInfo->option; int len = 0; /* check option which should not be null */ if (option == NULL || (len = strlen(option)) == 0) { - ereport(ERROR, - (errmodule(MOD_UDF), - errcode(ERRCODE_UNEXPECTED_NULL_VALUE), - errmsg("Invalid argument: should appoint an option of ls, addjar or rmjar."))); + ereport(ERROR, (errmodule(MOD_UDF), errcode(ERRCODE_UNEXPECTED_NULL_VALUE), + errmsg("Invalid argument: should appoint an option of ls, addjar or rmjar."))); } /* check option which should be ls, addjar or rmjar */ @@ -1984,10 +2229,8 @@ static void check_extend_library_option(LibraryInfo* libInfo) } else if ((len == 5) && (strncmp(option, "rmjar", len) == 0)) { libInfo->optionType = OPTION_RMJAR; } else { - ereport(ERROR, - (errmodule(MOD_UDF), - errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("Invalid argument: only support ls, addjar, rmjar options."))); + ereport(ERROR, (errmodule(MOD_UDF), errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("Invalid argument: only support ls, addjar, rmjar options."))); } } @@ -1997,21 +2240,21 @@ static void check_extend_library_option(LibraryInfo* libInfo) * @in filepath: another palloc string of the file path behind "obs://" in obsinfo * @out: OBSInfo structure */ -static OBSInfo* parse_obsinfo(char* obsinfo, char* filepath) +static OBSInfo *parse_obsinfo(char *obsinfo, char *filepath) { - OBSInfo* obsInfo = (OBSInfo*)palloc0(sizeof(OBSInfo)); + OBSInfo *obsInfo = (OBSInfo *)palloc0(sizeof(OBSInfo)); if (filepath == NULL) { ereport(ERROR, (errcode(ERRCODE_UNEXPECTED_NULL_VALUE), errmsg("filepath should not be NULL"))); } - char* p = filepath; + char *p = filepath; while (*p != '\0') { if (*p == '/' && p != filepath) { - obsInfo->bucket = (char*)palloc((p - filepath + 1) * sizeof(char)); + obsInfo->bucket = (char *)palloc((p - filepath + 1) * sizeof(char)); errno_t errorno = strncpy_s(obsInfo->bucket, p - filepath + 1, filepath, p - filepath); securec_check_c(errorno, "\0", "\0"); p++; - obsInfo->path = (char*)palloc((strlen(p) + 1) * sizeof(char)); + obsInfo->path = (char *)palloc((strlen(p) + 1) * sizeof(char)); errorno = strncpy_s(obsInfo->path, strlen(p) + 1, p, strlen(p)); securec_check_c(errorno, "\0", "\0"); break; @@ -2021,13 +2264,13 @@ static OBSInfo* parse_obsinfo(char* obsinfo, char* filepath) } if (obsInfo->bucket == NULL) { - ereport( - ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("Invalid argument: must set correct bucket."))); + ereport(ERROR, + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("Invalid argument: must set correct bucket."))); } if (obsInfo->path == NULL) { - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("Invalid argument: must set correct obs file path."))); + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("Invalid argument: must set correct obs file path."))); } if ((p = strstr(obsinfo, "accesskey=")) != NULL) { @@ -2057,7 +2300,7 @@ static OBSInfo* parse_obsinfo(char* obsinfo, char* filepath) return obsInfo; } -static void parse_extend_library_operation(LibraryInfo* libInfo) +static void parse_extend_library_operation(LibraryInfo *libInfo) { /* parse libInfo->operation * the operation format should be like either: @@ -2076,8 +2319,8 @@ static void parse_extend_library_operation(LibraryInfo* libInfo) * and in all cases except OPTION_LS, the 'libraryname' key can not be omitted. */ - char* operation = libInfo->operation; - char* tmp = NULL; + char *operation = libInfo->operation; + char *tmp = NULL; /* Check if there is "libraryname=", and it cannot be omitted except OPTION_LS */ if ((tmp = strstr(operation, "libraryname=")) != NULL) { @@ -2086,11 +2329,12 @@ static void parse_extend_library_operation(LibraryInfo* libInfo) if (libInfo->destpath == NULL) { if (libInfo->optionType != OPTION_LS) ereport(ERROR, (errmodule(MOD_UDF), errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("Invalid argument: must set a \"libraryname\"."))); + errmsg("Invalid argument: must set a \"libraryname\"."))); } else { if ((strstr(libInfo->destpath, "/") != NULL) || libInfo->destpath[0] == '.') - ereport(ERROR, (errmodule(MOD_UDF), errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("Invalid argument: \"libraryname\" should not contain \"/\" or starting with \".\"."))); + ereport(ERROR, + (errmodule(MOD_UDF), errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("Invalid argument: \"libraryname\" should not contain \"/\" or starting with \".\"."))); } if (libInfo->optionType != OPTION_ADDJAR) { @@ -2105,12 +2349,12 @@ static void parse_extend_library_operation(LibraryInfo* libInfo) tmp = operation + strlen("file://"); if ((libInfo->localpath = parse_value(tmp)) == NULL || libInfo->localpath[0] != '/') { ereport(ERROR, (errmodule(MOD_UDF), errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("Invalid argument: must set an absolute path followed by \"file:///\"."))); + errmsg("Invalid argument: must set an absolute path followed by \"file:///\"."))); } if (strlen(libInfo->localpath) < 4 || - (strncmp(libInfo->localpath + strlen(libInfo->localpath) - 4, ".jar", 4) != 0 )) { + (strncmp(libInfo->localpath + strlen(libInfo->localpath) - 4, ".jar", 4) != 0)) { ereport(ERROR, (errmodule(MOD_UDF), errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("Invalid argument: the source file must be .jar file."))); + errmsg("Invalid argument: the source file must be .jar file."))); } } else if (strncmp(operation, "obs://", strlen("obs://")) == 0) { /* Check if there is "obs://". @@ -2119,18 +2363,18 @@ static void parse_extend_library_operation(LibraryInfo* libInfo) */ if (!isSecurityMode) { ereport(ERROR, (errmodule(MOD_UDF), errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("Invalid argument: must set an absolute path followed by \"file:///\"."))); + errmsg("Invalid argument: must set an absolute path followed by \"file:///\"."))); } libInfo->isSourceLocal = false; tmp = operation + strlen("obs://"); if ((tmp = parse_value(tmp)) == NULL) { ereport(ERROR, (errmodule(MOD_UDF), errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("Invalid argument: must set bucket and filepath followed by \"obs://\"."))); + errmsg("Invalid argument: must set bucket and filepath followed by \"obs://\"."))); } if (strlen(tmp) < 4 || (strncmp(tmp + strlen(tmp) - 4, ".jar", 4) != 0)) { ereport(ERROR, (errmodule(MOD_UDF), errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("Invalid argument: the source file must be .jar file."))); + errmsg("Invalid argument: the source file must be .jar file."))); } libInfo->obsInfo = parse_obsinfo(operation, tmp); @@ -2138,7 +2382,7 @@ static void parse_extend_library_operation(LibraryInfo* libInfo) } else { /* must set "file://" or "obs://" when OPTION_ADDJAR */ ereport(ERROR, (errmodule(MOD_UDF), errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("must set correct source file path."))); + errmsg("must set correct source file path."))); } char pathbuf[MAXPGPATH]; @@ -2147,7 +2391,7 @@ static void parse_extend_library_operation(LibraryInfo* libInfo) join_path_components(pathbuf, pathbuf, libInfo->destpath); if (access(pathbuf, F_OK) == 0) { ereport(ERROR, (errmodule(MOD_UDF), errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("Invalid argument: the library already existed, please remove it first using rmjar."))); + errmsg("Invalid argument: the library already existed, please remove it first using rmjar."))); } } @@ -2158,7 +2402,7 @@ static void parse_extend_library_operation(LibraryInfo* libInfo) * @in tmp_path: $GAUSSHOME/share/postgresql/tmp * @out: void */ -static void get_obsfile(OBSInfo* obsInfo, const char* tmp_path) +static void get_obsfile(OBSInfo *obsInfo, const char *tmp_path) { #ifndef ENABLE_MULTIPLE_NODES DISTRIBUTED_FEATURE_NOT_SUPPORTED(); @@ -2170,26 +2414,19 @@ static void get_obsfile(OBSInfo* obsInfo, const char* tmp_path) check_backend_env(tmp_path); - appendStringInfo(&cmd, - "python %s/udstools.py %s %s %s %s %s %s", - tmp_path, obsInfo->accesskey, obsInfo->secretkey, obsInfo->path, - obsInfo->region, obsInfo->bucket, tmp_path); + appendStringInfo(&cmd, "python %s/udstools.py %s %s %s %s %s %s", tmp_path, obsInfo->accesskey, obsInfo->secretkey, + obsInfo->path, obsInfo->region, obsInfo->bucket, tmp_path); /* reset key buffer to avoid private info leak */ errno_t ret = memset_s(obsInfo->accesskey, strlen(obsInfo->accesskey) + 1, 0, strlen(obsInfo->accesskey) + 1); securec_check(ret, "\0", "\0"); ret = memset_s(obsInfo->secretkey, strlen(obsInfo->secretkey) + 1, 0, strlen(obsInfo->secretkey) + 1); securec_check(ret, "\0", "\0"); - ereport(DEBUG1, - (errmodule(MOD_UDF), - errmsg("[udstools]:%s/udstools.py\n" - "[path]:%s\n" - "[region]:%s\n" - "[bucket]:%s\n", - tmp_path, - obsInfo->path, - obsInfo->region, - obsInfo->bucket))); + ereport(DEBUG1, (errmodule(MOD_UDF), errmsg("[udstools]:%s/udstools.py\n" + "[path]:%s\n" + "[region]:%s\n" + "[bucket]:%s\n", + tmp_path, obsInfo->path, obsInfo->region, obsInfo->bucket))); /* execute downloading from obs */ check_input_for_security_s(cmd.data); @@ -2199,19 +2436,13 @@ static void get_obsfile(OBSInfo* obsInfo, const char* tmp_path) ret = memset_s(cmd.data, cmd.len, 0, cmd.len); securec_check(ret, "\0", "\0"); resetStringInfo(&cmd); - appendStringInfo(&cmd, - "python %s/udstools.py * * %s %s %s %s", - tmp_path, - obsInfo->path, - obsInfo->region, - obsInfo->bucket, - tmp_path); + appendStringInfo(&cmd, "python %s/udstools.py * * %s %s %s %s", tmp_path, obsInfo->path, obsInfo->region, + obsInfo->bucket, tmp_path); if (rc == -1 || WIFEXITED(rc) == 0) { - ereport(LOG, - (errcode(ERRCODE_SYSTEM_ERROR), errmsg("[status]:%d %d System error. \"%s\"", rc, WIFEXITED(rc), cmd.data))); - ereport(ERROR, - (errcode(ERRCODE_SYSTEM_ERROR), errmsg("%d %d: System error.", rc, WIFEXITED(rc)))); + ereport(LOG, (errcode(ERRCODE_SYSTEM_ERROR), + errmsg("[status]:%d %d System error. \"%s\"", rc, WIFEXITED(rc), cmd.data))); + ereport(ERROR, (errcode(ERRCODE_SYSTEM_ERROR), errmsg("%d %d: System error.", rc, WIFEXITED(rc)))); } ereport(LOG, (errmsg("[status]:%d %d %d [command]:\"%s\"", rc, WIFEXITED(rc), WEXITSTATUS(rc), cmd.data))); @@ -2225,42 +2456,34 @@ static void get_obsfile(OBSInfo* obsInfo, const char* tmp_path) case OBS_FAILED_IO_ERROR: ereport(ERROR, - ((errcode(ERRCODE_SQL_ROUTINE_EXCEPTION), - errmsg("%d: Failed to access system files.", rc)))); + ((errcode(ERRCODE_SQL_ROUTINE_EXCEPTION), errmsg("%d: Failed to access system files.", rc)))); break; case OBS_FAILED_INCORRECT_REGION: - ereport(ERROR, - ((errcode(ERRCODE_SQL_ROUTINE_EXCEPTION), - errmsg("%d: Invalid argument, must set correct region.", rc)))); + ereport(ERROR, ((errcode(ERRCODE_SQL_ROUTINE_EXCEPTION), + errmsg("%d: Invalid argument, must set correct region.", rc)))); break; case OBS_FAILED_INVALID_ARGUMENT: - ereport(ERROR, - ((errcode(ERRCODE_SQL_ROUTINE_EXCEPTION), errmsg("%d: Parameters error.", rc)))); + ereport(ERROR, ((errcode(ERRCODE_SQL_ROUTINE_EXCEPTION), errmsg("%d: Parameters error.", rc)))); break; case OBS_FAILED_BASE_EXCEPTION: - ereport( - ERROR, ((errcode(ERRCODE_SQL_ROUTINE_EXCEPTION), errmsg("%d: System error.", rc)))); + ereport(ERROR, ((errcode(ERRCODE_SQL_ROUTINE_EXCEPTION), errmsg("%d: System error.", rc)))); break; case OBS_FAILED_CONNECT_TO_UDS_SERVER: - ereport(ERROR, - ((errcode(ERRCODE_CONNECTION_FAILURE), - errmsg("%d: Failed to connect to obs server.", rc)))); + ereport(ERROR, ((errcode(ERRCODE_CONNECTION_FAILURE), errmsg("%d: Failed to connect to obs server.", rc)))); break; case OBS_FAILED_DOWNLOAD_FROM_UDS_SERVER: ereport(ERROR, - ((errcode(ERRCODE_SQL_ROUTINE_EXCEPTION), - errmsg("%d: Failed to download from obs server.", rc)))); + ((errcode(ERRCODE_SQL_ROUTINE_EXCEPTION), errmsg("%d: Failed to download from obs server.", rc)))); break; case OBS_FAILED_TRY_AGAIN: ereport(ERROR, - ((errcode(ERRCODE_CONNECTION_FAILURE), - errmsg("%d: Obs server is busy. Try again later.", rc)))); + ((errcode(ERRCODE_CONNECTION_FAILURE), errmsg("%d: Obs server is busy. Try again later.", rc)))); break; default: @@ -2272,7 +2495,7 @@ static void get_obsfile(OBSInfo* obsInfo, const char* tmp_path) #endif } -static void execute_udstools_commnd(LibraryInfo* libInfo) +static void execute_udstools_commnd(LibraryInfo *libInfo) { /* As gs_om addjar command can only distrubte local file, * so we have to download obs file using udstools.py into $GAUSSHOME/share/postgresql/tmp/ first, @@ -2283,20 +2506,20 @@ static void execute_udstools_commnd(LibraryInfo* libInfo) char pathbuf[MAXPGPATH]; get_share_path(my_exec_path, pathbuf); join_path_components(pathbuf, pathbuf, "tmp"); - char* tmp_path = pstrdup(pathbuf); + char *tmp_path = pstrdup(pathbuf); get_obsfile(libInfo->obsInfo, tmp_path); - join_path_components(pathbuf, - pathbuf, - (last_dir_separator(libInfo->obsInfo->path) == NULL) ? libInfo->obsInfo->path - : (last_dir_separator(libInfo->obsInfo->path) + 1)); + join_path_components(pathbuf, pathbuf, + (last_dir_separator(libInfo->obsInfo->path) == NULL) + ? libInfo->obsInfo->path + : (last_dir_separator(libInfo->obsInfo->path) + 1)); libInfo->localpath = pstrdup(pathbuf); pfree_ext(tmp_path); } -static char* execute_gsom_javaudf_command(LibraryInfo* libInfo) +static char *execute_gsom_javaudf_command(LibraryInfo *libInfo) { StringInfoData cmd; initStringInfo(&cmd); @@ -2309,7 +2532,7 @@ static char* execute_gsom_javaudf_command(LibraryInfo* libInfo) } ereport(DEBUG1, (errmodule(MOD_UDF), errmsg("[gs_om]: %s", cmd.data))); - FILE* fp = NULL; + FILE *fp = NULL; char results[MAXPGPATH]; /* handle the gs_om command return message per line. */ StringInfoData ret; initStringInfo(&ret); @@ -2328,7 +2551,7 @@ static char* execute_gsom_javaudf_command(LibraryInfo* libInfo) * or parse the expected results and stored in 'ret'. */ while (fgets(results, sizeof(results), fp) != NULL) { - char* tmp = NULL; + char *tmp = NULL; if ((tmp = strstr(results, "command not found")) != NULL) { /* get rid of the last \n */ tmp[strlen(tmp) - 1] = '\0'; @@ -2402,21 +2625,22 @@ static char* execute_gsom_javaudf_command(LibraryInfo* libInfo) ereport(DEBUG1, (errmodule(MOD_UDF), errmsg("[rm_tmp]: %s", cmd.data))); int rc = system(cmd.data); if (rc != 0) { - ereport(LOG, - (errcode(ERRCODE_SYSTEM_ERROR), - errmsg("[WARNING] remove the tmp file downloaded from the OBS Server failed, [command] %s", cmd.data))); + ereport(LOG, (errcode(ERRCODE_SYSTEM_ERROR), + errmsg("[WARNING] remove the tmp file downloaded from the OBS Server failed, [command] %s", + cmd.data))); } } if (isFailed) { - ereport(LOG, (errcode(ERRCODE_SQL_ROUTINE_EXCEPTION), errmsg("execute command failed, [command] %s", cmd.data))); - ereport(ERROR, (errmodule(MOD_UDF), - (errcode(ERRCODE_SQL_ROUTINE_EXCEPTION), errmsg("execute command failed")))); + ereport(LOG, + (errcode(ERRCODE_SQL_ROUTINE_EXCEPTION), errmsg("execute command failed, [command] %s", cmd.data))); + ereport(ERROR, + (errmodule(MOD_UDF), (errcode(ERRCODE_SQL_ROUTINE_EXCEPTION), errmsg("execute command failed")))); } if (isError) { ereport(ERROR, (errmodule(MOD_UDF), errcode(ERRCODE_SQL_ROUTINE_EXCEPTION), - errmsg("%s", (ret.len > 0) ? ret.data : "Cannot get detailed execution results."))); + errmsg("%s", (ret.len > 0) ? ret.data : "Cannot get detailed execution results."))); } /* the execution succeed, we return \"t\" as a successful operation */ @@ -2439,17 +2663,18 @@ PG_FUNCTION_INFO_V1(gs_extend_library); Datum gs_extend_library(PG_FUNCTION_ARGS) { #ifndef ENABLE_MULTIPLE_NODES - ereport(ERROR, (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), (errmsg("Unsupport feature"), - errdetail("gs_extend_library is not supported for centralize deployment"), - errcause("The function is not implemented."), erraction("Do not use this function.")))); + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + (errmsg("Unsupport feature"), errdetail("gs_extend_library is not supported for centralize deployment"), + errcause("The function is not implemented."), erraction("Do not use this function.")))); #endif if (!superuser()) { ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), - (errmsg("must be system admin to use the gs_extend_library function")))); + (errmsg("must be system admin to use the gs_extend_library function")))); } - LibraryInfo* libInfo = (LibraryInfo*)palloc0(sizeof(LibraryInfo)); + LibraryInfo *libInfo = (LibraryInfo *)palloc0(sizeof(LibraryInfo)); libInfo->option = PG_GETARG_CSTRING(0); libInfo->operation = PG_GETARG_CSTRING(1); libInfo->destpath = NULL; @@ -2457,7 +2682,7 @@ Datum gs_extend_library(PG_FUNCTION_ARGS) libInfo->isSourceLocal = true; libInfo->obsInfo = NULL; - char* result = NULL; + char *result = NULL; ExtendLibraryStatus status = EXTENDLIB_CHECK_OPTION; bool done = false; while (!done) { @@ -2474,9 +2699,8 @@ Datum gs_extend_library(PG_FUNCTION_ARGS) break; } - ereport(ERROR, (errmodule(MOD_UDF), - errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("Invaid argument: must at least set libraryname"))); + ereport(ERROR, (errmodule(MOD_UDF), errcode(ERRCODE_INVALID_PARAMETER_VALUE), + errmsg("Invaid argument: must at least set libraryname"))); } parse_extend_library_operation(libInfo); @@ -2533,11 +2757,11 @@ Datum gs_extend_library(PG_FUNCTION_ARGS) * @in extension: user-defined obs file name extension * @out: $GAUSSHOME/share/postgresql/tmp/ */ -char* get_obsfile_local(char* pathname, const char* basename, const char* extension) +char *get_obsfile_local(char *pathname, const char *basename, const char *extension) { - char* fullname = NULL; - char* tmp_path = NULL; - OBSInfo* obsinfo = NULL; + char *fullname = NULL; + char *tmp_path = NULL; + OBSInfo *obsinfo = NULL; StringInfoData tmp; initStringInfo(&tmp); @@ -2550,7 +2774,7 @@ char* get_obsfile_local(char* pathname, const char* basename, const char* extens tmp_path = parse_value(pathname); if (tmp_path == NULL) ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("Invalid argument: must set correct bucket and obs file path."))); + errmsg("Invalid argument: must set correct bucket and obs file path."))); appendStringInfo(&tmp, "%s", tmp_path); if (tmp_path[strlen(tmp_path) - 1] != '/') diff --git a/src/gausskernel/process/postmaster/pagerepair.cpp b/src/gausskernel/process/postmaster/pagerepair.cpp index 68ebc65e7..099d20caf 100644 --- a/src/gausskernel/process/postmaster/pagerepair.cpp +++ b/src/gausskernel/process/postmaster/pagerepair.cpp @@ -79,11 +79,27 @@ static void PushBadFileToRemoteHashTbl(RepairFileKey key); (key1).relfilenode.opt == (key2).relfilenode.opt && (key1).forknum == (key2).forknum && \ (key1).blocknum == (key2).blocknum) -#define NOT_SUPPORT_PAGE_REPAIR \ +#define NOT_SUPPORT_PAGE_REPAIR \ (g_instance.attr.attr_common.cluster_run_mode == RUN_MODE_STANDBY || \ g_instance.attr.attr_common.stream_cluster_run_mode == RUN_MODE_STANDBY || \ t_thrd.xlog_cxt.is_hadr_main_standby || t_thrd.xlog_cxt.is_cascade_standby) - +/* + * 功能:检查修复块的LSN(日志序列号) + * + * 参数列表: + * xlogreader:XLogReaderState结构体指针,用于读取XLOG记录 + * key:RepairBlockKey结构体,包含了要检查的块的关键信息 + * page_old_lsn:块的旧LSN + * last_lsn:用于返回块的最新LSN + * + * 返回值: + * 如果检查成功,返回CHECK_SUCCESS; + * 如果需要继续检查其他块,返回NEED_CONTINUE_CHECK; + * 如果检查失败,返回CHECK_FAIL。 + * + * 注意: + * 该函数用于检查修复块的LSN,通过比较块的最新LSN与旧LSN来判断块是否需要修复。 + */ int CheckBlockLsn(XLogReaderState *xlogreader, RepairBlockKey key, XLogRecPtr page_old_lsn, XLogRecPtr *last_lsn) { RepairBlockKey temp_key = {0}; @@ -97,44 +113,41 @@ int CheckBlockLsn(XLogReaderState *xlogreader, RepairBlockKey key, XLogRecPtr pa getlsn = XLogRecGetBlockLastLsn(xlogreader, block_id, last_lsn); Assert(getlsn); if (XLogRecPtrIsInvalid(*last_lsn)) { - ereport(LOG, - (errmsg("check the repair page successfully, last_lsn is 0," - "the page %u/%u/%u bucketnode %d, forknum is %u, blocknum is %u", - key.relfilenode.spcNode, key.relfilenode.dbNode, key.relfilenode.relNode, - key.relfilenode.bucketNode, key.forknum, key.blocknum))); + ereport(LOG, (errmsg("check the repair page successfully, last_lsn is 0," + "the page %u/%u/%u bucketnode %d, forknum is %u, blocknum is %u", + key.relfilenode.spcNode, key.relfilenode.dbNode, key.relfilenode.relNode, + key.relfilenode.bucketNode, key.forknum, key.blocknum))); return CHECK_SUCCESS; } /* if the xlog record last_lsn equal the current standby page lsn, means found a complete xlog chain */ if (*last_lsn == page_old_lsn) { - ereport(LOG, - (errmsg("check the repair page successfully, the page %u/%u/%u bucketnode %d, " - "forknum is %u, blocknum is %u", - key.relfilenode.spcNode, key.relfilenode.dbNode, key.relfilenode.relNode, - key.relfilenode.bucketNode, key.forknum, key.blocknum))); + ereport(LOG, (errmsg("check the repair page successfully, the page %u/%u/%u bucketnode %d, " + "forknum is %u, blocknum is %u", + key.relfilenode.spcNode, key.relfilenode.dbNode, key.relfilenode.relNode, + key.relfilenode.bucketNode, key.forknum, key.blocknum))); return CHECK_SUCCESS; } /* if the xlog record last lsn */ if (*last_lsn < page_old_lsn) { ereport(WARNING, - (errmsg("check the repair page, lsn not match, page_old_lsn is %X/%X, last_lsn is %X/%X, " - "could not repair the page %u/%u/%u bucketnode %d, forknum is %u, blocknum is %u", - (uint32)(page_old_lsn >> XLOG_LSN_SWAP), (uint32)page_old_lsn, - (uint32)(*last_lsn >> XLOG_LSN_SWAP), (uint32)*last_lsn, - key.relfilenode.spcNode, key.relfilenode.dbNode, key.relfilenode.relNode, - key.relfilenode.bucketNode, key.forknum, key.blocknum))); + (errmsg("check the repair page, lsn not match, page_old_lsn is %X/%X, last_lsn is %X/%X, " + "could not repair the page %u/%u/%u bucketnode %d, forknum is %u, blocknum is %u", + (uint32)(page_old_lsn >> XLOG_LSN_SWAP), (uint32)page_old_lsn, + (uint32)(*last_lsn >> XLOG_LSN_SWAP), (uint32)*last_lsn, key.relfilenode.spcNode, + key.relfilenode.dbNode, key.relfilenode.relNode, key.relfilenode.bucketNode, + key.forknum, key.blocknum))); return CHECK_FAIL; } } } if (!page_found) { - ereport(WARNING, - (errmsg("check the repair page, not get page info, page_old_lsn is %X/%X, last_lsn is %X/%X, " - "could not repair the page %u/%u/%u bucketnode %d, forknum is %u, blocknum is %u", - (uint32)(page_old_lsn >> XLOG_LSN_SWAP), (uint32)page_old_lsn, - (uint32)(*last_lsn >> XLOG_LSN_SWAP), (uint32)*last_lsn, - key.relfilenode.spcNode, key.relfilenode.dbNode, key.relfilenode.relNode, - key.relfilenode.bucketNode, key.forknum, key.blocknum))); + ereport(WARNING, (errmsg("check the repair page, not get page info, page_old_lsn is %X/%X, last_lsn is %X/%X, " + "could not repair the page %u/%u/%u bucketnode %d, forknum is %u, blocknum is %u", + (uint32)(page_old_lsn >> XLOG_LSN_SWAP), (uint32)page_old_lsn, + (uint32)(*last_lsn >> XLOG_LSN_SWAP), (uint32)*last_lsn, key.relfilenode.spcNode, + key.relfilenode.dbNode, key.relfilenode.relNode, key.relfilenode.bucketNode, + key.forknum, key.blocknum))); return CHECK_FAIL; } return NEED_CONTINUE_CHECK; @@ -143,6 +156,21 @@ int CheckBlockLsn(XLogReaderState *xlogreader, RepairBlockKey key, XLogRecPtr pa /* CheckPrimaryPageLSN * Check whether the data page of the primary DN forms a complete xlog chain with the page of the standby DN. */ +/* + * 功能:检查主块的LSN(日志序列号) + * + * 参数列表: + * page_old_lsn:块的旧LSN + * page_new_lsn:块的新LSN + * key:RepairBlockKey结构体,包含了块的关键信息 + * + * 返回值: + * 如果检查成功,返回true; + * 如果检查失败,返回false。 + * + * 注意: + * 该函数用于检查主块的LSN,通过解析XLOG记录来判断块是否需要修复。 + */ bool CheckPrimaryPageLSN(XLogRecPtr page_old_lsn, XLogRecPtr page_new_lsn, RepairBlockKey key) { XLogRecPtr prev_lsn = InvalidXLogRecPtr; @@ -159,9 +187,8 @@ bool CheckPrimaryPageLSN(XLogRecPtr page_old_lsn, XLogRecPtr page_new_lsn, Repai xlogreader = XLogReaderAllocate(&XLogPageRead, &readprivate); if (xlogreader == NULL) { - ereport(ERROR, - (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("out of memory"), - errdetail("Failed while allocating an XLog reading processor for pagerepair thread"))); + ereport(ERROR, (errcode(ERRCODE_OUT_OF_MEMORY), errmsg("out of memory"), + errdetail("Failed while allocating an XLog reading processor for pagerepair thread"))); } xlogreader->system_identifier = t_thrd.shemem_ptr_cxt.ControlFile->system_identifier; @@ -177,13 +204,12 @@ bool CheckPrimaryPageLSN(XLogRecPtr page_old_lsn, XLogRecPtr page_new_lsn, Repai } record = XLogReadRecord(xlogreader, last_lsn, &errormsg); if (record == NULL) { - ereport(WARNING, - (errmsg("check the repair page, page_old_lsn is %X/%X, could not get the xlog %X/%X " - "could not repair the page %u/%u/%u bucketnode %d, forknum is %u, blocknum is %u", - (uint32)(page_old_lsn >> XLOG_LSN_SWAP), (uint32)page_old_lsn, - (uint32)(last_lsn >> XLOG_LSN_SWAP), (uint32)last_lsn, - key.relfilenode.spcNode, key.relfilenode.dbNode, key.relfilenode.relNode, - key.relfilenode.bucketNode, key.forknum, key.blocknum))); + ereport(WARNING, (errmsg("check the repair page, page_old_lsn is %X/%X, could not get the xlog %X/%X " + "could not repair the page %u/%u/%u bucketnode %d, forknum is %u, blocknum is %u", + (uint32)(page_old_lsn >> XLOG_LSN_SWAP), (uint32)page_old_lsn, + (uint32)(last_lsn >> XLOG_LSN_SWAP), (uint32)last_lsn, key.relfilenode.spcNode, + key.relfilenode.dbNode, key.relfilenode.relNode, key.relfilenode.bucketNode, + key.forknum, key.blocknum))); return false; } @@ -199,18 +225,22 @@ bool CheckPrimaryPageLSN(XLogRecPtr page_old_lsn, XLogRecPtr page_new_lsn, Repai } ereport(WARNING, - (errmsg("check the repair page, could not found the page info from the xlog " - "could not repair the page, page old lsn is %X/%X, last lsn is %X/%X, page new lsn is %X/%X" - "page info is %u/%u/%u bucketnode %d, forknum is %u, blocknum is %u", - (uint32)(page_old_lsn >> XLOG_LSN_SWAP), (uint32)page_old_lsn, - (uint32)(last_lsn >> XLOG_LSN_SWAP), (uint32)last_lsn, - (uint32)(page_new_lsn >> XLOG_LSN_SWAP), (uint32)page_new_lsn, - key.relfilenode.spcNode, key.relfilenode.dbNode, key.relfilenode.relNode, - key.relfilenode.bucketNode, key.forknum, key.blocknum))); + (errmsg("check the repair page, could not found the page info from the xlog " + "could not repair the page, page old lsn is %X/%X, last lsn is %X/%X, page new lsn is %X/%X" + "page info is %u/%u/%u bucketnode %d, forknum is %u, blocknum is %u", + (uint32)(page_old_lsn >> XLOG_LSN_SWAP), (uint32)page_old_lsn, (uint32)(last_lsn >> XLOG_LSN_SWAP), + (uint32)last_lsn, (uint32)(page_new_lsn >> XLOG_LSN_SWAP), (uint32)page_new_lsn, + key.relfilenode.spcNode, key.relfilenode.dbNode, key.relfilenode.relNode, + key.relfilenode.bucketNode, key.forknum, key.blocknum))); return false; } - +/* + * 功能:初始化页面修复哈希表 + * + * 注意: + * 该函数用于初始化页面修复哈希表,包括分配锁和创建哈希表。 + */ void PageRepairHashTblInit(void) { HASHCTL ctl; @@ -227,8 +257,8 @@ void PageRepairHashTblInit(void) ctl.entrysize = sizeof(RepairBlockEntry); ctl.hash = tag_hash; ctl.hcxt = INSTANCE_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_STORAGE); - g_instance.repair_cxt.page_repair_hashtbl = hash_create("Page Repair Hash Table", MAX_REPAIR_PAGE_NUM, &ctl, - HASH_ELEM | HASH_FUNCTION |HASH_CONTEXT); + g_instance.repair_cxt.page_repair_hashtbl = + hash_create("Page Repair Hash Table", MAX_REPAIR_PAGE_NUM, &ctl, HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT); if (!g_instance.repair_cxt.page_repair_hashtbl) ereport(FATAL, (errmsg("could not initialize page repair Hash table"))); @@ -236,7 +266,12 @@ void PageRepairHashTblInit(void) return; } - +/* + * 功能:清理页面修复线程的内存 + * + * 注意: + * 该函数用于清理页面修复线程使用的内存,包括销毁哈希表。 + */ void ClearPageRepairTheadMem(void) { if (g_instance.repair_cxt.page_repair_hashtbl != NULL) { @@ -256,6 +291,16 @@ void ClearPageRepairTheadMem(void) * After remote read, copy the page to hash table and update the page_new_lsn * of page repair hash table, means that the page is correct. */ +/* + * 功能:将页面内容复制到修复哈希表中的条目 + * + * 参数列表: + * entry:指向修复哈希表中的条目的指针 + * page_content:指向页面内容的指针 + * + * 注意: + * 该函数用于将页面内容复制到修复哈希表中的指定条目,并根据错误类型更新条目的状态。 + */ void CopyPageToRepairHashTbl(RepairBlockEntry *entry, char *page_content) { XLogRecPtr page_lsn = PageGetLSN(page_content); @@ -272,7 +317,15 @@ void CopyPageToRepairHashTbl(RepairBlockEntry *entry, char *page_content) } return; } - +/* + * 功能:检查页面LSN并根据需要修复页面 + * + * 参数列表: + * key:修复页面的标识键 + * + * 注意: + * 该函数用于检查页面的LSN,如果需要修复,则执行修复操作。 + */ void CheckPageLSN(RepairBlockKey key) { HTAB *repair_hash = g_instance.repair_cxt.page_repair_hashtbl; @@ -284,7 +337,7 @@ void CheckPageLSN(RepairBlockKey key) LWLockAcquire(g_instance.repair_cxt.page_repair_hashtbl_lock, LW_EXCLUSIVE); - entry = (RepairBlockEntry*)hash_search(repair_hash, &(key), HASH_FIND, &found); + entry = (RepairBlockEntry *)hash_search(repair_hash, &(key), HASH_FIND, &found); if (found) { page_old_lsn = entry->page_old_lsn; page_lsn = PageGetLSN(entry->page_content); @@ -298,7 +351,7 @@ void CheckPageLSN(RepairBlockKey key) bool check = CheckPrimaryPageLSN(page_old_lsn, page_lsn, key); if (check) { LWLockAcquire(g_instance.repair_cxt.page_repair_hashtbl_lock, LW_EXCLUSIVE); - entry = (RepairBlockEntry*)hash_search(repair_hash, &(key), HASH_FIND, &found); + entry = (RepairBlockEntry *)hash_search(repair_hash, &(key), HASH_FIND, &found); if (found) { entry->page_new_lsn = page_lsn; entry->page_state = WAIT_REPAIR; @@ -306,21 +359,31 @@ void CheckPageLSN(RepairBlockKey key) } LWLockRelease(g_instance.repair_cxt.page_repair_hashtbl_lock); } else { - ereport(PANIC, - (errmsg("check the repair page lsn failed, could not repair the page, " - "page old lsn is %X/%X, primary lsn is %X/%X, " - "page info is %u/%u/%u bucketnode %d, forknum is %u, blocknum is %u", - (uint32)(page_old_lsn >> XLOG_LSN_SWAP), (uint32)page_old_lsn, - (uint32)(page_lsn >> XLOG_LSN_SWAP), (uint32)page_lsn, - key.relfilenode.spcNode, key.relfilenode.dbNode, key.relfilenode.relNode, - key.relfilenode.bucketNode, key.forknum, key.blocknum))); + ereport(PANIC, (errmsg("check the repair page lsn failed, could not repair the page, " + "page old lsn is %X/%X, primary lsn is %X/%X, " + "page info is %u/%u/%u bucketnode %d, forknum is %u, blocknum is %u", + (uint32)(page_old_lsn >> XLOG_LSN_SWAP), (uint32)page_old_lsn, + (uint32)(page_lsn >> XLOG_LSN_SWAP), (uint32)page_lsn, key.relfilenode.spcNode, + key.relfilenode.dbNode, key.relfilenode.relNode, key.relfilenode.bucketNode, + key.forknum, key.blocknum))); } } else { LWLockRelease(g_instance.repair_cxt.page_repair_hashtbl_lock); } return; } - +/* + * 功能:通过远程读取获取文件的大小,不处理错误 + * + * 参数列表: + * key:修复文件的标识键 + * size:存储文件大小的指针 + * + * 返回值:远程读取的结果代码 + * + * 注意: + * 该函数用于通过远程读取获取文件的大小,不处理错误。 + */ int RemoteReadFileSizeNoError(RepairFileKey *key, int64 *size) { /* get remote address */ @@ -330,20 +393,25 @@ int RemoteReadFileSizeNoError(RepairFileKey *key, int64 *size) GetRemoteReadAddress(remote_address1, remote_address2, MAXPGPATH); char *remote_address = remote_address1; + /* 如果远程地址为空或以冒号开头,表示远程不可用 */ if (remote_address[0] == '\0' || remote_address[0] == ':') { ereport(DEBUG1, (errmodule(MOD_REMOTE), errmsg("remote not available"))); return REMOTE_READ_IP_NOT_EXIST; } + /* 记录日志,指示正在远程读取文件的大小 */ ereport(LOG, (errmodule(MOD_REMOTE), errmsg("remote read file size, file %s from %s", - relpathperm(key->relfilenode, key->forknum), - remote_address))); + relpathperm(key->relfilenode, key->forknum), remote_address))); + /* 准备远程读取文件的键 */ RemoteReadFileKey read_key; read_key.relfilenode = key->relfilenode; read_key.forknum = key->forknum; read_key.blockstart = 0; + /* 启动远程读取的性能分析 */ PROFILING_REMOTE_START(); + + /* 调用远程获取文件大小的函数 */ int retCode = RemoteGetFileSize(remote_address, &read_key, InvalidXLogRecPtr, size, timeout); /* return file size + primary lsn */ PROFILING_REMOTE_END_READ(sizeof(uint64) + sizeof(uint64), (retCode == REMOTE_READ_OK)); @@ -353,68 +421,134 @@ int RemoteReadFileSizeNoError(RepairFileKey *key, int64 *size) /* RemoteReadFile * standby dn use this function repair file. */ -int RemoteReadFileNoError(RemoteReadFileKey *key, char *buf, XLogRecPtr lsn, uint32 size, - XLogRecPtr *remote_lsn, uint32 *remote_size) +/* + * 功能:通过远程读取文件内容,不处理错误 + * + * 参数列表: + * key:远程读取文件的标识键 + * buf:存储文件内容的缓冲区 + * lsn:请求的LSN(日志序列号) + * size:请求的文件内容大小 + * remote_lsn:实际获取的LSN + * remote_size:实际获取的文件内容大小 + * + * 返回值:远程读取的结果代码 + * + * 注意: + * 该函数用于通过远程读取文件内容,不处理错误。 + */ +int RemoteReadFileNoError(RemoteReadFileKey *key, char *buf, XLogRecPtr lsn, uint32 size, XLogRecPtr *remote_lsn, + uint32 *remote_size) { - /* get remote address */ - char remote_address1[MAXPGPATH] = {0}; /* remote_address1[0] = '\0'; */ - char remote_address2[MAXPGPATH] = {0}; /* remote_address2[0] = '\0'; */ + /* 获取远程地址 */ + char remote_address1[MAXPGPATH] = {0}; + char remote_address2[MAXPGPATH] = {0}; char *remote_address = NULL; GetRemoteReadAddress(remote_address1, remote_address2, MAXPGPATH); remote_address = remote_address1; int timeout = 0; + /* 如果远程地址为空或以冒号开头,表示远程不可用 */ if (remote_address[0] == '\0' || remote_address[0] == ':') { ereport(WARNING, (errcode(ERRCODE_IO_ERROR), errmodule(MOD_REMOTE), errmsg("remote not available"))); return REMOTE_READ_IP_NOT_EXIST; } - ereport(LOG, (errmodule(MOD_REMOTE), - errmsg("remote read file, file %s from %s, block start is %u", - relpathperm(key->relfilenode, key->forknum), remote_address, key->blockstart))); + /* 记录日志,指示正在远程读取文件内容 */ + ereport(LOG, (errmodule(MOD_REMOTE), + errmsg("remote read file, file %s from %s, block start is %u", + relpathperm(key->relfilenode, key->forknum), remote_address, key->blockstart))); + + /* 启动远程读取的性能分析 */ PROFILING_REMOTE_START(); + + /* 调用远程获取文件内容的函数 */ int retCode = RemoteGetFile(remote_address, key, lsn, size, buf, remote_lsn, remote_size, timeout); + + /* 结束远程读取的性能分析,返回实际获取的文件内容大小 */ PROFILING_REMOTE_END_READ(size, (retCode == REMOTE_READ_OK)); return retCode; } +/* + * 功能:通过远程读取数据块内容,不处理错误 + * + * 参数列表: + * key:远程读取数据块的标识键 + * buf:存储数据块内容的缓冲区 + * lsn:请求的LSN(日志序列号) + * pblk:物理块信息(可选) + * + * 返回值:远程读取的结果代码 + * + * 注意: + * 该函数用于通过远程读取数据块内容,不处理错误。 + */ int RemoteReadBlockNoError(RepairBlockKey *key, char *buf, XLogRecPtr lsn, const XLogPhyBlock *pblk) { - /* get remote address */ - char remote_address1[MAXPGPATH] = {0}; /* remote_address1[0] = '\0'; */ - char remote_address2[MAXPGPATH] = {0}; /* remote_address2[0] = '\0'; */ + /* 获取远程地址 */ + char remote_address1[MAXPGPATH] = {0}; + char remote_address2[MAXPGPATH] = {0}; + char *remote_address = NULL; GetRemoteReadAddress(remote_address1, remote_address2, MAXPGPATH); - char *remote_address = remote_address1; + remote_address = remote_address1; + + /* 如果远程地址为空或以冒号开头,表示远程不可用 */ if (remote_address[0] == '\0' || remote_address[0] == ':') { ereport(DEBUG1, (errmodule(MOD_REMOTE), errmsg("remote not available"))); return REMOTE_READ_IP_NOT_EXIST; } + + /* 记录日志,指示正在远程读取数据块内容 */ if (pblk != NULL) { ereport(LOG, (errmodule(MOD_REMOTE), errmsg("remote read page, file %s block %u (pblk %u/%d) from %s", - relpathperm(key->relfilenode, key->forknum), key->blocknum, pblk->relNode, pblk->block, remote_address))); + relpathperm(key->relfilenode, key->forknum), key->blocknum, + pblk->relNode, pblk->block, remote_address))); } else { - ereport(LOG, (errmodule(MOD_REMOTE), errmsg("remote read page, file %s block %u from %s", - relpathperm(key->relfilenode, key->forknum), key->blocknum, remote_address))); + ereport(LOG, (errmodule(MOD_REMOTE), + errmsg("remote read page, file %s block %u from %s", relpathperm(key->relfilenode, key->forknum), + key->blocknum, remote_address))); } + /* 设置远程读取的超时时间 */ const int TIMEOUT = 60; + + /* 启动远程读取的性能分析 */ PROFILING_REMOTE_START(); + + /* 调用远程获取数据块内容的函数 */ int retCode = RemoteGetPage(remote_address, key, BLCKSZ, lsn, buf, pblk, TIMEOUT); + + /* 结束远程读取的性能分析,返回实际获取的数据块内容大小 */ PROFILING_REMOTE_END_READ(BLCKSZ, (retCode == REMOTE_READ_OK)); return retCode; } +/* + * 功能:修复数据块,根据需要从远程读取数据块内容 + * + * 参数列表: + * entry:数据块修复条目 + * page:存储数据块内容的缓冲区 + * + * 注意: + * 该函数用于修复数据块,根据需要从远程读取数据块内容。 + */ static void RepairPage(RepairBlockEntry *entry, char *page) { int retCode = 0; + /* 如果数据块包含物理块信息 */ if (entry->pblk.relNode != InvalidOid) { retCode = RemoteReadBlockNoError(&entry->key, page, entry->page_old_lsn, &entry->pblk); } else { + /* 否则,只读取数据块内容 */ retCode = RemoteReadBlockNoError(&entry->key, page, entry->page_old_lsn, NULL); } + + /* 如果远程读取成功,则将数据块内容复制到修复哈希表中 */ if (retCode == REMOTE_READ_OK) { CopyPageToRepairHashTbl(entry, page); } @@ -423,6 +557,12 @@ static void RepairPage(RepairBlockEntry *entry, char *page) } const int MAX_CHECK_LSN_NUM = 100; +/* + * 功能:顺序远程读取数据块并进行修复 + * + * 注意: + * 该函数用于按顺序迭代修复哈希表中的数据块,根据需要远程读取数据块内容并进行修复。 + */ static void SeqRemoteReadPage() { HTAB *repair_hash = g_instance.repair_cxt.page_repair_hashtbl; @@ -436,29 +576,39 @@ static void SeqRemoteReadPage() LWLockAcquire(g_instance.repair_cxt.page_repair_hashtbl_lock, LW_EXCLUSIVE); + /* 初始化哈希表迭代器 */ hash_seq_init(&status, repair_hash); + + /* 遍历修复哈希表中的数据块 */ while ((entry = (RepairBlockEntry *)hash_seq_search(&status)) != NULL) { need_repair_num++; + switch (entry->page_state) { case WAIT_REMOTE_READ: + /* 修复数据块并根据需要进行LSN检查 */ RepairPage(entry, page); + if (entry->error_type == LSN_CHECK_FAIL && entry->page_state == WAIT_LSN_CHECK && check_lsn_num < MAX_CHECK_LSN_NUM) { lsncheck[check_lsn_num] = entry->key; check_lsn_num++; } + if (entry->page_state == WAIT_REPAIR) { + /* 如果需要修复,则向恢复线程发送信号 */ (void)gs_signal_send(entry->recovery_tid, SIGUSR1); repair_num++; } break; case WAIT_LSN_CHECK: if (check_lsn_num < MAX_CHECK_LSN_NUM) { + /* 将需要进行LSN检查的数据块加入检查队列 */ lsncheck[check_lsn_num] = entry->key; check_lsn_num++; } break; case WAIT_REPAIR: + /* 如果需要修复,则向恢复线程发送信号 */ repair_num++; (void)gs_signal_send(entry->recovery_tid, SIGUSR1); break; @@ -469,56 +619,76 @@ static void SeqRemoteReadPage() LWLockRelease(g_instance.repair_cxt.page_repair_hashtbl_lock); + /* 对需要进行LSN检查的数据块执行检查 */ for (int i = 0; i < check_lsn_num; i++) { RepairBlockKey temp = lsncheck[i]; CheckPageLSN(temp); } + + /* 如果所有需要修复的数据块都已修复完毕,设置修复请求标志为false */ if (need_repair_num == repair_num) { t_thrd.pagerepair_cxt.page_repair_requested = false; } + return; } +/* + * 功能:处理页面修复线程的中断请求 + * + * 注意: + * 该函数用于处理来自操作系统或其他进程的中断请求,包括SIGHUP和shutdown请求。 + */ static void PageRepairHandleInterrupts(void) { + /* 检查是否收到了SIGHUP信号 */ if (t_thrd.pagerepair_cxt.got_SIGHUP) { t_thrd.pagerepair_cxt.got_SIGHUP = false; + + /* 重新加载配置文件 */ ProcessConfigFile(PGC_SIGHUP); } + /* 检查是否收到了shutdown请求且启动进程已经退出 */ if (t_thrd.pagerepair_cxt.shutdown_requested && g_instance.pid_cxt.StartupPID == 0) { ereport(LOG, (errmodule(MOD_REDO), errmsg("pagerepair thread shut down"))); + /* 设置ExitOnAnyError标志为true,然后退出线程 */ u_sess->attr.attr_common.ExitOnAnyError = true; proc_exit(0); } } +/* + * 功能:页面修复线程的主函数 + */ void PageRepairMain(void) { MemoryContext pagerepair_context; char name[MAX_THREAD_NAME_LEN] = {0}; uint32 rc = 0; + /* 设置线程角色为PAGEREPAIR_THREAD */ t_thrd.role = PAGEREPAIR_THREAD; + /* 设置页面修复线程的信号处理钩子 */ SetupPageRepairSignalHook(); /* We allow SIGQUIT (quickdie) at all times */ (void)sigdelset(&t_thrd.libpq_cxt.BlockSig, SIGQUIT); + /* 记录页面修复线程已启动 */ ereport(LOG, (errmodule(MOD_REDO), errmsg("pagerepair started"))); /* * Create a resource owner to keep track of our resources (currently only * buffer pins). */ - errno_t err_rc = snprintf_s( - name, MAX_THREAD_NAME_LEN, MAX_THREAD_NAME_LEN - 1, "%s", "PageRepair"); + errno_t err_rc = snprintf_s(name, MAX_THREAD_NAME_LEN, MAX_THREAD_NAME_LEN - 1, "%s", "PageRepair"); securec_check_ss(err_rc, "", ""); - t_thrd.utils_cxt.CurrentResourceOwner = ResourceOwnerCreate(NULL, name, - THREAD_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_STORAGE)); + t_thrd.utils_cxt.CurrentResourceOwner = + ResourceOwnerCreate(NULL, name, THREAD_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_STORAGE)); /* * Create a memory context that we will do all our work in. We do this so @@ -526,8 +696,8 @@ void PageRepairMain(void) * possible memory leaks. Formerly this code just ran in * TopMemoryContext, but resetting that would be a really bad idea. */ - pagerepair_context = AllocSetContextCreate( - TopMemoryContext, name, ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); + pagerepair_context = AllocSetContextCreate(TopMemoryContext, name, ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); (void)MemoryContextSwitchTo(pagerepair_context); /* @@ -536,12 +706,14 @@ void PageRepairMain(void) gs_signal_setmask(&t_thrd.libpq_cxt.UnBlockSig, NULL); (void)gs_signal_unblock_sigusr2(); + /* 报告线程的应用程序名称和状态 */ pgstat_report_appname("PageRepair"); pgstat_report_activity(STATE_IDLE, NULL); /* * Loop forever */ + /* 进入无限循环,处理页面修复任务 */ for (;;) { PageRepairHandleInterrupts(); pgstat_report_activity(STATE_IDLE, NULL); @@ -552,6 +724,7 @@ void PageRepairMain(void) ResetLatch(&t_thrd.proc->procLatch); pgstat_report_activity(STATE_RUNNING, NULL); + /* 如果没有收到shutdown请求,则进行远程读取页面和文件操作 */ if (!t_thrd.pagerepair_cxt.shutdown_requested) { SeqRemoteReadPage(); SeqRemoteReadFile(); @@ -559,73 +732,92 @@ void PageRepairMain(void) } } +/* + * 功能:设置页面修复线程的信号处理钩子 + */ static void SetupPageRepairSignalHook(void) { /* - * Reset some signals that are accepted by postmaster but not here + * 重置一些信号,这些信号被postmaster接受,但在此处不需要 */ - (void)gspqsignal(SIGHUP, PageRepairSigHupHandler); - (void)gspqsignal(SIGINT, SIG_IGN); - (void)gspqsignal(SIGTERM, PageRepairShutDownHandler); - (void)gspqsignal(SIGQUIT, PageRepairQuickDie); /* hard crash time */ - (void)gspqsignal(SIGALRM, SIG_IGN); - (void)gspqsignal(SIGPIPE, SIG_IGN); - (void)gspqsignal(SIGUSR1, PageRepairSigUsr1Handler); - (void)gspqsignal(SIGUSR2, PageRepairSigUsr2Handler); + (void)gspqsignal(SIGHUP, PageRepairSigHupHandler); /* 重新读取配置文件 */ + (void)gspqsignal(SIGINT, SIG_IGN); /* 忽略中断信号 */ + (void)gspqsignal(SIGTERM, PageRepairShutDownHandler); /* 关闭线程 */ + (void)gspqsignal(SIGQUIT, PageRepairQuickDie); /* 强制崩溃 */ + (void)gspqsignal(SIGALRM, SIG_IGN); /* 忽略闹钟信号 */ + (void)gspqsignal(SIGPIPE, SIG_IGN); /* 忽略管道信号 */ + (void)gspqsignal(SIGUSR1, PageRepairSigUsr1Handler); /* 用户自定义信号1 */ + (void)gspqsignal(SIGUSR2, PageRepairSigUsr2Handler); /* 用户自定义信号2 */ /* - * Reset some signals that are accepted by postmaster but not here + * 重置一些信号,这些信号被postmaster接受,但在此处不需要 */ - (void)gspqsignal(SIGCHLD, SIG_DFL); - (void)gspqsignal(SIGTTIN, SIG_DFL); - (void)gspqsignal(SIGTTOU, SIG_DFL); - (void)gspqsignal(SIGCONT, SIG_DFL); - (void)gspqsignal(SIGWINCH, SIG_DFL); + (void)gspqsignal(SIGCHLD, SIG_DFL); /* 子进程状态改变 */ + (void)gspqsignal(SIGTTIN, SIG_DFL); /* 后台进程组中的子进程请求读 */ + (void)gspqsignal(SIGTTOU, SIG_DFL); /* 后台进程组中的子进程请求写 */ + (void)gspqsignal(SIGCONT, SIG_DFL); /* 继续停止的进程 */ + (void)gspqsignal(SIGWINCH, SIG_DFL); /* 窗口尺寸调整 */ } +/* + * 功能:处理SIGUSR1信号的处理函数 + */ static void PageRepairSigUsr1Handler(SIGNAL_ARGS) { int save_errno = errno; + /* 设置页面修复请求标志为true */ t_thrd.pagerepair_cxt.page_repair_requested = true; if (t_thrd.proc) { - SetLatch(&t_thrd.proc->procLatch); + SetLatch(&t_thrd.proc->procLatch); // 唤醒进程 } errno = save_errno; } +/* + * 功能:处理SIGUSR2信号的处理函数 + */ static void PageRepairSigUsr2Handler(SIGNAL_ARGS) { int save_errno = errno; + /* 设置文件修复请求标志为true */ t_thrd.pagerepair_cxt.file_repair_requested = true; if (t_thrd.proc) { - SetLatch(&t_thrd.proc->procLatch); + SetLatch(&t_thrd.proc->procLatch); // 唤醒进程 } errno = save_errno; } +/* + * 功能:处理SIGHUP信号的处理函数 + */ static void PageRepairSigHupHandler(SIGNAL_ARGS) { int save_errno = errno; + /* 设置接收到SIGHUP信号的标志为true */ t_thrd.pagerepair_cxt.got_SIGHUP = true; if (t_thrd.proc) { - SetLatch(&t_thrd.proc->procLatch); + SetLatch(&t_thrd.proc->procLatch); // 唤醒进程 } errno = save_errno; } +/* + * 功能:处理进程关闭请求的处理函数 + */ static void PageRepairShutDownHandler(SIGNAL_ARGS) { int save_errno = errno; + /* 设置进程关闭请求的标志为true */ t_thrd.pagerepair_cxt.shutdown_requested = true; if (t_thrd.proc) { - SetLatch(&t_thrd.proc->procLatch); + SetLatch(&t_thrd.proc->procLatch); // 唤醒进程 } errno = save_errno; @@ -658,16 +850,39 @@ static void PageRepairQuickDie(SIGNAL_ARGS) /* recovery thread function */ -bool PushBadPageToRemoteHashTbl(RepairBlockKey key, PageErrorType error_type, XLogRecPtr old_lsn, - XLogPhyBlock pblk, ThreadId tid) +/* + * 功能:将损坏的页面信息添加到远程哈希表中,如果未找到则添加,同时唤醒页修复线程 + * + * 参数列表: + * key:损坏页面的标识键 + * error_type:损坏页面的错误类型 + * old_lsn:旧的LSN(日志序列号) + * pblk:物理块信息 + * tid:线程ID + * + * 返回值:如果已找到相同键的条目,则返回true,否则返回false + * + * 注意: + * 该函数将页面的损坏信息添加到远程哈希表中,并唤醒页修复线程来处理页面。 + */ + +bool PushBadPageToRemoteHashTbl(RepairBlockKey key, PageErrorType error_type, XLogRecPtr old_lsn, XLogPhyBlock pblk, + ThreadId tid) { + // 获取页面修复的哈希表和初始化found标志 HTAB *repair_hash = g_instance.repair_cxt.page_repair_hashtbl; bool found = false; + // 断言修复哈希表不为空 Assert(repair_hash != NULL); + // 获取页面修复哈希表的锁 LWLockAcquire(g_instance.repair_cxt.page_repair_hashtbl_lock, LW_EXCLUSIVE); - RepairBlockEntry *entry = (RepairBlockEntry*)hash_search(repair_hash, &(key), HASH_ENTER, &found); + + // 在哈希表中查找页面 + RepairBlockEntry *entry = (RepairBlockEntry *)hash_search(repair_hash, &(key), HASH_ENTER, &found); + + // 如果未找到页面,则添加信息到哈希表中 if (!found) { entry->key = key; entry->recovery_tid = tid; @@ -677,14 +892,21 @@ bool PushBadPageToRemoteHashTbl(RepairBlockKey key, PageErrorType error_type, XL entry->page_new_lsn = InvalidXLogRecPtr; entry->pblk = pblk; } + + // 释放页面修复哈希表的锁 LWLockRelease(g_instance.repair_cxt.page_repair_hashtbl_lock); + // 如果未找到页面,则需要添加到数组并唤醒页修复线程 if (!found) { - /* need add array and wakeup the page repair thread */ + // 获取页修复线程的进程ID ThreadId PageRepairPID = g_instance.pid_cxt.PageRepairPID; + + // 如果页修复线程的进程ID不为空,则向其发送SIGUSR1信号以唤醒它 if (PageRepairPID != 0) { (void)gs_signal_send(PageRepairPID, SIGUSR1); } + + // 如果修复进程的latch不为空,则设置它以通知页修复线程 if (g_instance.repair_cxt.repair_proc_latch != NULL) { SetLatch(g_instance.repair_cxt.repair_proc_latch); } @@ -693,8 +915,26 @@ bool PushBadPageToRemoteHashTbl(RepairBlockKey key, PageErrorType error_type, XL return found; } -bool BlockNodeMatch(RepairBlockKey key, XLogPhyBlock pblk, RelFileNode node, - ForkNumber forknum, BlockNumber minblkno, bool segment_shrink) +/* + * 功能:检查块的标识键、物理块信息、关联文件节点、分支号和块号是否匹配 + * + * 参数列表: + * key:块的标识键 + * pblk:物理块信息 + * node:关联文件节点 + * forknum:分支号 + * minblkno:最小块号 + * segment_shrink:标志位,指示是否进行分段收缩 + * + * 返回值:如果匹配则返回true,否则返回false + * + * 注意: + * 该函数检查块的标识键、物理块信息、关联文件节点、分支号和块号是否匹配, + * 如果 segment_shrink 为 true,则会将块的 relNode 更新为物理块的 relNode。 + * 对于 bucket 文件节点,函数会使用 RelFileNodeEquals 检查文件节点是否相等。 + */ +bool BlockNodeMatch(RepairBlockKey key, XLogPhyBlock pblk, RelFileNode node, ForkNumber forknum, BlockNumber minblkno, + bool segment_shrink) { if (segment_shrink) { RelFileNode rnode = key.relfilenode; @@ -708,14 +948,31 @@ bool BlockNodeMatch(RepairBlockKey key, XLogPhyBlock pblk, RelFileNode node, } } +/* + * 功能:检查关联文件节点的特定分区节点和数据库节点是否匹配 + * + * 参数列表: + * rnode:关联文件节点 + * spcNode:期望的分区节点(可选) + * dbNode:期望的数据库节点(可选) + * + * 返回值:如果分区节点和数据库节点匹配则返回true,否则返回false + * + * 注意: + * 该函数检查关联文件节点的分区节点和数据库节点是否与给定的 spcNode 和 dbNode 匹配。 + * 如果 spcNode 或 dbNode 为有效值,并且与关联文件节点不匹配,则返回 false。 + */ bool dbNodeandSpcNodeMatch(RelFileNode *rnode, Oid spcNode, Oid dbNode) { + // 如果 spcNode 为有效值且与关联文件节点的分区节点不匹配,则返回 false。 if (OidIsValid(spcNode) && rnode->spcNode != spcNode) { return false; } + // 如果 dbNode 为有效值且与关联文件节点的数据库节点不匹配,则返回 false。 if (OidIsValid(dbNode) && rnode->dbNode != dbNode) { return false; } + // 如果分区节点和数据库节点都匹配,则返回 true。 return true; } @@ -723,24 +980,43 @@ bool dbNodeandSpcNodeMatch(RelFileNode *rnode, Oid spcNode, Oid dbNode) * drop database, or drop segmentspace, need clear the page repair hashTbl, * if the repair page key dbNode match and spcNode match, need remove. */ +/* + * 功能:批量清除页修复哈希表中与给定的分区节点和数据库节点匹配的条目 + * + * 参数列表: + * spcNode:期望的分区节点 + * dbNode:期望的数据库节点 + * + * 注意: + * 该函数用于批量清除页修复哈希表中与给定分区节点和数据库节点匹配的条目。 + */ void BatchClearPageRepairHashTbl(Oid spcNode, Oid dbNode) { + // 获取页修复哈希表 HTAB *repair_hash = g_instance.repair_cxt.page_repair_hashtbl; bool found = false; RepairBlockEntry *entry = NULL; HASH_SEQ_STATUS status; + // 获取页修复哈希表锁(独占模式) LWLockAcquire(g_instance.repair_cxt.page_repair_hashtbl_lock, LW_EXCLUSIVE); + // 初始化哈希表序列扫描状态 hash_seq_init(&status, repair_hash); + + // 循环扫描哈希表中的每个条目 while ((entry = (RepairBlockEntry *)hash_seq_search(&status)) != NULL) { + // 如果数据库节点和分区节点匹配,则删除该条目 if (dbNodeandSpcNodeMatch(&(entry->key.relfilenode), spcNode, dbNode)) { + // 删除匹配的条目 if (hash_search(repair_hash, &(entry->key), HASH_REMOVE, &found) == NULL) { + // 如果删除操作失败,报告数据损坏错误 ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("page repair hash table corrupted"))); } } } + // 释放页修复哈希表锁 LWLockRelease(g_instance.repair_cxt.page_repair_hashtbl_lock); return; @@ -750,25 +1026,45 @@ void BatchClearPageRepairHashTbl(Oid spcNode, Oid dbNode) * drop table, or truncate table, need clear the page repair hashTbl, if the * repair page Filenode match need remove. */ -void ClearPageRepairHashTbl(const RelFileNode &node, ForkNumber forknum, BlockNumber minblkno, - bool segment_shrink) +/* + * 功能:清除页修复哈希表中与给定条件匹配的条目 + * + * 参数列表: + * node:关联文件的RelFileNode + * forknum:关联文件的ForkNumber + * minblkno:最小块号,匹配的块号大于或等于此值 + * segment_shrink:是否在段缩小期间 + * + * 注意: + * 该函数用于清除页修复哈希表中与给定条件匹配的条目。 + */ +void ClearPageRepairHashTbl(const RelFileNode &node, ForkNumber forknum, BlockNumber minblkno, bool segment_shrink) { + // 获取页修复哈希表 HTAB *repair_hash = g_instance.repair_cxt.page_repair_hashtbl; bool found = false; RepairBlockEntry *entry = NULL; HASH_SEQ_STATUS status; + // 获取页修复哈希表锁(独占模式) LWLockAcquire(g_instance.repair_cxt.page_repair_hashtbl_lock, LW_EXCLUSIVE); + // 初始化哈希表序列扫描状态 hash_seq_init(&status, repair_hash); + + // 循环扫描哈希表中的每个条目 while ((entry = (RepairBlockEntry *)hash_seq_search(&status)) != NULL) { + // 如果条件匹配,则删除该条目 if (BlockNodeMatch(entry->key, entry->pblk, node, forknum, minblkno, segment_shrink)) { + // 删除匹配的条目 if (hash_search(repair_hash, &(entry->key), HASH_REMOVE, &found) == NULL) { + // 如果删除操作失败,报告数据损坏错误 ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("page repair hash table corrupted"))); } } } + // 释放页修复哈希表锁 LWLockRelease(g_instance.repair_cxt.page_repair_hashtbl_lock); return; @@ -777,21 +1073,37 @@ void ClearPageRepairHashTbl(const RelFileNode &node, ForkNumber forknum, BlockNu /* ClearSpecificsPageRepairHashTbl * If the page repair finish, need clear the page repair hashTbl. */ +/* + * 功能:清除页修复哈希表中与给定条件匹配的特定条目 + * + * 参数列表: + * key:要删除的特定条件的RepairBlockKey + * + * 注意: + * 该函数用于从页修复哈希表中删除与给定条件匹配的特定条目。 + */ void ClearSpecificsPageRepairHashTbl(RepairBlockKey key) { + // 初始化是否找到匹配条目的标志 bool found = false; + // 获取页修复哈希表 HTAB *repair_hash = g_instance.repair_cxt.page_repair_hashtbl; + // 获取页修复哈希表锁(独占模式) LWLockAcquire(g_instance.repair_cxt.page_repair_hashtbl_lock, LW_EXCLUSIVE); - if ((RepairBlockEntry*)hash_search(repair_hash, &(key), HASH_REMOVE, &found) == NULL) { + // 尝试从哈希表中删除具有给定条件的特定条目 + if ((RepairBlockEntry *)hash_search(repair_hash, &(key), HASH_REMOVE, &found) == NULL) { + // 如果没有找到匹配的条目,发出警告 ereport(WARNING, - (errmsg("the %u/%u/%u bucketnode %d forknum %u, blknum %u, remove form repair hashtbl, not found", - key.relfilenode.spcNode, key.relfilenode.dbNode, key.relfilenode.relNode, key.relfilenode.bucketNode, - key.forknum, key.blocknum))); + (errmsg("the %u/%u/%u bucketnode %d forknum %u, blknum %u, remove form repair hashtbl, not found", + key.relfilenode.spcNode, key.relfilenode.dbNode, key.relfilenode.relNode, + key.relfilenode.bucketNode, key.forknum, key.blocknum))); } + // 释放页修复哈希表锁 LWLockRelease(g_instance.repair_cxt.page_repair_hashtbl_lock); + return; } @@ -799,73 +1111,123 @@ void ClearSpecificsPageRepairHashTbl(RepairBlockKey key) * recovery thread check the primary page lsn (page_new_lsn) is in the range * from record_min_lsn to record_max_lsn, */ +/* + * 功能:检查修复页面是否在指定的LSN范围内,并返回是否可以恢复页面 + * + * 参数列表: + * key:用于查找修复页面的RepairBlockKey + * min_lsn:要比较的最小LSN(日志序列号) + * max_lsn:要比较的最大LSN(日志序列号) + * page:用于存储修复页面内容的缓冲区 + * + * 返回值:如果修复页面在LSN范围内且可以恢复,则返回true,否则返回false + * + * 注意: + * 该函数用于检查修复页面是否在指定的LSN范围内,并返回是否可以恢复页面。 + */ bool CheckRepairPage(RepairBlockKey key, XLogRecPtr min_lsn, XLogRecPtr max_lsn, char *page) { + // 初始化是否找到匹配条目的标志 bool found = false; + // 初始化是否可以恢复页面的标志 bool can_recovery = false; + // 用于存储查找到的修复页面条目 RepairBlockEntry *entry = NULL; + // 获取页修复哈希表 HTAB *repair_hash = g_instance.repair_cxt.page_repair_hashtbl; + // 获取页修复哈希表锁(独占模式) LWLockAcquire(g_instance.repair_cxt.page_repair_hashtbl_lock, LW_EXCLUSIVE); - entry = (RepairBlockEntry*)hash_search(repair_hash, &(key), HASH_FIND, &found); + // 尝试从哈希表中查找具有给定条件的修复页面条目 + entry = (RepairBlockEntry *)hash_search(repair_hash, &(key), HASH_FIND, &found); if (entry == NULL) { + // 如果没有找到匹配的条目,发出错误消息并报告哈希表损坏 ereport(ERROR, (errmsg("the page repair hash table corrupted "))); } - /* the page_new_lsn is in the range from record_min_lsn to record_max_lsn */ + // 如果修复页面的状态为等待修复,并且其LSN在指定的范围内 if (entry->page_state == WAIT_REPAIR && entry->page_new_lsn <= max_lsn && entry->page_new_lsn >= min_lsn) { + // 复制修复页面的内容到提供的缓冲区 errno_t rc; can_recovery = true; rc = memcpy_s(page, BLCKSZ, entry->page_content, BLCKSZ); securec_check(rc, "", ""); } + + // 释放页修复哈希表锁 LWLockRelease(g_instance.repair_cxt.page_repair_hashtbl_lock); + // 返回是否可以恢复页面的标志 return can_recovery; } +/* + * 功能:等待重放完成 + * + * 注意: + * 该函数用于等待重放完成,根据当前重做方式采用不同的等待策略。 + * 如果是极端重做(Extreme Redo),等待所有重放工作者空闲。 + * 如果是并行重做(Parallel Redo),等待所有页面工作者队列为空。 + * 否则,等待主机正在重放的LSN追赶挂起的LSN,然后继续。 + */ void WaitRepalyFinish() { - /* file repair finish, need clean the invalid page */ + /* 如果是极端重做,等待所有重放工作者空闲 */ if (IsExtremeRedo()) { extreme_rto::WaitAllReplayWorkerIdle(); - } else if (IsParallelRedo()) { + } + /* 如果是并行重做,等待所有页面工作者队列为空 */ + else if (IsParallelRedo()) { parallel_recovery::WaitAllPageWorkersQueueEmpty(); - } else { + } + /* 否则,等待主机正在重放的LSN追赶挂起的LSN,然后继续 */ + else { + // 获取主机当前正在重放的LSN XLogRecPtr standby_replay_lsn = GetXLogReplayRecPtr(NULL, NULL); + // 获取挂起的LSN XLogRecPtr suspend_lsn = pg_atomic_read_u64(&g_instance.startup_cxt.suspend_lsn); - /* if suspend_lsn > standby_replay_lsn then need wait */ + + /* 如果挂起的LSN大于正在重放的LSN,则需要等待 */ while (!XLByteLE(suspend_lsn, standby_replay_lsn)) { - /* sleep 1s */ + /* 休眠1秒 */ PageRepairHandleInterrupts(); pg_usleep(1000000L); - /* get current replay lsn again */ + /* 再次获取当前重放的LSN */ (void)GetXLogReplayRecPtr(NULL, &standby_replay_lsn); } } } const int MAX_REPAIR_FILE_NUM = 20; +/* + * 功能:初始化文件修复哈希表 + * + * 注意: + * 该函数用于初始化文件修复哈希表。如果哈希表未创建,则根据配置创建哈希表。 + * 哈希表以RepairFileKey为键,RepairFileEntry为值进行访问。 + */ void FileRepairHashTblInit(void) { HASHCTL ctl; + // 如果文件修复哈希表的锁为空,分配一个锁 if (g_instance.repair_cxt.file_repair_hashtbl_lock == NULL) { g_instance.repair_cxt.file_repair_hashtbl_lock = LWLockAssign(LWTRANCHE_FILE_REPAIR); } + // 如果文件修复哈希表为空,根据配置创建哈希表 if (g_instance.repair_cxt.file_repair_hashtbl == NULL) { - /* hash accessed by database file id */ + /* 使用数据库文件ID进行访问的哈希表 */ errno_t rc = memset_s(&ctl, sizeof(ctl), 0, sizeof(ctl)); securec_check(rc, "", ""); ctl.keysize = sizeof(RepairFileKey); ctl.entrysize = sizeof(RepairFileEntry); ctl.hash = tag_hash; ctl.hcxt = INSTANCE_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_STORAGE); - g_instance.repair_cxt.file_repair_hashtbl = hash_create("File Repair Hash Table", MAX_REPAIR_FILE_NUM, &ctl, - HASH_ELEM | HASH_FUNCTION |HASH_CONTEXT); + g_instance.repair_cxt.file_repair_hashtbl = + hash_create("File Repair Hash Table", MAX_REPAIR_FILE_NUM, &ctl, HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT); if (!g_instance.repair_cxt.file_repair_hashtbl) ereport(FATAL, (errmsg("could not initialize file repair Hash table"))); @@ -874,9 +1236,23 @@ void FileRepairHashTblInit(void) return; } +/* + * 功能:检查文件修复哈希表中是否存在特定条目 + * + * 参数列表: + * rnode:文件的关系文件节点(RelFileNode) + * forknum:文件的分叉号(ForkNumber) + * segno:文件段号 + * + * 返回值:如果存在与给定参数匹配的条目,则返回true,否则返回false + * + * 注意: + * 该函数用于检查文件修复哈希表中是否存在特定的条目。它使用关系文件节点、分叉号和段号作为关键来搜索哈希表。 + * 如果找到匹配的条目,将根据条目的状态判断是否返回true。如果哈希表为空,则直接返回false。 + */ bool CheckFileRepairHashTbl(RelFileNode rnode, ForkNumber forknum, uint32 segno) { - HTAB* file_hashtbl = g_instance.repair_cxt.file_repair_hashtbl; + HTAB *file_hashtbl = g_instance.repair_cxt.file_repair_hashtbl; RepairFileKey key; RepairFileEntry *entry = NULL; bool found = false; @@ -885,38 +1261,71 @@ bool CheckFileRepairHashTbl(RelFileNode rnode, ForkNumber forknum, uint32 segno) key.forknum = forknum; key.segno = segno; + // 如果文件修复哈希表为空,直接返回false if (file_hashtbl == NULL) { return found; } + + // 获取文件修复哈希表的共享锁 LWLockAcquire(FILE_REPAIR_LOCK, LW_SHARED); - entry = (RepairFileEntry*)hash_search(file_hashtbl, &(key), HASH_FIND, &found); + // 使用关键字在哈希表中查找条目,并返回是否找到的标志 + entry = (RepairFileEntry *)hash_search(file_hashtbl, &(key), HASH_FIND, &found); if (found) { + // 如果找到匹配的条目,根据条目的状态判断是否返回true if (entry->file_state == WAIT_FILE_REPAIR || entry->file_state == WAIT_RENAME) { found = true; } else { found = false; } } + // 释放文件修复哈希表的锁 LWLockRelease(FILE_REPAIR_LOCK); return found; } +/* + * 功能:检查是否需要记录坏文件信息到远程哈希表 + * + * 参数列表: + * key:修复文件的标识键 + * nblock:文件的块数(可选) + * blocknum:坏块的块号(可选) + * pblk:物理块信息(可选) + * + * 注意: + * 该函数用于检查是否需要记录坏文件信息到远程哈希表。它基于多个条件来决定是否执行这个操作,包括版本支持、块数、是否为主备模式、是否支持修复等。 + * 如果需要记录坏文件信息,将调用PushBadFileToRemoteHashTbl函数来完成。 + */ void CheckNeedRecordBadFile(RepairFileKey key, uint32 nblock, uint32 blocknum, const XLogPhyBlock *pblk) { + // 检查是否支持版本,以及是否满足其他条件 if (CheckVerionSupportRepair() && (nblock == 0 || blocknum / RELSEG_SIZE > nblock / RELSEG_SIZE) && IsPrimaryClusterStandbyDN() && g_instance.repair_cxt.support_repair) { + // 如果提供了物理块信息,则更新关系文件节点和段号 if (pblk != NULL) { key.relfilenode.relNode = pblk->relNode; key.segno = pblk->block / RELSEG_SIZE; } + // 如果是段文件节点,设置bucketNode为SegmentBktId if (IsSegmentFileNode(key.relfilenode)) { key.relfilenode.bucketNode = SegmentBktId; } + // 调用PushBadFileToRemoteHashTbl函数将坏文件信息记录到远程哈希表 PushBadFileToRemoteHashTbl(key); } } +/* + * 功能:将坏文件信息推送到远程哈希表 + * + * 参数列表: + * key:修复文件的标识键 + * + * 注意: + * 该函数用于将坏文件信息推送到远程哈希表中。它首先获取控制文件中的minRecoveryPoint,然后将坏文件的信息(包括文件路径、段号等)记录到哈希表中,状态为等待文件检查和修复。 + * 如果哈希表中已存在相同的修复文件信息,则不执行任何操作。 + */ static void PushBadFileToRemoteHashTbl(RepairFileKey key) { HTAB *file_hash = g_instance.repair_cxt.file_repair_hashtbl; @@ -924,13 +1333,17 @@ static void PushBadFileToRemoteHashTbl(RepairFileKey key) bool found = false; XLogRecPtr min_recovery_point; + // 获取控制文件中的minRecoveryPoint LWLockAcquire(ControlFileLock, LW_SHARED); min_recovery_point = t_thrd.shemem_ptr_cxt.ControlFile->minRecoveryPoint; LWLockRelease(ControlFileLock); + // 获取文件修复哈希表的锁 LWLockAcquire(FILE_REPAIR_LOCK, LW_EXCLUSIVE); - entry = (RepairFileEntry*)hash_search(file_hash, &(key), HASH_ENTER, &found); + // 尝试在哈希表中查找指定的修复文件信息 + entry = (RepairFileEntry *)hash_search(file_hash, &(key), HASH_ENTER, &found); if (!found) { + // 如果未找到相同的修复文件信息,则将其记录到哈希表中 entry->key.relfilenode.relNode = key.relfilenode.relNode; entry->key.relfilenode.dbNode = key.relfilenode.dbNode; entry->key.relfilenode.spcNode = key.relfilenode.spcNode; @@ -942,79 +1355,149 @@ static void PushBadFileToRemoteHashTbl(RepairFileKey key) entry->file_state = WAIT_FILE_CHECK_REPAIR; entry->primary_file_lsn = InvalidXLogRecPtr; + // 记录日志,说明已将坏文件信息推送到哈希表 ereport(LOG, (errmodule(MOD_REDO), - errmsg("[file repair] push to file repair hashtbl, path is %s segno is %u", - relpathperm(entry->key.relfilenode, entry->key.forknum), entry->key.segno))); + errmsg("[file repair] push to file repair hashtbl, path is %s segno is %u", + relpathperm(entry->key.relfilenode, entry->key.forknum), entry->key.segno))); } + // 释放文件修复哈希表的锁 LWLockRelease(FILE_REPAIR_LOCK); return; } +/* + * 功能:检查修复文件信息是否与给定参数匹配 + * + * 参数列表: + * key:修复文件的标识键 + * node:文件的RelFileNode + * forknum:文件的ForkNumber + * segno:段号 + * + * 返回值:如果修复文件信息与给定参数匹配,则返回true,否则返回false + * + * 注意: + * 该函数用于检查修复文件信息是否与给定的RelFileNode、ForkNumber和段号匹配。 + * 如果给定的文件信息与修复文件信息匹配,则返回true;否则返回false。 + */ bool FileNodeMatch(RepairFileKey key, RelFileNode node, ForkNumber forknum, uint32 segno) { + // 检查给定的RelFileNode是否与修复文件信息中的RelFileNode匹配 bool node_equal = RelFileNodeRelEquals(node, key.relfilenode); + // 返回结果:RelFileNode匹配,ForkNumber匹配,段号大于等于给定段号 return node_equal && key.forknum == forknum && key.segno >= segno; } +/* + * 功能:清除坏文件哈希表中的特定文件信息 + * + * 参数列表: + * node:文件的RelFileNode + * forknum:文件的ForkNumber + * segno:段号 + * + * 注意: + * 该函数用于清除坏文件哈希表中与给定的RelFileNode、ForkNumber和段号匹配的文件信息。 + * 如果匹配成功,将从哈希表中删除相应的文件信息。 + */ void ClearBadFileHashTbl(const RelFileNode &node, ForkNumber forknum, uint32 segno) { + // 获取坏文件哈希表 HTAB *file_hash = g_instance.repair_cxt.file_repair_hashtbl; RepairFileEntry *entry = NULL; bool found = false; HASH_SEQ_STATUS status; + // 获取文件修复锁以执行哈希表操作 LWLockAcquire(FILE_REPAIR_LOCK, LW_EXCLUSIVE); + // 初始化哈希表迭代器 hash_seq_init(&status, file_hash); while ((entry = (RepairFileEntry *)hash_seq_search(&status)) != NULL) { + // 如果给定的文件信息与哈希表中的某个文件信息匹配 if (FileNodeMatch(entry->key, node, forknum, segno)) { + // 从哈希表中删除匹配的文件信息 if (hash_search(file_hash, &(entry->key), HASH_REMOVE, &found) == NULL) { + // 如果删除操作失败,释放锁并报告数据损坏错误 LWLockRelease(FILE_REPAIR_LOCK); ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("file repair hash table corrupted"))); } else { + // 如果删除成功,记录文件信息已在删除表或截断表时被移除 ereport(LOG, (errmodule(MOD_REDO), - errmsg("[file repair] file %s segno is %u entry remove when drop table or truncate table", - relpathperm(entry->key.relfilenode, entry->key.forknum), entry->key.segno))); + errmsg("[file repair] file %s segno is %u entry remove when drop table or truncate table", + relpathperm(entry->key.relfilenode, entry->key.forknum), entry->key.segno))); } } } + // 释放文件修复锁 LWLockRelease(FILE_REPAIR_LOCK); return; } - +/* + * 功能:批量清除坏文件哈希表中的文件信息 + * + * 参数列表: + * spcNode:数据库表空间节点(可选) + * dbNode:数据库节点(可选) + * + * 注意: + * 该函数用于批量清除坏文件哈希表中与给定数据库表空间节点和数据库节点匹配的文件信息。 + * 如果匹配成功,将从哈希表中删除相应的文件信息。 + */ void BatchClearBadFileHashTbl(Oid spcNode, Oid dbNode) { + // 获取坏文件哈希表 HTAB *file_hash = g_instance.repair_cxt.file_repair_hashtbl; RepairFileEntry *entry = NULL; bool found = false; HASH_SEQ_STATUS status; + // 获取文件修复锁以执行哈希表操作 LWLockAcquire(FILE_REPAIR_LOCK, LW_EXCLUSIVE); + // 初始化哈希表迭代器 hash_seq_init(&status, file_hash); while ((entry = (RepairFileEntry *)hash_seq_search(&status)) != NULL) { + // 如果给定的数据库表空间节点和数据库节点匹配哈希表中的某个文件信息 if (dbNodeandSpcNodeMatch(&(entry->key.relfilenode), spcNode, dbNode)) { + // 从哈希表中删除匹配的文件信息 if (hash_search(file_hash, &(entry->key), HASH_REMOVE, &found) == NULL) { + // 如果删除操作失败,释放锁并报告数据损坏错误 LWLockRelease(FILE_REPAIR_LOCK); ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("file repair hash table corrupted"))); } else { - ereport(LOG, (errmodule(MOD_REDO), - errmsg("[file repair] file %s segno is %u entry remove when drop database or segment space", - relpathperm(entry->key.relfilenode, entry->key.forknum), entry->key.segno))); + // 如果删除成功,记录文件信息已在删除数据库或段空间时被移除 + ereport(LOG, + (errmodule(MOD_REDO), + errmsg("[file repair] file %s segno is %u entry remove when drop database or segment space", + relpathperm(entry->key.relfilenode, entry->key.forknum), entry->key.segno))); } } } + // 释放文件修复锁 LWLockRelease(FILE_REPAIR_LOCK); return; } +/* + * 功能:重命名修复文件 + * + * 参数列表: + * key:修复文件的关键信息 + * clear_entry:是否清除哈希表中的文件条目 + * + * 注意: + * 该函数用于将修复后的文件重命名回其原始名称。 + * 如果 clear_entry 为 true,则还会从哈希表中删除文件条目。 + */ void RenameRepairFile(RepairFileKey *key, bool clear_entry) { + // 声明变量 errno_t rc; bool found = false; HTAB *file_hash = g_instance.repair_cxt.file_repair_hashtbl; @@ -1022,32 +1505,35 @@ void RenameRepairFile(RepairFileKey *key, bool clear_entry) char *tempsegpath = (char *)palloc(strlen(path) + SEGLEN); char *segpath = (char *)palloc(strlen(path) + SEGLEN); - /* wait all dirty page flush */ - RequestCheckpoint(CHECKPOINT_FLUSH_DIRTY|CHECKPOINT_WAIT); + // 执行检查点以确保持久性 + RequestCheckpoint(CHECKPOINT_FLUSH_DIRTY | CHECKPOINT_WAIT); + // 根据段号生成临时路径和最终路径 if (key->segno == 0) { rc = sprintf_s(segpath, strlen(path) + SEGLEN, "%s", path); - securec_check_ss(rc, "", "") - rc = sprintf_s(tempsegpath, strlen(path) + SEGLEN, "%s.repair", path); + securec_check_ss(rc, "", "") rc = sprintf_s(tempsegpath, strlen(path) + SEGLEN, "%s.repair", path); securec_check_ss(rc, "", "") } else { rc = sprintf_s(segpath, strlen(path) + SEGLEN, "%s.%u", path, key->segno); - securec_check_ss(rc, "", "") - rc = sprintf_s(tempsegpath, strlen(path) + SEGLEN, "%s.%u.repair", path, key->segno); + securec_check_ss(rc, "", "") rc = + sprintf_s(tempsegpath, strlen(path) + SEGLEN, "%s.%u.repair", path, key->segno); securec_check_ss(rc, "", "") } + // 执行持久性重命名操作 rc = durable_rename(tempsegpath, segpath, WARNING); if (rc == 0) { - ereport(LOG, (errmodule(MOD_REDO), - errmsg("[file repair] file rename from %s to %s finish", tempsegpath, segpath))); + ereport(LOG, + (errmodule(MOD_REDO), errmsg("[file repair] file rename from %s to %s finish", tempsegpath, segpath))); - /* file repair finish, need clean the invalid page */ + // 根据修复的文件类型执行相应操作(Extreme Redo、Parallel Redo 或其他情况) if (IsExtremeRedo()) { + // 执行极端恢复操作 extreme_rto::DispatchCleanInvalidPageMarkToAllRedoWorker(*key); extreme_rto::DispatchClosefdMarkToAllRedoWorker(); extreme_rto::WaitAllReplayWorkerIdle(); } else if (IsParallelRedo()) { + // 执行并行恢复操作 if (AmStartupProcess()) { ProcTxnWorkLoad(true); } @@ -1055,26 +1541,37 @@ void RenameRepairFile(RepairFileKey *key, bool clear_entry) parallel_recovery::SendClosefdMarkToAllWorkers(); parallel_recovery::WaitAllPageWorkersQueueEmpty(); } else { + // 其他情况(未执行任何操作) } - forget_range_invalid_pages((void*)key); + + // 忘记无效页范围 + forget_range_invalid_pages((void *)key); smgrcloseall(); + // 获取文件修复锁以执行哈希表操作 LWLockAcquire(FILE_REPAIR_LOCK, LW_EXCLUSIVE); if (clear_entry) { + // 如果需要清除哈希表中的文件条目 if (hash_search(file_hash, key, HASH_REMOVE, &found) == NULL) { + // 如果删除操作失败,释放内存并报告数据损坏错误 pfree(path); pfree(segpath); pfree(tempsegpath); LWLockRelease(FILE_REPAIR_LOCK); ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("file repair hash table corrupted"))); } else { + // 如果删除成功,记录文件信息已在删除数据库或段空间时被移除 ereport(LOG, (errmodule(MOD_REDO), - errmsg("[file repair] file %s repair finish, remove the entry", segpath))); + errmsg("[file repair] file %s repair finish, remove the entry", segpath))); } } + + // 释放文件修复锁 LWLockRelease(FILE_REPAIR_LOCK); } + + // 释放内存 pfree(path); pfree(segpath); pfree(tempsegpath); @@ -1082,8 +1579,15 @@ void RenameRepairFile(RepairFileKey *key, bool clear_entry) return; } +/* + * 功能:检查是否需要重命名文件 + * + * 注意: + * 该函数用于检查修复文件是否需要进行重命名,以及是否需要进行其他操作。 + */ void CheckNeedRenameFile() { + // 声明变量 HASH_SEQ_STATUS status; RepairFileEntry *entry = NULL; HTAB *file_hash = g_instance.repair_cxt.file_repair_hashtbl; @@ -1093,22 +1597,31 @@ void CheckNeedRenameFile() errno_t rc = 0; uint32 i = 0; + // 获取文件修复锁以执行哈希表操作 LWLockAcquire(FILE_REPAIR_LOCK, LW_EXCLUSIVE); hash_seq_init(&status, file_hash); + + // 遍历哈希表以计算需要重命名和需要修复的文件数目 while ((entry = (RepairFileEntry *)hash_seq_search(&status)) != NULL) { if (entry->file_state == WAIT_RENAME) { need_rename_num++; } } + + // 如果有需要重命名的文件,则分配重命名键的内存空间 if (need_rename_num > 0) { - rename_key = (RepairFileKey*)palloc0(sizeof(RepairFileKey) * need_rename_num); + rename_key = (RepairFileKey *)palloc0(sizeof(RepairFileKey) * need_rename_num); } + // 重新初始化哈希表遍历状态 hash_seq_init(&status, file_hash); + i = 0; + // 再次遍历哈希表,根据文件状态执行相应操作 while ((entry = (RepairFileEntry *)hash_seq_search(&status)) != NULL) { switch (entry->file_state) { case WAIT_RENAME: + // 如果文件需要重命名,则将其记录到重命名键数组中 Assert(XLByteLE(entry->primary_file_lsn, GetXLogReplayRecPtr(NULL, NULL))); Assert(!IsSegmentFileNode(entry->key.relfilenode)); rc = memcpy_s(&rename_key[i], sizeof(RepairFileKey), &(entry->key), sizeof(RepairFileKey)); @@ -1117,45 +1630,56 @@ void CheckNeedRenameFile() break; case WAIT_FILE_REMOTE_READ: case WAIT_FILE_REPAIR_SEGMENT: + // 如果文件需要进行远程读取或段修复,则增加需要修复的文件数目 need_repair_num++; break; case WAIT_FOREGT_INVALID_PAGE: - { - forget_range_invalid_pages((void*)&entry->key); - bool found = false; - if (hash_search(file_hash, &(entry->key), HASH_REMOVE, &found) == NULL) { - LWLockRelease(FILE_REPAIR_LOCK); - ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("file repair hash table corrupted"))); - } else { - ereport(LOG, (errmodule(MOD_REDO), - errmsg("[file repair] file %s seg is %d, repair finish, remove the entry", - relpathperm(entry->key.relfilenode, entry->key.forknum), entry->key.segno))); - } + // 如果文件需要忘记无效页范围,则执行相应操作并从哈希表中删除该文件 + forget_range_invalid_pages((void *)&entry->key); + bool found = false; + if (hash_search(file_hash, &(entry->key), HASH_REMOVE, &found) == NULL) { + LWLockRelease(FILE_REPAIR_LOCK); + ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("file repair hash table corrupted"))); + } else { + ereport(LOG, (errmodule(MOD_REDO), + errmsg("[file repair] file %s seg is %d, repair finish, remove the entry", + relpathperm(entry->key.relfilenode, entry->key.forknum), entry->key.segno))); } break; default: break; } } + + // 释放文件修复锁 LWLockRelease(FILE_REPAIR_LOCK); + // 遍历需要重命名的文件并执行重命名操作,同时清除哈希表中的相应条目 for (i = 0; i < need_rename_num; i++) { RepairFileKey *key = &rename_key[i]; RenameRepairFile(key, true); } + + // 如果没有需要修复的文件,将恢复暂停标志设置为 false if (need_rename_num > 0) { pfree(rename_key); rename_key = NULL; } if (need_repair_num == 0) { SetRecoverySuspend(false); - ereport(LOG, (errmodule(MOD_REDO), - errmsg("set recovery suspend to false, the need repair num is zero"))); + ereport(LOG, (errmodule(MOD_REDO), errmsg("set recovery suspend to false, the need repair num is zero"))); } } +/* + * 功能:检查是否停止恢复 + * + * 注意: + * 该函数用于检查是否需要停止恢复,以及执行相应的操作。 + */ void CheckIsStopRecovery(void) { + // 声明变量 uint32 need_repair_num = 0; uint32 need_rename_num = 0; HASH_SEQ_STATUS status; @@ -1164,48 +1688,65 @@ void CheckIsStopRecovery(void) XLogRecPtr repaly = GetXLogReplayRecPtr(NULL, NULL); XLogRecPtr flush = GetStandbyFlushRecPtr(NULL); + // 如果文件修复哈希表为空,则返回 if (file_hash == NULL) { return; } + // 尝试获取文件修复锁,以执行哈希表操作 if (LWLockConditionalAcquire(FILE_REPAIR_LOCK, LW_EXCLUSIVE)) { + // 初始化哈希表遍历状态 hash_seq_init(&status, file_hash); + + // 遍历哈希表以检查文件状态并执行相应操作 while ((entry = (RepairFileEntry *)hash_seq_search(&status)) != NULL) { flush = GetStandbyFlushRecPtr(NULL); - if (!XLogRecPtrIsInvalid(entry->min_recovery_point) && XLByteLT(entry->min_recovery_point, repaly) - && entry->file_state == WAIT_FILE_CHECK_REPAIR) { + + // 检查是否需要将等待文件修复的文件状态设置为等待远程读取 + if (!XLogRecPtrIsInvalid(entry->min_recovery_point) && XLByteLT(entry->min_recovery_point, repaly) && + entry->file_state == WAIT_FILE_CHECK_REPAIR) { entry->file_state = WAIT_FILE_REMOTE_READ; } + // 检查是否需要进行远程读取或段修复,同时增加相应计数 if (entry->file_state == WAIT_FILE_REMOTE_READ || entry->file_state == WAIT_FILE_REPAIR_SEGMENT) { need_repair_num++; - ereport(LOG, (errmodule(MOD_REDO), - errmsg("[file repair] need remote read or segment file wait rename, file path %s segno is %u", - relpathperm(entry->key.relfilenode, entry->key.forknum), entry->key.segno))); + ereport(LOG, + (errmodule(MOD_REDO), + errmsg("[file repair] need remote read or segment file wait rename, file path %s segno is %u", + relpathperm(entry->key.relfilenode, entry->key.forknum), entry->key.segno))); } + // 检查是否需要重命名文件,同时增加相应计数 if ((entry->file_state == WAIT_FILE_REPAIR && !IsSegmentFileNode(entry->key.relfilenode) && - XLByteLT(entry->primary_file_lsn, repaly)) || entry->file_state == WAIT_RENAME) { + XLByteLT(entry->primary_file_lsn, repaly)) || + entry->file_state == WAIT_RENAME) { entry->file_state = WAIT_RENAME; need_rename_num++; ereport(LOG, (errmodule(MOD_REDO), - errmsg("[file repair] need rename, file path %s segno is %u", - relpathperm(entry->key.relfilenode, entry->key.forknum), entry->key.segno))); + errmsg("[file repair] need rename, file path %s segno is %u", + relpathperm(entry->key.relfilenode, entry->key.forknum), entry->key.segno))); } } + // 释放文件修复锁 LWLockRelease(FILE_REPAIR_LOCK); + // 如果有需要修复的文件或需要重命名的文件,则执行相应操作 if (need_repair_num > 0 || need_rename_num > 0) { load_server_mode(); + + // 如果不支持页面修复,则返回 if (NOT_SUPPORT_PAGE_REPAIR) { return; } + + // 如果服务器模式为STANDBY_MODE且支持修复,则设置恢复暂停标志为true if (t_thrd.xlog_cxt.server_mode == STANDBY_MODE && g_instance.repair_cxt.support_repair) { SetRecoverySuspend(true); ereport(LOG, (errmodule(MOD_REDO), - errmsg("set recovery suspend to true, the need repair num is %d, need rename num is %d", - need_repair_num, need_rename_num))); + errmsg("set recovery suspend to true, the need repair num is %d, need rename num is %d", + need_repair_num, need_rename_num))); } } } @@ -1214,72 +1755,125 @@ void CheckIsStopRecovery(void) } const int REPAIR_LEN = 8; +/* + * 功能:创建修复文件 + * + * 参数列表: + * path:原始文件路径 + * + * 返回值:成功时返回文件描述符(fd),失败时返回-1 + * + * 注意: + * 该函数用于创建一个用于修复的文件,并返回其文件描述符。 + */ int CreateRepairFile(char *path) { + // 初始化文件描述符为-1 int fd = -1; + // 初始化重试次数为0 int retry_times = 0; + // 定义最大重试次数 const int MAX_RETRY_TIME = 2; errno_t rc; + // 为修复文件路径分配内存 char *reapirpath = (char *)palloc(strlen(path) + REPAIR_LEN); + // 格式化修复文件路径 rc = sprintf_s(reapirpath, strlen(path) + REPAIR_LEN, "%s.repair", path); securec_check_ss(rc, "", ""); RETRY: - fd = BasicOpenFile((char*)reapirpath, O_CREAT | O_RDWR | PG_BINARY, S_IRUSR | S_IWUSR); + // 打开文件,并设置标志位为创建、读写、二进制模式,权限为用户可读和用户可写 + fd = BasicOpenFile((char *)reapirpath, O_CREAT | O_RDWR | PG_BINARY, S_IRUSR | S_IWUSR); retry_times++; + + // 检查文件是否成功打开 if (fd < 0) { + // 如果未成功打开文件,并且重试次数小于最大重试次数,则进行重试 if (retry_times < MAX_RETRY_TIME) { goto RETRY; } + + // 如果错误不是文件不存在(ENOENT),则报告警告并释放资源后返回-1 if (errno != ENOENT) { - ereport(WARNING, (errcode_for_file_access(), - errmsg("[file repair] could not open file \"%s\": %m", reapirpath))); + ereport(WARNING, + (errcode_for_file_access(), errmsg("[file repair] could not open file \"%s\": %m", reapirpath))); pfree(reapirpath); return -1; } } + // 释放修复文件路径的内存并返回文件描述符 pfree(reapirpath); return fd; } -int WriteRepairFile(int fd, char* path, char *buf, uint32 offset, uint32 size) +/* + * 功能:写修复文件 + * + * 参数列表: + * fd:文件描述符 + * path:原始文件路径 + * buf:要写入的数据缓冲区 + * offset:写入数据的偏移量 + * size:要写入的数据大小 + * + * 返回值:成功时返回0,失败时返回-1 + * + * 注意: + * 该函数用于向修复文件写入数据,具体实现了写入和同步操作。 + */ +int WriteRepairFile(int fd, char *path, char *buf, uint32 offset, uint32 size) { errno_t rc = 0; + // 为修复文件路径分配内存 char *reapirpath = (char *)palloc(strlen(path) + REPAIR_LEN); + // 格式化修复文件路径 rc = sprintf_s(reapirpath, strlen(path) + REPAIR_LEN, "%s.repair", path); securec_check_ss(rc, "", ""); + // 将文件偏移设置为指定的偏移量 if (lseek(fd, offset, SEEK_SET) < 0) { - ereport(WARNING, (errcode_for_file_access(), errmsg("[file repair] could not seek reapir file %s : %m", - reapirpath))); + ereport(WARNING, + (errcode_for_file_access(), errmsg("[file repair] could not seek repair file %s : %m", reapirpath))); pfree(reapirpath); return -1; } + // 写入数据到文件 if (write(fd, buf, size) != size) { /* if write didn't set errno, assume problem is no disk space */ if (errno == 0) { errno = ENOSPC; } - ereport(WARNING, (errcode_for_file_access(), errmsg("[file repair] could not write to temp file %s : %m", - reapirpath))); + ereport(WARNING, + (errcode_for_file_access(), errmsg("[file repair] could not write to temp file %s : %m", reapirpath))); pfree(reapirpath); return -1; } + // 同步文件,确保数据被写入磁盘 if (fsync(fd) != 0) { - ereport(WARNING, (errcode_for_file_access(), errmsg("[file repair] could not fsync temp file %s : %m", - reapirpath))); + ereport(WARNING, + (errcode_for_file_access(), errmsg("[file repair] could not fsync temp file %s : %m", reapirpath))); pfree(reapirpath); return -1; } + // 释放修复文件路径的内存并返回0表示成功 pfree(reapirpath); return 0; } - +/* + * 功能:删除旧的坏文件 + * + * 参数列表: + * path:要删除的文件的路径 + * key:修复文件的键 + * + * 注意: + * 该函数等待XLOG重放完成,并处理相关的后端线程。然后删除文件,并清除与该文件相关的共享缓冲区。 + */ void UnlinkOldBadFile(char *path, RepairFileKey key) { /* wait the xlog repaly finish */ @@ -1302,7 +1896,7 @@ void UnlinkOldBadFile(char *path, RepairFileKey key) } } /* wait all dirty page flush */ - RequestCheckpoint(CHECKPOINT_FLUSH_DIRTY|CHECKPOINT_WAIT); + RequestCheckpoint(CHECKPOINT_FLUSH_DIRTY | CHECKPOINT_WAIT); /* handle the backend thread */ RelFileNodeBackend rnode; @@ -1324,6 +1918,20 @@ void UnlinkOldBadFile(char *path, RepairFileKey key) return; } +/* + * 功能:修复分段文件 + * + * 参数列表: + * key:修复文件的键 + * segpath:分段文件的路径 + * seg_no:分段号 + * max_segno:最大分段号 + * size:文件大小 + * + * 注意: + * 函数首先创建一个修复文件并打开它,然后循环远程读取数据块,将数据写入修复文件中。之后等待 + * 修复文件的数据被刷新到磁盘。最后,如果不是分段文件,则删除旧文件,更新修复文件的状态。 + */ static void RepairSegFile(RepairFileKey key, char *segpath, uint32 seg_no, uint32 max_segno, uint64 size) { char *buf = 0; @@ -1338,22 +1946,25 @@ static void RepairSegFile(RepairFileKey key, char *segpath, uint32 seg_no, uint3 XLogRecPtr standby_flush_lsn = InvalidXLogRecPtr; bool found = false; + // 创建修复文件并打开它 fd = CreateRepairFile(segpath); if (fd < 0) { - ereport(WARNING, (errcode_for_file_access(), - errmsg("[file repair] could not create repair file \"%s\", segno is %d", - relpathperm(key.relfilenode, key.forknum), seg_no))); + ereport(WARNING, + (errcode_for_file_access(), errmsg("[file repair] could not create repair file \"%s\", segno is %d", + relpathperm(key.relfilenode, key.forknum), seg_no))); return; } read_key.relfilenode = key.relfilenode; read_key.forknum = key.forknum; read_key.blockstart = seg_no * RELSEG_SIZE; - buf = (char*)palloc(MAX_BATCH_READ_BLOCKNUM * BLCKSZ); + // 分配缓冲区以批量读取数据块 + buf = (char *)palloc(MAX_BATCH_READ_BLOCKNUM * BLCKSZ); seg_size = (seg_no < max_segno ? (RELSEG_SIZE * BLCKSZ) : (size % (RELSEG_SIZE * BLCKSZ))); int batch_size = MAX_BATCH_READ_BLOCKNUM * BLCKSZ; int max_times = seg_size % batch_size == 0 ? seg_size / batch_size : (seg_size / batch_size + 1); + // 循环读取数据块并写入修复文件 for (int j = 0; j < max_times; j++) { int read_size = 0; if (seg_size % batch_size != 0) { @@ -1368,16 +1979,16 @@ static void RepairSegFile(RepairFileKey key, char *segpath, uint32 seg_no, uint3 rc = WriteRepairFile(fd, segpath, buf, j * batch_size, read_size); if (rc != 0) { ereport(WARNING, (errcode_for_file_access(), - errmsg("[file repair] could not write repair file \"%s\", segno is %d", - relpathperm(key.relfilenode, key.forknum), seg_no))); + errmsg("[file repair] could not write repair file \"%s\", segno is %d", + relpathperm(key.relfilenode, key.forknum), seg_no))); pfree(buf); (void)close(fd); return; } } else { ereport(WARNING, (errcode_for_file_access(), - errmsg("[file repair] remote read file failed \"%s\", segno is %d, block start %u", - relpathperm(key.relfilenode, key.forknum), seg_no, read_key.blockstart))); + errmsg("[file repair] remote read file failed \"%s\", segno is %d, block start %u", + relpathperm(key.relfilenode, key.forknum), seg_no, read_key.blockstart))); pfree(buf); (void)close(fd); return; @@ -1386,27 +1997,29 @@ static void RepairSegFile(RepairFileKey key, char *segpath, uint32 seg_no, uint3 pfree(buf); (void)close(fd); + // 等待修复文件的数据被刷新到磁盘 if (ret_code == REMOTE_READ_OK) { standby_flush_lsn = GetStandbyFlushRecPtr(NULL); while (!XLByteLT(remote_lsn, standby_flush_lsn)) { PageRepairHandleInterrupts(); - /* sleep 10ms */ + /* 等待10毫秒 */ pg_usleep(10000L); - /* get current replay lsn again */ + /* 再次获取当前重放LSN */ standby_flush_lsn = GetStandbyFlushRecPtr(NULL); } - ereport(LOG, (errmsg("[file repair] wait lsn flush, remote lsn is %X/%X", - (uint32)(remote_lsn >> XLOG_LSN_SWAP), (uint32)remote_lsn))); + ereport(LOG, (errmsg("[file repair] wait lsn flush, remote lsn is %X/%X", (uint32)(remote_lsn >> XLOG_LSN_SWAP), + (uint32)remote_lsn))); } else { return; } /* wait xlog repaly */ + // 如果不是分段文件,则删除旧文件 if (!IsSegmentFileNode(key.relfilenode)) { if (stat(segpath, &statBuf) < 0) { if (errno != ENOENT) { ereport(WARNING, (errcode_for_file_access(), - errmsg("[file repair] could not stat file \"%s\" before repair: %m", segpath))); + errmsg("[file repair] could not stat file \"%s\" before repair: %m", segpath))); UnlinkOldBadFile(segpath, key); } } else { @@ -1416,8 +2029,8 @@ static void RepairSegFile(RepairFileKey key, char *segpath, uint32 seg_no, uint3 /* wait xlog repaly finish, need get lock */ LWLockAcquire(FILE_REPAIR_LOCK, LW_EXCLUSIVE); - RepairFileEntry *temp_entry = (RepairFileEntry*)hash_search(g_instance.repair_cxt.file_repair_hashtbl, &(key), - HASH_FIND, &found); + RepairFileEntry *temp_entry = + (RepairFileEntry *)hash_search(g_instance.repair_cxt.file_repair_hashtbl, &(key), HASH_FIND, &found); if (found) { temp_entry->file_state = IsSegmentFileNode(key.relfilenode) ? WAIT_FILE_REPAIR_SEGMENT : WAIT_FILE_REPAIR; temp_entry->primary_file_lsn = remote_lsn; @@ -1425,7 +2038,27 @@ static void RepairSegFile(RepairFileKey key, char *segpath, uint32 seg_no, uint3 LWLockRelease(FILE_REPAIR_LOCK); return; } - +/* + * 功能:检查所有分段文件是否已完成远程读取,并在满足条件时执行一系列操作 + * + * 参数列表: + * key:修复文件的键 + * max_segno:最大分段号 + * + * 返回值: + * 如果所有分段文件已完成远程读取,则返回true,否则返回false + * + * 注意: + * 该函数首先遍历所有分段文件,检查它们的状态是否为WAIT_FILE_REPAIR_SEGMENT(等待分段修复), + * 如果是,则增加`repair_num`计数。如果`repair_num`等于`max_segno + 1`,表示所有分段文件 + * 都已完成远程读取,然后进行以下操作: + * 1. 重命名所有分段文件。 + * 2. 打开所有分段文件。 + * 3. 更改文件状态为WAIT_FOREGT_INVALID_PAGE(等待忘记无效页面)。 + * 最后,函数返回true。 + * + * 如果没有满足条件的情况,则函数返回false。 + */ bool CheckAllSegmentFileRepair(RepairFileKey key, uint32 max_segno) { uint32 repair_num = 0; @@ -1443,8 +2076,8 @@ bool CheckAllSegmentFileRepair(RepairFileKey key, uint32 max_segno) temp_key.forknum = key.forknum; temp_key.segno = i; - RepairFileEntry *entry = (RepairFileEntry*)hash_search(g_instance.repair_cxt.file_repair_hashtbl, - &(temp_key), HASH_FIND, &found); + RepairFileEntry *entry = + (RepairFileEntry *)hash_search(g_instance.repair_cxt.file_repair_hashtbl, &(temp_key), HASH_FIND, &found); Assert(found); if (found && entry->file_state == WAIT_FILE_REPAIR_SEGMENT) { repair_num++; @@ -1467,7 +2100,7 @@ bool CheckAllSegmentFileRepair(RepairFileKey key, uint32 max_segno) rename_key.segno = i; LWLockAcquire(FILE_REPAIR_LOCK, LW_EXCLUSIVE); - (void*)hash_search(g_instance.repair_cxt.file_repair_hashtbl, &(rename_key), HASH_FIND, &found); + (void *)hash_search(g_instance.repair_cxt.file_repair_hashtbl, &(rename_key), HASH_FIND, &found); Assert(found); LWLockRelease(FILE_REPAIR_LOCK); if (found) { @@ -1491,8 +2124,8 @@ bool CheckAllSegmentFileRepair(RepairFileKey key, uint32 max_segno) change_key.forknum = key.forknum; change_key.segno = i; - RepairFileEntry *entry = (RepairFileEntry*)hash_search(g_instance.repair_cxt.file_repair_hashtbl, - &(change_key), HASH_FIND, &found); + RepairFileEntry *entry = (RepairFileEntry *)hash_search(g_instance.repair_cxt.file_repair_hashtbl, + &(change_key), HASH_FIND, &found); Assert(found); if (found) { entry->file_state = WAIT_FOREGT_INVALID_PAGE; @@ -1523,8 +2156,8 @@ RETYR: pfree(path); pfree(segpath); LWLockAcquire(FILE_REPAIR_LOCK, LW_EXCLUSIVE); - RepairFileEntry *temp_entry = (RepairFileEntry*)hash_search(g_instance.repair_cxt.file_repair_hashtbl, - &(key), HASH_FIND, &found); + RepairFileEntry *temp_entry = + (RepairFileEntry *)hash_search(g_instance.repair_cxt.file_repair_hashtbl, &(key), HASH_FIND, &found); if (found) { temp_entry->file_state = WAIT_CLEAN; } @@ -1534,13 +2167,15 @@ RETYR: max_segno = size / (RELSEG_SIZE * BLCKSZ); /* max_segno start from 0 */ if (key.segno > max_segno) { - ereport(WARNING, (errcode_for_file_access(), - errmsg("[file repair] primary this file %s , segno is %d also not exist, can not repair, wait clean", - relpathperm(key.relfilenode, key.forknum), key.segno))); + ereport( + WARNING, + (errcode_for_file_access(), + errmsg("[file repair] primary this file %s , segno is %d also not exist, can not repair, wait clean", + relpathperm(key.relfilenode, key.forknum), key.segno))); LWLockAcquire(FILE_REPAIR_LOCK, LW_EXCLUSIVE); - RepairFileEntry *temp_entry = (RepairFileEntry*)hash_search(g_instance.repair_cxt.file_repair_hashtbl, - &(key), HASH_FIND, &found); + RepairFileEntry *temp_entry = + (RepairFileEntry *)hash_search(g_instance.repair_cxt.file_repair_hashtbl, &(key), HASH_FIND, &found); if (found) { temp_entry->file_state = WAIT_CLEAN; } @@ -1555,7 +2190,7 @@ RETYR: /* wait all dirty page flush */ WaitRepalyFinish(); /* wait all dirty page flush */ - RequestCheckpoint(CHECKPOINT_FLUSH_DIRTY|CHECKPOINT_WAIT); + RequestCheckpoint(CHECKPOINT_FLUSH_DIRTY | CHECKPOINT_WAIT); df_clear_and_close_all_file(key, max_segno); } @@ -1575,7 +2210,26 @@ RETYR: pfree(segpath); return; } - +/* + * 功能:检查与当前文件具有相同键但不同分段号的其他分段文件是否需要修复,并执行相应的操作。 + * + * 参数列表: + * key:修复文件的键 + * max_segno:文件的最大分段号 + * size:文件的大小 + * + * 注意: + * 该函数循环遍历不同的分段号,对每个分段号执行以下操作: + * 1. 生成分段文件的路径。 + * 2. 如果分段号与当前文件的分段号相同,继续下一个分段号的检查。 + * 3. 检查是否在哈希表中找到具有相同键但状态为WAIT_FILE_REMOTE_READ的分段文件。 + * 4. 如果找到,释放哈希表的锁并执行RepairSegFile函数修复分段文件。 + * 5. 否则,检查是否分段文件存在,如果存在则继续下一个分段号的检查。 + * 6. + * 如果分段文件不存在或出现错误,则将具有相同键但不同分段号的分段文件添加到哈希表中,并标记为WAIT_FILE_REMOTE_READ状态,然后释放锁并执行RepairSegFile函数修复分段文件。 + * + * 最后,释放分配的内存并返回。 + */ static void checkOtherFile(RepairFileKey key, uint32 max_segno, uint64 size) { errno_t rc; @@ -1609,7 +2263,7 @@ static void checkOtherFile(RepairFileKey key, uint32 max_segno, uint64 size) temp_key.segno = i; LWLockAcquire(FILE_REPAIR_LOCK, LW_EXCLUSIVE); - temp_entry = (RepairFileEntry*)hash_search(file_hash, &(temp_key), HASH_FIND, &found); + temp_entry = (RepairFileEntry *)hash_search(file_hash, &(temp_key), HASH_FIND, &found); if (found && temp_entry->file_state == WAIT_FILE_REMOTE_READ) { LWLockRelease(FILE_REPAIR_LOCK); RepairSegFile(temp_key, segpath, i, max_segno, size); @@ -1628,7 +2282,7 @@ static void checkOtherFile(RepairFileKey key, uint32 max_segno, uint64 size) LWLockAcquire(FILE_REPAIR_LOCK, LW_EXCLUSIVE); - temp_entry = (RepairFileEntry*)hash_search(file_hash, &(temp_key), HASH_ENTER, &found); + temp_entry = (RepairFileEntry *)hash_search(file_hash, &(temp_key), HASH_ENTER, &found); if (!found) { LWLockAcquire(ControlFileLock, LW_SHARED); XLogRecPtr min_recovery_point = t_thrd.shemem_ptr_cxt.ControlFile->minRecoveryPoint; @@ -1638,9 +2292,10 @@ static void checkOtherFile(RepairFileKey key, uint32 max_segno, uint64 size) temp_entry->file_state = WAIT_FILE_REMOTE_READ; temp_entry->primary_file_lsn = InvalidXLogRecPtr; - ereport(LOG, (errmodule(MOD_REDO), - errmsg("[file repair] check other seg file push to file repair hashtbl, path is %s segno is %u", - relpathperm(key.relfilenode, key.forknum), i))); + ereport(LOG, + (errmodule(MOD_REDO), + errmsg("[file repair] check other seg file push to file repair hashtbl, path is %s segno is %u", + relpathperm(key.relfilenode, key.forknum), i))); LWLockRelease(FILE_REPAIR_LOCK); RepairSegFile(temp_key, segpath, i, max_segno, size); } else { @@ -1653,6 +2308,24 @@ static void checkOtherFile(RepairFileKey key, uint32 max_segno, uint64 size) } const int MAX_FILE_REPAIR_NUM = 10; +/* + * 功能:按顺序检查文件修复哈希表中的文件,并根据它们的状态执行不同的操作。 + * 注意: + * 该函数首先检查恢复是否被挂起,以及是否设置了挂起的LSN。如果未挂起恢复或LSN无效,则返回。 + * 然后,它创建一个用于存储需要远程读取的文件的数组 `remote_read`,并将其初始化为零。 + * 接着,函数等待XLOG恢复完成。 + * 随后,它获取文件修复锁,初始化哈希表的序列状态并遍历哈希表中的所有文件条目。 + * 对于每个文件条目,它根据文件的状态采取以下操作: + * - 如果文件状态是 WAIT_FILE_CHECK_REPAIR、WAIT_FILE_REPAIR、WAIT_FOREGT_INVALID_PAGE、WAIT_CLEAN 或 + * WAIT_RENAME,则不执行任何操作。 + * - 如果文件状态是 WAIT_FILE_REPAIR_SEGMENT 或 WAIT_FILE_REMOTE_READ,则将文件状态设置为 + * WAIT_FILE_REMOTE_READ,然后将文件条目复制到 `remote_read` 数组中,如果数组已满则停止复制。 + * - 如果文件状态无法识别,则释放文件修复锁,抛出错误。 + * 接下来,函数释放文件修复锁,并循环遍历 `remote_read` 数组中的文件,对每个文件执行 `StandbyRemoteReadFile` + * 函数,用于远程读取文件。 最后,函数再次获取文件修复锁,遍历哈希表并检查文件状态是否为 + * WAIT_FILE_REPAIR_SEGMENT、WAIT_FILE_REMOTE_READ 或 WAIT_RENAME。如果是,将 `need_repair_num` 增加。 + * 如果没有任何文件需要修复,将恢复挂起状态设置为 false。 + */ static void SeqRemoteReadFile() { HTAB *repair_hash = g_instance.repair_cxt.file_repair_hashtbl; @@ -1668,7 +2341,7 @@ static void SeqRemoteReadFile() } rc = memset_s(remote_read, sizeof(RepairFileEntry) * MAX_FILE_REPAIR_NUM, 0, - sizeof(RepairFileEntry) * MAX_FILE_REPAIR_NUM); + sizeof(RepairFileEntry) * MAX_FILE_REPAIR_NUM); securec_check(rc, "", ""); /* wait the xlog repaly finish */ WaitRepalyFinish(); @@ -1691,8 +2364,8 @@ static void SeqRemoteReadFile() if (need_repair_num >= MAX_FILE_REPAIR_NUM) { break; } else { - rc = memcpy_s(&remote_read[need_repair_num], sizeof(RepairFileEntry), - entry, sizeof(RepairFileEntry)); + rc = memcpy_s(&remote_read[need_repair_num], sizeof(RepairFileEntry), entry, + sizeof(RepairFileEntry)); securec_check(rc, "", ""); } break; @@ -1701,7 +2374,6 @@ static void SeqRemoteReadFile() ereport(ERROR, (errmsg("[file repair] error file state during remote read"))); break; } - } LWLockRelease(FILE_REPAIR_LOCK); for (uint32 i = 0; i < need_repair_num; i++) { @@ -1721,7 +2393,7 @@ static void SeqRemoteReadFile() if (need_repair_num == 0) { SetRecoverySuspend(false); ereport(LOG, (errmodule(MOD_REDO), - errmsg("pagerepair thread set recovery suspend to false, the need repair num is zero"))); + errmsg("pagerepair thread set recovery suspend to false, the need repair num is zero"))); } return; } diff --git a/src/gausskernel/process/postmaster/pagewriter.cpp b/src/gausskernel/process/postmaster/pagewriter.cpp index a1860f9a9..41f692053 100755 --- a/src/gausskernel/process/postmaster/pagewriter.cpp +++ b/src/gausskernel/process/postmaster/pagewriter.cpp @@ -85,17 +85,23 @@ static uint32 calculate_pagewriter_flush_num(); static void candidate_buf_push(int buf_id, int thread_id); static void seg_candidate_buf_push(int buf_id, int thread_id); static void init_candidate_list(); -static uint32 incre_ckpt_pgwr_flush_dirty_page(WritebackContext wb_context, - const CkptSortItem *dirty_buf_list, int start, int batch_num); +static uint32 incre_ckpt_pgwr_flush_dirty_page(WritebackContext wb_context, const CkptSortItem *dirty_buf_list, + int start, int batch_num); static void incre_ckpt_pgwr_flush_dirty_queue(WritebackContext wb_context); static void incre_ckpt_pgwr_scan_buf_pool(WritebackContext wb_context); static void push_to_candidate_list(BufferDesc *buf_desc); static uint32 get_candidate_buf_and_flush_list(uint32 start, uint32 end, uint32 max_flush_num, - bool *contain_hashbucket); + bool *contain_hashbucket); static int64 get_thread_candidate_nums(int thread_id); static int64 get_thread_seg_candidate_nums(int thread_id); const int XLOG_LSN_SWAP = 32; +/* + * 功能:获取当前节点的名称 + * + * 注意: + * 此函数用于获取当前节点的名称。如果节点名称未定义或为空,则返回 "not define"。 + */ Datum ckpt_view_get_node_name() { if (g_instance.attr.attr_common.PGXCNodeName == NULL || g_instance.attr.attr_common.PGXCNodeName[0] == '\0') { @@ -121,6 +127,17 @@ Datum ckpt_view_get_remian_dirty_page_num() } const int LSN_LENGTH = 64; +/* + * 功能:获取最小的恢复LSN(日志序列号) + * + * 参数列表:无 + * + * 返回值:返回一个文本数据类型的表示最小恢复LSN的字符串 + * + * 注意: + * 此函数用于获取数据库中的最小恢复LSN。LSN(日志序列号)用于标识WAL(写前日志)中的位置, + * 这对于数据库的恢复和一致性非常重要。函数将最小恢复LSN表示为十六进制字符串并返回。 + */ Datum ckpt_view_get_min_rec_lsn() { errno_t ret; @@ -130,14 +147,24 @@ Datum ckpt_view_get_min_rec_lsn() ret = memset_s(queue_rec_lsn_s, LSN_LENGTH, 0, LSN_LENGTH); securec_check(ret, "", ""); - ret = snprintf_s(queue_rec_lsn_s, LSN_LENGTH, LSN_LENGTH - 1, "%X/%X", - (uint32)(queue_rec_lsn >> XLOG_LSN_SWAP), (uint32)queue_rec_lsn); + ret = snprintf_s(queue_rec_lsn_s, LSN_LENGTH, LSN_LENGTH - 1, "%X/%X", (uint32)(queue_rec_lsn >> XLOG_LSN_SWAP), + (uint32)queue_rec_lsn); securec_check_ss(ret, "", ""); return CStringGetTextDatum(queue_rec_lsn_s); } +/* + * 功能:获取脏页队列的最小恢复LSN(日志序列号) + * + * 返回值:返回一个文本数据类型的表示脏页队列的最小恢复LSN的字符串 + * + * 注意: + * 此函数用于获取数据库中脏页队列的最小恢复LSN。LSN(日志序列号)用于标识WAL(写前日志)中的位置, + * 这对于数据库的恢复和一致性非常重要。函数将脏页队列的最小恢复LSN表示为十六进制字符串并返回。 + */ Datum ckpt_view_get_queue_rec_lsn() { + errno_t ret; char queue_rec_lsn_s[LSN_LENGTH]; XLogRecPtr queue_rec_lsn = get_dirty_page_queue_rec_lsn(); @@ -145,39 +172,69 @@ Datum ckpt_view_get_queue_rec_lsn() ret = memset_s(queue_rec_lsn_s, LSN_LENGTH, 0, LSN_LENGTH); securec_check(ret, "", ""); - ret = snprintf_s(queue_rec_lsn_s, LSN_LENGTH, LSN_LENGTH - 1, "%X/%X", - (uint32)(queue_rec_lsn >> XLOG_LSN_SWAP), (uint32)queue_rec_lsn); + ret = snprintf_s(queue_rec_lsn_s, LSN_LENGTH, LSN_LENGTH - 1, "%X/%X", (uint32)(queue_rec_lsn >> XLOG_LSN_SWAP), + (uint32)queue_rec_lsn); securec_check_ss(ret, "", ""); return CStringGetTextDatum(queue_rec_lsn_s); } +/* + * 功能:获取当前XLOG插入LSN(日志序列号) + * + * 返回值:返回一个文本数据类型的表示当前XLOG插入LSN的字符串 + * + * 注意: + * 此函数用于获取当前XLOG插入LSN,即当前数据库中正在写入的写前日志(WAL)位置。 + * LSN(日志序列号)用于标识WAL中的位置,对于数据库的一致性和恢复非常重要。 + */ + Datum ckpt_view_get_current_xlog_insert_lsn() { + // 声明一个错误号变量 errno_t ret; + // 用于保存LSN的字符数组 char current_lsn_s[LSN_LENGTH]; + // 获取当前XLOG插入位置 XLogRecPtr current_xlog_insert = GetXLogInsertRecPtr(); - + // 将字符数组初始化为零 ret = memset_s(current_lsn_s, LSN_LENGTH, 0, LSN_LENGTH); securec_check(ret, "", ""); - - ret = snprintf_s(current_lsn_s, LSN_LENGTH, LSN_LENGTH - 1, "%X/%X", - (uint32)(current_xlog_insert >> XLOG_LSN_SWAP), (uint32)current_xlog_insert); + // 将XLOG插入LSN表示为十六进制字符串 + ret = snprintf_s(current_lsn_s, LSN_LENGTH, LSN_LENGTH - 1, "%X/%X", (uint32)(current_xlog_insert >> XLOG_LSN_SWAP), + (uint32)current_xlog_insert); securec_check_ss(ret, "", ""); + // 将LSN字符串转换为文本数据类型并返回 return CStringGetTextDatum(current_lsn_s); } +/* + * 功能:获取当前重做点的XLOG插入LSN(日志序列号) + * + * 参数列表:无 + * + * 返回值:返回一个文本数据类型的表示当前重做点的XLOG插入LSN的字符串 + * + * 注意: + * 此函数用于获取当前数据库的重做点(redo point),它是数据库在恢复期间的关键标志。 + * 重做点表示数据库的一致性,用于确定需要重做哪些事务以保持数据库的一致性。 + */ + Datum ckpt_view_get_redo_point() { + // 声明一个错误号变量 errno_t ret; + // 用于保存LSN的字符数组 char redo_lsn_s[LSN_LENGTH]; + // 获取当前重做点的XLOG插入LSN XLogRecPtr redo_lsn = g_instance.ckpt_cxt_ctl->ckpt_view.ckpt_current_redo_point; - + // 将字符数组初始化为零 ret = memset_s(redo_lsn_s, LSN_LENGTH, 0, LSN_LENGTH); securec_check(ret, "", ""); - - ret = snprintf_s( - redo_lsn_s, LSN_LENGTH, LSN_LENGTH - 1, "%X/%X", (uint32)(redo_lsn >> XLOG_LSN_SWAP), (uint32)redo_lsn); + // 将XLOG插入LSN表示为十六进制字符串 + ret = snprintf_s(redo_lsn_s, LSN_LENGTH, LSN_LENGTH - 1, "%X/%X", (uint32)(redo_lsn >> XLOG_LSN_SWAP), + (uint32)redo_lsn); securec_check_ss(ret, "", ""); + // 将LSN字符串转换为文本数据类型并返回 return CStringGetTextDatum(redo_lsn_s); } @@ -255,11 +312,10 @@ const incre_ckpt_view_col g_pagewirter_view_two_col[CANDIDATE_VIEW_COL_NUM] = { {"get_buf_clock_sweep", INT8OID, ckpt_view_get_num_clock_sweep}, {"seg_candidate_slots", INT4OID, ckpt_view_get_seg_candidate_nums}, {"seg_get_buf_from_list", INT8OID, ckpt_view_seg_get_num_candidate_list}, - {"seg_get_buf_clock_sweep", INT8OID, ckpt_view_seg_get_num_clock_sweep} -}; + {"seg_get_buf_clock_sweep", INT8OID, ckpt_view_seg_get_num_clock_sweep}}; - -const incre_ckpt_view_col g_ckpt_view_col[INCRE_CKPT_VIEW_COL_NUM] = {{"node_name", TEXTOID, ckpt_view_get_node_name}, +const incre_ckpt_view_col g_ckpt_view_col[INCRE_CKPT_VIEW_COL_NUM] = { + {"node_name", TEXTOID, ckpt_view_get_node_name}, {"ckpt_redo_point", TEXTOID, ckpt_view_get_redo_point}, {"ckpt_clog_flush_num", INT8OID, ckpt_view_get_clog_flush_num}, {"ckpt_csnlog_flush_num", INT8OID, ckpt_view_get_csnlog_flush_num}, @@ -284,78 +340,105 @@ bool IsPagewriterProcess(void) const int MAX_DIRTY_LIST_FLUSH_NUM = 1000 * DW_DIRTY_PAGE_MAX_FOR_NOHBK; +/* + * 功能:增量检查点页写入模块的上下文初始化 + * + * 注意: + * 此函数用于初始化增量检查点页写入模块的上下文,包括分配内存、初始化数据结构等。 + */ + void incre_ckpt_pagewriter_cxt_init() { + // 切换内存上下文到增量检查点上下文 MemoryContext oldcontext = MemoryContextSwitchTo(g_instance.increCheckPoint_context); - int thread_num = g_instance.attr.attr_storage.pagewriter_thread_num + 1; /* sub thread + one main thread */ - - g_instance.ckpt_cxt_ctl->pgwr_procs.writer_proc = (PageWriterProc*)palloc0(sizeof(PageWriterProc) * thread_num); + // 计算线程数(包括主线程) + int thread_num = g_instance.attr.attr_storage.pagewriter_thread_num + 1; + // 分配页写入进程结构的内存,初始化相关信息 + g_instance.ckpt_cxt_ctl->pgwr_procs.writer_proc = (PageWriterProc *)palloc0(sizeof(PageWriterProc) * thread_num); g_instance.ckpt_cxt_ctl->pgwr_procs.num = thread_num; g_instance.ckpt_cxt_ctl->pgwr_procs.sub_num = g_instance.attr.attr_storage.pagewriter_thread_num; g_instance.ckpt_cxt_ctl->pgwr_procs.running_num = 0; - + // 初始化一些检查点缓冲区相关的变量 g_instance.ckpt_cxt_ctl->prepared = 0; g_instance.ckpt_cxt_ctl->CkptBufferIdsTail = 0; g_instance.ckpt_cxt_ctl->CkptBufferIdsFlushPages = 0; g_instance.ckpt_cxt_ctl->CkptBufferIdsCompletedPages = 0; - + // 计算每个线程的脏页列表大小 uint32 dirty_list_size = MAX_DIRTY_LIST_FLUSH_NUM / thread_num; - - /* init thread dw cxt and dirty list */ + // 初始化每个页写入进程的数据结构 for (int i = 0; i < thread_num; i++) { PageWriterProc *pgwr = &g_instance.ckpt_cxt_ctl->pgwr_procs.writer_proc[i]; - char *unaligned_buf = (char*)palloc0((DW_BUF_MAX_FOR_NOHBK + 1) * BLCKSZ); - pgwr->thrd_dw_cxt.dw_buf = (char*)TYPEALIGN(BLCKSZ, unaligned_buf); + // 分配并对齐缓冲区内存 + char *unaligned_buf = (char *)palloc0((DW_BUF_MAX_FOR_NOHBK + 1) * BLCKSZ); + pgwr->thrd_dw_cxt.dw_buf = (char *)TYPEALIGN(BLCKSZ, unaligned_buf); + // 初始化其他字段 pgwr->thrd_dw_cxt.dw_page_idx = -1; pgwr->thrd_dw_cxt.is_new_relfilenode = false; pgwr->dirty_list_size = dirty_list_size; pgwr->dirty_buf_list = (CkptSortItem *)palloc0(dirty_list_size * sizeof(CkptSortItem)); } - + // 初始化候选列表 init_candidate_list(); + // 恢复原始内存上下文 (void)MemoryContextSwitchTo(oldcontext); } +/* + * 功能:候选缓冲区的初始化 + * + * 注意: + * 此函数用于初始化候选缓冲区,分配内存并初始化相关数据结构。 + * 该功能在增量检查点模式下才启用。 + */ + void candidate_buf_init(void) { + // 标志变量,用于检查是否找到了候选缓冲区和候选自由映射 bool found_candidate_buf = false; bool found_candidate_fm = false; - int buffer_num = TOTAL_BUFFER_NUM; + int buffer_num = TOTAL_BUFFER_NUM; // 总缓冲区数量 + // 如果未启用增量检查点,则直接返回 if (!ENABLE_INCRE_CKPT) { return; } - /* - * Each thread manages a part of the buffer. Several slots are reserved to - * prevent the thread first and last slots equals. - */ - g_instance.ckpt_cxt_ctl->candidate_buffers = (Buffer *) - ShmemInitStruct("CandidateBuffers", buffer_num * sizeof(Buffer), &found_candidate_buf); - g_instance.ckpt_cxt_ctl->candidate_free_map = (bool *) - ShmemInitStruct("CandidateFreeMap", buffer_num * sizeof(bool), &found_candidate_fm); + // 分配候选缓冲区和候选自由映射的共享内存空间 + g_instance.ckpt_cxt_ctl->candidate_buffers = + (Buffer *)ShmemInitStruct("CandidateBuffers", buffer_num * sizeof(Buffer), &found_candidate_buf); + g_instance.ckpt_cxt_ctl->candidate_free_map = + (bool *)ShmemInitStruct("CandidateFreeMap", buffer_num * sizeof(bool), &found_candidate_fm); + + // 如果已经找到了候选缓冲区和候选自由映射,则断言两者都已经找到 if (found_candidate_buf || found_candidate_fm) { Assert(found_candidate_buf && found_candidate_fm); } else { - /* The memory of the memset sometimes exceeds 2 GB. so, memset_s cannot be used. */ - MemSet((char*)g_instance.ckpt_cxt_ctl->candidate_buffers, 0, buffer_num * sizeof(Buffer)); - MemSet((char*)g_instance.ckpt_cxt_ctl->candidate_free_map, 0, buffer_num * sizeof(bool)); + // 如果未找到,初始化候选缓冲区和候选自由映射的内存空间 + MemSet((char *)g_instance.ckpt_cxt_ctl->candidate_buffers, 0, buffer_num * sizeof(Buffer)); + MemSet((char *)g_instance.ckpt_cxt_ctl->candidate_free_map, 0, buffer_num * sizeof(bool)); - /* switchover will triggers the following code */ + // 如果页写入进程已经初始化,初始化候选列表 if (g_instance.ckpt_cxt_ctl->pgwr_procs.writer_proc != NULL) { init_candidate_list(); } } } +/* + * 功能:初始化候选列表 + * + * 注意: + * 此函数用于初始化候选列表,分配内存并设置相关数据结构,以支持增量检查点操作。 + */ + static void init_candidate_list() { int thread_num = g_instance.ckpt_cxt_ctl->pgwr_procs.sub_num; - int normal_avg_num = SharedBufferNumber / thread_num; - int seg_avr_num = SEGMENT_BUFFER_NUM / thread_num; + int normal_avg_num = SharedBufferNumber / thread_num; // 平均每个线程的普通候选缓冲区数量 + int seg_avr_num = SEGMENT_BUFFER_NUM / thread_num; // 平均每个线程的段候选缓冲区数量 PageWriterProc *pgwr = NULL; - /* Init main thread, the candidate list only store segment buffer */ + /* 初始化主线程,候选列表仅存储段缓冲区 */ pgwr = &g_instance.ckpt_cxt_ctl->pgwr_procs.writer_proc[0]; pgwr->cand_buf_list = NULL; pgwr->cand_list_size = 0; @@ -372,19 +455,21 @@ static void init_candidate_list() int end = start + normal_avg_num; int seg_start = SegmentBufferStartID + seg_avr_num * (i - 1); int seg_end = seg_start + seg_avr_num; + + // 如果是最后一个线程,调整结束索引以处理剩余的缓冲区 if (i == thread_num) { end += SharedBufferNumber % thread_num; seg_end += SEGMENT_BUFFER_NUM % thread_num; } - /* init normal candidat list */ + /* 初始化普通候选列表 */ pgwr->head = 0; pgwr->tail = 0; pgwr->buf_id_start = start; pgwr->cand_list_size = end - start; pgwr->cand_buf_list = &g_instance.ckpt_cxt_ctl->candidate_buffers[start]; - /* init segment candidat list */ + /* 初始化段候选列表 */ pgwr->seg_head = 0; pgwr->seg_tail = 0; pgwr->seg_cand_list_size = seg_end - seg_start; @@ -392,75 +477,137 @@ static void init_candidate_list() pgwr->seg_id_start = seg_start; } } - +/* + * 功能:获取脏页队列头缓冲区 + * + * 返回值:脏页队列头缓冲区的索引 + * + * 注意: + * 此函数用于获取脏页队列的头缓冲区。它从原子变量中读取队列头位置,计算实际位置,并返回对应缓冲区的索引。 + */ int get_dirty_page_queue_head_buffer() { + // 读取脏页队列头部的位置 uint64 dirty_queue_head = pg_atomic_read_u64(&g_instance.ckpt_cxt_ctl->dirty_page_queue_head); + // 计算实际位置 uint64 actual_loc = dirty_queue_head % g_instance.ckpt_cxt_ctl->dirty_page_queue_size; + // 返回脏页队列头部的缓冲区编号 return g_instance.ckpt_cxt_ctl->dirty_page_queue[actual_loc].buffer; } - -bool is_dirty_page_queue_full(BufferDesc* buf) +/* + * 功能:检查脏页队列是否已满 + * + * 参数列表: + * buf:要检查的缓冲区描述符 + * + * 返回值:如果脏页队列已满,返回true;否则,返回false + * + * 注意: + * 此函数用于检查脏页队列是否已满。如果脏页数量已达到队列容量的一定百分比,并且当前线程没有持有队列头缓冲区的锁, + * 则认为队列已满,返回true。 + */ +bool is_dirty_page_queue_full(BufferDesc *buf) { + // 检查脏页数量是否已达到一定百分比 if ((get_dirty_page_num() >= - g_instance.ckpt_cxt_ctl->dirty_page_queue_size * PAGE_QUEUE_SLOT_USED_MAX_PERCENTAGE) && + g_instance.ckpt_cxt_ctl->dirty_page_queue_size * PAGE_QUEUE_SLOT_USED_MAX_PERCENTAGE) && g_instance.ckpt_cxt_ctl->backend_wait_lock != buf->content_lock) { + // 获取脏页队列头部的缓冲区 Buffer queue_head_buffer = get_dirty_page_queue_head_buffer(); + // 如果队列头部缓冲区有效 if (!BufferIsInvalid(queue_head_buffer)) { - BufferDesc* queue_head_buffer_desc = GetBufferDescriptor(queue_head_buffer - 1); + // 获取队列头部缓冲区的描述符 + BufferDesc *queue_head_buffer_desc = GetBufferDescriptor(queue_head_buffer - 1); + // 如果当前线程没有持有队列头缓冲区的锁 if (!LWLockHeldByMeInMode(queue_head_buffer_desc->content_lock, LW_EXCLUSIVE)) { + // 认为队列已满,返回true return true; } } else { + // 如果队列头部缓冲区无效,也认为队列已满,返回true return true; } } + // 队列未满,返回false return false; } -bool atomic_push_pending_flush_queue(Buffer buffer, XLogRecPtr* queue_head_lsn, uint64* new_tail_loc) +/* + * 功能:原子方式将缓冲区推送到待刷新队列 + * + * 参数列表: + * buffer:要推送的缓冲区 + * queue_head_lsn:队列头部的LSN值 + * new_tail_loc:新的队列尾部位置 + * + * 返回值:如果成功推送缓冲区到队列,返回true;否则,返回false + * + * 注意: + * 此函数用于将缓冲区原子方式推送到待刷新队列。它首先比较并交换队列的头部LSN和尾部位置,然后检查 + * 待刷新队列是否已满,如果队列已满则返回false。如果队列未满,将缓冲区推送到队列,并返回true。 + */ +bool atomic_push_pending_flush_queue(Buffer buffer, XLogRecPtr *queue_head_lsn, uint64 *new_tail_loc) { + // 定义比较、交换和当前值的变量 uint128_u compare; uint128_u exchange; uint128_u current; - - compare = atomic_compare_and_swap_u128((uint128_u*)&g_instance.ckpt_cxt_ctl->dirty_page_queue_reclsn); + // 使用原子方式比较和交换队列的LSN值 + compare = atomic_compare_and_swap_u128((uint128_u *)&g_instance.ckpt_cxt_ctl->dirty_page_queue_reclsn); + // 断言确保LSN值的大小符合预期 Assert(sizeof(g_instance.ckpt_cxt_ctl->dirty_page_queue_reclsn) == SIZE_OF_UINT64); Assert(sizeof(g_instance.ckpt_cxt_ctl->dirty_page_queue_tail) == SIZE_OF_UINT64); - loop: + // 设置交换值为比较值的拷贝,并将尾部位置加1 exchange.u64[0] = compare.u64[0]; exchange.u64[1] = compare.u64[1] + 1; *new_tail_loc = exchange.u64[1]; - + // 检查待刷新队列是否已满,如果已满则返回false if ((uint64)(get_dirty_page_num() + PAGE_QUEUE_SLOT_MIN_RESERVE_NUM) >= g_instance.ckpt_cxt_ctl->dirty_page_queue_size) { return false; } - - current = atomic_compare_and_swap_u128( - (uint128_u*)&g_instance.ckpt_cxt_ctl->dirty_page_queue_reclsn, compare, exchange); - + // 使用原子方式比较和交换队列的LSN值和尾部位置 + current = + atomic_compare_and_swap_u128((uint128_u *)&g_instance.ckpt_cxt_ctl->dirty_page_queue_reclsn, compare, exchange); + // 如果比较和交换失败,重试 if (!UINT128_IS_EQUAL(compare, current)) { UINT128_COPY(compare, current); goto loop; } - + // 设置队列头部的LSN值为当前值的第一个64位 *queue_head_lsn = current.u64[0]; + // 减去1以更新新的尾部位置 *new_tail_loc -= 1; return true; } +/* + * 功能:将缓冲区推送到待刷新队列 + * + * 参数列表: + * buffer:要推送的缓冲区 + * + * 返回值:如果成功推送缓冲区到队列,返回true;否则,返回false + * + * 注意: + * 此函数用于将缓冲区推送到待刷新队列。它首先检查缓冲区的LSN是否无效,如果不是无效的LSN,则返回false。 + * 如果LSN无效,根据平台的不同使用不同的方式推送缓冲区到队列。如果缓冲区数量达到限制,则返回false。 + * 否则,将缓冲区推送到队列,更新LSN和队列位置等信息,并返回true。 + */ bool push_pending_flush_queue(Buffer buffer) { uint64 new_tail_loc = 0; uint64 actual_loc; XLogRecPtr queue_head_lsn = InvalidXLogRecPtr; - BufferDesc* buf_desc = GetBufferDescriptor(buffer - 1); + BufferDesc *buf_desc = GetBufferDescriptor(buffer - 1); bool push_finish = false; + // 断言确保缓冲区的LSN无效 Assert(XLogRecPtrIsInvalid(pg_atomic_read_u64(&buf_desc->rec_lsn))); + + // 根据平台使用不同的方式推送缓冲区到队列 #if defined(__x86_64__) || defined(__aarch64__) push_finish = atomic_push_pending_flush_queue(buffer, &queue_head_lsn, &new_tail_loc); if (!push_finish) { @@ -469,6 +616,7 @@ bool push_pending_flush_queue(Buffer buffer) #else SpinLockAcquire(&g_instance.ckpt_cxt_ctl->queue_lock); + // 检查队列是否已满,如果已满则释放自旋锁并返回false if ((uint64)(get_dirty_page_num() + PAGE_QUEUE_SLOT_MIN_RESERVE_NUM) >= g_instance.ckpt_cxt_ctl->dirty_page_queue_size) { SpinLockRelease(&g_instance.ckpt_cxt_ctl->queue_lock); @@ -479,6 +627,7 @@ bool push_pending_flush_queue(Buffer buffer) SpinLockRelease(&g_instance.ckpt_cxt_ctl->queue_lock); #endif + // 更新缓冲区的LSN值和位置信息 pg_atomic_write_u64(&buf_desc->rec_lsn, queue_head_lsn); actual_loc = new_tail_loc % g_instance.ckpt_cxt_ctl->dirty_page_queue_size; buf_desc->dirty_queue_loc = actual_loc; @@ -489,19 +638,44 @@ bool push_pending_flush_queue(Buffer buffer) return true; } -void remove_dirty_page_from_queue(BufferDesc* buf) +/* + * 功能:从脏页队列中移除缓冲区 + * + * 参数列表: + * buf:要从队列中移除的缓冲区 + * + * 注意: + * 此函数用于从脏页队列中移除缓冲区。它首先断言缓冲区的dirty_queue_loc不是PG_UINT64_MAX,以确保缓冲区在队列中。 + * 然后,将缓冲区在队列中的位置标记为空,将缓冲区的LSN标记为无效,将缓冲区的dirty_queue_loc标记为PG_UINT64_MAX, + * 并递减脏页队列中的实际脏页数量。 + */ +void remove_dirty_page_from_queue(BufferDesc *buf) { + // 断言确保缓冲区在队列中 Assert(buf->dirty_queue_loc != PG_UINT64_MAX); + // 将缓冲区在队列中的位置标记为空 g_instance.ckpt_cxt_ctl->dirty_page_queue[buf->dirty_queue_loc].buffer = 0; + // 将缓冲区的LSN标记为无效 pg_atomic_write_u64(&buf->rec_lsn, InvalidXLogRecPtr); + // 将缓冲区的dirty_queue_loc标记为PG_UINT64_MAX buf->dirty_queue_loc = PG_UINT64_MAX; + // 递减脏页队列中的实际脏页数量 (void)pg_atomic_fetch_sub_u32(&g_instance.ckpt_cxt_ctl->actual_dirty_page_num, 1); } +/* + * 功能:获取脏页队列的尾部位置 + * + * 注意: + * 此函数用于获取脏页队列的尾部位置。根据体系结构的不同,它使用不同的方式读取队列尾部位置。 + * 对于x86_64和aarch64体系结构,使用原子操作pg_atomic_barrier_read_u64()来读取尾部位置。 + * 对于其他体系结构,使用自旋锁来保护并读取尾部位置。 + */ uint64 get_dirty_page_queue_tail() { uint64 tail = 0; + // 根据体系结构的不同,采用不同的方式读取队列尾部位置 #if defined(__x86_64__) || defined(__aarch64__) tail = pg_atomic_barrier_read_u64(&g_instance.ckpt_cxt_ctl->dirty_page_queue_tail); #else @@ -513,11 +687,23 @@ uint64 get_dirty_page_queue_tail() return tail; } +/* + * 功能:获取脏页数量 + * + * 注意: + * 此函数用于获取脏页队列中的脏页数量。它通过读取脏页队列的头部和尾部位置, + * 计算两者之间的差值来确定脏页的数量。使用volatile关键字修饰dirty_page_head, + * 以确保编译器不会对其进行优化。 + */ int64 get_dirty_page_num() { + // 读取脏页队列的头部位置 volatile uint64 dirty_page_head = pg_atomic_read_u64(&g_instance.ckpt_cxt_ctl->dirty_page_queue_head); + // 获取脏页队列的尾部位置 uint64 dirty_page_tail = get_dirty_page_queue_tail(); + // 计算脏页数量 int64 page_num = dirty_page_tail - dirty_page_head; + // 确保脏页数量不小于0 Assert(page_num >= 0); return page_num; } @@ -569,54 +755,75 @@ static uint32 ckpt_get_expected_flush_num() * @out Offset to the new head * @return Actual number of dirty pages need to flush */ -const int MAX_SCAN_NUM = 131072; /* 1GB buffers */ +const int MAX_SCAN_NUM = 131072; /* 1GB buffers */ +/* + * 功能:对脏页队列进行排序以准备刷新 + * + * 参数列表: + * is_new_relfilenode:指示是否存在新的关系文件节点 + * flush_queue_num:要刷新的脏页队列的数量 + * + * 注意: + * 此函数用于对脏页队列中的脏页进行排序,以准备刷新操作。它会根据一定的策略选择要刷新的脏页, + * 并将它们存储在CkptBufferIds数组中。同时,函数会更新is_new_relfilenode以指示是否存在新的关系文件节点。 + */ static uint32 ckpt_qsort_dirty_page_for_flush(bool *is_new_relfilenode, uint32 flush_queue_num) { - uint32 num_to_flush = 0; - errno_t rc; - uint32 i; - uint32 scan_end = 0; - int64 dirty_page_num; - uint64 dirty_queue_head; - uint32 buf_num = TOTAL_BUFFER_NUM; - uint32 buffer_slot_num = MIN(flush_queue_num, buf_num); + uint32 num_to_flush = 0; // 要刷新的脏页数量 + errno_t rc; // 用于处理返回值的变量 + uint32 i; // 循环计数变量 + uint32 scan_end = 0; // 扫描结束位置 + int64 dirty_page_num; // 脏页数量 + uint64 dirty_queue_head; // 脏页队列头部位置 + uint32 buf_num = TOTAL_BUFFER_NUM; // 总的缓冲区数量 + uint32 buffer_slot_num = MIN(flush_queue_num, buf_num); // 要刷新的缓冲区槽数量,不超过flush_queue_num和buf_num - rc = memset_s(g_instance.ckpt_cxt_ctl->CkptBufferIds, buffer_slot_num * sizeof(CkptSortItem), - 0, buffer_slot_num * sizeof(CkptSortItem)); + // 初始化CkptBufferIds数组 + rc = memset_s(g_instance.ckpt_cxt_ctl->CkptBufferIds, buffer_slot_num * sizeof(CkptSortItem), 0, + buffer_slot_num * sizeof(CkptSortItem)); securec_check(rc, "", ""); /* - * Before selecting a batch of dirty pages to flush, move dirty page queue head to - * skip slot of invalid buffer of queue head. + * 在选择一批要刷新的脏页之前,将脏页队列头部移动,跳过队列头部的无效缓冲槽。 */ ckpt_try_prune_dirty_page_queue(); + + // 移动到队列头部的无效元素 ckpt_try_skip_invalid_elem_in_queue_head(); + + // 获取脏页数量和脏页队列头部位置 dirty_page_num = get_dirty_page_num(); dirty_queue_head = pg_atomic_read_u64(&g_instance.ckpt_cxt_ctl->dirty_page_queue_head); + + // 确定要扫描的结束位置,最多扫描MAX_SCAN_NUM个脏页 scan_end = MIN(MAX_SCAN_NUM, dirty_page_num); + // 循环扫描脏页队列 for (i = 0; i < scan_end; i++) { uint32 buf_state; Buffer buffer; - BufferDesc* buf_desc = NULL; - CkptSortItem* item = NULL; + BufferDesc *buf_desc = NULL; + CkptSortItem *item = NULL; uint64 temp_loc = (dirty_queue_head + i) % g_instance.ckpt_cxt_ctl->dirty_page_queue_size; - volatile DirtyPageQueueSlot* slot = &g_instance.ckpt_cxt_ctl->dirty_page_queue[temp_loc]; + volatile DirtyPageQueueSlot *slot = &g_instance.ckpt_cxt_ctl->dirty_page_queue[temp_loc]; - /* slot location is pre-occupied, but the buffer not set finish, need break. */ + /* 如果槽位的状态无效,说明槽位已被清空,跳出循环 */ if (!(pg_atomic_read_u32(&slot->slot_state) & SLOT_VALID)) { break; } pg_memory_barrier(); buffer = slot->buffer; - /* slot state is valid, buffer is invalid, the slot buffer set 0 when BufferAlloc or InvalidateBuffer */ + + /* 如果缓冲区无效,说明该脏页已被移除,继续下一个槽位的处理 */ if (BufferIsInvalid(buffer)) { - continue; /* this tempLoc maybe set 0 when remove dirty page */ + continue; } + // 获取缓冲区描述符 buf_desc = GetBufferDescriptor(buffer - 1); buf_state = LockBufHdr(buf_desc); + // 如果缓冲区状态为脏页,则加入要刷新的队列中 if (buf_state & BM_DIRTY) { buf_state |= BM_CHECKPOINT_NEEDED; item = &g_instance.ckpt_cxt_ctl->CkptBufferIds[num_to_flush++]; @@ -626,7 +833,7 @@ static uint32 ckpt_qsort_dirty_page_for_flush(bool *is_new_relfilenode, uint32 f item->bucketNode = buf_desc->tag.rnode.bucketNode; item->forkNum = buf_desc->tag.forkNum; item->blockNum = buf_desc->tag.blockNum; - if(IsSegmentFileNode(buf_desc->tag.rnode) || buf_desc->tag.rnode.opt != 0) { + if (IsSegmentFileNode(buf_desc->tag.rnode) || buf_desc->tag.rnode.opt != 0) { *is_new_relfilenode = true; } } @@ -637,46 +844,71 @@ static uint32 ckpt_qsort_dirty_page_for_flush(bool *is_new_relfilenode, uint32 f } } + // 对排序后的脏页进行排序 qsort(g_instance.ckpt_cxt_ctl->CkptBufferIds, num_to_flush, sizeof(CkptSortItem), ckpt_buforder_comparator); return num_to_flush; } +/* + * 功能:唤醒子线程 + * + * 注意: + * 此函数用于唤醒子线程,使它们开始工作。它将重置子线程的状态和需要刷新的数量, + * 然后通过设置线程的procLatch来触发线程的执行。 + */ static void wakeup_sub_thread() { PageWriterProc *pgwr = NULL; + // 遍历子线程列表,跳过主线程 for (int thread_loc = 1; thread_loc < g_instance.ckpt_cxt_ctl->pgwr_procs.num; thread_loc++) { pgwr = &g_instance.ckpt_cxt_ctl->pgwr_procs.writer_proc[thread_loc]; + // 重置子线程的状态和需要刷新的数量 pgwr->start_loc = 0; pgwr->need_flush_num = 0; - if (pgwr->proc != NULL) { + // 增加运行中的子线程计数 (void)pg_atomic_add_fetch_u32(&g_instance.ckpt_cxt_ctl->pgwr_procs.running_num, 1); pg_write_barrier(); + // 设置子线程需要刷新数据的标志 g_instance.ckpt_cxt_ctl->pgwr_procs.writer_proc[thread_loc].need_flush = true; pg_write_barrier(); + // 触发线程执行 SetLatch(&(pgwr->proc->procLatch)); } } } +/* + * 功能:准备脏页应用状态 + * + * 参数列表: + * requested_flush_num:请求刷新的脏页数量 + * is_new_relfilenode:是否为新的relfilenode + * + * 注意: + * 此函数用于准备脏页的应用状态。它初始化一些计数器和状态,以及标志是否为新的relfilenode。 + */ static void prepare_dirty_page_applied_state(uint32 requested_flush_num, bool is_new_relfilenode) { int thread_loc; int thread_num; - + // 初始化计数器 pg_atomic_init_u32(&g_instance.ckpt_cxt_ctl->CkptBufferIdsCompletedPages, 0); pg_atomic_init_u32(&g_instance.ckpt_cxt_ctl->CkptBufferIdsTail, 0); + // 设置请求刷新的脏页数量 g_instance.ckpt_cxt_ctl->CkptBufferIdsFlushPages = requested_flush_num; + // 获取线程数 thread_num = g_instance.ckpt_cxt_ctl->pgwr_procs.num; - PageWriterProc* pgwr; - + PageWriterProc *pgwr; + // 设置是否为新的relfilenode g_instance.dw_batch_cxt.is_new_relfilenode = is_new_relfilenode; + // 遍历子线程,目前没有实际操作 for (thread_loc = 1; thread_loc < thread_num; thread_loc++) { pgwr = &g_instance.ckpt_cxt_ctl->pgwr_procs.writer_proc[thread_loc]; } - pg_write_barrier(); + // 初始化准备状态 pg_atomic_init_u32(&g_instance.ckpt_cxt_ctl->prepared, 1); pg_write_barrier(); } @@ -699,7 +931,7 @@ static void ckpt_move_queue_head_after_flush() /* Finish flush dirty page, move the dirty page queue head, and clear the slot state. */ for (uint32 i = 0; i < dirty_page_num; i++) { uint64 temp_loc = (dirty_queue_head + i) % g_instance.ckpt_cxt_ctl->dirty_page_queue_size; - volatile DirtyPageQueueSlot* slot = &g_instance.ckpt_cxt_ctl->dirty_page_queue[temp_loc]; + volatile DirtyPageQueueSlot *slot = &g_instance.ckpt_cxt_ctl->dirty_page_queue[temp_loc]; if (!(pg_atomic_read_u32(&slot->slot_state) & SLOT_VALID)) { break; } @@ -729,8 +961,8 @@ static void ckpt_move_queue_head_after_flush() if (u_sess->attr.attr_storage.log_pagewriter) { ereport(LOG, (errmodule(MOD_INCRE_CKPT), - errmsg("Page Writer flushed: %u pages, remaining dirty_page_num: %ld", - g_instance.ckpt_cxt_ctl->page_writer_last_flush, get_dirty_page_num()))); + errmsg("Page Writer flushed: %u pages, remaining dirty_page_num: %ld", + g_instance.ckpt_cxt_ctl->page_writer_last_flush, get_dirty_page_num()))); } return; } @@ -739,76 +971,110 @@ static void ckpt_move_queue_head_after_flush() * @Description: pagewriter main thread select one batch dirty page, divide this batch page to all thread, * wait all thread finish flush, update the statistics. */ +/* + * 功能:检查并刷新脏页 + * + * 注意: + * 此函数用于检查并刷新脏页。它初始化写回上下文、获取预期刷新数量,并根据脏页排序获取请求刷新数量。 + * 接着准备脏页的应用状态,唤醒子线程,最后完成刷新操作。 + */ static void ckpt_pagewriter_main_thread_flush_dirty_page() { - WritebackContext wb_context; - uint32 requested_flush_num; - int32 expected_flush_num; - bool is_new_relfilenode = false; - + WritebackContext wb_context; // 写回上下文 + uint32 requested_flush_num; // 请求刷新的脏页数量 + int32 expected_flush_num; // 预期刷新的脏页数量 + bool is_new_relfilenode = false; // 是否为新的relfilenode + // 断言当前线程是主线程 Assert(t_thrd.pagewriter_cxt.pagewriter_id == 0); + // 初始化写回上下文 WritebackContextInit(&wb_context, &t_thrd.pagewriter_cxt.page_writer_after); + // 扩展资源拥有者的缓冲区 ResourceOwnerEnlargeBuffers(t_thrd.utils_cxt.CurrentResourceOwner); - + // 获取预期刷新的脏页数量 expected_flush_num = ckpt_get_expected_flush_num(); + // 如果预期刷新数量为0,唤醒子线程并返回 if (expected_flush_num == 0) { - /* if not dirty page flush, wakeup sub thread can the buffer pool */ wakeup_sub_thread(); return; } - + // 重置上次队列刷新和刷新的时间 g_instance.ckpt_cxt_ctl->page_writer_last_queue_flush = 0; g_instance.ckpt_cxt_ctl->page_writer_last_flush = 0; - + // 根据脏页排序获取请求刷新的脏页数量,并标志是否为新的relfilenode requested_flush_num = ckpt_qsort_dirty_page_for_flush(&is_new_relfilenode, expected_flush_num); - - /* Step 1: set up atomic state for dirty page appiled. */ + // 准备脏页的应用状态 prepare_dirty_page_applied_state(requested_flush_num, is_new_relfilenode); - - /* Step 2: wake up all subthreads and main thread sleep. */ + // 唤醒子线程 wakeup_sub_thread(); - + // 完成刷新操作 ckpt_move_queue_head_after_flush(); smgrcloseall(); return; } +/* + * 功能:获取页写入器的睡眠时间 + * + * 返回值: + * 返回页写入器需要睡眠的时间(以毫秒为单位) + * + * 注意: + * 此函数用于确定页写入器需要休眠的时间,以控制刷新脏页的频率。 + * 如果FULL_CKPT为真,表示全量检查点,不需要休眠,返回0。 + * 否则,计算当前时间和下一次刷新的时间差,将其限制在u_sess->attr.attr_storage.pageWriterSleep之内, + * 并返回休眠时间。 + */ static int64 get_pagewriter_sleep_time() { - uint64 now; - int64 time_diff; - + uint64 now; // 当前时间 + int64 time_diff; // 时间差 + // 如果是全量检查点,不需要休眠,返回0 if (FULL_CKPT) { return 0; } - + // 获取当前时间(以毫秒为单位) now = get_time_ms(); + // 计算下一次刷新的时间与当前时间的时间差 if (t_thrd.pagewriter_cxt.next_flush_time > now) { time_diff = MAX(t_thrd.pagewriter_cxt.next_flush_time - now, 1); } else { time_diff = 0; } + // 将时间差限制在u_sess->attr.attr_storage.pageWriterSleep之内 time_diff = MIN(time_diff, u_sess->attr.attr_storage.pageWriterSleep); + // 返回休眠时间 return time_diff; } +/* + * 功能:获取目标LSN所在位置的队列位置 + * + * 参数: + * target_lsn:目标LSN,用于查找位置 + * + * 返回值: + * 返回目标LSN所在位置的队列位置(uint64类型) + * + * 注意: + * 此函数用于查找目标LSN在脏页队列中的位置。 + * 它遍历脏页队列,根据LSN的比较确定目标LSN的位置,并返回队列位置。 + */ uint64 get_loc_for_lsn(XLogRecPtr target_lsn) { - uint64 last_loc = 0; - XLogRecPtr page_rec_lsn = InvalidXLogRecPtr; - uint64 queue_loc = pg_atomic_read_u64(&g_instance.ckpt_cxt_ctl->dirty_page_queue_head); - + uint64 last_loc = 0; // 最终位置 + XLogRecPtr page_rec_lsn = InvalidXLogRecPtr; // 页的LSN + uint64 queue_loc = pg_atomic_read_u64(&g_instance.ckpt_cxt_ctl->dirty_page_queue_head); // 队列位置 + // 如果脏页数为0,返回脏页队列的尾部位置 if (get_dirty_page_num() == 0) { return get_dirty_page_queue_tail(); } - + // 遍历脏页队列,查找目标LSN的位置 while (queue_loc < get_dirty_page_queue_tail()) { Buffer buffer; BufferDesc *buf_desc = NULL; uint64 temp_loc = queue_loc % g_instance.ckpt_cxt_ctl->dirty_page_queue_size; volatile DirtyPageQueueSlot *slot = &g_instance.ckpt_cxt_ctl->dirty_page_queue[temp_loc]; - - /* slot location is pre-occupied, but the buffer not set finish, need wait and retry. */ + /* slot位置被占用,但缓冲区未设置完成,需要等待并重试 */ if (!(pg_atomic_read_u32(&slot->slot_state) & SLOT_VALID)) { pg_usleep(1); queue_loc = pg_atomic_read_u64(&g_instance.ckpt_cxt_ctl->dirty_page_queue_head); @@ -816,53 +1082,68 @@ uint64 get_loc_for_lsn(XLogRecPtr target_lsn) } queue_loc++; pg_memory_barrier(); - buffer = slot->buffer; - /* slot state is vaild, buffer is invalid, the slot buffer set 0 when BufferAlloc or InvalidateBuffer */ + /* slot状态有效,但缓冲区无效,即缓冲区设置为0(BufferAlloc或InvalidateBuffer时的情况) */ if (BufferIsInvalid(buffer)) { continue; } buf_desc = GetBufferDescriptor(buffer - 1); page_rec_lsn = pg_atomic_read_u64(&buf_desc->rec_lsn); + // 如果缓冲区有效且目标LSN小于等于页的LSN,确定位置并中断循环 if (!BufferIsInvalid(slot->buffer) && XLByteLE(target_lsn, page_rec_lsn)) { last_loc = queue_loc - 1; break; } } - + // 如果未找到位置,返回脏页队列的尾部位置 if (last_loc == 0) { return get_dirty_page_queue_tail(); } - + // 返回目标LSN所在位置的队列位置 return last_loc; } -static uint32 get_page_num_for_lsn(XLogRecPtr target_lsn, uint32 max_num) +/* + * 功能:获取目标LSN之前的脏页数量(最多max_num个) + * + * 参数: + * target_lsn:目标LSN,用于计算脏页数量 + * max_num:最多计算的脏页数量 + * + * 返回值: + * 返回目标LSN之前的脏页数量(uint32类型) + * + * 注意: + * 此函数用于计算目标LSN之前的脏页数量,最多计算max_num个。 + * 它遍历脏页队列,根据LSN的比较确定脏页的数量,不超过max_num个。 + */ +uint32 get_page_num_for_lsn(XLogRecPtr target_lsn, uint32 max_num) { uint32 i; - uint32 num_for_lsn = 0; - XLogRecPtr page_rec_lsn = InvalidXLogRecPtr; - int64 dirty_page_num = get_dirty_page_num(); - uint64 dirty_queue_head = pg_atomic_read_u64(&g_instance.ckpt_cxt_ctl->dirty_page_queue_head); + uint32 num_for_lsn = 0; // 目标LSN之前的脏页数量 + XLogRecPtr page_rec_lsn = InvalidXLogRecPtr; // 页的LSN + int64 dirty_page_num = get_dirty_page_num(); // 脏页数量 + uint64 dirty_queue_head = pg_atomic_read_u64(&g_instance.ckpt_cxt_ctl->dirty_page_queue_head); // 队列头位置 + // 遍历脏页队列,计算目标LSN之前的脏页数量(不超过max_num个) for (i = 0; i < dirty_page_num; i++) { Buffer buffer; - BufferDesc* buf_desc = NULL; + BufferDesc *buf_desc = NULL; uint64 temp_loc = (dirty_queue_head + i) % g_instance.ckpt_cxt_ctl->dirty_page_queue_size; - volatile DirtyPageQueueSlot* slot = &g_instance.ckpt_cxt_ctl->dirty_page_queue[temp_loc]; - - /* slot location is pre-occupied, but the buffer not set finish, need break. */ + volatile DirtyPageQueueSlot *slot = &g_instance.ckpt_cxt_ctl->dirty_page_queue[temp_loc]; + /* slot位置被占用,但缓冲区未设置完成,需要中断循环 */ if (!(pg_atomic_read_u32(&slot->slot_state) & SLOT_VALID)) { break; } pg_read_barrier(); buffer = slot->buffer; - /* slot state is valid, buffer is invalid, the slot buffer set 0 when BufferAlloc or InvalidateBuffer */ + /* slot状态有效,但缓冲区无效,即缓冲区设置为0(BufferAlloc或InvalidateBuffer时的情况) */ if (BufferIsInvalid(buffer)) { - continue; /* this tempLoc maybe set 0 when remove dirty page */ + continue; /* 当移除脏页时,此tempLoc可能设置为0 */ } buf_desc = GetBufferDescriptor(buffer - 1); page_rec_lsn = pg_atomic_read_u64(&buf_desc->rec_lsn); + // 如果缓冲区有效且目标LSN小于等于页的LSN,计算完成并中断循环 if (!BufferIsInvalid(slot->buffer) && XLByteLE(target_lsn, page_rec_lsn)) { break; } @@ -876,98 +1157,114 @@ static uint32 get_page_num_for_lsn(XLogRecPtr target_lsn, uint32 max_num) const float HIGH_WATER = 0.75; const int BYTE_PER_KB = 1024; +/* + * 功能:计算最大的刷新数量 + * + * 注意: + * 此函数用于计算最大的刷新数量,以控制写入IO的速率。 + * 它基于LSN和缓冲区的使用情况,动态地调整队列刷新和列表刷新的最大数量。 + */ static void calculate_max_flush_num() { - uint32 blk_size = BLCKSZ / BYTE_PER_KB; - uint32 max_io = u_sess->attr.attr_storage.max_io_capacity / blk_size / 2; - float rate_lsn; - float rate_buf; - uint32 queue_flush_max; - uint32 list_flush_max; - XLogRecPtr min_lsn = InvalidXLogRecPtr; - XLogRecPtr cur_lsn = InvalidXLogRecPtr; - double lsn_percent; - + uint32 blk_size = BLCKSZ / BYTE_PER_KB; // 块大小(以KB为单位) + uint32 max_io = u_sess->attr.attr_storage.max_io_capacity / blk_size / 2; // 最大IO容量(以块为单位的一半) + float rate_lsn; // 基于LSN的刷新率 + float rate_buf; // 基于缓冲区的刷新率 + uint32 queue_flush_max; // 队列刷新的最大数量 + uint32 list_flush_max; // 列表刷新的最大数量 + XLogRecPtr min_lsn = InvalidXLogRecPtr; // 最小LSN + XLogRecPtr cur_lsn = InvalidXLogRecPtr; // 当前LSN + double lsn_percent; // LSN使用百分比 + // 如果处于FULL_CKPT模式且不处于恢复中,则将队列和列表的最大刷新数量都设置为最大IO容量的一半 if (unlikely(FULL_CKPT && !RecoveryInProgress())) { g_instance.ckpt_cxt_ctl->pgwr_procs.queue_flush_max = max_io; g_instance.ckpt_cxt_ctl->pgwr_procs.list_flush_max = 0; return; } - + // 获取最小LSN min_lsn = ckpt_get_min_rec_lsn(); if (XLogRecPtrIsInvalid(min_lsn)) { min_lsn = get_dirty_page_queue_rec_lsn(); } - - /* primary get the xlog insert loc, standby get the replay loc */ + // 如果处于恢复中,则获取回放LSN;否则获取当前插入LSN if (RecoveryInProgress()) { cur_lsn = GetXLogReplayRecPtr(NULL); } else { cur_lsn = GetXLogInsertRecPtr(); } - - lsn_percent = (double)(cur_lsn - min_lsn) / - ((double)u_sess->attr.attr_storage.max_redo_log_size * BYTE_PER_KB); - + // 计算LSN使用百分比 + lsn_percent = (double)(cur_lsn - min_lsn) / ((double)u_sess->attr.attr_storage.max_redo_log_size * BYTE_PER_KB); + // 计算基于LSN的刷新率 rate_lsn = 1 - HIGH_WATER / (lsn_percent + HIGH_WATER); - + // 限制LSN刷新率不超过HIGH_WATER rate_lsn = MIN(rate_lsn, HIGH_WATER); - + // 基于缓冲区的刷新率为1减去LSN刷新率 rate_buf = 1 - rate_lsn; - + // 计算队列刷新的最大数量和列表刷新的最大数量 queue_flush_max = max_io * rate_lsn; list_flush_max = max_io * rate_buf; - + // 设置队列刷新和列表刷新的最大数量 g_instance.ckpt_cxt_ctl->pgwr_procs.queue_flush_max = queue_flush_max; g_instance.ckpt_cxt_ctl->pgwr_procs.list_flush_max = list_flush_max; - + // 如果启用了日志记录,输出相关信息 if (u_sess->attr.attr_storage.log_pagewriter) { - ereport(LOG, (errmodule(MOD_INCRE_CKPT), - errmsg("calculate max io, lsn_percent is %f, rate_lsn is %f, queue flush num is %u, list flush num is %u", - lsn_percent, rate_lsn, queue_flush_max, list_flush_max))); + ereport( + LOG, + (errmodule(MOD_INCRE_CKPT), + errmsg("calculate max io, lsn_percent is %f, rate_lsn is %f, queue flush num is %u, list flush num is %u", + lsn_percent, rate_lsn, queue_flush_max, list_flush_max))); } } const int AVG_CALCULATE_NUM = 30; const uint UPDATE_REC_XLOG_NUM = 4; +/* + * 功能:计算Page Writer的刷新数量 + * + * 注意: + * 此函数用于计算Page Writer应该刷新的页面数量,以控制刷新速率。 + * 它基于多个因素,包括LSN、脏页百分比等。 + */ static uint32 calculate_pagewriter_flush_num() { - static XLogRecPtr prev_lsn = InvalidXLogRecPtr; - static XLogRecPtr avg_lsn_rate = InvalidXLogRecPtr; - static pg_time_t prev_time = 0; - static int64 total_flush_num = 0; - static uint32 avg_flush_num = 0; - static uint32 prev_lsn_num = 0; - static int counter = 0; - XLogRecPtr target_lsn; - XLogRecPtr cur_lsn; - XLogRecPtr min_lsn; - uint32 flush_num = 0; - uint64 now; - int64 time_diff; - float dirty_page_pct; - float dirty_slot_pct; - uint32 num_for_dirty; - uint32 num_for_lsn; - uint32 min_io = DW_DIRTY_PAGE_MAX_FOR_NOHBK; - uint32 max_io = g_instance.ckpt_cxt_ctl->pgwr_procs.queue_flush_max; - uint32 num_for_lsn_max; - float dirty_percent; - double lsn_target_percent = 0; - uint32 lsn_scan_factor = 3; + static XLogRecPtr prev_lsn = InvalidXLogRecPtr; // 上一次的LSN + static XLogRecPtr avg_lsn_rate = InvalidXLogRecPtr; // 平均LSN速率 + static pg_time_t prev_time = 0; // 上一次计算的时间 + static int64 total_flush_num = 0; // 总的刷新数量 + static uint32 avg_flush_num = 0; // 平均刷新数量 + static uint32 prev_lsn_num = 0; // 上一次LSN的刷新数量 + static int counter = 0; // 计数器,用于控制计算频率 + XLogRecPtr target_lsn; // 目标LSN + XLogRecPtr cur_lsn; // 当前LSN + XLogRecPtr min_lsn; // 最小LSN + uint32 flush_num = 0; // 刷新数量 + uint64 now; // 当前时间 + int64 time_diff; // 时间差 + float dirty_page_pct; // 脏页占比 + float dirty_slot_pct; // 脏槽占比 + uint32 num_for_dirty; // 用于脏页的数量 + uint32 num_for_lsn; // 用于LSN的数量 + uint32 min_io = DW_DIRTY_PAGE_MAX_FOR_NOHBK; // 最小IO容量(无热备模式下) + uint32 max_io = g_instance.ckpt_cxt_ctl->pgwr_procs.queue_flush_max; // 最大IO容量 + uint32 num_for_lsn_max; // LSN最大数量 + float dirty_percent; // 脏页百分比 + double lsn_target_percent = 0; // LSN目标百分比 + uint32 lsn_scan_factor = 3; // LSN扫描因子 + // 获取最小LSN min_lsn = ckpt_get_min_rec_lsn(); if (XLogRecPtrIsInvalid(min_lsn)) { min_lsn = get_dirty_page_queue_rec_lsn(); } - /* primary get the xlog insert loc, standby get the replay loc */ + // 如果处于恢复中,则获取回放LSN;否则获取当前插入LSN if (RecoveryInProgress()) { cur_lsn = GetXLogReplayRecPtr(NULL); } else { cur_lsn = GetXLogInsertRecPtr(); } + // 如果上一次LSN是无效的,则初始化一些变量并返回默认值 if (XLogRecPtrIsInvalid(prev_lsn)) { prev_lsn = cur_lsn; prev_time = get_time_ms(); @@ -975,38 +1272,42 @@ static uint32 calculate_pagewriter_flush_num() goto DEFAULT; } + // 累加总刷新数量和计算时间差 total_flush_num += g_instance.ckpt_cxt_ctl->page_writer_last_queue_flush; now = get_time_ms(); time_diff = now - prev_time; /* - * We update our variables every AVG_CALCULATE_NUM times to smooth - * pagewriter flush page nums; + * 我们每隔AVG_CALCULATE_NUM次更新一次变量,以平滑Page Writer的刷新数量; */ - if (++counter > AVG_CALCULATE_NUM || - time_diff > AVG_CALCULATE_NUM * u_sess->attr.attr_storage.pageWriterSleep) { + if (++counter > AVG_CALCULATE_NUM || time_diff > AVG_CALCULATE_NUM * u_sess->attr.attr_storage.pageWriterSleep) { time_diff = MAX(1, time_diff); - avg_flush_num = (uint32)((((double)total_flush_num) / time_diff * u_sess->attr.attr_storage.pageWriterSleep - + avg_flush_num) / 2); - avg_lsn_rate = ((double)(cur_lsn - prev_lsn) / time_diff * u_sess->attr.attr_storage.pageWriterSleep - + avg_lsn_rate) / 2; + // 计算平均刷新数量和平均LSN速率 + avg_flush_num = (uint32)((((double)total_flush_num) / time_diff * u_sess->attr.attr_storage.pageWriterSleep + + avg_flush_num) / + 2); + avg_lsn_rate = + ((double)(cur_lsn - prev_lsn) / time_diff * u_sess->attr.attr_storage.pageWriterSleep + avg_lsn_rate) / 2; - /* reset our variables */ + // 重置一些变量 prev_lsn = cur_lsn; prev_time = now; total_flush_num = 0; counter = 0; } + // 计算脏页百分比,以及脏槽百分比 dirty_page_pct = g_instance.ckpt_cxt_ctl->actual_dirty_page_num / (float)(g_instance.attr.attr_storage.NBuffers); dirty_slot_pct = get_dirty_page_num() / (float)(g_instance.ckpt_cxt_ctl->dirty_page_queue_size); dirty_percent = MAX(dirty_page_pct, dirty_slot_pct) / u_sess->attr.attr_storage.dirty_page_percent_max; + // 如果处于恢复中,则将最大IO容量降低到90% if (RecoveryInProgress()) { max_io = max_io * 0.9; } + // 根据脏页百分比计算用于脏页的数量和LSN的数量上限 if (dirty_percent < HIGH_WATER) { num_for_dirty = min_io; num_for_lsn_max = max_io; @@ -1018,29 +1319,34 @@ static uint32 calculate_pagewriter_flush_num() num_for_lsn_max = max_io * 2; } - lsn_target_percent = (double)(cur_lsn - min_lsn) / - ((double)u_sess->attr.attr_storage.max_redo_log_size * BYTE_PER_KB); + // 计算LSN的目标百分比 + lsn_target_percent = + (double)(cur_lsn - min_lsn) / ((double)u_sess->attr.attr_storage.max_redo_log_size * BYTE_PER_KB); + /* - * If the xlog generation speed is slower than dirty queue rec lsn update speed and not many dirty pages, - * no need to scan too many dirty page, because the dirty page rec lsn is same. + * 如果XLOG生成速度慢于脏页队列的LSN更新速度,且脏页较少, + * 则不需要扫描太多脏页,因为脏页的LSN相同。 */ if (dirty_percent < HIGH_WATER && avg_lsn_rate < XLOG_SEG_SIZE * UPDATE_REC_XLOG_NUM && lsn_target_percent < HIGH_WATER) { lsn_scan_factor = 1; } + // 计算目标LSN并获取用于LSN的页面数量 target_lsn = min_lsn + avg_lsn_rate * lsn_scan_factor; num_for_lsn = get_page_num_for_lsn(target_lsn, num_for_lsn_max); + // 如果LSN目标百分比低于HIGH_WATER,将用于LSN的数量除以LSN扫描因子并限制为max_io if (lsn_target_percent < HIGH_WATER) { num_for_lsn = MIN(num_for_lsn / lsn_scan_factor, max_io); } else if (lsn_target_percent < 1) { num_for_lsn = MIN(num_for_lsn / lsn_scan_factor, max_io) + - (float)(lsn_target_percent - HIGH_WATER) / (float)(1 - HIGH_WATER) * max_io; + (float)(lsn_target_percent - HIGH_WATER) / (float)(1 - HIGH_WATER) * max_io; } else { num_for_lsn = max_io * 2; } + // 计算最终的刷新数量,取平均刷新数量、用于脏页的数量和用于LSN的数量的平均值 num_for_lsn = (num_for_lsn + prev_lsn_num) / 2; prev_lsn_num = num_for_lsn; @@ -1048,10 +1354,11 @@ static uint32 calculate_pagewriter_flush_num() DEFAULT: + // 如果刷新数量超过最大IO容量,将其限制为max_io;如果低于min_io,将其设为min_io if (flush_num > max_io) { flush_num = max_io; } else if (flush_num < min_io) { - flush_num = min_io; + flush_num = min_io; } return flush_num; @@ -1066,8 +1373,8 @@ void dw_upgrade_batch() ereport(LOG, (errmodule(MOD_DW), errmsg("dw batch upgrade start"))); uint64 dw_file_size; - knl_g_dw_context* dw_batch_cxt = &g_instance.dw_batch_cxt; - dw_batch_file_context* dw_file_cxt = &dw_batch_cxt->batch_file_cxts[0]; + knl_g_dw_context *dw_batch_cxt = &g_instance.dw_batch_cxt; + dw_batch_file_context *dw_file_cxt = &dw_batch_cxt->batch_file_cxts[0]; (void)LWLockConditionalAcquire(dw_batch_cxt->flush_lock, LW_EXCLUSIVE); (void)LWLockConditionalAcquire(dw_file_cxt->flush_lock, LW_EXCLUSIVE); @@ -1089,18 +1396,16 @@ void dw_upgrade_batch() */ int fd = open(DW_BATCH_UPGRADE_META_FILE_NAME, (DW_FILE_FLAG | O_CREAT), DW_FILE_PERM); if (fd == -1) { - ereport(PANIC, - (errcode_for_file_access(), errmodule(MOD_DW), - errmsg("Could not create file \"%s\"", DW_BATCH_UPGRADE_META_FILE_NAME))); + ereport(PANIC, (errcode_for_file_access(), errmodule(MOD_DW), + errmsg("Could not create file \"%s\"", DW_BATCH_UPGRADE_META_FILE_NAME))); } /* create new version meta file and batch files */ dw_generate_meta_file(&dw_batch_cxt->batch_meta_file); if (close(fd) != 0 || unlink(DW_BATCH_UPGRADE_META_FILE_NAME) != 0) { - ereport(PANIC, - (errcode_for_file_access(), errmodule(MOD_DW), - errmsg("Could not close or remove the DW batch meta upgrade file"))); + ereport(PANIC, (errcode_for_file_access(), errmodule(MOD_DW), + errmsg("Could not close or remove the DW batch meta upgrade file"))); } /* @@ -1109,19 +1414,16 @@ void dw_upgrade_batch() */ fd = open(DW_BATCH_UPGRADE_BATCH_FILE_NAME, (DW_FILE_FLAG | O_CREAT), DW_FILE_PERM); if (fd == -1) { - ereport(PANIC, - (errcode_for_file_access(), errmodule(MOD_DW), - errmsg("Could not create file \"%s\"", DW_BATCH_UPGRADE_BATCH_FILE_NAME))); + ereport(PANIC, (errcode_for_file_access(), errmodule(MOD_DW), + errmsg("Could not create file \"%s\"", DW_BATCH_UPGRADE_BATCH_FILE_NAME))); } dw_file_size = DW_FILE_SIZE_UNIT * dw_batch_cxt->batch_meta_file.dw_file_size; dw_generate_batch_files(dw_batch_cxt->batch_meta_file.dw_file_num, dw_file_size); - if (close(fd) != 0 || unlink(DW_BATCH_UPGRADE_BATCH_FILE_NAME) != 0) { - ereport(PANIC, - (errcode_for_file_access(), errmodule(MOD_DW), - errmsg("Could not close or remove the DW batch upgrade file"))); + ereport(PANIC, (errcode_for_file_access(), errmodule(MOD_DW), + errmsg("Could not close or remove the DW batch upgrade file"))); } dw_cxt_init_batch(); @@ -1130,12 +1432,10 @@ void dw_upgrade_batch() /* close and remove old version dw batch file */ if (close(dw_file_cxt->fd) != 0 || unlink(OLD_DW_FILE_NAME) != 0) { - ereport(PANIC, - (errcode_for_file_access(), errmodule(MOD_DW), - errmsg("Could not close or remove the DW batch old version file"))); + ereport(PANIC, (errcode_for_file_access(), errmodule(MOD_DW), + errmsg("Could not close or remove the DW batch old version file"))); } - LWLockRelease(dw_file_cxt->flush_lock); pfree(dw_file_cxt); @@ -1149,50 +1449,67 @@ void dw_upgrade_batch() void dw_upgrade_single() { + // 如果DW功能未启用,则直接返回 if (!dw_enabled()) { return; } + // 记录升级开始的日志信息 ereport(LOG, (errmodule(MOD_DW), errmsg("dw single upgrade start"))); - knl_g_dw_context* dw_single_cxt = &g_instance.dw_single_cxt; + // 获取单文件DW上下文 + knl_g_dw_context *dw_single_cxt = &g_instance.dw_single_cxt; + + // 获取单文件DW的二次刷新锁 (void)LWLockAcquire(dw_single_cxt->second_flush_lock, LW_EXCLUSIVE); + + // 等待所有单文件DW的刷新操作完成 wait_all_single_dw_finish_flush_old(); + + // 等待Page Writer同步 PageWriterSync(); - /* create dw batch flush file */ + // 创建DW升级文件 int fd = open(DW_UPGRADE_FILE_NAME, (DW_FILE_FLAG | O_CREAT), DW_FILE_PERM); if (fd == -1) { - ereport(PANIC, - (errcode_for_file_access(), errmodule(MOD_DW), - errmsg("Could not create file \"%s\"", DW_UPGRADE_FILE_NAME))); + // 如果创建失败,记录错误信息并退出 + ereport(PANIC, (errcode_for_file_access(), errmodule(MOD_DW), + errmsg("Could not create file \"%s\"", DW_UPGRADE_FILE_NAME))); } - /* close old version file */ + + // 关闭旧版本的文件并删除 if (close(dw_single_cxt->fd) != 0 || unlink(SINGLE_DW_FILE_NAME) != 0) { - ereport(PANIC, - (errcode_for_file_access(), errmodule(MOD_DW), - errmsg("Could not close or remove the DW single old version file"))); + // 如果关闭或删除失败,记录错误信息并退出 + ereport(PANIC, (errcode_for_file_access(), errmodule(MOD_DW), + errmsg("Could not close or remove the DW single old version file"))); } + + // 重置单文件DW上下文的相关信息 dw_single_cxt->fd = -1; dw_single_cxt->single_stat_info.total_writes = 0; dw_single_cxt->single_stat_info.file_trunc_num = 0; dw_single_cxt->single_stat_info.file_reset_num = 0; + + // 生成新的单文件DW并打开 dw_generate_new_single_file(); dw_single_cxt->fd = open(SINGLE_DW_FILE_NAME, DW_FILE_FLAG, DW_FILE_PERM); if (dw_single_cxt->fd == -1) { - ereport(PANIC, - (errcode_for_file_access(), errmodule(MOD_DW), errmsg("during upgrade, could not open file \"%s\"", - SINGLE_DW_FILE_NAME))); + // 如果打开新文件失败,记录错误信息并退出 + ereport(PANIC, (errcode_for_file_access(), errmodule(MOD_DW), + errmsg("during upgrade, could not open file \"%s\"", SINGLE_DW_FILE_NAME))); } + + // 更新DW的版本信息为支持新的单文件刷新模式 pg_atomic_write_u32(&g_instance.dw_single_cxt.dw_version, DW_SUPPORT_NEW_SINGLE_FLUSH); + // 关闭并删除升级文件 if (close(fd) != 0 || unlink(DW_UPGRADE_FILE_NAME) != 0) { - ereport(PANIC, - (errcode_for_file_access(), errmodule(MOD_DW), - errmsg("Could not close or remove the DW upgrade file"))); + // 如果关闭或删除失败,记录错误信息并退出 + ereport(PANIC, (errcode_for_file_access(), errmodule(MOD_DW), + errmsg("Could not close or remove the DW upgrade file"))); } - /* after upgrade, need free the old version buf context */ + // 在升级后,释放旧版本的缓冲区上下文 if (dw_single_cxt->recovery_buf.unaligned_buf != NULL) { pfree(dw_single_cxt->recovery_buf.unaligned_buf); dw_single_cxt->recovery_buf.unaligned_buf = NULL; @@ -1202,20 +1519,33 @@ void dw_upgrade_single() pfree(dw_single_cxt->recovery_buf.single_flush_state); dw_single_cxt->recovery_buf.single_flush_state = NULL; } + + // 释放二次刷新锁 LWLockRelease(dw_single_cxt->second_flush_lock); + // 记录升级结束的日志信息 ereport(LOG, (errmodule(MOD_DW), errmsg("dw single upgrade end"))); return; } +/* + * 功能:处理PageWriter主线程中断 + * + * 参数列表:无 + * + * 注意: + * 此函数用于处理PageWriter主线程中的不同中断情况,包括SIGHUP信号和同步请求。 + */ static void HandlePageWriterMainInterrupts() { + // 如果收到SIGHUP信号,重新加载配置文件 if (t_thrd.pagewriter_cxt.got_SIGHUP) { t_thrd.pagewriter_cxt.got_SIGHUP = false; ProcessConfigFile(PGC_SIGHUP); } + // 如果有同步请求或重试标志为真,则执行PageWriterSyncWithAbsorption同步函数 if (t_thrd.pagewriter_cxt.sync_requested || t_thrd.pagewriter_cxt.sync_retry) { t_thrd.pagewriter_cxt.sync_requested = false; @@ -1226,10 +1556,10 @@ static void HandlePageWriterMainInterrupts() /* main thread should finally exit. */ while (t_thrd.pagewriter_cxt.shutdown_requested && g_instance.ckpt_cxt_ctl->page_writer_can_exit) { + // 如果当前仅剩一个PageWriter线程 if (pg_atomic_read_u32(&g_instance.ckpt_cxt_ctl->current_page_writer_count) == 1) { - ereport(LOG, - (errmodule(MOD_INCRE_CKPT), - errmsg("pagewriter thread shut down, id is %d", t_thrd.pagewriter_cxt.pagewriter_id))); + ereport(LOG, (errmodule(MOD_INCRE_CKPT), + errmsg("pagewriter thread shut down, id is %d", t_thrd.pagewriter_cxt.pagewriter_id))); /* * From here on, elog(ERROR) should end with exit(1), not send @@ -1243,37 +1573,47 @@ static void HandlePageWriterMainInterrupts() return; } +/* + * 功能:PageWriter主线程循环 + * + * 注意: + * 此函数用于PageWriter主线程的主循环,处理脏页刷新、中断处理和等待条件。 + */ static void ckpt_pagewriter_main_thread_loop(void) { - uint32 rc = 0; - uint64 now; - int64 sleep_time; - uint32 candidate_num = 0; - + uint32 rc = 0; // 函数返回码 + uint64 now; // 当前时间 + int64 sleep_time; // 睡眠时间 + uint32 candidate_num = 0; // 候选脏页数量 + // 处理主线程中断 HandlePageWriterMainInterrupts(); - + // 获取候选脏页数量 candidate_num = get_curr_candidate_nums(false) + get_curr_candidate_nums(true); + // 当脏页数量为0,候选脏页数等于总缓冲池页数,并且未请求关闭线程时进入循环 while (get_dirty_page_num() == 0 && candidate_num == (uint32)TOTAL_BUFFER_NUM && - !t_thrd.pagewriter_cxt.shutdown_requested) { + !t_thrd.pagewriter_cxt.shutdown_requested) { rc = WaitLatch(&t_thrd.proc->procLatch, WL_TIMEOUT | WL_POSTMASTER_DEATH, (long)TEN_MILLISECOND); if (rc & WL_POSTMASTER_DEATH) { gs_thread_exit(1); } - + // 处理主线程中断 HandlePageWriterMainInterrupts(); - + // 重新获取候选脏页数量 candidate_num = get_curr_candidate_nums(false) + get_curr_candidate_nums(true); + // 如果候选脏页数量为0且未请求关闭线程,唤醒子线程扫描缓冲池,初始化候选列表 if (candidate_num == 0 && !t_thrd.pagewriter_cxt.shutdown_requested) { - /* wakeup sub thread scan the buffer pool, init the candidate list */ wakeup_sub_thread(); } } - + // 如果PageWriter子线程全部结束且PageWriter主线程数量等于总线程数量 if (pg_atomic_read_u32(&g_instance.ckpt_cxt_ctl->pgwr_procs.running_num) == 0 && pg_atomic_read_u32(&g_instance.ckpt_cxt_ctl->current_page_writer_count) == (uint32)g_instance.ckpt_cxt_ctl->pgwr_procs.num) { + // 尝试跳过队列头部的无效元素 ckpt_try_skip_invalid_elem_in_queue_head(); + // 尝试修剪脏页队列 ckpt_try_prune_dirty_page_queue(); + // 吸收文件同步请求 PgwrAbsorbFsyncRequests(); /* Full checkpoint, don't sleep */ sleep_time = get_pagewriter_sleep_time(); @@ -1283,11 +1623,9 @@ static void ckpt_pagewriter_main_thread_loop(void) pg_usleep(MILLISECOND_TO_MICROSECOND); sleep_time -= 1; } - /* Calculate next flush time before flush this batch dirty page */ now = get_time_ms(); t_thrd.pagewriter_cxt.next_flush_time = now + u_sess->attr.attr_storage.pageWriterSleep; - /* pagewriter thread flush dirty page */ calculate_max_flush_num(); ckpt_pagewriter_main_thread_flush_dirty_page(); @@ -1295,7 +1633,12 @@ static void ckpt_pagewriter_main_thread_loop(void) } return; } - +/* + * 功能:唤醒PageWriter主线程 + * + * 注意: + * 如果当前候选列表为空,唤醒PageWriter主线程。 + */ static void wakeup_pagewriter_main_thread() { PageWriterProc *pgwr = &g_instance.ckpt_cxt_ctl->pgwr_procs.writer_proc[0]; @@ -1306,30 +1649,56 @@ static void wakeup_pagewriter_main_thread() } return; } - -static bool apply_batch_flush_pages(PageWriterProc* pgwr) +/* + * 功能:应用批量刷新页面 + * + * 参数列表: + * pgwr:PageWriter线程的处理器 + * + * 返回值: + * 返回是否成功应用批量刷新页面 + * + * 注意: + * 此函数用于应用批量刷新页面。它从全局的CkptBufferIdsFlushPages中获取页面,并将其分配给PageWriter线程进行刷新。 + * 该函数首先获取起始位置(start_loc),然后计算需要刷新的页面数量(need_flush_num)。 + * 如果起始位置加上需要刷新的页面数量超过了总共要刷新的页面数,则会将need_flush_num减少以保证不超过。 + * 最后,将start_loc和need_flush_num设置到pgwr的相应属性中,表示要刷新的页面范围。 + */ +static bool apply_batch_flush_pages(PageWriterProc *pgwr) { - uint32 start_loc; - int need_flush_num; - bool is_new_relfilenode = pgwr->thrd_dw_cxt.is_new_relfilenode; - int dw_batch_page_max = GET_DW_DIRTY_PAGE_MAX(is_new_relfilenode); - uint32 total_flush_pages = g_instance.ckpt_cxt_ctl->CkptBufferIdsFlushPages; + uint32 start_loc; // 起始位置,用于确定要刷新的页面范围的起始位置 + int need_flush_num; // 需要刷新的页面数量 + bool is_new_relfilenode = pgwr->thrd_dw_cxt.is_new_relfilenode; // 是否为新的关系文件节点 + int dw_batch_page_max = GET_DW_DIRTY_PAGE_MAX(is_new_relfilenode); // 获取批量刷新的最大页面数 + uint32 total_flush_pages = g_instance.ckpt_cxt_ctl->CkptBufferIdsFlushPages; // 获取总共要刷新的页面数 + // 使用原子操作获取起始位置,保证多线程环境下获取的位置不冲突 start_loc = pg_atomic_fetch_add_u32(&g_instance.ckpt_cxt_ctl->CkptBufferIdsTail, dw_batch_page_max); - + // 如果起始位置已经超过或等于总共要刷新的页面数,返回false表示无需刷新 if (start_loc >= total_flush_pages) { return false; } - + // 计算实际需要刷新的页面数量,如果超过总共要刷新的页面数,则减少以保证不超过 need_flush_num = dw_batch_page_max; if (start_loc + need_flush_num > total_flush_pages) { need_flush_num = total_flush_pages - start_loc; } - + // 将起始位置和需要刷新的页面数量设置到pgwr的相应属性中,表示要刷新的页面范围 pgwr->start_loc = start_loc; pgwr->need_flush_num = need_flush_num; return true; } - +/* + * 功能:PageWriter子线程主循环 + * + * 注意: + * 此函数是PageWriter子线程的主要工作循环,负责刷新脏页到磁盘。 + * 它首先检查是否需要关闭线程或进行其他操作。 + * 然后,它等待一个时间间隔,以防止频繁的无用循环。 + * 如果需要刷新脏页,它会扫描缓冲池以获取要刷新的页面列表,并将它们添加到刷新队列中。 + * 接着,它会不断地从刷新队列中获取要刷新的页面,刷新它们到磁盘,并更新已完成的页面数。 + * 当所有页面都刷新完成时,它会将prepared标志设置为0,表示准备完成。 + * 最后,它会关闭所有打开的文件,完成刷新工作。 + */ static void ckpt_pagewriter_sub_thread_loop() { uint32 rc = 0; @@ -1342,9 +1711,8 @@ static void ckpt_pagewriter_sub_thread_loop() WritebackContextInit(&wb_context, &t_thrd.pagewriter_cxt.page_writer_after); if (t_thrd.pagewriter_cxt.shutdown_requested && g_instance.ckpt_cxt_ctl->page_writer_can_exit) { - ereport(LOG, - (errmodule(MOD_INCRE_CKPT), - errmsg("pagewriter thread shut down, id is %d", t_thrd.pagewriter_cxt.pagewriter_id))); + ereport(LOG, (errmodule(MOD_INCRE_CKPT), + errmsg("pagewriter thread shut down, id is %d", t_thrd.pagewriter_cxt.pagewriter_id))); /* * From here on, elog(ERROR) should end with exit(1), not send control back to @@ -1358,7 +1726,7 @@ static void ckpt_pagewriter_sub_thread_loop() if (!t_thrd.pagewriter_cxt.shutdown_requested) { /* Wait first */ rc = WaitLatch(&t_thrd.proc->procLatch, WL_TIMEOUT | WL_LATCH_SET | WL_POSTMASTER_DEATH, - (long)u_sess->attr.attr_storage.pageWriterSleep /* ms */); + (long)u_sess->attr.attr_storage.pageWriterSleep /* ms */); } if (rc & WL_POSTMASTER_DEATH) { @@ -1366,7 +1734,7 @@ static void ckpt_pagewriter_sub_thread_loop() } ResetLatch(&t_thrd.proc->procLatch); - PageWriterProc* pgwr = &g_instance.ckpt_cxt_ctl->pgwr_procs.writer_proc[thread_id]; + PageWriterProc *pgwr = &g_instance.ckpt_cxt_ctl->pgwr_procs.writer_proc[thread_id]; if (pgwr->need_flush) { /* scan buffer pool, get flush list and candidate list */ @@ -1374,10 +1742,10 @@ static void ckpt_pagewriter_sub_thread_loop() if (t_thrd.pagewriter_cxt.next_scan_time <= now) { incre_ckpt_pgwr_scan_buf_pool(wb_context); now = get_time_ms(); - t_thrd.pagewriter_cxt.next_scan_time = now + - MAX(u_sess->attr.attr_storage.BgWriterDelay, u_sess->attr.attr_storage.pageWriterSleep); + t_thrd.pagewriter_cxt.next_scan_time = + now + MAX(u_sess->attr.attr_storage.BgWriterDelay, u_sess->attr.attr_storage.pageWriterSleep); } else if ((int64)(t_thrd.pagewriter_cxt.next_scan_time - now) > - MAX(u_sess->attr.attr_storage.BgWriterDelay, u_sess->attr.attr_storage.pageWriterSleep)) { + MAX(u_sess->attr.attr_storage.BgWriterDelay, u_sess->attr.attr_storage.pageWriterSleep)) { /* preventing Time Jumps */ t_thrd.pagewriter_cxt.next_scan_time = now; } @@ -1385,10 +1753,10 @@ static void ckpt_pagewriter_sub_thread_loop() pg_read_barrier(); total_flush_pages = g_instance.ckpt_cxt_ctl->CkptBufferIdsFlushPages; - while (pg_atomic_read_u32(&g_instance.ckpt_cxt_ctl->prepared) == 1 - && pg_atomic_read_u32(&g_instance.ckpt_cxt_ctl->CkptBufferIdsCompletedPages) < total_flush_pages) { + while (pg_atomic_read_u32(&g_instance.ckpt_cxt_ctl->prepared) == 1 && + pg_atomic_read_u32(&g_instance.ckpt_cxt_ctl->CkptBufferIdsCompletedPages) < total_flush_pages) { /* apply one batch dirty pages */ - if(!apply_batch_flush_pages(pgwr)) { + if (!apply_batch_flush_pages(pgwr)) { break; } @@ -1397,8 +1765,8 @@ static void ckpt_pagewriter_sub_thread_loop() incre_ckpt_pgwr_flush_dirty_queue(wb_context); /* add up completed pages */ - completed_pages = pg_atomic_add_fetch_u32( - &g_instance.ckpt_cxt_ctl->CkptBufferIdsCompletedPages, pgwr->need_flush_num); + completed_pages = + pg_atomic_add_fetch_u32(&g_instance.ckpt_cxt_ctl->CkptBufferIdsCompletedPages, pgwr->need_flush_num); /* if flush finished, set prepared to 0 */ if (completed_pages == total_flush_pages) { @@ -1487,7 +1855,20 @@ static void ckpt_pagewriter_handle_exception(MemoryContext pagewriter_context) return; } - +/* + * 功能:Pagewriter线程终止时的处理函数 + * + * 参数: + * code:退出代码 + * arg:附加参数 + * + * 注意: + * 此函数用于Pagewriter线程终止时的清理工作。 + * 它首先检查Pagewriter线程的ID,然后根据不同的线程ID执行不同的清理工作。 + * 对于ID为0的线程,销毁待处理的操作哈希表。 + * 然后,它确保线程退出时标记了需要刷新的状态,以避免主Pagewriter线程等待线程退出。 + * 最后,减少Page Writer线程计数,将相应线程的proc字段置为NULL,以表示线程已经终止。 + */ static void pagewriter_kill(int code, Datum arg) { int id = t_thrd.pagewriter_cxt.pagewriter_id; @@ -1497,7 +1878,8 @@ static void pagewriter_kill(int code, Datum arg) hash_destroy(u_sess->storage_cxt.pendingOps); u_sess->storage_cxt.pendingOps = NULL; } - /* Making sure that we mark our exit status (as sub threads) so that main pagewriter thread would not be waiting for us in vain */ + /* Making sure that we mark our exit status (as sub threads) so that main pagewriter thread would not be waiting for + * us in vain */ if (g_instance.ckpt_cxt_ctl->pgwr_procs.writer_proc[id].need_flush) { g_instance.ckpt_cxt_ctl->pgwr_procs.writer_proc[id].need_flush = false; pg_atomic_fetch_sub_u32(&g_instance.ckpt_cxt_ctl->pgwr_procs.running_num, 1); @@ -1528,8 +1910,8 @@ int get_pagewriter_thread_id(void) for (i = 0; i < g_instance.ckpt_cxt_ctl->pgwr_procs.num; i++) { void *expected = NULL; if (pg_atomic_compare_exchange_uintptr( - (uintptr_t *)&g_instance.ckpt_cxt_ctl->pgwr_procs.writer_proc[i].proc, - (uintptr_t *)&expected, (uintptr_t)t_thrd.proc)) { + (uintptr_t *)&g_instance.ckpt_cxt_ctl->pgwr_procs.writer_proc[i].proc, (uintptr_t *)&expected, + (uintptr_t)t_thrd.proc)) { t_thrd.pagewriter_cxt.pagewriter_id = i; break; } @@ -1593,9 +1975,8 @@ void ckpt_pagewriter_main(void) /* We allow SIGQUIT (quickdie) at all times */ (void)sigdelset(&t_thrd.libpq_cxt.BlockSig, SIGQUIT); - ereport(LOG, - (errmodule(MOD_INCRE_CKPT), - errmsg("pagewriter started, thread id is %d", t_thrd.pagewriter_cxt.pagewriter_id))); + ereport(LOG, (errmodule(MOD_INCRE_CKPT), + errmsg("pagewriter started, thread id is %d", t_thrd.pagewriter_cxt.pagewriter_id))); g_last_snapshot_ts = GetCurrentTimestamp(); /* @@ -1603,12 +1984,12 @@ void ckpt_pagewriter_main(void) * buffer pins). */ Assert(t_thrd.pagewriter_cxt.pagewriter_id >= 0); - errno_t err_rc = snprintf_s( - name, MAX_THREAD_NAME_LEN, MAX_THREAD_NAME_LEN - 1, "%s%d", "PageWriter", t_thrd.pagewriter_cxt.pagewriter_id); + errno_t err_rc = snprintf_s(name, MAX_THREAD_NAME_LEN, MAX_THREAD_NAME_LEN - 1, "%s%d", "PageWriter", + t_thrd.pagewriter_cxt.pagewriter_id); securec_check_ss(err_rc, "", ""); - t_thrd.utils_cxt.CurrentResourceOwner = ResourceOwnerCreate(NULL, name, - THREAD_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_STORAGE)); + t_thrd.utils_cxt.CurrentResourceOwner = + ResourceOwnerCreate(NULL, name, THREAD_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_STORAGE)); /* * Create a memory context that we will do all our work in. We do this so @@ -1616,8 +1997,8 @@ void ckpt_pagewriter_main(void) * possible memory leaks. Formerly this code just ran in * TopMemoryContext, but resetting that would be a really bad idea. */ - pagewriter_context = AllocSetContextCreate( - TopMemoryContext, name, ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); + pagewriter_context = AllocSetContextCreate(TopMemoryContext, name, ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); (void)MemoryContextSwitchTo(pagewriter_context); on_shmem_exit(pagewriter_kill, (Datum)0); @@ -1656,10 +2037,10 @@ void ckpt_pagewriter_main(void) InitSync(); } - pg_time_t now = (pg_time_t) time(NULL); + pg_time_t now = (pg_time_t)time(NULL); t_thrd.pagewriter_cxt.next_flush_time = now + u_sess->attr.attr_storage.pageWriterSleep; - t_thrd.pagewriter_cxt.next_scan_time = now + - MAX(u_sess->attr.attr_storage.BgWriterDelay, u_sess->attr.attr_storage.pageWriterSleep); + t_thrd.pagewriter_cxt.next_scan_time = + now + MAX(u_sess->attr.attr_storage.BgWriterDelay, u_sess->attr.attr_storage.pageWriterSleep); /* * Loop forever @@ -1689,8 +2070,8 @@ void ckpt_pagewriter_main(void) * when double write is disabled, pg_dw_meta will be created with dw_file_num = 0, so * here is for upgrading process. pagewrite will run when enable_incremetal_checkpoint = on. */ - if (pg_atomic_read_u32(&g_instance.dw_batch_cxt.dw_version) < DW_SUPPORT_REABLE_DOUBLE_WRITE - && t_thrd.proc->workingVersionNum >= DW_SUPPORT_REABLE_DOUBLE_WRITE) { + if (pg_atomic_read_u32(&g_instance.dw_batch_cxt.dw_version) < DW_SUPPORT_REABLE_DOUBLE_WRITE && + t_thrd.proc->workingVersionNum >= DW_SUPPORT_REABLE_DOUBLE_WRITE) { dw_upgrade_renable_double_write(); } @@ -1709,7 +2090,7 @@ static void print_dirty_page_queue_info(bool after_prune) { uint64 i = 0; uint64 temp_loc; - volatile DirtyPageQueueSlot* slot = NULL; + volatile DirtyPageQueueSlot *slot = NULL; uint64 print_info_num = MIN(((uint64)(MAX_VALID_BUF_SLOT + MAX_VALID_BUF_SLOT)), ((uint64)get_dirty_page_num())); uint64 dirty_queue_head = pg_atomic_read_u64(&g_instance.ckpt_cxt_ctl->dirty_page_queue_head); @@ -1717,17 +2098,14 @@ static void print_dirty_page_queue_info(bool after_prune) temp_loc = (dirty_queue_head + i) % g_instance.ckpt_cxt_ctl->dirty_page_queue_size; slot = &g_instance.ckpt_cxt_ctl->dirty_page_queue[temp_loc]; ereport(DEBUG1, - (errmodule(MOD_INCRE_CKPT), - errmsg("%s, dirty page queue loc is %lu, buffer is %d, slot_state is %u", - after_prune ? "after prune" : "before prune", - temp_loc, - slot->buffer, - slot->slot_state))); + (errmodule(MOD_INCRE_CKPT), + errmsg("%s, dirty page queue loc is %lu, buffer is %d, slot_state is %u", + after_prune ? "after prune" : "before prune", temp_loc, slot->buffer, slot->slot_state))); } } -static bool ckpt_found_valid_and_invalid_buffer_loc( - uint64* valid_buffer_array, uint32 array_size, uint32* valid_slot_num, uint64* last_invalid_slot) +static bool ckpt_found_valid_and_invalid_buffer_loc(uint64 *valid_buffer_array, uint32 array_size, + uint32 *valid_slot_num, uint64 *last_invalid_slot) { int64 i; uint64 temp_loc; @@ -1735,7 +2113,7 @@ static bool ckpt_found_valid_and_invalid_buffer_loc( uint32 max_invalid_slot = MAX_INVALID_BUF_SLOT; int64 dirty_page_num; uint64 dirty_queue_head; - volatile DirtyPageQueueSlot* slot = NULL; + volatile DirtyPageQueueSlot *slot = NULL; dirty_page_num = get_dirty_page_num(); @@ -1779,16 +2157,16 @@ static void ckpt_try_prune_dirty_page_queue() uint32 valid_slot_num = 0; uint64 last_invalid_slot = 0; bool can_found = false; - uint64* valid_buffer_array = NULL; + uint64 *valid_buffer_array = NULL; if (get_dirty_page_num() < g_instance.ckpt_cxt_ctl->dirty_page_queue_size * NEED_PRUNE_DIRTY_QUEUE_SLOT) { return; } - valid_buffer_array = (uint64*)palloc0(MAX_VALID_BUF_SLOT * sizeof(uint64)); + valid_buffer_array = (uint64 *)palloc0(MAX_VALID_BUF_SLOT * sizeof(uint64)); - can_found = ckpt_found_valid_and_invalid_buffer_loc( - valid_buffer_array, MAX_VALID_BUF_SLOT, &valid_slot_num, &last_invalid_slot); + can_found = ckpt_found_valid_and_invalid_buffer_loc(valid_buffer_array, MAX_VALID_BUF_SLOT, &valid_slot_num, + &last_invalid_slot); /* * Read valid_buffer_array form the last to first, move the buffer to last_invalid_slot, @@ -1799,9 +2177,9 @@ static void ckpt_try_prune_dirty_page_queue() uint64 temp_loc; uint64 move_loc; uint32 buf_state; - volatile DirtyPageQueueSlot* slot = NULL; - volatile DirtyPageQueueSlot* move_slot = NULL; - BufferDesc* bufhdr = NULL; + volatile DirtyPageQueueSlot *slot = NULL; + volatile DirtyPageQueueSlot *move_slot = NULL; + BufferDesc *bufhdr = NULL; /* * If full checkpoint set the full_ckpt_expected_flush_loc is queue slot 100, some @@ -1877,7 +2255,7 @@ static void ckpt_try_skip_invalid_elem_in_queue_head() for (i = 0; i < dirty_page_num; i++) { uint64 temp_loc = (dirty_queue_head + i) % g_instance.ckpt_cxt_ctl->dirty_page_queue_size; - volatile DirtyPageQueueSlot* slot = &g_instance.ckpt_cxt_ctl->dirty_page_queue[temp_loc]; + volatile DirtyPageQueueSlot *slot = &g_instance.ckpt_cxt_ctl->dirty_page_queue[temp_loc]; /* slot location is pre-occupied, but the buffer not set finish, need break. */ if (!(pg_atomic_read_u32(&slot->slot_state) & SLOT_VALID)) { break; @@ -1893,17 +2271,15 @@ static void ckpt_try_skip_invalid_elem_in_queue_head() } } if (u_sess->attr.attr_storage.log_pagewriter) { - ereport(DEBUG1, - (errmodule(MOD_INCRE_CKPT), - errmsg("skip invalid element dirty, page queue head add %u, dirty page remain: %ld", - head_move_num, - get_dirty_page_num()))); + ereport(DEBUG1, (errmodule(MOD_INCRE_CKPT), + errmsg("skip invalid element dirty, page queue head add %u, dirty page remain: %ld", + head_move_num, get_dirty_page_num()))); } return; } -static uint32 incre_ckpt_pgwr_flush_dirty_page(WritebackContext wb_context, - const CkptSortItem *dirty_buf_list, int start, int batch_num) +static uint32 incre_ckpt_pgwr_flush_dirty_page(WritebackContext wb_context, const CkptSortItem *dirty_buf_list, + int start, int batch_num) { uint32 num_actual_flush = 0; uint32 buf_state; @@ -1936,7 +2312,7 @@ static uint32 incre_ckpt_pgwr_flush_dirty_page(WritebackContext wb_context, static void incre_ckpt_pgwr_flush_dirty_queue(WritebackContext wb_context) { int thread_id = t_thrd.pagewriter_cxt.pagewriter_id; - PageWriterProc* pgwr = &g_instance.ckpt_cxt_ctl->pgwr_procs.writer_proc[thread_id]; + PageWriterProc *pgwr = &g_instance.ckpt_cxt_ctl->pgwr_procs.writer_proc[thread_id]; bool is_new_relfilenode = g_instance.dw_batch_cxt.is_new_relfilenode; uint32 start_loc = pgwr->start_loc; int need_flush_num = pgwr->need_flush_num; @@ -1973,7 +2349,7 @@ static void incre_ckpt_pgwr_flush_dirty_queue(WritebackContext wb_context) } static void incre_ckpt_pgwr_flush_dirty_list(WritebackContext wb_context, uint32 need_flush_num, - bool is_new_relfilenode) + bool is_new_relfilenode) { int thread_id = t_thrd.pagewriter_cxt.pagewriter_id; PageWriterProc *pgwr = &g_instance.ckpt_cxt_ctl->pgwr_procs.writer_proc[thread_id]; @@ -2014,19 +2390,19 @@ static void incre_ckpt_pgwr_flush_dirty_list(WritebackContext wb_context, uint32 } if (u_sess->attr.attr_storage.log_pagewriter) { - ereport(LOG, - (errmodule(MOD_INCRE_CKPT), - errmsg("flush dirty page %d, thread id is %d", num_actual_flush, thread_id))); + ereport(LOG, (errmodule(MOD_INCRE_CKPT), + errmsg("flush dirty page %d, thread id is %d", num_actual_flush, thread_id))); } } -static bool check_buffer_dirty_flag(BufferDesc* buf_desc) +static bool check_buffer_dirty_flag(BufferDesc *buf_desc) { bool segment_buf = (buf_desc->buf_id >= SegmentBufferStartID); Block tmpBlock = BufHdrGetBlock(buf_desc); uint32 local_buf_state = pg_atomic_read_u32(&buf_desc->state); bool check_lsn_not_match = (local_buf_state & BM_VALID) && !(local_buf_state & BM_DIRTY) && - XLByteLT(buf_desc->lsn_on_disk, PageGetLSN(tmpBlock)) && RecoveryInProgress() && !segment_buf; + XLByteLT(buf_desc->lsn_on_disk, PageGetLSN(tmpBlock)) && RecoveryInProgress() && + !segment_buf; if (check_lsn_not_match) { PinBuffer(buf_desc, NULL); @@ -2034,18 +2410,20 @@ static bool check_buffer_dirty_flag(BufferDesc* buf_desc) pg_memory_barrier(); local_buf_state = pg_atomic_read_u32(&buf_desc->state); check_lsn_not_match = (local_buf_state & BM_VALID) && !(local_buf_state & BM_DIRTY) && - XLByteLT(buf_desc->lsn_on_disk, PageGetLSN(tmpBlock)) && RecoveryInProgress(); + XLByteLT(buf_desc->lsn_on_disk, PageGetLSN(tmpBlock)) && RecoveryInProgress(); if (check_lsn_not_match) { MarkBufferDirty(BufferDescriptorGetBuffer(buf_desc)); LWLockRelease(buf_desc->content_lock); UnpinBuffer(buf_desc, true); const uint32 shiftSize = 32; - ereport(DEBUG1, (errmodule(MOD_INCRE_BG), - errmsg("check lsn is not matched on disk:%X/%X on page %X/%X, relnode info:%u/%u/%u %u %u stat:%u", - (uint32)(buf_desc->lsn_on_disk >> shiftSize), (uint32)(buf_desc->lsn_on_disk), - (uint32)(PageGetLSN(tmpBlock) >> shiftSize), (uint32)(PageGetLSN(tmpBlock)), - buf_desc->tag.rnode.spcNode, buf_desc->tag.rnode.dbNode, buf_desc->tag.rnode.relNode, - buf_desc->tag.blockNum, buf_desc->tag.forkNum, local_buf_state))); + ereport( + DEBUG1, + (errmodule(MOD_INCRE_BG), + errmsg("check lsn is not matched on disk:%X/%X on page %X/%X, relnode info:%u/%u/%u %u %u stat:%u", + (uint32)(buf_desc->lsn_on_disk >> shiftSize), (uint32)(buf_desc->lsn_on_disk), + (uint32)(PageGetLSN(tmpBlock) >> shiftSize), (uint32)(PageGetLSN(tmpBlock)), + buf_desc->tag.rnode.spcNode, buf_desc->tag.rnode.dbNode, buf_desc->tag.rnode.relNode, + buf_desc->tag.blockNum, buf_desc->tag.forkNum, local_buf_state))); return true; } else { @@ -2104,17 +2482,17 @@ static uint32 get_list_flush_num(bool is_segment) } if (cur_candidate_num >= high_water_mark) { - flush_num = min_io; /* only flush one batch dirty page */ + flush_num = min_io; /* only flush one batch dirty page */ } else if (cur_candidate_num >= total_target) { - flush_num = min_io + (float)(high_water_mark - cur_candidate_num) / - (float)(high_water_mark - total_target) * (max_io - min_io); + flush_num = min_io + (float)(high_water_mark - cur_candidate_num) / (float)(high_water_mark - total_target) * + (max_io - min_io); } else { /* every time flush max_io dirty pages */ flush_num = max_io; } ereport(DEBUG1, (errmodule(MOD_INCRE_BG), - errmsg("list flush_num is %u, now candidate buf is %u", flush_num, cur_candidate_num))); + errmsg("list flush_num is %u, now candidate buf is %u", flush_num, cur_candidate_num))); return flush_num; } @@ -2183,14 +2561,13 @@ static void incre_ckpt_pgwr_scan_buf_pool(WritebackContext wb_context) * @out: Return the number of dirty buffers and dirty buffer list and this batch buffer * whether hashbucket is included. */ -static uint32 get_candidate_buf_and_flush_list(uint32 start, uint32 end, uint32 max_flush_num, - bool *contain_hashbucket) +static uint32 get_candidate_buf_and_flush_list(uint32 start, uint32 end, uint32 max_flush_num, bool *contain_hashbucket) { uint32 need_flush_num = 0; uint32 candidates = 0; BufferDesc *buf_desc = NULL; uint32 local_buf_state; - CkptSortItem* item = NULL; + CkptSortItem *item = NULL; bool check_not_need_flush = false; bool check_usecount = false; int thread_id = t_thrd.pagewriter_cxt.pagewriter_id; @@ -2244,13 +2621,13 @@ static uint32 get_candidate_buf_and_flush_list(uint32 start, uint32 end, uint32 goto UNLOCK; } - check_not_need_flush = (need_flush_num >= max_flush_num || (!RecoveryInProgress() - && XLogNeedsFlush(BufferGetLSN(buf_desc)))); + check_not_need_flush = + (need_flush_num >= max_flush_num || (!RecoveryInProgress() && XLogNeedsFlush(BufferGetLSN(buf_desc)))); if (check_not_need_flush) { goto UNLOCK; } -PUSH_DIRTY: + PUSH_DIRTY: local_buf_state |= BM_CHECKPOINT_NEEDED; item = &dirty_buf_list[need_flush_num++]; item->buf_id = buf_id; @@ -2263,14 +2640,13 @@ PUSH_DIRTY: *contain_hashbucket = true; } -UNLOCK: + UNLOCK: UnlockBufHdr(buf_desc, local_buf_state); } if (u_sess->attr.attr_storage.log_pagewriter) { ereport(LOG, - (errmodule(MOD_INCRE_CKPT), - errmsg("get candidate buf %d, thread id is %d", candidates, thread_id))); + (errmodule(MOD_INCRE_CKPT), errmsg("get candidate buf %d, thread id is %d", candidates, thread_id))); } return need_flush_num; } @@ -2304,7 +2680,6 @@ static void push_to_candidate_list(BufferDesc *buf_desc) return; } - /** * @Description: Push buffer bufId to thread threadId's candidate list. * @in: buf_id, buffer id which need push to the list @@ -2329,7 +2704,19 @@ static void candidate_buf_push(int buf_id, int thread_id) pgwr->cand_buf_list[tail_loc] = buf_id; (void)pg_atomic_fetch_add_u64(&pgwr->tail, 1); } - +/* + * 功能:将缓冲区ID推入段候选列表 + * + * 参数: + * buf_id:缓冲区ID + * thread_id:线程ID + * + * 注意: + * 此函数用于将缓冲区ID推入指定线程的段候选列表。 + * 首先获取线程的候选列表大小和当前头尾位置。 + * 然后,检查如果尾位置减去头位置大于等于候选列表大小,则触发断言。 + * 否则,计算要推入的位置,将缓冲区ID存入候选列表,然后增加尾位置。 + */ static void seg_candidate_buf_push(int buf_id, int thread_id) { PageWriterProc *pgwr = &g_instance.ckpt_cxt_ctl->pgwr_procs.writer_proc[thread_id]; @@ -2368,7 +2755,7 @@ bool candidate_buf_pop(int *buf_id, int thread_id) volatile uint64 tail = pg_atomic_read_u64(&pgwr->tail); if (unlikely(head >= tail)) { - return false; /* candidate list is empty */ + return false; /* candidate list is empty */ } head_loc = head % list_size; @@ -2378,7 +2765,22 @@ bool candidate_buf_pop(int *buf_id, int thread_id) } } } - +/* + * 功能:从段候选列表中弹出缓冲区ID + * + * 参数: + * buf_id:用于返回弹出的缓冲区ID + * thread_id:线程ID + * + * 返回值: + * 如果成功弹出缓冲区ID,则返回true,否则返回false(候选列表为空) + * + * 注意: + * 此函数用于从指定线程的段候选列表中弹出一个缓冲区ID。 + * 首先获取线程的候选列表大小和当前头尾位置。 + * 然后,在循环中,检查如果头位置大于等于尾位置,则候选列表为空,返回false。 + * 否则,计算要弹出的位置,尝试原子操作地更新头位置并获取缓冲区ID,如果成功则返回true,否则继续循环。 + */ bool seg_candidate_buf_pop(int *buf_id, int thread_id) { PageWriterProc *pgwr = &g_instance.ckpt_cxt_ctl->pgwr_procs.writer_proc[thread_id]; @@ -2392,7 +2794,7 @@ bool seg_candidate_buf_pop(int *buf_id, int thread_id) volatile uint64 tail = pg_atomic_read_u64(&pgwr->seg_tail); if (unlikely(head >= tail)) { - return false; /* candidate list is empty */ + return false; /* candidate list is empty */ } head_loc = head % list_size; @@ -2403,31 +2805,66 @@ bool seg_candidate_buf_pop(int *buf_id, int thread_id) } } +/* + * 功能:获取线程的候选页面数量 + * + * 参数: + * thread_id:线程标识 + * + * 返回值: + * 当前候选页面数量 + * + * 注意: + * 此函数用于获取指定线程的候选页面数量。 + */ static int64 get_thread_candidate_nums(int thread_id) { PageWriterProc *pgwr = &g_instance.ckpt_cxt_ctl->pgwr_procs.writer_proc[thread_id]; - volatile uint64 head = pg_atomic_read_u64(&pgwr->head); - pg_memory_barrier(); - volatile uint64 tail = pg_atomic_read_u64(&pgwr->tail); - int64 curr_cand_num = tail - head; - Assert(curr_cand_num >= 0); + volatile uint64 head = pg_atomic_read_u64(&pgwr->head); // 获取头指针 + pg_memory_barrier(); // 内存屏障,确保内存读取的顺序 + volatile uint64 tail = pg_atomic_read_u64(&pgwr->tail); // 获取尾指针 + int64 curr_cand_num = tail - head; // 计算当前候选页面数量 + Assert(curr_cand_num >= 0); // 断言,确保候选页面数量非负数 return curr_cand_num; } - +/* + * 功能:获取线程的段候选页面数量 + * + * 参数: + * thread_id:线程标识 + * + * 返回值: + * 当前段候选页面数量 + * + * 注意: + * 此函数用于获取指定线程的段候选页面数量。 + */ static int64 get_thread_seg_candidate_nums(int thread_id) { PageWriterProc *pgwr = &g_instance.ckpt_cxt_ctl->pgwr_procs.writer_proc[thread_id]; - volatile uint64 head = pg_atomic_read_u64(&pgwr->seg_head); - pg_memory_barrier(); - volatile uint64 tail = pg_atomic_read_u64(&pgwr->seg_tail); - int64 curr_cand_num = tail - head; - Assert(curr_cand_num >= 0); + volatile uint64 head = pg_atomic_read_u64(&pgwr->seg_head); // 获取段头指针 + pg_memory_barrier(); // 内存屏障,确保内存读取的顺序 + volatile uint64 tail = pg_atomic_read_u64(&pgwr->seg_tail); // 获取段尾指针 + int64 curr_cand_num = tail - head; // 计算当前段候选页面数量 + Assert(curr_cand_num >= 0); // 断言,确保段候选页面数量非负数 return curr_cand_num; } /** * @Description: Return a rough estimate of the current number of buffers in the candidate list. */ +/* + * 功能:获取当前候选页面数量 + * + * 参数: + * segment:是否获取段的候选页面数量,true表示获取段的,false表示获取整体的 + * + * 返回值: + * 当前候选页面数量 + * + * 注意: + * 此函数用于获取当前候选页面数量,可以选择获取整体或段的候选页面数量。 + */ uint32 get_curr_candidate_nums(bool segment) { uint32 currCandidates = 0; @@ -2437,7 +2874,7 @@ uint32 get_curr_candidate_nums(bool segment) for (int i = 1; i < g_instance.ckpt_cxt_ctl->pgwr_procs.num; i++) { pgwr = &g_instance.ckpt_cxt_ctl->pgwr_procs.writer_proc[i]; if (pgwr->proc != NULL) { - currCandidates += get_thread_seg_candidate_nums(i); + currCandidates += get_thread_seg_candidate_nums(i); // 获取段的候选页面数量 } } return currCandidates; @@ -2446,33 +2883,45 @@ uint32 get_curr_candidate_nums(bool segment) for (int i = 1; i < g_instance.ckpt_cxt_ctl->pgwr_procs.num; i++) { pgwr = &g_instance.ckpt_cxt_ctl->pgwr_procs.writer_proc[i]; if (pgwr->proc != NULL) { - currCandidates += get_thread_candidate_nums(i); + currCandidates += get_thread_candidate_nums(i); // 获取整体的候选页面数量 } } return currCandidates; } +/* + * 功能:处理SIGHUP信号的处理函数 + * + * 参数: + * sigInfo:信号信息 + */ static void ckpt_pagewriter_sighup_handler(SIGNAL_ARGS) { int save_errno = errno; - t_thrd.pagewriter_cxt.got_SIGHUP = true; + t_thrd.pagewriter_cxt.got_SIGHUP = true; // 设置SIGHUP标志为true,表示收到SIGHUP信号 if (t_thrd.proc) { - SetLatch(&t_thrd.proc->procLatch); + SetLatch(&t_thrd.proc->procLatch); // 设置进程Latch,通知进程有SIGHUP信号 } errno = save_errno; } /* SIGINT: set flag to run a normal checkpoint right away */ +/* + * 功能:处理SIGINT信号的处理函数 + * + * 参数: + * sigInfo:信号信息 + */ static void ckpt_pagewriter_sigint_handler(SIGNAL_ARGS) { int save_errno = errno; - t_thrd.pagewriter_cxt.sync_requested = true; + t_thrd.pagewriter_cxt.sync_requested = true; // 设置同步请求标志为true,表示收到SIGINT信号 if (t_thrd.proc) - SetLatch(&t_thrd.proc->procLatch); + SetLatch(&t_thrd.proc->procLatch); // 设置进程Latch,通知进程有SIGINT信号 errno = save_errno; } @@ -2502,13 +2951,20 @@ static void ckpt_pagewriter_quick_die(SIGNAL_ARGS) gs_thread_exit(EXIT_MODE_TWO); } +/* + * 功能:处理请求关闭信号的处理函数 + * + * 参数: + * sigInfo:信号信息 + */ static void ckpt_pagewriter_request_shutdown_handler(SIGNAL_ARGS) { int save_errno = errno; - t_thrd.pagewriter_cxt.shutdown_requested = true; + t_thrd.pagewriter_cxt.shutdown_requested = true; // 设置关闭请求标志为true,表示收到请求关闭信号 + if (t_thrd.proc) { - SetLatch(&t_thrd.proc->procLatch); + SetLatch(&t_thrd.proc->procLatch); // 设置进程Latch,通知进程有请求关闭信号 } errno = save_errno; @@ -2535,7 +2991,6 @@ void ckpt_shutdown_pagewriter() } } - /* The following functions are used by the pagewriter thread to process file sync requests. */ Size PageWriterShmemSize(void) @@ -2559,11 +3014,11 @@ void PageWriterSyncShmemInit(void) bool found = false; g_instance.ckpt_cxt_ctl->incre_ckpt_sync_shmem = - (IncreCkptSyncShmemStruct*)ShmemInitStruct("Incre Ckpt Sync Data", size, &found); + (IncreCkptSyncShmemStruct *)ShmemInitStruct("Incre Ckpt Sync Data", size, &found); if (!found) { /* The memory of the memset sometimes exceeds 2 GB. so, memset_s cannot be used. */ - MemSet((char*)g_instance.ckpt_cxt_ctl->incre_ckpt_sync_shmem, 0, size); + MemSet((char *)g_instance.ckpt_cxt_ctl->incre_ckpt_sync_shmem, 0, size); SpinLockInit(&g_instance.ckpt_cxt_ctl->incre_ckpt_sync_shmem->sync_lock); g_instance.ckpt_cxt_ctl->incre_ckpt_sync_shmem->max_requests = TOTAL_BUFFER_NUM; g_instance.ckpt_cxt_ctl->incre_ckpt_sync_shmem->sync_queue_lwlock = LWLockAssign(LWTRANCHE_PGWR_SYNC_QUEUE); diff --git a/src/gausskernel/process/postmaster/pgaudit.cpp b/src/gausskernel/process/postmaster/pgaudit.cpp index 87a733645..1f96dc79f 100755 --- a/src/gausskernel/process/postmaster/pgaudit.cpp +++ b/src/gausskernel/process/postmaster/pgaudit.cpp @@ -118,10 +118,10 @@ typedef union { */ #ifdef WIN32 #define LBF_MODE _IONBF -#define DIR_SEP "\\" +#define DIR_SEP "\\" #else #define LBF_MODE _IOLBF -#define DIR_SEP "/" +#define DIR_SEP "/" #endif /* @@ -131,16 +131,12 @@ typedef union { */ #define READ_BUF_SIZE (2 * PIPE_CHUNK_SIZE) -THR_LOCAL int PolicyAudit_RotationAge = 5; // 5 seconds +THR_LOCAL int PolicyAudit_RotationAge = 5; // 5 seconds /* * Brief : bitnum in integer Audit_Session * Description : */ -typedef enum { - SESSION_LOGIN_SUCCESS = 0, - SESSION_LOGIN_FAILED, - SESSION_LOGOUT -} SessionType; +typedef enum { SESSION_LOGIN_SUCCESS = 0, SESSION_LOGIN_FAILED, SESSION_LOGOUT } SessionType; /* * Globally visible state (used by postmaster.c) @@ -150,8 +146,8 @@ static bool auditpipe_done = false; /* build audit pipe for auditor process? */ /* * Private state */ -static char* pgaudit_filename = "%s/%d_adt"; -static char* policy_audit_filename = "%s/%lld_event.bin"; +static char *pgaudit_filename = "%s/%d_adt"; +static char *policy_audit_filename = "%s/%lld_event.bin"; static int pgaudit_filemode = S_IRUSR | S_IWUSR; /* rotation time for auditing policy */ static THR_LOCAL pg_time_t policy_next_rotation_time; @@ -204,13 +200,13 @@ typedef struct AuditIndexItem { * Description : */ typedef struct AuditIndexTableNew { - uint32 maxnum; /* max count of the audit index item */ - uint32 begidx; /* the position of the first audit index item */ - uint32 thread_num; /* the running audit thread num */ - volatile uint32 latest_idx; /* the latest next position of all audit threads index items */ - uint32 curidx[MAX_AUDIT_NUM]; /* the position of the current audit thread index item */ - uint32 count; /* the count of the audit index item */ - pg_time_t last_audit_time; /* the audit time of the latest audit record */ + uint32 maxnum; /* max count of the audit index item */ + uint32 begidx; /* the position of the first audit index item */ + uint32 thread_num; /* the running audit thread num */ + volatile uint32 latest_idx; /* the latest next position of all audit threads index items */ + uint32 curidx[MAX_AUDIT_NUM]; /* the position of the current audit thread index item */ + uint32 count; /* the count of the audit index item */ + pg_time_t last_audit_time; /* the audit time of the latest audit record */ AuditIndexItem data[1]; } AuditIndexTableNew; @@ -232,7 +228,7 @@ static const char audit_indextbl_old_file[] = "index_table"; static const int indextbl_header_size = offsetof(AuditIndexTableNew, data); static const int old_indextbl_header_size = offsetof(AuditIndexTable, data); -static const char* AuditTypeDescs[] = {"unknown", +static const char *AuditTypeDescs[] = {"unknown", "login_success", "login_failed", "user_logout", @@ -282,13 +278,13 @@ static const char* AuditTypeDescs[] = {"unknown", "ddl_publication_subscription", "ddl_foreign_data_wrapper"}; -static const int AuditTypeNum = sizeof(AuditTypeDescs) / sizeof(char*); +static const int AuditTypeNum = sizeof(AuditTypeDescs) / sizeof(char *); #define AuditTypeDesc(type) (((type) > 0 && (type) < AuditTypeNum) ? AuditTypeDescs[(type)] : AuditTypeDescs[0]) -static const char* AuditResultDescs[] = {"unknown", "ok", "failed"}; +static const char *AuditResultDescs[] = {"unknown", "ok", "failed"}; -static const int AuditResultNum = sizeof(AuditResultDescs) / sizeof(char*); +static const int AuditResultNum = sizeof(AuditResultDescs) / sizeof(char *); #define AuditResultDesc(type) (((type) > 0 && (type) < AuditResultNum) ? AuditResultDescs[(type)] : AuditResultDescs[0]) @@ -336,46 +332,44 @@ typedef struct AuditData { #define MAXNUMLEN 16 -#define WRITE_TO_AUDITPIPE (auditpipe_done && t_thrd.role != AUDITOR) +#define WRITE_TO_AUDITPIPE (auditpipe_done && t_thrd.role != AUDITOR) #define WRITE_TO_STDAUDITFILE(ctype) (t_thrd.role == AUDITOR && ctype == STD_AUDIT_TYPE) #define WRITE_TO_UNIAUDITFILE(ctype) (t_thrd.role == AUDITOR && ctype == UNIFIED_AUDIT_TYPE) struct AuditEventInfo { - AuditEventInfo() : userid{0}, - username(NULL), - dbname(NULL), - appname(NULL), - remotehost(NULL), - client_info{0}, - threadid{0}, - localport{0}, - remoteport{0} {} + AuditEventInfo() + : userid{0}, + username(NULL), + dbname(NULL), + appname(NULL), + remotehost(NULL), + client_info{0}, + threadid{0}, + localport{0}, + remoteport{0} + {} char userid[MAXNUMLEN]; - const char* username; - const char* dbname; - const char* appname; - const char* remotehost; + const char *username; + const char *dbname; + const char *appname; + const char *remotehost; char client_info[MAX_CONNECTION_INFO_SIZE]; char threadid[MAXNUMLEN * 4]; char localport[MAXNUMLEN]; char remoteport[MAXNUMLEN]; }; -typedef enum { - SYSAUDITFILE_TYPE = 1, - POLICYAUDITFILE_TYPE, - UNKNOWNFILE_TYPE -} AuditFileType; +typedef enum { SYSAUDITFILE_TYPE = 1, POLICYAUDITFILE_TYPE, UNKNOWNFILE_TYPE } AuditFileType; /* Local subroutines */ -static void process_pipe_input(char* auditbuffer, int* bytes_in_auditbuffer); -static void flush_pipe_input(char* auditbuffer, int* bytes_in_auditbuffer); -static void pgaudit_write_file(char* buffer, int count); +static void process_pipe_input(char *auditbuffer, int *bytes_in_auditbuffer); +static void flush_pipe_input(char *auditbuffer, int *bytes_in_auditbuffer); +static void pgaudit_write_file(char *buffer, int count); static void auditfile_init(bool allow_errors = false); static FILE *auditfile_open(pg_time_t timestamp, const char *mode, bool allow_errors, - const char *filename = pgaudit_filename, bool ignore_num = false); + const char *filename = pgaudit_filename, bool ignore_num = false); static void auditfile_close(AuditFileType flag); /****** audit logs management ******/ @@ -391,28 +385,26 @@ static void sig_thread_quit_handler(); static void sig_thread_config_handler(int ¤tAuditRotationAge, int ¤tAuditRemainThreshold); static void pgauditor_kill(int code, Datum arg); -static void write_pipe_chunks(char* data, int len, AuditClassType type = STD_AUDIT_TYPE); -static void appendStringField(StringInfo str, const char* s); -static void pgaudit_close_file(FILE* fp, const char* file); -static void pgaudit_read_indexfile(const char* audit_directory); -static void pgaudit_update_indexfile(const char* mode, bool allow_errors); +static void write_pipe_chunks(char *data, int len, AuditClassType type = STD_AUDIT_TYPE); +static void appendStringField(StringInfo str, const char *s); +static void pgaudit_close_file(FILE *fp, const char *file); +static void pgaudit_read_indexfile(const char *audit_directory); +static void pgaudit_update_indexfile(const char *mode, bool allow_errors); static bool pgaudit_find_indexfile(void); static void pgaudit_indexfile_upgrade(void); -static void pgaudit_indexfile_sync(const char* mode, bool allow_errors); +static void pgaudit_indexfile_sync(const char *mode, bool allow_errors); static void pgaudit_rewrite_indexfile(void); static void pgaudit_indextbl_init_new(void); static void pgaudit_reset_indexfile(); -static const char* pgaudit_string_field(AuditData* adata, int num); -static void deserialization_to_tuple(Datum (&values)[PGAUDIT_QUERY_COLS], - AuditData *adata, - const AuditMsgHdr &header); +static const char *pgaudit_string_field(AuditData *adata, int num); +static void deserialization_to_tuple(Datum (&values)[PGAUDIT_QUERY_COLS], AuditData *adata, const AuditMsgHdr &header); static void pgaudit_query_file(Tuplestorestate *state, TupleDesc tdesc, uint32 fnum, TimestampTz begtime, TimestampTz endtime, const char *audit_directory); static void pgaudit_query_valid_check(const ReturnSetInfo *rsinfo, FunctionCallInfoData *fcinfo, TupleDesc &tupdesc); static uint32 pgaudit_get_auditfile_num(); -static void pgaudit_update_auditfile_time(pg_time_t timestamp, bool exist); -static void pgaudit_switch_next_auditfile(); +static void pgaudit_update_auditfile_time(pg_time_t timestamp, bool exist); +static void pgaudit_switch_next_auditfile(); /********** unified audit ******/ static void pgaudit_send_data_to_elastic(); @@ -420,11 +412,11 @@ static void pgaudit_query_file_for_elastic(); static void elasic_search_connection_test(); static void policy_auditfile_rotate(); static void set_next_policy_rotation_time(void); -static void pgaudit_write_policy_audit_file(const char* buffer, int count); +static void pgaudit_write_policy_audit_file(const char *buffer, int count); /********** toughness *********/ static void CheckAuditFile(void); -static bool pgaudit_valid_header(const AuditMsgHdr* header); +static bool pgaudit_valid_header(const AuditMsgHdr *header); static void pgaudit_mark_corrupt_info(uint32 fnum); static void audit_append_xid_info(const char *detail_info, char *detail_info_xid, uint32 len); static bool audit_status_check_ok(); @@ -587,11 +579,9 @@ NON_EXEC_STATIC void PgAuditorMain() init_audit_signal_handlers(); if (t_thrd.mem_cxt.pgAuditLocalContext == NULL) - t_thrd.mem_cxt.pgAuditLocalContext = AllocSetContextCreate(t_thrd.top_mem_cxt, - "audit memory context", - ALLOCSET_DEFAULT_MINSIZE, - ALLOCSET_DEFAULT_INITSIZE * 3, - ALLOCSET_DEFAULT_MAXSIZE * 3); + t_thrd.mem_cxt.pgAuditLocalContext = + AllocSetContextCreate(t_thrd.top_mem_cxt, "audit memory context", ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE * 3, ALLOCSET_DEFAULT_MAXSIZE * 3); on_shmem_exit(pgauditor_kill, (Datum)0); (void)MemoryContextSwitchTo(t_thrd.mem_cxt.pgAuditLocalContext); @@ -599,7 +589,7 @@ NON_EXEC_STATIC void PgAuditorMain() /* If an exception is encountered, processing resumes here. */ sigjmp_buf local_sigjmp_buf; int curTryCounter; - int* oldTryCounter = NULL; + int *oldTryCounter = NULL; if (sigsetjmp(local_sigjmp_buf, 1) != 0) { gstrace_tryblock_exit(true, oldTryCounter); pgaudit_handle_exception(); @@ -607,7 +597,6 @@ NON_EXEC_STATIC void PgAuditorMain() oldTryCounter = gstrace_tryblock_entry(&curTryCounter); t_thrd.log_cxt.PG_exception_stack = &local_sigjmp_buf; /* We can now handle ereport(ERROR) */ - /* remember active auditfile parameters */ currentAuditRotationAge = u_sess->attr.attr_security.Audit_RotationAge; currentAuditRemainThreshold = u_sess->attr.attr_security.Audit_RemainThreshold; @@ -743,7 +732,6 @@ NON_EXEC_STATIC void PgAuditorMain() if (t_thrd.audit.pipe_eof_seen) { break; } - } /* @@ -779,22 +767,37 @@ void pgaudit_send_data_to_elastic() { if (IS_PGXC_COORDINATOR && g_instance.attr.attr_security.use_elastic_search) { pgaudit_query_file_for_elastic(); - } + } } /* * Start all audit subprocesses */ +/* + * 功能:启动所有审计进程 + * + * 注意: + * 该函数会初始化审计进程上下文,并启动多个审计进程以处理审计日志。 + * 如果审计进程已经启动,则不会再次启动。 + * 对于每个审计进程,会记录其进程ID和日志信息。 + */ void pgaudit_start_all(void) { + // 如果审计进程数组为空,直接返回 if (g_instance.pid_cxt.PgAuditPID == NULL) { return; } + // 初始化审计进程上下文 audit_process_cxt_init(); + + // 遍历审计线程数 for (int i = 0; i < g_instance.audit_cxt.thread_num; ++i) { + // 如果审计进程未启动,则启动它 if (g_instance.pid_cxt.PgAuditPID[i] == 0) { g_instance.pid_cxt.PgAuditPID[i] = pgaudit_start(); + + // 记录审计进程启动信息到日志 ereport(LOG, (errmsg("auditor process %d started, pid=%lu", i, g_instance.pid_cxt.PgAuditPID[i]))); } } @@ -803,27 +806,56 @@ void pgaudit_start_all(void) /* * Stop all audit subprocesses */ +/* + * 功能:停止所有审计进程 + * + * 注意: + * 该函数会遍历所有审计进程,并向它们发送 SIGQUIT 信号以请求退出。 + * 在请求退出后,会释放审计进程的相关资源。 + * 此函数通常用于停止审计进程。 + */ void pgaudit_stop_all(void) { + // 遍历审计线程数 for (int i = 0; i < g_instance.audit_cxt.thread_num; ++i) { + // 如果审计进程已经启动,则发送 SIGQUIT 信号请求其退出 if (g_instance.pid_cxt.PgAuditPID[i] != 0) { Assert(!dummyStandbyMode); signal_child(g_instance.pid_cxt.PgAuditPID[i], SIGQUIT, -1); } } + + // 释放审计进程上下文资源 audit_process_cxt_exit(); } /* * Postmaster subroutine to start a sysauditor subprocess. */ +/* + * 功能:启动审计进程 + * + * 返回值: + * 返回审计进程的线程 ID(ThreadId)或 0(表示启动失败)。 + * + * 注意: + * 如果审计功能未启用(u_sess->attr.attr_security.Audit_enabled 为假),则不会启动审计进程。 + * 否则,它将通过调用 initialize_util_thread(AUDITOR) 来尝试初始化并启动审计进程。 + * 如果成功启动审计进程,则返回其线程 ID,否则返回 0。 + * 该函数通常在 PostgreSQL 启动时调用以启动审计功能。 + */ ThreadId pgaudit_start(void) { ThreadId sysauditorPid; + + // 如果未启用审计功能,直接返回 0 表示启动失败 if (!u_sess->attr.attr_security.Audit_enabled) return 0; + // 调用 initialize_util_thread(AUDITOR) 来启动审计进程 sysauditorPid = initialize_util_thread(AUDITOR); + + // 如果成功启动审计进程,返回其线程 ID if (sysauditorPid != 0) { /* success, in postmaster */ return (ThreadId)sysauditorPid; @@ -865,9 +897,9 @@ void allow_immediate_pgaudit_restart(void) * of bytes present. On exit, any not-yet-eaten data is left-justified in * auditbuffer, and *bytes_in_auditbuffer is updated. */ -static void process_pipe_input(char* auditbuffer, int* bytes_in_auditbuffer) +static void process_pipe_input(char *auditbuffer, int *bytes_in_auditbuffer) { - char* cursor = auditbuffer; + char *cursor = auditbuffer; int count = *bytes_in_auditbuffer; /* While we have enough for a header, process data... */ @@ -881,10 +913,10 @@ static void process_pipe_input(char* auditbuffer, int* bytes_in_auditbuffer) securec_check(errorno, "\0", "\0"); if (p.nuls[0] == '\0' && p.nuls[1] == '\0' && p.len > 0 && p.len <= PIPE_MAX_PAYLOAD && p.pid != 0 && (p.is_last == 't' || p.is_last == 'f')) { - List* buffer_list = NULL; - ListCell* cell = NULL; - save_buffer* existing_slot = NULL; - save_buffer* free_slot = NULL; + List *buffer_list = NULL; + ListCell *cell = NULL; + save_buffer *existing_slot = NULL; + save_buffer *free_slot = NULL; StringInfo str; chunklen = PIPE_HEADER_SIZE + p.len; /* Fall out of loop if we don't have the whole chunk yet */ @@ -894,7 +926,7 @@ static void process_pipe_input(char* auditbuffer, int* bytes_in_auditbuffer) /* Locate any existing buffer for this source pid */ buffer_list = t_thrd.audit.buffer_lists[p.pid % NBUFFER_LISTS]; foreach (cell, buffer_list) { - save_buffer* buf = (save_buffer*)lfirst(cell); + save_buffer *buf = (save_buffer *)lfirst(cell); if (buf->pid == p.pid) { existing_slot = buf; @@ -921,7 +953,7 @@ static void process_pipe_input(char* auditbuffer, int* bytes_in_auditbuffer) * Need a free slot, but there isn't one in the list, * so create a new one and extend the list with it. */ - free_slot = (save_buffer*)palloc(sizeof(save_buffer)); + free_slot = (save_buffer *)palloc(sizeof(save_buffer)); buffer_list = lappend(buffer_list, free_slot); t_thrd.audit.buffer_lists[p.pid % NBUFFER_LISTS] = buffer_list; } @@ -994,17 +1026,17 @@ static void process_pipe_input(char* auditbuffer, int* bytes_in_auditbuffer) * This is currently used only at sysauditor shutdown, but could perhaps be * useful at other times, so it is careful to leave things in a clean state. */ -static void flush_pipe_input(char* auditbuffer, int* bytes_in_auditbuffer) +static void flush_pipe_input(char *auditbuffer, int *bytes_in_auditbuffer) { int i; /* Dump any incomplete protocol messages */ for (i = 0; i < NBUFFER_LISTS; i++) { - List* list = t_thrd.audit.buffer_lists[i]; - ListCell* cell = NULL; + List *list = t_thrd.audit.buffer_lists[i]; + ListCell *cell = NULL; foreach (cell, list) { - save_buffer* buf = (save_buffer*)lfirst(cell); + save_buffer *buf = (save_buffer *)lfirst(cell); if (buf->pid != 0) { StringInfo str = &(buf->data); @@ -1040,7 +1072,7 @@ static void flush_pipe_input(char* auditbuffer, int* bytes_in_auditbuffer) * This allows the sysauditor process to record elog messages of its own, * even though its stderr does not point at the sysaudit pipe. */ -static void pgaudit_write_file(char* buffer, int count) +static void pgaudit_write_file(char *buffer, int count) { int rc; pg_time_t curtime; @@ -1051,10 +1083,10 @@ static void pgaudit_write_file(char* buffer, int count) curtime = time(NULL); errorno = memcpy_s(buffer + offsetof(AuditMsgHdr, time), READ_BUF_SIZE - offsetof(AuditMsgHdr, time), &curtime, - sizeof(pg_time_t)); + sizeof(pg_time_t)); securec_check(errorno, "\0", "\0"); - errorno = memcpy_s( - buffer + offsetof(AuditMsgHdr, size), READ_BUF_SIZE - offsetof(AuditMsgHdr, size), &count, sizeof(uint32)); + errorno = memcpy_s(buffer + offsetof(AuditMsgHdr, size), READ_BUF_SIZE - offsetof(AuditMsgHdr, size), &count, + sizeof(uint32)); securec_check(errorno, "\0", "\0"); errno = 0; @@ -1082,7 +1114,7 @@ retry1: (void)fflush(t_thrd.audit.sysauditFile); } -static void pgaudit_write_policy_audit_file(const char* buffer, int count) +static void pgaudit_write_policy_audit_file(const char *buffer, int count) { /* flush policy audit info only if elastic system configure */ if (!IS_PGXC_COORDINATOR || !g_instance.attr.attr_security.use_elastic_search) { @@ -1141,11 +1173,11 @@ static void auditfile_init(bool allow_errors) * Otherwise, errors are treated as fatal. */ static FILE *auditfile_open(pg_time_t timestamp, const char *mode, bool allow_errors, const char *_filename, - bool ignore_num) + bool ignore_num) { - FILE *fh = NULL; - char* filename = NULL; - uint32 fnum = 0; + FILE *fh = NULL; + char *filename = NULL; + uint32 fnum = 0; int thread_idx = t_thrd.audit.cur_thread_idx; struct stat st; @@ -1155,10 +1187,10 @@ static FILE *auditfile_open(pg_time_t timestamp, const char *mode, bool allow_er } ereport(DEBUG1, (errmsg("audit thread idx: %d auditfile_open ok fnum : %d", thread_idx, fnum))); - filename = (char*)palloc(MAXPGPATH); - int rc = snprintf_s( - filename, MAXPGPATH, MAXPGPATH - 1, _filename, g_instance.attr.attr_security.Audit_directory, fnum); - securec_check_intval(rc,, NULL); + filename = (char *)palloc(MAXPGPATH); + int rc = + snprintf_s(filename, MAXPGPATH, MAXPGPATH - 1, _filename, g_instance.attr.attr_security.Audit_directory, fnum); + securec_check_intval(rc, , NULL); /* * Note we do not let pgaudit_filemode disable IWUSR, since we certainly want @@ -1177,7 +1209,7 @@ static FILE *auditfile_open(pg_time_t timestamp, const char *mode, bool allow_er int save_errno = errno; ereport(allow_errors ? LOG : FATAL, - (errcode_for_file_access(), errmsg("could not open audit file \"%s\": %m", filename))); + (errcode_for_file_access(), errmsg("could not open audit file \"%s\": %m", filename))); errno = save_errno; } @@ -1186,7 +1218,7 @@ static FILE *auditfile_open(pg_time_t timestamp, const char *mode, bool allow_er int save_errno = errno; ereport(allow_errors ? LOG : FATAL, - (errcode_for_file_access(), errmsg("could not chmod audit file \"%s\": %m", filename))); + (errcode_for_file_access(), errmsg("could not chmod audit file \"%s\": %m", filename))); errno = save_errno; } } @@ -1199,33 +1231,71 @@ static FILE *auditfile_open(pg_time_t timestamp, const char *mode, bool allow_er * Brief : close the audit file. * Description : */ -static void auditfile_close(AuditFileType flag) +/* + * 功能:关闭审计文件 + * + * 参数: + * flag:表示审计文件类型的标志,可以是 SYSAUDITFILE_TYPE 或 POLICYAUDITFILE_TYPE。 + * SYSAUDITFILE_TYPE 表示系统审计文件,POLICYAUDITFILE_TYPE 表示策略审计文件。 + * + * 注意: + * 如果审计文件没有打开(t_thrd.audit.sysauditFile 或 t_thrd.audit.policyauditFile 为 NULL), + * 则不会执行任何操作,直接返回。 + * 如果 flag 为 SYSAUDITFILE_TYPE,将执行以下操作: + * 1. 切换到下一个审计文件。 + * 2. 更新审计索引文件(audit index file)。 + * 3. 关闭系统审计文件。 + * 如果 flag 为 POLICYAUDITFILE_TYPE,将关闭策略审计文件。 + * 此函数通常在审计文件的操作完成后调用,用于关闭文件并更新相关信息。 + */ +void auditfile_close(AuditFileType flag) { + // 如果审计文件未打开,直接返回 if (t_thrd.audit.sysauditFile == NULL) return; - if ((flag == SYSAUDITFILE_TYPE) && g_instance.audit_cxt.audit_indextbl != NULL) { - /* switch to next audit file */ - pgaudit_switch_next_auditfile(); - pgaudit_update_indexfile(PG_BINARY_W, true); - } + // 如果 flag 为 SYSAUDITFILE_TYPE,则执行系统审计文件的关闭和更新操作 if (flag == SYSAUDITFILE_TYPE) { + // 切换到下一个审计文件 + pgaudit_switch_next_auditfile(); + // 更新审计索引文件(audit index file) + pgaudit_update_indexfile(PG_BINARY_W, true); + // 关闭系统审计文件 fclose(t_thrd.audit.sysauditFile); t_thrd.audit.sysauditFile = NULL; } + + // 如果 flag 为 POLICYAUDITFILE_TYPE,则关闭策略审计文件 if (flag == POLICYAUDITFILE_TYPE) { fclose(t_thrd.audit.policyauditFile); t_thrd.audit.policyauditFile = NULL; } - - return; } +/* + * 功能:策略审计文件轮换 + * + * 注意: + * 此函数用于策略审计文件的轮换操作。根据配置的轮换条件,可能会关闭当前审计文件, + * 重命名它,并创建一个新的审计文件,然后继续写入审计数据。审计文件轮换可以根据 + * 配置的轮换周期(PolicyAudit_RotationAge)或文件大小来触发。 + * 如果策略审计文件未打开(t_thrd.audit.policyauditFile 为 NULL),则函数直接返回。 + * 如果启用了轮换,函数会根据配置的轮换周期和当前审计文件的大小,判断是否需要执行轮换操作。 + * 如果需要轮换,则会执行以下操作: + * 1. 关闭当前的策略审计文件。 + * 2. 根据轮换时间构建新文件名,并重命名当前文件。 + * 3. 创建一个新的策略审计文件,并将其打开以继续记录审计数据。 + * 4. 设置下一次轮换的时间。 + * 如果无法创建新的审计文件,函数会记录日志并停止尝试创建文件,但不会禁用审计。 + */ static void policy_auditfile_rotate() { + // 如果策略审计文件未打开,直接返回 if (t_thrd.audit.policyauditFile == NULL) return; - pg_time_t fntime = (pg_time_t) time(NULL); + + // 获取当前时间 + pg_time_t fntime = (pg_time_t)time(NULL); int rc; /* * When doing a time-based rotation, invent the new auditfile name based on @@ -1233,22 +1303,25 @@ static void policy_auditfile_rotate() * file name when we don't do the rotation immediately. */ int64 filesize = ftell(t_thrd.audit.policyauditFile); - if ((fntime + PolicyAudit_RotationAge) > policy_next_rotation_time && filesize > 0) { + if ((fntime + PolicyAudit_RotationAge) > policy_next_rotation_time && filesize > 0) { + // 关闭当前策略审计文件 auditfile_close(POLICYAUDITFILE_TYPE); char oldname[MAXPGPATH] = {0}; // current file rc = snprintf_s(oldname, sizeof(oldname), sizeof(oldname) - 1, "%s" DIR_SEP "0_event.bin", - g_instance.attr.attr_security.Audit_directory); + g_instance.attr.attr_security.Audit_directory); securec_check_ss(rc, "\0", "\0"); char newfile[MAXPGPATH] = {0}; // new rotate file rc = snprintf_s(newfile, sizeof(oldname), sizeof(oldname) - 1, "%s" DIR_SEP "done" DIR_SEP "%lld_event.bin", - g_instance.attr.attr_security.Audit_directory, (long long)fntime); + g_instance.attr.attr_security.Audit_directory, (long long)fntime); securec_check_ss(rc, "\0", "\0"); + // 将当前文件重命名为新文件 if (rename(oldname, newfile) != 0) { ereport(LOG, (errmsg("can't rename \"%s\" to \"%s\": %m", oldname, newfile))); } - FILE* fh = auditfile_open(fntime, "a", true, policy_audit_filename, true); + // 打开一个新的审计文件以继续记录 + FILE *fh = auditfile_open(fntime, "a", true, policy_audit_filename, true); if (fh == NULL) { /* @@ -1258,11 +1331,11 @@ static void policy_auditfile_rotate() * trying to create files. */ if (errno != ENFILE && errno != EMFILE) { - ereport(LOG, - (errmsg("disabling automatic rotation (use SIGHUP to re-enable)"))); + ereport(LOG, (errmsg("disabling automatic rotation (use SIGHUP to re-enable)"))); } return; } + // 设置下一次轮换的时间 t_thrd.audit.policyauditFile = fh; set_next_policy_rotation_time(); } @@ -1275,7 +1348,7 @@ static void policy_auditfile_rotate() static void auditfile_rotate(bool time_based_rotation, bool size_based_rotation) { pg_time_t fntime; - FILE* fh = NULL; + FILE *fh = NULL; t_thrd.audit.rotation_requested = false; /* * When doing a time-based rotation, invent the new auditfile name based on @@ -1309,12 +1382,25 @@ static void auditfile_rotate(bool time_based_rotation, bool size_based_rotation) set_next_rotation_time(); } +/* + * 功能:设置下一次策略审计文件轮换的时间 + * + * 注意: + * 此函数根据配置的策略审计文件轮换周期(PolicyAudit_RotationAge)来计算下一次轮换的时间。 + * 如果轮换周期小于或等于0,则函数直接返回,不设置下一次轮换时间。 + * + * 计算下一次轮换的时间是通过当前时间加上轮换周期得到的。 + * 轮换周期是以秒为单位的时间间隔,用于控制策略审计文件的轮换。 + */ static void set_next_policy_rotation_time(void) { + // 如果轮换周期小于或等于0,直接返回,不设置下一次轮换时间 if (PolicyAudit_RotationAge <= 0) { return; } - policy_next_rotation_time = (pg_time_t) time(NULL); + // 获取当前时间 + policy_next_rotation_time = (pg_time_t)time(NULL); + // 计算下一次轮换的时间,加上轮换周期 policy_next_rotation_time += PolicyAudit_RotationAge; } @@ -1324,7 +1410,7 @@ static void set_next_policy_rotation_time(void) static void set_next_rotation_time(void) { pg_time_t now; - struct pg_tm* tm = NULL; + struct pg_tm *tm = NULL; int rotinterval; /* nothing to do if time-based rotation is disabled */ if (u_sess->attr.attr_security.Audit_RotationAge <= 0) @@ -1347,7 +1433,21 @@ static void set_next_rotation_time(void) now -= tm->tm_gmtoff; t_thrd.audit.next_rotation_time = now; } - +/* + * 功能:生成审计文件警告信息 + * + * 参数: + * remain_time:剩余时间,以秒为单位 + * filesize:文件大小,以字节为单位 + * + * 注意: + * 此函数根据审计配置和审计文件状态生成警告信息。根据不同的配置和状态,可能会生成以下警告: + * 1. + * 当审计清理策略(Audit_CleanupPolicy)或剩余时间为0,且审计文件总空间超过审计空间限制(Audit_SpaceLimit)时,生成警告。 + * 2. 当审计清理策略为0,剩余时间大于0,且审计文件总空间超过审计空间限制时,生成警告。 + * 3. 当审计文件总数超过审计文件保留阈值(Audit_RemainThreshold)时,生成警告。 + * 警告信息包括审计文件的总空间、审计空间限制、审计文件的剩余时间、审计文件总数等信息。 + */ void pgaudit_gen_auditfile_warning(pg_time_t remain_time, uint4 filesize) { if ((u_sess->attr.attr_security.Audit_CleanupPolicy || remain_time == 0) && @@ -1373,11 +1473,11 @@ void pgaudit_gen_auditfile_warning(pg_time_t remain_time, uint4 filesize) (long long int)(g_instance.audit_cxt.pgaudit_totalspace + filesize), u_sess->attr.attr_security.Audit_SpaceLimit))); #else - ereport(WARNING, (errmsg("Based on time-priority policy, the oldest audit file is beyond %d days or " - "audit file total space(%ld B) exceed guc parameter(audit_space_limit: %d KB)", - u_sess->attr.attr_security.Audit_RemainAge, - (g_instance.audit_cxt.pgaudit_totalspace + filesize), - u_sess->attr.attr_security.Audit_SpaceLimit))); + ereport(WARNING, (errmsg("Based on time-priority policy, the oldest audit file is beyond %d days or " + "audit file total space(%ld B) exceed guc parameter(audit_space_limit: %d KB)", + u_sess->attr.attr_security.Audit_RemainAge, + (g_instance.audit_cxt.pgaudit_totalspace + filesize), + u_sess->attr.attr_security.Audit_SpaceLimit))); #endif if (g_instance.audit_cxt.audit_indextbl->count > (uint32)u_sess->attr.attr_security.Audit_RemainThreshold) ereport(WARNING, @@ -1385,7 +1485,26 @@ void pgaudit_gen_auditfile_warning(pg_time_t remain_time, uint4 filesize) g_instance.audit_cxt.audit_indextbl->count, u_sess->attr.attr_security.Audit_RemainThreshold))); ereport(WARNING, (errmsg("%s", t_thrd.audit.pgaudit_filepath))); } - +/* + * 功能:根据时间策略判断是否应保留审计文件 + * + * 参数: + * remain_time:剩余时间,以秒为单位 + * filesize:文件大小,以字节为单位 + * index:当前审计文件索引 + * item:当前审计文件的元数据信息 + * + * 返回值: + * 如果根据时间策略应该保留审计文件,则返回 true;否则返回 false。 + * + * 注意: + * 此函数根据审计文件的总数、剩余时间和空间限制以及时间戳信息判断是否应该保留审计文件。具体逻辑包括: + * 1.如果审计文件总数小于或等于审计文件保留阈值(Audit_RemainThreshold)并且剩余时间大于0,并且审计文件总空间未超过最大限制(SPACE_MAXIMUM_SIZE),则继续判断。 + * 2. 如果审计文件总空间超过审计空间限制(Audit_SpaceLimit),则生成警告信息。 + * 3.获取当前审计文件和下一个审计文件的时间戳信息,如果剩余时间大于或等于当前审计文件的创建时间距离上次审计的时间,或者下一个审计文件存在且剩余时间大于下一个审计文件的创建时间距离上次审计的时间,则返回 + * true,表示应该保留审计文件。 + * 4. 如果不满足上述条件,则返回 false,表示不应该保留审计文件。 + */ bool should_keep_basedon_timepolicy(pg_time_t remain_time, uint4 filesize, uint32 index, const AuditIndexItem *item) { if (g_instance.audit_cxt.audit_indextbl->count <= (uint32)u_sess->attr.attr_security.Audit_RemainThreshold && @@ -1424,7 +1543,7 @@ bool should_keep_basedon_timepolicy(pg_time_t remain_time, uint4 filesize, uint3 static void pgaudit_cleanup(void) { uint32 index = 0; - AuditIndexItem* item = NULL; + AuditIndexItem *item = NULL; bool truncated = false; if (g_instance.audit_cxt.audit_indextbl == NULL) return; @@ -1436,8 +1555,8 @@ static void pgaudit_cleanup(void) LWLockAcquire(g_instance.audit_cxt.index_file_lock, LW_EXCLUSIVE); index = g_instance.audit_cxt.audit_indextbl->begidx; while (g_instance.audit_cxt.pgaudit_totalspace + filesize >= - ((uint64)u_sess->attr.attr_security.Audit_SpaceLimit * 1024L) || - g_instance.audit_cxt.audit_indextbl->count > (uint32)u_sess->attr.attr_security.Audit_RemainThreshold) { + ((uint64)u_sess->attr.attr_security.Audit_SpaceLimit * 1024L) || + g_instance.audit_cxt.audit_indextbl->count > (uint32)u_sess->attr.attr_security.Audit_RemainThreshold) { errno_t errorno = EOK; item = g_instance.audit_cxt.audit_indextbl->data + index; uint32 fnum = item->filenum; @@ -1453,7 +1572,7 @@ static void pgaudit_cleanup(void) /* trunate audit file */ int rc = snprintf_s(t_thrd.audit.pgaudit_filepath, MAXPGPATH, MAXPGPATH - 1, pgaudit_filename, - g_instance.attr.attr_security.Audit_directory, item->filenum); + g_instance.attr.attr_security.Audit_directory, item->filenum); securec_check_intval(rc, , ); struct stat statbuf; if (stat(t_thrd.audit.pgaudit_filepath, &statbuf) == 0 && unlink(t_thrd.audit.pgaudit_filepath) < 0) { @@ -1502,6 +1621,17 @@ static void pgaudit_cleanup(void) * -------------------------------- */ /* SIGQUIT signal handler for auditor process */ +/* + * 功能:审计子进程的退出信号处理函数 + * + * 参数: + * SIGNAL_ARGS:信号处理函数的参数,通常为标准的 SIGNAL_ARGS 类型 + * + * 注意: + * 该函数被用于处理审计子进程的退出信号。在函数内部,它将 `t_thrd.audit.need_exit` 标志设置为 true, + * 然后通过 `SetLatch` 函数设置审计子进程的信号事件,以触发审计子进程的退出。该函数还保存和恢复了 `errno`, + * 以确保不影响其他代码。 + */ static void pgaudit_exit(SIGNAL_ARGS) { int save_errno = errno; @@ -1511,6 +1641,17 @@ static void pgaudit_exit(SIGNAL_ARGS) } /* SIGHUP: set flag to reload config file */ +/* + * 功能:SIGHUP信号处理函数 + * + * 参数: + * SIGNAL_ARGS:信号处理函数的参数,通常为标准的SIGNAL_ARGS类型 + * + * 注意: + * 该函数被用于处理SIGHUP信号。在函数内部,它将`t_thrd.audit.got_SIGHUP`标志设置为true, + * 然后通过`SetLatch`函数设置审计子进程的信号事件,以触发相关操作。该函数还保存和恢复了`errno`, + * 以确保不影响其他代码。 + */ static void sigHupHandler(SIGNAL_ARGS) { int save_errno = errno; @@ -1520,6 +1661,17 @@ static void sigHupHandler(SIGNAL_ARGS) } /* SIGUSR1: set flag to rotate auditfile */ +/* + * 功能:SIGUSR1信号处理函数 + * + * 参数: + * SIGNAL_ARGS:信号处理函数的参数,通常为标准的SIGNAL_ARGS类型 + * + * 注意: + * 该函数被用于处理SIGUSR1信号。在函数内部,它将`t_thrd.audit.rotation_requested`标志设置为true, + * 然后通过`SetLatch`函数设置审计子进程的信号事件,以触发相关操作。该函数还保存和恢复了`errno`, + * 以确保不影响其他代码。 + */ static void sigUsr1Handler(SIGNAL_ARGS) { int save_errno = errno; @@ -1550,7 +1702,7 @@ static void sigUsr1Handler(SIGNAL_ARGS) * warning from ignoring write()'s result, so do a little dance with casting * rc to void to shut up the compiler. */ -static void write_pipe_chunks(char* data, int len, AuditClassType type) +static void write_pipe_chunks(char *data, int len, AuditClassType type) { static volatile uint32 pipe_count = 0; int thread_num = g_instance.audit_cxt.thread_num; @@ -1603,7 +1755,20 @@ static void write_pipe_chunks(char* data, int len, AuditClassType type) * Brief : append a string field to a streamed data * Description : */ -static void appendStringField(StringInfo str, const char* s) +/* + * 功能:向StringInfo结构中添加一个字符串字段 + * + * 参数: + * str:指向StringInfo结构的指针,用于存储字符串字段 + * s:指向待添加的字符串的指针,可以为NULL + * + * 注意: + * 该函数用于将字符串字段添加到StringInfo结构中,StringInfo是一个用于构建和操作字符串的工具。 + * 如果输入的字符串指针s为NULL,则函数会添加一个空字符串字段,字段大小为0。 + * 否则,函数会计算字符串s的大小,将其大小添加到StringInfo中,然后再添加字符串s的内容。 + * 字符串字段的格式通常为:[字段大小(4字节)][字段内容],以便后续读取和解析。 + */ +static void appendStringField(StringInfo str, const char *s) { int size = 0; @@ -1611,35 +1776,59 @@ static void appendStringField(StringInfo str, const char* s) return; if (s == NULL) - appendBinaryStringInfo(str, (char*)&size, sizeof(int)); + appendBinaryStringInfo(str, (char *)&size, sizeof(int)); else { size = strlen(s) + 1; - appendBinaryStringInfo(str, (char*)&size, sizeof(int)); + appendBinaryStringInfo(str, (char *)&size, sizeof(int)); appendBinaryStringInfo(str, s, size); } } +/* + * 功能:获取当前的时间戳(以毫秒为单位) + * + * 返回值:以毫秒为单位的当前时间戳 + * + * 注意: + * 该函数使用系统调用`gettimeofday`来获取当前时间,精确到微秒级别。 + * 它将秒和微秒部分相加,将其转换为毫秒并返回。 + * 时间戳表示从某个参考点(通常是UNIX纪元)到现在经过的时间,以毫秒为单位。 + * 这在很多应用中都是有用的,如性能分析、事件计时等。 + */ static pg_time_t current_timestamp() { struct timeval te; - gettimeofday(&te, NULL); // get current time - pg_time_t milliseconds = te.tv_sec * 1000LL + te.tv_usec / 1000; // calculate milliseconds + gettimeofday(&te, NULL); // 获取当前时间 + pg_time_t milliseconds = te.tv_sec * 1000LL + te.tv_usec / 1000; // 计算毫秒 return milliseconds; } - +/* + * 功能:检查是否满足执行审计的条件 + * + * 返回值:如果满足执行审计的条件,则返回true,否则返回false + * + * 注意: + * 该函数用于确定是否应该执行审计操作。 + * 它会检查以下条件: + * 1. 是否已启用审计(通过检查配置参数u_sess->attr.attr_security.Audit_enabled)。 + * 2. 是否处于主节点模式(对于多节点集群,检查是否连接到主节点)。 + * 3. 是否处于等待模式(对于多节点集群,检查是否处于等待模式)。 + * 如果上述条件之一不满足,函数将返回false,表示不应执行审计操作。 + * 否则,将返回true,表示可以执行审计操作。 + */ static bool audit_status_check_ok() { #ifdef ENABLE_MULTIPLE_NODES /* check whether POSTMASTER is running in standby mode */ if (!u_sess->attr.attr_security.Audit_enabled || (PGSharedMemoryAttached() && t_thrd.postmaster_cxt.HaShmData && - (STANDBY_MODE == t_thrd.postmaster_cxt.HaShmData->current_mode || - PENDING_MODE == t_thrd.postmaster_cxt.HaShmData->current_mode))) + (STANDBY_MODE == t_thrd.postmaster_cxt.HaShmData->current_mode || + PENDING_MODE == t_thrd.postmaster_cxt.HaShmData->current_mode))) return false; #else /* After the standby read function is added, the standby node needs to be audited. */ if (!u_sess->attr.attr_security.Audit_enabled || (PGSharedMemoryAttached() && t_thrd.postmaster_cxt.HaShmData && - PENDING_MODE == t_thrd.postmaster_cxt.HaShmData->current_mode)) - return false; + PENDING_MODE == t_thrd.postmaster_cxt.HaShmData->current_mode)) + return false; #endif return true; @@ -1669,7 +1858,7 @@ static bool audit_type_validcheck(AuditType type) break; case AUDIT_LOCK_USER: case AUDIT_UNLOCK_USER: - type_status = (unsigned int)u_sess->attr.attr_security.Audit_LockUser; + type_status = (unsigned int)u_sess->attr.attr_security.Audit_LockUser; break; case AUDIT_GRANT_ROLE: case AUDIT_REVOKE_ROLE: @@ -1789,7 +1978,7 @@ static bool audit_type_validcheck(AuditType type) } /* get all Audit Event Info from Proc Port */ -static bool audit_get_clientinfo(AuditType type, const char* object_name, AuditEventInfo &event_info) +static bool audit_get_clientinfo(AuditType type, const char *object_name, AuditEventInfo &event_info) { /* * Note that the number of field should keep the same with PGAUDIT_QUERY_COLS @@ -1816,7 +2005,7 @@ static bool audit_get_clientinfo(AuditType type, const char* object_name, AuditE } else { *username = u_sess->proc_cxt.MyProcPort->user_name; } - + /* * append user id information, get user id from table as invalid in session * not safe when access table when run logout process as not in normal transaction @@ -1829,8 +2018,8 @@ static bool audit_get_clientinfo(AuditType type, const char* object_name, AuditE if (*username != NULL && useroid == 0) { ResourceOwner currentOwner = NULL; currentOwner = t_thrd.utils_cxt.CurrentResourceOwner; - ResourceOwner tmpOwner = ResourceOwnerCreate(t_thrd.utils_cxt.CurrentResourceOwner, "CheckUserOid", - THREAD_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_SECURITY)); + ResourceOwner tmpOwner = ResourceOwnerCreate(t_thrd.utils_cxt.CurrentResourceOwner, "CheckUserOid", + THREAD_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_SECURITY)); t_thrd.utils_cxt.CurrentResourceOwner = tmpOwner; useroid = get_role_oid(*username, true); ResourceOwnerRelease(tmpOwner, RESOURCE_RELEASE_BEFORE_LOCKS, true, true); @@ -1861,12 +2050,8 @@ static bool audit_get_clientinfo(AuditType type, const char* object_name, AuditE break; } t_thrd.audit.user_login_time = GetCurrentTimestamp(); - errorno = snprintf_s(threadid, - MAXNUMLEN * 4, - MAXNUMLEN * 4 - 1, - "%lu@%ld", - t_thrd.proc_cxt.MyProcPid, - t_thrd.audit.user_login_time); + errorno = snprintf_s(threadid, MAXNUMLEN * 4, MAXNUMLEN * 4 - 1, "%lu@%ld", t_thrd.proc_cxt.MyProcPid, + t_thrd.audit.user_login_time); securec_check_ss(errorno, "\0", "\0"); int portNum; @@ -1890,12 +2075,8 @@ static bool audit_get_clientinfo(AuditType type, const char* object_name, AuditE if (*remotehost == NULL || (**remotehost) == '\0') *remotehost = _("[unknown]"); - errorno = snprintf_s(event_info.client_info, - MAX_CONNECTION_INFO_SIZE, - MAX_CONNECTION_INFO_SIZE - 1, - "%s@%s", - *appname, - *remotehost); + errorno = snprintf_s(event_info.client_info, MAX_CONNECTION_INFO_SIZE, MAX_CONNECTION_INFO_SIZE - 1, "%s@%s", + *appname, *remotehost); securec_check_ss(errorno, "\0", "\0"); return true; @@ -1907,7 +2088,7 @@ static bool audit_get_clientinfo(AuditType type, const char* object_name, AuditE * 1. verify type and process to decide whehter to report audit or not * 2. get all audit info from connection * 3. append audit info to string buffer - * 4. last, write audit file or send to auditor process to deal with + * 4. last, write audit file or send to auditor process to deal with * the fileds are arraged as below sequence, Note it's not liable to modify them as to keep compatibility of version * header|userid|username|dbname|client_info|object_name|detail_info|nodename|threadid|localport|remoteport */ @@ -1929,15 +2110,15 @@ void audit_report(AuditType type, AuditResult result, const char *object_name, c AuditData adata; AuditEventInfo event_info; if (!audit_get_clientinfo(type, object_name, event_info)) { - return; + return; } char *userid = event_info.userid; - const char* username = event_info.username; - const char* dbname = event_info.dbname; - char* client_info = event_info.client_info; - char* threadid = event_info.threadid; - char* localport = event_info.localport; - char* remoteport = event_info.remoteport; + const char *username = event_info.username; + const char *dbname = event_info.dbname; + char *client_info = event_info.client_info; + char *threadid = event_info.threadid; + char *localport = event_info.localport; + char *remoteport = event_info.remoteport; /* append xid info when audit_xid_info = 1 */ char *detail_info_xid = NULL; @@ -1959,7 +2140,7 @@ void audit_report(AuditType type, AuditResult result, const char *object_name, c adata.type = type; adata.result = result; initStringInfo(&buf); - appendBinaryStringInfo(&buf, (char*)&adata, AUDIT_HEADER_SIZE); + appendBinaryStringInfo(&buf, (char *)&adata, AUDIT_HEADER_SIZE); /* append audit data */ appendStringField(&buf, userid); @@ -1995,7 +2176,21 @@ void audit_report(AuditType type, AuditResult result, const char *object_name, c } /* Brief : close a file. */ -static void pgaudit_close_file(FILE* fp, const char* file) +/* + * 功能:关闭文件并处理可能出现的错误 + * + * 参数: + * fp:要关闭的文件指针。 + * file:文件名,用于在出现错误时记录错误消息。 + * + * 注意: + * 该函数用于关闭文件,并在文件操作出现错误时记录相应的错误消息。 + * 它首先检查文件指针和文件名是否为空,如果为空,则直接返回,不执行任何操作。 + * 然后,它检查文件是否在写入过程中出现错误(使用ferror()函数),如果是,则记录相应的错误消息,并尝试关闭文件。 + * 最后,如果关闭文件时出现错误,也会记录相应的错误消息。 + * 函数的目的是确保文件能够正确关闭,并在出现错误时记录错误消息,以便进行故障排除。 + */ +static void pgaudit_close_file(FILE *fp, const char *file) { if (NULL == file || NULL == fp) { return; @@ -2012,16 +2207,16 @@ static void pgaudit_close_file(FILE* fp, const char* file) } /* Brief : read the index table into memory from file. */ -static void pgaudit_read_indexfile(const char* audit_directory) +static void pgaudit_read_indexfile(const char *audit_directory) { - FILE* fp = NULL; + FILE *fp = NULL; struct stat statbuf; char tblfile_path[MAXPGPATH] = {0}; size_t nread = 0; AuditIndexTableNew indextbl; int rc = snprintf_s(tblfile_path, MAXPGPATH, MAXPGPATH - 1, "%s/%s", audit_directory, audit_indextbl_file); - securec_check_intval(rc,,); + securec_check_intval(rc, , ); /* upgrade processing and sync old index table for old version */ if (pgaudit_find_indexfile()) { pgaudit_rewrite_indexfile(); @@ -2081,26 +2276,22 @@ static void pgaudit_read_indexfile(const char* audit_directory) * Brief : write the index table into file from memory. * Description : */ -static void pgaudit_update_indexfile(const char* mode, bool allow_errors) +static void pgaudit_update_indexfile(const char *mode, bool allow_errors) { - FILE* fp = NULL; + FILE *fp = NULL; char tblfile_path[MAXPGPATH] = {0}; size_t nwritten = 0; size_t count = 0; - int rc = snprintf_s(tblfile_path, - MAXPGPATH, - MAXPGPATH - 1, - "%s/%s", - g_instance.attr.attr_security.Audit_directory, - audit_indextbl_file); - securec_check_intval(rc,,); + int rc = snprintf_s(tblfile_path, MAXPGPATH, MAXPGPATH - 1, "%s/%s", g_instance.attr.attr_security.Audit_directory, + audit_indextbl_file); + securec_check_intval(rc, , ); /* Open the audit index table file to write out the current values. */ fp = AllocateFile(tblfile_path, mode); if (NULL == fp) { ereport(allow_errors ? LOG : FATAL, - (errcode_for_file_access(), errmsg("could not open audit index table file \"%s\": %m", tblfile_path))); + (errcode_for_file_access(), errmsg("could not open audit index table file \"%s\": %m", tblfile_path))); return; } /* check upgrade version to do audit upgrade processing */ @@ -2111,7 +2302,7 @@ static void pgaudit_update_indexfile(const char* mode, bool allow_errors) nwritten = fwrite(g_instance.audit_cxt.audit_indextbl, 1, count, fp); if (nwritten != count) ereport(allow_errors ? LOG : FATAL, - (errcode_for_file_access(), errmsg("could not write to audit index file: %m"))); + (errcode_for_file_access(), errmsg("could not write to audit index file: %m"))); } LWLockRelease(g_instance.audit_cxt.index_file_lock); ereport(DEBUG1, (errmsg("pgaudit_update_indexfile index size: %ld", (long)ftell(fp)))); @@ -2119,16 +2310,36 @@ static void pgaudit_update_indexfile(const char* mode, bool allow_errors) pgaudit_close_file(fp, tblfile_path); } +/* + * 功能:获取当前审计索引表中的最大文件号 + * + * 参数: + * old_thread_num - 旧的审计线程数 + * + * 返回值: + * 当前审计索引表中的最大文件号 + * + * 注意: + * 该函数遍历审计索引表中的当前索引项,找到最大的文件号,并返回它。 + * 文件号用于标识审计日志文件。 + */ static uint32 pgaudit_get_max_fnum(uint32 old_thread_num) { uint32 currrent_max_fnum = 0; + + // 遍历旧的审计线程数 for (uint32 i = 0; i < old_thread_num; ++i) { + // 获取当前索引项 AuditIndexItem *cur_item = g_instance.audit_cxt.audit_indextbl->data + g_instance.audit_cxt.audit_indextbl->curidx[i]; + + // 检查是否有更大的文件号 if (currrent_max_fnum < cur_item->filenum) { currrent_max_fnum = cur_item->filenum; } } + + // 返回当前审计索引表中的最大文件号 return currrent_max_fnum; } @@ -2140,12 +2351,8 @@ static bool pgaudit_find_indexfile(void) { struct stat statbuf; char tblfile_path[MAXPGPATH] = {0}; - int rc = snprintf_s(tblfile_path, - MAXPGPATH, - MAXPGPATH - 1, - "%s/%s", - g_instance.attr.attr_security.Audit_directory, - audit_indextbl_old_file); + int rc = snprintf_s(tblfile_path, MAXPGPATH, MAXPGPATH - 1, "%s/%s", g_instance.attr.attr_security.Audit_directory, + audit_indextbl_old_file); securec_check_ss(rc, "\0", "\0"); if (stat(tblfile_path, &statbuf) == 0) { return true; @@ -2164,13 +2371,9 @@ static void pgaudit_indexfile_upgrade(void) if (t_thrd.proc == NULL) { return; } - int rc = snprintf_s(tblfile_path, - MAXPGPATH, - MAXPGPATH - 1, - "%s/%s", - g_instance.attr.attr_security.Audit_directory, - audit_indextbl_old_file); - securec_check_intval(rc,,); + int rc = snprintf_s(tblfile_path, MAXPGPATH, MAXPGPATH - 1, "%s/%s", g_instance.attr.attr_security.Audit_directory, + audit_indextbl_old_file); + securec_check_intval(rc, , ); ereport(DEBUG1, (errmsg("audit upgrade processing index file upgrade enter"))); if (t_thrd.proc->workingVersionNum >= AUDIT_INDEX_TABLE_VERSION_NUM) { @@ -2198,24 +2401,25 @@ static void pgaudit_indexfile_upgrade(void) * Brief : pgaudit_indexfile_sync * Description : sync old and new index table file function */ -static void pgaudit_indexfile_sync(const char* mode, bool allow_errors) +static void pgaudit_indexfile_sync(const char *mode, bool allow_errors) { - FILE* fp = NULL; + FILE *fp = NULL; struct stat statbuf; char tblfile_path[MAXPGPATH] = {0}; size_t nwritten = 0; size_t count = 0; int rc = snprintf_s(tblfile_path, MAXPGPATH, MAXPGPATH - 1, "%s/%s", g_instance.attr.attr_security.Audit_directory, - audit_indextbl_old_file); - securec_check_intval(rc, ,); + audit_indextbl_old_file); + securec_check_intval(rc, , ); ereport(DEBUG1, (errmsg("audit upgrade processing index file sync enter"))); if (stat(tblfile_path, &statbuf) == 0) { /* old index table file is exist and sync file */ fp = AllocateFile(tblfile_path, mode); if (NULL == fp) { - ereport(allow_errors ? LOG : FATAL, (errcode_for_file_access(), - errmsg("could not open audit index table file \"%s\": %m", tblfile_path))); + ereport( + allow_errors ? LOG : FATAL, + (errcode_for_file_access(), errmsg("could not open audit index table file \"%s\": %m", tblfile_path))); return; } /* copy audit indextbl from new to old in memory */ @@ -2241,7 +2445,7 @@ static void pgaudit_indexfile_sync(const char* mode, bool allow_errors) nwritten = fwrite(g_instance.audit_cxt.audit_indextbl_old, 1, count, fp); if (nwritten != count) ereport(allow_errors ? LOG : FATAL, - (errcode_for_file_access(), errmsg("could not write to audit old index file: %m"))); + (errcode_for_file_access(), errmsg("could not write to audit old index file: %m"))); } LWLockRelease(g_instance.audit_cxt.index_file_lock); ereport(LOG, (errmsg("pgaudit_indexfile_sync index size: %ld", (long)ftell(fp)))); @@ -2255,23 +2459,19 @@ static void pgaudit_indexfile_sync(const char* mode, bool allow_errors) */ static void pgaudit_rewrite_indexfile(void) { - FILE* fp = NULL; + FILE *fp = NULL; char tblfile_path[MAXPGPATH] = {0}; size_t nread = 0; AuditIndexTable old_index_tbl; - int rc = snprintf_s(tblfile_path, - MAXPGPATH, - MAXPGPATH - 1, - "%s/%s", - g_instance.attr.attr_security.Audit_directory, - audit_indextbl_old_file); - securec_check_intval(rc,,); + int rc = snprintf_s(tblfile_path, MAXPGPATH, MAXPGPATH - 1, "%s/%s", g_instance.attr.attr_security.Audit_directory, + audit_indextbl_old_file); + securec_check_intval(rc, , ); /* open old index file and read audit index table */ fp = AllocateFile(tblfile_path, PG_BINARY_R); if (NULL == fp) { - ereport(LOG, - (errcode_for_file_access(), errmsg("could not open audit old index table file \"%s\": %m", tblfile_path))); + ereport(LOG, (errcode_for_file_access(), + errmsg("could not open audit old index table file \"%s\": %m", tblfile_path))); return; } ereport(LOG, (errmsg("audit upgrade processing rewrite enter"))); @@ -2292,8 +2492,8 @@ static void pgaudit_rewrite_indexfile(void) g_instance.audit_cxt.global_audit_context, (old_index_tbl.maxnum * sizeof(AuditIndexItem) + old_indextbl_header_size)); errorno = memcpy_s(g_instance.audit_cxt.audit_indextbl_old, - old_index_tbl.maxnum * sizeof(AuditIndexItem) + old_indextbl_header_size, - &old_index_tbl, old_indextbl_header_size); + old_index_tbl.maxnum * sizeof(AuditIndexItem) + old_indextbl_header_size, &old_index_tbl, + old_indextbl_header_size); securec_check(errorno, "\0", "\0"); /* rewrite old index table to new index table */ g_instance.audit_cxt.audit_indextbl = (AuditIndexTableNew *)MemoryContextAllocZero( @@ -2333,8 +2533,8 @@ static void pgaudit_indextbl_init_new(void) /* init new one when but not from index file when database init first time */ if (g_instance.audit_cxt.audit_indextbl == NULL) { ereport(LOG, (errmsg("pgaudit_indextbl_init_new first init"))); - g_instance.audit_cxt.audit_indextbl = - (AuditIndexTableNew *)MemoryContextAllocZero(g_instance.audit_cxt.global_audit_context, + g_instance.audit_cxt.audit_indextbl = (AuditIndexTableNew *)MemoryContextAllocZero( + g_instance.audit_cxt.global_audit_context, (u_sess->attr.attr_security.Audit_RemainThreshold + 1) * sizeof(AuditIndexItem) + indextbl_header_size); g_instance.audit_cxt.audit_indextbl->maxnum = u_sess->attr.attr_security.Audit_RemainThreshold + 1; g_instance.audit_cxt.audit_indextbl->count = 0; /* audit files count will be updated by auditfile_open */ @@ -2417,38 +2617,49 @@ static void pgaudit_indextbl_init_new(void) return; } +/* + * 功能:更新审计索引表的最大容量和数据 + * + * 注意: + * 该函数用于更新审计索引表的最大容量和数据。首先,它根据配置参数计算新的索引表的容量, + * 然后为新索引表分配内存,并将旧索引表中的数据复制到新索引表中。 + * 最后,函数释放旧索引表的内存,将新索引表设置为全局审计上下文的当前索引表。 + */ static void pgaudit_update_maxnum() { errno_t errorno = EOK; int thread_num = g_instance.attr.attr_security.audit_thread_num; + // 计算新索引表的数据长度 int new_indextbl_data_lenth = (u_sess->attr.attr_security.Audit_RemainThreshold + 1) * sizeof(AuditIndexItem); + + // 分配内存以存储新的审计索引表数据 AuditIndexTableNew *new_indextbl = (AuditIndexTableNew *)MemoryContextAllocZero( g_instance.audit_cxt.global_audit_context, new_indextbl_data_lenth + indextbl_header_size); - /* curidx and latest_idx should be updated later from old index table file */ + // 设置新索引表的属性 new_indextbl->begidx = 0; new_indextbl->maxnum = u_sess->attr.attr_security.Audit_RemainThreshold + 1; new_indextbl->last_audit_time = g_instance.audit_cxt.audit_indextbl->last_audit_time; new_indextbl->thread_num = g_instance.audit_cxt.audit_indextbl->thread_num; + // 如果旧索引表中有数据,则将其复制到新索引表中 if (g_instance.audit_cxt.audit_indextbl->count > 0) { AuditIndexItem *item = NULL; uint32 latest_idx = g_instance.audit_cxt.audit_indextbl->latest_idx; - uint32 last_idx = (latest_idx == 0) ? (g_instance.audit_cxt.audit_indextbl->maxnum - 1): (latest_idx - 1); + uint32 last_idx = (latest_idx == 0) ? (g_instance.audit_cxt.audit_indextbl->maxnum - 1) : (latest_idx - 1); uint32 index = g_instance.audit_cxt.audit_indextbl->begidx; uint32 pos = new_indextbl->begidx; + do { + // 复制旧索引表中的数据到新索引表 item = g_instance.audit_cxt.audit_indextbl->data + index; errorno = memcpy_s(new_indextbl->data + pos, (new_indextbl_data_lenth - (pos * sizeof(AuditIndexItem))), item, sizeof(AuditIndexItem)); securec_check(errorno, "\0", "\0"); new_indextbl->count++; - /* - * finished copy old index table file from range [begin, latest_idx) - * then update new index table file curidxes - */ + // 如果已复制到最后一个索引,则设置相关属性并退出循环 if (index == last_idx) { for (int i = 0; i < thread_num; ++i) { new_indextbl->curidx[i] = pos - thread_num + 1 + i; @@ -2461,6 +2672,8 @@ static void pgaudit_update_maxnum() index = (index + 1) % g_instance.audit_cxt.audit_indextbl->maxnum; } while (true); } + + // 释放旧索引表的内存,并将新索引表设置为当前索引表 pfree(g_instance.audit_cxt.audit_indextbl); g_instance.audit_cxt.audit_indextbl = new_indextbl; } @@ -2478,8 +2691,8 @@ static void pgaudit_reset_indexfile() uint32 old_maxnum = g_instance.audit_cxt.audit_indextbl->maxnum; if (old_maxnum > (uint32)u_sess->attr.attr_security.Audit_RemainThreshold + 1) { int rc = snprintf_s(t_thrd.audit.pgaudit_filepath, MAXPGPATH, MAXPGPATH - 1, "%s/%s", - g_instance.attr.attr_security.Audit_directory, audit_indextbl_file); - securec_check_intval(rc,,); + g_instance.attr.attr_security.Audit_directory, audit_indextbl_file); + securec_check_intval(rc, , ); if (unlink(t_thrd.audit.pgaudit_filepath) < 0) ereport(WARNING, (errmsg("could not remove audit index table file: %m"))); @@ -2502,12 +2715,12 @@ static void pgaudit_reset_indexfile() * Brief : get the specified string field. * Description : */ -static const char* pgaudit_string_field(AuditData* adata, int num) +static const char *pgaudit_string_field(AuditData *adata, int num) { int index = 0; uint32 size = 0; uint32 datalen = 0; - const char* field = NULL; + const char *field = NULL; if (adata == NULL) return NULL; @@ -2539,11 +2752,33 @@ static const char* pgaudit_string_field(AuditData* adata, int num) return field; } -static char* serialize_event_to_json(AuditData *adata, long long eventTime) +/* + * 功能:将 AuditData 结构体和事件时间序列化为 JSON 格式的字符串 + * + * 参数: + * adata:指向 AuditData 结构体的指针,包含要序列化的审计数据 + * eventTime:事件时间的时间戳 + * + * 返回值: + * 返回指向 JSON 格式字符串的指针(需要手动释放内存) + * + * 注意: + * 该函数用于将 AuditData 结构体和事件时间序列化为 JSON 格式的字符串。 + * 函数首先创建一个 AuditElasticEvent 结构体,然后调用 WRITE_JSON_START 宏开始 JSON 字符串的构建。 + * 接下来,函数将 AuditData 结构体中的各个字段以及事件时间填充到 AuditElasticEvent 结构体的相应字段中, + * 并使用 WRITE_JSON_STRING、WRITE_JSON_INT 等宏来将它们序列化到 JSON 字符串中。 + * 最后,函数通过 WRITE_JSON_END 宏结束 JSON 字符串的构建。 + * 返回的 JSON 格式字符串需要手动释放内存以避免内存泄漏。 + */ +static char *serialize_event_to_json(AuditData *adata, long long eventTime) { + // 创建 AuditElasticEvent 结构体 AuditElasticEvent event; + + // 开始构建 JSON 字符串 WRITE_JSON_START(AuditElasticEvent, &event); + // 填充 AuditElasticEvent 结构体的各个字段 event.aDataType = AuditTypeDesc(adata->type); WRITE_JSON_STRING(aDataType); event.aDataResult = AuditResultDesc(adata->result); @@ -2570,26 +2805,65 @@ static char* serialize_event_to_json(AuditData *adata, long long eventTime) WRITE_JSON_STRING(remotePortInfo); event.eventTime = eventTime; WRITE_JSON_INT(eventTime); + + // 结束 JSON 字符串的构建 WRITE_JSON_END(); + + // 返回 JSON 格式字符串的指针,需要手动释放内存 + return json_result; } struct AuditElasticIndex { - const char* _index; - const char* _type; + const char *_index; + const char *_type; }; -char* serialize_index_to_json(const char* audit_str) +/* + * 功能:将 AuditElasticIndex 结构体序列化为 JSON 格式的字符串 + * + * 参数: + * audit_str:用于填充 AuditElasticIndex 结构体的字符串 + * + * 返回值: + * 返回指向 JSON 格式字符串的指针(需要手动释放内存) + * + * 注意: + * 该函数用于将 AuditElasticIndex 结构体序列化为 JSON 格式的字符串。 + * AuditElasticIndex 结构体中包含两个字符串字段 _index 和 _type,这两个字段会被填充为传入的 audit_str 字符串。 + * 函数首先调用 WRITE_JSON_START 宏开始 JSON 字符串的构建,然后分别填充 _index 和 _type 字段, + * 最后通过 WRITE_JSON_END 宏结束 JSON 字符串的构建。 + * 返回的 JSON 格式字符串需要手动释放内存以避免内存泄漏。 + */ +char *serialize_index_to_json(const char *audit_str) { + // 创建 AuditElasticIndex 结构体 AuditElasticIndex index; + // 开始构建 JSON 字符串 WRITE_JSON_START(AuditElasticIndex, &index); + // 填充 _index 和 _type 字段 index._index = audit_str; WRITE_JSON_STRING(_index); index._type = audit_str; WRITE_JSON_STRING(_type); + // 结束 JSON 字符串的构建 WRITE_JSON_END(); + // 返回 JSON 格式字符串的指针,需要手动释放内存 + return json_result; } -void fix_json(char* json) +/* + * 功能:修复包含换行符的 JSON 字符串 + * + * 参数: + * json:需要修复的 JSON 字符串(传入参数会被修改) + * + * 注意: + * 该函数用于修复包含换行符的 JSON 字符串,将换行符替换为空格,以便更容易处理。 + * 函数会遍历字符串中的每个字符,如果字符为换行符 '\n',则将其替换为空格 ' '。 + * 如果字符为字符串终止符 '\0',则会提前结束循环,以避免处理字符串的后续部分。 + * 请注意,传入参数的内容会被修改,因此调用函数前请确保已备份原始数据或不需要保留原始数据。 + */ +void fix_json(char *json) { for (int i = 0; i < (int)strlen(json); i++) { if (json[i] == '\n') { @@ -2608,46 +2882,43 @@ static void pgaudit_query_file_for_elastic() int rc; char file_path[MAXPGPATH] = {0}; rc = snprintf_s(file_path, sizeof(file_path), sizeof(file_path) - 1, "%s" DIR_SEP "done", - g_instance.attr.attr_security.Audit_directory); + g_instance.attr.attr_security.Audit_directory); securec_check_ss(rc, "\0", "\0"); std::vector done_files; get_files_list(file_path, done_files, ".bin", MAX_QUEUE_SIZE); while (!done_files.empty()) { - for (const std::string& file_item : done_files) { + for (const std::string &file_item : done_files) { rc = snprintf_s(file_path, sizeof(file_path), sizeof(file_path) - 1, "%s" DIR_SEP "done" DIR_SEP "%s", - g_instance.attr.attr_security.Audit_directory, file_item.c_str()); + g_instance.attr.attr_security.Audit_directory, file_item.c_str()); securec_check_ss(rc, "\0", "\0"); /* Open the audit file to scan the audit record. */ - FILE* fp = AllocateFile(file_path, PG_BINARY_R); + FILE *fp = AllocateFile(file_path, PG_BINARY_R); if (fp == NULL) { ereport(WARNING, - (errcode_for_file_access(), errmsg("could not open audit file \"%s\": %m", file_path))); + (errcode_for_file_access(), errmsg("could not open audit file \"%s\": %m", file_path))); continue; } - FILE* bulkfile = fopen("audit_bulk.json", "w"); // , "a"); + FILE *bulkfile = fopen("audit_bulk.json", "w"); // , "a"); int event_count = 0; do { errno_t errorno = EOK; /* read the audit message header first */ - int nread = fread((char*)&header, sizeof(AuditMsgHdr), 1, fp); + int nread = fread((char *)&header, sizeof(AuditMsgHdr), 1, fp); if (nread == 0) { break; } - if (header.signature[0] != 'A' || - header.signature[1] != 'U' || - header.version != 0 || - header.fields != PGAUDIT_QUERY_COLS || - (header.size <= sizeof(AuditMsgHdr))) { - ereport(LOG, (errmsg("invalid data in audit file \"%s\"", file_path))); + if (header.signature[0] != 'A' || header.signature[1] != 'U' || header.version != 0 || + header.fields != PGAUDIT_QUERY_COLS || (header.size <= sizeof(AuditMsgHdr))) { + ereport(LOG, (errmsg("invalid data in audit file \"%s\"", file_path))); break; } /* read the whole audit record */ adata = (AuditData *)palloc(header.size); errorno = memcpy_s(adata, header.size, &header, sizeof(AuditMsgHdr)); securec_check(errorno, "\0", "\0"); - nread = fread((char*)adata + sizeof(AuditMsgHdr), header.size - sizeof(AuditMsgHdr), 1, fp); + nread = fread((char *)adata + sizeof(AuditMsgHdr), header.size - sizeof(AuditMsgHdr), 1, fp); if (nread != 1) { ereport(WARNING, (errcode_for_file_access(), errmsg("could not read audit file \"%s\": %m", file_path))); @@ -2655,11 +2926,11 @@ static void pgaudit_query_file_for_elastic() break; } - char* json = serialize_event_to_json(adata, adata->header.time); + char *json = serialize_event_to_json(adata, adata->header.time); ereport(DEBUG1, (errmsg("++++++++++++++++++++++++++ Write to JSON adata->type = %d(%s)", adata->type, - AuditTypeDesc(adata->type)))); + AuditTypeDesc(adata->type)))); fix_json(json); - const char* indexType = "audit"; + const char *indexType = "audit"; switch (adata->type) { case AUDIT_POLICY_EVENT: indexType = "audit_policy"; @@ -2673,7 +2944,7 @@ static void pgaudit_query_file_for_elastic() default: break; } - char* jsonIndex = serialize_index_to_json(indexType); + char *jsonIndex = serialize_index_to_json(indexType); fix_json(jsonIndex); /* And append a separator */ @@ -2700,13 +2971,13 @@ static void pgaudit_query_file_for_elastic() * --data-binary @audit_bulk.json curl -XPOST -k https://ip:9200/audit/events/_bulk?pretty -H * 'Content-type: application/json' --data-binary @audit_bulk.json */ - std::string url = ((std::string) g_instance.attr.attr_security.elastic_search_ip_addr) + + std::string url = ((std::string)g_instance.attr.attr_security.elastic_search_ip_addr) + ((std::string) ":9200/audit/events/_bulk?pretty"); m_curlUtils.http_post_file_request(url, "audit_bulk.json"); } } rc = snprintf_s(file_path, sizeof(file_path), sizeof(file_path) - 1, "%s" DIR_SEP "done", - g_instance.attr.attr_security.Audit_directory); + g_instance.attr.attr_security.Audit_directory); securec_check_ss(rc, "\0", "\0"); done_files.clear(); get_files_list(file_path, done_files, ".bin", MAX_QUEUE_SIZE); @@ -2716,12 +2987,10 @@ static void pgaudit_query_file_for_elastic() /* * Brief : scan the specified audit file into tuple * Description : Note we use old/new version to differ whether there is user_id field in the file. - * for expanding new field later, maybe we will depend on version id to implement + * for expanding new field later, maybe we will depend on version id to implement * backward compatibility but not bool variable */ -static void deserialization_to_tuple(Datum (&values)[PGAUDIT_QUERY_COLS], - AuditData *adata, - const AuditMsgHdr &header) +static void deserialization_to_tuple(Datum (&values)[PGAUDIT_QUERY_COLS], AuditData *adata, const AuditMsgHdr &header) { /* append timestamp info to data tuple */ int i = 0; @@ -2734,7 +3003,7 @@ static void deserialization_to_tuple(Datum (&values)[PGAUDIT_QUERY_COLS], * the older audit file do not have userid info, so let it to be null */ int index_field = 0; - const char* field = NULL; + const char *field = NULL; bool new_version = (header.fields == PGAUDIT_QUERY_COLS); field = new_version ? pgaudit_string_field(adata, index_field++) : NULL; values[i++] = CStringGetTextDatum(FILED_NULLABLE(field)); /* user id */ @@ -2760,75 +3029,96 @@ static void deserialization_to_tuple(Datum (&values)[PGAUDIT_QUERY_COLS], Assert(i == PGAUDIT_QUERY_COLS); } +/* + * 功能:从审计文件中查询数据并将其放入元组存储器 + * + * 参数: + * state:Tuplestorestate 元组存储器的状态 + * tdesc:TupleDesc 元组的描述符 + * fnum:审计文件的编号 + * begtime:查询的开始时间 + * endtime:查询的结束时间 + * audit_directory:审计文件的存储目录 + * + * 注意: + * 该函数从审计文件中读取数据,将满足时间范围条件的数据放入元组存储器中。 + * 首先,函数构建审计文件的完整路径,并尝试打开文件。 + * 如果文件无法打开,则记录错误消息并返回。 + * 然后,函数循环读取文件中的每个审计消息,检查消息的时间戳是否在指定的时间范围内, + * 如果满足条件,则将消息的数据反序列化为元组并添加到元组存储器中。 + * 最后,函数关闭文件并释放相关内存。 + */ static void pgaudit_query_file(Tuplestorestate *state, TupleDesc tdesc, uint32 fnum, TimestampTz begtime, TimestampTz endtime, const char *audit_directory) { - FILE* fp = NULL; + FILE *fp = NULL; size_t nread = 0; TimestampTz datetime; AuditMsgHdr header; - AuditData* adata = NULL; + AuditData *adata = NULL; + // 检查输入参数是否为NULL if (state == NULL || tdesc == NULL) return; + // 构建审计文件的完整路径并尝试打开文件 int rcs = snprintf_s(t_thrd.audit.pgaudit_filepath, MAXPGPATH, MAXPGPATH - 1, pgaudit_filename, audit_directory, fnum); - securec_check_intval(rcs,,); - /* Open the audit file to scan the audit record. */ + securec_check_intval(rcs, , ); fp = AllocateFile(t_thrd.audit.pgaudit_filepath, PG_BINARY_R); if (fp == NULL) { - ereport(LOG, - (errcode_for_file_access(), errmsg("could not open audit file \"%s\": %m", t_thrd.audit.pgaudit_filepath))); + ereport(LOG, (errcode_for_file_access(), + errmsg("could not open audit file \"%s\": %m", t_thrd.audit.pgaudit_filepath))); return; } + // 循环读取审计文件中的每个消息 do { Datum values[PGAUDIT_QUERY_COLS] = {0}; bool nulls[PGAUDIT_QUERY_COLS] = {0}; errno_t errorno = EOK; - /* - * two scenarios tell that the audit file corrupt - * 1. fail to parse the header length - * 2. header encoding is not valid - */ + + // 检查文件是否已经结束 if (fgetc(fp) == EOF) { break; } (void)fseek(fp, -1, SEEK_CUR); + + // 读取消息头 size_t header_available = fread(&header, sizeof(AuditMsgHdr), 1, fp); if (header_available != 1 || !pgaudit_valid_header(&header)) { ereport(LOG, (errmsg("invalid data in audit file \"%s\"", t_thrd.audit.pgaudit_filepath))); - /* label the currupt file num, then it may be reinit in audit thread but not here. */ pgaudit_mark_corrupt_info(fnum); break; } - /* read the whole audit record */ - adata = (AuditData*)palloc(header.size); + // 分配内存并读取完整消息 + adata = (AuditData *)palloc(header.size); errorno = memcpy_s(adata, header.size, &header, sizeof(AuditMsgHdr)); securec_check(errorno, "\0", "\0"); - nread = fread((char*)adata + sizeof(AuditMsgHdr), header.size - sizeof(AuditMsgHdr), 1, fp); + nread = fread((char *)adata + sizeof(AuditMsgHdr), header.size - sizeof(AuditMsgHdr), 1, fp); if (nread != 1) { - ereport(LOG, - (errcode_for_file_access(), - errmsg("could not read audit file \"%s\": %m", t_thrd.audit.pgaudit_filepath))); - /* label the currupt file num, then it may be reinit in audit thread but not here. */ + ereport(LOG, (errcode_for_file_access(), + errmsg("could not read audit file \"%s\": %m", t_thrd.audit.pgaudit_filepath))); pgaudit_mark_corrupt_info(fnum); pfree(adata); break; } - /* filt and assemble audit info into tuplestore */ + // 将时间戳转换为时间类型 datetime = time_t_to_timestamptz(adata->header.time); + + // 如果消息的时间在指定的时间范围内,并且消息标志为AUDIT_TUPLE_NORMAL,则将消息反序列化为元组并添加到元组存储器中 if (datetime >= begtime && datetime < endtime && header.flags == AUDIT_TUPLE_NORMAL) { deserialization_to_tuple(values, adata, header); tuplestore_putvalues(state, tdesc, values, nulls); } + // 释放内存 pfree(adata); } while (true); + // 关闭文件并释放内存 pgaudit_close_file(fp, t_thrd.audit.pgaudit_filepath); } @@ -2843,19 +3133,15 @@ static void pgaudit_delete_file(uint32 fnum, TimestampTz begtime, TimestampTz en TimestampTz datetime; AuditMsgHdr header; - int rc = snprintf_s(t_thrd.audit.pgaudit_filepath, - MAXPGPATH, - MAXPGPATH - 1, - pgaudit_filename, - g_instance.attr.attr_security.Audit_directory, - fnum); - securec_check_intval(rc,,); + int rc = snprintf_s(t_thrd.audit.pgaudit_filepath, MAXPGPATH, MAXPGPATH - 1, pgaudit_filename, + g_instance.attr.attr_security.Audit_directory, fnum); + securec_check_intval(rc, , ); /* Open the audit file to scan the audit record. */ fd = open(t_thrd.audit.pgaudit_filepath, O_RDWR, pgaudit_filemode); if (fd < 0) { - ereport(LOG, - (errcode_for_file_access(), errmsg("could not open audit file \"%s\": %m", t_thrd.audit.pgaudit_filepath))); + ereport(LOG, (errcode_for_file_access(), + errmsg("could not open audit file \"%s\": %m", t_thrd.audit.pgaudit_filepath))); return; } @@ -2865,11 +3151,8 @@ static void pgaudit_delete_file(uint32 fnum, TimestampTz begtime, TimestampTz en if (nread <= 0) break; - if (header.signature[0] != 'A' || - header.signature[1] != 'U' || - header.version != 0 || - !(header.fields == (PGAUDIT_QUERY_COLS - 1) || - header.fields == PGAUDIT_QUERY_COLS)) { + if (header.signature[0] != 'A' || header.signature[1] != 'U' || header.version != 0 || + !(header.fields == (PGAUDIT_QUERY_COLS - 1) || header.fields == PGAUDIT_QUERY_COLS)) { /* make sure we are compatible with the older version audit file */ ereport(LOG, (errmsg("invalid data in audit file \"%s\"", t_thrd.audit.pgaudit_filepath))); break; @@ -2903,7 +3186,7 @@ static bool pgaudit_check_system(TimestampTz begtime, TimestampTz endtime, uint3 bool satisfied = false; TimestampTz curr_filetime = 0; TimestampTz next_filetime = 0; - AuditIndexItem* item = t_thrd.audit.audit_indextbl->data + index; + AuditIndexItem *item = t_thrd.audit.audit_indextbl->data + index; uint32 earliest_idx = t_thrd.audit.audit_indextbl->latest_idx - g_instance.audit_cxt.thread_num; if (item->ctime > 0) { @@ -2976,14 +3259,14 @@ static void pgaudit_query_valid_check(const ReturnSetInfo *rsinfo, FunctionCallI */ Datum pg_query_audit(PG_FUNCTION_ARGS) { - ReturnSetInfo* rsinfo = (ReturnSetInfo*)fcinfo->resultinfo; + ReturnSetInfo *rsinfo = (ReturnSetInfo *)fcinfo->resultinfo; TupleDesc tupdesc = NULL; - Tuplestorestate* tupstore = NULL; + Tuplestorestate *tupstore = NULL; MemoryContext per_query_ctx = NULL; MemoryContext oldcontext = NULL; TimestampTz begtime = PG_GETARG_TIMESTAMPTZ(0); TimestampTz endtime = PG_GETARG_TIMESTAMPTZ(1); - char* audit_dir = NULL; + char *audit_dir = NULL; pgaudit_query_valid_check(rsinfo, fcinfo, tupdesc); @@ -3027,7 +3310,7 @@ Datum pg_query_audit(PG_FUNCTION_ARGS) bool satisfied = false; uint32 index = 0; uint32 fnum = 0; - AuditIndexItem* item = NULL; + AuditIndexItem *item = NULL; index = t_thrd.audit.audit_indextbl->begidx; do { @@ -3073,7 +3356,7 @@ Datum pg_delete_audit(PG_FUNCTION_ARGS) ereport(ERROR, (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("permission denied to delete audit"))); } - /* + /* * load the index audit table from global index audit table instance * then use the local thread one when iterate all audit files */ @@ -3095,7 +3378,7 @@ Datum pg_delete_audit(PG_FUNCTION_ARGS) bool satisfied = false; uint32 index; uint32 fnum; - AuditIndexItem* item = NULL; + AuditIndexItem *item = NULL; index = t_thrd.audit.audit_indextbl->begidx; do { @@ -3121,8 +3404,8 @@ Datum pg_delete_audit(PG_FUNCTION_ARGS) PG_RETURN_VOID(); } -/* - * if use_elastic_search is set on, user should make sure connection is ok, +/* + * if use_elastic_search is set on, user should make sure connection is ok, * or process will not start successfully */ static void elasic_search_connection_test() @@ -3130,12 +3413,12 @@ static void elasic_search_connection_test() if (!g_instance.attr.attr_security.use_elastic_search) { return; } - std::string url = ((std::string) g_instance.attr.attr_security.elastic_search_ip_addr) + + std::string url = ((std::string)g_instance.attr.attr_security.elastic_search_ip_addr) + ((std::string) ":9200/audit/events/_bulk?pretty"); (void)m_curlUtils.http_post_file_request(url, "", true); } -/* +/* * check and reinit the audit files * 1. whether the audit file is exist * 2. recognize the corrupt file @@ -3181,7 +3464,7 @@ static void CheckAuditFile(void) int fd = open(t_thrd.audit.pgaudit_filepath, O_RDWR | O_TRUNC, pgaudit_filemode); if (fd < 0) { ereport(ERROR, (errcode_for_file_access(), - errmsg("could not truncate audit file \"%s\": %m", t_thrd.audit.pgaudit_filepath))); + errmsg("could not truncate audit file \"%s\": %m", t_thrd.audit.pgaudit_filepath))); } else { close(fd); } @@ -3202,11 +3485,11 @@ static void CheckAuditFile(void) auditfile_init(true); } -static bool pgaudit_valid_header(const AuditMsgHdr* header) +static bool pgaudit_valid_header(const AuditMsgHdr *header) { return !((header->signature[0]) != 'A' || header->signature[1] != 'U' || header->version != 0 || - !(header->fields == (PGAUDIT_QUERY_COLS - 1) || header->fields == PGAUDIT_QUERY_COLS) || - (header->size <= sizeof(AuditMsgHdr))); + !(header->fields == (PGAUDIT_QUERY_COLS - 1) || header->fields == PGAUDIT_QUERY_COLS) || + (header->size <= sizeof(AuditMsgHdr))); } /* @@ -3244,7 +3527,7 @@ static void pgaudit_mark_corrupt_info(uint32 fnum) } ereport(WARNING, (errmsg("audit file num %d is corrupted.", fnum))); - + /* * if any other thread have updated the audit_fnum or fnum is older one, do nothing but break here */ @@ -3357,7 +3640,7 @@ void audit_process_cxt_init() /* init audit path */ char Audit_directory_Done[MAXPGPATH] = {0}; int rc = snprintf_s(Audit_directory_Done, sizeof(Audit_directory_Done), sizeof(Audit_directory_Done) - 1, "%s/done", - g_instance.attr.attr_security.Audit_directory); + g_instance.attr.attr_security.Audit_directory); securec_check_ss(rc, "\0", "\0"); (void)pg_mkdir_p(g_instance.attr.attr_security.Audit_directory, S_IRWXU); (void)pg_mkdir_p(Audit_directory_Done, S_IRWXU); @@ -3463,7 +3746,7 @@ void pgaudit_switch_next_auditfile() LWLockRelease(g_instance.audit_cxt.index_file_lock); ereport(DEBUG1, (errmsg("pgaudit_switch_next_auditfile new fnum :%d cur pgaudit_totalspace: %ld MB", new_fnum, - g_instance.audit_cxt.pgaudit_totalspace))); + g_instance.audit_cxt.pgaudit_totalspace))); } /* diff --git a/src/gausskernel/process/postmaster/pgstat.cpp b/src/gausskernel/process/postmaster/pgstat.cpp index 79d69bdb9..a0151eaf5 100644 --- a/src/gausskernel/process/postmaster/pgstat.cpp +++ b/src/gausskernel/process/postmaster/pgstat.cpp @@ -150,7 +150,7 @@ #define ThreadSiblingFile "/sys/devices/system/cpu/cpu0/topology/thread_siblings" #define CoreSiblingFile "/sys/devices/system/cpu/cpu0/topology/core_siblings" -extern void WLMGetCPUDataIndicator(PgBackendStatus*, WLMDataIndicator*); +extern void WLMGetCPUDataIndicator(PgBackendStatus *, WLMDataIndicator *); /* * Structures in which backends store per-table info that's waiting to be @@ -166,7 +166,7 @@ extern void WLMGetCPUDataIndicator(PgBackendStatus*, WLMDataIndicator*); #define TABSTAT_QUANTUM 100 /* we alloc this many at a time */ typedef struct TabStatusArray { - struct TabStatusArray* tsa_next; /* link to next array, if any */ + struct TabStatusArray *tsa_next; /* link to next array, if any */ int tsa_used; /* # entries currently used */ PgStat_TableStatus tsa_entries[TABSTAT_QUANTUM]; /* per-table data */ } TabStatusArray; @@ -174,7 +174,7 @@ typedef struct TabStatusArray { /* pgStatTabHash entry */ typedef struct TabStatHashEntry { PgStat_StatTabKey t_key; - PgStat_TableStatus* tsa_entry; + PgStat_TableStatus *tsa_entry; } TabStatHashEntry; /* @@ -186,15 +186,15 @@ typedef struct TabStatHashEntry { */ typedef struct PgStat_SubXactStatus { int nest_level; /* subtransaction nest level */ - struct PgStat_SubXactStatus* prev; /* higher-level subxact if any */ - PgStat_TableXactStatus* first; /* head of list for this subxact */ + struct PgStat_SubXactStatus *prev; /* higher-level subxact if any */ + PgStat_TableXactStatus *first; /* head of list for this subxact */ } PgStat_SubXactStatus; /* Record that's written to 2PC state file when pgstat state is persisted */ typedef struct TwoPhasePgStatRecord { - PgStat_Counter tuples_inserted; /* tuples inserted in xact */ - PgStat_Counter tuples_updated; /* tuples updated in xact */ - PgStat_Counter tuples_deleted; /* tuples deleted in xact */ + PgStat_Counter tuples_inserted; /* tuples inserted in xact */ + PgStat_Counter tuples_updated; /* tuples updated in xact */ + PgStat_Counter tuples_deleted; /* tuples deleted in xact */ PgStat_Counter tuples_inplace_updated; PgStat_Counter inserted_pre_trunc; /* tuples inserted prior to truncate */ PgStat_Counter updated_pre_trunc; /* tuples updated prior to truncate */ @@ -206,9 +206,9 @@ typedef struct TwoPhasePgStatRecord { PgStat_Counter tuples_deleted_accum; PgStat_Counter tuples_inplace_updated_accum; - Oid t_id; /* table's OID */ - bool t_shared; /* is it a shared catalog? */ - bool t_truncated; /* was the relation truncated? */ + Oid t_id; /* table's OID */ + bool t_shared; /* is it a shared catalog? */ + bool t_truncated; /* was the relation truncated? */ /* * if t_id is a parition oid , then t_statFlag is the corresponding * partitioned table oid; if t_id is a non-parition oid, then t_statFlag is InvlaidOId @@ -224,124 +224,73 @@ const OSRunInfoDesc osStatDescArrayOrg[TOTAL_OS_RUN_INFO_TYPES] = { /* cpu numbers */ {Int32GetNumberDatum, "NUM_CPUS", false, false, "Number of CPUs or processors available"}, - {Int32GetNumberDatum, - "NUM_CPU_CORES", - false, - false, - "Number of CPU cores available (includes subcores of multicore CPUs as well as single-core CPUs)"}, + {Int32GetNumberDatum, "NUM_CPU_CORES", false, false, + "Number of CPU cores available (includes subcores of multicore CPUs as well as single-core CPUs)"}, - {Int32GetNumberDatum, - "NUM_CPU_SOCKETS", - false, - false, - "Number of CPU sockets available (represents an absolute count of CPU chips on the system, regardless of " - "multithreading or multi-core architectures)"}, + {Int32GetNumberDatum, "NUM_CPU_SOCKETS", false, false, + "Number of CPU sockets available (represents an absolute count of CPU chips on the system, regardless of " + "multithreading or multi-core architectures)"}, /* cpu times */ - {Int64GetNumberDatum, - "IDLE_TIME", - true, - false, - "Number of hundredths of a second that a processor has been idle, totalled over all processors"}, + {Int64GetNumberDatum, "IDLE_TIME", true, false, + "Number of hundredths of a second that a processor has been idle, totalled over all processors"}, - {Int64GetNumberDatum, - "BUSY_TIME", - true, - false, - "Number of hundredths of a second that a processor has been busy executing user or kernel code, totalled over " - "all processors"}, + {Int64GetNumberDatum, "BUSY_TIME", true, false, + "Number of hundredths of a second that a processor has been busy executing user or kernel code, totalled over " + "all processors"}, - {Int64GetNumberDatum, - "USER_TIME", - true, - false, - "Number of hundredths of a second that a processor has been busy executing user code, totalled over all " - "processors"}, + {Int64GetNumberDatum, "USER_TIME", true, false, + "Number of hundredths of a second that a processor has been busy executing user code, totalled over all " + "processors"}, - {Int64GetNumberDatum, - "SYS_TIME", - true, - false, - "Number of hundredths of a second that a processor has been busy executing kernel code, totalled over all " - "processors"}, + {Int64GetNumberDatum, "SYS_TIME", true, false, + "Number of hundredths of a second that a processor has been busy executing kernel code, totalled over all " + "processors"}, - {Int64GetNumberDatum, - "IOWAIT_TIME", - true, - false, - "Number of hundredths of a second that a processor has been waiting for I/O to complete, totalled over all " - "processors"}, + {Int64GetNumberDatum, "IOWAIT_TIME", true, false, + "Number of hundredths of a second that a processor has been waiting for I/O to complete, totalled over all " + "processors"}, - {Int64GetNumberDatum, - "NICE_TIME", - true, - false, - "Number of hundredths of a second that a processor has been busy executing low-priority user code, totalled " - "over all processors"}, + {Int64GetNumberDatum, "NICE_TIME", true, false, + "Number of hundredths of a second that a processor has been busy executing low-priority user code, totalled " + "over all processors"}, /* avg cpu times */ - {Int64GetNumberDatum, - "AVG_IDLE_TIME", - true, - false, - "Number of hundredths of a second that a processor has been idle, averaged over all processors"}, + {Int64GetNumberDatum, "AVG_IDLE_TIME", true, false, + "Number of hundredths of a second that a processor has been idle, averaged over all processors"}, - {Int64GetNumberDatum, - "AVG_BUSY_TIME", - true, - false, - "Number of hundredths of a second that a processor has been busy executing user or kernel code, averaged over " - "all processors"}, + {Int64GetNumberDatum, "AVG_BUSY_TIME", true, false, + "Number of hundredths of a second that a processor has been busy executing user or kernel code, averaged over " + "all processors"}, - {Int64GetNumberDatum, - "AVG_USER_TIME", - true, - false, - "Number of hundredths of a second that a processor has been busy executing user code, averaged over all " - "processors"}, + {Int64GetNumberDatum, "AVG_USER_TIME", true, false, + "Number of hundredths of a second that a processor has been busy executing user code, averaged over all " + "processors"}, - {Int64GetNumberDatum, - "AVG_SYS_TIME", - true, - false, - "Number of hundredths of a second that a processor has been busy executing kernel code, averaged over all " - "processors"}, + {Int64GetNumberDatum, "AVG_SYS_TIME", true, false, + "Number of hundredths of a second that a processor has been busy executing kernel code, averaged over all " + "processors"}, - {Int64GetNumberDatum, - "AVG_IOWAIT_TIME", - true, - false, - "Number of hundredths of a second that a processor has been waiting for I/O to complete, averaged over all " - "processors"}, + {Int64GetNumberDatum, "AVG_IOWAIT_TIME", true, false, + "Number of hundredths of a second that a processor has been waiting for I/O to complete, averaged over all " + "processors"}, - {Int64GetNumberDatum, - "AVG_NICE_TIME", - true, - false, - "Number of hundredths of a second that a processor has been busy executing low-priority user code, averaged " - "over all processors"}, + {Int64GetNumberDatum, "AVG_NICE_TIME", true, false, + "Number of hundredths of a second that a processor has been busy executing low-priority user code, averaged " + "over all processors"}, /* virtual memory page in/out data */ - {Int64GetNumberDatum, - "VM_PAGE_IN_BYTES", - true, - false, - "Total number of bytes of data that have been paged in due to virtual memory paging"}, + {Int64GetNumberDatum, "VM_PAGE_IN_BYTES", true, false, + "Total number of bytes of data that have been paged in due to virtual memory paging"}, - {Int64GetNumberDatum, - "VM_PAGE_OUT_BYTES", - true, - false, - "Total number of bytes of data that have been paged out due to virtual memory paging"}, + {Int64GetNumberDatum, "VM_PAGE_OUT_BYTES", true, false, + "Total number of bytes of data that have been paged out due to virtual memory paging"}, /* os run load */ - {Float8GetNumberDatum, - "LOAD", - false, - false, - "Current number of processes that are either running or in the ready state, waiting to be selected by the " - "operating-system scheduler to run. On many platforms, this statistic reflects the average load over the past " - "minute."}, + {Float8GetNumberDatum, "LOAD", false, false, + "Current number of processes that are either running or in the ready state, waiting to be selected by the " + "operating-system scheduler to run. On many platforms, this statistic reflects the average load over the past " + "minute."}, /* physical memory size */ {Int64GetNumberDatum, "PHYSICAL_MEMORY_BYTES", false, false, "Total number of bytes of physical memory"}}; @@ -354,57 +303,57 @@ static void pgstat_exit(SIGNAL_ARGS); static void pgstat_beshutdown_hook(int code, Datum arg); static void pgstat_sighup_handler(SIGNAL_ARGS); -static PgStat_StatDBEntry* pgstat_get_db_entry(Oid databaseid, bool create); -static PgStat_StatTabEntry* pgstat_get_tab_entry( - PgStat_StatDBEntry* dbentry, Oid tableoid, bool create, uint32 statFlag); +static PgStat_StatDBEntry *pgstat_get_db_entry(Oid databaseid, bool create); +static PgStat_StatTabEntry *pgstat_get_tab_entry(PgStat_StatDBEntry *dbentry, Oid tableoid, bool create, + uint32 statFlag); static void pgstat_write_statsfile(bool permanent); -static HTAB* pgstat_read_statsfile(Oid onlydb, bool permanent); +static HTAB *pgstat_read_statsfile(Oid onlydb, bool permanent); static void backend_read_statsfile(void); -static void pgstat_send_tabstat(PgStat_MsgTabstat* tsmsg); +static void pgstat_send_tabstat(PgStat_MsgTabstat *tsmsg); static void pgstat_send_funcstats(void); -static HTAB* pgstat_collect_oids(Oid catalogid); -static HTAB* pgstat_collect_tabkeys(void); -static PgStat_TableStatus* get_tabstat_entry(Oid rel_id, bool isshared, uint32 statFlag); +static HTAB *pgstat_collect_oids(Oid catalogid); +static HTAB *pgstat_collect_tabkeys(void); +static PgStat_TableStatus *get_tabstat_entry(Oid rel_id, bool isshared, uint32 statFlag); static void pgstat_collect_thread_status_setup_memcxt(void); static void pgstat_collect_thread_status_clear_resource(void); -const char* pgstat_get_wait_io(WaitEventIO w); +const char *pgstat_get_wait_io(WaitEventIO w); -static void pgstat_setheader(PgStat_MsgHdr* hdr, StatMsgType mtype); -void pgstat_send(void* msg, int len); +static void pgstat_setheader(PgStat_MsgHdr *hdr, StatMsgType mtype); +void pgstat_send(void *msg, int len); -static void pgstat_recv_inquiry(PgStat_MsgInquiry* msg, int len); -static void pgstat_recv_tabstat(PgStat_MsgTabstat* msg, int len); -static void pgstat_recv_tabpurge(PgStat_MsgTabpurge* msg, int len); -static void pgstat_recv_dropdb(PgStat_MsgDropdb* msg, int len); -static void pgstat_recv_resetcounter(PgStat_MsgResetcounter* msg, int len); -static void pgstat_recv_resetsharedcounter(PgStat_MsgResetsharedcounter* msg, int len); -static void pgstat_recv_resetsinglecounter(PgStat_MsgResetsinglecounter* msg, int len); -static void pgstat_recv_autovac(PgStat_MsgAutovacStart* msg, int len); -static void pgstat_recv_vacuum(PgStat_MsgVacuum* msg, int len); -static void pgstat_recv_data_changed(PgStat_MsgDataChanged* msg, int len); -static void pgstat_recv_truncate(PgStat_MsgTruncate* msg, int len); -static void pgstat_recv_analyze(PgStat_MsgAnalyze* msg, int len); -static void pgstat_recv_bgwriter(PgStat_MsgBgWriter* msg, int len); -static void pgstat_recv_funcstat(PgStat_MsgFuncstat* msg, int len); -static void pgstat_recv_funcpurge(PgStat_MsgFuncpurge* msg, int len); -static void pgstat_recv_recoveryconflict(const PgStat_MsgRecoveryConflict* msg); -static void pgstat_recv_deadlock(const PgStat_MsgDeadlock* msg); -static void pgstat_recv_tempfile(PgStat_MsgTempFile* msg, int len); -static void pgstat_recv_memReserved(const PgStat_MsgMemReserved* msg); -static void pgstat_recv_autovac_stat(PgStat_MsgAutovacStat* msg, int len); -static void PgstatRecvPrunestat(PgStat_MsgPrune* msg, int len); +static void pgstat_recv_inquiry(PgStat_MsgInquiry *msg, int len); +static void pgstat_recv_tabstat(PgStat_MsgTabstat *msg, int len); +static void pgstat_recv_tabpurge(PgStat_MsgTabpurge *msg, int len); +static void pgstat_recv_dropdb(PgStat_MsgDropdb *msg, int len); +static void pgstat_recv_resetcounter(PgStat_MsgResetcounter *msg, int len); +static void pgstat_recv_resetsharedcounter(PgStat_MsgResetsharedcounter *msg, int len); +static void pgstat_recv_resetsinglecounter(PgStat_MsgResetsinglecounter *msg, int len); +static void pgstat_recv_autovac(PgStat_MsgAutovacStart *msg, int len); +static void pgstat_recv_vacuum(PgStat_MsgVacuum *msg, int len); +static void pgstat_recv_data_changed(PgStat_MsgDataChanged *msg, int len); +static void pgstat_recv_truncate(PgStat_MsgTruncate *msg, int len); +static void pgstat_recv_analyze(PgStat_MsgAnalyze *msg, int len); +static void pgstat_recv_bgwriter(PgStat_MsgBgWriter *msg, int len); +static void pgstat_recv_funcstat(PgStat_MsgFuncstat *msg, int len); +static void pgstat_recv_funcpurge(PgStat_MsgFuncpurge *msg, int len); +static void pgstat_recv_recoveryconflict(const PgStat_MsgRecoveryConflict *msg); +static void pgstat_recv_deadlock(const PgStat_MsgDeadlock *msg); +static void pgstat_recv_tempfile(PgStat_MsgTempFile *msg, int len); +static void pgstat_recv_memReserved(const PgStat_MsgMemReserved *msg); +static void pgstat_recv_autovac_stat(PgStat_MsgAutovacStat *msg, int len); +static void PgstatRecvPrunestat(PgStat_MsgPrune *msg, int len); static void pgstat_send_badblock_stat(void); -static void pgstat_recv_badblock_stat(PgStat_MsgBadBlock* msg, int len); +static void pgstat_recv_badblock_stat(PgStat_MsgBadBlock *msg, int len); static bool checkSysFileSystem(void); static bool checkLogicalCpu(uint32 cpuNum); -static uint32 parseSiblingFile(const char* path); -static void pgstat_recv_filestat(PgStat_MsgFile* msg, int len); -static void prepare_calculate(SqlRTInfoArray* sql_rt_info, int* counter); -static void pgstat_recv_sql_responstime(PgStat_SqlRT* msg, int len); +static uint32 parseSiblingFile(const char *path); +static void pgstat_recv_filestat(PgStat_MsgFile *msg, int len); +static void prepare_calculate(SqlRTInfoArray *sql_rt_info, int *counter); +static void pgstat_recv_sql_responstime(PgStat_SqlRT *msg, int len); void initGlobalBadBlockStat(); @@ -417,7 +366,7 @@ static void AttachMySessionTimeEntry(void); static void AttachMySessionMemoryEntry(void); static void endMySessionTimeEntry(int code, Datum arg); -static void DetachMySessionTimeEntry(volatile SessionTimeEntry* pEntry); +static void DetachMySessionTimeEntry(volatile SessionTimeEntry *pEntry); /* ------------------------------------------------------------ * Public functions called from postmaster follow @@ -491,8 +440,8 @@ void pgstat_init(void) #ifdef F_SETFD if (fcntl(g_instance.stat_cxt.pgStatSock, F_SETFD, FD_CLOEXEC) == -1) { - ereport(LOG, - (errcode_for_socket_access(), errmsg("setsockopt(FD_CLOEXEC) failed for statistics collector: %m"))); + ereport(LOG, (errcode_for_socket_access(), + errmsg("setsockopt(FD_CLOEXEC) failed for statistics collector: %m"))); closesocket(g_instance.stat_cxt.pgStatSock); g_instance.stat_cxt.pgStatSock = PGINVALID_SOCKET; continue; @@ -511,9 +460,9 @@ void pgstat_init(void) } alen = sizeof(pgStatAddr); - if (getsockname(g_instance.stat_cxt.pgStatSock, (struct sockaddr*)&pgStatAddr, &alen) < 0) { - ereport(LOG, - (errcode_for_socket_access(), errmsg("could not get address of socket for statistics collector: %m"))); + if (getsockname(g_instance.stat_cxt.pgStatSock, (struct sockaddr *)&pgStatAddr, &alen) < 0) { + ereport(LOG, (errcode_for_socket_access(), + errmsg("could not get address of socket for statistics collector: %m"))); closesocket(g_instance.stat_cxt.pgStatSock); g_instance.stat_cxt.pgStatSock = PGINVALID_SOCKET; continue; @@ -525,9 +474,9 @@ void pgstat_init(void) * provides a kernel-level check that only packets from this same * address will be received. */ - if (connect(g_instance.stat_cxt.pgStatSock, (struct sockaddr*)&pgStatAddr, alen) < 0) { - ereport( - LOG, (errcode_for_socket_access(), errmsg("could not connect socket for statistics collector: %m"))); + if (connect(g_instance.stat_cxt.pgStatSock, (struct sockaddr *)&pgStatAddr, alen) < 0) { + ereport(LOG, + (errcode_for_socket_access(), errmsg("could not connect socket for statistics collector: %m"))); closesocket(g_instance.stat_cxt.pgStatSock); g_instance.stat_cxt.pgStatSock = PGINVALID_SOCKET; continue; @@ -546,9 +495,8 @@ void pgstat_init(void) if (send(g_instance.stat_cxt.pgStatSock, &test_byte, 1, 0) <= 0) { if (errno == EINTR) goto retry1; /* if interrupted, just retry */ - ereport(LOG, - (errcode_for_socket_access(), - errmsg("could not send test message on socket for statistics collector: %m"))); + ereport(LOG, (errcode_for_socket_access(), + errmsg("could not send test message on socket for statistics collector: %m"))); closesocket(g_instance.stat_cxt.pgStatSock); g_instance.stat_cxt.pgStatSock = PGINVALID_SOCKET; continue; @@ -564,7 +512,7 @@ void pgstat_init(void) FD_ZERO(&rset); if (g_instance.stat_cxt.pgStatSock + 1 > FD_SETSIZE) { ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("fd + 1 cannot be greater than FD_SETSIZE"))); + (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("fd + 1 cannot be greater than FD_SETSIZE"))); } FD_SET(g_instance.stat_cxt.pgStatSock, &rset); @@ -587,9 +535,8 @@ void pgstat_init(void) * * errno will not be set meaningfully here, so don't use it. */ - ereport(LOG, - (errcode(ERRCODE_CONNECTION_FAILURE), - errmsg("test message did not get through on socket for statistics collector"))); + ereport(LOG, (errcode(ERRCODE_CONNECTION_FAILURE), + errmsg("test message did not get through on socket for statistics collector"))); closesocket(g_instance.stat_cxt.pgStatSock); g_instance.stat_cxt.pgStatSock = PGINVALID_SOCKET; continue; @@ -602,9 +549,8 @@ void pgstat_init(void) if (recv(g_instance.stat_cxt.pgStatSock, &test_byte, 1, 0) <= 0) { if (errno == EINTR) goto retry2; /* if interrupted, just retry */ - ereport(LOG, - (errcode_for_socket_access(), - errmsg("could not receive test message on socket for statistics collector: %m"))); + ereport(LOG, (errcode_for_socket_access(), + errmsg("could not receive test message on socket for statistics collector: %m"))); closesocket(g_instance.stat_cxt.pgStatSock); g_instance.stat_cxt.pgStatSock = PGINVALID_SOCKET; continue; @@ -612,9 +558,8 @@ void pgstat_init(void) if (test_byte != TESTBYTEVAL) { /* strictly paranoia ... */ - ereport(LOG, - (errcode(ERRCODE_WRONG_OBJECT_TYPE), - errmsg("incorrect test message transmission on socket for statistics collector"))); + ereport(LOG, (errcode(ERRCODE_WRONG_OBJECT_TYPE), + errmsg("incorrect test message transmission on socket for statistics collector"))); closesocket(g_instance.stat_cxt.pgStatSock); g_instance.stat_cxt.pgStatSock = PGINVALID_SOCKET; continue; @@ -634,8 +579,8 @@ void pgstat_init(void) * block waiting to send messages to the collector. */ if (!pg_set_noblock(g_instance.stat_cxt.pgStatSock)) { - ereport(LOG, - (errcode_for_socket_access(), errmsg("could not set statistics collector socket to nonblocking mode: %m"))); + ereport(LOG, (errcode_for_socket_access(), + errmsg("could not set statistics collector socket to nonblocking mode: %m"))); goto startup_failed; } @@ -651,7 +596,7 @@ void pgstat_init(void) int new_rcvbuf; ACCEPT_TYPE_ARG3 rcvbufsize = sizeof(old_rcvbuf); - if (getsockopt(g_instance.stat_cxt.pgStatSock, SOL_SOCKET, SO_RCVBUF, (char*)&old_rcvbuf, &rcvbufsize) < 0) { + if (getsockopt(g_instance.stat_cxt.pgStatSock, SOL_SOCKET, SO_RCVBUF, (char *)&old_rcvbuf, &rcvbufsize) < 0) { elog(LOG, "getsockopt(SO_RCVBUF) failed: %m"); /* if we can't get existing size, always try to set it */ old_rcvbuf = 0; @@ -659,9 +604,8 @@ void pgstat_init(void) new_rcvbuf = PGSTAT_MIN_RCVBUF; if (old_rcvbuf < new_rcvbuf) { - if (setsockopt( - g_instance.stat_cxt.pgStatSock, SOL_SOCKET, SO_RCVBUF, (char*)&new_rcvbuf, sizeof(new_rcvbuf)) < - 0) { + if (setsockopt(g_instance.stat_cxt.pgStatSock, SOL_SOCKET, SO_RCVBUF, (char *)&new_rcvbuf, + sizeof(new_rcvbuf)) < 0) { elog(LOG, "setsockopt(SO_RCVBUF) failed: %m"); } } @@ -700,10 +644,8 @@ startup_failed: */ void pgstat_reset_all(void) { - elog(LOG, - "[Pgstat] remove statfiles in %s, %s", - u_sess->stat_cxt.pgstat_stat_filename, - PGSTAT_STAT_PERMANENT_FILENAME); + elog(LOG, "[Pgstat] remove statfiles in %s, %s", u_sess->stat_cxt.pgstat_stat_filename, + PGSTAT_STAT_PERMANENT_FILENAME); unlink(u_sess->stat_cxt.pgstat_stat_filename); unlink(PGSTAT_STAT_PERMANENT_FILENAME); } @@ -766,8 +708,8 @@ static inline bool pgstat_tablist_too_big(const int len) /* destroy current u_sess->stat_cxt.pgStatTabList and make it be null */ static void pgstat_free_tablist(void) { - TabStatusArray* currItem = NULL; - TabStatusArray* nextItem = NULL; + TabStatusArray *currItem = NULL; + TabStatusArray *nextItem = NULL; currItem = u_sess->stat_cxt.pgStatTabList; @@ -797,7 +739,7 @@ void pgstat_report_stat(bool force) TimestampTz now; PgStat_MsgTabstat regular_msg; PgStat_MsgTabstat shared_msg; - TabStatusArray* tsa = NULL; + TabStatusArray *tsa = NULL; int i; int tablist_len = 0; /* length of u_sess->stat_cxt.pgStatTabList */ bool force_to_destory = false; @@ -839,9 +781,9 @@ void pgstat_report_stat(bool force) /* count the length of u_sess->stat_cxt.pgStatTabList */ tablist_len++; for (i = 0; i < tsa->tsa_used; i++) { - PgStat_TableStatus* entry = &tsa->tsa_entries[i]; - PgStat_MsgTabstat* this_msg = NULL; - PgStat_TableEntry* this_ent = NULL; + PgStat_TableStatus *entry = &tsa->tsa_entries[i]; + PgStat_MsgTabstat *this_msg = NULL; + PgStat_TableEntry *this_ent = NULL; /* Shouldn't have any pending transaction-dependent counts */ Assert(entry->trans == NULL); @@ -869,10 +811,8 @@ void pgstat_report_stat(bool force) } } /* zero out TableStatus structs after use */ - rc = memset_s(tsa->tsa_entries, - TABSTAT_QUANTUM * sizeof(PgStat_TableStatus), - 0, - tsa->tsa_used * sizeof(PgStat_TableStatus)); + rc = memset_s(tsa->tsa_entries, TABSTAT_QUANTUM * sizeof(PgStat_TableStatus), 0, + tsa->tsa_used * sizeof(PgStat_TableStatus)); securec_check(rc, "\0", "\0"); tsa->tsa_used = 0; @@ -882,8 +822,8 @@ void pgstat_report_stat(bool force) bool need_reset_counter = (is_unique_sql_enabled() && IS_PGXC_DATANODE && u_sess->unique_sql_cxt.last_stat_counter != NULL); if (need_reset_counter) { - rc = memset_s( - u_sess->unique_sql_cxt.last_stat_counter, sizeof(PgStat_TableCounts), 0, sizeof(PgStat_TableCounts)); + rc = memset_s(u_sess->unique_sql_cxt.last_stat_counter, sizeof(PgStat_TableCounts), 0, + sizeof(PgStat_TableCounts)); securec_check(rc, "\0", "\0"); } @@ -924,7 +864,7 @@ void pgstat_report_stat(bool force) /* * Subroutine for pgstat_report_stat: finish and send a tabstat message */ -static void pgstat_send_tabstat(PgStat_MsgTabstat* tsmsg) +static void pgstat_send_tabstat(PgStat_MsgTabstat *tsmsg) { int n; int len; @@ -969,7 +909,7 @@ static void pgstat_send_funcstats(void) static const PgStat_FunctionCounts all_zeroes = {0}; PgStat_MsgFuncstat msg; - PgStat_BackendFunctionEntry* entry = NULL; + PgStat_BackendFunctionEntry *entry = NULL; HASH_SEQ_STATUS fstat; if (u_sess->stat_cxt.pgStatFunctions == NULL) @@ -980,8 +920,8 @@ static void pgstat_send_funcstats(void) msg.m_nentries = 0; hash_seq_init(&fstat, u_sess->stat_cxt.pgStatFunctions); - while ((entry = (PgStat_BackendFunctionEntry*)hash_seq_search(&fstat)) != NULL) { - PgStat_FunctionEntry* m_ent = NULL; + while ((entry = (PgStat_BackendFunctionEntry *)hash_seq_search(&fstat)) != NULL) { + PgStat_FunctionEntry *m_ent = NULL; errno_t rc; /* Skip it if no counts accumulated since last time */ @@ -1019,13 +959,13 @@ static void pgstat_send_funcstats(void) */ void pgstat_vacuum_stat(void) { - HTAB* htab = NULL; + HTAB *htab = NULL; PgStat_MsgTabpurge msg; PgStat_MsgFuncpurge f_msg; HASH_SEQ_STATUS hstat; - PgStat_StatDBEntry* dbentry = NULL; - PgStat_StatTabEntry* tabentry = NULL; - PgStat_StatFuncEntry* funcentry = NULL; + PgStat_StatDBEntry *dbentry = NULL; + PgStat_StatTabEntry *tabentry = NULL; + PgStat_StatFuncEntry *funcentry = NULL; int len; if (g_instance.stat_cxt.pgStatSock == PGINVALID_SOCKET) @@ -1047,13 +987,13 @@ void pgstat_vacuum_stat(void) * collector to drop them. */ hash_seq_init(&hstat, u_sess->stat_cxt.pgStatDBHash); - while ((dbentry = (PgStat_StatDBEntry*)hash_seq_search(&hstat)) != NULL) { + while ((dbentry = (PgStat_StatDBEntry *)hash_seq_search(&hstat)) != NULL) { Oid dbid = dbentry->databaseid; CHECK_FOR_INTERRUPTS(); /* the DB entry for shared tables (with InvalidOid) is never dropped */ - if (OidIsValid(dbid) && hash_search(htab, (void*)&dbid, HASH_FIND, NULL) == NULL) + if (OidIsValid(dbid) && hash_search(htab, (void *)&dbid, HASH_FIND, NULL) == NULL) pgstat_drop_database(dbid); } @@ -1063,8 +1003,8 @@ void pgstat_vacuum_stat(void) /* * Lookup our own database entry; if not found, nothing more to do. */ - dbentry = (PgStat_StatDBEntry*)hash_search( - u_sess->stat_cxt.pgStatDBHash, (void*)&u_sess->proc_cxt.MyDatabaseId, HASH_FIND, NULL); + dbentry = (PgStat_StatDBEntry *)hash_search(u_sess->stat_cxt.pgStatDBHash, (void *)&u_sess->proc_cxt.MyDatabaseId, + HASH_FIND, NULL); if (dbentry == NULL || dbentry->tables == NULL) return; @@ -1082,12 +1022,12 @@ void pgstat_vacuum_stat(void) * Check for all tables listed in stats hashtable if they still exist. */ hash_seq_init(&hstat, dbentry->tables); - while ((tabentry = (PgStat_StatTabEntry*)hash_seq_search(&hstat)) != NULL) { + while ((tabentry = (PgStat_StatTabEntry *)hash_seq_search(&hstat)) != NULL) { PgStat_StatTabKey tabkey = tabentry->tablekey; CHECK_FOR_INTERRUPTS(); - if (hash_search(htab, (void*)(&tabkey), HASH_FIND, NULL) != NULL) + if (hash_search(htab, (void *)(&tabkey), HASH_FIND, NULL) != NULL) continue; /* @@ -1137,12 +1077,12 @@ void pgstat_vacuum_stat(void) f_msg.m_nentries = 0; hash_seq_init(&hstat, dbentry->functions); - while ((funcentry = (PgStat_StatFuncEntry*)hash_seq_search(&hstat)) != NULL) { + while ((funcentry = (PgStat_StatFuncEntry *)hash_seq_search(&hstat)) != NULL) { Oid funcid = funcentry->functionid; CHECK_FOR_INTERRUPTS(); - if (hash_search(htab, (void*)&funcid, HASH_FIND, NULL) != NULL) + if (hash_search(htab, (void *)&funcid, HASH_FIND, NULL) != NULL) continue; /* @@ -1184,9 +1124,9 @@ void pgstat_vacuum_stat(void) * so that it will be freed properly in event of an error.) * ---------- */ -static HTAB* pgstat_collect_oids(Oid catalogid) +static HTAB *pgstat_collect_oids(Oid catalogid) { - HTAB* htab = NULL; + HTAB *htab = NULL; HASHCTL hash_ctl; Relation rel; TableScanDesc scan; @@ -1199,17 +1139,17 @@ static HTAB* pgstat_collect_oids(Oid catalogid) hash_ctl.entrysize = sizeof(Oid); hash_ctl.hash = oid_hash; hash_ctl.hcxt = CurrentMemoryContext; - htab = hash_create( - "Temporary table of OIDs", PGSTAT_TAB_HASH_SIZE, &hash_ctl, HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT); + htab = hash_create("Temporary table of OIDs", PGSTAT_TAB_HASH_SIZE, &hash_ctl, + HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT); rel = heap_open(catalogid, AccessShareLock); scan = tableam_scan_begin(rel, SnapshotNow, 0, NULL); - while ((tup = (HeapTuple) tableam_scan_getnexttuple(scan, ForwardScanDirection)) != NULL) { + while ((tup = (HeapTuple)tableam_scan_getnexttuple(scan, ForwardScanDirection)) != NULL) { Oid thisoid = HeapTupleGetOid(tup); CHECK_FOR_INTERRUPTS(); - (void)hash_search(htab, (void*)&thisoid, HASH_ENTER, NULL); + (void)hash_search(htab, (void *)&thisoid, HASH_ENTER, NULL); } tableam_scan_end(scan); heap_close(rel, AccessShareLock); @@ -1225,9 +1165,9 @@ static HTAB* pgstat_collect_oids(Oid catalogid) * : done with it. (However, we make the table in CurrentMemoryContext * : so that it will be freed properly in event of an error.) */ -static HTAB* pgstat_collect_tabkeys(void) +static HTAB *pgstat_collect_tabkeys(void) { - HTAB* htab = NULL; + HTAB *htab = NULL; HASHCTL hash_ctl; Relation pgRelation; Relation pgPartition; @@ -1242,18 +1182,18 @@ static HTAB* pgstat_collect_tabkeys(void) hash_ctl.entrysize = sizeof(PgStat_StatTabKey); hash_ctl.hash = tag_hash; hash_ctl.hcxt = CurrentMemoryContext; - htab = hash_create( - "Temporary table of OIDs", PGSTAT_TAB_HASH_SIZE, &hash_ctl, HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT); + htab = hash_create("Temporary table of OIDs", PGSTAT_TAB_HASH_SIZE, &hash_ctl, + HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT); /* deal with relation */ tabkey.statFlag = InvalidOid; pgRelation = heap_open(RelationRelationId, AccessShareLock); scan = tableam_scan_begin(pgRelation, SnapshotNow, 0, NULL); - while ((tuple = (HeapTuple) tableam_scan_getnexttuple(scan, ForwardScanDirection)) != NULL) { + while ((tuple = (HeapTuple)tableam_scan_getnexttuple(scan, ForwardScanDirection)) != NULL) { tabkey.tableid = HeapTupleGetOid(tuple); CHECK_FOR_INTERRUPTS(); - (void)hash_search(htab, (void*)(&tabkey), HASH_ENTER, NULL); + (void)hash_search(htab, (void *)(&tabkey), HASH_ENTER, NULL); } tableam_scan_end(scan); heap_close(pgRelation, AccessShareLock); @@ -1261,7 +1201,7 @@ static HTAB* pgstat_collect_tabkeys(void) /* deal with partition */ pgPartition = heap_open(PartitionRelationId, AccessShareLock); scan = tableam_scan_begin(pgPartition, SnapshotNow, 0, NULL); - while ((tuple = (HeapTuple) tableam_scan_getnexttuple(scan, ForwardScanDirection)) != NULL) { + while ((tuple = (HeapTuple)tableam_scan_getnexttuple(scan, ForwardScanDirection)) != NULL) { partForm = (Form_pg_partition)GETSTRUCT(tuple); /* skip partitioned object in pg_partition */ @@ -1273,7 +1213,7 @@ static HTAB* pgstat_collect_tabkeys(void) CHECK_FOR_INTERRUPTS(); - (void)hash_search(htab, (void*)(&tabkey), HASH_ENTER, NULL); + (void)hash_search(htab, (void *)(&tabkey), HASH_ENTER, NULL); } tableam_scan_end(scan); heap_close(pgPartition, AccessShareLock); @@ -1292,12 +1232,14 @@ static HTAB* pgstat_collect_tabkeys(void) void pgstat_drop_database(Oid databaseid) { PgStat_MsgDropdb msg; - + // 检查全局变量 g_instance.stat_cxt.pgStatSock 是否为无效的套接字 if (g_instance.stat_cxt.pgStatSock == PGINVALID_SOCKET) return; pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_DROPDB); + // 将数据库的ID存储在 msg.m_databaseid 中。 msg.m_databaseid = databaseid; + // 将消息发送到统计收集系统 pgstat_send(&msg, sizeof(msg)); } @@ -1315,21 +1257,28 @@ void pgstat_drop_database(Oid databaseid) #ifdef NOT_USED void pgstat_drop_relation(Oid relid) { - PgStat_MsgTabpurge msg; - int len; + PgStat_MsgTabpurge msg; // 创建一个 PgStat_MsgTabpurge 结构的消息变量 + int len; // 用于存储消息的长度 - if (g_instance.stat_cxt.pgStatSock == PGINVALID_SOCKET) - return; - msg.m_entry[0].m_tableid = relid; - msg.m_entry[0].m_statFlag = STATFLG_RELATION; - msg.m_nentries = 1; + if (g_instance.stat_cxt.pgStatSock == PGINVALID_SOCKET) // 检查统计套接字是否有效 + return; // 如果无效,直接返回,不执行后续操作 + msg.m_entry[0].m_tableid = relid; // 设置消息中的表或索引的 ID + msg.m_entry[0].m_statFlag = STATFLG_RELATION; // 设置统计标志为关系(表或索引) + msg.m_nentries = 1; // 设置消息中的条目数为 1 + + // 计算消息的总长度,包括消息头和所有条目的长度 len = offsetof(PgStat_MsgTabpurge, m_entry[0]) + sizeof(PgStat_MsgTabEntry); + // 使用 pgstat_setheader 函数设置消息头的类型为 PGSTAT_MTYPE_TABPURGE pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_TABPURGE); - msg.m_databaseid = u_sess->proc_cxt.MyDatabaseId; + + msg.m_databaseid = u_sess->proc_cxt.MyDatabaseId; // 设置消息中的数据库 ID + + // 发送消息到统计收集系统,消息的大小为 len pgstat_send(&msg, len); } + #endif /* NOT_USED */ /* ---------- @@ -1340,17 +1289,21 @@ void pgstat_drop_relation(Oid relid) */ void pgstat_reset_counters(void) { - PgStat_MsgResetcounter msg; + PgStat_MsgResetcounter msg; // 创建一个 PgStat_MsgResetcounter 结构的消息变量 - if (g_instance.stat_cxt.pgStatSock == PGINVALID_SOCKET) - return; + if (g_instance.stat_cxt.pgStatSock == PGINVALID_SOCKET) // 检查统计套接字是否有效 + return; // 如果无效,直接返回,不执行后续操作 - if (!superuser()) + if (!superuser()) // 检查当前用户是否为超级用户 ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be system admin to reset statistics counters"))); + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be system admin to reset statistics counters"))); + // 使用 pgstat_setheader 函数设置消息头的类型为 PGSTAT_MTYPE_RESETCOUNTER pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_RESETCOUNTER); - msg.m_databaseid = u_sess->proc_cxt.MyDatabaseId; + + msg.m_databaseid = u_sess->proc_cxt.MyDatabaseId; // 设置消息中的数据库 ID + + // 发送消息到统计收集系统,消息的大小为 sizeof(msg) pgstat_send(&msg, sizeof(msg)); } @@ -1360,25 +1313,34 @@ void pgstat_reset_counters(void) * Tell the statistics collector to reset cluster-wide shared counters. * ---------- */ -void pgstat_reset_shared_counters(const char* target) +/* + * 功能:重置共享统计计数器 + * + * 参数列表: + * target:要重置的统计计数器目标 + * + * 注意: + * 此函数用于重置指定的共享统计计数器。它要求调用者必须具有超级用户权限。 + * 支持的目标值为 "bgwriter"。 + */ +void pgstat_reset_shared_counters(const char *target) { PgStat_MsgResetsharedcounter msg; + /* 检查统计套接字是否有效 */ if (g_instance.stat_cxt.pgStatSock == PGINVALID_SOCKET) return; - + /* 检查是否具有超级用户权限,否则报错 */ if (!superuser()) ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be system admin to reset statistics counters"))); - + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be system admin to reset statistics counters"))); + /* 根据目标值设置消息的重置目标 */ if (strcmp(target, "bgwriter") == 0) msg.m_resettarget = RESET_BGWRITER; else - ereport(ERROR, - (errcode(ERRCODE_INVALID_PARAMETER_VALUE), - errmsg("unrecognized reset target: \"%s\"", target), - errhint("Target must be \"bgwriter\"."))); - + ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE), errmsg("unrecognized reset target: \"%s\"", target), + errhint("Target must be \"bgwriter\"."))); + /* 设置消息头部并发送消息 */ pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_RESETSHAREDCOUNTER); pgstat_send(&msg, sizeof(msg)); } @@ -1389,23 +1351,35 @@ void pgstat_reset_shared_counters(const char* target) * Tell the statistics collector to reset a single counter. * ---------- */ +/* + * 功能:重置共享计数器 + * + * 参数列表: + * target:要重置的目标名称 + * + * 注意: + * 此函数用于向统计子系统发送消息以重置共享计数器。它要求调用者必须具有超级用户权限。 + * 函数根据传入的目标名称确定要重置的计数器类型,并将消息发送给统计子系统。 + */ void pgstat_reset_single_counter(Oid p_objoid, Oid objoid, PgStat_Single_Reset_Type type) { PgStat_MsgResetsinglecounter msg; + // 检查统计套接字是否有效 if (g_instance.stat_cxt.pgStatSock == PGINVALID_SOCKET) return; - + // 检查是否具有超级用户权限,否则报错 if (!superuser()) ereport(ERROR, - (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be system admin to reset statistics counters"))); - + (errcode(ERRCODE_INSUFFICIENT_PRIVILEGE), errmsg("must be system admin to reset statistics counters"))); + // 设置消息头部 pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_RESETSINGLECOUNTER); + // 设置消息的数据库ID、重置类型和对象ID msg.m_databaseid = u_sess->proc_cxt.MyDatabaseId; msg.m_resettype = type; msg.m_objectid = objoid; msg.p_objectid = p_objoid; - + // 发送消息 pgstat_send(&msg, sizeof(msg)); } @@ -1417,17 +1391,31 @@ void pgstat_reset_single_counter(Oid p_objoid, Oid objoid, PgStat_Single_Reset_T * the db OID must be passed in, instead. * ---------- */ +/* + * 功能:汇报自动清理任务的启动 + * + * 参数列表: + * dboid:数据库OID,表示要清理的数据库 + * + * 注意: + * 此函数用于向统计子系统报告自动清理任务的启动情况。如果统计子系统的套接字无效, + * 函数会立即返回。函数首先设置消息头,包括消息类型、数据库OID和任务启动时间,然后 + * 将消息发送给统计子系统。 + */ void pgstat_report_autovac(Oid dboid) { PgStat_MsgAutovacStart msg; + // 如果统计子系统的套接字无效,直接返回 if (g_instance.stat_cxt.pgStatSock == PGINVALID_SOCKET) return; + // 设置消息头,包括消息类型、数据库OID和任务启动时间 pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_AUTOVAC_START); msg.m_databaseid = dboid; msg.m_start_time = GetCurrentTimestamp(); + // 将消息发送给统计子系统 pgstat_send(&msg, sizeof(msg)); } @@ -1437,19 +1425,36 @@ void pgstat_report_autovac(Oid dboid) * Tell the collector about the table we just vacuumed. * --------- */ +/* + * 功能:汇报自动清理任务因超时而终止 + * + * 参数列表: + * tableoid:表的OID,表示触发自动清理的表 + * statFlag:统计标志,表示统计的类型(如索引、表、共享表等) + * shared:指示是否是共享表 + * + * 注意: + * 此函数用于向统计子系统报告自动清理任务因超时而终止的情况。 + * 如果统计子系统的套接字无效或者未启用统计计数,则函数会立即返回。 + * 首先,函数会设置消息头,包括消息类型、数据库OID(如果不是共享表)、表OID、统计标志和超时状态。 + * 然后,将消息发送给统计子系统以记录超时的自动清理任务。 + */ + void pgstat_report_autovac_timeout(Oid tableoid, uint32 statFlag, bool shared) { PgStat_MsgAutovacStat msg; - + // 如果统计子系统的套接字无效或者未启用统计计数,则直接返回 if (g_instance.stat_cxt.pgStatSock == PGINVALID_SOCKET || !u_sess->attr.attr_common.pgstat_track_counts) return; - + // 设置消息头 pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_AUTOVAC_STAT); + // 如果不是共享表,则使用当前会话的数据库OID msg.m_databaseid = shared ? InvalidOid : u_sess->proc_cxt.MyDatabaseId; + // 设置表的OID、统计标志和超时状态 msg.m_tableoid = tableoid; msg.m_statFlag = statFlag; msg.m_autovacStat = AV_TIMEOUT; - + // 发送消息给统计子系统以记录超时的自动清理任务 pgstat_send(&msg, sizeof(msg)); } @@ -1459,20 +1464,40 @@ void pgstat_report_autovac_timeout(Oid tableoid, uint32 statFlag, bool shared) * Tell the collector about the table we just vacuumed. * --------- */ +/* + * 功能:向统计子系统报告VACUUM操作的统计信息 + * + * 参数列表: + * tableoid:表的OID,表示触发VACUUM操作的表 + * statFlag:统计标志,表示统计的类型(如索引、表、共享表等) + * shared:指示是否是共享表 + * tuples:VACUUM操作影响的元组数量 + * + * 注意: + * 此函数用于向统计子系统报告VACUUM操作的统计信息。 + * 如果统计子系统的套接字无效或者未启用统计计数,则函数会立即返回。 + * 首先,函数会设置消息头,包括消息类型、数据库OID(如果不是共享表)、表OID、统计标志、 + * 是否为自动VACUUM工作进程、VACUUM操作的时间戳和影响的元组数量。 + * 然后,将消息发送给统计子系统以记录VACUUM操作的统计信息。 + */ + void pgstat_report_vacuum(Oid tableoid, uint32 statFlag, bool shared, PgStat_Counter tuples) { PgStat_MsgVacuum msg; - + // 如果统计子系统的套接字无效或者未启用统计计数,则直接返回 if (g_instance.stat_cxt.pgStatSock == PGINVALID_SOCKET || !u_sess->attr.attr_common.pgstat_track_counts) return; - + // 设置消息头 pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_VACUUM); + // 如果不是共享表,则使用当前会话的数据库OID msg.m_databaseid = shared ? InvalidOid : u_sess->proc_cxt.MyDatabaseId; + // 设置表的OID、统计标志、是否为自动VACUUM工作进程、VACUUM操作的时间戳和影响的元组数量 msg.m_tableoid = tableoid; msg.m_statFlag = statFlag; msg.m_autovacuum = IsAutoVacuumWorkerProcess() || IsFromAutoVacWoker(); msg.m_vacuumtime = GetCurrentTimestamp(); msg.m_tuples = tuples; + // 发送消息给统计子系统以记录VACUUM操作的统计信息 pgstat_send(&msg, sizeof(msg)); } @@ -1482,21 +1507,40 @@ void pgstat_report_vacuum(Oid tableoid, uint32 statFlag, bool shared, PgStat_Cou * Tell the collector how many blocks we scanned and successfully pruned * --------- */ -void PgstatReportPrunestat(Oid tableoid, uint32 statFlag, - bool shared, PgStat_Counter scanned, - PgStat_Counter pruned) +/* + * 功能:向统计子系统报告PRUNE操作的统计信息 + * + * 参数列表: + * tableoid:表的OID,表示触发PRUNE操作的表 + * statFlag:统计标志,表示统计的类型(如索引、表、共享表等) + * shared:指示是否是共享表 + * scanned:PRUNE操作扫描的块数 + * pruned:PRUNE操作修剪的块数 + * + * 注意: + * 此函数用于向统计子系统报告PRUNE操作的统计信息。 + * 如果统计子系统的套接字无效,则函数会立即返回。 + * 首先,函数会设置消息头,包括消息类型、数据库OID(如果不是共享表)、表OID、统计标志、 + * 扫描的块数和修剪的块数。 + * 然后,将消息发送给统计子系统以记录PRUNE操作的统计信息。 + */ + +void PgstatReportPrunestat(Oid tableoid, uint32 statFlag, bool shared, PgStat_Counter scanned, PgStat_Counter pruned) { PgStat_MsgPrune msg; - + // 如果统计子系统的套接字无效,则直接返回 if (g_instance.stat_cxt.pgStatSock == PGINVALID_SOCKET) return; - + // 设置消息头 pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_PRUNESTAT); + // 如果不是共享表,则使用当前会话的数据库OID msg.m_databaseid = shared ? InvalidOid : u_sess->proc_cxt.MyDatabaseId; + // 设置表的OID、统计标志、扫描的块数和修剪的块数 msg.m_tableoid = tableoid; msg.m_statFlag = statFlag; msg.m_scanned_blocks = scanned; msg.m_pruned_blocks = pruned; + // 发送消息给统计子系统以记录PRUNE操作的统计信息 pgstat_send(&msg, sizeof(msg)); } @@ -1507,18 +1551,36 @@ void PgstatReportPrunestat(Oid tableoid, uint32 statFlag, * copy/[exchange/truncate/drop] partition. * --------- */ +/* + * 功能:向统计子系统报告数据变更的统计信息 + * + * 参数列表: + * tableoid:表的OID,表示触发数据变更的表 + * statFlag:统计标志,表示统计的类型(如索引、表、共享表等) + * shared:指示是否是共享表 + * + * 注意: + * 此函数用于向统计子系统报告数据变更的统计信息。 + * 如果统计子系统的套接字无效或未启用统计,则函数会立即返回。 + * 首先,函数会设置消息头,包括消息类型、数据库OID(如果不是共享表)、表OID、统计标志和变更的时间戳。 + * 然后,将消息发送给统计子系统以记录数据变更的统计信息。 + */ + void pgstat_report_data_changed(Oid tableoid, uint32 statFlag, bool shared) { PgStat_MsgDataChanged msg; - + // 如果统计子系统的套接字无效或未启用统计,则直接返回 if (g_instance.stat_cxt.pgStatSock == PGINVALID_SOCKET || !u_sess->attr.attr_common.pgstat_track_counts) return; - + // 设置消息头 pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_DATA_CHANGED); + // 如果不是共享表,则使用当前会话的数据库OID msg.m_databaseid = shared ? InvalidOid : u_sess->proc_cxt.MyDatabaseId; + // 设置表的OID、统计标志和变更的时间戳 msg.m_tableoid = tableoid; msg.m_statFlag = statFlag; msg.m_changed_time = GetCurrentTimestamp(); + // 发送消息给统计子系统以记录数据变更的统计信息 pgstat_send(&msg, sizeof(msg)); } @@ -1528,33 +1590,65 @@ void pgstat_report_data_changed(Oid tableoid, uint32 statFlag, bool shared) * Tell the collector about the sql responsetime. * --------- */ +/* + * 功能:向统计子系统报告SQL响应时间统计信息 + * + * 参数列表: + * UniqueSQLId:唯一的SQL标识,表示SQL语句的唯一标识符 + * start_time:SQL开始执行的时间戳 + * rt:SQL的响应时间 + * + * 注意: + * 此函数用于向统计子系统报告SQL响应时间统计信息。 + * 如果是单节点模式、统计子系统的套接字无效、未启用实时百分位统计或者主机状态不是运行状态,则函数会立即返回。 + * 首先,函数会设置消息头,包括消息类型和SQL响应时间信息。 + * 然后,将消息发送给统计子系统以记录SQL的响应时间统计信息。 + */ + void pgstat_report_sql_rt(uint64 UniqueSQLId, int64 start_time, int64 rt) { PgStat_SqlRT msg; - + // 如果是单节点模式,直接返回(为了性能问题而禁用) if (IS_SINGLE_NODE) { - return; /* disable in single node tmp for performance issue */ - } - if (g_instance.stat_cxt.pgStatSock == PGINVALID_SOCKET || !u_sess->attr.attr_common.enable_instr_rt_percentile) - return; - if (!PMstateIsRun()) { return; } + // 如果统计子系统的套接字无效、未启用实时百分位统计或者主机状态不是运行状态,则直接返回 + if (g_instance.stat_cxt.pgStatSock == PGINVALID_SOCKET || !u_sess->attr.attr_common.enable_instr_rt_percentile || + !PMstateIsRun()) { + return; + } + // 设置消息头 pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_RESPONSETIME); + // 设置SQL响应时间信息 msg.sqlRT.UniqueSQLId = UniqueSQLId; msg.sqlRT.start_time = start_time; msg.sqlRT.rt = rt; + // 发送消息给统计子系统以记录SQL的响应时间统计信息 pgstat_send(&msg, sizeof(msg)); } +/* + * 功能:向统计子系统报告进程的百分位统计信息 + * + * 注意: + * 此函数用于向统计子系统报告进程的百分位统计信息。 + * 如果统计子系统的套接字无效,则函数会立即返回。 + * 首先,函数会设置消息头,包括消息类型和当前时间戳。 + * 然后,将消息发送给统计子系统以记录进程的百分位统计信息。 + */ + void pgstat_report_process_percentile() { PgStat_PrsPtl msg; - - if (g_instance.stat_cxt.pgStatSock == PGINVALID_SOCKET) + // 如果统计子系统的套接字无效,直接返回 + if (g_instance.stat_cxt.pgStatSock == PGINVALID_SOCKET) { return; + } + // 设置消息头 pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_PROCESSPERCENTILE); + // 设置当前时间戳 msg.now = GetCurrentTimestamp(); + // 发送消息给统计子系统以记录进程的百分位统计信息 pgstat_send(&msg, sizeof(msg)); } @@ -1564,17 +1658,37 @@ void pgstat_report_process_percentile() * Tell the collector about the table we just truncated. * --------- */ +/* + * 功能:向统计子系统报告表截断操作的统计信息 + * + * 参数列表: + * tableoid:被截断的表的OID + * statFlag:统计标志 + * shared:是否为共享表 + * + * 注意: + * 此函数用于向统计子系统报告表截断操作的统计信息。 + * 如果统计子系统的套接字无效或者未启用统计计数,则函数会立即返回。 + * 首先,函数会设置消息头,包括消息类型。 + * 然后,设置消息中的数据库ID、表OID和统计标志。 + * 最后,将消息发送给统计子系统以记录表截断操作的统计信息。 + */ + void pgstat_report_truncate(Oid tableoid, uint32 statFlag, bool shared) { PgStat_MsgTruncate msg; - - if (g_instance.stat_cxt.pgStatSock == PGINVALID_SOCKET || !u_sess->attr.attr_common.pgstat_track_counts) + // 如果统计子系统的套接字无效或者未启用统计计数,直接返回 + if (g_instance.stat_cxt.pgStatSock == PGINVALID_SOCKET || !u_sess->attr.attr_common.pgstat_track_counts) { return; - + } + // 设置消息头 pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_TRUNCATE); + // 设置数据库ID msg.m_databaseid = shared ? InvalidOid : u_sess->proc_cxt.MyDatabaseId; + // 设置表OID和统计标志 msg.m_tableoid = tableoid; msg.m_statFlag = statFlag; + // 发送消息给统计子系统以记录表截断操作的统计信息 pgstat_send(&msg, sizeof(msg)); } @@ -1602,7 +1716,7 @@ void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Count * committing.) */ if (rel->pgstat_info != NULL) { - PgStat_TableXactStatus* trans = NULL; + PgStat_TableXactStatus *trans = NULL; for (trans = rel->pgstat_info->trans; trans; trans = trans->upper) { livetuples -= trans->tuples_inserted - trans->tuples_deleted; @@ -1632,16 +1746,33 @@ void pgstat_report_analyze(Relation rel, PgStat_Counter livetuples, PgStat_Count * Tell the collector about a Hot Standby recovery conflict. * -------- */ +/* + * 功能:向统计子系统报告恢复冲突的统计信息 + * + * 参数列表: + * reason:恢复冲突的原因 + * + * 注意: + * 此函数用于向统计子系统报告恢复冲突的统计信息。 + * 如果统计子系统的套接字无效或者未启用统计计数,则函数会立即返回。 + * 首先,函数会设置消息头,包括消息类型。 + * 然后,设置消息中的数据库ID和恢复冲突的原因。 + * 最后,将消息发送给统计子系统以记录恢复冲突的统计信息。 + */ + void pgstat_report_recovery_conflict(int reason) { PgStat_MsgRecoveryConflict msg; - - if (g_instance.stat_cxt.pgStatSock == PGINVALID_SOCKET || !u_sess->attr.attr_common.pgstat_track_counts) + // 如果统计子系统的套接字无效或者未启用统计计数,直接返回 + if (g_instance.stat_cxt.pgStatSock == PGINVALID_SOCKET || !u_sess->attr.attr_common.pgstat_track_counts) { return; - + } + // 设置消息头 pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_RECOVERYCONFLICT); + // 设置数据库ID和恢复冲突的原因 msg.m_databaseid = u_sess->proc_cxt.MyDatabaseId; msg.m_reason = reason; + // 发送消息给统计子系统以记录恢复冲突的统计信息 pgstat_send(&msg, sizeof(msg)); } @@ -1651,15 +1782,29 @@ void pgstat_report_recovery_conflict(int reason) * Tell the collector about a deadlock detected. * -------- */ +/* + * 功能:向统计子系统报告死锁的统计信息 + * + * 注意: + * 此函数用于向统计子系统报告死锁的统计信息。 + * 如果统计子系统的套接字无效或者未启用统计计数,则函数会立即返回。 + * 首先,函数会设置消息头,包括消息类型。 + * 然后,设置消息中的数据库ID。 + * 最后,将消息发送给统计子系统以记录死锁的统计信息。 + */ + void pgstat_report_deadlock(void) { PgStat_MsgDeadlock msg; - - if (g_instance.stat_cxt.pgStatSock == PGINVALID_SOCKET || !u_sess->attr.attr_common.pgstat_track_counts) + // 如果统计子系统的套接字无效或者未启用统计计数,直接返回 + if (g_instance.stat_cxt.pgStatSock == PGINVALID_SOCKET || !u_sess->attr.attr_common.pgstat_track_counts) { return; - + } + // 设置消息头 pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_DEADLOCK); + // 设置数据库ID msg.m_databaseid = u_sess->proc_cxt.MyDatabaseId; + // 发送消息给统计子系统以记录死锁的统计信息 pgstat_send(&msg, sizeof(msg)); } @@ -1669,16 +1814,34 @@ void pgstat_report_deadlock(void) * Tell the collector about a temporary file. * -------- */ +/* + * 功能:向统计子系统报告临时文件的统计信息 + * + * 参数列表: + * filesize:临时文件的大小 + * + * 注意: + * 此函数用于向统计子系统报告临时文件的统计信息。 + * 如果统计子系统的套接字无效或者未启用统计计数,则函数会立即返回。 + * 首先,函数会设置消息头,包括消息类型。 + * 然后,设置消息中的数据库ID和临时文件的大小。 + * 最后,将消息发送给统计子系统以记录临时文件的统计信息。 + */ + void pgstat_report_tempfile(size_t filesize) { PgStat_MsgTempFile msg; - - if (g_instance.stat_cxt.pgStatSock == PGINVALID_SOCKET || !u_sess->attr.attr_common.pgstat_track_counts) + // 如果统计子系统的套接字无效或者未启用统计计数,直接返回 + if (g_instance.stat_cxt.pgStatSock == PGINVALID_SOCKET || !u_sess->attr.attr_common.pgstat_track_counts) { return; - + } + // 设置消息头 pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_TEMPFILE); + // 设置数据库ID msg.m_databaseid = u_sess->proc_cxt.MyDatabaseId; + // 设置临时文件的大小 msg.m_filesize = filesize; + // 发送消息给统计子系统以记录临时文件的统计信息 pgstat_send(&msg, sizeof(msg)); } @@ -1688,17 +1851,37 @@ void pgstat_report_tempfile(size_t filesize) * Tell the collector about memory reservation * ---------- */ +/* + * 功能:向统计子系统报告内存保留的统计信息 + * + * 参数列表: + * memReserved:内存保留的大小(以MB为单位) + * reserve_or_release:保留或释放内存的标志(0表示释放,1表示保留) + * + * 注意: + * 此函数用于向统计子系统报告内存保留的统计信息。 + * 如果统计子系统的套接字无效或者未启用统计计数,则函数会立即返回。 + * 首先,函数会设置消息头,包括消息类型。 + * 然后,设置消息中的数据库ID、内存保留的大小以及保留或释放内存的标志。 + * 最后,将消息发送给统计子系统以记录内存保留的统计信息。 + */ + void pgstat_report_memReserved(int4 memReserved, int reserve_or_release) { PgStat_MsgMemReserved msg; - - if (g_instance.stat_cxt.pgStatSock == PGINVALID_SOCKET || !u_sess->attr.attr_common.pgstat_track_counts) + // 如果统计子系统的套接字无效或者未启用统计计数,直接返回 + if (g_instance.stat_cxt.pgStatSock == PGINVALID_SOCKET || !u_sess->attr.attr_common.pgstat_track_counts) { return; - + } + // 设置消息头 pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_MEMRESERVED); + // 设置数据库ID msg.m_databaseid = u_sess->proc_cxt.MyDatabaseId; + // 设置内存保留的大小(以MB为单位) msg.m_memMbytes = memReserved; + // 设置保留或释放内存的标志(0表示释放,1表示保留) msg.m_reserve_or_release = reserve_or_release; + // 发送消息给统计子系统以记录内存保留的统计信息 pgstat_send(&msg, sizeof(msg)); } @@ -1708,14 +1891,25 @@ void pgstat_report_memReserved(int4 memReserved, int reserve_or_release) * Send some junk data to the collector to increase traffic. * ---------- */ +/* + * 功能:向统计子系统发送 ping 消息以保持连接 + * + * 注意: + * 此函数用于向统计子系统发送 ping 消息以保持连接。如果统计子系统的套接字无效,函数会立即返回。 + * 首先,函数会设置 ping 消息的消息头,包括消息类型。 + * 然后,将 ping 消息发送给统计子系统以保持连接。 + */ + void pgstat_ping(void) { PgStat_MsgDummy msg; - - if (g_instance.stat_cxt.pgStatSock == PGINVALID_SOCKET) + // 如果统计子系统的套接字无效,直接返回 + if (g_instance.stat_cxt.pgStatSock == PGINVALID_SOCKET) { return; - + } + // 设置消息头 pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_DUMMY); + // 发送 ping 消息给统计子系统以保持连接 pgstat_send(&msg, sizeof(msg)); } @@ -1726,12 +1920,25 @@ void pgstat_ping(void) * ts specifies the minimum acceptable timestamp for the stats file. * ---------- */ +/* + * 功能:发送一个查询消息给统计子系统 + * + * 参数列表: + * ts:查询时间戳 + * + * 注意: + * 此函数用于向统计子系统发送查询消息。首先,函数会设置查询消息的消息头,包括消息类型。 + * 然后,将查询时间戳添加到消息中,并将消息发送给统计子系统。 + */ + static void pgstat_send_inquiry(TimestampTz ts) { PgStat_MsgInquiry msg; - + // 设置消息头 pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_INQUIRY); + // 添加查询时间戳到消息中 msg.inquiry_time = ts; + // 发送查询消息给统计子系统 pgstat_send(&msg, sizeof(msg)); } @@ -1739,9 +1946,9 @@ static void pgstat_send_inquiry(TimestampTz ts) * Initialize function call usage data. * Called by the executor before invoking a function. */ -void pgstat_init_function_usage(FunctionCallInfoData* fcinfo, PgStat_FunctionCallUsage* fcu) +void pgstat_init_function_usage(FunctionCallInfoData *fcinfo, PgStat_FunctionCallUsage *fcu) { - PgStat_BackendFunctionEntry* htabent = NULL; + PgStat_BackendFunctionEntry *htabent = NULL; bool found = false; errno_t rc; @@ -1761,13 +1968,13 @@ void pgstat_init_function_usage(FunctionCallInfoData* fcinfo, PgStat_FunctionCal hash_ctl.entrysize = sizeof(PgStat_BackendFunctionEntry); hash_ctl.hash = oid_hash; hash_ctl.hcxt = u_sess->stat_cxt.pgStatLocalContext; - u_sess->stat_cxt.pgStatFunctions = hash_create( - "Function stat entries", PGSTAT_FUNCTION_HASH_SIZE, &hash_ctl, HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT); + u_sess->stat_cxt.pgStatFunctions = hash_create("Function stat entries", PGSTAT_FUNCTION_HASH_SIZE, &hash_ctl, + HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT); } /* Get the stats entry for this function, create if necessary */ - htabent = (PgStat_BackendFunctionEntry*)hash_search( - u_sess->stat_cxt.pgStatFunctions, &fcinfo->flinfo->fn_oid, HASH_ENTER, &found); + htabent = (PgStat_BackendFunctionEntry *)hash_search(u_sess->stat_cxt.pgStatFunctions, &fcinfo->flinfo->fn_oid, + HASH_ENTER, &found); if (!found) { rc = memset_s(&htabent->f_counts, sizeof(PgStat_FunctionCounts), 0, sizeof(PgStat_FunctionCounts)); securec_check(rc, "\0", "\0"); @@ -1791,13 +1998,29 @@ void pgstat_init_function_usage(FunctionCallInfoData* fcinfo, PgStat_FunctionCal * * If no entry, return NULL, don't create a new one */ -PgStat_BackendFunctionEntry* find_funcstat_entry(Oid func_id) +/* + * 功能:查找给定函数ID的统计信息条目 + * + * 参数列表: + * func_id:要查找的函数ID + * + * 返回值: + * 返回指向PgStat_BackendFunctionEntry结构的指针,表示找到的统计信息条目; + * 如果未找到,则返回NULL。 + * + * 注意: + * 此函数用于在统计信息中查找给定函数ID的统计信息条目。如果找到对应的统计信息条目, + * 则返回该条目的指针;否则返回NULL。 + */ +PgStat_BackendFunctionEntry *find_funcstat_entry(Oid func_id) { + // 如果统计信息的哈希表为空,返回NULL if (u_sess->stat_cxt.pgStatFunctions == NULL) return NULL; - return (PgStat_BackendFunctionEntry*)hash_search( - u_sess->stat_cxt.pgStatFunctions, (void*)&func_id, HASH_FIND, NULL); + // 在哈希表中查找指定函数ID的统计信息条目 + return (PgStat_BackendFunctionEntry *)hash_search(u_sess->stat_cxt.pgStatFunctions, (void *)&func_id, HASH_FIND, + NULL); } /* @@ -1809,9 +2032,9 @@ PgStat_BackendFunctionEntry* find_funcstat_entry(Oid func_id) * calls for what the user considers a single call of the function. The * finalize flag should be TRUE on the last call. */ -void pgstat_end_function_usage(PgStat_FunctionCallUsage* fcu, bool finalize) +void pgstat_end_function_usage(PgStat_FunctionCallUsage *fcu, bool finalize) { - PgStat_FunctionCounts* fs = fcu->fs; + PgStat_FunctionCounts *fs = fcu->fs; instr_time f_total; instr_time f_others; instr_time f_self; @@ -1872,7 +2095,7 @@ void pgstat_initstats(Relation rel) /* We only count stats for things that have storage */ if (!(relkind == RELKIND_RELATION || relkind == RELKIND_MATVIEW || relkind == RELKIND_INDEX || - relkind == RELKIND_GLOBAL_INDEX || relkind == RELKIND_TOASTVALUE || RELKIND_IS_SEQUENCE(relkind))) { + relkind == RELKIND_GLOBAL_INDEX || relkind == RELKIND_TOASTVALUE || RELKIND_IS_SEQUENCE(relkind))) { rel->pgstat_info = NULL; return; } @@ -1888,30 +2111,39 @@ void pgstat_initstats(Relation rel) } /* - * Make sure pgStatTabList and pgStatTabHash are initialized. + * 功能:确保统计表已初始化 + * + * 注意: + * 此函数用于确保统计表已初始化。如果尚未初始化,则会分配内存并进行初始化。 + * 初始化包括创建哈希表、分配内存空间等操作。 */ static void make_sure_stat_tab_initialized() { HASHCTL ctl; errno_t rc; + // 如果统计表列表为空,分配内存并初始化 if (u_sess->stat_cxt.pgStatTabList == NULL) { /* This is first time procedure is called */ - u_sess->stat_cxt.pgStatTabList = - (TabStatusArray*)MemoryContextAllocZero(SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_DFX), sizeof(TabStatusArray)); + u_sess->stat_cxt.pgStatTabList = (TabStatusArray *)MemoryContextAllocZero( + SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_DFX), sizeof(TabStatusArray)); } + // 如果哈希表已存在,直接返回 if (u_sess->stat_cxt.pgStatTabHash != NULL) { return; } + // 如果哈希表上下文为空,创建哈希表上下文 if (u_sess->stat_cxt.pgStatTabHashContext == NULL) { u_sess->stat_cxt.pgStatTabHashContext = AllocSetContextCreate(u_sess->top_mem_cxt, "PGStatLookupHashTableContext", ALLOCSET_DEFAULT_SIZES); } else { + // 如果哈希表上下文已存在,重置上下文以释放旧的哈希表内存 MemoryContextReset(u_sess->stat_cxt.pgStatTabHashContext); } + // 初始化哈希表控制信息 rc = memset_s(&ctl, sizeof(ctl), 0, sizeof(ctl)); securec_check(rc, "\0", "\0"); ctl.keysize = sizeof(PgStat_StatTabKey); @@ -1919,20 +2151,19 @@ static void make_sure_stat_tab_initialized() ctl.hash = tag_hash; ctl.hcxt = u_sess->stat_cxt.pgStatTabHashContext; - u_sess->stat_cxt.pgStatTabHash = hash_create("pgstat sessionid to tsa_entry lookup hash table", - TABSTAT_QUANTUM, - &ctl, - HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT); + // 创建哈希表 + u_sess->stat_cxt.pgStatTabHash = hash_create("pgstat sessionid to tsa_entry lookup hash table", TABSTAT_QUANTUM, + &ctl, HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT); } /* * get_tabstat_entry - find or create a PgStat_TableStatus entry for rel */ -static PgStat_TableStatus* get_tabstat_entry(Oid rel_id, bool isshared, uint32 statFlag) +static PgStat_TableStatus *get_tabstat_entry(Oid rel_id, bool isshared, uint32 statFlag) { - TabStatHashEntry* hash_entry = NULL; - PgStat_TableStatus* entry = NULL; - TabStatusArray* tsa = NULL; + TabStatHashEntry *hash_entry = NULL; + PgStat_TableStatus *entry = NULL; + TabStatusArray *tsa = NULL; bool found = false; make_sure_stat_tab_initialized(); @@ -1941,7 +2172,7 @@ static PgStat_TableStatus* get_tabstat_entry(Oid rel_id, bool isshared, uint32 s PgStat_StatTabKey rel_key; rel_key.tableid = rel_id; rel_key.statFlag = statFlag; - hash_entry = (TabStatHashEntry*)hash_search(u_sess->stat_cxt.pgStatTabHash, &rel_key, HASH_ENTER, &found); + hash_entry = (TabStatHashEntry *)hash_search(u_sess->stat_cxt.pgStatTabHash, &rel_key, HASH_ENTER, &found); if (found) { return hash_entry->tsa_entry; } @@ -1953,8 +2184,8 @@ static PgStat_TableStatus* get_tabstat_entry(Oid rel_id, bool isshared, uint32 s tsa = u_sess->stat_cxt.pgStatTabList; while (tsa->tsa_used == TABSTAT_QUANTUM) { if (tsa->tsa_next == NULL) { - tsa->tsa_next = (TabStatusArray*)MemoryContextAllocZero( - SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_DFX), sizeof(TabStatusArray)); + tsa->tsa_next = (TabStatusArray *)MemoryContextAllocZero(SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_DFX), + sizeof(TabStatusArray)); } tsa = tsa->tsa_next; } @@ -1981,52 +2212,89 @@ static PgStat_TableStatus* get_tabstat_entry(Oid rel_id, bool isshared, uint32 s * * If no entry, return NULL, don't create a new one */ -PgStat_TableStatus* find_tabstat_entry(Oid rel_id, uint32 statFlag) +/* + * 功能:查找统计表条目 + * + * 参数列表: + * rel_id:关系的Oid + * statFlag:统计标志 + * + * 返回值: + * 如果找到对应的统计表条目,返回该条目的指针;否则,返回NULL。 + * + * 注意: + * 此函数用于查找给定关系和统计标志的统计表条目。首先检查哈希表是否已经初始化, + * 如果未初始化则返回NULL。然后构建查找关键字,使用哈希表查找并返回相应的统计表条目。 + */ +PgStat_TableStatus *find_tabstat_entry(Oid rel_id, uint32 statFlag) { - TabStatHashEntry* hash_entry; + TabStatHashEntry *hash_entry; /* * There are no entries at all. */ + // 如果哈希表未初始化,返回NULL if (!u_sess->stat_cxt.pgStatTabHash) return NULL; - + // 构建查找关键字 PgStat_StatTabKey rel_key; rel_key.tableid = rel_id; rel_key.statFlag = statFlag; - hash_entry = (TabStatHashEntry*)hash_search(u_sess->stat_cxt.pgStatTabHash, &rel_key, HASH_FIND, NULL); + // 使用哈希表查找相应的统计表条目 + hash_entry = (TabStatHashEntry *)hash_search(u_sess->stat_cxt.pgStatTabHash, &rel_key, HASH_FIND, NULL); + // 如果未找到对应的条目,返回NULL if (!hash_entry) return NULL; - + // 返回找到的统计表条目的指针 return hash_entry->tsa_entry; } /* * get_tabstat_stack_level - add a new (sub)transaction stack entry if needed */ -static PgStat_SubXactStatus* get_tabstat_stack_level(int nest_level) +/* + * 功能:获取统计信息子事务状态的栈级别 + * + * 参数列表: + * nest_level:子事务的嵌套级别 + * + * 返回值: + * 返回对应子事务状态的指针,如果不存在则创建并返回新的子事务状态。 + * + * 注意: + * 此函数用于获取或创建子事务状态的栈级别。首先检查当前子事务状态栈是否为空, + * 或者当前栈顶的子事务状态的嵌套级别是否与给定的嵌套级别一致。如果不一致, + * 则创建新的子事务状态并将其推入栈中。 + */ +static PgStat_SubXactStatus *get_tabstat_stack_level(int nest_level) { - PgStat_SubXactStatus* xact_state = NULL; - + PgStat_SubXactStatus *xact_state = NULL; + // 获取当前子事务状态栈的栈顶状态 xact_state = u_sess->stat_cxt.pgStatXactStack; + // 如果栈为空或者栈顶子事务状态的嵌套级别与给定的嵌套级别不一致 if (xact_state == NULL || xact_state->nest_level != nest_level) { + // 分配新的子事务状态 xact_state = - (PgStat_SubXactStatus*)MemoryContextAlloc(u_sess->top_transaction_mem_cxt, sizeof(PgStat_SubXactStatus)); + (PgStat_SubXactStatus *)MemoryContextAlloc(u_sess->top_transaction_mem_cxt, sizeof(PgStat_SubXactStatus)); + // 设置子事务状态的嵌套级别 xact_state->nest_level = nest_level; + // 初始化子事务状态的链表 xact_state->prev = u_sess->stat_cxt.pgStatXactStack; xact_state->first = NULL; + // 将新的子事务状态推入栈中 u_sess->stat_cxt.pgStatXactStack = xact_state; } + // 返回子事务状态的指针 return xact_state; } /* * add_tabstat_xact_level - add a new (sub)transaction state record */ -static void add_tabstat_xact_level(PgStat_TableStatus* pgstat_info, int nest_level) +static void add_tabstat_xact_level(PgStat_TableStatus *pgstat_info, int nest_level) { - PgStat_SubXactStatus* xact_state = NULL; - PgStat_TableXactStatus* trans = NULL; + PgStat_SubXactStatus *xact_state = NULL; + PgStat_TableXactStatus *trans = NULL; /* * If this is the first rel to be modified at the current nest level, we @@ -2035,8 +2303,8 @@ static void add_tabstat_xact_level(PgStat_TableStatus* pgstat_info, int nest_lev xact_state = get_tabstat_stack_level(nest_level); /* Now make a per-table stack entry */ - trans = (PgStat_TableXactStatus*)MemoryContextAllocZero( - u_sess->top_transaction_mem_cxt, sizeof(PgStat_TableXactStatus)); + trans = (PgStat_TableXactStatus *)MemoryContextAllocZero(u_sess->top_transaction_mem_cxt, + sizeof(PgStat_TableXactStatus)); trans->nest_level = nest_level; trans->upper = pgstat_info->trans; trans->parent = pgstat_info; @@ -2048,17 +2316,30 @@ static void add_tabstat_xact_level(PgStat_TableStatus* pgstat_info, int nest_lev /* * pgstat_count_heap_insert - count a tuple insertion of n tuples */ +/* + * 功能:更新表插入操作的统计信息 + * + * 参数列表: + * rel:关系的指针 + * n:插入的元组数量 + * + * 注意: + * 此函数用于更新关系的插入操作的统计信息。首先检查关系是否具有 PgStat_TableStatus, + * 如果是,则获取当前事务的嵌套级别。然后,检查事务状态是否已创建或者是否与当前事务的嵌套级别不一致, + * 如果是,则创建新的事务状态。最后,更新事务状态中的插入元组数量。 + */ void pgstat_count_heap_insert(Relation rel, int n) { - PgStat_TableStatus* pgstat_info = rel->pgstat_info; - + PgStat_TableStatus *pgstat_info = rel->pgstat_info; + // 如果关系具有 PgStat_TableStatus if (pgstat_info != NULL) { /* We have to log the effect at the proper transactional level */ int nest_level = GetCurrentTransactionNestLevel(); - + // 如果事务状态为空或者事务状态的嵌套级别与当前事务的嵌套级别不一致 if (pgstat_info->trans == NULL || pgstat_info->trans->nest_level != nest_level) + // 创建新的事务状态 add_tabstat_xact_level(pgstat_info, nest_level); - + // 更新事务状态中的插入元组数量 pgstat_info->trans->tuples_inserted += n; pgstat_info->trans->tuples_inserted_accum += n; } @@ -2067,21 +2348,34 @@ void pgstat_count_heap_insert(Relation rel, int n) /* * pgstat_count_heap_update - count a tuple update */ +/* + * 功能:更新表更新操作的统计信息 + * + * 参数列表: + * rel:关系的指针 + * hot:是否为热更新 + * + * 注意: + * 此函数用于更新关系的更新操作的统计信息。首先检查关系是否具有 PgStat_TableStatus, + * 如果是,则获取当前事务的嵌套级别。然后,检查事务状态是否已创建或者是否与当前事务的嵌套级别不一致, + * 如果是,则创建新的事务状态。最后,更新事务状态中的更新元组数量以及热更新计数(如果是热更新)。 + */ void pgstat_count_heap_update(Relation rel, bool hot) { - PgStat_TableStatus* pgstat_info = rel->pgstat_info; - + PgStat_TableStatus *pgstat_info = rel->pgstat_info; + // 如果关系具有 PgStat_TableStatus if (pgstat_info != NULL) { /* We have to log the effect at the proper transactional level */ int nest_level = GetCurrentTransactionNestLevel(); - + // 如果事务状态为空或者事务状态的嵌套级别与当前事务的嵌套级别不一致 if (pgstat_info->trans == NULL || pgstat_info->trans->nest_level != nest_level) + // 创建新的事务状态 add_tabstat_xact_level(pgstat_info, nest_level); - + // 更新事务状态中的更新元组数量 pgstat_info->trans->tuples_updated++; pgstat_info->trans->tuples_updated_accum++; - /* t_tuples_hot_updated is nontransactional, so just advance it */ + // 如果是热更新,增加热更新计数 if (hot) pgstat_info->t_counts.t_tuples_hot_updated++; } @@ -2090,17 +2384,31 @@ void pgstat_count_heap_update(Relation rel, bool hot) /* * pgstat_count_heap_delete - count a tuple deletion */ +/* + * 功能:更新表删除操作的统计信息 + * + * 参数列表: + * rel:关系的指针 + * + * 注意: + * 此函数用于更新关系的删除操作的统计信息。首先检查关系是否具有 PgStat_TableStatus, + * 如果是,则获取当前事务的嵌套级别。然后,检查事务状态是否已创建或者是否与当前事务的嵌套级别不一致, + * 如果是,则创建新的事务状态。最后,更新事务状态中的删除元组数量。 + */ void pgstat_count_heap_delete(Relation rel) { - PgStat_TableStatus* pgstat_info = rel->pgstat_info; + PgStat_TableStatus *pgstat_info = rel->pgstat_info; + // 如果关系具有 PgStat_TableStatus if (pgstat_info != NULL) { + // 获取当前事务的嵌套级别 /* We have to log the effect at the proper transactional level */ int nest_level = GetCurrentTransactionNestLevel(); - + // 如果事务状态为空或者事务状态的嵌套级别与当前事务的嵌套级别不一致 if (pgstat_info->trans == NULL || pgstat_info->trans->nest_level != nest_level) + // 创建新的事务状态 add_tabstat_xact_level(pgstat_info, nest_level); - + // 更新事务状态中的删除元组数量 pgstat_info->trans->tuples_deleted++; pgstat_info->trans->tuples_deleted_accum++; } @@ -2114,13 +2422,30 @@ void pgstat_count_heap_delete(Relation rel) * the counters can be restored to the saved (pre-truncate) values. Note we do * this on the first truncate in any particular subxact level only. */ -static void pgstat_truncate_save_counters(PgStat_TableXactStatus* trans) +/* + * 功能:保存截断前的统计计数器值 + * + * 参数列表: + * trans:表事务状态的指针 + * + * 注意: + * 此函数用于保存截断前的统计计数器值。如果表事务状态的 "truncated" 字段为假, + * 则将以下统计计数器值保存在事务状态中,并将 "truncated" 字段设置为真: + * - 插入元组数量 (inserted_pre_trunc) + * - 更新元组数量 (updated_pre_trunc) + * - 删除元组数量 (deleted_pre_trunc) + * - 原地更新元组数量 (inplace_updated_pre_trunc) + */ +static void pgstat_truncate_save_counters(PgStat_TableXactStatus *trans) { + // 如果事务状态的 "truncated" 字段为假 if (!trans->truncated) { + // 保存统计计数器值 trans->inserted_pre_trunc = trans->tuples_inserted; trans->updated_pre_trunc = trans->tuples_updated; trans->deleted_pre_trunc = trans->tuples_deleted; trans->inplace_updated_pre_trunc = trans->tuples_inplace_updated; + // 设置 "truncated" 字段为真 trans->truncated = true; } } @@ -2128,9 +2453,25 @@ static void pgstat_truncate_save_counters(PgStat_TableXactStatus* trans) /* * pgstat_truncate_restore_counters - restore counters when a truncate aborts */ -static void pgstat_truncate_restore_counters(PgStat_TableXactStatus* trans) +/* + * 功能:恢复保存的截断前的统计计数器值 + * + * 参数列表: + * trans:表事务状态的指针 + * + * 注意: + * 此函数用于恢复之前保存的截断前的统计计数器值。如果表事务状态的 "truncated" 字段为真, + * 则将以下保存的统计计数器值恢复到事务状态中: + * - 插入元组数量 (inserted_pre_trunc) + * - 更新元组数量 (updated_pre_trunc) + * - 删除元组数量 (deleted_pre_trunc) + * - 原地更新元组数量 (inplace_updated_pre_trunc) + */ +static void pgstat_truncate_restore_counters(PgStat_TableXactStatus *trans) { + // 如果事务状态的 "truncated" 字段为真 if (trans->truncated) { + // 恢复保存的统计计数器值 trans->tuples_inserted = trans->inserted_pre_trunc; trans->tuples_updated = trans->updated_pre_trunc; trans->tuples_deleted = trans->deleted_pre_trunc; @@ -2141,9 +2482,22 @@ static void pgstat_truncate_restore_counters(PgStat_TableXactStatus* trans) /* * pgstat_count_truncate - update tuple counters due to truncate */ +/* + * 功能:更新与截断相关的统计计数器值 + * + * 参数列表: + * rel:要截断的关系的指针 + * + * 注意: + * 此函数用于更新与截断相关的统计计数器值。它首先检查关系的 PgStat_TableStatus + * 结构是否存在,并且在当前事务嵌套级别中是否存在 PgStat_TableXactStatus 结构。 + * 如果不存在,则使用 add_tabstat_xact_level 函数创建新的 PgStat_TableXactStatus 结构。 + * 然后,它调用 pgstat_truncate_save_counters 函数保存当前统计计数器值,并将这些值 + * 重置为零。 + */ void pgstat_count_truncate(Relation rel) { - PgStat_TableStatus* pgstat_info = rel->pgstat_info; + PgStat_TableStatus *pgstat_info = rel->pgstat_info; if (pgstat_info != NULL) { /* We have to log the effect at the proper transactional level */ @@ -2152,6 +2506,7 @@ void pgstat_count_truncate(Relation rel) if (pgstat_info->trans == NULL || pgstat_info->trans->nest_level != nest_level) add_tabstat_xact_level(pgstat_info, nest_level); + // 保存当前统计计数器值并将其重置为零 pgstat_truncate_save_counters(pgstat_info->trans); pgstat_info->trans->tuples_inserted = 0; pgstat_info->trans->tuples_updated = 0; @@ -2163,8 +2518,19 @@ void pgstat_count_truncate(Relation rel) /* * pgstat_count_uheap_update - count a tuple update inplace */ -void -PgstatCountHeapUpdateInplace(Relation rel) +/* + * 功能:更新在位更新的堆统计计数器值 + * + * 参数列表: + * rel:要进行在位更新的关系指针 + * + * 注意: + * 此函数用于更新在位更新的堆的统计计数器值。它首先检查关系的 PgStat_TableStatus + * 结构是否存在,并且在当前事务嵌套级别中是否存在 PgStat_TableXactStatus 结构。 + * 如果不存在,则使用 add_tabstat_xact_level 函数创建新的 PgStat_TableXactStatus 结构。 + * 然后,它递增了 tuples_updated 和 tuples_updated_accum 计数器,并增加了 tuples_inplace_updated 计数器。 + */ +void PgstatCountHeapUpdateInplace(Relation rel) { PgStat_TableStatus *pgstat_info = rel->pgstat_info; @@ -2177,9 +2543,11 @@ PgstatCountHeapUpdateInplace(Relation rel) } /* increase, similar to pgstat_count_heap_update */ + // 递增统计计数器 pgstat_info->trans->tuples_updated++; pgstat_info->trans->tuples_updated_accum++; + // 增加在位更新的计数器 pgstat_info->trans->tuples_inplace_updated++; } } @@ -2192,20 +2560,44 @@ PgstatCountHeapUpdateInplace(Relation rel) * rather than increasing, and the change goes straight into the per-table * counter, not into transactional state. */ +/* + * 功能:更新堆的死元组计数器值 + * + * 参数列表: + * rel:要更新死元组计数器的关系指针 + * delta:要增加(或减少,如果为负数)到死元组计数器的值 + * + * 注意: + * 此函数用于更新堆的死元组计数器值。它首先检查关系的 PgStat_TableStatus 结构是否存在。 + * 如果存在,则从该结构中减去 delta 的值,以更新死元组计数器。 + */ void pgstat_update_heap_dead_tuples(Relation rel, int delta) { - PgStat_TableStatus* pgstat_info = rel->pgstat_info; + PgStat_TableStatus *pgstat_info = rel->pgstat_info; - if (pgstat_info != NULL) + if (pgstat_info != NULL) { + // 减去 delta 的值以更新死元组计数器 pgstat_info->t_counts.t_delta_dead_tuples -= delta; + } } /* * pgstat_count_cu/dfs_update - count a cstore/dfs update */ +/* + * 功能:更新 CU 更新计数器值 + * + * 参数列表: + * rel:要更新 CU 更新计数器的关系指针 + * n:要增加到 CU 更新计数器的值 + * + * 注意: + * 此函数用于更新 CU 更新计数器值。它首先检查关系的 PgStat_TableStatus 结构是否存在。 + * 如果存在,则从该结构中减去 n 的值,以更新 CU 更新计数器。 + */ void pgstat_count_cu_update(Relation rel, int n) { - PgStat_TableStatus* pgstat_info = rel->pgstat_info; + PgStat_TableStatus *pgstat_info = rel->pgstat_info; if (pgstat_info != NULL) { /* We have to log the effect at the proper transactional level */ @@ -2223,24 +2615,49 @@ void pgstat_count_cu_update(Relation rel, int n) /* * pgstat_count_cu/dfs_delete - count a cstore/dfs deletion */ +/* + * 功能:更新 CU 删除计数器值 + * + * 参数列表: + * rel:要更新 CU 删除计数器的关系指针 + * n:要增加到 CU 删除计数器的值 + * + * 注意: + * 此函数用于更新 CU 删除计数器值。它首先检查关系的 PgStat_TableStatus 结构是否存在。 + * 如果存在,则从该结构中减去 n 的值,以更新 CU 删除计数器。 + */ void pgstat_count_cu_delete(Relation rel, int n) { - PgStat_TableStatus* pgstat_info = rel->pgstat_info; + // 检查关系的 PgStat_TableStatus 结构是否存在 + PgStat_TableStatus *pgstat_info = rel->pgstat_info; if (pgstat_info != NULL) { + // 获取当前事务嵌套级别 /* We have to log the effect at the proper transactional level */ int nest_level = GetCurrentTransactionNestLevel(); - - if (pgstat_info->trans == NULL || pgstat_info->trans->nest_level != nest_level) + // 如果关系的 PgStat_TableStatus 结构不存在或嵌套级别不匹配,则添加新的嵌套级别 + if (pgstat_info->trans == NULL || pgstat_info->trans->nest_level != nest_level) { add_tabstat_xact_level(pgstat_info, nest_level); + } + // 增加 CU 删除计数器的值,并累积到计数器中 pgstat_info->trans->tuples_deleted += n; pgstat_info->trans->tuples_deleted_accum += n; } } -static inline void init_tuplecount_truncate(PgStat_TableStatus* tabstat) +/* + * 功能:初始化表计数器的截断后统计值 + * + * 参数列表: + * tabstat:要初始化截断后统计值的 PgStat_TableStatus 结构指针 + * + * 注意: + * 此函数用于初始化表计数器的截断后统计值,将各统计项都设置为0。 + */ +static inline void init_tuplecount_truncate(PgStat_TableStatus *tabstat) { + // 初始化表计数器的截断后统计值为0 /* forget live/dead stats seen by backend thus far */ tabstat->t_counts.t_tuples_inserted_post_truncate = 0; tabstat->t_counts.t_tuples_updated_post_truncate = 0; @@ -2258,7 +2675,7 @@ static inline void init_tuplecount_truncate(PgStat_TableStatus* tabstat) */ void AtEOXact_PgStat(bool isCommit) { - PgStat_SubXactStatus* xact_state = NULL; + PgStat_SubXactStatus *xact_state = NULL; /* * Count transaction commit or abort. (We use counters, not just bools, @@ -2285,12 +2702,12 @@ void AtEOXact_PgStat(bool isCommit) */ xact_state = u_sess->stat_cxt.pgStatXactStack; if (xact_state != NULL) { - PgStat_TableXactStatus* trans = NULL; + PgStat_TableXactStatus *trans = NULL; Assert(xact_state->nest_level == 1); Assert(xact_state->prev == NULL); for (trans = xact_state->first; trans != NULL; trans = trans->next) { - PgStat_TableStatus* tabstat = NULL; + PgStat_TableStatus *tabstat = NULL; Assert(trans->nest_level == 1); Assert(trans->upper == NULL); @@ -2346,7 +2763,7 @@ void AtEOXact_PgStat(bool isCommit) */ void AtEOSubXact_PgStat(bool isCommit, int nestDepth) { - PgStat_SubXactStatus* xact_state = NULL; + PgStat_SubXactStatus *xact_state = NULL; /* * Transfer transactional insert/update counts into the next higher @@ -2354,14 +2771,14 @@ void AtEOSubXact_PgStat(bool isCommit, int nestDepth) */ xact_state = u_sess->stat_cxt.pgStatXactStack; if (xact_state != NULL && xact_state->nest_level >= nestDepth) { - PgStat_TableXactStatus* trans = NULL; - PgStat_TableXactStatus* next_trans = NULL; + PgStat_TableXactStatus *trans = NULL; + PgStat_TableXactStatus *next_trans = NULL; /* delink xact_state from stack immediately to simplify reuse case */ u_sess->stat_cxt.pgStatXactStack = xact_state->prev; for (trans = xact_state->first; trans != NULL; trans = next_trans) { - PgStat_TableStatus* tabstat = NULL; + PgStat_TableStatus *tabstat = NULL; next_trans = trans->next; Assert(trans->nest_level == nestDepth); @@ -2399,7 +2816,7 @@ void AtEOSubXact_PgStat(bool isCommit, int nestDepth) * into the parent level, though, and that might mean * pushing a new entry into the u_sess->stat_cxt.pgStatXactStack. */ - PgStat_SubXactStatus* upper_xact_state = NULL; + PgStat_SubXactStatus *upper_xact_state = NULL; upper_xact_state = get_tabstat_stack_level(nestDepth - 1); trans->next = upper_xact_state->first; @@ -2419,7 +2836,7 @@ void AtEOSubXact_PgStat(bool isCommit, int nestDepth) tabstat->t_counts.t_tuples_deleted += trans->tuples_deleted_accum; tabstat->t_counts.t_tuples_inplace_updated += trans->tuples_inplace_updated_accum; /* inserted tuples are dead, deleted tuples are unaffected */ - tabstat->t_counts.t_delta_dead_tuples += + tabstat->t_counts.t_delta_dead_tuples += trans->tuples_inserted + (trans->tuples_updated - trans->tuples_inplace_updated); tabstat->trans = trans->upper; pfree(trans); @@ -2438,16 +2855,16 @@ void AtEOSubXact_PgStat(bool isCommit, int nestDepth) */ void AtPrepare_PgStat(void) { - PgStat_SubXactStatus* xact_state = NULL; + PgStat_SubXactStatus *xact_state = NULL; xact_state = u_sess->stat_cxt.pgStatXactStack; if (xact_state != NULL) { - PgStat_TableXactStatus* trans = NULL; + PgStat_TableXactStatus *trans = NULL; Assert(xact_state->nest_level == 1); Assert(xact_state->prev == NULL); for (trans = xact_state->first; trans != NULL; trans = trans->next) { - PgStat_TableStatus* tabstat = NULL; + PgStat_TableStatus *tabstat = NULL; TwoPhasePgStatRecord record; Assert(trans->nest_level == 1); @@ -2490,7 +2907,7 @@ void AtPrepare_PgStat(void) */ void PostPrepare_PgStat(void) { - PgStat_SubXactStatus* xact_state = NULL; + PgStat_SubXactStatus *xact_state = NULL; /* * We don't bother to free any of the transactional state, since it's all @@ -2498,10 +2915,10 @@ void PostPrepare_PgStat(void) */ xact_state = u_sess->stat_cxt.pgStatXactStack; if (xact_state != NULL) { - PgStat_TableXactStatus* trans = NULL; + PgStat_TableXactStatus *trans = NULL; for (trans = xact_state->first; trans != NULL; trans = trans->next) { - PgStat_TableStatus* tabstat = NULL; + PgStat_TableStatus *tabstat = NULL; tabstat = trans->parent; tabstat->trans = NULL; @@ -2518,10 +2935,10 @@ void PostPrepare_PgStat(void) * * Load the saved counts into our local pgstats state. */ -void pgstat_twophase_postcommit(TransactionId xid, uint16 info, void* recdata, uint32 len) +void pgstat_twophase_postcommit(TransactionId xid, uint16 info, void *recdata, uint32 len) { - TwoPhasePgStatRecord* rec = (TwoPhasePgStatRecord*)recdata; - PgStat_TableStatus* pgstat_info = NULL; + TwoPhasePgStatRecord *rec = (TwoPhasePgStatRecord *)recdata; + PgStat_TableStatus *pgstat_info = NULL; /* Find or create a tabstat entry for the rel */ pgstat_info = get_tabstat_entry(rec->t_id, rec->t_shared, rec->t_statFlag); @@ -2542,7 +2959,7 @@ void pgstat_twophase_postcommit(TransactionId xid, uint16 info, void* recdata, u pgstat_info->t_counts.t_tuples_inplace_updated_post_truncate += rec->tuples_inplace_updated; } pgstat_info->t_counts.t_delta_live_tuples += rec->tuples_inserted - rec->tuples_deleted; - pgstat_info->t_counts.t_delta_dead_tuples += + pgstat_info->t_counts.t_delta_dead_tuples += (rec->tuples_updated - rec->tuples_inplace_updated) + rec->tuples_deleted; pgstat_info->t_counts.t_changed_tuples += rec->tuples_inserted + rec->tuples_updated + rec->tuples_deleted; } @@ -2553,10 +2970,10 @@ void pgstat_twophase_postcommit(TransactionId xid, uint16 info, void* recdata, u * Load the saved counts into our local pgstats state, but treat them * as aborted. */ -void pgstat_twophase_postabort(TransactionId xid, uint16 info, void* recdata, uint32 len) +void pgstat_twophase_postabort(TransactionId xid, uint16 info, void *recdata, uint32 len) { - TwoPhasePgStatRecord* rec = (TwoPhasePgStatRecord*)recdata; - PgStat_TableStatus* pgstat_info = NULL; + TwoPhasePgStatRecord *rec = (TwoPhasePgStatRecord *)recdata; + PgStat_TableStatus *pgstat_info = NULL; /* Find or create a tabstat entry for the rel */ pgstat_info = get_tabstat_entry(rec->t_id, rec->t_shared, rec->t_statFlag); @@ -2585,7 +3002,7 @@ void pgstat_twophase_postabort(TransactionId xid, uint16 info, void* recdata, ui * collector, so the caller is better off to report ZERO instead. * ---------- */ -PgStat_StatDBEntry* pgstat_fetch_stat_dbentry(Oid dbid) +PgStat_StatDBEntry *pgstat_fetch_stat_dbentry(Oid dbid) { /* * If not done for this transaction, read the statistics collector stats @@ -2596,7 +3013,7 @@ PgStat_StatDBEntry* pgstat_fetch_stat_dbentry(Oid dbid) /* * Lookup the requested database; return NULL if not found */ - return (PgStat_StatDBEntry*)hash_search(u_sess->stat_cxt.pgStatDBHash, (void*)&dbid, HASH_FIND, NULL); + return (PgStat_StatDBEntry *)hash_search(u_sess->stat_cxt.pgStatDBHash, (void *)&dbid, HASH_FIND, NULL); } /* ---------- @@ -2608,11 +3025,11 @@ PgStat_StatDBEntry* pgstat_fetch_stat_dbentry(Oid dbid) * collector, so the caller is better off to report ZERO instead. * ---------- */ -PgStat_StatTabEntry* pgstat_fetch_stat_tabentry(PgStat_StatTabKey* tabkey) +PgStat_StatTabEntry *pgstat_fetch_stat_tabentry(PgStat_StatTabKey *tabkey) { Oid dbid; - PgStat_StatDBEntry* dbentry = NULL; - PgStat_StatTabEntry* tabentry = NULL; + PgStat_StatDBEntry *dbentry = NULL; + PgStat_StatTabEntry *tabentry = NULL; /* * If not done for this transaction, read the statistics collector stats @@ -2624,9 +3041,9 @@ PgStat_StatTabEntry* pgstat_fetch_stat_tabentry(PgStat_StatTabKey* tabkey) * Lookup our database, then look in its table hash table. */ dbid = u_sess->proc_cxt.MyDatabaseId; - dbentry = (PgStat_StatDBEntry*)hash_search(u_sess->stat_cxt.pgStatDBHash, (void*)&dbid, HASH_FIND, NULL); + dbentry = (PgStat_StatDBEntry *)hash_search(u_sess->stat_cxt.pgStatDBHash, (void *)&dbid, HASH_FIND, NULL); if (dbentry != NULL && dbentry->tables != NULL) { - tabentry = (PgStat_StatTabEntry*)hash_search(dbentry->tables, (void*)tabkey, HASH_FIND, NULL); + tabentry = (PgStat_StatTabEntry *)hash_search(dbentry->tables, (void *)tabkey, HASH_FIND, NULL); if (tabentry != NULL) return tabentry; } @@ -2635,9 +3052,9 @@ PgStat_StatTabEntry* pgstat_fetch_stat_tabentry(PgStat_StatTabKey* tabkey) * If we didn't find it, maybe it's a shared table. */ dbid = InvalidOid; - dbentry = (PgStat_StatDBEntry*)hash_search(u_sess->stat_cxt.pgStatDBHash, (void*)&dbid, HASH_FIND, NULL); + dbentry = (PgStat_StatDBEntry *)hash_search(u_sess->stat_cxt.pgStatDBHash, (void *)&dbid, HASH_FIND, NULL); if (dbentry != NULL && dbentry->tables != NULL) { - tabentry = (PgStat_StatTabEntry*)hash_search(dbentry->tables, (void*)tabkey, HASH_FIND, NULL); + tabentry = (PgStat_StatTabEntry *)hash_search(dbentry->tables, (void *)tabkey, HASH_FIND, NULL); if (tabentry != NULL) return tabentry; } @@ -2652,10 +3069,10 @@ PgStat_StatTabEntry* pgstat_fetch_stat_tabentry(PgStat_StatTabKey* tabkey) * the collected statistics for one function or NULL. * ---------- */ -PgStat_StatFuncEntry* pgstat_fetch_stat_funcentry(Oid func_id) +PgStat_StatFuncEntry *pgstat_fetch_stat_funcentry(Oid func_id) { - PgStat_StatDBEntry* dbentry = NULL; - PgStat_StatFuncEntry* funcentry = NULL; + PgStat_StatDBEntry *dbentry = NULL; + PgStat_StatFuncEntry *funcentry = NULL; /* load the stats file if needed */ backend_read_statsfile(); @@ -2663,7 +3080,7 @@ PgStat_StatFuncEntry* pgstat_fetch_stat_funcentry(Oid func_id) /* Lookup our database, then find the requested function. */ dbentry = pgstat_fetch_stat_dbentry(u_sess->proc_cxt.MyDatabaseId); if (dbentry != NULL && dbentry->functions != NULL) { - funcentry = (PgStat_StatFuncEntry*)hash_search(dbentry->functions, (void*)&func_id, HASH_FIND, NULL); + funcentry = (PgStat_StatFuncEntry *)hash_search(dbentry->functions, (void *)&func_id, HASH_FIND, NULL); } return funcentry; @@ -2677,7 +3094,7 @@ PgStat_StatFuncEntry* pgstat_fetch_stat_funcentry(Oid func_id) * a pointer to the global statistics struct. * --------- */ -PgStat_GlobalStats* pgstat_fetch_global(void) +PgStat_GlobalStats *pgstat_fetch_global(void) { backend_read_statsfile(); @@ -2688,10 +3105,9 @@ PgStat_GlobalStats* pgstat_fetch_global(void) * Functions for management of the shared-memory PgBackendStatus array * ------------------------------------------------------------ */ -static THR_LOCAL char* BackendNspRelnameBuffer = NULL; - -PgBackendStatus* PgBackendStatusArray = NULL; +static THR_LOCAL char *BackendNspRelnameBuffer = NULL; +PgBackendStatus *PgBackendStatusArray = NULL; THR_LOCAL XLogStatCollect *g_xlog_stat_shared = NULL; @@ -2725,7 +3141,7 @@ void XLogStatShmemInit(void) * a pointer to WaitCountBuffer. * --------- */ -PgStat_WaitCountStatus* pgstat_fetch_waitcount(void) +PgStat_WaitCountStatus *pgstat_fetch_waitcount(void) { return t_thrd.shemem_ptr_cxt.WaitCountBuffer; } @@ -2757,12 +3173,12 @@ Size BackendStatusShmemSize(void) /** * init memory whose size may be large than INT_MAX by calling func memset_s many times */ -static void MemsetHugeSize(char* dest, const Size size, const char c) +static void MemsetHugeSize(char *dest, const Size size, const char c) { Size block_size = 0x40000000; errno_t rc; Size remain_size = size; - char* ptr = dest; + char *ptr = dest; while (remain_size > 0) { if (remain_size < block_size) { block_size = remain_size; @@ -2783,12 +3199,12 @@ void CreateSharedBackendStatus(void) Size size; bool found = false; int i; - char* buffer = NULL; + char *buffer = NULL; errno_t rc; /* Create or attach to the shared array */ size = mul_size(sizeof(PgBackendStatus), BackendStatusArray_size); - t_thrd.shemem_ptr_cxt.BackendStatusArray = (PgBackendStatus*)ShmemInitStruct("Backend Status Array", size, &found); + t_thrd.shemem_ptr_cxt.BackendStatusArray = (PgBackendStatus *)ShmemInitStruct("Backend Status Array", size, &found); if (!found) { /* @@ -2796,7 +3212,7 @@ void CreateSharedBackendStatus(void) * call MemsetHugeSize because the size may be larger than INT_MAX, * when memset_s can only init memory less than INT_MAX. */ - MemsetHugeSize((char*)t_thrd.shemem_ptr_cxt.BackendStatusArray, size, 0); + MemsetHugeSize((char *)t_thrd.shemem_ptr_cxt.BackendStatusArray, size, 0); /* init mutex for full/slow sql */ TimestampTz current_time = GetCurrentTimestamp(); @@ -2810,7 +3226,7 @@ void CreateSharedBackendStatus(void) /* Create or attach to the shared appname buffer */ size = mul_size(NAMEDATALEN, BackendStatusArray_size); t_thrd.shemem_ptr_cxt.BackendAppnameBuffer = - (char*)ShmemInitStruct("Backend Application Name Buffer", size, &found); + (char *)ShmemInitStruct("Backend Application Name Buffer", size, &found); if (!found) { rc = memset_s(t_thrd.shemem_ptr_cxt.BackendAppnameBuffer, size, 0, size); @@ -2827,13 +3243,13 @@ void CreateSharedBackendStatus(void) /* Create or attach to the shared conninfo buffer */ size = mul_size(CONNECTIONINFO_LEN, BackendStatusArray_size); t_thrd.shemem_ptr_cxt.BackendConninfoBuffer = - (char*)ShmemInitStruct("Backend Connection Information Buffer", size, &found); + (char *)ShmemInitStruct("Backend Connection Information Buffer", size, &found); if (!found) { /* call MemsetHugeSize because the size may be larger than INT_MAX, * when memset_s can only init memory less than INT_MAX. */ - MemsetHugeSize((char*)t_thrd.shemem_ptr_cxt.BackendConninfoBuffer, size, 0); + MemsetHugeSize((char *)t_thrd.shemem_ptr_cxt.BackendConninfoBuffer, size, 0); /* Initialize st_appname pointers. */ buffer = t_thrd.shemem_ptr_cxt.BackendConninfoBuffer; @@ -2846,7 +3262,7 @@ void CreateSharedBackendStatus(void) /* Create or attach to the shared client hostname buffer */ size = mul_size(NAMEDATALEN, BackendStatusArray_size); t_thrd.shemem_ptr_cxt.BackendClientHostnameBuffer = - (char*)ShmemInitStruct("Backend Client Host Name Buffer", size, &found); + (char *)ShmemInitStruct("Backend Client Host Name Buffer", size, &found); if (!found) { rc = memset_s(t_thrd.shemem_ptr_cxt.BackendClientHostnameBuffer, size, 0, size); @@ -2864,12 +3280,11 @@ void CreateSharedBackendStatus(void) t_thrd.shemem_ptr_cxt.BackendActivityBufferSize = mul_size(g_instance.attr.attr_common.pgstat_track_activity_query_size, BackendStatusArray_size); t_thrd.shemem_ptr_cxt.BackendActivityBuffer = - (char*)ShmemInitStruct("Backend Activity Buffer", t_thrd.shemem_ptr_cxt.BackendActivityBufferSize, &found); + (char *)ShmemInitStruct("Backend Activity Buffer", t_thrd.shemem_ptr_cxt.BackendActivityBufferSize, &found); if (!found) { /* call MemsetHugeSize instead because the size may be larger than INT_MAX. */ - MemsetHugeSize( - t_thrd.shemem_ptr_cxt.BackendActivityBuffer, t_thrd.shemem_ptr_cxt.BackendActivityBufferSize, 0); + MemsetHugeSize(t_thrd.shemem_ptr_cxt.BackendActivityBuffer, t_thrd.shemem_ptr_cxt.BackendActivityBufferSize, 0); /* Initialize st_activity pointers. */ buffer = t_thrd.shemem_ptr_cxt.BackendActivityBuffer; @@ -2881,7 +3296,8 @@ void CreateSharedBackendStatus(void) /* Create or attach to the shared array */ size = sizeof(PgStat_WaitCountStatus); - t_thrd.shemem_ptr_cxt.WaitCountBuffer = (PgStat_WaitCountStatus*)ShmemInitStruct("Wait Count Buffer", size, &found); + t_thrd.shemem_ptr_cxt.WaitCountBuffer = + (PgStat_WaitCountStatus *)ShmemInitStruct("Wait Count Buffer", size, &found); if (!found) { /* initialize */ rc = memset_s(t_thrd.shemem_ptr_cxt.WaitCountBuffer, size, 0, size); @@ -2890,7 +3306,7 @@ void CreateSharedBackendStatus(void) /* Create or attach to the shared relname buffer */ size = mul_size(NAMEDATALEN * 2, BackendStatusArray_size); - BackendNspRelnameBuffer = (char*)ShmemInitStruct("Backend Namespace Relname Buffer", size, &found); + BackendNspRelnameBuffer = (char *)ShmemInitStruct("Backend Namespace Relname Buffer", size, &found); if (!found) { rc = memset_s(BackendNspRelnameBuffer, size, 0, size); securec_check(rc, "\0", "\0"); @@ -2917,10 +3333,10 @@ static int GetAuxProcStatEntryIndex() return index; } -static const char* REMOTE_CONN_TYPET[REMOTE_CONN_GTM_TOOL + 1] = { - "app", "coordinator", "datanode", "gtm", "gtm_proxy", "inetrnal_tool", "gtm_tool"}; +static const char *REMOTE_CONN_TYPET[REMOTE_CONN_GTM_TOOL + 1] = {"app", "coordinator", "datanode", "gtm", + "gtm_proxy", "inetrnal_tool", "gtm_tool"}; -const char* remote_conn_type_string(int remote_conn_type) +const char *remote_conn_type_string(int remote_conn_type) { Assert(remote_conn_type >= REMOTE_CONN_APP && remote_conn_type <= REMOTE_CONN_GTM_TOOL); return REMOTE_CONN_TYPET[remote_conn_type]; @@ -2945,8 +3361,8 @@ void pgstat_initialize(void) if (t_thrd.proc_cxt.MyBackendId != InvalidBackendId) { Assert(t_thrd.proc_cxt.MyBackendId >= 1 && t_thrd.proc_cxt.MyBackendId <= (g_instance.attr.attr_common.enable_thread_pool - ? GLOBAL_RESERVE_SESSION_NUM - : g_instance.shmem_cxt.MaxBackends)); + ? GLOBAL_RESERVE_SESSION_NUM + : g_instance.shmem_cxt.MaxBackends)); t_thrd.shemem_ptr_cxt.MyBEEntry = &t_thrd.shemem_ptr_cxt.BackendStatusArray[t_thrd.proc_cxt.MyBackendId - 1]; } else { // Auxiliary thread @@ -2978,8 +3394,8 @@ void pgstat_initialize_session(void) Assert(t_thrd.shemem_ptr_cxt.BackendStatusArray); Assert(u_sess->session_ctr_index >= GLOBAL_RESERVE_SESSION_NUM && u_sess->session_ctr_index < MAX_BACKEND_SLOT); Assert(t_thrd.proc_cxt.MyBackendId != InvalidBackendId); - Assert( - t_thrd.shemem_ptr_cxt.MyBEEntry == &t_thrd.shemem_ptr_cxt.BackendStatusArray[t_thrd.proc_cxt.MyBackendId - 1]); + Assert(t_thrd.shemem_ptr_cxt.MyBEEntry == + &t_thrd.shemem_ptr_cxt.BackendStatusArray[t_thrd.proc_cxt.MyBackendId - 1]); /* * pool worker reports being coupled to one session @@ -3045,7 +3461,7 @@ void pgstat_bestart(void) TimestampTz proc_start_timestamp; Oid userid = InvalidOid; SockAddr clientaddr; - volatile PgBackendStatus* beentry = NULL; + volatile PgBackendStatus *beentry = NULL; errno_t rc = 0; /* @@ -3106,7 +3522,7 @@ void pgstat_bestart(void) beentry->st_xact_start_timestamp = 0; beentry->st_databaseid = u_sess->proc_cxt.MyDatabaseId; beentry->st_userid = userid; - rc = memcpy_s((void*)&beentry->st_clientaddr, sizeof(SockAddr), &clientaddr, sizeof(clientaddr)); + rc = memcpy_s((void *)&beentry->st_clientaddr, sizeof(SockAddr), &clientaddr, sizeof(clientaddr)); securec_check(rc, "\0", "\0"); beentry->st_clienthostname[0] = '\0'; beentry->st_state = STATE_UNDEFINED; @@ -3172,44 +3588,37 @@ void pgstat_bestart(void) if (u_sess->proc_cxt.MyProcPort != NULL) { if (u_sess->proc_cxt.MyProcPort->is_logic_conn) { if (u_sess->proc_cxt.MyProcPort->libcomm_addrinfo != NULL) { - rc = memcpy_s((void*)&beentry->remote_info.remote_name, - NAMEDATALEN, - u_sess->proc_cxt.MyProcPort->libcomm_addrinfo->nodename, - NAMEDATALEN); + rc = memcpy_s((void *)&beentry->remote_info.remote_name, NAMEDATALEN, + u_sess->proc_cxt.MyProcPort->libcomm_addrinfo->nodename, NAMEDATALEN); securec_check(rc, "\0", "\0"); - rc = memcpy_s((void*)&beentry->remote_info.remote_ip, - MAX_IP_STR_LEN, - u_sess->proc_cxt.MyProcPort->libcomm_addrinfo->host, - strlen(u_sess->proc_cxt.MyProcPort->libcomm_addrinfo->host) + 1); + rc = memcpy_s((void *)&beentry->remote_info.remote_ip, MAX_IP_STR_LEN, + u_sess->proc_cxt.MyProcPort->libcomm_addrinfo->host, + strlen(u_sess->proc_cxt.MyProcPort->libcomm_addrinfo->host) + 1); securec_check(rc, "\0", "\0"); - rc = memcpy_s((void*)&beentry->remote_info.remote_port, - MAX_PORT_LEN, - &(u_sess->proc_cxt.MyProcPort->libcomm_addrinfo->listen_port), - MAX_PORT_LEN); + rc = memcpy_s((void *)&beentry->remote_info.remote_port, MAX_PORT_LEN, + &(u_sess->proc_cxt.MyProcPort->libcomm_addrinfo->listen_port), MAX_PORT_LEN); securec_check(rc, "\0", "\0"); beentry->remote_info.socket = -1; beentry->remote_info.logic_id = u_sess->proc_cxt.MyProcPort->gs_sock.sid; } } else { if (u_sess->proc_cxt.MyProcPort->sock >= 0) { - const char* remote_node = remote_conn_type_string(u_sess->attr.attr_common.remoteConnType); - rc = memcpy_s( - (void*)&beentry->remote_info.remote_name, NAMEDATALEN, remote_node, strlen(remote_node) + 1); + const char *remote_node = remote_conn_type_string(u_sess->attr.attr_common.remoteConnType); + rc = memcpy_s((void *)&beentry->remote_info.remote_name, NAMEDATALEN, remote_node, + strlen(remote_node) + 1); securec_check(rc, "\0", "\0"); if (u_sess->proc_cxt.MyProcPort->remote_host != NULL) { - rc = memcpy_s((void*)&beentry->remote_info.remote_ip, - MAX_IP_STR_LEN, - u_sess->proc_cxt.MyProcPort->remote_host, - strlen(u_sess->proc_cxt.MyProcPort->remote_host) + 1); + rc = memcpy_s((void *)&beentry->remote_info.remote_ip, MAX_IP_STR_LEN, + u_sess->proc_cxt.MyProcPort->remote_host, + strlen(u_sess->proc_cxt.MyProcPort->remote_host) + 1); securec_check(rc, "\0", "\0"); } if (u_sess->proc_cxt.MyProcPort->remote_port != NULL && u_sess->proc_cxt.MyProcPort->remote_port[0] != '\0') { - rc = memcpy_s((void*)&beentry->remote_info.remote_port, - MAX_PORT_LEN, - u_sess->proc_cxt.MyProcPort->remote_port, - strlen(u_sess->proc_cxt.MyProcPort->remote_port) + 1); + rc = memcpy_s((void *)&beentry->remote_info.remote_port, MAX_PORT_LEN, + u_sess->proc_cxt.MyProcPort->remote_port, + strlen(u_sess->proc_cxt.MyProcPort->remote_port) + 1); securec_check(rc, "\0", "\0"); } @@ -3238,7 +3647,7 @@ void pgstat_bestart(void) * only if light-weight lock information is never accessed by other backends, * this backend can exit. */ -static void WaitUntilLWLockInfoNeverAccess(volatile PgBackendStatus* backendEntry) +static void WaitUntilLWLockInfoNeverAccess(volatile PgBackendStatus *backendEntry) { START_CRIT_SECTION(); @@ -3266,7 +3675,7 @@ static void WaitUntilLWLockInfoNeverAccess(volatile PgBackendStatus* backendEntr */ void pgstat_couple_decouple_session(bool is_couple) { - volatile PgBackendStatus* beentry = NULL; + volatile PgBackendStatus *beentry = NULL; Assert(u_sess->session_id != 0); /* @@ -3305,7 +3714,7 @@ void pgstat_couple_decouple_session(bool is_couple) */ static void pgstat_beshutdown_hook(int code, Datum arg) { - volatile PgBackendStatus* beentry = t_thrd.shemem_ptr_cxt.MyBEEntry; + volatile PgBackendStatus *beentry = t_thrd.shemem_ptr_cxt.MyBEEntry; /* * If we got as far as discovering our own database ID, we can report what @@ -3351,12 +3760,12 @@ static void pgstat_beshutdown_hook(int code, Datum arg) */ if (IS_THREAD_POOL_WORKER && u_sess->session_ctr_index >= GLOBAL_RESERVE_SESSION_NUM && u_sess->session_ctr_index < MAX_BACKEND_SLOT) { - release_statement_context(&t_thrd.shemem_ptr_cxt.BackendStatusArray[u_sess->session_ctr_index], - __FUNCTION__, __LINE__); + release_statement_context(&t_thrd.shemem_ptr_cxt.BackendStatusArray[u_sess->session_ctr_index], __FUNCTION__, + __LINE__); } if (t_thrd.proc_cxt.MyBackendId != InvalidBackendId) { release_statement_context(&t_thrd.shemem_ptr_cxt.BackendStatusArray[t_thrd.proc_cxt.MyBackendId - 1], - __FUNCTION__, __LINE__); + __FUNCTION__, __LINE__); } else { release_statement_context(t_thrd.shemem_ptr_cxt.MyBEEntry, __FUNCTION__, __LINE__); } @@ -3381,7 +3790,7 @@ void pgstat_beshutdown_session(int ctrl_index) { /* proc_exit already release the slot of MyBeEntry, cannot release again */ if (!t_thrd.proc_cxt.proc_exit_inprogress) { - volatile PgBackendStatus* beentry = &t_thrd.shemem_ptr_cxt.BackendStatusArray[ctrl_index]; + volatile PgBackendStatus *beentry = &t_thrd.shemem_ptr_cxt.BackendStatusArray[ctrl_index]; /* * If we got as far as discovering our own database ID, we can report what @@ -3420,7 +3829,7 @@ void pgstat_beshutdown_session(int ctrl_index) * if t_thrd.proc_cxt.proc_exit_inprogress is true, thread backend entry and * session backend entry can be reused by other backend(CleanupInvalidationState * is called before), so backend entry cann't be accessed during this stage. - * when false, just case that thread pool worker is detaching or closing session, + * when false, just case that thread pool worker is detaching or closing session, * so we need to release statemement context. */ release_statement_context(&t_thrd.shemem_ptr_cxt.BackendStatusArray[ctrl_index], __FUNCTION__, __LINE__); @@ -3476,9 +3885,9 @@ void pgstat_beshutdown_session(int ctrl_index) * ensure the compiler doesn't try to get cute. * ---------- */ -void pgstat_report_activity(BackendState state, const char* cmd_str) +void pgstat_report_activity(BackendState state, const char *cmd_str) { - volatile PgBackendStatus* beentry = t_thrd.shemem_ptr_cxt.MyBEEntry; + volatile PgBackendStatus *beentry = t_thrd.shemem_ptr_cxt.MyBEEntry; TimestampTz start_timestamp; TimestampTz current_timestamp; int len = 0; @@ -3541,16 +3950,16 @@ void pgstat_report_activity(BackendState state, const char* cmd_str) /* If mask successfully, store the mask_string. Otherwise, the cmd_str is recorded. */ if (mask_string == NULL) { - rc = memcpy_s((char*)beentry->st_activity, g_instance.attr.attr_common.pgstat_track_activity_query_size, - cmd_str, len); + rc = memcpy_s((char *)beentry->st_activity, g_instance.attr.attr_common.pgstat_track_activity_query_size, + cmd_str, len); securec_check(rc, "\0", "\0"); } else { int copy_len = strlen(mask_string); if (len < copy_len) { copy_len = len; } - rc = memcpy_s((char*)beentry->st_activity, g_instance.attr.attr_common.pgstat_track_activity_query_size, - mask_string, copy_len); + rc = memcpy_s((char *)beentry->st_activity, g_instance.attr.attr_common.pgstat_track_activity_query_size, + mask_string, copy_len); securec_check(rc, "\0", "\0"); pfree(mask_string); } @@ -3568,9 +3977,9 @@ void pgstat_report_activity(BackendState state, const char* cmd_str) * Called to update our application name. * ---------- */ -void pgstat_report_appname(const char* appname) +void pgstat_report_appname(const char *appname) { - volatile PgBackendStatus* beentry = t_thrd.shemem_ptr_cxt.MyBEEntry; + volatile PgBackendStatus *beentry = t_thrd.shemem_ptr_cxt.MyBEEntry; int len; errno_t rc; @@ -3588,7 +3997,7 @@ void pgstat_report_appname(const char* appname) pgstat_increment_changecount_before(beentry); if (len > 0) { - rc = memcpy_s((char*)beentry->st_appname, len, appname, len); + rc = memcpy_s((char *)beentry->st_appname, len, appname, len); securec_check(rc, "\0", "\0"); } @@ -3603,9 +4012,9 @@ void pgstat_report_appname(const char* appname) * Called to update our connection info. * ---------- */ -void pgstat_report_conninfo(const char* conninfo) +void pgstat_report_conninfo(const char *conninfo) { - volatile PgBackendStatus* beentry = t_thrd.shemem_ptr_cxt.MyBEEntry; + volatile PgBackendStatus *beentry = t_thrd.shemem_ptr_cxt.MyBEEntry; int len; errno_t rc; @@ -3623,7 +4032,7 @@ void pgstat_report_conninfo(const char* conninfo) pgstat_increment_changecount_before(beentry); if (len > 0) { - rc = memcpy_s((char*)beentry->st_conninfo, len, conninfo, len); + rc = memcpy_s((char *)beentry->st_conninfo, len, conninfo, len); securec_check(rc, "\0", "\0"); } @@ -3638,7 +4047,7 @@ void pgstat_report_conninfo(const char* conninfo) */ void pgstat_report_xact_timestamp(TimestampTz tstamp) { - volatile PgBackendStatus* beentry = t_thrd.shemem_ptr_cxt.MyBEEntry; + volatile PgBackendStatus *beentry = t_thrd.shemem_ptr_cxt.MyBEEntry; if (!u_sess->attr.attr_common.pgstat_track_activities || (beentry == NULL)) return; @@ -3653,43 +4062,81 @@ void pgstat_report_xact_timestamp(TimestampTz tstamp) pgstat_increment_changecount_after(beentry); } +/* + * 功能:汇报全局会话ID + * + * 参数列表: + * globalSessionId:全局会话ID + * + * 注意: + * 此函数用于汇报全局会话ID,但仅在启用多节点模式且会话ID不为0时才执行。 + * 首先,获取当前后端进程的状态信息,然后更新其中的全局会话ID信息。 + */ void pgstat_report_global_session_id(GlobalSessionId globalSessionId) { #ifndef ENABLE_MULTIPLE_NODES - return; + return; // 不启用多节点模式,直接返回 #endif - volatile PgBackendStatus* beentry = t_thrd.shemem_ptr_cxt.MyBEEntry; - + volatile PgBackendStatus *beentry = t_thrd.shemem_ptr_cxt.MyBEEntry; + // 如果未定义pgstate跟踪或会话ID为0,则直接返回 if (IS_PGSTATE_TRACK_UNDEFINE || globalSessionId.sessionId == 0) return; + // 在更新全局会话ID之前,增加状态信息的更改计数器 pgstat_increment_changecount_before(beentry); + // 更新状态信息中的全局会话ID信息 beentry->globalSessionId.sessionId = globalSessionId.sessionId; beentry->globalSessionId.nodeId = globalSessionId.nodeId; beentry->globalSessionId.seq = globalSessionId.seq; + // 在更新全局会话ID之后,增加状态信息的更改计数器 pgstat_increment_changecount_after(beentry); } +/* + * 功能:汇报唯一SQL标识 + * + * 参数列表: + * resetUniqueSql:是否重置唯一SQL标识 + * + * 注意: + * 此函数用于汇报唯一SQL标识,但仅在pgstate跟踪被定义时执行。 + * 根据resetUniqueSql参数的值,可以选择重置或更新唯一SQL标识。 + * 如果resetUniqueSql为真,则将唯一SQL标识重置为0。 + * 否则,将唯一SQL标识设置为当前会话的唯一SQL信息。 + */ void pgstat_report_unique_sql_id(bool resetUniqueSql) { - volatile PgBackendStatus* beentry = t_thrd.shemem_ptr_cxt.MyBEEntry; - + volatile PgBackendStatus *beentry = t_thrd.shemem_ptr_cxt.MyBEEntry; + // 如果pgstate跟踪未定义,则直接返回 if (IS_PGSTATE_TRACK_UNDEFINE) return; if (resetUniqueSql) { + // 如果需要重置唯一SQL标识,则将其设置为0 beentry->st_unique_sql_key.unique_sql_id = 0; beentry->st_unique_sql_key.cn_id = 0; beentry->st_unique_sql_key.user_id = 0; } else { + // 否则,将唯一SQL标识设置为当前会话的唯一SQL信息 beentry->st_unique_sql_key.unique_sql_id = u_sess->unique_sql_cxt.unique_sql_id; beentry->st_unique_sql_key.cn_id = u_sess->unique_sql_cxt.unique_sql_cn_id; beentry->st_unique_sql_key.user_id = u_sess->unique_sql_cxt.unique_sql_user_id; } } +/* + * 功能:汇报查询ID + * + * 参数列表: + * queryid:要汇报的查询ID + * + * 注意: + * 此函数用于汇报查询ID,但仅在pgstate跟踪被定义时执行。 + * 将查询ID设置为指定的queryid值。 + */ void pgstat_report_queryid(uint64 queryid) { - volatile PgBackendStatus* beentry = t_thrd.shemem_ptr_cxt.MyBEEntry; + volatile PgBackendStatus *beentry = t_thrd.shemem_ptr_cxt.MyBEEntry; + // 如果pgstate跟踪未定义,则直接返回 if (IS_PGSTATE_TRACK_UNDEFINE) return; @@ -3698,39 +4145,76 @@ void pgstat_report_queryid(uint64 queryid) * may modify, there seems no need to bother with the st_changecount * protocol. The update must appear atomic in any case. */ + // 设置查询ID为指定的queryid值 beentry->st_queryid = queryid; } +/* + * 功能:汇报追踪ID + * + * 参数列表: + * trace_cxt:追踪上下文 + * is_report_trace_id:是否要汇报追踪ID + * + * 注意: + * 此函数用于汇报追踪ID,但仅在pgstate跟踪被定义时执行。 + * 如果is_report_trace_id为true,则将追踪ID设置为trace_cxt中的值。 + */ void pgstat_report_trace_id(knl_u_trace_context *trace_cxt, bool is_report_trace_id) { - volatile PgBackendStatus* beentry = t_thrd.shemem_ptr_cxt.MyBEEntry; + volatile PgBackendStatus *beentry = t_thrd.shemem_ptr_cxt.MyBEEntry; + // 如果pgstate跟踪未定义,则直接返回 if (IS_PGSTATE_TRACK_UNDEFINE) return; + // 如果is_report_trace_id为true,则将追踪ID设置为trace_cxt中的值 if (is_report_trace_id) { - errno_t rc = - memcpy_s((void*)beentry->trace_cxt.trace_id, MAX_TRACE_ID_SIZE, trace_cxt->trace_id, - strlen(trace_cxt->trace_id) + 1); + errno_t rc = memcpy_s((void *)beentry->trace_cxt.trace_id, MAX_TRACE_ID_SIZE, trace_cxt->trace_id, + strlen(trace_cxt->trace_id) + 1); securec_check(rc, "\0", "\0"); } } +/* + * 功能:汇报作业ID + * + * 参数列表: + * jobid:作业ID + * + * 注意: + * 此函数用于汇报作业ID,但仅在pgstate跟踪被定义时执行。 + * 将作业ID设置为当前后端进程的作业ID。 + */ void pgstat_report_jobid(uint64 jobid) { - volatile PgBackendStatus* beentry = t_thrd.shemem_ptr_cxt.MyBEEntry; + volatile PgBackendStatus *beentry = t_thrd.shemem_ptr_cxt.MyBEEntry; + // 如果pgstate跟踪未定义,则直接返回 if (IS_PGSTATE_TRACK_UNDEFINE) return; /* * Update jobid when it starts */ + // 将作业ID设置为当前后端进程的作业ID beentry->st_jobid = jobid; } +/* + * 功能:汇报父会话ID和线程级别 + * + * 参数列表: + * sessionid:会话ID + * level:线程级别 + * + * 注意: + * 此函数用于汇报父会话ID和线程级别,但仅在pgstate跟踪被定义时执行。 + * 设置当前后端进程的会话ID、父会话ID和线程级别。 + */ void pgstat_report_parent_sessionid(uint64 sessionid, uint32 level) { - volatile PgBackendStatus* beentry = t_thrd.shemem_ptr_cxt.MyBEEntry; + volatile PgBackendStatus *beentry = t_thrd.shemem_ptr_cxt.MyBEEntry; + // 如果pgstate跟踪未定义,则直接返回 if (IS_PGSTATE_TRACK_UNDEFINE) return; @@ -3739,6 +4223,7 @@ void pgstat_report_parent_sessionid(uint64 sessionid, uint32 level) * may modify, there seems no need to bother with the st_changecount * protocol. The update must appear atomic in any case. */ + // 设置当前后端进程的会话ID、父会话ID和线程级别 beentry->st_sessionid = sessionid; beentry->st_parent_sessionid = sessionid; beentry->st_thread_level = level; @@ -3756,7 +4241,7 @@ void pgstat_report_parent_sessionid(uint64 sessionid, uint32 level) */ void pgstat_report_connected_gtm_host(GtmHostIndex gtm_host) { - volatile PgBackendStatus* beentry = t_thrd.shemem_ptr_cxt.MyBEEntry; + volatile PgBackendStatus *beentry = t_thrd.shemem_ptr_cxt.MyBEEntry; if (beentry == NULL) return; @@ -3784,7 +4269,7 @@ void pgstat_report_connected_gtm_host(GtmHostIndex gtm_host) */ void pgstat_report_connected_gtm_timeline(GTM_Timeline gtm_timeline) { - volatile PgBackendStatus* beentry = t_thrd.shemem_ptr_cxt.MyBEEntry; + volatile PgBackendStatus *beentry = t_thrd.shemem_ptr_cxt.MyBEEntry; if (beentry == NULL) return; @@ -3799,10 +4284,21 @@ void pgstat_report_connected_gtm_timeline(GTM_Timeline gtm_timeline) beentry->st_gtmtimeline = gtm_timeline; } +/* + * 功能:汇报SMP ID + * + * 参数列表: + * smpid:SMP ID + * + * 注意: + * 此函数用于汇报SMP ID,但仅在pgstate跟踪被定义时执行。 + * 设置当前后端进程的SMP ID。 + */ void pgstat_report_smpid(uint32 smpid) { - volatile PgBackendStatus* beentry = t_thrd.shemem_ptr_cxt.MyBEEntry; + volatile PgBackendStatus *beentry = t_thrd.shemem_ptr_cxt.MyBEEntry; + // 如果pgstate跟踪未定义,则直接返回 if (IS_PGSTATE_TRACK_UNDEFINE) return; @@ -3811,32 +4307,42 @@ void pgstat_report_smpid(uint32 smpid) * may modify, there seems no need to bother with the st_changecount * protocol. The update must appear atomic in any case. */ + // 设置当前后端进程的SMP ID beentry->st_smpid = smpid; } /* * report blocking session into waitLockThrd's PgBackendStatus */ -void pgstat_report_blocksid(void* waitLockThrd, uint64 blockSessionId) +/* + * 功能:汇报锁阻塞的会话ID + * + * 参数列表: + * waitLockThrd:等待锁的线程指针 + * blockSessionId:阻塞的会话ID + * + * 注意: + * 此函数用于汇报锁阻塞的会话ID,但仅在pgstate跟踪被定义时执行。 + * 设置线程的阻塞会话ID,用于跟踪锁的等待情况。 + */ +void pgstat_report_blocksid(void *waitLockThrd, uint64 blockSessionId) { + // 如果pgstate跟踪未定义,则直接返回 if (IS_PGSTATE_TRACK_UNDEFINE) return; - knl_thrd_context* thrd = (knl_thrd_context*)waitLockThrd; - volatile PgBackendStatus* beentry = thrd->shemem_ptr_cxt.MyBEEntry; + knl_thrd_context *thrd = (knl_thrd_context *)waitLockThrd; + volatile PgBackendStatus *beentry = thrd->shemem_ptr_cxt.MyBEEntry; if (beentry->st_block_sessionid != blockSessionId) { /* dont print lock info while wait ends. Moreover waitLock is not access at this time. */ if (blockSessionId == 0) { - ereport(DEBUG1, - (errmsg("thread %lu waiting for lock ends", thrd->proc_cxt.MyProcPid))); + ereport(DEBUG1, (errmsg("thread %lu waiting for lock ends", thrd->proc_cxt.MyProcPid))); } else if (thrd->proc->waitLock != NULL) { ereport(DEBUG1, - (errmsg("thread %lu waiting for %s on %s, blocking session %lu", - thrd->proc_cxt.MyProcPid, - GetLockmodeName(thrd->proc->waitLock->tag.locktag_lockmethodid, thrd->proc->waitLockMode), - LocktagToString(thrd->proc->waitLock->tag), - blockSessionId))); + (errmsg("thread %lu waiting for %s on %s, blocking session %lu", thrd->proc_cxt.MyProcPid, + GetLockmodeName(thrd->proc->waitLock->tag.locktag_lockmethodid, thrd->proc->waitLockMode), + LocktagToString(thrd->proc->waitLock->tag), blockSessionId))); } /* @@ -3852,23 +4358,46 @@ void pgstat_report_blocksid(void* waitLockThrd, uint64 blockSessionId) * updateMaxValueForAtomicType - using atomic type to store max value, * we need update the max value by using atomic method */ -static void updateMaxValueForAtomicType(uint64 new_val, uint64* max) +/* + * 功能:原子地更新最大值 + * + * 参数列表: + * new_val:新的值 + * max:当前最大值的指针 + * + * 注意: + * 此函数通过原子操作来更新最大值。它首先获取当前的最大值, + * 然后与新的值比较,如果新的值更大且替换成功,就更新最大值。 + */ +static void updateMaxValueForAtomicType(uint64 new_val, uint64 *max) { - uint64 prev; - do - prev = *max; - while (prev < new_val && !pg_atomic_compare_exchange_u64(max, &prev, new_val)); + uint64 prev; // 用于存储前一个最大值 + do { + prev = *max; // 获取当前的最大值 + } while (prev < new_val && !pg_atomic_compare_exchange_u64(max, &prev, new_val)); // 循环直到更新成功 } /* * updateMinValueForAtomicType - update ming value for atomic type */ -static void updateMinValueForAtomicType(uint64 new_val, uint64* mix) +/* + * 功能:原子地更新最小值 + * + * 参数列表: + * new_val:新的值 + * mix:当前最小值的指针 + * + * 注意: + * 此函数通过原子操作来更新最小值。它首先获取当前的最小值, + * 然后与新的值比较,如果新的值更小且替换成功,就更新最小值。 + */ +static void updateMinValueForAtomicType(uint64 new_val, uint64 *mix) { - uint64 prev; - do - prev = *mix; - while ((prev == 0 || prev > new_val) && !pg_atomic_compare_exchange_u64(mix, &prev, new_val)); + uint64 prev; // 用于存储前一个最小值 + do { + prev = *mix; // 获取当前的最小值 + } while ((prev == 0 || prev > new_val) && + !pg_atomic_compare_exchange_u64(mix, &prev, new_val)); // 循环直到更新成功 } /* @@ -3883,8 +4412,8 @@ static void RemoveWaitCount(Oid userId) } /* remove user from WaitCountHashTbl */ - WaitCountHashValue* waitCountIdx = - (WaitCountHashValue*)hash_search(g_instance.stat_cxt.WaitCountHashTbl, &userId, HASH_REMOVE, NULL); + WaitCountHashValue *waitCountIdx = + (WaitCountHashValue *)hash_search(g_instance.stat_cxt.WaitCountHashTbl, &userId, HASH_REMOVE, NULL); if (waitCountIdx == NULL) { return; } @@ -3892,24 +4421,37 @@ static void RemoveWaitCount(Oid userId) /* reset the location in WaitCountStatusList */ int dataId = waitCountIdx->idx % WAIT_COUNT_ARRAY_SIZE; int listNodeId = waitCountIdx->idx / WAIT_COUNT_ARRAY_SIZE; - ListCell* lc = list_nth_cell(g_instance.stat_cxt.WaitCountStatusList, listNodeId); - PgStat_WaitCountStatusCell* waitCountStatusCell = (PgStat_WaitCountStatusCell*)lfirst(lc); + ListCell *lc = list_nth_cell(g_instance.stat_cxt.WaitCountStatusList, listNodeId); + PgStat_WaitCountStatusCell *waitCountStatusCell = (PgStat_WaitCountStatusCell *)lfirst(lc); pg_atomic_write_u32(&waitCountStatusCell->WaitCountArray[dataId].userid, 0); } +/* + * 功能:检查指定的认证ID是否有效 + * + * 参数列表: + * authid:要检查的认证ID + * + * 返回值: + * 如果认证ID有效,返回true;否则返回false。 + * + * 注意: + * 此函数在系统缓存中搜索给定的认证ID。如果找到对应的元组, + * 表示认证ID有效,然后释放系统缓存并返回true,否则返回false。 + */ bool pg_check_authid(Oid authid) { - HeapTuple roletup = NULL; - + HeapTuple roletup = NULL; // 用于存储认证ID对应的元组 /* * Get the pg_authid entry and print the result */ + // 在系统缓存中搜索给定的认证ID roletup = SearchSysCache1(AUTHOID, ObjectIdGetDatum(authid)); if (HeapTupleIsValid(roletup)) { - ReleaseSysCache(roletup); - return true; + ReleaseSysCache(roletup); // 释放系统缓存 + return true; // 认证ID有效,返回true } else { - return false; + return false; // 认证ID无效,返回false } } @@ -3936,14 +4478,14 @@ bool CheckUserExist(Oid userId, bool removeCount) } return isExist; } -#define UPDATE_SQL_COUNT(count, elapseTime) \ - do { \ - TimestampTz duration = GetCurrentTimestamp() - GetCurrentStatementLocalStartTimestamp(); \ - duration = (duration == 0) ? 1 : duration; \ - pg_atomic_fetch_add_u64(&(count), 1); \ - pg_atomic_fetch_add_u64(&((elapseTime).total_time), duration); \ - updateMaxValueForAtomicType(duration, &((elapseTime).max_time)); \ - updateMinValueForAtomicType(duration, &((elapseTime).min_time)); \ +#define UPDATE_SQL_COUNT(count, elapseTime) \ + do { \ + TimestampTz duration = GetCurrentTimestamp() - GetCurrentStatementLocalStartTimestamp(); \ + duration = (duration == 0) ? 1 : duration; \ + pg_atomic_fetch_add_u64(&(count), 1); \ + pg_atomic_fetch_add_u64(&((elapseTime).total_time), duration); \ + updateMaxValueForAtomicType(duration, &((elapseTime).max_time)); \ + updateMinValueForAtomicType(duration, &((elapseTime).min_time)); \ } while (0) /* * @Description: according to wait_event_info to add sql count for user, @@ -3954,7 +4496,7 @@ bool CheckUserExist(Oid userId, bool removeCount) void pgstat_report_wait_count(unsigned int wait_event_info) { Oid userid; - WaitCountHashValue* WaitCountIdx = NULL; + WaitCountHashValue *WaitCountIdx = NULL; int dataid; int listNodeid; uint32 classId = wait_event_info & 0xFF000000; @@ -3968,11 +4510,11 @@ void pgstat_report_wait_count(unsigned int wait_event_info) } userid = GetUserId(); - WaitCountIdx = (WaitCountHashValue*)hash_search(g_instance.stat_cxt.WaitCountHashTbl, &userid, HASH_FIND, NULL); + WaitCountIdx = (WaitCountHashValue *)hash_search(g_instance.stat_cxt.WaitCountHashTbl, &userid, HASH_FIND, NULL); /* check if find the user in hashtable */ if (WaitCountIdx == NULL) { - ereport(LOG, - (errcode(ERRCODE_WARNING), (errmsg("can not find the user in sql count hashtable: userid %u", userid)))); + ereport(LOG, (errcode(ERRCODE_WARNING), + (errmsg("can not find the user in sql count hashtable: userid %u", userid)))); LWLockRelease(WaitCountHashLock); /* return if user does not exist */ @@ -3981,20 +4523,21 @@ void pgstat_report_wait_count(unsigned int wait_event_info) } LWLockAcquire(WaitCountHashLock, LW_EXCLUSIVE); - WaitCountIdx = (WaitCountHashValue*)hash_search(g_instance.stat_cxt.WaitCountHashTbl, &userid, HASH_FIND, NULL); + WaitCountIdx = + (WaitCountHashValue *)hash_search(g_instance.stat_cxt.WaitCountHashTbl, &userid, HASH_FIND, NULL); if (WaitCountIdx == NULL) { initWaitCount(userid); - ereport(LOG, - (errcode(ERRCODE_WARNING), - (errmsg("success to insert user into WaitCountHashTbl: userid %u!", userid)))); + ereport(LOG, (errcode(ERRCODE_WARNING), + (errmsg("success to insert user into WaitCountHashTbl: userid %u!", userid)))); } LWLockRelease(WaitCountHashLock); LWLockAcquire(WaitCountHashLock, LW_SHARED); - WaitCountIdx = (WaitCountHashValue*)hash_search(g_instance.stat_cxt.WaitCountHashTbl, &userid, HASH_FIND, NULL); + WaitCountIdx = + (WaitCountHashValue *)hash_search(g_instance.stat_cxt.WaitCountHashTbl, &userid, HASH_FIND, NULL); if (WaitCountIdx == NULL) { ereport(WARNING, - (errcode(ERRCODE_WARNING), - (errmsg("failed to insert user into WaitCountHashTbl: userid %u!, sql count failed!", userid)))); + (errcode(ERRCODE_WARNING), + (errmsg("failed to insert user into WaitCountHashTbl: userid %u!, sql count failed!", userid)))); LWLockRelease(WaitCountHashLock); return; } @@ -4003,11 +4546,11 @@ void pgstat_report_wait_count(unsigned int wait_event_info) /* Get the location of the user in g_instance.stat_cxt.WaitCountStatusList */ dataid = WaitCountIdx->idx % WAIT_COUNT_ARRAY_SIZE; listNodeid = WaitCountIdx->idx / WAIT_COUNT_ARRAY_SIZE; - ListCell* lc = NULL; - PgStat_WaitCountStatusCell* WaitCountStatusCell = NULL; + ListCell *lc = NULL; + PgStat_WaitCountStatusCell *WaitCountStatusCell = NULL; lc = list_nth_cell(g_instance.stat_cxt.WaitCountStatusList, listNodeid); - WaitCountStatusCell = (PgStat_WaitCountStatusCell*)lfirst(lc); + WaitCountStatusCell = (PgStat_WaitCountStatusCell *)lfirst(lc); /* Using pg atomic function to add count for corresponsible WaitEventSQL */ if (classId == PG_WAIT_SQL) { @@ -4015,19 +4558,19 @@ void pgstat_report_wait_count(unsigned int wait_event_info) switch (w) { case WAIT_EVENT_SQL_SELECT: { UPDATE_SQL_COUNT(WaitCountStatusCell->WaitCountArray[dataid].wc_cnt.wc_sql_select, - WaitCountStatusCell->WaitCountArray[dataid].wc_cnt.selectElapse); + WaitCountStatusCell->WaitCountArray[dataid].wc_cnt.selectElapse); } break; case WAIT_EVENT_SQL_UPDATE: { UPDATE_SQL_COUNT(WaitCountStatusCell->WaitCountArray[dataid].wc_cnt.wc_sql_update, - WaitCountStatusCell->WaitCountArray[dataid].wc_cnt.updateElapse); + WaitCountStatusCell->WaitCountArray[dataid].wc_cnt.updateElapse); } break; case WAIT_EVENT_SQL_INSERT: { UPDATE_SQL_COUNT(WaitCountStatusCell->WaitCountArray[dataid].wc_cnt.wc_sql_insert, - WaitCountStatusCell->WaitCountArray[dataid].wc_cnt.insertElapse); + WaitCountStatusCell->WaitCountArray[dataid].wc_cnt.insertElapse); } break; case WAIT_EVENT_SQL_DELETE: { UPDATE_SQL_COUNT(WaitCountStatusCell->WaitCountArray[dataid].wc_cnt.wc_sql_delete, - WaitCountStatusCell->WaitCountArray[dataid].wc_cnt.deleteElapse); + WaitCountStatusCell->WaitCountArray[dataid].wc_cnt.deleteElapse); } break; case WAIT_EVENT_SQL_MERGEINTO: pg_atomic_fetch_add_u64(&(WaitCountStatusCell->WaitCountArray[dataid].wc_cnt.wc_sql_mergeinto), 1); @@ -4058,14 +4601,25 @@ void pgstat_report_wait_count(unsigned int wait_event_info) * * ---------- */ +/* + * 功能:报告当前会话的资源等待状态 + * + * 参数列表: + * waiting:等待的资源类型,取值为WorkloadManagerEnqueueState枚举值之一 + * + * 注意: + * 此函数用于报告当前会话的资源等待状态。首先检查是否启用状态跟踪,如果未启用则返回。 + * 然后将资源等待状态设置为指定的值,并根据资源类型更新会话的等待状态。 + */ void pgstat_report_waiting_on_resource(WorkloadManagerEnqueueState waiting) { - volatile PgBackendStatus* beentry = t_thrd.shemem_ptr_cxt.MyBEEntry; - + volatile PgBackendStatus *beentry = t_thrd.shemem_ptr_cxt.MyBEEntry; + // 检查是否启用状态跟踪,如果未启用则返回 if (IS_PGSTATE_TRACK_UNDEFINE) return; - + // 将资源等待状态设置为指定的值 beentry->st_waiting_on_resource = waiting; + // 根据资源类型更新会话的等待状态 switch (waiting) { case STATE_ACTIVE_STATEMENTS: (void)pgstat_report_waitstatus(STATE_WAIT_ACTIVE_STATEMENT); @@ -4088,17 +4642,24 @@ void pgstat_report_waiting_on_resource(WorkloadManagerEnqueueState waiting) * set current statement wlm status, include block time, cpu time. * ---------- */ +/* + * 功能:报告当前会话的工作负载管理(WLM)状态 + * + * 注意: + * 此函数用于报告当前会话的工作负载管理状态。首先检查是否启用状态跟踪,如果未启用则返回。 + * 然后调用WLMGetStatistics函数获取相关的WLM统计信息,并将其存储在PgBackendStatus结构中。 + */ void pgstat_report_statement_wlm_status() { - volatile PgBackendStatus* beentry = t_thrd.shemem_ptr_cxt.MyBEEntry; + volatile PgBackendStatus *beentry = t_thrd.shemem_ptr_cxt.MyBEEntry; + // 检查是否启用状态跟踪,如果未启用则返回 if (IS_PGSTATE_TRACK_UNDEFINE) return; - /* get current workload backend state */ - WLMGetStatistics((TimestampTz*)&beentry->st_block_start_time, - (TimestampTz*)&beentry->st_elapsed_start_time, - (WLMStatistics*)&beentry->st_backstat); + // 调用WLMGetStatistics函数获取相关的WLM统计信息,并存储在PgBackendStatus结构中 + WLMGetStatistics((TimestampTz *)&beentry->st_block_start_time, (TimestampTz *)&beentry->st_elapsed_start_time, + (WLMStatistics *)&beentry->st_backstat); } /* ---------- @@ -4107,29 +4668,45 @@ void pgstat_report_statement_wlm_status() * refresh the block time and elapsed time for a statement state. * ---------- */ -void pgstat_refresh_statement_wlm_time(volatile PgBackendStatus* beentry) +/* + * 功能:刷新会话的工作负载管理(WLM)时间信息 + * + * 参数: + * beentry:指向PgBackendStatus的指针,包含会话状态信息 + * + * 注意: + * 此函数用于刷新会话的工作负载管理时间信息。首先检查传入的PgBackendStatus指针是否为NULL, + * 如果为NULL则返回。然后根据WLMStatistics结构中的状态信息更新相应的时间信息,包括blocktime、elapsedtime、maxcputime、totalcputime和skewpercent。 + */ +void pgstat_refresh_statement_wlm_time(volatile PgBackendStatus *beentry) { if (NULL != beentry) { - WLMStatistics* backstat = (WLMStatistics*)&beentry->st_backstat; + WLMStatistics *backstat = (WLMStatistics *)&beentry->st_backstat; + // 检查状态是否有效,如果无效则返回 if (!StringIsValid(backstat->status)) return; /* compute the block time and elapsed time */ if (strcmp(backstat->status, "pending") == 0) { + // 计算blocktime backstat->blocktime = (GetCurrentTimestamp() - beentry->st_block_start_time) / USECS_PER_SEC; backstat->elapsedtime = 0; - } else if (strcmp(backstat->status, "running") == 0) + } else if (strcmp(backstat->status, "running") == 0) { + // 计算elapsedtime backstat->elapsedtime = (GetCurrentTimestamp() - beentry->st_elapsed_start_time) / USECS_PER_SEC; + } WLMDataIndicator indicator; + // 初始化WLMDataIndicator结构 errno_t errval = memset_s(&indicator, sizeof(indicator), 0, sizeof(indicator)); securec_check_errval(errval, , LOG); /* get cpu collect info */ - WLMGetCPUDataIndicator((PgBackendStatus*)beentry, &indicator); + WLMGetCPUDataIndicator((PgBackendStatus *)beentry, &indicator); + // 更新maxcputime、totalcputime和skewpercent backstat->maxcputime = indicator.max_value / MSECS_PER_SEC; backstat->totalcputime = indicator.total_value / MSECS_PER_SEC; backstat->skewpercent = indicator.skew_percent; @@ -4153,6 +4730,12 @@ void pgstat_increase_session_spill(void) * increase current session spill size. * ---------- */ +/* + * 功能:增加会话的溢出次数统计 + * + * 注意: + * 此函数用于增加会话的溢出次数统计。它使用原子操作将溢出次数加1。 + */ void pgstat_increase_session_spill_size(int64 size) { gs_atomic_add_64(&t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->spillSize, size); @@ -4164,6 +4747,12 @@ void pgstat_increase_session_spill_size(int64 size) * add warning for early spill. * ---------- */ +/* + * 功能:设置会话的早期溢出警告标志 + * + * 注意: + * 此函数用于设置会话的早期溢出警告标志。它将指定的标志位设置为1。 + */ void pgstat_add_warning_early_spill() { t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->warning |= (1 << WLM_WARN_EARLY_SPILL); @@ -4197,9 +4786,20 @@ void pgstat_add_warning_hash_conflict() * set backend io state. * ---------- */ +/* + * 功能:设置IO状态并更新后端进程的状态 + * + * 参数: + * iostate:要设置的IO状态 + * + * 注意: + * 此函数用于设置IO状态,并根据需要更新后端进程的状态。首先检查是否启用了 + * 跟踪活动状态和当前进程是否有效。如果未启用或进程无效,则不执行任何操作。 + * 否则,将IO状态设置为给定值,并更新相应的WLM参数。 + */ void pgstat_set_io_state(WorkloadManagerIOState iostate) { - volatile PgBackendStatus* beentry = t_thrd.shemem_ptr_cxt.MyBEEntry; + volatile PgBackendStatus *beentry = t_thrd.shemem_ptr_cxt.MyBEEntry; if (!u_sess->attr.attr_common.pgstat_track_activities || (beentry == NULL)) return; @@ -4215,9 +4815,18 @@ void pgstat_set_io_state(WorkloadManagerIOState iostate) * set backend statement tag. * ---------- */ +/* + * 功能:设置语句标签 + * + * 参数: + * stmttag:要设置的语句标签 + * + * 注意: + * 此函数用于设置当前会话的语句标签。如果未启用 pgstat_track_activities 或未找到会话的后端状态,则不执行任何操作。 + */ void pgstat_set_stmt_tag(WorkloadManagerStmtTag stmttag) { - volatile PgBackendStatus* beentry = t_thrd.shemem_ptr_cxt.MyBEEntry; + volatile PgBackendStatus *beentry = t_thrd.shemem_ptr_cxt.MyBEEntry; if (!u_sess->attr.attr_common.pgstat_track_activities || (beentry == NULL)) return; @@ -4226,12 +4835,12 @@ void pgstat_set_stmt_tag(WorkloadManagerStmtTag stmttag) beentry->st_stmttag = stmttag; } -WaitInfo* read_current_instr_wait_info(void) +WaitInfo *read_current_instr_wait_info(void) { - volatile PgBackendStatus* beentry = NULL; + volatile PgBackendStatus *beentry = NULL; int i; errno_t rc; - WaitInfo* gsInstrWaitInfo = (WaitInfo*)palloc0(sizeof(WaitInfo) * 1); + WaitInfo *gsInstrWaitInfo = (WaitInfo *)palloc0(sizeof(WaitInfo) * 1); beentry = t_thrd.shemem_ptr_cxt.BackendStatusArray; for (i = 1; i <= BackendStatusArray_size; i++) { @@ -4248,7 +4857,7 @@ WaitInfo* read_current_instr_wait_info(void) int after_changecount; pgstat_save_changecount_before(beentry, before_changecount); - rc = memcpy_s(&waitinfo, sizeof(WaitInfo), (WaitInfo*)&beentry->waitInfo, sizeof(WaitInfo)); + rc = memcpy_s(&waitinfo, sizeof(WaitInfo), (WaitInfo *)&beentry->waitInfo, sizeof(WaitInfo)); securec_check(rc, "", ""); pgstat_save_changecount_after(beentry, after_changecount); if (before_changecount == after_changecount && (before_changecount & 1) == 0) @@ -4269,6 +4878,13 @@ WaitInfo* read_current_instr_wait_info(void) * reset current status * ---------- */ +/* + * 功能:重置当前会话的统计状态 + * + * 注意: + * 此函数用于清除当前会话的统计状态信息。它首先检查是否存在 pgStatLocalContext,如果存在则删除该内存上下文,然后将 + * u_sess->stat_cxt.pgStatLocalContext 和 u_sess->stat_cxt.localBackendStatusTable 设置为 NULL。 + */ void pgstat_reset_current_status(void) { if (u_sess->stat_cxt.pgStatLocalContext) { @@ -4285,6 +4901,19 @@ void pgstat_reset_current_status(void) * Return true if waiting for a lmgr lock. * ---------- */ +/* + * 功能:检查等待事件信息是否表示等待锁 + * + * 参数: + * wait_event_info:等待事件信息 + * + * 返回值: + * 如果等待事件信息表示等待锁,则返回 true;否则返回 false。 + * + * 注意: + * 此函数用于判断等待事件信息是否与锁相关。它提取等待事件信息的高位字节,并将其与 PG_WAIT_LOCK 的高位字节比较, + * 如果相同则表示等待锁,返回 true,否则返回 false。 + */ bool pgstat_get_waitlock(uint32 wait_event_info) { uint32 classId; @@ -4299,35 +4928,52 @@ bool pgstat_get_waitlock(uint32 wait_event_info) * Return a string representing the current wait event, backend is * waiting on. */ -const char* pgstat_get_wait_event(uint32 wait_event_info) +/* + * 功能:根据等待事件信息获取等待事件的名称 + * + * 参数: + * wait_event_info:等待事件信息 + * + * 返回值: + * 返回等待事件的名称(字符串) + * + * 注意: + * 此函数根据传入的等待事件信息,确定等待事件的类别(classId),然后获取对应的名称并返回。 + */ +const char *pgstat_get_wait_event(uint32 wait_event_info) { uint32 classId; uint16 eventId; - const char* event_name = NULL; + const char *event_name = NULL; + // 提取等待事件信息的高位字节和低位字节 classId = wait_event_info & 0xFF000000; eventId = wait_event_info & 0x0000FFFF; switch (classId) { case PG_WAIT_LWLOCK: + // 如果是等待轻量级锁(LWLock),获取对应的名称 event_name = GetLWLockIdentifier(classId, eventId); break; case PG_WAIT_LOCK: + // 如果是等待通用锁(Lock),获取对应的名称 event_name = GetLockNameFromTagType(eventId); break; case PG_WAIT_IO: { + // 如果是等待I/O事件,获取对应的名称 WaitEventIO w = (WaitEventIO)wait_event_info; event_name = pgstat_get_wait_io(w); break; } default: + // 如果是未知的等待事件类别,返回 "unknown wait event" event_name = "unknown wait event"; break; } return event_name; } -void LWLockReportWaitFailed(LWLock* lock) +void LWLockReportWaitFailed(LWLock *lock) { pgstat_report_wait_lock_failed(PG_WAIT_LWLOCK | lock->tranche); } @@ -4338,9 +4984,9 @@ void LWLockReportWaitFailed(LWLock* lock) * Convert WaitEventIO to string. * ---------- */ -const char* pgstat_get_wait_io(WaitEventIO w) +const char *pgstat_get_wait_io(WaitEventIO w) { - const char* event_name = "unknown wait event"; + const char *event_name = "unknown wait event"; switch (w) { case WAIT_EVENT_BUFFILE_READ: @@ -4596,10 +5242,10 @@ int pgstat_get_current_active_numbackends(void) return result_counter; } - PgBackendStatusNode* node = gs_stat_read_current_status(NULL); + PgBackendStatusNode *node = gs_stat_read_current_status(NULL); while (node != NULL) { - PgBackendStatus* beentry = node->data; + PgBackendStatus *beentry = node->data; /* * If the backend thread is valid and the state @@ -4607,7 +5253,7 @@ int pgstat_get_current_active_numbackends(void) */ if ((beentry != NULL) && beentry->st_sessionid > 0 && (beentry->st_state != STATE_IDLE && beentry->st_state != STATE_UNDEFINED && - beentry->st_state != STATE_DECOUPLED)) + beentry->st_state != STATE_DECOUPLED)) ++result_counter; node = node->next; } @@ -4620,9 +5266,9 @@ int pgstat_get_current_active_numbackends(void) } /* get the pointer to the specified backend status */ -PgBackendStatus* pgstat_get_backend_single_entry(uint64 sessionid) +PgBackendStatus *pgstat_get_backend_single_entry(uint64 sessionid) { - PgBackendStatus* beentry = NULL; + PgBackendStatus *beentry = NULL; int i = 0; beentry = t_thrd.shemem_ptr_cxt.BackendStatusArray + BackendStatusArray_size - 1; @@ -4649,7 +5295,7 @@ PgBackendStatus* pgstat_get_backend_single_entry(uint64 sessionid) * @Return: user data list * @See also: */ -List* pgstat_get_user_backend_entry(Oid userid) +List *pgstat_get_user_backend_entry(Oid userid) { // If BackendStatusArray is NULL, we will get it from other thread. if (t_thrd.shemem_ptr_cxt.BackendStatusArray == NULL) { @@ -4659,17 +5305,17 @@ List* pgstat_get_user_backend_entry(Oid userid) return NULL; } - List* entry_list = NULL; - PgBackendStatusNode* node = gs_stat_read_current_status(NULL); + List *entry_list = NULL; + PgBackendStatusNode *node = gs_stat_read_current_status(NULL); while (node != NULL) { - PgBackendStatus* beentry = node->data; + PgBackendStatus *beentry = node->data; /* * If the backend thread is valid and the user id * matched, we treat it as a active thread. */ if (beentry != NULL) { - WLMStatistics* backstat = (WLMStatistics*)&beentry->st_backstat; + WLMStatistics *backstat = (WLMStatistics *)&beentry->st_backstat; if (beentry->st_procpid <= 0 && beentry->st_sessionid == 0) { node = node->next; @@ -4685,9 +5331,9 @@ List* pgstat_get_user_backend_entry(Oid userid) (backstat->enqueue && strcmp(backstat->enqueue, "Transaction") == 0) || (backstat->enqueue && strcmp(backstat->enqueue, "StoredProc") == 0) || (backstat->status && strcmp(backstat->status, "pending") == 0 && backstat->enqueue && - (strcmp(backstat->enqueue, "None") == 0 || strcmp(backstat->enqueue, "Respool") == 0)) || + (strcmp(backstat->enqueue, "None") == 0 || strcmp(backstat->enqueue, "Respool") == 0)) || (backstat->status && strcmp(backstat->status, "finished") == 0 && backstat->enqueue && - strcmp(backstat->enqueue, "Respool") == 0)) { + strcmp(backstat->enqueue, "Respool") == 0)) { entry_list = lappend(entry_list, beentry); } } @@ -4704,7 +5350,7 @@ List* pgstat_get_user_backend_entry(Oid userid) * @Return: all thread of the user * @See also: */ -ThreadId* pgstat_get_user_io_entry(Oid userid, int* num) +ThreadId *pgstat_get_user_io_entry(Oid userid, int *num) { int idx = 0; @@ -4717,13 +5363,13 @@ ThreadId* pgstat_get_user_io_entry(Oid userid, int* num) } uint32 num_backends = 0; - PgBackendStatusNode* node = gs_stat_read_current_status(&num_backends); + PgBackendStatusNode *node = gs_stat_read_current_status(&num_backends); if (num_backends == 0) return NULL; Assert(!u_sess->stat_cxt.pgStatRunningInCollector); - ThreadId* threads = (ThreadId*)palloc0_noexcept(num_backends * sizeof(ThreadId)); + ThreadId *threads = (ThreadId *)palloc0_noexcept(num_backends * sizeof(ThreadId)); if (threads == NULL) { pgstat_reset_current_status(); ereport(LOG, (errmsg("alloc memory failed for get user io entry."))); @@ -4731,7 +5377,7 @@ ThreadId* pgstat_get_user_io_entry(Oid userid, int* num) } while (node != NULL) { - PgBackendStatus* beentry = node->data; + PgBackendStatus *beentry = node->data; /* * If the backend thread is valid and the io state @@ -4758,7 +5404,7 @@ ThreadId* pgstat_get_user_io_entry(Oid userid, int* num) * @Return: all thread with st_stmttag as write * @See also: */ -ThreadId* pgstat_get_stmttag_write_entry(int* num) +ThreadId *pgstat_get_stmttag_write_entry(int *num) { int idx = 0; @@ -4771,13 +5417,13 @@ ThreadId* pgstat_get_stmttag_write_entry(int* num) } uint32 num_backends = 0; - PgBackendStatusNode* node = gs_stat_read_current_status(&num_backends); + PgBackendStatusNode *node = gs_stat_read_current_status(&num_backends); if (num_backends == 0) return NULL; Assert(!u_sess->stat_cxt.pgStatRunningInCollector); - ThreadId* threads = (ThreadId*)palloc0_noexcept(num_backends * sizeof(ThreadId)); + ThreadId *threads = (ThreadId *)palloc0_noexcept(num_backends * sizeof(ThreadId)); if (threads == NULL) { pgstat_reset_current_status(); @@ -4786,7 +5432,7 @@ ThreadId* pgstat_get_stmttag_write_entry(int* num) } while (node != NULL) { - PgBackendStatus* beentry = node->data; + PgBackendStatus *beentry = node->data; node = node->next; /* * If the backend thread is valid and the stmt tag @@ -4799,7 +5445,7 @@ ThreadId* pgstat_get_stmttag_write_entry(int* num) beentry->st_stmttag == STMTTAG_WRITE) threads[idx++] = beentry->st_procpid; else { - WLMStatistics* backstat = (WLMStatistics*)&beentry->st_backstat; + WLMStatistics *backstat = (WLMStatistics *)&beentry->st_backstat; if (backstat->enqueue && strcmp(backstat->enqueue, "Transaction") == 0) threads[idx++] = beentry->st_procpid; } @@ -4819,7 +5465,7 @@ ThreadId* pgstat_get_stmttag_write_entry(int* num) * @OUT num: entries count * @Return: all node status which names are 'application name' */ -PgBackendStatusNode* pgstat_get_backend_status_by_appname(const char* appName, int* resultEntryNum) +PgBackendStatusNode *pgstat_get_backend_status_by_appname(const char *appName, int *resultEntryNum) { int idx = 0; @@ -4839,7 +5485,7 @@ PgBackendStatusNode* pgstat_get_backend_status_by_appname(const char* appName, i /* Get all status entries, which procpid or sessionid is valid */ uint32 numBackends = 0; - PgBackendStatusNode* node = gs_stat_read_current_status(&numBackends); + PgBackendStatusNode *node = gs_stat_read_current_status(&numBackends); /* If all entries procpid or sessionid are invalid, get numBackends is 0 and should return directly */ if (numBackends == 0) { @@ -4851,18 +5497,18 @@ PgBackendStatusNode* pgstat_get_backend_status_by_appname(const char* appName, i Assert(!u_sess->stat_cxt.pgStatRunningInCollector); /* Initialize head pointer, all nodes struct form to a list. This list does not match wich appname */ - PgBackendStatusNode* otherNodeList = (PgBackendStatusNode*)palloc(sizeof(PgBackendStatusNode)); - PgBackendStatusNode* otherNodeListHead = otherNodeList; + PgBackendStatusNode *otherNodeList = (PgBackendStatusNode *)palloc(sizeof(PgBackendStatusNode)); + PgBackendStatusNode *otherNodeListHead = otherNodeList; otherNodeList->data = NULL; otherNodeList->next = NULL; /* Initialize head pointer, all nodes struct form to a list, This list matches wich appname */ - PgBackendStatusNode* resultNodeList = (PgBackendStatusNode*)palloc(sizeof(PgBackendStatusNode)); - PgBackendStatusNode* resultNodeListHead = resultNodeList; + PgBackendStatusNode *resultNodeList = (PgBackendStatusNode *)palloc(sizeof(PgBackendStatusNode)); + PgBackendStatusNode *resultNodeListHead = resultNodeList; resultNodeList->data = NULL; resultNodeList->next = NULL; while (node != NULL) { - PgBackendStatus* beentry = node->data; + PgBackendStatus *beentry = node->data; /* If the backend thread is valid and application name of beentry equals to appName, record this thread pid */ if (beentry != NULL) { @@ -4918,9 +5564,9 @@ PgBackendStatusNode* pgstat_get_backend_status_by_appname(const char* appName, i * Note: return strings for special cases match pg_stat_get_backend_activity. * ---------- */ -const char* pgstat_get_backend_current_activity(ThreadId pid, bool checkUser) +const char *pgstat_get_backend_current_activity(ThreadId pid, bool checkUser) { - PgBackendStatus* beentry = NULL; + PgBackendStatus *beentry = NULL; int i; /* We go through BackendStatusArray from back to front. */ @@ -4936,7 +5582,7 @@ const char* pgstat_get_backend_current_activity(ThreadId pid, bool checkUser) * atomic, but let's play it safe.) We use a volatile pointer here to * ensure the compiler doesn't try to get cute. */ - volatile PgBackendStatus* vbeentry = beentry; + volatile PgBackendStatus *vbeentry = beentry; bool found = false; for (;;) { @@ -4989,9 +5635,9 @@ const char* pgstat_get_backend_current_activity(ThreadId pid, bool checkUser) * corrupted message. We also must take care not to trip on ereport(ERROR). * ---------- */ -const char* pgstat_get_crashed_backend_activity(ThreadId pid, char* buffer, int buflen) +const char *pgstat_get_crashed_backend_activity(ThreadId pid, char *buffer, int buflen) { - volatile PgBackendStatus* beentry = NULL; + volatile PgBackendStatus *beentry = NULL; int i; beentry = t_thrd.shemem_ptr_cxt.BackendStatusArray + BackendStatusArray_size - 1; @@ -5007,8 +5653,8 @@ const char* pgstat_get_crashed_backend_activity(ThreadId pid, char* buffer, int for (i = 1; i <= BackendStatusArray_size; i++) { if (beentry->st_procpid == pid) { /* Read pointer just once, so it can't change after validation */ - const char* activity = beentry->st_activity; - const char* activity_last = NULL; + const char *activity = beentry->st_activity; + const char *activity_last = NULL; /* * We mustn't access activity string before we verify that it @@ -5032,8 +5678,8 @@ const char* pgstat_get_crashed_backend_activity(ThreadId pid, char* buffer, int * problems when reporting the message; and be sure not to run off * the end of memory. */ - ascii_safe_strlcpy( - buffer, activity, Min(buflen, g_instance.attr.attr_common.pgstat_track_activity_query_size)); + ascii_safe_strlcpy(buffer, activity, + Min(buflen, g_instance.attr.attr_common.pgstat_track_activity_query_size)); return buffer; } @@ -5055,7 +5701,7 @@ const char* pgstat_get_crashed_backend_activity(ThreadId pid, char* buffer, int */ void pgstat_cancel_invalid_gtm_conn(void) { - volatile PgBackendStatus* beentry = NULL; + volatile PgBackendStatus *beentry = NULL; bool CalledByBackend = false; volatile GtmHostIndex hostindex = GTM_HOST_INVAILD; @@ -5097,10 +5743,9 @@ void pgstat_cancel_invalid_gtm_conn(void) if (gs_signal_send(beentry->st_procpid, SIGUSR2)) ereport(WARNING, (errmsg("could not send signal to thread %lu: %m", beentry->st_procpid))); else - ereport(LOG, - (errmsg("Success to send SIGUSR2 to openGauss thread: %lu in " - "pgstat_cancel_invalid_gtm_conn", - beentry->st_procpid))); + ereport(LOG, (errmsg("Success to send SIGUSR2 to openGauss thread: %lu in " + "pgstat_cancel_invalid_gtm_conn", + beentry->st_procpid))); } if (beentry->st_procpid == t_thrd.shemem_ptr_cxt.MyBEEntry->st_procpid) @@ -5125,7 +5770,7 @@ void pgstat_cancel_invalid_gtm_conn(void) * Set common header fields in a statistics message * ---------- */ -static void pgstat_setheader(PgStat_MsgHdr* hdr, StatMsgType mtype) +static void pgstat_setheader(PgStat_MsgHdr *hdr, StatMsgType mtype) { hdr->m_type = mtype; } @@ -5136,14 +5781,14 @@ static void pgstat_setheader(PgStat_MsgHdr* hdr, StatMsgType mtype) * Send out one statistics message to the collector * ---------- */ -void pgstat_send(void* msg, int len) +void pgstat_send(void *msg, int len) { int rc; if (g_instance.stat_cxt.pgStatSock == PGINVALID_SOCKET) return; - ((PgStat_MsgHdr*)msg)->m_size = len; + ((PgStat_MsgHdr *)msg)->m_size = len; /* We'll retry after EINTR, but ignore all other failures */ do { @@ -5197,11 +5842,11 @@ static void PgstatCollectThreadStatus(void) // setup a new memcontext pgstat_collect_thread_status_setup_memcxt(); - PgBackendStatusNode* node = gs_stat_read_current_status(NULL); + PgBackendStatusNode *node = gs_stat_read_current_status(NULL); while (node != NULL) { - PgBackendStatus* beentry = node->data; + PgBackendStatus *beentry = node->data; node = node->next; - char* wait_status = NULL; + char *wait_status = NULL; if (NULL == beentry) continue; @@ -5214,17 +5859,11 @@ static void PgstatCollectThreadStatus(void) // log out thread wait status from beentry. elog(LOG, - "PgstatCollectThreadStatus, node_name<%s>, datid<%u>, app_name<%s>, " - "query_id<%lu>, tid<%lu>, lwtid<%d>, parent_sessionid<%lu>, thread_level<%d>, wait_status<%s>", - g_instance.attr.attr_common.PGXCNodeName, - beentry->st_databaseid, - beentry->st_appname ? beentry->st_appname : "unnamed thread", - beentry->st_queryid, - beentry->st_procpid, - beentry->st_tid, - beentry->st_parent_sessionid, - beentry->st_thread_level, - wait_status); + "PgstatCollectThreadStatus, node_name<%s>, datid<%u>, app_name<%s>, " + "query_id<%lu>, tid<%lu>, lwtid<%d>, parent_sessionid<%lu>, thread_level<%d>, wait_status<%s>", + g_instance.attr.attr_common.PGXCNodeName, beentry->st_databaseid, + beentry->st_appname ? beentry->st_appname : "unnamed thread", beentry->st_queryid, beentry->st_procpid, + beentry->st_tid, beentry->st_parent_sessionid, beentry->st_thread_level, wait_status); pfree(wait_status); } @@ -5302,11 +5941,9 @@ void PgstatCollectorMain() u_sess->stat_cxt.pgStatRunningInCollector = true; u_sess->stat_cxt.pgStatDBHash = pgstat_read_statsfile(InvalidOid, true); - t_thrd.mem_cxt.mask_password_mem_cxt = AllocSetContextCreate(t_thrd.top_mem_cxt, - "MaskPasswordCtx", - ALLOCSET_DEFAULT_MINSIZE, - ALLOCSET_DEFAULT_INITSIZE, - ALLOCSET_DEFAULT_MAXSIZE); + t_thrd.mem_cxt.mask_password_mem_cxt = + AllocSetContextCreate(t_thrd.top_mem_cxt, "MaskPasswordCtx", ALLOCSET_DEFAULT_MINSIZE, + ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); /* * Loop to process messages until we get SIGQUIT or detect ungraceful * death of our parent postmaster. @@ -5325,9 +5962,8 @@ void PgstatCollectorMain() TimestampTz get_thread_status_current_time = GetCurrentTimestamp(); if (u_sess->attr.attr_common.pgstat_collect_thread_status_interval > 0 && - TimestampDifferenceExceeds(get_thread_status_start_time, - get_thread_status_current_time, - 60 * u_sess->attr.attr_common.pgstat_collect_thread_status_interval * 1000)) { + TimestampDifferenceExceeds(get_thread_status_start_time, get_thread_status_current_time, + 60 * u_sess->attr.attr_common.pgstat_collect_thread_status_interval * 1000)) { // transfer from minute into msec PgstatCollectThreadStatus(); get_thread_status_start_time = GetCurrentTimestamp(); @@ -5374,7 +6010,7 @@ void PgstatCollectorMain() #ifdef WIN32 pgwin32_noblock = 1; #endif - len = recv(g_instance.stat_cxt.pgStatSock, (char*)&msg, sizeof(PgStat_Msg), 0); + len = recv(g_instance.stat_cxt.pgStatSock, (char *)&msg, sizeof(PgStat_Msg), 0); #ifdef WIN32 pgwin32_noblock = 0; @@ -5406,103 +6042,103 @@ void PgstatCollectorMain() break; case PGSTAT_MTYPE_INQUIRY: - pgstat_recv_inquiry((PgStat_MsgInquiry*)&msg, len); + pgstat_recv_inquiry((PgStat_MsgInquiry *)&msg, len); break; case PGSTAT_MTYPE_TABSTAT: - pgstat_recv_tabstat((PgStat_MsgTabstat*)&msg, len); + pgstat_recv_tabstat((PgStat_MsgTabstat *)&msg, len); break; case PGSTAT_MTYPE_TABPURGE: - pgstat_recv_tabpurge((PgStat_MsgTabpurge*)&msg, len); + pgstat_recv_tabpurge((PgStat_MsgTabpurge *)&msg, len); break; case PGSTAT_MTYPE_DROPDB: - pgstat_recv_dropdb((PgStat_MsgDropdb*)&msg, len); + pgstat_recv_dropdb((PgStat_MsgDropdb *)&msg, len); break; case PGSTAT_MTYPE_RESETCOUNTER: - pgstat_recv_resetcounter((PgStat_MsgResetcounter*)&msg, len); + pgstat_recv_resetcounter((PgStat_MsgResetcounter *)&msg, len); break; case PGSTAT_MTYPE_RESETSHAREDCOUNTER: - pgstat_recv_resetsharedcounter((PgStat_MsgResetsharedcounter*)&msg, len); + pgstat_recv_resetsharedcounter((PgStat_MsgResetsharedcounter *)&msg, len); break; case PGSTAT_MTYPE_RESETSINGLECOUNTER: - pgstat_recv_resetsinglecounter((PgStat_MsgResetsinglecounter*)&msg, len); + pgstat_recv_resetsinglecounter((PgStat_MsgResetsinglecounter *)&msg, len); break; case PGSTAT_MTYPE_AUTOVAC_START: - pgstat_recv_autovac((PgStat_MsgAutovacStart*)&msg, len); + pgstat_recv_autovac((PgStat_MsgAutovacStart *)&msg, len); break; case PGSTAT_MTYPE_VACUUM: - pgstat_recv_vacuum((PgStat_MsgVacuum*)&msg, len); + pgstat_recv_vacuum((PgStat_MsgVacuum *)&msg, len); break; case PGSTAT_MTYPE_AUTOVAC_STAT: - pgstat_recv_autovac_stat((PgStat_MsgAutovacStat*)&msg, len); + pgstat_recv_autovac_stat((PgStat_MsgAutovacStat *)&msg, len); break; case PGSTAT_MTYPE_DATA_CHANGED: - pgstat_recv_data_changed((PgStat_MsgDataChanged*)&msg, len); + pgstat_recv_data_changed((PgStat_MsgDataChanged *)&msg, len); break; case PGSTAT_MTYPE_TRUNCATE: - pgstat_recv_truncate((PgStat_MsgTruncate*)&msg, len); + pgstat_recv_truncate((PgStat_MsgTruncate *)&msg, len); break; case PGSTAT_MTYPE_ANALYZE: - pgstat_recv_analyze((PgStat_MsgAnalyze*)&msg, len); + pgstat_recv_analyze((PgStat_MsgAnalyze *)&msg, len); break; case PGSTAT_MTYPE_BGWRITER: - pgstat_recv_bgwriter((PgStat_MsgBgWriter*)&msg, len); + pgstat_recv_bgwriter((PgStat_MsgBgWriter *)&msg, len); break; case PGSTAT_MTYPE_FUNCSTAT: - pgstat_recv_funcstat((PgStat_MsgFuncstat*)&msg, len); + pgstat_recv_funcstat((PgStat_MsgFuncstat *)&msg, len); break; case PGSTAT_MTYPE_FUNCPURGE: - pgstat_recv_funcpurge((PgStat_MsgFuncpurge*)&msg, len); + pgstat_recv_funcpurge((PgStat_MsgFuncpurge *)&msg, len); break; case PGSTAT_MTYPE_RECOVERYCONFLICT: - pgstat_recv_recoveryconflict((PgStat_MsgRecoveryConflict*)&msg); + pgstat_recv_recoveryconflict((PgStat_MsgRecoveryConflict *)&msg); break; case PGSTAT_MTYPE_DEADLOCK: - pgstat_recv_deadlock((PgStat_MsgDeadlock*)&msg); + pgstat_recv_deadlock((PgStat_MsgDeadlock *)&msg); break; case PGSTAT_MTYPE_FILE: - pgstat_recv_filestat((PgStat_MsgFile*)&msg, len); + pgstat_recv_filestat((PgStat_MsgFile *)&msg, len); break; case PGSTAT_MTYPE_TEMPFILE: - pgstat_recv_tempfile((PgStat_MsgTempFile*)&msg, len); + pgstat_recv_tempfile((PgStat_MsgTempFile *)&msg, len); break; case PGSTAT_MTYPE_MEMRESERVED: - pgstat_recv_memReserved((PgStat_MsgMemReserved*)&msg); + pgstat_recv_memReserved((PgStat_MsgMemReserved *)&msg); break; case PGSTAT_MTYPE_BADBLOCK: - pgstat_recv_badblock_stat((PgStat_MsgBadBlock*)&msg, len); + pgstat_recv_badblock_stat((PgStat_MsgBadBlock *)&msg, len); break; case PGSTAT_MTYPE_RESPONSETIME: - pgstat_recv_sql_responstime((PgStat_SqlRT*)&msg, len); + pgstat_recv_sql_responstime((PgStat_SqlRT *)&msg, len); break; case PGSTAT_MTYPE_CLEANUPHOTKEYS: - pgstat_recv_cleanup_hotkeys((PgStat_MsgCleanupHotkeys*)&msg, len); + pgstat_recv_cleanup_hotkeys((PgStat_MsgCleanupHotkeys *)&msg, len); break; case PGSTAT_MTYPE_PRUNESTAT: - PgstatRecvPrunestat((PgStat_MsgPrune*)&msg, len); + PgstatRecvPrunestat((PgStat_MsgPrune *)&msg, len); break; default: @@ -5513,9 +6149,8 @@ void PgstatCollectorMain() /* Sleep until there's something to do */ #ifndef WIN32 wr = WaitLatchOrSocket(&g_instance.stat_cxt.pgStatLatch, - WL_LATCH_SET | WL_POSTMASTER_DEATH | WL_SOCKET_READABLE | WL_TIMEOUT, - g_instance.stat_cxt.pgStatSock, - 60 * 1000L /* msec */); + WL_LATCH_SET | WL_POSTMASTER_DEATH | WL_SOCKET_READABLE | WL_TIMEOUT, + g_instance.stat_cxt.pgStatSock, 60 * 1000L /* msec */); #else /* @@ -5529,9 +6164,8 @@ void PgstatCollectorMain() * backend_read_statsfile. */ wr = WaitLatchOrSocket(&g_instance.stat_cxt.pgStatLatch, - WL_LATCH_SET | WL_POSTMASTER_DEATH | WL_SOCKET_READABLE | WL_TIMEOUT, - g_instance.stat_cxt.pgStatSock, - 2 * 1000L /* msec */); + WL_LATCH_SET | WL_POSTMASTER_DEATH | WL_SOCKET_READABLE | WL_TIMEOUT, + g_instance.stat_cxt.pgStatSock, 2 * 1000L /* msec */); #endif /* @@ -5578,15 +6212,15 @@ static void pgstat_sighup_handler(SIGNAL_ARGS) * table entry exists, initialize it, if the create parameter is true. * Else, return NULL. */ -static PgStat_StatDBEntry* pgstat_get_db_entry(Oid databaseid, bool create) +static PgStat_StatDBEntry *pgstat_get_db_entry(Oid databaseid, bool create) { - PgStat_StatDBEntry* result = NULL; + PgStat_StatDBEntry *result = NULL; bool found = false; HASHACTION action = (create ? HASH_ENTER : HASH_FIND); errno_t rc = EOK; /* Lookup or create the hash table entry for this database */ - result = (PgStat_StatDBEntry*)hash_search(u_sess->stat_cxt.pgStatDBHash, &databaseid, action, &found); + result = (PgStat_StatDBEntry *)hash_search(u_sess->stat_cxt.pgStatDBHash, &databaseid, action, &found); if (!create && !found) return NULL; @@ -5646,10 +6280,10 @@ static PgStat_StatDBEntry* pgstat_get_db_entry(Oid databaseid, bool create) * table entry exists, initialize it, if the create parameter is true. * Else, return NULL. */ -static PgStat_StatTabEntry* pgstat_get_tab_entry( - PgStat_StatDBEntry* dbentry, Oid tableoid, bool create, uint32 statFlag) +static PgStat_StatTabEntry *pgstat_get_tab_entry(PgStat_StatDBEntry *dbentry, Oid tableoid, bool create, + uint32 statFlag) { - PgStat_StatTabEntry* result = NULL; + PgStat_StatTabEntry *result = NULL; bool found = false; HASHACTION action = (create ? HASH_ENTER : HASH_FIND); PgStat_StatTabKey tabkey; @@ -5658,7 +6292,7 @@ static PgStat_StatTabEntry* pgstat_get_tab_entry( tabkey.tableid = tableoid; /* Lookup or create the hash table entry for this table */ - result = (PgStat_StatTabEntry*)hash_search(dbentry->tables, (void*)(&tabkey), action, &found); + result = (PgStat_StatTabEntry *)hash_search(dbentry->tables, (void *)(&tabkey), action, &found); if (!create && !found) return NULL; @@ -5713,13 +6347,13 @@ static void pgstat_write_statsfile(bool permanent) HASH_SEQ_STATUS hstat; HASH_SEQ_STATUS tstat; HASH_SEQ_STATUS fstat; - PgStat_StatDBEntry* dbentry = NULL; - PgStat_StatTabEntry* tabentry = NULL; - PgStat_StatFuncEntry* funcentry = NULL; - FILE* fpout = NULL; + PgStat_StatDBEntry *dbentry = NULL; + PgStat_StatTabEntry *tabentry = NULL; + PgStat_StatFuncEntry *funcentry = NULL; + FILE *fpout = NULL; int32 format_id; - const char* tmpfile = permanent ? PGSTAT_STAT_PERMANENT_TMPFILE : u_sess->stat_cxt.pgstat_stat_tmpname; - const char* statfile = permanent ? PGSTAT_STAT_PERMANENT_FILENAME : u_sess->stat_cxt.pgstat_stat_filename; + const char *tmpfile = permanent ? PGSTAT_STAT_PERMANENT_TMPFILE : u_sess->stat_cxt.pgstat_stat_tmpname; + const char *statfile = permanent ? PGSTAT_STAT_PERMANENT_FILENAME : u_sess->stat_cxt.pgstat_stat_filename; int rc; /* @@ -5727,8 +6361,8 @@ static void pgstat_write_statsfile(bool permanent) */ fpout = AllocateFile(tmpfile, PG_BINARY_W); if (fpout == NULL) { - ereport( - LOG, (errcode_for_file_access(), errmsg("could not open temporary statistics file \"%s\": %m", tmpfile))); + ereport(LOG, + (errcode_for_file_access(), errmsg("could not open temporary statistics file \"%s\": %m", tmpfile))); return; } @@ -5754,7 +6388,7 @@ static void pgstat_write_statsfile(bool permanent) * Walk through the database table. */ hash_seq_init(&hstat, u_sess->stat_cxt.pgStatDBHash); - while ((dbentry = (PgStat_StatDBEntry*)hash_seq_search(&hstat)) != NULL) { + while ((dbentry = (PgStat_StatDBEntry *)hash_seq_search(&hstat)) != NULL) { /* * Write out the DB entry including the number of live backends. We * don't write the tables or functions pointers, since they're of no @@ -5768,7 +6402,7 @@ static void pgstat_write_statsfile(bool permanent) * Walk through the database's access stats per table. */ hash_seq_init(&tstat, dbentry->tables); - while ((tabentry = (PgStat_StatTabEntry*)hash_seq_search(&tstat)) != NULL) { + while ((tabentry = (PgStat_StatTabEntry *)hash_seq_search(&tstat)) != NULL) { fputc('T', fpout); rc = fwrite(tabentry, sizeof(PgStat_StatTabEntry), 1, fpout); (void)rc; /* we'll check for error with ferror */ @@ -5778,7 +6412,7 @@ static void pgstat_write_statsfile(bool permanent) * Walk through the database's function stats table. */ hash_seq_init(&fstat, dbentry->functions); - while ((funcentry = (PgStat_StatFuncEntry*)hash_seq_search(&fstat)) != NULL) { + while ((funcentry = (PgStat_StatFuncEntry *)hash_seq_search(&fstat)) != NULL) { fputc('F', fpout); rc = fwrite(funcentry, sizeof(PgStat_StatFuncEntry), 1, fpout); (void)rc; /* we'll check for error with ferror */ @@ -5793,10 +6427,10 @@ static void pgstat_write_statsfile(bool permanent) if (g_instance.repair_cxt.global_repair_bad_block_stat != NULL) { LWLockAcquire(RepairBadBlockStatHashLock, LW_SHARED); HASH_SEQ_STATUS repairStat; - BadBlockEntry* repairEntry; + BadBlockEntry *repairEntry; // write the bad page information recorded in global_repair_bad_block_stat. hash_seq_init(&repairStat, g_instance.repair_cxt.global_repair_bad_block_stat); - while ((repairEntry = (BadBlockEntry*)hash_seq_search(&repairStat)) != NULL) { + while ((repairEntry = (BadBlockEntry *)hash_seq_search(&repairStat)) != NULL) { fputc('R', fpout); rc = fwrite(repairEntry, sizeof(BadBlockEntry), 1, fpout); (void)rc; /* we'll check for error with ferror */ @@ -5814,18 +6448,17 @@ static void pgstat_write_statsfile(bool permanent) fputc('E', fpout); if (ferror(fpout)) { - ereport( - LOG, (errcode_for_file_access(), errmsg("could not write temporary statistics file \"%s\": %m", tmpfile))); + ereport(LOG, + (errcode_for_file_access(), errmsg("could not write temporary statistics file \"%s\": %m", tmpfile))); FreeFile(fpout); unlink(tmpfile); } else if (FreeFile(fpout) < 0) { - ereport( - LOG, (errcode_for_file_access(), errmsg("could not close temporary statistics file \"%s\": %m", tmpfile))); + ereport(LOG, + (errcode_for_file_access(), errmsg("could not close temporary statistics file \"%s\": %m", tmpfile))); unlink(tmpfile); } else if (rename(tmpfile, statfile) < 0) { - ereport(LOG, - (errcode_for_file_access(), - errmsg("could not rename temporary statistics file \"%s\" to \"%s\": %m", tmpfile, statfile))); + ereport(LOG, (errcode_for_file_access(), + errmsg("could not rename temporary statistics file \"%s\" to \"%s\": %m", tmpfile, statfile))); unlink(tmpfile); } else { /* @@ -5840,8 +6473,8 @@ static void pgstat_write_statsfile(bool permanent) * message can cause more than one stats file write to occur. */ if (g_instance.stat_cxt.last_statrequest > g_instance.stat_cxt.last_statwrite) { - char* reqtime = NULL; - char* mytime = NULL; + char *reqtime = NULL; + char *mytime = NULL; /* Copy because timestamptz_to_str returns a static buffer */ reqtime = pstrdup(timestamptz_to_str(g_instance.stat_cxt.last_statrequest)); @@ -5865,24 +6498,24 @@ static void pgstat_write_statsfile(bool permanent) * databases' hash table (whose entries point to the tables' hash tables). * ---------- */ -static HTAB* pgstat_read_statsfile(Oid onlydb, bool permanent) +static HTAB *pgstat_read_statsfile(Oid onlydb, bool permanent) { - PgStat_StatDBEntry* dbentry = NULL; + PgStat_StatDBEntry *dbentry = NULL; PgStat_StatDBEntry dbbuf; - PgStat_StatTabEntry* tabentry = NULL; + PgStat_StatTabEntry *tabentry = NULL; PgStat_StatTabEntry tabbuf; PgStat_StatFuncEntry funcbuf; - PgStat_StatFuncEntry* funcentry = NULL; - BadBlockEntry* repairEntry = NULL; + PgStat_StatFuncEntry *funcentry = NULL; + BadBlockEntry *repairEntry = NULL; BadBlockEntry repairBuf; HASHCTL hash_ctl; - HTAB* dbhash = NULL; - HTAB* tabhash = NULL; - HTAB* funchash = NULL; - FILE* fpin = NULL; + HTAB *dbhash = NULL; + HTAB *tabhash = NULL; + HTAB *funchash = NULL; + FILE *fpin = NULL; int32 format_id; bool found = false; - const char* statfile = permanent ? PGSTAT_STAT_PERMANENT_FILENAME : u_sess->stat_cxt.pgstat_stat_filename; + const char *statfile = permanent ? PGSTAT_STAT_PERMANENT_FILENAME : u_sess->stat_cxt.pgstat_stat_filename; errno_t rc = EOK; /* @@ -5926,7 +6559,7 @@ static HTAB* pgstat_read_statsfile(Oid onlydb, bool permanent) if ((fpin = AllocateFile(statfile, PG_BINARY_R)) == NULL) { if (errno != ENOENT) ereport(u_sess->stat_cxt.pgStatRunningInCollector ? LOG : WARNING, - (errcode_for_file_access(), errmsg("could not open statistics file \"%s\": %m", statfile))); + (errcode_for_file_access(), errmsg("could not open statistics file \"%s\": %m", statfile))); elog(LOG, "[Pgstat] statfile %s is missing, using empty dbhash.", statfile); return dbhash; } @@ -5936,7 +6569,7 @@ static HTAB* pgstat_read_statsfile(Oid onlydb, bool permanent) */ if (fread(&format_id, 1, sizeof(format_id), fpin) != sizeof(format_id) || format_id != PGSTAT_FILE_FORMAT_ID) { ereport(u_sess->stat_cxt.pgStatRunningInCollector ? LOG : WARNING, - (errmsg("corrupted statistics file \"%s\"", statfile))); + (errmsg("corrupted statistics file \"%s\"", statfile))); goto done; } @@ -5945,7 +6578,7 @@ static HTAB* pgstat_read_statsfile(Oid onlydb, bool permanent) */ if (fread(u_sess->stat_cxt.globalStats, 1, sizeof(PgStat_GlobalStats), fpin) != sizeof(PgStat_GlobalStats)) { ereport(u_sess->stat_cxt.pgStatRunningInCollector ? LOG : WARNING, - (errmsg("corrupted statistics file \"%s\"", statfile))); + (errmsg("corrupted statistics file \"%s\"", statfile))); goto done; } @@ -5964,17 +6597,17 @@ static HTAB* pgstat_read_statsfile(Oid onlydb, bool permanent) if (fread(&dbbuf, 1, offsetof(PgStat_StatDBEntry, tables), fpin) != offsetof(PgStat_StatDBEntry, tables)) { ereport(u_sess->stat_cxt.pgStatRunningInCollector ? LOG : WARNING, - (errmsg("corrupted statistics file \"%s\"", statfile))); + (errmsg("corrupted statistics file \"%s\"", statfile))); goto done; } /* * Add to the DB hash */ - dbentry = (PgStat_StatDBEntry*)hash_search(dbhash, (void*)&dbbuf.databaseid, HASH_ENTER, &found); + dbentry = (PgStat_StatDBEntry *)hash_search(dbhash, (void *)&dbbuf.databaseid, HASH_ENTER, &found); if (found) { ereport(u_sess->stat_cxt.pgStatRunningInCollector ? LOG : WARNING, - (errmsg("corrupted statistics file \"%s\"", statfile))); + (errmsg("corrupted statistics file \"%s\"", statfile))); goto done; } @@ -5998,17 +6631,15 @@ static HTAB* pgstat_read_statsfile(Oid onlydb, bool permanent) hash_ctl.entrysize = sizeof(PgStat_StatTabEntry); hash_ctl.hash = tag_hash; hash_ctl.hcxt = u_sess->stat_cxt.pgStatLocalContext; - dbentry->tables = hash_create( - "Per-database table", PGSTAT_TAB_HASH_SIZE, &hash_ctl, HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT); + dbentry->tables = hash_create("Per-database table", PGSTAT_TAB_HASH_SIZE, &hash_ctl, + HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT); hash_ctl.keysize = sizeof(Oid); hash_ctl.entrysize = sizeof(PgStat_StatFuncEntry); hash_ctl.hash = oid_hash; hash_ctl.hcxt = u_sess->stat_cxt.pgStatLocalContext; - dbentry->functions = hash_create("Per-database function", - PGSTAT_FUNCTION_HASH_SIZE, - &hash_ctl, - HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT); + dbentry->functions = hash_create("Per-database function", PGSTAT_FUNCTION_HASH_SIZE, &hash_ctl, + HASH_ELEM | HASH_FUNCTION | HASH_CONTEXT); /* * Arrange that following records add entries to this @@ -6032,7 +6663,7 @@ static HTAB* pgstat_read_statsfile(Oid onlydb, bool permanent) case 'T': if (fread(&tabbuf, 1, sizeof(PgStat_StatTabEntry), fpin) != sizeof(PgStat_StatTabEntry)) { ereport(u_sess->stat_cxt.pgStatRunningInCollector ? LOG : WARNING, - (errmsg("corrupted statistics file \"%s\"", statfile))); + (errmsg("corrupted statistics file \"%s\"", statfile))); goto done; } @@ -6042,11 +6673,11 @@ static HTAB* pgstat_read_statsfile(Oid onlydb, bool permanent) if (tabhash == NULL) break; - tabentry = (PgStat_StatTabEntry*)hash_search(tabhash, (void*)&(tabbuf.tablekey), HASH_ENTER, &found); + tabentry = (PgStat_StatTabEntry *)hash_search(tabhash, (void *)&(tabbuf.tablekey), HASH_ENTER, &found); if (found) { ereport(u_sess->stat_cxt.pgStatRunningInCollector ? LOG : WARNING, - (errmsg("corrupted statistics file \"%s\"", statfile))); + (errmsg("corrupted statistics file \"%s\"", statfile))); goto done; } @@ -6060,7 +6691,7 @@ static HTAB* pgstat_read_statsfile(Oid onlydb, bool permanent) case 'F': if (fread(&funcbuf, 1, sizeof(PgStat_StatFuncEntry), fpin) != sizeof(PgStat_StatFuncEntry)) { ereport(u_sess->stat_cxt.pgStatRunningInCollector ? LOG : WARNING, - (errmsg("corrupted statistics file \"%s\"", statfile))); + (errmsg("corrupted statistics file \"%s\"", statfile))); goto done; } @@ -6071,11 +6702,11 @@ static HTAB* pgstat_read_statsfile(Oid onlydb, bool permanent) break; funcentry = - (PgStat_StatFuncEntry*)hash_search(funchash, (void*)&funcbuf.functionid, HASH_ENTER, &found); + (PgStat_StatFuncEntry *)hash_search(funchash, (void *)&funcbuf.functionid, HASH_ENTER, &found); if (found) { ereport(u_sess->stat_cxt.pgStatRunningInCollector ? LOG : WARNING, - (errmsg("corrupted statistics file \"%s\"", statfile))); + (errmsg("corrupted statistics file \"%s\"", statfile))); goto done; } @@ -6096,8 +6727,8 @@ static HTAB* pgstat_read_statsfile(Oid onlydb, bool permanent) /* * Add to the DB hash */ - repairEntry = (BadBlockEntry*)hash_search(g_instance.repair_cxt.global_repair_bad_block_stat, - (void*)&(repairBuf.key), HASH_ENTER, &found); + repairEntry = (BadBlockEntry *)hash_search(g_instance.repair_cxt.global_repair_bad_block_stat, + (void *)&(repairBuf.key), HASH_ENTER, &found); if (found) { ereport(u_sess->stat_cxt.pgStatRunningInCollector ? LOG : WARNING, (errmsg("corrupted statistics file \"%s\"", statfile))); @@ -6119,7 +6750,7 @@ static HTAB* pgstat_read_statsfile(Oid onlydb, bool permanent) default: ereport(u_sess->stat_cxt.pgStatRunningInCollector ? LOG : WARNING, - (errmsg("corrupted statistics file \"%s\"", statfile))); + (errmsg("corrupted statistics file \"%s\"", statfile))); goto done; } } @@ -6140,12 +6771,12 @@ done: * Returns TRUE if successful (timestamp is stored at *ts). * ---------- */ -static bool pgstat_read_statsfile_timestamp(bool permanent, TimestampTz* ts) +static bool pgstat_read_statsfile_timestamp(bool permanent, TimestampTz *ts) { PgStat_GlobalStats myGlobalStats; - FILE* fpin = NULL; + FILE *fpin = NULL; int32 format_id; - const char* statfile = permanent ? PGSTAT_STAT_PERMANENT_FILENAME : u_sess->stat_cxt.pgstat_stat_filename; + const char *statfile = permanent ? PGSTAT_STAT_PERMANENT_FILENAME : u_sess->stat_cxt.pgstat_stat_filename; /* * Try to open the status file. As above, anything but ENOENT is worthy @@ -6154,7 +6785,7 @@ static bool pgstat_read_statsfile_timestamp(bool permanent, TimestampTz* ts) if ((fpin = AllocateFile(statfile, PG_BINARY_R)) == NULL) { if (errno != ENOENT) ereport(u_sess->stat_cxt.pgStatRunningInCollector ? LOG : WARNING, - (errcode_for_file_access(), errmsg("could not open statistics file \"%s\": %m", statfile))); + (errcode_for_file_access(), errmsg("could not open statistics file \"%s\": %m", statfile))); return false; } @@ -6163,7 +6794,7 @@ static bool pgstat_read_statsfile_timestamp(bool permanent, TimestampTz* ts) */ if (fread(&format_id, 1, sizeof(format_id), fpin) != sizeof(format_id) || format_id != PGSTAT_FILE_FORMAT_ID) { ereport(u_sess->stat_cxt.pgStatRunningInCollector ? LOG : WARNING, - (errmsg("corrupted statistics file \"%s\"", statfile))); + (errmsg("corrupted statistics file \"%s\"", statfile))); (void)FreeFile(fpin); return false; } @@ -6173,7 +6804,7 @@ static bool pgstat_read_statsfile_timestamp(bool permanent, TimestampTz* ts) */ if (fread(&myGlobalStats, 1, sizeof(myGlobalStats), fpin) != sizeof(myGlobalStats)) { ereport(u_sess->stat_cxt.pgStatRunningInCollector ? LOG : WARNING, - (errmsg("corrupted statistics file \"%s\"", statfile))); + (errmsg("corrupted statistics file \"%s\"", statfile))); (void)FreeFile(fpin); return false; } @@ -6244,9 +6875,8 @@ static void backend_read_statsfile(void) } if (count >= PGSTAT_POLL_LOOP_COUNT) - ereport(LOG, - (errmsg("using stale statistics instead of current ones " - "because stats collector is not responding"))); + ereport(LOG, (errmsg("using stale statistics instead of current ones " + "because stats collector is not responding"))); /* Autovacuum launcher and the global stats tracker want stats about all databases */ if (IsAutoVacuumLauncherProcess() || IsGlobalStatsTrackerProcess()) @@ -6258,15 +6888,15 @@ static void backend_read_statsfile(void) void pgstat_read_analyzed() { /* Similar to pgstat_read_statsfile. */ - FILE* fpin = NULL; + FILE *fpin = NULL; int32 format_id; PgStat_StatDBEntry dbbuf; PgStat_StatTabEntry tabbuf; PgStat_StatFuncEntry funcbuf; HASHCTL hash_ctl; errno_t errorno = EOK; - PgStat_AnaCheckEntry* tabentry = NULL; - const char* statfile = u_sess->stat_cxt.pgstat_stat_filename; + PgStat_AnaCheckEntry *tabentry = NULL; + const char *statfile = u_sess->stat_cxt.pgstat_stat_filename; bool need_collected = false; bool found = false; @@ -6296,9 +6926,8 @@ void pgstat_read_analyzed() } if (count >= PGSTAT_POLL_LOOP_COUNT) - ereport(LOG, - (errmsg("using stale statistics instead of current ones " - "because stats collector is not responding"))); + ereport(LOG, (errmsg("using stale statistics instead of current ones " + "because stats collector is not responding"))); /* * Similar to pgstat_read_statsfile. @@ -6323,7 +6952,7 @@ void pgstat_read_analyzed() if ((fpin = AllocateFile(statfile, PG_BINARY_R)) == NULL) { if (errno != ENOENT) ereport(u_sess->stat_cxt.pgStatRunningInCollector ? LOG : WARNING, - (errcode_for_file_access(), errmsg("could not open statistics file \"%s\": %m", statfile))); + (errcode_for_file_access(), errmsg("could not open statistics file \"%s\": %m", statfile))); elog(LOG, "[Pgstat] statfile %s is missing, using empty dbhash.", statfile); return; } @@ -6331,14 +6960,14 @@ void pgstat_read_analyzed() /* 2.Verify it's of the expected format */ if (fread(&format_id, 1, sizeof(format_id), fpin) != sizeof(format_id) || format_id != PGSTAT_FILE_FORMAT_ID) { ereport(u_sess->stat_cxt.pgStatRunningInCollector ? LOG : WARNING, - (errmsg("corrupted statistics file \"%s\"", statfile))); + (errmsg("corrupted statistics file \"%s\"", statfile))); goto done; } /* 3.Read global stats struct */ if (fread(u_sess->stat_cxt.globalStats, 1, sizeof(PgStat_GlobalStats), fpin) != sizeof(PgStat_GlobalStats)) { ereport(u_sess->stat_cxt.pgStatRunningInCollector ? LOG : WARNING, - (errmsg("corrupted statistics file \"%s\"", statfile))); + (errmsg("corrupted statistics file \"%s\"", statfile))); goto done; } @@ -6353,7 +6982,7 @@ void pgstat_read_analyzed() if (fread(&dbbuf, 1, offsetof(PgStat_StatDBEntry, tables), fpin) != offsetof(PgStat_StatDBEntry, tables)) { ereport(u_sess->stat_cxt.pgStatRunningInCollector ? LOG : WARNING, - (errmsg("corrupted statistics file \"%s\"", statfile))); + (errmsg("corrupted statistics file \"%s\"", statfile))); goto done; } @@ -6374,7 +7003,7 @@ void pgstat_read_analyzed() case 'T': if (fread(&tabbuf, 1, sizeof(PgStat_StatTabEntry), fpin) != sizeof(PgStat_StatTabEntry)) { ereport(u_sess->stat_cxt.pgStatRunningInCollector ? LOG : WARNING, - (errmsg("corrupted statistics file \"%s\"", statfile))); + (errmsg("corrupted statistics file \"%s\"", statfile))); goto done; } @@ -6382,11 +7011,11 @@ void pgstat_read_analyzed() if (!need_collected || tabbuf.tablekey.statFlag != STATFLG_RELATION) break; - tabentry = (PgStat_AnaCheckEntry*)hash_search( - u_sess->stat_cxt.analyzeCheckHash, (void*)&(tabbuf.tablekey.tableid), HASH_ENTER, &found); + tabentry = (PgStat_AnaCheckEntry *)hash_search(u_sess->stat_cxt.analyzeCheckHash, + (void *)&(tabbuf.tablekey.tableid), HASH_ENTER, &found); if (found) { ereport(u_sess->stat_cxt.pgStatRunningInCollector ? LOG : WARNING, - (errmsg("corrupted statistics file \"%s\"", statfile))); + (errmsg("corrupted statistics file \"%s\"", statfile))); goto done; } @@ -6401,7 +7030,7 @@ void pgstat_read_analyzed() case 'F': if (fread(&funcbuf, 1, sizeof(PgStat_StatFuncEntry), fpin) != sizeof(PgStat_StatFuncEntry)) { ereport(u_sess->stat_cxt.pgStatRunningInCollector ? LOG : WARNING, - (errmsg("corrupted statistics file \"%s\"", statfile))); + (errmsg("corrupted statistics file \"%s\"", statfile))); goto done; } @@ -6413,7 +7042,7 @@ void pgstat_read_analyzed() default: ereport(u_sess->stat_cxt.pgStatRunningInCollector ? LOG : WARNING, - (errmsg("corrupted statistics file \"%s\"", statfile))); + (errmsg("corrupted statistics file \"%s\"", statfile))); goto done; } } @@ -6428,16 +7057,26 @@ done: * Create u_sess->stat_cxt.pgStatLocalContext, if not already done. * ---------- */ +/* + * 功能:设置用于统计信息快照的内存上下文 + * + * 注意: + * 如果当前会话的统计信息快照的内存上下文尚未创建(即为NULL),则创建一个新的内存上下文,并将其设置为统计信息的内存上下文。 + */ void pgstat_setup_memcxt(void) { if (u_sess->stat_cxt.pgStatLocalContext == NULL) - u_sess->stat_cxt.pgStatLocalContext = AllocSetContextCreate(u_sess->top_mem_cxt, - "Statistics snapshot", - ALLOCSET_SMALL_MINSIZE, - ALLOCSET_SMALL_INITSIZE, - ALLOCSET_SMALL_MAXSIZE); + u_sess->stat_cxt.pgStatLocalContext = + AllocSetContextCreate(u_sess->top_mem_cxt, "Statistics snapshot", ALLOCSET_SMALL_MINSIZE, + ALLOCSET_SMALL_INITSIZE, ALLOCSET_SMALL_MAXSIZE); } +/* + * 功能:清理统计信息快照的内存上下文 + * + * 注意: + * 如果当前会话的统计信息快照的内存上下文存在(即不为NULL),则删除该内存上下文。 + */ void pgstat_clean_memcxt(void) { if (u_sess->stat_cxt.pgStatLocalContext != NULL) { @@ -6446,23 +7085,39 @@ void pgstat_clean_memcxt(void) } } +/* + * 功能:设置用于收集线程状态的内存上下文 + * + * 注意: + * 该函数用于设置用于收集线程状态的内存上下文。首先,它会检查当前会话的统计信息快照的内存上下文是否存在(不为NULL),并断言这一点。 + * 接着,它会确保用于收集线程状态的内存上下文尚未分配(即u_sess->stat_cxt.pgStatCollectThdStatusContext为NULL), + * 如果已分配则会引发断言错误。 + * 最后,它会通过AllocSetContextCreate创建一个新的内存上下文,作为线程状态收集的内存上下文,并将其设置为当前会话的统计信息快照的内存上下文的子上下文。 + */ static void pgstat_collect_thread_status_setup_memcxt(void) { - Assert(u_sess->stat_cxt.pgStatLocalContext); + Assert(u_sess->stat_cxt.pgStatLocalContext); // 确保统计信息快照的内存上下文存在 + // 断言线程状态收集的内存上下文尚未分配 Assert(!u_sess->stat_cxt.pgStatCollectThdStatusContext); - - u_sess->stat_cxt.pgStatCollectThdStatusContext = AllocSetContextCreate(u_sess->stat_cxt.pgStatLocalContext, - "PgStatCollectThdStatus", - ALLOCSET_SMALL_MINSIZE, - ALLOCSET_SMALL_INITSIZE, - ALLOCSET_SMALL_MAXSIZE); + // 创建线程状态收集的内存上下文 + u_sess->stat_cxt.pgStatCollectThdStatusContext = + AllocSetContextCreate(u_sess->stat_cxt.pgStatLocalContext, "PgStatCollectThdStatus", ALLOCSET_SMALL_MINSIZE, + ALLOCSET_SMALL_INITSIZE, ALLOCSET_SMALL_MAXSIZE); } +/* + * 功能:清理线程状态收集相关的资源 + * + * 注意: + * 该函数用于清理线程状态收集相关的资源。它首先删除用于线程状态收集的内存上下文(u_sess->stat_cxt.pgStatCollectThdStatusContext), + * 然后将该内存上下文设置为NULL。接着,将当前会话的本地后端状态表(localBackendStatusTable)和本地后端数目(localNumBackends)都设置为NULL或0。 + */ static void pgstat_collect_thread_status_clear_resource(void) { + // 删除用于线程状态收集的内存上下文 MemoryContextDelete(u_sess->stat_cxt.pgStatCollectThdStatusContext); - /* Reset variables */ + // 将相关变量设置为NULL或0 u_sess->stat_cxt.pgStatCollectThdStatusContext = NULL; u_sess->stat_cxt.localBackendStatusTable = NULL; u_sess->stat_cxt.localNumBackends = 0; @@ -6498,22 +7153,39 @@ void pgstat_clear_snapshot(void) * Process stat inquiry requests. * ---------- */ -static void pgstat_recv_inquiry(PgStat_MsgInquiry* msg, int len) +static void pgstat_recv_inquiry(PgStat_MsgInquiry *msg, int len) { if (msg->inquiry_time > g_instance.stat_cxt.last_statrequest) g_instance.stat_cxt.last_statrequest = msg->inquiry_time; } -static void inline init_tabentry_truncate(PgStat_StatTabEntry* tabentry, PgStat_TableEntry* tabmsg) +/* + * 功能:初始化表的统计信息,用于截断操作 + * + * 注意: + * 该函数用于初始化表的统计信息,主要用于截断操作。它将给定的表统计信息结构体(PgStat_StatTabEntry)的存活元组数(n_live_tuples)和死亡元组数(n_dead_tuples)都设置为0。 + */ +static void inline init_tabentry_truncate(PgStat_StatTabEntry *tabentry, PgStat_TableEntry *tabmsg) { + // 将存活元组数和死亡元组数都设置为0 tabentry->n_live_tuples = 0; tabentry->n_dead_tuples = 0; } +/* + * 功能:检查加法操作是否会导致整数溢出 + * + * 参数列表: + * stat_value:当前统计值 + * add_value:要添加到当前统计值的值 + * + * 注意: + * 如果将 add_value 添加到 stat_value 会导致整数溢出,函数将触发错误报告并终止程序执行。 + */ static void overflow_check(int64 stat_value, int64 add_value) { if (stat_value > LONG_LONG_MAX - add_value) { - ereport(ERROR, (errmsg("Integer overflow when update database-wid stats"))); + ereport(ERROR, (errmsg("Integer overflow when updating database-wide statistics"))); } } @@ -6523,10 +7195,10 @@ static void overflow_check(int64 stat_value, int64 add_value) * Count what the backend has done. * ---------- */ -static void pgstat_recv_tabstat(PgStat_MsgTabstat* msg, int len) +static void pgstat_recv_tabstat(PgStat_MsgTabstat *msg, int len) { - PgStat_StatDBEntry* dbentry = NULL; - PgStat_StatTabEntry* tabentry = NULL; + PgStat_StatDBEntry *dbentry = NULL; + PgStat_StatTabEntry *tabentry = NULL; int i; bool found = false; @@ -6552,13 +7224,13 @@ static void pgstat_recv_tabstat(PgStat_MsgTabstat* msg, int len) * Process all table entries in the message. */ for (i = 0; i < msg->m_nentries; i++) { - PgStat_TableEntry* tabmsg = &(msg->m_entry[i]); + PgStat_TableEntry *tabmsg = &(msg->m_entry[i]); PgStat_StatTabKey tabkey; tabkey.statFlag = tabmsg->t_statFlag; tabkey.tableid = tabmsg->t_id; - tabentry = (PgStat_StatTabEntry*)hash_search(dbentry->tables, (void*)(&tabkey), HASH_ENTER, &found); + tabentry = (PgStat_StatTabEntry *)hash_search(dbentry->tables, (void *)(&tabkey), HASH_ENTER, &found); if (!found) { /* @@ -6651,7 +7323,7 @@ static void pgstat_recv_tabstat(PgStat_MsgTabstat* msg, int len) tabkey.tableid = tabmsg->t_statFlag; tabkey.statFlag = InvalidOid; - tabentry = (PgStat_StatTabEntry*)hash_search(dbentry->tables, (void*)(&tabkey), HASH_ENTER, &found); + tabentry = (PgStat_StatTabEntry *)hash_search(dbentry->tables, (void *)(&tabkey), HASH_ENTER, &found); if (!found) { /* @@ -6717,13 +7389,13 @@ static void pgstat_recv_tabstat(PgStat_MsgTabstat* msg, int len) * Arrange for dead table removal. * ---------- */ -static void pgstat_recv_tabpurge(PgStat_MsgTabpurge* msg, int len) +static void pgstat_recv_tabpurge(PgStat_MsgTabpurge *msg, int len) { - PgStat_StatDBEntry* dbentry = NULL; + PgStat_StatDBEntry *dbentry = NULL; PgStat_StatTabKey tabkey; - PgStat_StatTabEntry* tabentry = NULL; + PgStat_StatTabEntry *tabentry = NULL; PgStat_StatTabKey parentkey; - PgStat_StatTabEntry* parententry = NULL; + PgStat_StatTabEntry *parententry = NULL; int i; dbentry = pgstat_get_db_entry(msg->m_databaseid, false); @@ -6740,13 +7412,13 @@ static void pgstat_recv_tabpurge(PgStat_MsgTabpurge* msg, int len) tabkey.statFlag = msg->m_entry[i].m_statFlag; tabkey.tableid = msg->m_entry[i].m_tableid; - tabentry = (PgStat_StatTabEntry*)hash_search(dbentry->tables, (void*)(&tabkey), HASH_FIND, NULL); + tabentry = (PgStat_StatTabEntry *)hash_search(dbentry->tables, (void *)(&tabkey), HASH_FIND, NULL); /* modify parent table's stat info if it is a patition */ if ((tabentry != NULL) && tabkey.statFlag) { parentkey.tableid = tabkey.statFlag; parentkey.statFlag = InvalidOid; - parententry = (PgStat_StatTabEntry*)hash_search(dbentry->tables, (void*)(&parentkey), HASH_FIND, NULL); + parententry = (PgStat_StatTabEntry *)hash_search(dbentry->tables, (void *)(&parentkey), HASH_FIND, NULL); if (parententry != NULL) { parententry->n_dead_tuples = Max(0, parententry->n_dead_tuples - tabentry->n_dead_tuples); parententry->n_live_tuples = Max(0, parententry->n_live_tuples - tabentry->n_live_tuples); @@ -6755,7 +7427,7 @@ static void pgstat_recv_tabpurge(PgStat_MsgTabpurge* msg, int len) } /* Remove from hashtable if present; we don't care if it's not. */ - (void*)hash_search(dbentry->tables, (void*)(&tabkey), HASH_REMOVE, NULL); + (void *)hash_search(dbentry->tables, (void *)(&tabkey), HASH_REMOVE, NULL); } } @@ -6765,9 +7437,9 @@ static void pgstat_recv_tabpurge(PgStat_MsgTabpurge* msg, int len) * Arrange for dead database removal * ---------- */ -static void pgstat_recv_dropdb(PgStat_MsgDropdb* msg, int len) +static void pgstat_recv_dropdb(PgStat_MsgDropdb *msg, int len) { - PgStat_StatDBEntry* dbentry = NULL; + PgStat_StatDBEntry *dbentry = NULL; Oid db_oid = msg->m_databaseid; /* * Lookup the database in the hashtable. @@ -6783,11 +7455,9 @@ static void pgstat_recv_dropdb(PgStat_MsgDropdb* msg, int len) if (dbentry->functions != NULL) hash_destroy(dbentry->functions); - if (hash_search(u_sess->stat_cxt.pgStatDBHash, (void*)&(db_oid), HASH_REMOVE, NULL) == NULL) { - ereport(ERROR, - (errcode(ERRCODE_DATA_CORRUPTED), - errmsg("database hash table corrupted " - "during cleanup --- abort"))); + if (hash_search(u_sess->stat_cxt.pgStatDBHash, (void *)&(db_oid), HASH_REMOVE, NULL) == NULL) { + ereport(ERROR, (errcode(ERRCODE_DATA_CORRUPTED), errmsg("database hash table corrupted " + "during cleanup --- abort"))); } } @@ -6795,7 +7465,6 @@ static void pgstat_recv_dropdb(PgStat_MsgDropdb* msg, int len) * delete database related hotkeys statistics */ g_instance.stat_cxt.lru->DeleteHotkeysInDB(db_oid); - } /* ---------- @@ -6804,10 +7473,10 @@ static void pgstat_recv_dropdb(PgStat_MsgDropdb* msg, int len) * Reset the statistics for the specified database. * ---------- */ -static void pgstat_recv_resetcounter(PgStat_MsgResetcounter* msg, int len) +static void pgstat_recv_resetcounter(PgStat_MsgResetcounter *msg, int len) { HASHCTL hash_ctl; - PgStat_StatDBEntry* dbentry = NULL; + PgStat_StatDBEntry *dbentry = NULL; errno_t rc = EOK; /* @@ -6876,7 +7545,7 @@ static void pgstat_recv_resetcounter(PgStat_MsgResetcounter* msg, int len) * Reset some shared statistics of the cluster. * ---------- */ -static void pgstat_recv_resetsharedcounter(PgStat_MsgResetsharedcounter* msg, int len) +static void pgstat_recv_resetsharedcounter(PgStat_MsgResetsharedcounter *msg, int len) { errno_t rc = EOK; @@ -6900,9 +7569,9 @@ static void pgstat_recv_resetsharedcounter(PgStat_MsgResetsharedcounter* msg, in * Reset a statistics for a single object * ---------- */ -static void pgstat_recv_resetsinglecounter(PgStat_MsgResetsinglecounter* msg, int len) +static void pgstat_recv_resetsinglecounter(PgStat_MsgResetsinglecounter *msg, int len) { - PgStat_StatDBEntry* dbentry = NULL; + PgStat_StatDBEntry *dbentry = NULL; dbentry = pgstat_get_db_entry(msg->m_databaseid, false); if (dbentry == NULL) @@ -6919,9 +7588,9 @@ static void pgstat_recv_resetsinglecounter(PgStat_MsgResetsinglecounter* msg, in tabkey.statFlag = msg->p_objectid; tabkey.tableid = msg->m_objectid; - (void)hash_search(dbentry->tables, (void*)&(tabkey), HASH_REMOVE, NULL); + (void)hash_search(dbentry->tables, (void *)&(tabkey), HASH_REMOVE, NULL); } else if (msg->m_resettype == RESET_FUNCTION) { - (void)hash_search(dbentry->functions, (void*)&(msg->m_objectid), HASH_REMOVE, NULL); + (void)hash_search(dbentry->functions, (void *)&(msg->m_objectid), HASH_REMOVE, NULL); } } @@ -6931,9 +7600,9 @@ static void pgstat_recv_resetsinglecounter(PgStat_MsgResetsinglecounter* msg, in * Process an autovacuum signalling message. * ---------- */ -static void pgstat_recv_autovac(PgStat_MsgAutovacStart* msg, int len) +static void pgstat_recv_autovac(PgStat_MsgAutovacStart *msg, int len) { - PgStat_StatDBEntry* dbentry = NULL; + PgStat_StatDBEntry *dbentry = NULL; /* * Store the last autovacuum time in the database's hashtable entry. @@ -6949,10 +7618,10 @@ static void pgstat_recv_autovac(PgStat_MsgAutovacStart* msg, int len) * Process a VACUUM message. * ---------- */ -static void pgstat_recv_vacuum(PgStat_MsgVacuum* msg, int len) +static void pgstat_recv_vacuum(PgStat_MsgVacuum *msg, int len) { - PgStat_StatDBEntry* dbentry = NULL; - PgStat_StatTabEntry* tabentry = NULL; + PgStat_StatDBEntry *dbentry = NULL; + PgStat_StatTabEntry *tabentry = NULL; /* * Store the data in the table's hashtable entry. @@ -6983,10 +7652,10 @@ static void pgstat_recv_vacuum(PgStat_MsgVacuum* msg, int len) * Process a pruning message * ---------- */ -static void PgstatRecvPrunestat(PgStat_MsgPrune* msg, int len) +static void PgstatRecvPrunestat(PgStat_MsgPrune *msg, int len) { - PgStat_StatDBEntry* dbentry = NULL; - PgStat_StatTabEntry* tabentry = NULL; + PgStat_StatDBEntry *dbentry = NULL; + PgStat_StatTabEntry *tabentry = NULL; /* * Store the data in the table's hashtable entry. @@ -6998,17 +7667,16 @@ static void PgstatRecvPrunestat(PgStat_MsgPrune* msg, int len) tabentry->total_prune_cnt += msg->m_scanned_blocks; } - /* ---------- * pgstat_recv_data_changed() - * * Process a insert/delete/update/copy/[exchange/truncate/drop] partition message. * ---------- */ -static void pgstat_recv_data_changed(PgStat_MsgDataChanged* msg, int len) +static void pgstat_recv_data_changed(PgStat_MsgDataChanged *msg, int len) { - PgStat_StatDBEntry* dbentry = NULL; - PgStat_StatTabEntry* tabentry = NULL; + PgStat_StatDBEntry *dbentry = NULL; + PgStat_StatTabEntry *tabentry = NULL; /* * Store the data in the table's hashtable entry. @@ -7026,10 +7694,10 @@ static void pgstat_recv_data_changed(PgStat_MsgDataChanged* msg, int len) * Process a autovac stat message. * ---------- */ -static void pgstat_recv_autovac_stat(PgStat_MsgAutovacStat* msg, int len) +static void pgstat_recv_autovac_stat(PgStat_MsgAutovacStat *msg, int len) { - PgStat_StatDBEntry* dbentry = NULL; - PgStat_StatTabEntry* tabentry = NULL; + PgStat_StatDBEntry *dbentry = NULL; + PgStat_StatTabEntry *tabentry = NULL; /* * Store the data in the table's hashtable entry. @@ -7048,11 +7716,11 @@ static void pgstat_recv_autovac_stat(PgStat_MsgAutovacStat* msg, int len) * Process a TRUNCATE message. * ---------- */ -static void pgstat_recv_truncate(PgStat_MsgTruncate* msg, int len) +static void pgstat_recv_truncate(PgStat_MsgTruncate *msg, int len) { - PgStat_StatDBEntry* dbentry = NULL; - PgStat_StatTabEntry* tabentry = NULL; - PgStat_StatTabEntry* parent_entry = NULL; + PgStat_StatDBEntry *dbentry = NULL; + PgStat_StatTabEntry *tabentry = NULL; + PgStat_StatTabEntry *parent_entry = NULL; /* * Store the data in the table's hashtable entry. @@ -7082,10 +7750,10 @@ static void pgstat_recv_truncate(PgStat_MsgTruncate* msg, int len) * Process an ANALYZE message. * ---------- */ -static void pgstat_recv_analyze(PgStat_MsgAnalyze* msg, int len) +static void pgstat_recv_analyze(PgStat_MsgAnalyze *msg, int len) { - PgStat_StatDBEntry* dbentry = NULL; - PgStat_StatTabEntry* tabentry = NULL; + PgStat_StatDBEntry *dbentry = NULL; + PgStat_StatTabEntry *tabentry = NULL; /* * Store the data in the table's hashtable entry. @@ -7120,7 +7788,7 @@ static void pgstat_recv_analyze(PgStat_MsgAnalyze* msg, int len) * Process a BGWRITER message. * ---------- */ -static void pgstat_recv_bgwriter(PgStat_MsgBgWriter* msg, int len) +static void pgstat_recv_bgwriter(PgStat_MsgBgWriter *msg, int len) { if (u_sess->stat_cxt.globalStats->timed_checkpoints > (INT64_MAX - msg->m_timed_checkpoints)) { ereport(ERROR, (errmsg("timed_checkpoints overflow"))); @@ -7179,9 +7847,9 @@ static void pgstat_recv_bgwriter(PgStat_MsgBgWriter* msg, int len) * Process a RECOVERYCONFLICT message. * ---------- */ -static void pgstat_recv_recoveryconflict(const PgStat_MsgRecoveryConflict* msg) +static void pgstat_recv_recoveryconflict(const PgStat_MsgRecoveryConflict *msg) { - PgStat_StatDBEntry* dbentry = NULL; + PgStat_StatDBEntry *dbentry = NULL; dbentry = pgstat_get_db_entry(msg->m_databaseid, true); @@ -7209,9 +7877,8 @@ static void pgstat_recv_recoveryconflict(const PgStat_MsgRecoveryConflict* msg) dbentry->n_conflict_startup_deadlock++; break; default: - ereport(ERROR, - (errcode(ERRCODE_CASE_NOT_FOUND), - errmsg("unrecognized bypass recovery conflict reason: %d", msg->m_reason))); + ereport(ERROR, (errcode(ERRCODE_CASE_NOT_FOUND), + errmsg("unrecognized bypass recovery conflict reason: %d", msg->m_reason))); } } @@ -7221,9 +7888,9 @@ static void pgstat_recv_recoveryconflict(const PgStat_MsgRecoveryConflict* msg) * Process a DEADLOCK message. * ---------- */ -static void pgstat_recv_deadlock(const PgStat_MsgDeadlock* msg) +static void pgstat_recv_deadlock(const PgStat_MsgDeadlock *msg) { - PgStat_StatDBEntry* dbentry = NULL; + PgStat_StatDBEntry *dbentry = NULL; dbentry = pgstat_get_db_entry(msg->m_databaseid, true); @@ -7236,16 +7903,24 @@ static void pgstat_recv_deadlock(const PgStat_MsgDeadlock* msg) * Process a TEMPFILE message. * ---------- */ -static void pgstat_recv_tempfile(PgStat_MsgTempFile* msg, int len) +static void pgstat_recv_tempfile(PgStat_MsgTempFile *msg, int len) { - PgStat_StatDBEntry* dbentry = NULL; + PgStat_StatDBEntry *dbentry = NULL; dbentry = pgstat_get_db_entry(msg->m_databaseid, true); dbentry->n_temp_bytes += msg->m_filesize; dbentry->n_temp_files += 1; } - +/* + * 功能:更新响应时间百分位数统计 + * + * 注意: + * 该函数用于更新响应时间百分位数的统计信息。它首先获取当前语句的开始时间, + * 然后计算从开始时间到当前时间的持续时间。如果当前节点是单节点系统, + * 它会调用 pgstat_update_responstime_singlenode 函数来更新响应时间统计信息, + * 否则,它会调用 pgstat_report_sql_rt 函数来汇报响应时间统计信息。 + */ void pgstate_update_percentile_responsetime(void) { if (IS_PGXC_COORDINATOR || IS_SINGLE_NODE) { @@ -7260,7 +7935,21 @@ void pgstate_update_percentile_responsetime(void) } } } - +/* + * 功能:更新单节点系统的响应时间统计信息 + * + * 参数列表: + * UniqueSQLId:唯一SQL标识符 + * start_time:SQL语句的开始时间 + * rt:SQL语句的响应时间 + * + * 注意: + * 该函数用于更新单节点系统的响应时间统计信息。首先,它检查是否启用了响应时间统计。 + * 然后,它检查是否需要强制处理或者 SQL 响应时间信息数组是否为 NULL。 + * 如果需要强制处理或者数组已满,函数将设置标志并释放锁然后返回。 + * 否则,它将获取一个新的 SQL 响应时间统计信息的索引,并将相关信息写入数组中。 + * 最后,它释放锁。 + */ void pgstat_update_responstime_singlenode(uint64 UniqueSQLId, int64 start_time, int64 rt) { int32 sqlRTIndex; @@ -7287,51 +7976,102 @@ void pgstat_update_responstime_singlenode(uint64 UniqueSQLId, int64 start_time, LWLockRelease(PercentileLock); } -static void pgstat_recv_sql_responstime(PgStat_SqlRT* msg, int len) +/* + * 功能:接收并处理 SQL 响应时间统计信息 + * + * 参数列表: + * msg:接收到的响应时间统计信息 + * len:信息长度 + * + * 注意: + * 该函数用于接收和处理 SQL 响应时间统计信息。 + * 首先,它检查当前系统是否启用响应时间统计,如果未启用则直接返回。 + * 接着,它获取 SQL 响应时间信息数组的当前索引 sqlRTIndex。 + * 如果 sqlRTIndex 达到了最大限制 MAX_SQL_RT_INFO_COUNT,表示数组已满, + * 那么会标记数组为已满(isFull),并根据当前节点类型决定是否触发强制处理(force_process)。 + * 如果当前系统是协调节点,则会设置 force_process 为 true,然后释放 LWLock 并等待 force_process 处理完成。 + * 处理完成后重新获取 LWLock 并重置 sqlRTIndex 为 0。 + * 然后,将接收到的响应时间信息存储到数组中,并将 sqlRTIndex 加 1。 + * 最后,释放 LWLock。 + */ +static void pgstat_recv_sql_responstime(PgStat_SqlRT *msg, int len) { - + // 检查是否启用响应时间统计,如果未启用则直接返回 if (g_instance.stat_cxt.sql_rt_info_array == NULL) { return; } - + // 获取 SQL 响应时间信息数组的当前索引 sqlRTIndex LWLockAcquire(PercentileLock, LW_EXCLUSIVE); - int32 sqlRTIndex = g_instance.stat_cxt.sql_rt_info_array->sqlRTIndex; - + // 如果数组已满,标记为已满(isFull),根据节点类型决定是否触发强制处理(force_process) if (sqlRTIndex == MAX_SQL_RT_INFO_COUNT) { - g_instance.stat_cxt.sql_rt_info_array->isFull = true; // other cn will cover the value from first one + g_instance.stat_cxt.sql_rt_info_array->isFull = true; + // 如果当前节点是协调节点,则设置 force_process 为 true,并释放 LWLock 后等待 force_process 处理完成 if (PgxcIsCentralCoordinator(g_instance.attr.attr_common.PGXCNodeName)) { g_instance.stat_cxt.force_process = true; LWLockRelease(PercentileLock); while (g_instance.stat_cxt.force_process) { - pg_usleep(PGSTAT_RETRY_DELAY); // CCN wait force process percentile done + pg_usleep(PGSTAT_RETRY_DELAY); // 等待协调节点处理强制处理请求完成 } LWLockAcquire(PercentileLock, LW_EXCLUSIVE); } + // 重置 sqlRTIndex 为 0 sqlRTIndex = 0; g_instance.stat_cxt.sql_rt_info_array->sqlRTIndex = sqlRTIndex; } - + // 将接收到的响应时间信息存储到数组中,并将 sqlRTIndex 加 1 g_instance.stat_cxt.sql_rt_info_array->sqlRT[sqlRTIndex].UniqueSQLId = msg->sqlRT.UniqueSQLId; g_instance.stat_cxt.sql_rt_info_array->sqlRT[sqlRTIndex].start_time = msg->sqlRT.start_time; g_instance.stat_cxt.sql_rt_info_array->sqlRT[sqlRTIndex].rt = msg->sqlRT.rt; g_instance.stat_cxt.sql_rt_info_array->sqlRTIndex = g_instance.stat_cxt.sql_rt_info_array->sqlRTIndex + 1; + // 释放 LWLock LWLockRelease(PercentileLock); } -static int sqlRTComparator(const void* s1, const void* s2) +/* + * 功能:用于比较两个 SQL 响应时间信息的比较函数 + * + * 参数列表: + * s1:要比较的第一个 SQL 响应时间信息指针 + * s2:要比较的第二个 SQL 响应时间信息指针 + * + * 返回值: + * 如果第一个 SQL 响应时间信息的响应时间大于第二个,则返回 1; + * 如果第一个小于第二个,则返回 -1; + * 如果相等,则返回 0。 + * + * 注意: + * 该函数用于 qsort 等排序函数的比较操作。 + * 它比较两个 SQL 响应时间信息的响应时间字段(rt)的大小, + * 如果第一个大于第二个,返回 1; + * 如果第一个小于第二个,返回 -1; + * 如果相等,返回 0。 + */ +static int sqlRTComparator(const void *s1, const void *s2) { - const SqlRTInfo* sqlRT1 = (SqlRTInfo*)s1; - const SqlRTInfo* sqlRT2 = (SqlRTInfo*)s2; - if (sqlRT1->rt > sqlRT2->rt) - return 1; - else if (sqlRT1->rt < sqlRT2->rt) - return -1; - else - return 0; -} + const SqlRTInfo *sqlRT1 = (SqlRTInfo *)s1; + const SqlRTInfo *sqlRT2 = (SqlRTInfo *)s2; -static void prepare_calculate(SqlRTInfoArray* sql_rt_info, int* counter) + if (sqlRT1->rt > sqlRT2->rt) { + return 1; + } else if (sqlRT1->rt < sqlRT2->rt) { + return -1; + } else { + return 0; + } +} +/* + * 功能:准备进行百分位计算 + * + * 参数列表: + * sql_rt_info:包含 SQL 响应时间信息的数组 + * counter:计数器,用于返回要进行百分位计算的 SQL 数量 + * + * 注意: + * 该函数用于准备进行百分位计算,首先获取锁,然后根据 SQL 响应时间信息数组的情况, + * 初始化计数器和相关数据结构,最后释放锁并返回计数器值。 + */ +static void prepare_calculate(SqlRTInfoArray *sql_rt_info, int *counter) { int sql_rt_info_count = 0; LWLockAcquire(PercentileLock, LW_SHARED); @@ -7351,18 +8091,14 @@ static void prepare_calculate(SqlRTInfoArray* sql_rt_info, int* counter) if (u_sess->percentile_cxt.LocalsqlRT != NULL) pfree_ext(u_sess->percentile_cxt.LocalsqlRT); - u_sess->percentile_cxt.LocalsqlRT = (SqlRTInfo*)MemoryContextAlloc( - SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_DFX), sql_rt_info_count * sizeof(SqlRTInfo)); + u_sess->percentile_cxt.LocalsqlRT = (SqlRTInfo *)MemoryContextAlloc(SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_DFX), + sql_rt_info_count * sizeof(SqlRTInfo)); - int ss_rc = memset_s(u_sess->percentile_cxt.LocalsqlRT, - sql_rt_info_count * sizeof(SqlRTInfo), - 0, - sql_rt_info_count * sizeof(SqlRTInfo)); + int ss_rc = memset_s(u_sess->percentile_cxt.LocalsqlRT, sql_rt_info_count * sizeof(SqlRTInfo), 0, + sql_rt_info_count * sizeof(SqlRTInfo)); securec_check(ss_rc, "\0", "\0"); - ss_rc = memcpy_s(u_sess->percentile_cxt.LocalsqlRT, - sql_rt_info_count * sizeof(SqlRTInfo), - sql_rt_info->sqlRT, - sql_rt_info_count * sizeof(SqlRTInfo)); + ss_rc = memcpy_s(u_sess->percentile_cxt.LocalsqlRT, sql_rt_info_count * sizeof(SqlRTInfo), sql_rt_info->sqlRT, + sql_rt_info_count * sizeof(SqlRTInfo)); securec_check(ss_rc, "\0", "\0"); sql_rt_info->isFull = false; @@ -7371,36 +8107,60 @@ static void prepare_calculate(SqlRTInfoArray* sql_rt_info, int* counter) *counter = sql_rt_info_count; qsort(u_sess->percentile_cxt.LocalsqlRT, sql_rt_info_count, sizeof(SqlRTInfo), sqlRTComparator); } - -static void prepare_calculate_single(const SqlRTInfoArray* sql_rt_info, int* counter) +/* + * 功能:准备进行百分位计算(用于单节点情况) + * + * 参数列表: + * sql_rt_info:包含 SQL 响应时间信息的数组 + * counter:计数器,用于返回要进行百分位计算的 SQL 数量 + * + * 注意: + * 该函数用于准备进行百分位计算,但仅适用于单节点情况。首先获取当前 SQL 响应时间信息 + * 数组的长度,如果超过了最大允许的数量,则将其截断为最大值。然后初始化计数器和相 + * 关数据结构,接着释放百分位锁并返回计数器值。 + */ +static void prepare_calculate_single(const SqlRTInfoArray *sql_rt_info, int *counter) { + // 获取当前 SQL 响应时间信息数量 int32 sql_rt_info_count = g_instance.stat_cxt.sql_rt_info_array->sqlRTIndex; + // 如果数量超过上限,限制在上限内 if (sql_rt_info_count > MAX_SQL_RT_INFO_COUNT) { sql_rt_info_count = MAX_SQL_RT_INFO_COUNT; } + // 初始化计数器 *counter = 0; + + // 如果没有数据,直接返回 if (sql_rt_info_count == 0) { return; } - if (u_sess->percentile_cxt.LocalsqlRT != NULL) + // 如果已经存在临时数据数组,释放内存 + if (u_sess->percentile_cxt.LocalsqlRT != NULL) { pfree_ext(u_sess->percentile_cxt.LocalsqlRT); + } - u_sess->percentile_cxt.LocalsqlRT = (SqlRTInfo*)MemoryContextAllocZero( - SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_DFX), sql_rt_info_count * sizeof(SqlRTInfo)); + // 分配内存并将数据从原数组拷贝到临时数组 + u_sess->percentile_cxt.LocalsqlRT = (SqlRTInfo *)MemoryContextAllocZero(SESS_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_DFX), + sql_rt_info_count * sizeof(SqlRTInfo)); + // 获取锁,拷贝数据 LWLockAcquire(PercentileLock, LW_EXCLUSIVE); - errno_t ss_rc = memcpy_s(u_sess->percentile_cxt.LocalsqlRT, - sql_rt_info_count * sizeof(SqlRTInfo), - sql_rt_info->sqlRT, - sql_rt_info_count * sizeof(SqlRTInfo)); + errno_t ss_rc = memcpy_s(u_sess->percentile_cxt.LocalsqlRT, sql_rt_info_count * sizeof(SqlRTInfo), + sql_rt_info->sqlRT, sql_rt_info_count * sizeof(SqlRTInfo)); LWLockRelease(PercentileLock); securec_check(ss_rc, "\0", "\0"); + + // 将原始数据清零,准备计算 g_instance.stat_cxt.sql_rt_info_array->sqlRTIndex = 0; g_instance.stat_cxt.force_process = false; + + // 设置计数器为数据的数量 *counter = sql_rt_info_count; + + // 对新数据数组进行排序 qsort(u_sess->percentile_cxt.LocalsqlRT, sql_rt_info_count, sizeof(SqlRTInfo), sqlRTComparator); } @@ -7410,9 +8170,20 @@ static void prepare_calculate_single(const SqlRTInfoArray* sql_rt_info, int* cou * Process a reserving-memory message. * ---------- */ -static void pgstat_recv_memReserved(const PgStat_MsgMemReserved* msg) +/* + * 功能:处理接收到的内存预留消息 + * + * 参数列表: + * msg:指向接收到的内存预留消息的指针 + * + * 注意: + * 该函数用于处理接收到的内存预留消息,根据消息中的数据库 ID 查找或创建相应的数据库 + * 记录。根据消息中的操作类型(reserve_or_release)增加或减少相应数据库的内存预留 + * 大小(n_mem_mbytes_reserved)。如果操作后内存预留大小小于0,则将其重置为0。 + */ +static void pgstat_recv_memReserved(const PgStat_MsgMemReserved *msg) { - PgStat_StatDBEntry* dbentry = NULL; + PgStat_StatDBEntry *dbentry = NULL; dbentry = pgstat_get_db_entry(msg->m_databaseid, true); @@ -7431,11 +8202,11 @@ static void pgstat_recv_memReserved(const PgStat_MsgMemReserved* msg) * Count what the backend has done. * ---------- */ -static void pgstat_recv_funcstat(PgStat_MsgFuncstat* msg, int len) +static void pgstat_recv_funcstat(PgStat_MsgFuncstat *msg, int len) { - PgStat_FunctionEntry* funcmsg = &(msg->m_entry[0]); - PgStat_StatDBEntry* dbentry = NULL; - PgStat_StatFuncEntry* funcentry = NULL; + PgStat_FunctionEntry *funcmsg = &(msg->m_entry[0]); + PgStat_StatDBEntry *dbentry = NULL; + PgStat_StatFuncEntry *funcentry = NULL; int i; bool found = false; @@ -7445,7 +8216,8 @@ static void pgstat_recv_funcstat(PgStat_MsgFuncstat* msg, int len) * Process all function entries in the message. */ for (i = 0; i < msg->m_nentries; i++, funcmsg++) { - funcentry = (PgStat_StatFuncEntry*)hash_search(dbentry->functions, (void*)&(funcmsg->f_id), HASH_ENTER, &found); + funcentry = + (PgStat_StatFuncEntry *)hash_search(dbentry->functions, (void *)&(funcmsg->f_id), HASH_ENTER, &found); if (!found) { /* @@ -7472,9 +8244,9 @@ static void pgstat_recv_funcstat(PgStat_MsgFuncstat* msg, int len) * Arrange for dead function removal. * ---------- */ -static void pgstat_recv_funcpurge(PgStat_MsgFuncpurge* msg, int len) +static void pgstat_recv_funcpurge(PgStat_MsgFuncpurge *msg, int len) { - PgStat_StatDBEntry* dbentry = NULL; + PgStat_StatDBEntry *dbentry = NULL; int i; dbentry = pgstat_get_db_entry(msg->m_databaseid, false); @@ -7490,10 +8262,21 @@ static void pgstat_recv_funcpurge(PgStat_MsgFuncpurge* msg, int len) */ for (i = 0; i < msg->m_nentries; i++) { /* Remove from hashtable if present; we don't care if it's not. */ - (void)hash_search(dbentry->functions, (void*)&(msg->m_functionid[i]), HASH_REMOVE, NULL); + (void)hash_search(dbentry->functions, (void *)&(msg->m_functionid[i]), HASH_REMOVE, NULL); } } - +/* + * 功能:初始化分区的统计信息 + * + * 参数列表: + * part:指向要初始化统计信息的分区的指针 + * + * 注意: + * 该函数用于初始化分区的统计信息。首先,它检查是否应该进行统计信息的收集。 + * 如果不应该收集统计信息,则将分区的统计信息指针(pd_pgstat_info)设置为 NULL, + * 并提前返回。否则,它查找或创建与分区关联的 PgStat_TableStatus 条目, + * 并将分区的统计信息指针更新为该条目。 + */ void pgstat_initstats_partition(Partition part) { Oid part_id = part->pd_id; @@ -7518,6 +8301,17 @@ void pgstat_initstats_partition(Partition part) /* *we calculate cpu numbers from sysfs, so we should make sure we can access this file system. */ +/* + * 功能:检查系统文件系统 + * + * 返回值: + * 如果系统文件系统可用,返回 true,否则返回 false。 + * + * 注意: + * 该函数检查系统文件系统的可用性,它通过访问文件系统路径来判断其是否存在。 + * 如果 SysFileSystemPath、ThreadSiblingFile、CoreSiblingFile 这些路径中的任何一个不存在, + * 则返回 false,表示系统文件系统不可用;否则返回 true,表示系统文件系统可用。 + */ static bool checkSysFileSystem(void) { /* Read through sysfs. */ @@ -7536,26 +8330,40 @@ static bool checkSysFileSystem(void) /* *check whether the SysCpuPath is accessable.one accessable path represented one logical cpu. */ +/* + * 功能:检查逻辑 CPU + * + * 参数列表: + * cpuNum: 要检查的逻辑 CPU 编号 + * + * 返回值: + * 如果逻辑 CPU 存在,返回 true,否则返回 false。 + * + * 注意: + * 该函数检查指定逻辑 CPU 是否存在,它构造一个文件路径(pathbuf)来表示该 CPU, + * 然后通过 access 函数检查该路径是否存在。如果存在,表示逻辑 CPU 存在,返回 true, + * 否则返回 false,表示逻辑 CPU 不存在。 + */ static bool checkLogicalCpu(uint32 cpuNum) { char pathbuf[ProcPathMax] = ""; - + // 构造逻辑 CPU 的路径 errno_t ret = sprintf_s(pathbuf, sizeof(pathbuf), SysCpuPath, cpuNum); securec_check_ss(ret, "\0", "\0"); - + // 检查路径是否存在 return access(pathbuf, F_OK) == 0; } /* count the set bit in a mapping file */ #define pg_isxdigit(c) \ (((c) >= (int)'0' && (c) <= (int)'9') || ((c) >= (int)'a' && (c) <= (int)'f') || \ - ((c) >= (int)'A' && (c) <= (int)'F')) -static uint32 parseSiblingFile(const char* path) + ((c) >= (int)'A' && (c) <= (int)'F')) +static uint32 parseSiblingFile(const char *path) { int c; uint32 result = 0; char s[2]; - FILE* fp = NULL; + FILE *fp = NULL; union { uint32 a : 4; struct { @@ -7648,12 +8456,12 @@ void getCpuNums(void) */ void getCpuTimes(void) { - const char* statPath = "/proc/stat"; - FILE* fd = NULL; - char* line = NULL; + const char *statPath = "/proc/stat"; + FILE *fd = NULL; + char *line = NULL; size_t len = 0; uint64 readTime[NumOfCpuTimeReads]; - char* temp = NULL; + char *temp = NULL; int i; /* reset the member "got" of u_sess->stat_cxt.osStatDescArray to false */ @@ -7728,10 +8536,10 @@ void getCpuTimes(void) */ void getVmStat(void) { - const char* vmStatPath = "/proc/vmstat"; + const char *vmStatPath = "/proc/vmstat"; int fd = -1; char buffer[VmStatFileReadBuffer + 1]; - char* temp = NULL; + char *temp = NULL; uint64 inPages = 0; uint64 outPages = 0; uint64 pageSize = sysconf(_SC_PAGE_SIZE); @@ -7784,10 +8592,10 @@ void getVmStat(void) */ void getTotalMem(void) { - const char* memInfoPath = "/proc/meminfo"; - FILE* fd = NULL; - char* line = NULL; - char* temp = NULL; + const char *memInfoPath = "/proc/meminfo"; + FILE *fd = NULL; + char *line = NULL; + char *temp = NULL; uint64 ret = 0; size_t len = 0; @@ -7821,9 +8629,9 @@ void getTotalMem(void) */ void getOSRunLoad(void) { - const char* loadAvgPath = "/proc/loadavg"; - FILE* fd = NULL; - char* line = NULL; + const char *loadAvgPath = "/proc/loadavg"; + FILE *fd = NULL; + char *line = NULL; size_t len = 0; /* reset the member "got" of u_sess->stat_cxt.osStatDescArray to false */ @@ -7858,35 +8666,84 @@ Datum Int32GetNumberDatum(NumericValue value) return DirectFunctionCall1(int4_numeric, Int32GetDatum(value.int32Value)); } -void getSessionID(char* sessid, pg_time_t startTime, ThreadId Threadid) +/* + * 功能:获取会话 ID + * + * 参数列表: + * sessid: 输出参数,用于返回会话 ID + * startTime: 会话开始时间 + * Threadid: 线程 ID + * + * 注意: + * 该函数用于生成会话 ID,会话 ID 格式为 "startTime.Threadid"。 + * 先使用 snprintf_s 函数将 startTime 和 Threadid 格式化为字符串, + * 然后将结果存储在 sessid 中。 + */ +void getSessionID(char *sessid, pg_time_t startTime, ThreadId Threadid) { int rc = 0; + // 断言 sessid 不为空 Assert(NULL != sessid); + // 使用 snprintf_s 格式化 startTime 和 Threadid 为字符串,并将结果存储在 sessid 中 rc = snprintf_s(sessid, SESSION_ID_LEN, SESSION_ID_LEN - 1, "%ld.%lu", startTime, Threadid); securec_check_ss(rc, "\0", "\0"); } -void getThrdID(char* thrdid, pg_time_t startTime, ThreadId Threadid) +/* + * 功能:获取线程 ID + * + * 参数列表: + * thrdid: 输出参数,用于返回线程 ID + * startTime: 线程开始时间 + * Threadid: 线程 ID + * + * 注意: + * 该函数用于生成线程 ID,线程 ID 格式为 "startTime.Threadid"。 + * 先使用 snprintf_s 函数将 startTime 和 Threadid 格式化为字符串, + * 然后将结果存储在 thrdid 中。 + */ +void getThrdID(char *thrdid, pg_time_t startTime, ThreadId Threadid) { int rc = 0; + // 断言 thrdid 不为空 Assert(NULL != thrdid); + // 使用 snprintf_s 格式化 startTime 和 Threadid 为字符串,并将结果存储在 thrdid 中 rc = snprintf_s(thrdid, SESSION_ID_LEN, SESSION_ID_LEN - 1, "%ld.%lu", startTime, Threadid); securec_check_ss(rc, "\0", "\0"); } #ifdef ENABLE_MOT -MotSessionMemoryDetail* GetMotSessionMemoryDetail(uint32* num) +/* + * 功能:获取 MOT 存储引擎的会话内存详情 + * + * 参数列表: + * num: 输出参数,用于返回 MOT 存储引擎会话内存详情的数量 + * + * 返回值: + * MotSessionMemoryDetail* - 指向 MOT 存储引擎会话内存详情数组的指针 + * + * 注意: + * 该函数用于获取 MOT 存储引擎的会话内存详情,通过外部存储引擎接口获取。 + * 首先获取 MOT 存储引擎的外部数据包装器(Foreign Data Wrapper),然后 + * 获取外部数据包装器的接口函数,使用接口函数获取 MOT 存储引擎的会话内存详情。 + * 函数会返回 MOT 存储引擎会话内存详情的数组指针,并通过 num 参数返回详情数量。 + */ +MotSessionMemoryDetail *GetMotSessionMemoryDetail(uint32 *num) { - MotSessionMemoryDetail* returnDetailArray = NULL; - ForeignDataWrapper* fdw = NULL; - FdwRoutine* fdwroutine = NULL; + MotSessionMemoryDetail *returnDetailArray = NULL; + ForeignDataWrapper *fdw = NULL; + FdwRoutine *fdwroutine = NULL; + // 初始化详情数量为 0 *num = 0; + // 获取 MOT 存储引擎的外部数据包装器 fdw = GetForeignDataWrapperByName(MOT_FDW, false); if (fdw != NULL) { + // 获取外部数据包装器的接口函数 fdwroutine = GetFdwRoutine(fdw->fdwhandler); if (fdwroutine) { + // 使用接口函数获取 MOT 存储引擎的会话内存详情 returnDetailArray = fdwroutine->GetForeignSessionMemSize(num); } } @@ -7894,24 +8751,45 @@ MotSessionMemoryDetail* GetMotSessionMemoryDetail(uint32* num) return returnDetailArray; } -MotMemoryDetail* GetMotMemoryDetail(uint32* num, bool isGlobal) +/* + * 功能:获取 MOT 存储引擎的内存详情 + * + * 参数列表: + * num: 输出参数,用于返回 MOT 存储引擎内存详情的数量 + * isGlobal: 指示是否获取全局内存详情,如果为 true,则获取全局内存详情,否则获取会话内存详情 + * + * 返回值: + * MotMemoryDetail* - 指向 MOT 存储引擎内存详情数组的指针 + * + * 注意: + * 该函数用于获取 MOT 存储引擎的内存详情,可以选择获取全局内存详情或会话内存详情。 + * 首先获取 MOT 存储引擎的外部数据包装器(Foreign Data Wrapper),然后 + * 获取外部数据包装器的接口函数,使用接口函数获取 MOT 存储引擎的内存详情。 + * 函数会返回 MOT 存储引擎内存详情的数组指针,并通过 num 参数返回详情数量。 + */ +MotMemoryDetail *GetMotMemoryDetail(uint32 *num, bool isGlobal) { - MotMemoryDetail* returnDetailArray = NULL; - ForeignDataWrapper* fdw = NULL; - FdwRoutine* fdwroutine = NULL; + MotMemoryDetail *returnDetailArray = NULL; + ForeignDataWrapper *fdw = NULL; + FdwRoutine *fdwroutine = NULL; + // 初始化详情数量为 0 *num = 0; + // 获取 MOT 存储引擎的外部数据包装器 fdw = GetForeignDataWrapperByName(MOT_FDW, false); if (fdw != NULL) { + // 获取外部数据包装器的接口函数 fdwroutine = GetFdwRoutine(fdw->fdwhandler); if (fdwroutine != NULL) { + // 使用接口函数获取 MOT 存储引擎的内存详情 returnDetailArray = fdwroutine->GetForeignMemSize(num, isGlobal); } } return returnDetailArray; } + #endif int64 getCpuTime(void) @@ -7939,34 +8817,41 @@ Size sessionTimeShmemSize(void) return mul_size(SessionTimeArraySize, sizeof(SessionTimeEntry)); } -const char* TimeInfoTypeName[TOTAL_TIME_INFO_TYPES] = {"DB_TIME", - "CPU_TIME", - "EXECUTION_TIME", - "PARSE_TIME", - "PLAN_TIME", - "REWRITE_TIME", - "PL_EXECUTION_TIME", - "PL_COMPILATION_TIME", - "NET_SEND_TIME", - "DATA_IO_TIME"}; +const char *TimeInfoTypeName[TOTAL_TIME_INFO_TYPES] = { + "DB_TIME", "CPU_TIME", "EXECUTION_TIME", "PARSE_TIME", "PLAN_TIME", + "REWRITE_TIME", "PL_EXECUTION_TIME", "PL_COMPILATION_TIME", "NET_SEND_TIME", "DATA_IO_TIME"}; +/* + * 功能:初始化会话时间统计共享内存 + * + * 注意: + * 该函数用于初始化会话时间统计共享内存,首先尝试获取共享内存指针,如果不存在则创建。 + * 共享内存的大小由 SessionTimeArraySize 乘以 SessionTimeEntry 的大小决定。 + * 如果共享内存不存在,函数会将共享内存初始化为零。 + */ void sessionTimeShmemInit(void) { bool found = false; errno_t rc; - t_thrd.shemem_ptr_cxt.sessionTimeArray = (SessionTimeEntry*)ShmemInitStruct( + // 尝试获取或创建名为 "SessionTime Array" 的共享内存 + t_thrd.shemem_ptr_cxt.sessionTimeArray = (SessionTimeEntry *)ShmemInitStruct( "SessionTime Array", mul_size(SessionTimeArraySize, sizeof(SessionTimeEntry)), &found); if (!found) { - rc = memset_s(t_thrd.shemem_ptr_cxt.sessionTimeArray, - mul_size(SessionTimeArraySize, sizeof(SessionTimeEntry)), - 0, - mul_size(SessionTimeArraySize, sizeof(SessionTimeEntry))); + // 如果共享内存不存在,则将其初始化为零 + rc = memset_s(t_thrd.shemem_ptr_cxt.sessionTimeArray, mul_size(SessionTimeArraySize, sizeof(SessionTimeEntry)), + 0, mul_size(SessionTimeArraySize, sizeof(SessionTimeEntry))); securec_check(rc, "\0", "\0"); } } - +/* + * 功能:初始化当前会话时间统计数据 + * + * 注意: + * 该函数用于初始化当前会话时间统计数据,根据当前会话的属性和上下文设置不同的值。 + * 具体包括会话的唯一标识、是否活动、启动时间等信息。 + */ void initMySessionTimeEntry(void) { /* Initialize mySessionTimeEntry */ @@ -7976,8 +8861,8 @@ void initMySessionTimeEntry(void) if (t_thrd.proc_cxt.MyBackendId != InvalidBackendId) { Assert(t_thrd.proc_cxt.MyBackendId >= 1 && t_thrd.proc_cxt.MyBackendId <= (g_instance.attr.attr_common.enable_thread_pool - ? GLOBAL_RESERVE_SESSION_NUM - : g_instance.shmem_cxt.MaxBackends)); + ? GLOBAL_RESERVE_SESSION_NUM + : g_instance.shmem_cxt.MaxBackends)); t_thrd.shemem_ptr_cxt.mySessionTimeEntry = &t_thrd.shemem_ptr_cxt.sessionTimeArray[t_thrd.proc_cxt.MyBackendId - 1]; } else { @@ -8005,10 +8890,9 @@ void initMySessionTimeEntry(void) t_thrd.shemem_ptr_cxt.mySessionTimeEntry->myStartTime = t_thrd.proc_cxt.MyStartTime; - errno_t rc = memset_s((int64*)t_thrd.shemem_ptr_cxt.mySessionTimeEntry->array, - sizeof(t_thrd.shemem_ptr_cxt.mySessionTimeEntry->array), - 0, - sizeof(t_thrd.shemem_ptr_cxt.mySessionTimeEntry->array)); + errno_t rc = memset_s((int64 *)t_thrd.shemem_ptr_cxt.mySessionTimeEntry->array, + sizeof(t_thrd.shemem_ptr_cxt.mySessionTimeEntry->array), 0, + sizeof(t_thrd.shemem_ptr_cxt.mySessionTimeEntry->array)); securec_check(rc, "\0", "\0"); t_thrd.shemem_ptr_cxt.mySessionTimeEntry->changeCount++; @@ -8016,8 +8900,14 @@ void initMySessionTimeEntry(void) on_shmem_exit(endMySessionTimeEntry, 0); } - -static void DetachMySessionTimeEntry(volatile SessionTimeEntry* pEntry) +/* + * 功能:分离当前会话时间统计数据 + * + * 注意: + * 该函数用于将当前会话时间统计数据分离,保存并标记为不活动状态。 + * 具体包括更新全局时间统计信息、标记不活动、以及修改计数器。 + */ +static void DetachMySessionTimeEntry(volatile SessionTimeEntry *pEntry) { LWLockAcquire(InstanceTimeLock, LW_EXCLUSIVE); @@ -8040,6 +8930,17 @@ static void DetachMySessionTimeEntry(volatile SessionTimeEntry* pEntry) /* * mark t_thrd.shemem_ptr_cxt.mySessionTimeEntry closed when we shut down. */ +/* + * 功能:结束当前会话时间统计数据 + * + * 参数列表: + * code:退出代码(未使用) + * arg:参数(未使用) + * + * 注意: + * 该函数用于结束当前会话时间统计数据,分离并标记为不活动状态,然后将当前会话时间统计数据指针设置为 NULL。 + * 具体包括分离、标记不活动以及将数据指针置空操作。 + */ static void endMySessionTimeEntry(int code, Datum arg) { DetachMySessionTimeEntry(t_thrd.shemem_ptr_cxt.mySessionTimeEntry); @@ -8072,10 +8973,9 @@ void AttachMySessionTimeEntry(void) t_thrd.shemem_ptr_cxt.mySessionTimeEntry->myStartTime = timestamptz_to_time_t(u_sess->proc_cxt.MyProcPort->SessionStartTime); t_thrd.shemem_ptr_cxt.mySessionTimeEntry->sessionid = u_sess->session_id; - errno_t rc = memset_s((int64*)t_thrd.shemem_ptr_cxt.mySessionTimeEntry->array, - sizeof(t_thrd.shemem_ptr_cxt.mySessionTimeEntry->array), - 0, - sizeof(t_thrd.shemem_ptr_cxt.mySessionTimeEntry->array)); + errno_t rc = memset_s((int64 *)t_thrd.shemem_ptr_cxt.mySessionTimeEntry->array, + sizeof(t_thrd.shemem_ptr_cxt.mySessionTimeEntry->array), 0, + sizeof(t_thrd.shemem_ptr_cxt.mySessionTimeEntry->array)); securec_check(rc, "\0", "\0"); t_thrd.shemem_ptr_cxt.mySessionTimeEntry->isActive = true; } @@ -8083,7 +8983,13 @@ void AttachMySessionTimeEntry(void) t_thrd.shemem_ptr_cxt.mySessionTimeEntry->changeCount++; Assert((t_thrd.shemem_ptr_cxt.mySessionTimeEntry->changeCount & 1) == 0); } - +/* + * 功能:开始记录时间信息 + * + * 注意: + * 如果本地时间信息数组中的 DB_TIME 元素为 0(未记录时间),则将其设置为当前时间戳。 + * 如果启用了 CPU 计时器,则还将本地时间信息数组中的 CPU_TIME 元素设置为 CPU 时间。 + */ void timeInfoRecordStart(void) { if (u_sess->stat_cxt.localTimeInfoArray[DB_TIME] == 0) { @@ -8092,7 +8998,18 @@ void timeInfoRecordStart(void) u_sess->stat_cxt.localTimeInfoArray[CPU_TIME] = getCpuTime(); } } - +/* + * 功能:结束记录时间信息 + * + * 注意: + * 如果本地时间信息数组中的 DB_TIME 元素不为 0(已记录时间),则进行以下操作: + * 1. 增加当前会话的 changeCount,以表示时间信息已更新。 + * 2. 如果启用了 CPU 计时器,计算 CPU 时间的差值,并存储在数组中。 + * 3. 计算 DB 时间的差值,并存储在数组中。 + * 4. 增加相应类型的时间信息到当前会话的时间信息数组。 + * 5. 重置本地时间信息数组。 + * 6. 如果存在唯一 SQL ID 并且启用了唯一 SQL 统计,更新唯一 SQL 统计信息。 + */ void timeInfoRecordEnd(void) { errno_t rc; @@ -8133,24 +9050,38 @@ void timeInfoRecordEnd(void) UniqueSQLStat sqlStat; sqlStat.timeInfo = u_sess->stat_cxt.localTimeInfoArray; sqlStat.netInfo = u_sess->stat_cxt.localNetInfo; + // 如果存在唯一 SQL ID 并且启用了唯一 SQL 统计,更新唯一 SQL 统计信息。 if (u_sess->unique_sql_cxt.unique_sql_id != 0 && is_unique_sql_enabled()) UpdateUniqueSQLStat(NULL, NULL, 0, NULL, &sqlStat); - rc = memset_s(u_sess->stat_cxt.localTimeInfoArray, - sizeof(int64) * TOTAL_TIME_INFO_TYPES, - 0, - sizeof(int64) * TOTAL_TIME_INFO_TYPES); + + // 重置本地时间信息和网络信息数组。 + rc = memset_s(u_sess->stat_cxt.localTimeInfoArray, sizeof(int64) * TOTAL_TIME_INFO_TYPES, 0, + sizeof(int64) * TOTAL_TIME_INFO_TYPES); securec_check(rc, "\0", "\0"); - rc = memset_s(u_sess->stat_cxt.localNetInfo, - sizeof(uint64) * TOTAL_NET_INFO_TYPES, - 0, - sizeof(uint64) * TOTAL_NET_INFO_TYPES); + rc = memset_s(u_sess->stat_cxt.localNetInfo, sizeof(uint64) * TOTAL_NET_INFO_TYPES, 0, + sizeof(uint64) * TOTAL_NET_INFO_TYPES); securec_check(rc, "\0", "\0"); } } /* generate result of pv_session_time view */ +/* + * 功能:获取会话时间状态信息,并将其插入到元组存储中 + * + * 参数列表: + * tupStore:元组存储对象,用于存储获取的会话时间状态信息 + * tupDesc:元组描述符,描述存储的元组格式 + * insert:插入函数,将会话时间状态信息插入元组存储 + * + * 注意: + * 1. 分配一个本地会话时间状态信息结构体 localEntry。 + * 2. 遍历会话时间状态信息数组,读取每个条目 entry 并将其复制到 localEntry。 + * 3. 如果 localEntry 不活跃(isActive 为 false),则跳过该条目。 + * 4. 调用插入函数将 localEntry 插入到 tupStore 中。 + * 5. 释放本地会话时间状态信息结构体 localEntry。 + */ void getSessionTimeStatus(Tuplestorestate *tupStore, TupleDesc tupDesc, - void (*insert)(Tuplestorestate *tupStore, TupleDesc tupDesc, const SessionTimeEntry *entry)) + void (*insert)(Tuplestorestate *tupStore, TupleDesc tupDesc, const SessionTimeEntry *entry)) { SessionTimeEntry *localEntry = NULL; SessionTimeEntry *entry = NULL; @@ -8171,14 +9102,29 @@ void getSessionTimeStatus(Tuplestorestate *tupStore, TupleDesc tupDesc, } /* generate result of pv_instance_time view */ -SessionTimeEntry* getInstanceTimeStatus() +/* + * 功能:获取实例时间状态信息 + * + * 返回值: + * 返回一个指向 SessionTimeEntry 结构体的指针,包含了实例的时间状态信息 + * + * 注意: + * 1. 分配一个 SessionTimeEntry 结构体的内存块 retEntry,并初始化为零。 + * 2. 获取实例时间锁 InstanceTimeLock(共享锁)以遍历会话时间状态信息数组。 + * 3. 遍历数组中的每个条目 entry。 + * 4. 如果条目是活跃的,将其内容复制到本地 localEntry,并将其时间信息累加到 retEntry。 + * 5. 遍历 gInstanceTimeInfo,将实例的时间信息累加到 retEntry。 + * 6. 释放实例时间锁 InstanceTimeLock。 + * 7. 返回 retEntry,包含了实例的时间状态信息。 + */ +SessionTimeEntry *getInstanceTimeStatus() { - SessionTimeEntry* retEntry = NULL; + SessionTimeEntry *retEntry = NULL; SessionTimeEntry localEntry; - SessionTimeEntry* entry = NULL; + SessionTimeEntry *entry = NULL; int entryIndex, idx; - retEntry = (SessionTimeEntry*)palloc0(sizeof(SessionTimeEntry)); + retEntry = (SessionTimeEntry *)palloc0(sizeof(SessionTimeEntry)); LWLockAcquire(InstanceTimeLock, LW_SHARED); for (entryIndex = 0; entryIndex < SessionTimeArraySize; entryIndex++) { entry = &(t_thrd.shemem_ptr_cxt.sessionTimeArray[entryIndex]); @@ -8198,10 +9144,23 @@ SessionTimeEntry* getInstanceTimeStatus() } PgStat_RedoEntry redoStatistics; - +/* + * 功能:报告重做日志写入统计信息 + * + * 参数列表: + * blks:写入的块数 + * tim:写入的时间 + * + * 注意: + * 1. 如果写入的块数小于等于零,则直接返回,不进行统计。 + * 2. 增加 writes 计数,累加 writeBlks、writeTime。 + * 3. 计算平均 IO 时间 avgIOTime,并更新最后一次 IO 时间 lstIOTime。 + * 4. 如果当前 IO 时间小于最小 IO 时间 minIOTime 或 minIOTime 为零,更新 minIOTime。 + * 5. 如果当前 IO 时间大于最大 IO 时间 maxIOTime,更新 maxIOTime。 + */ void reportRedoWrite(PgStat_Counter blks, PgStat_Counter tim) { - PgStat_RedoEntry* entry = &redoStatistics; + PgStat_RedoEntry *entry = &redoStatistics; if (0 >= blks) { return; @@ -8227,21 +9186,32 @@ Size sessionStatShmemSize(void) { return mul_size(sizeof(SessionLevelStatistic), SessionStatArraySize); } - +/* + * 功能:初始化会话级别统计信息的共享内存 + * + * 注意: + * 1. 如果共享内存不存在(found = false),则将其内容初始化为零。 + */ void sessionStatShmemInit(void) { bool found = false; errno_t rc; t_thrd.shemem_ptr_cxt.sessionStatArray = - (SessionLevelStatistic*)ShmemInitStruct("Session Statistic Array", sessionStatShmemSize(), &found); + (SessionLevelStatistic *)ShmemInitStruct("Session Statistic Array", sessionStatShmemSize(), &found); if (!found) { rc = memset_s(t_thrd.shemem_ptr_cxt.sessionStatArray, sessionStatShmemSize(), 0, sessionStatShmemSize()); securec_check(rc, "\0", "\0"); } } - +/* + * 功能:初始化会话级别统计信息的入口 + * + * 注意: + * 1. 会话级别统计信息包括会话的开始时间、会话ID等。 + * 2. 根据线程类型初始化相应的统计信息。 + */ void initMySessionStatEntry(void) { /* Initialize mySessionStatEntry */ @@ -8251,8 +9221,8 @@ void initMySessionStatEntry(void) if (t_thrd.proc_cxt.MyBackendId != InvalidBackendId) { Assert(t_thrd.proc_cxt.MyBackendId >= 1 && t_thrd.proc_cxt.MyBackendId <= (g_instance.attr.attr_common.enable_thread_pool - ? GLOBAL_RESERVE_SESSION_NUM - : g_instance.shmem_cxt.MaxBackends)); + ? GLOBAL_RESERVE_SESSION_NUM + : g_instance.shmem_cxt.MaxBackends)); t_thrd.shemem_ptr_cxt.mySessionStatEntry = &t_thrd.shemem_ptr_cxt.sessionStatArray[t_thrd.proc_cxt.MyBackendId - 1]; } else { @@ -8261,10 +9231,8 @@ void initMySessionStatEntry(void) t_thrd.shemem_ptr_cxt.mySessionStatEntry = &t_thrd.shemem_ptr_cxt.sessionStatArray[index]; } - errno_t rc = memset_s((SessionLevelStatistic*)t_thrd.shemem_ptr_cxt.mySessionStatEntry, - sizeof(SessionLevelStatistic), - 0, - sizeof(SessionLevelStatistic)); + errno_t rc = memset_s((SessionLevelStatistic *)t_thrd.shemem_ptr_cxt.mySessionStatEntry, + sizeof(SessionLevelStatistic), 0, sizeof(SessionLevelStatistic)); securec_check(rc, "\0", "\0"); t_thrd.shemem_ptr_cxt.mySessionStatEntry->sessionStartTime = t_thrd.proc_cxt.MyStartTime; @@ -8280,7 +9248,13 @@ void initMySessionStatEntry(void) on_shmem_exit(endMySessionStatEntry, 0); } - +/* + * 功能:结束会话级别统计信息的入口 + * + * 注意: + * 1. 会将当前会话的有效标志设置为不活跃。 + * 2. 不再处理此入口。 + */ static void endMySessionStatEntry(int code, Datum arg) { /* mark my entry not active. */ @@ -8293,6 +9267,14 @@ static void endMySessionStatEntry(int code, Datum arg) /* * attach mySessionStatEntry to session entry */ +/* + * 功能:关联当前会话的会话级别统计信息 + * + * 注意: + * 1. 假定已经存在全局的会话统计信息数组(sessionStatArray)。 + * 2. 假定当前会话的会话ID(session_id)已经被设置。 + * 3. 如果当前会话的统计信息无效,将初始化会话统计信息。 + */ void AttachMySessionStatEntry(void) { Assert(t_thrd.shemem_ptr_cxt.sessionStatArray); @@ -8314,76 +9296,130 @@ void AttachMySessionStatEntry(void) } /* generate result of pv_session_stat view */ -void getSessionStatistics(Tuplestorestate* tupStore, TupleDesc tupDesc, - void (* insert)(Tuplestorestate* tupStore, TupleDesc tupDesc, const SessionLevelStatistic* entry)) +/* + * 功能:获取会话级别的统计信息并插入到指定的 Tuplestore 中 + * + * 注意: + * 1. 假定已经存在全局的会话级别统计信息数组(sessionStatArray)。 + * 2. 使用给定的 insert 函数将每个有效的会话统计信息插入到 Tuplestore 中。 + */ +void getSessionStatistics(Tuplestorestate *tupStore, TupleDesc tupDesc, + void (*insert)(Tuplestorestate *tupStore, TupleDesc tupDesc, + const SessionLevelStatistic *entry)) { - SessionLevelStatistic* entry = NULL; - SessionLevelStatistic* localEntry = NULL; - + SessionLevelStatistic *entry = NULL; + SessionLevelStatistic *localEntry = NULL; int entryIndex; errno_t rc; - localEntry = (SessionLevelStatistic*)palloc0(sizeof(SessionLevelStatistic)); + localEntry = (SessionLevelStatistic *)palloc0(sizeof(SessionLevelStatistic)); for (entryIndex = 0; entryIndex < SessionStatArraySize; entryIndex++) { entry = &(t_thrd.shemem_ptr_cxt.sessionStatArray[entryIndex]); + + // 将 entry 复制到 localEntry rc = memcpy_s(localEntry, sizeof(SessionLevelStatistic), entry, sizeof(SessionLevelStatistic)); securec_check(rc, "\0", "\0"); if (!localEntry->isValid) continue; + + // 调用 insert 函数将 localEntry 插入到 Tuplestore 中 insert(tupStore, tupDesc, localEntry); } + pfree(localEntry); } PgStat_FileEntry pgStatFileArray[NUM_FILES]; uint32 fileStatCount = 0; -void reportFileStat(PgStat_MsgFile* msg) +/* + * 功能:发送文件统计信息给统计模块 + * + * 参数列表: + * msg: 要发送的文件统计信息 + * + * 注意: + * 1. 通过设置消息头和消息类型来准备要发送的消息。 + * 2. 使用 pgstat_send 函数将消息发送给统计模块。 + */ +void reportFileStat(PgStat_MsgFile *msg) { + // 设置消息头和消息类型 pgstat_setheader(&(msg->m_hdr), PGSTAT_MTYPE_FILE); + + // 发送消息 pgstat_send(msg, sizeof(PgStat_MsgFile)); } -static void pgstat_recv_filestat(PgStat_MsgFile* msg, int len) +/* + * 功能:接收文件统计信息并更新文件统计数据 + * + * 参数: + * - msg: 要接收的文件统计信息 + * - len: 信息的长度 + * + * 注意: + * 1. 如果接收到的信息中块数小于等于0,不做任何处理,直接返回。 + * 2. 遍历文件统计数据,查找是否已经存在与接收到信息对应的文件。 + * 3. 如果文件不存在,则创建一个新的文件统计条目,并将接收到的信息更新到该条目中。 + * 4. 如果文件已经存在,则将接收到的信息更新到已有的文件统计条目中。 + */ +static void pgstat_recv_filestat(PgStat_MsgFile *msg, int len) { - PgStat_FileEntry* entry = NULL; + PgStat_FileEntry *entry = NULL; int i; errno_t rc; + // 如果接收到的块数小于等于0,不做任何处理,直接返回 if (0 >= msg->blks) { return; } + // 获取文件统计信息锁 LWLockAcquire(FileStatLock, LW_SHARED); + + // 遍历文件统计数组,查找是否已经存在与接收到信息对应的文件 for (i = 0; i < NUM_FILES; i++) { - entry = (PgStat_FileEntry*)&pgStatFileArray[i]; + entry = (PgStat_FileEntry *)&pgStatFileArray[i]; if (entry->fn == msg->fn || entry->fn == InvalidOid) { break; } } + + // 释放文件统计信息锁 LWLockRelease(FileStatLock); + // 如果遍历完数组还没有找到匹配的文件,或者找到了一个无效的文件统计条目 if (i == NUM_FILES || entry->fn == InvalidOid) { + // 获取文件统计信息锁(独占模式) LWLockAcquire(FileStatLock, LW_EXCLUSIVE); - entry = (PgStat_FileEntry*)&pgStatFileArray[fileStatCount]; - /* reset this entry */ + // 获取一个新的文件统计条目 + entry = (PgStat_FileEntry *)&pgStatFileArray[fileStatCount]; + + // 重置该文件统计条目 rc = memset_s(entry, sizeof(PgStat_FileEntry), 0, sizeof(PgStat_FileEntry)); securec_check(rc, "\0", "\0"); + // 更新文件统计信息 entry->dbid = msg->dbid; entry->spcid = msg->spcid; entry->fn = msg->fn; + // 更新文件统计数据计数器 fileStatCount = (fileStatCount + 1) % NUM_FILES; + + // 释放文件统计信息锁 LWLockRelease(FileStatLock); } + // 增加文件统计信息的更改计数器 entry->changeCount++; g_instance.stat_cxt.fileIOStat->changeCount++; + // 根据消息中的读写类型更新文件统计数据 if ('r' == msg->rw) { entry->reads += msg->cnt; entry->readBlks += msg->blks; @@ -8397,29 +9433,38 @@ static void pgstat_recv_filestat(PgStat_MsgFile* msg, int len) g_instance.stat_cxt.fileIOStat->writes += msg->cnt; g_instance.stat_cxt.fileIOStat->writeBlks += msg->blks; } + + // 增加全局文件统计信息的更改计数器 g_instance.stat_cxt.fileIOStat->changeCount++; + // 更新平均IO时间 if ((entry->reads + entry->writes) <= 0) { entry->avgIOTime = 0; } else { entry->avgIOTime = (entry->readTime + entry->writeTime) / (entry->reads + entry->writes); } + // 更新最后一次IO时间 entry->lstIOTime = msg->lsttim; + + // 更新最小IO时间 if (entry->minIOTime > msg->mintim || 0 == entry->minIOTime) { entry->minIOTime = msg->mintim; } + + // 更新最大IO时间 if (entry->maxIOTime < msg->maxtim) { entry->maxIOTime = msg->maxtim; } + // 增加文件统计信息的更改计数器 entry->changeCount++; return; } /* clear key message that may appear in core file for security */ -PgBackendStatus* GetMyBEEntry(void) +PgBackendStatus *GetMyBEEntry(void) { return t_thrd.shemem_ptr_cxt.MyBEEntry; } @@ -8431,40 +9476,59 @@ Size sessionMemoryShmemSize(void) return mul_size(sizeof(SessionLevelMemory), SessionMemoryArraySize); } +/* + * 功能:初始化会话内存统计数据的共享内存 + * + * 注意: + * 1. 首先尝试获取共享内存,如果找到已存在的则直接使用,否则创建一个新的共享内存段。 + * 2. 如果创建了新的共享内存段,则需要将共享内存清零以初始化。 + */ void sessionMemoryShmemInit(void) { bool found = false; errno_t rc; + // 尝试获取共享内存,如果找到已存在的则直接使用,否则创建一个新的共享内存段 t_thrd.shemem_ptr_cxt.sessionMemoryArray = - (SessionLevelMemory*)ShmemInitStruct("Session Memory Array", sessionMemoryShmemSize(), &found); + (SessionLevelMemory *)ShmemInitStruct("Session Memory Array", sessionMemoryShmemSize(), &found); if (!found) { + // 如果创建了新的共享内存段,则需要将共享内存清零以初始化 rc = memset_s(t_thrd.shemem_ptr_cxt.sessionMemoryArray, sessionMemoryShmemSize(), 0, sessionMemoryShmemSize()); securec_check(rc, "\0", "\0"); } } +/* + * 功能:初始化当前会话的内存统计数据 + * + * 注意: + * 1. 首先检查共享内存是否已经被初始化。 + * 2. 根据当前线程类型(后台线程或辅助线程)确定要初始化的会话内存统计数据的位置。 + * 3. 将会话内存统计数据的各个字段初始化为0或默认值。 + * 4. 最后注册一个回调函数,在进程退出时进行清理。 + */ void initMySessionMemoryEntry(void) { - /* Initialize mySessionStatEntry */ + // 检查共享内存是否已经被初始化 Assert(t_thrd.shemem_ptr_cxt.sessionMemoryArray); - // backend thread + // 根据当前线程类型(后台线程或辅助线程)确定要初始化的会话内存统计数据的位置 if (t_thrd.proc_cxt.MyBackendId != InvalidBackendId) { Assert(t_thrd.proc_cxt.MyBackendId >= 1 && t_thrd.proc_cxt.MyBackendId <= (g_instance.attr.attr_common.enable_thread_pool - ? GLOBAL_RESERVE_SESSION_NUM - : g_instance.shmem_cxt.MaxBackends)); + ? GLOBAL_RESERVE_SESSION_NUM + : g_instance.shmem_cxt.MaxBackends)); t_thrd.shemem_ptr_cxt.mySessionMemoryEntry = &t_thrd.shemem_ptr_cxt.sessionMemoryArray[t_thrd.proc_cxt.MyBackendId - 1]; } else { - // Auxiliary thread + // 辅助线程 int index = GetAuxProcStatEntryIndex(); t_thrd.shemem_ptr_cxt.mySessionMemoryEntry = &t_thrd.shemem_ptr_cxt.sessionMemoryArray[index]; } errno_t rc = EOK; + // 将会话内存统计数据的各个字段初始化为0或默认值 rc = memset_s(t_thrd.shemem_ptr_cxt.mySessionMemoryEntry, sizeof(SessionLevelMemory), 0, sizeof(SessionLevelMemory)); securec_check(rc, "\0", "\0"); @@ -8493,9 +9557,17 @@ void initMySessionMemoryEntry(void) t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->dnEndTime = 0; t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->isValid = true; + // 注册一个回调函数,在进程退出时进行清理 on_shmem_exit(endMySessionMemoryEntry, 0); } - +/* + * 功能:在进程退出时结束当前会话的内存统计数据,释放相关内存 + * + * 注意: + * 1. 释放当前会话的内存统计数据。 + * 2. 将会话内存统计数据标记为无效。 + * 3. 清空会话内存统计数据的指针,确保不再使用该数据。 + */ static void endMySessionMemoryEntry(int code, Datum arg) { @@ -8512,22 +9584,40 @@ static void endMySessionMemoryEntry(int code, Datum arg) /* * attach mySessionMemoryEntry to session entry */ +/* + * 功能:关联当前会话的内存统计数据 + * + * 注意: + * 1. 确保会话内存统计数据数组已经分配。 + * 2. 确保当前会话的索引在合理的范围内。 + * 3. 确保当前会话的后端 ID 不是无效的。 + * 4. 设置会话内存统计数据的指针,使其指向当前会话的内存统计数据。 + * 5. 如果当前会话的内存统计数据无效,初始化它。 + * 6. 将当前会话的后端 ID 关联到会话内存统计数据。 + */ void AttachMySessionMemoryEntry(void) { + /* 确保会话内存统计数据数组已经分配 */ Assert(t_thrd.shemem_ptr_cxt.sessionMemoryArray); + + /* 确保当前会话的索引在合理的范围内 */ Assert(u_sess->session_ctr_index >= GLOBAL_RESERVE_SESSION_NUM && u_sess->session_ctr_index < MAX_BACKEND_SLOT); + + /* 确保当前会话的后端 ID 不是无效的 */ Assert(t_thrd.proc_cxt.MyBackendId != InvalidBackendId); + + /* 确保当前会话的内存统计数据指针是正确的 */ Assert(t_thrd.shemem_ptr_cxt.mySessionMemoryEntry == &t_thrd.shemem_ptr_cxt.sessionMemoryArray[t_thrd.proc_cxt.MyBackendId - 1]); - /* change stat object to session */ + /* 设置会话内存统计数据的指针,使其指向当前会话的内存统计数据 */ t_thrd.shemem_ptr_cxt.mySessionMemoryEntry = &t_thrd.shemem_ptr_cxt.sessionMemoryArray[u_sess->session_ctr_index]; - /* initialize entry if necessary */ + /* 如果当前会话的内存统计数据无效,初始化它 */ if (t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->isValid == false) { errno_t rc = EOK; - rc = memset_s( - t_thrd.shemem_ptr_cxt.mySessionMemoryEntry, sizeof(SessionLevelMemory), 0, sizeof(SessionLevelMemory)); + rc = memset_s(t_thrd.shemem_ptr_cxt.mySessionMemoryEntry, sizeof(SessionLevelMemory), 0, + sizeof(SessionLevelMemory)); securec_check(rc, "\0", "\0"); t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->threadStartTime = timestamptz_to_time_t(u_sess->proc_cxt.MyProcPort->SessionStartTime); @@ -8549,20 +9639,20 @@ void AttachMySessionMemoryEntry(void) t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->isValid = true; } - /* worker's pgproc memory info is also related to the session */ + /* 将当前会话的后端 ID 关联到会话内存统计数据 */ t_thrd.proc->sessMemorySessionid = u_sess->session_id; } /* generate result of pv_session_stat view */ -void getSessionMemory(Tuplestorestate* tupStore, TupleDesc tupDesc, - void (* insert)(Tuplestorestate* tupStore, TupleDesc tupDesc, const SessionLevelMemory* entry)) +void getSessionMemory(Tuplestorestate *tupStore, TupleDesc tupDesc, + void (*insert)(Tuplestorestate *tupStore, TupleDesc tupDesc, const SessionLevelMemory *entry)) { - SessionLevelMemory* localEntry = NULL; - SessionLevelMemory* entry = NULL; + SessionLevelMemory *localEntry = NULL; + SessionLevelMemory *entry = NULL; int entryIndex; errno_t rc; - localEntry = (SessionLevelMemory*)palloc0(sizeof(SessionLevelMemory)); + localEntry = (SessionLevelMemory *)palloc0(sizeof(SessionLevelMemory)); for (entryIndex = 0; entryIndex < SessionMemoryArraySize; entryIndex++) { entry = &(t_thrd.shemem_ptr_cxt.sessionMemoryArray[entryIndex]); rc = memcpy_s(localEntry, sizeof(SessionLevelMemory), entry, sizeof(SessionLevelMemory)); @@ -8577,10 +9667,10 @@ void getSessionMemory(Tuplestorestate* tupStore, TupleDesc tupDesc, pfree(localEntry); } -typedef AllocSetContext* AllocSet; +typedef AllocSetContext *AllocSet; -static void calculateThreadMemoryContextStats(const volatile PGPROC* proc, const MemoryContext context, bool isShared, - Tuplestorestate* tupStore, TupleDesc tupDesc) +static void calculateThreadMemoryContextStats(const volatile PGPROC *proc, const MemoryContext context, bool isShared, + Tuplestorestate *tupStore, TupleDesc tupDesc) { AllocSet set = (AllocSet)context; @@ -8592,10 +9682,8 @@ static void calculateThreadMemoryContextStats(const volatile PGPROC* proc, const if (proc != NULL) { threadId = proc->pid; - rc = strncpy_s(threadType, - PROC_NAME_LEN, - (proc->myProgName != NULL) ? (const char*)proc->myProgName : "", - PROC_NAME_LEN - 1); + rc = strncpy_s(threadType, PROC_NAME_LEN, (proc->myProgName != NULL) ? (const char *)proc->myProgName : "", + PROC_NAME_LEN - 1); securec_check(rc, "\0", "\0"); getSessionID(sessId, proc->myStartTime, threadId); } else { @@ -8640,8 +9728,8 @@ static void calculateThreadMemoryContextStats(const volatile PGPROC* proc, const } } -static void recursiveThreadMemoryContext(const volatile PGPROC* proc, const MemoryContext context, bool isShared, - Tuplestorestate* tupStore, TupleDesc tupDesc) +static void recursiveThreadMemoryContext(const volatile PGPROC *proc, const MemoryContext context, bool isShared, + Tuplestorestate *tupStore, TupleDesc tupDesc) { MemoryContext child; @@ -8687,7 +9775,7 @@ static void recursiveThreadMemoryContext(const volatile PGPROC* proc, const Memo * Brief : * Description : */ -void getSharedMemoryDetail(Tuplestorestate* tupStore, TupleDesc tupDesc) +void getSharedMemoryDetail(Tuplestorestate *tupStore, TupleDesc tupDesc) { recursiveThreadMemoryContext(NULL, g_instance.instance_context, true, tupStore, tupDesc); } @@ -8700,11 +9788,11 @@ void getSharedMemoryDetail(Tuplestorestate* tupStore, TupleDesc tupDesc) * Notes : * Author : */ -void getThreadMemoryDetail(Tuplestorestate* tupStore, TupleDesc tupDesc, uint32* procIdx) +void getThreadMemoryDetail(Tuplestorestate *tupStore, TupleDesc tupDesc, uint32 *procIdx) { - uint32 max_thread_count = g_instance.proc_base->allProcCount - - g_instance.attr.attr_storage.max_prepared_xacts * NUM_TWOPHASE_PARTITIONS; - volatile PGPROC* proc = NULL; + uint32 max_thread_count = + g_instance.proc_base->allProcCount - g_instance.attr.attr_storage.max_prepared_xacts * NUM_TWOPHASE_PARTITIONS; + volatile PGPROC *proc = NULL; uint32 idx = 0; PG_TRY(); @@ -8716,10 +9804,10 @@ void getThreadMemoryDetail(Tuplestorestate* tupStore, TupleDesc tupDesc, uint32* *procIdx = idx; /* lock this proc's delete MemoryContext action */ - (void)syscalllockAcquire(&((PGPROC*)proc)->deleMemContextMutex); + (void)syscalllockAcquire(&((PGPROC *)proc)->deleMemContextMutex); if (proc->topmcxt != NULL) recursiveThreadMemoryContext(proc, proc->topmcxt, false, tupStore, tupDesc); - (void)syscalllockRelease(&((PGPROC*)proc)->deleMemContextMutex); + (void)syscalllockRelease(&((PGPROC *)proc)->deleMemContextMutex); } /* get memory context detail from postmaster thread */ @@ -8732,7 +9820,7 @@ void getThreadMemoryDetail(Tuplestorestate* tupStore, TupleDesc tupDesc, uint32* { if (*procIdx < max_thread_count) { proc = g_instance.proc_base_all_procs[*procIdx]; - (void)syscalllockRelease(&((PGPROC*)proc)->deleMemContextMutex); + (void)syscalllockRelease(&((PGPROC *)proc)->deleMemContextMutex); } PG_RE_THROW(); } @@ -8742,10 +9830,10 @@ void getThreadMemoryDetail(Tuplestorestate* tupStore, TupleDesc tupDesc, uint32* //////////////////////////////////////////////////////////////////////////////// #ifdef MEMORY_CONTEXT_CHECKING -extern void dumpAllocBlock(AllocSet set, StringInfoData* buf); -extern void dumpAsanBlock(AsanSet set, StringInfoData* buf); +extern void dumpAllocBlock(AllocSet set, StringInfoData *buf); +extern void dumpAsanBlock(AsanSet set, StringInfoData *buf); -static void recursiveMemoryContextForDump(const MemoryContext context, char* ctx_name, StringInfoData* buf) +static void recursiveMemoryContextForDump(const MemoryContext context, char *ctx_name, StringInfoData *buf) { #ifndef ENABLE_MEMORY_CHECK if (!strcmp(ctx_name, context->name) && @@ -8776,15 +9864,12 @@ void DumpMemoryContext(DUMP_TYPE type) // get the path of dump file is_absolute = is_absolute_path(u_sess->attr.attr_common.Log_directory); if (is_absolute) { - ss_rc = snprintf_s( - dump_dir, sizeof(dump_dir), sizeof(dump_dir) - 1, "%s/memdump", u_sess->attr.attr_common.Log_directory); + ss_rc = snprintf_s(dump_dir, sizeof(dump_dir), sizeof(dump_dir) - 1, "%s/memdump", + u_sess->attr.attr_common.Log_directory); securec_check_ss(ss_rc, "\0", "\0"); } else { - ss_rc = snprintf_s(dump_dir, - sizeof(dump_dir), - sizeof(dump_dir) - 1, - "%s/pg_log/memdump", - g_instance.attr.attr_common.data_directory); + ss_rc = snprintf_s(dump_dir, sizeof(dump_dir), sizeof(dump_dir) - 1, "%s/pg_log/memdump", + g_instance.attr.attr_common.data_directory); securec_check_ss(ss_rc, "\0", "\0"); } @@ -8814,20 +9899,15 @@ void DumpMemoryContext(DUMP_TYPE type) char dump_file[MAX_PATH_LEN] = {0}; #ifdef ENABLE_MEMORY_CHECK pid_t pid = getpid(); - int rc = sprintf_s(dump_file, - MAX_PATH_LEN, - "%s/%s_%s_%d.log", - dump_dir, - g_instance.attr.attr_common.PGXCNodeName, - g_instance.instance_context->name, - pid); + int rc = sprintf_s(dump_file, MAX_PATH_LEN, "%s/%s_%s_%d.log", dump_dir, g_instance.attr.attr_common.PGXCNodeName, + g_instance.instance_context->name, pid); #else ThreadId tid = gs_thread_self(); - int rc = sprintf_s( - dump_file, MAX_PATH_LEN, "%s/%s_%lu_%lu.log", dump_dir, ctx_name, (unsigned long)tid, (uint64)time(NULL)); + int rc = sprintf_s(dump_file, MAX_PATH_LEN, "%s/%s_%lu_%lu.log", dump_dir, ctx_name, (unsigned long)tid, + (uint64)time(NULL)); #endif securec_check_ss(rc, "\0", "\0"); - FILE* dump_fp = fopen(dump_file, "w"); + FILE *dump_fp = fopen(dump_file, "w"); if (NULL == dump_fp) { elog(LOG, "dump_memory: Failed to create file: %s, cause: %s", dump_file, strerror(errno)); return; @@ -8877,15 +9957,15 @@ void DumpMemoryContext(DUMP_TYPE type) } #endif // MEMORY_CONTEXT_CHECKING -static void FetchLWLockInfoOfAllBackends(lwm_lwlocks** lwlockInfo, int& nBackends) +static void FetchLWLockInfoOfAllBackends(lwm_lwlocks **lwlockInfo, int &nBackends) { nBackends = BackendStatusArray_size; - *lwlockInfo = (lwm_lwlocks*)palloc0(sizeof(lwm_lwlocks) * nBackends); + *lwlockInfo = (lwm_lwlocks *)palloc0(sizeof(lwm_lwlocks) * nBackends); - lwm_lwlocks* lwlock = *lwlockInfo; + lwm_lwlocks *lwlock = *lwlockInfo; for (int n = 0; n < nBackends; ++n) { - volatile PgBackendStatus* beentry = t_thrd.shemem_ptr_cxt.BackendStatusArray + n; + volatile PgBackendStatus *beentry = t_thrd.shemem_ptr_cxt.BackendStatusArray + n; /* we must guarantee atomic for set access flag */ START_CRIT_SECTION(); @@ -8919,9 +9999,9 @@ static void FetchLWLockInfoOfAllBackends(lwm_lwlocks** lwlockInfo, int& nBackend lwlock->want_lwlock.lock = beentry->lw_want_lock; const int needSize = (sizeof(lwlock_id_mode) * lwlock->lwlocks_num); if (lwlock->held_lwlocks != NULL) { - lwlock->held_lwlocks = (lwlock_id_mode*)repalloc(lwlock->held_lwlocks, needSize); + lwlock->held_lwlocks = (lwlock_id_mode *)repalloc(lwlock->held_lwlocks, needSize); } else { - lwlock->held_lwlocks = (lwlock_id_mode*)palloc0(needSize); + lwlock->held_lwlocks = (lwlock_id_mode *)palloc0(needSize); } copy_held_lwlocks(beentry->lw_held_locks, lwlock->held_lwlocks, lwlock->lwlocks_num); @@ -8947,24 +10027,19 @@ static void FetchLWLockInfoOfAllBackends(lwm_lwlocks** lwlockInfo, int& nBackend nBackends = (lwlock - *lwlockInfo); } -static void OutLogLWLockInfoOfAllBackends(lwm_lwlocks* lwlockInfo, int nBackends) +static void OutLogLWLockInfoOfAllBackends(lwm_lwlocks *lwlockInfo, int nBackends) { StringInfoData fmtOutLog; initStringInfo(&fmtOutLog); - lwm_lwlocks* info = lwlockInfo; + lwm_lwlocks *info = lwlockInfo; for (int beIdx = 0; beIdx < nBackends; ++beIdx) { /* format lwlock info of each backend */ - appendStringInfo(&fmtOutLog, - "[LWLOCK INFO] thread %lu, required (%s), held num %d, locks(name, mode):", - info->be_tid.thread_id, - T_NAME(info->want_lwlock.lock), - info->lwlocks_num); + appendStringInfo(&fmtOutLog, "[LWLOCK INFO] thread %lu, required (%s), held num %d, locks(name, mode):", + info->be_tid.thread_id, T_NAME(info->want_lwlock.lock), info->lwlocks_num); for (int lockIdx = 0; lockIdx < info->lwlocks_num; ++lockIdx) { - appendStringInfo(&fmtOutLog, - "(%s, %d)", - T_NAME(info->held_lwlocks[lockIdx].lock_addr.lock), - info->held_lwlocks[lockIdx].lock_sx); + appendStringInfo(&fmtOutLog, "(%s, %d)", T_NAME(info->held_lwlocks[lockIdx].lock_addr.lock), + info->held_lwlocks[lockIdx].lock_sx); } /* print lwlock info of each backend */ ereport(LOG, (errmsg("%s", fmtOutLog.data))); @@ -8976,9 +10051,9 @@ static void OutLogLWLockInfoOfAllBackends(lwm_lwlocks* lwlockInfo, int nBackends pfree(fmtOutLog.data); } -static void FreeLWLockInfoOfAllBackends(lwm_lwlocks* lwlockInfo, int nBackends) +static void FreeLWLockInfoOfAllBackends(lwm_lwlocks *lwlockInfo, int nBackends) { - lwm_lwlocks* info = lwlockInfo; + lwm_lwlocks *info = lwlockInfo; for (int i = 0; i < nBackends; ++i) { pfree(info->held_lwlocks); ++info; @@ -8988,7 +10063,7 @@ static void FreeLWLockInfoOfAllBackends(lwm_lwlocks* lwlockInfo, int nBackends) void DumpLWLockInfoToServerLog(void) { - lwm_lwlocks* lwlockInfo = NULL; + lwm_lwlocks *lwlockInfo = NULL; int nBackends = 0; FetchLWLockInfoOfAllBackends(&lwlockInfo, nBackends); OutLogLWLockInfoOfAllBackends(lwlockInfo, nBackends); @@ -8998,13 +10073,13 @@ void DumpLWLockInfoToServerLog(void) /* * read light-weight detect data from backends data. */ -lwm_light_detect* pgstat_read_light_detect(void) +lwm_light_detect *pgstat_read_light_detect(void) { const int n = BackendStatusArray_size; /* reset all this memory be 0 */ - lwm_light_detect* lw_detect = (lwm_light_detect*)palloc0(sizeof(lwm_light_detect) * n); - lwm_light_detect* lw_det = lw_detect; - volatile PgBackendStatus* beentry = t_thrd.shemem_ptr_cxt.BackendStatusArray; + lwm_light_detect *lw_detect = (lwm_light_detect *)palloc0(sizeof(lwm_light_detect) * n); + lwm_light_detect *lw_det = lw_detect; + volatile PgBackendStatus *beentry = t_thrd.shemem_ptr_cxt.BackendStatusArray; for (int i = 0; i < n; i++) { lwm_light_detect ld = {0, 0}; @@ -9052,12 +10127,12 @@ lwm_light_detect* pgstat_read_light_detect(void) * these diagnosis information includes: 1) lwlock to require; 2) thread id; * 3) the number and id of lwlocks held by this thread; */ -lwm_lwlocks* pgstat_read_diagnosis_data(lwm_light_detect* light_det, const int* candidates_pos, int num_candidates) +lwm_lwlocks *pgstat_read_diagnosis_data(lwm_light_detect *light_det, const int *candidates_pos, int num_candidates) { Size need_size = sizeof(lwm_lwlocks) * (uint32)num_candidates; /* Must all these memory be reset be 0 */ - lwm_lwlocks* lwlocks = (lwm_lwlocks*)palloc0(need_size); - lwm_lwlocks* cur_lwlock = NULL; + lwm_lwlocks *lwlocks = (lwm_lwlocks *)palloc0(need_size); + lwm_lwlocks *cur_lwlock = NULL; lwm_lwlocks tmplock; errno_t errval = memset_s(&tmplock, sizeof(tmplock), 0, sizeof(tmplock)); securec_check_errval(errval, , LOG); @@ -9066,10 +10141,10 @@ lwm_lwlocks* pgstat_read_diagnosis_data(lwm_light_detect* light_det, const int* for (int i = 0; i < num_candidates; i++) { /* remember its position */ tmplock.be_idx = candidates_pos[i]; - tmplock.held_lwlocks = (lwlock_id_mode*)palloc0(get_held_lwlocks_maxnum() * sizeof(lwlock_id_mode)); + tmplock.held_lwlocks = (lwlock_id_mode *)palloc0(get_held_lwlocks_maxnum() * sizeof(lwlock_id_mode)); - volatile PgBackendStatus* beentry = t_thrd.shemem_ptr_cxt.BackendStatusArray + candidates_pos[i]; - lwm_light_detect* cur_det = light_det + candidates_pos[i]; + volatile PgBackendStatus *beentry = t_thrd.shemem_ptr_cxt.BackendStatusArray + candidates_pos[i]; + lwm_light_detect *cur_det = light_det + candidates_pos[i]; bool success = true; /* We must guarantee atomic for set access flag */ @@ -9140,7 +10215,7 @@ lwm_lwlocks* pgstat_read_diagnosis_data(lwm_light_detect* light_det, const int* /* read xact starting time of this thread */ TimestampTz pgstat_read_xact_start_tm(int be_index) { - volatile PgBackendStatus* beentry = t_thrd.shemem_ptr_cxt.BackendStatusArray + be_index; + volatile PgBackendStatus *beentry = t_thrd.shemem_ptr_cxt.BackendStatusArray + be_index; TimestampTz tmp_xact_tm = 0; /* read xact starting time from this backend */ @@ -9166,42 +10241,41 @@ TimestampTz pgstat_read_xact_start_tm(int be_index) * @return : distribution info contain function state and tuple slot. * NOTICE : the memory palloced in this function should be free outside where it was called. */ -TableDistributionInfo* getTableDataDistribution(TupleDesc tuple_desc, char* schema_name, char* table_name) +TableDistributionInfo *getTableDataDistribution(TupleDesc tuple_desc, char *schema_name, char *table_name) { StringInfoData buf; - TableDistributionInfo* distribuion_info = NULL; + TableDistributionInfo *distribuion_info = NULL; /* the memory palloced here should be free outside where it was called. */ - distribuion_info = (TableDistributionInfo*)palloc0(sizeof(TableDistributionInfo)); + distribuion_info = (TableDistributionInfo *)palloc0(sizeof(TableDistributionInfo)); initStringInfo(&buf); if (schema_name != NULL && table_name != NULL) { /* get only one table's distribution info */ appendStringInfo(&buf, - "select " - "n.nspname, " - "c.relname, " - "pg_catalog.pgxc_node_str(), " - "pg_catalog.pg_table_size(c.oid) " - "from pg_catalog.pg_class c " - "inner join pg_catalog.pg_namespace n on c.relnamespace = n.oid " - "where n.nspname = \'%s\' and c.relname=\'%s\' ", - schema_name, - table_name); + "select " + "n.nspname, " + "c.relname, " + "pg_catalog.pgxc_node_str(), " + "pg_catalog.pg_table_size(c.oid) " + "from pg_catalog.pg_class c " + "inner join pg_catalog.pg_namespace n on c.relnamespace = n.oid " + "where n.nspname = \'%s\' and c.relname=\'%s\' ", + schema_name, table_name); } else { /* get all table's distribution in current database and we only get info about user's table without catalog * table. */ appendStringInfo(&buf, - "select " - "n.nspname, " - "c.relname, " - "pg_catalog.pgxc_node_str(), " - "pg_catalog.pg_table_size(c.oid) " - "from pg_class c " - "inner join pg_catalog.pg_namespace n on c.relnamespace = n.oid " - "where c.relkind = 'r' and c.oid > %d and n.nspname <> 'cstore' and n.nspname <> 'pmk';", - FirstNormalObjectId); + "select " + "n.nspname, " + "c.relname, " + "pg_catalog.pgxc_node_str(), " + "pg_catalog.pg_table_size(c.oid) " + "from pg_class c " + "inner join pg_catalog.pg_namespace n on c.relnamespace = n.oid " + "where c.relkind = 'r' and c.oid > %d and n.nspname <> 'cstore' and n.nspname <> 'pmk';", + FirstNormalObjectId); } /* send sql and parallel fetch distribution info from all data nodes */ @@ -9211,56 +10285,53 @@ TableDistributionInfo* getTableDataDistribution(TupleDesc tuple_desc, char* sche return distribuion_info; } -TableDistributionInfo* getTableStat(TupleDesc tuple_desc, int dirty_pecent, int n_tuples, char* schema_name) +TableDistributionInfo *getTableStat(TupleDesc tuple_desc, int dirty_pecent, int n_tuples, char *schema_name) { StringInfoData buf; - TableDistributionInfo* distribuion_info = NULL; + TableDistributionInfo *distribuion_info = NULL; /* the memory palloced here should be free outside where it was called. */ - distribuion_info = (TableDistributionInfo*)palloc0(sizeof(TableDistributionInfo)); + distribuion_info = (TableDistributionInfo *)palloc0(sizeof(TableDistributionInfo)); initStringInfo(&buf); /* get all table's stat in current database and we only get info about user's table without catalog table. */ if (schema_name == NULL) appendStringInfo(&buf, - "select " - "c.relname, " - "n.nspname, " - "pg_stat_get_tuples_inserted(c.oid), " - "pg_stat_get_tuples_updated(c.oid), " - "pg_stat_get_tuples_deleted(c.oid), " - "pg_stat_get_live_tuples(c.oid) n_live_tuples, " - "pg_stat_get_dead_tuples(c.oid) n_dead_tuples " - "from pg_class c " - "left join pg_namespace n on (c.relnamespace = n.oid) " - "where c.relkind = 'r' and relpersistence <> 't' " - "and cast((n_dead_tuples)/(n_live_tuples+n_dead_tuples+0.00001) * 100 " - "as numeric(5,2)) >= %d " - "and (n_live_tuples+n_dead_tuples) >= %d " - "and n.nspname not in ('pg_toast','information_schema','cstore','pmk'); ", - dirty_pecent, - n_tuples); + "select " + "c.relname, " + "n.nspname, " + "pg_stat_get_tuples_inserted(c.oid), " + "pg_stat_get_tuples_updated(c.oid), " + "pg_stat_get_tuples_deleted(c.oid), " + "pg_stat_get_live_tuples(c.oid) n_live_tuples, " + "pg_stat_get_dead_tuples(c.oid) n_dead_tuples " + "from pg_class c " + "left join pg_namespace n on (c.relnamespace = n.oid) " + "where c.relkind = 'r' and relpersistence <> 't' " + "and cast((n_dead_tuples)/(n_live_tuples+n_dead_tuples+0.00001) * 100 " + "as numeric(5,2)) >= %d " + "and (n_live_tuples+n_dead_tuples) >= %d " + "and n.nspname not in ('pg_toast','information_schema','cstore','pmk'); ", + dirty_pecent, n_tuples); else appendStringInfo(&buf, - "select " - "c.relname, " - "n.nspname, " - "pg_stat_get_tuples_inserted(c.oid), " - "pg_stat_get_tuples_updated(c.oid), " - "pg_stat_get_tuples_deleted(c.oid), " - "pg_stat_get_live_tuples(c.oid) n_live_tuples, " - "pg_stat_get_dead_tuples(c.oid) n_dead_tuples " - "from pg_class c " - "left join pg_namespace n on (c.relnamespace = n.oid) " - "where c.relkind = 'r' and relpersistence <> 't' " - "and cast((n_dead_tuples)/(n_live_tuples+n_dead_tuples+0.00001) * 100 " - "as numeric(5,2)) >= %d " - "and (n_live_tuples+n_dead_tuples) >= %d " - "and n.nspname = \'%s\'; ", - dirty_pecent, - n_tuples, - schema_name); + "select " + "c.relname, " + "n.nspname, " + "pg_stat_get_tuples_inserted(c.oid), " + "pg_stat_get_tuples_updated(c.oid), " + "pg_stat_get_tuples_deleted(c.oid), " + "pg_stat_get_live_tuples(c.oid) n_live_tuples, " + "pg_stat_get_dead_tuples(c.oid) n_dead_tuples " + "from pg_class c " + "left join pg_namespace n on (c.relnamespace = n.oid) " + "where c.relkind = 'r' and relpersistence <> 't' " + "and cast((n_dead_tuples)/(n_live_tuples+n_dead_tuples+0.00001) * 100 " + "as numeric(5,2)) >= %d " + "and (n_live_tuples+n_dead_tuples) >= %d " + "and n.nspname = \'%s\'; ", + dirty_pecent, n_tuples, schema_name); /* send sql and parallel fetch distribution info from all data nodes */ distribuion_info->state = RemoteFunctionResultHandler(buf.data, NULL, NULL, true, EXEC_ON_DATANODES, true); @@ -9269,201 +10340,364 @@ TableDistributionInfo* getTableStat(TupleDesc tuple_desc, int dirty_pecent, int return distribuion_info; } -TableDistributionInfo* get_remote_stat_pagewriter(TupleDesc tuple_desc) +/* + * 功能:获取远程Page Writer(页写入器)节点的统计信息 + * + * 参数: + * tuple_desc:Tuple描述符,用于构建返回的结果集 + * + * 返回值: + * TableDistributionInfo 结构的指针,包含从远程节点获取的Page Writer节点的统计信息 + * + * 注意: + * 该函数用于向远程数据节点发送SQL查询,以获取它们的Page Writer节点的统计信息。 + * 首先,函数分配并初始化一个TableDistributionInfo结构的指针distribuion_info,用于存储查询结果。 + * 然后,初始化一个StringInfoData结构的buf,用于构建SQL查询语句。 + * 接着,使用appendStringInfo将SQL查询语句添加到buf中。 + * 最后,调用RemoteFunctionResultHandler函数发送SQL查询到所有数据节点,并返回结果。 + * 注意:在函数外部应该释放在此处分配的内存。 + */ +TableDistributionInfo *get_remote_stat_pagewriter(TupleDesc tuple_desc) { StringInfoData buf; - TableDistributionInfo* distribuion_info = NULL; + TableDistributionInfo *distribuion_info = NULL; - /* the memory palloced here should be free outside where it was called. */ - distribuion_info = (TableDistributionInfo*)palloc0(sizeof(TableDistributionInfo)); + // 分配并初始化TableDistributionInfo结构 + distribuion_info = (TableDistributionInfo *)palloc0(sizeof(TableDistributionInfo)); + // 初始化buf,用于构建SQL查询语句 initStringInfo(&buf); - appendStringInfo(&buf, - "select " - "node_name, pgwr_actual_flush_total_num, pgwr_last_flush_num, remain_dirty_page_num, " - "queue_head_page_rec_lsn, queue_rec_lsn, current_xlog_insert_lsn, ckpt_redo_point " - "from local_pagewriter_stat(); "); + // 构建SQL查询语句 + appendStringInfo(&buf, "select " + "node_name, pgwr_actual_flush_total_num, pgwr_last_flush_num, remain_dirty_page_num, " + "queue_head_page_rec_lsn, queue_rec_lsn, current_xlog_insert_lsn, ckpt_redo_point " + "from local_pagewriter_stat(); "); - /* send sql and parallel fetch distribution info from all data nodes */ + // 发送SQL查询到所有数据节点并返回结果 distribuion_info->state = RemoteFunctionResultHandler(buf.data, NULL, NULL, true, EXEC_ON_ALL_NODES, true); distribuion_info->slot = MakeSingleTupleTableSlot(tuple_desc); return distribuion_info; } -TableDistributionInfo* get_remote_stat_ckpt(TupleDesc tuple_desc) +/* + * 功能:获取远程Checkpoint(检查点)节点的统计信息 + * + * 参数: + * tuple_desc:Tuple描述符,用于构建返回的结果集 + * + * 返回值: + * TableDistributionInfo 结构的指针,包含从远程节点获取的Checkpoint节点的统计信息 + * + * 注意: + * 该函数用于向远程数据节点发送SQL查询,以获取它们的Checkpoint节点的统计信息。 + * 首先,函数分配并初始化一个TableDistributionInfo结构的指针distribuion_info,用于存储查询结果。 + * 然后,初始化一个StringInfoData结构的buf,用于构建SQL查询语句。 + * 接着,使用appendStringInfo将SQL查询语句添加到buf中。 + * 最后,调用RemoteFunctionResultHandler函数发送SQL查询到所有数据节点,并返回结果。 + * 注意:在函数外部应该释放在此处分配的内存。 + */ +TableDistributionInfo *get_remote_stat_ckpt(TupleDesc tuple_desc) { StringInfoData buf; - TableDistributionInfo* distribuion_info = NULL; + TableDistributionInfo *distribuion_info = NULL; - /* the memory palloced here should be free outside where it was called. */ - distribuion_info = (TableDistributionInfo*)palloc0(sizeof(TableDistributionInfo)); + // 分配并初始化TableDistributionInfo结构 + distribuion_info = (TableDistributionInfo *)palloc0(sizeof(TableDistributionInfo)); + // 初始化buf,用于构建SQL查询语句 initStringInfo(&buf); - appendStringInfo(&buf, - "select " - "node_name,ckpt_redo_point,ckpt_clog_flush_num,ckpt_csnlog_flush_num, " - "ckpt_multixact_flush_num,ckpt_predicate_flush_num,ckpt_twophase_flush_num " - "from local_ckpt_stat(); "); + // 构建SQL查询语句 + appendStringInfo(&buf, "select " + "node_name,ckpt_redo_point,ckpt_clog_flush_num,ckpt_csnlog_flush_num, " + "ckpt_multixact_flush_num,ckpt_predicate_flush_num,ckpt_twophase_flush_num " + "from local_ckpt_stat(); "); - /* send sql and parallel fetch distribution info from all data nodes */ + // 发送SQL查询到所有数据节点并返回结果 distribuion_info->state = RemoteFunctionResultHandler(buf.data, NULL, NULL, true, EXEC_ON_ALL_NODES, true); distribuion_info->slot = MakeSingleTupleTableSlot(tuple_desc); return distribuion_info; } -TableDistributionInfo* get_remote_stat_bgwriter(TupleDesc tuple_desc) +/* + * 功能:获取远程BGWriter节点的统计信息 + * + * 参数列表: + * tuple_desc:Tuple描述符,用于构建返回的结果集 + * + * 返回值: + * TableDistributionInfo 结构的指针,包含从远程节点获取的BGWriter节点的统计信息 + * + * 注意: + * 该函数用于向远程数据节点发送SQL查询,以获取它们的BGWriter节点的统计信息。 + * 首先,函数分配并初始化一个TableDistributionInfo结构的指针distribuion_info,用于存储查询结果。 + * 然后,初始化一个StringInfoData结构的buf,用于构建SQL查询语句。 + * 接着,使用appendStringInfo将SQL查询语句添加到buf中。 + * 最后,调用RemoteFunctionResultHandler函数发送SQL查询到所有数据节点,并返回结果。 + * 注意:在函数外部应该释放在此处分配的内存。 + */ +TableDistributionInfo *get_remote_stat_bgwriter(TupleDesc tuple_desc) { StringInfoData buf; - TableDistributionInfo* distribuion_info = NULL; + TableDistributionInfo *distribuion_info = NULL; - /* the memory palloced here should be free outside where it was called. */ - distribuion_info = (TableDistributionInfo*)palloc0(sizeof(TableDistributionInfo)); + // 分配并初始化TableDistributionInfo结构 + distribuion_info = (TableDistributionInfo *)palloc0(sizeof(TableDistributionInfo)); + // 初始化buf,用于构建SQL查询语句 initStringInfo(&buf); - appendStringInfo(&buf, - "select " - "node_name,bgwr_actual_flush_total_num,bgwr_last_flush_num,candidate_slots, " - "get_buffer_from_list,get_buf_clock_sweep " - "from local_bgwriter_stat(); "); + // 构建SQL查询语句 + appendStringInfo(&buf, "select " + "node_name,bgwr_actual_flush_total_num,bgwr_last_flush_num,candidate_slots, " + "get_buffer_from_list,get_buf_clock_sweep " + "from local_bgwriter_stat(); "); - /* send sql and parallel fetch distribution info from all data nodes */ + // 发送SQL查询到所有数据节点并返回结果 distribuion_info->state = RemoteFunctionResultHandler(buf.data, NULL, NULL, true, EXEC_ON_ALL_NODES, true); distribuion_info->slot = MakeSingleTupleTableSlot(tuple_desc); return distribuion_info; } -TableDistributionInfo* get_remote_stat_candidate(TupleDesc tuple_desc) +/* + * 功能:获取远程候选节点的统计信息 + * + * 参数列表: + * tuple_desc:Tuple描述符,用于构建返回的结果集 + * + * 返回值: + * TableDistributionInfo 结构的指针,包含从远程节点获取的候选节点的统计信息 + * + * 注意: + * 该函数用于向远程数据节点发送SQL查询,以获取它们的候选节点的统计信息。 + * 首先,函数分配并初始化一个TableDistributionInfo结构的指针distribuion_info,用于存储查询结果。 + * 然后,初始化一个StringInfoData结构的buf,用于构建SQL查询语句。 + * 接着,使用appendStringInfo将SQL查询语句添加到buf中。 + * 最后,调用RemoteFunctionResultHandler函数发送SQL查询到所有数据节点,并返回结果。 + * 注意:在函数外部应该释放在此处分配的内存。 + */ +TableDistributionInfo *get_remote_stat_candidate(TupleDesc tuple_desc) { StringInfoData buf; - TableDistributionInfo* distribuion_info = NULL; + TableDistributionInfo *distribuion_info = NULL; - /* the memory palloced here should be free outside where it was called. */ - distribuion_info = (TableDistributionInfo*)palloc0(sizeof(TableDistributionInfo)); + // 分配并初始化TableDistributionInfo结构 + distribuion_info = (TableDistributionInfo *)palloc0(sizeof(TableDistributionInfo)); + // 初始化buf,用于构建SQL查询语句 initStringInfo(&buf); - appendStringInfo(&buf, - "select " - "node_name,candidate_slots,get_buf_from_list,get_buf_clock_sweep, " - "seg_candidate_slots,seg_get_buf_from_list,seg_get_buf_clock_sweep " - "from local_candidate_stat(); "); + // 构建SQL查询语句 + appendStringInfo(&buf, "select " + "node_name,candidate_slots,get_buf_from_list,get_buf_clock_sweep, " + "seg_candidate_slots,seg_get_buf_from_list,seg_get_buf_clock_sweep " + "from local_candidate_stat(); "); - /* send sql and parallel fetch distribution info from all data nodes */ + // 发送SQL查询到所有数据节点并返回结果 distribuion_info->state = RemoteFunctionResultHandler(buf.data, NULL, NULL, true, EXEC_ON_ALL_NODES, true); distribuion_info->slot = MakeSingleTupleTableSlot(tuple_desc); return distribuion_info; } - -TableDistributionInfo* get_remote_single_flush_dw_stat(TupleDesc tuple_desc) +/* + * 功能:获取远程单页刷新的双写统计信息 + * + * 参数: + * tuple_desc:Tuple描述符,用于构建返回的结果集 + * + * 返回值: + * TableDistributionInfo 结构的指针,包含从远程节点获取的单页刷新的双写统计信息 + * + * 注意: + * 该函数用于向远程数据节点发送SQL查询,以获取它们的单页刷新的双写统计信息。 + * 首先,函数分配并初始化一个TableDistributionInfo结构的指针distribuion_info,用于存储查询结果。 + * 然后,初始化一个StringInfoData结构的buf,用于构建SQL查询语句。 + * 接着,使用appendStringInfo将SQL查询语句添加到buf中。 + * 最后,调用RemoteFunctionResultHandler函数发送SQL查询到所有数据节点,并返回结果。 + * 注意:在函数外部应该释放在此处分配的内存。 + */ +TableDistributionInfo *get_remote_single_flush_dw_stat(TupleDesc tuple_desc) { StringInfoData buf; - TableDistributionInfo* distribuion_info = NULL; + TableDistributionInfo *distribuion_info = NULL; - /* the memory palloced here should be free outside where it was called. */ - distribuion_info = (TableDistributionInfo*)palloc0(sizeof(TableDistributionInfo)); + // 分配并初始化TableDistributionInfo结构 + distribuion_info = (TableDistributionInfo *)palloc0(sizeof(TableDistributionInfo)); + // 初始化buf,用于构建SQL查询语句 initStringInfo(&buf); - appendStringInfo(&buf, - "SELECT node_name, curr_dwn, curr_start_page, total_writes, file_trunc_num, file_reset_num " - "FROM local_single_flush_dw_stat();"); + // 构建SQL查询语句 + appendStringInfo(&buf, "SELECT node_name, curr_dwn, curr_start_page, total_writes, file_trunc_num, file_reset_num " + "FROM local_single_flush_dw_stat();"); - /* send sql and parallel fetch distribution info from all data nodes */ + // 发送SQL查询到所有数据节点并返回结果 distribuion_info->state = RemoteFunctionResultHandler(buf.data, NULL, NULL, true, EXEC_ON_ALL_NODES, true); distribuion_info->slot = MakeSingleTupleTableSlot(tuple_desc); return distribuion_info; } -TableDistributionInfo* get_remote_stat_double_write(TupleDesc tuple_desc) +/* + * 功能:获取远程统计的双写信息 + * + * 参数: + * tuple_desc:Tuple描述符,用于构建返回的结果集 + * + * 返回值: + * TableDistributionInfo 结构的指针,包含从远程节点获取的双写统计信息 + * + * 注意: + * 该函数用于向远程数据节点发送SQL查询,以获取它们的双写统计信息。 + * 首先,函数分配并初始化一个TableDistributionInfo结构的指针distribuion_info,用于存储查询结果。 + * 然后,初始化一个StringInfoData结构的buf,用于构建SQL查询语句。 + * 接着,使用appendStringInfo将SQL查询语句添加到buf中。 + * 最后,调用RemoteFunctionResultHandler函数发送SQL查询到所有数据节点,并返回结果。 + * 注意:在函数外部应该释放在此处分配的内存。 + */ +TableDistributionInfo *get_remote_stat_double_write(TupleDesc tuple_desc) { StringInfoData buf; - TableDistributionInfo* distribuion_info = NULL; + TableDistributionInfo *distribuion_info = NULL; - /* the memory palloced here should be free outside where it was called. */ - distribuion_info = (TableDistributionInfo*)palloc0(sizeof(TableDistributionInfo)); + // 分配并初始化TableDistributionInfo结构 + distribuion_info = (TableDistributionInfo *)palloc0(sizeof(TableDistributionInfo)); + // 初始化buf,用于构建SQL查询语句 initStringInfo(&buf); - appendStringInfo(&buf, - "SELECT node_name, curr_dwn, curr_start_page, file_trunc_num, file_reset_num, " - "total_writes, low_threshold_writes, high_threshold_writes, " - "total_pages, low_threshold_pages, high_threshold_pages, file_id " - "FROM local_double_write_stat();"); + // 构建SQL查询语句 + appendStringInfo(&buf, "SELECT node_name, curr_dwn, curr_start_page, file_trunc_num, file_reset_num, " + "total_writes, low_threshold_writes, high_threshold_writes, " + "total_pages, low_threshold_pages, high_threshold_pages, file_id " + "FROM local_double_write_stat();"); - /* send sql and parallel fetch distribution info from all data nodes */ + // 发送SQL查询到所有数据节点并返回结果 distribuion_info->state = RemoteFunctionResultHandler(buf.data, NULL, NULL, true, EXEC_ON_ALL_NODES, true); distribuion_info->slot = MakeSingleTupleTableSlot(tuple_desc); return distribuion_info; } -TableDistributionInfo* get_remote_stat_redo(TupleDesc tuple_desc) +/* + * 功能:获取远程统计的重做信息 + * + * 参数列表: + * tuple_desc:Tuple描述符,用于构建返回的结果集 + * + * 返回值: + * TableDistributionInfo 结构的指针,包含从远程节点获取的重做统计信息 + * + * 注意: + * 该函数用于向远程数据节点发送SQL查询,以获取它们的重做统计信息。 + * 首先,函数分配并初始化一个TableDistributionInfo结构的指针distribuion_info,用于存储查询结果。 + * 然后,初始化一个StringInfoData结构的buf,用于构建SQL查询语句。 + * 接着,使用appendStringInfo将SQL查询语句添加到buf中。 + * 最后,调用RemoteFunctionResultHandler函数发送SQL查询到所有数据节点,并返回结果。 + * 注意:在函数外部应该释放在此处分配的内存。 + */ +TableDistributionInfo *get_remote_stat_redo(TupleDesc tuple_desc) { StringInfoData buf; - TableDistributionInfo* distribuion_info = NULL; + TableDistributionInfo *distribuion_info = NULL; - /* the memory palloced here should be free outside where it was called. */ - distribuion_info = (TableDistributionInfo*)palloc0(sizeof(TableDistributionInfo)); + // 分配并初始化TableDistributionInfo结构 + distribuion_info = (TableDistributionInfo *)palloc0(sizeof(TableDistributionInfo)); + // 初始化buf,用于构建SQL查询语句 initStringInfo(&buf); - appendStringInfo(&buf, - "SELECT node_name, redo_start_ptr, redo_start_time, redo_done_time, curr_time, " - "min_recovery_point, read_ptr, last_replayed_read_ptr, recovery_done_ptr, " - "read_xlog_io_counter, read_xlog_io_total_dur, " - "read_data_io_counter, read_data_io_total_dur, " - "write_data_io_counter, write_data_io_total_dur, " - "process_pending_counter, process_pending_total_dur, " - "apply_counter, apply_total_dur, " - "speed, local_max_ptr, primary_flush_ptr, worker_info " - "FROM local_redo_stat();"); + // 构建SQL查询语句 + appendStringInfo(&buf, "SELECT node_name, redo_start_ptr, redo_start_time, redo_done_time, curr_time, " + "min_recovery_point, read_ptr, last_replayed_read_ptr, recovery_done_ptr, " + "read_xlog_io_counter, read_xlog_io_total_dur, " + "read_data_io_counter, read_data_io_total_dur, " + "write_data_io_counter, write_data_io_total_dur, " + "process_pending_counter, process_pending_total_dur, " + "apply_counter, apply_total_dur, " + "speed, local_max_ptr, primary_flush_ptr, worker_info " + "FROM local_redo_stat();"); - /* send sql and parallel fetch distribution info from all data nodes */ + // 发送SQL查询到所有数据节点并返回结果 distribuion_info->state = RemoteFunctionResultHandler(buf.data, NULL, NULL, true, EXEC_ON_ALL_NODES, true); distribuion_info->slot = MakeSingleTupleTableSlot(tuple_desc); return distribuion_info; } -TableDistributionInfo* get_rto_stat(TupleDesc tuple_desc) +/* + * 功能:获取RTO(Recovery Time Objective)统计信息 + * + * 参数: + * tuple_desc:Tuple描述符,用于构建返回的结果集 + * + * 返回值: + * TableDistributionInfo 结构的指针,包含从远程节点获取的RTO统计信息 + * + * 注意: + * 该函数用于向远程数据节点发送SQL查询,以获取它们的RTO统计信息。 + * 首先,函数分配并初始化一个TableDistributionInfo结构的指针distribuion_info,用于存储查询结果。 + * 然后,初始化一个StringInfoData结构的buf,用于构建SQL查询语句。 + * 接着,使用appendStringInfo将SQL查询语句添加到buf中。 + * 最后,调用RemoteFunctionResultHandler函数发送SQL查询到所有数据节点,并返回结果。 + */ +TableDistributionInfo *get_rto_stat(TupleDesc tuple_desc) { StringInfoData buf; - TableDistributionInfo* distribuion_info = NULL; + TableDistributionInfo *distribuion_info = NULL; - /* the memory palloced here should be free outside where it was called. */ - distribuion_info = (TableDistributionInfo*)palloc0(sizeof(TableDistributionInfo)); + // 分配并初始化TableDistributionInfo结构 + distribuion_info = (TableDistributionInfo *)palloc0(sizeof(TableDistributionInfo)); + // 初始化buf,用于构建SQL查询语句 initStringInfo(&buf); + // 构建SQL查询语句 appendStringInfo(&buf, "SELECT node_name, rto_info FROM local_rto_stat();"); - /* send sql and parallel fetch distribution info from all data nodes */ + // 发送SQL查询到所有数据节点并返回结果 distribuion_info->state = RemoteFunctionResultHandler(buf.data, NULL, NULL, true, EXEC_ON_ALL_NODES, true); distribuion_info->slot = MakeSingleTupleTableSlot(tuple_desc); return distribuion_info; } -TableDistributionInfo* get_recovery_stat(TupleDesc tuple_desc) +/* + * 功能:获取恢复状态信息 + * + * 参数: + * tuple_desc:Tuple描述符,用于构建返回的结果集 + * + * 返回值: + * TableDistributionInfo 结构的指针,包含从远程节点获取的恢复状态信息 + * + * 注意: + * 该函数用于向远程数据节点发送SQL查询,以获取它们的恢复状态信息。 + * 首先,函数分配并初始化一个TableDistributionInfo结构的指针distribuion_info,用于存储查询结果。 + * 然后,初始化一个StringInfoData结构的buf,用于构建SQL查询语句。 + * 接着,使用appendStringInfo将SQL查询语句添加到buf中。 + * 最后,调用RemoteFunctionResultHandler函数发送SQL查询到所有数据节点,并返回结果。 + */ +TableDistributionInfo *get_recovery_stat(TupleDesc tuple_desc) { StringInfoData buf; - TableDistributionInfo* distribuion_info = NULL; + TableDistributionInfo *distribuion_info = NULL; /* the memory palloced here should be free outside where it was called. */ - distribuion_info = (TableDistributionInfo*)palloc0(sizeof(TableDistributionInfo)); + // 分配并初始化TableDistributionInfo结构 + distribuion_info = (TableDistributionInfo *)palloc0(sizeof(TableDistributionInfo)); + // 初始化buf,用于构建SQL查询语句 initStringInfo(&buf); - appendStringInfo(&buf, + // 构建SQL查询语句 + appendStringInfo( + &buf, "SELECT node_name, standby_node_name, source_ip, source_port, dest_ip, dest_port, current_rto, target_rto, " "current_sleep_time FROM " "local_recovery_status();"); @@ -9475,21 +10709,21 @@ TableDistributionInfo* get_recovery_stat(TupleDesc tuple_desc) return distribuion_info; } -TableDistributionInfo* streaming_hadr_get_recovery_stat(TupleDesc tuple_desc) +TableDistributionInfo *streaming_hadr_get_recovery_stat(TupleDesc tuple_desc) { StringInfoData buf; - TableDistributionInfo* distribuion_info = NULL; + TableDistributionInfo *distribuion_info = NULL; /* the memory palloced here should be free outside where it was called. */ - distribuion_info = (TableDistributionInfo*)palloc0(sizeof(TableDistributionInfo)); + distribuion_info = (TableDistributionInfo *)palloc0(sizeof(TableDistributionInfo)); initStringInfo(&buf); appendStringInfo(&buf, - "SELECT hadr_sender_node_name, hadr_receiver_node_name, " - "source_ip, source_port, dest_ip, dest_port, current_rto, target_rto, current_rpo, target_rpo, " - "rto_sleep_time, rpo_sleep_time FROM " - "gs_hadr_local_rto_and_rpo_stat();"); + "SELECT hadr_sender_node_name, hadr_receiver_node_name, " + "source_ip, source_port, dest_ip, dest_port, current_rto, target_rto, current_rpo, target_rpo, " + "rto_sleep_time, rpo_sleep_time FROM " + "gs_hadr_local_rto_and_rpo_stat();"); /* send sql and parallel fetch distribution info from all data nodes */ distribuion_info->state = RemoteFunctionResultHandler(buf.data, NULL, NULL, true, EXEC_ON_ALL_NODES, true); @@ -9497,17 +10731,34 @@ TableDistributionInfo* streaming_hadr_get_recovery_stat(TupleDesc tuple_desc) return distribuion_info; } - -TableDistributionInfo* get_remote_node_xid_csn(TupleDesc tuple_desc) +/* + * 功能:获取远程节点的XID和CSN信息 + * + * 参数: + * tuple_desc:Tuple描述符,用于构建返回的结果集 + * + * 返回值: + * TableDistributionInfo 结构的指针,包含从远程节点获取的XID和CSN信息 + * + * 注意: + * 该函数用于向远程数据节点发送SQL查询,以获取它们的XID和CSN信息。 + * 首先,函数分配并初始化一个TableDistributionInfo结构的指针distribuion_info,用于存储查询结果。 + * 然后,初始化一个StringInfoData结构的buf,用于构建SQL查询语句。 + * 接着,使用appendStringInfo将SQL查询语句添加到buf中。 + * 最后,调用RemoteFunctionResultHandler函数发送SQL查询到所有数据节点,并返回结果。 + */ +TableDistributionInfo *get_remote_node_xid_csn(TupleDesc tuple_desc) { StringInfoData buf; - TableDistributionInfo* distribuion_info = NULL; + TableDistributionInfo *distribuion_info = NULL; /* the memory palloced here should be free outside where it was called.*/ - distribuion_info = (TableDistributionInfo*)palloc0(sizeof(TableDistributionInfo)); + distribuion_info = (TableDistributionInfo *)palloc0(sizeof(TableDistributionInfo)); + // 初始化buf,用于构建SQL查询语句 initStringInfo(&buf); + // 构建SQL查询语句 appendStringInfo(&buf, "select node_name, next_xid, next_csn FROM gs_get_next_xid_csn();"); /* send sql and parallel fetch distribution info from all data nodes */ @@ -9518,21 +10769,23 @@ TableDistributionInfo* get_remote_node_xid_csn(TupleDesc tuple_desc) } #ifdef ENABLE_MULTIPLE_NODES -TableDistributionInfo* get_remote_index_status(TupleDesc tuple_desc, const char *schname, const char *idxname) +TableDistributionInfo *get_remote_index_status(TupleDesc tuple_desc, const char *schname, const char *idxname) { StringInfoData buf; - TableDistributionInfo* distribuion_info = NULL; + TableDistributionInfo *distribuion_info = NULL; /* the memory palloced here should be free outside where it was called.*/ - distribuion_info = (TableDistributionInfo*)palloc0(sizeof(TableDistributionInfo)); + distribuion_info = (TableDistributionInfo *)palloc0(sizeof(TableDistributionInfo)); initStringInfo(&buf); - appendStringInfo(&buf, "select a.node_name::text, b.indisready, b.indisvalid from pg_index b " - "left join pgxc_node a on b.xc_node_id = a.node_id " - "where indexrelid = (select oid from pg_class where relnamespace = " - "(select oid from pg_namespace where nspname = %s) " - "and relname = %s);", quote_literal_cstr(schname), quote_literal_cstr(idxname)); + appendStringInfo(&buf, + "select a.node_name::text, b.indisready, b.indisvalid from pg_index b " + "left join pgxc_node a on b.xc_node_id = a.node_id " + "where indexrelid = (select oid from pg_class where relnamespace = " + "(select oid from pg_namespace where nspname = %s) " + "and relname = %s);", + quote_literal_cstr(schname), quote_literal_cstr(idxname)); /* send sql and parallel fetch distribution info from all nodes */ distribuion_info->state = RemoteFunctionResultHandler(buf.data, NULL, NULL, true, EXEC_ON_ALL_NODES, true); @@ -9551,7 +10804,7 @@ TableDistributionInfo* get_remote_index_status(TupleDesc tuple_desc, const char * update by pgstat_recv_badblock_stat() */ MemoryContext global_bad_block_mcxt = NULL; -HTAB* global_bad_block_stat = NULL; +HTAB *global_bad_block_stat = NULL; /* * Record a statistics of bad block: @@ -9571,12 +10824,9 @@ void initGlobalBadBlockStat() /* global bad block statistics */ /* in g_instance.instance_context */ if (global_bad_block_mcxt == NULL) { - global_bad_block_mcxt = AllocSetContextCreate((MemoryContext)g_instance.instance_context, - "bad block stat global memory context", - ALLOCSET_DEFAULT_MINSIZE, - ALLOCSET_DEFAULT_INITSIZE, - ALLOCSET_DEFAULT_MAXSIZE, - SHARED_CONTEXT); + global_bad_block_mcxt = AllocSetContextCreate( + (MemoryContext)g_instance.instance_context, "bad block stat global memory context", + ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE, SHARED_CONTEXT); } LWLockAcquire(BadBlockStatHashLock, LW_EXCLUSIVE); @@ -9608,11 +10858,9 @@ void initLocalBadBlockStat() /* local thread bad block statistics */ /* in t_thrd.top_mem_cxt */ if (t_thrd.stat_cxt.local_bad_block_mcxt == NULL) { - t_thrd.stat_cxt.local_bad_block_mcxt = AllocSetContextCreate((MemoryContext)t_thrd.top_mem_cxt, - "bad block session memory context", - ALLOCSET_DEFAULT_MINSIZE, - ALLOCSET_DEFAULT_INITSIZE, - ALLOCSET_DEFAULT_MAXSIZE); + t_thrd.stat_cxt.local_bad_block_mcxt = + AllocSetContextCreate((MemoryContext)t_thrd.top_mem_cxt, "bad block session memory context", + ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); } if (t_thrd.stat_cxt.local_bad_block_stat == NULL) { @@ -9636,7 +10884,7 @@ void initLocalBadBlockStat() * @IN forknum: forknum for page/cu * @See also: */ -void addBadBlockStat(const RelFileNode* relfilenode, ForkNumber forknum) +void addBadBlockStat(const RelFileNode *relfilenode, ForkNumber forknum) { if (t_thrd.stat_cxt.local_bad_block_stat == NULL || relfilenode == NULL) { return; @@ -9655,8 +10903,8 @@ void addBadBlockStat(const RelFileNode* relfilenode, ForkNumber forknum) bool found = false; /* insert if not find, if out of memory return NULL */ - BadBlockHashEnt* entry = - (BadBlockHashEnt*)hash_search(t_thrd.stat_cxt.local_bad_block_stat, &hash_key, HASH_ENTER, &found); + BadBlockHashEnt *entry = + (BadBlockHashEnt *)hash_search(t_thrd.stat_cxt.local_bad_block_stat, &hash_key, HASH_ENTER, &found); if (entry != NULL) { if (!found) { @@ -9717,19 +10965,19 @@ static void pgstat_send_badblock_stat(void) PgStat_MsgBadBlock msg; HASH_SEQ_STATUS hash_seq; - BadBlockHashEnt* badblock_entry = NULL; + BadBlockHashEnt *badblock_entry = NULL; pgstat_setheader(&msg.m_hdr, PGSTAT_MTYPE_BADBLOCK); msg.m_nentries = 0; hash_seq_init(&hash_seq, t_thrd.stat_cxt.local_bad_block_stat); - while ((badblock_entry = (BadBlockHashEnt*)hash_seq_search(&hash_seq)) != NULL) { + while ((badblock_entry = (BadBlockHashEnt *)hash_seq_search(&hash_seq)) != NULL) { /* jump empty */ if (badblock_entry->error_count == 0) continue; - BadBlockHashEnt* m_ent = &msg.m_entry[msg.m_nentries]; + BadBlockHashEnt *m_ent = &msg.m_entry[msg.m_nentries]; errno_t rc = memcpy_s(m_ent, sizeof(BadBlockHashEnt), badblock_entry, sizeof(BadBlockHashEnt)); securec_check(rc, "", ""); @@ -9754,13 +11002,13 @@ static void pgstat_send_badblock_stat(void) * @IN len: without use * @See also: */ -static void pgstat_recv_badblock_stat(PgStat_MsgBadBlock* msg, int /* len */) +static void pgstat_recv_badblock_stat(PgStat_MsgBadBlock *msg, int /* len */) { if (global_bad_block_stat == NULL || msg == NULL) { return; } - BadBlockHashEnt* badblock_entry = &(msg->m_entry[0]); + BadBlockHashEnt *badblock_entry = &(msg->m_entry[0]); LWLockAcquire(BadBlockStatHashLock, LW_EXCLUSIVE); @@ -9771,10 +11019,11 @@ static void pgstat_recv_badblock_stat(PgStat_MsgBadBlock* msg, int /* len */) for (int i = 0; i < msg->m_nentries; i++, badblock_entry++) { bool found = false; - BadBlockHashKey* badblock_key = &(badblock_entry->key); + BadBlockHashKey *badblock_key = &(badblock_entry->key); /* insert if not find, if out of memory return NULL */ - BadBlockHashEnt* entry = (BadBlockHashEnt*)hash_search(global_bad_block_stat, badblock_key, HASH_ENTER, &found); + BadBlockHashEnt *entry = + (BadBlockHashEnt *)hash_search(global_bad_block_stat, badblock_key, HASH_ENTER, &found); if (entry != NULL) { if (!found) { @@ -9794,20 +11043,32 @@ static void pgstat_recv_badblock_stat(PgStat_MsgBadBlock* msg, int /* len */) /* * get current total counter */ -void GetCurrentTotalTableCounter(PgStat_TableCounts* total_table_counter) +/* + * 功能:获取当前总表格计数器 + * + * 参数: + * total_table_counter:指向PgStat_TableCounts结构的指针,用于存储总表格计数器的结果。 + * + * 注意: + * 该函数用于获取当前总表格计数器,包括表格的各种计数信息。 + * 首先,函数初始化一个TabStatusArray指针tsa,用于遍历表格状态数组u_sess->stat_cxt.pgStatTabList。 + * 然后,使用循环遍历每个表格状态数组中的表格状态条目PgStat_TableStatus。 + * 对于每个表格状态条目,将其计数信息累加到total_table_counter中,通过调用UniqueSQLSumTableStatCounter()函数实现。 + * 接着,遍历表格状态条目中的事务状态链表PgStat_TableXactStatus,将事务相关的计数信息也累加到total_table_counter中。 + * 最后,函数返回总表格计数器的结果。 + */ +void GetCurrentTotalTableCounter(PgStat_TableCounts *total_table_counter) { - TabStatusArray* tsa = NULL; - + TabStatusArray *tsa = NULL; + // 遍历表格状态数组 for (tsa = u_sess->stat_cxt.pgStatTabList; tsa != NULL; tsa = tsa->tsa_next) { for (int i = 0; i < tsa->tsa_used; i++) { - PgStat_TableStatus* entry = &tsa->tsa_entries[i]; - PgStat_TableXactStatus* trans = NULL; - + PgStat_TableStatus *entry = &tsa->tsa_entries[i]; + PgStat_TableXactStatus *trans = NULL; if (entry != NULL) { - // get total table counts stat + // 累加表格计数信息到total_table_counter UniqueSQLSumTableStatCounter((*total_table_counter), entry->t_counts); - - // get sub xact counter(IUD counters are in PgStat_TableXactStatus) + // 遍历事务状态链表,累加事务相关的计数信息 for (trans = entry->trans; trans != NULL; trans = trans->upper) { total_table_counter->t_tuples_inserted += trans->tuples_inserted; total_table_counter->t_tuples_updated += trans->tuples_updated; @@ -9830,7 +11091,7 @@ void GetCurrentTotalTableCounter(PgStat_TableCounts* total_table_counter) * * return true if current_table_counter has values */ -bool CalcSQLRowStatCounter(PgStat_TableCounts* last_total_counter, PgStat_TableCounts* current_sql_table_counter) +bool CalcSQLRowStatCounter(PgStat_TableCounts *last_total_counter, PgStat_TableCounts *current_sql_table_counter) { Assert(current_sql_table_counter && last_total_counter); @@ -9845,9 +11106,8 @@ bool CalcSQLRowStatCounter(PgStat_TableCounts* last_total_counter, PgStat_TableC if (u_sess->storage_cxt.twoPhaseCommitInProgress) return false; - ereport(DEBUG1, - (errmodule(MOD_INSTR), - errmsg("[UniqueSQL] unique id: %lu, calc row stat counter", u_sess->unique_sql_cxt.unique_sql_id))); + ereport(DEBUG1, (errmodule(MOD_INSTR), errmsg("[UniqueSQL] unique id: %lu, calc row stat counter", + u_sess->unique_sql_cxt.unique_sql_id))); PrintPgStatTableCounter('L', last_total_counter); @@ -9883,69 +11143,151 @@ bool CalcSQLRowStatCounter(PgStat_TableCounts* last_total_counter, PgStat_TableC } } +/* + * 功能:获取SQL运行时间信息计数器 + * + * 返回值: + * 返回本地计数器u_sess->percentile_cxt.LocalCounter的值,表示已获取的SQL运行时间信息的数量。 + * 如果没有可用的信息,计数器将为零。 + * + * 注意: + * 该函数用于获取SQL运行时间信息的计数器。首先,它检查全局SQL运行时间信息数组g_instance.stat_cxt.sql_rt_info_array是否为NULL。 + * 如果数组不为NULL,函数将根据节点类型选择适当的函数进行准备计算。 + * 如果是单节点模式(IS_SINGLE_NODE),将调用prepare_calculate_single()函数。 + * 否则,将调用prepare_calculate()函数。 + * 准备计算函数将根据全局数组的内容计算本地计数器u_sess->percentile_cxt.LocalCounter的值,表示已获取的SQL运行时间信息的数量。 + * 最后,函数返回计数器的值。 + */ int pgstat_fetch_sql_rt_info_counter(void) { + // 检查全局SQL运行时间信息数组是否为NULL if (g_instance.stat_cxt.sql_rt_info_array != NULL) { + // 如果是单节点模式,调用prepare_calculate_single()函数 if (IS_SINGLE_NODE) { prepare_calculate_single(g_instance.stat_cxt.sql_rt_info_array, &u_sess->percentile_cxt.LocalCounter); } else { + // 否则,调用prepare_calculate()函数 prepare_calculate(g_instance.stat_cxt.sql_rt_info_array, &u_sess->percentile_cxt.LocalCounter); } } + // 返回本地计数器的值,表示已获取的SQL运行时间信息的数量 return u_sess->percentile_cxt.LocalCounter; } -void pgstat_fetch_sql_rt_info_internal(SqlRTInfo* sqlrt) +/* + * 功能:内部函数,用于获取SQL运行时间信息 + * + * 参数列表: + * sqlrt:存储SQL运行时间信息的目标结构 + * + * 注意: + * 该函数用于获取SQL运行时间信息。首先,它检查传入的参数sqlrt是否为NULL以及本地计数器和百分位数数据是否有效。 + * 如果有任何一个条件不满足,函数将直接返回,不执行任何操作。 + * 如果满足条件,函数调用memcpy_s()函数将百分位数数据u_sess->percentile_cxt.LocalsqlRT拷贝到目标结构sqlrt中。 + * 最后,函数释放百分位数数据的内存。 + */ +void pgstat_fetch_sql_rt_info_internal(SqlRTInfo *sqlrt) { + // 检查传入的参数sqlrt是否为NULL以及本地计数器和百分位数数据是否有效 if (sqlrt == NULL || u_sess->percentile_cxt.LocalCounter == 0 || u_sess->percentile_cxt.LocalsqlRT == NULL) return; - errno_t rc = memcpy_s(sqlrt, - u_sess->percentile_cxt.LocalCounter * sizeof(SqlRTInfo), - u_sess->percentile_cxt.LocalsqlRT, - u_sess->percentile_cxt.LocalCounter * sizeof(SqlRTInfo)); + // 使用memcpy_s()将百分位数数据拷贝到目标结构sqlrt中 + errno_t rc = memcpy_s(sqlrt, u_sess->percentile_cxt.LocalCounter * sizeof(SqlRTInfo), + u_sess->percentile_cxt.LocalsqlRT, u_sess->percentile_cxt.LocalCounter * sizeof(SqlRTInfo)); securec_check(rc, "\0", "\0"); + // 释放百分位数数据的内存 pfree_ext(u_sess->percentile_cxt.LocalsqlRT); } +/* + * 功能:回复百分位数记录计数 + * + * 注意: + * 该函数的功能是回复百分位数记录的计数。首先,它创建一个用于存储消息的StringInfoData缓冲区。 + * 然后,它将g_instance.stat_cxt.calculate_on_other_cn设置为true,表示需要进行其他计算。 + * 接着,它调用pgstat_fetch_sql_rt_info_counter()函数,该函数用于获取百分位数记录的计数信息。 + * 接着,函数创建并发送一条类型为'c'的消息,消息中包括本地计数器u_sess->percentile_cxt.LocalCounter的值。 + * 然后,函数创建并发送一条类型为'f'的消息,表示结束。最后,函数通过pq_flush()将所有消息刷新到接收方。 + */ void pgstat_reply_percentile_record_count() { StringInfoData buf; + // 设置calculate_on_other_cn为true,表示需要进行其他计算 g_instance.stat_cxt.calculate_on_other_cn = true; - + // 调用pgstat_fetch_sql_rt_info_counter()获取百分位数记录的计数信息 (void)pgstat_fetch_sql_rt_info_counter(); - + // 开始一个消息 pq_beginmessage(&buf, 'c'); + // 向消息中添加本地计数器u_sess->percentile_cxt.LocalCounter的值 pq_sendint(&buf, u_sess->percentile_cxt.LocalCounter, sizeof(int)); + // 结束消息 pq_endmessage(&buf); + // 开始一个结束消息 pq_beginmessage(&buf, 'f'); pq_endmessage(&buf); + // 刷新所有消息到接收方 pq_flush(); } +/* + * 功能:回复百分位数记录 + * + * 注意: + * 该函数的功能是回复百分位数记录。首先,它创建一个用于存储消息的StringInfoData缓冲区。 + * 然后,它检查本地的百分位数计数器u_sess->percentile_cxt.LocalCounter是否为NULL。 + * 如果不为NULL,它进入一个循环,为每个百分位数计数器创建一条消息,并将消息发送给接收方。 + * 这些消息包括UniqueSQLId和rt两个整数。完成消息的发送后,释放u_sess->percentile_cxt.LocalsqlRT的内存。 + * 接着,函数创建并发送一条类型为'f'的消息,表示结束。最后,函数将g_instance.stat_cxt.calculate_on_other_cn + * 设置为false,以指示不再进行其他计算。最终,函数通过pq_flush()将所有消息刷新到接收方。 + */ void pgstat_reply_percentile_record() { StringInfoData buf; + + // 检查本地的百分位数计数器是否为NULL if (u_sess->percentile_cxt.LocalsqlRT != NULL) { + // 遍历百分位数计数器数组 for (int i = 0; i < u_sess->percentile_cxt.LocalCounter; i++) { + // 开始一个消息 pq_beginmessage(&buf, 'r'); + + // 向消息中添加UniqueSQLId和rt两个整数 pq_sendint64(&buf, u_sess->percentile_cxt.LocalsqlRT[i].UniqueSQLId); pq_sendint64(&buf, u_sess->percentile_cxt.LocalsqlRT[i].rt); + + // 结束消息 pq_endmessage(&buf); } + // 释放百分位数计数器数组的内存 pfree_ext(u_sess->percentile_cxt.LocalsqlRT); } + // 开始一个结束消息 pq_beginmessage(&buf, 'f'); pq_endmessage(&buf); + // 设置calculate_on_other_cn为false,表示不再进行其他计算 g_instance.stat_cxt.calculate_on_other_cn = false; + // 刷新所有消息到接收方 pq_flush(); } +/* + * pgstat_release_session_memory_entry: Release session-level memory entry. + * + * This function is responsible for releasing memory associated with the + * session-level memory entry, including the query plan and query plan issue + * strings. + */ void pgstat_release_session_memory_entry() { + // Check if the session memory entry is not NULL if (t_thrd.shemem_ptr_cxt.mySessionMemoryEntry != NULL) { + // Free memory associated with the query plan if it exists pfree_ext(t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->query_plan); + + // Reset the plan_size to 0 t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->plan_size = 0; + + // Free memory associated with the query plan issue if it exists pfree_ext(t_thrd.shemem_ptr_cxt.mySessionMemoryEntry->query_plan_issue); } -} - +} \ No newline at end of file -- 2.34.1 From 9608c283dcbd6d8b6d4200bd2aa8546656d54925 Mon Sep 17 00:00:00 2001 From: lucky <3180017453@qq.com> Date: Wed, 4 Oct 2023 13:23:56 +0800 Subject: [PATCH 49/50] =?UTF-8?q?=E5=85=B3=E4=BA=8E=E2=80=9Csrc/gausskerne?= =?UTF-8?q?l/process"=E6=96=87=E4=BB=B6=E5=A4=B9=E4=B8=8B=E7=9A=84?= =?UTF-8?q?=E6=B3=A8=E9=87=8A=E6=96=87=E4=BB=B6=E6=9B=B4=E6=96=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../process/postmaster/lwlockmonitor.cpp | 583 +++++++-- src/gausskernel/process/postmaster/pgarch.cpp | 468 +++++-- .../process/postmaster/rbcleaner.cpp | 955 +++++++++++---- .../process/postmaster/snapcapturer.cpp | 648 +++++++--- .../process/postmaster/startup.cpp | 501 ++++++-- .../process/postmaster/syslogger.cpp | 1083 ++++++++++++----- .../process/postmaster/walwriter.cpp | 2 +- .../process/postmaster/walwriterauxiliary.cpp | 2 +- src/include/postmaster/snapcapturer.h | 21 +- src/include/postmaster/syslogger.h | 69 +- 10 files changed, 3246 insertions(+), 1086 deletions(-) diff --git a/src/gausskernel/process/postmaster/lwlockmonitor.cpp b/src/gausskernel/process/postmaster/lwlockmonitor.cpp index a7a7005bc..ea7c74a21 100644 --- a/src/gausskernel/process/postmaster/lwlockmonitor.cpp +++ b/src/gausskernel/process/postmaster/lwlockmonitor.cpp @@ -37,44 +37,45 @@ #include "utils/memutils.h" #include "utils/timestamp.h" -#define DEFAULT_HOLDERS_NUM 8 +#define DEFAULT_HOLDERS_NUM 8 // 定义默认的持有者数目 +// 用于表示锁的条目 typedef struct { - LWLockAddr lock_addr; // Use LWLockAddr instead of LWLock* to avoid misuse during key hash - int holders_curnum; - int holders_maxnum; - int waiters_curnum; - int waiters_maxnum; - holding_lockmode* holders; - lock_entry_id* waiters; + LWLockAddr lock_addr; // 表示锁的地址 // Use LWLockAddr instead of LWLock* to avoid misuse during key hash + int holders_curnum; // 表示当前持有锁的线程数 + int holders_maxnum; // 表示最大允许的持有锁的线程数 + int waiters_curnum; // 表示当前等待锁的线程数 + int waiters_maxnum; // 表示最大允许的等待锁的线程数 + holding_lockmode* holders; // 指向当前持有锁的线程的数组 + lock_entry_id* waiters; // 指向当前等待锁的线程的数组 } lock_entry; - +// 用于表示线程的条目 typedef struct { - lock_entry_id be_tid; - int be_idx; - LWLockAddr want_lwlock; + lock_entry_id be_tid; // 表示线程的ID + int be_idx; // 表示线程的索引 + LWLockAddr want_lwlock; // 表示线程想要获取的lwlock } thread_entry; - +// 用于表示lwlock死锁的信息 typedef struct { - LWLock *lock; - lock_entry_id waiter; - lock_entry_id blocker; - int waiter_index; + LWLock *lock; // 指向相关lwlock的指针 + lock_entry_id waiter; // 表示在等待锁的线程的ID + lock_entry_id blocker; // 表示在阻止其他线程获取锁的线程的ID + int waiter_index; // 表示在等待线程数组中的索引 /* whether blocker is holding in X mode */ - LWLockMode block_xmode; + LWLockMode block_xmode; // 表示阻止线程是否以X模式持有锁 } lw_deadlock_info; - +// 用于表示已访问的线程信息 typedef struct { - lock_entry_id* entry_ids; - int cur_num; - int max_num; + lock_entry_id* entry_ids; // 指向已访问线程的ID数组 + int cur_num; // 表示当前已访问线程的数量 + int max_num; // 表示最大允许的已访问线程数量 } lwm_visited_thread; - +// 用于表示lwlock死锁信息 typedef struct { - lw_deadlock_info* info; - int max_num; - int start; /* range [start, end) */ - int end; + lw_deadlock_info* info; // 指向死锁信息的指针数组 + int max_num; // 表示最大允许的死锁信息数量 + int start; /* range [start, end) */ // 表示范围的开始位置(包含) + int end; // 表示范围的结束位置(不包含) } lwm_deadlock; /* Signal handlers */ @@ -90,8 +91,22 @@ static void ut_test_find_deadlock_cycle(void); * 2) unchanged light-weight change-count, * 3) this thread is valid and active. */ + +/* + * 功能:检查是否有线程可能被其他线程或自身阻塞 + * + * 参数: + * ver1、ver2:用于比较线程的状态 + * + * 返回值: + * bool类型,表示是否可能被阻塞 + */ static inline bool maybe_this_thread_blocked(lwm_light_detect* ver1, lwm_light_detect* ver2) { + // 检查两个版本的线程ID是否相同 + // 检查两个版本的会话ID是否相同 + // 检查两个版本的轻量级锁计数是否相同 + // 如果以上条件都满足,则返回 true,表示可能被阻塞 return (0 != ver2->entry_id.thread_id && ver2->entry_id.thread_id == ver1->entry_id.thread_id && ver2->entry_id.st_sessionid == ver1->entry_id.st_sessionid && ver2->lw_count == ver1->lw_count); } @@ -100,46 +115,84 @@ static inline bool maybe_this_thread_blocked(lwm_light_detect* ver1, lwm_light_d * compare two light detect data and check whether any thread may * be blocked by others or itself. may be deadlock happend ? */ + +/* + * 功能:比较两个轻量级检测数据的版本,以确定是否有线程可能被其他线程或自身阻塞 + * + * 参数: + * olds、news:旧版本和新版本的轻量级检测数据 + * + * 返回值: + * bool类型,表示是否找到了可能被阻塞的线程 + */ bool lwm_compare_light_detect(lwm_light_detect* olds, lwm_light_detect* news) { const int n = BackendStatusArray_size; bool found = false; - for (int i = 0; i < n; ++i) { + for (int i = 0; i < n; ++i) { // 遍历数组元素 + // 检查是否可能被阻塞 if (maybe_this_thread_blocked(olds++, news++)) { + // 如果找到一个可能被阻塞的线程 + // 将 found 标志设置为 true 并跳出循环 found = true; break; } } - return found; + return found; // 表示是否找到了可能被阻塞的线程 } /* find all the candidates, remember their positions and number */ + +/* + * 功能:查找所有可能被阻塞的候选线程,并记住它们的位置和数量 + * + * 参数: + * olds、news:旧版本和新版本的轻量级检测数据 + * out_num:输出候选线程的数量 + * + * 返回值: + * 返回一个整数数组,表示候选线程的位置 + */ int* lwm_find_candidates(lwm_light_detect* olds, lwm_light_detect* news, int* out_num) { const int n = BackendStatusArray_size; int tmp_num = 0; - int* candidates_pos = (int*)palloc(sizeof(int) * n); + int* candidates_pos = (int*)palloc(sizeof(int) * n); // 用于存储候选线程的位置 /* the first assignment */ *out_num = 0; - for (int i = 0; i < n; ++i) { + for (int i = 0; i < n; ++i) { // 遍历数组元素 + // 检查是否可能被阻塞 if (maybe_this_thread_blocked(olds++, news++)) { + // 如果找到一个可能被阻塞的线程,将其位置添加到 candidates_pos 数组中 /* remember candidates of lwlock deadlock */ candidates_pos[tmp_num++] = i; } } /* remember the number of candidates */ - *out_num = tmp_num; + *out_num = tmp_num; // 赋值,表示候选线程的数量 return candidates_pos; } /* init entry if it's the first insert */ + +/* + * 功能:如果是第一次插入该条目则初始化锁条目 + * + * 参数: + * entry:指向 lock_entry 结构体的指针,表示要初始化的锁条目 + * entry_key:指向 LWLockAddr 结构体的指针,表示关联的锁地址 + * + * 返回值:无 + */ static inline void init_entry_in_the_first_insert(lock_entry* entry, LWLockAddr *entry_key) { - entry->lock_addr.lock = entry_key->lock; + entry->lock_addr.lock = entry_key->lock; // 将锁条目的锁地址设置为传入的锁地址 + // 分配内存来存储持有者和等待者线程的信息 entry->holders = (holding_lockmode*)palloc(sizeof(holding_lockmode) * DEFAULT_HOLDERS_NUM); entry->waiters = (lock_entry_id*)palloc(sizeof(lock_entry_id) * DEFAULT_HOLDERS_NUM); + // 表示当前没有持有者和等待者 entry->holders_curnum = entry->waiters_curnum = 0; entry->holders_maxnum = entry->waiters_maxnum = DEFAULT_HOLDERS_NUM; } @@ -148,24 +201,43 @@ static inline void init_entry_in_the_first_insert(lock_entry* entry, LWLockAddr * remember the holders of this lock. * now the holder holds the lockids whose number is n. */ + +/* + * 功能:将持有锁的线程映射到相应的锁条目 + * + * 参数: + * map:哈希表 + * held_lwlocks:表示持有的锁信息 + * n:表示持有的锁的数量 + * holder:表示持有锁的线程的ID + * + * 返回值:无 + */ static inline void map_from_lock_to_holder(HTAB* map, lwlock_id_mode* held_lwlocks, int n, lock_entry_id* holder) { lock_entry* entry = NULL; lwlock_id_mode* holding_lwlock = NULL; int i = 0; - bool found = false; - + bool found = false; // 用于指示是否在哈希表中找到相应的锁条目 + // 循环遍历持有的锁信息 for (i = 0, holding_lwlock = held_lwlocks; i < n; ++i, ++holding_lwlock) { + // 在哈希表中查找与锁地址相关的锁条目 entry = (lock_entry*)hash_search(map, &(holding_lwlock->lock_addr), HASH_ENTER, &found); + // 检查是否找到了锁条目 if (!found) { + // 如果没有找到,表示这是第一次插入该条目,初始化锁条目 init_entry_in_the_first_insert(entry, &(holding_lwlock->lock_addr)); } + // 检查当前持有者线程的数量是否超过了最大允许值 if (unlikely(entry->holders_curnum >= entry->holders_maxnum)) { - int new_size = entry->holders_maxnum * 2; + // 如果超过,表示需要扩展持有者数组 + int new_size = entry->holders_maxnum * 2; // 计算新的持有者数组的大小 + // 重新分配持有者数组的内存 entry->holders = (holding_lockmode*)repalloc(entry->holders, sizeof(holding_lockmode) * new_size); - entry->holders_maxnum = new_size; + entry->holders_maxnum = new_size; // 更新持有者数组的最大容量 } /* remember the holders of this lock */ + // 将持有锁的线程的信息添加到锁条目的持有者数组中 entry->holders[entry->holders_curnum].holder_tid.thread_id = holder->thread_id; entry->holders[entry->holders_curnum].holder_tid.st_sessionid = holder->st_sessionid; entry->holders[entry->holders_curnum].lock_sx = holding_lwlock->lock_sx; @@ -178,30 +250,46 @@ static inline void map_from_lock_to_holder(HTAB* map, lwlock_id_mode* held_lwloc * 1) lwlock id and its waiters; * 2) lwlock id and its holders; */ + +/* + * 功能:构建两个映射,一个从锁ID到等待者的映射,另一个从锁ID到持有者的映射 + * + * 参数: + * map:哈希表,用于将锁地址映射到锁条目。 + * candidates:一个 lwm_lwlocks 结构体的指针数组,表示候选的锁信息。 + * num_candidates:候选的锁的数量 + * + * 返回值:无 + */ static void build_holder_and_waiter_map(HTAB* map, lwm_lwlocks* candidates, int num_candidates) { lwm_lwlocks* lock = NULL; lock_entry* entry = NULL; bool found = false; - + // 遍历候选锁 lock = candidates; for (int i = 0; i < num_candidates; ++i, ++lock) { if (0 == lock->be_tid.thread_id) { /* skip this lock if thread id is not valid */ - continue; + continue; // 如果线程ID无效,则跳过该锁 } /* build map from lock address to waiters */ + // 构建从锁地址到等待者的映射 entry = (lock_entry*)hash_search(map, &(lock->want_lwlock), HASH_ENTER, &found); if (!found) { + // 如果没有找到,表示这是第一次插入该条目,初始化锁条目 init_entry_in_the_first_insert(entry, &(lock->want_lwlock)); } + // 检查当前等待者线程的数量是否超过了最大允许值 if (unlikely(entry->waiters_curnum >= entry->waiters_maxnum)) { + // 如果是则扩展等待者数组 int new_size = entry->waiters_maxnum * 2; entry->waiters = (lock_entry_id*)repalloc(entry->waiters, sizeof(lock_entry_id) * new_size); entry->waiters_maxnum = new_size; } /* remember all the waiters (thread id) about this lwlock */ + // 将所有等待者(线程ID)的信息添加到锁条目的等待者数组中 entry->waiters[entry->waiters_curnum].thread_id = lock->be_tid.thread_id; entry->waiters[entry->waiters_curnum].st_sessionid = lock->be_tid.st_sessionid; entry->waiters_curnum++; @@ -212,35 +300,60 @@ static void build_holder_and_waiter_map(HTAB* map, lwm_lwlocks* candidates, int } /* destroy lock hash table */ + +/* + * 功能:销毁锁哈希表,释放内存 + * + * 参数: + * lock_map:要销毁的哈希表 + * + * 返回值:无 + */ static void destroy_lock_hashtbl(HTAB* lock_map) { lock_entry* entry = NULL; HASH_SEQ_STATUS hseq_stat; - hash_seq_init(&hseq_stat, lock_map); + hash_seq_init(&hseq_stat, lock_map); // 初始化哈希表序列扫描器 + // 遍历哈希表中的每个锁条目 while ((entry = (lock_entry*)hash_seq_search(&hseq_stat)) != NULL) { + // 释放等待者和持有者数组的内存 pfree_ext(entry->waiters); pfree_ext(entry->holders); } + // 销毁哈希表 hash_destroy(lock_map); } /* build map between thread id and its acquired lwlock */ + +/* + * 功能:构建线程ID和其已获取的轻量级锁(lwlock)之间的映射 + * + * 参数: + * map:线程ID到锁ID的映射的哈希表 + * candidates:一个 lwm_lwlocks 结构体的指针数组,表示候选的锁信息。 + * num_candidates:候选的锁的数量 + * + * 返回值:无 + */ static void build_map_from_threadid_to_lockid(HTAB* map, lwm_lwlocks* candidates, int num_candidates) { thread_entry* entry = NULL; lwm_lwlocks* lock = NULL; bool found = false; - + // 初始化线程映射的哈希表序列扫描器 lock = candidates; for (int i = 0; i < num_candidates; ++i, ++lock) { if (0 == lock->be_tid.thread_id) { /* skip this lock if its thread id is not valid */ - continue; + continue; // 如果线程ID无效,则跳过该锁 } + // 在哈希表中查找与线程ID相关的线程条目,如果找不到,则创建一个新的 entry = (thread_entry*)hash_search(map, &(lock->be_tid), HASH_ENTER, &found); if (!found) { + // 如果没有找到,表示这是该线程的第一个锁,初始化线程条目 /* one thread acquires only one lwlock */ entry->want_lwlock = lock->want_lwlock; entry->be_idx = lock->be_idx; @@ -249,6 +362,7 @@ static void build_map_from_threadid_to_lockid(HTAB* map, lwm_lwlocks* candidates * see LWLockQueueSelf(). * one thread cannot once wait more than one lwlock. */ + // 一次只能等待一个 lwlock,如果找到了多个,则抛出断言错误 Assert(0); } } @@ -263,6 +377,21 @@ static void build_map_from_threadid_to_lockid(HTAB* map, lwm_lwlocks* candidates * deadlock [IN/OUT]: global infor about deadlock cycle * depth [IN]: recure depth */ + +/* + * 功能:递归地查找死锁循环 + * + * 参数: + * check_thread: 要检查的线程 + * lock_map: 锁与其等待者和持有者之间的映射关系 + * tid_map: 线程ID与所需lwlock之间的映射关系 + * visited: 关于已访问线程的全局信息 + * deadlock : 关于死锁循环的全局信息 + * depth: 递归深度 + * + * 返回值: + * bool类型,如果找到死锁循环,则返回 true;否则返回 false + */ static bool find_lock_cycle_recurse(thread_entry* check_thread, HTAB* lock_map, HTAB* tid_map, lwm_visited_thread* visited, lwm_deadlock* deadlock, int depth) { @@ -294,7 +423,7 @@ static bool find_lock_cycle_recurse(thread_entry* check_thread, HTAB* lock_map, visited->entry_ids[visited->cur_num].thread_id = check_threadid; visited->entry_ids[visited->cur_num].st_sessionid = check_sessionid; visited->cur_num++; - + // 在锁映射表中查找与该线程要求的锁相关的条目 entry = (lock_entry*)hash_search(lock_map, &(check_thread->want_lwlock), HASH_FIND, &found); AssertEreport(found && entry != NULL, MOD_ALL, "the wanted lock is not found in cache"); const int holder_num = entry->holders_curnum; @@ -334,6 +463,16 @@ static bool find_lock_cycle_recurse(thread_entry* check_thread, HTAB* lock_map, } /* find the start point for this deadlock loop */ + +/* + * 功能:查找死锁循环的起点 + * + * 参数: + * deadlock:关于死锁循环的全局信息 + * + * 返回值: + * bool类型,如果找到死锁循环的起点,则返回 true;否则返回 false + */ static bool find_cycle_start_point(lwm_deadlock* deadlock) { lw_deadlock_info* first = deadlock->info; @@ -358,6 +497,20 @@ static bool find_cycle_start_point(lwm_deadlock* deadlock) } /* enter point to find deadlock cycle */ + +/* + * 功能:查找死锁循环的入口点 + * + * 参数: + * lock:锁相关的线程条目。 + * lock_map:锁与其等待者和持有者之间的映射关系。 + * tid_map:线程ID与所需lwlock之间的映射关系。 + * visited:关于已访问线程的全局信息。 + * deadlock:关于死锁循环的全局信息。 + * + * 返回值: + * bool类型,如果找到死锁循环,则返回 true;否则返回 false + */ static bool find_lock_cycle( lwm_lwlocks* lock, HTAB* lock_map, HTAB* tid_map, lwm_visited_thread* visited, lwm_deadlock* deadlock) { @@ -368,33 +521,51 @@ static bool find_lock_cycle( deadlock->end = 0; bool found = false; + // 查找与给定锁的线程相关的条目 thread_entry* check_thread = (thread_entry*)hash_search(tid_map, &(lock->be_tid), HASH_FIND, &found); + // 如果找到相关的线程条目 if (found) { + // 查找死锁循环 if (find_lock_cycle_recurse(check_thread, lock_map, tid_map, visited, deadlock, 0)) { + // 如果找到死锁循环,查找死锁循环的起始点 + // 如果 find_cycle_start_point 返回 true + // 表示成功找到死锁循环的起始点,函数返回 true return find_cycle_start_point(deadlock); } } - return false; + return false; // 表示没有找到死锁循环 } /* * Report a detected deadlock, with available details. */ + +/* + * 功能:报告检测到的死锁 + * + * 参数: + * deadlock:关于死锁循环的全局信息 + * + * 返回值:无 + */ void lwm_deadlock_report(lwm_deadlock* deadlock) { StringInfoData clientbuf; /* errdetail for client */ StringInfoData logbuf; /* errdetail for server log */ int i = 0; - + // 初始化 initStringInfo(&clientbuf); initStringInfo(&logbuf); /* Generate the "waits for" lines sent to the client */ + // 循环处理死锁信息,生成发送给客户端的 "waits for" 行 for (i = deadlock->start; i < deadlock->end; ++i) { lw_deadlock_info* info = deadlock->info + i; - if (i > 0) { + if (i > 0) { // 检查当前循环是否不是第一次迭代 + // 如果不是第一次迭代,则在行之间添加换行符 appendStringInfoChar(&clientbuf, '\n'); } + // 向 clientbuf 添加文本信息,包括线程信息、会话信息、等待的锁信息等 appendStringInfo(&clientbuf, _("thread %lu , session %lu waits for LWLOCK (%s), but blocked by thread %lu, session id %lu\ lock mode is %d(Exclusive 0, Shared 1, wait 2). lock index is %d"), @@ -413,20 +584,20 @@ void lwm_deadlock_report(lwm_deadlock* deadlock) /* ... and add info about query strings */ for (i = deadlock->start; i < deadlock->end; ++i) { lw_deadlock_info* info = deadlock->info + i; - + // 向 logbuf 添加文本信息,包括线程信息和当前活动的查询信息 appendStringInfoChar(&logbuf, '\n'); appendStringInfo(&logbuf, _("thread %lu: %s"), info->waiter.thread_id, pgstat_get_backend_current_activity(info->waiter.thread_id, false)); } - + // 生成一个日志报告,报告了检测到的死锁情况 ereport(LOG, (errcode(ERRCODE_T_R_DEADLOCK_DETECTED), errmsg("lwlock deadlock detected"), errdetail_internal("%s", clientbuf.data), errdetail_log("%s", logbuf.data))); - + // 释放了之前分配的内存,以防止内存泄漏 pfree_ext(clientbuf.data); pfree_ext(logbuf.data); } @@ -437,6 +608,17 @@ void lwm_deadlock_report(lwm_deadlock* deadlock) * candidates [IN]: candidate threads who may be in deadlock loop. * num_candidates [IN]: number of candidates */ +/* + * 功能:检测和报告轻量级锁(lwlock)的死锁情况 + * + * 参数: + * deadlock:关于死锁循环的全局信息 + * candidates:候选线程数组 + * num_candidates:候选线程的数量 + * + * 返回值: + * bool类型,返回是否找到死锁 + */ bool lwm_heavy_diagnosis(lwm_deadlock* deadlock, lwm_lwlocks* candidates, int num_candidates) { HASHCTL hctl; @@ -446,20 +628,22 @@ bool lwm_heavy_diagnosis(lwm_deadlock* deadlock, lwm_lwlocks* candidates, int nu hctl.entrysize = sizeof(lock_entry); /* LWLockAddr is not suitable for Oid type, so use tab_hash */ hctl.hash = tag_hash; - + // 创建哈希表,用于存储锁的持有者和等待者信息 HTAB* lock_map = hash_create("LWLOCK holder and waiter", 1024, /* default node number */ &hctl, HASH_ELEM | HASH_FUNCTION); /* build maps about holders and acquirers */ + // 将锁的持有者和等待者信息添加到lock_map哈希表中 build_holder_and_waiter_map(lock_map, candidates, num_candidates); - + // 清零hctl结构体 ret = memset_s(&hctl, sizeof(HASHCTL), 0, sizeof(HASHCTL)); securec_check(ret, "\0", "\0"); + // 设置hctl结构体的字段,用于创建另一个哈希表 hctl.keysize = sizeof(lock_entry_id); hctl.entrysize = sizeof(thread_entry); hctl.hash = tag_hash; - + // 创建哈希表,用于建立线程ID和所需锁之间的映射关系 HTAB* thread_map = hash_create("thread and want lock", 1024, /* default node number */ &hctl, @@ -467,11 +651,11 @@ bool lwm_heavy_diagnosis(lwm_deadlock* deadlock, lwm_lwlocks* candidates, int nu /* build maps between thread id and lock to require */ build_map_from_threadid_to_lockid(thread_map, candidates, num_candidates); - lwm_visited_thread visited; + lwm_visited_thread visited; // 用于跟踪已访问的线程 visited.cur_num = 0; visited.max_num = 8; visited.entry_ids = (lock_entry_id*)palloc(sizeof(lock_entry_id) * visited.max_num); - + // 设置字段,用于存储死锁详情 deadlock->start = -1; deadlock->end = 0; deadlock->max_num = 8; @@ -479,18 +663,23 @@ bool lwm_heavy_diagnosis(lwm_deadlock* deadlock, lwm_lwlocks* candidates, int nu /* check and find deadlock cycle */ bool found = false; + // 循环遍历所有候选线程 for (int i = 0; i < num_candidates; ++i) { + // 检测是否存在死锁 if (find_lock_cycle(candidates + i, lock_map, thread_map, &visited, deadlock)) { /* print deadlock detail message */ + // 如果发现死锁,打印死锁的详细信息 lwm_deadlock_report(deadlock); found = true; break; } } - + // 释放分配的内存 pfree_ext(visited.entry_ids); + // 销毁哈希表 destroy_lock_hashtbl(lock_map); lock_map = NULL; + // 销毁哈希表 hash_destroy(thread_map); thread_map = NULL; return found; @@ -501,39 +690,57 @@ bool lwm_heavy_diagnosis(lwm_deadlock* deadlock, lwm_lwlocks* candidates, int nu * deadlock [IN]: deadlock details * out_idx [OUT]: backend position of this victim */ +/* + * 功能:从一组死锁信息中选择一个victim(线程)来解除死锁 + * 会选择具有以下特征的线程: + * 具有最接近的事务开始时间戳(nearest_xact_tm) + * 如果有多个线程具有相同的事务开始时间戳,将选择没有独占锁等待的线程 + * + * 参数: + * deadlock:关于死锁循环的全局信息 + * out_idx:victim在死锁信息数组中的位置 + * + * 返回值: + * int类型,代表选择的受害者线程的后端位置或者辅助进程类型 + * 如果返回值大于等于 0,表示选择的受害者是一个后端线程,并且返回值是该后端线程的后端位置 + * 如果返回值小于 0,表示选择的受害者是一个辅助进程,并且返回值通常是一个负数,表示辅助进程的类型 + */ static int choose_one_victim(lwm_deadlock* deadlock, int* out_idx) { + // 断言确保 deadlock 的开始位置在有效范围内 Assert(deadlock->start >= 0 && deadlock->start < deadlock->end); - TimestampTz nearest_xact_tm = 0; - TimestampTz tmp_xact_tm = 0; - int backend_index = -1; - int auxproc_index = NotAnAuxProcess; + TimestampTz nearest_xact_tm = 0; // 用于记录最接近的事务时间戳 + TimestampTz tmp_xact_tm = 0; // 用于暂存事务时间戳 + int backend_index = -1; // 记录被选中的后端线程索引 + int auxproc_index = NotAnAuxProcess; // 记录辅助进程线程索引 - int i = deadlock->start; - int be_index = deadlock->info[i].waiter_index; + int i = deadlock->start; // 从 deadlock 开始位置开始 + int be_index = deadlock->info[i].waiter_index; // 获取第一个等待者的线程索引 /* init the victim */ if (be_index < MAX_BACKEND_SLOT) { backend_index = be_index; + // 获取该线程的事务开始时间戳 nearest_xact_tm = pgstat_read_xact_start_tm(be_index); } else { - auxproc_index = be_index; + auxproc_index = be_index; // 如果是辅助进程线程,则记录索引 } /* init the position of the first victim */ - *out_idx = i; + *out_idx = i; // 初始化第一个受害者的位置 /* begin from (deadlock->start + 1) */ for (++i; i < deadlock->end; ++i) { - be_index = deadlock->info[i].waiter_index; + be_index = deadlock->info[i].waiter_index; // 获取下一个等待者的线程索引 if (be_index < MAX_BACKEND_SLOT) { - tmp_xact_tm = pgstat_read_xact_start_tm(be_index); + tmp_xact_tm = pgstat_read_xact_start_tm(be_index); // 获取该线程的事务开始时间戳 /* prefer the shortest exclusive lock run time,equal condition used to test case for xact tz is 0 */ if (timestamptz_cmp_internal(tmp_xact_tm, nearest_xact_tm) >= 0 && 0 == deadlock->info[i].block_xmode) { nearest_xact_tm = tmp_xact_tm; backend_index = be_index; - *out_idx = i; + *out_idx = i; // 更新受害者位置的输出参数 #ifdef HAVE_INT64_TIMESTAMP + // 记录日志,说明选择了一个受害者 ereport(LOG, (errmsg("choose_one_victim,lock_mode is %d,tran time is %lu,out index is %d,backend index is %d", deadlock->info[i].block_xmode, @@ -563,6 +770,15 @@ static int choose_one_victim(lwm_deadlock* deadlock, int* out_idx) return ((backend_index >= 0) ? backend_index : ((auxproc_index >= MAX_BACKEND_SLOT) ? auxproc_index : -1)); } +/* + * 功能:在检测到轻量级锁死锁时自动尝试解除死锁 + * + * 参数: + * deadlock:关于死锁循环的全局信息 + * + * 返回值: + * 无 + */ void lw_deadlock_auto_healing(lwm_deadlock* deadlock) { /* choose one thread to be victim */ @@ -571,21 +787,32 @@ void lw_deadlock_auto_healing(lwm_deadlock* deadlock) if (backend_victim >= 0) { if (backend_victim >= MAX_BACKEND_SLOT) { + // 如果受害者是辅助线程,则触发 PANIC,并终止进程 ereport(PANIC, (errmsg("process suicides because the victim of lwlock deadlock is an auxiliary thread"))); return; } /* wake up this victim */ lw_deadlock_info* info = deadlock->info + info_idx; + // 唤醒受害者线程,以解除死锁 wakeup_victim(info->lock, info->waiter.thread_id); } else { /* LOG, maybe deadlock disappear */ + // 如果没有找到受害者,可能是死锁已经解除,记录日志信息 ereport(LOG, (errmsg("victim not found, maybe lwlock deadlock disappear"))); } } +/* + * 功能:监视锁等待情况并执行相应的操作 + * + * 参数:无 + * + * 返回值: + * 无 + */ NON_EXEC_STATIC void FaultMonitorMain() { - sigjmp_buf localSigjmpBuf; + sigjmp_buf localSigjmpBuf; // 用于处理异常跳转 MemoryContext lwm_context = NULL; lwm_light_detect* prev_snapshot = NULL; @@ -593,12 +820,12 @@ NON_EXEC_STATIC void FaultMonitorMain() long cur_timeout = 0; /* we are a postmaster subprocess now */ - IsUnderPostmaster = true; + IsUnderPostmaster = true; // 表示当前进程是在 postmaster 进程下运行的 - t_thrd.proc_cxt.MyProcPid = gs_thread_self(); + t_thrd.proc_cxt.MyProcPid = gs_thread_self(); // 正确标识线程 ereport(DEBUG5, (errmsg("lwlockmonitor process is started: %lu", t_thrd.proc_cxt.MyProcPid))); - + // 设置信号处理程序 (void)gspqsignal(SIGHUP, LWLockMonitorSigHupHandler); /* set flag to read config file */ (void)gspqsignal(SIGINT, LWLockMonitorShutdownHandler); /* request shutdown */ (void)gspqsignal(SIGTERM, LWLockMonitorShutdownHandler); /* request shutdown */ @@ -624,48 +851,53 @@ NON_EXEC_STATIC void FaultMonitorMain() * possible memory leaks. Formerly this code just ran in * t_thrd.top_mem_cxt, but resetting that would be a really bad idea. */ + // 创建一个内存上下文 lwm_context = AllocSetContextCreate(t_thrd.top_mem_cxt, "LWLock Monitor", ALLOCSET_DEFAULT_MINSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); - (void)MemoryContextSwitchTo(lwm_context); + (void)MemoryContextSwitchTo(lwm_context); // 切换到该上下文 #ifdef ENABLE_UT /* unit testcase */ ut_test_find_deadlock_cycle(); #endif /* ENABLE_UT */ - + // 处理异常情况 int curTryCounter; int* oldTryCounter = NULL; if (sigsetjmp(localSigjmpBuf, 1) != 0) { + // 关闭所有打开的文件 gstrace_tryblock_exit(true, oldTryCounter); /* Since not using PG_TRY, must reset error stack by hand */ - t_thrd.log_cxt.error_context_stack = NULL; + t_thrd.log_cxt.error_context_stack = NULL; // 手动重置错误堆栈 - t_thrd.log_cxt.call_stack = NULL; + t_thrd.log_cxt.call_stack = NULL; // 清空调用栈 /* Prevent interrupts while cleaning up */ - HOLD_INTERRUPTS(); + HOLD_INTERRUPTS(); // 在执行清理操作期间禁止中断 /* Report the error to the server log */ - EmitErrorReport(); + EmitErrorReport(); // 报告错误到服务器日志 /* release resource held by lsc */ - AtEOXact_SysDBCache(false); + AtEOXact_SysDBCache(false); // 释放由系统数据库缓存持有的资源 /* * Now return to normal top-level context and clear ErrorContext for * next time. */ - (void)MemoryContextSwitchTo(lwm_context); + (void)MemoryContextSwitchTo(lwm_context); // 切换程序的内存上下文 + // 清除错误状态,将之前可能记录的错误信息和状态重置为空 FlushErrorState(); /* Flush any leaked data in the top-level context */ + // 重置并删除了之前创建的内存上下文 lwm_context 及其所有子上下文 + // 确保在异常处理完成后释放已分配的内存,以避免内存泄漏 MemoryContextResetAndDeleteChildren(lwm_context); /* Now we can allow interrupts again */ - RESUME_INTERRUPTS(); + RESUME_INTERRUPTS(); // 允许中断信号再次被捕获 /* * Sleep at least 1 second after any error. A write error is likely @@ -674,20 +906,27 @@ NON_EXEC_STATIC void FaultMonitorMain() */ pg_usleep(1000000L); } + // 记录当前的错误处理堆栈计数器 oldTryCounter = gstrace_tryblock_entry(&curTryCounter); /* We can now handle ereport(ERROR) */ + // 将错误处理的跳转缓冲区设置为当前线程的错误处理堆栈 t_thrd.log_cxt.PG_exception_stack = &localSigjmpBuf; /* Unblock signals (they were blocked when the postmaster forked us) */ + // 解除信号屏蔽,允许信号再次被捕获 gs_signal_setmask(&t_thrd.libpq_cxt.UnBlockSig, NULL); + // 解除对 SIGUSR2 信号的屏蔽,允许接收 SIGUSR2 信号 (void)gs_signal_unblock_sigusr2(); - + // 标识监视器进程应用程序名称 pgstat_report_appname("LWLock Monitor"); + // 设置当前监视器进程的活动状态为 "IDLE",表示监视器当前没有处理任何活动 pgstat_report_activity(STATE_IDLE, NULL); /* set current monitor timeout */ + // 设置当前监视器的超时时间,决定监视器多久执行一次锁状态的检查 cur_timeout = (long)u_sess->attr.attr_common.fault_mon_timeout * 60 * 1000; + // 表示目前还没有可用的快照数据 prev_snapshot = NULL; curr_snapshot = NULL; @@ -700,17 +939,18 @@ NON_EXEC_STATIC void FaultMonitorMain() /* Process any requests or signals received recently. */ if (t_thrd.lwm_cxt.got_SIGHUP) { t_thrd.lwm_cxt.got_SIGHUP = false; - ProcessConfigFile(PGC_SIGHUP); - + ProcessConfigFile(PGC_SIGHUP); // 处理配置文件的重新加载 + long newTimeout = (long)u_sess->attr.attr_common.fault_mon_timeout * 60 * 1000; - + // 如果新的超时时间与当前的超时时间不同 if (newTimeout != cur_timeout) { /* for lwlock debug info */ + // 将 LWLock 的调试信息写入服务器日志 DumpLWLockInfoToServerLog(); } /* update monitor timeout */ - cur_timeout = newTimeout; + cur_timeout = newTimeout; // 更新监视器的超时时间 } if (t_thrd.lwm_cxt.shutdown_requested) { @@ -720,22 +960,30 @@ NON_EXEC_STATIC void FaultMonitorMain() /* disable this feature if set u_sess->attr.attr_common.fault_mon_timeout be 0 */ if (u_sess->attr.attr_common.fault_mon_timeout > 0) { + // 如果表示启用了监视器功能 /* start to do main work */ - if (NULL != prev_snapshot) { - lwm_deadlock deadlock = {NULL, 0, 0, 0}; - bool continue_next = false; + if (NULL != prev_snapshot) { // 如果为空则表示首次运行监视器 + lwm_deadlock deadlock = {NULL, 0, 0, 0}; // 用于保存检测到的死锁信息 + bool continue_next = false; // 用于判断是否需要继续下一步的监视工作 /* phase 1: light-weight detect using fast changcount */ + // 获取当前的轻量级快照数据 curr_snapshot = pgstat_read_light_detect(); + // 比较前后两个快照数据,判断是否需要继续进行监视工作 + // 如果发现锁状态变化,将continue_next设为true,表示需要继续 continue_next = lwm_compare_light_detect(prev_snapshot, curr_snapshot); + // 如果 continue_next 为 true,表示锁状态发生变化,继续进行监视工作 if (continue_next) { /* phase 2 if needed: heavy-weight diagnosis for lwlock deadlock */ int candidates_num = 0; + // 查找可能进入死锁状态的候选锁 int* candidates_pos = lwm_find_candidates(prev_snapshot, curr_snapshot, &candidates_num); + // 读取用于诊断的数据,包括当前快照数据、候选锁的位置和数量 lwm_lwlocks* backend_locks = pgstat_read_diagnosis_data(curr_snapshot, candidates_pos, candidates_num); pfree_ext(candidates_pos); + // 诊断操作,并将结果保存在 deadlock 变量中 continue_next = lwm_heavy_diagnosis(&deadlock, backend_locks, candidates_num); /* clean up. */ @@ -748,27 +996,30 @@ NON_EXEC_STATIC void FaultMonitorMain() if (continue_next) { /* phase 3 if needed: auto healing for lwlock deadlock */ + // 尝试自动修复,解除死锁 lw_deadlock_auto_healing(&deadlock); } /* prepare for next monitor, and keep the current snapshot */ if (NULL != deadlock.info) { - pfree_ext(deadlock.info); + pfree_ext(deadlock.info); // 释放内存 } pfree_ext(prev_snapshot); - prev_snapshot = curr_snapshot; + prev_snapshot = curr_snapshot; // 更新快照数据 curr_snapshot = NULL; } else { + // 如果 prev_snapshot 为空,表示首次运行监视器 /* the first time to get snapshot */ - prev_snapshot = pgstat_read_light_detect(); + prev_snapshot = pgstat_read_light_detect(); // 获取当前的轻量级快照数据 curr_snapshot = NULL; } } else { /* just set a default timeout: 10min */ cur_timeout = 10 * 60 * 1000; } - + // 设置当前监视器进程的活动状态为 "IDLE",表示监视器当前没有处理任何活动 pgstat_report_activity(STATE_IDLE, NULL); + // 等待事件的发生 rc = WaitLatch(&t_thrd.proc->procLatch, WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH, cur_timeout); /* @@ -782,59 +1033,110 @@ NON_EXEC_STATIC void FaultMonitorMain() } /* SIGHUP: set flag to re-read config file at next convenient time */ +/* + *功能:处理 SIGHUP 信号,用于重新读取配置文件 + * + * 参数: + * SIGNAL_ARGS: 信号处理程序的参数列表,提供关于触发信号的上下文信息 + * + * 返回值: + * 无 + */ static void LWLockMonitorSigHupHandler(SIGNAL_ARGS) { - int save_errno = errno; + int save_errno = errno; // 保存当前的错误码 - t_thrd.lwm_cxt.got_SIGHUP = true; + t_thrd.lwm_cxt.got_SIGHUP = true; // 表示接收到 SIGHUP 信号 if (t_thrd.proc) - SetLatch(&t_thrd.proc->procLatch); + SetLatch(&t_thrd.proc->procLatch); // 设置该线程的latch - errno = save_errno; + errno = save_errno; // 恢复之前保存的错误码 } /* SIGTERM: set flag to exit normally */ +/* + *功能:处理 SIGTERM 信号,用于正常退出 + * + * 参数: + * SIGNAL_ARGS: 信号处理程序的参数列表,提供关于触发信号的上下文信息 + * + * 返回值: + * 无 + */ static void LWLockMonitorShutdownHandler(SIGNAL_ARGS) { - int save_errno = errno; + int save_errno = errno; // 保存当前的错误码 - t_thrd.lwm_cxt.shutdown_requested = true; + t_thrd.lwm_cxt.shutdown_requested = true; // 表示请求正常退出 - if (t_thrd.proc) - SetLatch(&t_thrd.proc->procLatch); + if (t_thrd.proc) // 线程存在 + SetLatch(&t_thrd.proc->procLatch); // 设置该线程的latch - errno = save_errno; + errno = save_errno; // 恢复之前保存的错误码 } #ifdef ENABLE_UT +/* + * 功能:释放测试中分配的内存 + * + * 参数: + * locks_array:用于存储测试用例中的锁信息 + * n_holders:用于存储锁的数量 + * + * 返回值: + * 无 + */ static void ut_free_locks(lwm_lwlocks* locks_array, int n_holders) { - for (int i = 0; i < n_holders; ++i) { + for (int i = 0; i < n_holders; ++i) { // 遍历释放内存 pfree_ext(locks_array[i].held_lwlocks); } - pfree_ext(locks_array); + pfree_ext(locks_array); // 释放内存 } +/* + * 功能:测试用例,用于模拟一个简单的死锁情况,以测试锁死锁检测和解锁功能 + * + * 参数: + * locks_array:要释放内存的锁信息数组 + * n_holders:要释放的锁信息数组的长度 + * + * 返回值: + * 无 + */ static void ut_test_deadlock00(lwm_lwlocks* locks, int* nlocks) { - *nlocks = 1; + *nlocks = 1; // 表示测试中有一个锁 /* holding lwlock 1 and acquiring lwlock 1 */ - locks->be_idx = 1; - locks->be_tid.thread_id = 1; - locks->want_lwlock.lock = GetMainLWLockByIndex(1); - locks->lwlocks_num = 1; + // 设置信息 + locks->be_idx = 1; // 线程索引 + locks->be_tid.thread_id = 1; // 线程的唯一标识符 + locks->want_lwlock.lock = GetMainLWLockByIndex(1); // 获取了编号为 1 的主要轻量级锁 + locks->lwlocks_num = 1; // 持有的锁的数量 + // 分配内存,用于存储持有的锁的信息 locks->held_lwlocks = (lwlock_id_mode*)palloc(sizeof(lwlock_id_mode)); + // 设置持有的锁的信息 locks->held_lwlocks[0].lock_addr.lock = GetMainLWLockByIndex(1); locks->held_lwlocks[0].lock_sx = (LWLockMode)0; } +/* + * 功能:模拟一个锁死情况,其中有两个线程尝试获取不同的两个锁,并且它们都持有一个锁,同时尝试获取另一个 + * + * 参数: + * nlocks:用于返回锁的数量 + * + * 返回值: + * lwm_lwlocks* 表示锁的状态 + */ static lwm_lwlocks* ut_test_deadlock01(int* nlocks) { - int nholders = 1; - *nlocks = 2; + int nholders = 1; // 表示每个线程持有一个锁 + *nlocks = 2; // 表示锁的总数量 + // 分配内存以存储锁信息的数组 lwm_lwlocks* locks_array = (lwm_lwlocks*)palloc(sizeof(lwm_lwlocks) * (*nlocks)); /* holding lwlock 2, and acquiring lwlock 1 */ @@ -858,6 +1160,15 @@ static lwm_lwlocks* ut_test_deadlock01(int* nlocks) return locks_array; } +/* + * 功能:模拟一个较复杂的锁死情况,其中有五个线程尝试获取不同的五个锁,并且它们之间形成一个环路 + * + * 参数: + * nlocks:用于返回锁的数量 + * + * 返回值: + * lwm_lwlocks* 表示锁的状态 + */ static lwm_lwlocks* ut_test_deadlock02(int* nlocks) { *nlocks = 5; @@ -918,19 +1229,29 @@ static lwm_lwlocks* ut_test_deadlock02(int* nlocks) #define held_lwlocks_1d(_x) locks_array[_x].held_lwlocks #define held_lwlocks_2d(_x, _y) locks_array[_x].held_lwlocks[_y] - +/* + * 功能:模拟一个更复杂的锁死情况,其中有五个线程尝试获取不同的五个锁,它们之间形成复杂的依赖关系 + * + * 参数: + * nlocks:用于返回锁的数量 + * + * 返回值: + * lwm_lwlocks* 表示锁的状态 + */ static lwm_lwlocks* ut_test_deadlock03(int* nlocks) { - *nlocks = 5; - int nholders = 5; + *nlocks = 5; // 表示锁的总数量 + int nholders = 5; // 表示每个线程持有五个锁 + // 分配内存以存储锁信息的数组 lwm_lwlocks* locks_array = (lwm_lwlocks*)palloc(sizeof(lwm_lwlocks) * (*nlocks)); - + // 初始化,每个元素表示一个线程的锁状态 held_lwlocks_1d(0) = (lwlock_id_mode*)palloc(sizeof(lwlock_id_mode) * (nholders)); held_lwlocks_1d(1) = (lwlock_id_mode*)palloc(sizeof(lwlock_id_mode) * (nholders)); held_lwlocks_1d(2) = (lwlock_id_mode*)palloc(sizeof(lwlock_id_mode) * (nholders)); held_lwlocks_1d(3) = (lwlock_id_mode*)palloc(sizeof(lwlock_id_mode) * (nholders)); held_lwlocks_1d(4) = (lwlock_id_mode*)palloc(sizeof(lwlock_id_mode) * (nholders)); + // 锁的状态设置成每个线程持有多个锁,并尝试获取其他锁,以创建复杂的依赖关系 locks_array[0].lwlocks_num = locks_array[1].lwlocks_num = locks_array[2].lwlocks_num = nholders; locks_array[3].lwlocks_num = locks_array[4].lwlocks_num = nholders; @@ -997,30 +1318,42 @@ typedef lwm_lwlocks* (*ut_test_deadlock)(int*); const ut_test_deadlock ut_testcases[] = {ut_test_deadlock01, ut_test_deadlock02, ut_test_deadlock03}; +/* + * 功能:测试锁死检测功能 + * + * 参数:无 + * + * 返回值:无 + */ static void ut_test_find_deadlock_cycle(void) { - int nlocks = 0; - bool has_cycle = false; + int nlocks = 0; // 用于存储锁的数量 + bool has_cycle = false; // 用于表示是否检测到死锁 - lwm_deadlock deadlock; - deadlock.info = NULL; + lwm_deadlock deadlock; // 用于存储死锁检测的相关信息 + deadlock.info = NULL; // 表示没有关于死锁的详细信息 - lwm_lwlocks locks; - locks.held_lwlocks = NULL; + lwm_lwlocks locks; // 用于存储锁的信息 + locks.held_lwlocks = NULL; // 表示当前没有线程持有任何锁 + // 模拟一个死锁情况,并将相关信息存储在 locks 中,同时更新 nlocks 的值以表示锁的数量 ut_test_deadlock00(&locks, &nlocks); + // 检测死锁情况 has_cycle = lwm_heavy_diagnosis(&deadlock, &locks, nlocks); - Assert(has_cycle); - pfree_ext(locks.held_lwlocks); - + Assert(has_cycle); // 断言检测死锁情况 + pfree_ext(locks.held_lwlocks); // 释放内存 + // 遍历测试用例数组 for (int i = 0; i < (int)(sizeof(ut_testcases) / sizeof(ut_testcases[0])); ++i) { + // 模拟不同的死锁情况 lwm_lwlocks* locks_array = ut_testcases[i](&nlocks); + // 检测当前测试用例下的死锁情况,并将结果存储在 has_cycle 变量中 has_cycle = lwm_heavy_diagnosis(&deadlock, locks_array, nlocks); + // 以确保检测到了死锁情况 Assert(has_cycle); - lw_deadlock_auto_healing(&deadlock); - ut_free_locks(locks_array, nlocks); + lw_deadlock_auto_healing(&deadlock); // 释放当前测试用例中分配的内存 + ut_free_locks(locks_array, nlocks); // 释放内存 } - pfree_ext(deadlock.info); + pfree_ext(deadlock.info); // 释放内存,清理死锁检测的相关信息 } #endif /* ENABLE_UT */ diff --git a/src/gausskernel/process/postmaster/pgarch.cpp b/src/gausskernel/process/postmaster/pgarch.cpp index 1926735fc..e3221ca6a 100755 --- a/src/gausskernel/process/postmaster/pgarch.cpp +++ b/src/gausskernel/process/postmaster/pgarch.cpp @@ -118,15 +118,31 @@ static void pgarch_ArchiverObsCopyLoop(XLogRecPtr flushPtr, doArchive fun); static void InitArchiverLastTaskLsn(ArchiveSlotConfig* obs_archive_slot); #endif +/* + * 功能:检查数据实例归档是否正常,用于告警处理 + * + * 参数: + * alarm:指向告警信息的指针,用于记录告警信息 + * additionalParam:指向告警附加参数的指针,用于填充告警附加信息 + * + * 返回值: + * AlarmCheckResult 枚举类型,表示归档检查结果 + * 如果 g_instance.WalSegmentArchSucceed 为 true,表示归档成功,返回 ALM_ACR_Normal + * 如果 g_instance.WalSegmentArchSucceed 为 false,表示归档异常,返回 ALM_ACR_Abnormal + */ AlarmCheckResult DataInstArchChecker(Alarm* alarm, AlarmAdditionalParam* additionalParam) { + // 检查全局归档状态变量 if (true == g_instance.WalSegmentArchSucceed) { // fill the resume message + // 如果归档成功,填写正常恢复消息 WriteAlarmAdditionalInfo( additionalParam, g_instance.attr.attr_common.PGXCNodeName, "", "", alarm, ALM_AT_Resume); + // 返回归档检查结果为正常 return ALM_ACR_Normal; } else { // fill the alarm message + // 如果归档异常,填写异常告警消息 WriteAlarmAdditionalInfo(additionalParam, g_instance.attr.attr_common.PGXCNodeName, "", @@ -134,17 +150,27 @@ AlarmCheckResult DataInstArchChecker(Alarm* alarm, AlarmAdditionalParam* additio alarm, ALM_AT_Fault, g_instance.attr.attr_common.PGXCNodeName); + // 返回归档检查结果为异常 return ALM_ACR_Abnormal; } } +/* + * 功能:设置观察者归档器的阻塞锁(Latch) + * + * 参数: + * latch:指向要设置的阻塞锁的指针 + * + * 返回值:无 + */ void setObsArchLatch(const Latch* latch) { /* use volatile pointer to prevent code rearrangement */ + // 由于这个函数可能在不同的线程中执行,所以使用了 volatile 指针以防止代码重排 volatile WalRcvData *walrcv = t_thrd.walreceiverfuncs_cxt.WalRcv; - SpinLockAcquire(&walrcv->mutex); - walrcv->obsArchLatch = (Latch *)latch; - SpinLockRelease(&walrcv->mutex); + SpinLockAcquire(&walrcv->mutex); // 获取 WalRcvData 结构体的互斥锁 + walrcv->obsArchLatch = (Latch *)latch; // 设置观察者归档器的阻塞锁为传入的阻塞锁 + SpinLockRelease(&walrcv->mutex); // 释放互斥锁 } /* ------------------------------------------------------------ * Public functions called from postmaster follow @@ -194,6 +220,14 @@ ThreadId pgarch_start() * The argc/argv parameters are valid only in EXEC_BACKEND case. However, * since we don't use 'em, it hardly matters... */ +/* + * 功能:归档程序主函数,归档程序的入口点 + * + * 参数: + * arg:线程参数 + * + * 返回值:无 + */ NON_EXEC_STATIC void PgArchiverMain(knl_thread_arg* arg) { IsUnderPostmaster = true; /* we are a postmaster subprocess now */ @@ -215,7 +249,7 @@ NON_EXEC_STATIC void PgArchiverMain(knl_thread_arg* arg) * Ignore all signals usually bound to some action in the postmaster, * except for SIGHUP, SIGTERM, SIGUSR1, SIGUSR2, and SIGQUIT. */ - + // 信号处理函数 (void)gspqsignal(SIGHUP, ArchSigHupHandler); (void)gspqsignal(SIGINT, SIG_IGN); (void)gspqsignal(SIGTERM, ArchSigTermHandler); @@ -237,15 +271,15 @@ NON_EXEC_STATIC void PgArchiverMain(knl_thread_arg* arg) /* * Identify myself via ps */ - init_ps_display("archiver process", "", "", ""); - setObsArchLatch(&t_thrd.arch.mainloop_latch); + init_ps_display("archiver process", "", "", ""); /* 初始化 ps 显示,标识归档程序 */ + setObsArchLatch(&t_thrd.arch.mainloop_latch); /* 设置观察者归档器的主循环阻塞锁 */ #ifndef ENABLE_LITE_MODE - InitArchiverLastTaskLsn(NULL); + InitArchiverLastTaskLsn(NULL); /* 初始化归档程序的最后任务 LSN(逻辑序列号) */ #endif - pgarch_MainLoop(); + pgarch_MainLoop(); /* 进入归档程序的主循环 */ - gs_thread_exit(0); + gs_thread_exit(0); /* 退出线程,返回值为 0 */ } /* SIGQUIT signal handler for archiver process */ @@ -256,21 +290,37 @@ static void pgarch_exit(SIGNAL_ARGS) } /* SIGHUP signal handler for archiver process */ +/* + * 功能:SIGHUP 信号处理函数,用于处理配置文件重读信号 + * + * 参数: + * SIGNAL_ARGS:信号处理函数的参数宏 + * + * 返回值:无 + */ static void ArchSigHupHandler(SIGNAL_ARGS) { - int save_errno = errno; + int save_errno = errno; /* 保存当前的错误码 */ /* set flag to re-read config file at next convenient time */ - t_thrd.arch.got_SIGHUP = true; - SetLatch(&t_thrd.arch.mainloop_latch); + t_thrd.arch.got_SIGHUP = true; /* 设置 SIGHUP 信号标志为真 */ + SetLatch(&t_thrd.arch.mainloop_latch); /* 设置主循环的阻塞锁以唤醒主循环 */ - errno = save_errno; + errno = save_errno; /* 恢复之前保存的错误码 */ } /* SIGTERM signal handler for archiver process */ +/* + * 功能:SIGTERM 信号处理函数,用于处理终止信号 + * + * 参数: + * SIGNAL_ARGS:信号处理函数的参数宏 + * + * 返回值:无 + */ static void ArchSigTermHandler(SIGNAL_ARGS) { - int save_errno = errno; + int save_errno = errno; /* 保存当前的错误码 */ /* * The postmaster never sends us SIGTERM, so we assume that this means @@ -278,45 +328,70 @@ static void ArchSigTermHandler(SIGNAL_ARGS) * too long we'll get SIGKILL'd. Set flag to prevent starting any more * archive commands. */ - t_thrd.arch.got_SIGTERM = true; - SetLatch(&t_thrd.arch.mainloop_latch); + t_thrd.arch.got_SIGTERM = true; /* 设置 SIGTERM 信号标志为真 */ + SetLatch(&t_thrd.arch.mainloop_latch); /* 设置主循环的阻塞锁以唤醒主循环 */ - errno = save_errno; + errno = save_errno; /* 恢复之前保存的错误码 */ } /* SIGUSR1 signal handler for archiver process */ +/* + * 功能:SIGUSR1 信号处理函数,用于唤醒归档程序 + * + * 参数: + * SIGNAL_ARGS:信号处理函数的参数宏 + * + * 返回值:无 + */ static void pgarch_waken(SIGNAL_ARGS) { - int save_errno = errno; + int save_errno = errno; /* 保存当前的错误码 */ /* set flag that there is work to be done */ - t_thrd.arch.wakened = true; - SetLatch(&t_thrd.arch.mainloop_latch); - errno = save_errno; + t_thrd.arch.wakened = true; /* 设置唤醒标志为真 */ + SetLatch(&t_thrd.arch.mainloop_latch); /* 设置主循环的阻塞锁以唤醒主循环 */ + errno = save_errno; /* 恢复之前保存的错误码 */ } /* SIGUSR2 signal handler for archiver process */ +/* + * 功能:SIGUSR2 信号处理函数,用于通知归档程序准备停止 + * + * 参数: + * SIGNAL_ARGS:信号处理函数的参数宏 + * + * 返回值:无 + */ static void pgarch_waken_stop(SIGNAL_ARGS) { - int save_errno = errno; + int save_errno = errno; /* 保存当前的错误码 */ /* set flag to do a final cycle and shut down afterwards */ - t_thrd.arch.ready_to_stop = true; - SetLatch(&t_thrd.arch.mainloop_latch); + t_thrd.arch.ready_to_stop = true; /* 设置准备停止标志为真 */ + SetLatch(&t_thrd.arch.mainloop_latch); /* 设置主循环的阻塞锁以唤醒主循环 */ - errno = save_errno; + errno = save_errno; /* 恢复之前保存的错误码 */ } - +/* + * 功能:验证目标目录是否为空或创建目录 + * + * 参数: + * dirname:要验证的目录名 + * + * 返回值:无 + */ static void VerifyDestDirIsEmptyOrCreate(char* dirname) { switch (pg_check_dir(dirname)) { case 0: /* Does not exist, so create */ + // 目录不存在,创建目录,并赋予所有者读写执行权限 if (pg_mkdir_p(dirname, S_IRWXU) == -1) { ereport(FATAL, (errmsg_internal("could not create directory \"%s\": %s\n", dirname, strerror(errno)))); } case -1: /* Access problem */ + // 目录无法访问,报告致命错误 ereport(FATAL, (errmsg_internal("could not access directory \"%s\": %s\n", dirname, strerror(errno)))); default: /* Nothing */ break; @@ -330,11 +405,18 @@ static void VerifyDestDirIsEmptyOrCreate(char* dirname) * * Main loop for archiver */ +/* + * 功能:归档程序的主循环,执行归档任务 + * + * 参数:无 + * + * 返回值:无 + */ static void pgarch_MainLoop(void) { struct timeval last_copy_time; - gettimeofday(&last_copy_time, NULL); - bool time_to_stop = false; + gettimeofday(&last_copy_time, NULL); /* 获取当前时间,用于记录上一次复制的时间点 */ + bool time_to_stop = false; /* 标志是否需要停止归档 */ #ifndef ENABLE_LITE_MODE doArchive fun = NULL; const int millitosec = 1000; @@ -346,7 +428,7 @@ static void pgarch_MainLoop(void) * archiver died unexpectedly). After that we wait for a signal or * timeout before doing more. */ - t_thrd.arch.wakened = true; + t_thrd.arch.wakened = true; /* 唤醒标志设为真,表示需要唤醒归档操作 */ ArchiveSlotConfig *obs_archive_slot = NULL; if (XLogArchiveDestSet()) { @@ -359,18 +441,18 @@ static void pgarch_MainLoop(void) * wakes up occasionally to allow herself to be proactive. */ do { - ResetLatch(&t_thrd.arch.mainloop_latch); + ResetLatch(&t_thrd.arch.mainloop_latch); /* 重置主循环的阻塞锁 */ struct timeval curtime; /* When we get SIGUSR2, we do one more archive cycle, then exit */ - time_to_stop = t_thrd.arch.ready_to_stop; + time_to_stop = t_thrd.arch.ready_to_stop; /* 获取停止标志 */ /* Check for config update */ if (t_thrd.arch.got_SIGHUP) { - t_thrd.arch.got_SIGHUP = false; - ProcessConfigFile(PGC_SIGHUP); + t_thrd.arch.got_SIGHUP = false; /* 重置 SIGHUP 信号标志 */ + ProcessConfigFile(PGC_SIGHUP); /* 处理配置文件重读信号 */ if (!XLogArchivingActive() && getArchiveReplicationSlot() == NULL) { - ereport(LOG, (errmsg("PgArchiver exit"))); + ereport(LOG, (errmsg("PgArchiver exit"))); /* 如果归档不活跃且无复制槽,记录日志并退出 */ return; } } @@ -388,10 +470,10 @@ static void pgarch_MainLoop(void) if (t_thrd.arch.last_sigterm_time == 0) t_thrd.arch.last_sigterm_time = icurtime; else if ((unsigned int)(icurtime - t_thrd.arch.last_sigterm_time) >= (unsigned int)60) - break; + break; /* 如果 SIGTERM 信号接收到后,经过 60 秒,退出循环 */ } #ifndef ENABLE_LITE_MODE - load_server_mode(); + load_server_mode(); /* 加载服务器模式 */ if (IsServerModeStandby()) { ArchiveTaskStatus *archive_task_status = NULL; @@ -403,7 +485,7 @@ static void pgarch_MainLoop(void) /* if we should do pitr archive, for standby */ volatile unsigned int *pitr_task_status = &archive_task_status->pitr_task_status; if (unlikely(pg_atomic_read_u32(pitr_task_status) == PITR_TASK_GET)) { - pgarch_archiveRoachForPitrStandby(); + pgarch_archiveRoachForPitrStandby(); /* 执行 PITR 归档操作 */ pg_atomic_write_u32(pitr_task_status, PITR_TASK_DONE); update_archive_start_end_location_file(archive_task_status->archive_task.targetLsn, TIME_GET_MILLISEC(last_copy_time)); @@ -413,7 +495,7 @@ static void pgarch_MainLoop(void) /* Do what we're here for */ if (t_thrd.arch.wakened || time_to_stop) { - t_thrd.arch.wakened = false; + t_thrd.arch.wakened = false; /* 重置唤醒标志 */ #ifndef ENABLE_LITE_MODE obs_archive_slot = getArchiveReplicationSlot(); if (obs_archive_slot != NULL && !IsServerModeStandby()) { @@ -456,24 +538,27 @@ static void pgarch_MainLoop(void) (errmsg("pgarch_ArchiverObsCopyLoop failed when call SyncRepGetSyncRecPtr"))); } } - + /* + * 检查是否满足执行归档的条件: + * 1. 时间间隔大于等于归档间隔(归档周期)或者待归档的数据达到 OBS_XLOG_SLICE_BLOCK_SIZE 大小。 + */ if (time_diff >= (u_sess->attr.attr_storage.archive_interval * millitosec) || XLByteDifference(flushPtr, t_thrd.arch.pitr_task_last_lsn) >= OBS_XLOG_SLICE_BLOCK_SIZE) { if (IS_PGXC_COORDINATOR) { - fun = &pgarch_archiveRoachForCoordinator; + fun = &pgarch_archiveRoachForCoordinator; /* 如果是协调器节点,选择协调器的归档函数 */ } else { - fun = &pgarch_archiveRoachForPitrMaster; + fun = &pgarch_archiveRoachForPitrMaster; /* 否则,选择 PITR 主节点的归档函数 */ #ifndef ENABLE_MULTIPLE_NODES if (g_instance.attr.attr_storage.dcf_attr.enable_dcf) - fun = &DcfArchiveRoachForPitrMaster; + fun = &DcfArchiveRoachForPitrMaster; /* 如果启用 DCF,选择 DCF 主节点的归档函数 */ #endif } - pgarch_ArchiverObsCopyLoop(flushPtr, fun); + pgarch_ArchiverObsCopyLoop(flushPtr, fun); /* 执行归档操作,传入待归档数据的位置和归档函数 */ } } else { #endif - pgarch_ArchiverCopyLoop(); - gettimeofday(&last_copy_time, NULL); + pgarch_ArchiverCopyLoop(); /* 否则,执行普通归档操作 */ + gettimeofday(&last_copy_time, NULL); /* 更新上一次复制的时间点 */ #ifndef ENABLE_LITE_MODE } #endif @@ -489,21 +574,21 @@ static void pgarch_MainLoop(void) long last_time; /* Don't wait during last iteration */ if (obs_archive_slot != NULL) { - wait_interval = t_thrd.arch.task_wait_interval; - last_time = t_thrd.arch.last_arch_time; + wait_interval = t_thrd.arch.task_wait_interval; /* 获取任务等待间隔时间 */ + last_time = t_thrd.arch.last_arch_time; /* 获取上一次归档的时间点 */ } else { - wait_interval = PGARCH_AUTOWAKE_INTERVAL; - last_time = TIME_GET_MILLISEC(last_copy_time); + wait_interval = PGARCH_AUTOWAKE_INTERVAL; /* 默认等待间隔时间 */ + last_time = TIME_GET_MILLISEC(last_copy_time); /* 获取上一次复制的时间点 */ } gettimeofday(&curtime, NULL); - long time_diff = (long)TIME_GET_MILLISEC(curtime) - last_time; + long time_diff = (long)TIME_GET_MILLISEC(curtime) - last_time; /* 计算时间差 */ if (time_diff < 0) { time_diff = 0; } - long timeout = wait_interval - time_diff; + long timeout = wait_interval - time_diff; /* 计算超时时间 */ if (timeout < 0 && IsServerModeStandby()) { /* sleep 100ms for check next task */ - timeout = 100; + timeout = 100; /* 如果超时时间小于 0,设置默认等待 100 毫秒 */ } if (timeout > 0) { int rc; @@ -537,6 +622,15 @@ static void pgarch_MainLoop(void) * * Archives all outstanding xlogs then returns */ +/* + * 功能:执行归档操作的主循环 + * + * 该函数用于循环处理需要进行归档的 xlog 文件,执行归档操作。在归档过程中,会检查是否接收到终止信号,以及是否需要更新配置文件。 + * + * 参数:无 + * + * 返回值:无 + */ static void pgarch_ArchiverCopyLoop(void) { char xlog[MAX_XFN_CHARS + 1]; @@ -603,28 +697,39 @@ static void pgarch_ArchiverCopyLoop(void) * * Returns true if successful */ +/* + * 功能:将指定的 xlog 文件归档到目标位置 + * + * 参数: + * xlog:需要归档的 xlog 文件的名称 + * + * 返回值: + * bool 类型,表示归档是否成功 + * 如果成功,返回 true;否则返回 false + */ static bool PgarchArchiveXlogToDest(const char* xlog) { - int fdSrc = -1; - int fdDest = -1; - char srcPath[PATH_MAX + 1] = {0}; - char destPath[PATH_MAX + 1] = {0}; - char activitymsg[MAXFNAMELEN + 16]; - long int fileBytes = 0; - int rc = 0; - char tempPath[PATH_MAX] = {0}; - char* retVal = NULL; + int fdSrc = -1; // 源文件描述符 + int fdDest = -1; // 目标文件描述符 + char srcPath[PATH_MAX + 1] = {0}; // 源文件路径 + char destPath[PATH_MAX + 1] = {0}; // 目标文件路径 + char activitymsg[MAXFNAMELEN + 16]; // 用于记录操作消息 + long int fileBytes = 0; // 读取的文件字节数 + int rc = 0; // 用于记录 snprintf_s 的返回值 + char tempPath[PATH_MAX] = {0}; // 用于构建文件路径的临时缓冲区 + char* retVal = NULL; // 用于记录 realpath 的返回值 if (xlog == NULL) { return false; } - + // 构建源文件路径 rc = snprintf_s(tempPath, PATH_MAX, PATH_MAX - 1, XLOGDIR "/%s", xlog); securec_check_ss(rc, "\0", "\0"); retVal = realpath(tempPath, srcPath); if (retVal == NULL) { ereport(FATAL, (errmsg_internal("realpath src %s failed:%m\n", tempPath))); } + // 构建目标文件路径 retVal = realpath(u_sess->attr.attr_storage.XLogArchiveDest, destPath); if (retVal == NULL) { ereport(FATAL, (errmsg_internal("realpath dest %s failed:%m\n", u_sess->attr.attr_storage.XLogArchiveDest))); @@ -634,7 +739,7 @@ static bool PgarchArchiveXlogToDest(const char* xlog) if ((fdSrc = open(srcPath, O_RDONLY)) >= 0) { if ((fdDest = open(destPath, O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR)) >= 0) { - char pbuff[ARCHIVE_BUF_SIZE] = {0}; + char pbuff[ARCHIVE_BUF_SIZE] = {0}; // 用于缓存读取的数据 while ((fileBytes = read(fdSrc, pbuff, sizeof(pbuff))) > 0) { if (write(fdDest, pbuff, fileBytes) != fileBytes) { @@ -651,7 +756,7 @@ static bool PgarchArchiveXlogToDest(const char* xlog) ereport(FATAL, (errmsg_internal("could not read file\"%s\":%m\n", xlog))); } - g_instance.WalSegmentArchSucceed = true; + g_instance.WalSegmentArchSucceed = true; // 标记归档成功 ereport(DEBUG1, (errmsg("archived transaction log file \"%s\"", xlog))); rc = snprintf_s(activitymsg, sizeof(activitymsg), sizeof(activitymsg) - 1, "last was %s", xlog); @@ -674,13 +779,24 @@ static bool PgarchArchiveXlogToDest(const char* xlog) * Update archived xlog LSN for barrier. * */ +/* + * 功能:更新归档的LSN(日志序列号) + * + * 参数: + * targetLsn:要更新的目标LSN + * + * 返回值:无 + */ static inline void UpdateArchivedLsn(XLogRecPtr targetLsn) { - ArchiveTaskStatus *archive_task_status = nullptr; + ArchiveTaskStatus *archive_task_status = nullptr; // 用于存储归档任务的状态 + // 查找与当前线程关联的归档任务状态 archive_task_status = find_archive_task_status(&t_thrd.arch.archive_task_idx); + // 如果未找到归档任务状态,则直接返回 if (archive_task_status == nullptr) { return; } + // 更新归档任务状态中的已归档LSN archive_task_status->archived_lsn = targetLsn; } @@ -690,6 +806,14 @@ static inline void UpdateArchivedLsn(XLogRecPtr targetLsn) * * Archives all outstanding xlogs then returns */ + +/* + * 功能:执行归档操作的主循环(用于归档观察者模式) + * + * 参数: + * flushPtr:目标LSN,表示要归档的日志的上限 + * fun:执行归档操作的函数指针 + */ static void pgarch_ArchiverObsCopyLoop(XLogRecPtr flushPtr, doArchive fun) { ereport(LOG, @@ -716,6 +840,7 @@ static void pgarch_ArchiverObsCopyLoop(XLogRecPtr flushPtr, doArchive fun) * command, and the second is to avoid conflicts with another * archiver spawned by a newer postmaster. */ + // 如果收到SIGTERM信号、Postmaster已停止或时间到达停止时间,结束归档循环 if (t_thrd.arch.got_SIGTERM || !PostmasterIsAlive() || time_to_stop) { return; } @@ -725,10 +850,11 @@ static void pgarch_ArchiverObsCopyLoop(XLogRecPtr flushPtr, doArchive fun) * setting for archive_command as soon as possible, even if there * is a backlog of files to be archived. */ - if (t_thrd.arch.got_SIGHUP) { + if (t_thrd.arch.got_SIGHUP) {// 处理SIGHUP信号,更新配置 ProcessConfigFile(PGC_SIGHUP); t_thrd.arch.got_SIGHUP = false; } + // 计算目标LSN if (flushPtr == InvalidXLogRecPtr) { targetLsn = t_thrd.arch.pitr_task_last_lsn + OBS_XLOG_SLICE_BLOCK_SIZE - (t_thrd.arch.pitr_task_last_lsn % OBS_XLOG_SLICE_BLOCK_SIZE) - 1; @@ -741,13 +867,14 @@ static void pgarch_ArchiverObsCopyLoop(XLogRecPtr flushPtr, doArchive fun) if (t_thrd.arch.pitr_task_last_lsn == targetLsn) { targetLsn = Min(targetLsn + OBS_XLOG_SLICE_BLOCK_SIZE, flushPtr); } - + // 执行归档操作 if (fun(targetLsn) == false) { ereport(WARNING, (errmsg("xlog file \"%X/%X\" could not be archived: try again", (uint32)(targetLsn >> 32), (uint32)(targetLsn)))); pg_usleep(1000000L); /* wait a bit before retrying */ } else { + // 归档成功的处理 if (g_instance.roach_cxt.isXLogForceRecycled && !g_instance.roach_cxt.forceAdvanceSlotTigger) { g_instance.roach_cxt.isXLogForceRecycled = false; ereport(LOG, (errmsg("PgArch force advance slot success"))); @@ -776,6 +903,15 @@ static void pgarch_ArchiverObsCopyLoop(XLogRecPtr flushPtr, doArchive fun) * * Returns true if successful */ +/* + * 功能:尝试将指定的WAL文件进行归档 + * + * 参数: + * xlog:要归档的WAL文件名 + * + * 返回值: + * 如果成功归档,则返回true,否则返回false + */ static bool pgarch_archiveXlog(char* xlog) { char xlogarchcmd[MAXPGPATH]; @@ -785,11 +921,12 @@ static bool pgarch_archiveXlog(char* xlog) char* endp = NULL; const char* sp = NULL; int rc = 0; - + // 构建WAL文件的完整路径 rc = snprintf_s(pathname, MAXPGPATH, MAXPGPATH - 1, XLOGDIR "/%s", xlog); securec_check_ss(rc, "\0", "\0"); /* archive_dest is preferred over archive_command */ + // 如果已设置XLog归档目标,则调用 PgarchArchiveXlogToDest 函数进行归档 if (XLogArchiveDestSet()) { return PgarchArchiveXlogToDest(xlog); } @@ -800,7 +937,7 @@ static bool pgarch_archiveXlog(char* xlog) dp = xlogarchcmd; endp = xlogarchcmd + MAXPGPATH - 1; *endp = '\0'; - + // 构建归档命令 for (sp = u_sess->attr.attr_storage.XLogArchiveCommand; *sp; sp++) { if (*sp == '%') { switch (sp[1]) { @@ -845,10 +982,11 @@ static bool pgarch_archiveXlog(char* xlog) rc = snprintf_s(activitymsg, sizeof(activitymsg), sizeof(activitymsg) - 1, "archiving %s", xlog); securec_check_ss(rc, "\0", "\0"); - set_ps_display(activitymsg, false); - + set_ps_display(activitymsg, false); // 设置进程状态显示 + // 执行归档命令 rc = gs_popen_security(xlogarchcmd); if (rc != 0) { + // 归档失败的处理 /* If execute archive command failed, we report an alarm */ g_instance.WalSegmentArchSucceed = false; @@ -898,6 +1036,7 @@ static bool pgarch_archiveXlog(char* xlog) return false; } + // 归档成功的处理 g_instance.WalSegmentArchSucceed = true; ereport(DEBUG1, (errmsg("archived transaction log file \"%s\"", xlog))); @@ -930,6 +1069,17 @@ static bool pgarch_archiveXlog(char* xlog) * higher priority for archiving. This seems okay, or at least not * obviously worth changing. */ +/* + * + * 功能:查找 pg_xlog 目录下准备好归档的最旧 WAL 文件的名称 + * + * 参数: + * xlog:用于存储找到的 WAL 文件名的字符数组,至少要有 XLOG_FNAME_LEN + 1 字节 + * + * 返回值: + * 如果找到合适的文件,则将其名称存储在 xlog 中,并返回 true,否则返回 false + * + */ static bool pgarch_readyXlog(char* xlog, int xlog_length) { /* @@ -938,36 +1088,38 @@ static bool pgarch_readyXlog(char* xlog, int xlog_length) * this code, though only a single file is expected on the vast majority * of calls, so.... */ - char XLogArchiveStatusDir[MAXPGPATH]; - char newxlog[MAX_XFN_CHARS + 6 + 1]; - DIR* rldir = NULL; - struct dirent* rlde = NULL; - bool found = false; - int rc = 0; - + char XLogArchiveStatusDir[MAXPGPATH]; // 归档状态目录的路径 + char newxlog[MAX_XFN_CHARS + 6 + 1]; // 用于存储找到的最新的准备好归档的XLog文件名 + DIR* rldir = NULL; // 目录句柄 + struct dirent* rlde = NULL; // 目录项结构 + bool found = false; // 是否找到准备好归档的XLog文件 + int rc = 0; // 用于保存函数返回值 + // 构建归档状态目录的完整路径 rc = snprintf_s(XLogArchiveStatusDir, MAXPGPATH, MAXPGPATH - 1, XLOGDIR "/archive_status"); securec_check_ss(rc, "", ""); - + // 打开归档状态目录 rldir = AllocateDir(XLogArchiveStatusDir); if (rldir == NULL) ereport(ERROR, (errcode_for_file_access(), errmsg("could not open archive status directory \"%s\": %m", XLogArchiveStatusDir))); - + // 遍历归档状态目录下的文件 while ((rlde = ReadDir(rldir, XLogArchiveStatusDir)) != NULL) { int basenamelen = (int)strlen(rlde->d_name) - 6; - + // 检查文件名是否符合归档文件的命名规范 if (basenamelen >= MIN_XFN_CHARS && basenamelen <= MAX_XFN_CHARS && strspn(rlde->d_name, VALID_XFN_CHARS) >= (size_t)basenamelen && strcmp(rlde->d_name + basenamelen, ".ready") == 0) { errno_t rc = EOK; if (!found) { + // 第一次找到准备好归档的文件 rc = strcpy_s(newxlog, MAX_XFN_CHARS + 6 + 1, rlde->d_name); securec_check(rc, "\0", "\0"); found = true; } else { + // 比较文件名,选取较新的文件 if (strcmp(rlde->d_name, newxlog) < 0) { rc = strcpy_s(newxlog, MAX_XFN_CHARS + 6 + 1, rlde->d_name); securec_check(rc, "\0", "\0"); @@ -975,8 +1127,8 @@ static bool pgarch_readyXlog(char* xlog, int xlog_length) } } } - FreeDir(rldir); - + FreeDir(rldir); // 关闭目录句柄 + // 如果找到准备好归档的文件,将其赋值给传入的xlog参数 if (found) { errno_t rc = EOK; @@ -985,7 +1137,7 @@ static bool pgarch_readyXlog(char* xlog, int xlog_length) rc = strcpy_s(xlog, (size_t)xlog_length, newxlog); securec_check(rc, "\0", "\0"); } - return found; + return found; // 返回是否找到准备好归档的文件 } /* @@ -996,21 +1148,44 @@ static bool pgarch_readyXlog(char* xlog, int xlog_length) * Eventually, a checkpoint process will notice this and delete both the * NNN.done file and the xlog file itself. */ +/* + * 功能:将指定的WAL文件从".ready"状态归档到".done"状态 + * + * 参数: + * xlog:要从".ready"状态归档到".done"状态的WAL文件名 + * + * 返回值:无 + */ static void pgarch_archiveDone(const char* xlog) { char rlogready[MAXPGPATH]; char rlogdone[MAXPGPATH]; - + // 生成 ".ready" 和 ".done" 文件的完整路径 StatusFilePath(rlogready, MAXPGPATH, xlog, ".ready"); StatusFilePath(rlogdone, MAXPGPATH, xlog, ".done"); + + // 将 ".ready" 文件重命名为 ".done" 文件来表示成功归档 (void)durable_rename(rlogready, rlogdone, WARNING); } +/* + * archKill + * + * 功能:用于处理归档线程的退出操作,释放相关资源 + * + * 参数: + * code:退出代码 + * arg:额外的参数(未使用) + * + * 返回值:无 + */ static void archKill(int code, Datum arg) { - setObsArchLatch(NULL); + setObsArchLatch(NULL);// 清除归档线程的主循环Latch + // 打印日志,表示归档线程已关闭 ereport(LOG, (errmsg("arch thread shut down, slotName: %s", t_thrd.arch.slot_name))); - pfree_ext(t_thrd.arch.slot_name); + pfree_ext(t_thrd.arch.slot_name); // 释放归档线程使用的slot名称内存 + // 释放连接配置相关内存,如果存在 if (t_thrd.arch.archive_config != NULL && t_thrd.arch.archive_config->archive_config.conn_config != NULL) { pfree_ext(t_thrd.arch.archive_config->archive_config.conn_config->obs_address); pfree_ext(t_thrd.arch.archive_config->archive_config.conn_config->obs_bucket); @@ -1024,14 +1199,29 @@ static void archKill(int code, Datum arg) * pgarch_archiveRoachForPitrStandby * get signal from walreceiver, fork a roach process to archive xlog */ + +/* + * 功能:用于Standby节点进行处理PITR(Point-In-Time Recovery)模式下的归档操作, + * 首先获取归档任务的状态,然后根据任务的状态进行归档。 + * 如果归档成功,将设置归档任务的完成标志。 + * 如果归档失败,将设置归档任务的失败标志。 + * 最后,函数会打印相应的日志记录归档的详细信息。 + * + * 参数:无 + * + * 返回值:无 + */ static void pgarch_archiveRoachForPitrStandby() { ArchiveTaskStatus *archive_task_status = NULL; + // 获取归档任务的状态 archive_task_status = find_archive_task_status(&t_thrd.arch.archive_task_idx); if (archive_task_status == NULL) { + // 如果无法获取归档任务状态,则报错并退出 ereport(ERROR, (errmsg("pgarch_archiveRoachForPitrStandby failed because could not get an archive task status."))); } + // 打印日志,记录归档任务的详细信息 ereport(LOG, (errmsg("pgarch_archiveRoachForPitrStandby %s : %X/%X, term:%d, subterm:%d", archive_task_status->slotname, @@ -1040,13 +1230,16 @@ static void pgarch_archiveRoachForPitrStandby() archive_task_status->archive_task.term, archive_task_status->archive_task.sub_term))); if (archive_task_status->archive_task.targetLsn == InvalidXLogSegPtr) { + // 如果归档任务的目标LSN无效,则报告并强制提前进入下一个Slot volatile unsigned int *pitr_task_status = &archive_task_status->pitr_task_status; pg_atomic_write_u32(pitr_task_status, PITR_TASK_NONE); ereport(LOG, (errmsg("PgArch standby receive invalid lsn for slot force advance"))); } + // 调用ArchiveReplicationAchiver函数进行归档,如果归档成功,设置完成标志,否则设置失败标志 if (ArchiveReplicationAchiver(&archive_task_status->archive_task) == 0) { archive_task_status->pitr_finish_result = true; } else { + // 如果归档失败,打印警告信息并设置失败标志 ereport(WARNING, (errmsg("error when pgarch_archiveRoachForPitrStandby %s : %X/%X, term:%d, subterm:%d", archive_task_status->slotname, @@ -1063,59 +1256,80 @@ static void pgarch_archiveRoachForPitrStandby() * pgarch_archiveRoachForPitrMaster * choose a walsender to send archive command */ +/* + * 功能:用于主节点进行处理PITR(Point-In-Time Recovery)模式下的归档操作 + * + * 参数: + * targetLsn:要归档到的目标LSN + * + * 返回值: + * 如果成功发起归档任务,则返回true,否则返回false + */ static bool pgarch_archiveRoachForPitrMaster(XLogRecPtr targetLsn) { ArchiveTaskStatus *archive_task_status = NULL; + // 获取归档任务的状态 archive_task_status = find_archive_task_status(&t_thrd.arch.archive_task_idx); if (archive_task_status == NULL) { + // 如果无法获取归档任务状态,则返回失败 return false; } - + // 更新归档任务的目标LSN、时间线和term archive_task_status->archive_task.targetLsn = targetLsn; archive_task_status->archive_task.tli = get_controlfile_timeline(); archive_task_status->archive_task.term = Max(g_instance.comm_cxt.localinfo_cxt.term_from_file, g_instance.comm_cxt.localinfo_cxt.term_from_xlog); if (g_instance.roach_cxt.forceAdvanceSlotTigger) { + // 如果需要强制提前进入下一个Slot,则设置目标LSN为无效 archive_task_status->archive_task.targetLsn = InvalidXLogRecPtr; g_instance.roach_cxt.forceAdvanceSlotTigger = false; ereport(LOG, (errmsg("PgArch need force advance this time in primary"))); } /* subterm update when walsender changed */ + // 更新归档任务的slot名称 int rc = strcpy_s(archive_task_status->archive_task.slot_name, NAMEDATALEN, t_thrd.arch.slot_name); securec_check(rc, "\0", "\0"); + // 打印日志,记录归档任务的详细信息 ereport(LOG, (errmsg("%s : pgarch_archiveRoachForPitrMaster %X/%X", t_thrd.arch.slot_name, (uint32)(targetLsn >> 32), (uint32)(targetLsn)))); + // 选择一个合适的WAL发送进程 WalSnd* walsnd = pgarch_chooseWalsnd(targetLsn); if (walsnd == NULL) { + // 如果没有健康的Standby节点可用,则返回失败 ereport(WARNING, (errmsg("pgarch_archiveRoachForPitrMaster failed for no health standby %X/%X", (uint32)(targetLsn >> 32), (uint32)(targetLsn)))); return false; } + // 将归档任务添加到归档任务列表,并通知WAL发送进程执行归档 archive_task_status->archiver_latch = &t_thrd.arch.mainloop_latch; add_archive_task_to_list(t_thrd.arch.archive_task_idx, walsnd); SetLatch(&walsnd->latch); ResetLatch(&t_thrd.arch.mainloop_latch); + // 等待归档任务完成或超时 rc = WaitLatch(&t_thrd.arch.mainloop_latch, WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH, 3000L); if (rc & WL_POSTMASTER_DEATH) { + // 如果Postmaster已经退出,则退出线程 gs_thread_exit(1); } if (rc & WL_TIMEOUT) { + // 如果等待超时,则返回失败 return false; } /* * check targetLsn and g_instance.archive_obs_cxt.archive_task.targetLsn for deal message with wrong order */ + // 检查归档任务的完成状态,如果完成且目标LSN匹配,则返回成功 if (archive_task_status->pitr_finish_result == true && XLByteEQ(archive_task_status->archive_task.targetLsn, targetLsn)) { archive_task_status->pitr_finish_result = false; return true; } else { - return false; + return false; // 否则返回失败 } } @@ -1123,22 +1337,36 @@ static bool pgarch_archiveRoachForPitrMaster(XLogRecPtr targetLsn) * pgarch_archiveRoachForPitrMaster * choose a walsender to send archive command */ +/* + * 功能:用于处理协调节点(Coordinator)的PITR(Point-In-Time Recovery)模式下的归档操作 + * + * 参数: + * targetLsn:要归档到的目标LSN + * + * 返回值: + * 如果成功发起归档任务,则返回true,否则返回false + */ static bool pgarch_archiveRoachForCoordinator(XLogRecPtr targetLsn) { struct timeval tv; gettimeofday(&tv, NULL); ArchiveTaskStatus *archive_task_status = NULL; + // 获取归档任务的状态 archive_task_status = find_archive_task_status(&t_thrd.arch.archive_task_idx); if (archive_task_status == NULL) { + // 如果无法获取归档任务状态,则返回失败 ereport(ERROR, (errmsg("pgarch_archiveRoachForCoordinator failed because could not get an archive task status."))); } + // 初始化归档信息结构 ArchiveXlogMessage archive_xlog_info; archive_xlog_info.targetLsn = targetLsn; archive_xlog_info.term = 0; archive_xlog_info.sub_term = 0; archive_xlog_info.tli = 0; + // 调用归档函数进行归档 if (ArchiveReplicationAchiver(&archive_xlog_info) != 0) { + // 归档失败,设置归档完成标志为false,打印错误信息,然后等待一段时间后重试 archive_task_status->pitr_finish_result = false; ereport(WARNING, (errmsg("error when pgarch_archiveRoachForCoordinator %s : %X/%X, term:%d, subterm:%d", @@ -1150,16 +1378,19 @@ static bool pgarch_archiveRoachForCoordinator(XLogRecPtr targetLsn) ResetLatch(&t_thrd.arch.mainloop_latch); int rc; /* wait and try again */ + // 等待一段时间后重试 rc = WaitLatch(&t_thrd.arch.mainloop_latch, WL_TIMEOUT | WL_POSTMASTER_DEATH, 500); return false; } else { + // 归档成功,设置归档完成标志为true,更新归档起始和结束位置文件 archive_task_status->pitr_finish_result = true; archive_task_status->archive_task.targetLsn = targetLsn; update_archive_start_end_location_file(archive_task_status->archive_task.targetLsn, TIME_GET_MILLISEC(tv)); } + // 打印成功的归档信息 ereport(LOG, (errmsg("pgarch_archiveRoachForCoordinator %X/%X", (uint32)(targetLsn >> 32), (uint32)(targetLsn)))); @@ -1167,10 +1398,20 @@ static bool pgarch_archiveRoachForCoordinator(XLogRecPtr targetLsn) } /* check if there is any wal sender alive. */ +/* + * 功能:选择适当的WAL发送者(Walsnd)用于进行归档 + * + * 参数: + * targetLsn:要归档到的目标LSN + * + * 返回值: + * 如果成功选择Walsnd,则返回指向选定的Walsnd的指针,否则返回NULL + */ static WalSnd* pgarch_chooseWalsnd(XLogRecPtr targetLsn) { int i; volatile WalSnd* walsnd = NULL; + // 首先尝试选择与同步Walsnd(如果存在) if (t_thrd.arch.sync_walsender_idx >= 0) { walsnd = &t_thrd.walsender_cxt.WalSndCtl->walsnds[t_thrd.arch.sync_walsender_idx]; SpinLockAcquire(&walsnd->mutex); @@ -1182,7 +1423,7 @@ static WalSnd* pgarch_chooseWalsnd(XLogRecPtr targetLsn) } SpinLockRelease(&walsnd->mutex); } - + // 尝试选择其他适合归档的Walsnd for (i = 0; i < g_instance.attr.attr_storage.max_wal_senders; i++) { /* use volatile pointer to prevent code rearrangement */ walsnd = &t_thrd.walsender_cxt.WalSndCtl->walsnds[i]; @@ -1196,6 +1437,7 @@ static WalSnd* pgarch_chooseWalsnd(XLogRecPtr targetLsn) ArchiveTaskStatus *archive_status = NULL; archive_status = find_archive_task_status(t_thrd.arch.slot_name); if (archive_status == NULL) { + // 未找到归档任务状态时,抛出错误 ereport(ERROR, (errmsg("pgarch_chooseWalsnd has change from %d to %d, but not find slot", t_thrd.arch.sync_walsender_idx, i))); @@ -1209,22 +1451,32 @@ static WalSnd* pgarch_chooseWalsnd(XLogRecPtr targetLsn) return (WalSnd*)walsnd; } } - SpinLockRelease(&walsnd->mutex); + SpinLockRelease(&walsnd->mutex); // 释放锁 } - return NULL; + return NULL; // 如果没有合适的Walsnd可供选择,返回NULL } +/* + * 功能:从服务器获取最后一个任务的LSN + * + * 参数: + * obs_archive_slot:归档插槽配置信息 + * + * 返回值: + * 返回获取到的最后一个任务的LSN + */ static XLogRecPtr GetLastTaskLsnFromServer(ArchiveSlotConfig* obs_archive_slot) { ArchiveXlogMessage obs_archive_info; XLogRecPtr pitr_task_last_lsn; - + // 尝试从服务器获取最后一个任务的LSN信息 if (archive_replication_get_last_xlog(&obs_archive_info, &obs_archive_slot->archive_config) == 0) { pitr_task_last_lsn = obs_archive_info.targetLsn; ereport(LOG, (errmsg("initLastTaskLsn update lsn to %X/%X from server", (uint32)(pitr_task_last_lsn >> 32), (uint32)(pitr_task_last_lsn)))); } else { + // 如果从服务器获取失败,从本地获取 XLogRecPtr targetLsn = GetFlushRecPtr(); pitr_task_last_lsn = targetLsn - (targetLsn % XLogSegSize); ereport(LOG, @@ -1234,19 +1486,32 @@ static XLogRecPtr GetLastTaskLsnFromServer(ArchiveSlotConfig* obs_archive_slot) return pitr_task_last_lsn; } +/* + * 功能:初始化归档器的最后一个任务LSN + * + * 参数: + * obs_archive_slot:归档插槽配置信息 + * + * 注意: + * 该函数用于初始化归档器的最后一个任务LSN。首先尝试从本地或服务器获取最后一个任务的LSN信息, + * 然后将其设置为归档器的最后一个任务LSN。如果获取失败或无效,则会抛出错误或警告。 + */ static void InitArchiverLastTaskLsn(ArchiveSlotConfig* obs_archive_slot) { struct timeval tv; - load_server_mode(); - gettimeofday(&tv,NULL); - + load_server_mode(); // 加载服务器模式 + gettimeofday(&tv,NULL); // 获取当前时间 + // 如果未提供归档插槽配置信息,则尝试获取 if (obs_archive_slot == NULL) { - t_thrd.arch.last_arch_time = TIME_GET_MILLISEC(tv); + t_thrd.arch.last_arch_time = TIME_GET_MILLISEC(tv); // 初始化最后归档时间 obs_archive_slot = getArchiveReplicationSlot(); } - volatile int *slot_idx = &t_thrd.arch.slot_idx; + volatile int *slot_idx = &t_thrd.arch.slot_idx; // 获取当前线程的槽索引 + if (obs_archive_slot != NULL && !IsServerModeStandby() && !RecoveryInProgress()) { + // 如果传入的归档复制槽配置信息不为空,且当前不处于备机模式和不处于恢复中 if (likely(*slot_idx != -1) && *slot_idx < g_instance.attr.attr_storage.max_replication_slots) { + // 如果槽索引有效且小于最大复制槽数 ReplicationSlot *slot = &t_thrd.slot_cxt.ReplicationSlotCtl->replication_slots[*slot_idx]; SpinLockAcquire(&slot->mutex); if (slot->in_use == true && slot->archive_config != NULL) { @@ -1255,13 +1520,16 @@ static void InitArchiverLastTaskLsn(ArchiveSlotConfig* obs_archive_slot) * position, but in new version is initialized from local slot. * During the upgrade, the local restart lsn may be 0, so initialize it with old version way. */ + // 如果槽正在使用且具有归档配置 if (slot->data.restart_lsn == InvalidXLogRecPtr && t_thrd.proc->workingVersionNum < PITR_INIT_VERSION_NUM) { - SpinLockRelease(&slot->mutex); + // 如果重启LSN无效且当前工作版本号小于PITR_INIT_VERSION_NUM + SpinLockRelease(&slot->mutex); // 获取锁 + // 从服务器获取最后任务LSN t_thrd.arch.pitr_task_last_lsn = GetLastTaskLsnFromServer(obs_archive_slot); } else { t_thrd.arch.pitr_task_last_lsn = slot->data.restart_lsn; - SpinLockRelease(&slot->mutex); + SpinLockRelease(&slot->mutex); // 释放锁 } } else { SpinLockRelease(&slot->mutex); diff --git a/src/gausskernel/process/postmaster/rbcleaner.cpp b/src/gausskernel/process/postmaster/rbcleaner.cpp index 729dd7396..26aaaee99 100644 --- a/src/gausskernel/process/postmaster/rbcleaner.cpp +++ b/src/gausskernel/process/postmaster/rbcleaner.cpp @@ -68,6 +68,14 @@ const int RBCLEANER_INTERVAL_MS = 3000; * RbCleanerShmemSize * Size of Rbcleaner related shared memory */ + +/* + * 功能: 计算 Rbcleaner 相关共享内存的大小 + * + * 参数:无 + * + * 返回值:共享内存的大小 + */ Size RbCleanerShmemSize() { return sizeof(RbCleanerShmemStruct); @@ -77,92 +85,147 @@ Size RbCleanerShmemSize() * RbCleanerShmemInit * Allocate and initialize Rbcleanr -related shared memory */ + +/* + * 功能: 分配和初始化与 Rbcleaner 相关的共享内存 + * + * 参数:无 + * + * 返回值:无 + */ void RbCleanerShmemInit(void) { - bool found = false; - + bool found = false; // 用于标记是否找到了现有的共享内存 + // 获取共享内存段,并返回指向共享内存的指针 + // 如果找到现有的共享内存,则将 found 设置为 true t_thrd.rbcleaner_cxt.RbCleanerShmem = (RbCleanerShmemStruct *)ShmemInitStruct("RbCleaner Data", sizeof(RbCleanerShmemStruct), &found); - if (!found) { + if (!found) { // 如果共享内存尚不存在 /* * First time through, so initialize. Note that we zero the whole * requests array; this is so that CompactCheckpointerRequestQueue * can assume that any pad bytes in the request structs are zeroes. */ + // 将共享内存的内容初始化为零 errno_t ret = memset_s(t_thrd.rbcleaner_cxt.RbCleanerShmem, sizeof(RbCleanerShmemStruct), 0, sizeof(RbCleanerShmemStruct)); securec_check(ret, "\0", "\0"); - + // 初始化与 Rbcleaner 相关的队列或数据结构 RbQueueInit(&t_thrd.rbcleaner_cxt.RbCleanerShmem->queue); } return; } +/* + * 功能: 检查Rbcleaner进程是否存活 + * + * 参数:无 + * + * 返回值:bool类型,存活则返回true + */ static bool RbCleanerIsAlive() { + // 读取Rbcleaner共享内存结构体中的 rbCleanerPid 字段的值,并检查其是否不等于0 + // 不等于0,函数返回 true,表示Rbcleaner正在运行 return pg_atomic_read_u64(&t_thrd.rbcleaner_cxt.RbCleanerShmem->rbCleanerPid) != 0; } +/* + * 功能: 等待Rbcleaner启动 + * + * 参数:无 + * + * 返回值:无 + */ static void RbCleanerAlive() { - int ntries; - int maxtries = 50; + int ntries; // 用于记录尝试的次数 + int maxtries = 50; // 表示最多等待50次 /* Wait 5s until rbcleaner startup. */ for (ntries = 0;; ntries++) { + // 检查 rbCleanerPid 是否不等于0,如果是,则表示Rbcleaner已经启动,循环会结束 if (pg_atomic_read_u64(&t_thrd.rbcleaner_cxt.RbCleanerShmem->rbCleanerPid) != 0) { break; } + // 如果 ntries 达到了 maxtries,即等待次数达到上限 if (ntries >= maxtries) { + // 报告一个错误,表示Rbcleaner未能启动 ereport(ERROR, (errmsg("rbcleaner not running"))); } + // 如果尚未启动,记录一条日志消息,表示正在重试等待Rbcleaner启动 elog(LOG, "retry to wait rbcleaner started"); /* wait 0.1 sec, then retry */ - pg_usleep(100000L); + pg_usleep(100000L); // 休眠0.1秒 } } +/* + * 功能: 唤醒Rbcleaner进程 + * + * 参数:无 + * + * 返回值:无 + */ static void RbCltWakeupCleaner() { SetLatch(t_thrd.rbcleaner_cxt.RbCleanerShmem->rbCleanerLatch); } +/* + * 功能: 向消息队列提交Purge消息 + * + * 参数: + * localMsg:指向 PurgeMsg 结构体的指针,包含了要提交的Purge消息的信息 + * wait:表示如果消息队列已满时是否等待 + * + * 返回值: + * uint32类型,返回已提交消息的ID或无效ID + */ static uint32 RbCltSubmit(PurgeMsg *localMsg, bool wait = true) { - PurgeMsgQueue *msgQue = RbGetQueue(); + PurgeMsgQueue *msgQue = RbGetQueue(); // 获取指向消息队列的指针 PurgeMsg *prMsg = NULL; uint64 currMsgId; - while (true) { + while (true) { // 循环,直到消息队列不再满为止 + // 使用自旋锁 SpinLockAcquire 来获取消息队列的互斥锁 SpinLockAcquire(&msgQue->mutex); + // 检查消息队列是否已满 if (!RbQueueIsFull(msgQue)) { - break; + break; // 如果消息队列不满,跳出循环 } - SpinLockRelease(&msgQue->mutex); + SpinLockRelease(&msgQue->mutex); // 释放锁 if (wait) { /* wait 0.01 sec, then retry */ + // wait为true,提交消息 elog(LOG, "purge message queue is full, try again."); - pg_usleep(10000L); + pg_usleep(10000L); // 等待一段时间 } else { + // 否则,记录一条日志消息 elog(LOG, "purge message queue is full, ignore."); - return RB_INVALID_MSGID; + return RB_INVALID_MSGID;- // 返回一个特殊的无效消息ID } } - prMsg = RbQueueNext(msgQue); + prMsg = RbQueueNext(msgQue); // 一旦消息队列不再满,获取下一个可用的消息指针 - SpinLockAcquire(&prMsg->mutex); + SpinLockAcquire(&prMsg->mutex); // 使用自旋锁 SpinLockAcquire 来获取 prMsg 的互斥锁 /* Set id. */ - currMsgId = RbGetNextMsgId(); + currMsgId = RbGetNextMsgId(); // 设置当前消息的ID为下一个可用的消息ID + // 确保它是队列中的下一个位置 Assert((currMsgId % RB_MAX_MSGQ_SIZE) == msgQue->tail); + // 如果 prMsg 的ID不是无效消息ID if (prMsg->id != RB_INVALID_MSGID) { + // 会发出警告消息,表示当前消息将覆盖已存在的消息 elog(WARNING, "purge message %lu was overrided by %lu", prMsg->id, currMsgId); } + // 设置 prMsg 的ID、请求、响应以及标记 prMsg->id = currMsgId; /* Set req. */ RbMsgCopyReq(&prMsg->req, &localMsg->req); @@ -171,77 +234,121 @@ static uint32 RbCltSubmit(PurgeMsg *localMsg, bool wait = true) /* Set Latch. */ InitLatch(&prMsg->latch); - RbQueuePush(msgQue); + RbQueuePush(msgQue); // 将消息推入消息队列 + // 使用自旋锁 SpinLockRelease 释放 prMsg 和消息队列的互斥锁 SpinLockRelease(&prMsg->mutex); SpinLockRelease(&msgQue->mutex); - return prMsg->id; + return prMsg->id; // 返回已提交消息的ID } +/* + * 功能: 将Purge消息提交到消息队列 + * + * 参数: + * localMsg:指向 PurgeMsg 结构体的指针,包含了要提交的Purge消息的信息 + * + * 返回值: + * uint32类型,返回已提交消息的ID或无效ID + */ static uint32 RbCltPushMsg(PurgeMsg *localMsg) { uint32 id; /* Make sure rbcleaner alive. */ - RbCleanerAlive(); + RbCleanerAlive(); // 确保Rbcleaner正在运行 /* Submit a purge message. */ - id = RbCltSubmit(localMsg); + id = RbCltSubmit(localMsg); // 提交Purge消息,并将返回的消息ID /* Wakeup cleaner to process a new prMsg. */ - RbCltWakeupCleaner(); + RbCltWakeupCleaner(); // 唤醒Rbcleaner,以便处理新的Purge消息 - return id; + return id; // 返回刚刚提交的消息的ID } +/* + * 功能: 取消指定ID的Purge消息 + * + * 参数: + * id:Purge消息的ID + * + * 返回值:无 + */ static void RbCltCancel(uint64 id) { - PurgeMsg *rbMsg = RbMsg(id); - SpinLockAcquire(&rbMsg->mutex); + PurgeMsg *rbMsg = RbMsg(id); // 用消息ID获取指向相应消息的指针 + SpinLockAcquire(&rbMsg->mutex); // 获取消息的互斥锁 + // 检查消息的响应状态是否为 PURGE_MSG_STATUS_OVERRIDE if (rbMsg->res.status != PURGE_MSG_STATUS_OVERRIDE) { + // 如果不是,则将消息的 cancel 标志设置为 true rbMsg->req.cancel = true; } - SpinLockRelease(&rbMsg->mutex); + SpinLockRelease(&rbMsg->mutex); // 释放消息的互斥锁 } +/* + * 功能: 处理进程中断 + * + * 参数: + * rc:等待Latch的结果,其中包括标志位,指示是否有进程中断等信息 + * id:Purge消息的ID + * + * 返回值:无 + */ void RbCltProcInterrupts(int rc, uint64 id) { + // 检查 rc 中的标志位,以确定是否有进程中断 if (((unsigned int)rc) & WL_POSTMASTER_DEATH) { + // 如果检测到 "WL_POSTMASTER_DEATH" 标志位,表示主进程已经终止 + // 告一个日志消息,表示连接将被终止 ereport(LOG, (errmsg("terminating connection due to postmaster death."))); - proc_exit(1); + proc_exit(1); // 退出当前进程 } - + // 如果有进程退出或管理员命令 if (t_thrd.int_cxt.ProcDiePending || t_thrd.proc_cxt.proc_exit_inprogress) { - RbCltCancel(id); + RbCltCancel(id); // 取消等待的消息 + // 报告一个致命错误消息 ereport(FATAL, (errcode(ERRCODE_ADMIN_SHUTDOWN), errmsg("canceling the wait for rbcleaner response and " "terminating connection due to administrator command"))); } - + // 如果用户发出了取消查询的请求 if (t_thrd.int_cxt.QueryCancelPending) { - RbCltCancel(id); + RbCltCancel(id); // 取消等待的消息 + // 报告一个错误消息 ereport(ERROR, (errmsg("canceling wait for rbcleaner response due to user request"))); } - if (!RbCleanerIsAlive()) { - ereport(ERROR, (errmsg("rbcleaner not running"))); + if (!RbCleanerIsAlive()) { // 如果Rbcleaner未在运行中 + ereport(ERROR, (errmsg("rbcleaner not running"))); // 报告一个错误消息 } } + +/* + * 功能: 等待Rbcleaner进程对提交的Purge消息做出响应 + * + * 参数: + * id:Purge消息的ID + * localMsg:指向 PurgeMsg 结构体的指针,包含了要提交的Purge消息的信息 + * + * 返回值:无 + */ static void RbCltWaitMsg(uint64 id, PurgeMsg *localMsg) { - PurgeMsg *rbMsg = RbMsg(id); - uint32 totalTimes = 300; - uint32 retryNums = 0; - + PurgeMsg *rbMsg = RbMsg(id); // 获取到指向相应Purge消息的指针 + uint32 totalTimes = 300; // 表示最大等待Rbcleaner响应的次数 + uint32 retryNums = 0; // 记录已经重试的次数 + // 循环,一直等待Rbcleaner的响应,或者在达到最大等待次数后退出 while (true) { int rc; /* Clear any already-pending wakeups */ - ResetLatch(&rbMsg->latch); + ResetLatch(&rbMsg->latch); // 清除已挂起的 rbMsg 的 latch,以准备等待下一次的响应 /* Wait latch for a maximum of 1 sec for a rbcleaner response. */ rc = WaitLatch(&rbMsg->latch, WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH, 1000L); @@ -251,25 +358,35 @@ static void RbCltWaitMsg(uint64 id, PurgeMsg *localMsg) /* Check rbcleaner response. */ RbMsgGetRes(id, &localMsg->res, true); + // 检查消息的响应状态是否表示消息已经完成 if (RbMsgIsDone(localMsg->res.status)) { - break; + break; // 如果是则退出循环 } /* Break if rbcleaner not response in a maximum of 300 seconds. */ if (retryNums > totalTimes) { - RbCltCancel(id); + RbCltCancel(id); // 取消等待的消息 + // 响应状态设置为错误状态,并提供相应的错误信息 RbMsgSetStatusErrLocal(&localMsg->res, PURGE_MSG_STATUS_ERROR, ERRCODE_INTERNAL_ERROR, "wait response from rbcleaner timeout over 300 seconds"); break; } - retryNums++; + retryNums++; // 增加 retryNums 重试的次数计数器 } } +/* + * 功能: 执行Purge消息的提交和等待,以确保消息被处理,同时处理可能的响应覆盖情况 + * + * 参数: + * localMsg:指向 PurgeMsg 结构体的指针,包含了要提交的Purge消息的信息 + * + * 返回值:无 + */ static void RbCltExec(PurgeMsg *localMsg) { int ntries; - int maxtries = 10; + int maxtries = 10; // 最多重试 maxtries 次 for (ntries = 0;; ntries++) { uint32 id; @@ -282,25 +399,43 @@ static void RbCltExec(PurgeMsg *localMsg) /* 3. Break if res not overrided. */ if (localMsg->res.status != PURGE_MSG_STATUS_OVERRIDE) { - return; + return; // 表示响应没有被覆盖,函数会返回 } + // 响应被覆盖了,记录一条日志消息 elog(LOG, "purge message \"%u\" result overrided, try again", id); } - + // 消息的响应状态一直被覆盖,超过了最大重试次数,记录一个错误消息 elog(ERROR, "purge message result overrided over \"%d\" times", maxtries); } +/* + * 功能: 报告由于锁冲突而导致的错误情况 + * + * 参数: + * res:指向 PurgeMsgRes 结构体的指针,包含有关Purge消息响应的信息 + * + * 返回值:无 + */ static void RbErrReportLockConflict(const PurgeMsgRes *res) { const char *rbErrFormatLockConflict = "autopurge failed due to lock conflict: %s. " "DETAIL: %d objects purged, %d objects lock conflicted, " "%d objects not exist."; + // 报告一个错误 ereport(ERROR, (errcode(ERRCODE_RBIN_LOCK_NOT_AVAILABLE), errmsg(rbErrFormatLockConflict, res->errMsg, res->purgedNum, res->skippedNum, res->undefinedNum))); } +/* + * 功能: 报告意外错误情况 + * + * 参数: + * res:指向 PurgeMsgRes 结构体的指针,包含有关Purge消息响应的信息 + * + * 返回值:无 + */ static void RbErrReport(PurgeMsgRes *res) { const char *rbErrFormatError = "autopurge failed due to unexpected error: %s. " @@ -312,181 +447,304 @@ static void RbErrReport(PurgeMsgRes *res) res->skippedNum, res->undefinedNum))); } +/* + * 功能: 处理Purge消息的完成情况,包括检查是否有错误、锁冲突以及DML操作的结果 + * + * 参数: + * msg:指向 PurgeMsg 结构体的指针,包含了有关Purge消息的信息 + * isDML:表示是否是DML(数据操作语言)类型的Purge消息 + * purged:用于存储DML操作是否成功清理了对象 + * + * 返回值:无 + */ static void RbCltDone(PurgeMsg *msg, bool isDML = false, bool *purged = NULL) { - if (RbResIsError(msg->res.errCode)) { - RbErrReport(&msg->res); + if (RbResIsError(msg->res.errCode)) { // 如果Purge消息的响应中包含错误 + RbErrReport(&msg->res); // 报告错误 } /* non-DML purge cmds */ - if (!isDML) { - if (msg->res.skippedNum > 0) { - RbErrReportLockConflict(&msg->res); + if (!isDML) { // 如果不是DML类型的Purge消息 + if (msg->res.skippedNum > 0) { // 如果消息中存在锁冲突对象 + RbErrReportLockConflict(&msg->res); // 报告锁冲突错误 } - return; + return; // 否则,直接返回 } - + // 如果是DML类型的Purge消息 + // 根据清理的对象数量和锁冲突的情况来确定是否成功清理了对象 /* DML purge cmd */ + + // 如果已清理的对象数量大于0 if (msg->res.purgedNum + msg->res.undefinedNum > 0) { - *purged = true; + *purged = true; // 表示成功清理了对象 } else if (msg->res.skippedNum == 0) { - *purged = false; + // 如果没有锁冲突对象 + *purged = false; // 表示没有成功清理对象 } else { - RbErrReportLockConflict(&msg->res); + RbErrReportLockConflict(&msg->res); // 报告锁冲突的错误 } } +/* + * 功能: 清理指定空间(表空间)中的DML操作生成的垃圾数据 + * + * 参数: + * spcId:要清理的表空间的OID + * + * 返回值: + * bool类型,表示是否成功清理了垃圾数据 + */ bool RbCltPurgeSpaceDML(Oid spcId) { - if (!TcapFeatureAvail()) { - return false; + if (!TcapFeatureAvail()) { // 检查是否支持Tcap特性 + return false; // 如果不支持,则返回 false,表示清理操作失败 } - PurgeMsg prMsg; + PurgeMsg prMsg; // 用于传递清理操作的相关信息 bool purged = false; - if (TrRbIsEmptySpc(spcId)) { - return false; + if (TrRbIsEmptySpc(spcId)) { // 检查指定表空间 spcId 是否为空 + return false; // 如果为空,则返回 false,表示清理操作失败 } - + // 初始化 + // 设置清理消息类型为DML,指定要清理的表空间 spcId,以及当前数据库的ID RbMsgInit(&prMsg, PURGE_MSG_TYPE_DML, spcId, u_sess->proc_cxt.MyDatabaseId); - RbCltExec(&prMsg); - RbCltDone(&prMsg, true, &purged); + RbCltExec(&prMsg); // 提交清理消息,并等待处理 + RbCltDone(&prMsg, true, &purged); // 处理清理消息的结果 - return purged; + return purged; // 返回purged,表示是否成功清理了垃圾数据 } +/* + * 功能: 清理指定空间(表空间)中的回收站数据,但前提是没有其他回收站对象依赖于该空间 + * + * 参数: + * spcId:要清理的表空间的OID + * + * 返回值:无 + */ void RbCltPurgeSpace(Oid spcId) { - if (!TcapFeatureAvail()) { - return; + if (!TcapFeatureAvail()) { // 检查是否支持Tcap特性 + return; // 如果不支持,则返回 } - if (!TrRbIsEmptySpc(spcId)) { + if (!TrRbIsEmptySpc(spcId)) { // 检查指定表空间 spcId 是否为空 + // 如果不为空,表示有其他回收站对象依赖于该空间 + // 报告错误消息,指示无法执行清理操作 elog(ERROR, "cannot execute this command because other recycle objects depend on the object, " "use \"purge recyclebin\" to clean recyclebin then try again."); } } +/* + * 功能: 清理数据库回收站中的数据,以删除不再需要的对象 + * + * 参数:无 + * + * 返回值:无 + */ void RbCltPurgeRecyclebin() { - if (!TcapFeatureAvail()) { - return; + if (!TcapFeatureAvail()) { // 检查是否支持Tcap特性 + return; // 如果不支持,则返回 } PurgeMsg prMsg; - + // 检查当前数据库的回收站是否为空 if (TrRbIsEmptyDb(u_sess->proc_cxt.MyDatabaseId)) { - return; + return; // 如果为空,表示没有需要清理的对象,返回 } - + // 初始化 + // 设置清理消息类型为RECYCLEBIN,指定要清理的数据库,即当前数据库 RbMsgInit(&prMsg, PURGE_MSG_TYPE_RECYCLEBIN, InvalidOid, u_sess->proc_cxt.MyDatabaseId); - RbCltExec(&prMsg); - RbCltDone(&prMsg); + RbCltExec(&prMsg); // 提交清理消息,并等待处理 + RbCltDone(&prMsg); // 处理清理消息的结果 } +/* + * 功能: 清理指定的数据库模式(schema)中的回收站数据 + * 前提是没有其他回收站对象依赖于该模式 + * + * 参数: + * nspId:要清理的数据库模式的OID + * + * 返回值:无 + */ void RbCltPurgeSchema(Oid nspId) { - if (!TcapFeatureAvail()) { - return; + if (!TcapFeatureAvail()) { // 检查是否支持Tcap特性 + return; // 如果不支持,则返回 } - if (!TrRbIsEmptySchema(nspId)) { + if (!TrRbIsEmptySchema(nspId)) { // 检查指定模式 nspId 是否为空 + // 如果不为空,表示有其他回收站对象依赖于该模式 + // 报告错误消息,指示无法执行清理操作 elog(ERROR, "cannot execute this command because other recycle objects depend on the object, " "use \"purge recyclebin\" to clean recyclebin then try again."); return; } } +/* + * 功能: 清理指定的用户角色(user role)相关的回收站数据 + * 前提是没有其他回收站对象依赖于该用户角色 + * + * 参数: + * nspId:要清理的数据库模式的OID + * + * 返回值:无 + */ void RbCltPurgeUser(Oid roleId) { - if (!TcapFeatureAvail()) { - return; + if (!TcapFeatureAvail()) { // 检查是否支持Tcap特性 + return; // 如果不支持,则返回 } - if (!TrRbIsEmptyUser(roleId)) { + if (!TrRbIsEmptyUser(roleId)) { // 检查指定用户角色 roleId 是否为空 + // 如果不为空,表示有其他回收站对象依赖于该用户角色 + // 报告错误消息,指示无法执行清理操作 elog(ERROR, "cannot execute this command because other recycle objects depend on the object, " "use \"purge recyclebin\" to clean recyclebin then try again."); } } +/* + * 功能: 清理指定的数据库中的回收站数据 + * 前提是没有其他回收站对象依赖于该数据库 + * + * 参数: + * dbId:要清理的数据库的OID + * + * 返回值:无 + */ void RbCltPurgeDatabase(Oid dbId) { - if (!TcapFeatureAvail()) { - return; + if (!TcapFeatureAvail()) { // 检查是否支持Tcap特性 + return; // 如果不支持,则返回 } - if (!TrRbIsEmptyDb(dbId)) { + if (!TrRbIsEmptyDb(dbId)) { // 检查指定数据库 dbId 是否为空 + // 如果不为空,表示有其他回收站对象依赖于该数据库 + // 报告错误消息,指示无法执行清理操作 elog(ERROR, "cannot execute this command because other recycle objects depend on the object, " "use \"purge recyclebin\" to clean recyclebin then try again."); } } +/* + * 功能: 启动回收站清理进程 + * + * 参数:无 + * + * 返回值:无 + */ ThreadId StartRbCleaner(void) { - if (!IsPostmasterEnvironment) { + if (!IsPostmasterEnvironment) { // 检查是否处于Postmaster环境下 return 0; } - if (canAcceptConnections(false) == CAC_OK) { - return initialize_util_thread(RBCLEANER); + if (canAcceptConnections(false) == CAC_OK) { // 检查是否可以接受新连接 + return initialize_util_thread(RBCLEANER); // 如果可以,启动回收站清理进程 } - + // 如果无法接受新连接,记录一条日志消息,指示尚未准备好启动回收站清理进程 ereport(LOG, (errmsg("not ready to start recyclebin cleaner."))); return 0; } +/* + * 功能: 检查当前进程是否为回收站清理进程 + * + * 参数:无 + * + * 返回值: + * bool类型,如果当前进程是回收站清理进程,则返回 true,否则返回 false + */ bool IsRbCleanerProcess(void) { return t_thrd.role == RBCLEANER; } +/* + * 功能: 检查回收站工作进程是否存活 + * + * 参数:无 + * + * 返回值: + * bool类型,存活则返回 true,否则返回 false + */ static bool RbWorkerIsAlive() { - RbWorkerInfo *workInfo = RbGetWorkerInfo(); + RbWorkerInfo *workInfo = RbGetWorkerInfo(); // 获取回收站工作进程信息 int ntries; int maxtries = 500; /* Wait 5s until rbcleaner stopped. */ + // 使用循环等待的方式检查回收站工作进程的状态 for (ntries = 0;; ntries++) { + // 如果工作进程的PID为0 if (pg_atomic_read_u64(&workInfo->rbworkerPid) == 0) { - return false; + return false; // 表示工作进程已经停止 } + // 如果超过重试次数 if (ntries >= maxtries) { - return true; + return true; // 表示工作进程仍然存活 } /* wait 0.01 sec, then retry */ - pg_usleep(10000L); + pg_usleep(10000L); // 如果工作进程的PID不为0,等待一段时间后再次检查 } return true; } /* SIGHUP: set flag to re-read config file at next convenient time */ + +/* + *功能:处理 SIGHUP 信号,用于重新读取配置文件 + * + * 参数: + * SIGNAL_ARGS: 信号处理程序的参数列表,提供关于触发信号的上下文信息 + * + * 返回值: + * 无 + */ static void RbSighupHandler(SIGNAL_ARGS) { - int saveErrno = errno; + int saveErrno = errno; // 保存当前的错误码 - t_thrd.rbcleaner_cxt.got_SIGHUP = true; + t_thrd.rbcleaner_cxt.got_SIGHUP = true; // 表示接收到 SIGHUP 信号 - SetLatch(&t_thrd.proc->procLatch); + SetLatch(&t_thrd.proc->procLatch); // 设置该线程的latch - errno = saveErrno; + errno = saveErrno; // 恢复之前保存的错误码 - elog(LOG, "rbcleaner signaled: SIGHUP"); + elog(LOG, "rbcleaner signaled: SIGHUP"); // 记录消息 } +/* + *功能:发送SIGINT信号来取消回收站工作进程 + * + * 参数:无 + * + * 返回值: + * 无 + */ static void RbCancelRbworker() { - RbWorkerInfo *workInfo = RbGetWorkerInfo(); + RbWorkerInfo *workInfo = RbGetWorkerInfo(); // 获取回收站工作进程信息 + // 获取工作进程的PID ThreadId rbworkerPid = pg_atomic_read_u64(&workInfo->rbworkerPid); + // 如果工作进程的PID不为0,表示工作进程存在 + // 向工作进程发送SIGINT信号,如果发送信号失败 if (rbworkerPid != 0 && gs_signal_send(rbworkerPid, SIGINT)) { + // 报告一个错误消息,指示无法发送SIGINT信号给工作进程 ereport(ERROR, (errmsg("could not send SIGINT signal to rbworker %lu: %m", rbworkerPid))); } - + // 记录一条日志消息,指示已向工作进程发送了SIGINT信号 elog(LOG, "rbcleaner: send SIGINT to rbworker %lu", rbworkerPid); } @@ -494,30 +752,43 @@ static void RbCancelRbworker() * Query-cancel signal from postmaster: abort current transaction * at soonest convenient time */ + +/* + *功能:处理来自Postmaster的SIGINT信号,以中断当前事务并取消回收站工作进程 + * + * 参数: + * SIGNAL_ARGS: 信号处理程序的参数列表,提供关于触发信号的上下文信息 + * + * 返回值: + * 无 + */ void RbSigintHandler(SIGNAL_ARGS) { - int saveErrno = errno; + int saveErrno = errno; // 保存当前的错误码 - RbCancelRbworker(); + RbCancelRbworker(); // 发送SIGINT信号以取消回收站工作进程 /* * Don't joggle the elbow of proc_exit */ + // 检查是否正在执行 proc_exit 函数 if (!t_thrd.proc_cxt.proc_exit_inprogress) { - InterruptPending = true; - t_thrd.int_cxt.QueryCancelPending = true; + // 如果不在 proc_exit + InterruptPending = true; // 表示有中断请求 + t_thrd.int_cxt.QueryCancelPending = true; // 表示有取消请求 /* * in libcomm interrupt is not allow, * gs_r_cancel will signal libcomm and * libcomm will then check for interrupt. */ - gs_r_cancel(); + gs_r_cancel(); // 通知 libcomm 库检查是否有中断请求 /* * If it's safe to interrupt, and we're waiting for input or a lock, * service the interrupt immediately */ + // 检查是否可以立即处理中断,是否其他中断挂起,以及是否进入关键区域 if (t_thrd.int_cxt.ImmediateInterruptOK && t_thrd.int_cxt.InterruptHoldoffCount == 0 && t_thrd.int_cxt.CritSectionCount == 0) { /* bump holdoff count to make ProcessInterrupts() a no-op */ @@ -525,41 +796,64 @@ void RbSigintHandler(SIGNAL_ARGS) t_thrd.int_cxt.InterruptHoldoffCount++; LockErrorCleanup(); /* prevent CheckDeadLock from running */ t_thrd.int_cxt.InterruptHoldoffCount--; - ProcessInterrupts(); + ProcessInterrupts(); // 处理中断操作 } } /* If we're still here, waken anything waiting on the process latch */ if (t_thrd.proc) - SetLatch(&t_thrd.proc->procLatch); + SetLatch(&t_thrd.proc->procLatch); // 唤醒该进程的进程标记 - errno = saveErrno; + errno = saveErrno; // 恢复错误码 } /* SIGTERM: time to die */ + +/* + *功能:处理 SIGTERM 信号,用于正常退出 + * + * 参数: + * SIGNAL_ARGS: 信号处理程序的参数列表,提供关于触发信号的上下文信息 + * + * 返回值: + * 无 + */ static void RbSigtermHander(SIGNAL_ARGS) { - int saveErrno = errno; + int saveErrno = errno; // 保存当前的错误码 - t_thrd.rbcleaner_cxt.got_SIGTERM = true; + t_thrd.rbcleaner_cxt.got_SIGTERM = true; // 表示请求正常退出 - SetLatch(&t_thrd.proc->procLatch); + SetLatch(&t_thrd.proc->procLatch); // 设置该线程的latch - errno = saveErrno; + errno = saveErrno; // 恢复之前保存的错误码 elog(LOG, "rbcleaner signaled: SIGTERM"); } +/* + *功能:收到 SIGQUIT 信号时,执行一些清理操作,并将当前线程标记为退出状态 + * 然后记录日志消息以指示 SIGQUIT 信号的发生 + * + * 参数: + * SIGNAL_ARGS: 信号处理程序的参数列表,提供关于触发信号的上下文信息 + * + * 返回值: + * 无 + */ static void RbSigquitHandler(SIGNAL_ARGS) { + // 将 SIGQUIT 信号添加到名为 BlockSig 的信号集中,防止嵌套调用 sigaddset(&t_thrd.libpq_cxt.BlockSig, SIGQUIT); /* prevent nested calls */ + // 将当前线程的信号掩码设置为 BlockSig 中定义的信号掩码 + // 确保在处理 SIGQUIT 信号期间,只有 SIGQUIT 信号被阻塞,而其他信号仍然可以触发 gs_signal_setmask(&t_thrd.libpq_cxt.BlockSig, NULL); - on_exit_reset(); + on_exit_reset(); // 执行一些与退出操作相关的清理工作 - gs_thread_exit(2); + gs_thread_exit(2); // 将当前线程标记为退出状态,并传递参数 2 作为退出代码 - elog(LOG, "rbcleaner signaled: SIGQUIT"); + elog(LOG, "rbcleaner signaled: SIGQUIT"); // 记录一条日志消息 } /* @@ -567,87 +861,138 @@ static void RbSigquitHandler(SIGNAL_ARGS) */ /* SIGUSR2: there is some message to purge the tablespace */ + +/* + *功能:处理 SIGUSR2 信号 + * + * 参数: + * SIGNAL_ARGS: 信号处理程序的参数列表,提供关于触发信号的上下文信息 + * + * 返回值: + * 无 + */ static void RbSigusr2Hander(SIGNAL_ARGS) { - int saveErrno = errno; + int saveErrno = errno; // 保存错误码 - SetLatch(&t_thrd.proc->procLatch); + SetLatch(&t_thrd.proc->procLatch); // 设置当前进程的进程标记,以唤醒等待该标记的线程 - errno = saveErrno; + errno = saveErrno; // 恢复错误码 - elog(LOG, "rbcleaner signaled: SIGUSR2"); + elog(LOG, "rbcleaner signaled: SIGUSR2"); // 记录一条日志消息 } +/* + *功能:执行清理操作 + * + * 参数: + * code:退出代码 + * arg:参数 + * 返回值: + * 无 + */ static void RbCleanerQuitAndClean(int code, Datum arg) { pg_atomic_write_u64(&t_thrd.rbcleaner_cxt.RbCleanerShmem->rbCleanerPid, 0); } +/* + *功能:获取与给定消息ID相关的数据库列表 + * + * 参数: + * id:要处理的消息ID + * + * 返回值: + * 获取到的数据库列表 + */ static List *RbGetDbList(uint64 id) { - PurgeMsg *msg = RbMsg(id); - List *l = NIL; + PurgeMsg *msg = RbMsg(id); // 获取消息ID对应的消息对象 + List *l = NIL; // 用于存储数据库列表 - if (msg->req.dbId != InvalidOid) { + if (msg->req.dbId != InvalidOid) { // 如果请求中指定了特定的数据库 char *dbname = NULL; + // 获取该数据库的名称 dbname = get_database_name(msg->req.dbId); + // 如果指定的数据库不存在 if (dbname == NULL) { + // 生成一个错误报告并抛出一个错误,指示数据库不存在 ereport(ERROR, (errcode(ERRCODE_UNDEFINED_DATABASE), errmsg("database \"%u\" does not exist", msg->req.dbId))); } - return lappend(l, dbname); + return lappend(l, dbname); // 将 dbname 添加到数据库列表 l 中,并将该列表作为函数的返回值 } - Assert(id == msg->id); + Assert(id == msg->id); // 检查 id 是否等于消息对象中的 msg->id + // 据消息请求的类型执行不同的操作 switch (msg->req.type) { case PURGE_MSG_TYPE_DML: case PURGE_MSG_TYPE_TABLESPACE: case PURGE_MSG_TYPE_CAS_TABLESPACE: - l = TrGetDbListSpc(msg->req.objId); + l = TrGetDbListSpc(msg->req.objId); // 获取与指定表空间相关的数据库列表,并将结果赋给列表 l break; case PURGE_MSG_TYPE_RECYCLEBIN: - l = TrGetDbListRcy(); + l = TrGetDbListRcy(); // 获取与回收站相关的数据库列表,并将结果赋给列表 l break; case PURGE_MSG_TYPE_CAS_SCHEMA: - l = TrGetDbListSchema(msg->req.objId); + l = TrGetDbListSchema(msg->req.objId); // 获取与指定模式相关的数据库列表,并将结果赋给列表 l break; case PURGE_MSG_TYPE_CAS_USER: - l = TrGetDbListUser(msg->req.objId); + l = TrGetDbListUser(msg->req.objId); // 获取与指定用户相关的数据库列表,并将结果赋给列表 l break; case PURGE_MSG_TYPE_AUTO: - l = TrGetDbListAuto(); + l = TrGetDbListAuto(); // 获取与自动清理相关的数据库列表,并将结果赋给列表 l break; default: elog(ERROR, "unknown purge message type: %d", msg->req.type); break; } - return l; + return l; // 返回获取到的数据库列表 l } +/* + *功能:完成消息的清理过程,并设置消息的最终状态 + * + * 参数: + * id:要处理的消息ID + * + * 返回值:无 + */ static void RbCleanerPurgeFinal(uint64 id) { - int errCode = *(volatile int *)&RbMsg(id)->res.errCode; - + int errCode = *(volatile int *)&RbMsg(id)->res.errCode; // 获取消息的错误代码 + // 根据 errCode 是否表示错误,函数设置消息的最终状态 PurgeMsgStatus status = RbResIsError(errCode) ? PURGE_MSG_STATUS_ERROR : PURGE_MSG_STATUS_SUCCESS; - RbMsgSetStatus(id, status, true); + RbMsgSetStatus(id, status, true); // 设置消息的状态 return; } +/* + *功能:检查消息的步骤结果是否满足某些条件 + * + * 参数: + * id:要处理的消息ID + * + * 返回值: + * bool类型,满足返回true,否则返回false + */ static bool RbMsgStepRes(uint64 id) { - PurgeMsg *rbMsg = RbMsg(id); + PurgeMsg *rbMsg = RbMsg(id); // 获取消息对象 PurgeMsgRes localRes; - Assert(id == rbMsg->id); + Assert(id == rbMsg->id); // 确保消息的ID和消息对象的ID相匹配 RbMsgGetRes(id, &localRes); + // 检查 localRes.status 是否为 PURGE_MSG_STATUS_STEPDONE Assert(localRes.status == PURGE_MSG_STATUS_STEPDONE); + // 如果 localRes.errCode 表示错误,或者 localRes.skippedNum 大于 0 if (RbResIsError(localRes.errCode) || localRes.skippedNum > 0) { - return true; + return true; // 表示消息需要进一步处理 } if (rbMsg->req.type == PURGE_MSG_TYPE_DML) { return localRes.purgedNum > 0; @@ -655,21 +1000,30 @@ static bool RbMsgStepRes(uint64 id) return localRes.skippedNum > 0; } +/* + *功能:处理来自操作系统或其他组件的中断和信号 + * + * 参数: + * rc 是传递给函数的中断标志位 + * id:要处理的消息ID + * + * 返回值:无 + */ static void RbCleanerProcInterrupts(int rc, uint64 id = RB_INVALID_MSGID) { /* Process sinval catchup interrupts that happened while sleeping */ - ProcessCatchupInterrupt(); + ProcessCatchupInterrupt(); // 处理 SINVAL中断 /* * Emergency bailout if postmaster has died. This is to avoid the * necessity for manual cleanup of all postmaster children. */ - if (((unsigned int)rc) & WL_POSTMASTER_DEATH) { + if (((unsigned int)rc) & WL_POSTMASTER_DEATH) { // 表示 postmaster 已经终止 proc_exit(1); } /* the normal shutdown case */ - if (t_thrd.rbcleaner_cxt.got_SIGTERM) { + if (t_thrd.rbcleaner_cxt.got_SIGTERM) { // 表示 rbcleaner 正在关闭 elog(LOG, "rbcleaner is shutting down."); proc_exit(0); } @@ -677,12 +1031,13 @@ static void RbCleanerProcInterrupts(int rc, uint64 id = RB_INVALID_MSGID) /* * reload the postgresql.conf */ - if (t_thrd.rbcleaner_cxt.got_SIGHUP) { + if (t_thrd.rbcleaner_cxt.got_SIGHUP) { // 表示收到了 SIGHUP 信号 t_thrd.rbcleaner_cxt.got_SIGHUP = false; ProcessConfigFile(PGC_SIGHUP); } /* Process rbclt cancel message. */ + // 如果 id 不等于 RB_INVALID_MSGID 并且消息被取消 if ((id != RB_INVALID_MSGID && RbMsgCanceled(id))) { RbCancelRbworker(); ereport(ERROR, @@ -691,50 +1046,70 @@ static void RbCleanerProcInterrupts(int rc, uint64 id = RB_INVALID_MSGID) /* Process rbclt cancel message. */ if (t_thrd.int_cxt.QueryCancelPending) { - ProcessInterrupts(); + ProcessInterrupts(); // 处理中断 } } +/* + * 功能:检查 rbworker 进程是否在一定时间内启动 + * + * 参数: + * launchTime: rbworker 进程启动的时间戳 + * + * 返回值: + * bool类型 + * 计算当前时间与启动时间之间的时间差,如果时间差超过30秒,函数返回 true 表示超时 + */ static bool RbStartWorkerTimeout(TimestampTz launchTime) { /* The rbcleaner waits for a maximum of 30 seconds for the rbworker to start. */ return TimestampDifferenceExceeds(launchTime, GetCurrentTimestamp(), 30000); } +/* + * 功能:执行消息的清理过程 + * + * 参数: + * id :要处理的消息的 ID + * + * 返回值:无 + */ static void RbCleanerPurgeImpl(uint64 id) { List *l = NULL; ListCell *cell = NULL; const long int rbCleanerPurgeIntervalMs = 1000L; - RbCleanerProcInterrupts(0, id); + RbCleanerProcInterrupts(0, id); // 处理中断和信号 - StartTransactionCommand(); - l = RbGetDbList(id); - foreach (cell, l) { + StartTransactionCommand(); // 开始一个事务 + l = RbGetDbList(id); // 获取需要处理的数据库列表 + foreach (cell, l) { // 遍历每个数据库 RbWorkerInfo *workerInfo = NULL; TimestampTz launchTime; - + // 检查是否已经存在活动的 rbworker 进程 if (RbWorkerIsAlive()) { + // 如果存在,则抛出错误 elog(ERROR, "start rbworker failed: rbworker already exists!"); } - + // 初始化 RbWorkerInfo 结构体 workerInfo = RbInitWorkerInfo(id, (char *)lfirst(cell)); + // 启动 rbworker 进程 SendPostmasterSignal(PMSIGNAL_START_RB_WORKER); - launchTime = GetCurrentTimestamp(); - while (true) { + launchTime = GetCurrentTimestamp(); // 记录 rbworker 进程启动的时间戳 + while (true) { // 循环等待 rbworker 进程的响应,最长等待时间为1秒 int rc; /* Clear any already-pending wakeups */ - ResetLatch(&workerInfo->latch); + ResetLatch(&workerInfo->latch); // 清除任何已经挂起的等待,确保从头开始等待 rbworker 的响应 /* Wait latch for a maximum of 1 sec for a rbworker response. */ rc = WaitLatch(&workerInfo->latch, WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH, rbCleanerPurgeIntervalMs); - RbCleanerProcInterrupts(rc, id); + RbCleanerProcInterrupts(rc, id); // 处理可能发生的中断 /* * We raise an ERROR if rbworker not enter PURGE_MSG_STATUS_STEPIN as soon, @@ -746,87 +1121,111 @@ static void RbCleanerPurgeImpl(uint64 id) break; } - if (RbMsgStepDone(id)) { + if (RbMsgStepDone(id)) { // 如果rbworker 进程已经完成了消息的处理 break; } } - if (RbMsgStepRes(id)) { - break; + if (RbMsgStepRes(id)) { // 如果消息的处理结果满足条件 + break; // 表示需要进一步处理,退出循环 } } - list_free_deep(l); + list_free_deep(l); // 释放数据库列表的内存 l = NULL; - CommitTransactionCommand(); + CommitTransactionCommand(); // 提交事务 - RbCleanerPurgeFinal(id); + RbCleanerPurgeFinal(id); // 设置消息的最终状态 } +/* + * 功能:从队列中取出任务并执行 + * + * 参数:无 + * + * 返回值:无 + */ static void RbCleanerPurge() { - PurgeMsgQueue *msqQue = RbGetQueue(); - while (!RbQueueIsEmpty(msqQue)) { - uint64 id = RbQueueFront(msqQue)->id; - PG_TRY(); + PurgeMsgQueue *msqQue = RbGetQueue(); // 获取待处理的消息队列 + while (!RbQueueIsEmpty(msqQue)) { // 持续处理消息队列中的任务,直到队列为空 + uint64 id = RbQueueFront(msqQue)->id; // 从队列头部取出任务的唯一标识 + PG_TRY(); // 异常处理块 { - RbCleanerPurgeImpl(id); + RbCleanerPurgeImpl(id); // 处理具体的清理操作 } PG_CATCH(); { + // 如果发生异常,捕获异常后将任务的状态设置为错误 RbMsgSetStatusErr(id, PURGE_MSG_STATUS_ERROR, geterrcode(), Geterrmsg(), true); - RbQueuePop(msqQue); + RbQueuePop(msqQue); // 从队列中移除该任务 PG_RE_THROW(); - } - PG_END_TRY(); - RbQueuePop(msqQue); + } + PG_END_TRY(); // 结束异常处理块 + RbQueuePop(msqQue); // 从队列中移除该任务 } } +/* + * 功能:向队列中添加新的自动清理任务 + * + * 参数:无 + * + * 返回值:无 + */ static void RbAutoPurge() { PurgeMsg prMsg; - + // 初始化为自动清理类型的消息 RbMsgInit(&prMsg, PURGE_MSG_TYPE_AUTO, InvalidOid, InvalidOid); - (void)RbCltSubmit(&prMsg, false); + (void)RbCltSubmit(&prMsg, false); // 将自动清理任务提交到消息队列中,但不等待任务完成 } +/* + * 功能:rbcleaner 进程的主要执行函数 + * + * 参数:无 + * + * 返回值:无 + */ NON_EXEC_STATIC void RbCleanerMain() { - sigjmp_buf localSigjmpBuf; + sigjmp_buf localSigjmpBuf; // 用于处理异常跳转 bool lastError = false; TimestampTz nextTimestamp; /* we are a postmaster subprocess now */ - IsUnderPostmaster = true; - t_thrd.role = RBCLEANER; + IsUnderPostmaster = true; // 表示当前进程是在 postmaster 进程下运行的 + t_thrd.role = RBCLEANER; // 标识线程 /* tell datasender we update the values. */ - pg_memory_barrier(); + pg_memory_barrier();// 执行内存屏障操作,用于确保前面的写操作对其他线程可见 /* reset t_thrd.proc_cxt.MyProcPid */ - t_thrd.proc_cxt.MyProcPid = gs_thread_self(); + t_thrd.proc_cxt.MyProcPid = gs_thread_self(); // 正确标识线程 /* record Start Time for logging */ - t_thrd.proc_cxt.MyStartTime = time(NULL); + t_thrd.proc_cxt.MyStartTime = time(NULL); // 记录了当前线程的启动时间 - t_thrd.proc_cxt.MyProgName = "RbCleaner"; + t_thrd.proc_cxt.MyProgName = "RbCleaner"; // 设置了当前线程的程序名称 /* Identify myself via ps */ - init_ps_display("rbcleaner process", "", "", ""); + init_ps_display("rbcleaner process", "", "", "");// 初始化进程状态显示 /* save the pid & latch into share memory */ pg_atomic_write_u64(&t_thrd.rbcleaner_cxt.RbCleanerShmem->rbCleanerPid, t_thrd.proc_cxt.MyProcPid); t_thrd.rbcleaner_cxt.RbCleanerShmem->rbCleanerLatch = &t_thrd.proc->procLatch; ereport(LOG, (errmsg("rbcleaner started"))); - + // 检查是否配置了 PostAuthDelay if (u_sess->attr.attr_security.PostAuthDelay) { + // 如果配置了延迟时间,那么工作进程将休眠相应的时间 pg_usleep(u_sess->attr.attr_security.PostAuthDelay * 1000000L); } - SetProcessingMode(InitProcessing); - + SetProcessingMode(InitProcessing); // 设置处理模式为初始化处理模式 + + // 设置信号处理程序 (void)gspqsignal(SIGHUP, RbSighupHandler); (void)gspqsignal(SIGINT, RbSigintHandler); /* Cancel signal */ (void)gspqsignal(SIGTERM, RbSigtermHander); @@ -849,95 +1248,107 @@ NON_EXEC_STATIC void RbCleanerMain() BaseInit(); #ifndef EXEC_BACKEND + // 不在执行后台情况下,创建了一个 PGPROC 结构体,并对其进行初始化 InitProcess(); #endif on_proc_exit(RbCleanerQuitAndClean, 0); - + // 设置当前进程的数据库和用户信息 t_thrd.proc_cxt.PostInit->SetDatabaseAndUser((char*)pstrdup(DEFAULT_DATABASE), InvalidOid, NULL); t_thrd.proc_cxt.PostInit->InitRbCleaner(); + // 设置当前进程的处理模式为 "NormalProcessing",表示正常的处理模式 SetProcessingMode(NormalProcessing); - + // 创建一个内存上下文 MemoryContext workMxt = AllocSetContextCreate( t_thrd.top_mem_cxt, "RbCleaner", ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); - (void)MemoryContextSwitchTo(workMxt); + (void)MemoryContextSwitchTo(workMxt); // 切换到该上下文 + // 处理异常情况 int curTryCounter = 0; int* oldTryCounter = NULL; if (sigsetjmp(localSigjmpBuf, 1) != 0) { + // 关闭所有打开的文件 gstrace_tryblock_exit(true, oldTryCounter); /* Since not using PG_TRY, must reset error stack by hand */ - t_thrd.log_cxt.error_context_stack = NULL; + t_thrd.log_cxt.error_context_stack = NULL; // 手动重置错误堆栈 - t_thrd.log_cxt.call_stack = NULL; + t_thrd.log_cxt.call_stack = NULL; // 清空调用栈 /* Prevent interrupts while cleaning up */ - HOLD_INTERRUPTS(); + HOLD_INTERRUPTS(); // 在执行清理操作期间禁止中断 /* Forget any pending QueryCancel request */ - t_thrd.int_cxt.QueryCancelPending = false; - (void)disable_sig_alarm(true); + t_thrd.int_cxt.QueryCancelPending = false; // 表示取消请求不再挂起 + (void)disable_sig_alarm(true); // 禁用信号闹钟,确保不会在错误处理期间触发超时信号 t_thrd.int_cxt.QueryCancelPending = false; /* Report the error to the server log */ - EmitErrorReport(); + EmitErrorReport(); // 报告错误到服务器日志 /* * Abort the current transaction in order to recover. */ + // 中止当前事务以进行恢复,任何未提交的更改将被回滚,以确保数据库的一致性 AbortCurrentTransaction(); /* release resource held by lsc */ - AtEOXact_SysDBCache(false); + AtEOXact_SysDBCache(false); // 释放由系统数据库缓存持有的资源 - LWLockReleaseAll(); + LWLockReleaseAll(); // 释放锁 /* * Now return to normal top-level context and clear ErrorContext for * next time. */ - (void)MemoryContextSwitchTo(workMxt); + (void)MemoryContextSwitchTo(workMxt); // 切换程序的内存上下文 + // 清除错误状态,将之前可能记录的错误信息和状态重置为空 FlushErrorState(); /* Flush any leaked data in the top-level context */ + // 重置并删除了之前创建的内存上下文 workMxt 及其所有子上下文 + // 确保在异常处理完成后释放已分配的内存,以避免内存泄漏 MemoryContextResetAndDeleteChildren(workMxt); /* Now we can allow interrupts again */ - RESUME_INTERRUPTS(); + RESUME_INTERRUPTS(); // 允许中断信号再次被捕获 /* if in shutdown mode, no need for anything further; just go away */ if (t_thrd.rbcleaner_cxt.got_SIGTERM) { goto shutdown; } - lastError = true; + lastError = true; // 表示上一次执行的操作出现了错误 } - + // 记录当前的错误处理堆栈计数器 oldTryCounter = gstrace_tryblock_entry(&curTryCounter); /* We can now handle ereport(ERROR) */ + // 将错误处理的跳转缓冲区设置为当前线程的错误处理堆栈 t_thrd.log_cxt.PG_exception_stack = &localSigjmpBuf; /* * Unblock signals (they were blocked when the postmaster forked us) */ + // 解除信号屏蔽,允许信号再次被捕获 gs_signal_setmask(&t_thrd.libpq_cxt.UnBlockSig, NULL); + // 解除对 SIGUSR2 信号的屏蔽,允许接收 SIGUSR2 信号 (void)gs_signal_unblock_sigusr2(); nextTimestamp = GetCurrentTimestamp() + USECS_PER_MINUTE; /* loop until shutdown request */ + // 循环直到收到了关闭请求或Tcap 版本不再可用 while (!t_thrd.rbcleaner_cxt.got_SIGTERM && ENABLE_TCAP_RECYCLEBIN) { int rc; /* Clear any already-pending wakeups */ - ResetLatch(&t_thrd.proc->procLatch); + ResetLatch(&t_thrd.proc->procLatch); // 清除任何已经挂起的等待 /* * Wait until 3s naptime expires or we get some type of signal (all the @@ -946,29 +1357,36 @@ NON_EXEC_STATIC void RbCleanerMain() * Note: If an error occurred during the last message processing, we will * wait less time 10ms to continue the next process message quickly. */ + + // 等待信号或超时 等待以下条件之一发生: + // 收到了 WL_LATCH_SET 信号,表示等待标志已经被设置。 + // 收到了 WL_TIMEOUT 信号,表示经过了一段时间(由 SNAP_CAPTURE_INTERVAL 控制)。 + // 收到了 WL_POSTMASTER_DEATH 信号,表示 postmaster 进程已经退出。 rc = WaitLatch(&t_thrd.proc->procLatch, WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH, lastError ? RBCLEANER_INTERVAL_QUICK_MS : RBCLEANER_INTERVAL_MS); lastError = false; - + // 处理不同的中断情况,根据传入的返回码 rc 执行相应的操作 RbCleanerProcInterrupts(rc); - if (TcapFeatureAvail()) { + if (TcapFeatureAvail()) { // 检查是否支持 TcapFeature /* Do the hard work. */ - RbCleanerPurge(); + RbCleanerPurge(); // 执行实际清理工作 /* Do auto purge. */ - TimestampTz now = GetCurrentTimestamp(); + TimestampTz now = GetCurrentTimestamp(); // 获取当前的时间戳 + // 检查当前时间是否满足某些条件,控制自动清理的频率 if (now >= nextTimestamp || now + USECS_PER_MINUTE < nextTimestamp) { - nextTimestamp = now + USECS_PER_MINUTE; - RbAutoPurge(); + nextTimestamp = now + USECS_PER_MINUTE; // 更新时间戳 + RbAutoPurge(); // 执行自动清理任务 } } - + // 重置并删除了之前创建的内存上下文 workMxt 及其所有子上下文 MemoryContextResetAndDeleteChildren(workMxt); } shutdown: + // 记录一条调试级别的日志消息,指示 rbcleaner 进程正在关闭 elog(LOG, "rbcleaner is shutting down."); proc_exit(0); @@ -978,90 +1396,138 @@ shutdown: * Recyclebin Cleaner WorkerMain Code ********************************************************************/ +/* + * 功能:判断当前进程是否是 Recyclebin Cleaner Worker 进程 + * + * 参数:无 + * + * 返回值: + * bool类型 + * 通过检查 t_thrd.role 的值来确定进程的角色是否为 RBWORKER + * 如果是,则返回 true,表示当前进程是 Worker 进程,否则返回 false + */ bool IsRbWorkerProcess(void) { return t_thrd.role == RBWORKER; } +/* + * 功能:SIGQUIT 信号处理函数 + * + * 参数: + * SIGNAL_ARGS: 信号处理程序的参数列表,提供关于触发信号的上下文信息 + * + * 返回值:无 + */ static void RbWorkerSigquitHandler(SIGNAL_ARGS) { + // 将 SIGQUIT 添加到阻止的信号集,以防止嵌套调用 sigaddset(&t_thrd.libpq_cxt.BlockSig, SIGQUIT); /* prevent nested calls */ gs_signal_setmask(&t_thrd.libpq_cxt.BlockSig, NULL); - on_exit_reset(); + on_exit_reset(); // 清除任何已注册的退出回调 - gs_thread_exit(2); + gs_thread_exit(2); // 退出 - elog(LOG, "rbworker signaled: SIGQUIT"); + elog(LOG, "rbworker signaled: SIGQUIT"); // 记录一条消息,指示工作者进程接收到 SIGQUIT 信号 } +/* + * 功能:在工作者进程退出时执行清理操作 + * + * 参数: + * code:退出代码 + * arg:未使用的参数 + * + * 返回值:无 + */ static void RbWorkerQuitAndClean(int code, Datum arg) { - RbWorkerInfo *workerInfo = RbGetWorkerInfo(); + RbWorkerInfo *workerInfo = RbGetWorkerInfo(); // 获取工作者进程的 RbWorkerInfo 结构 + // 有效地标记了工作者进程不再处于活动状态或正在运行 pg_atomic_write_u64(&workerInfo->rbworkerPid, 0); } +/* + * 功能:根据提供的消息 ID (id) 执行实际的清理任务 + * + * 参数: + * id:消息的唯一标识符 + * + * 返回值:无 + */ static void RbWorkerPurge(int64 id) { - PurgeMsgType type = RbMsg(id)->req.type; + PurgeMsgType type = RbMsg(id)->req.type; // 获取消息类型 + // 根据消息类型执行不同的清理操作 switch (type) { case PURGE_MSG_TYPE_DML: - TrPurgeTablespaceDML(id); + TrPurgeTablespaceDML(id); // 执行清理表空间的操作 break; case PURGE_MSG_TYPE_TABLESPACE: case PURGE_MSG_TYPE_CAS_TABLESPACE: - TrPurgeTablespace(id); + TrPurgeTablespace(id); // 执行相应的表空间清理操作 break; case PURGE_MSG_TYPE_RECYCLEBIN: - TrPurgeRecyclebin(id); + TrPurgeRecyclebin(id); // 执行回收站清理操作 break; case PURGE_MSG_TYPE_CAS_SCHEMA: - TrPurgeSchema(id); + TrPurgeSchema(id); // 执行模式清理 break; case PURGE_MSG_TYPE_CAS_USER: - TrPurgeUser(id); + TrPurgeUser(id); // 执行用户清理操作 break; case PURGE_MSG_TYPE_AUTO: - TrPurgeAuto(id); + TrPurgeAuto(id); // 执行自动清理操作 break; default: elog(ERROR, "unknown purge message type: %d", type); break; } - + // 设置消息的状态,表示清理操作已完成 RbMsgSetStatus(id, PURGE_MSG_STATUS_STEPDONE, false); + // 设置了工作者进程的Latch,以通知主清理进程(回收站清理器)已完成清理任务 SetLatch(&t_thrd.rbcleaner_cxt.RbCleanerShmem->workerInfo.latch); } +/* + * 功能:rbcleaner 进程的主要执行函数 + * + * 参数:无 + * + * 返回值:无 + */ NON_EXEC_STATIC void RbWorkerMain() { - sigjmp_buf localSigjmpBuf; - RbWorkerInfo *workInfo = RbGetWorkerInfo(); + sigjmp_buf localSigjmpBuf; // 用于处理异常跳转 + RbWorkerInfo *workInfo = RbGetWorkerInfo(); // 获取工作者进程的信息 + // 使用原子操作将当前进程的进程ID(PID)写入工作者进程信息中的 rbworkerPid 字段 pg_atomic_write_u64(&workInfo->rbworkerPid, t_thrd.proc_cxt.MyProcPid); /* we are a postmaster subprocess now */ - IsUnderPostmaster = true; - t_thrd.role = RBWORKER; + IsUnderPostmaster = true; // 表示当前进程是在 postmaster 进程下运行的 + t_thrd.role = RBWORKER; // 标识线程 /* tell datasender we update the values. */ - pg_memory_barrier(); + pg_memory_barrier();// 执行内存屏障操作,用于确保前面的写操作对其他线程可见 /* reset t_thrd.proc_cxt.MyProcPid */ - t_thrd.proc_cxt.MyProcPid = gs_thread_self(); + t_thrd.proc_cxt.MyProcPid = gs_thread_self(); // 正确标识线程 /* record Start Time for logging */ - t_thrd.proc_cxt.MyStartTime = time(NULL); + t_thrd.proc_cxt.MyStartTime = time(NULL); // 记录了当前线程的启动时间 - t_thrd.proc_cxt.MyProgName = "RbWorker"; + t_thrd.proc_cxt.MyProgName = "RbWorker"; // 设置了当前线程的程序名称 /* Identify myself via ps */ - init_ps_display("rbworker process", "", "", ""); + init_ps_display("rbworker process", "", "", ""); // 初始化进程状态显示 ereport(DEBUG1, (errmsg("rbworker started"))); - SetProcessingMode(InitProcessing); + SetProcessingMode(InitProcessing); // 设置处理模式为初始化处理模式 + // 设置信号处理程序 (void)gspqsignal(SIGHUP, SIG_IGN); (void)gspqsignal(SIGINT, StatementCancelHandler); (void)gspqsignal(SIGTERM, die); @@ -1084,100 +1550,113 @@ NON_EXEC_STATIC void RbWorkerMain() BaseInit(); #ifndef EXEC_BACKEND + // 不在执行后台情况下,创建了一个 PGPROC 结构体,并对其进行初始化 InitProcess(); #endif - on_proc_exit(RbWorkerQuitAndClean, 0); - + on_proc_exit(RbWorkerQuitAndClean, 0); // 注册一个进程退出时的回调函数 + // 设置当前进程的数据库和用户信息 t_thrd.proc_cxt.PostInit->SetDatabaseAndUser((char*)pstrdup(NameStr(workInfo->dbName)), InvalidOid, NULL); t_thrd.proc_cxt.PostInit->InitRbWorker(); - + // 设置当前进程的处理模式为 "NormalProcessing",表示正常的处理模式 SetProcessingMode(NormalProcessing); - + // 创建一个内存上下文 MemoryContext workMxt = AllocSetContextCreate( t_thrd.top_mem_cxt, "RbWorker", ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); - (void)MemoryContextSwitchTo(workMxt); - + (void)MemoryContextSwitchTo(workMxt); // 切换到该上下文 + // 处理异常情况 int curTryCounter; int* oldTryCounter = NULL; if (sigsetjmp(localSigjmpBuf, 1) != 0) { + // 关闭所有打开的文件 gstrace_tryblock_exit(true, oldTryCounter); /* Since not using PG_TRY, must reset error stack by hand */ - t_thrd.log_cxt.error_context_stack = NULL; + t_thrd.log_cxt.error_context_stack = NULL; // 手动重置错误堆栈 - t_thrd.log_cxt.call_stack = NULL; + t_thrd.log_cxt.call_stack = NULL; // 清空调用栈 /* Prevent interrupts while cleaning up */ - HOLD_INTERRUPTS(); + HOLD_INTERRUPTS(); // 在执行清理操作期间禁止中断 /* Forget any pending QueryCancel request */ - t_thrd.int_cxt.QueryCancelPending = false; - (void)disable_sig_alarm(true); + t_thrd.int_cxt.QueryCancelPending = false; // 表示取消请求不再挂起 + (void)disable_sig_alarm(true); // 禁用信号闹钟,确保不会在错误处理期间触发超时信号 t_thrd.int_cxt.QueryCancelPending = false; /* Report the error to the server log */ - EmitErrorReport(); + EmitErrorReport(); // 报告错误到服务器日志 /* Report the error to rbcleaner */ + // 设置消息的错误状态,将错误信息设置为消息的状态,用于通知主清理进程 RbMsgSetStatusErr(workInfo->id, PURGE_MSG_STATUS_STEPDONE, geterrcode(), Geterrmsg()); + // 设置工作者进程的Latch,以通知主清理进程消息已处理完毕 SetLatch(&t_thrd.rbcleaner_cxt.RbCleanerShmem->workerInfo.latch); /* * Abort the current transaction in order to recover. */ + // 中止当前事务以进行恢复,任何未提交的更改将被回滚,以确保数据库的一致性 AbortCurrentTransaction(); /* release resource held by lsc */ - AtEOXact_SysDBCache(false); + AtEOXact_SysDBCache(false); // 释放由系统数据库缓存持有的资源 - LWLockReleaseAll(); + LWLockReleaseAll(); // 释放锁 /* * Now return to normal top-level context and clear ErrorContext for * next time. */ - (void)MemoryContextSwitchTo(workMxt); + (void)MemoryContextSwitchTo(workMxt); // 切换程序的内存上下文 + // 清除错误状态,将之前可能记录的错误信息和状态重置为空 FlushErrorState(); /* Flush any leaked data in the top-level context */ + // 重置并删除了之前创建的内存上下文 workMxt 及其所有子上下文 + // 确保在异常处理完成后释放已分配的内存,以避免内存泄漏 MemoryContextResetAndDeleteChildren(workMxt); /* Now we can allow interrupts again */ - RESUME_INTERRUPTS(); + RESUME_INTERRUPTS(); // 允许中断信号再次被捕获 /* just go away */ goto shutdown; } RbMsgSetStatus(workInfo->id, PURGE_MSG_STATUS_STEPIN); - + // 记录当前的错误处理堆栈计数器 oldTryCounter = gstrace_tryblock_entry(&curTryCounter); /* We can now handle ereport(ERROR) */ + // 将错误处理的跳转缓冲区设置为当前线程的错误处理堆栈 t_thrd.log_cxt.PG_exception_stack = &localSigjmpBuf; /* * Unblock signals (they were blocked when the postmaster forked us) */ + // 解除信号屏蔽,允许信号再次被捕获 gs_signal_setmask(&t_thrd.libpq_cxt.UnBlockSig, NULL); + // 解除对 SIGUSR2 信号的屏蔽,允许接收 SIGUSR2 信号 (void)gs_signal_unblock_sigusr2(); /* Set lockwait_timeout/update_lockwait_timeout to 30s avoid unexpected suspend. */ + // 设置配置选项,将锁等待超时时间设置为 30 秒,以避免意外的挂起 SetConfigOption("lockwait_timeout", "30s", PGC_SUSET, PGC_S_OVERRIDE); SetConfigOption("update_lockwait_timeout", "30s", PGC_SUSET, PGC_S_OVERRIDE); /* Do the hard work */ RbWorkerPurge(workInfo->id); - + // 重置并删除了之前创建的内存上下文 workMxt 及其所有子上下文 MemoryContextResetAndDeleteChildren(workMxt); shutdown: + // 记录一条调试级别的日志消息,指示 rbworker 进程正在关闭 elog(DEBUG1, "rbworker is shutting down."); proc_exit(0); diff --git a/src/gausskernel/process/postmaster/snapcapturer.cpp b/src/gausskernel/process/postmaster/snapcapturer.cpp index 9f46cbf60..b33293f1c 100644 --- a/src/gausskernel/process/postmaster/snapcapturer.cpp +++ b/src/gausskernel/process/postmaster/snapcapturer.cpp @@ -68,6 +68,14 @@ static const int SNAP_CAPTURE_INTERVAL = 3; * TxnSnapCapShmemSize * Size of TxnSnapCapturer related shared memory */ + +/* + * 功能:计算并返回与TxnSnapCapturer组件相关的共享内存大小 + * + * 参数:无 + * + * 返回值:返回与TxnSnapCapturer组件相关的共享内存大小 + */ Size TxnSnapCapShmemSize() { return sizeof(TxnSnapCapShmemStruct); @@ -77,19 +85,28 @@ Size TxnSnapCapShmemSize() * TxnSnapCapShmemInit * Allocate and initialize TxnSnapCapturer -related shared memory */ + +/* + * 功能:初始化与TxnSnapCapturer组件相关的共享内存 + * + * 参数:无 + * + * 返回值:无 + */ void TxnSnapCapShmemInit(void) { bool found = false; - + // 检查名为 "TxnSnapCapturer Data" 的共享内存是否已存在。如果存在,则将 found 变量设置为 true t_thrd.snapcapturer_cxt.snapCapShmem = (TxnSnapCapShmemStruct *)ShmemInitStruct("TxnSnapCapturer Data", sizeof(TxnSnapCapShmemStruct), &found); - if (!found) { + if (!found) { // 如果共享内存不存在 /* * First time through, so initialize. Note that we zero the whole * requests array; this is so that CompactCheckpointerRequestQueue * can assume that any pad bytes in the request structs are zeroes. */ + // 初始化共享内存 errno_t ret = memset_s(t_thrd.snapcapturer_cxt.snapCapShmem, sizeof(TxnSnapCapShmemStruct), 0, sizeof(TxnSnapCapShmemStruct)); securec_check(ret, "\0", "\0"); @@ -103,181 +120,312 @@ void TxnSnapCapShmemInit(void) */ /* SIGHUP: set flag to re-read config file at next convenient time */ + +/* + *功能:处理 SIGHUP 信号,用于重新读取配置文件 + * + * 参数: + * SIGNAL_ARGS: 信号处理程序的参数列表,提供关于触发信号的上下文信息 + * + * 返回值: + * 无 + */ static void TxnSnapSighupHandler(SIGNAL_ARGS) { - int saveErrno = errno; + int saveErrno = errno; // 保存当前的错误码 - t_thrd.snapcapturer_cxt.got_SIGHUP = true; + t_thrd.snapcapturer_cxt.got_SIGHUP = true; // 表示接收到 SIGHUP 信号 - if (t_thrd.proc) - SetLatch(&t_thrd.proc->procLatch); + if (t_thrd.proc) // 如果存在进程 + SetLatch(&t_thrd.proc->procLatch); // 设置该进程的进程latch - errno = saveErrno; + errno = saveErrno; // 恢复之前保存的错误码 } +/* + *功能:启动事务快照捕获器线程并返回线程的标识符 + * + * 参数:无 + * + * 返回值: + * 返回线程的标识符 ThreadId + */ ThreadId StartTxnSnapCapturer(void) { - if (!IsPostmasterEnvironment) { + if (!IsPostmasterEnvironment) { // 检查是否处于Postmaster环境 return 0; } - - if (canAcceptConnections(false) == CAC_OK) { + // 检查当前是否可以接受数据库连接 (只检查是否可以接受连接,而不实际接受连接) + if (canAcceptConnections(false) == CAC_OK) { + // 如果可以接受连接,返回一个表示新线程的 ThreadId return initialize_util_thread(TXNSNAP_CAPTURER); } - + // 生成一个LOG消息级别的日志消息,表示不准备启动快照捕获器 ereport(LOG, (errmsg("not ready to start snapshot capturer."))); return 0; } +/* + *功能:检查当前进程是否为事务快照捕获器进程 + * + * 参数:无 + * + * 返回值: + * bool类型,是则返回true,表示当前进程是事务快照捕获器进程;否则返回true + */ bool IsTxnSnapCapturerProcess(void) { + // 比较全局变量 t_thrd.role 的值与 TXNSNAP_CAPTURER 常量来确定当前进程的角色 return t_thrd.role == TXNSNAP_CAPTURER; } +/* + *功能:检查当前进程是否为事务快照工作进程 + * + * 参数:无 + * + * 返回值: + * 是则返回 true,表示当前进程是事务快照工作进程,否则返回 false + */ bool IsTxnSnapWorkerProcess(void) { + // 通过比较全局变量 t_thrd.role 的值与 TXNSNAP_WORKER 常量来确定当前进程的角色 return t_thrd.role == TXNSNAP_WORKER; } +/* + *功能:从文本中解析出一个 GTM_Timeline 值 + * + * 参数: + * prefix:要匹配的前缀字符串 + * s:指向输入文本的指针 + * + * 返回值: + * 返回解析出的 GTM_Timeline 值 + */ static GTM_Timeline TxnSnapParseTimelineFromText(const char* prefix, char** s) { char* ptr = *s; int prefixlen = strlen(prefix); GTM_Timeline val; - + // 检查输入文本是否以 prefix 开头 if (strncmp(ptr, prefix, prefixlen) != 0) { + // 如果不是,会生成一个错误消息并抛出异常 ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid snapshot data in gs_txn_snapshot"))); } - + // 将指针 ptr 向前移动,跳过已匹配的前缀部分 ptr += prefixlen; + // 从文本中提取一个无符号整数 GTM_Timeline if (sscanf_s(ptr, "%u", &val) != 1) { + // 如果提取失败,会生成错误消息并抛出异常 ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid snapshot data in gs_txn_snapshot"))); } - + // 在文本中查找下一个换行符的位置,并将 ptr 指向该位置 ptr = strchr(ptr, '\n'); if (ptr == NULL) { + // 如果找不到换行符,生成一个错误消息并抛出异常 ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid snapshot data in gs_txn_snapshot"))); } - *s = ptr + 1; - return val; + *s = ptr + 1; // 将 s 指针指向下一行的开头,以便在下次调用时处理下一行的文本 + return val; // 返回解析出的 GTM_Timeline 值 } +/* + *功能:从文本中解析出一个 TransactionId(事务标识符) + * + * 参数: + * prefix:要匹配的前缀字符串 + * s:指向输入文本的指针 + * + * 返回值: + * 返回解析出的 TransactionId 值 + */ static TransactionId TxnSnapParseXidFromText(const char* prefix, char** s) { char* ptr = *s; - int prefixlen = strlen(prefix); + int prefixlen = strlen(prefix); // 计算 prefix 字符串的长度 TransactionId val; - + // 检查文本是否以指定的前缀字符串 prefix 开头 if (strncmp(ptr, prefix, prefixlen) != 0) { + // 如果不是,生成一个错误消息并抛出异常 ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid snapshot data in gs_txn_snapshot"))); } - + // 将指针 ptr 向前移动,跳过已匹配的前缀部分 ptr += prefixlen; + // 解析一个事务标识符 if (sscanf_s(ptr, XID_FMT, &val) != 1) { + // 如果解析失败,生成一个错误消息并抛出异常 ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid snapshot data in gs_txn_snapshot"))); } - + // 查找下一个换行符的位置,并将 ptr 指向该位置 ptr = strchr(ptr, '\n'); if (ptr == NULL) { + // 如果找不到换行符,生成一个错误消息并抛出异常 ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid snapshot data in gs_txn_snapshot"))); } - + // 将 s 指针指向下一行的开头,以便在下次调用时处理下一行的文本 *s = ptr + 1; - return val; + return val; // 返回解析出的 TransactionId 值 } +/* + *功能:从文本中解析出一个整数值并返回 + * + * 参数: + * prefix:要匹配的前缀字符串 + * s:指向输入文本的指针 + * + * 返回值: + * 返回解析出的整数值 + */ static int TxnSnapParseIntFromText(const char* prefix, char** s) { char* ptr = *s; - int prefixlen = strlen(prefix); + int prefixlen = strlen(prefix); // 计算 prefix 字符串的长度 int val; - + // 检查文本是否以指定的前缀字符串 prefix 开头 if (strncmp(ptr, prefix, prefixlen) != 0) + // 如果不是,生成一个错误消息并抛出异常 ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid snapshot data in gs_txn_snapshot"))); + // 将指针 ptr 向前移动,跳过已匹配的前缀部分 ptr += prefixlen; + // 解析一个事务标识符 if (sscanf_s(ptr, "%d", &val) != 1) + // 如果解析失败,生成一个错误消息并抛出异常 ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid snapshot data in gs_txn_snapshot"))); + // 查找下一个换行符的位置,并将 ptr 指向该位置 ptr = strchr(ptr, '\n'); if (ptr == NULL) + // 如果找不到换行符,生成一个错误消息并抛出异常 ereport(ERROR, (errcode(ERRCODE_INVALID_TEXT_REPRESENTATION), errmsg("invalid snapshot data in gs_txn_snapshot"))); + // 将 s 指针指向下一行的开头,以便在下次调用时处理下一行的文本 *s = ptr + 1; - return val; + return val; // 返回解析出的整数值 } +/* + *功能:将快照信息序列化到一个字符串缓冲区 + * + * 参数: + * Snapshot snap:表示要序列化的快照 + * StringInfo buf:表示用于存储序列化结果的字符串缓冲区 + * + * 返回值:无 + */ static void TxnSnapSerialize(Snapshot snap, StringInfo buf) { + // 将字符串追加到 buf 中 + // 将 xmin 的值格式化为带有前缀 "xmin:" 和换行符的字符串,然后追加到缓冲区中 appendStringInfo(buf, "xmin:" XID_FMT "\n", snap->xmin); + // 将 xmax 的值格式化为带有前缀 "xmax:" 和换行符的字符串,然后追加到缓冲区中 appendStringInfo(buf, "xmax:" XID_FMT "\n", snap->xmax); + // 将 snapshotcsn 的值格式化为带有前缀 "snapshotcsn:" 和换行符的字符串,然后追加到缓冲区中 appendStringInfo(buf, "snapshotcsn:" XID_FMT "\n", snap->snapshotcsn); + // 将 timeline 的值格式化为带有前缀 "timeline:" 和换行符的字符串,然后追加到缓冲区中 appendStringInfo(buf, "timeline:%u\n", snap->timeline); + // 将 takenDuringRecovery 的值格式化为带有前缀 "rec:" 和换行符的字符串,然后追加到缓冲区中 appendStringInfo(buf, "rec:%u\n", snap->takenDuringRecovery); - buf->data[buf->len] = '\0'; + buf->data[buf->len] = '\0'; // 在缓冲区的末尾添加一个空字符 } +/* + *功能:从文本中反序列化快照信息,并将解析出的值存储到 Snapshot 结构体中 + * 用于从文本中还原数据结构 + * + * 参数: + * buf:表示包含序列化数据的文本字符串 + * snap:表示要存储解析结果的快照结构体 + * + * 返回值:无 + */ void TxnSnapDeserialize(char *buf, Snapshot snap) { - char *tmpBuf = buf; + char *tmpBuf = buf; // 用于追踪文本的解析位置 + // 解析以 "xmin:" 开头的文本,将结果存储到 snap->xmin 中 snap->xmin = TxnSnapParseXidFromText("xmin:", &tmpBuf); + // 解析以 "xmax:" 开头的文本,将结果存储到 snap->xmax 中 snap->xmax = TxnSnapParseXidFromText("xmax:", &tmpBuf); + // 解析以 "snapshotcsn:" 开头的文本,将结果存储到 snap->snapshotcsn 中 snap->snapshotcsn = TxnSnapParseXidFromText("snapshotcsn:", &tmpBuf); + // 解析以 "timeline:" 开头的文本,将结果存储到 snap->timeline 中 snap->timeline = TxnSnapParseTimelineFromText("timeline:", &tmpBuf); + // 解析以 "rec:" 开头的文本,将结果存储到 snap->takenDuringRecovery 中 snap->takenDuringRecovery = TxnSnapParseIntFromText("rec:", &tmpBuf); } /* * Get the current snapshot and record to pg_snapshot. */ + +/* + *功能:将当前快照信息插入到数据库的 pg_snapshot 表中 + * + * 参数:无 + * + * 返回值:无 + */ static void TxnSnapInsert(void) { - Relation rel; - HeapTuple tup; - bool nulls[Natts_pg_snapshot] = { false }; - Datum values[Natts_pg_snapshot] = { 0 }; - Snapshot snap = (Snapshot)palloc0(sizeof(SnapshotData)); - StringInfoData buf; + Relation rel; // 用于表示要插入数据的数据库表 + HeapTuple tup; // 用于表示要插入的数据行 + // 用于表示插入数据行中各列的空值情况 false表示没有空值 + bool nulls[Natts_pg_snapshot] = { false }; + Datum values[Natts_pg_snapshot] = { 0 }; // 用于存储要插入的数据行的各个列的值 + Snapshot snap = (Snapshot)palloc0(sizeof(SnapshotData)); // 用于存储快照信息 + StringInfoData buf; // 用于存储序列化后的快照信息的文本 - (void)GetSnapshotData(snap, false); + (void)GetSnapshotData(snap, false); // 获取当前快照的数据 - initStringInfo(&buf); - TxnSnapSerialize(snap, &buf); + initStringInfo(&buf); // 初始化 buf,以便在后续步骤中将序列化的快照信息追加到其中 + TxnSnapSerialize(snap, &buf); // 将快照信息序列化并追加到 buf 中 + // 设置数据行的各列值 + // 设置为当前时间戳 values[Anum_pg_snapshot_snptime - 1] = TimestampTzGetDatum(GetCurrentTimestamp()); + // 设置为 snap->xmin 的值 values[Anum_pg_snapshot_snpxmin - 1] = Int64GetDatum(snap->xmin); + // 设置为 snap->snapshotcsn 的值 values[Anum_pg_snapshot_snpcsn - 1] = Int64GetDatum(snap->snapshotcsn); + // 设置为 buf.data 的值,这是序列化后的快照信息 values[Anum_pg_snapshot_snpsnapshot - 1] = CStringGetTextDatum(buf.data); + // 打开数据库中的 pg_snapshot 表,并将其关系对象赋值 rel = heap_open(SnapshotRelationId, RowExclusiveLock); + // 使用给定的列描述符、列值和空值信息创建一个堆元组 tup = heap_form_tuple(RelationGetDescr(rel), values, nulls); - (void)simple_heap_insert(rel, tup); - CatalogUpdateIndexes(rel, tup); + (void)simple_heap_insert(rel, tup); // 将创建的数据行插入到数据库表中 + CatalogUpdateIndexes(rel, tup); // 更新数据库表的索引 - heap_close(rel, RowExclusiveLock); + heap_close(rel, RowExclusiveLock); // 关闭数据库表 + // 释放之前分配的 snap 结构体的内存,包括其深层次的内存 FreeSnapshotDeepForce(snap); + // 释放序列化缓冲区 buf 的内存 pfree(buf.data); + // 释放堆元组 tup 的内存 heap_freetuple_ext(tup); return; @@ -287,30 +435,45 @@ static void TxnSnapInsert(void) * Delete the out-date snapshots, that is, * DELETE FROM pg_snapshot WHERE snptime <= now() - '3 days'; */ + +/* + *功能:删除过期的快照数据 + * + * 参数:无 + * + * 返回值:无 + */ static void TxnSnapDelete(void) { -#define TXNSNAP_EXTRA_RETRNTION_TIME 900 - Relation rel; - ScanKeyData skey[2]; - SysScanDesc sd; - HeapTuple tup; +#define TXNSNAP_EXTRA_RETRNTION_TIME 900 // 将额外的保留时间设置为 900 秒 + Relation rel; // 用于表示要插入数据的数据库表 + ScanKeyData skey[2]; // 用于定义扫描条件 + SysScanDesc sd; // 用于执行系统表扫描 + HeapTuple tup; // 用于表示要插入的数据行 /* Retent snapshots for up to undo_retention_time + 15min. */ + // 计算了快照的最大保留时间 + // 包括了配置的 undo_retention_time 和额外的 15 分钟(900 秒) const int64 snapRetentionMs = 1000L * (u_sess->attr.attr_storage.undo_retention_time + TXNSNAP_EXTRA_RETRNTION_TIME); + // 表示要删除的快照的最早保留时间点,即当前时间减去最大保留时间 TimestampTz ft = TimestampTzPlusMilliseconds(GetCurrentTimestamp(), snapRetentionMs * -1); + // 打开数据库中的 pg_snapshot 表,并将其关系对象赋值 rel = heap_open(SnapshotRelationId, RowExclusiveLock); + // 初始化扫描键 skey,用于查找小于或等于 ft 的快照数据行 ScanKeyInit(&skey[0], Anum_pg_snapshot_snptime, BTLessEqualStrategyNumber, F_TIMESTAMP_LE, TimestampTzGetDatum(ft)); + // 使用定义的扫描条件 skey,开始对数据库表的系统扫描 sd = systable_beginscan(rel, SnapshotTimeCsnIndexId, true, NULL, 1, skey); + // 遍历满足扫描条件的快照数据行 通过 systable_getnext 获取下一个符合条件的数据行 while ((tup = systable_getnext(sd)) != NULL) { - simple_heap_delete(rel, &tup->t_self); + simple_heap_delete(rel, &tup->t_self); // 删除了当前循环中指定的数据行 } - systable_endscan(sd); - heap_close(rel, RowExclusiveLock); + systable_endscan(sd); // 结束系统表扫描 + heap_close(rel, RowExclusiveLock); // 关闭数据库表 } /* @@ -318,63 +481,98 @@ static void TxnSnapDelete(void) * * maintains the sys table pg_snapshot. */ + +/* + *功能:维护事务快照工作进程 + * + * 参数:无 + * + * 返回值:无 + */ static void TxnSnapWorkerImpl(void) { - int retentionTime = u_sess->attr.attr_storage.undo_retention_time; - TimestampTz result; + int retentionTime = u_sess->attr.attr_storage.undo_retention_time; // 表示保留事务快照的时间 + TimestampTz result; // 用于存储计算后的时间戳值 - StartTransactionCommand(); + StartTransactionCommand(); // 启动一个新的事务 - TxnSnapInsert(); + TxnSnapInsert(); // 将当前快照信息插入到数据库表 - TxnSnapDelete(); + TxnSnapDelete(); // 删除过期的快照信息 +// 根据是否定义了 HAVE_INT64_TIMESTAMP 宏来执行不同的分支 #ifdef HAVE_INT64_TIMESTAMP + // 计算时间戳,以 int64 类型表示 result = GetCurrentTimestamp() - retentionTime * (INT64CONST(1000000)); #else result = GetCurrentTimestamp() - retentionTime; #endif + // 将计算的时间点存储在全局变量中 g_instance.flashback_cxt.oldestXminInFlashback = TvFetchSnpxminRecycle(result); - CommitTransactionCommand(); + CommitTransactionCommand(); // 提交当前的事务 } +/* + * 功能:用于快照工作进程的退出和清理操作 + * + * 参数: + * code:表示退出代码 + * arg:表示附加的数据 + * + * 返回值:无 + */ static void TxnSnapWorkerQuitAndClean(int code, Datum arg) { + // 获取共享内存中的快照工作进程信息 TxnSnapWorkerInfo *workInfo = &t_thrd.snapcapturer_cxt.snapCapShmem->workerInfo; + // 使用原子操作将快照工作进程的进程ID设置为 0,表示没有正在运行的工作进程 pg_atomic_write_u64(&workInfo->snapworkerPid, 0); + // 将快照工作进程的状态设置为 TXNWORKER_DONE 表示工作进程已经完成其任务 *(volatile TxnWorkerStatus *)&workInfo->status = TXNWORKER_DONE; } +/* + * 功能:事务快照工作进程的主要执行逻辑 + * + * 参数:无 + * + * 返回值:无 + */ NON_EXEC_STATIC void TxnSnapWorkerMain() { - sigjmp_buf localSigjmpBuf; + sigjmp_buf localSigjmpBuf; // 用于处理异常跳转 + // 获取共享内存中的快照工作进程信息 TxnSnapWorkerInfo *workInfo = &t_thrd.snapcapturer_cxt.snapCapShmem->workerInfo; + // 使用原子操作将当前工作进程的进程ID设置到 snapworkerPid 字段中,标识当前工作进程 pg_atomic_write_u64(&workInfo->snapworkerPid, t_thrd.proc_cxt.MyProcPid); /* we are a postmaster subprocess now */ - IsUnderPostmaster = true; - t_thrd.role = TXNSNAP_WORKER; + IsUnderPostmaster = true; // 表示当前进程是在 postmaster 进程下运行的 + t_thrd.role = TXNSNAP_WORKER; // 表示它是事务快照工作进程 /* tell datasender we update the values. */ - pg_memory_barrier(); + pg_memory_barrier(); // 执行内存屏障操作,用于确保前面的写操作对其他线程可见 /* reset t_thrd.proc_cxt.MyProcPid */ - t_thrd.proc_cxt.MyProcPid = gs_thread_self(); + t_thrd.proc_cxt.MyProcPid = gs_thread_self(); // 正确标识线程 /* record Start Time for logging */ - t_thrd.proc_cxt.MyStartTime = time(NULL); + t_thrd.proc_cxt.MyStartTime = time(NULL); // 记录了当前进程的启动时间 - t_thrd.proc_cxt.MyProgName = "TxnSnapWorker"; + t_thrd.proc_cxt.MyProgName = "TxnSnapWorker"; // 设置了当前进程的程序名称 /* Identify myself via ps */ - init_ps_display("txnsnapworker process", "", "", ""); + init_ps_display("txnsnapworker process", "", "", ""); // 初始化进程状态显示 + // 记录了调试级别的日志,表示快照工作进程已经启动 elog(DEBUG1, "txnsnapworker started(%s)", NameStr(workInfo->dbName)); + // 检查是否配置了 PostAuthDelay if (u_sess->attr.attr_security.PostAuthDelay) + // 如果配置了延迟时间,那么工作进程将休眠相应的时间 pg_usleep(u_sess->attr.attr_security.PostAuthDelay * 1000000L); - SetProcessingMode(InitProcessing); + SetProcessingMode(InitProcessing); // 设置处理模式为初始化处理模式 /* * Properly accept or ignore signals the postmaster might send us @@ -384,26 +582,27 @@ NON_EXEC_STATIC void TxnSnapWorkerMain() * want to wait for the backends to exit, whereupon the postmaster will * tell us it's okay to shut down (via SIGUSR2). */ - (void)gspqsignal(SIGINT, StatementCancelHandler); - (void)gspqsignal(SIGTERM, die); - (void)gspqsignal(SIGQUIT, quickdie); - (void)gspqsignal(SIGALRM, SIG_IGN); - (void)gspqsignal(SIGPIPE, SIG_IGN); + // 设置信号处理程序 + (void)gspqsignal(SIGINT, StatementCancelHandler); // 用于处理中断信号 + (void)gspqsignal(SIGTERM, die); // 用于处理终止信号 正常终止进程 + (void)gspqsignal(SIGQUIT, quickdie); // 用于处理退出信号 + (void)gspqsignal(SIGALRM, SIG_IGN); // 忽略 + (void)gspqsignal(SIGPIPE, SIG_IGN); // 忽略 (void)gspqsignal(SIGUSR1, procsignal_sigusr1_handler); - (void)gspqsignal(SIGUSR2, SIG_IGN); - (void)gspqsignal(SIGFPE, FloatExceptionHandler); - (void)gspqsignal(SIGCHLD, SIG_DFL); - (void)gspqsignal(SIGHUP, TxnSnapSighupHandler); - (void)gspqsignal(SIGTTIN, SIG_DFL); - (void)gspqsignal(SIGTTOU, SIG_DFL); - (void)gspqsignal(SIGCONT, SIG_DFL); - (void)gspqsignal(SIGWINCH, SIG_DFL); + (void)gspqsignal(SIGUSR2, SIG_IGN); // 忽略 + (void)gspqsignal(SIGFPE, FloatExceptionHandler); // 用于处理浮点异常信号 + (void)gspqsignal(SIGCHLD, SIG_DFL); // 恢复默认行为 + (void)gspqsignal(SIGHUP, TxnSnapSighupHandler); // 用于处理终端挂断或配置文件重新加载信号 + (void)gspqsignal(SIGTTIN, SIG_DFL); // 恢复默认行为 + (void)gspqsignal(SIGTTOU, SIG_DFL); // 恢复默认行为 + (void)gspqsignal(SIGCONT, SIG_DFL); // 恢复默认行为 + (void)gspqsignal(SIGWINCH, SIG_DFL); // 恢复默认行为 /* We allow SIGQUIT (quickdie) at all times */ (void)sigdelset(&t_thrd.libpq_cxt.BlockSig, SIGQUIT); /* Early initialization */ - BaseInit(); + BaseInit(); // 早期初始化 /* * Create a per-backend PGPROC struct in shared memory, except in the @@ -412,14 +611,17 @@ NON_EXEC_STATIC void TxnSnapWorkerMain() * had to do some stuff with LWLocks). */ #ifndef EXEC_BACKEND + // 不在执行后台情况下,创建了一个 PGPROC 结构体,并对其进行初始化 InitProcess(); #endif - on_proc_exit(TxnSnapWorkerQuitAndClean, 0); + on_proc_exit(TxnSnapWorkerQuitAndClean, 0); // 注册在进程退出时执行的清理函数 + // 设置当前进程的数据库和用户信息 t_thrd.proc_cxt.PostInit->SetDatabaseAndUser((char*)pstrdup(NameStr(workInfo->dbName)), InvalidOid, NULL); t_thrd.proc_cxt.PostInit->InitTxnSnapWorker(); + // 设置当前进程的处理模式为 "NormalProcessing",表示正常的处理模式 SetProcessingMode(NormalProcessing); /* @@ -427,12 +629,14 @@ NON_EXEC_STATIC void TxnSnapWorkerMain() * that we can reset the context during error recovery and thereby avoid * possible memory leaks. */ + // 创建一个内存上下文 MemoryContext workMxt = AllocSetContextCreate( t_thrd.top_mem_cxt, "TxnSnapWorker", ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); + // 切换到该上下文 (void)MemoryContextSwitchTo(workMxt); /* @@ -440,36 +644,42 @@ NON_EXEC_STATIC void TxnSnapWorkerMain() * * See notes in postgres.c about the design of this coding. */ + // 处理异常情况 int curTryCounter = 0; int* oldTryCounter = NULL; if (sigsetjmp(localSigjmpBuf, 1) != 0) { + // 关闭所有打开的文件 gstrace_tryblock_exit(true, oldTryCounter); /* Since not using PG_TRY, must reset error stack by hand */ + // 手动重置错误堆栈 t_thrd.log_cxt.error_context_stack = NULL; - t_thrd.log_cxt.call_stack = NULL; + t_thrd.log_cxt.call_stack = NULL; // 清空调用栈 /* Prevent interrupts while cleaning up */ - HOLD_INTERRUPTS(); + HOLD_INTERRUPTS(); // 在执行清理操作期间禁止中断 /* Forget any pending QueryCancel request */ - t_thrd.int_cxt.QueryCancelPending = false; - (void)disable_sig_alarm(true); + t_thrd.int_cxt.QueryCancelPending = false; // 表示取消请求不再挂起 + (void)disable_sig_alarm(true); // 禁用信号闹钟,确保不会在错误处理期间触发超时信号 t_thrd.int_cxt.QueryCancelPending = false; /* Report the error to the server log */ - EmitErrorReport(); + EmitErrorReport(); // 报告错误到服务器日志 /* Notify txnsnapworker done in time */ + // 设置 latch 来通知 txnsnapworker 进程已完成 SetLatch(&t_thrd.snapcapturer_cxt.snapCapShmem->workerInfo.latch); /* * Abort the current transaction in order to recover. */ + // 中止当前事务以进行恢复,任何未提交的更改将被回滚,以确保数据库的一致性 AbortCurrentTransaction(); /* release resource held by lsc */ + // 释放由系统数据库缓存持有的资源 AtEOXact_SysDBCache(false); /* Notice: at the most time it isn't necessary to call because @@ -478,94 +688,125 @@ NON_EXEC_STATIC void TxnSnapWorkerMain() * example the following InitMultinodeExecutor() calling ) * maybe hold LWLocks unused. */ - LWLockReleaseAll(); + LWLockReleaseAll(); // 释放锁 /* * Now return to normal top-level context and clear ErrorContext for * next time. */ + // 切换程序的内存上下文 (void)MemoryContextSwitchTo(workMxt); + // 清除错误状态,将之前可能记录的错误信息和状态重置为空 FlushErrorState(); /* Flush any leaked data in the top-level context */ + // 重置并删除了之前创建的内存上下文 workMxt 及其所有子上下文 + // 确保在异常处理完成后释放已分配的内存,以避免内存泄漏 MemoryContextResetAndDeleteChildren(workMxt); /* Now we can allow interrupts again */ - RESUME_INTERRUPTS(); + RESUME_INTERRUPTS(); // 允许中断信号再次被捕获 /* just go away */ goto shutdown; } - + // 将当前进程状态设置为 TXNWORKER_STARTED *(volatile TxnWorkerStatus *)&workInfo->status = TXNWORKER_STARTED; - + // 记录当前的错误处理堆栈计数器 oldTryCounter = gstrace_tryblock_entry(&curTryCounter); /* We can now handle ereport(ERROR) */ + // 将错误处理的跳转缓冲区设置为当前线程的错误处理堆栈 t_thrd.log_cxt.PG_exception_stack = &localSigjmpBuf; /* * Unblock signals (they were blocked when the postmaster forked us) */ + // 解除信号屏蔽,允许信号再次被捕获 gs_signal_setmask(&t_thrd.libpq_cxt.UnBlockSig, NULL); + // 解除对 SIGUSR2 信号的屏蔽,允许接收 SIGUSR2 信号 (void)gs_signal_unblock_sigusr2(); /* Set lockwait_timeout/update_lockwait_timeout to 30s avoid unexpected suspend. */ + // 设置锁等待超时时间 以避免在等待锁的过程中发生意外的挂起 SetConfigOption("lockwait_timeout", "30s", PGC_SUSET, PGC_S_OVERRIDE); SetConfigOption("update_lockwait_timeout", "30s", PGC_SUSET, PGC_S_OVERRIDE); /* Do the hard work */ - if (TcapFeatureAvail()) { - TxnSnapWorkerImpl(); + if (TcapFeatureAvail()) { // 检查是否支持 TcapFeature + TxnSnapWorkerImpl(); // 执行实际的工作 } - + // 重置并删除了之前创建的内存上下文 workMxt 及其所有子上下文 MemoryContextResetAndDeleteChildren(workMxt); shutdown: + // 记录一条调试级别的日志消息,指示 txnsnapworker 进程正在关闭 elog(DEBUG1, "txnsnapworker shutting down(%s)", NameStr(workInfo->dbName)); proc_exit(0); } +/* + * 功能:初始化事务快照工作进程信息结构体 TxnSnapWorkerInfo + * + * 参数: + * dbName:数据库名称 + * + * 返回值: + * 返回指向初始化后的 workerInfo 结构体的指针 + */ static inline TxnSnapWorkerInfo *TxnSnapWorkerInfoInit(char *dbName) { TxnSnapWorkerInfo *workerInfo = &t_thrd.snapcapturer_cxt.snapCapShmem->workerInfo; errno_t rc; - workerInfo->status = TXNWORKER_DEFAULT; - + workerInfo->status = TXNWORKER_DEFAULT; // 设置默认状态 + // 设置工作进程所属的数据库名称 rc = strcpy_s(NameStr(workerInfo->dbName), NAMEDATALEN, dbName); securec_check_c(rc, "\0", "\0"); - + // 初始化 workerInfo 结构体中的 latch,用于实现线程之间的同步和通信 InitLatch(&workerInfo->latch); - return workerInfo; + return workerInfo; // 返回指向初始化后的 workerInfo 结构体的指针 } +/* + * 功能:处理不同类型的进程中断信号 + * + * 参数: + * rc:表示传递给函数的中断标志位,用于确定发生了哪些类型的中断 + * + * 返回值:无 + */ static void TxnSnapCapProcInterrupts(int rc) { /* Process sinval catchup interrupts that happened while sleeping */ - ProcessCatchupInterrupt(); + ProcessCatchupInterrupt(); // 处理 sinval catchup 中断 /* * Emergency bailout if postmaster has died. This is to avoid the * necessity for manual cleanup of all postmaster children. */ + // 如果 rc 中包含 WL_POSTMASTER_DEATH 标志位,表示 postmaster 进程已经终止 if (((unsigned int)rc) & WL_POSTMASTER_DEATH) { + // 紧急情况,终止当前进程,以避免需要手动清理所有 postmaster 的子进程 proc_exit(1); } /* the normal shutdown case */ + // 如果接收到了 SIGTERM 信号 if (t_thrd.snapcapturer_cxt.got_SIGTERM) { + // 记录一条日志消息 elog(LOG, "txnsnapcapturer is shutting down."); - proc_exit(0); + proc_exit(0); // 正常退出进程 } /* * reload the postgresql.conf */ + // 如果接收到了 SIGHUP 信号 if (t_thrd.snapcapturer_cxt.got_SIGHUP) { - t_thrd.snapcapturer_cxt.got_SIGHUP = false; - ProcessConfigFile(PGC_SIGHUP); + t_thrd.snapcapturer_cxt.got_SIGHUP = false; // 表示已处理 + ProcessConfigFile(PGC_SIGHUP); // 重新加载配置文件 postgresql.conf } } @@ -573,164 +814,213 @@ static void TxnSnapCapProcInterrupts(int rc) * TxnGetDatabaseList * Return a list of all databases found in pg_database. */ + +/* + * 功能:获取系统中所有数据库的列表 + * + * 参数:无 + * + * 返回值: + * 返回存储了数据库名的 dblist 列表 + */ static List* TxnGetDatabaseList(void) { - List* dblist = NIL; - Relation rel; - SysScanDesc sd; - HeapTuple tup; + List* dblist = NIL; // 用于存储数据库名的列表 + Relation rel; // 用于表示数据库关系 + SysScanDesc sd; // 用于系统扫描表 + HeapTuple tup; // 用于表示表中的元组 + // 打开 pg_database 表,并获取一个共享锁 rel = heap_open(DatabaseRelationId, AccessShareLock); + // 开始系统扫描 pg_database 表 sd = systable_beginscan(rel, InvalidOid, false, NULL, 0, NULL); - + // 遍历表中的每个元组 while ((tup = systable_getnext(sd)) != NULL) { Form_pg_database pgdatabase = (Form_pg_database)GETSTRUCT(tup); + // 检查数据库名是否为 "template0" 或 "template1" if (strcmp(NameStr(pgdatabase->datname), "template0") == 0 || strcmp(NameStr(pgdatabase->datname), "template1") == 0) { - continue; + continue; // 如果是,则跳过不处理 } + // 否则将其复制为 C 字符串,并将其添加到 dblist 列表中 dblist = lappend(dblist, pstrdup(NameStr(pgdatabase->datname))); } - systable_endscan(sd); - heap_close(rel, AccessShareLock); + systable_endscan(sd); // 结束系统扫描 + heap_close(rel, AccessShareLock); // 闭 pg_database 表,并释放共享锁 - return dblist; + return dblist; // 返回存储了数据库名的 dblist 列表 } +/* + * 功能:检查事务快照工作进程是否仍然存活 + * + * 参数:无 + * + * 返回值: + * bool类型,存活则返回true,否则返回false + */ static bool TxnSnapWorkerIsAlive() { + // 获取当前事务快照工作进程的信息 TxnSnapWorkerInfo *workInfo = &t_thrd.snapcapturer_cxt.snapCapShmem->workerInfo; - int ntries; - const int maxtries = 500; + int ntries; // 用于记录循环的尝试次数 + const int maxtries = 500; // 表示最大尝试次数 /* Wait 5s until txnsnapworker stopped. */ for (ntries = 0; ; ntries++) { + // 检查当前工作进程的进程ID是否等于0 if (pg_atomic_read_u64(&workInfo->snapworkerPid) == 0) { - return false; + return false; // 如果进程ID为0,表示工作进程已经停止 } - if (ntries >= maxtries) { + if (ntries >= maxtries) { // 检查循环的尝试次数是否大于或等于 maxtries + // 如果尝试次数达到上限,函数返回 true,表示工作进程仍然存活 return true; } /* wait 0.01 sec, then retry */ - pg_usleep(10000L); + pg_usleep(10000L); // 等待 0.01 秒 } - return true; + return true; // 表示工作进程仍然存活 } +/* + * 功能:启动事务快照工作进程,并监控其状态,以确保其正常启动和运行 + * + * 参数:无 + * + * 返回值:无 + */ static void TxnSnapCapImpl(void) { - List *dblist; + List *dblist; // 用于存储数据库列表 ListCell* cell = NULL; - const long int snapcapIntervalMs = 10L; - const long int snapcapStartThreshMs = 30000L; + const long int snapcapIntervalMs = 10L; // 表示快照捕获的时间间隔 + const long int snapcapStartThreshMs = 30000L; // 表示等待快照捕获开始的最大时间阈值 - StartTransactionCommand(); + StartTransactionCommand(); // 启动一个新的事务 + // 返回所有在 pg_database 中找到的数据库列表,并将其存储 dblist = TxnGetDatabaseList(); - + // 遍历数据库列表中的每个数据库 foreach (cell, dblist) { - TimestampTz launchTime; - TxnSnapWorkerInfo *workInfo; + TimestampTz launchTime; // 用于记录事务快照工作进程启动的时间 + TxnSnapWorkerInfo *workInfo; // 用于存储事务快照工作进程的信息 + // 检查是否已经存在活动的事务快照工作进程 if (TxnSnapWorkerIsAlive()) { + // 如果已存在,会触发一个错误消息,表示启动事务快照工作进程失败 elog(ERROR, "start txnsnapworker failed: txnsnapworker already exists!"); } - + // 初始化 workInfo = TxnSnapWorkerInfoInit((char *)lfirst(cell)); - + // 发送一个信号给 postmaster 进程,以启动事务快照工作进程 SendPostmasterSignal(PMSIGNAL_START_TXNSNAPWORKER); - + // 记录当前时间戳,表示事务快照工作进程的启动时间 launchTime = GetCurrentTimestamp(); + while (true) { /* Clear any already-pending wakeups */ - ResetLatch(&workInfo->latch); + ResetLatch(&workInfo->latch); // 清除任何已经挂起的 latch /* Wait latch for a maximum of 10ms for a snapworker done. */ + // 等待 latch 的设置 int rc = WaitLatch(&workInfo->latch, WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH, snapcapIntervalMs); - TxnSnapCapProcInterrupts(rc); + TxnSnapCapProcInterrupts(rc); // 处理中断 /* * We raise an ERROR if txnsnapworker not started as soon during to * unexcepted error or hang in initialization. */ + // 如果 workInfo 的状态为 TXNWORKER_STARTED + // 且从启动到当前时间的时间差超过了 snapcapStartThreshMs if (*(volatile TxnWorkerStatus *)&workInfo->status < TXNWORKER_STARTED && TimestampDifferenceExceeds(launchTime, GetCurrentTimestamp(), snapcapStartThreshMs)) { + // 触发一个错误消息,表示事务快照工作进程启动时间过长 ereport(ERROR, (errcode(ERRCODE_INTERNAL_ERROR), errmsg("txnsnapworker took too long to start over 30 seconds."))); break; } - + // 如果 workInfo 的状态变为 TXNWORKER_DONE 则退出循环 if (*(volatile TxnWorkerStatus *)&workInfo->status == TXNWORKER_DONE) { break; } } } - list_free_deep(dblist); - CommitTransactionCommand(); + list_free_deep(dblist); // 释放数据库列表所占用的内存 + CommitTransactionCommand(); // 提交当前事务 } +/* + * 功能:事务快照捕获进程的主要入口函数,用于捕获数据库的快照 + * + * 参数:无 + * + * 返回值:无 + */ NON_EXEC_STATIC void TxnSnapCapturerMain() { - sigjmp_buf localSigjmpBuf; + sigjmp_buf localSigjmpBuf; // 用于处理异常跳转 /* we are a postmaster subprocess now */ - IsUnderPostmaster = true; - t_thrd.role = TXNSNAP_CAPTURER; + IsUnderPostmaster = true; // 表示当前进程是在 postmaster 进程下运行的 + t_thrd.role = TXNSNAP_CAPTURER; // 表示它是事务快照捕获进程 /* tell datasender we update the values. */ - pg_memory_barrier(); + pg_memory_barrier();// 执行内存屏障操作,用于确保前面的写操作对其他线程可见 /* reset t_thrd.proc_cxt.MyProcPid */ - t_thrd.proc_cxt.MyProcPid = gs_thread_self(); + t_thrd.proc_cxt.MyProcPid = gs_thread_self(); // 正确标识线程 /* record Start Time for logging */ - t_thrd.proc_cxt.MyStartTime = time(NULL); + t_thrd.proc_cxt.MyStartTime = time(NULL); // 记录了当前线程的启动时间 - t_thrd.proc_cxt.MyProgName = "TxnSnapCapturer"; + t_thrd.proc_cxt.MyProgName = "TxnSnapCapturer"; // 设置了当前线程的程序名称 /* Identify myself via ps */ - init_ps_display("txnsnapcapturer process", "", "", ""); - + init_ps_display("txnsnapcapturer process", "", "", ""); // 初始化进程状态显示 + // 记录了调试级别的日志,表示快照捕获进程已经启动 ereport(LOG, (errmsg("txnsnapcapturer started"))); - + // 检查是否配置了 PostAuthDelay if (u_sess->attr.attr_security.PostAuthDelay) + // 如果配置了延迟时间,那么工作进程将休眠相应的时间 pg_usleep(u_sess->attr.attr_security.PostAuthDelay * 1000000L); - SetProcessingMode(InitProcessing); + SetProcessingMode(InitProcessing); // 设置处理模式为初始化处理模式 - (void)gspqsignal(SIGINT, StatementCancelHandler); - (void)gspqsignal(SIGTERM, die); - (void)gspqsignal(SIGQUIT, quickdie); - (void)gspqsignal(SIGALRM, SIG_IGN); - (void)gspqsignal(SIGPIPE, SIG_IGN); + // 设置信号处理程序 + (void)gspqsignal(SIGINT, StatementCancelHandler); // 用于处理中断信号 + (void)gspqsignal(SIGTERM, die); // 用于处理终止信号 正常终止进程 + (void)gspqsignal(SIGQUIT, quickdie); // 用于处理退出信号 + (void)gspqsignal(SIGALRM, SIG_IGN); // 忽略 + (void)gspqsignal(SIGPIPE, SIG_IGN); // 忽略 (void)gspqsignal(SIGUSR1, procsignal_sigusr1_handler); - (void)gspqsignal(SIGUSR2, SIG_IGN); - (void)gspqsignal(SIGFPE, FloatExceptionHandler); - (void)gspqsignal(SIGCHLD, SIG_DFL); - (void)gspqsignal(SIGHUP, TxnSnapSighupHandler); - (void)gspqsignal(SIGTTIN, SIG_DFL); - (void)gspqsignal(SIGTTOU, SIG_DFL); - (void)gspqsignal(SIGCONT, SIG_DFL); - (void)gspqsignal(SIGWINCH, SIG_DFL); + (void)gspqsignal(SIGUSR2, SIG_IGN); // 忽略 + (void)gspqsignal(SIGFPE, FloatExceptionHandler); // 用于处理浮点异常信号 + (void)gspqsignal(SIGCHLD, SIG_DFL); // 恢复默认行为 + (void)gspqsignal(SIGHUP, TxnSnapSighupHandler); // 用于处理终端挂断或配置文件重新加载信号 + (void)gspqsignal(SIGTTIN, SIG_DFL); // 恢复默认行为 + (void)gspqsignal(SIGTTOU, SIG_DFL); // 恢复默认行为 + (void)gspqsignal(SIGCONT, SIG_DFL); // 恢复默认行为 + (void)gspqsignal(SIGWINCH, SIG_DFL); // 恢复默认行为 /* We allow SIGQUIT (quickdie) at all times */ (void)sigdelset(&t_thrd.libpq_cxt.BlockSig, SIGQUIT); /* Early initialization */ - BaseInit(); + BaseInit(); // 早期初始化 #ifndef EXEC_BACKEND + // 不在执行后台情况下,创建了一个 PGPROC 结构体,并对其进行初始化 InitProcess(); #endif - + // 设置当前进程的数据库和用户信息 t_thrd.proc_cxt.PostInit->SetDatabaseAndUser((char*)pstrdup(DEFAULT_DATABASE), InvalidOid, NULL); t_thrd.proc_cxt.PostInit->InitTxnSnapCapturer(); - + // 设置当前进程的处理模式为 "NormalProcessing",表示正常的处理模式 SetProcessingMode(NormalProcessing); /* @@ -738,57 +1028,65 @@ NON_EXEC_STATIC void TxnSnapCapturerMain() * that we can reset the context during error recovery and thereby avoid * possible memory leaks. */ + // 创建一个内存上下文 MemoryContext workMxt = AllocSetContextCreate( t_thrd.top_mem_cxt, "TxnSnapCapturer", ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_INITSIZE, ALLOCSET_DEFAULT_MAXSIZE); + // 切换到该上下文 (void)MemoryContextSwitchTo(workMxt); + // 处理异常情况 int curTryCounter = 0; int* oldTryCounter = NULL; if (sigsetjmp(localSigjmpBuf, 1) != 0) { + // 关闭所有打开的文件 gstrace_tryblock_exit(true, oldTryCounter); /* Since not using PG_TRY, must reset error stack by hand */ - t_thrd.log_cxt.error_context_stack = NULL; + t_thrd.log_cxt.error_context_stack = NULL; // 手动重置错误堆栈 - t_thrd.log_cxt.call_stack = NULL; + t_thrd.log_cxt.call_stack = NULL; // 清空调用栈 /* Prevent interrupts while cleaning up */ - HOLD_INTERRUPTS(); + HOLD_INTERRUPTS(); // 在执行清理操作期间禁止中断 /* Forget any pending QueryCancel request */ - t_thrd.int_cxt.QueryCancelPending = false; - (void)disable_sig_alarm(true); + t_thrd.int_cxt.QueryCancelPending = false; // 表示取消请求不再挂起 + (void)disable_sig_alarm(true); // 禁用信号闹钟,确保不会在错误处理期间触发超时信号 t_thrd.int_cxt.QueryCancelPending = false; /* Report the error to the server log */ - EmitErrorReport(); + EmitErrorReport(); // 报告错误到服务器日志 /* * Abort the current transaction in order to recover. */ + // 中止当前事务以进行恢复,任何未提交的更改将被回滚,以确保数据库的一致性 AbortCurrentTransaction(); /* release resource held by lsc */ - AtEOXact_SysDBCache(false); + AtEOXact_SysDBCache(false); // 释放由系统数据库缓存持有的资源 - LWLockReleaseAll(); + LWLockReleaseAll(); // 释放锁 /* * Now return to normal top-level context and clear ErrorContext for * next time. */ - (void)MemoryContextSwitchTo(workMxt); + (void)MemoryContextSwitchTo(workMxt); // 切换程序的内存上下文 + // 清除错误状态,将之前可能记录的错误信息和状态重置为空 FlushErrorState(); /* Flush any leaked data in the top-level context */ + // 重置并删除了之前创建的内存上下文 workMxt 及其所有子上下文 + // 确保在异常处理完成后释放已分配的内存,以避免内存泄漏 MemoryContextResetAndDeleteChildren(workMxt); /* Now we can allow interrupts again */ - RESUME_INTERRUPTS(); + RESUME_INTERRUPTS(); // 允许中断信号再次被捕获 /* if in shutdown mode, no need for anything further; just go away */ if (t_thrd.snapcapturer_cxt.got_SIGTERM) { @@ -801,46 +1099,56 @@ NON_EXEC_STATIC void TxnSnapCapturerMain() */ pg_usleep(1000000L); } - + // 记录当前的错误处理堆栈计数器 oldTryCounter = gstrace_tryblock_entry(&curTryCounter); /* We can now handle ereport(ERROR) */ + // 将错误处理的跳转缓冲区设置为当前线程的错误处理堆栈 t_thrd.log_cxt.PG_exception_stack = &localSigjmpBuf; /* * Unblock signals (they were blocked when the postmaster forked us) */ + // 解除信号屏蔽,允许信号再次被捕获 gs_signal_setmask(&t_thrd.libpq_cxt.UnBlockSig, NULL); + // 解除对 SIGUSR2 信号的屏蔽,允许接收 SIGUSR2 信号 (void)gs_signal_unblock_sigusr2(); /* loop until shutdown request */ + // 循环直到收到了关闭请求或Tcap 版本不再可用 while (!t_thrd.snapcapturer_cxt.got_SIGTERM && ENABLE_TCAP_VERSION) { int rc; - CHECK_FOR_INTERRUPTS(); + CHECK_FOR_INTERRUPTS(); // 检查是否收到了中断请求 /* Clear any already-pending wakeups */ - ResetLatch(&t_thrd.proc->procLatch); + ResetLatch(&t_thrd.proc->procLatch); // 清除任何已经挂起的等待 /* * Wait until naptime expires or we get some type of signal (all the * signal handlers will wake us by calling SetLatch). */ + // 等待信号或超时 等待以下条件之一发生: + // 收到了 WL_LATCH_SET 信号,表示等待标志已经被设置。 + // 收到了 WL_TIMEOUT 信号,表示经过了一段时间(由 SNAP_CAPTURE_INTERVAL 控制)。 + // 收到了 WL_POSTMASTER_DEATH 信号,表示 postmaster 进程已经退出。 rc = WaitLatch(&t_thrd.proc->procLatch, WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH, SNAP_CAPTURE_INTERVAL * 1000L); + // 处理不同的中断情况,根据传入的返回码 rc 执行相应的操作 TxnSnapCapProcInterrupts(rc); /* Do the hard work. */ - if (TcapFeatureAvail()) { - TxnSnapCapImpl(); + if (TcapFeatureAvail()) { // 检查是否支持 TcapFeature + TxnSnapCapImpl(); // 执行实际的工作 } - + // 重置并删除了之前创建的内存上下文 workMxt 及其所有子上下文 MemoryContextResetAndDeleteChildren(workMxt); } shutdown: + // 记录一条调试级别的日志消息,指示 txnsnapcapturer 进程正在关闭 elog(LOG, "txnsnapcapturer shutting down"); proc_exit(0); } diff --git a/src/gausskernel/process/postmaster/startup.cpp b/src/gausskernel/process/postmaster/startup.cpp index a6fa6b8e8..20a3bda57 100755 --- a/src/gausskernel/process/postmaster/startup.cpp +++ b/src/gausskernel/process/postmaster/startup.cpp @@ -1,3 +1,5 @@ +// startup启动进程负责初始化服务器并执行恢复操作。一旦初始化完成,启动进程就会结束 + /* * * startup.cpp @@ -60,9 +62,19 @@ static void SetStaticConnNum(void); * Some backend has bought the farm, * so we need to stop what we're doing and exit. */ +/* + *功能:处理快速终止信号(当主进程发出SIGQUIT信号) + * 紧急退出操作,不会触发正常的清理和关闭步骤,因为共享内存可能已损坏 + * + * 参数: + * SIGNAL_ARGS: 信号处理程序的参数列表,提供关于触发信号的上下文信息 + * + * 返回值: + * 无 + */ static void startupproc_quickdie(SIGNAL_ARGS) { - gs_signal_setmask(&t_thrd.libpq_cxt.BlockSig, NULL); + gs_signal_setmask(&t_thrd.libpq_cxt.BlockSig, NULL); // 解除对信号的阻塞 /* * We DO NOT want to run proc_exit() callbacks -- we're here because @@ -72,7 +84,7 @@ static void startupproc_quickdie(SIGNAL_ARGS) * things by calling exit() directly, we have to reset the callbacks * explicitly to make this work as intended. */ - on_exit_reset(); + on_exit_reset(); // 重置进程退出时的回调函数 /* * Note we do exit(2) not exit(0). This is to force the postmaster into a @@ -82,40 +94,62 @@ static void startupproc_quickdie(SIGNAL_ARGS) * should ensure the postmaster sees this as a crash, too, but no harm in * being doubly sure.) */ - exit(2); + exit(2); // 以状态码 2 退出进程,表示进程异常退出 } /* SIGUSR1: let latch facility handle the signal */ +/* + *功能: 处理 SIGUSR1 信号,用于唤醒进程中的等待操作 + * + * 参数: + * SIGNAL_ARGS: 信号处理程序的参数列表,提供关于触发信号的上下文信息 + * + * 返回值: + * 无 + */ static void StartupProcSigUsr1Handler(SIGNAL_ARGS) { - int save_errno = errno; + int save_errno = errno; // 保存当前的错误码 - latch_sigusr1_handler(); + latch_sigusr1_handler(); // 处理 SIGUSR1 信号 - errno = save_errno; + errno = save_errno; // 恢复之前保存的错误码 } -#ifndef ENABLE_MULTIPLE_NODES +#ifndef ENABLE_MULTIPLE_NODES // 只在未启用多节点功能时才会编译和执行以下代码块 +/* + * 功能:等待 DCF (分布式事务一致性框架)日志的应用 + * 如果成功应用所有 DCF 日志,将取消当前线程的 DCF 领导者标记,并生成相应的日志消息 + * 如果应用失败,将生成一个致命错误消息 + * + * 参数:无 + * + * 返回值:无 + */ static void WaitApplyAllDCFLog(void) { unsigned int all_applied = 0; // Check if walreceiver has written all DCF log into xlog + // 检查是否启用了 DCF,以及当前线程是否被标记为 DCF 领导者 if (g_instance.attr.attr_storage.dcf_attr.enable_dcf && t_thrd.dcf_cxt.dcfCtxInfo->dcf_to_be_leader) { - ereport(LOG, (errmsg("Begin to wait read xlog from DCF!"))); + ereport(LOG, (errmsg("Begin to wait read xlog from DCF!"))); // 生成日志消息(LOG级别) 表示开始等待从 DCF 读取 xlog /* Check if all the dcf log has been applied to xlog every 10 milliseconds. */ - int ret = 0; + int ret = 0; // 用于存储后续函数调用的返回值 + // 检查是否所有 DCF 日志都已应用到 xlog while ((ret = dcf_check_if_all_logs_applied(1, &all_applied)) == 0) { + // 如果已应用的日志数量不等于0,表示所有 DCF 日志都已经被应用 if (all_applied != 0) { - t_thrd.dcf_cxt.dcfCtxInfo->dcf_to_be_leader = false; - ereport(LOG, (errmsg("All DCF log has been applied!"))); - return; + t_thrd.dcf_cxt.dcfCtxInfo->dcf_to_be_leader = false; // 当前线程不再被标记为 DCF 领导者 + ereport(LOG, (errmsg("All DCF log has been applied!"))); // 生成日志消息(LOG级别),表示所有 DCF 日志都已经被应用 + return; // 函数返回,结束循环 } - pg_usleep(10000L); /* 10 milliseconds */ + pg_usleep(10000L); /* 10 milliseconds */ // 如果未应用的日志数量仍然为0,函数会休眠10毫秒继续循环 } + // 如果ret不为0,表示应用 DCF 日志失败 if (ret) { - t_thrd.dcf_cxt.dcfCtxInfo->dcf_to_be_leader = false; - ereport(FATAL, (errmsg("Apply all DCF log failed for dcf return false!"))); + t_thrd.dcf_cxt.dcfCtxInfo->dcf_to_be_leader = false; // 当前线程不再被标记为 DCF 领导者 + ereport(FATAL, (errmsg("Apply all DCF log failed for dcf return false!"))); // 生成一个致命错误日志消息(FATAL级别),表示 DCF 日志的应用失败 } } } @@ -128,158 +162,243 @@ static void WaitApplyAllDCFLog(void) * When the SIGUSR2 is receiverd, then check the reason of the SIGUSR2 * and do the corresponding operations */ +/* + * 功能:处理 SIGUSR2 信号,根据不同的通知信号采取相应的操作 + * + * 参数: + * SIGNAL_ARGS: 信号处理程序的参数列表,提供关于触发信号的上下文信息 + * + * 返回值: + * 无 + */ static void StartupProcSigusr2Handler(SIGNAL_ARGS) { - int save_errno = errno; - + int save_errno = errno; // 保存当前的错误码 + // 检查变量 dummyStandbyMode 是否为真,即检查是否处于虚拟备用模式 if (dummyStandbyMode) - return; + return; // 如果是,返回 - if (CheckNotifySignal(NOTIFY_PRIMARY)) { - t_thrd.startup_cxt.primary_triggered = true; - } else if (CheckNotifySignal(NOTIFY_STANDBY)) { - t_thrd.startup_cxt.standby_triggered = true; + if (CheckNotifySignal(NOTIFY_PRIMARY)) { // 检查是否收到 NOTIFY_PRIMARY 通知信号 + t_thrd.startup_cxt.primary_triggered = true; // 表示主服务器触发了某个事件 + } else if (CheckNotifySignal(NOTIFY_STANDBY)) { // 检查是否收到 NOTIFY_STANDBY 通知信号 + t_thrd.startup_cxt.standby_triggered = true; // 表示备用服务器触发了某个事件 + // 检查故障切换是否已经触发以及当前服务器是否是高可用性和灾难恢复(HADR)系统的主备模式中的主服务器 if (t_thrd.startup_cxt.failover_triggered && t_thrd.postmaster_cxt.HaShmData->is_hadr_main_standby) { - t_thrd.startup_cxt.failover_triggered = false; + t_thrd.startup_cxt.failover_triggered = false; // 重置故障切换触发标志,避免多次触发故障切换逻辑,确保只有一个服务器被选为主服务器 } - } else if (CheckNotifySignal(NOTIFY_CASCADE_STANDBY)) { - t_thrd.startup_cxt.standby_triggered = true; - } else if (CheckNotifySignal(NOTIFY_FAILOVER)) { - t_thrd.startup_cxt.failover_triggered = true; -#ifndef ENABLE_MULTIPLE_NODES - WaitApplyAllDCFLog(); + } else if (CheckNotifySignal(NOTIFY_CASCADE_STANDBY)) { // 检查是否收到 NOTIFY_CASCADE_STANDBY 通知信号 + t_thrd.startup_cxt.standby_triggered = true; // 表示级联备用服务器触发了某个事件 + } else if (CheckNotifySignal(NOTIFY_FAILOVER)) { // 检查是否收到 NOTIFY_FAILOVER 通知信号 + t_thrd.startup_cxt.failover_triggered = true; // 表示发生了故障切换 +#ifndef ENABLE_MULTIPLE_NODES // 如果未启用多节点功能 + WaitApplyAllDCFLog(); // 等待所有 DCF 日志的应用 #endif - WakeupRecovery(); - } else if (CheckNotifySignal(NOTIFY_SWITCHOVER)) { - t_thrd.startup_cxt.switchover_triggered = true; -#ifndef ENABLE_MULTIPLE_NODES - WaitApplyAllDCFLog(); + WakeupRecovery(); // 唤醒恢复进程 + } else if (CheckNotifySignal(NOTIFY_SWITCHOVER)) { // 检查是否收到 NOTIFY_SWITCHOVER 通知信号 + t_thrd.startup_cxt.switchover_triggered = true; // 表示发生了切换 +#ifndef ENABLE_MULTIPLE_NODES // 如果未启用多节点功能 + WaitApplyAllDCFLog(); // 等待所有 DCF 日志的应用 #endif - WakeupRecovery(); + WakeupRecovery(); // 唤醒恢复进程 } - errno = save_errno; + errno = save_errno; // 恢复之前保存的错误码 } /* SIGHUP: set flag to re-read config file at next convenient time */ +/* + *功能:处理 SIGHUP 信号,用于重新读取配置文件 + * + * 参数: + * SIGNAL_ARGS: 信号处理程序的参数列表,提供关于触发信号的上下文信息 + * + * 返回值: + * 无 + */ static void StartupProcSigHupHandler(SIGNAL_ARGS) { - int save_errno = errno; + int save_errno = errno; // 保存当前的错误码 - t_thrd.startup_cxt.got_SIGHUP = true; - WakeupRecovery(); + t_thrd.startup_cxt.got_SIGHUP = true; // 表示接收到 SIGHUP 信号 + WakeupRecovery(); // 唤醒恢复进程 - errno = save_errno; + errno = save_errno; // 恢复之前保存的错误码 } /* SIGINT: set flag to check repair page */ +/* + *功能:处理 SIGINT 信号 请求进程终止 + * + * 参数: + * SIGNAL_ARGS: 信号处理程序的参数列表,提供关于触发信号的上下文信息 + * + * 返回值: + * 无 + */ static void StartupProcSigIntHandler(SIGNAL_ARGS) { - int save_errno = errno; + int save_errno = errno; // 保存当前的错误码 - t_thrd.startup_cxt.check_repair = true; + t_thrd.startup_cxt.check_repair = true; // 表示接收到 SIGINT 信号后需要进行检查修复操作 - errno = save_errno; + errno = save_errno; // 恢复之前保存的错误码 } /* SIGTERM: set flag to abort redo and exit */ +/* + *功能:处理 SIGTERM 信号,用于正常退出 + * + * 参数: + * SIGNAL_ARGS: 信号处理程序的参数列表,提供关于触发信号的上下文信息 + * + * 返回值: + * 无 + */ static void StartupProcShutdownHandler(SIGNAL_ARGS) { - int save_errno = errno; + int save_errno = errno; // 保存当前的错误码 - if (t_thrd.startup_cxt.in_restore_command) - proc_exit(1); + if (t_thrd.startup_cxt.in_restore_command) // 检查当前是否正在执行恢复命令 + proc_exit(1); // 启动进程退出 else - t_thrd.startup_cxt.shutdown_requested = true; + t_thrd.startup_cxt.shutdown_requested = true; // 表示已经收到了关闭请求 - WakeupRecovery(); + WakeupRecovery(); // 唤醒恢复进程 - errno = save_errno; + errno = save_errno; // 恢复之前保存的错误码 } +/* + * 功能:处理启动过程中的页面修复 + * + * 参数: + * key:一个表示修复块的关键信息的结构或标识 + * pblk:一个表示物理块的标识或信息 + * + * 返回值:无 + */ void HandleStartupPageRepair(RepairBlockKey key, XLogPhyBlock pblk) { - XLogReaderState *record = g_instance.startup_cxt.current_record; + XLogReaderState *record = g_instance.startup_cxt.current_record; // 用于解析和处理 WAL记录,获取当前正在处理的 WAL 记录 + // 记录坏块信息并将其推送到远程位置 + // 参数:record-当前的 WAL 记录;key-修复块的关键信息;CRC_CHECK_FAIL-表示 CRC 校验失败 + // InvalidXLogRecPtr-表示无效的 XLog 记录位置;pblk-表示物理块的标识或信息 parallel_recovery::RecordBadBlockAndPushToRemote(record, key, CRC_CHECK_FAIL, InvalidXLogRecPtr, pblk); return; } /* Handle SIGHUP and SIGTERM signals of startup process */ +/* + * 功能:处理启动进程接收到的SIGHUP 和 SIGTERM 信号 + * -处理 SIGHUP 信号,重新读取配置文件 + * -执行块检查和修复操作 + * -处理关闭请求,但避免在智能关闭状态下退出 + * -紧急退出启动进程(当不是 Postmaster 进程或 Postmaster 进程已经终止) + * + * 参数:无 + * + * 返回值:无 + */ void HandleStartupProcInterrupts(void) { /* * Check if we were requested to re-read config file. */ - if (t_thrd.startup_cxt.got_SIGHUP) { + if (t_thrd.startup_cxt.got_SIGHUP) { // 检查是否收到 SIGHUP 信号 t_thrd.startup_cxt.got_SIGHUP = false; - ProcessConfigFile(PGC_SIGHUP); + ProcessConfigFile(PGC_SIGHUP); // 重新读取配置文件 } - if (t_thrd.startup_cxt.check_repair) { + if (t_thrd.startup_cxt.check_repair) { // 检查是否需要执行块检查和修复操作 + // 如果当前不处于极端重做且不是并行重做 if (!IsExtremeRedo() && !IsParallelRedo()) { - parallel_recovery::SeqCheckRemoteReadAndRepairPage(); + parallel_recovery::SeqCheckRemoteReadAndRepairPage(); // 执行块检查和修复操作 } - t_thrd.startup_cxt.check_repair = false; + t_thrd.startup_cxt.check_repair = false; // 表示检查和修复操作已完成 } /* * Check if we were requested to exit without finishing recovery. */ + // 检查是否收到了关闭请求且非智能关闭(避免在智能关闭过程中强制退出) if (t_thrd.startup_cxt.shutdown_requested && SmartShutdown != g_instance.status) { - proc_exit(1); + proc_exit(1); // 紧急退出启动进程 } /* * Emergency bailout if postmaster has died. This is to avoid the * necessity for manual cleanup of all postmaster children. */ + // 检查是否处于 Postmaster 进程下,以及Postmaster 是否还存活 if (IsUnderPostmaster && !PostmasterIsAlive()) - gs_thread_exit(1); + gs_thread_exit(1); // 强制退出 } +/* + * 功能:在启动过程中释放所有锁 + * 销毁哈希表(如果存在)。 + * 根据当前的热备模式状态,释放标准锁和用户级别的锁。 + * 在需要时清理与锁相关的错误状态。 + * + * 参数: + * code、arg:未被使用,可能是函数的历史原因或未来扩展性考虑而存在的 + * + * 返回值:无 + */ static void StartupReleaseAllLocks(int code, Datum arg) { - Assert(t_thrd.proc != NULL); + Assert(t_thrd.proc != NULL); // 确保当前线程的 proc 结构不为空 - if (g_instance.startup_cxt.badPageHashTbl != NULL) { - hash_destroy(g_instance.startup_cxt.badPageHashTbl); - g_instance.startup_cxt.badPageHashTbl = NULL; + if (g_instance.startup_cxt.badPageHashTbl != NULL) { // 检查哈希表是否不为空 + hash_destroy(g_instance.startup_cxt.badPageHashTbl); // 销毁哈希表,释放哈希表中的内存 + g_instance.startup_cxt.badPageHashTbl = NULL; // 表示哈希表已被销毁 } /* Do nothing if we're not in hot standby mode */ + // 检查当前线程的 standbyState 是否等于 STANDBY_DISABLED,即检查当前是否处于热备模式 if (t_thrd.xlog_cxt.standbyState == STANDBY_DISABLED) - return; + return; // 不处于则返回 /* If waiting, get off wait queue (should only be needed after error) */ - LockErrorCleanup(); + LockErrorCleanup(); // 清理与锁相关的错误状态 /* Release standard locks, including session-level if aborting */ - LockReleaseAll(DEFAULT_LOCKMETHOD, true); + LockReleaseAll(DEFAULT_LOCKMETHOD, true); // 释放所有标准锁(默认锁定方法)并确保它们在事务终止时被释放,防止锁定资源泄漏或死锁 /* * User locks are not released by transaction end, so be sure to release * them explicitly. */ - LockReleaseAll(USER_LOCKMETHOD, true); + LockReleaseAll(USER_LOCKMETHOD, true); // 释放所有用户级别的锁 并确保它们在事务结束时被释放 } +/* + * 功能:在集群备用模式下删除一个文件如果文件不存在或删除失败,会生成相应的警告消息。 + * + * + * 参数:无 + * + * 返回值:无 + */ void DeleteDisConnFileInClusterStandby() { - if (!IS_SHARED_STORAGE_MODE) { + if (!IS_SHARED_STORAGE_MODE) { // 检查是否处于共享存储模式 return; } - struct stat st; - if (stat(disable_conn_file, &st) < 0) { + struct stat st; // 用于存储文件状态信息 + if (stat(disable_conn_file, &st) < 0) { // 如果获取文件状态信息失败 return; } - int ret = unlink(disable_conn_file); - if (ret < 0) { + int ret = unlink(disable_conn_file); // 尝试删除指定的文件,如果删除成功,则返回值为0,否则为-1 + if (ret < 0) { // 如果删除文件失败 + // 输出一条警告消息,表示无法删除文件,并包含错误消息 ereport(WARNING, (errcode_for_file_access(), errmsg("cluster standby mode, could not remove file \"%s\": %m", disable_conn_file))); - } else { + } else { // 如果删除文件成功 + // 输出一条日志消息,表示文件删除成功 ereport(LOG, (errcode_for_file_access(), errmsg("removed file \"%s\" success.", disable_conn_file))); } } @@ -289,6 +408,13 @@ void DeleteDisConnFileInClusterStandby() * Startup Process main entry point * ---------------------------------- */ +/* + * 功能:启动进程的主要入口函数 StartupProcessMain,负责启动进程的初始化和执行 + * + * 参数:无 + * + * 返回值:无 + */ void StartupProcessMain(void) { /* @@ -301,77 +427,90 @@ void StartupProcessMain(void) /* * Reset some signals that are accepted by postmaster but not here */ - (void)gspqsignal(SIGHUP, StartupProcSigHupHandler); /* reload config file */ - (void)gspqsignal(SIGINT, StartupProcSigIntHandler); /* check repair page and file */ - (void)gspqsignal(SIGTERM, StartupProcShutdownHandler); /* request shutdown */ + (void)gspqsignal(SIGHUP, StartupProcSigHupHandler); /* reload config file */ // 重新加载配置文件 + (void)gspqsignal(SIGINT, StartupProcSigIntHandler); /* check repair page and file */ // 检查修复页和文件 + (void)gspqsignal(SIGTERM, StartupProcShutdownHandler); /* request shutdown */ // 请求关闭 (void)gspqsignal(SIGQUIT, startupproc_quickdie); /* hard crash time */ - if (g_instance.attr.attr_storage.EnableHotStandby) - (void)gspqsignal(SIGALRM, handle_standby_sig_alarm); /* ignored unless + if (g_instance.attr.attr_storage.EnableHotStandby) // 检查是否启用了热备模式 + (void)gspqsignal(SIGALRM, handle_standby_sig_alarm); /* ignored unless // 如果启用了热备模式,设置SIGALRM的信号处理函数 * InHotStandby */ else - (void)gspqsignal(SIGALRM, SIG_IGN); + (void)gspqsignal(SIGALRM, SIG_IGN); // 否则忽略该信号 - (void)gspqsignal(SIGPIPE, SIG_IGN); - (void)gspqsignal(SIGUSR1, StartupProcSigUsr1Handler); + (void)gspqsignal(SIGPIPE, SIG_IGN); // 忽略SIGPIPE + (void)gspqsignal(SIGUSR1, StartupProcSigUsr1Handler); // 设置信号处理函数 (void)gspqsignal(SIGUSR2, StartupProcSigusr2Handler); /* * Reset some signals that are accepted by postmaster but not here */ + // 设置信号处理函数为默认行为 (void)gspqsignal(SIGCHLD, SIG_DFL); (void)gspqsignal(SIGTTIN, SIG_DFL); (void)gspqsignal(SIGTTOU, SIG_DFL); (void)gspqsignal(SIGCONT, SIG_DFL); (void)gspqsignal(SIGWINCH, SIG_DFL); + // 注册回调函数,以处理重做期间的中断和页面修复操作 (void)RegisterRedoInterruptCallBack(HandleStartupProcInterrupts); (void)RegisterRedoPageRepairCallBack(HandleStartupPageRepair); /* * Unblock signals (they were blocked when the postmaster forked us) */ - gs_signal_setmask(&t_thrd.libpq_cxt.UnBlockSig, NULL); - (void)gs_signal_unblock_sigusr2(); + gs_signal_setmask(&t_thrd.libpq_cxt.UnBlockSig, NULL); // 解除信号的阻塞状态,允许信号传递 + (void)gs_signal_unblock_sigusr2(); // 解除对 SIGUSR2 信号的阻塞 + // 创建了资源拥有者,用于跟踪和管理资源的分配和释放,确保在启动进程执行期间对资源的正确管理 t_thrd.utils_cxt.CurrentResourceOwner = ResourceOwnerCreate(NULL, "StartupXLOG", THREAD_GET_MEM_CXT_GROUP(MEMORY_CONTEXT_STORAGE)); - SetStaticConnNum(); - pgstat_report_appname("Startup"); - pgstat_report_activity(STATE_IDLE, NULL); + SetStaticConnNum(); // 设置静态连接数,用于配置启动进程的连接池参数 + pgstat_report_appname("Startup"); // 设置进程的应用程序名称 + pgstat_report_activity(STATE_IDLE, NULL); // 设置进程的活动状态为 "STATE_IDLE",表示进程当前处于空闲状态 - if (dummyStandbyMode) { - StartupDummyStandby(); + if (dummyStandbyMode) { // 检查是否处于虚拟备用模式 + StartupDummyStandby(); // 如果处于虚拟备用模式,执行虚拟备用模式下的操作 } else { - on_shmem_exit(StartupReleaseAllLocks, 0); + on_shmem_exit(StartupReleaseAllLocks, 0); // 注册一个在共享内存退出时执行的回调函数,用于释放所有锁 -#ifdef ENABLE_MOT +#ifdef ENABLE_MOT // 检查是否启用了 MOT 存储引擎 +// 如果启用了 执行以下代码块 /* * Init MOT first */ - InitMOT(); + InitMOT(); // 进行 MOT 存储引擎的初始化 /* * MOT recovery is part of StartupXlog */ #endif - DeleteDisConnFileInClusterStandby(); - if (!dummyStandbyMode) { + DeleteDisConnFileInClusterStandby(); // 删除集群备用模式下的连接文件(如果存在) + if (!dummyStandbyMode) { // 检查是否处于虚拟备用模式 + // 如果不是 NULL,表示该表已经存在,发生错误。确保在创建之前没有遗留的数据结构 Assert(g_instance.startup_cxt.badPageHashTbl == NULL); + // 创建一个坏块哈希表,用于记录坏块的信息,在恢复过程中跟踪坏块的状态 g_instance.startup_cxt.badPageHashTbl = parallel_recovery::BadBlockHashTblCreate(); } - StartupXLOG(); + StartupXLOG(); // 处理事务日志和执行数据库恢复 } /* * Exit normally. Exit code 0 tells postmaster that we completed recovery * successfully. */ - proc_exit(0); + proc_exit(0); // 正常退出进程。退出码为0表示成功完成恢复过程,通知主进程恢复成功 } +/* + * 功能:在执行恢复命令之前进行一些预处理操作 + * + * 参数:无 + * + * 返回值:无 + */ void PreRestoreCommand(void) { /* @@ -380,37 +519,53 @@ void PreRestoreCommand(void) * Check if we had already received the signal, so that we don't miss a * shutdown request received just before this. */ - t_thrd.startup_cxt.in_restore_command = true; + t_thrd.startup_cxt.in_restore_command = true; // 表示启动进程正在执行恢复命令 - if (t_thrd.startup_cxt.shutdown_requested) { - proc_exit(1); + if (t_thrd.startup_cxt.shutdown_requested) { // 检查是否收到关闭请求, 以免错过在执行恢复命令前立即退出的机会 + proc_exit(1); // 如果收到关闭请求,退出 } } void PostRestoreCommand(void) { - t_thrd.startup_cxt.in_restore_command = false; + t_thrd.startup_cxt.in_restore_command = false; // 退出恢复命令状态 } +/* + * 功能:检查是否触发了故障切换信号 + * + * 参数:无 + * + * 返回值:bool类型 ,如果触发了则返回 true,否则返回 false。 + */ bool IsFailoverTriggered(void) { - if (AmStartupProcess()) { - return t_thrd.startup_cxt.failover_triggered; + if (AmStartupProcess()) { // 检查当前是否处于启动进程 + return t_thrd.startup_cxt.failover_triggered; // 返回,表示故障切换信号已触发 } else { - uint32 tgigger = pg_atomic_read_u32(&(extreme_rto::g_startupTriggerState)); - if (tgigger == (uint32)extreme_rto::TRIGGER_FAILOVER) { + uint32 tgigger = pg_atomic_read_u32(&(extreme_rto::g_startupTriggerState)); // 通过原子操作读取全局变量,以确保线程安全 + // 检查读取的值是否等于 extreme_rto::TRIGGER_FAILOVER,如果相等,表示故障切换信号已触发 + if (tgigger == (uint32)extreme_rto::TRIGGER_FAILOVER) { return true; } } return false; } +/* + * 功能:检查是否触发了切换信号 + * + * 参数:无 + * + * 返回值:bool类型,如果触发了则返回 true,否则返回 false。 + */ bool IsSwitchoverTriggered(void) { - if (AmStartupProcess()) { - return t_thrd.startup_cxt.switchover_triggered; + if (AmStartupProcess()) { // 检查当前是否处于启动进程 + return t_thrd.startup_cxt.switchover_triggered; // 返回,表示切换信号已触发 } else { - uint32 tgigger = pg_atomic_read_u32(&(extreme_rto::g_startupTriggerState)); + uint32 tgigger = pg_atomic_read_u32(&(extreme_rto::g_startupTriggerState)); // 通过原子操作读取全局变量,以确保线程安全 + // 检查读取的值是否等于 extreme_rto::TRIGGER_SWITCHOVER,如果相等,表示切换信号已触发 if (tgigger == (uint32)extreme_rto::TRIGGER_SWITCHOVER) { return true; } @@ -418,12 +573,20 @@ bool IsSwitchoverTriggered(void) return false; } +/* + * 功能:检查是否触发了主节点信号 + * + * 参数:无 + * + * 返回值:bool类型,如果触发了则返回 true,否则返回 false。 + */ bool IsPrimaryTriggered(void) { - if (AmStartupProcess()) { - return t_thrd.startup_cxt.primary_triggered; + if (AmStartupProcess()) { // 检查当前是否处于启动进程 + return t_thrd.startup_cxt.primary_triggered; // 返回,表示主节点信号已触发 } else { - uint32 tgigger = pg_atomic_read_u32(&(extreme_rto::g_startupTriggerState)); + uint32 tgigger = pg_atomic_read_u32(&(extreme_rto::g_startupTriggerState)); // 通过原子操作读取全局变量,以确保线程安全 + // 检查读取的值是否等于 extreme_rto::TRIGGER_PRIMARY,如果相等,表示主节点信号已触发 if (tgigger == (uint32)extreme_rto::TRIGGER_PRIMARY) { return true; } @@ -431,12 +594,20 @@ bool IsPrimaryTriggered(void) return false; } +/* + * 功能:检查是否触发了备用节点信号 + * + * 参数:无 + * + * 返回值:bool类型,如果触发了则返回 true,否则返回 false。 + */ bool IsStandbyTriggered(void) { - if (AmStartupProcess()) { - return t_thrd.startup_cxt.standby_triggered; + if (AmStartupProcess()) { // 检查当前是否处于启动进程 + return t_thrd.startup_cxt.standby_triggered; // 返回,表示备用节点信号已触发 } else { - uint32 tgigger = pg_atomic_read_u32(&(extreme_rto::g_startupTriggerState)); + uint32 tgigger = pg_atomic_read_u32(&(extreme_rto::g_startupTriggerState)); // 通过原子操作读取全局变量,以确保线程安全 + // 检查读取的值是否等于 extreme_rto::TRIGGER_STADNBY,如果相等,表示备用节点信号已触发 if (tgigger == (uint32)extreme_rto::TRIGGER_STADNBY) { return true; } @@ -444,44 +615,92 @@ bool IsStandbyTriggered(void) return false; } +// 重置触发信号的状态变量 + +/* + * 功能:重置故障切换信号触发状态 + * + * 参数:无 + * + * 返回值:无 + */ void ResetFailoverTriggered(void) { - t_thrd.startup_cxt.failover_triggered = false; + t_thrd.startup_cxt.failover_triggered = false; // 表示故障切换信号未触发 } +/* + * 功能:重置切换信号触发状态 + * + * 参数:无 + * + * 返回值:无 + */ void ResetSwitchoverTriggered(void) { - t_thrd.startup_cxt.switchover_triggered = false; + t_thrd.startup_cxt.switchover_triggered = false; // 表示切换信号未触发 } +/* + * 功能:重置主节点信号触发状态 + * + * 参数:无 + * + * 返回值:无 + */ void ResetPrimaryTriggered(void) { - t_thrd.startup_cxt.primary_triggered = false; + t_thrd.startup_cxt.primary_triggered = false; // 表示主节点信号未触发 } +/* + * 功能:重置备用节点信号触发状态 + * + * 参数:无 + * + * 返回值:无 + */ void ResetStandbyTriggered(void) { - t_thrd.startup_cxt.standby_triggered = false; + t_thrd.startup_cxt.standby_triggered = false; // 表示备用节点信号未触发 } +// 管理共享内存中的通知信号数据 + +/* + * 功能:计算通知信号数据结构的大小 + * + * 参数:无 + * + * 返回值:Size类型,返回通知信号数据结构的大小 + */ Size NotifySignalShmemSize(void) { Size size = 0; - size = add_size(size, sizeof(NotifySignalData)); + size = add_size(size, sizeof(NotifySignalData)); // 获取通知信号数据结构 NotifySignalData 的大小,并添加到 size 变量中 return size; } +/* + * 功能:初始化通知信号数据结构的共享内存 + * + * 参数:无 + * + * 返回值:无 + */ void NotifySignalShmemInit(void) { - bool found = false; - errno_t rc = 0; + bool found = false; // 用于表示在共享内存中是否已经找到通知信号数据结构 + errno_t rc = 0; // 用于处理错误码 + // 获取通知信号数据结构的共享内存指针,获取成功 found->true t_thrd.startup_cxt.NotifySigState = (NotifySignalData*)ShmemInitStruct("NotifySignalState", NotifySignalShmemSize(), &found); - if (!found) { + if (!found) { // 如果在共享内存中没有找到通知信号数据结构 + // 将通知信号数据结构的内存区域初始化为0,以确保数据的初始状态是清零的 rc = memset_s(t_thrd.startup_cxt.NotifySigState, NotifySignalShmemSize(), 0, NotifySignalShmemSize()); securec_check(rc, "", ""); } @@ -490,11 +709,23 @@ void NotifySignalShmemInit(void) /* * Set the reason in notify signal share memory, and send the SIGUSR2 to the process */ + +/* + * 功能:发送通知信号并触发相应操作 + * + * 参数: + * ProcPid-进程的线程标识 + * + * 返回值:无 + */ void SendNotifySignal(NotifyReason reason, ThreadId ProcPid) { + // 根据指定的通知原因 reason 将数组中相应的标志设置为 true,表示某个特定事件或条件已经发生 t_thrd.startup_cxt.NotifySigState->NotifySignalFlags[reason] = true; + // 使用 gs_signal_send 函数向指定的进程(由 ProcPid 参数指定)发送 SIGUSR2 信号 if (0 != gs_signal_send(ProcPid, SIGUSR2)) { + // 如果发送信号失败,生成一个警告消息,指示信号发送失败 ereport(WARNING, (errmsg("Send signal failed"))); } } @@ -502,10 +733,22 @@ void SendNotifySignal(NotifyReason reason, ThreadId ProcPid) /* * Check the notify sinal share memory, and find the reason of the signal. */ + +/* + * 功能:检查通知信号 + * + * 参数: + * reason-要检查的通知原 + * + * 返回值: + * bool类型,如果通知信号已触发,返回true;否则,返回false + */ bool CheckNotifySignal(NotifyReason reason) { /* Careful here --- don't clear flag if we haven't seen it set */ + // 根据指定的通知原因 reason 检查数组中相应的标志是否为 true,即某个特定事件或条件是否已经发生 if (t_thrd.startup_cxt.NotifySigState->NotifySignalFlags[reason]) { + // 重新设置为false,表示通知信号已被处理 t_thrd.startup_cxt.NotifySigState->NotifySignalFlags[reason] = false; return true; } @@ -515,19 +758,29 @@ bool CheckNotifySignal(NotifyReason reason) /* * Updata the static connection numbers in HaSHmData */ + +/* + * 功能:更新哈希共享内存数据结构中的静态连接数 + * + * 参数:无 + * + * 返回值:无 + */ static void SetStaticConnNum(void) { - volatile HaShmemData* hashmdata = t_thrd.postmaster_cxt.HaShmData; + volatile HaShmemData* hashmdata = t_thrd.postmaster_cxt.HaShmData; // 用于存储与高可用相关的共享信息 int i = 0; - int repl_list_num = 0; + int repl_list_num = 0; // 用于统计连接数 - for (i = 1; i < MAX_REPLNODE_NUM; i++) { + for (i = 1; i < MAX_REPLNODE_NUM; i++) { // 循环检查连接数组中的每个元素 + // 检查 ReplConnArray 数组中的元素是否为空指针,如果不为空则增加 repl_list_num 计数器的值 repl_list_num = (t_thrd.postmaster_cxt.ReplConnArray[i] != NULL) ? (repl_list_num + 1) : repl_list_num; + // 检查 CrossClusterReplConnArray 数组中的元素是否为空指针,如果不为空则增加 repl_list_num 计数器的值 repl_list_num = (t_thrd.postmaster_cxt.CrossClusterReplConnArray[i] != NULL) ? (repl_list_num + 1) : repl_list_num; - SpinLockAcquire(&hashmdata->mutex); - hashmdata->repl_list_num = repl_list_num; - SpinLockRelease(&hashmdata->mutex); + SpinLockAcquire(&hashmdata->mutex); // 获取哈希共享内存数据结构中的互斥锁 + hashmdata->repl_list_num = repl_list_num; // 更新计算得到的连接数值 + SpinLockRelease(&hashmdata->mutex); // 释放互斥锁 } } diff --git a/src/gausskernel/process/postmaster/syslogger.cpp b/src/gausskernel/process/postmaster/syslogger.cpp index fdb776080..db06f8aa9 100644 --- a/src/gausskernel/process/postmaster/syslogger.cpp +++ b/src/gausskernel/process/postmaster/syslogger.cpp @@ -47,7 +47,7 @@ #include "utils/timestamp.h" #include "gssignal/gs_signal.h" - +// 定义日志轮换的大小阈值,当日志文件大小达到或超过20MB时,将触发日志轮换 #define PROFILE_LOG_ROTATE_SIZE ((long)20 * 1024 * 1024L) /* @@ -55,6 +55,8 @@ * not have it, and interprets _IOLBF as _IOFBF (bozos). So use _IONBF * instead on Windows. */ +// 根据操作系统类型定义了用于日志文件输出的缓冲模式。 +// 在Windows上使用_IONBF缓冲模式,其他操作系统使用_IOLBF #ifdef WIN32 #define LBF_MODE _IONBF #else @@ -66,15 +68,19 @@ * left after processing can be moved down to the front and we'll still have * room to read a full chunk. */ +// 定义用于读取日志消息的缓冲区大小,是LOGPIPE_CHUNK_SIZE[系统管道的最大原子写入大小:512]的两倍 #define READ_BUF_SIZE (2 * LOGPIPE_CHUNK_SIZE) +// 定义用于保存错误消息的缓冲区大小,为1024字节 #define ERROR_BUF_SIZE 1024 +// 定义用于存储时区、主机名和节点名的字符数组 static char logCtlTimeZone[TZ_STRLEN_MAX + 1] = {0}; static char logCtlHostName[LOG_MAX_NODENAME_LEN] = {0}; static char logCtlNodeName[LOG_MAX_NODENAME_LEN] = {0}; /* put all LogControlData of all log types into this array */ +// 定义数组,用于存储不同日志类型的LogControlData结构 static LogControlData* allLogCtl[LOG_TYPE_MAXVALID + 1] = { NULL, /* LOG_TYPE_ELOG */ NULL, /* LOG_TYPE_PLOG */ @@ -84,6 +90,7 @@ static LogControlData* allLogCtl[LOG_TYPE_MAXVALID + 1] = { }; /* exclude error log in this FOR loop */ +// 用于循环处理日志控制数据的迭代器,排除了错误日志类型 #define foreach_logctl(_logctl) for (int i = LOG_TYPE_ELOG + 1; ((_logctl) = allLogCtl[i]) != NULL; ++i) /* @@ -97,18 +104,21 @@ static LogControlData* allLogCtl[LOG_TYPE_MAXVALID + 1] = { * An inactive buffer is not removed from its list, just held for re-use. * An inactive buffer has pid == 0 and undefined contents of data. */ +// 用于保存来自不同后端进程的部分消息 +// 有效地管理来自多个后端进程的部分消息,减少处理每个传入消息时需要检查的条目数量,以便在适当的时候将它们组装成完整的消息并进行记录 typedef struct { - ThreadId pid; /* PID of source process */ - StringInfoData data; /* accumulated data, as a StringInfo */ + ThreadId pid; /* PID of source process */ // 源进程的进程ID,对于相同的源进程PID,不允许存在多个缓冲区条目 + StringInfoData data; /* accumulated data, as a StringInfo */ // 用于存储累积的数据 } save_buffer; /* These must be exported for EXEC_BACKEND case ... annoying */ #ifndef WIN32 #else -HANDLE syslogPipe[2] = {0, 0}; +HANDLE syslogPipe[2] = {0, 0}; // 用于在Windows下处理系统日志的管道 #endif +// 用于处理多线程操作 #ifdef WIN32 static HANDLE threadHandle = 0; static CRITICAL_SECTION sysloggerSection; @@ -118,15 +128,20 @@ static CRITICAL_SECTION sysloggerSection; * Flags set by interrupt handlers for later service in the main loop. */ /* Local subroutines */ +// 用于设置syslogger的文件描述符,以指定将日志写入的目标文件或管道 static void syslogger_setfd(int fd); +// 用于处理从管道中读取的日志输入,将其存储在缓冲区中 static void process_pipe_input(char* logbuffer, int* bytes_in_logbuffer); +// 用于刷新管道输入缓冲区中的内容 static void flush_pipe_input(char* logbuffer, int* bytes_in_logbuffer); +// 用于打开CSV格式的日志文件,以准备记录日志消息 static void open_csvlogfile(void); #ifdef ENABLE_UT #define static #endif +// 用于打开指定的日志文件,接受文件名、模式和是否允许错误作为参数 static FILE* logfile_open(const char* filename, const char* mode, bool allow_errors); #if defined(ENABLE_UT) && defined(static) @@ -134,14 +149,23 @@ static FILE* logfile_open(const char* filename, const char* mode, bool allow_err #endif #ifdef WIN32 +// 仅在Windows下定义,用于处理管道输入的线程 static unsigned int __stdcall pipeThread(void* arg); #endif +// 用于执行日志文件的轮换操作,可以基于时间或大小触发 static void logfile_rotate(bool time_based_rotation, int size_rotation_for); +// 用于生成日志文件的名称,接受时间戳、后缀、日志目录和文件名作为参数 static char* logfile_getname(pg_time_t timestamp, const char* suffix, const char* logdir, const char* filename); +// 用于设置下一次日志文件轮换的时间 static void set_next_rotation_time(void); +// 用于处理SIGHUP和SIGUSR1信号 static void sigHupHandler(SIGNAL_ARGS); static void sigUsr1Handler(SIGNAL_ARGS); +// 用于管理日志控制数据的一组函数 +// 设置全局名称、设置时区、获取日志目录、 +// 创建日志目录、写入文件头、检查是否需要轮换日志文件、 +// 刷新缓冲区、获取文件名模式以及处理输入消息 static void LogCtlSetGlobalNames(void); static void LogCtlSetTimeZone(void); static char* LogCtlGetLogDirectory(const char* logid, bool include_nodename); @@ -151,6 +175,7 @@ static void LogCtlRotateLogFileIfNeeded(LogControlData*, bool); static void LogCtlFlushBuf(LogControlData* logctl); static char* LogCtlGetFilenamePattern(const char* post_suffix); static void LogCtlProcessInput(LogControlData* logctl, const char* msg, int len); +// 用于初始化日志控制数据以及特定日志类型的轮换操作的一组函数 static void PLogCtlInit(void); static void slow_query_logfile_rotate(bool time_based_rotation, int size_rotation_for); static void asp_logfile_rotate(bool time_based_rotation, int size_rotation_for); @@ -159,35 +184,45 @@ static void asp_logfile_rotate(bool time_based_rotation, int size_rotation_for); * Main entry point for syslogger process * argc/argv parameters are valid only in EXEC_BACKEND case. */ +/* + * 功能:syslogger进程的主要入口点函数 + * + * 参数: + * fd:在 EXEC_BACKEND 情况下才有效,用于指定日志写入的文件描述符 + * + * 返回值:无 + */ NON_EXEC_STATIC void SysLoggerMain(int fd) { #ifndef WIN32 - char logbuffer[READ_BUF_SIZE]; - int bytes_in_logbuffer = 0; + char logbuffer[READ_BUF_SIZE]; // 用于缓冲日志消息 + int bytes_in_logbuffer = 0; // 用于跟踪缓冲区中的字节数 #endif - LogControlData* logctl = NULL; + LogControlData* logctl = NULL; // 用于管理日志控制数据的指针 + // 用于跟踪当前日志目录、文件名和轮换年龄的变量 char* currentLogDir = NULL; char* currentLogFilename = NULL; int currentLogRotationAge; + // 当前时间的时间戳 pg_time_t now; DISABLE_MEMORY_PROTECT(); - + IsUnderPostmaster = true; /* we are a postmaster subprocess now */ - + // 设置为当前线程的PID t_thrd.proc_cxt.MyProcPid = gs_thread_self(); /* reset t_thrd.proc_cxt.MyProcPid */ - + // 记录syslogger启动时间 t_thrd.proc_cxt.MyStartTime = time(NULL); /* set our start time in case we call elog */ now = t_thrd.proc_cxt.MyStartTime; - + // 标识syslogger的进程名 t_thrd.proc_cxt.MyProgName = "syslogger"; - + // 标识syslogger的逻辑线程ID t_thrd.myLogicTid = noProcLogicTid + SYSLOGGER_LID; - + // 设置syslogger的文件描述符,指定将日志写入的目标文件或管道 syslogger_setfd(fd); - t_thrd.role = SYSLOGGER; - + t_thrd.role = SYSLOGGER; // 表示syslogger的角色 + // 初始化进程状态显示,设置进程的名称 init_ps_display("logger process", "", "", ""); /* @@ -197,6 +232,7 @@ NON_EXEC_STATIC void SysLoggerMain(int fd) */ #ifdef WIN32 else + // 在Windows环境下,将标准错误的模式设置为文本模式 _setmode(_fileno(stderr), _O_TEXT); #endif @@ -206,14 +242,16 @@ NON_EXEC_STATIC void SysLoggerMain(int fd) * case, the postmaster already did this.) */ #ifndef WIN32 + // 关闭写入端的管道描述符,用于确保可以正确检测管道的EOF t_thrd.postmaster_cxt.syslogPipe[1] = -1; #else syslogPipe[1] = 0; #endif - + // 初始化Latch支持,用于等待事件 InitializeLatchSupport(); /* needed for latch waits */ /* Initialize private latch for use by signal handlers */ + // 初始化sysLoggerLatch,用于等待信号处理程序 InitLatch(&t_thrd.logger.sysLoggerLatch); /* @@ -226,6 +264,9 @@ NON_EXEC_STATIC void SysLoggerMain(int fd) /* * Reset some signals that are accepted by postmaster but not here */ + // 设置信号处理程序 + // 其中 SIGHUP 用于重新加载配置文件,SIGUSR1 用于请求日志轮换 + // 其余信号忽略 (void)gspqsignal(SIGHUP, sigHupHandler); /* set flag to read config file */ (void)gspqsignal(SIGINT, SIG_IGN); (void)gspqsignal(SIGTERM, SIG_IGN); @@ -238,37 +279,51 @@ NON_EXEC_STATIC void SysLoggerMain(int fd) /* * Reset some signals that are accepted by postmaster but not here */ + // 设置对子进程状态改变等信号的处理方式,恢复默认行为 (void)gspqsignal(SIGCHLD, SIG_DFL); (void)gspqsignal(SIGTTIN, SIG_DFL); (void)gspqsignal(SIGTTOU, SIG_DFL); (void)gspqsignal(SIGCONT, SIG_DFL); (void)gspqsignal(SIGWINCH, SIG_DFL); + // 设置信号掩码,确保SIGUSR2信号不被阻塞 gs_signal_setmask(&t_thrd.libpq_cxt.UnBlockSig, NULL); (void)gs_signal_unblock_sigusr2(); #ifdef WIN32 /* Fire up separate data transfer thread */ + // 初始化一个临界区,用于确保多个线程不会同时访问关键部分的代码,以避免竞态条件 InitializeCriticalSection(&sysloggerSection); + // 进入临界区,标志着当前线程要执行关键部分的代码 EnterCriticalSection(&sysloggerSection); - + // 在 Windows 下创建一个新的线程,用于数据传输 + // pipeThread 是线程的入口点函数 threadHandle = (HANDLE)_beginthreadex(NULL, 0, pipeThread, NULL, 0, NULL); - if (threadHandle == 0) + if (threadHandle == 0) // 检查线程创建是否成功 + // 如果线程创建失败,报告致命错误并退出进程 ereport(FATAL, (errmsg("could not create syslogger data transfer thread: %m"))); #endif /* WIN32 */ - PLogCtlInit(); + PLogCtlInit(); // 初始化日志控制数据,用于管理不同类型的日志 /* create slow query directory */ + // 检查配置文件中是否指定了慢查询日志目录 if (g_instance.attr.attr_common.query_log_directory == NULL) { + // 如果没有指定,则调用 init_instr_log_directory 创建默认的目录 init_instr_log_directory(true, SLOWQUERY_LOG_TAG); } else { + // 如果指定了目录,则使用 pg_mkdir_p 函数创建目录 + // 设置其权限为 S_IRWXU(用户可读、可写、可执行) (void)pg_mkdir_p(g_instance.attr.attr_common.query_log_directory, S_IRWXU); } /* create asp directory */ + // 检查配置文件中是否指定了ASP 日志目录 if (g_instance.attr.attr_common.asp_log_directory == NULL) { + // 如果没有指定,则调用 init_instr_log_directory 创建默认的目录 init_instr_log_directory(true, ASP_LOG_TAG); } else { + // 如果指定了目录,则使用 pg_mkdir_p 函数创建目录 + // 设置其权限为 S_IRWXU(用户可读、可写、可执行) (void)pg_mkdir_p(g_instance.attr.attr_common.asp_log_directory, S_IRWXU); } @@ -278,12 +333,16 @@ NON_EXEC_STATIC void SysLoggerMain(int fd) * time because passing down just the pg_time_t is a lot cheaper than * passing a whole file path in the EXEC_BACKEND case. */ + // 记录当前活跃的日志文件的名称 + // 节省在 EXEC_BACKEND 情况下传递整个文件路径的开销 t_thrd.logger.last_file_name = logfile_getname(t_thrd.logger.first_syslogger_file_time, NULL, u_sess->attr.attr_common.Log_directory, u_sess->attr.attr_common.Log_filename); + // 打开当前活跃的日志文件,使用 "a" 模式打开文件以追加写入 + // false 表示不允许在发生错误时报告错误 t_thrd.logger.syslogFile = logfile_open(t_thrd.logger.last_file_name, "a", false); - + // 循环处理其他类型的日志文件 foreach_logctl(logctl) { /* * fd leaking maybe happens when syslogger thread is @@ -291,23 +350,28 @@ NON_EXEC_STATIC void SysLoggerMain(int fd) * maybe include switch over case. * if the thread restarted, the original memory context must be deleted and need not free the memory */ + // 计算当前日志文件的名称 logctl->now_file_name = logfile_getname(t_thrd.logger.first_syslogger_file_time, NULL, logctl->log_dir, logctl->filename_pattern); - + // 如果当前日志文件的文件描述符不为 NULL,则关闭文件 if (logctl->now_file_fd != NULL) { (void)fclose(logctl->now_file_fd); logctl->now_file_fd = NULL; } + // 打开当前日志文件,使用 "a" 模式追加写入 + // false 表示不允许在发生错误时报告错误 logctl->now_file_fd = logfile_open(logctl->now_file_name, "a", false); + // 写入文件头部信息 LogCtlWriteFileHeader(logctl); } /* remember active logfile parameters */ + // 记录当前活跃的日志文件的目录、文件名和轮换年龄 currentLogDir = pstrdup(u_sess->attr.attr_common.Log_directory); currentLogFilename = pstrdup(u_sess->attr.attr_common.Log_filename); currentLogRotationAge = u_sess->attr.attr_common.Log_RotationAge; /* set next planned rotation time */ - set_next_rotation_time(); + set_next_rotation_time(); // 设置下一次计划轮换的时间 /* main worker loop */ for (;;) { @@ -321,39 +385,46 @@ NON_EXEC_STATIC void SysLoggerMain(int fd) #endif /* Clear any already-pending wakeups */ - ResetLatch(&t_thrd.logger.sysLoggerLatch); + ResetLatch(&t_thrd.logger.sysLoggerLatch); // 清除任何已经发生的等待事件 /* * Process any requests or signals received recently. */ - if (t_thrd.logger.got_SIGHUP) { - t_thrd.logger.got_SIGHUP = false; - ProcessConfigFile(PGC_SIGHUP); + if (t_thrd.logger.got_SIGHUP) { // 如果接收到 SIGHUP 信号 + t_thrd.logger.got_SIGHUP = false; // 清除标志 got_SIGHUP + ProcessConfigFile(PGC_SIGHUP); // 处理配置文件的变化 /* * Check if the log directory or filename pattern changed in * postgresql.conf. If so, force rotation to make sure we're * writing the logfiles in the right place. */ + // 如果配置文件中的日志目录 Log_directory 发生了变化 if (strcmp(u_sess->attr.attr_common.Log_directory, currentLogDir) != 0) { - pfree(currentLogDir); + pfree(currentLogDir); // 释放之前的目录字符串 + // 重新分配一个新的,以反映新的目录 currentLogDir = pstrdup(u_sess->attr.attr_common.Log_directory); - t_thrd.logger.rotation_requested = true; + t_thrd.logger.rotation_requested = true; // 表示需要执行日志轮换 /* not affect pLogCtl's Log_directory */ /* * Also, create new directory if not present; ignore errors */ + // 如果新的目录不存在,则创建一个新目录,权限设置为 S_IRWXU(用户可读、可写、可执行) mkdir(u_sess->attr.attr_common.Log_directory, S_IRWXU); } + // 如果配置文件中的日志文件名 Log_filename 发生了变化 if (strcmp(u_sess->attr.attr_common.Log_filename, currentLogFilename) != 0) { - pfree(currentLogFilename); + pfree(currentLogFilename); // 释放之前的目录字符串 + // 重新分配一个新的,以反映新的目录 currentLogFilename = pstrdup(u_sess->attr.attr_common.Log_filename); - t_thrd.logger.rotation_requested = true; - foreach_logctl(logctl) { + t_thrd.logger.rotation_requested = true; // 表示需要执行日志轮换 + foreach_logctl(logctl) { // 遍历每个日志类型 + // 如果其文件名模式 filename_pattern 不为空 if (logctl->filename_pattern != NULL) { - pfree_ext(logctl->filename_pattern); + pfree_ext(logctl->filename_pattern); // 释放之前的模式字符串 } /* recompute log file pattern */ + // 重新计算新的模式 logctl->filename_pattern = LogCtlGetFilenamePattern(logctl->file_suffix); logctl->rotation_requested = true; } @@ -363,8 +434,10 @@ NON_EXEC_STATIC void SysLoggerMain(int fd) * If rotation time parameter changed, reset next rotation time, * but don't immediately force a rotation. */ + // 如果配置文件中的日志轮换时间参数 Log_RotationAge 发生了变化 if (currentLogRotationAge != u_sess->attr.attr_common.Log_RotationAge) { currentLogRotationAge = u_sess->attr.attr_common.Log_RotationAge; + // 重新计算下一次计划轮换的时间,但不会立即强制执行日志轮换 set_next_rotation_time(); } @@ -372,49 +445,70 @@ NON_EXEC_STATIC void SysLoggerMain(int fd) * If we had a rotation-disabling failure, re-enable rotation * attempts after SIGHUP, and force one immediately. */ + // 如果之前因为某种原因禁用了日志轮换 if (t_thrd.logger.rotation_disabled) { t_thrd.logger.rotation_disabled = false; - t_thrd.logger.rotation_requested = true; + t_thrd.logger.rotation_requested = true; // 重新启用轮换 + // 对每个日志类型标记为需要轮换 foreach_logctl(logctl) { /* keep the same step with error log, and rotate this log file */ logctl->rotation_requested = true; } } } - + // 处理时间和大小导致的日志轮换 + // 如果启用了基于时间的日志轮换且未被禁用,会检查是否应该进行时间导致的轮换 if (u_sess->attr.attr_common.Log_RotationAge > 0 && !t_thrd.logger.rotation_disabled) { /* Do a logfile rotation if it's time */ - now = (pg_time_t)time(NULL); + now = (pg_time_t)time(NULL); // 获取当前时间 + // 比较是否超过了下一次计划轮换的时间 next_rotation_time if (now >= t_thrd.logger.next_rotation_time) { + // 如果超过了,则标记需要执行时间轮换 t_thrd.logger.rotation_requested = time_based_rotation = true; + // 对每个日志类型标记需要轮换 foreach_logctl(logctl) { /* share the same rotation age */ logctl->rotation_requested = true; } } } - + // 如果没有任何轮换请求,且启用了基于大小的日志轮换且未被禁用,会检查是否应该进行大小导致的轮换 if (!t_thrd.logger.rotation_requested && u_sess->attr.attr_common.Log_RotationSize > 0 && !t_thrd.logger.rotation_disabled) { /* Do a rotation if file is too big */ + // ftell函数用于获取当前打开文件的位置指针,也就是当前文件的大小(以字节为单位) + // u_sess->attr.attr_common.Log_RotationSize 是配置文件中设置的日志大小轮换阈值,以千字节为单位 + // 检查当前日志文件的大小是否超过了配置文件中设置的轮换大小 if (ftell(t_thrd.logger.syslogFile) >= u_sess->attr.attr_common.Log_RotationSize * 1024L) { + // 如果超过了,则标记需要执行大小轮换 t_thrd.logger.rotation_requested = true; + // 指示标准错误日志需要进行轮换 size_rotation_for |= LOG_DESTINATION_STDERR; } + // 类似的检查针对其他日志类型,CSV 日志、查询日志和 ASP 日志 + // 检查当前日志文件的大小是否超过了配置文件中设置的轮换大小 if (t_thrd.logger.csvlogFile != NULL && ftell(t_thrd.logger.csvlogFile) >= u_sess->attr.attr_common.Log_RotationSize * 1024L) { + // 如果超过了,则标记需要执行大小轮换 t_thrd.logger.rotation_requested = true; + // 指示标准错误日志需要进行轮换 size_rotation_for |= LOG_DESTINATION_CSVLOG; } + // 检查当前日志文件的大小是否超过了配置文件中设置的轮换大小 if (t_thrd.logger.querylogFile != NULL && ftell(t_thrd.logger.querylogFile) >= u_sess->attr.attr_common.Log_RotationSize * 1024L) { + // 如果超过了,则标记需要执行大小轮换 t_thrd.logger.rotation_requested = true; + // 指示标准错误日志需要进行轮换 size_rotation_for |= LOG_DESTINATION_QUERYLOG; } + // 检查当前日志文件的大小是否超过了配置文件中设置的轮换大小 if (t_thrd.logger.asplogFile != NULL && ftell(t_thrd.logger.asplogFile) >= u_sess->attr.attr_common.Log_RotationSize * 1024L) { + // 如果超过了,则标记需要执行大小轮换 t_thrd.logger.rotation_requested = true; + // 指示标准错误日志需要进行轮换 size_rotation_for |= LOG_DESTINATION_ASPLOG; } } @@ -424,21 +518,28 @@ NON_EXEC_STATIC void SysLoggerMain(int fd) * logfile_rotate() will change t_thrd.logger.next_rotation_time value by calling * set_next_rotation_time(). */ + // 遍历不同类型的日志 foreach_logctl(logctl) { + // 检查特定类型日志文件是否需要进行轮换操作 + // logctl 是当前正在检查的日志类型的控制结构,time_based_rotation 是一个标志,指示是否基于时间执行轮换 LogCtlRotateLogFileIfNeeded(logctl, time_based_rotation); } - if (t_thrd.logger.rotation_requested) { + if (t_thrd.logger.rotation_requested) { // 检查是否需要执行日志轮换 /* * Force rotation when both values are zero. It means the request * was sent by pg_rotate_logfile. */ + // 如果既不是基于时间的轮换也没有特定的大小轮换要求,那么强制执行轮换 if (!time_based_rotation && size_rotation_for == 0) size_rotation_for = LOG_DESTINATION_STDERR | LOG_DESTINATION_CSVLOG | LOG_DESTINATION_QUERYLOG | LOG_DESTINATION_ASPLOG; + // 执行 ASP日志的轮换操作 asp_logfile_rotate(time_based_rotation, size_rotation_for); + // 执行慢查询日志的轮换操作 slow_query_logfile_rotate(time_based_rotation, size_rotation_for); /* only last one can recalculate next_rotation_time */ + // 执行错误日志的轮换操作 logfile_rotate(time_based_rotation, size_rotation_for); } @@ -454,17 +555,18 @@ NON_EXEC_STATIC void SysLoggerMain(int fd) * could be more than INT_MAX msec in the future. In that case we'll * wait no more than INT_MAX msec, and try again. */ + // 检查是否启用了时间轮换且未禁用轮换 if (u_sess->attr.attr_common.Log_RotationAge > 0 && !t_thrd.logger.rotation_disabled) { pg_time_t delay; - + // 计算距离下一次时间轮换的剩余时间 delay = t_thrd.logger.next_rotation_time - now; - if (delay > 0) { - if (delay > INT_MAX / 1000) - delay = INT_MAX / 1000; - cur_timeout = delay * 1000L; /* msec */ - } else - cur_timeout = 0; - cur_flags = WL_TIMEOUT; + if (delay > 0) { // 如果还需要等待一段时间才能进行时间轮换 + if (delay > INT_MAX / 1000) // 如果剩余时间超过了 INT_MAX 毫秒 + delay = INT_MAX / 1000; // 避免整数溢出 + cur_timeout = delay * 1000L; /* msec */ // 将剩余时间转换为毫秒,以便在休眠时使用 + } else // 表示下一次轮换已到期 + cur_timeout = 0; // 以便立即唤醒进程 + cur_flags = WL_TIMEOUT; // 根据等待超时时间设置标志,表示只等待超时事件 } else { cur_timeout = -1L; cur_flags = 0; @@ -474,25 +576,30 @@ NON_EXEC_STATIC void SysLoggerMain(int fd) * Sleep until there's something to do */ #ifndef WIN32 + // 使用 WaitLatchOrSocket 函数等待多个事件 + // 包括 WL_LATCH_SET(等待事件)、WL_SOCKET_READABLE(套接字可读事件)和 cur_flags(超时事件) rc = WaitLatchOrSocket(&t_thrd.logger.sysLoggerLatch, WL_LATCH_SET | WL_SOCKET_READABLE | cur_flags, t_thrd.postmaster_cxt.syslogPipe[0], cur_timeout); - if (rc & WL_SOCKET_READABLE) { + if (rc & WL_SOCKET_READABLE) { // 如果套接字可读事件发生 int bytesRead; - + // 从管道读取数据并调用 process_pipe_input 处理数据 bytesRead = read(t_thrd.postmaster_cxt.syslogPipe[0], logbuffer + bytes_in_logbuffer, sizeof(logbuffer) - bytes_in_logbuffer); - if (bytesRead < 0) { - if (errno != EINTR) + if (bytesRead < 0) { // 如果读取的字节数小于零 + if (errno != EINTR) // 如果 errno 不是 EINTR + // 记录一条日志 ereport(LOG, (errcode_for_socket_access(), errmsg("could not read from logger pipe: %m"))); } else if (bytesRead > 0) { + // 如果读取的字节数大于零,表示管道中有新的数据 bytes_in_logbuffer += bytesRead; process_pipe_input(logbuffer, &bytes_in_logbuffer); - continue; + continue; // 继续处理 } else { + // 如果读取的字节数等于零,表示管道已关闭 /* * ELSE branch is never executed forever in multi-thread mode * @@ -501,9 +608,10 @@ NON_EXEC_STATIC void SysLoggerMain(int fd) * with the pipe write end open. Therefore, the postmaster * and all backends are shut down, and we are done. */ - t_thrd.logger.pipe_eof_seen = true; + t_thrd.logger.pipe_eof_seen = true; // 表示日志子进程已退出 /* if there's any data left then force it out now */ + // 强制刷新管道中的剩余数据 flush_pipe_input(logbuffer, &bytes_in_logbuffer); } } @@ -518,18 +626,20 @@ NON_EXEC_STATIC void SysLoggerMain(int fd) * of the threads is active at a time by entering the critical section * whenever we're not sleeping. */ + // 离开临界区,表示不再占用共享资源 LeaveCriticalSection(&sysloggerSection); - + // 等待事件的发生 (void)WaitLatch(&t_thrd.logger.sysLoggerLatch, WL_LATCH_SET | cur_flags, cur_timeout); - + // 重新进入临界区,继续占用共享资源 EnterCriticalSection(&sysloggerSection); #endif /* WIN32 */ - if (t_thrd.logger.pipe_eof_seen) { + if (t_thrd.logger.pipe_eof_seen) { // 检查是否看到了管道已关闭的标志 /* * seeing this message on the real stderr is annoying - so we make * it DEBUG1 to suppress in normal use. */ + // 如果是,记录一条 DEBUG1 级别的日志,表示日志子进程正在关闭 ereport(DEBUG1, (errmsg("logger shutting down"))); /* @@ -547,12 +657,20 @@ NON_EXEC_STATIC void SysLoggerMain(int fd) /* * Postmaster subroutine to start a syslogger subprocess. */ +/* + * 功能:启动系统日志管理进程 + * + * 参数:无 + * + * 返回值:ThreadId类型,表示新创建的子进程的线程 ID + */ ThreadId SysLogger_Start(void) { - ThreadId sysloggerPid; - char* filename = NULL; - + ThreadId sysloggerPid; // 用于存储新创建的子进程的线程 ID + char* filename = NULL; // 用于存储日志文件名 + // 检查配置参数 Logging_collector 是否为假 if (!g_instance.attr.attr_common.Logging_collector) + // 如果为假,则直接返回 0,表示不启动日志收集进程 return 0; /* @@ -568,21 +686,25 @@ ThreadId SysLogger_Start(void) * is a bit klugy but we have little choice. */ #ifndef WIN32 - if (t_thrd.postmaster_cxt.syslogPipe[0] < 0) { + if (t_thrd.postmaster_cxt.syslogPipe[0] < 0) { // 检查管道是否已经创建 + // 如果管道尚未创建,则创建 if (pipe(t_thrd.postmaster_cxt.syslogPipe) < 0) + // 如果创建失败,将触发致命错误,系统无法继续执行 ereport(FATAL, (errcode_for_socket_access(), (errmsg("could not create pipe for syslog: %m")))); } #else - if (!t_thrd.postmaster_cxt.syslogPipe[0]) { + if (!t_thrd.postmaster_cxt.syslogPipe[0]) { // 检查管道是否已创建 SECURITY_ATTRIBUTES sa; - + // 初始化 errno_t rc = memset_s(&sa, sizeof(SECURITY_ATTRIBUTES), 0, sizeof(SECURITY_ATTRIBUTES)); securec_check_c(rc, "\0", "\0"); - + // 设置结构体的长度和允许继承句柄的标志 sa.nLength = sizeof(SECURITY_ATTRIBUTES); sa.bInheritHandle = TRUE; + // 创建管道,保存读取端和写入端的句柄 if (!CreatePipe(&t_thrd.postmaster_cxt.syslogPipe[0], &t_thrd.postmaster_cxt.syslogPipe[1], &sa, 32768)) + // 如果创建失败,将触发致命错误,系统无法继续执行 ereport(FATAL, (errcode_for_file_access(), (errmsg("could not create pipe for syslog: %m")))); } #endif @@ -590,13 +712,14 @@ ThreadId SysLogger_Start(void) /* * Create log directory if not present; ignore errors */ + // 如果目录不存在,创建日志目录;如果目录已经存在或者创建失败,会忽略错误 (void)pg_mkdir_p(u_sess->attr.attr_common.Log_directory, S_IRWXU); /* create log directory */ - LogCtlCreateLogParentDirectory(); + LogCtlCreateLogParentDirectory(); // 创建日志的父目录 /* set global names from postmaster */ - LogCtlSetGlobalNames(); + LogCtlSetGlobalNames(); // 设置全局的日志文件名和文件后缀 /* set time zone from postmaster */ - LogCtlSetTimeZone(); + LogCtlSetTimeZone(); // 设置日志的时区 /* * The initial logfile is created right in the postmaster, to verify that @@ -610,31 +733,42 @@ ThreadId SysLogger_Start(void) * is consistent with the normal rules, because by definition this is not * a time-based rotation. */ + // 创建初始的日志文件,以验证日志目录是否可写。 + // 函数记录了创建这个文件的时间戳,以供后续的系统日志管理进程使用 t_thrd.logger.first_syslogger_file_time = time(NULL); + // 生成日志文件的名称,并将其存储 filename = logfile_getname(t_thrd.logger.first_syslogger_file_time, NULL, u_sess->attr.attr_common.Log_directory, u_sess->attr.attr_common.Log_filename); - pfree(filename); + pfree(filename); // 释放内存 - sysloggerPid = initialize_util_thread(SYSLOGGER); + sysloggerPid = initialize_util_thread(SYSLOGGER); // 启动syslogger线程 /* success, in postmaster */ if (sysloggerPid != 0) { + // 如果成功启动了系统日志管理进程 /* now we redirect stderr, if not done already */ if (!t_thrd.postmaster_cxt.redirection_done) { #ifndef WIN32 - fflush(stdout); + // 在非 Windows 平台上重定向 stderr 输出 + fflush(stdout); // 刷新标准输出缓冲区,以确保任何待输出的内容都被写入 + // 使用 dup2 函数将管道的写入端复制到标准输出的文件描述符 if (dup2(t_thrd.postmaster_cxt.syslogPipe[1], fileno(stdout)) < 0) + // 如果复制失败,会触发致命错误 ereport(FATAL, (errcode_for_file_access(), errmsg("could not redirect stdout: %m"))); - fflush(stderr); + fflush(stderr); // 刷新标准错误输出缓冲区,以确保任何待输出的错误信息都被写入 + // 使用 dup2 函数将管道的写入端复制到标准错误输出的文件描述符 if (dup2(t_thrd.postmaster_cxt.syslogPipe[1], fileno(stderr)) < 0) + // 如果复制失败,会触发致命错误 ereport(FATAL, (errcode_for_file_access(), errmsg("could not redirect stderr: %m"))); /* Now we are done with the write end of the pipe. */ + // 关闭管道的写入端,因为标准输出和标准错误输出已经重定向到了该管道 close(t_thrd.postmaster_cxt.syslogPipe[1]); t_thrd.postmaster_cxt.syslogPipe[1] = -1; #else + // 在 Windows 平台上重定向 stderr 输出 int fd; /* @@ -658,9 +792,9 @@ ThreadId SysLogger_Start(void) #endif t_thrd.postmaster_cxt.redirection_done = true; } - + // 表示该进程已经开始管理日志 t_thrd.logger.syslogFile = NULL; - return sysloggerPid; + return sysloggerPid; // 返回新创建的子进程的线程 ID } /* we should never reach here */ @@ -668,11 +802,19 @@ ThreadId SysLogger_Start(void) } /* close the t_thrd.logger.syslogFile */ +/* + * 功能:关闭 syslogger 进程中的日志文件 + * + * 参数:无 + * + * 返回值:无 + */ void SysLoggerClose(void) { - if (t_thrd.logger.syslogFile) { + if (t_thrd.logger.syslogFile) { // 检查当前线程是否已经打开了一个日志文件 + // 如果已经打开了日志文件,则关闭该文件。将文件缓冲区中的数据写入磁盘,并释放相关的资源 fclose(t_thrd.logger.syslogFile); - t_thrd.logger.syslogFile = NULL; + t_thrd.logger.syslogFile = NULL; // 表示日志文件已经关闭 } } @@ -683,25 +825,36 @@ void SysLoggerClose(void) * * print error in syslogger thread */ + +/* + * 功能:将日志信息写入指定的文件 + * + * 参数: + * file:用于指定要写入的文件 + * buffer:用于指定要写入的内容 + * + * 返回值:无 + */ static void syslogger_erewrite(FILE* file, const char* buffer) { - int tryTimes = 0; - for (;;) { + int tryTimes = 0; // 用于记录重试的次数 + for (;;) { // 无限循环,用于多次尝试写入内容 /* clear errno before calling IO write */ - errno = 0; - int buffer_len = (int)strlen(buffer); - int rc = fwrite(buffer, 1, buffer_len, file); + errno = 0; // 清除之前可能存在的错误信息 + int buffer_len = (int)strlen(buffer); // 计算要写入的内容 buffer 的长度 + int rc = fwrite(buffer, 1, buffer_len, file); // 将 buffer 中的内容写入到指定的文件 + // 检查写入操作是否发生了错误或未完全写入 if ((errno != 0) || (rc != buffer_len)) { - tryTimes++; - if (tryTimes >= 3) - break; + tryTimes++; // 增加 tryTimes 计数器,表示尝试次数加一 + if (tryTimes >= 3) // 如果尝试次数达到 3 次,表示已经尝试了多次写入但仍然失败 + break; // 退出循环 /* * if no disk space, we will retry, * and we can not report a log, because there is not space to write. */ - if (errno == ENOSPC) { - break; + if (errno == ENOSPC) { // 如果错误代码 errno 表示磁盘空间不足 + break; // 退出循环,因为在没有足够磁盘空间的情况下,无法继续写入日志信息 } } break; @@ -713,15 +866,26 @@ static void syslogger_erewrite(FILE* file, const char* buffer) * * Extract data from the arglist for exec'ed syslogger process */ + +/* + * 功能:从参数中提取文件描述符,并为其创建文件流 + * + * 参数: + * fd:表示文件描述符 + * + * 返回值:无 + */ static void syslogger_setfd(int fd) { - if (fd != -1) { + if (fd != -1) { // 检查是否存在有效的文件描述符 + // 将文件描述符 fd 打开为文件流,并将文件流指针赋值 t_thrd.logger.syslogFile = fdopen(fd, "a"); - if (t_thrd.logger.syslogFile == NULL) { + if (t_thrd.logger.syslogFile == NULL) { // 检查文件流是否创建成功 + // 如果为空表示创建失败,生成错误报告 ereport(ERROR, (errcode_for_file_access(), errmsg("syslogger could not open file %d: %m,exit\n", fd))); proc_exit(1); } - + // 设置文件流缓冲方式为不使用缓冲(_IONBF),参数 0 表示不设置缓冲大小 setvbuf(t_thrd.logger.syslogFile, NULL, LBF_MODE, 0); } } @@ -732,15 +896,34 @@ static void syslogger_setfd(int fd) * * check whether p has the features of a LogPipeProtoHeader */ + +/* + * 功能:检查给定的数据结构 LogPipeProtoHeader 是否符合一定的特征 + * + * 参数: + * P:LogPipeProtoHeader 结构体 + * + * 返回值:bool类型,表示是否符合特定特征 + * 如果符合特征,返回 true,表示通过检查; + * 否则返回 false,表示未通过检查 + */ static bool CheckPipeProtoHeader(const LogPipeProtoHeader p) { + /* 检查结构体 p 是否符合: + * 结构体的前两个字节是否都为 NULL 字符('\0'),表示字符串结束符 + * 结构体的 len 字段是否大于 0 且不超过 LOGPIPE_MAX_PAYLOAD,len 表示数据负载的大小 + * 结构体的 pid 字段是否不等于 0,pid 表示进程的 ID + * 结构体的 is_last 字段是否为指定的字符 + * 结构体的 logtype 字段是否在指定范围内,logtype 表示日志类型 + * 结构体的 magic 字段是否等于指定的魔术数字 PROTO_HEADER_MAGICNUM + */ if (p.nuls[0] == '\0' && p.nuls[1] == '\0' && p.len > 0 && p.len <= LOGPIPE_MAX_PAYLOAD && p.pid != 0 && (p.is_last == 't' || p.is_last == 'f' || p.is_last == 'T' || p.is_last == 'F') && p.logtype >= LOG_TYPE_ELOG && p.logtype < LOG_TYPE_MAXVALID && p.magic == PROTO_HEADER_MAGICNUM) - return true; - return false; + return true; // 符合上述特征,返回 true,表示通过检查 + return false; // 返回 false,表示未通过检查 } /* -------------------------------- * pipe protocol handling @@ -769,70 +952,97 @@ static bool CheckPipeProtoHeader(const LogPipeProtoHeader p) * of bytes present. On exit, any not-yet-eaten data is left-justified in * logbuffer, and *bytes_in_logbuffer is updated. */ + +/* + * 功能:处理通过 syslogger 管道接收到的数据,并根据日志管道协议将日志消息组装起来 + * + * 参数: + * logbuffer:数据输入缓冲区 + * bytes_in_logbuffer:缓冲区中的字节数 + * + * 返回值:bool类型,表示是否符合特定特征 + * 如果符合特征,返回 true,表示通过检查; + * 否则返回 false,表示未通过检查 + */ static void process_pipe_input(char* logbuffer, int* bytes_in_logbuffer) { char* cursor = logbuffer; - int count = *bytes_in_logbuffer; - int dest = LOG_DESTINATION_STDERR; + int count = *bytes_in_logbuffer; // 表示数据输入缓冲区中的字节数 + int dest = LOG_DESTINATION_STDERR; // 表示日志的目标,初始值为标准错误输出 /* While we have enough for a header, process data... */ + // 进入一个循环,只要数据输入缓冲区中的字节数足够解析一个日志管道协议的头部,就继续处理数据 while (count >= (int)sizeof(LogPipeProtoHeader)) { - LogPipeProtoHeader p; + LogPipeProtoHeader p; // 用于存储日志管道协议的头部 int chunklen; /* Do we have a valid header? */ errno_t rcs = memcpy_s(&p, sizeof(LogPipeProtoHeader), cursor, sizeof(LogPipeProtoHeader)); securec_check(rcs, "\0", "\0"); - + // 检查 p 是否符合日志管道协议的头部特征 if (CheckPipeProtoHeader(p) == true) { + // 如果是,则进入处理协议消息的分支 List* buffer_list = NULL; ListCell* cell = NULL; save_buffer* existing_slot = NULL; save_buffer* free_slot = NULL; StringInfo str; - + // 计算协议消息的总长度,包括头部大小和数据负载大小 chunklen = LOGPIPE_HEADER_SIZE + p.len; /* Fall out of loop if we don't have the whole chunk yet */ + // 如果数据输入缓冲区中的字节数不足以完整接收当前消息,则跳出循环等待更多数据 if (count < chunklen) break; - + // 如果消息的日志类型为错误日志、查询计划日志或慢查询日志,则进入处理这些日志的分支 if (p.logtype == LOG_TYPE_ELOG || p.logtype == LOG_TYPE_PLAN_LOG || p.logtype == LOG_TYPE_ASP_LOG) { - if (p.logtype == LOG_TYPE_PLAN_LOG) - dest = LOG_DESTINATION_QUERYLOG; + if (p.logtype == LOG_TYPE_PLAN_LOG) + dest = LOG_DESTINATION_QUERYLOG; // 表示将日志写入查询日志 else if (p.logtype == LOG_TYPE_ASP_LOG) - dest = LOG_DESTINATION_ASPLOG; + dest = LOG_DESTINATION_ASPLOG; // 表示将日志写入慢查询日志 else + // 根据 p.is_last 的值来确定 dest 的值 + // 'T' 或 'F',则将 dest 设置为 LOG_DESTINATION_CSVLOG,表示将日志写入 CSV 日志文件; + // 否则,将 dest 设置为 LOG_DESTINATION_STDERR,表示将日志写入标准错误输出 dest = (p.is_last == 'T' || p.is_last == 'F') ? LOG_DESTINATION_CSVLOG : LOG_DESTINATION_STDERR; /* Locate any existing buffer for this source pid */ + // 查找是否存在已保存该来源进程的日志缓冲区 buffer_list = t_thrd.logger.buffer_lists[p.pid % NBUFFER_LISTS]; + // 访问相应 pid 对应的缓冲区列表,并遍历列表中的每个缓冲区 foreach (cell, buffer_list) { save_buffer* buf = (save_buffer*)lfirst(cell); - + // 查找与当前消息来源进程 pid 匹配的缓冲区 if (buf->pid == p.pid) { + // 如果找到匹配的缓冲区,就会保存该缓冲区的引用 existing_slot = buf; break; } + // 如果没有找到匹配的缓冲区,并且还没有分配空闲的缓冲区 if (buf->pid == 0 && free_slot == NULL) - free_slot = buf; + free_slot = buf; // 创建一个新的缓冲区 } - + // 如果当前消息不是最后一个消息的分片,将消息的数据添加到相应的缓冲区 if (p.is_last == 'f' || p.is_last == 'F') { /* * Save a complete non-final chunk in a per-pid buffer */ + // 如果消息块不是最后一个 + // 如果已经存在与来源进程 pid 匹配的缓冲区 if (existing_slot != NULL) { /* Add chunk to data from preceding chunks */ + // 将当前消息块添加到之前已保存的消息数据中 str = &(existing_slot->data); appendBinaryStringInfo(str, cursor + LOGPIPE_HEADER_SIZE, p.len); } else { /* First chunk of message, save in a new buffer */ + // 如果没有已存在的缓冲区,则创建一个新的缓冲区 if (free_slot == NULL) { /* * Need a free slot, but there isn't one in the list, * so create a new one and extend the list with it. */ + // 将当前消息块保存到该缓冲区中,然后将该缓冲区添加到相应的缓冲区列表中 free_slot = (save_buffer*)palloc(sizeof(save_buffer)); buffer_list = lappend(buffer_list, free_slot); t_thrd.logger.buffer_lists[p.pid % NBUFFER_LISTS] = buffer_list; @@ -843,33 +1053,44 @@ static void process_pipe_input(char* logbuffer, int* bytes_in_logbuffer) appendBinaryStringInfo(str, cursor + LOGPIPE_HEADER_SIZE, p.len); } } else { + // 如果消息块是最后一个 /* * Final chunk --- add it to anything saved for that pid, and * either way write the whole thing out. */ + // 如果已经存在与来源进程 pid 匹配的缓冲区 if (existing_slot != NULL) { + // 将当前消息块添加到之前已保存的消息数据中 str = &(existing_slot->data); appendBinaryStringInfo(str, cursor + LOGPIPE_HEADER_SIZE, p.len); + // 将整个消息写入日志文件 write_syslogger_file(str->data, str->len, dest); /* Mark the buffer unused, and reclaim string storage */ existing_slot->pid = 0; pfree(str->data); } else { + // 如果没有已存在的缓冲区,说明整个消息就是一个块 /* The whole message was one chunk, evidently. */ + // 直接将消息块写入日志文件 write_syslogger_file(cursor + LOGPIPE_HEADER_SIZE, p.len, dest); } } - } else if (p.logtype < LOG_TYPE_MAXVALID) { + } else if (p.logtype < LOG_TYPE_MAXVALID) { // 如果消息的日志类型小于 LOG_TYPE_MAXVALID Assert(LOG_TYPE_MAXVALID <= LOG_TYPE_UPLIMIT); + // 处理消息块数据,根据消息的类型将消息传递到相应的日志文件 LogCtlProcessInput(allLogCtl[(int)p.logtype], cursor + LOGPIPE_HEADER_SIZE, p.len); } else { + // 如果消息的日志类型超出了 LOG_TYPE_MAXVALID 的上限 + // 触发断言失败,表示发现了不支持的日志类型 Assert(0); } /* Finished processing this chunk */ + // 更新游标位置和剩余字节数 cursor += chunklen; count -= chunklen; } else { + // 如果消息头不符合日志管道协议的格式 /* Process non-protocol data */ /* * Look for the start of a protocol header. If found, dump data @@ -880,7 +1101,11 @@ static void process_pipe_input(char* logbuffer, int* bytes_in_logbuffer) * message will arrive all in one read(), and we want to respect * the read() boundary if possible.) */ + // 遍历查找非协议数据的消息头起始位置 for (chunklen = 1; chunklen < count; chunklen++) { + // 如果找到了消息头起始位置,则截取到消息头前的数据并继续循环处理 + // 否则将所有非协议数据写入标准错误日志文件 + // 确保非协议消息在写入日志文件时不会被截断,尽量保持完整性 if (cursor[chunklen] == '\0') break; } @@ -892,11 +1117,13 @@ static void process_pipe_input(char* logbuffer, int* bytes_in_logbuffer) } /* We don't have a full chunk, so left-align what remains in the buffer */ + // 如果剩余数据不足以组成一个完整的消息块 if (count > 0 && cursor != logbuffer) { + // 将剩余数据左移以覆盖前面已处理的数据,以便下一次处理 errno_t rc = memmove_s(logbuffer, count, cursor, count); securec_check_c(rc, "\0", "\0"); } - *bytes_in_logbuffer = count; + *bytes_in_logbuffer = count; // 更新剩余字节数 } /* @@ -905,25 +1132,40 @@ static void process_pipe_input(char* logbuffer, int* bytes_in_logbuffer) * This is currently used only at syslogger shutdown, but could perhaps be * useful at other times, so it is careful to leave things in a clean state. */ + +/* + * 功能:强制将任何缓冲的数据输出到日志文件 + * + * 参数: + * logbuffer:用于存储待输出数据的缓冲区 + * bytes_in_logbuffer:缓冲区中的字节数 + * + * 返回值:bool类型,表示是否符合特定特征 + * 如果符合特征,返回 true,表示通过检查; + * 否则返回 false,表示未通过检查 + */ static void flush_pipe_input(char* logbuffer, int* bytes_in_logbuffer) { int i; /* Dump any incomplete protocol messages */ + // 遍历不同来源进程的缓冲区列表,处理不完整的协议消息 for (i = 0; i < NBUFFER_LISTS; i++) { + // 获取当前来源进程的缓冲区列表,以及该列表的元素迭代器 List* list = t_thrd.logger.buffer_lists[i]; ListCell* cell = NULL; foreach (cell, list) { save_buffer* buf = (save_buffer*)lfirst(cell); - if (buf->pid != 0) { + if (buf->pid != 0) { // 检查缓冲区是否包含数据 + // 如果缓冲区中包含数据,将数据写入标准错误日志文件 + // 获取缓冲区中的数据字符串 str,并使用其中的数据和长度来写入日志 StringInfo str = &(buf->data); - write_syslogger_file(str->data, str->len, LOG_DESTINATION_STDERR); /* Mark the buffer unused, and reclaim string storage */ - buf->pid = 0; - pfree(str->data); + buf->pid = 0; // 标记缓冲区未使用 + pfree(str->data); // 释放缓冲区中的字符串存储空间,以便重用 } } } @@ -932,9 +1174,10 @@ static void flush_pipe_input(char* logbuffer, int* bytes_in_logbuffer) * Force out any remaining pipe data as-is; we don't bother trying to * remove any protocol headers that may exist in it. */ - if (*bytes_in_logbuffer > 0) + if (*bytes_in_logbuffer > 0) // 检查缓冲区中是否有剩余的数据 + // 如果有则将其写入标准错误日志文件 write_syslogger_file(logbuffer, *bytes_in_logbuffer, LOG_DESTINATION_STDERR); - *bytes_in_logbuffer = 0; + *bytes_in_logbuffer = 0; // 表示缓冲区现在为空 } /* -------------------------------- @@ -948,15 +1191,31 @@ static void flush_pipe_input(char* logbuffer, int* bytes_in_logbuffer) * This allows the syslogger process to record elog messages of its own, * even though its stderr does not point at the syslog pipe. */ + +/* + * 功能:将文本写入当前打开的日志文件 + * + * 参数: + * buffer:包含要写入日志文件的文本数据的缓冲区 + * count:要写入的字节数 + * destination:指示写入的日志文件的目标,可以是以下选项之一: + * LOG_DESTINATION_STDERR:标准错误日志文件。 + * LOG_DESTINATION_CSVLOG:CSV 日志文件。 + * LOG_DESTINATION_QUERYLOG:查询日志文件。 + * LOG_DESTINATION_ASPLOG:活动会话分析日志文件 + * + * 返回值:无 + */ void write_syslogger_file(char* buffer, int count, int destination) { int rc; - FILE* logfile = NULL; - bool doOpen = false; - + FILE* logfile = NULL; // 用于表示当前要写入的日志文件 + bool doOpen = false; // 用于表示是否需要打开日志文件 + // 检查目标是否为 CSV 日志,并且当前 CSV 日志文件未打开 if (destination == LOG_DESTINATION_CSVLOG && t_thrd.logger.csvlogFile == NULL) - open_csvlogfile(); - + open_csvlogfile(); // 如果是,则打开 CSV 日志文件 + + // 根据目标确定要写入的日志文件,并根据需要打开该文件 if (destination == LOG_DESTINATION_QUERYLOG) logfile = (FILE *)SQMOpenLogFile(&doOpen); else if (destination == LOG_DESTINATION_ASPLOG) @@ -966,20 +1225,23 @@ void write_syslogger_file(char* buffer, int count, int destination) errno = 0; - rc = fwrite(buffer, 1, count, logfile); + rc = fwrite(buffer, 1, count, logfile); // 将指定数量的字节从 buffer 写入 logfile /* can't use ereport here because of possible recursion */ - if (rc != count) { + if (rc != count) { // 检查写入的字节数是否与指定的字节数相等,以确定写入是否成功 /* * if no disk space, we will retry, * and we can not report a log, because there is not space to write. */ + // 如果写入失败的原因是磁盘空间不足 if (errno == ENOSPC) { - return; + return; // 函数返回,不进行错误处理 } + // 构造错误消息字符串 errorbuf,包含错误信息和描述信息 char errorbuf[ERROR_BUF_SIZE] = {'\0'}; rc = sprintf_s(errorbuf, ERROR_BUF_SIZE, "ERROR: could not write to log file: %s\n", gs_strerror(errno)); securec_check_ss_c(rc, "\0", "\0"); + // 将错误消息写入日志文件以记录错误信息 syslogger_erewrite(logfile, errorbuf); } } @@ -993,18 +1255,30 @@ void write_syslogger_file(char* buffer, int count, int destination) * unnamed pipes: it always reports "signaled", so the blocking ReadFile won't * allow for SIGHUP; and select is for sockets only. */ + +/* + * 功能:强制刷新管道中的缓冲数据 + * 不断从管道中读取数据,处理数据并写入日志文件,直到检测到管道已关闭 + * 如果当前日志文件大小达到了日志轮换的大小限制,会触发主线程执行日志轮换操作 + * + * 参数: + * char* logbuffer:指向包含数据的缓冲区的指针 + * int* bytes_in_logbuffer:指向表示缓冲区中字节数的整数指针 + * + * 返回值:无 + */ static unsigned int __stdcall pipeThread(void* arg) { - char logbuffer[READ_BUF_SIZE]; - int bytes_in_logbuffer = 0; - - for (;;) { - DWORD bytesRead; - BOOL result = false; + char logbuffer[READ_BUF_SIZE]; // 定义一个缓冲区用于存储从管道中读取的数据 + int bytes_in_logbuffer = 0; // 记录缓冲区中当前的字节数 + for (;;) { // 循环遍历多个缓冲区列表 不断从管道中读取数据 + DWORD bytesRead; // 用于记录从管道中实际读取的字节数 + BOOL result = false; // 用于记录ReadFile函数的执行结果 + // 从管道中读取数据到logbuffer中 result = ReadFile(t_thrd.postmaster_cxt.syslogPipe[0], - logbuffer + bytes_in_logbuffer, - sizeof(logbuffer) - bytes_in_logbuffer, + logbuffer + bytes_in_logbuffer, // // 将数据追加到缓冲区的末尾 + sizeof(logbuffer) - bytes_in_logbuffer, // // 剩余缓冲区大小 &bytesRead, 0); @@ -1014,23 +1288,25 @@ static unsigned int __stdcall pipeThread(void* arg) * palloc()/pfree() in particular are not safe outside the critical * section. */ + // 进入临界区,保护后续操作不被其他线程干扰 EnterCriticalSection(&sysloggerSection); if (!result) { DWORD error = GetLastError(); - + // 处理从管道中读取数据时出现的错误 if (error == ERROR_HANDLE_EOF || error == ERROR_BROKEN_PIPE) - break; + break; // 管道已关闭,退出循环 _dosmaperr(error); ereport(LOG, (errcode_for_file_access(), errmsg("could not read from logger pipe: %m"))); } else if (bytesRead > 0) { - bytes_in_logbuffer += bytesRead; - process_pipe_input(logbuffer, &bytes_in_logbuffer); + bytes_in_logbuffer += bytesRead; // 更新缓冲区中的字节数 + process_pipe_input(logbuffer, &bytes_in_logbuffer); // 处理从管道中读取的数据 } /* * If we've filled the current logfile, nudge the main thread to do a * log rotation. */ + // 如果当前日志文件达到了日志轮换的大小限制,则通知主线程执行日志轮换 if (u_sess->attr.attr_common.Log_RotationSize > 0) { if (ftell(t_thrd.logger.syslogFile) >= u_sess->attr.attr_common.Log_RotationSize * 1024L || (t_thrd.logger.querylogFile != NULL && @@ -1039,21 +1315,21 @@ static unsigned int __stdcall pipeThread(void* arg) ftell(t_thrd.logger.asplogFile) >= u_sess->attr.attr_common.Log_RotationSize * 1024L) || (t_thrd.logger.csvlogFile != NULL && ftell(t_thrd.logger.csvlogFile) >= u_sess->attr.attr_common.Log_RotationSize * 1024L)) - SetLatch(&t_thrd.logger.sysLoggerLatch); + SetLatch(&t_thrd.logger.sysLoggerLatch); // 设置信号量,通知主线程执行日志轮换 } - LeaveCriticalSection(&sysloggerSection); + LeaveCriticalSection(&sysloggerSection); // 退出临界区 } /* We exit the above loop only upon detecting pipe EOF */ - t_thrd.logger.pipe_eof_seen = true; + t_thrd.logger.pipe_eof_seen = true; // 表示管道已关闭 /* if there's any data left then force it out now */ flush_pipe_input(logbuffer, &bytes_in_logbuffer); /* set the latch to waken the main thread, which will quit */ - SetLatch(&t_thrd.logger.sysLoggerLatch); + SetLatch(&t_thrd.logger.sysLoggerLatch); // 设置信号量,通知主线程退出 - LeaveCriticalSection(&sysloggerSection); + LeaveCriticalSection(&sysloggerSection); // 退出线程 _endthread(); return 0; } @@ -1067,18 +1343,30 @@ static unsigned int __stdcall pipeThread(void* arg) * process, not during rotations. As with opening the main log file, we * always append in this situation. */ + +/* + * 功能:打开CSV格式的日志文件,但是只有在第一次打开该文件时才执行,不会在日志轮换时执行 + * 构建CSV日志文件的文件名,然后以追加模式打开文件 + * + * 参数:无 + * + * 返回值:无 + */ static void open_csvlogfile(void) { - char* filename = NULL; - + char* filename = NULL; // 用于存储CSV日志文件的文件名 + // 构建CSV日志文件的文件名 filename = logfile_getname( time(NULL), ".csv", u_sess->attr.attr_common.Log_directory, u_sess->attr.attr_common.Log_filename); - + + // 打开CSV日志文件,以追加模式打开 t_thrd.logger.csvlogFile = logfile_open(filename, "a", false); - + + // 如果上一次打开的CSV日志文件名不为空,则释放其内存 if (t_thrd.logger.last_csv_file_name != NULL) /* probably shouldn't happen */ pfree(t_thrd.logger.last_csv_file_name); - + + // 记录当前打开的CSV日志文件名 t_thrd.logger.last_csv_file_name = filename; } @@ -1092,26 +1380,40 @@ static void open_csvlogfile(void) * (with errno still correct for the fopen failure). * Otherwise, errors are treated as fatal. */ + +/* + * 功能:以指定的模式打开一个新的日志文件,并设置适当的权限和缓冲选项 + * + * 参数: + * filename:要打开的文件的路径和名称 + * mode:打开文件的模式,如 "r"、"w"、"a" 等 + * allow_errors:是否允许出现错误,如果为 true,则只记录错误,不会引发致命错误 + * + * 返回值: + * 如果成功打开文件,则返回指向文件的指针(文件句柄),如果失败,则返回 NULL + */ static FILE* logfile_open(const char* filename, const char* mode, bool allow_errors) { - FILE* fh = NULL; - struct stat checkdir; - bool dirIsExist = false; + FILE* fh = NULL; // 文件句柄 + struct stat checkdir; // 用于检查目录是否存在的结构体 + bool dirIsExist = false; // 标志目录是否存在 /* * Note we do not let Log_file_mode disable IWUSR, since we certainly want * to be able to write the files ourselves. */ + // 如果 filename 为 NULL,报错,否则继续执行 if (filename == NULL) { ereport(ERROR, (errcode(ERRCODE_UNEXPECTED_NULL_VALUE), errmsg("group_name can not be NULL "))); } - + // 检查文件或目录是否存在 if (stat(filename, &checkdir) == 0) { dirIsExist = true; } - + // 打开文件,使用指定的模式 fh = fopen(filename, mode); if (fh != NULL) { + // 如果成功打开,设置文件的缓冲模式 setvbuf(fh, NULL, LBF_MODE, 0); #ifdef WIN32 @@ -1122,6 +1424,7 @@ static FILE* logfile_open(const char* filename, const char* mode, bool allow_err int save_errno = errno; if (allow_errors) { + // 如果允许出现错误,记录打开文件失败的错误信息 ereport(LOG, (errcode_for_file_access(), errmsg("could not open log file \"%s\": %m", filename))); errno = save_errno; } else { @@ -1129,12 +1432,13 @@ static FILE* logfile_open(const char* filename, const char* mode, bool allow_err * If open file failed, we can not write any log to file, make the * system crash is safe. */ + // 如果打开文件失败且不允许错误,则将系统置为崩溃状态,报错 ereport(WARNING, (errcode_for_file_access(), errmsg("failed to open log file \"%s\": %m", filename))); - + // 禁止立即中断 t_thrd.int_cxt.ImmediateInterruptOK = false; - fflush(stdout); - fflush(stderr); - abort(); + fflush(stdout); // 刷新标准输出流,确保任何在输出缓冲区中的数据都被写入到标准输出 + fflush(stderr); // 刷新标准错误流,确保任何在错误缓冲区中的数据都被写入到标准错误 + abort(); // 用于表示发生了严重错误,需要立即停止程序的执行 } } @@ -1142,18 +1446,22 @@ static FILE* logfile_open(const char* filename, const char* mode, bool allow_err * Note we do not let Log_file_mode disable IWUSR, since we certainly want * to be able to write the files ourselves. */ + // 如果目录不存在,则设置文件的权限 if (!dirIsExist) { + // 检查 chmod 调用是否成功,将日志文件的权限设置为允许文件的拥有者写入,并确保不允许执行文件 + // chmod函数是一个系统调用,用于更改文件的权限模式 + // 参数:要修改权限的文件的路径 filename 和新的权限模式 if (chmod(filename, (((mode_t)u_sess->attr.attr_common.Log_file_mode | S_IWUSR) & (~S_IXUSR) & (~S_IXOTH) & (~S_IXGRP))) < 0) { - int save_errno = errno; - + int save_errno = errno; // 保存错误码 + // 记录更改文件权限失败的错误信息 ereport(allow_errors ? LOG : FATAL, (errcode_for_file_access(), errmsg("could not chmod log file \"%s\": %m", filename))); - errno = save_errno; + errno = save_errno; // 恢复错误码 } } - return fh; + return fh; // 返回文件句柄 } #if defined(ENABLE_UT) && defined(static) #undef static @@ -1162,27 +1470,41 @@ static FILE* logfile_open(const char* filename, const char* mode, bool allow_err /* * perform logfile rotation */ + +/* + * 功能:执行日志文件的轮换 + * + * 参数: + * time_based_rotation:一个布尔值,指示是否基于时间进行轮换 + * size_rotation_for:一个整数,指示是否根据文件大小进行轮换的标志 + * + * 返回值:无 + */ static void logfile_rotate(bool time_based_rotation, int size_rotation_for) { - char* filename = NULL; - char* csvfilename = NULL; - pg_time_t fntime; - FILE* fh = NULL; + char* filename = NULL; // 用于存储新日志文件的文件名 + char* csvfilename = NULL; // 用于存储新的 CSV 日志文件的文件名 + pg_time_t fntime; // 用于存储轮换的时间戳 + FILE* fh = NULL; // 用于打开新的日志文件 - t_thrd.logger.rotation_requested = false; + t_thrd.logger.rotation_requested = false; // 表示不需要执行日志文件轮换 /* * When doing a time-based rotation, invent the new logfile name based on * the planned rotation time, not current time, to avoid "slippage" in the * file name when we don't do the rotation immediately. */ + // 如果time_based_rotation 为 true if (time_based_rotation) + // 使用预定的轮换时间而不是当前时间,以避免文件名的“滑动” fntime = t_thrd.logger.next_rotation_time; else - fntime = time(NULL); + fntime = time(NULL); // 否则,使用当前时间 + // 构造新日志文件的文件名,包括目录、文件名前缀等信息 filename = logfile_getname(fntime, NULL, u_sess->attr.attr_common.Log_directory, u_sess->attr.attr_common.Log_filename); - if (t_thrd.logger.csvlogFile != NULL) + if (t_thrd.logger.csvlogFile != NULL) // 如果存在 CSV 日志文件 + // 构造 CSV 文件的文件名 csvfilename = logfile_getname( fntime, ".csv", u_sess->attr.attr_common.Log_directory, u_sess->attr.attr_common.Log_filename); @@ -1194,77 +1516,87 @@ static void logfile_rotate(bool time_based_rotation, int size_rotation_for) * * Note: t_thrd.logger.last_file_name should never be NULL here, but if it is, append. */ + // 如果是基于时间的轮换或需要轮换到标准错误日志 if (time_based_rotation || (size_rotation_for & LOG_DESTINATION_STDERR)) { + // 如果配置中允许在轮换时截断日志文件,并且轮换是基于时间触发的,且新的文件名与上次不同 if (u_sess->attr.attr_common.Log_truncate_on_rotation && time_based_rotation && t_thrd.logger.last_file_name != NULL && strcmp(filename, t_thrd.logger.last_file_name) != 0) + // 打开新的日志文件以进行写入操作,如果文件已存在,将其截断为空文件 fh = logfile_open(filename, "w", true); else + // 打开新的日志文件以进行追加写入操作,不截断已存在的文件内容 fh = logfile_open(filename, "a", true); - if (fh == NULL) { + if (fh == NULL) { // 如果打开新的日志文件失败 /* * ENFILE/EMFILE are not too surprising on a busy system; just * keep using the old file till we manage to get a new one. * Otherwise, assume something's wrong with Log_directory and stop * trying to create files. */ + // 如果错误代码不是 ENFILE 或 EMFILE,则表示可能存在更严重的问题 if (errno != ENFILE && errno != EMFILE) { + // 发出一个日志记录消息,禁用自动轮换,以防止继续尝试创建新文件 ereport(LOG, (errmsg("disabling automatic rotation (use SIGHUP to re-enable)"))); t_thrd.logger.rotation_disabled = true; } if (filename != NULL) - pfree(filename); + pfree(filename); // 释放分配的文件名内存,以避免内存泄漏 if (csvfilename != NULL) - pfree(csvfilename); - return; + pfree(csvfilename); // 释放分配的 CSV 文件名内存 + return; // 不执行后续的日志轮换操作 } - fclose(t_thrd.logger.syslogFile); + fclose(t_thrd.logger.syslogFile); // 关闭先前的日志文件,确保将日志写入新文件 t_thrd.logger.syslogFile = fh; /* instead of pfree'ing filename, remember it for next time */ if (t_thrd.logger.last_file_name != NULL) - pfree(t_thrd.logger.last_file_name); + pfree(t_thrd.logger.last_file_name); // 释放上一次的文件名内存,以避免内存泄漏 t_thrd.logger.last_file_name = filename; - filename = NULL; + filename = NULL; // 以避免重复释放内存 } /* Same as above, but for csv file. */ + // 如果 t_thrd.logger.csvlogFile 不为 NULL, + // 且 轮换基于时间触发或轮换基于文件大小触发或当前日志目的地包括 CSV 日志 if (t_thrd.logger.csvlogFile != NULL && (time_based_rotation || (size_rotation_for & LOG_DESTINATION_CSVLOG)) && ((unsigned int)t_thrd.log_cxt.Log_destination & LOG_DESTINATION_CSVLOG)) { if (u_sess->attr.attr_common.Log_truncate_on_rotation && time_based_rotation && t_thrd.logger.last_csv_file_name != NULL && strcmp(csvfilename, t_thrd.logger.last_csv_file_name) != 0) - fh = logfile_open(csvfilename, "w", true); + fh = logfile_open(csvfilename, "w", true); // 以覆盖模式或追加模式打开 CSV 日志文件 else fh = logfile_open(csvfilename, "a", true); - if (fh == NULL) { + if (fh == NULL) { // 如果文件打开失败 /* * ENFILE/EMFILE are not too surprising on a busy system; just * keep using the old file till we manage to get a new one. * Otherwise, assume something's wrong with Log_directory and stop * trying to create files. */ + // 如果错误码不是 ENFILE 或 EMFILE if (errno != ENFILE && errno != EMFILE) { + // 发出一条日志消息,通知禁用自动轮换 ereport(LOG, (errmsg("disabling automatic rotation (use SIGHUP to re-enable)"))); - t_thrd.logger.rotation_disabled = true; + t_thrd.logger.rotation_disabled = true; // 表明自动轮换已禁用 } if (filename != NULL) - pfree(filename); + pfree(filename); // 释放分配的文件名内存 if (csvfilename != NULL) pfree(csvfilename); return; } - fclose(t_thrd.logger.csvlogFile); + fclose(t_thrd.logger.csvlogFile); // 关闭之前的日志文件 t_thrd.logger.csvlogFile = fh; /* instead of pfree'ing filename, remember it for next time */ if (t_thrd.logger.last_csv_file_name != NULL) - pfree(t_thrd.logger.last_csv_file_name); - t_thrd.logger.last_csv_file_name = csvfilename; + pfree(t_thrd.logger.last_csv_file_name); // 释放上一次的文件名内存 + t_thrd.logger.last_csv_file_name = csvfilename; // 将新的文件名赋值,以备下一次轮换操作使用 csvfilename = NULL; } @@ -1273,7 +1605,7 @@ static void logfile_rotate(bool time_based_rotation, int size_rotation_for) if (csvfilename != NULL) pfree(csvfilename); - set_next_rotation_time(); + set_next_rotation_time(); // 设置下一次的日志轮换时间 } /* @@ -1284,35 +1616,59 @@ static void logfile_rotate(bool time_based_rotation, int size_rotation_for) * * Result is palloc'd. */ + +/* + * 功能:构建日志文件的文件名 + * + * 参数: + * timestamp:时间戳 + * suffix:文件名后缀 + * logdir:日志目录 + * filename_pattern:文件名模式 + * + * 返回值:动态分配的字符串,包含构建的文件名 + */ static char* logfile_getname(pg_time_t timestamp, const char* suffix, const char* logdir, const char* filename_pattern) { char* filename = NULL; int len = 0; int ret = 0; - filename = (char*)palloc(MAXPGPATH); - + filename = (char*)palloc(MAXPGPATH); // 分配内存 + // 将 logdir 的内容格式化并复制到 filename 中 ret = snprintf_s(filename, MAXPGPATH, MAXPGPATH - 1, "%s/", logdir); securec_check_ss(ret, "", ""); - len = strlen(filename); + len = strlen(filename); // 计算 filename 字符串的长度 /* treat Log_filename as a strftime pattern */ + // 将格式化时间信息附加到 filename 的末尾 pg_strftime(filename + len, MAXPGPATH - len, filename_pattern, pg_localtime(×tamp, log_timezone)); - + + // 检查是否提供了文件名后缀 if (suffix != NULL) { - len = strlen(filename); + len = strlen(filename); // 计算 filename 字符串的长度 + // 检查文件名是否以 ".log" 结尾 if (len > 4 && (strcmp(filename + (len - 4), ".log") == 0)) - len -= 4; + len -= 4; // 如果是,将从文件名中移除 ".log" + // 将后缀 suffix 复制到 filename 的末尾,确保不会超出文件名的最大长度 MAXPGPATH - len strlcpy(filename + len, suffix, MAXPGPATH - len); } - return filename; + return filename; // 返回构建的文件名字符串 } /* * Determine the next planned rotation time, and store in t_thrd.logger.next_rotation_time. */ + +/* + * 功能:确定下一次计划的日志文件轮换时间 + * + * 参数:无 + * + * 返回值:无 + */ static void set_next_rotation_time(void) { pg_time_t now; @@ -1320,8 +1676,9 @@ static void set_next_rotation_time(void) int rotinterval; /* nothing to do if time-based rotation is disabled */ + // 检查是否禁用了基于时间的日志轮换 if (u_sess->attr.attr_common.Log_RotationAge <= 0) - return; + return; // 禁用z阿返回 /* * The requirements here are to choose the next time > now that is a @@ -1329,16 +1686,22 @@ static void set_next_rotation_time(void) * fairly loosely. In this version we align to log_timezone rather than * GMT. */ + // 计算日志轮换的时间间隔 rotinterval = u_sess->attr.attr_common.Log_RotationAge * SECS_PER_MINUTE; /* convert to seconds */ - now = (pg_time_t)time(NULL); - tm_t = pg_localtime(&now, log_timezone); - if (NULL == tm_t) { - return; + now = (pg_time_t)time(NULL); // 获取当前时间 + tm_t = pg_localtime(&now, log_timezone); // 将当前时间 now 转换为本地时间 + if (NULL == tm_t) { // 检查是否成功获取本地时间 + return; // 如果获取失败,直接返回 } + // 将当前时间调整为 GMT 偏移量的时间,以便对齐到日志轮换的时间间隔 now += tm_t->tm_gmtoff; + // 计算 now 与轮换时间间隔的模,并将其减去,以得到下一个轮换时间的起点 now -= now % rotinterval; + // 将计划的轮换时间增加一个完整的轮换时间间隔,以得到下一次计划的轮换时间 now += rotinterval; + // 将计划的轮换时间调整回本地时间,以得到最终的计划轮换时间 now -= tm_t->tm_gmtoff; + // 将计划的轮换时间存储在全局变量中,以供日志轮换时使用 t_thrd.logger.next_rotation_time = now; } @@ -1347,43 +1710,72 @@ static void set_next_rotation_time(void) * -------------------------------- */ /* SIGHUP: set flag to reload config file */ + +/* + *功能:处理 SIGHUP 信号,用于重新读取配置文件 + * + * 参数: + * SIGNAL_ARGS: 信号处理程序的参数列表,提供关于触发信号的上下文信息 + * + * 返回值: + * 无 + */ static void sigHupHandler(SIGNAL_ARGS) { - int save_errno = errno; + int save_errno = errno; // 保存当前的错误码 - t_thrd.logger.got_SIGHUP = true; - SetLatch(&t_thrd.logger.sysLoggerLatch); + t_thrd.logger.got_SIGHUP = true; // 表示接收到 SIGHUP 信号 + SetLatch(&t_thrd.logger.sysLoggerLatch); // 设置该进程的进程latch - errno = save_errno; + errno = save_errno; // 恢复之前保存的错误码 } /* SIGUSR1: set flag to rotate logfile */ + +/* + *功能: 处理 SIGUSR1 信号,用于刷新缓冲区请求,以及日志文件的轮换请求 + * + * 参数: + * SIGNAL_ARGS: 信号处理程序的参数列表,提供关于触发信号的上下文信息 + * + * 返回值: + * 无 + */ static void sigUsr1Handler(SIGNAL_ARGS) { - int save_errno = errno; + int save_errno = errno; // 保存当前的错误码 LogControlData* logctl = NULL; - + // 检查是否有刷新缓冲区的请求 if (g_instance.flush_buf_requested) { + // 如果有,遍历所有的 LogControlData 结构体 foreach_logctl(logctl) { /* request to flush buffer data */ - logctl->flush_requested = true; + logctl->flush_requested = true; // 表示需要刷新缓冲区中的数据 } /* reset this request */ - g_instance.flush_buf_requested = false; + g_instance.flush_buf_requested = false; // 重置标志,表示刷新缓冲区请求已经被处理 } else { - t_thrd.logger.rotation_requested = true; + // 如果没有刷新缓冲区的请求 + t_thrd.logger.rotation_requested = true; // 表示需要进行日志文件的轮换 /* all log should be rotation requested */ foreach_logctl(logctl) { - logctl->rotation_requested = true; + logctl->rotation_requested = true; // 表示需要进行日志文件的轮换 } } - SetLatch(&t_thrd.logger.sysLoggerLatch); - errno = save_errno; + SetLatch(&t_thrd.logger.sysLoggerLatch); // 设置该进程的进程latch + errno = save_errno; // 恢复之前保存的错误码 } +/* + * 功能:设置一个全局标志,表示要求刷新缓冲区 + * + * 参数:无 + * + * 返回值:无 + */ void set_flag_to_flush_buffer(void) { - g_instance.flush_buf_requested = true; + g_instance.flush_buf_requested = true; // 表示要求刷新缓冲区 } /* @@ -1395,10 +1787,12 @@ void set_flag_to_flush_buffer(void) */ static char* LogCtlGetLogDirectory(const char* logid, bool include_nodename) { - char path[MAXPGPATH] = {0}; - char* rootdir = gs_getenv_r("GAUSSLOG"); - char log_rootdir[PATH_MAX + 1] = {'\0'}; + char path[MAXPGPATH] = {0}; // 用于存储最终的日志目录路径 + char* rootdir = gs_getenv_r("GAUSSLOG"); // 获取环境变量 $GAUSSLOG 的值 + char log_rootdir[PATH_MAX + 1] = {'\0'};// 存储 $GAUSSLOG 环境变量的解析后的绝对路径 + // 检查环境变量 $GAUSSLOG 是否有效 if (rootdir == NULL || realpath(rootdir, log_rootdir) == NULL) { + // 如果无效则发出警告消息 ereport(WARNING, (errmodule(MOD_EXECUTOR), errcode(ERRCODE_EXTERNAL_ROUTINE_INVOCATION_EXCEPTION), errmsg("Failed to obtain environment value $GAUSSLOG!"), @@ -1406,46 +1800,65 @@ static char* LogCtlGetLogDirectory(const char* logid, bool include_nodename) errcause("Incorrect environment value."), erraction("Please refer to backend log for more details."))); } - rootdir = NULL; - int rc = 0; + rootdir = NULL; // 释放 rootdir 内存 + int rc = 0; // 存储函数调用的返回值或错误码 /* * $GAUSSLOG env must not be an empty string. * if so, log directory will be under root dir '/' and permition denied. */ if (*log_rootdir != '\0') { - check_backend_env(log_rootdir); + check_backend_env(log_rootdir); // 检查指定路径是否可用 + // 拼接解析后的 $GAUSSLOG 环境变量路径到 path 中 rc = strcat_s(path, MAXPGPATH, log_rootdir); securec_check_c(rc, "\0", "\0"); + // 在 path 后面添加斜杠,形成路径分隔符 rc = strcat_s(path, MAXPGPATH, "/"); securec_check_c(rc, "\0", "\0"); } /* if GAUSSLOG not set, create directory under node rootdir */ + // 将 logid 参数指定的日志类型添加到 path 中 rc = strcat_s(path, MAXPGPATH, logid); securec_check_c(rc, "\0", "\0"); - rc = strcat_s(path, MAXPGPATH, "/"); + rc = strcat_s(path, MAXPGPATH, "/"); // 在 path 后面再次添加斜杠 securec_check_c(rc, "\0", "\0"); if (include_nodename) { rc = strcat_s(path, MAXPGPATH, g_instance.attr.attr_common.PGXCNodeName); securec_check_c(rc, "\0", "\0"); } - return pstrdup(path); + return pstrdup(path); // 返回最终的日志目录路径的复制 } /* copy name of src to dst whose max capacity is LOG_MAX_NODENAME_LEN */ + +/* + * 功能:将源字符串 src 复制到目标字符串 dst 中 + * 但要确保目标字符串 dst 的最大容量不超过 LOG_MAX_NODENAME_LEN。 + * 如果源字符串 src 的长度超过了 LOG_MAX_NODENAME_LEN,则会进行截断处理 + * + * 参数: + * src:源字符串 + * dst:目标字符串 + * + * 返回值:无 + */ static void copy_name(const char* src, char* dst) { - size_t len = strlen(src); + size_t len = strlen(src); // 计算源字符串 src 的长度 + // 检查源字符串的长度是否超过了目标字符串的最大容量 if (len >= LOG_MAX_NODENAME_LEN) { /* truncate this name */ + // 如果长度超过了最大容量,将长度限制为 LOG_MAX_NODENAME_LEN - 1 + // 以确保目标字符串末尾可以添加终止符\0 len = LOG_MAX_NODENAME_LEN - 1; } + // 将源字符串的内容复制到目标字符串中 int rc = memcpy_s(dst, LOG_MAX_NODENAME_LEN, src, len + 1); securec_check(rc, "\0", "\0"); - dst[len] = '\0'; + dst[len] = '\0'; // 手动添加终止符\0,以确保目标字符串的正确终止 } /* @@ -1453,18 +1866,26 @@ static void copy_name(const char* src, char* dst) * it's a pity that PGXCNodeName is null in syslog thread, * so we have to set this global information from postmaster thread. */ + +/* + * 功能:初始化全局变量 logCtlNodeName 和 logCtlHostName + * + * 参数:无 + * + * 返回值:无 + */ static void LogCtlSetGlobalNames(void) { - if (0 == logCtlNodeName[0]) { + if (0 == logCtlNodeName[0]) { // 检查 logCtlNodeName 是否已经被初始化 copy_name(g_instance.attr.attr_common.PGXCNodeName, logCtlNodeName); } - if (0 == logCtlHostName[0]) { - const char* hostname = gs_getenv_r("HOSTNAME"); - if (NULL == hostname) { + if (0 == logCtlHostName[0]) { // 检查 logCtlHostName 是否已经被初始化 + const char* hostname = gs_getenv_r("HOSTNAME"); // 获取环境变量 HOSTNAME 的值,即主机名 + if (NULL == hostname) { // 检查获取到的主机名是否为 NULL /* just set a default hostname */ - hostname = "UnknownHostname"; + hostname = "UnknownHostname"; // 设置一个默认的主机名 } - check_backend_env(hostname); + check_backend_env(hostname); // 检查主机名是否符合一些要求 copy_name(hostname, logCtlHostName); } } @@ -1474,9 +1895,18 @@ static void LogCtlSetGlobalNames(void) * it's a pity that log_timezone is null in syslog thread, * so we have to set this global information from postmaster thread. */ + +/* + * 功能:初始化全局变量 logCtlTimeZone + * + * 参数:无 + * + * 返回值:无 + */ static void LogCtlSetTimeZone(void) { - if (log_timezone) { + if (log_timezone) { // 检查 log_timezone 是否已经被初始化 + // 复制时区名称并确保安全性 int rc = memcpy_s(logCtlTimeZone, TZ_STRLEN_MAX + 1, pg_get_timezone_name(log_timezone), TZ_STRLEN_MAX + 1); securec_check(rc, "\0", "\0"); } @@ -1490,16 +1920,18 @@ static void LogCtlSetTimeZone(void) */ static void LogCtlWriteFileHeader(LogControlData* logctl) { + // 获取主机名、节点名和时区字符串的长度,包括末尾的0 size_t hostname_len = strlen(logCtlHostName) + 1; /* including tail 0 */ size_t nodename_len = strlen(logCtlNodeName) + 1; /* including tail 0 */ size_t timezone_len = strlen(logCtlTimeZone) + 1; /* including tail 0 */ + // 计算头部数据的总长度 size_t total_len = sizeof(LogFileHeader) + hostname_len + nodename_len + timezone_len; int rc = 0; - total_len = MAXALIGN(total_len); - Assert(total_len <= BLCKSZ); + total_len = MAXALIGN(total_len); // 对齐头部数据长度 + Assert(total_len <= BLCKSZ); // 确保头部数据长度不超过块大小 - char* buf = (char*)palloc0(total_len); + char* buf = (char*)palloc0(total_len); // 分配内存缓冲区用于存储头部数据 int off = 0; /* the first magic data */ @@ -1541,15 +1973,17 @@ static void LogCtlWriteFileHeader(LogControlData* logctl) Assert(total_len - off >= sizeof(unsigned long)); *(unsigned long*)(buf + total_len - sizeof(unsigned long)) = LOG_MAGICNUM; - Assert(logctl->now_file_fd); + Assert(logctl->now_file_fd); // 确保文件描述符存在 errno = 0; /* clear errno before disk write */ + // 写入头部数据到文件 size_t ret_len = fwrite(buf, 1, total_len, logctl->now_file_fd); - + // 检查写入是否成功 if ((errno != 0) || (ret_len != total_len)) { char errorbuf[ERROR_BUF_SIZE] = {'\0'}; rc = sprintf_s( errorbuf, ERROR_BUF_SIZE, "ERROR: could not write file head for binary log: %s\n", gs_strerror(errno)); securec_check_ss_c(rc, "\0", "\0"); + // 写入错误消息到系统日志 syslogger_erewrite(logctl->now_file_fd, errorbuf); } pfree(buf); @@ -1564,9 +1998,10 @@ static void LogCtlWriteFileHeader(LogControlData* logctl) */ static void LogCtlRotateFile(LogControlData* logctl, bool time_based_rotation) { - logctl->rotation_requested = false; - + logctl->rotation_requested = false; // 标记为不需要轮换 + // 获取旋转时的时间戳 pg_time_t fntime = time_based_rotation ? t_thrd.logger.next_rotation_time : time(NULL); + // 构建新日志文件的名称 char* filename = logfile_getname(fntime, NULL, logctl->log_dir, logctl->filename_pattern); /* @@ -1580,11 +2015,11 @@ static void LogCtlRotateFile(LogControlData* logctl, bool time_based_rotation) char* write_mode = NULL; if (u_sess->attr.attr_common.Log_truncate_on_rotation && time_based_rotation && logctl->now_file_name && strcmp(filename, logctl->now_file_name) != 0) { - write_mode = "w"; + write_mode = "w"; // 覆盖文件 } else { - write_mode = "a"; + write_mode = "a"; // 追加内容 } - FILE* fh = logfile_open(filename, write_mode, true); + FILE* fh = logfile_open(filename, write_mode, true); // 打开新的日志文件 if (fh == NULL) { if (errno != ENFILE && errno != EMFILE) { @@ -1597,8 +2032,8 @@ static void LogCtlRotateFile(LogControlData* logctl, bool time_based_rotation) return; } - fclose(logctl->now_file_fd); - logctl->now_file_fd = fh; + fclose(logctl->now_file_fd); // 关闭当前的日志文件 + logctl->now_file_fd = fh; // 更新当前的日志文件句柄为新的文件 /* instead of pfree'ing filename, remember it for next time */ if (logctl->now_file_name != NULL) { @@ -1693,12 +2128,12 @@ static void LogCtlFlushBuf(LogControlData* logctl) static void LogCtlProcessInput(LogControlData* logctl, const char* msg, int len) { if (logctl->cur_len + len > logctl->max_len) { - LogCtlFlushBuf(logctl); + LogCtlFlushBuf(logctl); // 如果缓冲区即将满,先刷新缓冲区 } - + // 将输入的日志消息复制到缓冲区 int rc = memcpy_s(logctl->log_buf + logctl->cur_len, logctl->max_len, msg, len); securec_check(rc, "\0", "\0"); - logctl->cur_len += len; + logctl->cur_len += len; // 更新当前缓冲区长度 } /* @@ -1744,12 +2179,15 @@ static char* LogCtlGetFilenamePattern(const char* post_suffix) static void PLogCtlInit(void) { t_thrd.log_cxt.pLogCtl = (LogControlData*)palloc0(sizeof(LogControlData)); - t_thrd.log_cxt.pLogCtl->ver = PROFILE_LOG_VERSION; - t_thrd.log_cxt.pLogCtl->rotation_requested = false; - t_thrd.log_cxt.pLogCtl->flush_requested = false; + // 设置性能日志控制数据的各个字段 + t_thrd.log_cxt.pLogCtl->ver = PROFILE_LOG_VERSION; // 设置性能日志版本号 + t_thrd.log_cxt.pLogCtl->rotation_requested = false; // 初始化日志旋转请求标志为假 + t_thrd.log_cxt.pLogCtl->flush_requested = false; // 初始化日志刷新请求标志为假 if (NULL == t_thrd.log_cxt.pLogCtl->log_dir) { + // 如果日志目录尚未设置,则获取日志目录 t_thrd.log_cxt.pLogCtl->log_dir = LogCtlGetLogDirectory(PROFILE_LOG_TAG, true); + // 创建日志目录,如果目录不存在 if (0 == mkdir(t_thrd.log_cxt.pLogCtl->log_dir, S_IRWXU) || (EEXIST == errno)) { /* make sure dir permition is 700 */ (void)chmod(t_thrd.log_cxt.pLogCtl->log_dir, S_IRWXU); @@ -1764,6 +2202,7 @@ static void PLogCtlInit(void) } if (NULL == t_thrd.log_cxt.pLogCtl->filename_pattern) { + // 如果日志文件名模式尚未设置,则获取日志文件名模式 t_thrd.log_cxt.pLogCtl->file_suffix = PROFILE_LOG_SUFFIX; /* plog file pattern should be the same with error log, but post suffix */ t_thrd.log_cxt.pLogCtl->filename_pattern = LogCtlGetFilenamePattern(PROFILE_LOG_SUFFIX); @@ -1772,6 +2211,7 @@ static void PLogCtlInit(void) } if (NULL == t_thrd.log_cxt.pLogCtl->log_buf) { + // 如果日志缓冲区尚未分配,则分配一个新的缓冲区 t_thrd.log_cxt.pLogCtl->log_buf = (char*)palloc(READ_BUF_SIZE); t_thrd.log_cxt.pLogCtl->max_len = READ_BUF_SIZE; t_thrd.log_cxt.pLogCtl->cur_len = 0; @@ -1779,7 +2219,7 @@ static void PLogCtlInit(void) /* do the last two steps */ t_thrd.log_cxt.pLogCtl->inited = true; - allLogCtl[LOG_TYPE_PLOG] = t_thrd.log_cxt.pLogCtl; + allLogCtl[LOG_TYPE_PLOG] = t_thrd.log_cxt.pLogCtl; // 将性能日志控制数据结构添加到全局数组中 } /* @@ -1845,38 +2285,69 @@ void LogCtlLastFlushBeforePMExit(void) } } +/* + * 功能:用于打开AS(Adaptive Server)性能日志文件 + * + * 参数: + * doOpen:指向布尔值的指针,用于指示是否需要打开新的日志文件 + * + * 返回值:返回指向打开的AS性能日志文件的指针 + * 如果doOpen参数不为NULL且需要打开新文件,则设置为true; + * 否则,设置为false + */ void* ASPOpenLogFile(bool *doOpen) { - if (doOpen != NULL) { - *doOpen = false; + if (doOpen != NULL) { // 检查是否传递了doOpen指针 + *doOpen = false; // 如果有的话,将其设置为false,表示不需要打开新的日志文件 } - + // 检查全局变量t_thrd.logger.asplogFile是否为NULL if (t_thrd.logger.asplogFile == NULL) { + // 如果为NULL,表示当前没有打开的AS性能日志文件 + // 生成一个新的AS性能日志文件的文件名 char *filename = logfile_getname(time(NULL), ".log", g_instance.attr.attr_common.asp_log_directory, u_sess->attr.attr_common.asp_log_filename); + // 使用生成的文件名和写入模式"a" 打开AS性能日志文件 t_thrd.logger.asplogFile = logfile_open(filename, "a", false); - + // 检查全局变量t_thrd.logger.last_asp_file_name if (t_thrd.logger.last_asp_file_name != NULL) /* probably shouldn't happen */ - pfree(t_thrd.logger.last_asp_file_name); - t_thrd.logger.last_asp_file_name = filename; - if (doOpen != NULL) { - *doOpen = true; + pfree(t_thrd.logger.last_asp_file_name); // 释放其内存 + t_thrd.logger.last_asp_file_name = filename; // 设置新生成的文件名,以便在下次打开文件时记住上一个文件的名称 + if (doOpen != NULL) { // 如果传递了doOpen指针 + *doOpen = true; // 设置为true,表示已经打开了新的AS性能日志文件 } } - return (void *)t_thrd.logger.asplogFile; + return (void *)t_thrd.logger.asplogFile; // 返回指向打开的AS性能日志文件的指针 } +/* + * 功能:关闭AS(Adaptive Server)性能日志文件 + * + * 参数:无 + * + * 返回值:无 + */ void ASPCloseLogFile() { + // 检查全局变量t_thrd.logger.asplogFile是否为NULL,以确保已经打开了AS性能日志文件 if (t_thrd.logger.asplogFile != NULL) { - fclose(t_thrd.logger.asplogFile); - t_thrd.logger.asplogFile = NULL; + fclose(t_thrd.logger.asplogFile); // 如果AS性能日志文件已经打开,关闭该文件 + t_thrd.logger.asplogFile = NULL; // 表示AS性能日志文件已经关闭 } } /* * * perform logfile rotation * */ + +/* + * 功能:AS性能日志文件轮换 + * + * 参数: + * time_based_rotation:表示是否基于时间进行日志文件的轮换 + * size_rotation_for:表示用于决定是否进行日志文件旋转的条件 + * + * 返回值:无 + */ static void asp_logfile_rotate(bool time_based_rotation, int size_rotation_for) { char* aspFilename = NULL; @@ -1890,10 +2361,12 @@ static void asp_logfile_rotate(bool time_based_rotation, int size_rotation_for) * the planned rotation time, not current time, to avoid "slippage" in the * file name when we don't do the rotation immediately. */ + // 如果是基于时间的轮换 if (time_based_rotation) - fntime = t_thrd.logger.next_rotation_time; + fntime = t_thrd.logger.next_rotation_time; // 使用计划的轮换时间 else - fntime = time(NULL); + fntime = time(NULL); // 否则使用当前时间 + // 生成新的AS性能日志文件名 aspFilename = logfile_getname(time(NULL), ".log", g_instance.attr.attr_common.asp_log_directory, @@ -1906,20 +2379,22 @@ static void asp_logfile_rotate(bool time_based_rotation, int size_rotation_for) * * Note: t_thrd.logger.last_file_name should never be NULL here, but if it is, append. */ + if ((time_based_rotation || (size_rotation_for & LOG_DESTINATION_ASPLOG)) && pmState == PM_RUN) { if (u_sess->attr.attr_common.Log_truncate_on_rotation && time_based_rotation && t_thrd.logger.asplogFile != NULL && strcmp(aspFilename, t_thrd.logger.last_asp_file_name) != 0) { - fh = logfile_open(aspFilename, "w", true); + fh = logfile_open(aspFilename, "w", true); // 覆盖 } else { - fh = logfile_open(aspFilename, "a", true); + fh = logfile_open(aspFilename, "a", true); // 追加写入 } - if (fh == NULL) { + if (fh == NULL) { // 如果无法打开新的AS性能日志文件 /* * ENFILE/EMFILE are not too surprising on a busy system; just * keep using the old file till we manage to get a new one. * Otherwise, assume something's wrong with Log_directory and stop * trying to create files. */ + // 发出一些错误消息,禁用自动轮换 if (errno != ENFILE && errno != EMFILE) { ereport(LOG, (errmsg("disabling automatic rotation (use SIGHUP to re-enable)"))); t_thrd.logger.rotation_disabled = true; @@ -1929,7 +2404,7 @@ static void asp_logfile_rotate(bool time_based_rotation, int size_rotation_for) pfree(aspFilename); return; } - + // 如果新的文件打开成功,关闭旧的AS性能日志文件并将文件句柄切换到新文件 if (t_thrd.logger.asplogFile != NULL) { fclose(t_thrd.logger.asplogFile); } @@ -1938,7 +2413,7 @@ static void asp_logfile_rotate(bool time_based_rotation, int size_rotation_for) /* instead of pfree'ing filename, remember it for next time */ if (t_thrd.logger.last_asp_file_name != NULL) - pfree(t_thrd.logger.last_asp_file_name); + pfree(t_thrd.logger.last_asp_file_name); // 释放新文件名的内存 t_thrd.logger.last_asp_file_name = aspFilename; aspFilename = NULL; } @@ -1948,37 +2423,69 @@ static void asp_logfile_rotate(bool time_based_rotation, int size_rotation_for) } } +/* + * 功能:用于打开AS(Adaptive Server)性能日志文件 + * + * 参数: + * doOpen:指向布尔值的指针,用于指示是否需要打开新的日志文件 + * + * 返回值:返回查询日志文件的文件指针 + */ void* SQMOpenLogFile(bool *doOpen) { - if (doOpen != NULL) + if (doOpen != NULL) // 检查传入的doOpen指针是否为NULL *doOpen = false; - if (t_thrd.logger.querylogFile == NULL) { + if (t_thrd.logger.querylogFile == NULL) { // 检查全局变量t_thrd.logger.querylogFile是否为空 + // 如果为空,表示没有已经打开的查询日志文件,需要打开一个新的文件 + // 构造查询日志文件的名称,包括时间戳、后缀等信息 char *filename = logfile_getname(time(NULL), ".log", g_instance.attr.attr_common.query_log_directory, u_sess->attr.attr_common.query_log_file); + // 打开查询日志文件,使用附加模式("a"),允许创建新文件 t_thrd.logger.querylogFile = logfile_open(filename, "a", false); - pfree(filename); + pfree(filename); // 释放构造文件名时分配的内存 if (doOpen != NULL) { - *doOpen = true; + *doOpen = true; // 表示成功打开了查询日志文件 } } - return (void *)t_thrd.logger.querylogFile; + return (void *)t_thrd.logger.querylogFile; // 返回查询日志文件的文件指针 } +/* + * 功能:关闭SQL Monitor(SQM)查询日志文件 + * + * 参数:无 + * + * 返回值:无 + */ void SQMCloseLogFile() { if (t_thrd.logger.querylogFile != NULL) { + // 如果不为空,表示有一个已经打开的查询日志文件需要关闭 + // 关闭查询日志文件,确保任何未刷新的数据被刷新到磁盘 fclose(t_thrd.logger.querylogFile); - t_thrd.logger.querylogFile = NULL; + t_thrd.logger.querylogFile = NULL; // 表示查询日志文件已成功关闭 } } +/* + * 功能:执行慢查询日志文件的轮换操作 + * + * 参数: + * time_based_rotation:表示是否基于时间进行轮换 + * size_rotation_for:表示日志文件轮换的原因,可以是以下几种之一: + * LOG_DESTINATION_QUERYLOG:表示基于慢查询日志大小进行轮换。 + * LOG_DESTINATION_SYSLOG:表示基于系统日志大小进行轮换。 + * LOG_DESTINATION_CSVLOG:表示基于CSV日志大小进行轮换 + * + * 返回值:无 + */ static void slow_query_logfile_rotate(bool time_based_rotation, int size_rotation_for) { - char* queryFilename = NULL; - pg_time_t fntime; - FILE* fh = NULL; + char* queryFilename = NULL; // 用于存储新日志文件的文件名 + pg_time_t fntime; // 存储计划的轮换时间或当前时间 + FILE* fh = NULL; // 文件句柄,用于打开新的日志文件 t_thrd.logger.rotation_requested = false; @@ -1987,10 +2494,11 @@ static void slow_query_logfile_rotate(bool time_based_rotation, int size_rotatio * the planned rotation time, not current time, to avoid "slippage" in the * file name when we don't do the rotation immediately. */ + // 根据轮换类型选择计划的轮换时间或当前时间,以计算新日志文件名 if (time_based_rotation) - fntime = t_thrd.logger.next_rotation_time; + fntime = t_thrd.logger.next_rotation_time; // 基于时间的轮换,使用计划的轮换时间 else - fntime = time(NULL); + fntime = time(NULL); // 非时间基础轮换,使用当前时间 queryFilename = logfile_getname(time(NULL), ".log", g_instance.attr.attr_common.query_log_directory, u_sess->attr.attr_common.query_log_file); /* @@ -2001,12 +2509,13 @@ static void slow_query_logfile_rotate(bool time_based_rotation, int size_rotatio * * Note: t_thrd.logger.last_file_name should never be NULL here, but if it is, append. */ + // 判断是否需要覆盖或追加到日志文件 if ((time_based_rotation || (size_rotation_for & LOG_DESTINATION_QUERYLOG)) && pmState == PM_RUN) { if (u_sess->attr.attr_common.Log_truncate_on_rotation && time_based_rotation && t_thrd.logger.querylogFile != NULL && strcmp(queryFilename, t_thrd.logger.last_query_log_file_name) != 0) { - fh = logfile_open(queryFilename, "w", true); + fh = logfile_open(queryFilename, "w", true); // 覆盖模式 } else { - fh = logfile_open(queryFilename, "a", true); + fh = logfile_open(queryFilename, "a", true); // 追加模式 } if (fh == NULL) { @@ -2016,39 +2525,51 @@ static void slow_query_logfile_rotate(bool time_based_rotation, int size_rotatio * Otherwise, assume something's wrong with Log_directory and stop * trying to create files. */ + // 处理文件打开失败的情况 if (errno != ENFILE && errno != EMFILE) { ereport(LOG, (errmsg("disabling automatic rotation (use SIGHUP to re-enable)"))); t_thrd.logger.rotation_disabled = true; } if (queryFilename != NULL) - pfree(queryFilename); + pfree(queryFilename); // 释放分配的文件名内存 return; } if (t_thrd.logger.querylogFile != NULL) { - fclose(t_thrd.logger.querylogFile); + fclose(t_thrd.logger.querylogFile); // 关闭当前日志文件 } - t_thrd.logger.querylogFile = fh; + t_thrd.logger.querylogFile = fh; // 设置新的日志文件句柄 /* instead of pfree'ing filename, remember it for next time */ + // 记录新的日志文件名 if (t_thrd.logger.last_query_log_file_name != NULL) - pfree(t_thrd.logger.last_query_log_file_name); - t_thrd.logger.last_query_log_file_name = queryFilename; + pfree(t_thrd.logger.last_query_log_file_name); // 释放上一个日志文件名的内存 + t_thrd.logger.last_query_log_file_name = queryFilename; // 记录新的日志文件名 queryFilename = NULL; } if (queryFilename != NULL) - pfree(queryFilename); + pfree(queryFilename); // 释放内存 } +/* + * 功能:初始化指定类型的日志目录,并根据需要设置全局变量以指定日志目录的路径 + * + * 参数: + * include_nodename:指示是否应该在日志目录路径中包括节点名称 + * logid:表示要初始化的日志类型。 + * 根据传入的值,可以是 "ASPY"、"SLOWQUERY" 或 "PERF_JOB" + * + * 返回值:无 + */ void init_instr_log_directory(bool include_nodename, const char* logid) { /* create directory for aspy & slow query log */ - char* logdir = NULL; - logdir = LogCtlGetLogDirectory(logid, include_nodename); - + char* logdir = NULL; // 用于存储日志目录路径 + logdir = LogCtlGetLogDirectory(logid, include_nodename); // 获取日志目录路径 + // 检查是否成功创建日志目录或目录已存在 if (pg_mkdir_p(logdir, S_IRWXU) == 0 || (errno == EEXIST)) { /* * make sure dir permition is 700. @@ -2056,26 +2577,28 @@ void init_instr_log_directory(bool include_nodename, const char* logid) * chmod() may be called by many process, and it maybe failed. * ignore its returned value of this case. */ - (void)chmod(logdir, S_IRWXU); + (void)chmod(logdir, S_IRWXU); // 设置目录权限 } else { /* this directory may be created already, don't care this case */ if (errno != EEXIST) { - pfree(logdir); + pfree(logdir); // 释放分配的内存 ereport(FATAL, (errmsg( "ERROR: could not create instr log directory \"%s\": %s\n", logid, gs_strerror(errno)))); } } - if (include_nodename) { + if (include_nodename) { // 如果包括节点名称 + // 根据日志ID设置不同的全局目录路径 if (strcmp(logid, ASP_LOG_TAG) == 0) { - g_instance.attr.attr_common.asp_log_directory = logdir; + g_instance.attr.attr_common.asp_log_directory = logdir; // 设置aspy日志目录路径 } else if (strcmp(logid, SLOWQUERY_LOG_TAG) == 0) { - g_instance.attr.attr_common.query_log_directory = logdir; + g_instance.attr.attr_common.query_log_directory = logdir; // 设置慢查询日志目录路径 } else if (strcmp(logid, PERF_JOB_TAG) == 0) { - g_instance.attr.attr_common.Perf_directory = logdir; + g_instance.attr.attr_common.Perf_directory = logdir; // 设置性能日志目录路径 } } else { - pfree(logdir); + pfree(logdir); // 释放分配的内存 + } } diff --git a/src/gausskernel/process/postmaster/walwriter.cpp b/src/gausskernel/process/postmaster/walwriter.cpp index f7a290f58..8491da04a 100755 --- a/src/gausskernel/process/postmaster/walwriter.cpp +++ b/src/gausskernel/process/postmaster/walwriter.cpp @@ -420,7 +420,7 @@ void WalWriterMain(void) * so we need to stop what we're doing and exit. */ /* - *功能:处理快速终止信号 + *功能:处理快速终止信号(当主进程发出SIGQUIT信号) * 紧急退出操作,不会触发正常的清理和关闭步骤,因为共享内存可能已损坏 * * 参数: diff --git a/src/gausskernel/process/postmaster/walwriterauxiliary.cpp b/src/gausskernel/process/postmaster/walwriterauxiliary.cpp index 4cd38e3e5..0490be47c 100755 --- a/src/gausskernel/process/postmaster/walwriterauxiliary.cpp +++ b/src/gausskernel/process/postmaster/walwriterauxiliary.cpp @@ -299,7 +299,7 @@ void WalWriterAuxiliaryMain(void) * so we need to stop what we're doing and exit. */ /* - *功能:处理快速终止信号 + *功能:处理快速终止信号(当主进程发出SIGQUIT信号) * 紧急退出操作,不会触发正常的清理和关闭步骤,因为共享内存可能已损坏 * * 参数: diff --git a/src/include/postmaster/snapcapturer.h b/src/include/postmaster/snapcapturer.h index 02541ff4a..67d42aea9 100644 --- a/src/include/postmaster/snapcapturer.h +++ b/src/include/postmaster/snapcapturer.h @@ -23,21 +23,24 @@ #define SNAPCAPTURER_H #include "utils/snapshot.h" - +// 定义了三种不同的事务快照工作进程状态,用于跟踪进程的执行状态 typedef enum TxnWorkerStatus { - TXNWORKER_DEFAULT, - TXNWORKER_STARTED, - TXNWORKER_DONE + TXNWORKER_DEFAULT, // 表示事务快照工作进程的默认状态,通常在进程启动前使用 + TXNWORKER_STARTED, // 表示事务快照工作进程已经启动并正在运行 + TXNWORKER_DONE // 表示事务快照工作进程已完成其任务 } TxnWorkerStatus; +// 存储有关事务快照工作进程的信息,以便其他组件可以访问和操作这些信息 typedef struct TxnSnapWorkerInfo { - NameData dbName; - ThreadId snapworkerPid; /* PID (0 if not started) */ - TxnWorkerStatus status; - Latch latch; + NameData dbName; // 存储了数据库名称的数据结构 + ThreadId snapworkerPid; /* PID (0 if not started) */ // 表示事务快照工作进程的进程ID + TxnWorkerStatus status; // 表示事务快照工作进程的状态 + Latch latch; // 用于线程同步的信号量,用于等待事件的发生或通知其他进程 } TxnSnapWorkerInfo; + +// 用于定义共享内存中存储 typedef struct TxnSnapCapShmemStruct { - TxnSnapWorkerInfo workerInfo; + TxnSnapWorkerInfo workerInfo; // 存储有关事务快照工作进程的信息,以便协同工作 } TxnSnapCapShmemStruct; extern Size TxnSnapCapShmemSize(void); diff --git a/src/include/postmaster/syslogger.h b/src/include/postmaster/syslogger.h index f48f1dd5f..d7af81bcc 100644 --- a/src/include/postmaster/syslogger.h +++ b/src/include/postmaster/syslogger.h @@ -68,31 +68,20 @@ enum LogType { LOG_TYPE_UPLIMIT = 127 }; +// 用于管理日志控制相关的数据 typedef struct LogControlData { - bool inited; - uint16 ver; - - /* rotation request */ - volatile sig_atomic_t rotation_requested; - - /* to flush buffer request */ - volatile sig_atomic_t flush_requested; - - /* log directory */ - char* log_dir; - - /* pattern of log file name */ - char* filename_pattern; - char* file_suffix; - - /* current log file name and its fd */ - char* now_file_name; - FILE* now_file_fd; - - /* log chunk buffer */ - char* log_buf; - int cur_len; - int max_len; + bool inited; //-> 标识日志控制是否已经初始化 + uint16 ver; //-> 用于记录日志控制的版本信息 + volatile sig_atomic_t rotation_requested; //-> 用于表示是否请求执行日志轮换操作的标志 + volatile sig_atomic_t flush_requested; //-> 用于表示是否请求刷新日志缓冲区的标志 + char* log_dir; //-> 存储日志文件的目录路径 + char* filename_pattern; //-> 存储日志文件名的模式 + char* file_suffix; //-> 存储日志文件的后缀 + char* now_file_name; //-> 存储当前日志文件的名称 + FILE* now_file_fd; //-> 存储当前日志文件的文件描述符 + char* log_buf; //-> 存储日志数据的缓冲区 + int cur_len; //-> 当前缓冲区中的日志数据长度 + int max_len; //->缓冲区的最大容量 } LogControlData; /* @@ -110,14 +99,15 @@ typedef struct LogControlData { #define PROFILE_LOG_VERSION 1 /* header data in each binary log file */ +// 存储二进制日志文件的头部信息 typedef struct { /* must be the first */ - unsigned long fst_magic; + unsigned long fst_magic; //-> 表示日志文件头的起始标志 - uint16 version; - uint8 hostname_len; - uint8 nodename_len; - uint16 timezone_len; + uint16 version; //-> 存储日志文件的版本信息 + uint8 hostname_len; //-> 存储主机名的长度 + uint8 nodename_len; //-> 存储节点名称的长度 + uint16 timezone_len; //-> 存储时区信息的长度 /* * part1: hostname <- hostname_len @@ -126,24 +116,27 @@ typedef struct { */ /* must be the last */ - unsigned long lst_magic; + unsigned long lst_magic; //->表示日志文件头的结束标志 } LogFileHeader; +// 描述管道通信中的协议头信息 typedef struct { - char nuls[2]; /* always \0\0 */ - uint16 len; /* size of this chunk (counts data only) */ - char logtype; /* which log type, see LogType */ + char nuls[2]; /* always \0\0 */ // -> 用于对齐结构体以确保后续成员按照正确的字节边界排列 + uint16 len; /* size of this chunk (counts data only) */ // -> 表示当前数据块的大小 + char logtype; /* which log type, see LogType */ // -> 表示日志的类型 char is_last; /* last chunk of message? 't' or 'f' ('T' or * 'F' for CSV case) */ + // -> 表示是否是消息的最后一个块 /* writer's pid. be placed the last, and make data 8 bytes alligned */ - ThreadId pid; - uint64 magic; /* magic number to check the proto header */ - char data[FLEXIBLE_ARRAY_MEMBER]; /* data payload starts here */ + ThreadId pid; // -> 表示写入数据的线程的进程标识符 + uint64 magic; /* magic number to check the proto header */ // -> 用于检查协议头的完整性 + char data[FLEXIBLE_ARRAY_MEMBER]; /* data payload starts here */ // -> 用于存储实际的数据内容 } LogPipeProtoHeader; +// 在管道通信中用于数据的传输 typedef union { - LogPipeProtoHeader proto; - char filler[LOGPIPE_CHUNK_SIZE]; + LogPipeProtoHeader proto; //-> 表示管道通信的协议头信息 + char filler[LOGPIPE_CHUNK_SIZE]; //-> 用于占用一定的内存空间,以保证结构体的大小 } LogPipeProtoChunk; #define LOGPIPE_HEADER_SIZE offsetof(LogPipeProtoHeader, data) -- 2.34.1 From 3e267c7796413fdc838d1b59347f4ce1f9e90147 Mon Sep 17 00:00:00 2001 From: nuoya <1204149038@qq.com> Date: Wed, 4 Oct 2023 21:24:54 +0800 Subject: [PATCH 50/50] Update cstore_allocspace.cpp --- .../storage/cstore/cstore_allocspace.cpp | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/gausskernel/storage/cstore/cstore_allocspace.cpp b/src/gausskernel/storage/cstore/cstore_allocspace.cpp index 0d0710779..78eac70fd 100644 --- a/src/gausskernel/storage/cstore/cstore_allocspace.cpp +++ b/src/gausskernel/storage/cstore/cstore_allocspace.cpp @@ -460,28 +460,34 @@ void CStoreFreeSpace::Push(const CStoreFreeSpaceDesc& desc) m_descs[i] = desc; } -void CStoreFreeSpace::PopDescWithMaxSize(CStoreFreeSpaceDesc& desc) +/* + * 功能:弹出最大大小的空闲空间描述 + * + * 参数: + * desc:用于存储弹出的空闲空间描述 + */ +void CStoreFreeSpace::PopDescWithMaxSize(CStoreFreeSpaceDesc &desc) { CStoreFreeSpaceDesc tmp; int i = 1; int subi = 2; if (m_descNum == 0) - return; + return; // 如果没有空闲空间描述,直接返回 - desc = m_descs[1]; - tmp = m_descs[m_descNum--]; + desc = m_descs[1]; // 将根节点的空闲空间描述赋值给传入的参数 desc + tmp = m_descs[m_descNum--]; // 取出最后一个空闲空间描述,并减少描述数量 while (subi <= m_descNum) { if (subi < m_descNum && m_descs[subi].size < m_descs[subi + 1].size) - subi++; + subi++; // 如果右子节点比左子节点大,选择右子节点 if (tmp.size >= m_descs[subi].size) - break; - m_descs[i] = m_descs[subi]; + break; // 如果当前节点比子节点大,退出循环 + m_descs[i] = m_descs[subi]; // 否则将子节点上移 i = subi; subi *= 2; } - m_descs[i] = tmp; + m_descs[i] = tmp; // 将原根节点(最大的)放入空闲空间描述数组的正确位置 } void CStoreFreeSpace::GetDescWithMaxSize(_out_ CStoreFreeSpaceDesc& desc) -- 2.34.1