*** a/src/backend/storage/buffer/bufmgr.c --- b/src/backend/storage/buffer/bufmgr.c *************** *** 1834,1840 **** BufferGetTag(Buffer buffer, RelFileNode *rnode, ForkNumber *forknum, * written.) * * If the caller has an smgr reference for the buffer's relation, pass it ! * as the second parameter. If not, pass NULL. */ static void FlushBuffer(volatile BufferDesc *buf, SMgrRelation reln) --- 1834,1843 ---- * written.) * * If the caller has an smgr reference for the buffer's relation, pass it ! * as the second parameter. If not, pass NULL. In the latter case, the ! * relation will be marked as "transient" so that the corresponding ! * kernel-level file descriptors are closed when the current transaction ends, ! * if any. */ static void FlushBuffer(volatile BufferDesc *buf, SMgrRelation reln) *************** *** 1856,1864 **** FlushBuffer(volatile BufferDesc *buf, SMgrRelation reln) errcontext.previous = error_context_stack; error_context_stack = &errcontext; ! /* Find smgr relation for buffer */ if (reln == NULL) reln = smgropen(buf->tag.rnode, InvalidBackendId); TRACE_POSTGRESQL_BUFFER_FLUSH_START(buf->tag.forkNum, buf->tag.blockNum, --- 1859,1870 ---- errcontext.previous = error_context_stack; error_context_stack = &errcontext; ! /* Find smgr relation for buffer, and mark it as transient */ if (reln == NULL) + { reln = smgropen(buf->tag.rnode, InvalidBackendId); + smgrsettransient(reln); + } TRACE_POSTGRESQL_BUFFER_FLUSH_START(buf->tag.forkNum, buf->tag.blockNum, *** a/src/backend/storage/file/fd.c --- b/src/backend/storage/file/fd.c *************** *** 125,136 **** static int max_safe_fds = 32; /* default if not changed */ /* these are the assigned bits in fdstate below: */ #define FD_TEMPORARY (1 << 0) /* T = delete when closed */ #define FD_XACT_TEMPORARY (1 << 1) /* T = delete at eoXact */ ! /* ! * Flag to tell whether it's worth scanning VfdCache looking for temp files to ! * close ! */ ! static bool have_xact_temporary_files = false; typedef struct vfd { --- 125,135 ---- /* these are the assigned bits in fdstate below: */ #define FD_TEMPORARY (1 << 0) /* T = delete when closed */ #define FD_XACT_TEMPORARY (1 << 1) /* T = delete at eoXact */ + #define FD_XACT_TRANSIENT (1 << 2) /* T = close (not delete) at aoXact, + * but keep VFD */ ! /* Flag to tell whether there are files to close/delete at end of transaction */ ! static bool have_pending_fd_cleanup = false; typedef struct vfd { *************** *** 953,959 **** OpenTemporaryFile(bool interXact) VfdCache[file].resowner = CurrentResourceOwner; /* ensure cleanup happens at eoxact */ ! have_xact_temporary_files = true; } return file; --- 952,958 ---- VfdCache[file].resowner = CurrentResourceOwner; /* ensure cleanup happens at eoxact */ ! have_pending_fd_cleanup = true; } return file; *************** *** 1027,1032 **** OpenTemporaryFileInTablespace(Oid tblspcOid, bool rejectError) --- 1026,1070 ---- } /* + * Set the transient flag on a file + * + * This flag tells CleanupTempFiles to close the kernel-level file descriptor + * (but not the VFD itself) at end of transaction. + */ + void + FileSetTransient(File file) + { + Vfd *vfdP; + + Assert(FileIsValid(file)); + + vfdP = &VfdCache[file]; + vfdP->fdstate |= FD_XACT_TRANSIENT; + + have_pending_fd_cleanup = true; + } + + /* + * Close a file at the kernel level, but keep the VFD open + */ + static void + FileKernelClose(File file) + { + Vfd *vfdP; + + Assert(FileIsValid(file)); + + vfdP = &VfdCache[file]; + + if (!FileIsNotOpen(file)) + { + if (close(vfdP->fd)) + elog(ERROR, "could not close file \"%s\": %m", vfdP->fileName); + vfdP->fd = VFD_CLOSED; + } + } + + /* * close a file when done with it */ void *************** *** 1778,1785 **** AtEOSubXact_Files(bool isCommit, SubTransactionId mySubid, * particularly care which). All still-open per-transaction temporary file * VFDs are closed, which also causes the underlying files to be deleted * (although they should've been closed already by the ResourceOwner ! * cleanup). Furthermore, all "allocated" stdio files are closed. We also ! * forget any transaction-local temp tablespace list. */ void AtEOXact_Files(void) --- 1816,1824 ---- * particularly care which). All still-open per-transaction temporary file * VFDs are closed, which also causes the underlying files to be deleted * (although they should've been closed already by the ResourceOwner ! * cleanup). Transient files have their kernel file descriptors closed. ! * Furthermore, all "allocated" stdio files are closed. We also forget any ! * transaction-local temp tablespace list. */ void AtEOXact_Files(void) *************** *** 1802,1808 **** AtProcExit_Files(int code, Datum arg) } /* ! * Close temporary files and delete their underlying files. * * isProcExit: if true, this is being called as the backend process is * exiting. If that's the case, we should remove all temporary files; if --- 1841,1850 ---- } /* ! * General cleanup routine for fd.c. ! * ! * Temporary files are closed, and their underlying files deleted. ! * Transient files are closed. * * isProcExit: if true, this is being called as the backend process is * exiting. If that's the case, we should remove all temporary files; if *************** *** 1819,1853 **** CleanupTempFiles(bool isProcExit) * Careful here: at proc_exit we need extra cleanup, not just * xact_temporary files. */ ! if (isProcExit || have_xact_temporary_files) { Assert(FileIsNotOpen(0)); /* Make sure ring not corrupted */ for (i = 1; i < SizeVfdCache; i++) { unsigned short fdstate = VfdCache[i].fdstate; ! if ((fdstate & FD_TEMPORARY) && VfdCache[i].fileName != NULL) { ! /* ! * If we're in the process of exiting a backend process, close ! * all temporary files. Otherwise, only close temporary files ! * local to the current transaction. They should be closed by ! * the ResourceOwner mechanism already, so this is just a ! * debugging cross-check. ! */ ! if (isProcExit) ! FileClose(i); ! else if (fdstate & FD_XACT_TEMPORARY) { ! elog(WARNING, ! "temporary file %s not closed at end-of-transaction", ! VfdCache[i].fileName); ! FileClose(i); } } } ! have_xact_temporary_files = false; } /* Clean up "allocated" stdio files and dirs. */ --- 1861,1909 ---- * Careful here: at proc_exit we need extra cleanup, not just * xact_temporary files. */ ! if (isProcExit || have_pending_fd_cleanup) { Assert(FileIsNotOpen(0)); /* Make sure ring not corrupted */ for (i = 1; i < SizeVfdCache; i++) { unsigned short fdstate = VfdCache[i].fdstate; ! if (VfdCache[i].fileName != NULL) { ! if (fdstate & FD_TEMPORARY) ! { ! /* ! * If we're in the process of exiting a backend process, close ! * all temporary files. Otherwise, only close temporary files ! * local to the current transaction. They should be closed by ! * the ResourceOwner mechanism already, so this is just a ! * debugging cross-check. ! */ ! if (isProcExit) ! FileClose(i); ! else if (fdstate & FD_XACT_TEMPORARY) ! { ! elog(WARNING, ! "temporary file %s not closed at end-of-transaction", ! VfdCache[i].fileName); ! FileClose(i); ! } ! } ! else if (fdstate & FD_XACT_TRANSIENT) { ! /* ! * Close the kernel file descriptor, but also remove the ! * flag from the VFD. This is to ensure that if the VFD is ! * reused in the future for non-transient access, we don't ! * close it inappropriately then. ! */ ! FileKernelClose(i); ! VfdCache[i].fdstate &= ~FD_XACT_TRANSIENT; } } } ! have_pending_fd_cleanup = false; } /* Clean up "allocated" stdio files and dirs. */ *** a/src/backend/storage/smgr/md.c --- b/src/backend/storage/smgr/md.c *************** *** 288,293 **** mdcreate(SMgrRelation reln, ForkNumber forkNum, bool isRedo) --- 288,296 ---- pfree(path); + if (reln->smgr_transient) + FileSetTransient(fd); + reln->md_fd[forkNum] = _fdvec_alloc(); reln->md_fd[forkNum]->mdfd_vfd = fd; *************** *** 542,547 **** mdopen(SMgrRelation reln, ForkNumber forknum, ExtensionBehavior behavior) --- 545,553 ---- pfree(path); + if (reln->smgr_transient) + FileSetTransient(fd); + reln->md_fd[forknum] = mdfd = _fdvec_alloc(); mdfd->mdfd_vfd = fd; *************** *** 1556,1561 **** _mdfd_openseg(SMgrRelation reln, ForkNumber forknum, BlockNumber segno, --- 1562,1570 ---- if (fd < 0) return NULL; + if (reln->smgr_transient) + FileSetTransient(fd); + /* allocate an mdfdvec entry for it */ v = _fdvec_alloc(); *** a/src/backend/storage/smgr/smgr.c --- b/src/backend/storage/smgr/smgr.c *************** *** 165,181 **** smgropen(RelFileNode rnode, BackendId backend) --- 165,198 ---- reln->smgr_targblock = InvalidBlockNumber; reln->smgr_fsm_nblocks = InvalidBlockNumber; reln->smgr_vm_nblocks = InvalidBlockNumber; + reln->smgr_transient = false; reln->smgr_which = 0; /* we only have md.c at present */ /* mark it not open */ for (forknum = 0; forknum <= MAX_FORKNUM; forknum++) reln->md_fd[forknum] = NULL; } + else + /* if it was transient before, it no longer is */ + reln->smgr_transient = false; return reln; } /* + * smgrsettransient() -- mark an SMgrRelation object as transaction-bound + * + * The main effect of this is that all opened files are marked to be + * kernel-level closed (but not necessarily VFD-closed) when the current + * transaction ends. + */ + void + smgrsettransient(SMgrRelation reln) + { + reln->smgr_transient = true; + } + + /* * smgrsetowner() -- Establish a long-lived reference to an SMgrRelation object * * There can be only one owner at a time; this is sufficient since currently *** a/src/include/storage/fd.h --- b/src/include/storage/fd.h *************** *** 61,66 **** extern int max_files_per_process; --- 61,67 ---- /* Operations on virtual Files --- equivalent to Unix kernel file ops */ extern File PathNameOpenFile(FileName fileName, int fileFlags, int fileMode); extern File OpenTemporaryFile(bool interXact); + extern void FileSetTransient(File file); extern void FileClose(File file); extern int FilePrefetch(File file, off_t offset, int amount); extern int FileRead(File file, char *buffer, int amount); *** a/src/include/storage/smgr.h --- b/src/include/storage/smgr.h *************** *** 62,67 **** typedef struct SMgrRelationData --- 62,68 ---- * submodules. Do not touch them from elsewhere. */ int smgr_which; /* storage manager selector */ + bool smgr_transient; /* T if files are to be closed at EOXact */ /* for md.c; NULL for forks that are not open */ struct _MdfdVec *md_fd[MAX_FORKNUM + 1]; *************** *** 74,79 **** typedef SMgrRelationData *SMgrRelation; --- 75,81 ---- extern void smgrinit(void); extern SMgrRelation smgropen(RelFileNode rnode, BackendId backend); + extern void smgrsettransient(SMgrRelation reln); extern bool smgrexists(SMgrRelation reln, ForkNumber forknum); extern void smgrsetowner(SMgrRelation *owner, SMgrRelation reln); extern void smgrclose(SMgrRelation reln);