forked from huawei/openGauss-server
1275 lines
46 KiB
C++
Executable File
1275 lines
46 KiB
C++
Executable File
/*
|
|
* 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
|
|
*
|
|
* 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.
|
|
* -------------------------------------------------------------------------
|
|
*
|
|
* datarcvwriter.cpp
|
|
* functions for data receive management
|
|
*
|
|
*
|
|
* IDENTIFICATION
|
|
* src/gausskernel/storage/replication/datarcvwriter.cpp
|
|
*
|
|
* -------------------------------------------------------------------------
|
|
*/
|
|
#include "postgres.h"
|
|
#include "knl/knl_variable.h"
|
|
|
|
#include "access/xlogutils.h"
|
|
#include "libpq/pqsignal.h"
|
|
#include "miscadmin.h"
|
|
#include "catalog/catalog.h"
|
|
#include "catalog/pg_tablespace.h"
|
|
#include "replication/dataqueue.h"
|
|
#include "replication/datareceiver.h"
|
|
#include "replication/datasender.h"
|
|
#include "replication/walsender.h"
|
|
#include "storage/bufmgr.h"
|
|
#include "storage/cu.h"
|
|
#include "storage/custorage.h"
|
|
#include "storage/ipc.h"
|
|
#include "storage/proc.h"
|
|
#include "storage/lmgr.h"
|
|
#include "storage/smgr.h"
|
|
#include "postmaster/alarmchecker.h"
|
|
#include "utils/guc.h"
|
|
#include "utils/hsearch.h"
|
|
#include "utils/memutils.h"
|
|
#include "utils/ps_status.h"
|
|
#include "utils/resowner.h"
|
|
#include "gssignal/gs_signal.h"
|
|
|
|
#define MAX_DUMMY_DATA_FILE (RELSEG_SIZE * BLCKSZ)
|
|
|
|
typedef enum { NONEEXISTDATABASEDIR = 0, EXISTDATABASEDIR, DATABASEDIRCREATECANCEL } DataBaseDirState;
|
|
|
|
/* max dummy data write file (default: 1GB) */
|
|
static int dummy_data_writer_file_fd = -1;
|
|
static uint32 dummy_data_writer_file_offset = 0;
|
|
bool dummy_data_writer_use_file = false;
|
|
|
|
/* Signal handlers */
|
|
static void DataRcvWriterSigHupHandler(SIGNAL_ARGS);
|
|
static void DataRcvWriterQuickDie(SIGNAL_ARGS);
|
|
static void ReqShutdownHandler(SIGNAL_ARGS);
|
|
|
|
static void SetDataRcvWriterLatch(void);
|
|
static void EmptyDataRcvWriterLatch(void);
|
|
|
|
static void DummyStandbyDoDataWrite(char* buf, uint32 nbytes);
|
|
|
|
static void DataWriterHashCreate(void);
|
|
static bool DataWriterHashSearch(const RelFileNode& node, int attid, ForkNumber forkno, StorageEngine type,
|
|
HASHACTION action, Relation& reln, CUStorage*& cuStorage);
|
|
static void DataWriterHashRemove(bool flushdata);
|
|
static bool DatabaseHashSearch(Oid spcoid, Oid dboid);
|
|
static DataBaseDirState CheckDatabaseReady(Oid spcNode, Oid dbNode);
|
|
static void DummyStandbyDataRcvWrite(char* buf, uint32 nbytes);
|
|
static bool CanWriteBuffer(SMgrRelation smgr, ForkNumber forkNum, const char* path);
|
|
|
|
/*
|
|
* Called when the DataRcvWriterMain is ending.
|
|
*/
|
|
static void ShutdownDataRcvWriter(int code, Datum arg)
|
|
{
|
|
/* If waiting, get off wait queue (should only be needed after error),clear WriterPid */
|
|
LockErrorCleanup();
|
|
LockReleaseCurrentOwner();
|
|
EmptyDataRcvWriterLatch();
|
|
}
|
|
|
|
void DataRcvWriterMain(void)
|
|
{
|
|
sigjmp_buf localSigjmpBuf;
|
|
MemoryContext datarcvWriterContext;
|
|
t_thrd.xlog_cxt.InRecovery = true;
|
|
|
|
knl_thread_set_name("DataRcvWriter");
|
|
|
|
ereport(LOG, (errmsg("DataRcvWriter thread started")));
|
|
/*
|
|
* Reset some signals that are accepted by postmaster but not here
|
|
*/
|
|
(void)gspqsignal(SIGHUP, DataRcvWriterSigHupHandler);
|
|
(void)gspqsignal(SIGINT, SIG_IGN);
|
|
(void)gspqsignal(SIGTERM, ReqShutdownHandler);
|
|
(void)gspqsignal(SIGQUIT, DataRcvWriterQuickDie); /* hard crash time */
|
|
(void)gspqsignal(SIGALRM, SIG_IGN);
|
|
(void)gspqsignal(SIGPIPE, SIG_IGN);
|
|
(void)gspqsignal(SIGUSR1, SIG_IGN);
|
|
(void)gspqsignal(SIGUSR2, SIG_IGN);
|
|
|
|
/*
|
|
* 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);
|
|
|
|
/* We allow SIGQUIT (quickdie) at all times */
|
|
(void)sigdelset(&t_thrd.libpq_cxt.BlockSig, SIGQUIT);
|
|
|
|
on_shmem_exit(ShutdownDataRcvWriter, 0);
|
|
|
|
/*
|
|
* Create a resource owner to keep track of our resources (currently only
|
|
* buffer pins).
|
|
*/
|
|
t_thrd.utils_cxt.CurrentResourceOwner = ResourceOwnerCreate(NULL, "DataReceive Writer");
|
|
|
|
/*
|
|
* 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.
|
|
*/
|
|
datarcvWriterContext = AllocSetContextCreate(t_thrd.top_mem_cxt,
|
|
"DataReceive Writer",
|
|
ALLOCSET_DEFAULT_MINSIZE,
|
|
ALLOCSET_DEFAULT_INITSIZE,
|
|
ALLOCSET_DEFAULT_MAXSIZE);
|
|
(void)MemoryContextSwitchTo(datarcvWriterContext);
|
|
|
|
/* init the dummy standby data num to write */
|
|
if (dummyStandbyMode) {
|
|
InitDummyDataNum();
|
|
t_thrd.datarcvwriter_cxt.dummy_data_writer_file_num++;
|
|
dummy_data_writer_file_offset = 0;
|
|
}
|
|
|
|
/*
|
|
* If an exception is encountered, processing resumes here.
|
|
*
|
|
* 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;
|
|
|
|
/* Prevent interrupts while cleaning up */
|
|
HOLD_INTERRUPTS();
|
|
|
|
/* Report the error to the server log */
|
|
EmitErrorReport();
|
|
|
|
/* abort async io, must before LWlock release */
|
|
AbortAsyncListIO();
|
|
|
|
/*
|
|
* These operations are really just a minimal subset of
|
|
* AbortTransaction(). We don't have very many resources to worry
|
|
* about in pagewriter, but we do have LWLocks, buffers, and temp files.
|
|
*/
|
|
LWLockReleaseAll();
|
|
AbortBufferIO();
|
|
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);
|
|
|
|
/* If waiting, get off wait queue (should only be needed after error) */
|
|
LockErrorCleanup();
|
|
LockReleaseCurrentOwner();
|
|
|
|
AtEOXact_Files();
|
|
AtEOXact_HashTables(false);
|
|
|
|
/*
|
|
* Now return to normal top-level context and clear ErrorContext for
|
|
* next time.
|
|
*/
|
|
(void)MemoryContextSwitchTo(datarcvWriterContext);
|
|
FlushErrorState();
|
|
|
|
/* Close fd in data writer hash table if any. */
|
|
DataWriterHashRemove(false);
|
|
|
|
/* Flush any leaked data in the top-level context */
|
|
MemoryContextResetAndDeleteChildren(datarcvWriterContext);
|
|
|
|
/* Now we can allow interrupts again */
|
|
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);
|
|
|
|
/*
|
|
* 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();
|
|
}
|
|
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);
|
|
(void)gs_signal_unblock_sigusr2();
|
|
|
|
/*
|
|
* Use the recovery target timeline ID during recovery
|
|
*/
|
|
if (!RecoveryInProgress())
|
|
ereport(FATAL, (errmsg("cannot continue Data streaming, recovery has already ended")));
|
|
|
|
t_thrd.xlog_cxt.ThisTimeLineID = GetRecoveryTargetTLI();
|
|
|
|
/*
|
|
* register procLatch to datarcv shared memory
|
|
*/
|
|
SetDataRcvWriterLatch();
|
|
|
|
/*
|
|
* init a hash table to store the rel file node fd.
|
|
*/
|
|
DataWriterHashCreate();
|
|
|
|
/*
|
|
* Loop forever
|
|
*/
|
|
for (;;) {
|
|
int rc;
|
|
|
|
/* Clear any already-pending wakeups */
|
|
ResetLatch(&t_thrd.proc->procLatch);
|
|
|
|
if (t_thrd.datarcvwriter_cxt.gotSIGHUP) {
|
|
t_thrd.datarcvwriter_cxt.gotSIGHUP = false;
|
|
ProcessConfigFile(PGC_SIGHUP);
|
|
}
|
|
|
|
while (!t_thrd.datarcvwriter_cxt.shutdownRequested && DataRcvWrite() > 0)
|
|
;
|
|
|
|
if (t_thrd.datarcvwriter_cxt.shutdownRequested) {
|
|
ereport(LOG, (errmsg("DataRcvWriter thread shut down")));
|
|
/*
|
|
* 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;
|
|
/* Normal exit from the pagewriter is here */
|
|
proc_exit(0); /* done */
|
|
}
|
|
|
|
rc = WaitLatch(&t_thrd.proc->procLatch, WL_LATCH_SET | WL_TIMEOUT | WL_POSTMASTER_DEATH, (long)1000 /* ms */);
|
|
if (rc & WL_POSTMASTER_DEATH) {
|
|
ereport(LOG, (errmsg("DataRcvWriter thread shut down with exit code 1")));
|
|
gs_thread_exit(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
static void DataRcvWriterSigHupHandler(SIGNAL_ARGS)
|
|
{
|
|
int saveErrno = errno;
|
|
|
|
t_thrd.datarcvwriter_cxt.gotSIGHUP = true;
|
|
if (t_thrd.proc) {
|
|
SetLatch(&t_thrd.proc->procLatch);
|
|
}
|
|
|
|
errno = saveErrno;
|
|
}
|
|
|
|
static void DataRcvWriterQuickDie(SIGNAL_ARGS)
|
|
{
|
|
gs_signal_setmask(&t_thrd.libpq_cxt.BlockSig, NULL);
|
|
|
|
/*
|
|
* 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
|
|
* transaction. Just nail the windows shut and get out of town. Now that
|
|
* there's an atexit callback to prevent third-party code from breaking
|
|
* things by calling exit() directly, we have to reset the callbacks
|
|
* explicitly to make this work as intended.
|
|
*/
|
|
on_exit_reset();
|
|
|
|
/*
|
|
* 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
|
|
* backend. This is necessary precisely because we don't clean up our
|
|
* shared memory state. (The "dead man switch" mechanism in pmsignal.c
|
|
* should ensure the postmaster sees this as a crash, too, but no harm in
|
|
* being doubly sure.)
|
|
*/
|
|
gs_thread_exit(2);
|
|
}
|
|
|
|
static void ReqShutdownHandler(SIGNAL_ARGS)
|
|
{
|
|
int saveErrno = errno;
|
|
|
|
t_thrd.datarcvwriter_cxt.shutdownRequested = true;
|
|
|
|
/* cancel the wait for database directory */
|
|
t_thrd.int_cxt.ProcDiePending = true;
|
|
if (t_thrd.proc) {
|
|
SetLatch(&t_thrd.proc->procLatch);
|
|
}
|
|
errno = saveErrno;
|
|
}
|
|
|
|
void SetDataRcvWriterPID(ThreadId tid)
|
|
{
|
|
/* use volatile pointer to prevent code rearrangement */
|
|
volatile DataRcvData* datarcv = t_thrd.datareceiver_cxt.DataRcv;
|
|
SpinLockAcquire(&datarcv->mutex);
|
|
datarcv->writerPid = tid;
|
|
SpinLockRelease(&datarcv->mutex);
|
|
}
|
|
|
|
static void SetDataRcvWriterLatch(void)
|
|
{
|
|
/* use volatile pointer to prevent code rearrangement */
|
|
volatile DataRcvData* datarcv = t_thrd.datareceiver_cxt.DataRcv;
|
|
SpinLockAcquire(&datarcv->mutex);
|
|
datarcv->datarcvWriterLatch = &t_thrd.proc->procLatch;
|
|
datarcv->writerPid = t_thrd.proc_cxt.MyProcPid;
|
|
SpinLockRelease(&datarcv->mutex);
|
|
}
|
|
|
|
static void EmptyDataRcvWriterLatch(void)
|
|
{
|
|
/* use volatile pointer to prevent code rearrangement */
|
|
volatile DataRcvData* datarcv = t_thrd.datareceiver_cxt.DataRcv;
|
|
SpinLockAcquire(&datarcv->mutex);
|
|
datarcv->datarcvWriterLatch = NULL;
|
|
datarcv->writerPid = 0;
|
|
SpinLockRelease(&datarcv->mutex);
|
|
}
|
|
|
|
bool DataRcvWriterInProgress(void)
|
|
{
|
|
/* use volatile pointer to prevent code rearrangement */
|
|
volatile DataRcvData* datarcv = t_thrd.datareceiver_cxt.DataRcv;
|
|
|
|
SpinLockAcquire(&datarcv->mutex);
|
|
if (datarcv->writerPid == 0) {
|
|
SpinLockRelease(&datarcv->mutex);
|
|
return false;
|
|
}
|
|
SpinLockRelease(&datarcv->mutex);
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Write data to disk of DummyStandby.
|
|
*/
|
|
static void DummyStandbyDoDataWrite(char* buf, uint32 nbytes)
|
|
{
|
|
uint32 sum = 0;
|
|
uint32 currentlen = 0;
|
|
char* caculatebuf = buf;
|
|
dummy_data_writer_use_file = true;
|
|
errno_t rc = 0;
|
|
while (nbytes > 0) {
|
|
rc = memcpy_s((void*)¤tlen, sizeof(uint32), caculatebuf, sizeof(uint32));
|
|
securec_check(rc, "", "");
|
|
if ((sum + currentlen) > (uint32)g_instance.attr.attr_storage.MaxSendSize * 1024) {
|
|
DummyStandbyDataRcvWrite(buf, (uint32)sum);
|
|
buf = caculatebuf;
|
|
sum = 0;
|
|
} else {
|
|
caculatebuf += currentlen;
|
|
sum += currentlen;
|
|
nbytes -= currentlen;
|
|
}
|
|
}
|
|
DummyStandbyDataRcvWrite(buf, (uint32)sum);
|
|
dummy_data_writer_use_file = false;
|
|
}
|
|
|
|
/*
|
|
* Write data to disk, the data is acquire by GetFromDataQueue.
|
|
* 1. When enable_mix_replication is off, and Data sender/DataRcv started,
|
|
* we periodly Write old-format ROW_STORE and COLUMN_STORE data to disk
|
|
* 2. When enable_mix_replication is on, the data is from WalRcv, and written to
|
|
* disk by walrcvwriter, for 'd'message. In this case, we have skip the new-format
|
|
* data when call DoDataWrite
|
|
*/
|
|
uint32 DoDataWrite(char* buf, uint32 nbytes)
|
|
{
|
|
#define InvalidRelFileNode ((RelFileNode){0, 0, 0, -1})
|
|
|
|
RelFileNode curnode = InvalidRelFileNode;
|
|
RelFileNode prevnode = InvalidRelFileNode;
|
|
int curattid = -1;
|
|
int prevattid = -1;
|
|
|
|
DataElementHeaderData datahdr;
|
|
DataQueuePtr lastqueueoffset = {0, 0};
|
|
/* buf unit */
|
|
uint32 currentlen = 0;
|
|
int headerlen = sizeof(DataElementHeaderData);
|
|
Relation reln = NULL;
|
|
CUStorage* cuStorage = NULL;
|
|
errno_t errorno = EOK;
|
|
|
|
#ifdef DATA_DEBUG
|
|
pg_crc32 crc;
|
|
#endif
|
|
|
|
while (nbytes > 0) {
|
|
if (!g_instance.attr.attr_storage.enable_mix_replication) {
|
|
/* parse total_len for this slice of data */
|
|
errorno = memcpy_s(¤tlen, sizeof(uint32), buf, sizeof(uint32));
|
|
securec_check(errorno, "", "");
|
|
buf += sizeof(uint32);
|
|
}
|
|
|
|
/* parse data element header, and skip the header to parse payload below */
|
|
errorno = memcpy_s((void*)&datahdr, headerlen, buf, headerlen);
|
|
securec_check(errorno, "", "");
|
|
RelFileNodeCopy(curnode, datahdr.rnode, GETBUCKETID(datahdr.attid));
|
|
curattid = (int)GETATTID((uint32)datahdr.attid);
|
|
buf += headerlen;
|
|
|
|
if (!g_instance.attr.attr_storage.enable_mix_replication) {
|
|
Assert(currentlen == (sizeof(uint32) + (uint32)headerlen + datahdr.data_size));
|
|
/* for release version, if data is invalid, longjmp */
|
|
if (currentlen != (sizeof(uint32) + (uint32)headerlen + datahdr.data_size)) {
|
|
ereport(PANIC,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("corrupt wal data write len %u bytes, "
|
|
"the expected write data_size %u",
|
|
currentlen,
|
|
datahdr.data_size)));
|
|
}
|
|
}
|
|
ereport(DEBUG5,
|
|
(errmsg("DoDataWrite write page: rnode[%u,%u,%u], blocknum[%u], "
|
|
"pageoffset[%lu], size[%u], queueoffset[%u/%u], attid[%d]",
|
|
datahdr.rnode.spcNode,
|
|
datahdr.rnode.dbNode,
|
|
datahdr.rnode.relNode,
|
|
datahdr.blocknum,
|
|
datahdr.offset,
|
|
datahdr.data_size,
|
|
datahdr.queue_offset.queueid,
|
|
datahdr.queue_offset.queueoff,
|
|
(int)GETATTID((uint32)datahdr.attid))));
|
|
|
|
#ifdef DATA_DEBUG
|
|
INIT_CRC32(crc);
|
|
COMP_CRC32(crc, buf, datahdr.data_size);
|
|
FIN_CRC32(crc);
|
|
|
|
if (!EQ_CRC32(datahdr.data_crc, crc)) {
|
|
ereport(PANIC,
|
|
(errmsg("writing incorrect data page checksum at: "
|
|
"rnode[%u,%u,%u], blocknum[%u], "
|
|
"pageoffset[%u], size[%u], queueoffset[%u/%u]",
|
|
datahdr.rnode.spcNode,
|
|
datahdr.rnode.dbNode,
|
|
datahdr.rnode.relNode,
|
|
datahdr.blocknum,
|
|
datahdr.offset,
|
|
datahdr.data_size,
|
|
datahdr.queue_offset.queueid,
|
|
datahdr.queue_offset.queueoff)));
|
|
}
|
|
#endif
|
|
/* when enable_mix_replication is on, the ROW_STORE type is not supported now! */
|
|
if (g_instance.attr.attr_storage.enable_mix_replication && ROW_STORE == datahdr.type) {
|
|
Assert(false);
|
|
ereport(PANIC,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED),
|
|
errmsg("The Row Store Heap Log SHOULD NOT BE synchronized in the WAL Streaming. "
|
|
"Tracking the data header info: "
|
|
"rnode[%u/%u/%u] blocknum[%u] pageoffset[%lu] "
|
|
"size[%u] queueoffset[%u/%u].",
|
|
datahdr.rnode.spcNode,
|
|
datahdr.rnode.dbNode,
|
|
datahdr.rnode.relNode,
|
|
datahdr.blocknum,
|
|
datahdr.offset,
|
|
datahdr.data_size,
|
|
datahdr.queue_offset.queueid,
|
|
datahdr.queue_offset.queueoff)));
|
|
return nbytes;
|
|
}
|
|
|
|
if ((memcmp(&curnode, &prevnode, sizeof(RelFileNode)) != 0) || (curattid != prevattid)) {
|
|
prevnode = curnode;
|
|
prevattid = curattid;
|
|
|
|
if (!DataWriterHashSearch(curnode, curattid, MAIN_FORKNUM, datahdr.type, HASH_FIND, reln, cuStorage)) {
|
|
/*
|
|
* Before we open the smgr, we should insure that the database
|
|
* directory of current relation is ready based on previous write.
|
|
* If it's not ready, wait for recovery to create.
|
|
*/
|
|
if ((curnode.spcNode != GLOBALTABLESPACE_OID) && !DatabaseHashSearch(curnode.spcNode, curnode.dbNode)) {
|
|
DataBaseDirState databaseDirState = CheckDatabaseReady(curnode.spcNode, curnode.dbNode);
|
|
if (databaseDirState == DATABASEDIRCREATECANCEL) {
|
|
/*
|
|
* when enable_mix_replcation is off,
|
|
* Cancel has been requested, give up the following write.
|
|
* when enable_mix_replcation is on, ERROR
|
|
*/
|
|
if (!g_instance.attr.attr_storage.enable_mix_replication) {
|
|
WakeupDataRecovery();
|
|
} else {
|
|
ereport(ERROR,
|
|
(errmsg("Failed to write the wal data: the database path %s doesn't exist.",
|
|
GetDatabasePath(curnode.dbNode, curnode.spcNode))));
|
|
}
|
|
break;
|
|
} else if (databaseDirState == NONEEXISTDATABASEDIR) {
|
|
/* xlog is already redo where we dropped the database, so we skip this record */
|
|
buf += datahdr.data_size;
|
|
nbytes -= currentlen;
|
|
continue;
|
|
} else {
|
|
/* database dir is already been created, go ahead */
|
|
;
|
|
}
|
|
}
|
|
|
|
if (ROW_STORE == datahdr.type) {
|
|
reln = CreateFakeRelcacheEntry(curnode);
|
|
|
|
/* Open rel at the smgr level if not already done */
|
|
RelationOpenSmgr(reln);
|
|
|
|
/* insert it to the hash table */
|
|
DataWriterHashSearch(curnode, curattid, MAIN_FORKNUM, ROW_STORE, HASH_ENTER, reln, cuStorage);
|
|
} else if (COLUMN_STORE == datahdr.type) {
|
|
CFileNode cFileNode(curnode);
|
|
cFileNode.m_forkNum = MAIN_FORKNUM;
|
|
cFileNode.m_attid = curattid;
|
|
|
|
cuStorage = New(CurrentMemoryContext) CUStorage(cFileNode);
|
|
|
|
/* insert it to the hash table */
|
|
DataWriterHashSearch(curnode, curattid, MAIN_FORKNUM, COLUMN_STORE, HASH_ENTER, reln, cuStorage);
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Row store write data page by Buffer Manager API
|
|
*
|
|
* Lock relation for extension is nessesary for write received pages.
|
|
* We use this lock in reader, this doesn't comply with the principle
|
|
* of lock relation for extension, then read buffer, then lock buffer,
|
|
* then release relation extension. Cause standby server doesn't vacuum
|
|
* the pages, it's ok.
|
|
*/
|
|
if (ROW_STORE == datahdr.type) {
|
|
Buffer buffer;
|
|
Page page;
|
|
SMgrRelation smgr;
|
|
char* path = NULL;
|
|
|
|
#ifdef ENABLE_MULTIPLE_NODES
|
|
LockRelFileNode(curnode, AccessShareLock);
|
|
#endif
|
|
smgr = smgropen(curnode, InvalidBackendId);
|
|
path = relpath(smgr->smgr_rnode, MAIN_FORKNUM);
|
|
|
|
retry:
|
|
/* Do not dirty the buffer any more if relation file was unlinked after we open it */
|
|
if (CanWriteBuffer(smgr, MAIN_FORKNUM, path) == true) {
|
|
/*
|
|
* We use RBM_ZERO_ON_ERROR rather than RBM_ZERO_AND_LOCK here
|
|
* 'cause if the page has not been loaded into buffer, buffer
|
|
* manager would just allocate a zero buffer rather than read the
|
|
* page from smgr. See more in ReadBuffer_common() comments inside.
|
|
*/
|
|
buffer = XLogReadBufferExtended(curnode, MAIN_FORKNUM, datahdr.blocknum, RBM_ZERO_ON_ERROR);
|
|
Assert(BufferIsValid(buffer));
|
|
LockBuffer(buffer, BUFFER_LOCK_EXCLUSIVE);
|
|
page = (Page)BufferGetPage(buffer);
|
|
|
|
/* Ignore pages with small LSN */
|
|
XLogRecPtr local_lsn;
|
|
XLogRecPtr peer_lsn;
|
|
local_lsn = PageGetLSN(page);
|
|
peer_lsn = PageGetLSN((Page)buf);
|
|
/*
|
|
* In data page replicated scenes, maybe appear an old page to cover
|
|
* an new page. Explame for two scenes:
|
|
* 1: when standby is crash, page A will be copyed to dummystandby,
|
|
* then dummystandby is crash, stanby is ok, and primary update
|
|
* A to A', standby will get A', then primary is crash, stanby
|
|
* will failover, it will get page A to cover the page A'.
|
|
* 2: when session1 is copying page A to table t1; session2 is update
|
|
* page A to A'; standby redo the A' first, then write the A, and
|
|
* A will cover A'.
|
|
*/
|
|
if (XLByteLT(peer_lsn, local_lsn))
|
|
; /* do nothing */
|
|
else {
|
|
errno_t rc = 0;
|
|
rc = memcpy_s(page, BLCKSZ, buf, datahdr.data_size);
|
|
securec_check(rc, "", "");
|
|
MarkBufferDirty(buffer);
|
|
}
|
|
UnlockReleaseBuffer(buffer);
|
|
} else {
|
|
/* check again */
|
|
if (CheckFileExists(path) == FILE_EXIST) {
|
|
goto retry;
|
|
}
|
|
ereport(WARNING,
|
|
(errmsg("HA-DoDataWrite: No File Write(file not exists), rnode %u/%u/%u, blockno %u ",
|
|
curnode.spcNode,
|
|
curnode.dbNode,
|
|
curnode.relNode,
|
|
datahdr.blocknum)));
|
|
}
|
|
#ifdef ENABLE_MULTIPLE_NODES
|
|
UnlockRelFileNode(curnode, AccessShareLock);
|
|
#endif
|
|
|
|
pfree_ext(path);
|
|
|
|
if (u_sess->attr.attr_storage.HaModuleDebug) {
|
|
ereport(LOG,
|
|
(errmsg("HA-DoDataWrite: rnode %u/%u/%u, blockno %u ",
|
|
curnode.spcNode,
|
|
curnode.dbNode,
|
|
curnode.relNode,
|
|
datahdr.blocknum)));
|
|
}
|
|
} else if (COLUMN_STORE == datahdr.type) {
|
|
Assert(cuStorage->m_cnode.m_attid == GETATTID(datahdr.attid));
|
|
if (u_sess->attr.attr_storage.HaModuleDebug) {
|
|
check_cu_block(buf, datahdr.data_size);
|
|
ereport(LOG,
|
|
(errmsg("HA-DoDataWrite: rnode %u/%u/%u, col %u, "
|
|
"blockno %lu, cuUnitCount %u",
|
|
datahdr.rnode.spcNode,
|
|
datahdr.rnode.dbNode,
|
|
datahdr.rnode.relNode,
|
|
GETATTID((uint)datahdr.attid),
|
|
datahdr.offset / ALIGNOF_CUSIZE,
|
|
datahdr.data_size / ALIGNOF_CUSIZE)));
|
|
}
|
|
#ifdef ENABLE_MULTIPLE_NODES
|
|
LockRelFileNode(curnode, AccessShareLock);
|
|
#endif
|
|
|
|
/* direct write the data file when column store */
|
|
cuStorage->SaveCU(buf, datahdr.offset, datahdr.data_size, false);
|
|
|
|
#ifdef ENABLE_MULTIPLE_NODES
|
|
UnlockRelFileNode(curnode, AccessShareLock);
|
|
#endif
|
|
}
|
|
|
|
buf += datahdr.data_size;
|
|
|
|
if (!g_instance.attr.attr_storage.enable_mix_replication) {
|
|
nbytes -= currentlen;
|
|
lastqueueoffset = datahdr.queue_offset;
|
|
|
|
/* Use volatile pointer to prevent code rearrangement */
|
|
volatile DataRcvData* datarcv = t_thrd.datareceiver_cxt.DataRcv;
|
|
|
|
ereport(DEBUG5,
|
|
(errmsg("recwriter: receive: %u/%u, local: %u/%u, offset %u/%u",
|
|
datarcv->receivePosition.queueid,
|
|
datarcv->receivePosition.queueoff,
|
|
datarcv->localWritePosition.queueid,
|
|
datarcv->localWritePosition.queueoff,
|
|
datahdr.queue_offset.queueid,
|
|
datahdr.queue_offset.queueoff)));
|
|
|
|
WakeupDataRecovery();
|
|
} else
|
|
nbytes -= ((uint32)headerlen + datahdr.data_size);
|
|
}
|
|
|
|
/* Sync all relations to the disk and then clear the hash table */
|
|
DataWriterHashRemove(true);
|
|
|
|
/* Update shared-memory status when datareceiver is started */
|
|
if (!g_instance.attr.attr_storage.enable_mix_replication) {
|
|
volatile DataRcvData* datarcv = t_thrd.datareceiver_cxt.DataRcv;
|
|
|
|
SpinLockAcquire(&datarcv->mutex);
|
|
datarcv->receivePosition.queueid = lastqueueoffset.queueid;
|
|
datarcv->receivePosition.queueoff = lastqueueoffset.queueoff;
|
|
SpinLockRelease(&datarcv->mutex);
|
|
}
|
|
|
|
return nbytes;
|
|
}
|
|
|
|
/*
|
|
* Write data to disk.
|
|
*/
|
|
int DataRcvWrite(void)
|
|
{
|
|
/* Use volatile pointer to prevent code rearrangement */
|
|
volatile DataRcvData* datarcv = t_thrd.datareceiver_cxt.DataRcv;
|
|
char* writeBuf = NULL;
|
|
uint32 nbytes = 0;
|
|
uint32 remainbytes = 0;
|
|
DataQueuePtr curstartpos;
|
|
DataQueuePtr curendpos;
|
|
|
|
/* avoid concurrent WalDataRcvWrite by WalRcvWriteLock */
|
|
START_CRIT_SECTION();
|
|
LWLockAcquire(RcvWriteLock, LW_EXCLUSIVE);
|
|
|
|
SpinLockAcquire(&datarcv->mutex);
|
|
curstartpos.queueid = datarcv->localWritePosition.queueid;
|
|
curstartpos.queueoff = datarcv->localWritePosition.queueoff;
|
|
SpinLockRelease(&datarcv->mutex);
|
|
|
|
nbytes = GetFromDataQueue(writeBuf,
|
|
g_instance.attr.attr_storage.DataQueueBufSize * 1024,
|
|
curstartpos,
|
|
curendpos,
|
|
true,
|
|
t_thrd.dataqueue_cxt.DataWriterQueue);
|
|
if (nbytes == 0) {
|
|
LWLockRelease(RcvWriteLock);
|
|
END_CRIT_SECTION();
|
|
return 0;
|
|
}
|
|
|
|
/* Write data. */
|
|
if (dummyStandbyMode)
|
|
DummyStandbyDoDataWrite(writeBuf, nbytes);
|
|
else
|
|
remainbytes = DoDataWrite(writeBuf, nbytes);
|
|
|
|
/* Close all open files */
|
|
smgrcloseall();
|
|
|
|
/* Update share memory from the the next operation. */
|
|
nbytes -= remainbytes;
|
|
SpinLockAcquire(&datarcv->mutex);
|
|
datarcv->localWritePosition.queueid = curstartpos.queueid;
|
|
datarcv->localWritePosition.queueoff = curstartpos.queueoff;
|
|
DQByteAdvance(datarcv->localWritePosition, nbytes);
|
|
SpinLockRelease(&datarcv->mutex);
|
|
|
|
PopFromDataQueue(((DataRcvData*)datarcv)->localWritePosition, t_thrd.dataqueue_cxt.DataWriterQueue);
|
|
|
|
LWLockRelease(RcvWriteLock);
|
|
END_CRIT_SECTION();
|
|
|
|
return nbytes;
|
|
}
|
|
|
|
static void DataWriterHashCreate(void)
|
|
{
|
|
/* if no the hash table we will create it */
|
|
if (t_thrd.datarcvwriter_cxt.data_writer_rel_tab == NULL) {
|
|
HASHCTL ctl;
|
|
errno_t rc = 0;
|
|
rc = memset_s(&ctl, sizeof(ctl), 0, sizeof(ctl));
|
|
securec_check(rc, "", "");
|
|
ctl.keysize = sizeof(data_writer_rel_key);
|
|
ctl.entrysize = sizeof(data_writer_rel);
|
|
ctl.hash = tag_hash;
|
|
t_thrd.datarcvwriter_cxt.data_writer_rel_tab =
|
|
hash_create("data writer rel table", 100, &ctl, HASH_ELEM | HASH_FUNCTION);
|
|
} else
|
|
return;
|
|
}
|
|
|
|
static bool DataWriterHashSearch(const RelFileNode& node, int attid, ForkNumber forkno, StorageEngine type,
|
|
HASHACTION action, Relation& reln, CUStorage*& cuStorage)
|
|
{
|
|
data_writer_rel_key key;
|
|
data_writer_rel* hentry = NULL;
|
|
bool found = false;
|
|
errno_t rc = 0;
|
|
|
|
rc = memset_s(&key, sizeof(data_writer_rel_key), 0, sizeof(data_writer_rel_key));
|
|
securec_check(rc, "", "");
|
|
key.node = node;
|
|
key.forkno = forkno;
|
|
key.attid = attid;
|
|
key.type = type;
|
|
|
|
if (t_thrd.datarcvwriter_cxt.data_writer_rel_tab == NULL) {
|
|
DataWriterHashCreate();
|
|
}
|
|
|
|
hentry = (data_writer_rel*)hash_search(t_thrd.datarcvwriter_cxt.data_writer_rel_tab, (void*)&key, action, &found);
|
|
|
|
if (action == HASH_FIND) {
|
|
/* if the action is find, we will get the fd */
|
|
if (found) {
|
|
reln = hentry->reln;
|
|
cuStorage = hentry->cuStorage;
|
|
}
|
|
}
|
|
|
|
if (action == HASH_ENTER) {
|
|
/* if the action is enter, we will insert the fd */
|
|
Assert(!found);
|
|
hentry->reln = reln;
|
|
hentry->cuStorage = cuStorage;
|
|
}
|
|
|
|
return found;
|
|
}
|
|
|
|
static bool DatabaseHashSearch(Oid spcoid, Oid dboid)
|
|
{
|
|
HASH_SEQ_STATUS status;
|
|
data_writer_rel* hentry = NULL;
|
|
|
|
if (t_thrd.datarcvwriter_cxt.data_writer_rel_tab == NULL)
|
|
return false; /* nothing to do */
|
|
|
|
hash_seq_init(&status, t_thrd.datarcvwriter_cxt.data_writer_rel_tab);
|
|
|
|
while ((hentry = (data_writer_rel*)hash_seq_search(&status)) != NULL) {
|
|
if (hentry->key.node.spcNode == spcoid && hentry->key.node.dbNode == dboid) {
|
|
hash_seq_term(&status);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Remove relations in current data writer hash table, flush data if requested.
|
|
* We use the hash table to cach the relation info we are handling to reduce the
|
|
* overhead of IO.
|
|
* Call the function once, after we handle all the data in data writer queue.
|
|
*/
|
|
static void DataWriterHashRemove(bool flushdata)
|
|
{
|
|
#define ERRORDATA_FLUSH_NUM 5
|
|
|
|
HASH_SEQ_STATUS status;
|
|
data_writer_rel* hentry = NULL;
|
|
Relation reln;
|
|
CUStorage* cuStorage = NULL;
|
|
|
|
if (t_thrd.datarcvwriter_cxt.data_writer_rel_tab == NULL)
|
|
return;
|
|
|
|
/*
|
|
* After flush data failed five times, maybe the storage has damage,
|
|
* In order to prevent operation hang up, the process will PANIC.
|
|
*/
|
|
if (t_thrd.datarcvwriter_cxt.dataRcvWriterFlushPageErrorCount++ >= ERRORDATA_FLUSH_NUM)
|
|
ereport(PANIC, (errmsg_internal("ERRORDATA_FLUSH_NUM exceeded")));
|
|
|
|
hash_seq_init(&status, t_thrd.datarcvwriter_cxt.data_writer_rel_tab);
|
|
while ((hentry = (data_writer_rel*)hash_seq_search(&status)) != NULL) {
|
|
if (hentry->key.type == ROW_STORE && !g_instance.attr.attr_storage.enable_mix_replication) {
|
|
reln = hentry->reln;
|
|
|
|
if (flushdata) {
|
|
char* path = relpath(reln->rd_smgr->smgr_rnode, MAIN_FORKNUM);
|
|
#ifdef ENABLE_MULTIPLE_NODES
|
|
LockRelFileNode(reln->rd_node, AccessExclusiveLock);
|
|
#endif
|
|
|
|
/* do not sync the file if it not exists any more */
|
|
if (smgrexists(reln->rd_smgr, MAIN_FORKNUM) && CheckFileExists(path) == FILE_EXIST) {
|
|
FlushRelationBuffers(reln);
|
|
smgrimmedsync(reln->rd_smgr, MAIN_FORKNUM);
|
|
} else {
|
|
ereport(WARNING,
|
|
(errmsg("HA-DataWriterHashRemove: No File SYNC, rnode %u/%u/%u dose not exists",
|
|
reln->rd_node.spcNode,
|
|
reln->rd_node.dbNode,
|
|
reln->rd_node.relNode)));
|
|
}
|
|
#ifdef ENABLE_MULTIPLE_NODES
|
|
UnlockRelFileNode(reln->rd_node, AccessExclusiveLock);
|
|
#endif
|
|
pfree_ext(path);
|
|
}
|
|
RelationCloseSmgr(reln);
|
|
FreeFakeRelcacheEntry(reln);
|
|
} else if (hentry->key.type == COLUMN_STORE) {
|
|
cuStorage = hentry->cuStorage;
|
|
if (cuStorage != NULL) {
|
|
if (flushdata) {
|
|
#ifdef ENABLE_MULTIPLE_NODES
|
|
LockRelFileNode(hentry->key.node, AccessShareLock);
|
|
#endif
|
|
/*
|
|
* unlink/truncate the cu file begin at 0, so we just check
|
|
* whether the xxx_Cx.0 is exist.
|
|
*/
|
|
if (cuStorage->IsDataFileExist(0)) {
|
|
cuStorage->FlushDataFile();
|
|
}
|
|
#ifdef ENABLE_MULTIPLE_NODES
|
|
UnlockRelFileNode(hentry->key.node, AccessShareLock);
|
|
#endif
|
|
if (u_sess->attr.attr_storage.HaModuleDebug)
|
|
ereport(LOG,
|
|
(errmsg("HA-DataWriterHashRemove: rnode %u/%u/%u, col %d",
|
|
hentry->key.node.spcNode,
|
|
hentry->key.node.dbNode,
|
|
hentry->key.node.relNode,
|
|
hentry->key.attid)));
|
|
}
|
|
DELETE_EX(cuStorage);
|
|
}
|
|
}
|
|
hash_search(t_thrd.datarcvwriter_cxt.data_writer_rel_tab, (void*)&hentry->key, HASH_REMOVE, NULL);
|
|
}
|
|
|
|
/* Reset flush page error num after flush page successfully */
|
|
if (flushdata)
|
|
t_thrd.datarcvwriter_cxt.dataRcvWriterFlushPageErrorCount = 0;
|
|
}
|
|
|
|
/*
|
|
* We only handle path in pg_tblspc/. Wait until the tablepsace been redo
|
|
* If the spcNode we got from the tablespace path does not exist. If the spcNode
|
|
* is not a link, throw a error here.
|
|
*/
|
|
static DataBaseDirState CheckDatabaseReady(Oid spcNode, Oid dbNode)
|
|
{
|
|
char* dbpath = NULL;
|
|
struct stat st;
|
|
int nRet = 0;
|
|
DataBaseDirState dbDirState = NONEEXISTDATABASEDIR;
|
|
|
|
dbpath = GetDatabasePath(dbNode, spcNode);
|
|
pg_memory_barrier();
|
|
|
|
Assert(OidIsValid(spcNode));
|
|
retry:
|
|
if (stat(dbpath, &st) < 0) {
|
|
pg_memory_barrier();
|
|
/* xlog already redo where we dropped the database, so we skip this record */
|
|
if (t_thrd.xlog_cxt.RedoDone) {
|
|
dbDirState = NONEEXISTDATABASEDIR;
|
|
ereport(LOG, (errmsg("skip the dropped database directory")));
|
|
return dbDirState;
|
|
}
|
|
/* Directory does not exist? */
|
|
if (errno == ENOENT) {
|
|
#if defined(HAVE_READLINK) || defined(WIN32)
|
|
char tbpath[64] = {0};
|
|
char linkpath[MAXPGPATH] = {0};
|
|
int rllen = 0;
|
|
|
|
/* wait if it's under global and base */
|
|
if (spcNode == GLOBALTABLESPACE_OID || spcNode == DEFAULTTABLESPACE_OID)
|
|
goto invalid_handle;
|
|
|
|
nRet = snprintf_s(tbpath, sizeof(tbpath), 63, "pg_tblspc/%u", spcNode);
|
|
securec_check_ss(nRet, "", "");
|
|
|
|
rllen = readlink(tbpath, linkpath, sizeof(linkpath));
|
|
if (rllen < 0) {
|
|
if (errno == EINVAL || errno == ENOTDIR) {
|
|
pfree(dbpath);
|
|
dbpath = NULL;
|
|
ereport(PANIC, (errmsg("could not read symbolic link \"%s\": %m", tbpath)));
|
|
} else if (errno == ENOENT) {
|
|
ereport(DEBUG3, (errmsg("sleep a while waiting for tablespace \"%s\" ready", tbpath)));
|
|
goto invalid_handle;
|
|
} else {
|
|
pfree(dbpath);
|
|
dbpath = NULL;
|
|
ereport(ERROR, (errcode_for_file_access(), errmsg("invalid tablespace link %s: %m", tbpath)));
|
|
}
|
|
} else if (rllen >= (int)sizeof(linkpath)) {
|
|
pfree(dbpath);
|
|
dbpath = NULL;
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("symbolic link \"%s\" target is too long", tbpath)));
|
|
} else {
|
|
linkpath[rllen] = '\0';
|
|
if (stat(linkpath, &st) < 0 || !S_ISDIR(st.st_mode))
|
|
ereport(PANIC,
|
|
(errcode(ERRCODE_WRONG_OBJECT_TYPE), errmsg("invalid tablespace directory %s: %m", tbpath)));
|
|
goto invalid_handle;
|
|
}
|
|
#else
|
|
/*
|
|
* If the platform does not have symbolic links, it should not be
|
|
* possible to have tablespaces - clearly somebody else created
|
|
* them. Warn about it and ignore.
|
|
*/
|
|
pfree(dbpath);
|
|
dbpath = NULL;
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_FEATURE_NOT_SUPPORTED), errmsg("tablespaces are not supported on this platform")));
|
|
#endif
|
|
} else {
|
|
pfree(dbpath);
|
|
dbpath = NULL;
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
|
errmsg("\"database %u/%u\" invalid directory : %m", spcNode, dbNode)));
|
|
}
|
|
} else {
|
|
pfree(dbpath);
|
|
dbpath = NULL;
|
|
/* Is it not a directory? */
|
|
if (!S_ISDIR(st.st_mode))
|
|
ereport(ERROR,
|
|
(errcode(ERRCODE_WRONG_OBJECT_TYPE),
|
|
errmsg("\"database %u/%u\" exists but is not a directory", spcNode, dbNode)));
|
|
dbDirState = EXISTDATABASEDIR;
|
|
return dbDirState;
|
|
}
|
|
|
|
invalid_handle:
|
|
if ((t_thrd.int_cxt.ProcDiePending || t_thrd.proc_cxt.proc_exit_inprogress) &&
|
|
!t_thrd.datarcvwriter_cxt.AmDataReceiverForDummyStandby) {
|
|
ereport(WARNING,
|
|
(errcode(ERRCODE_ADMIN_SHUTDOWN),
|
|
errmsg("canceling the wait for database directory \"%s\" "
|
|
"being created in recovery",
|
|
dbpath)));
|
|
pfree(dbpath);
|
|
dbpath = NULL;
|
|
dbDirState = DATABASEDIRCREATECANCEL;
|
|
return dbDirState;
|
|
}
|
|
ereport(LOG, (errmsg("waiting for database directory \"%s\" being created in recovery", dbpath)));
|
|
pg_usleep(5000000L);
|
|
goto retry;
|
|
}
|
|
|
|
static void DummyStandbyDataRcvWrite(char* buf, uint32 nbytes)
|
|
{
|
|
ssize_t byteswritten;
|
|
char path[MAXPGPATH] = {0};
|
|
volatile DataRcvData* datarcv = t_thrd.datareceiver_cxt.DataRcv;
|
|
|
|
/* buf unit */
|
|
DataElementHeaderData dataehdr;
|
|
uint32 currentlen = 0;
|
|
int headerlen = sizeof(DataElementHeaderData);
|
|
uint32 caculatebytes = nbytes;
|
|
char* caculatebuf = buf;
|
|
int nRet = 0;
|
|
|
|
/*
|
|
* get the file num to store:
|
|
* 1. If MAX_DUMMY_DATA_FILE <= dummy_data_writer_file_offset, open
|
|
* the next file to store
|
|
* 2. If the current file can not store the nbytes, discard the current file space,
|
|
* open the next file to store.
|
|
*/
|
|
if (dummy_data_writer_file_offset >= MAX_DUMMY_DATA_FILE ||
|
|
(MAX_DUMMY_DATA_FILE - dummy_data_writer_file_offset < (uint32)(nbytes + sizeof(nbytes)))) {
|
|
ereport(DEBUG2, (errmsg("data file num %u", t_thrd.datarcvwriter_cxt.dummy_data_writer_file_num)));
|
|
t_thrd.datarcvwriter_cxt.dummy_data_writer_file_num++;
|
|
dummy_data_writer_file_offset = 0;
|
|
|
|
if (dummy_data_writer_file_fd >= 0) {
|
|
close(dummy_data_writer_file_fd);
|
|
dummy_data_writer_file_fd = -1;
|
|
}
|
|
}
|
|
|
|
/* open the file */
|
|
if (dummy_data_writer_file_fd < 0) {
|
|
/* get the file path */
|
|
nRet = snprintf_s(path,
|
|
sizeof(path),
|
|
MAXPGPATH - 1,
|
|
"base/dummy_standby/%u",
|
|
t_thrd.datarcvwriter_cxt.dummy_data_writer_file_num);
|
|
securec_check_ss_c(nRet, "", "");
|
|
|
|
dummy_data_writer_file_fd = open(path, O_RDWR | O_CREAT | PG_BINARY, S_IRUSR | S_IWUSR);
|
|
if (dummy_data_writer_file_fd < 0)
|
|
ereport(PANIC,
|
|
(errcode_for_file_access(),
|
|
errmsg("could not create data file \"%s\", dummy_data_writer_file_fd=%d: %m",
|
|
path,
|
|
dummy_data_writer_file_fd)));
|
|
}
|
|
|
|
while (caculatebytes > 0) {
|
|
errno_t rc = 0;
|
|
rc = memcpy_s((void*)¤tlen, sizeof(uint32), caculatebuf, sizeof(uint32));
|
|
securec_check(rc, "", "");
|
|
caculatebuf += sizeof(uint32);
|
|
|
|
rc = memcpy_s((void*)&dataehdr, headerlen, caculatebuf, headerlen);
|
|
securec_check(rc, "", "");
|
|
caculatebuf += headerlen;
|
|
caculatebuf += dataehdr.data_size;
|
|
caculatebytes -= currentlen;
|
|
}
|
|
|
|
errno = 0;
|
|
/* OK to write the data */
|
|
byteswritten = write(dummy_data_writer_file_fd, &nbytes, sizeof(uint32));
|
|
if (byteswritten < (ssize_t)sizeof(uint32)) {
|
|
/* if write didn't set errno, assume no disk space */
|
|
if (errno == 0) {
|
|
errno = ENOSPC;
|
|
}
|
|
ereport(PANIC, (errcode_for_file_access(),
|
|
errmsg("could not write to data file %s buffer len %u, length %u: %m", path, nbytes, nbytes)));
|
|
}
|
|
|
|
errno = 0;
|
|
byteswritten = write(dummy_data_writer_file_fd, buf, nbytes);
|
|
if (byteswritten < nbytes) {
|
|
/* if write didn't set errno, assume no disk space */
|
|
if (errno == 0)
|
|
errno = ENOSPC;
|
|
ereport(PANIC,
|
|
(errcode_for_file_access(),
|
|
errmsg("could not write to data file %s "
|
|
"at offset %u, length %u: %m",
|
|
path,
|
|
(uint32)dummy_data_writer_file_offset,
|
|
nbytes)));
|
|
}
|
|
|
|
dummy_data_writer_file_offset = dummy_data_writer_file_offset + nbytes + sizeof(nbytes);
|
|
|
|
if (u_sess->attr.attr_storage.HaModuleDebug) {
|
|
ereport(LOG,
|
|
(errmsg("HA-DummyStandbyDataRcvWrite: fileno %u, nbytes %u, queueoffset %u/%u,"
|
|
" dummy_data_writer_file_offset %u, dummy_data_writer_file_fd %d",
|
|
t_thrd.datarcvwriter_cxt.dummy_data_writer_file_num,
|
|
nbytes,
|
|
dataehdr.queue_offset.queueid,
|
|
dataehdr.queue_offset.queueoff,
|
|
dummy_data_writer_file_offset,
|
|
dummy_data_writer_file_fd)));
|
|
}
|
|
|
|
/* use fdatasync to make sure the received data to flush the disk */
|
|
if (pg_fdatasync(dummy_data_writer_file_fd) != 0)
|
|
ereport(PANIC,
|
|
(errcode_for_file_access(),
|
|
errmsg("could not fdatasync data file num %u, fd %d: %m",
|
|
t_thrd.datarcvwriter_cxt.dummy_data_writer_file_num,
|
|
dummy_data_writer_file_fd)));
|
|
|
|
/* Update shared-memory status */
|
|
SpinLockAcquire(&datarcv->mutex);
|
|
datarcv->receivePosition.queueid = dataehdr.queue_offset.queueid;
|
|
datarcv->receivePosition.queueoff = dataehdr.queue_offset.queueoff;
|
|
SpinLockRelease(&datarcv->mutex);
|
|
}
|
|
|
|
/*
|
|
* close data fd for rm data file
|
|
*/
|
|
extern void CloseDataFile(void)
|
|
{
|
|
close(dummy_data_writer_file_fd);
|
|
dummy_data_writer_file_fd = -1;
|
|
dummy_data_writer_file_offset = 0;
|
|
}
|
|
|
|
void InitDummyDataNum(void)
|
|
{
|
|
DIR* dir = NULL;
|
|
struct dirent* de = NULL;
|
|
int max_num_file = 0;
|
|
int min_num_file = 0;
|
|
char* dirpath = DUMMY_STANDBY_DATADIR;
|
|
|
|
/* open the dir of base/dummy_standby */
|
|
errno = 0;
|
|
dir = AllocateDir(dirpath);
|
|
if (dir == NULL && errno == ENOENT) {
|
|
if (mkdir(dirpath, S_IRWXU) < 0) {
|
|
/* Failure other than not exists */
|
|
ereport(ERROR, (errcode_for_file_access(), errmsg("could not create directory \"%s\": %m", dirpath)));
|
|
}
|
|
dir = AllocateDir(dirpath);
|
|
}
|
|
if (dir == NULL) {
|
|
ereport(ERROR, (errcode_for_file_access(), errmsg("could not open directory \"%s\": %m", dirpath)));
|
|
return;
|
|
}
|
|
|
|
/* loop read the file name of base/dummy_standby */
|
|
while ((de = ReadDir(dir, dirpath)) != NULL) {
|
|
/* Skip special stuff */
|
|
if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0)
|
|
continue;
|
|
|
|
min_num_file = atoi(de->d_name);
|
|
break;
|
|
}
|
|
|
|
FreeDir(dir);
|
|
dir = AllocateDir(dirpath);
|
|
|
|
/* loop read the file name of base/dummy_standby */
|
|
while ((de = ReadDir(dir, dirpath)) != NULL) {
|
|
/* Skip special stuff */
|
|
if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0)
|
|
continue;
|
|
|
|
/* search the max num file */
|
|
max_num_file = Max(atoi(de->d_name), max_num_file);
|
|
min_num_file = Min(atoi(de->d_name), min_num_file);
|
|
ereport(DEBUG5,
|
|
(errmsg("InitDummyDataNum de->d_name=%s; max_num_path=%d; min_num_file=%d.",
|
|
de->d_name,
|
|
max_num_file,
|
|
min_num_file)));
|
|
}
|
|
|
|
t_thrd.datarcvwriter_cxt.dummy_data_writer_file_num = max_num_file;
|
|
t_thrd.datasender_cxt.dummy_data_read_file_num = min_num_file;
|
|
FreeDir(dir);
|
|
}
|
|
|
|
/* check whether the data writer thread can write data through shared buffer. */
|
|
static bool CanWriteBuffer(SMgrRelation smgr, ForkNumber forkNum, const char* path)
|
|
{
|
|
if (CheckFileExists(path) == FILE_EXIST) {
|
|
return true;
|
|
} else {
|
|
/*
|
|
* In the case that the physical file is unlink and the fd of file is still held by other thread, the datawriter
|
|
* thead can not write data to shared buffer. Thus, it may dirty the shared buffer and the pendingOpsTable hash
|
|
* table, and it may cause coredump when doing checkpoint due to unlinked file.
|
|
*/
|
|
if ((smgr->md_fd[forkNum] == NULL) && (FdRefcntIsZero(smgr, forkNum))) {
|
|
return true;
|
|
} else {
|
|
return false;
|
|
}
|
|
}
|
|
}
|