Skip to content

Commit 8c3fdbb

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 d7cb64a commit 8c3fdbb

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
@@ -169,13 +169,7 @@ static int nfile = 0;
169169
/*
170170
* List of stdio FILEs and <dirent.h> DIRs opened with AllocateFile
171171
* and AllocateDir.
172-
*
173-
* Since we don't want to encourage heavy use of AllocateFile or AllocateDir,
174-
* it seems OK to put a pretty small maximum limit on the number of
175-
* simultaneously allocated descs.
176172
*/
177-
#define MAX_ALLOCATED_DESCS 32
178-
179173
typedef enum
180174
{
181175
AllocateDescFile,
@@ -185,16 +179,17 @@ typedef enum
185179
typedef struct
186180
{
187181
AllocateDescKind kind;
182+
SubTransactionId create_subid;
188183
union
189184
{
190185
FILE *file;
191186
DIR *dir;
192187
} desc;
193-
SubTransactionId create_subid;
194188
} AllocateDesc;
195189

196190
static int numAllocatedDescs = 0;
197-
static AllocateDesc allocatedDescs[MAX_ALLOCATED_DESCS];
191+
static int maxAllocatedDescs = 0;
192+
static AllocateDesc *allocatedDescs = NULL;
198193

199194
/*
200195
* Number of temporary files opened during the current session;
@@ -220,6 +215,7 @@ static int nextTempTableSpace = 0;
220215
* Insert - put a file at the front of the Lru ring
221216
* LruInsert - put a file at the front of the Lru ring and open it
222217
* ReleaseLruFile - Release an fd by closing the last entry in the Lru ring
218+
* ReleaseLruFiles - Release fd(s) until we're under the max_safe_fds limit
223219
* AllocateVfd - grab a free (or new) file record (from VfdArray)
224220
* FreeVfd - free a file record
225221
*
@@ -247,11 +243,14 @@ static void LruDelete(File file);
247243
static void Insert(File file);
248244
static int LruInsert(File file);
249245
static bool ReleaseLruFile(void);
246+
static void ReleaseLruFiles(void);
250247
static File AllocateVfd(void);
251248
static void FreeVfd(File file);
252249

253250
static int FileAccess(File file);
254251
static File OpenTemporaryFileInTablespace(Oid tblspcOid, bool rejectError);
252+
static bool reserveAllocatedDesc(void);
253+
static int FreeDesc(AllocateDesc *desc);
255254
static void AtProcExit_Files(int code, Datum arg);
256255
static void CleanupTempFiles(bool isProcExit);
257256
static void RemovePgTempFilesInDir(const char *tmpdirname);
@@ -654,11 +653,8 @@ LruInsert(File file)
654653

655654
if (FileIsNotOpen(file))
656655
{
657-
while (nfile + numAllocatedDescs >= max_safe_fds)
658-
{
659-
if (!ReleaseLruFile())
660-
break;
661-
}
656+
/* Close excess kernel FDs. */
657+
ReleaseLruFiles();
662658

663659
/*
664660
* The open could still fail for lack of file descriptors, eg due to
@@ -697,6 +693,9 @@ LruInsert(File file)
697693
return 0;
698694
}
699695

696+
/*
697+
* Release one kernel FD by closing the least-recently-used VFD.
698+
*/
700699
static bool
701700
ReleaseLruFile(void)
702701
{
@@ -715,6 +714,20 @@ ReleaseLruFile(void)
715714
return false; /* no files available to free */
716715
}
717716

717+
/*
718+
* Release kernel FDs as needed to get under the max_safe_fds limit.
719+
* After calling this, it's OK to try to open another file.
720+
*/
721+
static void
722+
ReleaseLruFiles(void)
723+
{
724+
while (nfile + numAllocatedDescs >= max_safe_fds)
725+
{
726+
if (!ReleaseLruFile())
727+
break;
728+
}
729+
}
730+
718731
static File
719732
AllocateVfd(void)
720733
{
@@ -868,11 +881,8 @@ PathNameOpenFile(FileName fileName, int fileFlags, int fileMode)
868881
file = AllocateVfd();
869882
vfdP = &VfdCache[file];
870883

871-
while (nfile + numAllocatedDescs >= max_safe_fds)
872-
{
873-
if (!ReleaseLruFile())
874-
break;
875-
}
884+
/* Close excess kernel FDs. */
885+
ReleaseLruFiles();
876886

877887
vfdP->fd = BasicOpenFile(fileName, fileFlags, fileMode);
878888

@@ -1402,6 +1412,66 @@ FilePathName(File file)
14021412
}
14031413

14041414

1415+
/*
1416+
* Make room for another allocatedDescs[] array entry if needed and possible.
1417+
* Returns true if an array element is available.
1418+
*/
1419+
static bool
1420+
reserveAllocatedDesc(void)
1421+
{
1422+
AllocateDesc *newDescs;
1423+
int newMax;
1424+
1425+
/* Quick out if array already has a free slot. */
1426+
if (numAllocatedDescs < maxAllocatedDescs)
1427+
return true;
1428+
1429+
/*
1430+
* If the array hasn't yet been created in the current process, initialize
1431+
* it with FD_MINFREE / 2 elements. In many scenarios this is as many as
1432+
* we will ever need, anyway. We don't want to look at max_safe_fds
1433+
* immediately because set_max_safe_fds() may not have run yet.
1434+
*/
1435+
if (allocatedDescs == NULL)
1436+
{
1437+
newMax = FD_MINFREE / 2;
1438+
newDescs = (AllocateDesc *) malloc(newMax * sizeof(AllocateDesc));
1439+
/* Out of memory already? Treat as fatal error. */
1440+
if (newDescs == NULL)
1441+
ereport(ERROR,
1442+
(errcode(ERRCODE_OUT_OF_MEMORY),
1443+
errmsg("out of memory")));
1444+
allocatedDescs = newDescs;
1445+
maxAllocatedDescs = newMax;
1446+
return true;
1447+
}
1448+
1449+
/*
1450+
* Consider enlarging the array beyond the initial allocation used above.
1451+
* By the time this happens, max_safe_fds should be known accurately.
1452+
*
1453+
* We mustn't let allocated descriptors hog all the available FDs, and in
1454+
* practice we'd better leave a reasonable number of FDs for VFD use. So
1455+
* set the maximum to max_safe_fds / 2. (This should certainly be at
1456+
* least as large as the initial size, FD_MINFREE / 2.)
1457+
*/
1458+
newMax = max_safe_fds / 2;
1459+
if (newMax > maxAllocatedDescs)
1460+
{
1461+
newDescs = (AllocateDesc *) realloc(allocatedDescs,
1462+
newMax * sizeof(AllocateDesc));
1463+
/* Treat out-of-memory as a non-fatal error. */
1464+
if (newDescs == NULL)
1465+
return false;
1466+
allocatedDescs = newDescs;
1467+
maxAllocatedDescs = newMax;
1468+
return true;
1469+
}
1470+
1471+
/* Can't enlarge allocatedDescs[] any more. */
1472+
return false;
1473+
}
1474+
14051475
/*
14061476
* Routines that want to use stdio (ie, FILE*) should use AllocateFile
14071477
* rather than plain fopen(). This lets fd.c deal with freeing FDs if
@@ -1427,16 +1497,15 @@ AllocateFile(const char *name, const char *mode)
14271497
DO_DB(elog(LOG, "AllocateFile: Allocated %d (%s)",
14281498
numAllocatedDescs, name));
14291499

1430-
/*
1431-
* The test against MAX_ALLOCATED_DESCS prevents us from overflowing
1432-
* allocatedFiles[]; the test against max_safe_fds prevents AllocateFile
1433-
* from hogging every one of the available FDs, which'd lead to infinite
1434-
* looping.
1435-
*/
1436-
if (numAllocatedDescs >= MAX_ALLOCATED_DESCS ||
1437-
numAllocatedDescs >= max_safe_fds - 1)
1438-
elog(ERROR, "exceeded MAX_ALLOCATED_DESCS while trying to open file \"%s\"",
1439-
name);
1500+
/* Can we allocate another non-virtual FD? */
1501+
if (!reserveAllocatedDesc())
1502+
ereport(ERROR,
1503+
(errcode(ERRCODE_INSUFFICIENT_RESOURCES),
1504+
errmsg("exceeded maxAllocatedDescs (%d) while trying to open file \"%s\"",
1505+
maxAllocatedDescs, name)));
1506+
1507+
/* Close excess kernel FDs. */
1508+
ReleaseLruFiles();
14401509

14411510
TryAgain:
14421511
if ((file = fopen(name, mode)) != NULL)
@@ -1543,16 +1612,15 @@ AllocateDir(const char *dirname)
15431612
DO_DB(elog(LOG, "AllocateDir: Allocated %d (%s)",
15441613
numAllocatedDescs, dirname));
15451614

1546-
/*
1547-
* The test against MAX_ALLOCATED_DESCS prevents us from overflowing
1548-
* allocatedDescs[]; the test against max_safe_fds prevents AllocateDir
1549-
* from hogging every one of the available FDs, which'd lead to infinite
1550-
* looping.
1551-
*/
1552-
if (numAllocatedDescs >= MAX_ALLOCATED_DESCS ||
1553-
numAllocatedDescs >= max_safe_fds - 1)
1554-
elog(ERROR, "exceeded MAX_ALLOCATED_DESCS while trying to open directory \"%s\"",
1555-
dirname);
1615+
/* Can we allocate another non-virtual FD? */
1616+
if (!reserveAllocatedDesc())
1617+
ereport(ERROR,
1618+
(errcode(ERRCODE_INSUFFICIENT_RESOURCES),
1619+
errmsg("exceeded maxAllocatedDescs (%d) while trying to open directory \"%s\"",
1620+
maxAllocatedDescs, dirname)));
1621+
1622+
/* Close excess kernel FDs. */
1623+
ReleaseLruFiles();
15561624

15571625
TryAgain:
15581626
if ((dir = opendir(dirname)) != NULL)

0 commit comments

Comments
 (0)