Commit 6b3094c2 authored by Tom Lane's avatar Tom Lane

Lots of comment-fixing, and minor cosmetic cleanup, in pg_dump/parallel.c.

The commentary in this file was in extremely sad shape.  The author(s)
had clearly never heard of the project convention that a function header
comment should provide an API spec of some sort for that function.  Much
of it was flat out wrong, too --- maybe it was accurate when written, but
if so it had not been updated to track subsequent code revisions.  Rewrite
and rearrange to try to bring it up to speed, and annotate some of the
places where more work is needed.  (I've refrained from actually fixing
anything of substance ... yet.)

Also, rename a couple of functions for more clarity as to what they do,
do some very minor code rearrangement, remove some pointless Asserts,
fix an incorrect Assert in readMessageFromPipe, and add a missing socket
close in one error exit from pgpipe().  The last would be a bug if we
tried to continue after pgpipe() failure, but since we don't, it's just
cosmetic at present.

Although this is only cosmetic, back-patch to 9.3 where parallel.c was
added.  It's sufficiently invasive that it'll pose a hazard for future
back-patching if we don't.

Discussion: <25239.1464386067@sss.pgh.pa.us>
parent 807b4537
...@@ -2,20 +2,61 @@ ...@@ -2,20 +2,61 @@
* *
* parallel.c * parallel.c
* *
* Parallel support for the pg_dump archiver * Parallel support for pg_dump and pg_restore
* *
* Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group * Portions Copyright (c) 1996-2016, PostgreSQL Global Development Group
* Portions Copyright (c) 1994, Regents of the University of California * Portions Copyright (c) 1994, Regents of the University of California
* *
* The author is not responsible for loss or damages that may
* result from its use.
*
* IDENTIFICATION * IDENTIFICATION
* src/bin/pg_dump/parallel.c * src/bin/pg_dump/parallel.c
* *
*------------------------------------------------------------------------- *-------------------------------------------------------------------------
*/ */
/*
* Parallel operation works like this:
*
* The original, master process calls ParallelBackupStart(), which forks off
* the desired number of worker processes, which each enter WaitForCommands().
*
* The master process dispatches an individual work item to one of the worker
* processes in DispatchJobForTocEntry(). That calls
* AH->MasterStartParallelItemPtr, a routine of the output format. This
* function's arguments are the parents archive handle AH (containing the full
* catalog information), the TocEntry that the worker should work on and a
* T_Action value indicating whether this is a backup or a restore task. The
* function simply converts the TocEntry assignment into a command string that
* is then sent over to the worker process. In the simplest case that would be
* something like "DUMP 1234", with 1234 being the TocEntry id.
*
* The worker process receives and decodes the command and passes it to the
* routine pointed to by AH->WorkerJobDumpPtr or AH->WorkerJobRestorePtr,
* which are routines of the current archive format. That routine performs
* the required action (dump or restore) and returns a malloc'd status string.
* The status string is passed back to the master where it is interpreted by
* AH->MasterEndParallelItemPtr, another format-specific routine. That
* function can update state or catalog information on the master's side,
* depending on the reply from the worker process. In the end it returns a
* status code, which is 0 for successful execution.
*
* Remember that we have forked off the workers only after we have read in
* the catalog. That's why our worker processes can also access the catalog
* information. (In the Windows case, the workers are threads in the same
* process. To avoid problems, they work with cloned copies of the Archive
* data structure; see init_spawned_worker_win32().)
*
* In the master process, the workerStatus field for each worker has one of
* the following values:
* WRKR_IDLE: it's waiting for a command
* WRKR_WORKING: it's been sent a command
* WRKR_FINISHED: it's returned a result
* WRKR_TERMINATED: process ended
* The FINISHED state indicates that the worker is idle, but we've not yet
* dealt with the status code it returned from the prior command.
* ReapWorkerStatus() extracts the unhandled command status value and sets
* the workerStatus back to WRKR_IDLE.
*/
#include "postgres_fe.h" #include "postgres_fe.h"
#include "parallel.h" #include "parallel.h"
...@@ -30,15 +71,11 @@ ...@@ -30,15 +71,11 @@
#include <fcntl.h> #include <fcntl.h>
#endif #endif
/* Mnemonic macros for indexing the fd array returned by pipe(2) */
#define PIPE_READ 0 #define PIPE_READ 0
#define PIPE_WRITE 1 #define PIPE_WRITE 1
/* file-scope variables */
#ifdef WIN32 #ifdef WIN32
static unsigned int tMasterThreadId = 0;
static HANDLE termEvent = INVALID_HANDLE_VALUE;
static int pgpipe(int handles[2]);
static int piperead(int s, char *buf, int len);
/* /*
* Structure to hold info passed by _beginthreadex() to the function it calls * Structure to hold info passed by _beginthreadex() to the function it calls
...@@ -47,25 +84,34 @@ static int piperead(int s, char *buf, int len); ...@@ -47,25 +84,34 @@ static int piperead(int s, char *buf, int len);
typedef struct typedef struct
{ {
ArchiveHandle *AH; ArchiveHandle *AH;
int worker;
int pipeRead; int pipeRead;
int pipeWrite; int pipeWrite;
} WorkerInfo; } WorkerInfo;
/* Windows implementation of pipe access */
static int pgpipe(int handles[2]);
static int piperead(int s, char *buf, int len);
#define pipewrite(a,b,c) send(a,b,c,0) #define pipewrite(a,b,c) send(a,b,c,0)
#else
#else /* !WIN32 */
/* /*
* aborting is only ever used in the master, the workers are fine with just * Variables for handling signals. aborting is only ever used in the master,
* wantAbort. * the workers just need wantAbort.
*/ */
static bool aborting = false; static bool aborting = false;
static volatile sig_atomic_t wantAbort = 0; static volatile sig_atomic_t wantAbort = 0;
/* Non-Windows implementation of pipe access */
#define pgpipe(a) pipe(a) #define pgpipe(a) pipe(a)
#define piperead(a,b,c) read(a,b,c) #define piperead(a,b,c) read(a,b,c)
#define pipewrite(a,b,c) write(a,b,c) #define pipewrite(a,b,c) write(a,b,c)
#endif
#endif /* WIN32 */
/*
* State info for archive_close_connection() shutdown callback.
*/
typedef struct ShutdownInformation typedef struct ShutdownInformation
{ {
ParallelState *pstate; ParallelState *pstate;
...@@ -74,20 +120,27 @@ typedef struct ShutdownInformation ...@@ -74,20 +120,27 @@ typedef struct ShutdownInformation
static ShutdownInformation shutdown_info; static ShutdownInformation shutdown_info;
#ifdef WIN32
/* file-scope variables */
static unsigned int tMasterThreadId = 0;
static HANDLE termEvent = INVALID_HANDLE_VALUE;
static DWORD tls_index;
/* globally visible variables (needed by exit_nicely) */
bool parallel_init_done = false;
DWORD mainThreadId;
#endif /* WIN32 */
static const char *modulename = gettext_noop("parallel archiver"); static const char *modulename = gettext_noop("parallel archiver");
/* Local function prototypes */
static ParallelSlot *GetMyPSlot(ParallelState *pstate); static ParallelSlot *GetMyPSlot(ParallelState *pstate);
static void archive_close_connection(int code, void *arg); static void archive_close_connection(int code, void *arg);
static void ShutdownWorkersHard(ParallelState *pstate); static void ShutdownWorkersHard(ParallelState *pstate);
static void WaitForTerminatingWorkers(ParallelState *pstate); static void WaitForTerminatingWorkers(ParallelState *pstate);
static void RunWorker(ArchiveHandle *AH, int pipefd[2]);
#ifndef WIN32
static void sigTermHandler(int signum);
#endif
static void SetupWorker(ArchiveHandle *AH, int pipefd[2], int worker);
static bool HasEveryWorkerTerminated(ParallelState *pstate); static bool HasEveryWorkerTerminated(ParallelState *pstate);
static void lockTableForWorker(ArchiveHandle *AH, TocEntry *te);
static void lockTableNoWait(ArchiveHandle *AH, TocEntry *te);
static void WaitForCommands(ArchiveHandle *AH, int pipefd[2]); static void WaitForCommands(ArchiveHandle *AH, int pipefd[2]);
static char *getMessageFromMaster(int pipefd[2]); static char *getMessageFromMaster(int pipefd[2]);
static void sendMessageToMaster(int pipefd[2], const char *str); static void sendMessageToMaster(int pipefd[2], const char *str);
...@@ -103,14 +156,10 @@ static char *readMessageFromPipe(int fd); ...@@ -103,14 +156,10 @@ static char *readMessageFromPipe(int fd);
#define messageEquals(msg, pattern) \ #define messageEquals(msg, pattern) \
(strcmp(msg, pattern) == 0) (strcmp(msg, pattern) == 0)
#ifdef WIN32
static void shutdown_parallel_dump_utils(int code, void *unused);
bool parallel_init_done = false;
static DWORD tls_index;
DWORD mainThreadId;
#endif
/*
* Shutdown callback to clean up socket access
*/
#ifdef WIN32 #ifdef WIN32
static void static void
shutdown_parallel_dump_utils(int code, void *unused) shutdown_parallel_dump_utils(int code, void *unused)
...@@ -121,6 +170,11 @@ shutdown_parallel_dump_utils(int code, void *unused) ...@@ -121,6 +170,11 @@ shutdown_parallel_dump_utils(int code, void *unused)
} }
#endif #endif
/*
* Initialize parallel dump support --- should be called early in process
* startup. (Currently, this is called whether or not we intend parallel
* activity.)
*/
void void
init_parallel_dump_utils(void) init_parallel_dump_utils(void)
{ {
...@@ -130,32 +184,43 @@ init_parallel_dump_utils(void) ...@@ -130,32 +184,43 @@ init_parallel_dump_utils(void)
WSADATA wsaData; WSADATA wsaData;
int err; int err;
/* Prepare for threaded operation */
tls_index = TlsAlloc(); tls_index = TlsAlloc();
mainThreadId = GetCurrentThreadId(); mainThreadId = GetCurrentThreadId();
/* Initialize socket access */
err = WSAStartup(MAKEWORD(2, 2), &wsaData); err = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (err != 0) if (err != 0)
{ {
fprintf(stderr, _("%s: WSAStartup failed: %d\n"), progname, err); fprintf(stderr, _("%s: WSAStartup failed: %d\n"), progname, err);
exit_nicely(1); exit_nicely(1);
} }
/* ... and arrange to shut it down at exit */
on_exit_nicely(shutdown_parallel_dump_utils, NULL); on_exit_nicely(shutdown_parallel_dump_utils, NULL);
parallel_init_done = true; parallel_init_done = true;
} }
#endif #endif
} }
/*
* Find the ParallelSlot for the current worker process or thread.
*
* Returns NULL if no matching slot is found (this implies we're the master).
*/
static ParallelSlot * static ParallelSlot *
GetMyPSlot(ParallelState *pstate) GetMyPSlot(ParallelState *pstate)
{ {
int i; int i;
for (i = 0; i < pstate->numWorkers; i++) for (i = 0; i < pstate->numWorkers; i++)
{
#ifdef WIN32 #ifdef WIN32
if (pstate->parallelSlot[i].threadId == GetCurrentThreadId()) if (pstate->parallelSlot[i].threadId == GetCurrentThreadId())
#else #else
if (pstate->parallelSlot[i].pid == getpid()) if (pstate->parallelSlot[i].pid == getpid())
#endif #endif
return &(pstate->parallelSlot[i]); return &(pstate->parallelSlot[i]);
}
return NULL; return NULL;
} }
...@@ -163,27 +228,25 @@ GetMyPSlot(ParallelState *pstate) ...@@ -163,27 +228,25 @@ GetMyPSlot(ParallelState *pstate)
/* /*
* A thread-local version of getLocalPQExpBuffer(). * A thread-local version of getLocalPQExpBuffer().
* *
* Non-reentrant but reduces memory leakage. (On Windows the memory leakage * Non-reentrant but reduces memory leakage: we'll consume one buffer per
* will be one buffer per thread, which is at least better than one per call). * thread, which is much better than one per fmtId/fmtQualifiedId call.
*/ */
#ifdef WIN32
static PQExpBuffer static PQExpBuffer
getThreadLocalPQExpBuffer(void) getThreadLocalPQExpBuffer(void)
{ {
/* /*
* The Tls code goes awry if we use a static var, so we provide for both * The Tls code goes awry if we use a static var, so we provide for both
* static and auto, and omit any use of the static var when using Tls. * static and auto, and omit any use of the static var when using Tls. We
* rely on TlsGetValue() to return 0 if the value is not yet set.
*/ */
static PQExpBuffer s_id_return = NULL; static PQExpBuffer s_id_return = NULL;
PQExpBuffer id_return; PQExpBuffer id_return;
#ifdef WIN32
if (parallel_init_done) if (parallel_init_done)
id_return = (PQExpBuffer) TlsGetValue(tls_index); /* 0 when not set */ id_return = (PQExpBuffer) TlsGetValue(tls_index);
else else
id_return = s_id_return; id_return = s_id_return;
#else
id_return = s_id_return;
#endif
if (id_return) /* first time through? */ if (id_return) /* first time through? */
{ {
...@@ -194,24 +257,19 @@ getThreadLocalPQExpBuffer(void) ...@@ -194,24 +257,19 @@ getThreadLocalPQExpBuffer(void)
{ {
/* new buffer */ /* new buffer */
id_return = createPQExpBuffer(); id_return = createPQExpBuffer();
#ifdef WIN32
if (parallel_init_done) if (parallel_init_done)
TlsSetValue(tls_index, id_return); TlsSetValue(tls_index, id_return);
else else
s_id_return = id_return; s_id_return = id_return;
#else
s_id_return = id_return;
#endif
} }
return id_return; return id_return;
} }
#endif /* WIN32 */
/* /*
* pg_dump and pg_restore register the Archive pointer for the exit handler * pg_dump and pg_restore call this to register the cleanup handler
* (called from exit_nicely). This function mainly exists so that we can * as soon as they've created the ArchiveHandle.
* keep shutdown_info in file scope only.
*/ */
void void
on_exit_close_archive(Archive *AHX) on_exit_close_archive(Archive *AHX)
...@@ -281,12 +339,19 @@ archive_close_connection(int code, void *arg) ...@@ -281,12 +339,19 @@ archive_close_connection(int code, void *arg)
} }
/* /*
* Check to see if we've been told to abort, and exit the process/thread if
* so. We don't print any error message; that would just clutter the screen.
*
* If we have one worker that terminates for some reason, we'd like the other * If we have one worker that terminates for some reason, we'd like the other
* threads to terminate as well (and not finish with their 70 GB table dump * threads to terminate as well (and not finish with their 70 GB table dump
* first...). Now in UNIX we can just kill these processes, and let the signal * first...). In Unix, the master sends SIGTERM and the worker's signal
* handler set wantAbort to 1. In Windows we set a termEvent and this serves * handler sets wantAbort to 1. In Windows we set a termEvent and this serves
* as the signal for everyone to terminate. We don't print any error message, * as the signal for worker threads to exit. Note that while we check this
* that would just clutter the screen. * fairly frequently during data transfers, an idle worker doesn't come here
* at all, so additional measures are needed to force shutdown.
*
* XXX in parallel restore, slow server-side operations like CREATE INDEX
* are not interrupted by anything we do here. This needs more work.
*/ */
void void
checkAborting(ArchiveHandle *AH) checkAborting(ArchiveHandle *AH)
...@@ -300,7 +365,7 @@ checkAborting(ArchiveHandle *AH) ...@@ -300,7 +365,7 @@ checkAborting(ArchiveHandle *AH)
} }
/* /*
* Shut down any remaining workers, waiting for them to finish. * Forcibly shut down any remaining workers, waiting for them to finish.
*/ */
static void static void
ShutdownWorkersHard(ParallelState *pstate) ShutdownWorkersHard(ParallelState *pstate)
...@@ -392,22 +457,24 @@ WaitForTerminatingWorkers(ParallelState *pstate) ...@@ -392,22 +457,24 @@ WaitForTerminatingWorkers(ParallelState *pstate)
} }
} }
/*
* Signal handler (Unix only)
*/
#ifndef WIN32 #ifndef WIN32
/* Signal handling (UNIX only) */
static void static void
sigTermHandler(int signum) sigTermHandler(SIGNAL_ARGS)
{ {
wantAbort = 1; wantAbort = 1;
} }
#endif #endif
/* /*
* This function is called by both UNIX and Windows variants to set up * This function is called by both Unix and Windows variants to set up
* and run a worker process. Caller should exit the process (or thread) * and run a worker process. Caller should exit the process (or thread)
* upon return. * upon return.
*/ */
static void static void
SetupWorker(ArchiveHandle *AH, int pipefd[2], int worker) RunWorker(ArchiveHandle *AH, int pipefd[2])
{ {
/* /*
* Call the setup worker function that's defined in the ArchiveHandle. * Call the setup worker function that's defined in the ArchiveHandle.
...@@ -416,32 +483,44 @@ SetupWorker(ArchiveHandle *AH, int pipefd[2], int worker) ...@@ -416,32 +483,44 @@ SetupWorker(ArchiveHandle *AH, int pipefd[2], int worker)
Assert(AH->connection != NULL); Assert(AH->connection != NULL);
/*
* Execute commands until done.
*/
WaitForCommands(AH, pipefd); WaitForCommands(AH, pipefd);
} }
/*
* Thread base function for Windows
*/
#ifdef WIN32 #ifdef WIN32
static unsigned __stdcall static unsigned __stdcall
init_spawned_worker_win32(WorkerInfo *wi) init_spawned_worker_win32(WorkerInfo *wi)
{ {
ArchiveHandle *AH; ArchiveHandle *AH;
int pipefd[2] = {wi->pipeRead, wi->pipeWrite}; int pipefd[2] = {wi->pipeRead, wi->pipeWrite};
int worker = wi->worker;
/*
* Clone the archive so that we have our own state to work with, and in
* particular our own database connection.
*/
AH = CloneArchive(wi->AH); AH = CloneArchive(wi->AH);
free(wi); free(wi);
SetupWorker(AH, pipefd, worker);
/* Run the worker ... */
RunWorker(AH, pipefd);
/* Clean up and exit the thread */
DeCloneArchive(AH); DeCloneArchive(AH);
_endthreadex(0); _endthreadex(0);
return 0; return 0;
} }
#endif #endif /* WIN32 */
/* /*
* This function starts the parallel dump or restore by spawning off the * This function starts a parallel dump or restore by spawning off the worker
* worker processes in both Unix and Windows. For Windows, it creates a number * processes. For Windows, it creates a number of threads; on Unix the
* of threads while it does a fork() on Unix. * workers are created with fork().
*/ */
ParallelState * ParallelState *
ParallelBackupStart(ArchiveHandle *AH) ParallelBackupStart(ArchiveHandle *AH)
...@@ -471,17 +550,21 @@ ParallelBackupStart(ArchiveHandle *AH) ...@@ -471,17 +550,21 @@ ParallelBackupStart(ArchiveHandle *AH)
* set and falls back to AHX otherwise. * set and falls back to AHX otherwise.
*/ */
shutdown_info.pstate = pstate; shutdown_info.pstate = pstate;
getLocalPQExpBuffer = getThreadLocalPQExpBuffer;
#ifdef WIN32 #ifdef WIN32
/* Set up thread management state */
tMasterThreadId = GetCurrentThreadId(); tMasterThreadId = GetCurrentThreadId();
termEvent = CreateEvent(NULL, true, false, "Terminate"); termEvent = CreateEvent(NULL, true, false, "Terminate");
/* Make fmtId() and fmtQualifiedId() use thread-local storage */
getLocalPQExpBuffer = getThreadLocalPQExpBuffer;
#else #else
/* Set up signal handling state */
signal(SIGTERM, sigTermHandler); signal(SIGTERM, sigTermHandler);
signal(SIGINT, sigTermHandler); signal(SIGINT, sigTermHandler);
signal(SIGQUIT, sigTermHandler); signal(SIGQUIT, sigTermHandler);
#endif #endif
/* Create desired number of workers */
for (i = 0; i < pstate->numWorkers; i++) for (i = 0; i < pstate->numWorkers; i++)
{ {
#ifdef WIN32 #ifdef WIN32
...@@ -493,6 +576,7 @@ ParallelBackupStart(ArchiveHandle *AH) ...@@ -493,6 +576,7 @@ ParallelBackupStart(ArchiveHandle *AH)
int pipeMW[2], int pipeMW[2],
pipeWM[2]; pipeWM[2];
/* Create communication pipes for this worker */
if (pgpipe(pipeMW) < 0 || pgpipe(pipeWM) < 0) if (pgpipe(pipeMW) < 0 || pgpipe(pipeWM) < 0)
exit_horribly(modulename, exit_horribly(modulename,
"could not create communication channels: %s\n", "could not create communication channels: %s\n",
...@@ -511,10 +595,9 @@ ParallelBackupStart(ArchiveHandle *AH) ...@@ -511,10 +595,9 @@ ParallelBackupStart(ArchiveHandle *AH)
pstate->parallelSlot[i].pipeRevWrite = pipeWM[PIPE_WRITE]; pstate->parallelSlot[i].pipeRevWrite = pipeWM[PIPE_WRITE];
#ifdef WIN32 #ifdef WIN32
/* Allocate a new structure for every worker */ /* Create transient structure to pass args to worker function */
wi = (WorkerInfo *) pg_malloc(sizeof(WorkerInfo)); wi = (WorkerInfo *) pg_malloc(sizeof(WorkerInfo));
wi->worker = i;
wi->AH = AH; wi->AH = AH;
wi->pipeRead = pipeMW[PIPE_READ]; wi->pipeRead = pipeMW[PIPE_READ];
wi->pipeWrite = pipeWM[PIPE_WRITE]; wi->pipeWrite = pipeWM[PIPE_WRITE];
...@@ -522,7 +605,7 @@ ParallelBackupStart(ArchiveHandle *AH) ...@@ -522,7 +605,7 @@ ParallelBackupStart(ArchiveHandle *AH)
handle = _beginthreadex(NULL, 0, (void *) &init_spawned_worker_win32, handle = _beginthreadex(NULL, 0, (void *) &init_spawned_worker_win32,
wi, 0, &(pstate->parallelSlot[i].threadId)); wi, 0, &(pstate->parallelSlot[i].threadId));
pstate->parallelSlot[i].hThread = handle; pstate->parallelSlot[i].hThread = handle;
#else #else /* !WIN32 */
pid = fork(); pid = fork();
if (pid == 0) if (pid == 0)
{ {
...@@ -535,15 +618,6 @@ ParallelBackupStart(ArchiveHandle *AH) ...@@ -535,15 +618,6 @@ ParallelBackupStart(ArchiveHandle *AH)
pstate->parallelSlot[i].pid = getpid(); pstate->parallelSlot[i].pid = getpid();
/*
* Call CloneArchive on Unix as well even though technically we
* don't need to because fork() gives us a copy in our own address
* space already. But CloneArchive resets the state information
* and also clones the database connection (for parallel dump)
* which both seem kinda helpful.
*/
pstate->parallelSlot[i].args->AH = CloneArchive(AH);
/* close read end of Worker -> Master */ /* close read end of Worker -> Master */
closesocket(pipeWM[PIPE_READ]); closesocket(pipeWM[PIPE_READ]);
/* close write end of Master -> Worker */ /* close write end of Master -> Worker */
...@@ -559,31 +633,43 @@ ParallelBackupStart(ArchiveHandle *AH) ...@@ -559,31 +633,43 @@ ParallelBackupStart(ArchiveHandle *AH)
closesocket(pstate->parallelSlot[j].pipeWrite); closesocket(pstate->parallelSlot[j].pipeWrite);
} }
SetupWorker(pstate->parallelSlot[i].args->AH, pipefd, i); /*
* Call CloneArchive on Unix as well as Windows, even though
* technically we don't need to because fork() gives us a copy in
* our own address space already. But CloneArchive resets the
* state information and also clones the database connection which
* both seem kinda helpful.
*/
pstate->parallelSlot[i].args->AH = CloneArchive(AH);
/* Run the worker ... */
RunWorker(pstate->parallelSlot[i].args->AH, pipefd);
/* We can just exit(0) when done */
exit(0); exit(0);
} }
else if (pid < 0) else if (pid < 0)
{
/* fork failed */ /* fork failed */
exit_horribly(modulename, exit_horribly(modulename,
"could not create worker process: %s\n", "could not create worker process: %s\n",
strerror(errno)); strerror(errno));
}
/* we are the Master, pid > 0 here */ /* In Master after successful fork */
Assert(pid > 0); pstate->parallelSlot[i].pid = pid;
/* close read end of Master -> Worker */ /* close read end of Master -> Worker */
closesocket(pipeMW[PIPE_READ]); closesocket(pipeMW[PIPE_READ]);
/* close write end of Worker -> Master */ /* close write end of Worker -> Master */
closesocket(pipeWM[PIPE_WRITE]); closesocket(pipeWM[PIPE_WRITE]);
#endif /* WIN32 */
pstate->parallelSlot[i].pid = pid;
#endif
} }
/* /*
* Having forked off the workers, disable SIGPIPE so that master isn't * Having forked off the workers, disable SIGPIPE so that master isn't
* killed if it tries to send a command to a dead worker. * killed if it tries to send a command to a dead worker. We don't want
* the workers to inherit this setting, though.
*/ */
#ifndef WIN32 #ifndef WIN32
signal(SIGPIPE, SIG_IGN); signal(SIGPIPE, SIG_IGN);
...@@ -593,99 +679,45 @@ ParallelBackupStart(ArchiveHandle *AH) ...@@ -593,99 +679,45 @@ ParallelBackupStart(ArchiveHandle *AH)
} }
/* /*
* Tell all of our workers to terminate. * Close down a parallel dump or restore.
*
* Pretty straightforward routine, first we tell everyone to terminate, then
* we listen to the workers' replies and finally close the sockets that we
* have used for communication.
*/ */
void void
ParallelBackupEnd(ArchiveHandle *AH, ParallelState *pstate) ParallelBackupEnd(ArchiveHandle *AH, ParallelState *pstate)
{ {
int i; int i;
/* No work if non-parallel */
if (pstate->numWorkers == 1) if (pstate->numWorkers == 1)
return; return;
/* There should not be any unfinished jobs */
Assert(IsEveryWorkerIdle(pstate)); Assert(IsEveryWorkerIdle(pstate));
/* close the sockets so that the workers know they can exit */ /* Close the sockets so that the workers know they can exit */
for (i = 0; i < pstate->numWorkers; i++) for (i = 0; i < pstate->numWorkers; i++)
{ {
closesocket(pstate->parallelSlot[i].pipeRead); closesocket(pstate->parallelSlot[i].pipeRead);
closesocket(pstate->parallelSlot[i].pipeWrite); closesocket(pstate->parallelSlot[i].pipeWrite);
} }
/* Wait for them to exit */
WaitForTerminatingWorkers(pstate); WaitForTerminatingWorkers(pstate);
/* /*
* Remove the pstate again, so the exit handler in the parent will now * Unlink pstate from shutdown_info, so the exit handler will again fall
* again fall back to closing AH->connection (if connected). * back to closing AH->connection (if connected).
*/ */
shutdown_info.pstate = NULL; shutdown_info.pstate = NULL;
/* Release state (mere neatnik-ism, since we're about to terminate) */
free(pstate->parallelSlot); free(pstate->parallelSlot);
free(pstate); free(pstate);
} }
/* /*
* The sequence is the following (for dump, similar for restore): * Dispatch a job to some free worker (caller must ensure there is one!)
*
* The master process starts the parallel backup in ParllelBackupStart, this
* forks the worker processes which enter WaitForCommand().
*
* The master process dispatches an individual work item to one of the worker
* processes in DispatchJobForTocEntry(). It calls
* AH->MasterStartParallelItemPtr, a routine of the output format. This
* function's arguments are the parents archive handle AH (containing the full
* catalog information), the TocEntry that the worker should work on and a
* T_Action act indicating whether this is a backup or a restore item. The
* function then converts the TocEntry assignment into a string that is then
* sent over to the worker process. In the simplest case that would be
* something like "DUMP 1234", with 1234 being the TocEntry id.
*
* The worker receives the message in the routine pointed to by
* WorkerJobDumpPtr or WorkerJobRestorePtr. These are also pointers to
* corresponding routines of the respective output format, e.g.
* _WorkerJobDumpDirectory().
*
* Remember that we have forked off the workers only after we have read in the
* catalog. That's why our worker processes can also access the catalog
* information. Now they re-translate the textual representation to a TocEntry
* on their side and do the required action (restore or dump).
*
* The result is again a textual string that is sent back to the master and is
* interpreted by AH->MasterEndParallelItemPtr. This function can update state
* or catalog information on the master's side, depending on the reply from
* the worker process. In the end it returns status which is 0 for successful
* execution.
*
* ---------------------------------------------------------------------
* Master Worker
*
* enters WaitForCommands()
* DispatchJobForTocEntry(...te...)
*
* [ Worker is IDLE ]
*
* arg = (MasterStartParallelItemPtr)()
* send: DUMP arg
* receive: DUMP arg
* str = (WorkerJobDumpPtr)(arg)
* [ Worker is WORKING ] ... gets te from arg ...
* ... dump te ...
* send: OK DUMP info
*
* In ListenToWorkers():
*
* [ Worker is FINISHED ]
* receive: OK DUMP info
* status = (MasterEndParallelItemPtr)(info)
* *
* In ReapWorkerStatus(&ptr): * te is the TocEntry to be processed, act is the action to be taken on it.
* *ptr = status;
* [ Worker is IDLE ]
* ---------------------------------------------------------------------
*/ */
void void
DispatchJobForTocEntry(ArchiveHandle *AH, ParallelState *pstate, TocEntry *te, DispatchJobForTocEntry(ArchiveHandle *AH, ParallelState *pstate, TocEntry *te,
...@@ -695,20 +727,24 @@ DispatchJobForTocEntry(ArchiveHandle *AH, ParallelState *pstate, TocEntry *te, ...@@ -695,20 +727,24 @@ DispatchJobForTocEntry(ArchiveHandle *AH, ParallelState *pstate, TocEntry *te,
char *arg; char *arg;
/* our caller makes sure that at least one worker is idle */ /* our caller makes sure that at least one worker is idle */
Assert(GetIdleWorker(pstate) != NO_SLOT);
worker = GetIdleWorker(pstate); worker = GetIdleWorker(pstate);
Assert(worker != NO_SLOT); Assert(worker != NO_SLOT);
/* Construct and send command string */
arg = (AH->MasterStartParallelItemPtr) (AH, te, act); arg = (AH->MasterStartParallelItemPtr) (AH, te, act);
sendMessageToWorker(pstate, worker, arg); sendMessageToWorker(pstate, worker, arg);
/* XXX aren't we leaking string here? (no, because it's static. Ick.) */
/* Remember worker is busy, and which TocEntry it's working on */
pstate->parallelSlot[worker].workerStatus = WRKR_WORKING; pstate->parallelSlot[worker].workerStatus = WRKR_WORKING;
pstate->parallelSlot[worker].args->te = te; pstate->parallelSlot[worker].args->te = te;
} }
/* /*
* Find the first free parallel slot (if any). * Find an idle worker and return its slot number.
* Return NO_SLOT if none are idle.
*/ */
int int
GetIdleWorker(ParallelState *pstate) GetIdleWorker(ParallelState *pstate)
...@@ -716,13 +752,15 @@ GetIdleWorker(ParallelState *pstate) ...@@ -716,13 +752,15 @@ GetIdleWorker(ParallelState *pstate)
int i; int i;
for (i = 0; i < pstate->numWorkers; i++) for (i = 0; i < pstate->numWorkers; i++)
{
if (pstate->parallelSlot[i].workerStatus == WRKR_IDLE) if (pstate->parallelSlot[i].workerStatus == WRKR_IDLE)
return i; return i;
}
return NO_SLOT; return NO_SLOT;
} }
/* /*
* Return true iff every worker process is in the WRKR_TERMINATED state. * Return true iff every worker is in the WRKR_TERMINATED state.
*/ */
static bool static bool
HasEveryWorkerTerminated(ParallelState *pstate) HasEveryWorkerTerminated(ParallelState *pstate)
...@@ -730,8 +768,10 @@ HasEveryWorkerTerminated(ParallelState *pstate) ...@@ -730,8 +768,10 @@ HasEveryWorkerTerminated(ParallelState *pstate)
int i; int i;
for (i = 0; i < pstate->numWorkers; i++) for (i = 0; i < pstate->numWorkers; i++)
{
if (pstate->parallelSlot[i].workerStatus != WRKR_TERMINATED) if (pstate->parallelSlot[i].workerStatus != WRKR_TERMINATED)
return false; return false;
}
return true; return true;
} }
...@@ -744,39 +784,51 @@ IsEveryWorkerIdle(ParallelState *pstate) ...@@ -744,39 +784,51 @@ IsEveryWorkerIdle(ParallelState *pstate)
int i; int i;
for (i = 0; i < pstate->numWorkers; i++) for (i = 0; i < pstate->numWorkers; i++)
{
if (pstate->parallelSlot[i].workerStatus != WRKR_IDLE) if (pstate->parallelSlot[i].workerStatus != WRKR_IDLE)
return false; return false;
}
return true; return true;
} }
/* /*
* --------------------------------------------------------------------- * Acquire lock on a table to be dumped by a worker process.
* One danger of the parallel backup is a possible deadlock: *
* The master process is already holding an ACCESS SHARE lock. Ordinarily
* it's no problem for a worker to get one too, but if anything else besides
* pg_dump is running, there's a possible deadlock:
* *
* 1) Master dumps the schema and locks all tables in ACCESS SHARE mode. * 1) Master dumps the schema and locks all tables in ACCESS SHARE mode.
* 2) Another process requests an ACCESS EXCLUSIVE lock (which is not granted * 2) Another process requests an ACCESS EXCLUSIVE lock (which is not granted
* because the master holds a conflicting ACCESS SHARE lock). * because the master holds a conflicting ACCESS SHARE lock).
* 3) The worker process also requests an ACCESS SHARE lock to read the table. * 3) A worker process also requests an ACCESS SHARE lock to read the table.
* The worker's not granted that lock but is enqueued behind the ACCESS * The worker is enqueued behind the ACCESS EXCLUSIVE lock request.
* EXCLUSIVE lock request. * 4) Now we have a deadlock, since the master is effectively waiting for
* --------------------------------------------------------------------- * the worker. The server cannot detect that, however.
* *
* Now what we do here is to just request a lock in ACCESS SHARE but with * To prevent an infinite wait, prior to touching a table in a worker, request
* NOWAIT in the worker prior to touching the table. If we don't get the lock, * a lock in ACCESS SHARE mode but with NOWAIT. If we don't get the lock,
* then we know that somebody else has requested an ACCESS EXCLUSIVE lock and * then we know that somebody else has requested an ACCESS EXCLUSIVE lock and
* are good to just fail the whole backup because we have detected a deadlock. * so we have a deadlock. We must fail the backup in that case.
*/ */
static void static void
lockTableNoWait(ArchiveHandle *AH, TocEntry *te) lockTableForWorker(ArchiveHandle *AH, TocEntry *te)
{ {
Archive *AHX = (Archive *) AH; Archive *AHX = (Archive *) AH;
const char *qualId; const char *qualId;
PQExpBuffer query = createPQExpBuffer(); PQExpBuffer query;
PGresult *res; PGresult *res;
Assert(AH->format == archDirectory); /* Nothing to do for BLOBS */
Assert(strcmp(te->desc, "BLOBS") != 0); if (strcmp(te->desc, "BLOBS") == 0)
return;
query = createPQExpBuffer();
/*
* XXX this is an unbelievably expensive substitute for knowing how to dig
* a table name out of a TocEntry.
*/
appendPQExpBuffer(query, appendPQExpBuffer(query,
"SELECT pg_namespace.nspname," "SELECT pg_namespace.nspname,"
" pg_class.relname " " pg_class.relname "
...@@ -815,11 +867,9 @@ lockTableNoWait(ArchiveHandle *AH, TocEntry *te) ...@@ -815,11 +867,9 @@ lockTableNoWait(ArchiveHandle *AH, TocEntry *te)
} }
/* /*
* That's the main routine for the worker. * WaitForCommands: main routine for a worker process.
* When it starts up it enters this routine and waits for commands from the *
* master process. After having processed a command it comes back to here to * Read and execute commands from the master until we see EOF on the pipe.
* wait for the next command. Finally it will receive a TERMINATE command and
* exit.
*/ */
static void static void
WaitForCommands(ArchiveHandle *AH, int pipefd[2]) WaitForCommands(ArchiveHandle *AH, int pipefd[2])
...@@ -827,13 +877,14 @@ WaitForCommands(ArchiveHandle *AH, int pipefd[2]) ...@@ -827,13 +877,14 @@ WaitForCommands(ArchiveHandle *AH, int pipefd[2])
char *command; char *command;
DumpId dumpId; DumpId dumpId;
int nBytes; int nBytes;
char *str = NULL; char *str;
TocEntry *te; TocEntry *te;
for (;;) for (;;)
{ {
if (!(command = getMessageFromMaster(pipefd))) if (!(command = getMessageFromMaster(pipefd)))
{ {
/* EOF ... clean up */
PQfinish(AH->connection); PQfinish(AH->connection);
AH->connection = NULL; AH->connection = NULL;
return; return;
...@@ -841,55 +892,44 @@ WaitForCommands(ArchiveHandle *AH, int pipefd[2]) ...@@ -841,55 +892,44 @@ WaitForCommands(ArchiveHandle *AH, int pipefd[2])
if (messageStartsWith(command, "DUMP ")) if (messageStartsWith(command, "DUMP "))
{ {
Assert(AH->format == archDirectory); /* Decode the command */
sscanf(command + strlen("DUMP "), "%d%n", &dumpId, &nBytes); sscanf(command + strlen("DUMP "), "%d%n", &dumpId, &nBytes);
Assert(nBytes == strlen(command) - strlen("DUMP ")); Assert(nBytes == strlen(command) - strlen("DUMP "));
te = getTocEntryByDumpId(AH, dumpId); te = getTocEntryByDumpId(AH, dumpId);
Assert(te != NULL); Assert(te != NULL);
/* /* Acquire lock on this table within the worker's session */
* Lock the table but with NOWAIT. Note that the parent is already lockTableForWorker(AH, te);
* holding a lock. If we cannot acquire another ACCESS SHARE MODE
* lock, then somebody else has requested an exclusive lock in the
* meantime. lockTableNoWait dies in this case to prevent a
* deadlock.
*/
if (strcmp(te->desc, "BLOBS") != 0)
lockTableNoWait(AH, te);
/* /* Perform the dump command */
* The message we return here has been pg_malloc()ed and we are
* responsible for free()ing it.
*/
str = (AH->WorkerJobDumpPtr) (AH, te); str = (AH->WorkerJobDumpPtr) (AH, te);
Assert(AH->connection != NULL);
/* Return status to master */
sendMessageToMaster(pipefd, str); sendMessageToMaster(pipefd, str);
/* we are responsible for freeing the status string */
free(str); free(str);
} }
else if (messageStartsWith(command, "RESTORE ")) else if (messageStartsWith(command, "RESTORE "))
{ {
Assert(AH->format == archDirectory || AH->format == archCustom); /* Decode the command */
Assert(AH->connection != NULL);
sscanf(command + strlen("RESTORE "), "%d%n", &dumpId, &nBytes); sscanf(command + strlen("RESTORE "), "%d%n", &dumpId, &nBytes);
Assert(nBytes == strlen(command) - strlen("RESTORE ")); Assert(nBytes == strlen(command) - strlen("RESTORE "));
te = getTocEntryByDumpId(AH, dumpId); te = getTocEntryByDumpId(AH, dumpId);
Assert(te != NULL); Assert(te != NULL);
/* /* Perform the restore command */
* The message we return here has been pg_malloc()ed and we are
* responsible for free()ing it.
*/
str = (AH->WorkerJobRestorePtr) (AH, te); str = (AH->WorkerJobRestorePtr) (AH, te);
Assert(AH->connection != NULL);
/* Return status to master */
sendMessageToMaster(pipefd, str); sendMessageToMaster(pipefd, str);
/* we are responsible for freeing the status string */
free(str); free(str);
} }
else else
exit_horribly(modulename, exit_horribly(modulename,
"unrecognized command on communication channel: %s\n", "unrecognized command received from master: \"%s\"\n",
command); command);
/* command was pg_malloc'd and we are responsible for free()ing it. */ /* command was pg_malloc'd and we are responsible for free()ing it. */
...@@ -898,18 +938,21 @@ WaitForCommands(ArchiveHandle *AH, int pipefd[2]) ...@@ -898,18 +938,21 @@ WaitForCommands(ArchiveHandle *AH, int pipefd[2])
} }
/* /*
* --------------------------------------------------------------------- * Check for status messages from workers.
* Note the status change:
* *
* DispatchJobForTocEntry WRKR_IDLE -> WRKR_WORKING * If do_wait is true, wait to get a status message; otherwise, just return
* ListenToWorkers WRKR_WORKING -> WRKR_FINISHED / WRKR_TERMINATED * immediately if there is none available.
* ReapWorkerStatus WRKR_FINISHED -> WRKR_IDLE
* ---------------------------------------------------------------------
* *
* Just calling ReapWorkerStatus() when all workers are working might or might * When we get a status message, we let MasterEndParallelItemPtr process it,
* not give you an idle worker because you need to call ListenToWorkers() in * then save the resulting status code and switch the worker's state to
* between and only thereafter ReapWorkerStatus(). This is necessary in order * WRKR_FINISHED. Later, caller must call ReapWorkerStatus() to verify
* to get and deal with the status (=result) of the worker's execution. * that the status was "OK" and push the worker back to IDLE state.
*
* XXX Rube Goldberg would be proud of this API, but no one else should be.
*
* XXX is it worth checking for more than one status message per call?
* It seems somewhat unlikely that multiple workers would finish at exactly
* the same time.
*/ */
void void
ListenToWorkers(ArchiveHandle *AH, ParallelState *pstate, bool do_wait) ListenToWorkers(ArchiveHandle *AH, ParallelState *pstate, bool do_wait)
...@@ -917,22 +960,23 @@ ListenToWorkers(ArchiveHandle *AH, ParallelState *pstate, bool do_wait) ...@@ -917,22 +960,23 @@ ListenToWorkers(ArchiveHandle *AH, ParallelState *pstate, bool do_wait)
int worker; int worker;
char *msg; char *msg;
/* Try to collect a status message */
msg = getMessageFromWorker(pstate, do_wait, &worker); msg = getMessageFromWorker(pstate, do_wait, &worker);
if (!msg) if (!msg)
{ {
/* If do_wait is true, we must have detected EOF on some socket */
if (do_wait) if (do_wait)
exit_horribly(modulename, "a worker process died unexpectedly\n"); exit_horribly(modulename, "a worker process died unexpectedly\n");
return; return;
} }
/* Process it and update our idea of the worker's status */
if (messageStartsWith(msg, "OK ")) if (messageStartsWith(msg, "OK "))
{ {
TocEntry *te = pstate->parallelSlot[worker].args->te;
char *statusString; char *statusString;
TocEntry *te;
pstate->parallelSlot[worker].workerStatus = WRKR_FINISHED;
te = pstate->parallelSlot[worker].args->te;
if (messageStartsWith(msg, "OK RESTORE ")) if (messageStartsWith(msg, "OK RESTORE "))
{ {
statusString = msg + strlen("OK RESTORE "); statusString = msg + strlen("OK RESTORE ");
...@@ -951,22 +995,23 @@ ListenToWorkers(ArchiveHandle *AH, ParallelState *pstate, bool do_wait) ...@@ -951,22 +995,23 @@ ListenToWorkers(ArchiveHandle *AH, ParallelState *pstate, bool do_wait)
exit_horribly(modulename, exit_horribly(modulename,
"invalid message received from worker: \"%s\"\n", "invalid message received from worker: \"%s\"\n",
msg); msg);
pstate->parallelSlot[worker].workerStatus = WRKR_FINISHED;
} }
else else
exit_horribly(modulename, exit_horribly(modulename,
"invalid message received from worker: \"%s\"\n", "invalid message received from worker: \"%s\"\n",
msg); msg);
/* both Unix and Win32 return pg_malloc()ed space, so we free it */ /* Free the string returned from getMessageFromWorker */
free(msg); free(msg);
} }
/* /*
* This function is executed in the master process. * Check to see if any worker is in WRKR_FINISHED state. If so,
* return its command status code into *status, reset it to IDLE state,
* and return its slot number. Otherwise return NO_SLOT.
* *
* This function is used to get the return value of a terminated worker * This function is executed in the master process.
* process. If a process has terminated, its status is stored in *status and
* the id of the worker is returned.
*/ */
int int
ReapWorkerStatus(ParallelState *pstate, int *status) ReapWorkerStatus(ParallelState *pstate, int *status)
...@@ -987,9 +1032,16 @@ ReapWorkerStatus(ParallelState *pstate, int *status) ...@@ -987,9 +1032,16 @@ ReapWorkerStatus(ParallelState *pstate, int *status)
} }
/* /*
* This function is executed in the master process. * Wait, if necessary, until we have at least one idle worker.
* Reap worker status as necessary to move FINISHED workers to IDLE state.
*
* We assume that no extra processing is required when reaping a finished
* command, except for checking that the status was OK (zero).
* Caution: that assumption means that this function can only be used in
* parallel dump, not parallel restore, because the latter has a more
* complex set of rules about handling status.
* *
* It looks for an idle worker process and only returns if there is one. * This function is executed in the master process.
*/ */
void void
EnsureIdleWorker(ArchiveHandle *AH, ParallelState *pstate) EnsureIdleWorker(ArchiveHandle *AH, ParallelState *pstate)
...@@ -1029,9 +1081,16 @@ EnsureIdleWorker(ArchiveHandle *AH, ParallelState *pstate) ...@@ -1029,9 +1081,16 @@ EnsureIdleWorker(ArchiveHandle *AH, ParallelState *pstate)
} }
/* /*
* This function is executed in the master process. * Wait for all workers to be idle.
* Reap worker status as necessary to move FINISHED workers to IDLE state.
*
* We assume that no extra processing is required when reaping a finished
* command, except for checking that the status was OK (zero).
* Caution: that assumption means that this function can only be used in
* parallel dump, not parallel restore, because the latter has a more
* complex set of rules about handling status.
* *
* It waits for all workers to terminate. * This function is executed in the master process.
*/ */
void void
EnsureWorkersFinished(ArchiveHandle *AH, ParallelState *pstate) EnsureWorkersFinished(ArchiveHandle *AH, ParallelState *pstate)
...@@ -1053,10 +1112,11 @@ EnsureWorkersFinished(ArchiveHandle *AH, ParallelState *pstate) ...@@ -1053,10 +1112,11 @@ EnsureWorkersFinished(ArchiveHandle *AH, ParallelState *pstate)
} }
/* /*
* This function is executed in the worker process. * Read one command message from the master, blocking if necessary
* until one is available, and return it as a malloc'd string.
* On EOF, return NULL.
* *
* It returns the next message on the communication channel, blocking until it * This function is executed in worker processes.
* becomes available.
*/ */
static char * static char *
getMessageFromMaster(int pipefd[2]) getMessageFromMaster(int pipefd[2])
...@@ -1065,9 +1125,9 @@ getMessageFromMaster(int pipefd[2]) ...@@ -1065,9 +1125,9 @@ getMessageFromMaster(int pipefd[2])
} }
/* /*
* This function is executed in the worker process. * Send a status message to the master.
* *
* It sends a message to the master on the communication channel. * This function is executed in worker processes.
*/ */
static void static void
sendMessageToMaster(int pipefd[2], const char *str) sendMessageToMaster(int pipefd[2], const char *str)
...@@ -1081,9 +1141,8 @@ sendMessageToMaster(int pipefd[2], const char *str) ...@@ -1081,9 +1141,8 @@ sendMessageToMaster(int pipefd[2], const char *str)
} }
/* /*
* A select loop that repeats calling select until a descriptor in the read * Wait until some descriptor in "workerset" becomes readable.
* set becomes readable. On Windows we have to check for the termination event * Returns -1 on error, else the number of readable descriptors.
* from time to time, on Unix we can just block forever.
*/ */
static int static int
select_loop(int maxFd, fd_set *workerset) select_loop(int maxFd, fd_set *workerset)
...@@ -1092,13 +1151,13 @@ select_loop(int maxFd, fd_set *workerset) ...@@ -1092,13 +1151,13 @@ select_loop(int maxFd, fd_set *workerset)
fd_set saveSet = *workerset; fd_set saveSet = *workerset;
#ifdef WIN32 #ifdef WIN32
/* should always be the master */
Assert(tMasterThreadId == GetCurrentThreadId());
for (;;) for (;;)
{ {
/* /*
* sleep a quarter of a second before checking if we should terminate. * Sleep a quarter of a second before checking if we should terminate.
*
* XXX we're not actually checking for a cancel interrupt ... but we
* should be.
*/ */
struct timeval tv = {0, 250000}; struct timeval tv = {0, 250000};
...@@ -1110,8 +1169,7 @@ select_loop(int maxFd, fd_set *workerset) ...@@ -1110,8 +1169,7 @@ select_loop(int maxFd, fd_set *workerset)
if (i) if (i)
break; break;
} }
#else /* UNIX */ #else /* !WIN32 */
for (;;) for (;;)
{ {
*workerset = saveSet; *workerset = saveSet;
...@@ -1131,19 +1189,25 @@ select_loop(int maxFd, fd_set *workerset) ...@@ -1131,19 +1189,25 @@ select_loop(int maxFd, fd_set *workerset)
continue; continue;
break; break;
} }
#endif #endif /* WIN32 */
return i; return i;
} }
/* /*
* This function is executed in the master process. * Check for messages from worker processes.
*
* If a message is available, return it as a malloc'd string, and put the
* index of the sending worker in *worker.
* *
* It returns the next message from the worker on the communication channel, * If nothing is available, wait if "do_wait" is true, else return NULL.
* optionally blocking (do_wait) until it becomes available.
* *
* The id of the worker is returned in *worker. * If we detect EOF on any socket, we'll return NULL. It's not great that
* that's hard to distinguish from the no-data-available case, but for now
* our one caller is okay with that.
*
* This function is executed in the master process.
*/ */
static char * static char *
getMessageFromWorker(ParallelState *pstate, bool do_wait, int *worker) getMessageFromWorker(ParallelState *pstate, bool do_wait, int *worker)
...@@ -1153,14 +1217,13 @@ getMessageFromWorker(ParallelState *pstate, bool do_wait, int *worker) ...@@ -1153,14 +1217,13 @@ getMessageFromWorker(ParallelState *pstate, bool do_wait, int *worker)
int maxFd = -1; int maxFd = -1;
struct timeval nowait = {0, 0}; struct timeval nowait = {0, 0};
/* construct bitmap of socket descriptors for select() */
FD_ZERO(&workerset); FD_ZERO(&workerset);
for (i = 0; i < pstate->numWorkers; i++) for (i = 0; i < pstate->numWorkers; i++)
{ {
if (pstate->parallelSlot[i].workerStatus == WRKR_TERMINATED) if (pstate->parallelSlot[i].workerStatus == WRKR_TERMINATED)
continue; continue;
FD_SET(pstate->parallelSlot[i].pipeRead, &workerset); FD_SET(pstate->parallelSlot[i].pipeRead, &workerset);
/* actually WIN32 ignores the first parameter to select()... */
if (pstate->parallelSlot[i].pipeRead > maxFd) if (pstate->parallelSlot[i].pipeRead > maxFd)
maxFd = pstate->parallelSlot[i].pipeRead; maxFd = pstate->parallelSlot[i].pipeRead;
} }
...@@ -1177,7 +1240,7 @@ getMessageFromWorker(ParallelState *pstate, bool do_wait, int *worker) ...@@ -1177,7 +1240,7 @@ getMessageFromWorker(ParallelState *pstate, bool do_wait, int *worker)
} }
if (i < 0) if (i < 0)
exit_horribly(modulename, "error in ListenToWorkers(): %s\n", strerror(errno)); exit_horribly(modulename, "select() failed: %s\n", strerror(errno));
for (i = 0; i < pstate->numWorkers; i++) for (i = 0; i < pstate->numWorkers; i++)
{ {
...@@ -1186,6 +1249,16 @@ getMessageFromWorker(ParallelState *pstate, bool do_wait, int *worker) ...@@ -1186,6 +1249,16 @@ getMessageFromWorker(ParallelState *pstate, bool do_wait, int *worker)
if (!FD_ISSET(pstate->parallelSlot[i].pipeRead, &workerset)) if (!FD_ISSET(pstate->parallelSlot[i].pipeRead, &workerset))
continue; continue;
/*
* Read the message if any. If the socket is ready because of EOF,
* we'll return NULL instead (and the socket will stay ready, so the
* condition will persist).
*
* Note: because this is a blocking read, we'll wait if only part of
* the message is available. Waiting a long time would be bad, but
* since worker status messages are short and are always sent in one
* operation, it shouldn't be a problem in practice.
*/
msg = readMessageFromPipe(pstate->parallelSlot[i].pipeRead); msg = readMessageFromPipe(pstate->parallelSlot[i].pipeRead);
*worker = i; *worker = i;
return msg; return msg;
...@@ -1195,9 +1268,9 @@ getMessageFromWorker(ParallelState *pstate, bool do_wait, int *worker) ...@@ -1195,9 +1268,9 @@ getMessageFromWorker(ParallelState *pstate, bool do_wait, int *worker)
} }
/* /*
* This function is executed in the master process. * Send a command message to the specified worker process.
* *
* It sends a message to a certain worker on the communication channel. * This function is executed in the master process.
*/ */
static void static void
sendMessageToWorker(ParallelState *pstate, int worker, const char *str) sendMessageToWorker(ParallelState *pstate, int worker, const char *str)
...@@ -1208,7 +1281,8 @@ sendMessageToWorker(ParallelState *pstate, int worker, const char *str) ...@@ -1208,7 +1281,8 @@ sendMessageToWorker(ParallelState *pstate, int worker, const char *str)
{ {
/* /*
* If we're already aborting anyway, don't care if we succeed or not. * If we're already aborting anyway, don't care if we succeed or not.
* The child might have gone already. * The child might have gone already. (XXX but if we're aborting
* already, why are we here at all?)
*/ */
#ifndef WIN32 #ifndef WIN32
if (!aborting) if (!aborting)
...@@ -1220,8 +1294,11 @@ sendMessageToWorker(ParallelState *pstate, int worker, const char *str) ...@@ -1220,8 +1294,11 @@ sendMessageToWorker(ParallelState *pstate, int worker, const char *str)
} }
/* /*
* The underlying function to read a message from the communication channel * Read one message from the specified pipe (fd), blocking if necessary
* (fd) with optional blocking (do_wait). * until one is available, and return it as a malloc'd string.
* On EOF, return NULL.
*
* A "message" on the channel is just a null-terminated string.
*/ */
static char * static char *
readMessageFromPipe(int fd) readMessageFromPipe(int fd)
...@@ -1232,59 +1309,53 @@ readMessageFromPipe(int fd) ...@@ -1232,59 +1309,53 @@ readMessageFromPipe(int fd)
int ret; int ret;
/* /*
* The problem here is that we need to deal with several possibilities: we * In theory, if we let piperead() read multiple bytes, it might give us
* could receive only a partial message or several messages at once. The * back fragments of multiple messages. (That can't actually occur, since
* caller expects us to return exactly one message however. * neither master nor workers send more than one message without waiting
* * for a reply, but we don't wish to assume that here.) For simplicity,
* We could either read in as much as we can and keep track of what we * read a byte at a time until we get the terminating '\0'. This method
* delivered back to the caller or we just read byte by byte. Once we see * is a bit inefficient, but since this is only used for relatively short
* (char) 0, we know that it's the message's end. This would be quite * command and status strings, it shouldn't matter.
* inefficient for more data but since we are reading only on the command
* channel, the performance loss does not seem worth the trouble of
* keeping internal states for different file descriptors.
*/ */
bufsize = 64; /* could be any number */ bufsize = 64; /* could be any number */
msg = (char *) pg_malloc(bufsize); msg = (char *) pg_malloc(bufsize);
msgsize = 0; msgsize = 0;
for (;;) for (;;)
{ {
Assert(msgsize <= bufsize); Assert(msgsize < bufsize);
ret = piperead(fd, msg + msgsize, 1); ret = piperead(fd, msg + msgsize, 1);
/* worker has closed the connection or another error happened */
if (ret <= 0) if (ret <= 0)
break; break; /* error or connection closure */
Assert(ret == 1); Assert(ret == 1);
if (msg[msgsize] == '\0') if (msg[msgsize] == '\0')
return msg; return msg; /* collected whole message */
msgsize++; msgsize++;
if (msgsize == bufsize) if (msgsize == bufsize) /* enlarge buffer if needed */
{ {
/* could be any number */ bufsize += 16; /* could be any number */
bufsize += 16;
msg = (char *) pg_realloc(msg, bufsize); msg = (char *) pg_realloc(msg, bufsize);
} }
} }
/* /* Other end has closed the connection */
* Worker has closed the connection, make sure to clean up before return
* since we are not returning msg (but did allocate it).
*/
pg_free(msg); pg_free(msg);
return NULL; return NULL;
} }
#ifdef WIN32 #ifdef WIN32
/* /*
* This is a replacement version of pipe for Win32 which allows returned * This is a replacement version of pipe(2) for Windows which allows the pipe
* handles to be used in select(). Note that read/write calls must be replaced * handles to be used in select().
* with recv/send. "handles" have to be integers so we check for errors then *
* cast to integers. * Reads and writes on the pipe must go through piperead()/pipewrite().
*
* For consistency with Unix we declare the returned handles as "int".
* This is okay even on WIN64 because system handles are not more than
* 32 bits wide, but we do have to do some casting.
*/ */
static int static int
pgpipe(int handles[2]) pgpipe(int handles[2])
...@@ -1349,6 +1420,8 @@ pgpipe(int handles[2]) ...@@ -1349,6 +1420,8 @@ pgpipe(int handles[2])
{ {
write_msg(modulename, "pgpipe: could not connect socket: error code %d\n", write_msg(modulename, "pgpipe: could not connect socket: error code %d\n",
WSAGetLastError()); WSAGetLastError());
closesocket(handles[1]);
handles[1] = -1;
closesocket(s); closesocket(s);
return -1; return -1;
} }
...@@ -1367,15 +1440,20 @@ pgpipe(int handles[2]) ...@@ -1367,15 +1440,20 @@ pgpipe(int handles[2])
return 0; return 0;
} }
/*
* Windows implementation of reading from a pipe.
*/
static int static int
piperead(int s, char *buf, int len) piperead(int s, char *buf, int len)
{ {
int ret = recv(s, buf, len, 0); int ret = recv(s, buf, len, 0);
if (ret < 0 && WSAGetLastError() == WSAECONNRESET) if (ret < 0 && WSAGetLastError() == WSAECONNRESET)
/* EOF on the pipe! (win32 socket based implementation) */ {
/* EOF on the pipe! */
ret = 0; ret = 0;
}
return ret; return ret;
} }
#endif #endif /* WIN32 */
...@@ -2326,6 +2326,9 @@ _allocAH(const char *FileSpec, const ArchiveFormat fmt, ...@@ -2326,6 +2326,9 @@ _allocAH(const char *FileSpec, const ArchiveFormat fmt,
return AH; return AH;
} }
/*
* Write out all data (tables & blobs)
*/
void void
WriteDataChunks(ArchiveHandle *AH, ParallelState *pstate) WriteDataChunks(ArchiveHandle *AH, ParallelState *pstate)
{ {
...@@ -2343,15 +2346,18 @@ WriteDataChunks(ArchiveHandle *AH, ParallelState *pstate) ...@@ -2343,15 +2346,18 @@ WriteDataChunks(ArchiveHandle *AH, ParallelState *pstate)
{ {
/* /*
* If we are in a parallel backup, then we are always the master * If we are in a parallel backup, then we are always the master
* process. * process. Dispatch each data-transfer job to a worker.
*/ */
EnsureIdleWorker(AH, pstate); EnsureIdleWorker(AH, pstate);
Assert(GetIdleWorker(pstate) != NO_SLOT);
DispatchJobForTocEntry(AH, pstate, te, ACT_DUMP); DispatchJobForTocEntry(AH, pstate, te, ACT_DUMP);
} }
else else
WriteDataChunksForTocEntry(AH, te); WriteDataChunksForTocEntry(AH, te);
} }
/*
* If parallel, wait for workers to finish.
*/
EnsureWorkersFinished(AH, pstate); EnsureWorkersFinished(AH, pstate);
} }
...@@ -3819,13 +3825,11 @@ restore_toc_entries_parallel(ArchiveHandle *AH, ParallelState *pstate, ...@@ -3819,13 +3825,11 @@ restore_toc_entries_parallel(ArchiveHandle *AH, ParallelState *pstate,
par_list_remove(next_work_item); par_list_remove(next_work_item);
Assert(GetIdleWorker(pstate) != NO_SLOT);
DispatchJobForTocEntry(AH, pstate, next_work_item, ACT_RESTORE); DispatchJobForTocEntry(AH, pstate, next_work_item, ACT_RESTORE);
} }
else else
{ {
/* at least one child is working and we have nothing ready. */ /* at least one child is working and we have nothing ready. */
Assert(!IsEveryWorkerIdle(pstate));
} }
for (;;) for (;;)
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment