diff options
author | Heikki Linnakangas <heikki.linnakangas@iki.fi> | 2012-11-27 10:25:50 +0200 |
---|---|---|
committer | Heikki Linnakangas <heikki.linnakangas@iki.fi> | 2012-11-27 10:25:50 +0200 |
commit | 1f67078ea324d492a366a24abb2ac313c629314f (patch) | |
tree | ba8f833b8d1054ec8f687484a744af2f533704a4 /src/backend/storage/file/fd.c | |
parent | 532994299e2ff208a58376134fab75f5ae471e41 (diff) | |
download | postgresql-1f67078ea324d492a366a24abb2ac313c629314f.tar.gz postgresql-1f67078ea324d492a366a24abb2ac313c629314f.zip |
Add OpenTransientFile, with automatic cleanup at end-of-xact.
Files opened with BasicOpenFile or PathNameOpenFile are not automatically
cleaned up on error. That puts unnecessary burden on callers that only want
to keep the file open for a short time. There is AllocateFile, but that
returns a buffered FILE * stream, which in many cases is not the nicest API
to work with. So add function called OpenTransientFile, which returns a
unbuffered fd that's cleaned up like the FILE* returned by AllocateFile().
This plugs a few rare fd leaks in error cases:
1. copy_file() - fixed by by using OpenTransientFile instead of BasicOpenFile
2. XLogFileInit() - fixed by adding close() calls to the error cases. Can't
use OpenTransientFile here because the fd is supposed to persist over
transaction boundaries.
3. lo_import/lo_export - fixed by using OpenTransientFile instead of
PathNameOpenFile.
In addition to plugging those leaks, this replaces many BasicOpenFile() calls
with OpenTransientFile() that were not leaking, because the code meticulously
closed the file on error. That wasn't strictly necessary, but IMHO it's good
for robustness.
The same leaks exist in older versions, but given the rarity of the issues,
I'm not backpatching this. Not yet, anyway - it might be good to backpatch
later, after this mechanism has had some more testing in master branch.
Diffstat (limited to 'src/backend/storage/file/fd.c')
-rw-r--r-- | src/backend/storage/file/fd.c | 125 |
1 files changed, 108 insertions, 17 deletions
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c index ecb62ba01ae..07ee51cf5aa 100644 --- a/src/backend/storage/file/fd.c +++ b/src/backend/storage/file/fd.c @@ -30,11 +30,29 @@ * routines (e.g., open(2) and fopen(3)) themselves. Otherwise, we * may find ourselves short of real file descriptors anyway. * - * This file used to contain a bunch of stuff to support RAID levels 0 - * (jbod), 1 (duplex) and 5 (xor parity). That stuff is all gone - * because the parallel query processing code that called it is all - * gone. If you really need it you could get it from the original - * POSTGRES source. + * INTERFACE ROUTINES + * + * PathNameOpenFile and OpenTemporaryFile are used to open virtual files. + * A File opened with OpenTemporaryFile is automatically deleted when the + * File is closed, either explicitly or implicitly at end of transaction or + * process exit. PathNameOpenFile is intended for files that are held open + * for a long time, like relation files. It is the caller's responsibility + * to close them, there is no automatic mechanism in fd.c for that. + * + * AllocateFile, AllocateDir and OpenTransientFile are wrappers around + * fopen(3), opendir(3), and open(2), respectively. They behave like the + * corresponding native functions, except that the handle is registered with + * the current subtransaction, and will be automatically closed at abort. + * These are intended for short operations like reading a configuration file. + * and there is a fixed limit on the number files that can be open using these + * functions at any one time. + * + * Finally, BasicOpenFile is a just thin wrapper around open() that can + * release file descriptors in use by the virtual file descriptors if + * necessary. There is no automatic cleanup of file descriptors returned by + * BasicOpenFile, it is solely the caller's responsibility to close the file + * descriptor by calling close(2). + * *------------------------------------------------------------------------- */ @@ -94,11 +112,11 @@ int max_files_per_process = 1000; /* * Maximum number of file descriptors to open for either VFD entries or - * AllocateFile/AllocateDir operations. This is initialized to a conservative - * value, and remains that way indefinitely in bootstrap or standalone-backend - * cases. In normal postmaster operation, the postmaster calls - * set_max_safe_fds() late in initialization to update the value, and that - * value is then inherited by forked subprocesses. + * AllocateFile/AllocateDir/OpenTransientFile operations. This is initialized + * to a conservative value, and remains that way indefinitely in bootstrap or + * standalone-backend cases. In normal postmaster operation, the postmaster + * calls set_max_safe_fds() late in initialization to update the value, and + * that value is then inherited by forked subprocesses. * * Note: the value of max_files_per_process is taken into account while * setting this variable, and so need not be tested separately. @@ -171,10 +189,10 @@ static bool have_xact_temporary_files = false; static uint64 temporary_files_size = 0; /* - * List of stdio FILEs and <dirent.h> DIRs opened with AllocateFile - * and AllocateDir. + * List of OS handles opened with AllocateFile, AllocateDir and + * OpenTransientFile. * - * Since we don't want to encourage heavy use of AllocateFile or AllocateDir, + * Since we don't want to encourage heavy use of those functions, * it seems OK to put a pretty small maximum limit on the number of * simultaneously allocated descs. */ @@ -183,7 +201,8 @@ static uint64 temporary_files_size = 0; typedef enum { AllocateDescFile, - AllocateDescDir + AllocateDescDir, + AllocateDescRawFD } AllocateDescKind; typedef struct @@ -193,6 +212,7 @@ typedef struct { FILE *file; DIR *dir; + int fd; } desc; SubTransactionId create_subid; } AllocateDesc; @@ -1523,8 +1543,49 @@ TryAgain: return NULL; } + /* - * Free an AllocateDesc of either type. + * Like AllocateFile, but returns an unbuffered fd like open(2) + */ +int +OpenTransientFile(FileName fileName, int fileFlags, int fileMode) +{ + int fd; + + + DO_DB(elog(LOG, "OpenTransientFile: Allocated %d (%s)", + numAllocatedDescs, fileName)); + + /* + * The test against MAX_ALLOCATED_DESCS prevents us from overflowing + * allocatedFiles[]; the test against max_safe_fds prevents BasicOpenFile + * from hogging every one of the available FDs, which'd lead to infinite + * looping. + */ + if (numAllocatedDescs >= MAX_ALLOCATED_DESCS || + numAllocatedDescs >= max_safe_fds - 1) + elog(ERROR, "exceeded MAX_ALLOCATED_DESCS while trying to open file \"%s\"", + fileName); + + fd = BasicOpenFile(fileName, fileFlags, fileMode); + + if (fd >= 0) + { + AllocateDesc *desc = &allocatedDescs[numAllocatedDescs]; + + desc->kind = AllocateDescRawFD; + desc->desc.fd = fd; + desc->create_subid = GetCurrentSubTransactionId(); + numAllocatedDescs++; + + return fd; + } + + return -1; /* failure */ +} + +/* + * Free an AllocateDesc of any type. * * The argument *must* point into the allocatedDescs[] array. */ @@ -1542,6 +1603,9 @@ FreeDesc(AllocateDesc *desc) case AllocateDescDir: result = closedir(desc->desc.dir); break; + case AllocateDescRawFD: + result = close(desc->desc.fd); + break; default: elog(ERROR, "AllocateDesc kind not recognized"); result = 0; /* keep compiler quiet */ @@ -1583,6 +1647,33 @@ FreeFile(FILE *file) return fclose(file); } +/* + * Close a file returned by OpenTransientFile. + * + * Note we do not check close's return value --- it is up to the caller + * to handle close errors. + */ +int +CloseTransientFile(int fd) +{ + int i; + + DO_DB(elog(LOG, "CloseTransientFile: Allocated %d", numAllocatedDescs)); + + /* Remove fd from list of allocated files, if it's present */ + for (i = numAllocatedDescs; --i >= 0;) + { + AllocateDesc *desc = &allocatedDescs[i]; + + if (desc->kind == AllocateDescRawFD && desc->desc.fd == fd) + return FreeDesc(desc); + } + + /* Only get here if someone passes us a file not in allocatedDescs */ + elog(WARNING, "fd passed to CloseTransientFile was not obtained from OpenTransientFile"); + + return close(fd); +} /* * Routines that want to use <dirent.h> (ie, DIR*) should use AllocateDir @@ -1874,7 +1965,7 @@ AtProcExit_Files(int code, Datum arg) * exiting. If that's the case, we should remove all temporary files; if * that's not the case, we are being called for transaction commit/abort * and should only remove transaction-local temp files. In either case, - * also clean up "allocated" stdio files and dirs. + * also clean up "allocated" stdio files, dirs and fds. */ static void CleanupTempFiles(bool isProcExit) @@ -1916,7 +2007,7 @@ CleanupTempFiles(bool isProcExit) have_xact_temporary_files = false; } - /* Clean up "allocated" stdio files and dirs. */ + /* Clean up "allocated" stdio files, dirs and fds. */ while (numAllocatedDescs > 0) FreeDesc(&allocatedDescs[0]); } |