Skip to content

Commit a9ec978

Browse files
committed
Remove fixed limit on the number of concurrent AllocateFile() requests.
AllocateFile(), AllocateDir(), and some sister routines share a small array for remembering requests, so that the files can be closed on transaction failure. Previously that array had a fixed size, MAX_ALLOCATED_DESCS (32). While historically that had seemed sufficient, Steve Toutant pointed out that this meant you couldn't scan more than 32 file_fdw foreign tables in one query, because file_fdw depends on the COPY code which uses AllocateFile(). There are probably other cases, or will be in the future, where this nonconfigurable limit impedes users. We can't completely remove any such limit, at least not without a lot of work, since each such request requires a kernel file descriptor and most platforms limit the number we can have. (In principle we could "virtualize" these descriptors, as fd.c already does for the main VFD pool, but not without an additional layer of overhead and a lot of notational impact on the calling code.) But we can at least let the array size be configurable. Hence, change the code to allow up to max_safe_fds/2 allocated file requests. On modern platforms this should allow several hundred concurrent file_fdw scans, or more if one increases the value of max_files_per_process. To go much further than that, we'd need to do some more work on the data structure, since the current code for closing requests has potentially O(N^2) runtime; but it should still be all right for request counts in this range. Back-patch to 9.1 where contrib/file_fdw was introduced.
1 parent a56c92f commit a9ec978

File tree

1 file changed

+106
-38
lines changed
  • src/backend/storage/file

1 file changed

+106
-38
lines changed

src/backend/storage/file/fd.c

Lines changed: 106 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -179,13 +179,7 @@ static uint64 temporary_files_size = 0;
179179
/*
180180
* List of stdio FILEs and <dirent.h> DIRs opened with AllocateFile
181181
* and AllocateDir.
182-
*
183-
* Since we don't want to encourage heavy use of AllocateFile or AllocateDir,
184-
* it seems OK to put a pretty small maximum limit on the number of
185-
* simultaneously allocated descs.
186182
*/
187-
#define MAX_ALLOCATED_DESCS 32
188-
189183
typedef enum
190184
{
191185
AllocateDescFile,
@@ -195,16 +189,17 @@ typedef enum
195189
typedef struct
196190
{
197191
AllocateDescKind kind;
192+
SubTransactionId create_subid;
198193
union
199194
{
200195
FILE *file;
201196
DIR *dir;
202197
} desc;
203-
SubTransactionId create_subid;
204198
} AllocateDesc;
205199

206200
static int numAllocatedDescs = 0;
207-
static AllocateDesc allocatedDescs[MAX_ALLOCATED_DESCS];
201+
static int maxAllocatedDescs = 0;
202+
static AllocateDesc *allocatedDescs = NULL;
208203

209204
/*
210205
* Number of temporary files opened during the current session;
@@ -230,6 +225,7 @@ static int nextTempTableSpace = 0;
230225
* Insert - put a file at the front of the Lru ring
231226
* LruInsert - put a file at the front of the Lru ring and open it
232227
* ReleaseLruFile - Release an fd by closing the last entry in the Lru ring
228+
* ReleaseLruFiles - Release fd(s) until we're under the max_safe_fds limit
233229
* AllocateVfd - grab a free (or new) file record (from VfdArray)
234230
* FreeVfd - free a file record
235231
*
@@ -257,11 +253,14 @@ static void LruDelete(File file);
257253
static void Insert(File file);
258254
static int LruInsert(File file);
259255
static bool ReleaseLruFile(void);
256+
static void ReleaseLruFiles(void);
260257
static File AllocateVfd(void);
261258
static void FreeVfd(File file);
262259

263260
static int FileAccess(File file);
264261
static File OpenTemporaryFileInTablespace(Oid tblspcOid, bool rejectError);
262+
static bool reserveAllocatedDesc(void);
263+
static int FreeDesc(AllocateDesc *desc);
265264
static void AtProcExit_Files(int code, Datum arg);
266265
static void CleanupTempFiles(bool isProcExit);
267266
static void RemovePgTempFilesInDir(const char *tmpdirname);
@@ -664,11 +663,8 @@ LruInsert(File file)
664663

665664
if (FileIsNotOpen(file))
666665
{
667-
while (nfile + numAllocatedDescs >= max_safe_fds)
668-
{
669-
if (!ReleaseLruFile())
670-
break;
671-
}
666+
/* Close excess kernel FDs. */
667+
ReleaseLruFiles();
672668

673669
/*
674670
* The open could still fail for lack of file descriptors, eg due to
@@ -707,6 +703,9 @@ LruInsert(File file)
707703
return 0;
708704
}
709705

706+
/*
707+
* Release one kernel FD by closing the least-recently-used VFD.
708+
*/
710709
static bool
711710
ReleaseLruFile(void)
712711
{
@@ -725,6 +724,20 @@ ReleaseLruFile(void)
725724
return false; /* no files available to free */
726725
}
727726

727+
/*
728+
* Release kernel FDs as needed to get under the max_safe_fds limit.
729+
* After calling this, it's OK to try to open another file.
730+
*/
731+
static void
732+
ReleaseLruFiles(void)
733+
{
734+
while (nfile + numAllocatedDescs >= max_safe_fds)
735+
{
736+
if (!ReleaseLruFile())
737+
break;
738+
}
739+
}
740+
728741
static File
729742
AllocateVfd(void)
730743
{
@@ -878,11 +891,8 @@ PathNameOpenFile(FileName fileName, int fileFlags, int fileMode)
878891
file = AllocateVfd();
879892
vfdP = &VfdCache[file];
880893

881-
while (nfile + numAllocatedDescs >= max_safe_fds)
882-
{
883-
if (!ReleaseLruFile())
884-
break;
885-
}
894+
/* Close excess kernel FDs. */
895+
ReleaseLruFiles();
886896

887897
vfdP->fd = BasicOpenFile(fileName, fileFlags, fileMode);
888898

@@ -1461,6 +1471,66 @@ FilePathName(File file)
14611471
}
14621472

14631473

1474+
/*
1475+
* Make room for another allocatedDescs[] array entry if needed and possible.
1476+
* Returns true if an array element is available.
1477+
*/
1478+
static bool
1479+
reserveAllocatedDesc(void)
1480+
{
1481+
AllocateDesc *newDescs;
1482+
int newMax;
1483+
1484+
/* Quick out if array already has a free slot. */
1485+
if (numAllocatedDescs < maxAllocatedDescs)
1486+
return true;
1487+
1488+
/*
1489+
* If the array hasn't yet been created in the current process, initialize
1490+
* it with FD_MINFREE / 2 elements. In many scenarios this is as many as
1491+
* we will ever need, anyway. We don't want to look at max_safe_fds
1492+
* immediately because set_max_safe_fds() may not have run yet.
1493+
*/
1494+
if (allocatedDescs == NULL)
1495+
{
1496+
newMax = FD_MINFREE / 2;
1497+
newDescs = (AllocateDesc *) malloc(newMax * sizeof(AllocateDesc));
1498+
/* Out of memory already? Treat as fatal error. */
1499+
if (newDescs == NULL)
1500+
ereport(ERROR,
1501+
(errcode(ERRCODE_OUT_OF_MEMORY),
1502+
errmsg("out of memory")));
1503+
allocatedDescs = newDescs;
1504+
maxAllocatedDescs = newMax;
1505+
return true;
1506+
}
1507+
1508+
/*
1509+
* Consider enlarging the array beyond the initial allocation used above.
1510+
* By the time this happens, max_safe_fds should be known accurately.
1511+
*
1512+
* We mustn't let allocated descriptors hog all the available FDs, and in
1513+
* practice we'd better leave a reasonable number of FDs for VFD use. So
1514+
* set the maximum to max_safe_fds / 2. (This should certainly be at
1515+
* least as large as the initial size, FD_MINFREE / 2.)
1516+
*/
1517+
newMax = max_safe_fds / 2;
1518+
if (newMax > maxAllocatedDescs)
1519+
{
1520+
newDescs = (AllocateDesc *) realloc(allocatedDescs,
1521+
newMax * sizeof(AllocateDesc));
1522+
/* Treat out-of-memory as a non-fatal error. */
1523+
if (newDescs == NULL)
1524+
return false;
1525+
allocatedDescs = newDescs;
1526+
maxAllocatedDescs = newMax;
1527+
return true;
1528+
}
1529+
1530+
/* Can't enlarge allocatedDescs[] any more. */
1531+
return false;
1532+
}
1533+
14641534
/*
14651535
* Routines that want to use stdio (ie, FILE*) should use AllocateFile
14661536
* rather than plain fopen(). This lets fd.c deal with freeing FDs if
@@ -1486,16 +1556,15 @@ AllocateFile(const char *name, const char *mode)
14861556
DO_DB(elog(LOG, "AllocateFile: Allocated %d (%s)",
14871557
numAllocatedDescs, name));
14881558

1489-
/*
1490-
* The test against MAX_ALLOCATED_DESCS prevents us from overflowing
1491-
* allocatedFiles[]; the test against max_safe_fds prevents AllocateFile
1492-
* from hogging every one of the available FDs, which'd lead to infinite
1493-
* looping.
1494-
*/
1495-
if (numAllocatedDescs >= MAX_ALLOCATED_DESCS ||
1496-
numAllocatedDescs >= max_safe_fds - 1)
1497-
elog(ERROR, "exceeded MAX_ALLOCATED_DESCS while trying to open file \"%s\"",
1498-
name);
1559+
/* Can we allocate another non-virtual FD? */
1560+
if (!reserveAllocatedDesc())
1561+
ereport(ERROR,
1562+
(errcode(ERRCODE_INSUFFICIENT_RESOURCES),
1563+
errmsg("exceeded maxAllocatedDescs (%d) while trying to open file \"%s\"",
1564+
maxAllocatedDescs, name)));
1565+
1566+
/* Close excess kernel FDs. */
1567+
ReleaseLruFiles();
14991568

15001569
TryAgain:
15011570
if ((file = fopen(name, mode)) != NULL)
@@ -1602,16 +1671,15 @@ AllocateDir(const char *dirname)
16021671
DO_DB(elog(LOG, "AllocateDir: Allocated %d (%s)",
16031672
numAllocatedDescs, dirname));
16041673

1605-
/*
1606-
* The test against MAX_ALLOCATED_DESCS prevents us from overflowing
1607-
* allocatedDescs[]; the test against max_safe_fds prevents AllocateDir
1608-
* from hogging every one of the available FDs, which'd lead to infinite
1609-
* looping.
1610-
*/
1611-
if (numAllocatedDescs >= MAX_ALLOCATED_DESCS ||
1612-
numAllocatedDescs >= max_safe_fds - 1)
1613-
elog(ERROR, "exceeded MAX_ALLOCATED_DESCS while trying to open directory \"%s\"",
1614-
dirname);
1674+
/* Can we allocate another non-virtual FD? */
1675+
if (!reserveAllocatedDesc())
1676+
ereport(ERROR,
1677+
(errcode(ERRCODE_INSUFFICIENT_RESOURCES),
1678+
errmsg("exceeded maxAllocatedDescs (%d) while trying to open directory \"%s\"",
1679+
maxAllocatedDescs, dirname)));
1680+
1681+
/* Close excess kernel FDs. */
1682+
ReleaseLruFiles();
16151683

16161684
TryAgain:
16171685
if ((dir = opendir(dirname)) != NULL)

0 commit comments

Comments
 (0)