diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 842558d673..afa087a346 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -1025,10 +1025,41 @@ SET ENABLE_SEQSCAN TO OFF;
+
+ Disk
+
+
+
+ temp_file_limit (integer)
+
+ temp_file_limit> configuration parameter
+
+
+
+ Specifies the maximum amount of disk space that a session can use
+ for temporary files, such as sort and hash temporary files, or the
+ storage file for a held cursor.
+ The value is specified in kilobytes, and -1> (the
+ default) means no limit.
+ Only superusers can change this setting.
+
+
+ This setting constrains the total space used at any instant by all
+ temporary files used by a given PostgreSQL> session.
+ It should be noted that disk space used for explicit temporary
+ tables, as opposed to temporary files used behind-the-scenes in query
+ execution, does not count against this limit.
+
+
+
+
+
+
+
Kernel Resource Usage
-
+
max_files_per_process (integer)
diff --git a/src/backend/storage/file/fd.c b/src/backend/storage/file/fd.c
index 820e6dbfd9..884d915127 100644
--- a/src/backend/storage/file/fd.c
+++ b/src/backend/storage/file/fd.c
@@ -128,9 +128,6 @@ static int max_safe_fds = 32; /* default if not changed */
#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
{
int fd; /* current FD, or VFD_CLOSED if none */
@@ -140,6 +137,7 @@ typedef struct vfd
File lruMoreRecently; /* doubly linked recency-of-use list */
File lruLessRecently;
off_t seekPos; /* current logical file position */
+ off_t fileSize; /* current size of file (0 if not temporary) */
char *fileName; /* name of file, or NULL for unused VFD */
/* NB: fileName is malloc'd, and must be free'd when closing the VFD */
int fileFlags; /* open(2) flags for (re)opening the file */
@@ -159,6 +157,17 @@ static Size SizeVfdCache = 0;
*/
static int nfile = 0;
+/* True if there are files to close/delete at end of transaction */
+static bool have_pending_fd_cleanup = false;
+
+/*
+ * Tracks the total size of all temporary files. Note: when temp_file_limit
+ * is being enforced, this cannot overflow since the limit cannot be more
+ * than INT_MAX kilobytes. When not enforcing, it could theoretically
+ * overflow, but we don't care.
+ */
+static uint64 temporary_files_size = 0;
+
/*
* List of stdio FILEs and DIRs opened with AllocateFile
* and AllocateDir.
@@ -887,6 +896,7 @@ PathNameOpenFile(FileName fileName, int fileFlags, int fileMode)
vfdP->fileFlags = fileFlags & ~(O_CREAT | O_TRUNC | O_EXCL);
vfdP->fileMode = fileMode;
vfdP->seekPos = 0;
+ vfdP->fileSize = 0;
vfdP->fdstate = 0x0;
vfdP->resowner = NULL;
@@ -1123,6 +1133,10 @@ FileClose(File file)
if (unlink(vfdP->fileName))
elog(LOG, "could not unlink file \"%s\": %m", vfdP->fileName);
}
+
+ /* Subtract its size from current usage */
+ temporary_files_size -= vfdP->fileSize;
+ vfdP->fileSize = 0;
}
/* Unregister it from the resource owner */
@@ -1242,6 +1256,31 @@ FileWrite(File file, char *buffer, int amount)
if (returnCode < 0)
return returnCode;
+ /*
+ * If enforcing temp_file_limit and it's a temp file, check to see if the
+ * write would overrun temp_file_limit, and throw error if so. Note: it's
+ * really a modularity violation to throw error here; we should set errno
+ * and return -1. However, there's no way to report a suitable error
+ * message if we do that. All current callers would just throw error
+ * immediately anyway, so this is safe at present.
+ */
+ if (temp_file_limit >= 0 && (VfdCache[file].fdstate & FD_TEMPORARY))
+ {
+ off_t newPos = VfdCache[file].seekPos + amount;
+
+ if (newPos > VfdCache[file].fileSize)
+ {
+ uint64 newTotal = temporary_files_size;
+
+ newTotal += newPos - VfdCache[file].fileSize;
+ if (newTotal > (uint64) temp_file_limit * (uint64) 1024)
+ ereport(ERROR,
+ (errcode(ERRCODE_CONFIGURATION_LIMIT_EXCEEDED),
+ errmsg("temporary file size exceeds temp_file_limit (%dkB)",
+ temp_file_limit)));
+ }
+ }
+
retry:
errno = 0;
returnCode = write(VfdCache[file].fd, buffer, amount);
@@ -1251,7 +1290,21 @@ retry:
errno = ENOSPC;
if (returnCode >= 0)
+ {
VfdCache[file].seekPos += returnCode;
+
+ /* maintain fileSize and temporary_files_size if it's a temp file */
+ if (VfdCache[file].fdstate & FD_TEMPORARY)
+ {
+ off_t newPos = VfdCache[file].seekPos;
+
+ if (newPos > VfdCache[file].fileSize)
+ {
+ temporary_files_size += newPos - VfdCache[file].fileSize;
+ VfdCache[file].fileSize = newPos;
+ }
+ }
+ }
else
{
/*
@@ -1854,11 +1907,11 @@ CleanupTempFiles(bool isProcExit)
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 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);
diff --git a/src/backend/utils/errcodes.txt b/src/backend/utils/errcodes.txt
index 0315f6b6f0..d27fe2c3d9 100644
--- a/src/backend/utils/errcodes.txt
+++ b/src/backend/utils/errcodes.txt
@@ -4,7 +4,7 @@
#
# Copyright (c) 2003-2011, PostgreSQL Global Development Group
#
-# This list serves a basis for generating source files containing error
+# This list serves as the basis for generating source files containing error
# codes. It is kept in a common format to make sure all these source files have
# the same contents.
# The files generated from this one are:
@@ -24,14 +24,14 @@
# sqlstate E/W/S errcode_macro_name spec_name
#
# where sqlstate is a five-character string following the SQLSTATE conventions,
-# the second field determines if the code means an error, a warning or success,
+# the second field indicates if the code means an error, a warning or success,
# errcode_macro_name is the C macro name starting with ERRCODE that will be put
-# in errcodes.h and spec_name is a lowercase, underscore-separated name that
+# in errcodes.h, and spec_name is a lowercase, underscore-separated name that
# will be used as the PL/pgSQL condition name and will also be included in the
# SGML list. The last field is optional, if not present the PL/pgSQL condition
# and the SGML entry will not be generated.
#
-# Empty lines and ones starting with a hash are comments.
+# Empty lines and lines starting with a hash are comments.
#
# There are also special lines in the format of:
#
@@ -368,6 +368,7 @@ Section: Class 53 - Insufficient Resources
53100 E ERRCODE_DISK_FULL disk_full
53200 E ERRCODE_OUT_OF_MEMORY out_of_memory
53300 E ERRCODE_TOO_MANY_CONNECTIONS too_many_connections
+53400 E ERRCODE_CONFIGURATION_LIMIT_EXCEEDED configuration_limit_exceeded
Section: Class 54 - Program Limit Exceeded
@@ -393,7 +394,7 @@ Section: Class 57 - Operator Intervention
57P01 E ERRCODE_ADMIN_SHUTDOWN admin_shutdown
57P02 E ERRCODE_CRASH_SHUTDOWN crash_shutdown
57P03 E ERRCODE_CANNOT_CONNECT_NOW cannot_connect_now
-57P04 E ERRCODE_DATABASE_DROPPED database_dropped
+57P04 E ERRCODE_DATABASE_DROPPED database_dropped
Section: Class 58 - System Error (errors external to PostgreSQL itself)
diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c
index 72e9310791..3b33549a57 100644
--- a/src/backend/utils/misc/guc.c
+++ b/src/backend/utils/misc/guc.c
@@ -425,6 +425,8 @@ int log_min_duration_statement = -1;
int log_temp_files = -1;
int trace_recovery_messages = LOG;
+int temp_file_limit = -1;
+
int num_temp_buffers = 1024;
char *data_directory;
@@ -535,6 +537,8 @@ const char *const config_group_names[] =
gettext_noop("Resource Usage"),
/* RESOURCES_MEM */
gettext_noop("Resource Usage / Memory"),
+ /* RESOURCES_DISK */
+ gettext_noop("Resource Usage / Disk"),
/* RESOURCES_KERNEL */
gettext_noop("Resource Usage / Kernel Resources"),
/* RESOURCES_VACUUM_DELAY */
@@ -1693,6 +1697,17 @@ static struct config_int ConfigureNamesInt[] =
check_max_stack_depth, assign_max_stack_depth, NULL
},
+ {
+ {"temp_file_limit", PGC_SUSET, RESOURCES_DISK,
+ gettext_noop("Limits the total size of all temp files used by each session."),
+ gettext_noop("-1 means no limit."),
+ GUC_UNIT_KB
+ },
+ &temp_file_limit,
+ -1, -1, INT_MAX,
+ NULL, NULL, NULL
+ },
+
{
{"vacuum_cost_page_hit", PGC_USERSET, RESOURCES_VACUUM_DELAY,
gettext_noop("Vacuum cost for a page found in the buffer cache."),
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index 940329377a..fa67e7a3d5 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -119,6 +119,11 @@
#maintenance_work_mem = 16MB # min 1MB
#max_stack_depth = 2MB # min 100kB
+# - Disk -
+
+#temp_file_limit = -1 # limits per-session temp file space
+ # in kB, or -1 for no limit
+
# - Kernel Resource Usage -
#max_files_per_process = 1000 # min 25
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index 011f6b7f00..6b3b5c6694 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -209,6 +209,8 @@ extern int client_min_messages;
extern int log_min_duration_statement;
extern int log_temp_files;
+extern int temp_file_limit;
+
extern int num_temp_buffers;
extern char *data_directory;
diff --git a/src/include/utils/guc_tables.h b/src/include/utils/guc_tables.h
index 7f8c69fcf4..d3b25812a0 100644
--- a/src/include/utils/guc_tables.h
+++ b/src/include/utils/guc_tables.h
@@ -59,6 +59,7 @@ enum config_group
CONN_AUTH_SECURITY,
RESOURCES,
RESOURCES_MEM,
+ RESOURCES_DISK,
RESOURCES_KERNEL,
RESOURCES_VACUUM_DELAY,
RESOURCES_BGWRITER,