diff options
author | Tom Lane <tgl@sss.pgh.pa.us> | 2017-09-25 16:09:19 -0400 |
---|---|---|
committer | Tom Lane <tgl@sss.pgh.pa.us> | 2017-09-25 16:09:19 -0400 |
commit | 899bd785c0edf376077d3f5d65c316f92c1b64b5 (patch) | |
tree | 36b34442e55b2267f3b93cfc5459b83635bd50cb /src/backend/storage/ipc/dsm_impl.c | |
parent | 716ea626a88ac510523ab3af5bc779d78eeced58 (diff) | |
download | postgresql-899bd785c0edf376077d3f5d65c316f92c1b64b5.tar.gz postgresql-899bd785c0edf376077d3f5d65c316f92c1b64b5.zip |
Avoid SIGBUS on Linux when a DSM memory request overruns tmpfs.
On Linux, shared memory segments created with shm_open() are backed by
swap files created in tmpfs. If the swap file needs to be extended,
but there's no tmpfs space left, you get a very unfriendly SIGBUS trap.
To avoid this, force allocation of the full request size when we create
the segment. This adds a few cycles, but none that we wouldn't expend
later anyway, assuming the request isn't hugely bigger than the actual
need.
Make this code #ifdef __linux__, because (a) there's not currently a
reason to think the same problem exists on other platforms, and (b)
applying posix_fallocate() to an FD created by shm_open() isn't very
portable anyway.
Back-patch to 9.4 where the DSM code came in.
Thomas Munro, per a bug report from Amul Sul
Discussion: https://postgr.es/m/1002664500.12301802.1471008223422.JavaMail.yahoo@mail.yahoo.com
Diffstat (limited to 'src/backend/storage/ipc/dsm_impl.c')
-rw-r--r-- | src/backend/storage/ipc/dsm_impl.c | 54 |
1 files changed, 52 insertions, 2 deletions
diff --git a/src/backend/storage/ipc/dsm_impl.c b/src/backend/storage/ipc/dsm_impl.c index c63780139eb..a5879060d07 100644 --- a/src/backend/storage/ipc/dsm_impl.c +++ b/src/backend/storage/ipc/dsm_impl.c @@ -73,6 +73,7 @@ static bool dsm_impl_posix(dsm_op op, dsm_handle handle, Size request_size, void **impl_private, void **mapped_address, Size *mapped_size, int elevel); +static int dsm_impl_posix_resize(int fd, off_t size); #endif #ifdef USE_DSM_SYSV static bool dsm_impl_sysv(dsm_op op, dsm_handle handle, Size request_size, @@ -319,7 +320,8 @@ dsm_impl_posix(dsm_op op, dsm_handle handle, Size request_size, } request_size = st.st_size; } - else if (*mapped_size != request_size && ftruncate(fd, request_size)) + else if (*mapped_size != request_size && + dsm_impl_posix_resize(fd, request_size) != 0) { int save_errno; @@ -392,7 +394,55 @@ dsm_impl_posix(dsm_op op, dsm_handle handle, Size request_size, return true; } -#endif + +/* + * Set the size of a virtual memory region associated with a file descriptor. + * If necessary, also ensure that virtual memory is actually allocated by the + * operating system, to avoid nasty surprises later. + * + * Returns non-zero if either truncation or allocation fails, and sets errno. + */ +static int +dsm_impl_posix_resize(int fd, off_t size) +{ + int rc; + + /* Truncate (or extend) the file to the requested size. */ + rc = ftruncate(fd, size); + + /* + * On Linux, a shm_open fd is backed by a tmpfs file. After resizing with + * ftruncate, the file may contain a hole. Accessing memory backed by a + * hole causes tmpfs to allocate pages, which fails with SIGBUS if there + * is no more tmpfs space available. So we ask tmpfs to allocate pages + * here, so we can fail gracefully with ENOSPC now rather than risking + * SIGBUS later. + */ +#if defined(HAVE_POSIX_FALLOCATE) && defined(__linux__) + if (rc == 0) + { + /* We may get interrupted, if so just retry. */ + do + { + rc = posix_fallocate(fd, 0, size); + } while (rc == -1 && errno == EINTR); + + if (rc != 0 && errno == ENOSYS) + { + /* + * Kernel too old (< 2.6.23). Rather than fail, just trust that + * we won't hit the problem (it typically doesn't show up without + * many-GB-sized requests, anyway). + */ + rc = 0; + } + } +#endif /* HAVE_POSIX_FALLOCATE && __linux__ */ + + return rc; +} + +#endif /* USE_DSM_POSIX */ #ifdef USE_DSM_SYSV /* |